Программная проблема синхронизации UART на основе прерывания PIC

Я пытался реализовать программный UART на PIC18F452 , используя прерывания TIMER0, и я не могу настроить синхронизацию.

Я использую MPLAB ASM для компиляции и PICkit2 для программирования.

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

PIC отправляет данные на PORTD0, как и предполагалось, но это не те данные, которые он должен отсылать. Когда я выбираю то, что передается (с Realterm), отправляемый байт равен 11011111 или 10111111, а иногда и 11111111 вместо 01111111.

Кроме того, поскольку я начал использовать «переменные» в коде, я заметил, что мне приходилось копировать младшую часть временных смещений с HIGH и старшие байты с LOW в регистры TMR0H:L - кто-нибудь знает, почему это работает таким образом? Возможно, я путаю значение этих регистров: я использовал их раньше, как если бы регистр TMR0H содержал более значимые 8 бит 2-байтового счетчика - это правильно?

Ответы (1)

ПОЛУЧЕНИЕ

Обычный подход к реализации программного асинхронного приемника состоит в том, чтобы иметь отметку таймера, которая непрерывно работает со скоростью, в 3 или 5 раз превышающей скорость передачи данных (примечание: нечетные числа лучше, чем четные). Следите за тем, чтобы вход был низким на двух последовательных тиках. Как только это будет замечено, начинайте сэмплировать ввод на каждом третьем тике, пока вы не сэмплируете его еще девять раз. Если ввод высокий в девятый раз, вы получили правильно оформленный символ. Если в девятый раз он низкий, у вас ошибка кадрирования.

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

