2025-08-25 7:36 AM
Dear STM32 Community,
We believe that the example projects provided for the STM32Cube embedded software are a valuable resource when developing projects.
We would like to better understand how developers use these examples in their workflow and explore ways to improve them. Therefore, we have a few questions we would like to ask:
From where do you obtain the examples, and why?
How do you use the examples in your work with STM32 embedded software?
What is working well, and what can we do to improve the examples and/or your experience working with them?
You can either reply directly in this thread or feel free to send me a private message.
Best regards,
Emil
2025-08-25 10:05 AM
More content without HAL please ;)
2025-08-25 10:11 PM
I find that I cannot use most of the examples for several reasons.
Now, the github examples I don't find useful at all, they generally don't solve what I need to solve.
The example projects, though, I find somewhat frustrating. You create example projects for ST boards, not at all unreasonable, but neglect (likely because you use BSP drivers) a bare bones, "It's not our hardware configuration" custom boards.
Sometimes, your examples assume too much, because you, of course, know exactly what you're doing. As I've mentioned in the past, every program is written with an assumption of "of course they'll write it like that to do it this way." When the assumptions you make are not the same as the person reading the example, then there's a problem.
I never, for example, got Touch-GFX to work well, for both that reason and the fact that the two tool chains were not well integrated when I tried them.
Hope this helps a bit.
2025-08-27 2:25 AM
> From where do you obtain the examples, and why?
All of them depending on purposes. STM32Cube based projects use STM32Cube MCU packages. If I need to adapt a project for another MCU or board, I prefer to fork your github project and keep my changes under version control.
>How do you use the examples in your work with STM32 embedded software?
All of that your have listed.
>What is working well, and what can we do to improve the examples and/or your experience working with them?
Your support works well here at community.st.com.
Regarding improvements.
Some projects have no ioc-files. It makes difficult using them as reference code.
It would be nice if you contribute to the TinyUSB project to support your chips with two USBs (STM32N6 etc).
2025-08-29 12:20 AM - edited 2025-08-29 12:34 AM
Hello @Radosław
Are you missing LL examples? If yes, what types of examples are you missing?
Best Regards,
Emil
2025-08-29 12:33 AM
Hello @Harvey White ,
Thanks a lot for your thorough answer, it helps a lot :)
For some of the points you made, i have a few follow up questions:
I use FreeRTOS, and have a well developed ecostructure. Your examples need to be adapted
Is this due to missing examples around FreeRTOS, or general you not being able to reuse or examples, since they require to much work to fit in your own ecosystem?
As is normal, the examples don't do what I need, so I need to deduce what I can.
Would you be able to give more info? Would like to understand why this is the case, and how we can do it different to help?
Finally it would also be very appreciated if you can describe what your challenges with GitHub examples are.
Again thanks a lot for your input.
Best Regards,
Emil
2025-08-29 2:36 AM
Emil thank You for response.
LL Is still HAL, all register names are changed, and each CPU package have only few of them.
I'm was very happy to see snippets for STM32L0, as a packege and at the and of Reference Manual.
For Cube Hal St provides many Aplications note, but i'm still waiting for thinks like cookbooks (AN4539 for example).
Hal gives easy and quick start, but any knowledge about MCU architecture, posible option even not standard or typical,
Additionally, for example, In hal ST provides functrions for dlash programing that need to be called from RAM,
ex. __RAM_FUNC HAL_StatusTypeDef HAL_FLASHEx_ProgramParallelHalfPage(uin .....
There is no explanation WHY, additionally Linker for GCC do not have proper section for that, (so example is incomplete).
2025-08-29 7:34 AM
Thanks. Let's see if I can address the points:
--------------------------------------------------------
For some of the points you made, i have a few follow up questions:
I use FreeRTOS, and have a well developed ecostructure. Your examples need to be adapted
Is this due to missing examples around FreeRTOS, or general you not being able to reuse or examples, since they require to much work to fit in your own ecosystem?
---------------------------------------------------------
Let's take the HAL_SPI drivers: They work well enough, although some have complained about excess code.
In theory, they have the equivalent of semaphores to keep the code well behaved in a multi threaded operating system, they're not explained. (I regard HAL code to be poorly commented, but that's just personal) In actuality, I have to put semaphores around them to keep everything well behaved. In another specific example, many displays (ILI9341 chip, 320 * 240 color TFT) cannot be used without some modification to your drivers. Your drivers assume that (hardware wise) you assert CS, transmit or receive, release CS. The ILI9341 needs CS asserted, command bytes sent, data bytes sent, and then CS released. Can't use the hardware to do this, need to program it. I also am programming in C++, since the graphics system I wrote won't work otherwise.
int32_t HAL_SPI::Send(
uint8_t* cmdptr,
uint32_t cmd_count,
uint8_t* dataptr,
uint32_t data_count,
bool USE_CS,
enum SPI_TRANSFER_MODE Override)
{
enum SPI_TRANSFER_MODE NEW_mode = OUT_mode;
// int old_divisor;
// ********************************** check input parameters***********************************
// need to check overrides
switch (Override)
{
case SPI_DMA: // TRANSMIT IS DMA FROM BUFFER, RECEIVE IS IRQ QUEUE (MAY CHANGE)
case SPI_IRQ: // TRANSMIT IS IRQ DRIVEN
case SPI_BLOCKING: // TRANSMIT AND RECEIVE ARE BYTE BLOCKING
{
NEW_mode = Override;
break;
}
default:
{
NEW_mode = OUT_mode;
// leave mode as is, cannot override
}
}
switch (NEW_mode)
{
case SPI_BLOCKING: // TRANSMIT AND RECEIVE ARE BYTE BLOCKING
{
time_start = _MICROSECOND_TIMER.Instance->CNT;
// always semaphore protected
if (!dedicated) xSemaphoreTake(busy, portMAX_DELAY);
// direct call to STMICRO driver, blocking mode
hspi->Instance->CR1 &= ~SPI_BAUDRATEPRESCALER_256;// reset to highest baudrate
hspi->Instance->CR1 |= transmit_divisor;
if (RESET.port != nullptr) RESET.set(); // make sure reset if high
if (USE_CS) RESET_CS(); // CS low if needed
if (cmdptr != nullptr)
{
// command pointer says to send data with A0 down
if (A0.port != nullptr) A0.reset();
result = HAL_SPI_Transmit(hspi, cmdptr, cmd_count, timeout);
// place to add delay if available
}
if (dataptr != nullptr)
{
// command pointer says to send data with A0 down
if (A0.port != nullptr) A0.set();
result = HAL_SPI_Transmit(hspi, dataptr, data_count, timeout);
// place to add delay if available
}
// error reporting
if (USE_CS) SET_CS(); // CS high if needed
ERROR_CODE = hspi->ErrorCode;
ERROR_REASON = (enum COMM_ERROR_TYPE) ERROR_CODE;
operations++;
// record errors if needed
switch (result)
{
case HAL_ERROR: //= 0x01U,
{
fail++;
break;
}
case HAL_BUSY: //= 0x02U,
{
fail++;
break;
}
case HAL_TIMEOUT: //= 0x03U
{
fail++;
break;
}
case HAL_OK:
{
// was OK
succeed++;
break;
}
}
// return semaphore for next operation
if (!dedicated) xSemaphoreGive(busy);
time_stop = _MICROSECOND_TIMER.Instance->CNT;
delta_time = time_stop - time_start;
break;
}
case SPI_QUEUE: // TRANSMIT QUEUE SENDS
{
// always semaphore protected
if (!dedicated) xSemaphoreTake(busy, portMAX_DELAY);
for (uint32_t i = 0; i < data_count; i++)
{
// send data
xQueueSend(send_queue, &dataptr, portMAX_DELAY);
dataptr++;
}
operations++;
// return semaphore for next operation
if (!dedicated) xSemaphoreGive(busy);
break;
}
case SPI_IRQ: //
{
time_start = _MICROSECOND_TIMER.Instance->CNT;
// always semaphore protected
if (!dedicated) xSemaphoreTake(busy, portMAX_DELAY);
// direct call to STMICRO driver, blocking mode
xSemaphoreTake(done, portMAX_DELAY); // if can take, no active DMA
hspi->Instance->CR1 &= ~SPI_BAUDRATEPRESCALER_256;// reset to highest baudrate
hspi->Instance->CR1 |= transmit_divisor;
if (RESET.port != nullptr) RESET.set(); // make sure reset if high
if (USE_CS) RESET_CS(); // CS low if needed
if (cmdptr != nullptr)
{
// command pointer says to send data with A0 down
if (A0.port != nullptr) A0.reset();
result = HAL_SPI_Transmit(hspi, cmdptr, cmd_count,timeout);
// place to add delay if available
}
if (dataptr != nullptr)
{
// command pointer says to send data with A0 down
if (A0.port != nullptr) A0.set();
result = HAL_SPI_Transmit_IT(hspi, dataptr, data_count);
xSemaphoreTake(done, portMAX_DELAY);
}
// error reporting
if (USE_CS) SET_CS(); // CS high if needed
ERROR_CODE = hspi->ErrorCode;
ERROR_REASON = (enum COMM_ERROR_TYPE) ERROR_CODE;
operations++;
// record errors if needed
switch (result)
{
case HAL_ERROR: //= 0x01U,
{
fail++;
break;
}
case HAL_BUSY: //= 0x02U,
{
fail++;
break;
}
case HAL_TIMEOUT: //= 0x03U
{
fail++;
break;
}
case HAL_OK:
{
// was OK
succeed++;
break;
}
}
// return semaphore for next operation
xSemaphoreGive(done);
if (!dedicated) xSemaphoreGive(busy);
time_stop = _MICROSECOND_TIMER.Instance->CNT;
delta_time = time_stop - time_start;
break;
}
case SPI_DMA: // TRANSMIT IS DMA FROM BUFFER, RECEIVE IS IRQ QUEUE (MAY CHANGE)
{
time_start = _MICROSECOND_TIMER.Instance->CNT;
// always semaphore protected
if (!dedicated) xSemaphoreTake(busy, portMAX_DELAY);
// direct call to STMICRO driver, blocking mode
xSemaphoreTake(done, portMAX_DELAY); // if can take, no active DMA
hspi->Instance->CR1 &= ~SPI_BAUDRATEPRESCALER_256;// reset to highest baudrate
hspi->Instance->CR1 |= transmit_divisor;
if (RESET.port != nullptr) RESET.set(); // make sure reset if high
if (USE_CS) RESET_CS(); // CS low if needed
if (cmdptr != nullptr)
{
// command pointer says to send data with A0 down
if (A0.port != nullptr) A0.reset();
result = HAL_SPI_Transmit(hspi, cmdptr, cmd_count,timeout);
// xSemaphoreTake(done, portMAX_DELAY);
// place to add delay if available
}
if (dataptr != nullptr)
{
// command pointer says to send data with A0 down
if (A0.port != nullptr) A0.set();
result = HAL_SPI_Transmit_DMA(hspi, dataptr, data_count);
xSemaphoreTake(done, portMAX_DELAY);
}
// error reporting
if (USE_CS) SET_CS(); // CS high if needed
ERROR_CODE = hspi->ErrorCode;
ERROR_REASON = (enum COMM_ERROR_TYPE) ERROR_CODE;
operations++;
// record errors if needed
switch (result)
{
case HAL_ERROR: //= 0x01U,
{
fail++;
break;
}
case HAL_BUSY: //= 0x02U,
{
fail++;
break;
}
case HAL_TIMEOUT: //= 0x03U
{
fail++;
break;
}
case HAL_OK:
{
// was OK
succeed++;
break;
}
}
// return semaphore for next operation
xSemaphoreGive(done);
if (!dedicated) xSemaphoreGive(busy);
time_stop = _MICROSECOND_TIMER.Instance->CNT;
delta_time = time_stop - time_start;
break;
}
default:
{
// do nothing
}
} // end out mode switch
return HAL_OK;
}
The driver is designed to adapt to several situations:
HAL mode can be either blocking, IRQ, or DMA depending on setup.
SPI clock speed is set for each device.
Driver can set a command field and a data field.
A0 line is used to differentiate between them, which is defined (from the .IOC file) in setup. it can be omitted and either command or data fields can be omitted, defaulting to a single transmit sequence. Commands are short and sent blocking. Data is longer and may be sent with DMA.
Semaphores protect the code operation unless the interface is dedicated, in which case, at this level, busy semaphores are not checked. There's a busy at a higher level, so the overall driver is still protected. Busy at this level allows the interface to be shared (ex: Touchscreen and SD card).
The interface can be set up to run in an override mode, which allows short commands to be blocking I/O, and data transfers (writing to the display) to be DMA. Set up the driver for DMA and then override with blocking mode.
The particular answer to your question is the use of semaphores. None of your code allows for that. Granted, you're pushing AZURE, but AZURE has a very awkward queue mechanism (to me) and performs badly with C++ code, in my experience.
I've managed to work around the quirks in FreeRTOS, and even managed to piece together how to get Tracealyzer to work.
I could suggest that in the HAL code, you might want to put in a comment such as:
// SYSTEM SEMAPHORE may be needed for threadsafe operation
// insert take semaphore operation here to restrict access
// end SYSTEM SEMAPHORE take
................... HAL code ........................
// if using SYSTEM SEMAPHORE
// add code to release semaphore
// end SYSTEM SEMAPHORE give
As is normal, the examples don't do what I need, so I need to deduce what I can.
Would you be able to give more info? Would like to understand why this is the case, and how we can do it different to help?
As in the above example, the hardware CSS signal cannot be controlled without rewriting your chip support routine, so it needed a wrapper and I had to forgo using the hardware CSS signal. I had to figure out how your CSS signal was generated and where in the code. I don't really expect your HAL drivers to give this level of flexibility, but it's what I need. I don't trust the use of the lock you put in the HAL driver, not sure that it works, and I've had instances where it didn't. Your code is not written for multiple threads.
and lastly:
Finally it would also be very appreciated if you can describe what your challenges with GitHub examples are
For one, the GitHub examples may use LL drivers, which is fine, but, as above, are not written with the idea of an operating system. My code is generally parametrically driven, and I had enough fun writing graphics drivers at the chip level. I'd rather the support code take care of that, but in an understandable manner (comments!).
Hope this answers the questions.
2025-09-01 1:14 AM
Hello,
Yes, it's really useful.
I use Github, thanks to it I can understand how some peripherals works and I compare my code with ST's and see what I'm doing wrong. I use Git to not have 2 IDE and avoid to use computer storage.
I don't use to much examples in IDE because sometimes is not quick to adapt for my electronics card. Because of external input/output and wrong CPU for example. But we use it to test new components thanks to demo board too.
Keep doing many different examples please :D
2025-09-02 10:22 AM
The example code provided by y'all is very helpful, but almost entirely to be torn down. I agree with the other commentor that you rely too heavily on the BSP code for certain boards. For example, when developing camera applications for the STM32N6 recently, I had to dig heavily into the example code, because it relied so heavily on the BSP code for the Discovery Kit board. I was using a custom-spun board with the N6 as the centerpiece, but all of the examples assumed very specific setups about the DK, or used obfuscated BSP calls that jumped over a ton of steps, skipped over lots of HAL calls, etc.
I have also encountered, multiple times, instances where the example code contains a Cube MX bug fix or workaround that isn't mentioned anywhere, so when I try to recreate the project bones in Cube MX, it doesn't work due to these little tricks that are probably obvious for ST employees working with the chip for months.
I am also not a fan of the examples being primarily ST Cube IDE based. The way the file structures are set up in that IDE are very different than anything else, and it takes a significant amount of work, to the point where I'd rather recreate the project elsewhere, to attempt to migrate it to a CMake project for general development. I understand the "Oh need to use our own IDE" but there has to be a limit where it's understood that most developers use a different environment like VSCode, and a different build system.