STM32 Returns - System Workbench, STM32Cube, C++, FreeRTOS, MAX7219 and Conway's Game of Life29 Oct 2017
Last year, I had written about my experience of getting started with STM32 microcontrollers. There was (and still is) a bewildering number of choices when it comes to ARM programming. After some research, I had settled on using ARM GCC, Eclipse, and Standard Peripheral Library to program an STM32F103RB chip. Recently, since I am working again with STM32 for a product, I decided to revisit development options for this chip family.
What I finally settled on is the following:
- System Workbench (SW4STM32)
A few thoughts on the above choices:
System Workbench (SW4STM32)
You can program SMT32 on a variety of toolchains and IDEs. I wanted to use ARM GCC based solution (not interested in spending $$$ on Keil, IAR, etc.) which was easy to set up and get going. System Workbench, which is built on top of Eclipse is a great choice, especially as it is supported by STMicroelectronics. (Note that you need to register and login before you see a download link on the SW4STM32 website.)
STM32Cube is an initiative by STMicroelectronics consisting of a software platform - HAL (Hardware Abstraction Layer), LL (Low-layer API), and middleware components - plus STM32CubeMX, a graphical tool for generating initialization code for your project - clock configuration, GPIOs, etc. 32 bit ARM chips are beasts compared to their petite 8 bit ancestors, and everything is complicated - whether you are setting up the clock or configuring a peripheral. Yes, ultimately it’s a matter of setting the right values in the right registers and you can code it up by hand, but the amount of time you will save with STM32Cube is quite significant. Also having a graphical tool like STM32CubeMX has many advantages. When you have a complex project, it’s very convenient to have a visual tool that helps you allocate resources of your chip without conflicts. As you will see below, it can also generate an SW4STM32 project for you directly.
The other compelling factor for moving away from the Standard Peripheral Library is that STMicro themselves seems to have ditched it. There are other options like libopencm3, but I feel it’s better to stick to something supported by the chip manufacturer, especially when you are working on a product.
The boilerplate for any microcontroller project consists of a while loop and a bunch of interrupt routines - assuming that you aren’t coding “Arduino-style” by peppering your code with delay() calls. As your projects increase in complexity and you start torturing the chip, you will soon find yourself prioritising interrupts, managing communication between routines, and synchronising data access. Unknowingly, you are writing your own RTOS (Real Time Operating System). So why not just use one designed for the purpose? FreeRTOS seems to fit the bill, and does not have restrictions for commercial use. As you will see below, STM32CubeMX can generate a FreeRTOS project for you. You don’t have to download on install anything separately.
The most common coding language for microcontrollers is C, and that’s what I stick to, most of the time. But having worked on complex software projects for many years, sometimes I miss a higher level of abstaction in the code. Although the first instinct might be to reject C++ as too heavy for microcontrollers, some research will show that C++ use is quite feasible - especially for 32 bit ARM chips with significant Flash/RAM. Personally, the ability to use classes is reason enought for me to use C++ - the code is way more organized than a C project.
I understand that the above choices won’t always fit the bill, especially if you are trying to heavily optimise Flash/RAM usage on the chip. Now, on to the project.
In this project, we will use an STMicroelectronics NUCLEO-F103RB board (with the STM32F103RB chip) to run Conway’s Game Of Life simulation on an 8x8 LED grid that uses the MAX7219 driver. The project will use the framework discussed above.
For the 8x8 LED grid, I used an assembled kit from ebay with the MAX7219 driver chip.
Here’s what the pinout of the Nucleo F103RB board looks like, from the datasheet.
The connections are as follows:
The Max7219 board is powered separately using a 5V power supply.
Now let’s get started with the software part.
Fire up SMT32CubeMX, start a new project, and select the NUCLEO-F103RB board. The pinout tab look like this:
Above, you can see that we have done the following:
- Enabled FreeRTOS
- Enabled SYS/TIM1 (For the RTOS)
- Enabled SPI2 and assigned pins PB13, PB14 and PB15
- Enabled USART2 and assigned PA2 (USART2_TX)
The clock configuration tab will let you set the (complex) clock system in a visual manner. (In this case we just choose the default.)
Now, go to Project->Settings:
You can see above that we have chosen SW4STM32 as the toolchain and set up the project directory. Now go to Project->Generate Code. When you do this the first time, it will download some resources (like FreeRTOS files) and at the end, it will give you an option to open this project directly inside System Workbench.
Once the project loads in System Workbench, the first thing you need to do is right-click on the project and choose the “Convert to C++” option. Once that is done, rename the main.c file as main.cpp.
To upload the code to the Nucleo board, go to Run->Debug Configurations” and you will see an *Ac6 STM32 Debugging option. You just need to add your project to this section and you are all set for uploading code and debugging it under System Workbench.
The above procedure was for creating a project from scratch. But now let’s look at the code details for this project.
If you download the code for this project, you will see the following structure:
So STM32CubeMX has downloaded and set up all the initial setup code, required HAL files, as well as FreeRTOS files.
Now let’s look at the main() function in main.cpp:
In the code above, after HAL and clock configuration, we initialise the GPIOs, SPI2 and the USART2 peripherals. Next, the default FreeRTOS task StartDefaultTask is created. Then we test out the UART using HAL_UART_Transmit(). (You can see the output by connecting your Nucelo board via USB to your computer and setting up a serial terminal software.) Then we call osKernelStart() and FreeRTOS takes over. From that point, tasks or threads take care of the program execution.
Also, notice above that we are using functions like osKernelStart rather than vTaskStartScheduler for FreeRTOS. This is because we are using the CMSIS abstraction for an RTOS, and these functions are defined in cmsis_os.h/cmsis_os.c. This is a good thing, because if you switch to a different RTOS, you won’t need to change all your calling code.
Here’s the task/thread function for the default task:
In the thread, we’re toggling the LED on the board and signaling the conway object, with a small delay in between.
To implement Conway’s Game Of Life on the 8x8 LED grid, we will make use of three classes - Conway64, MAX7219 and BitBuf88.
The BitBuf88 is a simple struct (header only) that abstracts an 8x8 1-bit buffer for LED display.
Since each LED is either ON or OFF, we need only 64 bits or 8 x uint8_t data to represent the grid in an efficient way.
The MAX7219 is the driver class for the chip, which is an LED driver. The chip uses a simple serial protocol to control the LEDs.
(Above image is from the MAX7219 datasheet.)
The above protocol can be easily implemented using the SPI peripheral of the STM32F103RB.
Above, you can see in sendPacket() how SPI is used to send a two-byte packet consisting of register and data. The setBuffer() method uses this to send data for the whole LED 8x8 grid.
Now that we know how to turn LEDs ON/OFF in the grid, let’s see how to implement Conway’s Game Of Life on it. Here’s the Conway64 class header.
The Conway64 class holds a grid, a thread handle and a reference to the MAX7219 driver. Here’s how a Conway64 object is initialised:
Above, we set up the correct registers on the MAX7219 driver (or the LEDs won’t light up), and start the conway task. Here’s the task function:
Above we add a glider to the grid, and in the for loop, we call the osSignalWait() function. This is an example of an inter-thread communication mechanism - another reason to use a full-fledged RTOS. So this code will block till it get the signal that it’s expecting. Once it gets the signal, it will call the update() method which updates the simulation by one time step.
You may recall that we called conway.signal() in the main task. Here’s what it does:
This is the call that wakes up the blocked execution in the conway task.
And now for the core of the simulation - the update function:
I won’t go into the details of Game Of Life here. I’ve written an entire chapter on it in my book Python Playground. The above code updates the grid according to the rules of the game and the MAX7219 object is used to send this updated grid to the 8x8 LED grid.
You can see the project in action here:
I think System Workbench + STM32CubeMX + FreeRTOS + C++ gives you a good framework for building complex application on STM32 microcontrollers.
You can download the code for this project from the git repo below: