Arduino Uno зависает по неизвестной причине во время выполнения кода

Я борюсь с поведением своего Arduino, которое я не до конца понимаю.

Короче говоря, длинная история:

При запуске связи по I2C Arduino зависает. На мой взгляд, во время команды "Wire.write ()".

Длинная история и предыстория:

Я хочу подключить свой Arduino Uno к Wi-Fi 1010 Arduino MKR через I2C. Я реализовал код для связи шаг за шагом, поэтому начал с приведенного ниже кода.

Код для главного Arduino Uno:

#include <Wire.h>
#include <Arduino.h>

#define SLAVE_ADRESS                            2

void Send_Data(int receiver, int data)
{
    Wire.beginTransmission(receiver);
    Wire.write(data);          
    Wire.endTransmission();
    Serial.println(F("Data sent"));
}

void setup() {
  Serial.begin(38400);
  while (!Serial) {;} /*  Подождите, пока последовательный интерфейс будет подключен и готов к использованию. */
  Serial.setTimeout(2000);
  Serial.println(F("Готово к последовательному подключению"));
  Wire.begin(); // присоединиться к шине i2c (адрес необязательно для master)
}

void loop() {
  static int LedStatus = 0;
  if (LedStatus == 0)
  {
      LedStatus = 1;
  }
  else
  {
      LedStatus = 0;
  }
  Serial.print(F("Master LED status: "));
  Serial.println(LedStatus);         
  Send_Data(SLAVE_ADRESS,LedStatus);
  delay(500);
}n

Код для ведомого Arduino MKR Wifi 1010:


#include <Wire.h>
#include <Arduino.h>

#define NODE_ADDRESS 2

int led = 0;

void setup() 
{
  Serial.begin(38400);
  while (!Serial)
  {
        ; /* Дождитесь подключения последовательного интерфейса и его готовности к использованию. */
  }
  Serial.setTimeout(1000);
  
  Wire.begin(NODE_ADDRESS);
  Wire.onReceive(receiveEvent);
}

void receiveEvent(int bytes)
{
  led = Wire.read();              
  Serial.println("Data reveived!");
  Serial.print("Slave Value led: "); 
  Serial.println(led);

}

void loop() 
{

}

Этот фрагмент кода работает нормально, как и ожидалось. Поэтому я внедрил ту же функциональность для мастера в свой проект.

/****************************************************************************/
/*-------------------------------Description--------------------------------*/
/****************************************************************************/

/*
Used board: Arduino Uno

>>> Pin usage overview <<<
0:
1:
2: waterlevel tank sensor low (black sensor wires)
3: waterlevel tank sensor high (red sensor wires)
4: waterlevel reservoir sensor low (not used yet)
5: waterlevel reservoir sensor high (not used yet)
6: pump level tank
7: pump level reservoir
8: water temperature sensor
9: water temperature sensor
10: 
11:
12:
13:
A0: EC sensor
A1: pH sensor
A2:
A3:
A4:
A5:
/*

/****************************************************************************/
/*---------------------------------Includes---------------------------------*/
/****************************************************************************/

#include <Arduino.h>
#include <RTClib.h>
#include <TimerOne.h>
#include <Wire.h>
#include "WaterCondition.h"
#include "WaterFlow.h"
#include "Serial.h"

/****************************************************************************/
/*----------------------------------Macros----------------------------------*/
/****************************************************************************/

#define TIMER_INTERVAL                          2000000 // 5sec (timer in microsec)

#define RTC_INIT_TIME_SECOND                    0
#define RTC_INIT_TIME_MINUTE                    0
#define RTC_INIT_TIME_HOUR                      0
#define RTC_INIT_TIME_DAY                       0
#define RTC_INIT_TIME_MONTH                     0
#define RTC_INIT_TIME_YEAR                      0

#define OPERATING_MODE_AUTO                     0
#define OPERATING_MODE_TEST_TEMP_SENSOR         1
#define OPERATING_MODE_TEST_EC_SENSOR           2
#define OPERATING_MODE_TEST_PH_SENSOR           3
#define OPERATING_MODE_TEST_RTC                 4
#define OPERATING_MODE_TEST_WATERLEVEL          5
#define OPERATING_MODE_TEST_COMMUNICATION       6

#define SLAVE_ADRESS                            2 // adress of the slave for communication

/****************************************************************************/
/*-----------------------------Global Variables-----------------------------*/
/****************************************************************************/

/* 
OPERATING_MODE 0: Automatic
OPERATING_MODE 1: Testing / Calibration
*/
uint8_t OperatingMode = 0;

