¿Qué tiene de malo esta detección de cambio de pin PIC?

Tengo dos PIC18F4620 conectados a través de SPI + Slave Select + línea IRQ adicional. Ambos controladores son impulsados ​​desde el mismo oscilador de cristal usando la misma configuración de reloj. El maestro envía un byte y luego espera hasta que el esclavo alterna esa línea IRQ adicional. La duración de la alternancia es de 4 ciclos de instrucción. Todos los bordes se ven bien en el osciloscopio y la comunicación SPI funciona correctamente, excepto por la detección de la alternancia ( while(!PORTBbits.RB1);).

Este es mi código de envío SPI:

    while (spi_out_msg_buffer.write_cursor > spi_out_msg_buffer.read_cursor)
    {
        DisableInterrupts;
        SSPBUF = spi_out_msg_buffer.data[spi_out_msg_buffer.read_cursor];
        LATBbits.LATB0 = 1;
        while(!PORTBbits.RB1); // wait for toggle on IRQ line
        LATBbits.LATB0 = 0;
        EnableInterrupts;
        spi_out_msg_buffer.read_cursor++;
    }

El while(!PORTBbits.RB1);se traduce en dos instrucciones:

BTFSS PORTB, 1, ACCESS
BRA 0x188

Inserté esa B0línea con fines de depuración, puede verla en la parte inferior de este diagrama de tiempo:

sincronización

Puede ver el cambio de la línea IRQ (segunda desde abajo) y cómo pasa desapercibida, porque la B0línea de depuración permanece alta. Cuando detengo la ejecución a través de ICD, se cuelga dentro del archivo while. Vale la pena mencionar que normalmente funciona durante unos pocos bytes y luego se detiene, como se puede ver aquí:

todo el tiempo

Medí que el pulso es en realidad 4 ciclos de instrucción (= 16 ciclos PLL = 4 ciclos de reloj) de largo:

longitud de pulso

Creo que eso debería ser suficiente para que se detecte el pulso. Incluso si el primer BTFSS lo pierde porque el puerto se muestrea al comienzo del ciclo de instrucción, entonces el segundo debería obtenerlo:

incluso más tiempo

10 MHz -> PLL -> 40 MHz -> 10 M instrucciones por segundo -> 100 ns por instrucción.

¿No debería ser lo suficientemente largo para salir del while?

verifique su código de ensamblaje, podría ser fácilmente más de 4 instrucciones, aunque en este caso podría ser menos.
¿Por qué no utilizar la función de cambio de nivel de varias líneas de E/S del PIC? Esto actualiza una bandera (y/o activa una interrupción) cuando cambia el valor en el pin. Esto es independiente del reloj de instrucciones y debería hacer que su sistema sea más confiable.

Respuestas (3)

Está utilizando un compilador, por lo que no tiene idea de cuántos ciclos toma ese ciclo de sondeo. En ensamblador, podría reducirlo a 3 ciclos, pero renunció al derecho de contar ciclos cuando escribió el código en un lenguaje de alto nivel.

Sin embargo, el problema real es el enfoque general. No es una buena idea pedirle al código que detecte un error breve. Incluso si pudiera garantizar que el tiempo de ciclo es menor que el tiempo de falla, ahora no puede activar las interrupciones durante la espera. Esto puede presentar problemas arquitectónicos más adelante.

Una idea mucho mejor es usar el hardware que ya tiene para detectar la falla y luego hacer que el firmware verifique ese hardware. Lo más simple sería conectar la falla a una de las líneas INTx y luego buscar el indicador INTxIF. Cambiar los pines de detección también funcionaría, pero tenga en cuenta que la bandera se establece en ambos bordes y debe eliminar la falta de coincidencia para eliminar la condición.

