Почему объявление глобальной переменной может изменить способ оценки несвязанного условного оператора?

Вот весь мой скетч. Это мой первый проект Arduino. Я включил все это, потому что не знаю, что важно. Я распечатываю на 4-значном 7-сегментном дисплее. Для этого я использую sprintf для форматирования содержимого char[], называемого tempString, и оно отправляется на дисплей. Я объявлял новый локальный char[] каждый раз, когда он мне был нужен. У меня возникла проблема с нехваткой памяти Arduino Pro Mini. Я попробовал объявить глобальную диаграмму[] и повторно использовать ее, когда она мне была нужна, но это изменило способ выполнения программы. Это было единственное изменение, которое я сделал.

После того, как я внес это изменение, Arduino всегда будет считать это правдой:

 if (countDownTimer.TimeCheck(0, 0, 0))

CountDownTimer всегда вычислял время, равное 0, даже после того, как ему было назначено допустимое время и таймер не начал работать. Я проверил, что установил правильно, но к тому времени, когда условие было достигнуто, таймер вернулся к 0.

Если я удалю глобальное объявление для tempString[] и вернусь к локальным значениям, логика будет работать так, как задумано. Я уменьшил tempString[] до локального значения, и это, похоже, решило проблему с памятью, но мне хотелось бы знать, почему объявление глобального значения вызвало проблему.

 #include <SoftwareSerial.h>
#include <CountUpDownTimer.h>
#include <EEPROM.h> //Необходимо для доступа к функциям чтения и записи EEPROM

//значения для EEPROM
int stepsToWrite = 0;
int stepsToRead = 0;
int priorValue = 0;

// адреса для записи и чтения
const int thirtySecondAddress = 0;  // постоянный
const int threeMinuteAddress = 4;   // постоянный
int addressToUse = 0;   // это будет меняться в зависимости от времени, выбранного пользователем

// устанавливаем последовательный дисплей
const int displayTx = 5;  // контакты, которые нам нужны для создания последовательного экземпляра программного обеспечения
const int displayRx = 7;   // мы будем использовать только Tx
SoftwareSerial s7s(displayRx, displayTx);

//настраиваем таймер
CountUpDownTimer countDownTimer(DOWN);
const long displaychangeInterval = 10000; // в миллисекундах
const long countdownInterval = 1000; // в миллисекундах

long previousMillis = 0; // для переключения между оставшимся временем и количеством шагов
long previousTimerMillis = 0;  // для обновления дисплея новым временем
const int thirtySecondTime = 30;
const int threeMinuteTime = 180; // 3 минуты, выраженные в секундах
int timerStartValue = 0;  // необходимо для некоторой управляющей логики

// логические значения для управления тем, что отображается
bool doDisplayStepCount = false;
bool doDisplayCountDown = false;
bool doDisplayHighScore = true;  // мы хотим отображать это при запуске

// настраиваем кнопки
const int timerbuttonPin = 6;     // номер контакта кнопки таймера
const int resetbuttonPin = 4;     // номер контакта кнопки сброса

int timerbuttonPushCounter = 0;   // счетчик количества нажатий кнопок
int timerbuttonState = 0;         // текущее состояние кнопки
int timerlastButtonState = 0;     // предыдущее состояние кнопки

int resetbuttonPushCounter = 0;   // счетчик количества нажатий кнопок
int resetbuttonState = 0;         // текущее состояние кнопки
int resetlastButtonState = 0;     // предыдущее состояние кнопки

int stepsTaken = 0; // количество шагов (прыжков)
boolean timerStarted = false;

// для силового резистора
const int FSR_PIN = A0; // Вывод подключен к FSR/резисторному делителю
const float VCC = 5.10; // Измеренное напряжение линии Ardunio 5В
const float R_DIV = 3265.0; // Измеренное сопротивление резистора 3,3 кОм

double currentForce = 0;
double previousForce = 0;
boolean previousPos = false;
boolean currentPos = false;

boolean doChangeDisplay = false;

char tempString[4];
void setup() {

  boolean debug = true;
  if (debug)
  {
    EEPROMWriteInt(thirtySecondAddress, 0);  // обнуляем EEPROM
    EEPROMWriteInt(threeMinuteAddress, 0);  // обнуляем EEPROM
  }
  // запускаем последовательное соединение
  Serial.begin(9600);

  // устанавливаем пины
  pinMode(FSR_PIN, INPUT);
  pinMode(timerbuttonPin, INPUT);
  pinMode(resetbuttonPin, INPUT);

  // последовательный дисплей
  // Должен начаться последовательный порт программного обеспечения s7s с правильной скоростью передачи данных.

  // Очистка дисплея
  // Значение по умолчанию для s7s — 9600.
  s7s.begin(9600);
  //очиститьдисплей(); // Очищаем дисплей, сбрасываем курсор
  // s7s.print("-HI-");
  // задержка(1000);

  displayHighScores();

}

