2021-03-07 08:09 PM
I have a large (moderately, perhaps) C++ project generated under CubeMX, using FreeRTOS, written in C++, and using tasks. I've been having problems getting the variables in the task to relate back to the C++ class that initialized and created that task.
I have a solution and some theories why, as well as a few things that don't work.
More in the next post, since I'd rather work in the standard reply format.
2021-03-07 09:06 PM
Now that I can format code.....
FreeRTOS 10.2.1
TrueStudio latest version
C++ code called from main.c
My initial setup was to have a class, which owned a task pointer, and also included, within the class, a task to do something. As an example, an LED class would create a task to blink the LED, turn it off, turn in on; obviating the need to have hardware blink it, etc. The task would eliminate the need to use a timer and an output pin to PWM the led. All you need is a GPIO pin.
Now I do have other ways of controlling the LED. I could use a timer for PWM, but there's only so many of them and so many pins that can be dedicated to PWM. I could use a PCA9634 FM LED controller, but that's adding hardware. I could use an FPGA, but that also is adding hardware.
However the question is not about how to control the hardware, but how to make tasks work the way they need to, using C++, FreeRTOS, and your average CPU.
Remember that I'm using an LED class (for example), which has an instance per LED.
What I tried that didn't work (but not why)
1) task has taskhandle, TASK is member of class. Compiler whines about "cannot convert function to appropriate TaskHandle. Does not pass argument to task. ( pointer is pointing to something other than initialization value) Mostly works anyway. I don't like code that works "in spite of" rather than "because of".
2) wrap task in C++ sheepskin, courtesy of Richard Damon. Eliminates the "cannot convert function to taskhandle.... Argument at task is still not valid. Remember, FreeRTOS, C++ calling, and so on.
3) various tries at redefining the task handle in the create task routCine don't work, giving compile time errors. Best that works is (taskhandle_t) &<classname>::taskname Note that taskname is not taskname()
Compiler still whines....
So, tried a series of experiments. Success is define as 1) compiles and 2) argument is actually valid.
Solution: do the following (using LED as an example)
In the class, declare:
TaskHandle_t task_handle;
You'd do that anyway. Do NOT declare the task as a member of the class. If you declared it static, then there'd only be one, regardless. If dealing with virtual functions, see a separate post, but yes, there's a way around that, too.
Next, not as a member of the class, declare
void LED_TASK(void const * argument);
// DO NOT declare this in the task
// it used to be:
void TASK(void const * argument);
// as declared in the class structure.
// Don't do this, have the class separate as in the first line
Next, create the task. In my code, there's a create routine for setting up parameters, but the important part is:
task_handle = xTaskCreateStatic(
(TaskFunction_t) &LED_TASK,
name,
LED_STACK_SIZE,
(void*) this,
2,
GREEN_LED_STACK, // heap grows downwards but allocated upwards
&GREEN_LED_TCB);
The important thing to note here is that the definition of the task function is simply (TaskFunction_t) & LED_TASK. The argument to be passed to the function is (void*) this, which copies a pointer to the instance of this class to the function. That's what wasn't working before, and is now.
Next, the definition of the task itself is simple:
void LED_TASK(void const * argument)
{
// LED* LED;
uint32_t mask;
LED* me;
me = (LED*)argument;
// ************************ initialization **********************************************************************
// LED = (LED*) argument;
mask = 0x80000000;
while (1)
{
if (me->blink_mask & mask)
{
me->SET(LED_ON, 100);
vTaskDelay(90);
}
Note that the CLASS name is LED, and me is cast as a pointer to it. Note also that the defined this is not a valid pointer. That's why the instance of this task, as based on a unique value of Taskpointer_t is passed to the task itself.
,
Note that any variables within the class that is supposed to own this are referred to by the pointer me->
What I think is happening:
FreeRTOS is a C program, and when the task create routine is called, has no idea of the C++ class it's called from. The class exists, as the create routine (in my code) is called later, when the program decides to make one of these. Apparently, the references that FreeRTOS uses get lost in the translation if they could be found in the first place. I've already seen that the C code created by FreeRTOS is really not a member of the class, doesn't share variables, and is simply a pointer.
This approach seems to solve that. Been tested with single and multiple instances of the class and seems to work.
Oh, and wrapping the function in C++ sheepskin apparently doesn't work when done within a class, pull out the actual task, and still doesn't work. Needs to be a C function, then it works. If I weren't looking at things owned by a class, perhaps it would work.
Comments welcome:
2021-03-08 04:07 AM
Task1.h
#ifndef SRC_TASK1_H_
#define SRC_TASK1_H_
#include <string>
class Task1
{
public:
Task1();
Task1(const std::string & initText);
virtual ~Task1();
void run(void);
void setText(const std::string & initText) { mString=initText;}
static void task1Run(const void *argument);
private:
std::string mString;
int mCounter=0;
};
#endif /* SRC_TASK1_H_ */
Task1.cpp
Task1::Task1()
{
// TODO Auto-generated constructor stub
}
Task1::Task1(const std::string & initText) :
mString(initText),
mCounter(0)
{
// TODO Auto-generated constructor stub
}
Task1::~Task1()
{
// TODO Auto-generated destructor stub
}
void Task1::run(void)
{
for(;;)
{
printf("%s %d\r\n",mString.c_str(), mCounter++);
osDelay(1000);
}
}
extern "C" void Task1::task1Run(const void *argument)
{
Task1 * obj = const_cast<Task1 *>(static_cast<const Task1 *>(argument));
obj->run();
}
main.cpp
Task1 thirdTask;
osThreadId task3ID;
int main(void)
{
/* USER CODE BEGIN 1 */
...
...
...
/* Call init function for freertos objects (in freertos.c) */
MX_FREERTOS_Init();
Task1 *firstTask=new Task1("my first Task");
osThreadDef(firstTaskName, Task1::task1Run, osPriorityNormal, 0, CPPTASK1_TASK_STACKSIZE);
osThreadId task1ID = osThreadCreate(osThread(firstTaskName), firstTask);
Task1 *secondTask=new Task1("my second Task");
osThreadDef(secondTaskName, Task1::task1Run, osPriorityNormal, 0, CPPTASK1_TASK_STACKSIZE);
osThreadId task2ID = osThreadCreate(osThread(secondTaskName), secondTask);
osThreadDef(thirdTaskName, Task1::task1Run, osPriorityNormal, 0, CPPTASK1_TASK_STACKSIZE);
task3ID = osThreadCreate(osThread(thirdTaskName), &thirdTask);
thirdTask.setText("my third Task");
/* Start scheduler */
osKernelStart();
...
...
output
...
my first Task 0
my second Task 0
my third Task 0
my first Task 1
my second Task 1
my third Task 1
my first Task 2
my second Task 2
my third Task 2
my first Task 3
my second Task 3
2021-03-08 07:50 AM
Does this work if the task is a member function of a class
such as...
class foo
{
TaskHandle_t task_handle;
void TASK(void const * argument);
}
That's the very specific thing I could not get to work properly.
2021-03-08 10:54 AM
Hello,
freertos calls the task entry function, when it starts a task.
This is the wrapper function Task1::task1Run with C-linkage (extern "C").
This calls the calls the member function Task1::run of class Task1.
The tasks are then running in the for(;;) loop the member function run of class Task1.
see "mixing c and c++ code"
2023-12-17 07:02 PM
Hello,
If I wanted to create a queue between/among those tasks, how would I go about doing that with the way you've provided? Thanks!
2024-03-20 11:29 AM
Nvm, as I am learning there are many ways to perform task synchronization besides queues (task notify, event flags, etc)