Интерфейс UART жизненно необходим при разработке устройств на основе микроконтроллеров. Даже если ваш проект не подразумевает подключение устройства к компьютеру (что проще всего сделать через интерфейс UART), этот интерфейс может сильно помочь в диагностике и отладке прошивок.
Микроконтроллер sam3x8e обладает одним портом UART и аж тремя портами USART. В этом посте мы не будем касаться особенностей USART, скажу лишь что USART является надмножеством UART, так что будем считать, что в нашем распоряжении 4 порта UART.
Как и вся остальная периферия, UART/USART управляется регистрами, которые отображены в адресное пространство микроконтроллера. Управляющие регистры портов находятся по следующим адрксам:
У каждого порта имеются такие регистры (R в колонке "доступ" обозначает доступность регистра для чтения, W - доступность регистра для записи).
Для удобного доступа к этим ячейкам-регистрам в заголовочных файлах 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 (переполнение).
Микроконтроллер sam3x8e обладает одним портом UART и аж тремя портами USART. В этом посте мы не будем касаться особенностей USART, скажу лишь что USART является надмножеством UART, так что будем считать, что в нашем распоряжении 4 порта UART.
Как и вся остальная периферия, UART/USART управляется регистрами, которые отображены в адресное пространство микроконтроллера. Управляющие регистры портов находятся по следующим адрксам:
Порт | Адреса регистров |
---|---|
UART | 0x400E0800-0x400E0924 |
USART1 | 0x40098000-0x40098124 |
USART2 | 0x4009C000-0x4009C124 |
USART3 | 0x400A4000-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 определены такие структуры:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
... | |
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; | |
... |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
... | |
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 можно осуществить примерно таким кодом:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} |
Три метода работы с последовательным портом: поллинг, прерывания и прямой доступ к памяти
Пожалуй, самый простой способ общения с внешним устройством это просто прямой опрос содержимого регистров портов. Нужно просто достаточно часто проверять: пришёл/ушёл ли очередной байт. При приёме очередного байта нужно просто прочитать его из регистра UART_RHR и обработать. Соответственно при успешной отправке очередного байта, нужно положить в UART_THR очередной байт на отправку (если есть ещё данные для передачи). Такой периодический опрос устройства из основной программы называется английским словом поллинг -- polling.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
... | |
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; | |
} | |
//Делаем что-то ещё | |
... | |
} | |
... |
Мне встречалась такая аналогия для поллинга: хозяин дома постоянно проверяет входную дверь: не пришли ли гости? Если от управляющей программы требуется одновременно делать что-то ещё кроме обмена данными с 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 имеет свой вектор прерывания, один на устройство. Разные события в одном и том же устройстве вызывают один и тот же обработчик прерывания. В этом обработчике надо проверить регистры устройства, чтобы понять, что за событие вызвало прерывание. Обработчик прерываний может выглядеть примерно так:
Второй способ общения с портом заключается в том, что мы назначаем прерывание на различные события, происходящие в UART. Этими событиями могут быть либо приход нового байта через линию Rx либо отправка байта через Tx. В основной программе не надо думать о постоянном опросе регистров UART. Если вы хотите использовать прерывания при работе с UART, то при инициализации надо указать, на какие события мы хотим генерировать прерывание, выставив соответствующие биты в регистре UART_IER. В примере кода инициализации выше за это отвечает такая строчка:
UART->UART_IER = UART_IER_RXRDY | UART_IER_OVRE | UART_IER_FRAME;
Здесь мы включаем прерывание на приход очередного байта через Rx, ошибку переполнения и "ошибку фрейма" -- когда приёмник ожидает стоп-бит, но получает старотовый бит. Почему есть прерывание на приём, но нет прерывание на передачу? Просто мы пока что ничего не передаём. Как только начнём что-то отправлять, добавим прерывание на передачу байтов. Каждое устройство UART/USART имеет свой вектор прерывания, один на устройство. Разные события в одном и том же устройстве вызывают один и тот же обработчик прерывания. В этом обработчике надо проверить регистры устройства, чтобы понять, что за событие вызвало прерывание. Обработчик прерываний может выглядеть примерно так:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
} | |
} |
Механизм прямого доступа к памяти (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. Точно такие же регистры есть и у другой периферии: 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.
Итак, в таблице регистров 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.
Комментариев нет:
Отправить комментарий