пятница, 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 определены такие структуры:

...
typedef struct {
WoReg UART_CR; /**< \brief (Uart Offset: 0x0000) Control Register */
RwReg UART_MR; /**< \brief (Uart Offset: 0x0004) Mode Register */
WoReg UART_IER; /**< \brief (Uart Offset: 0x0008) Interrupt Enable Register */
WoReg UART_IDR; /**< \brief (Uart Offset: 0x000C) Interrupt Disable Register */
RoReg UART_IMR; /**< \brief (Uart Offset: 0x0010) Interrupt Mask Register */
RoReg UART_SR; /**< \brief (Uart Offset: 0x0014) Status Register */
RoReg UART_RHR; /**< \brief (Uart Offset: 0x0018) Receive Holding Register */
WoReg UART_THR; /**< \brief (Uart Offset: 0x001C) Transmit Holding Register */
RwReg UART_BRGR; /**< \brief (Uart Offset: 0x0020) Baud Rate Generator Register */
RoReg Reserved1[55];
RwReg UART_RPR; /**< \brief (Uart Offset: 0x100) Receive Pointer Register */
RwReg UART_RCR; /**< \brief (Uart Offset: 0x104) Receive Counter Register */
RwReg UART_TPR; /**< \brief (Uart Offset: 0x108) Transmit Pointer Register */
RwReg UART_TCR; /**< \brief (Uart Offset: 0x10C) Transmit Counter Register */
RwReg UART_RNPR; /**< \brief (Uart Offset: 0x110) Receive Next Pointer Register */
RwReg UART_RNCR; /**< \brief (Uart Offset: 0x114) Receive Next Counter Register */
RwReg UART_TNPR; /**< \brief (Uart Offset: 0x118) Transmit Next Pointer Register */
RwReg UART_TNCR; /**< \brief (Uart Offset: 0x11C) Transmit Next Counter Register */
WoReg UART_PTCR; /**< \brief (Uart Offset: 0x120) Transfer Control Register */
RoReg UART_PTSR; /**< \brief (Uart Offset: 0x124) Transfer Status Register */
} Uart;
...
...
typedef struct {
WoReg US_CR; /**< \brief (Usart Offset: 0x0000) Control Register */
RwReg US_MR; /**< \brief (Usart Offset: 0x0004) Mode Register */
WoReg US_IER; /**< \brief (Usart Offset: 0x0008) Interrupt Enable Register */
WoReg US_IDR; /**< \brief (Usart Offset: 0x000C) Interrupt Disable Register */
RoReg US_IMR; /**< \brief (Usart Offset: 0x0010) Interrupt Mask Register */
RoReg US_CSR; /**< \brief (Usart Offset: 0x0014) Channel Status Register */
RoReg US_RHR; /**< \brief (Usart Offset: 0x0018) Receiver Holding Register */
WoReg US_THR; /**< \brief (Usart Offset: 0x001C) Transmitter Holding Register */
RwReg US_BRGR; /**< \brief (Usart Offset: 0x0020) Baud Rate Generator Register */
RwReg US_RTOR; /**< \brief (Usart Offset: 0x0024) Receiver Time-out Register */
RwReg US_TTGR; /**< \brief (Usart Offset: 0x0028) Transmitter Timeguard Register */
RoReg Reserved1[5];
RwReg US_FIDI; /**< \brief (Usart Offset: 0x0040) FI DI Ratio Register */
RoReg US_NER; /**< \brief (Usart Offset: 0x0044) Number of Errors Register */
RoReg Reserved2[1];
RwReg US_IF; /**< \brief (Usart Offset: 0x004C) IrDA Filter Register */
RoReg Reserved3[37];
RwReg US_WPMR; /**< \brief (Usart Offset: 0xE4) Write Protect Mode Register */
RoReg US_WPSR; /**< \brief (Usart Offset: 0xE8) Write Protect Status Register */
RoReg Reserved4[5];
RwReg US_RPR; /**< \brief (Usart Offset: 0x100) Receive Pointer Register */
RwReg US_RCR; /**< \brief (Usart Offset: 0x104) Receive Counter Register */
RwReg US_TPR; /**< \brief (Usart Offset: 0x108) Transmit Pointer Register */
RwReg US_TCR; /**< \brief (Usart Offset: 0x10C) Transmit Counter Register */
RwReg US_RNPR; /**< \brief (Usart Offset: 0x110) Receive Next Pointer Register */
RwReg US_RNCR; /**< \brief (Usart Offset: 0x114) Receive Next Counter Register */
RwReg US_TNPR; /**< \brief (Usart Offset: 0x118) Transmit Next Pointer Register */
RwReg US_TNCR; /**< \brief (Usart Offset: 0x11C) Transmit Next Counter Register */
WoReg US_PTCR; /**< \brief (Usart Offset: 0x120) Transfer Control Register */
RoReg US_PTSR; /**< \brief (Usart Offset: 0x124) Transfer Status Register */
} Usart;
...