void loop() {

  // Serial.print("freeMemory()=");
  // Serial.println(freeMemory());

  // Serial.println("Начальный цикл");
  // нажатия кнопок
  // еслиtinmerbuttonPushCounter = 1, установите timerValue равным 30 секундам
  // если 2 нажатия, устанавливаем 3 минуты и сбрасываем счетчик нажатий на 0

  //Serial.println("Чтение кнопки таймера, состояние: " + timerbuttonState);
  timerbuttonState = digitalRead(timerbuttonPin);
  // Serial.println("Чтение кнопки сброса, состояние: " +resetbuttonState);
  resetbuttonState = digitalRead(resetbuttonPin);

  // сравниваем состояние кнопки с предыдущим состоянием

  if (timerbuttonState != timerlastButtonState)
  {
    Serial.println("selecting time");
    if (timerbuttonState == HIGH)
    {

      doDisplayHighScore = false;
      timerbuttonPushCounter++;
      // если текущее состояние ВЫСОКОЕ, то кнопка таймера переключилась из выключенного состояния во включенное:
      if (timerbuttonPushCounter == 1)
      { 
        doChangeDisplay = false;
        doDisplayCountDown = true;
        timerStartValue = thirtySecondTime;
        addressToUse = thirtySecondAddress;
        Serial.println("timer button pushed: " + String(timerbuttonPushCounter));
        Serial.println("start value: " + String(timerStartValue));
        clearDisplay();
        setDecimals(0b010000);  // Включаем двоеточие
        sprintf(tempString, "%04d", thirtySecondTime);
        Serial.println("Timer set to: " + String(tempString));
        s7s.print(tempString);
      }

      else if (timerbuttonPushCounter == 2)
      {
        timerStartValue = threeMinuteTime;
        addressToUse = threeMinuteAddress;
        doChangeDisplay = true;

        int timerinminutes = timerStartValue / 60;
        Serial.println("timer button pushed: " + timerbuttonPushCounter);
        Serial.println("start value  " + timerStartValue);
        timerbuttonPushCounter = 0;
        clearDisplay();
        setDecimals(0b010000);  // Включаем двоеточие
        sprintf(tempString, "0%d00", timerinminutes);
        Serial.println("Timer set to: " + String(tempString));
        s7s.print(tempString);
      }
    }  // конец, если таймер находится в высоком состоянии

    // Небольшая задержка, чтобы избежать подпрыгивания
    delay(50);
  }
  // сохраняем текущее состояние как последнее для следующего прохода цикла
  timerlastButtonState = timerbuttonState;

  // для кнопки сброса логика намного проще
  // если состояние высокое, кнопка была нажата, поэтому все переустанавливаем
  if (resetbuttonState != resetlastButtonState)
  {
    if (resetbuttonState == HIGH)
    {
      Serial.println("Reset button pushed!");
      doDisplayHighScore = true;
      resetStepCounter();
      // Небольшая задержка, чтобы избежать подпрыгивания
      delay(50);
    }
  }

  resetlastButtonState = resetbuttonState;

  // логика пошаговой панели
  int fsrADC = analogRead(FSR_PIN);
  previousForce = currentForce;
  currentForce = calculateForce(fsrADC);
  // пробуем это, используя принудительные значения
  // когда мы делаем шаг, сила всегда положительна
  // когда мы поднимаем ногу, сила всегда отрицательна

  if (currentForce > 0)
  {
    currentPos = true;
    Serial.println("Force detected!");
    delay(1000);
  }
  if (previousForce < 0)
  {
    previousPos = true;
  }

  if (currentPos && previousPos)
  {
    // запускаем таймер на первом шаге
    if (!timerStarted)
    {
      Serial.println("First steop!");
      timerStarted = true;
      // Serial.println("Начальное значение таймера: " +String(timerStartValue));
      countDownTimer.SetTimer(0, 0, timerStartValue);
      countDownTimer.StartTimer();

      // символ tempstring[10];
      sprintf(tempString, "%4d", countDownTimer.ShowMinutes(), countDownTimer.ShowSeconds());
      Serial.println("Timer set to: " + String(tempString));
      // Serial.println("Таймер запущен");
      // задержка(2000);
      doDisplayCountDown = true;
    }

    stepsTaken++;
    Serial.println("Step Detected!");
    currentPos = false;
    previousPos = false;
  }

  // если таймер равен 0, у нас закончилось время
  // это нужно сделать здесь, так как это нужно делать всегда и это не может зависеть
  // если displayCountDown истинен


  if (timerStarted)
  {
    if (countDownTimer.TimeCheck(0, 0, 0))
    {
      //Serial.println("Время истекло");
      //задержка(500);
      timerStarted = false;  // устанавливаем timerRunning в значение false
      countDownTimer.StopTimer();
      if (timerStartValue == thirtySecondTime)
      {
        WriteHighScore(thirtySecondAddress, stepsTaken);
      }

      if (timerStartValue == threeMinuteTime)
      {
        WriteHighScore(threeMinuteAddress, stepsTaken);
      }
      Serial.println("wrote high score");
      delay(500);
      // ОчиститьДисплей();
      //displayStepCount();
      // сбросStepCounter();

      // displayCountDownTimer();
    }

    //еще


    if (!(countDownTimer.TimeCheck(0, 0, 0))) // && таймер запущен)
    {

      countDownTimer.Timer(); // запускаем таймер
      Serial.println("Timer is running");
      // логика таймера
      // каждые 10 секунд меняем логические значения того, что отображается
      unsigned long currentMillis = millis();
      // последние 10 секунд отображают только количество шагов
      //если (countDownTimer.TimeCheck(0, 0, 10))
      // {
      //
      // displayStepCount();
      // }

      // еще
      // {

      if (doChangeDisplay)
      {
        if ((currentMillis - previousMillis) >= displaychangeInterval)
        {
          // сохраняем последний раз, когда мы проверяли интервал
          // Serial.println("Интервал 10 секунд!");
          previousMillis = currentMillis;

          // если это правда, установите значение false
          // устанавливаем для отображения обратного отсчета значение true
          if (doDisplayStepCount)
          {
            doDisplayStepCount = false;
            doDisplayCountDown = true;
          }

          else
            // отображение обратного отсчета верно, поэтому установите для него значение false
            // и устанавливаем для отображения количества шагов значение true
          {
            doDisplayStepCount = true;
            doDisplayCountDown = false;
          }

        }
      }

      // отображаем количество шагов только в том случае, если это интервал для этого
      if (doDisplayStepCount)
      {
        displayStepCount();
      }

      // отображать таймер обратного отсчета только в том случае, если это 10-секундный интервал для этого
      if (doDisplayCountDown)
      {
        displayCountDownTimer();
      }

      // }


    }
  }



}



