Overview

Task loop:

  1. Measure voltage using ADC
  2. Take mutex
  3. Write to global voltage variable
  4. Send a task notification to the Status LED task
  5. Give the mutex
  6. Update task health flag to feed the watchdog

Tasks

NamePriorityStack SizePeriodicity/TriggerDescription
static TaskHandle_t xTaskSensorLow128 words100 msMonitor voltage using ADC
staticTaskHandle_t xTaskAlarmHigh128 wordsEvent-driven (sensor update)Control LEDs
static TaskHandle_t xTaskUARTMedium128 wordsEvent-driven (sensor update)Transmit data
  • Static: limit variable’s scope to main.c file for encapsulation

Concurrency and Synchronization

ComponentDescription
SemaphoreHandle_t xMutexManage shared access for fBatteryVoltage
SemaphoreHandle_t xBinSemaphoreWake up TaskAlarm only when there is new data
QueueHandle_t xUARTQueueQueue to send messages to TaskUART
  • Why does TaskSensor gives the semaphore before giving the mutex?
    • Prevent tasks from fighting over the mutex before they are “synced” by the semaphore
    • Give the semaphore first to let the scheduler organize the ready tasks
  • Why statically allocate tasks, mutexes, and semaphores?
    • Ensures determinism, no risk of runtime memory allocation failure

Variables

VariableDescription
volatile float fBatteryVoltageBattery voltage read by ADC
const float fThresholdVoltageThreshold for lighting up green/red LED
volatile uint32_t currentTimeButton debouncing
volatile uint32_t prevTimeButton debouncing
  • Volatile: ensure compiler doesn’t optimize read/writes

Messages

typedef enum {
    MSG_TYPE_VOLTAGE,
    MSG_TYPE_BUTTON
} MsgType_t;
 
typedef struct {
    MsgType_t type;    
    float value;       
} UARTMsg_t;

ADC - Ring Buffer

Implemented a ring buffer that saves the last 8 readings. Passes the average value to the alarm task to make it more consistent

Task Notification

  • Clear the value after reading it

Checking a task notification

BaseType_t xTaskNotifyWait(
    uint32_t ulBitsToClearOnEntry, 
    uint32_t ulBitsToClearOnExit, 
    uint32_t *pulNotificationValue,
    TickType_t xTicksToWait
);
  1. Enter the function xTaskNotifyWait
  2. Clear the bits in the mask ulBitsToClearOnEntry
  3. Check the value of the notification
    • Each task has a 32-bit value
      • Value was sent from another task and received by the task that calls xTaskNotifyWait
    • If there is a notification present (check the flag that indicates if there is a notif)
      • Read the result and store it in the pulNotificationValue
      • Clear bits in ulBitsToClearOnExit
      • Return pdTRUE
    • If there is no notification present
      • Wait for xTicksToWait until a notification arrives
      • When it arrives, go to step 3
      • If it doesn’t arrive, return pdFALSE

ADC Conversion

Code

  1. Start the ADC conversion
    • Makes the hardware start sampling the voltage and converting it to a digital value
    • 100 is the timeout value; wait 100 ms for the ADC conversion
  2. Get the most recent digital value converted. Add it to the ring buffer, which takes the average.
  3. Convert the ADC bit value to voltage
    • The ADC is 12 bits
    • which is the maximum value. The maximum ADC value corresponds to 3.3V

Inside the loop for the voltage manager task:

HAL_ADC_Start(&hadc1);
 
    RETURN_IF_ERROR_CODE_HAL(HAL_ADC_PollForConversion(&hadc1,100), &healthFlags.voltage_mgr);
    RETURN_IF_ERROR_CODE_CMSIS(osMutexAcquire(xMutexHandle,100), &healthFlags.voltage_mgr);
 
    uint32_t adcRaw = filterADCReading(HAL_ADC_GetValue(&hadc1));
    fBatteryVoltage = (adcRaw * 3.3f) / 4095.0f;
    UARTMsg_t batteryMessage = {.type = MSG_TYPE_VOLTAGE, .value = fBatteryVoltage};
    RETURN_IF_ERROR_CODE_CMSIS(osMessageQueuePut(xUARTQueueHandle, &batteryMessage, 0U, 10), &healthFlags.voltage_mgr);
    // osSemaphoreRelease(xBinSemHandle);
    xTaskNotifyGive(xTaskAlarmHandle);
    RETURN_IF_ERROR_CODE_CMSIS(osMutexRelease(xMutexHandle), &healthFlags.voltage_mgr);
    healthFlags.voltage_mgr = HEALTH_OK;
    osDelay(100);

The sampling rate is 1/100ms

It takes a sample every 100ms, which means there are 10 samples per second.

Circuit

The potentiometer is a voltage divider because the ADC pin is in between 2 resistances.