In this project, we will practice writing FreeRTOS programs and apply the knowledge in previous projects to build a slightly more complex system. We will be building a system that has a 1602 LCD display and a temperature sensor. The microcontroller will read the temperature value and show it on the LCD, the temperature value will be updated once every second.
The diagram in Figure 1 shows the components of the system. A temperature sensor is connected to an analog pin of an Arduino Mega 2560 board. The temperature sensor being used in this project is a MF52 NTC thermistor. Basically it is a resistor whose value is dependent on the ambient temperature. If temperature is increased, its resistance is decreased. To read temperature, we connect the thermistor in series with a normal resistor with a value of 10 kOhm. Because the resistance changes with the temperature, the ADC value that the microcontroller reads on the analog pin changes accordingly. Because we know the relationship between the resistance and the temperature of the thermistor, we can work out a formula to calculate the temperature based on the ADC value on the analog pin.
After capturing the temperature, the microcontroller shows it on a LCD 1602 display. We will be using the standard library LiquidCrystal in Arduino IDE to communicate with the LCD. Basically, we will need to initialise the digital pins that are connecting to the LCD. We can then use the functions setCursor() and print() to write characters to a certain position of the LCD.
Defining freeRTOS tasks
The first job when writing a RTOS program is to determine what tasks to be used. For our project, it is reasonable to split jobs into two tasks: lcd_task and temp_task. The first task, lcd_task, has solely one purpose: it shows the temperature on the LCD. It does not need to know how the temperature is captured, instead, if a temperature value is provided, it will update the LCD screen to show that value. The second task, temp_task, on the other hand, will work on only one thing: capture the temperature. What it does is to read the analog value from the analog pin, calculate the temperature from that value, and send it to the lcd_task. It does not know and care about how to interface with the LCD. We can see that there is a need to send the temperature value from temp_task to lcd_task. For task to task communication, we can think of using freeRTOS Queue.
Firstly, we create two tasks using xTaskCreate() and a queue using xQueueCreate() function.
Similar to previous project, we declare a variable named queue_1 of type QueueHandle_t. We call xQueueCreate() with two parameters. The first parameter is the number of items that queue_1 can hold, which is set to 3 here. The second parameter is the the size of each item in the queue. We are passing sizeof(float) here because we the temperature is a float number and want to send it from temp_task to lcd_task. After calling xTaskCreate() to create two tasks of the same priority of 1, we call vTaskStartScheduler() to start the scheduler.
Implement LCD task
The LCD task function can be implemented as follow:
In this project, we are connecting digital pins 7, 8, 9, 10, 11, 12 of the arduino to pin RST, EN, D4, D5, D6, and D7 of the LCD, respectively. We are using the LiquidCrystal library from Arduino IDE, so we will need to include the macro #include <LiquidCrystal.h> at the top of the sketch. We create an object lcd of class LiquidCrystal and call constructor function to pass in the pins that are being used. The LCD is then initialised by calling lcd.begin(16, 2). We declare a float variable named temperature to hold the value that is received from the queue. In the main loop, the lcd_task calls xQueueReceive() and passes in queue_1, the address of the variable temperature and portMAX_DELAY. If we recall from previous project, because we are passing portMAX_DELAY in the third argument, when calling xQueueReceive, if the queue is not empty, it returns pdPASS, if the queue is empty, the calling task (which is lcd_task) will enter Blocked state to wait until the queue is not empty, it is then unblock and continue execution. What it means is the lcd_task will essentially wait until the temperature value is available on the queue, then it will wake up and update the LCD screen by calling lcd.setCursor() and lcd.print() functions.
Implement temperature task
The temperature task temp_task to read current temperature can be implemented as
Firstly, we declare a variable of current_temperature of type float to store the current temperature and enter the main loop. In the main loop, we call function read_temperature() to get the current temperature. Then the value is sent to the queue_1 by calling xQueueSend() and passing in queue_1 as first parameter, the address of the current_temperature variable as second parameter and portMAX_DELAY as third parameter. After sending to the queue, we call vTaskDelay() to put the temp_task to Blocked state for 1 second. We need to use the macro pdMS_TO_TICKS(1000) as the vTaskDelay() function accepts argument in ticks. After 1 second has passed, the temp_task is unblocked and goes to Ready state. It is ready to be selected by the kernel and will run again when selected. It repeats the loop and essentially read the temperature once every second and sent it to the lcd_task.
To read temperature, we read the analog pin of the microcontroller and do some calculation to convert the ADC value to the temperature. Here I am using the MF52 thermistor and the code below will work with this type of thermistor only. If you are using another type of temperature sensors, you might need to read its datasheet and find out the formula to derive the temperature from the reading.
After putting everything together, you will be able to see the temperature displayed on the LCD and its value is updated every 1 second. That’s it for today. We will be doing more practical projects to master FreeRTOS concepts in the coming posts.