Баг при замене if (); if() на if(); else;

alexandervolikov
Offline
Зарегистрирован: 26.02.2017

Здравствуйте.

Разрабатываю систему электронной отметке для спортивного ориентирвоания  с использованием 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().

В принципе, "работает - не трогай", но такое поведение крайне странное и, возможно, есть какие-то уязвимости, которые могут повлиять на надежность всей системы. Поэтому буду весьма признателен, если подскажете откуда растут корни у этой проблемы.

 

alexandervolikov
Offline
Зарегистрирован: 26.02.2017

Знакомый программист посоветовал протестировать с #pragma GCC optimize ("-O0")

В итоге программа отказалась работать вообще, просто резитится в начале loop.

Его соображения на этот счёт:

"

мне кажется там есть какая-то тонкость в сторожевом таймере, 4тактном ожидании и оптимизациях компилятора. 

По умолчанию компилятор имеет опцию -Os, означающую оптимизацию размера бинарника. В общем случае бинарный код каждой функции зависит от кода всех остальных и изменение в одной части программы может изменить другую, логически даже не связанную. В частности это может привести к тому, что между инструкциями установки регистра может быть как больше так и меньше 4 тактов.

Поскольку sleep_light и sleep_deep отличаются друг от друга единственным битом, задающим интервал, можно предположить, что при оптимизации одной функции повезло, другой нет. 



http://www.avrfreaks.net/forum/watchdog-enable



Задание прагмы 

#pragma GCC optimize ("-O0")

заставляет компилятор не оптимизировать код идущий ниже, оставлять как есть. Если логика кода была бы не затронута компилятором, то и работа программы не изменилась бы. Работа изменилась, значит компилятор при оптимизации делает там что-то незаконное. Выключение оптимизации, на первый взгляд окончательно доламывает программу, но на самом деле, это переход бага из скрытой плавающей формы(когда он может появляться в любой момент при любом изменении исходного кода) в явную постоянную.

То, что у тебя сломались обе функции говорит о том, что дело в исходном коде. Однако оптимизация тоже что-то ломает и так получилось, что баг оптимизации в сочетании с багом в коде при определенной записи друг друга уничтожили. Как костыль это можно было бы и оставить, если бы оптимизация всегда происходила одинаково.

"

nevkon
Offline
Зарегистрирован: 20.01.2015

Не пробовали написать "как положено"?


if (work) {sleep_light();} //уход в сон на 250 мс
else {sleep_deep();} //уход в сон на 1000мс в случае бездействия более 6 часов.

 

alexandervolikov
Offline
Зарегистрирован: 26.02.2017

Попробовал и со скобками, всё также, с двумя if работает с else вылазит баг.

Пробовал также и на разном железе, проблема остается.

nevkon
Offline
Зарегистрирован: 20.01.2015

извини, хрустальный шар разбился и не хочет показывать объявление переменных.

xDriver
xDriver аватар
Offline
Зарегистрирован: 14.08.2015

nevkon пишет:

Не пробовали написать "как положено"?

if (work) {sleep_light();} //уход в сон на 250 мс
else {sleep_deep();} //уход в сон на 1000мс в случае бездействия более 6 часов.

А где то прописано о необходимости заключать в скобки после IF или ELSE один оператор, вызов функции или процедуры ?

nevkon пишет:

извини, хрустальный шар разбился и не хочет показывать объявление переменных.

Тогда надо сходить по ссылке, которую ТС дал в начале.

https://github.com/alexandervolikov/sportIDuino/blob/master/Stantion/Arduino_Scetches/Stantion/Stantion.ino

 

Вообще, интересный казус.

а если попробывать так

if (!work) sleep_deep(); //уход в сон на 1000мс в случае бездействия более 6 часов.
else sleep_light(); //уход в сон на 250 мс

Я понимаю, что проблемма где то глубже, но выяснить "симптомы"  уже пол дела.

 

alexandervolikov
Offline
Зарегистрирован: 26.02.2017

Предложенный вариант заработал.

Спасибо)) Теперь бы ещё понять почему))

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Предположение: функция sleep_ligрt() меняет переменную work.

xDriver
xDriver аватар
Offline
Зарегистрирован: 14.08.2015

andriano пишет:

Предположение: функция sleep_ligрt() меняет переменную work.

нет, work меняется только в фунции rfid().

alexandervolikov
Offline
Зарегистрирован: 26.02.2017

Прошу прощения. Наверное, стоило привести весь код, пусть он и длинный. К тому же не гитхабе он изменится при следующем коммите, а тут пусть будет в проблемном виде:

#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

 

Волшебник
Offline
Зарегистрирован: 22.12.2016

 Самые удивительные вещи начинают происходить когда заканчивается память.

Тогда и перестановка двух букв будет похожа на волшебство -)

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());
     }

 

alexandervolikov
Offline
Зарегистрирован: 26.02.2017

Память проверил. Тыкал функцию freeRam во всевомзожные места программы, всегда стабильно показывает 1565 (на Atmega328).

При компиляции пишет, что свободно 1591 байт.

Пока тыкал, обнаружил любопытную особенность. Если поставить delay(1); в любое место процедуры deep_sleep() между установкой битов вотчдога и концом процедуры, то баг исчезает.