Creating an application for STM32C5 TSD Knob Display
In this tutorial, we will create and load an application for the TSD TSS013005A-AD-C5 display knob module. This is an entry-level graphics 1.3” round display module based on the STM32C5A3 with 8 MB external QSPI flash, running with a partial framebuffer. The application will have a simple GUI and show how to use the interfaces of the display module (physical controls, ambient light, and UART communication). The MCU platform in the TSD TSS013005A-AD-C5 display knob module has the following specifications:
- 144 MHz CPU (STM32C5A3)
- 256 kB internal SRAM
- 1 MB internal flash
- 8 MB external 106 MHz QSPI flash
- 72 MHz SPI display interface
To follow this tutorial, Tutorial 1 and Tutorial 2 are recommended prerequisites.
Step 1: Starting a TouchGFX Project
In TouchGFX Designer, an application template (TBS) is provided for the display module, which sets up all the basic functionality. Create a new project based on this:
- Under the "Create" tab, select the board called "TSD TSS013005A-AD-C5".
- Set the "Application Name" to e.g. "KnobApplication".
- Click "Create".
Step 2: Building a Basic GUI
After creating the project, we have a blank 240 x 240 pixels canvas. In this step, we will design the GUI for our application. It will have a single screen (Screen1) looking like this:
The image assets for this can be downloaded from this link. Unzip the file to the TouchGFX/assets/images folder in your project. To create the GUI, add and configure widgets in TouchGFX Designer according to this table:
| Order | Widget | Name | Properties |
|---|---|---|---|
| 1 | Image | backgroundImage |
|
| 2 | CircleProgress | valueCircleProgress |
|
| 3 | Image | wifiImage |
|
| 4 | Image | alarmImage |
|
| 5 | Image | unlockedImage |
|
| 6 | Image | lockedImage |
|
Step 3: Programming the Application to the Knob Module
Now we will program the preliminary application to the display module to see the GUI on the display. All Discovery Kits and NUCLEO boards made by STMicroelectronics have ST-LINK hardware on board, which is normally used to program applications to these. This third-party display module does not have a built-in ST-LINK. Instead, the project application template provides an alternative way to program it through the USB port.
The USB-C port on the knob module adapter board is used to power the unit, but it can also be used for communication. When the switch on the backside of the adapter board is set to "USB" position, the USB port data lines are connected directly to the USB interface of the STM32C5 inside the knob module. This can be used to load a new application using the bootloader on the STM32C5. This is the default method used in this project, when clicking "Run target" in TouchGFX Designer. For the "Run target" script in this project to work, it has the following prerequisites:
Display module is connected to the PC through a data USB cable.
The switch on the backside of the adapter board is set to "USB" position.
STM32CubeProgrammer (v2.22.0 or later) is installed in the default location.
The display module is in bootloader mode, which can be achieved using the buttons on the top side of the adapter board with the following procedure:
- Press and hold the BOOT0 button.
- Press the RESET button.
- Release the RESET button.
- Release the BOOT0 button.
- The LED ring on the knob module lights pale green, indicating that the bootloader is active.
- (If the above procedure does not work, ensure that the option byte BOOT_SEL is TRUE using STM32CubeProgrammer and an ST-LINK. This is default in the delivered modules. After ensuring this, repeat the steps above.)
After clicking "Run target" and waiting for the compilation and programming to complete, the GUI from Designer canvas should be visible on the module display.
Tip
Note
- Connect to the built-in bootloader
- Program a custom bootloader to MCU RAM (based on ST OpenBootloader) and execute it, to be able to program external flash.
- Connect to the custom bootloader
- Program the TouchGFX application to the internal flash of the MCU and the external flash.
- Reboot the MCU into the TouchGFX application.
Step 4: Add Interactions to Physical Inputs
The display knob module has three physical user input mechanisms: turn left, turn right, and press. In this step, we will make the GUI change the value of the CircleProgress when the knob is turned and toggle screen lock on/off when it is pressed.
The code in TouchGFX/target/KeySampler.cpp reads the hardware inputs and maps them to different key IDs. These can be used to trigger "Hardware button" interactions in TouchGFX Designer as shown below.
Add one interaction for each of the inputs according to the table below.
| Interaction | Properties |
|---|---|
| Knob_Turn_Left |
|
| Knob_Turn_Right |
|
| Knob_Press |
|
After adding the interactions and generating code in TouchGFX Designer, code that calls the specified virtual functions is now generated in TouchGFX/generated/gui_generated/src/screen1_screen/Screen1ViewBase.cpp. To implement our desired functionality we therefore need to add them (and a helper function) to our user code screen class header file:
TouchGFX/gui/include/gui/screen1_screen/Screen1View.hpp
public:
...
virtual void toggleLock();
virtual void decrementValue();
virtual void incrementValue();
void changeValue(int change);
And implement them in our user code screen class source file:
TouchGFX/gui/src/screen1_screen/Screen1View.cpp
void Screen1View::toggleLock()
{
lockedImage.setVisible(!lockedImage.isVisible());
lockedImage.invalidate();
}
void Screen1View::decrementValue()
{
if (!lockedImage.isVisible())
{
changeValue(-5);
}
}
void Screen1View::incrementValue()
{
if (!lockedImage.isVisible())
{
changeValue(5);
}
}
void Screen1View::changeValue(int change)
{
const int newValue = valueCircleProgress.getValue() + change;
valueCircleProgress.updateValue(newValue, 0);
}
The display module can now be programmed with the updated application. You can now press the knob to unlock the GUI and afterwards turn the knob to change the value of the CircleProgress.
Tip
Step 5: Control the LED Ambient Light
The display knob module has a ring of red, green and blue LEDs which can be turned on for ambient light around it. Each color channel is controlled with a PWM-capable output from the MCU. In the application template we are using, a function for controlling this light is implemented in TSD_drivers/knob_interface.cpp and available through TSD_drivers/knob_interface.hpp:
TSD_drivers/knob_interface.hpp
...
void knobSetAmbientLightRGB(uint8_t red, uint8_t green, uint8_t blue);
...
In this demo, we will control the color of the light based on the value of the CircleProgress. We will do this such that the light is blue when the value is zero and gradually turning red through purple as the value increases. This can be done by adding the following code to Screen1View:
TouchGFX/gui/src/screen1_screen/Screen1View.cpp
#include <gui/screen1_screen/Screen1View.hpp>
#ifndef SIMULATOR
#include "knob_interface.hpp"
#endif
void Screen1View::setupScreen()
{
Screen1ViewBase::setupScreen();
changeValue(0); // Ensure that the system state is synchronized at start-up
}
void Screen1View::changeValue(int change)
{
const int newValue = valueCircleProgress.getValue() + change;
valueCircleProgress.updateValue(newValue, 0);
#ifndef SIMULATOR
const int value = valueCircleProgress.getValue(); // Always inside the range set in TouchGFX Designer (0-100)
//Update ambient light
const uint8_t red = (value * 255) / 100;
const uint8_t green = 0;
const uint8_t blue = 255 - red;
knobSetAmbientLightRGB(red, green, blue);
#endif
}
When running the application on the display module, it will now activate the ambient light and change the color when the knob is unlocked and turned.
Tip
Note
Step 6: Send Data on UART
The knob display module also has a UART communication interface available on the GPIOs in its connector cable. In this step, we will make the application transmit an ASCII string on the UART every time the value of the CircleProgress is updated. The UART interface is configured in the template project for the display module and available through TSD_drivers/knob_interface.hpp:
TSD_drivers/knob_interface.hpp
...
void knobUartSendByte(uint8_t data);
void knobUartSendBytes(uint8_t* data, uint16_t size);
void knobUartSendString(uint8_t* data);
...
Here, we will use the knobUartSendByte function multiple times to transmit the different ASCII characters of a string representing the value:
TouchGFX/gui/src/screen1_screen/Screen1View.cpp
void Screen1View::changeValue(int change)
{
const int newValue = valueCircleProgress.getValue() + change;
valueCircleProgress.updateValue(newValue, 0);
#ifndef SIMULATOR
const int value = valueCircleProgress.getValue(); // Always inside the range set in TouchGFX Designer (0-100)
//Update ambient light
const uint8_t red = (value * 255) / 100;
const uint8_t green = 0;
const uint8_t blue = 255 - red;
knobSetAmbientLightRGB(red, green, blue);
// Send new value on UART
knobUartSendByte('0' + value / 100);
knobUartSendByte('0' + (value % 100) / 10);
knobUartSendByte('0' + value % 10);
knobUartSendByte('\r');
knobUartSendByte('\n');
#endif
}
When running the application on the display module, it will now transmit data on the UART when the knob is unlocked and turned. This UART data can be received on the PC through the same interface as used for UART programming. This requires the switch on the backside of the adapter board to be set to "UART" position and the USB-to-UART driver installed on the PC, as described in the Programming using UART section.
The UART communication can be received using a "Terminal application" installed on the PC, e.g. Tera Term or PuTTY. Here is the needed interface configuration done in Tera Term as example:

