How to create a global structure (struct), to store all the data printed to the serial port?

This is an excellent and popular definition of a conglomerate type database (structure) that we can create using struct keyword.

I don't know how popular it is :slight_smile:

From my classroom experience.

Your single line definition contains the important features of a structure -- user-defined type and the tag (typename).

my 15 seconds of fame then :slight_smile:

1 Like

No, your line is not correct. Think of e.g. a pixel on a screen; its position is defined in e.g.

struct POINT
{
  int x;
  int y;
};

Both types are the same.

The definition of a structure could be written as:

A structure is a way to group together data items/variables of similar/dissimilar types.

You're missing the point that they should be related.

There is nothing between similar and dissimilar, so that part of your phrase is irrelevant.

My book/work based knowledge is up to that on the definition of a structure type database that I create using struct keyword.

post #20 @J-M-L has enumerated the definition of a structure covering wider scope.

Your example of post #25 is a structure that contains two members of similar types.

struct POINT
{
  int x;
  int y;
};

The following is an example of a structure that contains 3 members of dissimilar types:

struct Data
{
  int state;
  float frequency;
  char myData[5];
};

do you mean that in my code, task4 has missed a bunch of value changes that have occurred inside the struct, as task4 happens so infrequently compared to the other tasks? and in order to print every value change that has occurred in the struct, a semaphore/ mutex must be used.

here is an example with a Mutex when you access the critical resource that is the current slot in the array.

#if CONFIG_FREERTOS_UNICORE
static const BaseType_t app_cpu = 0;
#else
static const BaseType_t app_cpu = 1;
#endif

struct t_Record {
  byte taskID;
  long randomValue;
  unsigned long chrono;
} ;

const size_t maxRecords = 100;
t_Record records[maxRecords];
size_t currentRecord = 0;

SemaphoreHandle_t recordMutex = nullptr;

void Task1(void *argp) {
  size_t myIndex;
  while (true) {
    if (xSemaphoreTake(recordMutex, 10) == pdTRUE )  {
      if (currentRecord < maxRecords) {
        myIndex = currentRecord++;
        records[myIndex].taskID = 1;
        records[myIndex].randomValue = random(-1000, 1001);
        records[myIndex].chrono = millis();
      }
      xSemaphoreGive(recordMutex);
    }
    vTaskDelay(20); //execute this task every 20ms
  }
}

void Task2(void *argp) {
  size_t myIndex;
  while (true) {
    if (xSemaphoreTake(recordMutex, 10) == pdTRUE )  {
      if (currentRecord < maxRecords) {
        myIndex = currentRecord++;
        records[myIndex].taskID = 2;
        records[myIndex].randomValue = random(-2000, 2001);
        records[myIndex].chrono = millis();
      }
      xSemaphoreGive(recordMutex);
    }
    vTaskDelay(60); //execute this task every 60ms
  }
}


void setup() {
  Serial.begin(115200); Serial.println();
  recordMutex = xSemaphoreCreateMutex();

  // Task1 to run forever
  xTaskCreatePinnedToCore(
    Task1,        // Function to be called
    "Task1",      // Name of task
    1024,         // Stack size (bytes in ESP32, words in FreeRTOS)
    NULL,         // Parameter to pass to function
    1,            // Task priority (0 to configMAX_PRIORITIES - 1)
    NULL,         // Task handle
    app_cpu);     // Run on one core

  // Task2 to run forever
  xTaskCreatePinnedToCore(
    Task2,        // Function to be called
    "Task2",      // Name of task
    1024,         // Stack size (bytes in ESP32, words in FreeRTOS)
    NULL,         // Parameter to pass to function
    1,            // Task priority (0 to configMAX_PRIORITIES - 1)
    NULL,         // Task handle
    app_cpu);     // Run on one core
}

void loop() {
  static size_t lastIndex = 0;
  size_t currentRecordCopy = 0;

  if (xSemaphoreTake(recordMutex, 10) == pdTRUE )  {
    currentRecordCopy = currentRecord;
    xSemaphoreGive(recordMutex);
    if (lastIndex != currentRecordCopy) {
      for (size_t i = lastIndex; i < currentRecordCopy; i++) {
        Serial.print(i); Serial.write('\t');
        Serial.print(records[i].taskID); Serial.write('\t');
        Serial.print(records[i].randomValue); Serial.write('\t');
        Serial.println(records[i].chrono);
      }
    }
    lastIndex = currentRecordCopy;
  }
}

totally untested, so not sure this works but that should give you an idea

1 Like

How do you categorise these circled keywords? I don't know what they are. At first, I thought it was a datatype but it can't be because sometimes they have a datatype assigned to them e.g. 'const'.


I'd appreciate it if someone can explain them to me.
Thank you :slight_smile:

you could have asked the question in the other thread. ➜ merging (and answering there)

So those are indeed types - either part of standard C++ like size_t (std::size_t - cppreference.com) or custom types offered by the Espressif or FreeRTOS APIs. for example BaseType_t is defined in task.h as

