Баг при замене if (); if() на if(); else;
- Войдите на сайт для отправки комментариев
Здравствуйте.
Разрабатываю систему электронной отметке для спортивного ориентирвоания с использованием Arduino (https://github.com/alexandervolikov/sportIDuino)
Наткнулся на непонятный мне баг.
Весь код прошивки можно посмотреть тут:
https://github.com/alexandervolikov/sportIDuino/blob/master/Stantion/Ard...
Здесь привожу кусок проблемного кода:
void loop ()
{
// clear various "reset" flags
MCUSR = 0;
// allow changes, disable reset
WDTCSR = bit (WDCE) | bit (WDE);
// set reset mode and an interval
// WDTCSR = bit (WDIE) | bit (WDP2) | bit (WDP1) ;
WDTCSR = bit (WDE) | bit (WDP2) | bit (WDP1); // set WDE, and 1 second delay
wdt_reset(); // pat the dog
rfid(); //производит запись чипа и меняет work на true в случае записи
if (work) sleep_light(); //уход в сон на 250 мс
if (!work) sleep_deep(); //уход в сон на 1000мс в случае бездействия более 6 часов.
} // end of loop
void sleep_deep(){
for (byte i = 0; i <= A5; i++)
{
pinMode(i,OUTPUT);
digitalWrite (i, LOW); // ditto
}
// disable ADC
ADCSRA = 0;
// clear various "reset" flags
MCUSR = 0;
delay(1);
// allow changes, disable reset
WDTCSR = bit (WDCE) | bit (WDE);
// set interrupt mode and an interval
WDTCSR = bit (WDIE) | bit (WDP2) | bit (WDP1) ; // set WDIE, and 1 second delay
wdt_reset(); // pat the dog
set_sleep_mode (SLEEP_MODE_PWR_DOWN);
noInterrupts (); // timed sequence follows
sleep_enable();
// turn off brown-out enable in software
MCUCR = bit (BODS) | bit (BODSE);
MCUCR = bit (BODS);
interrupts (); // guarantees next instruction executed
sleep_cpu ();
// cancel sleep as a precaution
sleep_disable();
}конструкции if, if на if else :
if (work) sleep_light(); //уход в сон на 250 мс else sleep_deep(); //уход в сон на 1000мс в случае бездействия более 6 часов.
Происходит некорректная работа программы, изначально в программе work==false, и она должна уходить в сон на 1 секунду, но вместо этого она уходит в сон на 8 секунд (те же 8 секунд, которые устанавливаются в начале loop при запуске watchdog, если там поменять на 4 секунды, будет уходить на 4). По каким-то причинам процедура срабатывает неправильно и не записывает новые биты в регистр watchdog.
c if, if работает нормально, сразу же уходит в сон на 1 секунду.
Если убрать watchdog из начала или убрать процедуру rfid(), то баг исчезает. Специально устанавливал библеотеку с проверкой объёма ОЗУ в разных частях программы, потребление не превышает 500 байт, памяти достаточно. Также баг исчезает если убрать процедуру NoInterrupts() из sleep_deep().
В принципе, "работает - не трогай", но такое поведение крайне странное и, возможно, есть какие-то уязвимости, которые могут повлиять на надежность всей системы. Поэтому буду весьма признателен, если подскажете откуда растут корни у этой проблемы.
Знакомый программист посоветовал протестировать с #pragma GCC optimize ("-O0")
В итоге программа отказалась работать вообще, просто резитится в начале loop.
Его соображения на этот счёт:
"
мне кажется там есть какая-то тонкость в сторожевом таймере, 4тактном ожидании и оптимизациях компилятора.
По умолчанию компилятор имеет опцию -Os, означающую оптимизацию размера бинарника. В общем случае бинарный код каждой функции зависит от кода всех остальных и изменение в одной части программы может изменить другую, логически даже не связанную. В частности это может привести к тому, что между инструкциями установки регистра может быть как больше так и меньше 4 тактов.
Поскольку sleep_light и sleep_deep отличаются друг от друга единственным битом, задающим интервал, можно предположить, что при оптимизации одной функции повезло, другой нет.
http://www.avrfreaks.net/forum/watchdog-enable
Задание прагмы
#pragma GCC optimize ("-O0")
заставляет компилятор не оптимизировать код идущий ниже, оставлять как есть. Если логика кода была бы не затронута компилятором, то и работа программы не изменилась бы. Работа изменилась, значит компилятор при оптимизации делает там что-то незаконное. Выключение оптимизации, на первый взгляд окончательно доламывает программу, но на самом деле, это переход бага из скрытой плавающей формы(когда он может появляться в любой момент при любом изменении исходного кода) в явную постоянную.
То, что у тебя сломались обе функции говорит о том, что дело в исходном коде. Однако оптимизация тоже что-то ломает и так получилось, что баг оптимизации в сочетании с багом в коде при определенной записи друг друга уничтожили. Как костыль это можно было бы и оставить, если бы оптимизация всегда происходила одинаково.
"
Не пробовали написать "как положено"?
if (work) {sleep_light();} //уход в сон на 250 мс else {sleep_deep();} //уход в сон на 1000мс в случае бездействия более 6 часов.Попробовал и со скобками, всё также, с двумя if работает с else вылазит баг.
Пробовал также и на разном железе, проблема остается.
извини, хрустальный шар разбился и не хочет показывать объявление переменных.
Не пробовали написать "как положено"?
if (work) {sleep_light();} //уход в сон на 250 мс else {sleep_deep();} //уход в сон на 1000мс в случае бездействия более 6 часов.А где то прописано о необходимости заключать в скобки после IF или ELSE один оператор, вызов функции или процедуры ?
извини, хрустальный шар разбился и не хочет показывать объявление переменных.
Тогда надо сходить по ссылке, которую ТС дал в начале.
https://github.com/alexandervolikov/sportIDuino/blob/master/Stantion/Arduino_Scetches/Stantion/Stantion.ino
Вообще, интересный казус.
а если попробывать так
Я понимаю, что проблемма где то глубже, но выяснить "симптомы" уже пол дела.
Предложенный вариант заработал.
Спасибо)) Теперь бы ещё понять почему))
Предположение: функция sleep_ligрt() меняет переменную work.
Предположение: функция sleep_ligрt() меняет переменную work.
нет, work меняется только в фунции rfid().
Прошу прощения. Наверное, стоило привести весь код, пусть он и длинный. К тому же не гитхабе он изменится при следующем коммите, а тут пусть будет в проблемном виде:
#include <avr/sleep.h> #include <avr/wdt.h> #include <Wire.h> #include "ds3231.h" #include <SPI.h> #include <MFRC522.h> #include <EEPROM.h> #include "Hamming.h" const byte LED = 4; const byte BUZ = 3; const byte VCC_C = 5; const byte RST_PIN = 9; const byte SS_PIN = 10; const byte worktime = 6; byte stantion; boolean work = false; struct ts t; byte temph; byte err = 0; MFRC522 mfrc522(SS_PIN, RST_PIN); // Create MFRC522 instance // watchdog interrupt ISR (WDT_vect) { wdt_disable(); // disable watchdog } // end of WDT_vect void setup () { pinMode(VCC_C,OUTPUT); digitalWrite(VCC_C,HIGH); delay(5000); DS3231_init(DS3231_INTCN); DS3231_get(&t); temph=t.hour; digitalWrite(VCC_C, LOW); flash(); flash(); /* резервирование EEPROM Номер станции записывается в ячейки 100, 101, 102 При считывании, если хотя бы две из трех ячеек равны, записывается их значение */ byte eep[3]; eep[0]=EEPROM.read(100); eep[1]=EEPROM.read(101); eep[2]=EEPROM.read(102); if (eep[0]==eep[1]){ stantion = eep[0]; } else if (eep[0]==eep[2]){ stantion = eep[0]; } else if (eep[1]==eep[2]){ stantion = eep[1]; } else error(1); //Ошибка: испортились ячейки памяти EEPROM //Если был резет по вотч-догу, ячейка EEPROM 202 увеличится на 1 if(MCUSR & (1<<WDRF)) error(3); } // led and buzzer signal void flash(){ pinMode (LED, OUTPUT); pinMode (BUZ, OUTPUT); digitalWrite (LED, HIGH); tone (BUZ,4000,100); delay (200); digitalWrite (LED, LOW); pinMode (LED, INPUT); pinMode (BUZ, INPUT); } void sounderr(){ pinMode (BUZ, OUTPUT); tone (BUZ,4000,50); delay (100); pinMode (BUZ, INPUT); } //запись ошибок в EEPROM void error(byte er){ switch (er){ case 1: //испортились ячейки памяти EEPROM { if ((EEPROM.read(200))!=1){ EEPROM.write(200,1); } sounderr(); sounderr(); sounderr(); } case 2: //Ошибка записи чипа { if ((EEPROM.read(201))<=254){ EEPROM.write(201,(EEPROM.read(201)+1)); } } case 3: //Ошибка перезагрузка по вотчдогу { if ((EEPROM.read(202))<=254){ EEPROM.write(202,(EEPROM.read(202)+1)); } } case 4: //Сбросились часы { if ((EEPROM.read(203))!=1){ EEPROM.write(203,1); } } } } void sleep_light(){ for (byte i = 0; i <= A5; i++) { pinMode(i,OUTPUT); digitalWrite (i, LOW); // ditto } // disable ADC ADCSRA = 0; // allow changes, disable reset WDTCSR = bit (WDCE) | bit (WDE); // set interrupt mode and an interval WDTCSR = bit (WDIE) | bit (WDP2); // set WDIE, and 0.25 second delay wdt_reset(); // pat the dog set_sleep_mode (SLEEP_MODE_PWR_DOWN); noInterrupts (); // timed sequence follows sleep_enable(); // turn off brown-out enable in software MCUCR = bit (BODS) | bit (BODSE); MCUCR = bit (BODS); interrupts (); // guarantees next instruction executed sleep_cpu (); // cancel sleep as a precaution sleep_disable(); } void sleep_deep(){ for (byte i = 0; i <= A5; i++) { pinMode(i,OUTPUT); digitalWrite (i, LOW); // ditto } // disable ADC ADCSRA = 0; // allow changes, disable reset WDTCSR = bit (WDCE) | bit (WDE); // set interrupt mode and an interval WDTCSR = bit (WDIE) | bit (WDP2) | bit (WDP1) ; // set WDIE, and 1 second delay wdt_reset(); // pat the dog set_sleep_mode (SLEEP_MODE_PWR_DOWN); noInterrupts (); // timed sequence follows sleep_enable(); // turn off brown-out enable in software MCUCR = bit (BODS) | bit (BODSE); MCUCR = bit (BODS); interrupts (); // guarantees next instruction executed sleep_cpu (); // cancel sleep as a precaution sleep_disable(); } void rfid(){ digitalWrite(VCC_C,HIGH); delay(10); DS3231_get(&t); digitalWrite(VCC_C,LOW); if ((t.hour >= temph && t.hour-temph >= worktime) || (t.hour < temph && temph-t.hour <= (24-worktime))) work = false; //Если время бездействия больше 6 часов, станция входит в режим ожидания if (t.year/1000!= 2){ error(4); //Ошибка: часы сбросились } SPI.begin(); // Init SPI bus mfrc522.PCD_Init(); // Init MFRC522 // Look for new cards if ( ! mfrc522.PICC_IsNewCardPresent()) { return; } // Select one of the cards if ( ! mfrc522.PICC_ReadCardSerial()) { return; } // Формируем дату в виде секунд с начала месяца uint32_t dat = t.sec + t.min*60 + t.hour*3600 + t.mday*86400; // Делим дату (до 22 бит) на две части по 11 бит uint16_t part1 = (dat & 0x7ff); uint16_t part2 = dat >> 11; // Кодируем дату в две 16 битовых переменных uint16_t code1; code1 = hamming_encode16(part1); uint16_t code2; code2 = hamming_encode16(part2); //Делим код на байты uint8_t part11 = (code1 & 0xff); uint8_t part12 = code1>>8; uint8_t part21 = (code2 & 0xff); uint8_t part22 = code2>>8; byte WBuff[] = {part22, part21, part12, part11}; //Write to Tag MFRC522::StatusCode status; status = mfrc522.MIFARE_Ultralight_Write(stantion, WBuff, 4); //Check tag status = mfrc522.MIFARE_Ultralight_CheckWrite(stantion, WBuff, 4); if (status == MFRC522::STATUS_OK) { flash(); } else { error(2); //Ошибка: незапись карточки } work = true; temph=t.hour; SPI.end(); } void loop () { // allow changes WDTCSR = bit (WDCE) | bit (WDE); // set reset mode and an interval WDTCSR = bit (WDE) | bit (WDP3) | bit (WDP0); // set WDE, and 8 second delay wdt_reset(); // pat the dog rfid(); if (work) { sleep_light(); //уход в сон на 250 мс } if (!work) { sleep_deep(); //уход в сон на 1000мс в случае бездействия более 6 часов. /* Если поменять "if (!work)" на "else", возникает баг - станция уходит в сон на 8 секунд а не на 1. */ } } // end of loopСамые удивительные вещи начинают происходить когда заканчивается память.
Тогда и перестановка двух букв будет похожа на волшебство -)
int freeRam () { extern int __heap_start, *__brkval; int v; return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); } ////// if (incomingByte == 'm') { Serial.println(freeRam()); }Память проверил. Тыкал функцию freeRam во всевомзожные места программы, всегда стабильно показывает 1565 (на Atmega328).
При компиляции пишет, что свободно 1591 байт.
Пока тыкал, обнаружил любопытную особенность. Если поставить delay(1); в любое место процедуры deep_sleep() между установкой битов вотчдога и концом процедуры, то баг исчезает.