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