typedef BaseType_t (*TaskHookFunction_t)( void * );

same goes for the semaphore stuff, it's part of the API.

Reading the doc on the mutex or semaphore would give you the high level view on what you need to know and how to use those.

My 2 cents

Queue handle and structure

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;
  int    cngPress    = 0; // pressure change 0= no change, -1 slow change +1 fast change
} x_eData; // environmental data

In setup() the queue is assigned a pointer and configured to xQ_eData = xQueueCreate( 1, sizeof(stu_eData) ); send a queue copy of the structure

This task writes directly into the structure created in global memory. Note the use of a semaphore to prevent all other access to the structure during structure access, xSemaphoreTake ( sema_eData, portMAX_DELAY );.

void fFindDewPointWithHumidity( void *pvParameters )
{
  float temperature = 0.0f;
  for ( ;; )
  {
    xEventGroupWaitBits (eg, evtDewPoint, pdTRUE, pdTRUE, portMAX_DELAY );
    temperature = (x_eData.oTemperature - 32) / 1.8f; // Celsius (°C) = (Fahrenheit - 32) / 1.8  convert to C
    xSemaphoreTake( sema_eData, portMAX_DELAY );
    x_eData.DewPoint = log(x_eData.oHumidity / 100) + (17.62 * temperature) / (243.12 + temperature);
    x_eData.DewPoint =  243.12 * x_eData.DewPoint / (17.62 - x_eData.DewPoint);
    x_eData.DewPoint = (x_eData.DewPoint * 1.8f) + 32.0f; // convert back to F
    xSemaphoreGive ( sema_eData );
  }
  vTaskDelete( NULL );
}

This task uses a copy of the structure to access data that may be changing. Note how the structure is prevented from being updated during the actual structure copy, xSemaphoreTake( sema_eData, portMAX_DELAY ); with a semaphore.

void fDoTheDisplayThing( void * parameter )
{
  float *ptr = CollectionPressure;
  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 );
    xSemaphoreTake( sema_eData, portMAX_DELAY );
    struct stu_eData px_eData = x_eData;
    xSemaphoreGive ( sema_eData );
    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( char(223) + "F" );
    display.print( "F" );
    display.setCursor( CurrentX + 10, CurrentY + 40);
    display.print( String(px_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)px_eData.oHumidity) + "%" );
    display.setCursor( CurrentX, CurrentY + 40);
    display.drawRect( CurrentX + (boxSpacing * 2 ), CurrentY , 70, 55, GxEPD_BLACK);
    display.setCursor( CurrentX + 163, CurrentY + 15 );
    display.print( "Dew Pt" );
    display.setCursor( CurrentX + 165, CurrentY + 35 );
    display.print( String(px_eData.DewPoint) );
    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(px_eData.IAQ)) + "%" );
    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(px_eData.RM0)) + "%" );
    // end of first line
    if ( px_eData.SunRiseMin < 10 )
    {
      temp1.concat( "0" + String(px_eData.SunRiseMin) );
    } else {
      temp1.concat( String(px_eData.SunRiseMin) );
    }
    if ( px_eData.SunSetMin < 10 )
    {
      temp2.concat( "0" + String(px_eData.SunSetMin) );
    } else {
      temp2.concat( String(px_eData.SunSetMin) );
    }
    CurrentY += yIncrement;
    CurrentY += yIncrement;
    CurrentY += yIncrement;
    CurrentY += yIncrement;
    display.setCursor( CurrentX, CurrentY );
    display.print( "Wind: " );
    CurrentY += yIncrement;
    display.setCursor( CurrentX, CurrentY );
    display.print( "Speed " + String(px_eData.WS) + "KPH, Dir " + String(px_eData.WD) + " Chill " + String(px_eData.WindChill) + "F" );
    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(px_eData.SunRiseHr) + ":" + temp1 );
    display.setCursor( CurrentX + 5, CurrentY + 50 );
    display.print( String(px_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(px_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(px_eData.CO2)) );
    display.setCursor( CurrentX + 165, CurrentY + 50 );
    display.print( "PPM" );
    //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 = (int)CollectionPressure[0];
    int BaseLine = (int)*ptr;
    int offsetX = 0;
    for ( int j = 0; j < BufferCount; j++ )
    {
      if ( *(ptr + j) != 0.0f )
      {
        //int yAdj = BaseLine - (int)CollectionPressure[j];
        int yAdj = BaseLine - (int)*(ptr + j);
        display.setCursor( CurrentX + offsetX, CurrentY + yAdj );
        display.print( "-" );
        offsetX += 5;
        // log_i( "pressure %f item %d", CollectionPressure[j], j );
      }
    }
    CurrentY += yIncrement;
    display.setCursor( CurrentX, CurrentY );
    display.print( String(px_eData.oPressure) + "mmHg" );
    int Xone = 48;
    int Yone = 59;
    CurrentY += yIncrement;
    display.setCursor( CurrentX, CurrentY );
    display.print( PressureRateOfChange() );
    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 )

