Многозадачность Arduino с использованием millis не работает

#c #multithreading #arduino #arduino-uno

#c #многопоточность #arduino #arduino-uno

Вопрос:

Я пытаюсь использовать millis , потому что мне нужно одновременно запускать несколько циклов. Один для получения значения GPS и температуры, затем отправляет каждое значение на номер телефона пользователя при получении запроса, а другой для отображения этих значений на OLED. Но мои функции, похоже, блокируют друг друга, даже несмотря на то, что я использую millis . Это мой код:

инициализация

 #include<TinyGPS  .h>
#include <SoftwareSerial.h>
#include "DHT.h"

#include <Adafruit_ssd1306syp.h>                        
Adafruit_ssd1306syp display(A4,A5);                  

//dht
#define DHTPIN 5
#define DHTTYPE DHT11
//gps
const double Home_LAT = -6.1887;                     
const double Home_LNG = 106.7;
float glatitude;
float glongitude;
//sms  
char ReceivedSms;
short DHT_OK=-1, GPS_OK=-1;
String DataSms;
//dht
int nilaiSuhu;
int nilaiKelembaban;
//millis
unsigned long counting_millis;
unsigned long millis_now;
const unsigned long delay_val = 2000;
const unsigned long delay_val2 = 1000;

TinyGPSPlus gps;
DHT dht(DHTPIN, DHTTYPE);
SoftwareSerial sim800l(2,3);
 

пустая настройка

 void setup(){
  
  sim800l.begin(9600);
  Serial.begin(9600);
  dht.begin();
  Serial.println("Starting..");
  delay(7000);
  ReceiveMode();

  display.initialize();                                  
  display.clear();                                      
   
}
 

цикл void

 void loop() {
 
  millis_now = millis();
  
  if(millis_now - counting_millis > delay_val)
  {
    oled_Display();
    counting_millis = millis_now;
  
  }
  
  if(millis_now - counting_millis > delay_val2){
    mergeFunction();
    counting_millis = millis_now;
  }
  
}
 

oled_Display () — это функция для отображения каждого значения на OLED

 void oled_Display() {

      display.clear();
      display.setCursor(0,0); 
  
      float nilaiSuhu = dht.readTemperature();
      float nilaiKelembaban = dht.readHumidity();
      
      display.print("Suhu      : ");
      display.println(nilaiSuhu);
      display.print("Kelembaban: ");
      display.println(nilaiKelembaban);
      display.print("Latitude  : ");
      display.println(gps.location.lat(), 5);
      display.print("Longitude : ");
      display.println(gps.location.lng(), 4);
      display.print("Satellites: ");
      display.println(gps.satellites.value());
      display.print("Speed     : ");
      display.println(gps.speed.mph());
      
      unsigned long Distance_To_Home = (unsigned long)TinyGPSPlus::distanceBetween(gps.location.lat(),gps.location.lng(),Home_LAT, Home_LNG);
      display.print("KM asal   :");                        // Have TinyGPS Calculate distance to home and display it
      display.print(Distance_To_Home);
      display.update();                                       
}
 

mergeFunction() — это функция для получения значения GPS и температуры и отправки его пользователю на основе запроса.

 void mergeFunction(){

   nilaiSuhu = dht.readTemperature();
   nilaiKelembaban = dht.readHumidity();
   String RSMS;
  
    while(sim800l.available() > 0) {
      ReceivedSms = sim800l.read();
      Serial.print(ReceivedSms);
      RSMS.concat(ReceivedSms);
      DHT_OK = RSMS.indexOf("Suhu");
      GPS_OK = RSMS.indexOf("All");
      
    }
  
     while(Serial.available() > 0){
      if(gps.encode(Serial.read()))
        readGPS();
      
    }
  
    if(DHT_OK!=-1){
      Serial.println("DHT found");
      
      Serial.print("Temperature = ");
      Serial.print(nilaiSuhu);
      Serial.print("*C      Humidity = ");
      Serial.print(nilaiKelembaban); 
      Serial.println(" %");
  
      DataSms = "DHT11nSuhu = " String(nilaiSuhu,1) " C" " nKelembaban =" String(nilaiKelembaban,1) " %";
      
      SendData();
      ReceiveMode(); 
  
      DHT_OK=-1; 
    }
     
    if(gps.location.isValid())
    { 
        glatitude = gps.location.lat();
        glongitude = gps.location.lng();
        
        String link = "http://www.google.com/maps/place/"   String(glatitude,6)   ","   String(glongitude,6);
        
        DataSms = "ALLnSuhu = " String(nilaiSuhu,1) " C" " nKelembaban =" String(nilaiKelembaban,1) " %" " nMaps = " link;
        if(GPS_OK!=-1)
        {
        SendData();
        ReceiveMode();
        GPS_OK = -1;
        } 
    } 
    else
    {
        if(GPS_OK!=-1)
        {
        DataSms = "ALLnSuhu = " String(nilaiSuhu,1) " C" " nKelembaban =" String(nilaiKelembaban,1) " %" " nMaps = " "Lokasi tidak ditemukan";
        SendData();
        ReceiveMode();
        GPS_OK = -1;
        } 
    }

}
 

