How do you plan before starting RTOS coding

Do you use FreeRTOS ?

I'm having trouble to understand how programmer plan for RTOS system before starting to code. I think the first thing to do is to split system tasks into RTOS task. There can be hard real time task, soft real time tasks and non real time tasks.

How do you split project tasks into RTOS Task's ? If you have developed freertos project let me ask you more specific question

It depends on the computer system that is used and the specific things that are involved. A programmer has often experience with a certain system and knows how to deal with it. Starting from scratch after learning about a (preemptive) multitasking system is hard.

If there is hardware that has specific timing, then that hardware gets all the attention. The rest of the code can run in a low priority task.

The step to turn something of the real world into logic parts is the main effort of programming. Implementing every part in code needs less creativity.
I'm talking about small embedded systems that are made by a single person. Large companies with large projects use tools and protocols for designing.

There is a way to look at a project by looking at the data streams. Every input generates a data stream (even a button) and the stream of data is flowing through the code and that results in a output stream (for example turning a induction cooker on and off).
If you can look at a problem by both thinking in code and in data streams, that will help a lot.

If you can visualize balloons with strings of wires between them, then you can lean back and see every task as a balloon and data going over the strings. If you are really good at it (I'm not) then you can feel which balloon is about to burst, and perhaps that balloon needs to be split into more balloons.

1 Like

I break my code up into functional blocks or operations. Some functional blocks would be tasks that run the peripherals or data collectors, other tasks would be data processors, and I can use event handlers, not always a ISR, to handle events.

I run freeRTOS on ESP32's

1 Like

Do you use FreeRTOS just for hobby purpose ? or Do you use it for an industrial purpose ?

I don't get exectly what do you mean functional blocks or operations

Can you share your sample code if you don't mind ? I can ask you how did you divide tasks?

1 Like

A division of code by its higher level semantics, such as "compute an FFT" or "turn on a light" or "print the time"... for example.

here is something I am currently working on.

/*
  https://github.com/G6EJD/ESP32-e-Paper-Weather-Display/blob/master/examples/Waveshare_4_2/Waveshare_4_2.ino
  Stole some code from that guy.
*/
#include "MyBitmap.h"
#include <WiFi.h>
#include <PubSubClient.h>
#include "certs.h"
#include "sdkconfig.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include "Adafruit_BME680.h"
#include <GxEPD2_BW.h>
#include <U8g2_for_Adafruit_GFX.h> // Select u8g2 font from here: https://github.com/olikraus/u8g2/wiki/fntlistall
#include <Fonts/FreeMonoBold9pt7b.h> //https://learn.adafruit.com/adafruit-gfx-graphics-library/using-fonts
#include <Fonts/FreeMono9pt7b.h>
#include <HardwareSerial.h>
#include <SimpleKalmanFilter.h>
#include "MHZ19.h"
#include <ESP32Time.h>
#include <SolarCalculator.h>
////
ESP32Time rtc;
MHZ19 myMHZ19;
GxEPD2_BW<GxEPD2_420, GxEPD2_420::HEIGHT> display(GxEPD2_420(/*CS=5*/ SS, /*DC=*/ 17, /*RST=*/ 16, /*BUSY=*/ 4)); // GDEW042T2
Adafruit_BME680 bme( GPIO_NUM_15 );
WiFiClient   wifiClient; // do the WiFi instantiation thing
PubSubClient MQTTclient( mqtt_server, mqtt_port, wifiClient );
U8G2_FOR_ADAFRUIT_GFX u8g2Fonts;
////
#define evtStoreAirPressure   ( 1 << 0 )
#define evtWaitForBME         ( 1 << 1 )
#define evtParseMQTT          ( 1 << 3 )
#define evtDisplayUpdate      ( 1 << 4 )
#define evtDoBME              ( 1 << 5 )
#define OneMinuteGroup ( evtDoBME )
EventGroupHandle_t eg;
//////
QueueHandle_t xQ_WindChillDewPoint;
QueueHandle_t xQ_eData;
struct stu_eData
{
  float  oTemperature = 0.0f;
  float  oHumidity    = 0.0f;
  float  oPressure    = 0.0f;
  // for outside aqi???????????????????????? what does the RPi send an int or float???
  float  Temperature  = 0.0f;
  float  Pressure    = 0.0f;
  float  Humidity    = 0.0f;
  float  IAQ         = 0.0f; // Index Air Quality
  float  RM0         = 0.0f; // Remaining Moisture from sensor 0
  //float  PM2         = 0.0f; // particles in air
  float  WS          = 0.0f; // wind speed
  String WD          = "";   // wind direction
  float  RF          = 0.0f; // rainfall
  //float  WSV         = 0.0f; // weather station volts
  //float  WSC         = 0.0f; // weather station current
  //float  WSP         = 0.0f; // weather station power
  float  WindChill   = 0.0f; //windchill
  float  DewPoint    = 0.0f; //dew point or dew index
  int    SunRiseHr   = 0;    // sunrise hour
  int    SunRiseMin  = 0;    //sunrise minute
  int    SunSetHr    = 0;    //sunset hour
  int    SunSetMin   = 0;    //sunset minute
  int    DuskHr      = 0;    //dusk
  int    DuskMin     = 0;    //dusk
  int    DawnHr      = 0;    // dawn
  int    DawnMin     = 0;    // dawn
  int    TransitHr   = 0;    // 'noon' time
  int    TransitMin  = 0;    // 'noon' time
  double azimuth     = 0.0f;   // Sun's azimuth, in degrees
  double elevation   = 0.0f;     // Sun's elevation, in degrees
  float  CO2         = 0.0f;
  float  PressureH   = 0.0f;
  float  PressureL   = 10000.0f;
} x_eData; // environmental data
QueueHandle_t xQ_Message; // payload and topic queue of MQTT payload and topic
const int payloadSize = 100;
struct stu_message
{
  char payload [payloadSize] = {'\0'};
  String topic ;
} x_message;
////
const float oGasResistanceBaseLine = 149598.0f;
int mqttOK = 0;
volatile bool TimeSet = false;
const int BufferCount = 60;
float CollectionPressure[BufferCount] = {0.0f};
boolean LargeIcon = true, SmallIcon = false;
//bool CollectionDone = false;
#define Large  11           // For icon drawing, needs to be odd number for best effect
#define Small  5            // For icon drawing, needs to be odd number for best effect
enum alignment {LEFT, RIGHT, CENTER};
SemaphoreHandle_t sema_MQTT_KeepAlive;
SemaphoreHandle_t sema_PublishPM;
SemaphoreHandle_t sema_mqttOK;
SemaphoreHandle_t sema_CollectPressure;
////
//serial(2) = pin25 RX, pin26 TX
HardwareSerial co2Serial ( 2 );
////
void IRAM_ATTR onTimer()
{
  BaseType_t xHigherPriorityTaskWoken;
  xEventGroupSetBitsFromISR(eg, OneMinuteGroup, &xHigherPriorityTaskWoken);
} // void IRAM_ATTR onTimer()
// interrupt service routine for WiFi events put into IRAM
void IRAM_ATTR WiFiEvent(WiFiEvent_t event)
{
  switch (event) {
      break;
    default: break;
  }
} // void IRAM_ATTR WiFiEvent(WiFiEvent_t event)
void IRAM_ATTR mqttCallback(char* topic, byte * payload, unsigned int length)
{
  memset( x_message.payload, '\0', payloadSize ); // clear payload char buffer
  x_message.topic = ""; //clear topic string buffer
  x_message.topic = topic; //store new topic
  int i = 0; // extract payload
  for ( i; i < length; i++)
  {
    x_message.payload[i] = ((char)payload[i]);
  }
  x_message.payload[i] = '\0';
  xQueueOverwrite( xQ_Message, (void *) &x_message );// send data to queue
} // void mqttCallback(char* topic, byte* payload, unsigned int length)
////
void setup()
{
  // hardware timer 4 set for one minute alarm
  hw_timer_t * timer = NULL;
  timer = timerBegin( 3, 80, true );
  timerAttachInterrupt( timer, &onTimer, true );
  timerAlarmWrite(timer, 60000000, true);
  timerAlarmEnable(timer);
  ///
  co2Serial.begin( 9600 , SERIAL_8N1, 25, 26 ); // pin25 RX, pin26 TX
  x_eData.WD.reserve(50);
  x_message.topic.reserve( payloadSize );
  xQ_WindChillDewPoint = xQueueCreate( 1, sizeof(stu_eData) );
  xQ_Message  = xQueueCreate( 1, sizeof(stu_message) );
  xQ_eData    = xQueueCreate( 1, sizeof(stu_eData) ); // sends a queue copy of the structure
  //
  sema_PublishPM = xSemaphoreCreateBinary();
  xSemaphoreGive( sema_PublishPM );
  sema_mqttOK    =  xSemaphoreCreateBinary();
  xSemaphoreGive( sema_mqttOK );
  sema_CollectPressure = xSemaphoreCreateBinary();
  xSemaphoreGive( sema_CollectPressure );
  //
  eg = xEventGroupCreate(); // get an event group handle
  //
  xTaskCreatePinnedToCore( fparseMQTT, "fparseMQTT", 7000,  NULL, 5, NULL, 1 );
  xTaskCreatePinnedToCore( MQTTkeepalive, "MQTTkeepalive", 5000, NULL, 6, NULL, 1 );
  xTaskCreatePinnedToCore( DoTheBME680Thing, "DoTheBME280Thing", 20000, NULL, 5, NULL, 1);
  xTaskCreatePinnedToCore( fmqttWatchDog, "fmqttWatchDog", 5000, NULL, 3, NULL, 1 );
  xTaskCreatePinnedToCore( fDoTheDisplayThing, "fDoTheDisplayThing", 30000, NULL, 3, NULL, 1 );
  xTaskCreatePinnedToCore( fGetCO2, "fGetCO2", 4500, NULL, 2, NULL, 1 );
  xTaskCreatePinnedToCore( fParseDewPointWindChill, "fParseDewPointWindChill", 4500, NULL, 2, NULL, 1 );
  xTaskCreatePinnedToCore( fSolarCalculations, "fSolarCalculations", 10000, NULL, 2, NULL, 1 );
  xTaskCreatePinnedToCore( fStoreAirPressure, "fStoreAirPressure", 10000, NULL, 2, NULL, 1 );
} //void setup()
////
void fStoreAirPressure ( void *pvParemeters )
{
  int cellCount = 0;
  int Ticks     = 0;
  bool Filled   = false;
  const int ticksTrigger = 120; // triggered at 1 minute intervals
  for (;;)
  {
    //triggered by BME which is triggered by the 1 minute hardware timer.
    xEventGroupWaitBits (eg, evtStoreAirPressure, pdTRUE, pdTRUE, portMAX_DELAY );
    xSemaphoreTake( sema_CollectPressure, portMAX_DELAY );
    if ( !Filled )
    {
      CollectionPressure[cellCount] = x_eData.oPressure;
    }
    if ( Filled )
    {
      if ( Ticks == ticksTrigger )
      {
        //shift contents left and insert new value at the end
        for ( int i = 0; i <= BufferCount - 2; i++ )
        {
          CollectionPressure[i] = CollectionPressure[i + 1];
        }
      }
      CollectionPressure[BufferCount - 1] = x_eData.oPressure;
    }
    if ( x_eData.oPressure > x_eData.PressureH )
    {
      x_eData.PressureH = x_eData.oPressure;
    }
    if ( x_eData.oPressure < x_eData.PressureL )
    {
      x_eData.PressureL = x_eData.oPressure;
    }
    Ticks++;
    if ( Ticks >= (ticksTrigger + 1) )
    {
      Ticks = 1;
      cellCount++;
    }
    if ( cellCount == (BufferCount - 1) )
    {
      cellCount = 0;
      if ( !Filled )
      {
        Filled = true;
      }
    }
    xSemaphoreGive( sema_CollectPressure );
    //
    //log_i( " high watermark % d",  uxTaskGetStackHighWaterMark( NULL ) );
  } //for (;;)
  vTaskDelete( NULL );

} //void fStoreAirPressure ( void *pvParemeters )
////
void fSolarCalculations ( void *pvParameters )
{
  double sunrise;  // Sunrise, in hours (UTC)
  double transit;  // Solar noon, in hours (UTC)
  double sunset;   // Sunset, in hours (UTC)
  double dawn;     // Civil dawn, in hours (UTC)
  double dusk;     // Civil dusk, in hours (UTC)
  //double rt_ascension;  // Sun's right ascension, in degrees
  //double declination;   // Sun's declination, in degrees
  const  float time_zone = -7.0f;
  TickType_t xLastWakeTime = xTaskGetTickCount();
  const TickType_t xFrequency = 1000; //delay for mS
  int count  = 3590;
  int monthX = 1;
  int dayX   = 1;
  for (;;)
  {
    if ( count % 60 == 0 )
    {
      if ( (rtc.getHour(true) >= 12) & (rtc.getHour(true) <= 23) )
      {
        dayX = 0;
      } else {
        dayX = 1;
      }
      calcSunriseSunset( rtc.getYear(), (rtc.getMonth() + monthX) , (rtc.getDay() + dayX), latitude, longitude, transit, sunrise, sunset );  // Calculate the times of sunrise, transit and sunset
      sunrise += time_zone;
      sunset  += time_zone;
      SolarTimeFormat( sunrise, 0 );
      SolarTimeFormat( sunset, 1 );
      calcCivilDawnDusk( rtc.getYear(), (rtc.getMonth() + monthX) , rtc.getDay(), latitude, longitude, transit, dawn, dusk); // Calculate the times of civil dawn and dusk (UTC)
      transit += time_zone;
      dawn    += time_zone;
      dusk    += time_zone;
      SolarTimeFormat( dawn, 2 );
      SolarTimeFormat( dusk, 3 );
      SolarTimeFormat( transit, 4 );
      calcHorizontalCoordinates( rtc.getYear(), (rtc.getMonth() + monthX) , rtc.getDay(),  rtc.getHour(true) , rtc.getMinute(), rtc.getSecond(), latitude, longitude, x_eData.azimuth, x_eData.elevation );
      x_eData.azimuth   = double(round(x_eData.azimuth * 100)) / 100; // Round to two decimal places
      x_eData.elevation = double(round(x_eData.elevation * 100)) / 100;
      if (count >= 3600 )
      {
        SolarTimeFormat( 0.0f, 5 ); // publish MQTT
        //log_i( "Hour:%d Azimuth %f, elevation %f, transit %dhr %dmin, dawn %dhr %dmin, dusk %dhr %dmin", rtc.getHour(true), x_eData.azimuth, x_eData.elevation, x_eData.TransitHr, x_eData.TransitMin, x_eData.DawnHr, x_eData.DawnMin, x_eData.DuskHr, x_eData.DuskMin );
        count = 0;
      }
    }
    //log_i( " high watermark % d",  uxTaskGetStackHighWaterMark( NULL ) );
    xLastWakeTime = xTaskGetTickCount();
    vTaskDelayUntil( &xLastWakeTime, xFrequency );
    count++;
  } //for (;;)
  vTaskDelete( NULL );
} //void fSolarCalculations ( )
////
void SolarTimeFormat( double h, int i  )
{
  int hours   = 0;
  int minutes = 0;
  if ( h != 0.0f )
  {
    int m = int(round(h * 60));
    hours = (m / 60) % 24;
    minutes = m % 60;
  }
  switch ( i )
  {
    case 0:
      x_eData.SunRiseHr = hours;
      x_eData.SunRiseMin = minutes;
      break;
    case 1:
      x_eData.SunSetHr = hours;
      x_eData.SunSetMin = minutes;
      break;
    case 2:
      x_eData.DawnHr = hours;
      x_eData.DawnMin = minutes;
      break;
    case 3:
      x_eData.DuskHr = hours;
      x_eData.DawnMin = minutes;
      break;
    case 4:
      x_eData.TransitHr = hours;
      x_eData.TransitMin = minutes;
      break;
    case 5:
      String sTopic = "";
      sTopic.reserve( 35 );
      sTopic.concat( String(x_eData.SunRiseHr) + "," );
      sTopic.concat( String(x_eData.SunRiseMin) + "," );
      sTopic.concat( String(x_eData.SunSetHr) + "," );
      sTopic.concat( String(x_eData.SunSetMin) + "," );
      sTopic.concat( String(x_eData.DawnHr) + "," );
      sTopic.concat( String(x_eData.DawnMin) + "," );
      sTopic.concat( String(x_eData.TransitHr) + "," );
      sTopic.concat( String(x_eData.TransitMin) );
      xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY );
      MQTTclient.publish( topicSRSSDDT, sTopic.c_str() );
      xSemaphoreGive( sema_MQTT_KeepAlive );
      sTopic = "";
      sTopic.concat( String(x_eData.azimuth) + "," + String(x_eData.elevation) );
      xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY );
      MQTTclient.publish( topicAzEle, sTopic.c_str() );
      xSemaphoreGive( sema_MQTT_KeepAlive );
      sTopic = "";
      break;
  } // switch ( i ) {
} // void SolarTimeFormat( double h, int i  )
/*
  250-400ppm Normal background concentration in outdoor ambient air
  400-1,000ppm  Concentrations typical of occupied indoor spaces with good air exchange
  1,000-2,000ppm  Complaints of drowsiness and poor air.
  2,000-5,000 ppm Headaches, sleepiness and stagnant, stale, stuffy air. Poor concentration, loss of attention, increased heart rate and slight nausea may also be present.
  5,000 Workplace exposure limit (as 8-hour TWA) in most jurisdictions.
  >40,000 ppm Exposure may lead to serious oxygen deprivation resulting in permanent brain damage, coma, even death.
*/
void fParseDewPointWindChill( void *pvParameters )
{
  while ( !MQTTclient.connected() )
  {
    vTaskDelay( 250 );
  }
  struct stu_message px_message;
  String sDewPoint = "";
  String sWindChill = "";
  sDewPoint.reserve( payloadSize );
  sWindChill.reserve( payloadSize );
  for (;;)
  {
    if ( xQueueReceive(xQ_WindChillDewPoint, &px_message, portMAX_DELAY) == pdTRUE )
    {
      sDewPoint = px_message.payload;
      int commaIndex = sDewPoint.indexOf(',');
      sWindChill.concat ( sDewPoint.substring(0, commaIndex) );
      sDewPoint.remove( 0, (commaIndex + 1) );
      x_eData.WindChill = sWindChill.toFloat();
      x_eData.DewPoint = sDewPoint.toFloat();
      sDewPoint = "";
      sWindChill = "";
    }
    //log_i( " high watermark % d",  uxTaskGetStackHighWaterMark( NULL ) );
  }
  vTaskDelete( NULL );
}
////
void fGetCO2 ( void *pvParameters )
{
  uint64_t TimePastKalman  = esp_timer_get_time();
  myMHZ19.begin( co2Serial );
  myMHZ19.autoCalibration();
  TickType_t xLastWakeTime = xTaskGetTickCount();
  const TickType_t xFrequency = 1000; //delay for mS
  SimpleKalmanFilter KF_CO2( 1.0f, 1.0f, .01f );
  for ( ;; )
  {
    KF_CO2.setProcessNoise( (esp_timer_get_time() - TimePastKalman) / 1000000.0f );
    x_eData.CO2 = KF_CO2.updateEstimate( myMHZ19.getCO2() ); // apply simple Kalman filter
    TimePastKalman = esp_timer_get_time();
    xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY );
    MQTTclient.publish( topicCO2, String(round(x_eData.CO2)).c_str() );
    xSemaphoreGive( sema_MQTT_KeepAlive );
    // process wind chill and dew point
    xLastWakeTime = xTaskGetTickCount();
    vTaskDelayUntil( &xLastWakeTime, xFrequency );
    //log_i( " high watermark % d",  uxTaskGetStackHighWaterMark( NULL ) );
  }
  vTaskDelete( NULL );
} //void fMHZ19B ( void *pvParameters )
////
void fparseMQTT( void *pvParameters )
{
  struct stu_message px_message;
  for (;;)
  {
    if ( xQueueReceive(xQ_Message, &px_message, portMAX_DELAY) == pdTRUE )
    {
      xSemaphoreTake( sema_mqttOK, portMAX_DELAY );
      mqttOK = 0;
      xSemaphoreGive( sema_mqttOK );
      if (px_message.topic == topicAQIndex )
      {

      }

      if ( px_message.topic == topicOutsidePressure )
      {
        x_eData.oPressure = String(px_message.payload).toFloat();
      }
      if ( px_message.topic == topicOutsideHumidity )
      {
        x_eData.oHumidity = String(px_message.payload).toFloat();
      }
      if ( px_message.topic == topicOutsideTemperature )
      {
        x_eData.oTemperature = String(px_message.payload).toFloat();
      }
      if ( px_message.topic == topicRemainingMoisture_0 )
      {
        x_eData.RM0  = String(px_message.payload).toFloat();
      }
      if ( px_message.topic == topicWindSpeed )
      {
        x_eData.WS = String(px_message.payload).toFloat();
      }
      if ( px_message.topic == topicWindDirection )
      {
        x_eData.WD = "";
        x_eData.WD = String(px_message.payload);
      }
      if ( px_message.topic == topicRainfall )
      {
        x_eData.RF = String(px_message.payload).toFloat();
      }

      //      if ( px_message.topic == topicWSVolts )
      //      {
      //        x_eData.WSV = String(px_message.payload).toFloat();
      //      }
      //      if ( px_message.topic == topicWSCurrent )
      //      {
      //        x_eData.WSC = String(px_message.payload).toFloat();
      //      }
      //      if ( px_message.topic == topicWSPower )
      //      {
      //        x_eData.WSP = String(px_message.payload).toFloat();
      //      }
      if ( px_message.topic == topicDPnWI )
      {
        xQueueSend( xQ_WindChillDewPoint, (void *) &px_message, 1 );
      }
      if ( String(px_message.topic) == topicOK )
      {
        if ( !TimeSet)
        {
          String temp = "";
          temp.reserve(50);
          temp.concat( String(px_message.payload[0]) );
          temp.concat( String(px_message.payload[1]) );
          temp.concat( String(px_message.payload[2]) );
          temp.concat( String(px_message.payload[3]) );
          int year =  temp.toInt();
          temp = "";
          temp.concat( String(px_message.payload[5]) + String(px_message.payload[6]) );
          int month =  temp.toInt();
          temp =  "";
          temp.concat(String(px_message.payload[8]) + String(px_message.payload[9]) );
          int day =  temp.toInt();
          temp = "";
          temp.concat( String(px_message.payload[11]) + String(px_message.payload[12]) );
          int hour =  temp.toInt();
          temp = "";
          temp.concat( String(px_message.payload[14]) + String(px_message.payload[15]) );
          int min =  temp.toInt();
          rtc.setTime( 0, min, hour, day, month, year );
          log_i( "rtc  %s Year %d month %d day %d", rtc.getTime(), rtc.getYear(), (rtc.getMonth() + 1), rtc.getDay() );
          TimeSet = true;
        }
      }
    } //if ( xQueueReceive(xQ_Message, &px_message, portMAX_DELAY) == pdTRUE )
  } //for(;;)
  vTaskDelete( NULL );
} // void fparseMQTT( void *pvParameters )
////
void fDoTheDisplayThing( void * parameter )
{
  struct stu_eData px_eData;
  const char HelloWorld[] = "Hello World!";
  //display.init();
  //display.setFont(&FreeMonoBold9pt7b);
  //display.setTextColor(GxEPD_BLACK);
  int yIncrement = 18;
  int CurrentY = 20;
  int CurrentX = 5;
  String temp1 = "";
  temp1.reserve(10);
  String temp2 = "";
  temp2.reserve(10);
  int boxSpacing = 80;
  size_t item_size;
  for (;;)
  {
    xEventGroupWaitBits (eg, evtDisplayUpdate, pdTRUE, pdTRUE, portMAX_DELAY );
    //log_i( "pip" );
    CurrentY = 20;
    display.init();
    //display.setFont(&FreeMonoBold9pt7b);
    display.setFont(&FreeMono9pt7b);
    //u8g2Fonts.setFont(u8g2_font_helvB08_tf);
    display.setTextColor(GxEPD_BLACK);
    display.setFullWindow();
    display.fillScreen(GxEPD_WHITE); // set the background to white (fill the buffer with value for white)
    display.setCursor( CurrentX, CurrentY );
    // first line
    display.drawRect( CurrentX, CurrentY , 70, 55, GxEPD_BLACK);
    display.drawBitmap( CurrentX + 10, CurrentY + 5, temperature_icon16x16, 16, 16, GxEPD_BLACK);
    display.setCursor( CurrentX + 30, CurrentY + 15 );
    display.print( "F" );
    display.setCursor( CurrentX + 10, CurrentY + 40);
    display.print( String(x_eData.oTemperature) );
    display.drawRect( CurrentX + boxSpacing, CurrentY , 70, 55, GxEPD_BLACK);
    display.setCursor( CurrentX + 90, CurrentY + 15 );
    display.print( "R.H.");
    display.setCursor( CurrentX + 90, CurrentY + 35 );
    display.print( String((int)x_eData.oHumidity) );
    // end of first line
    if ( x_eData.SunRiseMin < 10 )
    {
      temp1.concat( "0" + String(x_eData.SunRiseMin) );
    } else {
      temp1.concat( String(x_eData.SunRiseMin) );
    }
    if ( x_eData.SunSetMin < 10 )
    {
      temp2.concat( "0" + String(x_eData.SunSetMin) );
    } else {
      temp2.concat( String(x_eData.SunSetMin) );
    }
    CurrentY += yIncrement;
    CurrentY += yIncrement;
    CurrentY += yIncrement;
    display.setCursor( CurrentX, CurrentY );

    display.print( "Wind: " );
    CurrentY += yIncrement;
    display.setCursor( CurrentX, CurrentY );
    display.print( "Speed " + String(x_eData.WS) + "KPH, Dir " + String(x_eData.WD) + " Chill " + String(x_eData.WindChill) + "F" );
    CurrentY += yIncrement;
    CurrentY += yIncrement;
    display.drawRect( CurrentX, CurrentY , 70, 55, GxEPD_BLACK);
    addsun( 35, CurrentY + 30 , Small, SmallIcon );
    display.setCursor( CurrentX + 5, CurrentY + 15 );
    display.print( "0" + String(x_eData.SunRiseHr) + ":" + temp1 );
    display.setCursor( CurrentX + 5, CurrentY + 50 );
    display.print( String(x_eData.SunSetHr) + ":" + temp2 );
    display.drawRect( CurrentX + boxSpacing, CurrentY , 70, 55, GxEPD_BLACK);
    addraindrop(CurrentX + 110, CurrentY + 15, 7);
    display.setCursor( CurrentX + 90, CurrentY + 35 );
    display.print( String(x_eData.RF) );
    display.setCursor( CurrentX + 100, CurrentY + 50 );
    display.print( "mm" );
    display.drawRect( CurrentX + (boxSpacing * 2 ), CurrentY , 70, 55, GxEPD_BLACK);
    display.setCursor( CurrentX + 177, CurrentY + 15 );
    display.print( "C02" );
    display.setCursor( CurrentX + 165, CurrentY + 35 );
    display.print( String(int(x_eData.CO2)) );
    display.setCursor( CurrentX + 165, CurrentY + 50 );
    display.print( "PPM" );
    display.drawRect( CurrentX + (boxSpacing * 3 ), CurrentY , 70, 55, GxEPD_BLACK);
    display.setCursor( CurrentX + 246, CurrentY + 15 );
    display.print( "AQI" );
    display.setCursor( CurrentX + 246, CurrentY + 35 );
    display.print( String(int(x_eData.IAQ)) );
    display.setCursor( CurrentX + 246, CurrentY + 50 );
    display.print( "%" );
    display.drawRect( CurrentX + (boxSpacing * 4 ), CurrentY , 70, 55, GxEPD_BLACK);
    display.setCursor( CurrentX + 327, CurrentY + 15 );
    display.print( "R.M." );
    display.setCursor( CurrentX + 327, CurrentY + 35 );
    display.print( String(int(x_eData.RM0)) + "%" );
    //make graph
    xSemaphoreTake( sema_CollectPressure, portMAX_DELAY );
    CurrentY += yIncrement * 6;
    display.setCursor( CurrentX, CurrentY); //set cursor position
    //display.drawLine( CurrentX, CurrentY, CurrentX + 200, CurrentY, GxEPD_BLACK);
    int BaseLine = CollectionPressure[0];
    int offsetX = 0;
    for ( int j = 0; j < BufferCount; j++ )
    {
      if ( CollectionPressure[j] != 0.0f )
      {
        int yAdj = (int)CollectionPressure[j] - BaseLine;
        display.setCursor( CurrentX + offsetX, CurrentY + yAdj );
        display.print( "-" );
        offsetX += 5;
        log_i( "pressure %f item %d", CollectionPressure[j], j );
      }
    }
    xSemaphoreGive( sema_CollectPressure );
    temp2 = "";
    temp1 = "";
    //
    display.display(false); // full update
    display.hibernate();
    //log_i( "DoTheBME280Thing high watermark % d",  uxTaskGetStackHighWaterMark( NULL ) );
  } //for (;;)
  vTaskDelete( NULL );
} //void fDoTheDisplayTHing( void * parameter )
////
void fmqttWatchDog( void * paramater )
{
  int UpdateImeTrigger = 86400; //seconds in a day
  int UpdateTimeInterval = 86300; // 1st time update in 100 counts
  int maxNonMQTTresponse = 15;
  for (;;)
  {
    vTaskDelay( 1000 );
    if ( mqttOK >= maxNonMQTTresponse )
    {
      ESP.restart();
    }
    xSemaphoreTake( sema_mqttOK, portMAX_DELAY );
    mqttOK++;
    xSemaphoreGive( sema_mqttOK );
    UpdateTimeInterval++; // trigger new time get
    if ( UpdateTimeInterval >= UpdateImeTrigger )
    {
      TimeSet = false; // sets doneTime to false to get an updated time after a days count of seconds
      UpdateTimeInterval = 0;
    }
  }
  vTaskDelete( NULL );
}
////
float fCalulate_IAQ_Index( int gasResistance, float Humidity)
{
  float hum_baseline = 40.0f;
  float hum_weighting = 0.25f;
  float gas_offset = 0.0f;
  float hum_offset = 0.0f;
  float hum_score = 0.0f;
  float gas_score = 0.0f;
  gas_offset = oGasResistanceBaseLine - float( gasResistance );
  hum_offset = float( Humidity ) - hum_baseline;
  // calculate hum_score as distance from hum_baseline
  if ( hum_offset > 0.0f )
  {
    hum_score = 100.0f - hum_baseline - hum_offset;
    hum_score /= ( 100.0f - hum_baseline );
    hum_score *= ( hum_weighting * 100.0f );
  } else {
    hum_score = hum_baseline + hum_offset;
    hum_score /= hum_baseline;
    hum_score *= ( 100.0f - (hum_weighting * 100.0f) );
  }
  //calculate gas score as distance from baseline
  if ( gas_offset > 0.0f )
  {
    gas_score = float( gasResistance ) / oGasResistanceBaseLine;
    gas_score *= ( 100.0f - (hum_weighting * 100.0f ) );
  } else {
    gas_score = 100.0f - ( hum_weighting * 100.0f );
  }
  return ( hum_score + gas_score );
} //void fCalulate_IAQ_Index( int gasResistance, float Humidity):
////
void DoTheBME680Thing( void *pvParameters )
{
  SPI.begin(); // initialize the SPI library
  vTaskDelay( 10 );
  if (!bme.begin()) {
    log_i("Could not find a valid BME680 sensor, check wiring!");
    while (1);
  }
  // Set up oversampling and filter initialization
  bme.setTemperatureOversampling(BME680_OS_8X);
  bme.setHumidityOversampling(BME680_OS_2X);
  bme.setPressureOversampling(BME680_OS_4X);
  bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
  bme.setGasHeater(320, 150); // 320*C for 150 ms
  //wait for a mqtt connection
  while ( !MQTTclient.connected() )
  {
    vTaskDelay( 250 );
  }
  xEventGroupSetBits( eg, evtWaitForBME );
  String bmeInfo = "";
  bmeInfo.reserve( 100 );
  for (;;)
  {
    xEventGroupWaitBits (eg, evtDoBME, pdTRUE, pdTRUE, portMAX_DELAY );
    x_eData.Temperature  = bme.readTemperature();
    x_eData.Temperature  = ( x_eData.Temperature * 1.8f ) + 32.0f; // (Celsius x 1.8) + 32
    x_eData.Pressure     = bme.readPressure();
    x_eData.Pressure     = x_eData.Pressure / 133.3223684f; //converts to mmHg
    x_eData.Humidity     = bme.readHumidity();
    x_eData.IAQ          = fCalulate_IAQ_Index( bme.readGas(), x_eData.Humidity );
    //log_i( " temperature % f, Pressure % f, Humidity % f IAQ % f", x_eData.Temperature, x_eData.Pressure, x_eData.Humidity, x_eData.IAQ);
    bmeInfo.concat( String(x_eData.Temperature, 2) );
    bmeInfo.concat( "," );
    bmeInfo.concat( String(x_eData.Pressure, 2) );
    bmeInfo.concat( "," );
    bmeInfo.concat( String(x_eData.Humidity, 2) );
    bmeInfo.concat( "," );
    bmeInfo.concat( String(x_eData.IAQ, 2) );
    xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY );
    if ( MQTTclient.connected() )
    {
      MQTTclient.publish( topicInsideInfo, bmeInfo.c_str() );
    }
    xSemaphoreGive( sema_MQTT_KeepAlive );
    xSemaphoreGive( sema_PublishPM ); // release publish of dust density
    xSemaphoreTake( sema_mqttOK, portMAX_DELAY );
    mqttOK ++;
    xSemaphoreGive( sema_mqttOK );
    xEventGroupSetBits( eg, evtDisplayUpdate );
    bmeInfo = ""; // empty the string buffer
    findDewPointWithHumidity( x_eData.Humidity, x_eData.Temperature );
    xEventGroupSetBits( eg, evtStoreAirPressure );
    // log_i( "DoTheBME280Thing high watermark % d",  uxTaskGetStackHighWaterMark( NULL ) );
  }
  vTaskDelete ( NULL );
}
////
/*
  Important to not set vTaskDelay/vTaskDelayUntil to less then 10. Errors begin to develop with the MQTT and network connection.
  makes the initial wifi/mqtt connection and works to keeps those connections open.
*/
void MQTTkeepalive( void *pvParameters )
{
  sema_MQTT_KeepAlive   = xSemaphoreCreateBinary();
  xSemaphoreGive( sema_MQTT_KeepAlive ); // found keep alive can mess with a publish, stop keep alive during publish
  MQTTclient.setKeepAlive( 90 ); // setting keep alive to 90 seconds makes for a very reliable connection, must be set before the 1st connection is made.
  TickType_t xLastWakeTime = xTaskGetTickCount();
  const TickType_t xFrequency = 250; //delay for ms
  for (;;)
  {
    //check for a is-connected and if the WiFi 'thinks' its connected, found checking on both is more realible than just a single check
    if ( (wifiClient.connected()) && (WiFi.status() == WL_CONNECTED) )
    {
      xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY ); // whiles MQTTlient.loop() is running no other mqtt operations should be in process
      MQTTclient.loop();
      xSemaphoreGive( sema_MQTT_KeepAlive );
    }
    else {
      log_i( "MQTT keep alive found MQTT status % s WiFi status % s", String(wifiClient.connected()), String(WiFi.status()) );
      if ( !(wifiClient.connected()) || !(WiFi.status() == WL_CONNECTED) )
      {
        connectToWiFi();
      }
      connectToMQTT();
    }
    //log_i( " high watermark % d",  uxTaskGetStackHighWaterMark( NULL ) );
    xLastWakeTime = xTaskGetTickCount();
    vTaskDelayUntil( &xLastWakeTime, xFrequency );
  }
  vTaskDelete ( NULL );
}
////
void connectToMQTT()
{
  byte mac[5]; // create client ID from mac address
  WiFi.macAddress(mac); // get mac address
  String clientID = String(mac[0]) + String(mac[4]) ; // use mac address to create clientID
  while ( !MQTTclient.connected() )
  {
    MQTTclient.connect( clientID.c_str(), mqtt_username, mqtt_password );
    vTaskDelay( 250 );
  }
  MQTTclient.setCallback ( mqttCallback );
  MQTTclient.subscribe   ( topicOK );
  MQTTclient.subscribe   ( topicRemainingMoisture_0 );
  MQTTclient.subscribe   ( topicWindSpeed );
  MQTTclient.subscribe   ( topicWindDirection );
  MQTTclient.subscribe   ( topicDPnWI );
  MQTTclient.subscribe   ( topicOutsideTemperature );
  MQTTclient.subscribe   ( topicOutsideHumidity );
  MQTTclient.subscribe   ( topicOutsidePressure );
} //void connectToMQTT()
void connectToWiFi()
{
  int TryCount = 0;
  while ( WiFi.status() != WL_CONNECTED )
  {
    TryCount++;
    WiFi.disconnect();
    WiFi.begin( SSID, PASSWORD );
    vTaskDelay( 4000 );
    if ( TryCount == 10 )
    {
      ESP.restart();
    }
  }
  WiFi.onEvent( WiFiEvent );
}
////
float findDewPointWithHumidity( float humi, float temperature )
{
  //Celcius
  float ans =  (temperature - (14.55 + 0.114 * temperature) * (1 - (0.01 * humi)) - pow(((2.5 + 0.007 * temperature) * (1 - (0.01 * humi))), 3) - (15.9 + 0.117 * temperature) * pow((1 - (0.01 * humi)), 14));
  //log_i( "%f", ans );
  return ans;
}
////
void addraindrop(int x, int y, int scale)
{
  display.fillCircle(x, y, scale / 2, GxEPD_BLACK);
  display.fillTriangle(x - scale / 2, y, x, y - scale * 1.2, x + scale / 2, y , GxEPD_BLACK);
  x = x + scale * 1.6; y = y + scale / 3;
  display.fillCircle(x, y, scale / 2, GxEPD_BLACK);
  display.fillTriangle(x - scale / 2, y, x, y - scale * 1.2, x + scale / 2, y , GxEPD_BLACK);
}
////
void addsun(int x, int y, int scale, bool IconSize)
{
  int linesize = 3;
  if (IconSize == SmallIcon) linesize = 1;
  display.fillRect(x - scale * 2, y, scale * 4, linesize, GxEPD_BLACK);
  display.fillRect(x, y - scale * 2, linesize, scale * 4, GxEPD_BLACK);
  display.drawLine(x - scale * 1.3, y - scale * 1.3, x + scale * 1.3, y + scale * 1.3, GxEPD_BLACK);
  display.drawLine(x - scale * 1.3, y + scale * 1.3, x + scale * 1.3, y - scale * 1.3, GxEPD_BLACK);
  if (IconSize == LargeIcon) {
    display.drawLine(1 + x - scale * 1.3, y - scale * 1.3, 1 + x + scale * 1.3, y + scale * 1.3, GxEPD_BLACK);
    display.drawLine(2 + x - scale * 1.3, y - scale * 1.3, 2 + x + scale * 1.3, y + scale * 1.3, GxEPD_BLACK);
    display.drawLine(3 + x - scale * 1.3, y - scale * 1.3, 3 + x + scale * 1.3, y + scale * 1.3, GxEPD_BLACK);
    display.drawLine(1 + x - scale * 1.3, y + scale * 1.3, 1 + x + scale * 1.3, y - scale * 1.3, GxEPD_BLACK);
    display.drawLine(2 + x - scale * 1.3, y + scale * 1.3, 2 + x + scale * 1.3, y - scale * 1.3, GxEPD_BLACK);
    display.drawLine(3 + x - scale * 1.3, y + scale * 1.3, 3 + x + scale * 1.3, y - scale * 1.3, GxEPD_BLACK);
  }
  display.fillCircle(x, y, scale * 1.3, GxEPD_WHITE);
  display.fillCircle(x, y, scale, GxEPD_BLACK);
  display.fillCircle(x, y, scale - linesize, GxEPD_WHITE);
}
////
void loop() { }
1 Like