/* Used for Serial.h to read the input from serial connection */
extern char StringBuffer[STRING_BUFFER_SIZE];
extern float WaterConPhVolt, WaterConEcVolt, WaterConPhVal, WaterConEcVal, WaterConTempVal;

/****************************************************************************/
/*------------------------------Initialization------------------------------*/
/****************************************************************************/

RTC_DS3231 rtc;
DateTime now;


int freeRam() 
{
    extern int __heap_start, *__brkval; 
    int v; 
    return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}


/* standard operations */
void ISR_Task(void) 
{
    if (OperatingMode == OPERATING_MODE_AUTO) // Automatic
    {
        Serial.println(F("Running in automatic mode"));

        uint8_t min;
        min = now.second(),DEC;
        if ((min == PUMP_TANK_ON_INTERVALL_1) || (min == PUMP_TANK_ON_INTERVALL_2))
        {
            //Serial.println(F("Test: RTC min = 0 or 30");
            //Activate pump
            //Function tbd
        }
        else if ((min == PUMP_TANK_OFF_INTERVALL_1) || (min == PUMP_TANK_OFF_INTERVALL_2))
        {
            //Serial.println(F("Test: RTC min = 20 or 50");
            //Deactivate pump
            //Function tbd
        }
        WaterConTempVal = WaterCon_Sensor_TEMP(0);
        WaterConEcVal = WaterCon_Sensor_EC();
        WaterConPhVal = WaterCon_Sensor_PH();
        if (Waterflow_Tank() == 99)
        { 
            // tbd
        }
        
    }
    else // Testing
    {
        /* >>> Test the temperature sensor <<< */
        if (OperatingMode == OPERATING_MODE_TEST_TEMP_SENSOR)
        {
            Serial.print(F("Water temperature filtered: "));
            Serial.println(WaterCon_Sensor_TEMP(0));
        }

        /* >>> Test the PH sensor <<< */
        else if (OperatingMode == OPERATING_MODE_TEST_PH_SENSOR)
        {
            float test_ph = 0;
            test_ph = WaterCon_Analog_Read(PH_PIN, VOLTAGE_PIN, RESOLUTION_SENSOR_PH);
            Serial.print(F("PH-Sensor-Value raw from 0 - 5000mV: "));
            Serial.println(test_ph);
            Serial.print(F("PH-Value from 0 - 14: "));
            Serial.println(WaterCon_Sensor_PH());
        }

        /* >>> Test the PH sensor <<< */
        else if (OperatingMode == OPERATING_MODE_TEST_EC_SENSOR)
        {
            float test_ec = 0;
            test_ec = WaterCon_Analog_Read(EC_PIN, VOLTAGE_PIN, RESOLUTION_SENSOR_EC);
            Serial.print(F("EC-Sensor-Value raw from 0 - 5000mV: "));
            Serial.println(test_ec);
            Serial.print(F("EC-Value from 0 - 2000uS: "));
            Serial.println(WaterCon_Sensor_EC());
        }

        /* >>> Test the RTC clock <<< */
        else if (OperatingMode == OPERATING_MODE_TEST_RTC)
        {
            Serial.print(F("\nRTC-Time: "));
            Serial.println(now.second(),DEC);
        }

        /* >>> Test the waterlevel sensors <<< */
        else if (OperatingMode == OPERATING_MODE_TEST_WATERLEVEL)
        {
            int WaterLvlSensorLow = 0;
            int WaterLvlSensorHigh = 0;
            WaterLvlSensorLow = digitalRead(TANK_LVL_LOW_PIN);
            WaterLvlSensorHigh = digitalRead(TANK_LVL_HIGH_PIN);
            
            Serial.print(F("Waterlevel Sensor Low: "));
            Serial.println(WaterLvlSensorLow);
            Serial.print(F("Waterlevel Sensor High: "));
            Serial.println(WaterLvlSensorHigh);
        }

        /* >>> Test the communication over I2C <<< */
        else if (OperatingMode == OPERATING_MODE_TEST_COMMUNICATION)
        {
            static int LedStatus = 0;
            if (LedStatus == 0)
            {
                LedStatus = 1;
            }
            else
            {
                LedStatus = 0;
            }                        
            Serial.print(F("Master LED status: "));
            Serial.println(LedStatus);
            Send_Data(SLAVE_ADRESS,LedStatus);
        }
    }
}

void Send_Data(int receiver, int data)
{
    Wire.beginTransmission(receiver);
    Wire.write(data);        
    Wire.endTransmission();
    Serial.println(F("Data sent"));
}

