Problema de temporización de UART suave basado en interrupción de PIC

Intenté implementar un UART de software en un PIC18F452 usando interrupciones TIMER0 y no puedo hacer que funcione el tiempo.

Estoy usando MPLAB ASM para la compilación y el PICkit2 para la programación.

LIST P=18F452
include <P18F452.inc>
CONFIG WDT=OFF, LVP=OFF, OSC=HS, CP0=OFF

variable cyclesPerBaud=1            ; 1 cycle(s) per baud
variable cyclesInt=0xFFFF           ; counter in 16 bit timer
variable freq = 20000000            ; clock frequency 20Mhz
variable baud = 9600                    ; baud rate
variable cyclesMain=33              ; cycles in Main program branch (intr->checkBitCounter->startStopBit->transfer)
variable cyclesIdle=16              ; cycles in Idle branch (intr->Idle)
variable offset = (freq/(4*baud))/(cyclesPerBaud)
variable initOffset = cyclesInt - offset
variable durrOffset = cyclesInt - offset + cyclesMain
variable hurrOffset = cyclesInt - offset + cyclesIdle

cblock 0x20
    char                    ; character to send
    bitCounter      ; bits left to send
    startStopBit    ; checks to send start of stop bit
    buffer              ; buffer where char is rotated
    cycle                   ; the current cycle in cyclesPerBaud starting high
endc

org 0000h
    goto main
org 0008h
    goto intr

main
bcf OSCCON, SCS     ; use primary clock
bsf RCON, IPEN      ; enable priority levels

; interrupt config:
bsf INTCON, GIEH    ; enable high priority interrupts
bcf INTCON, GIEL    ;   disable low priority interrupts
bsf INTCON, TMR0IE; enable TMR0 interrupts
bcf INTCON, INT0IE; dis. ext. interrupts
bcf INTCON, RBIE    ; dis. rb port change int.
bcf INTCON, TMR0IF; clear the TMR0 intr. flag bit
bcf INTCON, INT0IF; 
bcf INTCON, RBIF    ;

bsf INTCON2, TMR0IP; set TMR0 high priority

; timer 0 config:   
bcf T0CON, TMR0ON   ; temp. disable timer
bcf T0CON, T08BIT   ; 16 bit counter
bcf T0CON, T0CS     ; T0CKI pin input as source
bcf T0CON, T0SE     ; switch on falling edge
bsf T0CON, PSA      ; disable prescaler
bcf T0CON, T0PS2    ; doesn't really matter
bcf T0CON, T0PS1    ; -||-
bcf T0CON, T0PS0    ; -||-

; init output registers and variables
clrf TRISD
clrf LATD
movlw 0xFF
movwf PORTD             ; our output port set to high (serial 0 - but not sure about that)
movlw b'01111111' ; symbol to send
movwf char
movlw cyclesPerBaud
movwf cycle             ; unused
clrf bitCounter     ; zeros
clrf startStopBit   ; zeros
clrf buffer             ; zeros
; eof init 
movlw LOW initOffset    ; dunno why but HIGH has to be reversed with LOW
movwf TMR0H             
movlw HIGH initOffset   
movwf TMR0L             
bsf T0CON, TMR0ON   ; turn on timer

loop 
goto loop                   ; waiting for the     interrupt

intr                ; main branch: 8 cycles
btfss INTCON, TMR0IF
    retfie
bcf T0CON, TMR0ON

; had to comment this section out - didn't want to work with it - dunno why
;   decf cycle, F
;   btfss STATUS, Z
;       goto idle
;   movlw cyclesPerBaud
;   movwf cycle

checkBitCounter             ; all branches until 'tmr_ret': 16 cycles
movf bitCounter, F
btfss STATUS, Z
    goto rotateBuffer

;startStopBit

movf startStopBit, F
btfss STATUS, Z
    goto stopCopy
movlw 0x8
movwf bitCounter
movf char, W
movwf buffer
rlncf buffer, F
movlw 0x00

transfer
movwf PORTD
decf bitCounter, F
btfsc STATUS, Z
    incf startStopBit, F

tmr_ret         ; 5 cycles (+8 from intr = 13 cycles)
movlw LOW durrOffset    ; dunno why but HIGH has to be reversed with LOW
movwf TMR0H             
movlw HIGH durrOffset   
movwf TMR0L 

bsf T0CON, TMR0ON
retfie

stopCopy
clrf startStopBit
movlw 0xFF
nop
goto transfer

rotateBuffer
rrncf buffer, F
movf buffer, W
nop
nop
nop
nop
goto transfer   

idle        ; 5 cycles (+7 from intr = 12 cycles)
movlw LOW hurrOffset    ; dunno why but HIGH has to be reversed with LOW
movwf TMR0H             
movlw HIGH hurrOffset   
movwf TMR0L 

bsf T0CON, TMR0ON
retfie