It looks like you're buling weather station using ESP32 and you are using FreeRTOS for this. you are using MQTT communication protocoal for project

Can you describe functional part of your project ?

I found out some functional part

You are displaying Temperature
You are displaying Humidity
You are displaying Pressure

Well there are the functions to connect to the network and maintain a open network connection, there are the functional parts to collect data, the functional parts, which I am still working on, to display data, there is the functional part to receive data from the mqtt broker, a functional part to parse the data, and many more functional parts.

You've got freeRTOS code to turn-in for your homework assignment, I'm not going to do the write up for you.

It's not homework by the way and I don't need code right now.

I'm just trying to figure out how you splits system tasks into rtos tasks, so I chose your example to get idea.

I'm trying to figure out what are hard real time , soft real time and non real task's in your project.

This project is from an indoor display unit that has some measurement capabilities. There is an ESP32 outside in the snow sending data to a MQTT Broker. The data from the Broker gets distributed. One of the end points of the data distribution is the project from above, the display.

The task xTaskCreatePinnedToCore( MQTTkeepalive, "MQTTkeepalive", 5000, NULL, 6, NULL, 1 ); starts the show off. The keepalive task when first ran detects no network and no MQTT connection and the first thing that task does is connect. Upon connecting comes a subscription to the MQTT Broker.