/****************************************************************************/
/*-------------------------Function Implementations-------------------------*/
/****************************************************************************/

void setup() {
    Serial.begin(38400);
    while (!Serial) {;} /* Wait for serial interface to be connected and ready for use. */
    Serial.setTimeout(2000);
    Serial.println(F("Serial Connection ready"));
    

    /* Initialize sensors */
    Serial.println(F("Setup Sensors"));
    WaterCon_Sensor_Init();
    Waterflow_Sensor_Init();

    /* Initialize RTC */
    Serial.println(F("Setup RTC"));
    rtc.begin();
    Serial.println(F("Test"));

    /* Initialize timers */
    Serial.println(F("Setup Timers"));
    Timer1.initialize(TIMER_INTERVAL);
    Timer1.attachInterrupt(ISR_Task);
    Serial.println(F("Setting the time for the RTC..."));
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));


    /* Initialize I2C Master-Slave */
    Wire.begin();
}

void loop()
{
    now = rtc.now();

    delay(200);
    Serial_Read(1);
    /* Change operating mode to automatic */
    if (strcmp("opauto",StringBuffer) == 0)
    {
        Serial.println(F("Operating Mode changed to automatic"));
        OperatingMode = 0;
    }
    /* Change operating mode to testing */
    else if (strcmp("optest1",StringBuffer) == 0)
    {
        Serial.println(F("Operating Mode changed to Testing: temperature sensor"));
        OperatingMode = 1;
    }
    else if (strcmp("optest2",StringBuffer) == 0)
    {
        Serial.println(F("Operating Mode changed to Testing: EC sensor"));
        OperatingMode = 2;
    }    
    else if (strcmp("optest3",StringBuffer) == 0)
    {
        Serial.println(F("Operating Mode changed to Testing: pH sensor"));
        OperatingMode = 3;
    }
    else if (strcmp("optest4",StringBuffer) == 0)
    {
        Serial.println(F("Operating Mode changed to Testing: RTC clock"));
        OperatingMode = 4;
    }
    else if (strcmp("optest5",StringBuffer) == 0)
    {
        Serial.println(F("Operating Mode changed to Testing: Waterlevel Sensors"));
        OperatingMode = 5;
    }
    else if (strcmp("optest6",StringBuffer) == 0)
    {
        Serial.println(F("Operating Mode changed to Testing: Communication via I2C Master-Slave"));
        OperatingMode = 6;
    }
}

Некоторое объяснение, чтобы вы поняли, что я пытаюсь сделать: Выше вы можете увидеть код из файла Main.ino для гидропонного проекта. Я хотел реализовать различные режимы работы для автоматического и тестирования. Поэтому, если я столкнусь с неисправностью, я хочу иметь возможность подключить ноутбук к USB-порту и начать считывать некоторую информацию через последовательный порт. Позже это должно быть сделано по воздуху, но для начала это должно быть нормально. Чтобы добиться этого, я ввел сравнение строк в цикл, чтобы заметить, сделал ли пользователь какой-то ввод и ответил на него. Я много тестировал эту функциональность, и она работала нормально. Я могу перейти из любого режима работы в другой.

Существуют внешние часы реального времени, работающие для использования прерываний и выполнения кода в определенное время. Во время этого прерывания я проверяю, какой режим работы активен, и запускаю соответствующий код. С помощью сравнения строк, о котором я упоминал выше, я могу перейти к соответствующему коду со следующим прерыванием.

ПРОБЛЕМА: Теперь я могу описать реальную проблему. Каждый раз, когда я вхожу в "рабочий режим 6", чтобы проверить соединение I2C, Arduino зависает. Как вы можете видеть, я использовал тот же самый код внутри "иначе, если", как проверялось ранее. Это соответствующая часть кодекса:

        /* >>> Test the communication over I2C <<< */
        else if (OperatingMode == OPERATING_MODE_TEST_COMMUNICATION)
        {
            static int LedStatus = 0;
            if (LedStatus == 0)
            {
                LedStatus = 1;
            }
            else
            {
                LedStatus = 0;
            }                        
            Serial.print(F("Master LED status: "));
            Serial.println(LedStatus);
            Send_Data(SLAVE_ADRESS,LedStatus);
        }
    }
}

void Send_Data(int receiver, int data)
{
    Wire.beginTransmission(receiver);
    Wire.write(data);        
    Wire.endTransmission();
    Serial.println(F("Data sent"));
}