end

El PIC está enviando cosas en PORTD0, como se supone que debe hacer, pero no son los datos que se supone que debe enviar. Cuando pico lo que se transmite (con Realterm), el byte que se envía es 11011111 o 10111111 y, a veces, 11111111 en lugar de 01111111.

Además, desde que comencé a usar 'variables' en el código, noté que tenía que copiar la parte inferior de las compensaciones de tiempo con ALTO y los bytes altos con BAJO en los registros TMR0H:L. ¿Alguien sabe por qué funciona de esa manera? Tal vez estoy confundiendo el significado de estos registros: los usé antes como si el registro TMR0H pudiera contener los 8 bits más significativos del contador de 2 bytes, ¿es correcto?

Respuestas (1)

RECEPCIÓN

El enfoque normal para implementar un receptor asincrónico de software es tener un tictac del temporizador que se ejecute continuamente a 3x o 5x la velocidad en baudios (nota: los números impares son mejores que los números pares). Observe que la entrada sea baja en dos tics consecutivos. Una vez que se observe eso, comience a muestrear la entrada cada tercer tic, hasta que la haya muestreado nueve veces más. Si la entrada es alta en la novena vez, ha recibido un personaje enmarcado correctamente. Si es bajo en la novena vez, tiene un error de encuadre.

* * * S - - 0 - - 1 - - 2 - - 3 - - 4 - - 5 - - 6 - - 7 - - S
-----______000000111111222222333333444444555555666666777777----
--______000000111111222222333333444444555555666666777777-------

