UART: скорость, передача, приём и printf
UART — самый простой способ связать микроконтроллер с компьютером: через тот же кабель USB, которым прошивается Arduino Uno, программа может печатать значения и принимать команды. Нужно лишь правильно задать скорость и знать два флага.
Обновлено Редакция GAW
AVR с нуляурок 8 из 830 мин
Цель урока: Рассчитать UBRR0 для нужной скорости с оценкой ошибки, передавать и принимать байты через USART0 и выводить текст функцией printf.
Последний урок трека связывает микроконтроллер с компьютером. На Arduino Uno выводы 0 и 1 — это RXD (PD0) и TXD (PD1) модуля USART0 (раздел 14.3.3 даташита), и они подключены к мосту USB-UART платы — тому самому, через который в уроке 1 работал загрузчик. Поэтому для связи с компьютером не нужно ничего, кроме кабеля USB и монитора порта.
Скорость: UBRR0
USART0 получает скорость, деля тактовую частоту. Значение делителя записывают в 12-битный регистр UBRR0 (от 0 до 4095), формула — в табл. 20-1:
Бит U2X0 в регистре UCSR0A уменьшает делитель с 16 до 8 и удваивает скорость (раздел 20.3.2), тогда в знаменателе стоит 8. Результат округляют до целого, поэтому реальная скорость отличается от заданной. Ошибку считают так: реальная скорость = f_osc / (16 · (UBRR0 + 1)), ошибка = реальная / заданная − 1.
Для 9600 бит/с при 16 МГц: 16 000 000 / (16 · 9600) − 1 ≈ 103,17 → UBRR0 = 103, реальная скорость 16 000 000 / (16 · 104) ≈ 9615 бит/с, ошибка +0,2 % — совпадает с табл. 20-7 даташита.
Насколько можно ошибиться? Приёмник подстраивается по стартовому биту, но к концу кадра ошибка накапливается. Для 8 бит данных без чётности наибольшая допустимая суммарная ошибка — +4,58/−4,54 % при U2X0 = 0 и +3,90/−4,00 % при U2X0 = 1, а рекомендуемая ошибка скорости приёмника — ±2,0 % и ±1,5 % соответственно (табл. 20-2 и 20-3). Рекомендации даны в предположении, что приёмник и передатчик делят суммарную ошибку поровну (раздел 20.8.3), поэтому ошибку из таблицы скоростей сравнивают с ±2,0 % и ±1,5 %, а не с суммарным пределом. Несколько строк табл. 20-7 для 16 МГц:
| Скорость, бит/с | UBRR0 при U2X0 = 0 | Ошибка | UBRR0 при U2X0 = 1 | Ошибка |
|---|---|---|---|---|
| 9600 | 103 | 0,2 % | 207 | 0,2 % |
| 38 400 | 25 | 0,2 % | 51 | 0,2 % |
| 57 600 | 16 | 2,1 % | 34 | −0,8 % |
| 76 800 | 12 | 0,2 % | 25 | 0,2 % |
| 115 200 | 8 | −3,5 % | 16 | 2,1 % |
Видно, что 115 200 при 16 МГц выходит за рекомендуемые пределы в обоих режимах. Для учебных программ надёжнее 9600 или 38 400.
Формат и включение
Регистр UCSR0C задаёт формат кадра. После сброса он равен 0x06: 8 бит данных, без чётности, 1 стоп-бит — привычный формат 8N1, так что трогать его не нужно (раздел 20.11). Регистр UCSR0B включает передатчик битом TXEN0 и приёмник битом RXEN0 (раздел 20.5).
Работают два флага регистра UCSR0A:
- UDRE0 — буфер передачи пуст, можно записывать следующий байт в UDR0 (раздел 20.6.3);
- RXC0 — принят байт, его можно читать из UDR0 (раздел 20.7).
Передача, приём и эхо
#define F_CPU 16000000UL
#define BAUD 9600UL
#include <avr/io.h>
#include <stdint.h>
#define UBRR_VALUE ((F_CPU / (16UL * BAUD)) - 1) /* 103 at 16 MHz */
static void uart_init(void)
{
UBRR0 = UBRR_VALUE; /* baud rate, U2X0 = 0 */
UCSR0B = _BV(RXEN0) | _BV(TXEN0); /* receiver and transmitter on */
/* UCSR0C keeps its reset value 0x06: 8 data bits, no parity, 1 stop bit */
}
static void uart_putc(char c)
{
while (!(UCSR0A & _BV(UDRE0))) {
/* wait for an empty transmit buffer */
}
UDR0 = c;
}
static char uart_getc(void)
{
while (!(UCSR0A & _BV(RXC0))) {
/* wait for a received byte */
}
return UDR0;
}
int main(void)
{
uart_init();
for (;;) {
char c = uart_getc();
if (c >= 'a' && c <= 'z') {
c = c - 'a' + 'A'; /* echo letters in upper case */
}
uart_putc(c);
}
}
Откройте монитор порта на 9600 бит/с с форматом 8N1 — например, в Arduino IDE или любой терминальной программе — и наберите текст: плата вернёт его заглавными буквами. Учтите, что открытие порта на Uno перезапускает плату: загрузчик около секунды ждёт новую прошивку, а затем запускает вашу программу.
Целочисленная формула UBRR_VALUE отбрасывает дробную часть, а не округляет. Для 9600 бит/с это не важно, но для других скоростей лучше заголовок avr-libc util/setbaud.h: по заданным F_CPU и BAUD он сам вычисляет UBRRH_VALUE, UBRRL_VALUE и решает, нужен ли U2X0.
printf через UART
Функция printf из avr-libc пишет в поток stdout, а поток можно связать с собственной функцией вывода символа макросом FDEV_SETUP_STREAM. Добавим к программе выше (без функции main из эха) печать результата АЦП из урока 7:
#include <stdio.h>
static int uart_putchar(char c, FILE *stream)
{
if (c == '\n') {
uart_putchar('\r', stream); /* terminals expect CR LF */
}
uart_putc(c);
return 0;
}
static FILE uart_out = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);
int main(void)
{
uart_init();
stdout = &uart_out;
uint16_t n = 0;
for (;;) {
printf("tick %u\n", n++);
/* add adc_read(0) from lesson 7 and _delay_ms(500) from util/delay.h here */
}
}
printf по умолчанию не умеет печатать float — для этого нужна полная версия vfprintf и ключи компоновщика -Wl,-u,vfprintf -lprintf_flt -lm. Обычно проще печатать целые: милливольты вместо вольт.
Безопасность
- Выводы 0 и 1 Uno уже подключены к мосту USB-UART. Если подключить к ним ещё одно устройство с активным выходом TX, два выхода окажутся соединены друг с другом. Внешний модуль подключают к этим выводам только при отключённом USB или через резисторы около 1 кОм (наша рекомендация).
- Уровни UART ATmega328P — логические 0 и 5 В. Порт RS-232 компьютера использует уровни до ±15 В и требует преобразователя вроде MAX232: напрямую его к выводам подключать нельзя — абсолютный максимум на выводе VCC + 0,5 В (раздел 29.1).
Задание
Нужна скорость 57 600 бит/с при 16 МГц. Посчитайте UBRR0 и ошибку в обоих режимах — с U2X0 = 0 и U2X0 = 1 — и выберите режим.
Ответ и решение
U2X0 = 0: 16 000 000 / (16 · 57 600) − 1 ≈ 16,36 → UBRR0 = 16. Реальная скорость 16 000 000 / (16 · 17) ≈ 58 824 бит/с, ошибка ≈ +2,1 % — больше рекомендуемых ±2,0 %.
U2X0 = 1: 16 000 000 / (8 · 57 600) − 1 ≈ 33,72 → UBRR0 = 34. Реальная скорость 16 000 000 / (8 · 35) ≈ 57 143 бит/с, ошибка ≈ −0,8 % — укладывается в ±1,5 %.
Выбираем U2X0 = 1: UCSR0A = _BV(U2X0); UBRR0 = 34;. Оба результата совпадают с табл. 20-7 даташита.
Что дальше
Это последний урок первой части трека. Дальше в плане — внешние прерывания, EEPROM, сторожевой таймер и сон, SPI и I2C. А пока можно соединить всё пройденное: таймер с прерыванием каждые 500 мс запускает АЦП и печатает результат через UART. UBRR для любой частоты и скорости с ошибкой посчитает калькулятор скорости UART, полный код инициализации USART, таймеров и АЦП соберёт генератор кода AVR. Регистры USART0 с битами — на странице регистров USART0 ATmega328P. Сам интерфейс подробно разобран в статье «UART», а как подключить плату к компьютеру — в ответе «Как подключить UART к компьютеру». Коротко о понятиях — термины «Скорость передачи» и «Старт- и стоп-биты». Все уроки трека — на странице «AVR с нуля».
Частые вопросы
Почему вместо текста в мониторе порта иероглифы?
Почти всегда не совпадает скорость: в программе одна, в мониторе порта другая, или F_CPU не равна реальной частоте кристалла. На чистом ATmega328P с завода частота 1 МГц, и UBRR0, посчитанный для 16 МГц, даст скорость в 16 раз ниже. Реже причина в формате кадра — число бит данных, чётность или стоп-биты.
Можно ли работать на 115200 бит/с при 16 МГц?
Можно, но с запасом хуже, чем на 9600. По таблице даташита ошибка скорости при 16 МГц — −3,5 % без U2X0 и 2,1 % с U2X0, а рекомендуемый предел для 8 бит данных — ±2,0 % и ±1,5 % соответственно. Надёжнее выбрать 76 800 или 38 400 бит/с, где ошибка 0,2 %, или кварц 18,432 МГц.
Почему printf не печатает дробные числа?
По умолчанию avr-libc компонует сокращённую версию vfprintf без поддержки float, чтобы сэкономить флеш-память: вместо числа выводится знак вопроса. Полную версию подключают ключами компоновщика -Wl,-u,vfprintf -lprintf_flt -lm, но она занимает заметно больше места. Часто проще печатать целые милливольты.
Источники
- Microchip. ATmega48A/PA/88A/PA/168A/PA/328/P Data Sheet (DS40002061B) — табл. 20-1: UBRRn = fosc/(16·BAUD) − 1, при U2Xn = 1 — fosc/(8·BAUD) − 1, UBRRn от 0 до 4095; разд. 20.3.2: U2Xn уменьшает делитель с 16 до 8; разд. 20.5: инициализация — скорость, формат, включение передатчика и приёмника; разд. 20.6.3: флаг UDREn — буфер передачи пуст; разд. 20.7: флаг RXCn — принят байт; табл. 20-2 и 20-3: для 8 бит данных наибольшая суммарная ошибка +4,58/−4,54 % (U2Xn = 0) и +3,90/−4,00 % (U2Xn = 1), рекомендуемая ошибка приёмника ±2,0 % и ±1,5 %; разд. 20.8.3: рекомендации исходят из того, что приёмник и передатчик делят суммарную ошибку поровну; табл. 20-7: значения UBRRn и ошибки при 16 МГц; разд. 20.11: UCSR0C после сброса 0x06 — 8 бит, без чётности, 1 стоп-бит; разд. 14.3.3: RXD — PD0, TXD — PD1
- AVR-LibC 2.3.2: stdio.h и util/setbaud.h (github.com/avrdudes/avr-libc) — FDEV_SETUP_STREAM(put, get, rwflag) и _FDEV_SETUP_WRITE, функция вывода int put(char, FILE *), назначение stdout; printf по умолчанию без чисел с плавающей точкой, версия с ними — ключами -Wl,-u,vfprintf -lprintf_flt -lm; util/setbaud.h считает UBRRH_VALUE, UBRRL_VALUE и USE_2X по F_CPU и BAUD
- ArduinoCore-avr, boards.txt (github.com/arduino/ArduinoCore-avr) — Uno: загрузчик на том же последовательном порту, скорость загрузки 115200 бит/с, частота 16 МГц; листинг optiboot_atmega328.lst: после сброса загрузчик ждёт данные с тайм-аутом сторожевого таймера watchdogConfig(WATCHDOG_1S), около 1 с
Нашли ошибку?