I'm trying to pick up C, using an esp32. While looking at exactly how FreeRTOS works, I found the following page regarding how to use the tasks, and best practices etc.
https://www.freertos.org/implementing-a-FreeRTOS-task.html
According to this page, to prevent starvation, tasks should be event based. Regarding what I am trying to achieve, I will try to provide a very simplified example.
Background
I have a LCD screen, which should display data from a sensor. The data shown on the LCD will be done using a task, which according to the documentation, should never exit and should be event driven to prevent starvation.
I have a way of controlling the data shown on the LCD screen, which would be a rotary encoder. This encoder can be clicked, which should refresh the sensor's data.
Question
How would I implement the event based tasks, which are described on the FreeRTOS page, in this specific context? I had a look at the documentation and the "simple" example projects on their github, but as a beginner within C and embedded, they were extremely overwhelming.
Simple demo code
void update_sensor_task(void *pvParameters)
{
// Ensure the task keeps on running
for( ; ; )
{
if(event_update_sensor) // How would I be able to notify the task that this should be run?
{
// update the data
}
}
// Tasks should not be returning, but if they happen to do so, ensure a clean exit
vTaskDelete(NULL);
}
void screen_temperature_task(void *pvParameters)
{
for(; ;)
{
if(event_sensor_updated)
{
// Update the lcd screen with the new data
}
}
vTaskDelete(NULL);
}
void on_rotary_clicked(void *pvParameters)
{
// Notify the sensor task that it should be updating?
}
EDIT:
By using what has been marked as the correct answer, I have managed to get it to work by implementing it the following way:
/* Queue used to send and receive the data */
QueueHandle_t xStructQueue = NULL;
/* Struct which shall be used to hold and pass around the data for the LCD screen*/
struct LcdData
{
int current_temp;
int current_humidity;
} xLcdData;
void initialize_queues(void)
{
xLcdData.current_humidity = 0;
xLcdData.current_temp = 0;
xStructQueue = xQueueCreate(
/* The maximum number of items the queue can hold*/
5,
/* The size of each struct, which the queue should be able to hold */
sizeof( xLcdData )
);
if(xStructQueue == NULL)
{
ESP_LOGE(TAG, "Queue has not been initialized successfully");
}
}
void screen_temperature_task_simplified(void *pvParameters)
{
int counter = 0;
for(; ;)
{
struct LcdData xReceivedStructure;
BaseType_t result;
result = xQueueReceive(xStructQueue, &xReceivedStructure, ( TickType_t ) 10);
if(result == pdPASS)
{
counter = counter + 1;
char snum_current_counter[12];
sprintf(snum_current_counter, "%d", counter);
i2c_lcd1602_clear (lcd_info);
i2c_lcd1602_write_string (lcd_info, snum_current_counter);
}
}
vTaskDelete(NULL);
}
void update_sensor_struct(void)
{
xLcdData.current_temp = DHT11_read().temperature;
xLcdData.current_humidity = DHT11_read().humidity;
// Log the results in the console
printf("Temperature is %d \n", xLcdData.current_temp);
printf("Humidity is %d\n", xLcdData.current_humidity);
ESP_LOGI(TAG, "Data has been updated");
}
void on_rotary_clicked_simplified()
{
ESP_LOGI(TAG, "Rotary encoder has been clicked!");
// Update the struct which holds the data
update_sensor_struct();
/* Send the entire struct to the queue */
xQueueSend(
/* The handle of the queue */
xStructQueue,
/* The adress of the struct which should be sent */
(void *) &xLcdData,
/* Block time of 0 says don't block if the queue is already full.
Check the value returned by xQueueSend() to know if the message
was sent to the queue successfully. */
( TickType_t ) 0
);
}
I use FRTOS and event driven development.
The typical flow here would be:
for(;;)
{
BaseType_t result;
result = xQueueReceive(LCD_Event_Queue, &someLCDEvent, QUEUE_TIMEOUT);
if (result == pdPASS)
{
/* We have new event data in someLCDEvent; Use that data to update the LCD */
}
else
{
/* No new event, do some brief idle-time processing if necessary */
}
}
In brief, wait up to QUEUE_TIMEOUT time for a new event to arrive.
If a new event arrives within that timeframe successfully, then process the data in that event and update your screen.
If a new event does not arrive, you have an opportunity to do some other maintenance work.
Designing and defining the structure-type of someLCDEvent, and putting data into the queue is a big topic, and will depend a lot on your specific project.
Related
Quick summary
video stream crashes if multiple clients connect at the same time due to the clients (all but 1) that skip the media-configure callback trying to change the bitrate by accessing a not yet configured pipeline. I'm asking how to wait with calling change_bitrate as long as the configure-media callback hasn't yet finished.
Detailed overview
I'm developing a door phone application that shows video footage of a user (that just rang the door) over the RTSP protocol on one or multiple screens (called clients from now on) in e.g. an appartment building.
When the application is running, it will not create a pipeline before the first client has connected. A new client callback is created in the following way:
/* Configure Callbacks */
/* Create new client handler (Called on new client connect) */
LOG_debug("Creating 'client-connected' signal handler");
g_signal_connect(info.server, "client-connected", G_CALLBACK(new_client_handler), &info);
Which calls this function as soon as a client has connected:
/**
* new_client_handler
* Called by rtsp server on a new client connection
*/
static void new_client_handler(GstRTSPServer *server, GstRTSPClient *client, struct stream_info *si)
{
DEBUG_ENTER;
/* Used to initiate the media-configure callback */
static gboolean first_run = TRUE;
GstRTSPConnection *connection = gst_rtsp_client_get_connection(client);
if (connection == NULL)
{
LOG_err("Could not get RTSP connection");
DEBUG_EXIT;
return;
}
GstRTSPUrl *url = gst_rtsp_connection_get_url(connection);
if (url == NULL)
{
LOG_err("Could not get RTSP connection URL");
DEBUG_EXIT;
return;
}
si->num_cli++;
gchar* uri = gst_rtsp_url_get_request_uri(url);
LOG_info("[%d]A new client %s has connected", si->num_cli, uri);
g_free(uri);
si->connected = TRUE;
/* Create media-configure handler */
/*relevant part for question*/
if (si->num_cli == 1)
{ /* Initial Setup */
/**
* Stream info is required, which is only
* available on the first connection. Stream info is created
* upon the first connection and is never destroyed after that.
*/
if (first_run == TRUE)
{
LOG_debug("Creating 'media-configure' signal handler");
g_signal_connect(si->factory, "media-configure", G_CALLBACK(media_configure_handler),
si);
}
}
else
{
change_bitrate(si); //This makes video stream crash if 'media_configure_handler' isn't yet finished
}
/* Create new client_close_handler */
LOG_debug("Creating 'closed' signal handler");
g_signal_connect(client, "closed", G_CALLBACK(client_close_handler), si);
first_run = FALSE;
DEBUG_EXIT;
}
When a client is the first one to connect, it sets up the media-configure callback to initialize the pipeline. The configuration code looks like this:
**
* media_configure_handler
* Setup pipeline when the stream is first configured
*/
static void media_configure_handler(GstRTSPMediaFactory *factory, GstRTSPMedia *media,
struct stream_info *si)
{
DEBUG_ENTER;
si->media = media;
LOG_info("[%d]Configuring pipeline...", si->num_cli);
si->pipeline = GST_BIN(gst_rtsp_media_get_element(media)); //Pipeline gets configured here
setup_elements(si);
if (si->num_cli == 1)
{
/* Create Msg Event Handler */
LOG_debug("Creating 'periodic message' handler");
g_timeout_add(si->msg_rate * 1000, (GSourceFunc) periodic_msg_handler, si);
}
DEBUG_EXIT;
}
A second (or nth) client that connects skips the media configuration step and instead goes to change_bitrate. Here the bitrate is adjusted based on the amount of connected clients.
/**
* change_bitrate
* handle changing of bitrates
*/
static void change_bitrate(struct stream_info *si)
{
DEBUG_ENTER;
int c = si->curr_bitrate;
int step = (si->max_bitrate - si->min_bitrate) / si->steps;
GstElement *elem = search_pipeline(si->pipeline, "enc"); //crashes due to an unitialized pipeline
const gchar *name = g_ascii_strdown(G_OBJECT_TYPE_NAME(elem), -1);
GstStructure *extra_controls;
...
}
This all works fine if a single client connects first. Later, the connection can handle multiple clients and adjusts the bitrate accordingly.
The problem arises if the first connection is by multiple clients:
In this case, both clients enter an instance of new_client_handler, in which the first one will set up the media_configure_handler. The second connection tries to change the bitrate, but fails because the pipeline is not yet configured by the callback.
How can i make the second (and nth) connection wait until the media configure callback has finished and thus a pipeline is available?
Solved this in the end with the following code (in function new_client_handler)
/* Create media-configure handler */
if (si->num_cli == 1)
{ /* Initial Setup */
/**
* Stream info is required, which is only
* available on the first connection. Stream info is created
* upon the first connection and is never destroyed after that.
*/
if (first_run == TRUE)
{
LOG_debug("Creating 'media-constructor' signal handler");
g_signal_connect(si->factory, "media-constructed", G_CALLBACK(media_configure_handler),
si);
}
}
else if(si->pipeline != 0)
{
change_bitrate(si);
}
else
{
g_signal_connect(si->factory, "media-configure", G_CALLBACK(media_constructed_handler),
si);
}
Pipeline object's construction is now hooked to the media-constructed event, which runs before the media-configure event.
A second client will only change bitrate if pipeline is initialized. If not, the client hooks in the media-configure callback and changes bitrate there. This callback is guaranteed to run after the media-constructed callback.
Hello everyone im doing my first steps with RTOS. Im trying to receive an amount of data using UART in an interrupt mode. I have a Display Task where the commands are being written to a global buffer, and i just created a UART Handler Task where i want to read the bytes. The problems im facing are.
The semaphore i use inside the UART Task is unknown, even though i declared it global in the main function, so the xSemaphoreTake() function has errors there. Maybe a helpful Note: the UART Task is in a seperated file.
Is my implemntation of the HAL_UART_RxCpltCallback and the UART Task clean?
here is the code i wrote:
SemaphoreHandle_t uartInterruptSemaphore = NULL;
int main(void)
{
/* USER CODE BEGIN 1 */
void mainTask(void* param) {
uartInterruptSemaphore = xSemaphoreCreateBinary();
if(uartInterruptSemaphore != NULL) {
// Display Thread with a 2 priority
xTaskCreate(&displayTask, "Display Thread", 1000, &huart4, 2, NULL);
// deferred Interrupt to be synchronized with the Display Task, must have a higher priority than the display task
xTaskCreate(&UartHandlerTask, "UART Handler Task", 1000, &huart4, 3, NULL);
}
for(;;){
}
}
the callback function i wrote:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *uart_cb) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if(uart_cb->Instance == USART4) {
xSemaphoreGiveFromISR(uartInterruptSemaphore, &xHigherPriorityTaskWoken);
}
portEND_SWITCHING_ISR(xHigherPriorityTaskWoken);
}
and the handler task:
void UartHandlerTask(void* param) {
huart_cache = param;
const uint8_t tmp = rx_byte; //rx byte is global volatile variable
for(;;){
if(xSemaphoreTake(uartInterruptSemaphore, portMAX_DELAY) == pdPASS) {
HAL_UART_Receive_IT((UART_HandleTypeDef *)huart_cache, (uint8_t *)&rx_byte, 1);
// write data to the buffer
RX_interrupt(tmp);
}
}
}
I would recommend getting a better handle on C before trying to use an RTOS. This will also show you a better way of unblocking a task form an interrupt than using a binary semaphore: https://www.freertos.org/2020/09/decrease-ram-footprint-and-accelerate-execution-with-freertos-notifications.html
I have two task in which i am receving data from bluetooth and if i receive a particular hex value , i want a task(which is Toggling LED State) to run on the basis of the received data.
If there was no data received , then both task should run as per they are scheduled.
I have been trying to use xTaskAbortDelay function , the task does run from the input from bluetooth data, however , after that the LED task is running continously.
Does xTaskAbortDelay creating some problem here?
Should I use something else to achieve the same functionality?
TaskHandle_t lora_send_data_handle;
TaskHandle_t ble_send_data_handle;
TaskHandle_t test_data_handle;
static void button_task_check(void * pvParameter)
{
TickType_t xLastWakeTime;
const TickType_t xFrequency = 1024;
xLastWakeTime = xTaskGetTickCount();
while(1)
{
nrf_delay_ms(100);
SEGGER_RTT_printf(0,"%s","INSIDE SWITCHING\r\n");
xTaskAbortDelay(test_data_handle);
vTaskDelayUntil( &xLastWakeTime, (TickType_t) 1024);
}
}
/*TASK TO RUN LEDS CHECK */
static void led_task_check(void * pvParameter)
{
TickType_t xLastWakeTime;
const TickType_t xFrequency = 122880;
xLastWakeTime = xTaskGetTickCount();
while(1)
{
SEGGER_RTT_printf(0,"%s","TEST TASK\r\n");
nrf_gpio_pin_write(RED,1);
nrf_gpio_pin_write(GREEN,1);
nrf_gpio_pin_write(BLUE,1);
nrf_gpio_pin_write(RED,0);
nrf_gpio_pin_write(GREEN,1);
nrf_gpio_pin_write(BLUE,1);
nrf_delay_ms(1000);
nrf_gpio_pin_write(RED,1);
nrf_gpio_pin_write(GREEN,0);
nrf_gpio_pin_write(BLUE,1);
nrf_delay_ms(1000);
nrf_gpio_pin_write(RED,1);
nrf_gpio_pin_write(GREEN,1);
nrf_gpio_pin_write(BLUE,0);
nrf_delay_ms(1000);
nrf_gpio_pin_write(RED,0);
nrf_gpio_pin_write(GREEN,0);
nrf_gpio_pin_write(BLUE,0);
nrf_delay_ms(1000);
vTaskDelayUntil( &xLastWakeTime, (TickType_t) 122880);
}
}
int main(void)
{
uint8_t rx_qspi[255];
SEGGER_RTT_printf(0,"%s","reset\r\n");
nrf_delay_ms(100);
xQueue1 = xQueueCreate(1, 30);
ret_code_t err_code;
err_code = nrf_drv_clock_init();
SEGGER_RTT_WriteString(0, err_code);
UNUSED_VARIABLE(xTaskCreate( button_task_check, "t", \
configMINIMAL_STACK_SIZE + 200, NULL,3, &lora_send_data_handle));
UNUSED_VARIABLE(xTaskCreate(led_task_check, "et", \
configMINIMAL_STACK_SIZE + 200, NULL, 2, &test_data_handle));
vTaskStartScheduler();
while(1);
}
Reputation to low to comment. From what you say, everything is working as you said. Need more information:
What does the LED task looks like?
Do you use preemptive or cooperative scheduler (#define configUSE_PREEMPTION 1 in freertosconfig.h file).
What are the priorities of the three tasks?
Something else to consider is: do you put the task back in BLOCKED state after it has served it's purose? You should check that first. How do you block the task in the first place?
Maybe try using calling vTaskResume( <LED task handle> ) from the bluetooth tasks and calling vTaskSuspend() from the LED task once it has finished it's job. I don't personally think this is the best approach, but it should work.
I have a light sensor that prints the value of its input to the Serial monitor. It's pretty much a trip wire but when an object is in its way, it prints the value every 1 millisecond. If I add a delay it won;t trigger the second sensor until the delay is done. How would I get it to only print once, without any disturbance or interference with the other sensors?
void loop() {
if (analogRead(sensor1) == 0) {
timer.start ();
tStop = false;
//Serial.println (timer.elapsed());
Serial.println ("Start Time = 0");
}
This is quite an interesting problem, in the normal world of computers we would solve this via threading. However as you are running without an OS we have to do one of two things, implement coroutines (fake threading without an OS) or use asynchronous code and interrupts.
My understanding is that you print something when an object first comes into the way of your sensor, as the arduino uno as opposed to the due is not easy to implement coroutines on we shall try the interrupt route.
First you will likely be interested in this library http://playground.arduino.cc/Code/Timer1
It allows you to add an interrupt service routine to run on a timer. Use the attachInterrupt(function, period) function in the library for this.
In your interrupt service routine you will want to check the sensor, set a variable to say how long ago since it was last triggered and print the message if appropriate. This means your main loop is completely free to run other code and will not block your other sensors.
For example:
void TimFun()
{
static int LastRead;
if(LastRead && (0 == analogRead(sensor1))
{
Serial.println("SensorTrip");
}
LastRead = analogRead(sensor1);
}
void loop()
{
// Do other stuff here
}
void setup()
{
Timer1.initialize(100000);
Timer1.attachInterrupt(TimFun);
// Rest of setup Here
}
I managed to make an int before the void setup and then used a while loop. with in the if statement.
int i = 1;
if (analogRead(sensor1) == 0) {
timer.start ();
tStop = false;
while (i == 1) {
Serial.println ("Start Time = 0");
i++;
}
}
You probably should use an if instead of a while loop that will never execute more than once.
bool tripped = false;
void setup(){
//setup stuff here
}
void loop() {
if ( analogRead(sensor1) == 0 )
{
timer.start ();
tStop = false;
if ( tripped == false )
{
Serial.println ("Start Time = 0");
tripped = true;
}
}
}
I have developed my own hybrid stream cipher and for the GUI i am using Qt. Initially i wrote it on a single thread but it being a stream cipher was making GUI dysfunctional when operating on large files. So i shifted the encryption/decryption to a separate Qthread. Also to show the progress i included a standard QProgressbar onto the GUI. But when I run the File I/O the encryption/decryption works perfectly but the progress bar doesn't update properly. After the whole operation completes, the progress bar suddenly goes from 0% to 100% showing that it didn't get the chance to update during the operation. For the code, I emitted the completed percentage from the FileCrypto to the main GUI thread onto the QProgressbar's setValue(int) slot. Since it didn't work I also tried to sent a int poitner over to the FileCrypto thread whilst updating the pointer with the percentage and using a QTimer on the GUI thread to check the value of the int value locally and update the progress bar but still I got the exact same result.
Here is my code:
The FileCrypto class:
#include <QThread>
#include <QFile>
#include <PolyVernam.h> //my algo header
class FileCrypto : public QThread
{
Q_OBJECT
public:
FileCrypto(QString, QString, int);
bool stopIt;
protected:
void run();
signals:
void completed(int);
void msg(QString);
void pathMsg1(QString);
void pathMsg2(QString);
void keyMsg(QString);
private:
QFile src, dest;
QString tag;
int mode;
qint64 length;
PolyVernam pv;
};
The Code:
#include <FileCrypto.h>
FileCrypto::FileCrypto(QString input, QString keyFile, int mode)
{
stopIt = false;
this->mode = mode;
src.setFileName(input);
if(mode == 1)
{
emit msg("Current Encryption/Decryption status: Encrypting file... :D:D");
tag = "-encrypted";
pv.setMode("encrypt", "");
}
else
{
emit msg("Current Encryption/Decryption status: Decrypting file... :D:D");
tag = "-decrypted";
pv.setMode("decrypt", keyFile);
}
dest.setFileName(QFileInfo(src).absolutePath() + "/" + QFileInfo(src).baseName()
+ tag + "." + QFileInfo(src).completeSuffix());
length = src.bytesAvailable();
}
void FileCrypto::run()
{
qint64 done = 0;
quint8 r, outChar;
char ch;
QDataStream in(&src);
in.setVersion(QDataStream::Qt_4_7);
src.open(QIODevice::ReadOnly);
QDataStream out(&dest);
out.setVersion(QDataStream::Qt_4_7);
dest.open(QIODevice::WriteOnly);
while(!in.atEnd() && !stopIt)
{
done++;
in >> r;
ch = char(r);
if(mode == 1)
outChar = pv.encrypt(QString(ch)).at(0).toAscii();
else
outChar = pv.decrypt(QString(ch)).at(0).toAscii();
out << outChar;
emit completed(int((done / length) * 100));
}
src.close();
dest.close();
if(stopIt)
this->exit(0);
if(mode == 1)
{
emit pathMsg1(QFileInfo(src).absoluteFilePath());
emit pathMsg2(QFileInfo(dest).absoluteFilePath());
}
else
{
emit pathMsg1(QFileInfo(dest).absoluteFilePath());
emit pathMsg2(QFileInfo(src).absoluteFilePath());
}
emit keyMsg(pv.keyFilePath);
emit msg("Current Encryption/Decryption status: Idle... :'(");
}
This is how I am making the thread and connecting it on the main GUI thread:
FileCrypto *fc = new FileCrypto(ui->lineEdit_4->text(), "", 1);
connect(fc, SIGNAL(completed(int)), ui->progressBar, SLOT(setValue(int)));
connect(fc, SIGNAL(msg(QString)), ui->statusBar, SLOT(showMessage(QString)));
connect(fc, SIGNAL(pathMsg1(QString)), ui->lineEdit_4, SLOT(setText(QString)));
connect(fc, SIGNAL(pathMsg2(QString)), ui->lineEdit_5, SLOT(setText(QString)));
connect(fc, SIGNAL(keyMsg(QString)), ui->lineEdit_2, SLOT(setText(QString)));
connect(fc, SIGNAL(keyMsg(QString)), this, SLOT(done()));
If I don't update the progress bar i.e. don't emit the percentage, the process happens much faster. I also tried printing the percentage. It slows it down like hell but the values are fine. Also can you suggest a way to change it to buffered IO....
Any sort of help is much appreciated here.......
The problem does not lie in the fact that you are calling from a different thread. It is located in:
emit completed(int((done / length) * 100));
Since done and length are int types, and done <= length, done/length == 0. So change it to:
emit completed(100 * done / length);
(it can lead to arithmetic overflow).