Managing Power State of External Hardware

Hello everyone,

I am new to FreeRTOS, and RTOSes in general, and was hoping to get some advice on the proper way to manage the power state of external hardware (e.g. radios, modems, industrial equipment, etc.). If my system interfaces with an external device that has a warm-up time, interaction with that external device should be blocked until that warm-up time has elapsed, even if that external device is free (i.e. not being used by another task).

For simplicity, let’s make some assumptions:

  1. I have a system that does some timekeeping with an RTC, measures some ADC inputs, logs data to external memory, and when the external memory is full, transmits a payload via a radio.
  2. I have two tasks: Task A, a master task that contains the main loop and does most everything; and Task B, a task that monitors the power state of the radio.
  3. The radio takes 3 minutes to “warm up”.

Taking these assumptions, I have created what I think Task B could look like. Task B uses a state machine and a FreeRTOS one-shot timer to keep track of the power state of the radio. The intention would be for Task A to call “radio_start(…)” and then wait until “radio_get_state(…)” returns RADIO_STATE_READY.

I have pasted the code below.

Could anybody take a look at my code and give me some feedback? Is there a better way to approach this?

Thanks.


#include <stddef.h>
#include <FreeRTOS.h>
#include <task.h>
#include <timers.h>

#include "gpio_driver_name.h"
#include "radio_module_header.h"


////////////////////////////////////////////////////////////////////////////////
/////////// RADIO MODULE "HEADER" START ////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

typedef struct
{
	RADIO_STATE_OFF = 0,
	RADIO_STATE_START,
	RADIO_STATE_STARTING,
	RADIO_STATE_READY,
} RADIO_STATE;

void radio_start(void);
void radio_stop(void);
RADIO_STATE radio_get_state(void);

////////////////////////////////////////////////////////////////////////////////
/////////// RADIO MODULE "HEADER" END //////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////


/* ======== PRIVATE FUNCTIONS: PROTOTYPES =================================== */

static void timer_callback(TimerHandle_t xTimer);
static void create_or_reset_timer(void);


/* ======== PRIVATE VARIABLES =============================================== */

static TimerHandle_t 		xRadioTimer						= NULL;
static const int			g_radio_warmup_time_ms 			= 1000*60*3;
static RADIO_STATE			g_radio_state 					= RADIO_STATE_OFF;


/* ======== PUBLIC FUNCTIONS: IMPLEMENTATION ================================ */

void radio_start(void)
{
	g_radio_state = RADIO_STATE_STARTUP;
	GPIO_set(MY_RADIO_LOADSWITCH);
	return;
}

void radio_stop(void)
{
	g_radio_state = RADIO_STATE_OFF;
	GPIO_clear(MY_RADIO_LOADSWITCH);
	return;
}

RADIO_STATE radio_get_state(void)
{
	return g_radio_state;
}


/* ======== PRIVATE FUNCTIONS: IMPLEMENTATION =============================== */

void task_radio_power_manager(void *pvParameters)
{
	for (;;)
	{
		switch(g_radio_state)
		{
		case (RADIO_STATE_OFF):
		    //wait for radio_start(...) function call
			break;
		case (RADIO_STATE_START):
			//start the timer
			create_or_reset_timer();
			g_radio_state = RADIO_STATE_STARTING;
			break;
		case (RADIO_STATE_STARTING):
			//wait for the timer callback
			break;
		case (RADIO_STATE_READY):
			//wait for radio_stop(...) function call
			break;
		}
	}

	vTaskDelete(NULL);
}

static void timer_callback(TimerHandle_t xTimer);
{
	if (RADIO_STATE_STARTING != g_radio_state)
	{
		while (1); //TODO: handle error case
	}
	g_radio_state = RADIO_STATE_READY;
	return;
}

static void create_or_reset_timer(void)
{
	if (NULL == xRadioTimer)
	{
		xRadioTimer = xTimerCreate(NULL, pdMS_TO_TICKS(g_radio_warmup_time_ms), pdFALSE, 0, handle_timer_callback);
		if (NULL == xRadioTimer)
		{
			while (1); //TODO: handle error state
		}
	}
	else
	{
		ret = xTimerReset(xRadioTimer, pdMS_TO_TICKS(g_radio_warmup_time_ms));
    	if (pdFAIL == ret)
		{
			while (1); //TODO: handle error state
		}
	}
}

I think it looks ok. Additional hint/fix: Make task shared variables like g_radio_state volatile because their value might get changed without knowledge of the compiler (because the compiler has no idea about concurrent threads of execution).

First, the idea of a “Master Task” that “does everything” is, In my opinion, a road to getting yourself in trouble. That is how you write a NON “real-time” program, and you need to make sure that task know EVERYTHING about what is going on (or at least how to ask about it).

If warm up takes a fixed time, then just use a timer to have its call-back change the state (and perhaps signal a semaphore that a task can wait on if you want a task to be able to wait for the radio to be ready). Note, a “Master Task” can’t afford to do that, so will need to waste time polling to see if the warm-up has happened.

Alternativly, if the time is somewhat variable, the radio may be able to give a message when it is ready, so you just need something listening for that message.

If you need to periodically test the radio to see if it is up, then you want a task doing that. If your going “Master Task” mode, there is no reason that can’t be one of the operations in the Master Task, but more likely, you will have a task handling messages from the radio, and that task can do that.

For my own code, I would have created the timer as part of pre-scheduler initialization. Note, timers when created are dormant, so after creating you still need to start/reset them to get them to fire.

Your task_radio_power_manager doesn’t show it blocking on anything (just some comments about waiting for thing). ALL TASKS SHOULD BE BLOCKING WAITING FOR THERE NEXT STEP unless they are ACTIVELY doing a step.

Also, for my code, radio_start would be a function called by some (or any) task, that would check to see if started or starting, and if not, initiate the starting sequence and kick the time. (No need for a “Start” state, you are Off, Starting, or On. While the radio is in the starting state, you could have calls to operate the radio either return an error condition or wait on an event for the system to be started (at 3 minutes warm up, maybe blocking isn’t what you want to do until you get a task dedicated for the radio). Perhaps if off, it could automatically start depending on if that helps the code or not.

Note, there is no need for your “task” as it isn’t actually doing any thing except “wait” for things to happen, except the “start” operation, which can just be a function the task calls to do what is needed.