Обе эти функции работают отлично, когда они не выполняются одновременно. Я не знаю, почему обе мои функции блокируют друг друга, когда они выполняются одновременно, даже если я использую millis . Как правильно запустить многозадачность Arduino? Просветите меня.

Комментарии:

1. Конечно, функции могут выполняться параллельно, только если вы запустили их в отдельных потоках. Но вы не создали никаких потоков, и Arduino Uno также не может запускать несколько потоков (по крайней мере, не так просто, как вы ожидали). Вам нужно создать для них таймеры

2. кроме того, вам может повезти с Arduino

3. недостаточно ли использовать millis, как в моем цикле void?

4. нет. нет ничего, что заставляет 2 блока if работать параллельно. Сначала будет запущен первый, а затем второй

5. ваша ошибка заключается в том, что вы сбрасываете время начала каждый раз для всех. вам нужна отдельная counting_millis для каждой функции. и в функциях не должно быть цикла while. обработайте то, что доступно, и верните. продолжайте читать входные данные при следующем вызове функции.

Ответ №1:

Инструкции в C всегда выполняются последовательно сверху вниз. Как вы можете ожидать, что 2 функции будут выполняться параллельно, если они ничем не отличаются от других функций?

 oled_Display();
mergeFunction(); // this will be run only after the previous statements have finished
 

Если это так, то setup() or loop() также выполняются параллельно. В обычной одиночной программе параллельно выполняются только потоки. Однако в крошечной встроенной системе, такой как Arduino, многопоточность не поддерживается, поскольку переключение контекста является очень дорогостоящим. Посмотрите, как я могу создать несколько запущенных потоков?

На компьютерах вы используете прерывания для ожидания событий вместо опроса, подобного тому, который тратит много циклов. Одно из прерываний для своевременного запуска называется таймером. В Arduino вы можете использовать arduino-timer. Просто установите 2 таймера для 2 процедур и дождитесь их запуска

Ответ №2:

Как упоминал @Juraj, вам нужна независимая counting_millis переменная для каждой функции, которую вы пытаетесь вызвать.

Основной проблемой здесь является неправильное представление о том, что:

Мне нужно одновременно запускать несколько циклов

что невозможно с микроконтроллером AVR. С помощью функции можно выполнять многозадачность millis() , однако это не означает, что оба процесса будут выполняться одновременно.

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

Я бы лично распечатал данные на OLED после обработки данных с GPS. Это полностью основано на недавнем опыте разработки сложной системы, которая запрашивает множество датчиков, пользовательских вводов и последовательной связи. Последнее, с чем вы хотите иметь дело, — это OLED, поскольку наиболее важной частью (я полагаю) является отображение соответствующих данных.

Я предлагаю обновлять экран каждые 40 (-иш) миллисекунд, что соответствует приблизительной минимальной частоте обновления 24 кадра в секунду, что человеческий глаз может воспринимать как плавное движение. Это не учитывает время, затрачиваемое библиотекой на отправку данных на OLED. Однако, в конце концов, вы не увидите никакого влияния на вашу производительность, если вы это сделаете.

Комментарии:

1. Я пытался использовать RTOS, но все равно не работает. Я думаю, что это также вызвано ограничением объема памяти моего MCU (Arduino uno), у него всего 2 КБ оперативной памяти, и вся программа потребляла более 50% места для хранения. Независимо от размера OLED-модуля, драйвер SSD1306 имеет встроенную память графического отображения данных объемом 1 КБ (GDDRAM) для экрана, который содержит отображаемый битовый шаблон. Я собираюсь предположить, что моему OLED не хватает памяти для буферизации всего дисплея.

2. Что я сделал, так это изменил свою библиотеку для запуска OLED и создал расписание с использованием библиотеки простого таймера. Теперь вся программа просто потребляла 47% дискового пространства. Да, он работает в течение нескольких минут, а затем зависает: D. Я думаю, мне нужно сменить свой MCU