пятница, 18 ноября 2016 г.

Исользование UART в микроконтроллере sam3x8e и Arduino Due

Интерфейс UART жизненно необходим при разработке устройств на основе микроконтроллеров. Даже если ваш проект не подразумевает подключение устройства к компьютеру (что проще всего сделать через интерфейс UART), этот интерфейс может сильно помочь в диагностике и отладке прошивок.

Микроконтроллер sam3x8e обладает одним портом UART и аж тремя портами USART. В этом посте мы не будем касаться особенностей USART, скажу лишь что USART является надмножеством UART,  так что будем считать, что в нашем распоряжении 4 порта UART.

Как и вся остальная периферия, UART/USART управляется регистрами, которые отображены в адресное пространство микроконтроллера. Управляющие регистры портов находятся по следующим адрксам:

ПортАдреса регистров
UART0x400E0800-0x400E0924
USART10x40098000-0x40098124
USART20x4009C000-0x4009C124
USART30x400A4000-0x400A4124

У каждого порта имеются такие регистры (R в колонке "доступ" обозначает доступность регистра для чтения, W - доступность регистра для записи).

Смещение Регистр UART Регистр USART Назначение Доступ
0x0000 UART_CR US_CR Control register W
0x0004 UART_MR US_MR Mode register RW
0x0008 UART_IER US_IER Interupt Enable register W
0x000C UART_IDR US_IDR Interupt Disable Register W
0x0010 UART_IMR US_IMR Interupt Mask Register R
0x0014 UART_SR US_SR Status Register R
0x0018 UART_RHR US_RHR Receive Holding Register R
0x001C UART_THR US_THR Transmit Holding Register W
0x0020 UART_BRGR US_BRGR Baud Rate Generator Register W
0x0100-0x0124 PDC Area PDC Area Registers controlling DMA


Для удобного доступа к этим ячейкам-регистрам в заголовочных файлах libsam определены такие структуры:


Видно, что функциаонально эквивалентные регистры USART и UART находятся по одинаковым смещениям, поэтому в функции, принимающие в качестве аргумента указатели на объект типа Uart можно передавать объекты типа Usart, и по идее в этом случае UART-подмножество функционала USART будет работать!

Обмен через Uart осуществляется примерно так: если передатчик готов принимать следующий байт, то в регистре статуса UART_SR выставляется бит UART_SR_TXRDY. Это значит, что в регистр UART_THR можно записать следующий передаваемый байт. После записи значения в ргистр UART_THR бит UART_SR_TXRDY в регистре UART_SR выставляется в 0 (передатчик не готов принимать следующее значение). Значение будет "храниться" в UART_THR до тех пор, пока сдвиговый регистр UART, обеспечивающий передачу битов предыдущего байта не освободится. Как только это произойдёт, значение UART_THR запищется в сдвиговый регистр UART, а бит UART_SR_TXRDY в регистре UART_SR выставляется в 1 (передатчик готов принимать следующее значение).

При приёме очередной байт также побитово записывается в сдвиговый регистр приёмника UART. Как только байт прочита полностью, он переносится в регистр UART_RHR, откуда его можно прочитать программно. При этом выставляется бит UART_SR_RXRDY в регистре UART_SR. Если к моменту заполнения сдвигового регистра, регистр UART_RHR не был прочитан, в регистре UART_SR выставляется бит UART_SR_OVRE (переполнение).

Инициализация порта Uart

Инициализацию UART можно осуществить примерно таким кодом:


Три метода работы с последовательным портом: поллинг, прерывания и прямой доступ к памяти

Пожалуй, самый простой способ общения с внешним устройством это просто прямой опрос содержимого регистров портов. Нужно просто достаточно часто проверять: пришёл/ушёл ли очередной байт. При приёме очередного байта нужно просто прочитать его из регистра UART_RHR и обработать. Соответственно при успешной отправке очередного байта, нужно положить в UART_THR очередной байт на отправку (если есть ещё данные для передачи). Такой периодический опрос устройства из основной программы называется английским словом поллинг -- polling.


Мне встречалась такая аналогия для поллинга: хозяин дома постоянно проверяет входную дверь: не пришли ли гости? Если от управляющей программы требуется одновременно делать что-то ещё кроме обмена данными с Uart-портом, то такой подход может усложнить разработку. Здесь на помощь приходят прерывания и прямой доступ к памяти (direct memory access, DMA).

Второй способ общения с портом заключается в том, что мы назначаем прерывание на различные события, происходящие в UART. Этими событиями могут быть либо приход нового байта через линию Rx либо отправка байта через Tx. В основной программе не надо думать о постоянном опросе регистров UART. Если вы хотите использовать прерывания при работе с UART, то при инициализации надо указать, на какие события мы хотим генерировать прерывание, выставив соответствующие биты в регистре UART_IER. В примере кода инициализации выше за это отвечает такая строчка:

