Task Understanding

theskaz
Posts: 22
Joined: Mon Feb 13, 2017 5:22 pm

Task Understanding

Postby theskaz » Wed Jan 23, 2019 3:37 pm

I have been reading about the ESP32 (on Arduino IDE) and it's FreeRTOS implementation and something isnt quite clicking with me. The Task examples that I have seen are very simple with Task1 and Task2 being called (or some variation of that).

is there a more complicated example out there? I am trying to figure out how I want to start coding my project where there are 9 "tasks" and I want to utilize both cores efficiently. In the examples, each task is in an infinite loop. Im not sure that applies to me. I "could" code my tasks to stay in an infinite loop, but not sure that is good practice. here is a breakdown of what I need

Task1 - runs every hour or so (not crazy time restrictive)
Task2 - runs every second (but can be pushed back if another task with a higher priority needs to run then
Task3 - runs every time the display is touched (touchscreen) and trumps all other tasks
Task4 - runs only when needed by task 3.
Task5 - runs non-stop at lowest priority
Task6 - runs every few seconds
Task7 - runs when invoked and until Task3 tells it to stop
Task8 - same as Task7
Task9 - not sure (this task deals with incoming MQTT requests)

Each task would have a different priority except for Task7 and 8. they are very similar and would never run at the same time.

ultimately, Im trying to understand when to use a task, and when not to (utilizing the millis() to control timing) and if there are any considerations to be had.

ESP_Sprite
Posts: 9051
Joined: Thu Nov 26, 2015 4:08 am

Re: Task Understanding

Postby ESP_Sprite » Thu Jan 24, 2019 3:08 am

No, tasks usually run in an infinite loop - they wait (well, they actually block, meaning the scheduler doesn't give them any CPU time) until something happens that they're interested in - touch screen gets touched, a delay passes, some other task wants them to do something, whatever. Things like 'runs only when taskx needs them' are done using message passing using queues or semaphores.

The alternative is to create and destroy tasks on demand, which certainly is possible - but as it's pretty expensive to do so (costs quite a few CPU cycles), the common method is to spin them all up at startup and have them block.

idahowalker
Posts: 166
Joined: Wed Aug 01, 2018 12:06 pm

Re: Task Understanding

Postby idahowalker » Fri Jan 25, 2019 1:00 pm

Yes and no to the tasks running in infinite loops.

This task run in an infinite loop infinaltly:

Code: Select all

void fBlinkBuiltIn( void* pvParameters )
{
  // toggle built in LED off/on
  for (;;)
  {
    vTaskDelay( pdMS_TO_TICKS( 10 ) );
    REG_WRITE( GPIO_OUT_W1TC_REG, BIT2 ); // GPIO2 LOW (clear)
    vTaskDelay( pdMS_TO_TICKS( OneK ) );
    REG_WRITE( GPIO_OUT_W1TS_REG, BIT2 ); //GPIO2 HIGH (set)
  }
  vTaskDelete( NULL );
}
This task runs in an infinite loop but the task items are only ran when triggered:

Code: Select all

void fDoLIDAR( void * pvParameters )
{
  int iLIDAR_Distance = 0;
  int iLIDAR_Strength = 0;
  while (1)
  {
    xEventGroupWaitBits (eg, evtDoLIDAR, pdTRUE, pdTRUE, portMAX_DELAY) ;
    tfmini.externalTrigger();
    iLIDAR_Distance = tfmini.getDistance();
    if ( iLIDAR_Distance > LIDAR_Max_Distance )
    {
      Serial.print ( " TFMini distance issue " );
      Serial.println (  iLIDAR_Distance );
    }
    iLIDAR_Strength = tfmini.getRecentSignalStrength();
    xSemaphoreTake( sema_LIDAR_INFO, xSemaphoreTicksToWait );
    xSemaphoreTake( sema_LIDAR_FOR_ALARM, xSemaphoreTicksToWait );
    x_LIDAR_INFO.Range[x_LIDAR_INFO.CurrentCell] = iLIDAR_Distance;
    xSemaphoreGive( sema_LIDAR_INFO );
    xSemaphoreGive( sema_LIDAR_FOR_ALARM );
    xEventGroupSetBits( eg, evtLIDAR_ServoAspectChange );
    //            Serial.print( "fDoLIDAR " );
    //        Serial.print(uxTaskGetStackHighWaterMark( NULL ));
    //            Serial.println();
    //            Serial.flush();
  }
  vTaskDelete( NULL );
}
This line

Code: Select all

xEventGroupWaitBits (eg, evtDoLIDAR, pdTRUE, pdTRUE, portMAX_DELAY) ;
holds the task until the task is triggered by an event.
You can run your taks by time or by round robin. You can create tasks groups that will trigger several tasks at the same time or just one task at a time.

Bookmark this site https://www.freertos.org/ and refer to it often.

To pin a task to a core use:

Code: Select all

xTaskCreatePinnedToCore( fDoLIDAR, "fDoLIDAR", TaskStack15K, NULL, Priority4, NULL, TaskCore1 ); //assigned to core 1
Use:

Code: Select all

  Serial.print( "fDoLIDAR " );
          Serial.print(uxTaskGetStackHighWaterMark( NULL ));
               Serial.println();
               Serial.flush();
to find out your tasks stack use. Starting with 10K is typical.

idahowalker
Posts: 166
Joined: Wed Aug 01, 2018 12:06 pm

Re: Task Understanding

Postby idahowalker » Fri Jan 25, 2019 1:09 pm

Here is an example of a GPS program using various task schemes as well as semaphores and queues:

Code: Select all

#include <driver/adc.h>
#include <Adafruit_BMP280.h>
#include <SPI.h>
#include "UTM.h"
#include <U8g2lib.h>
#include <stdio.h> //for function sprintf
#include <TinyGPS++.h>
#include <HardwareSerial.h>
//
/*
  Flash mode QI0 is the fastest
  Flash size = 4Mb
*/
#include "esp_system.h" //This inclusion configures the peripherals in the ESP system.
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "freertos/event_groups.h"
/* define event bits */
#define CalDistance               ( 1 << 0 ) //1
#define AnalogVoltReadTask        ( 1 << 1 ) //10
#define DO_UTM_TASK_BIT           ( 1 << 2 ) //100
#define UPDATE_DISPLAY_TASK_BIT   ( 1 << 3 ) //1000
#define GPS_Parse                 ( 1 << 4 ) //10000 
#define Not_Used                  ( 1 << 5 ) //100000
#define PressureEvent             ( 1 << 6 ) //1000000
#define DoPressureDataCollectionEvent ( 1 << 7 ) //10000000
#define RunTimeEvent              ( 1 << 8 ) //100000000
#define OneSecondGroupBits ( UPDATE_DISPLAY_TASK_BIT | RunTimeEvent )
////////////////////////////////////////////////////////////////////////////////////
/* create a hardware timer */
hw_timer_t * timer = NULL;
/* create event group */
EventGroupHandle_t eg;
//////////////////////////////////
SemaphoreHandle_t sema_CalDistance;
SemaphoreHandle_t sema_PressureDataCollection;
SemaphoreHandle_t sema_UTM;
SemaphoreHandle_t sema_RunTime;
SemaphoreHandle_t sema_AnalogVoltRead;
SemaphoreHandle_t sema_CheckPressure;
SemaphoreHandle_t sema_GPS_Gate;
SemaphoreHandle_t sema_Time;
SemaphoreHandle_t sema_Date;
SemaphoreHandle_t sema_Posit;
SemaphoreHandle_t sema_Sats;
SemaphoreHandle_t sema_Alti;
SemaphoreHandle_t sema_Hdg;
SemaphoreHandle_t sema_Speed;
//SemaphoreHandle_t sema_UTM_Posit;
////
U8G2_SSD1327_EA_W128128_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
////
#define TimerDivider 80
#define TaskCore1 1
#define TaskCore0 0
// #define A0Pin 36 //analog pin
// #define A1Pin 39 //analog pin
#define BlinkLED 2
#define FiveHundred 500
#define EightHundred 800
#define OneK 1000
#define OneKfive 1500
#define TwoK 2000
#define ThreeThreeK 3300
#define TaskStack10K 10000
#define TaskStack10K1 10100
#define TaskStack10K2 10200
#define TaskStack15K 15000
#define TwentyK 20000
#define UpdateDisplayK 20500
#define Priority1 1
#define Priority2 2
#define Priority3 3
#define Priority4 4
#define Priority5 5
//#define DEGREE 600000
// #define uPvolts 3.3
// #define ADbits 4095
#define SerialDataBits 115200
#define GPS_DataBits 9600
#define TIMER_FOUR 3
#define PressureDataArrayCount 8
/////////////////////////////////
TickType_t xTicksToWait0 = 0;
TickType_t QueueReceiveDelayTime = 10;
TickType_t xTicksToWait20 = 20;
TickType_t xSemaphoreTicksToWait = 12;
////////////////////////////////
HardwareSerial GPSSerial(2);
// Connect to the GPS on the hardware port
// The TinyGPS++ object
TinyGPSPlus GPS;
// Adafruit_GPS GPS(&GPSSerial);
// UltimateGPS rx to ESP32 I017 - Serial2
// UltimateGPS tx to ESP32 IO16 - Serial2
// pin 16=RX, pin 17=TX
//?????????????????????????????????????????
volatile int iTicCount = 0; //counts milliseconds
bool bLED_On = false;
///////////////////////////////////////////
QueueHandle_t xQ_DistanceMessage;
struct stuDistance
{
  unsigned long Distance = 0; //meters
} xDistanceMessage;
QueueHandle_t xQ_Speed;
struct stuSpeed
{
  float MPH = 0.0; // m.p.h.
  float KPH = 0.0;
} xSpeed;
////
QueueHandle_t xQ_PowerMessage;
struct stuPower
{
  float Vbatt = 0.0;
  float BattPercent = 0;
} xPowerMessage;
/////////////////////////////////////
// from lat long to UTM conversion, shared data between two functions
QueueHandle_t xQ_UTM_Message;
struct stuUTM
{
  float UTM_North = 0.0; // holds northing info
  float UTM_West = 0.0; // holds easting info
  int iZone = 0; // holds zone info
} xUTM_Message;
////////////////////////////////////////////////////////////////
//temperature, pressure, altitude, sealevelpressure
#define DEVICE_PIN 5
// BME_SCK 18 // keep for reference info
// BME_MISO 19
// BME_MOSI 23
Adafruit_BMP280 bme(DEVICE_PIN); // hardware SPI
// Adafruit_BMP280 bme(DEVICE_PIN, BME_MOSI, BME_MISO,  BME_SCK); // keep for reference
QueueHandle_t xQ_PressureMessage;
struct stuPressureMessage
{
  float BMEpressure = 0.0;
  float BMEtemperature = 0.0;
} xPressureMessage;
// float BMEpressure = 0.0;
// float BMEtemperature = 0.0;
////////////////////////////////////////////////////////////////
QueueHandle_t xQ_PressureDataMessage;
struct stuPressureData
{
  int mmHg_Hourly[8] =
  {
    0, 0, 0, 0, 0, 0, 0, 0
  };
  int BMEpressure = 0;
} xPressureData;
//
QueueHandle_t xQ_RunTimeMessage;
struct stuRunTimeData
{
  int iSeconds;
  int iMinutes;
  int iHours;
} xRunTimeData;
//
QueueHandle_t xQ_Time;
struct stuTime
{
  int iSeconds;
  int iMinutes;
  int iHours;
} xTime;
////
struct stuDate
{
  int iDay;
  int iMonth;
  int iYear;
} xDate;
////
QueueHandle_t xQ_Posit;
struct stuPosit
{
  float Lat;
  float Lon;
} xPosit;
////
QueueHandle_t xQ_Sats;
////
QueueHandle_t xQ_Alti;
////
QueueHandle_t xQ_UTM_Posit;
struct stuUTM_Posit
{
  float Lat;
  float Lon;
} xUTM_Posit;
////
QueueHandle_t xQ_Hdg;
////
////////////////////////////////////////////////////////////////
// timer ISR callback set at 1000X a second
void IRAM_ATTR onTimer()
{
  BaseType_t xHigherPriorityTaskWoken;
  iTicCount++;
  //
  if ( (xSemaphoreTakeFromISR(sema_GPS_Gate, &xHigherPriorityTaskWoken)) == pdTRUE ) // grab semaphore, no wait
  {
    xEventGroupSetBitsFromISR(eg, GPS_Parse, &xHigherPriorityTaskWoken); // trigger every 1mS, if not already processing
  }
  if ( iTicCount == OneK )
  {
    xEventGroupSetBitsFromISR(eg, OneSecondGroupBits, &xHigherPriorityTaskWoken); // trigger every 1X a second
    // reset counter to start again
    iTicCount = 0;
  }
} // void IRAM_ATTR onTimer()
////
////
void setup()
{
  ////
  // Wire.setClock( 100000 );
  Wire.setClock( 2000000 ); // shoud use pullups
  //Wire.setClock( 3400000 ); // shoud use pullups
  Serial.begin( SerialDataBits );
  // Serial.println(MOSI); // 23 Master Out Slave In, SDI, DIN
  // Serial.println(MISO); // 19 Master In Slave Out, SDO
  // Serial.println(SCK); // 18 Clock
  // Serial.println(SS); // 5 Slave Select, Chip Seclect
  pinMode( BlinkLED, OUTPUT ); // for blink LED
  SPI.begin();
  while (!bme.begin())
  {
    Serial.println("Could not find BME280 sensor!");
    vTaskDelay( pdMS_TO_TICKS( OneK ) );
  }
  //
  u8g2.begin();
  ////
  eg = xEventGroupCreate();
  //
  GPSSerial.begin( GPS_DataBits ); // begin GPS hardwareware serial
  //
  vTaskDelay( pdMS_TO_TICKS( OneK ) ); // delay one second
  // https://dl.espressif.com/doc/esp-idf/latest/api-reference/peripherals/adc.html
  // set up A:D channels
  adc1_config_width(ADC_WIDTH_12Bit);
  adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_11db);
  adc1_config_width(ADC_WIDTH_12Bit);
  adc1_config_channel_atten(ADC1_CHANNEL_1, ADC_ATTEN_11db);
  /*
     When VDD_A is 3.3V: 11dB attenuation (ADC_ATTEN_11db) gives full-scale voltage 3.9V
  */
  ////
  /* Use 4th timer of 4.
    1 tick 1/(80MHZ/80) = 1us set divider 80 and count up.
    Attach onTimer function to timer
    Set alarm to call timer ISR, every 10000uS and repeat / reset ISR (true) after each alarm
    Start an timer alarm
  */
  timer = timerBegin( TIMER_FOUR, TimerDivider, true );
  timerAttachInterrupt( timer, &onTimer, true );
  timerAlarmWrite(timer, OneK, true);
  timerAlarmEnable(timer);
  ////
  ///////////// create queues
  // queue length, queue item size
  xQ_PressureMessage = xQueueCreate( 1, sizeof(struct stuPressureMessage*) ); // sends a queue pointer to the structure
  xQ_PressureDataMessage = xQueueCreate( 1, sizeof(struct stuPressureData*) ); // sends a queue pointer to the structure
  xQ_DistanceMessage = xQueueCreate( 5, sizeof(struct stuDistance) ); // sends a copy of the structure
  xQ_Speed = xQueueCreate( 1, sizeof(struct stuSpeed) ); // sends a copy of the structure
  xQ_PowerMessage = xQueueCreate( 1, sizeof(struct stuPower*) ); // sends a queue pointer to the structure
  xQ_UTM_Message = xQueueCreate( 1, sizeof(struct stuUTM) ); //sends copy of the structure
  xQ_RunTimeMessage = xQueueCreate( 1, sizeof(struct stuRunTimeData*) ); //sends a queue pointer to the structure
  xQ_Time = xQueueCreate( 1, sizeof(struct stuTime*) );
  xQ_Sats = xQueueCreate( 1, sizeof(int) );
  xQ_Alti = xQueueCreate( 1, sizeof(float) );
  xQ_UTM_Posit = xQueueCreate( 1, sizeof(stuUTM_Posit*) );
  xQ_Hdg = xQueueCreate( 1, sizeof(float) );
  xQ_Posit = xQueueCreate( 1, sizeof(struct stuUTM_Posit) ); //sends copy of the structure
  ////////////////////////////
  //  Task function, name of task, Stack size of task, parameter of the task, priority of the task, Task handle to keep track of created task, task core
  /////////////////////////// TASK CORE 0
  xTaskCreatePinnedToCore( fGPS_Parse, "fGPS_Parse", TaskStack10K2, NULL, Priority4, NULL, TaskCore0 ); // assigned to core 0
  sema_Time = xSemaphoreCreateMutex();
  xSemaphoreGive( sema_Time );
  sema_Date = xSemaphoreCreateMutex();
  xSemaphoreGive( sema_Date );
  sema_Posit = xSemaphoreCreateMutex();
  xSemaphoreGive( sema_Posit);
  sema_Sats = xSemaphoreCreateMutex();
  xSemaphoreGive( sema_Sats );
  sema_Alti = xSemaphoreCreateMutex();
  xSemaphoreGive( sema_Alti );
  xQ_UTM_Posit = xSemaphoreCreateMutex();
  xSemaphoreGive( xQ_UTM_Posit );
  sema_CalDistance = xSemaphoreCreateMutex(); // create Semaphore
  xSemaphoreGive( sema_CalDistance );
  sema_Hdg = xSemaphoreCreateMutex();
  xSemaphoreGive( sema_Hdg );
  sema_GPS_Gate = xSemaphoreCreateMutex();
  xSemaphoreGive( sema_GPS_Gate );
  xTaskCreatePinnedToCore( fGetUTM, "fGetUTM", TaskStack15K, NULL, Priority4, NULL, TaskCore0 ); // assigned to core 0
  sema_UTM = xSemaphoreCreateMutex();
  xSemaphoreGive( sema_UTM );
  xTaskCreatePinnedToCore( fRunTime, "fRunTime", TaskStack10K2, NULL, Priority4, NULL, TaskCore0 ); //assigned to core 0
  sema_RunTime = xSemaphoreCreateMutex();
  xSemaphoreGive( sema_RunTime );
  xTaskCreatePinnedToCore( fDoPressureDataCollection, "fDoPressureDataCollection", TaskStack10K, NULL, Priority4, NULL, TaskCore0 ); // assigned to core 0
  sema_PressureDataCollection = xSemaphoreCreateMutex();
  xSemaphoreGive( sema_PressureDataCollection );
  sema_Speed = xSemaphoreCreateMutex();
  xSemaphoreGive( sema_Speed );
  //////////////////////////////// TASK CORE 1 ///////////////////////////////////////////////////////////////////////////////////////////////////
  xTaskCreatePinnedToCore( fUpdateDisplay, "fUpdateDisplay", TaskStack15K, NULL, Priority4, NULL, TaskCore1 ); // assigned to core 1
  xTaskCreatePinnedToCore( fCheck_Pressure, "fCheck_Pressure", TaskStack10K1, NULL, Priority3, NULL, TaskCore1 ); //assigned to core 1
  sema_CheckPressure = xSemaphoreCreateMutex(); // create Semaphore
  xSemaphoreGive( sema_CheckPressure );
  xTaskCreatePinnedToCore( fBlinkBuiltIn, "fBlinkBuiltIn", TaskStack10K1, NULL, Priority2, NULL, TaskCore1 ); //assigned to core 1
  xTaskCreatePinnedToCore( TaskAnalogVoltRead, "fTaskAnalogVoltRead", TaskStack10K1, NULL, Priority3, NULL, TaskCore1 ); // assigned to core 1
  sema_AnalogVoltRead = xSemaphoreCreateMutex();
  xSemaphoreGive( sema_AnalogVoltRead );
  //////
} //void setup() // runs on core 1
///////////////////////////
void loop() {} // runs on core 1
//////////////////////////
/////////////////////////
//
void fRunTime( void * parameter )
{
  struct stuRunTimeData *pxMessage;
  for (;;)
  {
    xEventGroupWaitBits (eg, RunTimeEvent, pdTRUE, pdTRUE, portMAX_DELAY) ;
    if ( xSemaphoreTake( sema_RunTime, xSemaphoreTicksToWait ) == pdTRUE )
    {
      xRunTimeData.iSeconds++;
      if ( xRunTimeData.iSeconds == 60 )
      {
        xRunTimeData.iMinutes++;
        xRunTimeData.iSeconds = 0;
      }
      if ( xRunTimeData.iMinutes == 60 )
      {
        xRunTimeData.iHours++;
        xRunTimeData.iMinutes = 0;
      }
      pxMessage = &xRunTimeData;
      xQueueOverwrite( xQ_RunTimeMessage, (void *) &pxMessage );
      xSemaphoreGive( sema_RunTime );
    }
    // Serial.println( "fRunTime" );
    //    Serial.print( "fRunTime " );
    //    Serial.print(uxTaskGetStackHighWaterMark( NULL ));
    //    Serial.println();
    //    Serial.flush();
  }
  vTaskDelete( NULL );
} // void fRunTime( void * parameter )
//
void fDoPressureDataCollection( void * parameter )
{
  struct stuPressureData *pxMessage;
  int iArrayCell = 0;
  bool bDidInitialArray = false;
  int iPast = 0;
  for (;;)
  {
    xEventGroupWaitBits (eg, DoPressureDataCollectionEvent, pdTRUE, pdTRUE, portMAX_DELAY) ;
    //seed initial value and hour cell
    if ( bDidInitialArray == false )
    {
      if ( xPressureData.BMEpressure != 0 )
      {
        if ( xSemaphoreTake( sema_Time, xSemaphoreTicksToWait ) == pdTRUE ) // grab semaphore to get minute data
        {
          iPast = xTime.iMinutes; // store periodic
          xSemaphoreGive( sema_Time ); // give up semaphore
          xPressureData.mmHg_Hourly[iArrayCell] = xPressureData.BMEpressure;
          iArrayCell++;
          pxMessage = &xPressureData;
          xQueueOverwrite( xQ_PressureDataMessage, (void *) &pxMessage );
          bDidInitialArray = true;
        }
      }
    } //  if ( bDidInitialArray == fasle )
    else
    {
      if ( xSemaphoreTake( sema_Time, xSemaphoreTicksToWait ) == pdTRUE ) // grab semaphore to get minute data
      {
        int iCurrentMinute = xTime.iMinutes;
        xSemaphoreGive( sema_Time ); // give up semaphore
        if ( iPast != iCurrentMinute )
        {
          if ( ((iCurrentMinute % 15) == 0) )
          {
            if ( xSemaphoreTake( sema_PressureDataCollection, xSemaphoreTicksToWait ) == pdTRUE )
            {
              iPast = iCurrentMinute; // keep current minute
              ////
              xPressureData.mmHg_Hourly[iArrayCell] = xPressureData.BMEpressure; //add new periodic reading to array
              ////
              iArrayCell++; // go to the next cell
              pxMessage = &xPressureData;
              xQueueOverwrite( xQ_PressureDataMessage, (void *) &pxMessage );
              xSemaphoreGive( sema_PressureDataCollection );
              if ( iArrayCell == PressureDataArrayCount )
              {
                iArrayCell = 0; // start array count over
              } // if ( iArrayCell == PressureDataArrayCount )
            } // if( xSemaphoreTake( s_Pressure, xTicksToWait20 ) == pdTRUE )
          } //  if ( ((GPS.minute % 15) == 0) )
          else // if ( iPast != GPS.minute )
          {
            if ( xSemaphoreTake( sema_PressureDataCollection, xTicksToWait0 ) == pdTRUE )
            {
              pxMessage = &xPressureData;
              xQueueOverwrite( xQ_PressureDataMessage, (void *) &pxMessage ); // just send the collected data without adding any new data
              xSemaphoreGive( sema_PressureDataCollection );
            } // if( xSemaphoreTake( s_Pressure, ( TickType_t ) TicDe
          } // if ( iPast != GPS.minute )
        } // else if ( iPast != GPS.minute )
      }
      else // if ( iPast != GPS.minute )
      {
        if ( xSemaphoreTake( sema_PressureDataCollection, xTicksToWait0 ) == pdTRUE )
        {
          pxMessage = &xPressureData;
          xQueueOverwrite( xQ_PressureDataMessage, (void *) &pxMessage ); // just send the collected data without adding any new data
          xSemaphoreGive( sema_PressureDataCollection );
        } // if( xSemaphoreTake( s_Pressure, ( TickType_t ) TicDe
      }
    }
    // Serial.println( "fDoPressureDataGather " );
    //    Serial.print( "fDoPressureDataGather " );
    //    Serial.print(uxTaskGetStackHighWaterMark( NULL ));
    //    Serial.println();
    //    Serial.flush();
  }
  vTaskDelete( NULL );
} // void fDoPressureDataCollection
////
////
/* The display routine will trigger the UTN update when needed.
    The display runs once per second
    the display will only call on the UTM update as needed
    the UTM runs on the other core from the display
*/
void fGetUTM(  void * parameter )
{
  struct stuUTM pxMessage;
  float tmpUTM_North = 0.0; // holds northing info
  float tmpUTM_West = 0.0; // holds easting info
  int tmpZone = 0; // holds zone info
  for (;;)
  {
    EventBits_t xbit = xEventGroupWaitBits (eg, DO_UTM_TASK_BIT, pdTRUE, pdTRUE, portMAX_DELAY) ;
    //    int LatLonToUTMXY (FLOAT lat, FLOAT lon, int zone, FLOAT & x, FLOAT & y)
    if ( xSemaphoreTake( sema_UTM, xSemaphoreTicksToWait ) == pdTRUE )
    {
      tmpZone = LatLonToUTMXY(xUTM_Posit.Lat, xUTM_Posit.Lon, 0, tmpUTM_North, tmpUTM_West );
      pxMessage.UTM_North = tmpUTM_North;
      pxMessage.UTM_West = tmpUTM_West;
      pxMessage.iZone = tmpZone;
      xQueueOverwrite( xQ_UTM_Message, (void *) &pxMessage );
      xSemaphoreGive( sema_UTM );
    }
  }
  vTaskDelete( NULL );
}
////
void fUpdateDisplay( void * parameter )
{
  struct stuPressureMessage *pxRxedPressureMessage; // a pointer to a structure
  struct stuDistance pxDistanceMessage; // a copy of the queue sent
  struct stuPower *pxRxedPowerMessage; // a pointer to a structure
  struct stuUTM UTM_MessageReceiced; // a copy of the queue sent
  struct stuPressureData *pxPressureDataMessage; // a pointer to a structure
  struct stuRunTimeData *RunTimeDataMessage; // a pointer to a structure
  struct stuTime *pxTime;
  struct stuSpeed pxSpeed; // a copy of the queue sent
  // struct stuDate pxDate;
  struct stuPosit pxPosit;
  int iSats = 0;
  static int iCount = 0;
  float Alti = 0.0;
  float Hdg = 0.0;
  float f = 0.0;
  static char buf[25];
  int iHdgY = 72;
  int iAltiY = 61;
  int iYposition = 94;
  int iLeftPosition = 0;
  int iTotalCount = 30;
  u8g2.setFont( u8g2_font_t0_11_mr );
  for (;;)
  {
    /* wait forever until event bit of task 1 is set */
    EventBits_t xbit = xEventGroupWaitBits (eg, UPDATE_DISPLAY_TASK_BIT, pdTRUE, pdTRUE, portMAX_DELAY) ;
    u8g2.clearBuffer(); // clear the internal memory
    //
    if ( (xQ_Time != NULL) && (uxQueueMessagesWaiting(xQ_Time)) ) // if queue not null and something is waiting in queue
    {
      if (xQueueReceive ( xQ_Time, &(pxTime), QueueReceiveDelayTime ) ) // receive queue info
      {
        if ( xSemaphoreTake( sema_Time, xSemaphoreTicksToWait ) == pdTRUE ) // grab semaphore and place data
        {
          sprintf(buf, "UTC: %02d:%02d:%02d", pxTime->iHours, pxTime->iMinutes, pxTime->iSeconds);
          xSemaphoreGive( sema_Time ); // give up semaphore
          u8g2.setCursor(1, 10); u8g2.print(buf); // place data in display buffer
        } //  if ( xSemaphoreTake( sema_Time, xSemaphoreTicksToWait ) == pdTRUE )
      } // if (xQueueReceive ( xQ_RunTimeMessage, &(RunTimeDataMessage), QueueReceiveDelayTime ) )
    } // if ( (xQ_Time not NULL) && (uxQueueMessagesWaiting(xQ_Time)) )
    //
    if ( uxQueueMessagesWaiting(xQ_Sats) )
    {
      xSemaphoreTake( sema_Sats, xSemaphoreTicksToWait );
      xQueueReceive(xQ_Sats, &iSats, QueueReceiveDelayTime );
      xSemaphoreGive( sema_Sats );
      u8g2.drawStr(85, 10, "^");
      u8g2.setCursor(95, 10); u8g2.print( iSats );
    }
    if ( (iCount >= 0) &&  (iCount <= 9) )
    {
      if ( xSemaphoreTake( sema_Date, xSemaphoreTicksToWait ) == pdTRUE ) // grab semaphore and place data
      {
        sprintf( buf, "Date: %02d/%02d/%02d", xDate.iMonth, xDate.iDay, xDate.iYear );
        xSemaphoreGive( sema_Date ); // give up semaphore
        u8g2.setCursor(0, 21); u8g2.print(buf);
      } //  if ( xSemaphoreTake( sema_Date, xSemaphoreTicksToWait ) == pdTRUE )
    } //  if ( (iCount >= 0) &&  (iCount <= 9) )
    else
    {
      // is the queue not NULL && is there a queue message waiting? 0 = no messages
      if ( (xQ_RunTimeMessage != NULL) && (uxQueueMessagesWaiting(xQ_RunTimeMessage) > 0) )
      {
        if (xQueueReceive ( xQ_RunTimeMessage, &(RunTimeDataMessage), QueueReceiveDelayTime ) )
        {
          if ( xSemaphoreTake( sema_RunTime, xSemaphoreTicksToWait ) == pdTRUE )
          {
            sprintf(buf, "Run: %03d:%02d:%02d", RunTimeDataMessage->iHours, RunTimeDataMessage->iMinutes, RunTimeDataMessage->iSeconds);
            xSemaphoreGive( sema_RunTime );
            u8g2.setCursor(0, 23); u8g2.print(buf);
          }
        }
      }
    } // else  if ( (iCount >= 0) &&  (iCount <= 9) )
    u8g2.drawHLine(3, 27, 100);
    u8g2.drawVLine(3, 27, 22);
    u8g2.drawHLine(3, 49, 100);
    u8g2.drawVLine(103, 27, 23);
    //    // Display Lat and long
    if ( (iCount >= 0) &&  (iCount <= 9) )
    {
      if ( xSemaphoreTake( sema_Posit, xSemaphoreTicksToWait ) == pdTRUE ) // grab semaphore and place data
      {
        if (xQueueReceive ( xQ_Posit, &pxPosit, QueueReceiveDelayTime ) )
        {
          // Serial.println( pxPosit.Lat );
          sprintf( buf, "%0.6f", pxPosit.Lat );
          u8g2.setCursor(20, 37 ); u8g2.print( buf ) ;
          sprintf( buf, "%0.6f", pxPosit.Lon );
          u8g2.setCursor(20, 47); u8g2.print( buf );
        }
        xSemaphoreGive( sema_Posit ); // give up semaphore
      } //  if ( xSemaphoreTake( sema_Posit, xSemaphoreTicksToWait ) == pdTRUE )
    } // if ( (iCount >= 0) &&  (iCount <= 9) )
    if ( (iCount >= 10) &&  (iCount <= 19) )
    {
      if ( (xQ_UTM_Message != NULL ) && (uxQueueMessagesWaiting(xQ_UTM_Message)) )
      {
        if (xQueueReceive ( xQ_UTM_Message, &(UTM_MessageReceiced), QueueReceiveDelayTime ) )
        {
          if ( xSemaphoreTake( sema_UTM, xSemaphoreTicksToWait ) == pdTRUE )
          {
            //display UTM northing, easting, and zone
            u8g2.setCursor(12, 37); u8g2.print( UTM_MessageReceiced.UTM_North );
            u8g2.setCursor(12, 47); u8g2.print( UTM_MessageReceiced.UTM_West);
            u8g2.setCursor(75, 37); u8g2.print( UTM_MessageReceiced.iZone);
            xSemaphoreGive( sema_UTM );
          }
        }
      }
    } // if ( (iCount >= 0) &&  (iCount <= 9) )
    if ( (iCount >= 20) &&  (iCount <= iTotalCount) )
    {
      //// portMAX_DELAY will cause a wait forever for something to show up in the buffer. time of portMAX_DELAY is 47 days
      //// wait for 10 clock tics for data to arrive in the queue buffer
      if ( (xQ_PressureDataMessage != 0)  && (uxQueueMessagesWaiting(xQ_PressureDataMessage)) )
      {
        if (xQueueReceive ( xQ_PressureDataMessage, &(pxPressureDataMessage), QueueReceiveDelayTime ) )
        {
          if ( pxPressureDataMessage != 0) // check if structure is null
          {
            if ( xSemaphoreTake( sema_PressureDataCollection, xSemaphoreTicksToWait ) == pdTRUE )
            {
              int x = 30; // set x position
              int yBase = 38;
              // int y1 = yBase;
              int x2 = 5;
              int pressureBase = 0;
              int iBaseLineReading = pxPressureDataMessage->mmHg_Hourly[0];
              u8g2.drawHLine( x, yBase, x2 );
              for ( int i = 1; i <= (PressureDataArrayCount - 1); i++ )
              {
                if ( pxPressureDataMessage->mmHg_Hourly[i] != 0 )
                {
                  x = x + 6; // position next x
                  u8g2.drawHLine( x, yBase + (iBaseLineReading - pxPressureDataMessage->mmHg_Hourly[i] ), x2 );
                }
                sprintf( buf, "%04d", iBaseLineReading ) ; // display basy line reading to the left
                u8g2.setCursor( 9, 43 ); u8g2.print( buf );
              } // for (int i = 1; i <= (PressureDataArrayCount - 1); i++)
              xSemaphoreGive( sema_PressureDataCollection );
            } //  if ( xSemaphoreTake( s_Pressure, ( TickType_t ) 10 ) == pdTRUE )
          }
          else
          {
            Serial.println( "pxPressureDataMessage" );
          }
        } //  if (xQueueReceive ( xQ_PressureDataMessage, &(pxPressureDataMessage), ( TickType_t ) QueueReceiveDelayTime ) )
      } // if ( xQ_PressureDataMessage != 0 )
    } //  if ( (iCount >= 20) &&  (iCount <= 30) )
    if ( uxQueueMessagesWaiting(xQ_Hdg) )
    {
      xSemaphoreTake( sema_Hdg, xSemaphoreTicksToWait );
      xQueueReceive(xQ_Hdg, &Hdg, QueueReceiveDelayTime );
      xSemaphoreGive( sema_Hdg );
      u8g2.drawStr(iLeftPosition, iHdgY, "Hdg: ");
      u8g2.setCursor(28, iHdgY); u8g2.print( Hdg );
    }
    if ( uxQueueMessagesWaiting(xQ_Alti) )
    {
      xSemaphoreTake( sema_Alti, xSemaphoreTicksToWait );
      xQueueReceive(xQ_Alti, &Alti, QueueReceiveDelayTime );
      xSemaphoreGive( sema_Alti );
      u8g2.drawStr(iLeftPosition, iAltiY, "Alti: ");
      u8g2.setCursor(30, iAltiY); u8g2.print( (Alti * 3.2808) );
    }
    if ( (iCount >= 1) &&  (iCount <= 15) )
    {
      if ( xSemaphoreTake( sema_Speed, xSemaphoreTicksToWait ) == pdTRUE ) // grab semaphore to stop the queue from being over wrote
      {
        if (xQueueReceive ( xQ_Speed, &pxSpeed, QueueReceiveDelayTime ) )
        {
          sprintf( buf, "%.1fKPH %.1fMPH" , pxSpeed.KPH, pxSpeed.MPH );
          u8g2.setCursor( iLeftPosition, 83 ); u8g2.print( buf );
        }
        xSemaphoreGive( sema_Speed ); // release semaphore
      } //  if ( xSemaphoreTake( sema_Speed, xSemaphoreTicksToWait ) == pdTRUE )
    }
    else
    {
      if ( xSemaphoreTake( sema_CalDistance, xSemaphoreTicksToWait ) == pdTRUE ) // grab semaphore to stop the queue from being over wrote
      {
        if (xQueueReceive ( xQ_DistanceMessage, &pxDistanceMessage, QueueReceiveDelayTime ) )
        {
          sprintf( buf, "%d " , pxDistanceMessage.Distance );
          u8g2.setCursor( iLeftPosition, 83 ); u8g2.print( buf );
        }
        xSemaphoreGive( sema_CalDistance );
      } //   if ( xSemaphoreTake( sema_CalDistance, xSemaphoreTicksToWait ) == pdTRUE )
    }
    if ( (iCount >= 1) &&  (iCount <= 10) )
    {
      if ( xQ_PowerMessage != 0 )
      {
        if ( xQueueReceive( xQ_PowerMessage, &( pxRxedPowerMessage ), QueueReceiveDelayTime ) )
        {
          if ( pxRxedPowerMessage != 0) // check if structure is null
          {
            if ( xSemaphoreTake( sema_AnalogVoltRead, xSemaphoreTicksToWait ) == pdTRUE )
            {
              sprintf( buf, "V: %.2f %.2f% ", pxRxedPowerMessage->Vbatt, pxRxedPowerMessage->BattPercent );
              u8g2.setCursor( iLeftPosition, iYposition ); u8g2.print( buf );
              xSemaphoreGive( sema_AnalogVoltRead );
            }
          }
          else
          {
            Serial.println( "pxRxedPowerMessage" );
          }
        }
      } // if ( xQ_PowerMessage != 0 )
    } // if ( (iCount >= 1) &&  (iCount <= 10) )
    if ( (iCount >= 11) &&  (iCount <= 20) )
    {
      // Receive a message on the created queue.
      if ( xQ_PressureMessage != 0 )
      {
        if ( xQueueReceive( xQ_PressureMessage, &( pxRxedPressureMessage ), QueueReceiveDelayTime ) )
        {
          if ( pxRxedPressureMessage != 0) // check if structure is null
          {
            if ( xSemaphoreTake( sema_CheckPressure, xSemaphoreTicksToWait ) == pdTRUE )
            {
              sprintf( buf, "%.2fC %.2fF", pxRxedPressureMessage->BMEtemperature, ((pxRxedPressureMessage->BMEtemperature * 1.8) + 32) );
              u8g2.setCursor( iLeftPosition, iYposition ); u8g2.print( buf );
              xSemaphoreGive( sema_CheckPressure );
            }
          }
          else
          {
            Serial.println( "pxRxedPressureMessage 1" );
          }
        }
      }
    } // if ( (iCount >= 11) &&  (iCount <= 20) )
    if ( (iCount >= 21) &&  (iCount <= 30) )
    {
      if ( xQ_PressureMessage != 0 )
      {
        if ( xQueueReceive( xQ_PressureMessage, &( pxRxedPressureMessage ), QueueReceiveDelayTime ) )
        {
          if ( pxRxedPressureMessage != 0) // check if structure is null
          {
            if ( xSemaphoreTake( sema_CheckPressure, xSemaphoreTicksToWait ) == pdTRUE )
            {
              sprintf( buf, "%.2fmmHg", pxRxedPressureMessage->BMEpressure );
              u8g2.setCursor( iLeftPosition, iYposition ); u8g2.print( buf );
              xSemaphoreGive( sema_CheckPressure );
            }
          }
          else
          {
            Serial.println( "pxRxedPressureMessage 2" );
          }
        }
      }
    } // if ( (iCount >= 21) &&  (iCount <= 30) )
    u8g2.sendBuffer(); // transfer internal memory to the display
    if (  (iCount >= 20) &&  (iCount <= 29) )
    {
      xEventGroupSetBits( eg, PressureEvent ); // trigger pressure readings
    } // if (  (iCount >= 20 ) &&  (iCount <= 29) )
    if (  (iCount >= 10) &&  (iCount <= 19) )
    {
      xEventGroupSetBits( eg, PressureEvent ); // trigger pressure readings
    } // if (  (iCount >= 10) &&  (iCount <= 19) )
    if ( (iCount >= 8) &&  (iCount <= 18) )
    {
      // trigger UTM update, update utm just before its used as a display value
      xEventGroupSetBits( eg, DO_UTM_TASK_BIT ); // trigger UTM calculations
    } //  if ( (iCount >= 8) &&  (iCount <= 18) )
    if ( (iCount >= 0) &&  (iCount <= 9) )
    {
      xEventGroupSetBits( eg, AnalogVoltReadTask ); // trigger analog voltage read and calculation
    } // if ( (iCount >= 0) &&  (iCount <= 9) )
    iCount++;
    if ( iCount >= iTotalCount )
    {
      iCount = 0;
    }
    //        Serial.print( "fUpdateDisplay " );
    //        Serial.print( " " );
    //        Serial.print(uxTaskGetStackHighWaterMark( NULL ));
    //        Serial.println();
    //        Serial.flush();
    //    xSemaphoreGive( sema_GPS_Gate ); // see (xSemaphoreTakeFromISR(sema_GPS_Gate, &xHigherPriorityTaskWoken)) in void IRAM_ATTR onTimer()
  }
  vTaskDelete( NULL );
} // void fUpdateDisplay( void * parameter )
//////
void fGPS_Parse( void * parameter )
{
  TickType_t xDoDistanceExpireTicks;
  struct stuTime *pxTime;
  //  struct stuDate pxDate;
  struct stuPosit pxPosit;
  struct stuDistance pxDistance;
  struct stuSpeed pxSpeed;
  float Alti;
  float Hdg;
  int Sats;
  float LatNow;
  float LonNow;
  float LatPast;
  float LonPast;
  int DoDistanceTicks = 5000;
  ////
  xDoDistanceExpireTicks = xTaskGetTickCount() + pdMS_TO_TICKS( DoDistanceTicks ); // add DoDistanceTicks mS worth of ticks to current tick count
  for (;;)
  {
    xEventGroupWaitBits (eg, GPS_Parse, pdTRUE, pdTRUE, portMAX_DELAY) ;
    //query GPS: has a new complete chunk of data been received?
    if ( GPSSerial.available() > 1 )
    {
      if ( GPS.encode(GPSSerial.read()) )
      {
        //         if (totalGPGSVMessages.isUpdated()) ///????????????????
        //         {
        if (  GPS.location.isValid())
        {
          //used for distance calculation
          LatNow = GPS.location.lat();
          LonNow = GPS.location.lng();
          //          //
          if ( xSemaphoreTake( sema_Posit, xSemaphoreTicksToWait ) == pdTRUE )
          {
            pxPosit.Lat = LatNow; // store data into structure
            pxPosit.Lon = LonNow;
            xQueueOverwrite( xQ_Posit, (void *) &pxPosit );
            if ( xSemaphoreTake( sema_UTM, xSemaphoreTicksToWait ) == pdTRUE )  // place lat lon for utm calculation
            {
              xUTM_Posit.Lat = pxPosit.Lat;
              xUTM_Posit.Lon = pxPosit.Lon;
              xSemaphoreGive( sema_UTM );
            }
            xSemaphoreGive( sema_Posit );
          }
        } // if (  GPS.location.isValid())
        // do distance calculations
        if ( xTaskGetTickCount() >= xDoDistanceExpireTicks ) // do only once every xDoDistanceExpireTicks
        {
          if ( xSemaphoreTake( sema_CalDistance, xSemaphoreTicksToWait ) == pdTRUE )  // has tick count expired
          {
            if ( LatPast != 0.0 )
            {
              pxDistance.Distance = (unsigned long)TinyGPSPlus::distanceBetween( LatNow, LonNow, LatPast, LonPast) / 1000; // in Kilometers
              xQueueSendToBack( xQ_DistanceMessage, (void *) &pxDistance, xTicksToWait0 );
              // unsigned long distanceKmToLondon = (unsigned long)TinyGPSPlus::distanceBetween( gps.location.lat(), gps.location.lng(), LONDON_LAT, LONDON_LON) / 1000;
            }
            LatPast = LatNow; // update past lat n lon with now lat lon for next calculation
            LonPast = LonNow;
            xSemaphoreGive( sema_CalDistance );
          }
          xDoDistanceExpireTicks = xTaskGetTickCount() + pdMS_TO_TICKS( DoDistanceTicks ); // set new time peorid to do distance calculation
        } //  if ( xTaskGetTickCount() >= xDoDistanceExpireTicks )
        if (GPS.speed.isValid())
        {
          if ( xSemaphoreTake( sema_Speed, xSemaphoreTicksToWait ) == pdTRUE )  // has tick count expired
          {
            pxSpeed.MPH = GPS.speed.mph();
            pxSpeed.KPH = GPS.speed.kmph();
            // xSpeed.MPH = GPS.speed.mph();
            // xSpeed.KPH = GPS.speed.kmph();
            xQueueOverwrite( xQ_Speed, (void *) &pxSpeed );
            xSemaphoreGive( sema_Speed );
          }
          //}
        } //  if (  GPS.location.isValid())
        if (GPS.time.isValid())
        {
          xTime.iSeconds = GPS.time.second();
          xTime.iMinutes = GPS.time.minute();
          xTime.iHours = GPS.time.hour();
          if ( xSemaphoreTake( sema_Time, xSemaphoreTicksToWait ) == pdTRUE )
          {
            pxTime = &xTime;
            xQueueOverwrite( xQ_Time, (void *) &pxTime );
            xSemaphoreGive( sema_Time );
          }
        } // if (GPS.time.isValid())
        if (GPS.date.isValid())
        {
          if ( xSemaphoreTake( sema_Date, xSemaphoreTicksToWait ) == pdTRUE )
          {
            xDate.iMonth = GPS.date. month();
            xDate.iDay = GPS.date.day();
            xDate.iYear = GPS.date.year();
            xSemaphoreGive( sema_Date );
          }
        }
        if (  GPS.satellites.isValid() )
        {
          if ( xSemaphoreTake( sema_Sats, xSemaphoreTicksToWait ) == pdTRUE )
          {
            Sats = GPS.satellites.value();
            xQueueOverwrite( xQ_Sats, ( void * ) &Sats );
            xSemaphoreGive( sema_Sats );
          }
        }
        if (  GPS.altitude.isValid() )
        {
          if ( xSemaphoreTake( sema_Alti, xSemaphoreTicksToWait ) == pdTRUE )
          {
            Alti = GPS.altitude.meters();
            xQueueOverwrite( xQ_Alti, ( void * ) &Alti );
            xSemaphoreGive( sema_Alti );
          }
        } //  if (  GPS.altitude.isValid() )
        if ( GPS.course.isUpdated() )
        {
          if ( xSemaphoreTake( sema_Hdg, xSemaphoreTicksToWait ) == pdTRUE )
          {
            Hdg = GPS.course.deg();
            xQueueOverwrite( xQ_Hdg, ( void * ) &Hdg );
            xSemaphoreGive( sema_Hdg );
          }
        } // if ( GPS.course.isUpdated() )
        //                Serial.print( "fGPS_Parse " );
        //                Serial.print( " " );
        //                Serial.print(uxTaskGetStackHighWaterMark( NULL ));
        //                Serial.println();
        //                Serial.flush();

      } // if ( GPS.encode(GPSSerial.read()))
    } // if ( GPSSerial.available() > 0 )
    //    else
    //    {
    xSemaphoreGive( sema_GPS_Gate ); // see (xSemaphoreTakeFromISR(sema_GPS_Gate, &xHigherPriorityTaskWoken)) in void IRAM_ATTR onTimer()
    //    }
  } // for (;;)
  vTaskDelete( NULL );
} // void fGPS_Parse( void *pdata )
//////////////////////////////////////////////////
void fBlinkBuiltIn( void* pvParameters )
{
  // toggle built in LED off/on
  for (;;)
  {
    vTaskDelay( pdMS_TO_TICKS( 10 ) );
    REG_WRITE( GPIO_OUT_W1TC_REG, BIT2 ); // GPIO2 LOW (clear)
    vTaskDelay( pdMS_TO_TICKS( OneK ) );
    REG_WRITE( GPIO_OUT_W1TS_REG, BIT2 ); //GPIO2 HIGH (set)
  }
  vTaskDelete( NULL );
}
////////////////////////////////////////////////
// example of a ticks to wait use
void TaskAnalogVoltRead( void *pvParameters )
{
  struct stuPower *pxMessage;
  float ADbits = 4095.0;
  float  uPvolts = 3.3;
  float r1 = 27000.0; // R1 in ohm, 27k = 27000.0 //actual resistor size 27K
  float r2 = 10000.0;                       // R2 in ohm, 10k = 10000.0 //actual 10K
  // ADC1 channel 0 is GPIO36
  // ADC1 channel 1 is GPIO37
  // https://dl.espressif.com/doc/esp-idf/latest/api-reference/peripherals/adc.html
  for (;;)
  {
    // group handle, WaitForBits, ClearOnExitBit, WaitForAllBits, TicksToWait
    xEventGroupWaitBits( eg, AnalogVoltReadTask, pdTRUE, pdTRUE, portMAX_DELAY );
    // read the input
    //     ADC1 channel 1 input is ground, any ground noise readings get substracted from voltage reading
    if ( xSemaphoreTake( sema_AnalogVoltRead, xSemaphoreTicksToWait ) == pdTRUE )
    {
      xPowerMessage.Vbatt = ( (( uPvolts * float(adc1_get_raw(ADC1_CHANNEL_0) - adc1_get_raw(ADC1_CHANNEL_1))) / ADbits) / r2 * ( r1 + r2) );
      fBatteryPercentRemaingCharge();
      pxMessage = &xPowerMessage;
      xQueueOverwrite( xQ_PowerMessage, (void *) &pxMessage );
      xSemaphoreGive( sema_AnalogVoltRead );
    }
    //            Serial.print( "TaskAnalogVoltRead " );
    //    Serial.print(uxTaskGetStackHighWaterMark( NULL ));
    //        Serial.println();
    //        Serial.flush();
  }
  vTaskDelete( NULL );
}
/////////////////////////////////////////////////////////
void fBatteryPercentRemaingCharge()
{
  byte V_BattNominal = 8;
  byte V_MinRange = 6; //volts is lowest recomemded operating voltage
  ////
  if ( xPowerMessage.Vbatt >= V_BattNominal)
  {
    xPowerMessage.BattPercent = 100.00;
  }
  else
  {
    xPowerMessage.BattPercent = ( ((xPowerMessage.Vbatt - V_MinRange) * 100.0) / (V_BattNominal - V_MinRange) );
  }
}// end fBatteryPercentRemaingCharge()
//*****************************************************************************************
///////////////////////////////////////////////////////////////////////////////////////////
void fCheck_Pressure( void * parameter )
{
  struct stuPressureMessage *pxMessage;
  while (1)
  {
    xEventGroupWaitBits (eg, PressureEvent, pdTRUE, pdTRUE, portMAX_DELAY) ;
    if ( xSemaphoreTake( sema_CheckPressure, xSemaphoreTicksToWait ) == pdTRUE )
    {
      xPressureMessage.BMEtemperature = bme.readTemperature();
      xPressureMessage.BMEpressure = bme.readPressure() / 133.3223684; // mmHg
      if ( xSemaphoreTake(sema_PressureDataCollection, xSemaphoreTicksToWait ) == pdTRUE )
      {
        xPressureData.BMEpressure = int( xPressureMessage.BMEpressure ); // transfer pressure reading into xPressureData.BMEpressure
        xSemaphoreGive( sema_PressureDataCollection );
      }
      //// BMEpressure = bme.readPressure() *  0.0002952998751 ; // inHg
      pxMessage = &xPressureMessage;
      xQueueOverwrite( xQ_PressureMessage,  (void *) &pxMessage ); // sends a copy of the structure in the queue
      xEventGroupSetBits( eg, DoPressureDataCollectionEvent ); // trigger fDoPressureDataCollection
      xSemaphoreGive( sema_CheckPressure );
    }
    //    Serial.print( "fCheck_Pressure " );
    //    Serial.print(uxTaskGetStackHighWaterMark( NULL ));
    //    Serial.println();
    //    Serial.flush();
  }
  vTaskDelete( NULL );
} // void fCheck_Pressure( void * parameter )
//*****************************************************************************************
// templates and examples
//*****************************************************************************************
//void fTaskTemplateEventGroupUsingHardwareTimerTriggers( void * parameter )
//{
//  for (;;) {
/* Wait a maximum of 100ms for either bit 0 or bit 4 to be set within
  the event group.  Clear the bits before exiting. */
// uxBits = xEventGroupWaitBits(
//            xEventGroup,   /* The event group being tested. */
//            BIT_0 | BIT_4, /* The bits within the event group to wait for. */
//            pdTRUE,        /* BIT_0 & BIT_4 should be cleared before returning. */
//            pdFALSE,       /* Don't wait for both bits, either bit will do. */
//            xTicksToWait );/* Wait a maximum of 100ms for either bit to be set. */
// if( ( uxBits & ( BIT_0 | BIT_4 ) ) == ( BIT_0 | BIT_4 ) )
//  {
//      /* xEventGroupWaitBits() returned because both bits were set. */
//  }
//  else if( ( uxBits & BIT_0 ) != 0 )
//  {
//      /* xEventGroupWaitBits() returned because just BIT_0 was set. */
//  }
//  else if( ( uxBits & BIT_4 ) != 0 )
//  {
//      /* xEventGroupWaitBits() returned because just BIT_4 was set. */
//  }
//  else
//  {
//      /* xEventGroupWaitBits() returned because xTicksToWait ticks passed
//      without either BIT_0 or BIT_4 becoming set. */
//  }
//}
//    /* wait forever until event bit of task 1 is set */
//    EventBits_t xbit = xEventGroupWaitBits (eg, TASK_BIT, pdTRUE, pdTRUE, portMAX_DELAY) ;
// do this things here
//  }
//  vTaskDelete( NULL );
//}
//////
//void fTaskTemplateWithInternalDelay( void * parameter )
//{
//  for (;;)
//  {
//    do the ting here
//   vTaskDelay( pdMS_TO_TICKS( lLIDAR_SequenceInterval ) );
//   or
//   vTaskDelay( 1 / portTICK_PERIOD_MS );
//  }
//  vTaskDelete( NULL );
//}

theskaz
Posts: 22
Joined: Mon Feb 13, 2017 5:22 pm

Re: Task Understanding

Postby theskaz » Fri Jan 25, 2019 3:54 pm

Thank you for that. Ill read through that example and try to fully understand it.

for the task block item, if i had a task that needed to run every second, and that task has a block in it's while loop. that block will not hold up the CPU like a delay will?

im coming from an ESP8266 with this same code. I upgraded my board to use an esp32 and now porting the code over. i used millis() for EVERYTHING as to not block code execution.

theskaz
Posts: 22
Joined: Mon Feb 13, 2017 5:22 pm

Re: Task Understanding

Postby theskaz » Fri Jan 25, 2019 6:45 pm

Here is what I am thinking for one of the tasks. there is a global variable of "TickType_t oneWireDelay = pdMS_TO_TICKS(750);"

The idea is that this task will run forever, and not block anything else from running. and vTaskDelete should never be hit. its there just as a precaution.

Am I understanding this correctly?

Code: Select all

void OneWireTask(void * parameter) {
  for (;;)
  {
    Serial2.println("Requesting Temperatures");
    sensorC.requestTemperatures();
    sensorB.requestTemperatures();
    sensorA.requestTemperatures();
    
    vTaskDelay(oneWireDelay);
    
    Serial2.println("Updating Temperatures");
    if ( sensorC.getTempFByIndex(0) > 0 && sensorC.getTempFByIndex(0) < 212)  actualSensorCTemp = sensorC.getTempFByIndex(0);
    if ( sensorA.getTempFByIndex(0) > 0 && sensorA.getTempFByIndex(0) < 212)  actualSensorATemp = sensorA.getTempFByIndex(0);
    if ( sensorB.getTempFByIndex(0) > 0 && sensorB.getTempFByIndex(0) < 212)  actualSensorBTemp = sensorB.getTempFByIndex(0);
    Serial2.print("SensorA: ");
    Serial2.println(actualSensorATemp);
    Serial2.print("SensorB: ");
    Serial2.println(actualSensorBTemp);
    Serial2.print("SensorC: ");
    Serial2.println(actualSensorCTemp);
  }
  vTaskDelete(NULL);
}

idahowalker
Posts: 166
Joined: Wed Aug 01, 2018 12:06 pm

Re: Task Understanding

Postby idahowalker » Fri Jan 25, 2019 7:39 pm

theskaz wrote:
Fri Jan 25, 2019 3:54 pm
Thank you for that. Ill read through that example and try to fully understand it.

for the task block item, if i had a task that needed to run every second, and that task has a block in it's while loop. that block will not hold up the CPU like a delay will?

im coming from an ESP8266 with this same code. I upgraded my board to use an esp32 and now porting the code over. i used millis() for EVERYTHING as to not block code execution.
Event waits do not stop the CPU.

Semaphores do not stop the CPU.

vTaskDelay does not stop the CPU.


vTaskDelay( pdMS_TO_TICKS( 10 ) ) and vTaskDelay( 10 ) are way two different delays.

This is vTaskDelay( pdMS_TO_TICKS( 10 ) ) a delay of 10mSec, this vTaskDelay( 10 ) is a delay of 10 clock ticks. With the ESP32 running at 240Mhz it is 0.0000041666666666667 ms per clock tick. Multiply 0.0000041666666666667 ms by X to get your time delay or you can use pdMS_TO_TICKS( X ) which will do it for you.

theskaz
Posts: 22
Joined: Mon Feb 13, 2017 5:22 pm

Re: Task Understanding

Postby theskaz » Fri Jan 25, 2019 8:34 pm

the delay should be 750ms in this example.

Who is online

Users browsing this forum: Baidu [Spider] and 138 guests