// Отправляем команду очистки дисплея (0x76)
// Это очистит дисплей и сбросит курсор
void clearDisplay()
{
  s7s.write(0x76);  // Команда очистки дисплея
}


// Включите любую десятичную дробь, ее отсутствие или все десятичные дроби.
// Шесть младших битов в параметре decimals задают десятичное число
// (или двоеточие, или апостроф) вкл. или выкл. 1 означает включение, 0 — выключение.
// [MSB] (X)(X)(Apos)(Двоеточие)(Цифра 4)(Цифра 3)(Цифра2)(Цифра1)
// (0b010000) - только двоеточие
// (0b111111) - все десятичные дроби
void setDecimals(byte decimals)
{
  s7s.write(0x77);
  s7s.write(decimals);
}

void resetStepCounter()
{
  timerbuttonPushCounter = 0;
  if (timerStarted)
  {
    countDownTimer.StopTimer();
    timerStarted = false;
  }

  // очистка
  stepsTaken = 0;
  clearDisplay();
  timerStartValue = 0;
  doDisplayHighScore = true;
}

void displayStepCount()
{
  setDecimals(0b000000);  // Отключаем двоеточие
  // Будет использоваться со sprintf для создания строк
  sprintf(tempString, "%04d", stepsTaken);
  Serial.print("Steps taken: " + String(tempString));
  clearDisplay();
  s7s.print(tempString);
}

