Phipps Electronics

Order within the next 

FREE SHIPPING OVER $199

50,000+ ORDERS

WORLDWIDE SHIPPING

SSL SECURED

Writing your first RTOS program using STM32CubeMX

Contents

Have you experienced a dilemma wherein you need to run several peripherals or tasks at the same time? After that, you realize you don’t want to write a complex state machine for the given scenario. This thinking may be all too common, and RTOS is here to help.

Introduction:

Throughout the years, computer hardware was developed so it could run different kinds of tasks and peripherals. With this, software developers realized the need to manage different kinds of resources more efficiently and reliably. This gave way for them to create operating systems that schedule all of these tasks along with proper peripheral use.

RTOS stands for Real-Time Operating System. As its name implies, it’s an operating system (same as for computers) that’s dedicated to smaller embedded systems and microcontrollers. RTOS does the nitty-gritty stuff of scheduling different kinds of competing tasks (and peripherals) that can take hold of your MCU’s precious processing time.

We’ll try to demonstrate a simple RTOS application so you can understand the basic concepts of task scheduling.

Part 1. Why use an RTOS?

Writing complex state machines to line up the execution of different tasks can be avoided using a real-time operating system. This is also why software engineers tend to avoid old-fashioned “bare-metal” code schemes when making complicated and large projects. The intricacies of “scheduling” a task become automated with RTOS. Additionally, resource sharing and messaging between tasks are also arranged by this intelligent system.

If you want a complete understanding of the RTOS theories and fundamentals, you can visit the FreeRTOS page. In this article, we’ll make actual code to get your RTOS fundamentals going.

We’ll create a demo that illustrates the concept of task scheduling using RTOS without the complexities of more advanced concepts such as inter-task communication. This will be discussed in the next part.

Part 2. An example scenario

