Простейшая программная реализация UART для микроконтроллера.


В данной статье рассмотрен простейший вариант программной реализация UART на примере микроконтроллера PIC16F628A. В принципе, по приведенному алгоритму, несложно написать аналогичную программу для любого другого контроллера. Зачем это нужно? Ну, во-первых, аппаратный UART есть не во всех контроллерах, а во-вторых, получив несколько UART на одном контроллере, можно сваять много прикольных штуковин, например: преобразователь скорости UART (для обмена данными по RS-232 между компьютером и устройством, работающим на нестандартных скоростях), расширитель COM-портов и т.д. Ну, в общем, много чего интересного, о чем мы обязательно напишем, но попозже.

Для тестов будем использовать восьмибитный формат данных, без проверки на четность, с одним стоповым битом, т.е. каждый кадр сообщения у нас будет выглядеть вот так:

SB - старт бит, DB0...DB7 - биты данных, STB - стоп бит.

В стандарте RS232 логическая единица называется "mark", при этом уровень сигнала на линии -5..-15В. Логический ноль в стандарте RS232 называется "space", ему соответствует уровень сигнала +5..+15В.

В отсутствии передачи на линии всегда логическая единица. О начале передачи данных сигнализирует старт бит (логический ноль). Конец передачи данных обозначается стоп битом (логическая единица). Если во время приёма стоп бита на линии будет обнаружен логический ноль, то приемник выставит флаг ошибки приема.

Длительность одного бита (в микросекундах) вычисляется по формуле: T=106/V, где V - скорость передачи в бодах. Например, для скорости 19200 бод длительность одного бита составляет 1000000/19200=52 мкс.

Не забывайте, - контроллер работает с TTL уровнями сигналов, которые отличаются от уровней интерфейса RS-232, поэтому для связи компьютера с контроллером по UART необходимо использовать преобразователь уровней RS-232<->TTL. Для связи по UART двух контроллеров преобразователи можно не использовать.

Итак, с форматом разобрались, переходим, собственно, к программе.

Наша простейшая программа будет принимать один байт от компьютера, прибавлять к нему единицу и посылать обратно. Начало приема (старт-бит) будем определять по прерыванию от входа контроллера, к которому у нас подключен приемник (пришло прерывание - проверили, если на ноге приемника ноль - значит пришел старт бит).

Пусть приемник, у нас, будет подключен ко входу RB4. Вообще, pic-контроллер может генерировать прерывания по изменению уровня на входах RB4..RB7 и по заднему фронту на входе RB0, соответственно, эти входы и могут использоваться в качестве входов приемника.

Для экспериментов можно воспользоваться тем же устройством, которое мы использовали для тестирования аппаратного USART, только провод, который шёл ко входу Rx, надо отпаять и припаять ко входу RB4. Провод, который шёл к Tx оставим на той же ноге (хотя вообще, для передатчика можно использовать любой выход).

Алгоритм:

Алгоритм программной реализации UART для контроллера

Программа:

;---------------------------------------------------------
 list    p = 16f628a
 __config 03F30h
;*** Переменные ******************************************
  CBLOCK 0x20  ; Начальный адрес блока
    Tbyte      ; регистр передатчика
    Rbyte      ; регистр приемника
    Flags      ; флаги (0/1) (0 - состояние Tbyte (пуст/не пуст),
               ; 1 -  состояние Rbyte (пуст/не пуст),
               ; 2 - переполнение приемника (нет/есть),
               ; 3 - ошибка приема (нет/есть))
    Tx_Count   ; счетчик переданных бит
    Rx_Count   ; счетчик принятых бит
    SCount     ; счетчик циклов задержки
    Byte       ; просто байт (сюда мы будем читать данные из приемника,
               ; и отсюда посылать в передатчик)
  ENDC
;*** Константы / Адреса регистров ************************
Baudrate  equ  .10  ; подсчит-ое число циклов задержки для данной скорости
;---------------------------------------------------------
STATUS    equ  03h  ; Регистр выбора банка
PORTA     equ  05h  ; Порт А
PORTB     equ  06h  ; Порт В
TRISA     equ  05h  ; Конфигурация порта А (банк 1)
TRISB     equ  06h  ; Конфигурация порта В (банк 1)
CMCON     equ  1Fh  ; Управление компараторами
INTCON    equ  0Bh  ; Регистр разрешения(1)/запрета(0) прерываний
;**********************************************************
;***(пусть передатчик - RB2, приемник - RB4)***************

          org 0h
      goto   start
;--- Подпрограмма прерывания ------------------------------
          org 4h
      call   Recieve
      bcf    INTCON,0  ; сбросить флаг прерывания от RB4...RB7
      retfie
;*** Настройка портов *************************************
start movlw  .7
      movwf  CMCON     ; выкл-ть компараторы
      clrf   PORTA     ; инициализация защелок порта А
      clrf   PORTB     ; инициализация защелок порта В
      bsf    PORTB,2   ; когда передачи нет - на линии висит 1
      bsf    STATUS,5  ; Перейти в 1-й банк
      clrf   TRISA     ; Записать конф-ю порта A в TrisA
                       ; (свободные пины также настраиваем как выходы)
      movlw  .16       ; .2=00010000
                       ; (все пины, кроме RB4, настраиваем как выходы)
      movwf  TRISB     ; Скопировать конф-ю порта B из W в TrisB
      bcf    STATUS,5  ; Перейти в 0-й банк