Data comes pouring in as its updated through the MQTT callback, the data gets parsed and stored in the x_eData structure. Some of the data being received is arriving at 11 times a second. The receipt, keep alive and parsing of the MQTT broker data is one independent process, a functional block.

Next you may have noticed the

#define evtStoreAirPressure   ( 1 << 0 )
#define evtWaitForBME         ( 1 << 1 )
#define evtParseMQTT          ( 1 << 3 )
#define evtDisplayUpdate      ( 1 << 4 )
#define evtDoBME              ( 1 << 5 )
#define OneMinuteGroup ( evtDoBME )
EventGroupHandle_t eg;

Where some event groups are setup. Notice the OneMinuteGroup? The OneMinuteGroup event is triggered by the void IRAM_ATTR onTimer() function. When the event is triggered from the timer function then the BME is read which triggers the display to data update.

The weather station sitting in the yard under the snow is busy with doing things. The solarcalculations, which run according to their own timings, calculates some info and publishes that info for the outside weather stations use.

Let me know what grade you get.

1 Like

Are you calculating the weather probability ?

You put ESP32 outside your home. What device do you have indoor where you receive data from ESP32 ?

it's important to understand the purpose of your project first. What are benefits of using freeRTOS insted of bare metal coding ?

Yes.

The Raspberry Pi, one of its functions, is doing predictive calculations on the information sent to it by the various ESP32's connected to the RPi's MQTT Broker. The RPi is using Linear Regression, TensorFlow, and KNN ML algorithms to do predictive calculations.