El diagrama de tiempo anterior muestra cómo deberían funcionar las cosas. La línea superior muestra lo que sucede en cada tic del temporizador (cada no en blanco es un tic del temporizador; el * es un tic que espera el bit de inicio, una S verifica que la línea siga siendo baja después de haber recibido lo que parecía un bit de inicio. Los números 0 a 7 representan el muestreo de los bits 0 a 7, y S es el bit de parada. Las dos líneas inferiores muestran los datos entrantes en los casos en los que apenas se detecta el comienzo de un bit de inicio (en la tercera asterisco), o donde uno apenas no lo detecta en una interrupción (por lo que la línea ha estado baja durante un tercio de un bit en el momento en que se nota el bit de inicio). Tenga en cuenta que incluso con ese nivel de incertidumbre, la fuente está garantizada estar enviando el bit 0 cuando se muestrea, y así mismo para el resto de bits.

Un enfoque de software es muestrear la línea en las interrupciones indicadas e ignorarlas en las marcadas con guiones. Una alternativa es muestrear ciegamente la línea en cada interrupción en un registro de desplazamiento de 32 bits, usando algo como:

  rrf   headtail,w
  bcf   _tail,3
  btfsc _INPUTPORT,_INPUTBIT
   bsf  _STATUS,_tail,3
  rrf   buff2,w
  movff buff1,buff2
  movff buff0,buff1
  movwf buff0
  rrf   headtail,f

Los últimos cuatro bits recibidos del puerto estarán en los bits headtail 3..0. Los 24 bits anteriores estarán en buff0..buff2, y cada uno tendrá uno de cada tres bits. Los bits anteriores estarán en los bits de cabecera 7..4. Si uno hace eso, puede verificar si headtail tiene el patrón de bits 00xxxx1x. Si es así, copia buff1 donde quieras los datos entrantes, copia buff1 donde quieras los datos, O headtail con 11000011, y full buff0..buff2 con FF. De lo contrario, no hagas nada. Este enfoque puede ser un poco más lento que cargar selectivamente o ignorar la entrada de datos, pero puede recuperarse mejor de los errores de trama.

TRANSMISIÓN

La transmisión es más fácil que la recepción. Haga arreglos para que un fragmento de código se ejecute una vez por bit de tiempo (si su marca está a 3 veces la velocidad en baudios, ejecute el código cada tercera marca). La forma más sencilla de configurar el código es usar un par de bytes para contener los datos que deben enviarse por el puerto, incluidos los bits de inicio y parada. Hay una variedad de enfoques que uno puede usar. Uno bastante versátil sería:

  ; At start of interrupt
  btfss  TransmitBuffL,0
   bcf   OUTPUT
  btfsc  TransmitBuffL,0
   bsf   OUTPUT

  ; Later, after having handled reception:
  btfss  TransmitBitsLeft,7 ; Assume this counts down to -1 (i.e. 255)
   decfsz TransmitTicks,f
   goto  noTransmit ; Transmit if no bits left, or no ticks
  movlw  3
  movwf  TransmitTicks ; Handle bit transmission every third interrupt
  decf   TransmitBitsLeft,f
  rrf    TransmitBuffH,f
  rrf    TransmitBuffL,f
NoTransmit:

Se puede preparar un byte para la transmisión cuando se establece el bit 7 de TransmitTicks. Cuando ese sea el caso, coloque la secuencia de bits que se establecerá en TransmitBuffH y TransmitBuffL, establezca TransmitTicks en la longitud del primer bit (normalmente 3) y TransmitBitsLeft en la longitud total de la palabra, incluidos los bits de inicio y parada. Algo como:

TxByte: ; Sends byte in W.  Assumes TransmitBitsLeft has high bit set
  movwf   TransmitBuffL,f
  movlw   3
  movwf   TransmitTicks
  movwf   TransmitBuffH,f  ; LSB (stop bit) is set.  Upper bits don't matter.
  movlw   9 ; Total frame length (incl. start and stop) minus one
  movwf   TransmitBitsLeft
  bcf     _STATUS,_CARRY ; Start bit should be low
  rlf     TransmitBuffL,f ; Stick start bit in front of what we're sending
  rlf     TransmitBuffH,f

Este enfoque puede enviar tramas de datos normales usando el estilo indicado. Si necesita más bits de parada, puede asegurarse de que TransmitBuffH tenga suficientes bits configurados y aumente TransmitBitsLeft de manera adecuada. Si uno quiere enviar un BREAK, puede establecer TransmitBuffL en 2 y TransmitBitsLeft en 1, establecer TransmitTicks en la longitud deseada de la señal de interrupción (en tics). Para dejar la línea inactiva durante un tiempo determinado, establezca TransmitBuffL en 1, TransmitBitsLeft en 0 y establezca TransmitTicks en el tiempo de inactividad deseado (en tics).

Muchas gracias por los consejos; sin duda lo tendré en cuenta al implementar métodos de recepción de datos. Sin embargo, aún no he pasado la etapa de envío de datos y tal vez no expresé mi problema lo suficientemente claro. Así que estoy tratando de enviar datos desde el PIC a una PC. La PC escucha en un puerto adecuado usando Realterm, con una velocidad de transmisión de 9600. Intenté programar la imagen para que también envíe datos con una velocidad de transmisión de 9600. Sin embargo, no funciona. No sé por qué y esperaba que alguien pudiera detectar el error en mi código. Gracias
Para la transmisión, le sugiero que comience organizando que un fragmento de código se ejecute una vez por bit de tiempo. Si cambia un cable a esa velocidad, la PC debería ver un flujo continuo de caracteres "U". Una vez que pueda hacer eso, tal vez use algo como lo que muestro arriba.
Ok, creo que logré identificar el error, al menos uno de ellos. Es decir, cuando conecté la salida de PORTD0 (que es mi puerto PIC Tx previsto) a un osciloscopio, noté, al enviar un byte 01010101, que la duración de un solo bit es de aproximadamente 6,2 us, lo que da una velocidad de transmisión de aproximadamente 161290,32. Esto no está ni cerca del valor esperado de 9600. Mientras intentaba jugar con la corrección, también noté que no importaba qué tipo de compensación escribiera en los registros TMR0H y TMR0L, a través de los cuales, pensé, podía controlar el intervalo entre interrupciones tmr0. ¿Alguna sugerencia de por qué puede ser?
Correcto, me di cuenta de que no borré el TMR0IF en el registro INTCON; por eso, tan pronto como salí de la función de interrupción, volvió a entrar. Entonces, ahora puedo controlar los tiempos de las interrupciones. Pero algo todavía parece extraño. El byte que se envía se distorsiona terriblemente; tal vez deba dedicar más tiempo a los tiempos. Pero resumamos el proceso de envío: primero: establezca el pin PORTD0 en 1 ('marca'), antes de enviar los datos, configure el bit de inicio 0 ('espacio'), envíe los bits: primero el bit bajo, último el alto, envíe un bit de parada 1, repetir. ¿Eso esta bien? ¿Tal vez debería estar enviando el bit más significativo?
@drinker: La línea debe estar inactiva en alto. Cada byte debe comenzar con un tiempo de bit bajo, seguido de los bits de datos (LSB primero); la línea debe estar inactiva durante un mínimo absoluto de medio bit de tiempo entre bytes (el receptor debe ver la línea alta en el momento que considera 9,5 bits veces después del bit de inicio, que puede diferir ligeramente de lo que el transmisor considera 9,5 tiempos de bit después del bit de inicio). En la práctica, por lo general se debe tratar de garantizar que haya al menos un tiempo completo de inactividad entre bytes, con una excepción: si uno recibe y retransmite continuamente...
...datos, y si la tasa de baudios del reloj del transmisor es un poco más rápida (por ejemplo, 1%) en relación con la propia, entonces se puede recibir un byte cada 9,9 bits. En el tiempo requerido para recibir 1000 bytes a esa velocidad, solo se pueden enviar 990. Para evitar la pérdida de datos, habría que enviar algunos bytes con bits de parada ligeramente cortos; desafortunadamente, no todos los UART admiten esto.