Видно, что функциаонально эквивалентные регистры 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 можно осуществить примерно таким кодом:

void setupUART()
{
PIO_Configure(PIOA, PIO_PERIPH_A,PIO_PA8A_URXD|PIO_PA9A_UTXD, PIO_DEFAULT);
/* Enable the pull up on the Rx and Tx pin */
PIOA->PIO_PUER = PIO_PA8A_URXD | PIO_PA9A_UTXD;
/*Включаем UART, подавая на него тактирование*/
pmc_enable_periph_clk(ID_UART);
/* Отключаем DMA для приёма и для передачи */
UART->UART_PTCR = UART_PTCR_RXTDIS | UART_PTCR_TXTDIS;
// Reset and disable receiver and transmitter
UART->UART_CR = UART_CR_RSTRX | UART_CR_RSTTX | UART_CR_RXDIS | UART_CR_TXDIS;
// Configure mode
uint32_t dwMode = US_MR_CHRL_8_BIT | US_MR_NBSTOP_1_BIT | UART_MR_PAR_NO;
uint32_t modeReg = dwMode & 0x00000E00;
UART->UART_MR = modeReg;
// Configure baudrate (asynchronous, no oversampling)
uint32_t dwBaudRate = 9600;
UART->UART_BRGR = (SystemCoreClock / dwBaudRate) >> 4;
/*Конфигурируем прерывания*/
/*Отключаем их*/
UART->UART_IDR = 0xFFFFFFFF;
/*Включаем нужные нам прерывания. Если работаем с портом через поллинг, то
включать прерывания не нужно*/
#ifndef USE_POLLING
UART->UART_IER = UART_IER_RXRDY | UART_IER_OVRE | UART_IER_FRAME;
#endif
NVIC_EnableIRQ(UART_IRQn);
/*Включаем передачу и приём*/
UART->UART_CR = UART_CR_RXEN | UART_CR_TXEN;
}
view raw setup_uart.c hosted with ❤ by GitHub

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

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

...
while (1)
{
//Если получили очередной байт из UART - прочитаем его в переменную received_data
if ((UART->UART_SR & UART_SR_RXRDY) == UART_SR_RXRDY )
{
uint32_t received_data = UART->UART_RHR;
...
}
//Если готовы отправить очередной байт (зранится в data_to_transmit) через UART - запишем в регистр UART_THR
if ((UART->UART_SR & UART_SR_TXRDY) == UART_SR_TXRDY)
{
UART->UART_THR = data_to_transmit;
}
//Делаем что-то ещё
...
}
...
view raw polling_loop.c hosted with ❤ by GitHub

Мне встречалась такая аналогия для поллинга: хозяин дома постоянно проверяет входную дверь: не пришли ли гости? Если от управляющей программы требуется одновременно делать что-то ещё кроме обмена данными с 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 имеет свой вектор прерывания, один на устройство. Разные события в одном и том же устройстве вызывают один и тот же обработчик прерывания. В этом обработчике надо проверить регистры устройства, чтобы понять, что за событие вызвало прерывание. Обработчик прерываний может выглядеть примерно так:


// IT handlers
void UART_Handler(void)
{
uint32_t status = UART->UART_SR;
//Did we receive new byte?
if ((status & UART_SR_RXRDY) == UART_SR_RXRDY)
{
//Read new byte from UART_RHR
uint8_t c = UART->UART_RHR;
//Do something else
....
}
//Is transmitter is ready to send new data?
if ((status & UART_SR_TXRDY) == UART_SR_TXRDY)
{
uint8_t c = ... //
UART->UART_THR = c;
...
}
// Acknowledge errors
if ((status & UART_SR_OVRE) == UART_SR_OVRE || (status & UART_SR_FRAME) == UART_SR_FRAME)
{
//Process error
...
//Reset error
UART->UART_CR |= UART_CR_RSTSTA;
}
}
view raw uart_handler.c hosted with ❤ by GitHub



Механизм прямого доступа к памяти (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. 

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

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