void displayCountDownTimer()
{
  // примеры форматов
  //sprintf(tempString, "%4d", deciSecond); //Преобразуем deciSecond в строку, настроенную правильно
  //sprintf(tempString, "%d", deciSecond); //Преобразуем deciSecond в строку с оставленной поправкой
  //sprintf(tempString, "%04d", deciSecond); //Преобразуем deciSecond в строку с ведущими нулями
  //sprintf(tempString, "%4d", deciSecond * -1); //Показывает отрицательный знак перед скорректированным вправо числом
  unsigned long currentMillis = millis();
  int seconds = 0;
  int minutes = 0;

  // обновляем отображение только по прошествии 1 секунды
  // if(currentMillis - previousTimerMillis >= countdownInterval)
  // CountUpDownTimer TimeHasChanged() возвращает логическое значение (в секундах)

  if (countDownTimer.TimeHasChanged())
  {
    // предыдущийTimerMillis = currentMillis;
    seconds = countDownTimer.ShowSeconds();
    Serial.println("Time left: " + seconds);
    minutes = countDownTimer.ShowMinutes();
    sprintf(tempString, "%02d%02d", minutes, seconds);

    clearDisplay();
    setDecimals(0b010000);  // Включаем двоеточие
    Serial.println(tempString);
    s7s.print(tempString);
  }
}
void EEPROMWriteInt(const int p_address, const int p_value)
{
  byte lowByte = ((p_value >> 0) & 0xFF);
  byte highByte = ((p_value >> 8) & 0xFF);

  EEPROM.write(p_address, lowByte);
  EEPROM.write(p_address + 1, highByte);
}

//Эта функция прочитает 2-байтовое целое число из EEPROM по указанному адресу и адресу + 1
int EEPROMReadInt(const int p_address)
{
  byte lowByte = EEPROM.read(p_address);
  byte highByte = EEPROM.read(p_address + 1);

  return ((lowByte << 0) & 0xFF) + ((highByte << 8) & 0xFF00);
}

void WriteHighScore(int address, int value)
{
  // int valueRead = EEPROMReadInt(адрес);
  //если (значениеRead < значение)
  // {
  EEPROMWriteInt(address, value);

  delay(500);
  sprintf(tempString, "%04d", value);
  Serial.print("high score: " + String(tempString));
  clearDisplay();
  s7s.print("-HI-");
  delay(500);
  clearDisplay();
  s7s.print("SCOR");
  delay(500);
  clearDisplay();
  s7s.print(tempString);
  delay(500);

  // }
}
void displayHighScores()
{
  int thirtySecondScore = EEPROMReadInt(thirtySecondAddress);
  int threeMinuteScore = EEPROMReadInt(threeMinuteAddress);
  sprintf(tempString, "%04d", thirtySecondScore);
  Serial.println("thirty second score: " + String(tempString));
  sprintf(tempString, "%04d", threeMinuteScore);
  Serial.println("Three minute score: " + String(tempString));

  clearDisplay();  // Очищаем дисплей, сбрасываем курсор
  s7s.println("-HI-");
  delay(1000);
  clearDisplay();
  Serial.println("SCOR");
  s7s.print("SCOR");
  delay(1000);
  clearDisplay();
  sprintf(tempString, "%04d", thirtySecondScore);
  Serial.println(tempString);
  s7s.print(tempString);
  delay(1000);

  clearDisplay();
  sprintf(tempString, "%04d", threeMinuteScore);
  Serial.println(tempString);
  s7s.print(tempString);
  delay(1000);
  clearDisplay();
}

float calculateForce(int pinvalue)
{
  // Используем показания АЦП для расчета напряжения:
  float fsrV = pinvalue * VCC / 1023.0; // 1023 — количество единиц напряжения от 0 до 5 В.
  // Используем напряжение и значение статического резистора для
  // вычисляем сопротивление FSR:
  float fsrR = R_DIV * (VCC / fsrV - 1.0);
  // Serial.println("Сопротивление: " + String(fsrR) + " Ом");
  // Предполагаемая сила на основе наклонов на рисунке 3
  // Спецификация FSR:
  float force;
  float fsrG = 1.0 / fsrR; // Вычисляем проводимость
  // Разбиваем параболическую кривую на два линейных наклона:
  force = (fsrG - 0.00075) / 0.00000032639;
  return force;
}

, 👍0

Обсуждение

добавьте позицию для завершающего нуля в tempString. tempString[5]. теперь 0 записывается sprintf в память после массива, @Juraj


1 ответ


Лучший ответ:

3

Эта строка:

sprintf(tempString, "%04d", thirtySecondTime);

записывает в tempString 5 символов, четыре цифры и завершающий ноль. Вы дали место только на четверых.

Это:

Serial.println("Timer set to: " + String(tempString));

— это пустая трата ресурсов. Если вы хотите поговорить об использовании памяти, то не используйте класс String и уж точно не используйте его там, где он просто не нужен.

Запишите это как:

Serial.print("Timer set to: ");
Serial.println(tempString);

или еще лучше с точки зрения памяти:

Serial.print(F("Timer set to: "));
Serial.println(tempString);
,

Спасибо. Я читал об использовании памяти String() и изменю это. Большая часть последовательного вывода будет удалена после того, как я закончу всю отладку. Из комментария @Juraj я провел небольшое исследование и теперь понимаю прекращение действия., @David Green