Screenshot of the interface configuration in Tera Term. Note that the COM Port will vary and should be selected to match your setup.
After connecting the terminal application on the PC, it should now start receiving and showing the value strings when the knob is turned:
Step 7: Receive Data from UART
In this step, we will make the application react when receiving UART commands from the PC, according to this simple protocol:
| Command | Action |
|---|---|
| 'q' | Hide wifi icon |
| 'w' | Show wifi icon |
| 'e' | Hide alarm icon |
| 'r' | Show alarm icon |
There is a function for receiving UART data available in the project template in TSD_drivers/knob_interface.hpp:
TSD_drivers/knob_interface.hpp
...
uint8_t knobUartReceiveByte(uint8_t* data);
...
This function returns a non-zero value when new data has been received and copies the received byte to the given pointer. We will use this function to poll the UART receive buffer in every tick of the TouchGFX application. To do so, we first need to add a TouchGFX interaction to call a method every tick:
| Interaction | Properties |
|---|---|
| Poll UART communication |
|
We now add the following code to read the UART and implement the above protocol:
TouchGFX/gui/include/gui/screen1_screen/Screen1View.hpp
public:
...
virtual void toggleLock();
virtual void decrementValue();
virtual void incrementValue();
void changeValue(int change);
virtual void receiveUartCommand();
TouchGFX/gui/src/screen1_screen/Screen1View.cpp
void Screen1View::receiveUartCommand()
{
#ifndef SIMULATOR
uint8_t data;
if (knobUartReceiveByte(&data))
{
switch (data)
{
case 'q':
wifiImage.setVisible(false);
wifiImage.invalidate();
break;
case 'w':
wifiImage.setVisible(true);
wifiImage.invalidate();
break;
case 'e':
alarmImage.setVisible(false);
alarmImage.invalidate();
break;
case 'r':
alarmImage.setVisible(true);
alarmImage.invalidate();
break;
}
}
#endif
}
When running the updated application, it is now possible to hide and show the two icons on the display according to the protocol in the table above. This can be tested with the same terminal program used in the previous step. Here, the commands can simply be sent by selecting the window of the connected terminal program and clicking the q, w, e, and r key on the keyboard.
Tip
This step concludes the tutorial.
Note
Further reading
Alternative Programming Methods
Besides the default USB programming method, there are two alternative ways to program the knob module. These are described in the following sections.
Programming using UART
The display module adapter board has a USB-to-UART chip, which enables serial communication between the PC and the MCU inside the display module. It is also possible to program the display module this way, utilizing the UART interface of the MCU bootloader. Compared to the USB load this has the advantage of not needing the button presses to get into bootloader mode, but the disadvantage of slower transfer speed and the need for installing the USB-to-UART chip driver on the PC. The UART load can be done by running the script UART_load.rb from the Loading folder, e.g. with the following command:
ruby UART_load.rb ../MyTGFXApp_cmake/build/debug_GCC_STM32C5A3CGT6/MyTGFXApp_complete.hex
It has the following prerequisites:
- Display module is connected to the PC through a data USB cable.
- The switch on the backside of the adapter board is set to "UART" position.
- STM32CubeProgrammer (v2.22.0 or later) is installed in the default location.
- Driver for the USB-to-UART is installed. Can be downloaded here: https://www.wch-ic.com/downloads/ch341ser_exe.html.
- The BOOT_SEL option byte in the MCU must be set to TRUE. This is default in the delivered modules.
Programming using external ST-LINK
Another way to program the module is by using an external debugger with wire connections to the pins on the display module adapter board. When using an ST-LINK/V2 debugger it needs wire connections like shown in the table below.
| ST-LINK/V2, 20-pin connector | Knob adapter board |
|---|---|
| 1 | 5V |
| 4 | GND |
| 7 | SWD |
| 9 | SWC |
To be able to also load the external flash using ST-LINK and STM32CubeProgrammer, an External Loader file is provided in the project in Loading/W25Q64JV_TSD_C5A3ZG_KNOB.stldr.
One advantage of using an ST-LINK is that it besides programming also enables debugging of the running application, e.g. using STM32CubeIDE.