На следующем рисунке похоже, что плата зависает во время последовательной связи, когда она выводит "Состояние основного светодиода: ". Я проверяю это, комментируя команды serial.print и вставляя команду печати между Wire.begin и Wire.write в коде.

Я думаю, что основная проблема вызвана функцией Wire.write, но я не могу ее отладить, кроме как с помощью команд serial.print. Но напечатанные команды не коррелируют со строкой кода, когда плата фактически зависает. Я думаю, что мне понадобится настоящий отладчик, который останавливает процессор и дает мне возможность пошагово просматривать код.
Сначала я подумал, что эта проблема была вызвана тем, что SRAM был заполнен. Поэтому я поместил команды Serial.print во флэш-память и вставил несколько строк кода для проверки использования SRAM и стека. Вы можете видеть функцию "FreeRAM()" в коде, но я удалил их для лучшей читаемости.

У кого-нибудь была подобная проблема или вы знаете, что может вызвать эту проблему?

Заранее большое спасибо!

, 👍4

Обсуждение

В дополнение к ответу @chrisl, пожалуйста, прочитайте статью Ника Гэммона о [Прерываниях](http://gammon.com.au/interrupts)., @hcheung


1 ответ


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

5

Программа, скорее всего, зависает в Wire.endTransmission ();, потому что именно там происходит фактическая связь I2C. Функция Wire.write() ничего не делает, кроме как помещает данные во внутренний буфер библиотеки Wire.

У вас возникают проблемы, потому что вы вызываете код, зависящий от прерывания, внутри ISR, где прерывания по умолчанию отключены.

Библиотека проводов использует аппаратное обеспечение I2C Uno. Это аппаратное обеспечение генерирует прерывания для различных этапов связи (например, генерирует прерывание, чтобы библиотека могла поместить следующий байт данных в аппаратный буфер). Это означает, что библиотека проводов не будет работать, если по какой-либо причине будут отключены прерывания. Он будет вечно ждать прерывания, которое никогда не наступит.

При вводе ISR (в данном случае прерывания Timer1) другие прерывания по умолчанию отключаются. В конце ISR компилятор снова включит прерывания. Любые прерывания, которые происходят во время выполнения текущего ISR, будут выполняться после его завершения.

С помощью Wire.endTransmission() у вас в настоящее время есть вызов функции в вашем ISR Timer1, который будет ждать запуска соответствующих ISR I2C, что не может произойти внутри ISR Timer1.

Что теперь делать: Вам нужно реструктурировать свой код. В любом случае вам не следует делать так много вещей в ISR. Он должен быть как можно короче, чтобы не нарушать другие функции, основанные на прерываниях. В ISR просто установите простую 1-байтовую переменную флага. Затем в цикле() проверьте эту переменную флага. Если он установлен, вы можете делать то, что в настоящее время находится в вашем ISR. А затем вы сбрасываете флаг, чтобы быть готовым к следующему ISR, снова устанавливая его. Чтобы это работало правильно, вам нужно отключить вызов delay() в цикле(). Если вы хотите выполнять действия по времени, вы можете использовать millis (), как в примере BlinkWithoutDelay, который поставляется с Arduino IDE. Убедитесь, что ничто не может заблокировать функцию loop() в течение длительного периода времени (то есть дольше, чем интервал между 2 прерываниями или дольше, чем минимальное разрешение по времени, которое вы хотите в своей системе).


Примечание: Вы подумали о неправильной строке во время отладки из-за вашего последовательного вывода. Но последовательный вывод только в определенной степени помогает при отладке, так как для правильной работы ему тоже нужны прерывания. Все данные, которые будут напечатаны в вашем ISR, будут отправлены только после выхода ISR. Serial.print() и его братья и сестры заполняют только буфер библиотеки, в то время как фактическая отправка выполняется в фоновом режиме через ISR. Таким образом, отправка прекратилась, когда она застряла в ISR, но это не значит, что на самом деле это было то, что вы напечатали непосредственно перед этой строкой. Даже если не требуется прерываний, это может быть не так. Просто имейте это в виду при использовании последовательных отпечатков для отладки.

,

Большое вам спасибо за то, что нашли время дать мне такое подробное представление о теме ISR и I2C. Я ценю каждый комментарий, который помогает мне писать лучший код :). Я реструктурирую свой код и оставлю отзыв!, @IceCreamToast

Реструктуризация кода, как вы упомянули выше, решила проблему. Я создал флаг для ISR и изменил это значение только внутри ISR. Все остальное было помещено в петлю()., @IceCreamToast