Найдите проблему в embedded‑фрагменте на Arduino: volatile int flag=0; ISR(){ flag=1; } loop(){ while(!flag); doWork(); flag=0; } — какие риски зависания или пропуска событий тут есть и как правильно синхронизировать прерывания

19 Сен в 04:12
1 +1
0
Ответы
1
Коротко — какие проблемы и как правильно.
Проблемы
- Неатомарный доступ: на AVR тип `int` обычно имеет ширину 161616 бит, доступ к нему занимает две инструкции. Чтение/запись из main может прерваться ISR и получится «рваное» значение (torn read).
- Потеря событий: флаг бинарный — ISR ставит `flag = 111`. Если во время `doWork()` пришло новое событие, ISR снова поставит `flag = 111` (ничего не изменится), а затем main делает `flag = 000` и событие теряется. Таким образом можно пропустить несколько подряд событий.
- Busy‑wait: `while(!flag);` — неоптимально, тратит CPU; это не ошибка синхронизации, но нежелательно для энергосбережения/планирования.
Почему `volatile` не решает всё: `volatile` предотвращает оптимизации, но не делает операции атомарными и не защищает от гонок.
Как правильно синхронизировать
1) Для простого сигнала (и только если тип размером 888 бит):
- Используйте 8‑битный тип (`uint8_t`) чтобы операции могли быть атомарными на AVR, и защищайте чтение/изменение критической области. Лучше явно блокировать прерывания только на короткое время:
#include volatile uint8_t evt = 000;
ISR(...){
evt++; // счётчик событий
}
void loop(){
// дождаться события, извлечь один счётчик атомарно
uint8_t have;
for(;;){
ATOMIC_BLOCK(ATOMIC_RESTORESTATE){
if(evt > 000){ evt--; have = 111; break; }
}
// можно sleep или yield вместо активного ожидания
}
if(have) doWork();
}
Плюс: счётчик позволяет не терять несколько событий подряд.
2) Если нужен булев флаг, избегайте потери события — блокировать прерывания при очистке:
volatile uint8_t flag = 000;
ISR(...){ flag = 111; }
void loop(){
while(1){
// ждать
while(flag == 000);
// кратко отключить прерывания, сбросить флаг безопасно
uint8_t tmp;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE){
tmp = flag;
flag = 000;
}
if(tmp) doWork();
}
}
Но это всё ещё теряет дополнительные события, если вам нужно учитывать каждое — используйте счётчик.
3) Для многобайтных переменных (например `int` 161616-бит) — всегда защищайте доступ (ATOMIC_BLOCK или cli()/sei()).
Резюме (рекомендации)
- Не использовать `int` для межконтекстной синхронизации на AVR — используйте `uint8_t` или явно защищайте доступ для многобайтовых типов. (числа: 888, 161616).
- Для учёта нескольких событий применяйте счётчик в ISR (`evt++`) и атомарно уменьшайте/читайте в main.
- Используйте (ATOMIC_BLOCK) или краткие cli()/sei() участки при доступе к общим данным.
- Избегайте долгого busy‑wait; лучше sleep/yield/посткороткий ожидание.
19 Сен в 04:18
Не можешь разобраться в этой теме?
Обратись за помощью к экспертам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 2 часов
Проверка работы на плагиат
Поможем написать учебную работу
Прямой эфир