Raspberry Pi.

The purpose of the project is to occupy my time.

Why not use freeRTOS on a ESP32, when an ESP32 boots freeRTOS as its internal OS. Meaning I am, with an ESP32, more 'bare metal' then if I used the Arduino Core.

1 Like

As I understand freeRTOS is real time opearting system and we use it wherever there is need guraanteed responce time. In the RTOS project we need to first tasks divde into priority tasks, hard real time tasks, soft real time task, non real time task

In generic weather station I don't understand what are real time tasks. Can you tell me which tasks are highy priority, hard real time , soft real time , non real time task's in your project ?

who cares if you read the temperature in 10 seconds or 10.07 seconds, or maybe even miss one reading ?

I want to use freeRTOS, My use of freeRTOS is because I want to use it. freeRTOS comes with a ESP32, I want to use as many features of the ESP32 that I can.

Again, I want to use freeRTOS, just like you want to ask me a bunch of questions as to why I am using freeRTOS. I want to use freeRTOS. Got it?


xTaskCreatePinnedToCore( fparseMQTT, "fparseMQTT", 7000, NULL, 5, NULL, 1 );
xTaskCreatePinnedToCore( MQTTkeepalive, "MQTTkeepalive", 5000, NULL, 6, NULL, 1 );
xTaskCreatePinnedToCore( DoTheBME680Thing, "DoTheBME280Thing", 20000, NULL, 5, NULL, 1);
xTaskCreatePinnedToCore( fmqttWatchDog, "fmqttWatchDog", 5000, NULL, 3, NULL, 1 );
xTaskCreatePinnedToCore( fDoTheDisplayThing, "fDoTheDisplayThing", 30000, NULL, 3, NULL, 1 );
xTaskCreatePinnedToCore( fGetCO2, "fGetCO2", 4500, NULL, 2, NULL, 1 );
xTaskCreatePinnedToCore( fParseDewPointWindChill, "fParseDewPointWindChill", 4500, NULL, 2, NULL, 1 );
xTaskCreatePinnedToCore( fSolarCalculations, "fSolarCalculations", 10000, NULL, 2, NULL, 1 );
xTaskCreatePinnedToCore( fStoreAirPressure, "fStoreAirPressure", 10000, NULL, 2, NULL, 1 );