;--- Настройка прерываний ---------------------------------
      clrf   Flags
      bsf    INTCON,3  ; Разрешить прерывания от RB4..RB7
      bsf    INTCON,7  ; Разрешить все немаскированные прерывания
;*** Основная часть программы *****************************
Prog  btfss  Flags,1   ; ждем приема
      goto   Prog
      movf   Rbyte,0   ; считать принятый байт в аккумулятор
      movwf  Byte      ; записать его в Byte
      bcf    Flags,1   ; сбросить флаг приемника
;----------------------------------------------------------
      incf   Byte,0
      movwf  Tbyte
      bcf    INTCON,7  ; запретить прерывания
      call   Transmit
      bsf    INTCON,7  ; разрешить прерывания
      goto   Prog
;**********************************************************
;--- Передача байта (нужно вызвать после после загр-ки данных в TByte)
Transmit
      bsf    Flags,0   ; устанавливаем флаг состояния передатчика в 1
      movlw  .8
      movwf  Tx_Count  ; Счетчик переданных бит=8
      bcf    PORTB,2   ; Посылаем старт бит (=0)
Next_Tx
      call   Delay
      btfss  Tbyte,0   ; Проверяем бит для посылки
                       ; (посылаем биты от младшего к старшему)
      goto   Zero      ; если 0 - идем сюда, если 1, пропуск-м эту команду
      bsf    PORTB,2   ; Посылаем единицу
      goto   One
Zero  bcf    PORTB,2   ; Посылаем ноль
One   rrf    Tbyte,1   ; Переходим к следующему биту
      decfsz Tx_Count,1; Уменьш. счетчик переданных бит и проверяем,
                       ; если он = 0, то след-щая команда пропуск-ся
      goto   Next_Tx   ; Переходим к посылке следующего бита
      call   Delay         ; Задержка для последнего бита
      bsf    PORTB,2   ; Посылаем стоп-бит
      call   Delay     ; Ждем (пока его не принял приемник - нельзя
                       ; посылать следующий байт)
      clrf   Tbyte     ; Очищаем регистр TByte
      bcf    Flags,0   ; и сбрасываем флаг его состояния
      return
;--- Прием байта (начинается автоматически после обнаружения старт-бита)
;--- после приема байта его надо считать из регистра приемника
;--- и сбросить флаг состояния приемника, иначе прием -----
;--- следующего байта вызовет переполнение ----------------
Recieve
      btfsc  PORTB,4     ; если вход=0, то обнаружен старт-бит
      goto   Exit
      btfsc  Flags,1     ; проверяем - пуст ли приемник,
      goto   Err_overflow; если нет - выставл. флаг переполн. и выходим
      bsf    Flags,1     ; ставим признак принятия посылки
      movlw  .8
      movwf  Rx_Count    ; счетчик принятых бит=8
      call   Delay2      ; пауза = 1/4 длительности бита (чтобы
                         ; читать бит ближе к середине, а не у фронта)
Next_Rx
      call   Delay
      bcf    STATUS,0   ; сбрасываем флаг cf
      rrf    Rbyte,1    ; сдвигаем регистр вправо
      btfsc  PORTB,4    ; если вход=1, то пишем 1, если нет - пропускаем
      bsf    Rbyte,7    ; ставим бит=1
      decfsz Rx_Count,1 ; уменьш.счетчик и провер.- приняли 8 бит или нет
      goto   Next_Rx    ; если нет - принимаем следующий бит
      call   Delay
      btfsc  PORTB,4    ; если вход=0, то стоп бит не пришел
      goto   Exit
      bsf    Flags,3    ; если стоп бит не пришел - ошибка приема
      goto   Exit
Err_overflow
      bsf    Flags,2    ; выставляем флаг переполнения
Exit  return
;--- Задержка, в соответствии с baudrate -----------------
;--- (в данном случае от 9 мкс до 1,029 мс с шагом 4 мкс)
Delay movlw  Baudrate
      movwf  SCount
Next  nop
      decfsz SCount,1
      goto   Next
      return
;--- Задержка/4 ------------------------------------------
Delay2
      movlw  Baudrate
      movwf  SCount
      bcf    STATUS,0
      rrf    SCount,1
      bcf    STATUS,0
      rrf    SCount,1
Next2 nop
      decfsz SCount,1
      goto   Next2
      return
 end
;---------------------------------------------------------
      

Как для нашей программы рассчитать значение константы Baudrate, для произвольной скорости?

Это делается очень просто. Baudrate - это количество циклов задержки для подпрограммы Delay. Оно должно быть таким, чтобы задержка между посылкой (или приемом) битов равнялась длительности бита на данной скорости. После подсчета времени выполнения различных участков кода, у меня получилась следующая зависимость длительности от Baudrate: T=9+4*Baudrate мкс, отсюда Baudrate=(T-9)/4, где T - длительность одного бита.

Например, вычислим Baudrate для скорости 19200 бод: T=106/V=52 мкс, Baudrate=(52-9)/4=10,75 циклов. В программе я взял 10 циклов - отлично работает.

Скачать готовую прошивку и asm-файл

Для проверки работы и отладки можно пользоваться программой для работы с com-портом RH_Com, которую можно скачать здесь.