openlang

TMR3 ISR теряет capture-события, хотя флаги w1c и сбрасываются

Контроллер reflow-печи, клон Cortex-M0, arm-none-eabi-gcc -O2, Fedora. TMR3 работает на 1 МГц. Update event — тик 1 мс, CH1 захватывает переход сетевого напряжения через ноль, сравнение CH2 запускает симистор.

Проблема: после 60 с на 50 Гц zc_count должен быть около 6000. У меня получается от 5900 до 5990, при каждом запуске по-разному. Пропущенный zero-cross значит, что в этой полуволне нет импульса на управляющий электрод, и мощность нагревателя мерцает. Становится хуже, когда я повышаю частоту логов по UART.

Что я проверил:

  • Осциллограф показывает чистые импульсы zero-cross, интервал постоянный, 10 мс. Так что это не оптрон и не шум.
  • Счётчики volatile.
  • Перенёс блок capture выше блока тика. Потери стали реже, но не пропали.
  • Более высокий приоритет прерывания TMR3 ничего не меняет.

Не понимаю. В даташите сказано, что флаги SR сбрасываются записью единицы (write-1-to-clear), и я сбрасываю каждый флаг сразу, как обработал его. Если флаг установлен, NVIC должен снова войти в обработчик. Так как может потеряться событие с установленным флагом?

void TMR3_IRQHandler(void)
{
    uint32_t sr = TMR3_SR;

    if (sr & TMR_SR_UIF) {
        TMR3_SR |= TMR_SR_UIF;
        tick_ms++;
        if ((tick_ms % 500u) == 0)
            log_due = 1;
    }
    if (sr & TMR_SR_CC1IF) {
        TMR3_SR |= TMR_SR_CC1IF;
        zc_count++;
        zc_stamp = TMR3_CCR1;
        TMR3_CCR2 = (zc_stamp + fire_delay_us) & 0xFFFFu;
    }
    if (sr & TMR_SR_CC2IF) {
        TMR3_SR |= TMR_SR_CC2IF;
        TRIAC_ON();
    }
}

SR определён в regs.h как обычный volatile uint32_t по адресу base + 0x10. В конце концов, возможно, у клона есть кремниевый баг, но проверить без железа я не могу. Только счётчики по UART и осциллограф.

Переведено с английскогоПоказать оригинал

Разговор продолжается 5

Следуя доводам
  1. Ссылка на ответ

    У меня было что-то похожее на лабораторном логгере данных, на отладочной плате, с которой я работал несколько недель. Счётчик событий оказывался на несколько процентов меньше. Обработчик делал SR |= flag. Это читает все установленные в данный момент флаги и записывает их все обратно, так что флаг, который поднялся между чтением и записью, сбрасывался без обработки. Простое SR = flag у меня всё исправило.

    Переведено с английскогоПоказать оригинал
  2. Ссылка на ответ

    Добавлю одну деталь к замечанию bkt: в опубликованном обработчике потеря происходит, хотя sr читается только один раз в начале. TMR3_SR |= TMR_SR_UIF выполняет новое чтение, так что если CC1IF поднялся после первого чтения, запись обратно сбросит его необработанным. То же касается каждого |= в обработчике.

    Одна оговорка: я бы проверил в reference manual клона, что SR действительно write-1-to-clear. Некоторые таймеры работают как write-0-to-clear, и там правильная форма — SR = ~flag.

    Переведено с английскогоПоказать оригинал
  3. Ссылка на ответ

    Ок, это объясняет потери. Между первым чтением и записью |= есть окно, и 5900–5990 с этим сходится, а большая нагрузка на UART его расширяет.

    В мануале написано w1c, я проверил. Значит, TMR3_SR = TMR_SR_CC1IF затрагивает только этот один бит. Хорошо.

    Вопрос: всегда ли |= на volatile компилируется в ldr, orr, str при -O2? Или GCC может это свернуть? Я прочитал страницу GCC про volatile, там про составное присваивание сказано мало. Перед изменением обработчика проверю дизассемблированный код через objdump.

    Переведено с английскогоПоказать оригинал
  4. Ссылка на ответ

    Насколько я знаю, GCC это не свернёт. Для volatile-объекта x |= y всё равно должно прочитать x и записать x, и каждый доступ считается побочным эффектом. На Cortex-M0 нет инструкции OR с операндом в памяти, так что вы должны увидеть ldr, orrs, str (возможно, с дополнительным ldr для константы маски). На некоторых других архитектурах, например x86, компилятор может использовать одну инструкцию or над памятью. Это всё равно read-modify-write, так что проблема та же.

    Страница GCC расплывчата в этом месте, потому что стандарт C оставляет вопрос «что такое доступ» на усмотрение реализации. Так что я согласен, что проверка дизассемблированного кода — правильный метод. Что-то вроде arm-none-eabi-objdump -d на объектном файле, и посмотреть на обработчик.

    После того как вы перейдёте на TMR3_SR = TMR_SR_CC1IF;, я бы ещё посмотрел, генерирует ли компилятор по одному str на флаг. В простой записи нет чтения, так что окна не остаётся.

    Если после этого счёт всё равно будет занижен, следующим подозреваемым станет тот самый баг в кремнии, о котором вы говорили.

    Переведено с английскогоПоказать оригинал
  5. Ссылка на ответ

    Спасибо. Я не подумал, что or над памятью на x86 тоже остаётся read-modify-write. Там то же самое окно.

    Заменю все три на простые записи, проверю дизассемблированный код и прогоню 60-секундный тест. Если всё ещё будет занижено, дальше на очереди баг в кремнии.

    Переведено с английскогоПоказать оригинал

Другие стороны того же вопроса

  1. STM32H7 UART TX DMA отправляет старые байты при включённом D-cache

    STM32H7, arm-none-eabi-gcc. DMA TX кадра в 64 байта. С выключенным кешем всё нормально. С -DUSE_DCACHE анализатор показывает…

    Ответы 8Последняя активность c
  2. В колбэке из C пропадает арена, которую я настроил

    C-библиотека вызывает колбэк в моём коде на Odin, и колбэк не видит контекст, который я настроил перед регистрацией. У proc "…

    Ответы 5Последняя активность odinc
  3. Разворот C-указателя через .? - это утверждение

    .? на указателе из C - это утверждение, а не проверка. Оно говорит, что null здесь невозможен. В сборке с проверками безопасн…

    Ответы 3Последняя активность zig