You want to create separate tasks for each function that uses a peripheral in your microcontroller as listed below:

      1. You get input from a user through a series of menu choices executed through a UART interface that displays it on a PC. The goal of this task is to display a simple menu layout to capture the user’s choice and then execute a specific operation. This task has normal priority and occasionally sleeps for the same or lower priority tasks.

        • Let’s call this task as dispUARTTask( )

          1. A peripheral uses your MCU’s ADC to monitor the output voltage of a unit (think battery monitoring system). This task would always be running and may contain code to process data. This task is designated to have an above-normal priority. This task sleeps itself for a certain duration for other non-critical tasks with lower priority to function.

            • Let’s call this task as getADCTask( )

              1. A series of push-button switches must be monitored. These switches mimic certain operations assigned by the user. These switches are continuously monitored, the same as in the ADC, but only with normal priority. This task can allocate some sleep time while waiting for button presses giving time for other lower or same priority tasks to do their work. Incidentally, switch debounce time was used as a sleep state.

                • Let’s call this task as pickButtonTask( )

                  1. Lastly, an external display device (an OLED) is used to extend the visibility of the data captured from your ADC. This task also has normal priority, and you have the option of just letting the task finish its work (instead of sleeping) to have a finer output. Other tasks with the same priority can take turns in utilizing MCU processing time.

                    • Let’s call this task as dispOLEDTask( )

                  Note that in RTOS, tasks with the same priorities can take turns executing their individual codes with each other, while tasks with higher priorities will always hog the MCU’s processing time. Because of this, it’s important to let higher-priority tasks sleep for a certain duration for other lower-priority tasks to function. To sleep we’ll use the osDelay( ) function. OsDelay( ) can also be used for other lower priority tasks while waiting for an event to happen (like UART or button presses) so as not to waste MCU instruction cycles. We’ll look at the process and intricacies of the code in the next part.

                  Part 3. Making firmware

                  We’ll be using the Blue Pill and the STM32Cube IDE to demo basic RTOS functionality. The Blue Pill has an Arm Cortex M3 processor running at a maximum clock rate of 72 MHz with 20kB of RAM. These specs make it ideal for RTOS tasks. You can get the BluePill through Phipp’s Electronics through this link. Additionally, many development boards and kits are available for you to practice making your RTOS codes.

                  Initially, you need to download STM32CubeIDE here. It’s a complete development environment that can set up your MCU’s peripherals (using a neat GUI tool called STM32CubeMX) and then continue developing and debugging code through its versatile editor.

                  After downloading the STM32CubeIDE installer, run it and install it on your system. Next, open the STM32CubeIDE program and try to familiarize yourself with the environment. Next, follow the steps below:

                      1. Click File -> New -> STM32 Project.

                        1. A target selector window will appear, which is part of the STM32CubeMX program. Enter STM32F103C8 as the part number, as this is what the BluePill has. Then select that part from the MCU/MPU list.

                          1. Click next and enter a project name and directory.

                            1. Click next until it finishes.

                              1. The GUI of CubeMX appears, along with an image of the MCU’s packaging. You can left-click on the pins to assign functions to them or right-click on them to assign labels.

                                1. On the right-hand side in the System Core category, click RCC, then choose Ceramic Crystal Resonator as High-Speed Clock (HSE) source.

                                  1. Go to the clock configuration tab. Set up to use HSE and PLLCLK as sources. Enter 72MHz on HCLK to get the maximum clock speed. Once you hit enter, an automatic configuration may occur.

                                    1. On the System Core category, go to SYS and use any timer as Timebase Source. This is the method preferred by the FreeRTOS environment.

                                      1. Next, we’ll set up the ADC. Go back to the Pinout & Configuration tab and choose ADC. Choose a single ADC port (IN0) by clicking it both on Mode and the Pinout view. Name it ADC1_X on the Pinout view. Leave its Configuration at its defaults.

                                        1. Next, setup I2C for the OLED display. Same as the steps for the ADC, check the necessary parameters below. We’ll be using Fast mode 400KHz for the I2C clock. Choose PB6 and PB7 as I2C1_SCL and I2C1_SDA.

                                          1. The same goes true with UART. Here we’ll be enabling both TX and RX functions on PA9 and PA10 at a bit rate of 115200. Name them as USART1_RX and USART1_TX.

                                            1. For the buttons, allot two pins, namely PB9 and PB12. Make them as input pins. You have the option to use internal or external pull-ups on them. Name them Button1 and Button2, respectively.

                                              1. The last few and most important steps are to set up RTOS in the Middleware category. Set the parameters as below. Remember to increase the TOTAL HEAP SIZE to at least 3600 in the Config parameters tab.

                                                1. We’ll need to set up the tasks we implemented in Part 2. Go to Tasks and Queue and add a new Task. Below you’ll see the four tasks added along with their priorities:

                                                  • pickButtonTask – Normal

                                                  • getADCTask        – Above Normal

                                                  • dispOLEDTask    – Normal

                                                  • dispUARTTask   – Normal

                                                Their entry function in the code will be their task names without the word Task. Click OK on each task to save them. Leave other parameters to default.

                                                    1. Generate code by clicking the save button. Click yes to generate code. If you’re asked to change to the C editor perspective, click yes.

                                                      1. You can compile the program by clicking the compile button. Running a program in CubeMX requires you to click the run button.

                                                        1. Next, edit each task to program their actions. You can compile and run them afterward.

                                                      Here are the codes for the different tasks:

                                                      pickButton Task:

                                                      void pickButton(void *argument)
                                                      {
                                                        /* USER CODE BEGIN pickButton */
                                                        /* Infinite loop */
                                                        for(;;)
                                                        {
                                                      
                                                      	  if(HAL_GPIO_ReadPin(Button1_GPIO_Port, Button1_Pin) == GPIO_PIN_RESET)
                                                      	  {
                                                      		  osDelay(300);	// debounce
                                                      		  button1_pressed = 1;
                                                      	  }
                                                      
                                                      	  if(HAL_GPIO_ReadPin(Button2_GPIO_Port, Button2_Pin) == GPIO_PIN_RESET)
                                                      	  {
                                                      		  osDelay(300);	// debounce
                                                      		  button2_pressed = 1;
                                                      	  }
                                                      
                                                          osDelay(100);
                                                        }
                                                        /* USER CODE END pickButton */
                                                      }

                                                      This code simply waits for the button presses inside a loop. The osDelay( ) functions allow this task to suspend/sleep allowing for other tasks to commence.

                                                      getADC Task:

                                                      void getADC(void *argument)
                                                      {
                                                        /* USER CODE BEGIN getADC */
                                                        /* Infinite loop */
                                                        for(;;)
                                                        {
                                                      
                                                      	  HAL_ADC_Start(&hadc1);
                                                      	  HAL_ADC_PollForConversion(&hadc1, 10);
                                                      	  x_val = (HAL_ADC_GetValue(&hadc1));
                                                      	  HAL_ADC_Stop(&hadc1);
                                                      
                                                      	  // process ADC values here
                                                      
                                                      
                                                          osDelay(100);
                                                        }
                                                        /* USER CODE END getADC */
                                                      }

                                                      This code polls for the values acquired from the ADC. Note that this task has a higher priority than other tasks, allowing ADC data to be processed immediately.

                                                      dispOLED Task:

                                                      void dispOLED(void *argument)
                                                      {
                                                        /* USER CODE BEGIN dispOLED */
                                                      	  SSD1306_Clear();
                                                      	  SSD1306_UpdateScreen();
                                                        /* Infinite loop */
                                                        for(;;)
                                                        {
                                                      
                                                      	  SSD1306_GotoXY (0,0);
                                                      	  sprintf(buffer, "ADC=%d      ", x_val);
                                                      	  SSD1306_Puts (buffer, &Font_11x18, 1);
                                                      	  SSD1306_UpdateScreen(); //display
                                                      	  
                                                        }
                                                        /* USER CODE END dispOLED */
                                                      }

                                                      This code displays the ADC values almost in real-time to the OLED display.

                                                      The SSD1306 hardware library used is open source code courtesy of:

                                                          • Tilen Majerle

                                                          • modification for STM32f10x: Alexander Lutsai

                                                        dispUART Task:

                                                        void dispUART(void *argument)
                                                        {
                                                        	  /* USER CODE BEGIN UARTmenu */
                                                        	  /* Infinite loop */
                                                        
                                                        		Menu_Display();
                                                        
                                                        	  for(;;)
                                                        	  {
                                                        		  // select user input
                                                        		  if (HAL_UART_Receive(&huart1, &choice, sizeof(choice), 10) == HAL_OK)
                                                        		  {
                                                        			switch (choice) {
                                                        
                                                        				case '1':
                                                        					//HAL_UART_Transmit(&huart1, (uint8_t*)"2 pressed\r\n", sizeof("1 pressed\r\n"), 10);
                                                        					sprintf(buffer, "Voltage = %d\r\n",x_val);
                                                        					HAL_UART_Transmit(&huart1, buffer, sizeof(buffer), 10);
                                                        					break;
                                                        				case '2':
                                                        					Menu_Display();
                                                        					break;
                                                        				default:
                                                        					break;
                                                        			}
                                                        		  }else{
                                                        
                                                        		  }
                                                        
                                                        		  if(button1_pressed)
                                                        		  {
                                                        			  HAL_UART_Transmit(&huart1, (uint8_t*)"Button1 pressed\r\n", sizeof("Button1 pressed\r\n"), 10);
                                                        			  button1_pressed = 0;
                                                        		  }
                                                        
                                                        		  if(button2_pressed)
                                                        		  {
                                                        			  HAL_UART_Transmit(&huart1, (uint8_t*)"Button2 pressed\r\n", sizeof("Button2 pressed\r\n"), 10);
                                                        			  button2_pressed = 0;
                                                        		  }
                                                        
                                                        
                                                        	    osDelay(100);
                                                        	  }
                                                        	  /* USER CODE END UARTmenu */
                                                        }

                                                        You’ll see in this code that it contains a menu interface that’s updated through UART. It can grab and show you the current ADC value. It can also notify the user of button presses. osDelay( ) is given time while there are no UART activities or button presses happening.

                                                        Here is the actual view in a UART terminal:

                                                        While this is the actual circuit used complete with the BluePill, buttons, OLED display, and a USB to UART interface.

                                                        The entire CubeMX program can be downloaded below:

                                                        Writing-your-first-RTOS-program-using-STM32CubeMX.zip

                                                        Part 4. Summary

                                                        Basic RTOS scheduling was explained through a practical example. This was illustrated by using different peripherals on an STM32 BluePill seemingly simultaneously.

                                                        By incorporating RTOS in your codes, you can avoid the difficulties of writing complex state machines for your design. With this, you’ll be able to focus more on your intended goals. You’ll also be able to subdivide code tasks more efficiently in a group.

                                                        SUBSCRIBE FOR NEW POST ALERTS

                                                        Subscribe to be the first to know when we publish a new article!
                                                        List Subscriptions(Required)

                                                        POPULAR POSTS

                                                        Scroll to Top