UART->UART_IER = UART_IER_RXRDY | UART_IER_OVRE | UART_IER_FRAME;

Здесь мы включаем прерывание на приход очередного байта через Rx, ошибку переполнения и "ошибку фрейма" -- когда приёмник ожидает стоп-бит, но получает старотовый бит. Почему есть прерывание на приём, но нет прерывание на передачу? Просто мы пока что ничего не передаём. Как только начнём что-то отправлять, добавим прерывание на передачу байтов. Каждое устройство UART/USART имеет свой вектор прерывания, один на устройство. Разные события в одном и том же устройстве вызывают один и тот же обработчик прерывания. В этом обработчике надо проверить регистры устройства, чтобы понять, что за событие вызвало прерывание. Обработчик прерываний может выглядеть примерно так:





Механизм прямого доступа к памяти (DMA) позволяет ещё больше избавить центральный процессор микроконтроллера от необходимости отвлекаться на приём и передачу. Вместо этого усторойство (в нашем случае UART) само считывет из указанной области памяти данные байт за байтом (или слово за словом) и отправляет их на вывод, или наоборот последовательно складывает принятые данные в указанную область памяти. DMA не поддерживается некоторыми ARM Cortex M микроконтроллерами, например его нет в lpc1114. Но в ардуиновском sam3x8e он есть. В sam3x8e механизм DMA реализован единообразно для различных периферийных устройств (ADC, DAC, UART, USART и.т.д.). USART поддерживает дуплексный обмен с помощью DMA. Это значит, что в один и тот же момент времени данные могут передвавться из одного буфера в линию Tx, и приниматься через линию Rx в другой буфер. Механизм DMA эффективно сочетается с прерываниями: можно назначить прерывание на окончание приёма/передачи буфера и, например, назначить передачу по DMA следующего буфера. На самом деле в sam3x8e в мезанизме DMA аппаратно встроена "очередь" из двух буферов (об этом -- ниже), так что если вам надо передавать не больше двух раздельно лежащих буферов, то можно обойтись и без прерывание.

Итак, в таблице регистров UART/USART я указал, что по смещениям 0x100-0x124 лежит область PDC, регистров для управления DMA. Вот эти регистры:


Смещение Регистр UART Регистр USART Назначение Доступ
0x0100 UART_RPR US_RPR Receive Pointer Register RW
0x0104 UART_RCR US_RCR Receive Counter Register RW
0x0108 UART_TPR US_TPR Transmit Pointer Register RW
0x010C UART_TCR US_TCR Transmit Counter Register RW
0x0110 UART_RNPR US_RNPR Receive Next Pointer Register RW
0x0114 UART_RNCR US_RNCR Receive Next Counter Register RW
0x0118 UART_TNPR US_TNPR Transmit Next Pointer Register RW
0x011C UART_TNCR US_TNCR Transmit Next Counter Register RW
0x0120 UART_PTCR US_PTCR Transfer Control Register W
0x0124 UART_PTSR US_PTSR Transfer Status Register R


Эта "структура" не является специфичной для UART/USART. Точно такие же регистры есть и у другой периферии: ADC, DAC, SPI, TWI итд.  Для того, чтобы передать какой-то буфер с помощью DMA нужно записать в UART_TPR адрес начала буфера, а в UART_TCR количество байтов для передачи. Аналогично для приёма нужно записать в UART_RPR адрес начала буфера, куда будут записаваться принимаемые данные, а в UART_RCR -- количество принимаемых байт. С помощью регистра UART_PTCR можно управлять передачей данных. Например, записав в него бит UART_PTCR_TXTDIS мы остановим передачу данных, а записав бит UART_PTCR_TXTEN -- наоборот, запустим. Мы видим, что имеется "второй комплект" регистров для следующего передаваемого буфера: UART_RNPR, UART_RNCR, UART_TNPR, UART_TNCR. Они задают "следующие" передаваемые буфферы. Как только передача текущего буфера завершится, значения из из регистров UART_RNPR и UART_RNCR будут переданы в регистры UART_RPR и UART_RCR соответственно. Аналогично, регистры UART_TNPR, UART_TNCR будут перенесены в UART_TPR и UART_TCR и следующие буферы станут текущими.

Я создал репозиторий GitHub, в котором собрал кусочки с различными способами работы с UART (не заботясь о стиле и оформлении). Надеюсь этот пост поможет кому-то разобраться с UART в Cortex M3 микроконтроллерах фирмы Atmel. 

Комментариев нет:

Отправить комментарий