Overview
Task loop:
- Measure voltage using ADC
- Take mutex
- Write to global voltage variable
- Send a task notification to the Status LED task
- Give the mutex
- Update task health flag to feed the watchdog
Tasks
| Name | Priority | Stack Size | Periodicity/Trigger | Description |
|---|---|---|---|---|
static TaskHandle_t xTaskSensor | Low | 128 words | 100 ms | Monitor voltage using ADC |
staticTaskHandle_t xTaskAlarm | High | 128 words | Event-driven (sensor update) | Control LEDs |
static TaskHandle_t xTaskUART | Medium | 128 words | Event-driven (sensor update) | Transmit data |
- Static: limit variable’s scope to
main.cfile for encapsulation
Concurrency and Synchronization
| Component | Description |
|---|---|
SemaphoreHandle_t xMutex | Manage shared access for fBatteryVoltage |
SemaphoreHandle_t xBinSemaphore | Wake up TaskAlarm only when there is new data |
QueueHandle_t xUARTQueue | Queue 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
| Variable | Description |
|---|---|
volatile float fBatteryVoltage | Battery voltage read by ADC |
const float fThresholdVoltage | Threshold for lighting up green/red LED |
volatile uint32_t currentTime | Button debouncing |
volatile uint32_t prevTime | Button 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
);- Enter the function
xTaskNotifyWait - Clear the bits in the mask
ulBitsToClearOnEntry - 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
- Value was sent from another task and received by the task that calls
- 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
- Read the result and store it in the
- If there is no notification present
- Wait for
xTicksToWaituntil a notification arrives - When it arrives, go to step 3
- If it doesn’t arrive, return
pdFALSE
- Wait for
- Each task has a 32-bit value
ADC Conversion
Code
- 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
- Get the most recent digital value converted. Add it to the ring buffer, which takes the average.
- 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.