Todavía puede contar ciclos cuando usa un lenguaje de alto nivel, solo se necesita un poco de excavación. Estoy de acuerdo, sin embargo, usar hardware sería una mejor manera en general.
@Kevin: Puedes contar ciclos con algo de excavación, pero la respuesta no es muy significativa. Solo le dirá qué hace esa versión del compilador en esa configuración de optimización y otros parámetros posiblemente no obvios. Si necesita garantizar la cantidad de ciclos en un ciclo, debe deshabilitar las interrupciones y escribirlo en ensamblador.
Deshabilité las interrupciones como puede ver en el código y copié el fragmento de ensamblaje en la pregunta directamente desde el desensamblado, así que eso es exactamente lo que se ejecuta en el PIC, ¿no es así?
Ya utilicé deliberadamente el pin B1 para la línea IRQ, por lo que puedo aprovechar su capacidad de interrupción. Sin embargo, tengo mucha curiosidad por qué no funciona de esta manera.

La última instrucción es una bifurcación incondicional, ¿verdad? Por lo general, las bifurcaciones y los accesos a la memoria (realizados en la primera instrucción, ¿verdad?) tardan entre 2 y 3 veces más en ejecutarse, por lo que en su caso, esas instrucciones podrían tardar unos 16 ciclos en ejecutarse, que es 400 ns (en el mejor de los casos). ).

¿Puede dar más información sobre cómo el OP (póster original) podría descubrir esto? ¿Cómo puedes evitar esto? ¡Resolvamos un problema! Señalé en mi comentario que deberían verificar su código de ensamblaje.
No soy un experto en PIC18, pero según este sitio, tiene un indicador que se actualiza cuando finaliza la operación de envío/recepción de SPI. Intenta reemplazar esa línea por while(!SSPSTATbits.BF);.
Oh, de hecho, no estaba al tanto del hecho de que el BRA toma dos ciclos de instrucción, pero aún así, 400 ns deberían ser suficientes, ¿o no? Actualicé la pregunta con cómo imagino el peor de los casos.
@AndreKR: PORTB es una dirección, ¿verdad? Si es así, BRFSS probablemente tardará el mismo tiempo que la bifurcación en completarse (porque la MCU tiene que cargar el valor en PORTB a un registro). ¿Por qué no prueba mi sugerencia en el comentario anterior, que creo que es lo mismo de @MattJenkins en la primera respuesta?
De la hoja de datos: todas las instrucciones de una sola palabra se ejecutan en un solo ciclo de instrucción, a menos que una prueba condicional sea verdadera o el contador del programa cambie como resultado de la instrucción. En estos casos, la ejecución toma dos ciclos de instrucción, con los ciclos de instrucción adicionales ejecutados como NOP. Tu sugerencia no tiene nada que ver con mi problema. MattJenkins' es lo mismo que la sugerencia de Olin, vea mi comentario allí.
@AndreKR: consulte la Tabla 24-2 del manual
¿Te refieres a los "1 (2 o 3) ciclos" del BTFSS? Eso es 1 ciclo si no se cumple la condición (el bit no está establecido), 2 ciclos si y solo si el bit está establecido, por lo que tiene que omitir y 3 ciclos para omitir una instrucción de dos palabras (que BRA no es).
@AndreKR: Lo siento, lo he leído mal. Pensé que cuando no se cumple la condición, debe omitir la siguiente instrucción. Mientras echaba un vistazo a ese manual, vi un código ensamblador para escribir en SPIbuf, podrías echarle un vistazo.
No hay problema, en realidad tienen instrucciones para eso, pero no las usé. De todos modos, esto no se trata en absoluto de SPI, tal vez nunca debería haber mencionado SPI.

La razón real era que yo era un imbécil. Mi DisableInterruptsmacro lo hizo INTCONbits.GIE=1.

Encontré esto alternando un pin durante la espera ocupada:

while(!PORTBbits.RB1)
{
    LATBbits.LATB0 = 0;
    LATBbits.LATB0 = 1;
}

Noté que la alternancia se detuvo en intervalos regulares y cuando el pulso de notificación cae en una de estas ventanas, pasa desapercibido.

ingrese la descripción de la imagen aquí

Ninguno de ustedes podría saber eso, sin embargo, +1 para sus dos respuestas por señalarme el recuento de ciclos y la interrupción de cambio de pin.