16. UART / USART
Первое, что подключают к плате, и последнее, что остаётся работать, когда всё остальное сломалось. Два провода, никакой синхронизации, любая длина сообщения — поэтому UART служит и рабочим интерфейсом, и главным инструментом отладки.
Как это называется в документации
Оригинал Перевод baud rate скорость передачи, бод frame кадр, посылка start / stop bit стартовый / стоповый бит parity контроль чётности transmit data register empty ( TXE)регистр передачи свободен transmission complete ( TC)передача завершена read data register not empty ( RXNE)принятый байт готов overrun error ( ORE)приём поверх непрочитанного байта (переполнение приёмника) idle line пауза в линии framing error ошибка кадра oversampling передискретизация Modbus RTU протокол «ведущий — ведомые» поверх UART holding register регистр хранения exception response ответ-исключение broadcast широковещательный запрос driver enable (DE) включение передатчика RS-485
16.1 Как устроен кадр
Линия в покое держит единицу. Передача начинается стартовым битом — переходом в ноль, — за ним идут биты данных младшим вперёд, необязательный бит чётности и один или два стоповых бита (снова единица).
Стандартная запись «115200 8N1» означает: скорость 115200 бод, 8 бит данных, без чётности (N), один стоповый бит.
Синхронизации нет вообще — приёмник ловит фронт стартового бита и дальше отсчитывает время сам. Отсюда главное требование: обе стороны должны иметь близкие тактовые частоты. Расхождение больше 2–3 % ломает приём.
Так кадр символа «A» выглядит на экране логического анализатора:
16.2 Расчёт скорости
Скорость задаётся делителем в регистре BRR:
USART1->BRR = (частота_шины + baud / 2u) / baud;
Прибавление половины скорости перед делением — это округление к ближайшему вместо отбрасывания дробной части. Мелочь, которая вдвое уменьшает наибольшую возможную ошибку делителя — с целой единицы до половины.
⚠️ Делитель считается от частоты своей шины, а они разные. У F1
USART1сидит на APB2 (72 МГц), аUSART2иUSART3— на APB1 (36 МГц). Один и тот же код настройки, применённый к разным портам, даст разные скорости. У F4 та же история:USART1иUSART6на APB2, остальные на APB1.
Насколько это критично, видно на числах:
| Порт | Частота | Делитель для 115200 | Фактическая скорость | Погрешность |
|---|---|---|---|---|
USART1 |
72 МГц | 625 ровно | 115200 | 0 % |
USART2 |
36 МГц | 312,5 → 313 | 115015 | 0,16 % |
USART2 без округления |
36 МГц | 312 | 115384 | 0,16 % |
USART2 у F4 |
42 МГц | 364,58 → 365 | 115068 | 0,11 % |
USART2 у F4 без округления |
42 МГц | 364 | 115384 | 0,16 % |
На 36 МГц делитель 312,5 лежит ровно посередине, и обе ошибки равны по модулю; на 42 МГц округление уже выигрывает. Все погрешности приемлемы. Проблемы начинаются, когда тактирование берётся от внутреннего генератора HSI. У F103 заводская настройка держит его в пределах −1,1…+1,8 % при 25 °C, а во всём диапазоне температур — −2…+2,5 %. Эта ошибка относительная и одинакова на любой скорости, и вместе с ошибкой собеседника она подходит к пределу приёма. Поэтому для UART вне комнатных условий нужен кварц (раздел 6). Высокая скорость добавляет своё: делитель маленький, и ошибка его округления растёт.
16.3 🔧 Передача
void usart1_send_byte(uint8_t b)
{
while (!(USART1->SR & USART_SR_TXE)) { } /* можно класть следующий байт */
USART1->DR = b;
}
void usart1_send_string(const char *s)
{
while (*s) {
usart1_send_byte((uint8_t)*s++);
}
while (!(USART1->SR & USART_SR_TC)) { } /* всё ушло в линию */
}
⚠️
TXEиTC— не одно и то же.TXEозначает, что байт переложен из регистра данных в сдвиговый и туда можно класть следующий.TC— что последний бит физически покинул вывод. Разница проявляется в двух местах: при переключении направления полудуплексной линии (RS‑485) и при уходе в сон сразу после передачи. Дождались толькоTXE— обрежете конец посылки.
16.4 Приём: три способа и одна ловушка
Опросом — просто, но процессор занят ожиданием и легко пропустить байт. Годится для отладочного вывода, не годится для приёма.
По прерыванию — каждый принятый байт вызывает обработчик. Рабочий вариант для умеренных скоростей.
Через DMA — байты складываются в буфер сами. Единственный разумный способ для потока данных.
void USART1_IRQHandler(void)
{
uint32_t sr = USART1->SR;
/* Переполнение: пришёл байт, пока предыдущий не забрали. */
if (sr & USART_SR_ORE) {
(void)USART1->DR;
return;
}
if (sr & USART_SR_RXNE) {
uint8_t b = (uint8_t)USART1->DR; /* чтение сбрасывает RXNE */
/* ... */
}
}
⚠️ Переполнение надо снимать. Если байт пришёл раньше, чем вы забрали предыдущий, взводится флаг
ORE, и новый байт теряется. Сам приёмник на F1 и F4 не останавливается: следующий байт ляжет вDR, как только прежний прочитан. Флаг снимает последовательность «чтениеSR, затемDR» — её и делает обработчик выше. Опасен флаг, оставшийся стоять: приRXNEIE, а при приёме через DMA — приEIE, он вызывает прерывание снова и снова, и устройство выглядит зависшим. Так бывает, когдаDRчитает DMA — он не читаетSR, — или когда байт пришёл между чтениямиSRиDR:OREстоит, аRXNEуже нет, и обработчик, который смотрит только наRXNE, флаг не снимет. Насовсем замолкает приём в HAL: поOREон прерывается, и запускать его заново надо вHAL_UART_ErrorCallback. У USART нового образца флаг снимают записью вICR(16.9).
16.5 🔧 Приём пакетов переменной длины
Классическая задача: приходят сообщения разной длины, разделитель заранее неизвестен. Ждать фиксированного числа байт нельзя, а разбирать каждый байт в прерывании дорого.
Решение — обнаружение паузы в линии. Если после последнего байта линия
молчала дольше времени одного кадра, взводится флаг IDLE: значит, пакет
закончился.
USART1->CR1 |= USART_CR1_RXNEIE | USART_CR1_IDLEIE;
/* в обработчике */
if (sr & USART_SR_IDLE) {
(void)USART1->DR; /* снять флаг: чтение SR, затем DR */
/* здесь пакет длиной rx_len готов к разбору */
rx_len = 0;
}
Правильная промышленная схема — сочетание с DMA: контроллер складывает
байты в кольцевой буфер без участия процессора, а прерывание по IDLE
срабатывает один раз на весь пакет. Сколько именно байт пришло, узнают
по счётчику оставшихся передач DMA (раздел 9).
Получается приём любой длины на любой скорости ценой одного прерывания на сообщение.
16.6 printf в порт
Самый ходовой способ отладки, когда отладчик недоступен или мешает (например, нельзя останавливать двигатель).
Достаточно перенаправить вывод стандартной библиотеки в порт — в Keil это
переопределение fputc, в GCC — _write. После этого работает обычный
printf.
⚠️
printfдорог. Он тянет за собой несколько килобайт кода, а форматный вывод чисел с плавающей точкой — ещё больше и медленно. На камне с 64 КБ Flash это заметно. Лёгкие альтернативы: собственная функция вывода числа, компактные библиотеки форматирования или передача сырых данных с разбором на стороне компьютера. Точные цифры для F103 — в разделе 74.4: печать целого добавляет около полукилобайта, вещественного — больше четырёх.
Второй недостаток — блокирование: пока строка уходит в линию на 115200 бод, проходят миллисекунды. В обработчике прерывания это недопустимо (раздел 7); там либо кладите данные в буфер, либо выводите через DMA.
16.7 ⚠️ Уровни сигналов: не перепутайте
Тонкость, которая сжигает выводы.
| Что | Уровни |
|---|---|
| UART микроконтроллера | 0 / 3,3 В |
| порт RS‑232 компьютера | ±12 В, инвертированные |
| RS‑485 | дифференциальная пара |
Прямое соединение вывода STM32 с классическим COM‑портом выведет вывод
из строя. Нужен преобразователь уровней. Именно поэтому для отладки
используют переходник USB–UART. Но уровни у него проверьте до подключения:
многие дешёвые переходники по умолчанию работают на 5 В и переключаются
перемычкой 3,3/5 В. Пятивольтовый TX можно подавать только на вывод
с допуском 5 В — с меткой FT в таблице выводов даташита. У F103 PA10
(приём USART1) такой, а PA3 (приём USART2, как в 16.8) — нет
(приложение E).
Отдельно про совместимость: у переходника обязательно должна быть общая
земля с платой, а TX одного идёт на RX другого — линии перекрещиваются.
Отсутствие связи чаще всего объясняется именно этим, а не настройками.
16.8 🔧 Ведомый Modbus RTU
Modbus RTU поверх UART — самый распространённый протокол промышленных приборов: счётчики, частотные преобразователи, датчики на шине RS-485. Кадр и CRC подробно разобраны в разделе 89.4. Здесь — ведомый целиком: как по UART понять, где кончился кадр, что проверить и как ответить.
Символ — 11 бит: старт, 8 бит данных, бит чётности и стоп. Без
чётности вместо неё ставят второй стоповый бит. У STM32 бит чётности
входит в длину слова: 8 бит данных с чётностью — это девятибитное слово,
M = 1 и PCE = 1. В DR чётность приходит девятым битом, и приведение
к uint8_t её отбрасывает.
Конец кадра — тишина в 3,5 символа: на 9600 бод это 4011 мкс.
Флаг IDLE (16.5) взводится после тишины в один символ — этого мало.
Тишину отсчитывает таймер в одноимпульсном режиме. Каждый принятый байт
обнуляет его и запускает заново; если таймер дотикал, кадр закончен.
Разрыв внутри кадра дольше 1,5 символа (1719 мкс) стандарт считает
ошибкой. Простая схема его не ловит, но такой кадр всё равно отсеет CRC.
Выше 19 200 бод паузы по символам не считают: стандарт советует постоянные — 1750 мкс на конец кадра и 750 мкс на разрыв внутри него (раздел 89.4). Иначе на 115 200 бод 3,5 символа — всего 335 мкс, и за конец кадра ведомый примет любую заминку ведущего — например, компьютера с преобразователем USB — RS-485.
void USART2_IRQHandler(void)
{
uint32_t sr = USART2->SR;
uint8_t b = (uint8_t)USART2->DR; /* чтение SR, затем DR сбрасывает флаги; бит 8 — чётность */
if (sr & (USART_SR_PE | USART_SR_FE))
rx.overflow = 1; /* битый символ — весь кадр в корзину */
else if (sr & USART_SR_RXNE)
mb_rx_byte(&rx, b);
TIM2->CNT = 0; /* каждый байт откладывает конец кадра */
TIM2->CR1 |= TIM_CR1_CEN;
}
void TIM2_IRQHandler(void)
{
TIM2->SR = 0;
frame_len = mb_rx_end(&rx); /* тишина 3,5 символа — кадр закончен */
}
/* Ответ: включить передатчик, выдать байты, дождаться TC — не TXE — и
* только тогда выключить: иначе обрежется последний байт (раздел 89.4) */
static void mb_send(const uint8_t *p, size_t n)
{
GPIOA->BSRR = 1u << 1;
for (size_t i = 0; i < n; i++) {
while (!(USART2->SR & USART_SR_TXE)) { }
USART2->DR = p[i];
}
while (!(USART2->SR & USART_SR_TC)) { }
GPIOA->BSRR = 1u << (1 + 16);
}
Настройка — USART2 на PA2 и PA3, вывод DE приёмопередатчика RS-485 на PA1:
| F103 | F407 | |
|---|---|---|
| USART2 | APB1 36 МГц, BRR = 3750 |
APB1 42 МГц, BRR = 4375 |
| TIM2 | удвоенные 72 МГц, PSC = 71 — тик 1 мкс |
удвоенные 84 МГц, PSC = 83 — тик 1 мкс |
| пауза | ARR = 4010, OPM = 1 |
то же |
| пауза выше 19 200 бод | ARR = 1749 — 1750 мкс |
то же |
| слово | M = 1, PCE = 1 |
то же |
На обоих камнях скорость получается ровно 9600 бод, без погрешности.
Обработка кадра — переносимая функция, одна для любого камня:
/* Обработать кадр: вернёт длину ответа в resp или 0 — не отвечать
* (обрезанный кадр, неверная CRC, чужой адрес, широковещательный адрес 0). */
size_t mb_process(uint8_t addr, const uint8_t *req, size_t n, uint8_t *resp)
{
if (n < 4 || n > MB_ADU || mb_crc(req, n - 2) != (uint16_t)(req[n - 2] | (req[n - 1] << 8)))
return 0;
int broadcast = req[0] == 0;
if (!broadcast && req[0] != addr)
return 0;
uint8_t fn = req[1];
size_t k;
if (fn == 3 && !broadcast) {
uint16_t first = n == 8 ? be16(req + 2) : 0, count = n == 8 ? be16(req + 4) : 0;
if (count < 1 || count > 125) {
k = mb_error(resp, addr, fn, MB_ILLEGAL_VALUE);
} else if ((uint32_t)first + count > MB_REGS) {
k = mb_error(resp, addr, fn, MB_ILLEGAL_ADDRESS);
} else {
resp[0] = addr;
resp[1] = fn;
resp[2] = (uint8_t)(2 * count);
for (uint16_t i = 0; i < count; i++) {
resp[3 + 2 * i] = (uint8_t)(mb_regs[first + i] >> 8);
resp[4 + 2 * i] = (uint8_t)mb_regs[first + i];
}
k = mb_finish(resp, 3u + 2u * count);
}
} else if (fn == 16) {
uint16_t first = n >= 11 ? be16(req + 2) : 0, count = n >= 11 ? be16(req + 4) : 0;
if (count < 1 || count > 123 || req[6] != 2 * count || n != 9u + req[6]) {
k = mb_error(resp, addr, fn, MB_ILLEGAL_VALUE);
} else if ((uint32_t)first + count > MB_REGS) {
k = mb_error(resp, addr, fn, MB_ILLEGAL_ADDRESS);
} else {
for (uint16_t i = 0; i < count; i++)
mb_regs[first + i] = be16(req + 7 + 2 * i);
for (int i = 0; i < 6; i++) /* ответ: адрес, функция, первый регистр, число */
resp[i] = req[i];
k = mb_finish(resp, 6);
}
} else {
k = mb_error(resp, addr, fn, MB_ILLEGAL_FUNCTION);
}
return broadcast ? 0 : k;
}
be16 собирает число из двух байт старшим вперёд. mb_finish дописывает
CRC младшим байтом вперёд. mb_error строит ответ-исключение: номер
функции с поднятым старшим битом и код причины. Функция занимает 40
байт стека, ответ собирается прямо в буфер передачи.
| Что пришло | Что делает ведомый |
|---|---|
| чужой адрес | молчит |
| неверная CRC или обрезанный кадр | молчит — ведущий повторит запрос |
| адрес 0, широковещательный | выполняет запись и не отвечает |
| функция не поддерживается | исключение 01 |
| регистр за пределами | исключение 02 |
| неверное число регистров или длина | исключение 03 |
Кадр не длиннее 256 байт, поэтому за раз можно прочитать до 125 регистров и записать до 123. Самый длинный кадр на 9600 бод идёт 293 мс, а полезных бит в символе 8 из 11 — это 72,7 % скорости линии.
Опыт. Против того же mb_process, собранного для компьютера,
работал настоящий ведущий pymodbus. Кадры RTU шли по TCP так, как их
передаёт преобразователь Ethernet — RS-485, а конец кадра определялся
той же паузой:
| Что прислал ведущий | Ответ ведомого |
|---|---|
| чтение регистров 0–3, функция 03 | 100, 101, 102, 103 |
| запись 21 и 42 в регистры 2 и 3, функция 16 | подтверждение; чтение даёт 100, 101, 21, 42 |
| чтение регистров 14–17, а их всего 16 | исключение 02 |
| функция 01 | исключение 01 |
| запрос к адресу 2 | тишина |
| кадр с испорченной CRC | тишина |
| широковещательная запись 777 в регистр 5 | тишина; чтение даёт 777 |
01 10 00 11 00 02 04 00 15 00 2A A3 74 — запись в регистры 17 и 18 |
исключение 02: 01 90 02 CD C1 |
Все десять кадров выделены паузой, ни один не склеился с соседним.
⚠️ Типичные ошибки самодельных ведомых. - CRC читают как
*(uint16_t *)(buf + n). Это невыровненное чтение (на Cortex-M0 —HardFault), и оно зависит от порядка байт. CRC собирают из двух байт: младший первым. - Номер регистра из запроса не проверяют, и запрос вроде последнего в таблице пишет за край массива. - Данные записи берут с байта 6. Но там счётчик байт, а данные начинаются с байта 7, старшим вперёд. - Ответ на запись собирают из 5 байт. Нужно 6 — адрес, функция, первый регистр, число регистров — плюс CRC. - CRC исключения считают по двум байтам. Нужно по трём, включая код.
16.9 F0 и G0: USART нового образца
У F0 и G0 (как у F3, F7, L4 и дальше) блок USART другой: вместо двух регистров
SR и DR — четыре. Код с F103 на F0 не собирается: «no member named 'SR'
in 'USART_TypeDef'».
| F1, F4 | F0 | G0 | |
|---|---|---|---|
| флаги | SR |
ISR (0x1C) |
ISR |
| сброс флагов | чтением SR и DR |
запись в ICR (0x20) |
запись в ICR |
| приём / передача | один DR |
RDR (0x24) / TDR (0x28) |
RDR / TDR |
| очередь FIFO, предделитель | нет | нет | есть: FIFOEN, PRESC |
| такт USART2 | APB1ENR |
APB1ENR |
APBENR1 |
Делитель считается по той же формуле, что в 16.2: при 48 МГц у F0 — 417, при 64 МГц у G0 — 556, оба дают 115 107 бод, ошибка 0,08 %.
Передача и приём на F0:
while (!(USART2->ISR & USART_ISR_TXE)) { /* флаги — в ISR, а не в SR */
}
USART2->TDR = (uint8_t)c; /* передача — свой регистр TDR */
if (USART2->ISR & USART_ISR_ORE)
USART2->ICR = USART_ICR_ORECF; /* переполнение сбрасывается в ICR */
return (int)(USART2->RDR & 0xFFu); /* чтение RDR сбрасывает RXNE */
Переполнение сбрасывают сами. У F1 флаг ORE уходил при чтении SR и
DR; здесь он сам не уходит, и пока он стоит, приёмник не принимает новые
байты. Отсюда классика: «после первого переполнения UART замолчал навсегда».
Сброс — записью USART_ICR_ORECF в ICR.
G0: очередь. С битом FIFOEN у передатчика и приёмника появляется
очередь, и флаги меняют смысл — это видно по именам: TXE_TXFNF — «в очереди
есть место», RXNE_RXFNE — «очередь не пуста»:
USART2->CR1 = USART_CR1_FIFOEN | USART_CR1_TE | USART_CR1_RE | USART_CR1_UE;
while (!(USART2->ISR & USART_ISR_TXE_TXFNF)) { /* с FIFO: «в очереди есть место» */
}
if (!(USART2->ISR & USART_ISR_RXNE_RXFNE)) /* с FIFO: «очередь не пуста» */
Предделитель PRESC делит такт блока перед BRR — он нужен на медленных
скоростях, когда делитель не помещается в 16 бит. Ноль в нём — без деления.
16.10 Грабли
Мусор вместо текста. Скорости не совпадают либо неверно посчитан делитель для этой шины. Проверьте, на APB1 или APB2 сидит ваш порт.
Приём молчит после потока данных. Не снят флаг переполнения ORE
или не перезапущен прерванный им приём HAL (16.4).
Обрезается конец посылки. Дождались TXE вместо TC перед сном
или переключением направления.
Первый байт теряется. Приёмник включён позже, чем начал передавать собеседник, либо мусор от переходного процесса при включении питания.
Ничего не работает, линии перепутаны. TX должен идти на RX.
Работает только с отладчиком. Признак гонки: при пошаговом выполнении байты успевают обрабатываться, на полной скорости — нет. Ищите переполнение.
Кириллица в терминале. UART передаёт байты, кодировку выбирает терминал. Если микроконтроллер шлёт UTF‑8, а терминал ждёт CP866, получится каша.
Ведомый Modbus отвечает через раз. Конец кадра ищут по IDLE или по
фиксированной задержке, и кадры то склеиваются, то режутся.
Ошибка чётности на каждом байте. Чётность включили, а слово оставили
8-битным (M = 0): бит чётности съел старший бит данных.
Ведущий видит тайм-аут, хотя ответ ушёл. Передатчик RS-485 выключен
по TXE, и последний байт CRC обрезан.
16.11 Итоги
- Кадр: старт, данные младшим битом вперёд, необязательная чётность, стоп. Синхронизации нет — частоты сторон должны сходиться в пределах пары процентов.
- Делитель считается от частоты своей шины:
USART1на APB2, остальные на APB1, и скорости при одинаковом коде получатся разными. - Округление к ближайшему при расчёте делителя вдвое уменьшает наибольшую возможную ошибку делителя — с единицы до половины.
TXE— можно класть следующий байт,TC— всё физически ушло. Путать нельзя.- Переполнение
OREна F1 и F4 снимают чтениемSR, затемDR; оставшийся флаг зацикливает прерывание, а приём HAL по нему прерывается. - Пакеты переменной длины принимают по паузе в линии; лучшая схема —
DMA плюс прерывание
IDLE. printfудобен, но дорог по памяти и блокирует выполнение.- Уровни микроконтроллера — 3,3 В; классический COM‑порт с его ±12 В подключать напрямую нельзя.
- Modbus RTU поверх UART: символ из 11 бит (чётность у STM32 означает
M= 1), конец кадра — 3,5 символа тишины по таймеру. На чужой адрес и битую CRC ведомый молчит, на ошибки отвечает исключениями 01, 02, 03.
Полные исходники
uart_f0.c — USART2 у STM32F072 на 48 МГц.
/* Раздел 16.9: USART2 на PA2/PA3 у STM32F072 при 48 МГц.
* Блок USART нового образца: флаги в ISR, сброс флагов — в ICR, TDR и RDR отдельно. */
#include <stddef.h>
#include "stm32f0xx.h"
#define PCLK_HZ 48000000u
#define BAUD 115200u
#define BRR_VALUE ((PCLK_HZ + BAUD / 2u) / BAUD)
_Static_assert(BRR_VALUE == 417u, "делитель — та же формула, что у F1 и F4");
_Static_assert(offsetof(USART_TypeDef, ISR) == 0x1Cu && offsetof(USART_TypeDef, ICR) == 0x20u, "флаги и сброс");
_Static_assert(offsetof(USART_TypeDef, RDR) == 0x24u && offsetof(USART_TypeDef, TDR) == 0x28u, "приём и передача");
void uart_init_f0(void)
{
RCC->AHBENR |= RCC_AHBENR_GPIOAEN;
RCC->APB1ENR |= RCC_APB1ENR_USART2EN;
GPIOA->MODER = (GPIOA->MODER & ~(3u << 4) & ~(3u << 6)) | (2u << 4) | (2u << 6); /* PA2, PA3 — AF */
GPIOA->AFR[0] = (GPIOA->AFR[0] & ~(0xFu << 8) & ~(0xFu << 12)) | (1u << 8) | (1u << 12); /* AF1 */
USART2->BRR = BRR_VALUE;
USART2->CR1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_UE;
}
void uart_putc_f0(char c)
{
while (!(USART2->ISR & USART_ISR_TXE)) { /* флаги — в ISR, а не в SR */
}
USART2->TDR = (uint8_t)c; /* передача — свой регистр TDR */
}
int uart_getc_f0(void)
{
if (USART2->ISR & USART_ISR_ORE)
USART2->ICR = USART_ICR_ORECF; /* переполнение сбрасывается в ICR */
if (!(USART2->ISR & USART_ISR_RXNE))
return -1;
return (int)(USART2->RDR & 0xFFu); /* чтение RDR сбрасывает RXNE */
}
uart_g0.c — USART2 у STM32G071 на 64 МГц с очередью FIFO.
/* Раздел 16.9: USART2 на PA2/PA3 у STM32G071 при 64 МГц — с очередью FIFO. */
#include <stddef.h>
#include "stm32g0xx.h"
#define PCLK_HZ 64000000u
#define BAUD 115200u
#define BRR_VALUE ((PCLK_HZ + BAUD / 2u) / BAUD)
_Static_assert(BRR_VALUE == 556u, "64 МГц / 115200");
_Static_assert(offsetof(USART_TypeDef, PRESC) == 0x2Cu, "предделитель — отдельный регистр PRESC");
void uart_init_g0(void)
{
RCC->IOPENR |= RCC_IOPENR_GPIOAEN;
RCC->APBENR1 |= RCC_APBENR1_USART2EN; /* у G0 — APBENR1, не APB1ENR */
GPIOA->MODER = (GPIOA->MODER & ~(3u << 4) & ~(3u << 6)) | (2u << 4) | (2u << 6);
GPIOA->AFR[0] = (GPIOA->AFR[0] & ~(0xFu << 8) & ~(0xFu << 12)) | (1u << 8) | (1u << 12);
USART2->PRESC = 0u; /* такт без деления */
USART2->BRR = BRR_VALUE;
USART2->CR1 = USART_CR1_FIFOEN | USART_CR1_TE | USART_CR1_RE | USART_CR1_UE;
}
void uart_putc_g0(char c)
{
while (!(USART2->ISR & USART_ISR_TXE_TXFNF)) { /* с FIFO: «в очереди есть место» */
}
USART2->TDR = (uint8_t)c;
}
int uart_getc_g0(void)
{
if (USART2->ISR & USART_ISR_ORE)
USART2->ICR = USART_ICR_ORECF;
if (!(USART2->ISR & USART_ISR_RXNE_RXFNE)) /* с FIFO: «очередь не пуста» */
return -1;
return (int)(USART2->RDR & 0xFFu);
}
Дальше: раздел 16а «RS-485: линия, терминаторы, смещение и направление
передачи» — шина, смещение под порог приёмника и DE по TC или силами USART.