STM32, UART, и тихая смерть от OVERRUN
Это история о том, как я три дня искал, почему через час работы у меня отваливается RS-485, и обнаружил флаг, который НИКТО в datasheet нормально не описал.
Симптомы
Устройство-датчик. STM32G071. RS-485 наружу, master периодически нас опрашивает, мы отвечаем. На стенде работает идеально. На полевом испытании — час-полтора, потом тишина. Reset выводит обратно в работу. Через час-полтора — снова тишина.
Что я думал
Сначала: «наверное, помехи». Поставил осциллограф — на проводе всё чисто, master корректно отправляет запросы.
Потом: «наверное, прерывание не вызывается». Поставил toggle GPIO в начале USART2_IRQHandler — нет, прерывание вызывается на каждый принятый байт.
Что оказалось
В USART_ISR у STM32 есть флаг ORE. Я знал про него и аккуратно проверял. Если он стоит — читаю байт, скидываю флаг, пишу в лог.
Я не знал: пока стоит ORE, бит RXNE не выставляется. UART перестаёт принимать байты в обычном режиме.
«Так я же его сбрасываю», — сказал я.
Сбрасывал. Но не так. На STM32G0/G4/H7 сброс ORE делается записью единицы в USART_ICR.ORECF. Я по привычке делал «чтение SR, потом чтение DR» (как на F1/F4). На G0 это ничего не сбрасывает.
Почему три дня
В обычной работе ORE срабатывал и сбрасывался волшебным образом — потому что HAL'овская обёртка в одном из путей делала запись в ICR. Этот вызов раз в час чистил мне ORE. Я думал, что я его чищу сам.
Через час, когда master стрелял очередью с злой частотой, ORE поднимался, не чистился, RXNE больше не поднимался, и UART становился каменной плитой.
Решение
void USART2_IRQHandler(void) {
uint32_t isr = USART2->ISR;
if (isr & USART_ISR_ORE) {
USART2->ICR = USART_ICR_ORECF; // вот эта строка
}
if (isr & USART_ISR_RXNE_RXFNE) {
uint8_t b = USART2->RDR;
rb_put(b);
}
}
Что я выучил
- Reference Manual про ORE говорит «RXNE will remain at 0 until the ORE flag is cleared». В моих закладках этой фразы не было. Сейчас есть.
- HAL и LL — разные миры.
- Между F1 и G0 пять лет разницы и совсем разные periphery.
- Если сбой случается «через час, потом ещё через час» — это переполнение буфера или флаг, который ты забыл.