Простейшая программная реализация 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 оставим на той же ноге (хотя вообще, для передатчика можно использовать любой выход).
Алгоритм:
Программа:
;--------------------------------------------------------- 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, которую можно скачать здесь.