Приведенная выше временная диаграмма показывает, как все должно работать. Верхняя строка показывает, что происходит на каждом такте таймера (каждое непустое значение — это такт таймера; * — это тик, ожидающий стартового бита, S — проверка, чтобы убедиться, что линия все еще находится на низком уровне после получения того, что выглядело как стартовый бит.Числа 0-7 представляют собой выборку битов 0-7, а S - стоповый бит.Нижние две строки показывают входящие данные в тех случаях, когда едва удается уловить начало стартового бита (на третьей звездочка), или когда он едва перехватил его на одном прерывании (таким образом, к моменту обнаружения стартового бита линия находилась в состоянии низкого уровня в течение трети бита времени). Обратите внимание, что даже при таком уровне неопределенности источник гарантированно для отправки бита 0 при его выборке, а также для остальных битов.

Один программный подход состоит в том, чтобы сэмплировать линию на указанных прерываниях и игнорировать ее на тех, которые отмечены тире. Альтернативой является слепая выборка строки при каждом прерывании в 32-битный регистр сдвига, используя что-то вроде:

  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

Последние четыре бита, полученные от порта, будут битами заголовка 3..0. 24 бита перед ними будут в buff0..buff2, где каждый будет содержать каждый третий бит. Биты перед этим будут в битах заголовка 7..4. Если это сделать, можно проверить, содержит ли заголовок битовый шаблон 00xxxx1x. Если это так, скопируйте buff1 туда, куда вы хотите входящие данные, скопируйте buff1 в то место, где вы хотите получить данные, ИЛИ headtail с 11000011 и full buff0..buff2 с FF. В противном случае ничего не делайте. Этот подход может быть немного медленнее, чем выборочная загрузка или игнорирование входных данных, но может лучше восстанавливаться после ошибок кадрирования.

ПЕРЕДАЧА ИНФЕКЦИИ

Передача проще, чем прием. Устройте так, чтобы фрагмент кода запускался один раз в бит (если ваш тик в 3 раза превышает скорость передачи данных, запускайте код каждый третий тик). Самый простой способ настроить код — использовать пару байтов для хранения данных, которые должны быть отправлены через порт, включая стартовые и стоповые биты. Есть множество подходов, которые можно использовать. Довольно универсальным будет:

  ; 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:

Можно подготовить байт для передачи, когда установлен бит 7 TransmitTicks. В этом случае поместите битовую последовательность, которую необходимо установить, в TransmitBuffH и TransmitBuffL, установите TransmitTicks на длину первого бита (обычно 3), а TransmitBitsLeft на общую длину слова, включая начальный и стоповый биты. Что-то вроде:

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

Этот подход может отправлять обычные кадры данных, используя указанный стиль. Если нужно больше стоповых битов, можно убедиться, что в TransmitBuffH установлено достаточное количество битов, и соответствующим образом увеличить TransmitBitsLeft. Если кто-то хочет отправить BREAK, можно установить TransmitBuffL на 2 и TransmitBitsLeft на 1, установить TransmitTicks на желаемую длину сигнала прерывания (в тиках). Чтобы линия простаивала в течение определенного периода времени, установите TransmitBuffL в 1, TransmitBitsLeft в 0 и установите TransmitTicks в желаемое время бездействия (в тиках).

Большое спасибо за советы - я обязательно приму это во внимание при реализации методов получения данных. Однако я еще не прошел этап отправки данных и, возможно, недостаточно ясно изложил свою проблему. Итак, я пытаюсь отправить данные с ПОС на ПК. ПК прослушивает правильный порт, используя Realterm, со скоростью передачи данных 9600 бод. Я попытался запрограммировать изображение так, чтобы оно также отправляло данные со скоростью 9600 бод. Тем не менее, это не работает. Я не знаю почему, и я надеялся, что кто-нибудь заметит ошибку в моем коде. Спасибо
Что касается передачи, я бы посоветовал вам начать с организации фрагмента кода, который будет выполняться один раз за битовое время. Если вы переключаете провод с такой скоростью, ПК должен увидеть непрерывный поток символов «U». Как только вы сможете это сделать, возможно, используйте что-то вроде того, что я показал выше.
Хорошо, я думаю, что мне удалось точно определить ошибку - по крайней мере, одну из них. А именно, когда я подключил выход PORTD0 (который является моим предполагаемым портом PIC Tx) к осциллографу, я заметил, отправив байт 01010101, что длительность одного бита составляет около 6,2 мкс, что дает скорость передачи около 161290,32. Это далеко не ожидаемое значение 9600. Когда я пытался поиграться с исправлением, я также заметил, что не имеет значения, какое смещение я записываю в регистры TMR0H и TMR0L, через которые, однако, я могу контролировать интервал между прерываниями tmr0. Любые предложения, почему это может быть?
Итак, я заметил, что не очистил TMR0IF в регистре INTCON, поэтому, как только я вышел из функции прерывания, она снова вернулась. Итак, теперь я могу контролировать тайминги прерываний. Но что-то все равно кажется странным. Отправляемый байт ужасно искажается - может быть, мне нужно потратить еще немного времени на тайминги. Но давайте подытожим процесс отправки: сначала установите вывод PORTD0 в 1 («метка»), перед отправкой данных установите стартовый бит 0 («пробел»), передайте биты — младший бит первым, старший последним, отправьте один стоповый бит 1, повторить. Это нормально? Может быть, я должен отправить самый значащий бит?
@drinker: Линия должна простаивать высоко. Каждый байт должен начинаться с одного младшего бита, за которым следуют биты данных (сначала младший бит); линия должна простаивать в течение как минимум половины битового времени между байтами (приемник должен видеть высокий уровень строки в тот момент, когда он считает, что это 9,5 битов после стартового бита, что может немного отличаться от того, что передатчик считает 9,5 битами). бит раз после стартового бита). На практике обычно следует пытаться обеспечить хотя бы одно полное битовое время простоя между байтами, за одним исключением: если кто-то непрерывно получает и ретранслирует...
... данные, и если скорость передачи часов передатчика немного выше (например, 1%) по сравнению с собственными, то можно получать один байт каждые 9,9 бит. За время, необходимое для получения 1000 байтов с такой скоростью, можно было бы отправить только 990. Чтобы избежать потери данных, нужно было бы отправить несколько байтов с немного короткими стоповыми битами; к сожалению, не все UART поддерживают это.