Перейти к содержанию

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:

UBRR0=fosc16⋅BAUD−1\text{UBRR0} = \frac{f_\text{osc}}{16 \cdot \text{BAUD}} - 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Ошибка
96001030,2 %2070,2 %
38 400250,2 %510,2 %
57 600162,1 %34−0,8 %
76 800120,2 %250,2 %
115 2008−3,5 %162,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, но она занимает заметно больше места. Часто проще печатать целые милливольты.

Источники

  1. 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
  2. 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
  3. ArduinoCore-avr, boards.txt (github.com/arduino/ArduinoCore-avr) — Uno: загрузчик на том же последовательном порту, скорость загрузки 115200 бит/с, частота 16 МГц; листинг optiboot_atmega328.lst: после сброса загрузчик ждёт данные с тайм-аутом сторожевого таймера watchdogConfig(WATCHDOG_1S), около 1 с

Нашли ошибку?