Oh and see how the display task takes info from the copied structure and uses that info to be displayed? Sort of like printing to the Serial monitor. While the copied structure is being displayed the other tasks can be updating the global structure.

The right-most word prior to the variable name is the data type.
Words like unsigned, long, short, double etc. are type modifiers.
Words like const, volatile and mutable are type qualifiers.
Worlds like static, extern, register are storage classes.

1 Like

Another version with semaphore and queue. I think it accomplishes the intent of OP's Post #17 --- although that intent still seems rather odd to me.

Compiles but untested:

#include "Arduino.h"

SemaphoreHandle_t recordMutex;
xQueueHandle recordQueue;

struct Struct_Data {
	int frequency; //Task2's variable
	unsigned short int avg_val; //Task3's variable
};

Struct_Data loggedInfo;

void Task1(void *argp) {
	int buttom_state;
	Struct_Data localInfo;
	while (1) {
		buttom_state = rand() % 2; //generate a random int between 0 and 1 (i.e. 0 or 1)
		if (buttom_state == 1) {
			xSemaphoreTake(recordMutex, portMAX_DELAY);
			localInfo = loggedInfo;
			xSemaphoreGive(recordMutex);
			xQueueSendToBack(recordQueue, &localInfo, portMAX_DELAY);
		}
		vTaskDelay(200); //execute this task every 200ms
	}
}

void Task2(void *argp) {
	int anlg_freq;
	while (1) {
		xSemaphoreTake(recordMutex, portMAX_DELAY);
		anlg_freq = 500 + (rand() % (1000 - 500 + 1)); //randomly set frequency to an int between 500 to 1000
		loggedInfo.frequency = anlg_freq;
		xSemaphoreGive(recordMutex);
		vTaskDelay(1000); //execute this periodic task every second
	}
}

void Task3(void *argp) {
	int filter_analg_val;
	int new_analg_val;
	while (1) {
		xSemaphoreTake(recordMutex, portMAX_DELAY);
		new_analg_val = 0;
		for (int i = 0; i <= 3; i++) { //take 4 readings
			int analg_val = 0 + (rand() % (100 - 0 + 1)); //randomly set analg_val to an int between 0 to 100
			new_analg_val = new_analg_val + analg_val;
		}
		filter_analg_val = new_analg_val / 4; //average of the last 4 readings
		loggedInfo.avg_val = filter_analg_val;
		xSemaphoreGive(recordMutex);
		vTaskDelay(40); //execute this task every 40ms
	}
}

//Task4 - print values to the serial port ONLY when state=1;
void Task4(void *argp) {
	Struct_Data localInfo;
	while (1) {
		xQueueReceive(recordQueue, &localInfo, portMAX_DELAY);
		Serial.print(localInfo.frequency);
		Serial.print(",");
		Serial.println(localInfo.avg_val);
	}
}

void setup() {
	Serial.begin(115200);
	delay(1000);

	recordMutex = xSemaphoreCreateMutex();
	xSemaphoreGive(recordMutex);

	recordQueue = xQueueCreate(10, sizeof(Struct_Data));

	// Task1 to run forever
	xTaskCreatePinnedToCore(
			Task1, // Function to be called
			"Task1", // Name of task
			1024, // Stack size (bytes in ESP32, words in FreeRTOS)
			NULL, // Parameter to pass to function
			1, // Task priority (0 to configMAX_PRIORITIES - 1)
			NULL, // Task handle
			ARDUINO_RUNNING_CORE); // Run on one core

	// Task2 to run forever
	xTaskCreatePinnedToCore(
			Task2, // Function to be called
			"Task2", // Name of task
			1024, // Stack size (bytes in ESP32, words in FreeRTOS)
			NULL, // Parameter to pass to function
			1, // Task priority (0 to configMAX_PRIORITIES - 1)
			NULL, // Task handle
			ARDUINO_RUNNING_CORE); // Run on one core

	// Task3 to run forever
	xTaskCreatePinnedToCore(
			Task3, // Function to be called
			"Task3", // Name of task
			1024, // Stack size (bytes in ESP32, words in FreeRTOS)
			NULL, // Parameter to pass to function
			1, // Task priority (0 to configMAX_PRIORITIES - 1)
			NULL, // Task handle
			ARDUINO_RUNNING_CORE); // Run on one core

	// Task4 to run forever
	xTaskCreatePinnedToCore(
			Task4, // Function to be called
			"Task4", // Name of task
			1024, // Stack size (bytes in ESP32, words in FreeRTOS)
			NULL, // Parameter to pass to function
			1, // Task priority (0 to configMAX_PRIORITIES - 1)
			NULL, // Task handle
			ARDUINO_RUNNING_CORE); // Run on one core

}
void loop() {
	vTaskDelete(NULL);
}
2 Likes

Hey. Good job with the queue and semaphore use.

a bit more complicated than that, for example const char * const myPtr;

you read right to left so this is a constant pointer (*) to a character that is constant

Hmm, didn't see that one in the screenshot.

OK - missed the point that you were specific to @cinnamonroll's code.
makes sense in that context.