# How to make tasks and determine stack size in freeRTOS?

**URL:** <https://forum.arduino.cc/t/how-to-make-tasks-and-determine-stack-size-in-freertos/978325>\
**Category:** Programming\
**Created:** [April 7, 2022, 11:45am UTC](https://forum.arduino.cc/t/how-to-make-tasks-and-determine-stack-size-in-freertos/978325 "2022-04-07T11:45:18Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![cinnamonroll](https://avatars.discourse-cdn.com/v4/letter/c/ea666f/32.png) [@cinnamonroll](https://forum.arduino.cc/u/cinnamonroll)\
**Post date:** [April 7, 2022, 11:45am UTC](https://forum.arduino.cc/t/how-to-make-tasks-and-determine-stack-size-in-freertos/978325/1 "2022-04-07T11:45:18Z")

</div>

Hello,  
I don't think I'm fully understanding how to create tasks in freeRTOS. So I made a very simple scenario to help me understand the structure:

- 
  1. I have two LEDs (red and green),

- 
  1. I want red to turn on for 1 second, every 40 milliseconds,

- 
  1. I want the green to turn on for 1 second, every 1000 milliseconds.

I wrote this code for it:

```cpp
/*
 * Trying to understand how to create a task using freeRTOS
 */

// Use only core 1 to implement everything on the single core
#if CONFIG_FREERTOS_UNICORE
static const BaseType_t app_cpu = 0;
#else
static const BaseType_t app_cpu = 1;
#endif

byte red_GPIO = 15; //LED pin
byte green_GPIO = 16; //LED pin

//Task1
void Task1() {
  digitalWrite(red_GPIO, HIGH);
  vTaskDelay(1000); //pulse width
  digitalWrite(red_GPIO, LOW);
}

void Task2() {
  digitalWrite(green_GPIO, HIGH);
  vTaskDelay(1000); //pulse width
  digitalWrite(green_GPIO, LOW);
}

//the task is to run the task1 function
void RUNtask1(void *argp) { 
  while (1)
  {
    Task1();
    vTaskDelay(40); //The system must executes the following periodic task at the specified period
  }
}

//the task is to run the task2 function
void RUNtask2(void *argp) { 
  while (1)
  {
    Task2();
    vTaskDelay(1000); //The system must executes the following periodic task at the specified period
  }
}

void setup() {

  Serial.begin(9600);
  pinMode(red_GPIO, OUTPUT);
  pinMode(green_GPIO, OUTPUT);

// Task1 to run forever
  xTaskCreatePinnedToCore(
    RUNtask1, // 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(
    RUNtask2, // 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() {
  // put your main code here, to run repeatedly:

}

```

Looking at the LEDs, it looks like it is doing what it is supposed to BUT I would like to weather my code is the correct or not because there are a few things I am unsure of:

1. Do I have to make another function, in this case, RUNtask1, just to run the task 1 function OR can I do everything in the task1 function? like this:

```cpp
/*
 * Trying to understand how to create a task using freeRTOS
 */

// Use only core 1 to implement everything on the single core
#if CONFIG_FREERTOS_UNICORE
static const BaseType_t app_cpu = 0;
#else
static const BaseType_t app_cpu = 1;
#endif

byte red_GPIO = 15; //LED pin
byte green_GPIO = 16; //LED pin

//Task1
void Task1(void *argp) {
    while (1)
  {
  digitalWrite(red_GPIO, HIGH);
  vTaskDelay(1000); //pulse width
  digitalWrite(red_GPIO, LOW);
  vTaskDelay(40);
  }
}

void Task2(void *argp) {
      while (1)
  {
  digitalWrite(green_GPIO, HIGH);
  vTaskDelay(1000); //pulse width
  digitalWrite(green_GPIO, LOW);
  vTaskDelay(1000);
}
}

void setup() {

  Serial.begin(9600);
  pinMode(red_GPIO, OUTPUT);
  pinMode(green_GPIO, OUTPUT);

// 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() {
  // put your main code here, to run repeatedly:

}

```

1. How on earth do I determine the stack size? I wrote this script to get information about the stack size of task 1:

```cpp
/*
   Trying to understand how to create a task using freeRTOS
*/

// Use only core 1 to implement everything on the single core
#if CONFIG_FREERTOS_UNICORE
static const BaseType_t app_cpu = 0;
#else
static const BaseType_t app_cpu = 1;
#endif

int counter;
byte red_GPIO = 15; //LED pin

//Task1
void Task1(int counter) {

  printf("Task1() called ... count = %d\n", counter);

  digitalWrite(red_GPIO, HIGH);
  vTaskDelay(1000); //pulse width
  digitalWrite(red_GPIO, LOW);
  vTaskDelay(40);
  unsigned int temp = uxTaskGetStackHighWaterMark(nullptr);
  printf("high stack water mark is %u\n", temp);
  Task1(counter + 1); //call the function inside itself
  printf("\n");

}

void CALLTask1(void *argp) {
  Task1(1);
}

void setup() {

  Serial.begin(9600);
  pinMode(red_GPIO, OUTPUT);

  // Task1 to run forever
  xTaskCreatePinnedToCore(
    CALLTask1, // 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

}
void loop() {
  // put your main code here, to run repeatedly:

}

```

and found out that task1 gets called 7 times before it runs out of stack, and at the end I am left with 32 stacks. I feel like that's useful information but I have no idea how I can use it to determine the correct number of stacks.

Some help and advice is highly appreciated! thank you 🙂

---

<div class="post-metadata">

**Author:** ![Perehama](https://dub1.discourse-cdn.com/arduino/user_avatar/forum.arduino.cc/perehama/32/256713_2.png) [@Perehama](https://forum.arduino.cc/u/Perehama)\
**Post date:** [April 7, 2022, 11:58am UTC](https://forum.arduino.cc/t/how-to-make-tasks-and-determine-stack-size-in-freertos/978325/2 "2022-04-07T11:58:03Z")

</div>

Hello. Welcome to the forum. What is your hardware setup? Why have you chosen freeRTOS for such a light program?

---

<div class="post-metadata">

**Author:** ![gfvalvo](https://dub1.discourse-cdn.com/arduino/user_avatar/forum.arduino.cc/gfvalvo/32/1013044_2.png) [@gfvalvo](https://forum.arduino.cc/u/gfvalvo)\
**Post date:** [April 7, 2022, 12:02pm UTC](https://forum.arduino.cc/t/how-to-make-tasks-and-determine-stack-size-in-freertos/978325/3 "2022-04-07T12:02:00Z")

</div>

Which "Arduino" board are you using? The two [.pdf files on this site](https://freertos.org/Documentation/RTOS_book.html) are pretty good references. However, there will be some differences between those documents and the FreeRTOS implementation on your actual target board.

---

<div class="post-metadata">

**Author:** ![gfvalvo](https://dub1.discourse-cdn.com/arduino/user_avatar/forum.arduino.cc/gfvalvo/32/1013044_2.png) [@gfvalvo](https://forum.arduino.cc/u/gfvalvo)\
**Post date:** [April 7, 2022, 12:09pm UTC](https://forum.arduino.cc/t/how-to-make-tasks-and-determine-stack-size-in-freertos/978325/4 "2022-04-07T12:09:17Z")

</div>

> [@cinnamonroll](#):
>
> I want red to turn on for 1 second, every 40 milliseconds,

That doesn't make sense. Perhaps you mean "turn on for 40 ms every second"?

> [@cinnamonroll](#):
>
> I want the green to turn on for 1 second, every 1000 milliseconds.

Same here. The led would always be on.

---

<div class="post-metadata">

**Author:** ![Idahowalker](https://dub1.discourse-cdn.com/arduino/user_avatar/forum.arduino.cc/idahowalker/32/238858_2.png) [@Idahowalker](https://forum.arduino.cc/u/Idahowalker)\
**Post date:** [April 7, 2022, 12:13pm UTC](https://forum.arduino.cc/t/how-to-make-tasks-and-determine-stack-size-in-freertos/978325/5 "2022-04-07T12:13:46Z")

</div>

First for safety set the initial task size to 10000.

In the task print the task sized using something like this, log\_i( "fDoTheHumidityThing high watermark %d", uxTaskGetStackHighWaterMark( NULL ) );. If not using ESP32 log printing, which is faster than serial print, then change log\_i to use serial prints.

```cpp
void fDoTheHumidityThing( void * parameter )
{
  for (;;)
  {
    xEventGroupWaitBits (eg, evtDoTheHumidityThing, pdTRUE, pdTRUE, portMAX_DELAY );
    vTaskDelay( 4 );
    xSemaphoreTake( sema_TheHumidityThing, portMAX_DELAY );
    if ( IntDataPtr[3] == 1 )
    {
            if ( IntDataPtr[2] + 1 < (int)ePtr[2] )
            {
              REG_WRITE(GPIO_OUT_W1TC_REG, BIT2);
            }
            if ( (IntDataPtr[2] - 1) > (int)ePtr[2] )
            {
              // energize humidifier
              REG_WRITE(GPIO_OUT_W1TS_REG, BIT2);
            }
          } else {
            //de energize humidifier
            REG_WRITE(GPIO_OUT_W1TC_REG, BIT2);
    }
    xSemaphoreGive( sema_TheHumidityThing );
    //log_i( "fDoTheHumidityThing high watermark %d", uxTaskGetStackHighWaterMark( NULL ) );
  }
  vTaskDelete( NULL );
} // void fDoTheHumidityThing( void * parameter )

```

Which will give you the current stack size. Let the task run for about a minute or so to make sure the task size has stabilized.

Adjust the task size to be about 2000 more bytes than reported.

---

<div class="post-metadata">

**Author:** ![Idahowalker](https://dub1.discourse-cdn.com/arduino/user_avatar/forum.arduino.cc/idahowalker/32/238858_2.png) [@Idahowalker](https://forum.arduino.cc/u/Idahowalker)\
**Post date:** [April 7, 2022, 12:17pm UTC](https://forum.arduino.cc/t/how-to-make-tasks-and-determine-stack-size-in-freertos/978325/6 "2022-04-07T12:17:51Z")

</div>

> [@cinnamonroll](#):
>
> ```arduino
> void CALLTask1(void *argp) {
> Task1(1);
> }
> 
> ```

That is not the proper way to trigger a task to run.

> [@Idahowalker](#):
>
> ```arduino
> }
> vTaskDelete( NULL );
> } //
> 
> ```

That auto kills a task that jumps its boundaries and should be included in all freeRTOS tasks.

---

<div class="post-metadata">

**Author:** ![Idahowalker](https://dub1.discourse-cdn.com/arduino/user_avatar/forum.arduino.cc/idahowalker/32/238858_2.png) [@Idahowalker](https://forum.arduino.cc/u/Idahowalker)\
**Post date:** [April 7, 2022, 12:20pm UTC](https://forum.arduino.cc/t/how-to-make-tasks-and-determine-stack-size-in-freertos/978325/7 "2022-04-07T12:20:39Z")

</div>

> [@cinnamonroll](#):
>
> ```arduino
> void Task1(int counter) {
> 
> printf("Task1() called ... count = %d\n", counter);
> 
> digitalWrite(red_GPIO, HIGH);
> vTaskDelay(1000); //pulse width
> digitalWrite(red_GPIO, LOW);
> vTaskDelay(40);
> unsigned int temp = uxTaskGetStackHighWaterMark(nullptr);
> printf("high stack water mark is %u\n", temp);
> Task1(counter + 1); //call the function inside itself
> printf("\n");
> 
> }
> 
> ```

That is not a properly formatted task and will exceeded its task stack space.

A properly formatted freeRTOS task that blinks an LED

```arduino
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "freertos/event_groups.h"

#define ESP32_WROVER_LED1 27 // LED D4
#define ESP32_WROVER_LED2 0 // LED D3
#define ESP32_LED 2
#define TaskStack10K 10000
#define TaskCore1 1
#define TaskCore0 0
#define Priority2 2
#define Priority3 3
#define Priority4 4
#define TestPin 4
#define OneK 1000

void setup() 
{
  Serial.begin(115200);
pinMode (ESP32_LED, OUTPUT);
pinMode ( TestPin, OUTPUT);
 xTaskCreatePinnedToCore( fBlinkBuiltIn, "fBlinkBuiltIn", TaskStack10K, NULL, Priority2, NULL, TaskCore1 ); //assigned to core 1
 
}
void loop() {}

void fBlinkBuiltIn( void* pvParameters )
{
  // toggle built in LED off/on
  for (;;)
  {
    vTaskDelay( 10 );
    REG_WRITE( GPIO_OUT_W1TC_REG, BIT2 ); // GPIO2 LOW (clear)
    vTaskDelay( OneK );
    REG_WRITE( GPIO_OUT_W1TS_REG, BIT2 ); //GPIO2 HIGH (set)
  }
  vTaskDelete( NULL );
}

```

As soon as a task has been created it begins to run.

---

<div class="post-metadata">

**Author:** ![gfvalvo](https://dub1.discourse-cdn.com/arduino/user_avatar/forum.arduino.cc/gfvalvo/32/1013044_2.png) [@gfvalvo](https://forum.arduino.cc/u/gfvalvo)\
**Post date:** [April 7, 2022, 12:46pm UTC](https://forum.arduino.cc/t/how-to-make-tasks-and-determine-stack-size-in-freertos/978325/8 "2022-04-07T12:46:20Z")

</div>

Have you looked at:

 ![image](https://europe1.discourse-cdn.com/arduino/original/4X/0/f/3/0f36080f3cf96d7e918efd0c1179513930fb3dd5.png)

---

<div class="post-metadata">

**Author:** ![Idahowalker](https://dub1.discourse-cdn.com/arduino/user_avatar/forum.arduino.cc/idahowalker/32/238858_2.png) [@Idahowalker](https://forum.arduino.cc/u/Idahowalker)\
**Post date:** [April 7, 2022, 12:50pm UTC](https://forum.arduino.cc/t/how-to-make-tasks-and-determine-stack-size-in-freertos/978325/9 "2022-04-07T12:50:46Z")

</div>

That LED blink task, post#7, was my first freeRTOS task I wrote. Why? To learn how to do it.

---

<div class="post-metadata">

**Author:** ![cinnamonroll](https://avatars.discourse-cdn.com/v4/letter/c/ea666f/32.png) [@cinnamonroll](https://forum.arduino.cc/u/cinnamonroll)\
**Post date:** [April 7, 2022, 1:11pm UTC](https://forum.arduino.cc/t/how-to-make-tasks-and-determine-stack-size-in-freertos/978325/10 "2022-04-07T13:11:55Z")

</div>

Thank you for the replies.  
@Perehama and @gfvalvo I am using ESP32. I need to implement freeRTOS for an assignment much more complex than my example here. I am trying to break down the fundamental features of freeRTOS (e.g. Tasks, queues, semaphores etc) separately by creating a simple program to understand how it works so I can do my assignment which includes all these features. In this example, I am trying to understand tasks. I have read up on it but I prefer to learn by doing.

@gfvalvo I mean, I want the rate at which task1 gets executed to be 40ms. so every 40 ms the red led gets turned on for 1 second. The rate at which task2 gets executed is 1 second so every 1 second, the green led gets turned on for 1 second. The green led would look like this: on for a second, off for a second.

@Idahowalker Thank you for the solutions, I will try them out when I get home and let you know how it goes.

---

<div class="post-metadata">

**Author:** ![Idahowalker](https://dub1.discourse-cdn.com/arduino/user_avatar/forum.arduino.cc/idahowalker/32/238858_2.png) [@Idahowalker](https://forum.arduino.cc/u/Idahowalker)\
**Post date:** [April 7, 2022, 1:15pm UTC](https://forum.arduino.cc/t/how-to-make-tasks-and-determine-stack-size-in-freertos/978325/11 "2022-04-07T13:15:24Z")

</div>

This task uses event triggers, a millis() way of timing, semaphores, queues and other freeRTOS things. Hope it helps as an example.

```cpp
/*
  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 evtDewPoint ( 1 << 6 )
#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;
  int cngPress = 0; // pressure change 0= no change, -1 slow change +1 fast change
} 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;
SemaphoreHandle_t sema_eData;
////
//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 );
  sema_eData = xSemaphoreCreateBinary();
  xSemaphoreGive ( sema_eData );
  //
  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( fProcessAirPressure, "fProcessAirPressure", 5000, NULL, 2, NULL, 1 );
  xTaskCreatePinnedToCore( fFindDewPointWithHumidity, "fFindDewPointWithHumidity", 5000, NULL, 2, NULL, 1 );
} //void setup()
////
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 );
}
////
String PressureRateOfChange ()
{
  String s = "";
  s.reserve( 10 );
  xSemaphoreTake ( sema_eData, portMAX_DELAY );
  switch ( x_eData.cngPress )
  {
    case 0:
      s = "Slow";
      break;
    case 1:
      s = "Unsettled";
      break;
    case 2:
      s = "Fast";
      break;
    case 3:
      s = "Steady";
      break;
  }
  xSemaphoreGive ( sema_eData );
  return s;
}
/*
   Approaching storms and wind cause barometric pressure to decrease.
   Rising pressure indicates fair weather.
   The longer it takes barometric pressure to change,
   the longer the coming weather pattern can be expected to last.
   It is possible that a small weather event, such as a passing shower, may trigger no change in barometric pressure.
   slow rate of change .0762mmHg - 1.016mmHg < 3 hours
   fast rate of change 4.572mmHg in < 3 hours
   steady <=.0762Hg in <= 3 hours
*/
int CalculatePressureFactors( float pastPressure, float currentPressure )
{
  float rateChange = abs(pastPressure - currentPressure);
  //log_i ( " pressure rate of change %f", rateChange );
  if ( (rateChange >= .0762f) && (rateChange <= 1.016f ) )
  {
    //log_i( "slow %f", rateChange );
    return 0; //slow
  }
  if ( (rateChange > 1.016f) && (rateChange < 4.572f) )
  {
    //log_i( "unsettled %f", rateChange );
    return 1;
  }
  if ( rateChange >= 4.572f )
  {
    //log_i( "fast %f", rateChange );
    return 2;//fast
  }
  if ( rateChange <= .0762 )
  {
    //log_i( "steady %f", rateChange );
    return 3;//steady
  }
} //void CalculatePressureFactors()
////
void fProcessAirPressure ( void *pvParemeters )
{
  int cellCount = 58;
  int Ticks = 118;
  bool Filled = false;
  float *ptr = CollectionPressure;
  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 );
    xSemaphoreTake ( sema_eData, portMAX_DELAY );
    if ( !Filled )
    {
      for ( int j = 0; j < BufferCount; j++ )
      {
        *( ptr + j ) = x_eData.oPressure;
      }
      Filled = true;
    } else {
      if ( Ticks == ticksTrigger )
      {
        //shift contents left and insert new value at the end
        for ( int i = 0; i <= BufferCount - 2; i++ )
        {
          *( ptr + i ) = *( ptr + (i+1) );
          //CollectionPressure[i] = CollectionPressure[i + 1];
        }
      }
      *( ptr + (BufferCount - 1) ) = x_eData.oPressure;
      //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;
    }
    //x_eData.cngPress = CalculatePressureFactors( CollectionPressure[57], CollectionPressure[59] ); // going back 4 hours
    x_eData.cngPress = CalculatePressureFactors( *( ptr + 57), *( ptr + 59) ); // going back 4 hours
    xSemaphoreGive( sema_eData );
    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 );
      xSemaphoreTake( sema_eData, portMAX_DELAY );
      calcHorizontalCoordinates( rtc.getYear(), (rtc.getMonth() + monthX) , rtc.getDay(), rtc.getHour(true) , rtc.getMinute(), rtc.getSecond(), latitude, longitude, x_eData.azimuth, x_eData.elevation );
      xSemaphoreGive( sema_eData );
      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:
      xSemaphoreTake( sema_eData, portMAX_DELAY );
      x_eData.SunRiseHr = hours;
      x_eData.SunRiseMin = minutes;
      xSemaphoreGive( sema_eData );
      break;
    case 1:
      xSemaphoreTake( sema_eData, portMAX_DELAY );
      x_eData.SunSetHr = hours;
      x_eData.SunSetMin = minutes;
      xSemaphoreGive( sema_eData );
      break;
    case 2:
      xSemaphoreTake( sema_eData, portMAX_DELAY );
      x_eData.DawnHr = hours;
      x_eData.DawnMin = minutes;
      xSemaphoreGive( sema_eData );
      break;
    case 3:
      xSemaphoreTake( sema_eData, portMAX_DELAY );
      x_eData.DuskHr = hours;
      x_eData.DawnMin = minutes;
      xSemaphoreGive( sema_eData );
      break;
    case 4:
      xSemaphoreTake( sema_eData, portMAX_DELAY );
      x_eData.TransitHr = hours;
      x_eData.TransitMin = minutes;
      xSemaphoreGive( sema_eData );
      break;
    case 5:
      xSemaphoreTake( sema_eData, portMAX_DELAY );
      struct stu_eData px_eData = x_eData;
      xSemaphoreGive( sema_eData );
      String sTopic = "";
      sTopic.reserve( 35 );
      sTopic.concat( String(px_eData.SunRiseHr) + "," );
      sTopic.concat( String(px_eData.SunRiseMin) + "," );
      sTopic.concat( String(px_eData.SunSetHr) + "," );
      sTopic.concat( String(px_eData.SunSetMin) + "," );
      sTopic.concat( String(px_eData.DawnHr) + "," );
      sTopic.concat( String(px_eData.DawnMin) + "," );
      sTopic.concat( String(px_eData.TransitHr) + "," );
      sTopic.concat( String(px_eData.TransitMin) );
      xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY );
      MQTTclient.publish( topicSRSSDDT, sTopic.c_str() );
      xSemaphoreGive( sema_MQTT_KeepAlive );
      sTopic = "";
      px_eData.azimuth = double(round(px_eData.azimuth * 100)) / 100; // Round to two decimal places
      px_eData.elevation = double(round(px_eData.elevation * 100)) / 100;
      sTopic.concat( String(px_eData.azimuth) + "," + String(px_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) );
      xSemaphoreTake ( sema_eData, portMAX_DELAY );
      x_eData.WindChill = sWindChill.toFloat();
      xSemaphoreGive( sema_eData );
      xEventGroupSetBits( eg, evtDewPoint );
      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 );
  float temp = 0.0f;
  for ( ;; )
  {
    KF_CO2.setProcessNoise( (esp_timer_get_time() - TimePastKalman) / 1000000.0f );
    temp = KF_CO2.updateEstimate( myMHZ19.getCO2() ); // apply simple Kalman filter
    TimePastKalman = esp_timer_get_time();
    temp = round(temp);
    xSemaphoreTake ( sema_eData, portMAX_DELAY );
    x_eData.CO2 = temp;
    xSemaphoreGive ( sema_eData );
    xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY );
    MQTTclient.publish( topicCO2, String(temp).c_str() );
    xSemaphoreGive( sema_MQTT_KeepAlive );
    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 )
      {
        //xSemaphoreTake( sema_eData, portMAX_DELAY );
        x_eData.oPressure = String(px_message.payload).toFloat();
        //xSemaphoreGive ( sema_eData );
      }
      if ( px_message.topic == topicOutsideHumidity )
      {
        //xSemaphoreTake( sema_eData, portMAX_DELAY );
        x_eData.oHumidity = String(px_message.payload).toFloat();
        //xSemaphoreGive ( sema_eData );
      }
      if ( px_message.topic == topicOutsideTemperature )
      {
        //xSemaphoreTake( sema_eData, portMAX_DELAY );
        x_eData.oTemperature = String(px_message.payload).toFloat();
        //xSemaphoreGive ( sema_eData );
      }
      if ( px_message.topic == topicRemainingMoisture_0 )
      {
        //xSemaphoreTake( sema_eData, portMAX_DELAY );
        x_eData.RM0 = String(px_message.payload).toFloat();
        //xSemaphoreGive ( sema_eData );
      }
      if ( px_message.topic == topicWindSpeed )
      {
        //xSemaphoreTake( sema_eData, portMAX_DELAY );
        x_eData.WS = String(px_message.payload).toFloat();
        //xSemaphoreGive ( sema_eData );
      }
      if ( px_message.topic == topicWindDirection )
      {
        xSemaphoreTake( sema_eData, portMAX_DELAY );
        x_eData.WD = "";
        x_eData.WD = String(px_message.payload);
        xSemaphoreGive ( sema_eData );
      }
      if ( px_message.topic == topicRainfall )
      {
        xSemaphoreTake( sema_eData, portMAX_DELAY );
        x_eData.RF = String(px_message.payload).toFloat();
        xSemaphoreGive ( sema_eData );
      }

      // 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 )
{
  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 )
////
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 );
    xSemaphoreTake ( sema_eData, 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 );
    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) );
    xSemaphoreGive ( sema_eData );
    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 );
    bmeInfo = ""; // empty the string buffer
    xEventGroupSetBits( eg, evtStoreAirPressure );
    vTaskDelay( 50 );
    xEventGroupSetBits( eg, evtDisplayUpdate );
    // 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 );
  MQTTclient.subscribe ( topicRainfall );
} //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 );
}
////
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() { }

```

---

<div class="post-metadata">

**Author:** ![gfvalvo](https://dub1.discourse-cdn.com/arduino/user_avatar/forum.arduino.cc/gfvalvo/32/1013044_2.png) [@gfvalvo](https://forum.arduino.cc/u/gfvalvo)\
**Post date:** [April 7, 2022, 1:15pm UTC](https://forum.arduino.cc/t/how-to-make-tasks-and-determine-stack-size-in-freertos/978325/12 "2022-04-07T13:15:35Z")

</div>

> [@cinnamonroll](#):
>
> so every 40 ms the red led gets turned on for 1 second.

Again, that's impossible. The "ON" time exceeds the total period. Do you really mean:

On for 1 second  
Off for 40ms  
On for 1 second  
Off for 40ms  
On for 1 second  
Off for 40ms  
On for 1 second  
Off for 40ms  
On for 1 second

---

<div class="post-metadata">

**Author:** ![cinnamonroll](https://avatars.discourse-cdn.com/v4/letter/c/ea666f/32.png) [@cinnamonroll](https://forum.arduino.cc/u/cinnamonroll)\
**Post date:** [April 8, 2022, 10:47am UTC](https://forum.arduino.cc/t/how-to-make-tasks-and-determine-stack-size-in-freertos/978325/13 "2022-04-08T10:47:37Z")

</div>

@Idahowalker I had some time to look at your suggestions. I haven't reached the stage of studying semaphores yet, that's next on my list of topics to learn. I'll probably do that today. But I now understand what you mean about my script exceeding its task stack space. Ever since my last post, I found another way of optimising my stack size:  
so for each task I got the leftover stack, printed it,

 ![image](https://europe1.discourse-cdn.com/arduino/original/4X/3/b/5/3b5de823f07aec1a91a9d9ad99581fef8760087a.png)  
then subtracted it from the amount I had originally set it to.  
 ![image](https://europe1.discourse-cdn.com/arduino/original/4X/7/0/6/70674968f3ecd6fd6da0c07831e490d1e371eb83.png)

that's the full code:

```arduino
/*
   Simple scenario to understand how to create tasks using freeRTOS:
   I have two LEDs (red and green),
   I want red to turn on for 1 second, every 40 milliseconds,
   I want the green to turn on for 1 second, every 1000 milliseconds.
*/

// Use only core 1 to implement everything on the single core
#if CONFIG_FREERTOS_UNICORE
static const BaseType_t app_cpu = 0;
#else
static const BaseType_t app_cpu = 1;
#endif

byte red_GPIO = 15; //LED pin
byte green_GPIO = 16; //LED pin

//Task1
void RUNtask1(void *argp) {
  while (1)
  {
    digitalWrite(red_GPIO, HIGH);
    vTaskDelay(1000); //pulse width
    digitalWrite(red_GPIO, LOW);
    vTaskDelay(40); //The system must executes the following periodic task at the specified period

    //get stack size for task 1
    unsigned int temp1 = uxTaskGetStackHighWaterMark(nullptr);
    Serial.print("task1="); Serial.println(temp1);
  }
}

//Task2
void RUNtask2(void *argp) {
  while (1)
  {
    digitalWrite(green_GPIO, HIGH);
    vTaskDelay(1000); //pulse width
    digitalWrite(green_GPIO, LOW);
    vTaskDelay(1000); //The system must executes the following periodic task at the specified period

    //get stack for task 2
    unsigned int temp2 = uxTaskGetStackHighWaterMark(nullptr);
    Serial.print("task2="); Serial.println(temp2);
  }
}

void setup() {

  Serial.begin(9600);
  pinMode(red_GPIO, OUTPUT);
  pinMode(green_GPIO, OUTPUT);

  // Task1 to run forever
  xTaskCreatePinnedToCore(
    RUNtask1, // Function to be called
    "Task1", // Name of task
    1024 - 500, // 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(
    RUNtask2, // Function to be called
    "Task2", // Name of task
    1024 - 500, // 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() {
}

```

what do you think of this?

Also I don't understand what you mean about

> [@Idahowalker](#):
>
> That is not the proper way to trigger a task to run.

What do you mean by "auto"?

@gfvalvo I drew a diagram to describe what I mean:

 ![image](https://europe1.discourse-cdn.com/arduino/original/4X/5/4/1/54148d66ae24ceb6d03d2fceea662fa64e857e5c.jpeg)  
so if the 40ms signal is positive, the red will be on for 1 second. if the signal is negative, red will be off. Same case with the green but different timings.

---

<div class="post-metadata">

**Author:** ![Idahowalker](https://dub1.discourse-cdn.com/arduino/user_avatar/forum.arduino.cc/idahowalker/32/238858_2.png) [@Idahowalker](https://forum.arduino.cc/u/Idahowalker)\
**Post date:** [April 8, 2022, 11:26am UTC](https://forum.arduino.cc/t/how-to-make-tasks-and-determine-stack-size-in-freertos/978325/14 "2022-04-08T11:26:04Z")

</div>

By setting the task size to the exact amount that the task is using, there is no room for stack growth for a just in case but do as you will. Good luck.

---

<div class="post-metadata">

**Author:** ![cinnamonroll](https://avatars.discourse-cdn.com/v4/letter/c/ea666f/32.png) [@cinnamonroll](https://forum.arduino.cc/u/cinnamonroll)\
**Post date:** [April 8, 2022, 11:31am UTC](https://forum.arduino.cc/t/how-to-make-tasks-and-determine-stack-size-in-freertos/978325/15 "2022-04-08T11:31:07Z")

</div>

Yes I know, the stack leftover was something like 556 and I subtracted 500

---

<div class="post-metadata">

**Author:** ![Idahowalker](https://dub1.discourse-cdn.com/arduino/user_avatar/forum.arduino.cc/idahowalker/32/238858_2.png) [@Idahowalker](https://forum.arduino.cc/u/Idahowalker)\
**Post date:** [April 8, 2022, 11:35am UTC](https://forum.arduino.cc/t/how-to-make-tasks-and-determine-stack-size-in-freertos/978325/16 "2022-04-08T11:35:42Z")

</div>

Your way will eventually cause issue at some point with a task that will incur unexpected stack growth.

Simple example.

Task calls a function. the function will be loaded into the task's stack space. If the task is assigned an exact stack value, the program will crash upon external function call.

ESPRESSIF recommends, use the highwatermark get the stack size used and add 2000 bytes.

---

<div class="post-metadata">

**Author:** ![cinnamonroll](https://avatars.discourse-cdn.com/v4/letter/c/ea666f/32.png) [@cinnamonroll](https://forum.arduino.cc/u/cinnamonroll)\
**Post date:** [April 8, 2022, 11:46am UTC](https://forum.arduino.cc/t/how-to-make-tasks-and-determine-stack-size-in-freertos/978325/17 "2022-04-08T11:46:59Z")

</div>

Ok, I didn't know that, makes sense. So what am I adding 2000 bytes to?

---

<div class="post-metadata">

**Author:** ![Idahowalker](https://dub1.discourse-cdn.com/arduino/user_avatar/forum.arduino.cc/idahowalker/32/238858_2.png) [@Idahowalker](https://forum.arduino.cc/u/Idahowalker)\
**Post date:** [April 8, 2022, 11:59am UTC](https://forum.arduino.cc/t/how-to-make-tasks-and-determine-stack-size-in-freertos/978325/18 "2022-04-08T11:59:41Z")

</div>

the reading given by the highwater mark after observing highwatermakrk for about 5ish minutes. Then use that value to set stack size.

---

<div class="post-metadata">

**Author:** ![cinnamonroll](https://avatars.discourse-cdn.com/v4/letter/c/ea666f/32.png) [@cinnamonroll](https://forum.arduino.cc/u/cinnamonroll)\
**Post date:** [April 8, 2022, 12:28pm UTC](https://forum.arduino.cc/t/how-to-make-tasks-and-determine-stack-size-in-freertos/978325/19 "2022-04-08T12:28:00Z")

</div>

you mean:  
optimum stack size = **the original stack size** - ( **highwatermakrk** + **2000 bytes** )

---

<div class="post-metadata">

**Author:** ![Idahowalker](https://dub1.discourse-cdn.com/arduino/user_avatar/forum.arduino.cc/idahowalker/32/238858_2.png) [@Idahowalker](https://forum.arduino.cc/u/Idahowalker)\
**Post date:** [April 8, 2022, 12:41pm UTC](https://forum.arduino.cc/t/how-to-make-tasks-and-determine-stack-size-in-freertos/978325/20 "2022-04-08T12:41:53Z")

</div>

it is fun to experiment.

[Next page](https://forum.arduino.cc/t/how-to-make-tasks-and-determine-stack-size-in-freertos/978325.md?page=2)
