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.
  • Если сбой случается «через час, потом ещё через час» — это переполнение буфера или флаг, который ты забыл.
если ставишь debug-прерыв в IRQ, всегда смотри регистры ICR и ISR ОБА.
← в архив · → в ленту