You can see the tasks priority in the above code that I have already posted.

Got it. I appricate your work

I can see that you have divided program is divided into separate threads. I will try to figure out what ecah thread does and how it does

Threads
fparseMQTT
MQTTkeepalive
DoTheBME680Thing
fmqttWatchDog
fDoTheDisplayThing
fGetCO2
fParseDewPointWindChill
fSolarCalculations
fStoreAirPressure

MQTTkeepalive is high priority task that scheduler run first

Hi @Rehan11

The advantage of using a RTOS, is that it allows the programmer more control over systems with moderate to high complexity.

A RTOS uses a scheduler that allows tasks to be prioritised, such that those with the most important and timely requirements are processed first. The scheduler also allows multi-tasking between tasks, using time-slicing to periodically switch between them.

External hardware interrupts from either pin or peripheral, provide another opportunity for the scheduler to switch tasks. The hardware interrupt service routine (ISR) need only notify the RTOS and in response the scheduler will switch to a high priority task to service it. This also has the additional benefit of keeping ISRs very short.

Inter-task communication is provided by constructs such as queues, binary/counting semaphores, event groups and task notifications. The receiving task can choose to either block, time-out, or continue without waiting.

FreeRTOS also provides auto and oneshot software timers for code that needs to be run periodically, which can be really handy.

Another benefit is that whenever the RTOS runs out of things to do, it runs the idle task. As well as performing housekeeping duties, it also provides the opportunity to run your own custom code in the idle task hook function. This can be used to put microcontroller into idle or power-down sleep modes.

The main disadvantage (if task preemption is turned-on), is that you need to be constantly mindful of the tasks' concurrent nature and what this entails in terms of resource sharing, read-modify-write operations, non-atomic access to variables and function re-entrancy.

Here's an excellent tutorial on FreeRTOS:

Here's the reference guide:

I'd start by downloading the FreeRTOS library for your target system and have a look at the example code provided. This will give you a feel for how the RTOS's tasks and timers function.

reading the book says "at some point, programming sequentially , you are also doing what RTOS does.. by polling states, inputs and time division the output as well..."
hehe
Im also getting into it. very interested btw.
cheers

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.