STM32 - How To
Jump to navigation
Jump to search
Fix Eclipse's Small Icon issue
1 Find the installation folder for your STM32CubeIDE_1.9.0. For me, this is C:\ST\STM32CubeIDE_1.9.0\STM32CubeIDE 2 Open stm32cubeide.ini in your favorite text editor 3 Add the following bolded line of text in the -vmargs section: -vmargs -Dswt.autoScale=quarter -Dosgi.requiredJavaVersion=1.8 -Dosgi.instance.area.default=
printf()
The printf() function is commonly used for "C" programming to output formatted text to a terminal. This allows easy debugging in that variables, intermediate values, entering/leaving functions can be monitored and status displayed with the printf() stdio library function. The STM32 development environment provides an stdio printf() library function, but some "connecting glue" is required to provide the desired output. The printf() function calls _write() to perform an output function. See the _write() function in syscalls.c. This function was given the (weak) attribute, allowing the function to be over-written (without causing a warning or error message.) This default _write() function calls __io_putchar() to output one character at a time. Redefining either function to write characters to a serial port will produce a functioning printf(). Here's some sample code to get your printf() functional: /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ #define HAL_SMALL_DELAY 40 // Define serial input and output functions using UART2 int __io_putchar(int ch) { HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, HAL_SMALL_DELAY); // infinite delay value return 1; } // Read a character from the UART with small timeout // If HAL_UART_Receive() returns error, return EOF (-1), else return the character read int __io_getchar(void) { uint8_t byte; HAL_StatusTypeDef hal_status = HAL_UART_Receive(&huart2, &byte, sizeof(byte), HAL_SMALL_DELAY); if(HAL_OK == hal_status) { return byte; } else return EOF; } /* USER CODE END 0 */
Alternate printf() output
There are times when you want to send formatted string output to some other device - some serial port, some other display, etc. Here's a way to accomplish this: // Print function that writes to alternate output device (this would normally be in a private header file) void dbg_printf(const char * format, ...); // Print the formatted string to a buffer, then send the buffer to alternate output device // Buffer used by vsprintf() to construct string for printing // This buffer has been enlarged to support long strings #define PRINT_BUFFER_SIZE 256 char dbg_buf[PRINT_BUFFER_SIZE]; // Place the buffer in global space vs stack space void dbg_printf(const char * format, ...) { va_list args; va_start(args, format); uint32_t length = (uint32_t)vsnprintf(dbg_buf,PRINT_BUFFER_SIZE,format,args); // print to buffer, return length alternate_write_bytes(dbg_buf,length); // write the buffer to alternate output device va_end(args); } --- The following macro, placed at the top of a file, will redefine all occurrences of printf() to use dbg_printf() instead: // For this file, define printf() to call dbg_printf #define printf(format, ...) dbg_printf(format,##__VA_ARGS__)
I2C
We will use the upper right two pins on the Arduino header. These are mapped as follows: D15: P8_8 (SCL) and D14: P8_9 (SDA) By default, STM's pin selector will map I2C1 to different pins. To change this, open the project's .ioc file, select "Pinout & Configuration" tab Select Connectivity category, and then I2C1 If not yet enabled, enable it in the "Mode" section as "I2C" In the "GPIO Settings" section, we want it to display PB8 for I2C_SCL and PB9 for I2C_SDA. If it doesn't, go to the Pinout view on the right, select the PB8 pin, and then select the "I2C1_SCL" function. Doing this will usually define both pins. If not, repeat the process for PB9, with function I2C_SDA. Here's my example of several interactive I2C commands that work with the command_line code: cl_i2c.c cl_i2c.h
USART Serial
Until recently, I've been assuming the USART serial interfaces each had both transmit and receive FIFO buffers. Well, it seems there is no buffering. The serial shift register has just one byte of storage for each RX and TX. This helps explain why I tend to miss characters during reception. To solve this issue, DMA is the answer. See my Wiki page: STM32_-_Using_DMA_for_Serial_Receive Links to web pages as I search for solutions: https://deepbluembedded.com/stm32-usart-uart-tutorial/ https://deepbluembedded.com/how-to-receive-uart-serial-data-with-stm32-dma-interrupt-polling/ https://community.st.com/s/article/faq-stm32-hal-driver-api-and-callbacks
Interrupts
If you are using a NUCLEO board, and started your project with STM32CubeIDE, you should select your board with the board selector. This will generate initialization code for your LED as well as your blue user button. Example: main.h: #define B1_Pin GPIO_PIN_13 #define B1_GPIO_Port GPIOC #define B1_EXTI_IRQn EXTI15_10_IRQn main.c: static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; /* USER CODE BEGIN MX_GPIO_Init_1 */ /* USER CODE END MX_GPIO_Init_1 */ /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE(); __HAL_RCC_GPIOD_CLK_ENABLE(); /*Configure GPIO pin : B1_Pin */ GPIO_InitStruct.Pin = B1_Pin; GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(B1_GPIO_Port, &GPIO_InitStruct); // At this point, the GPIO pin, PC_13, has been configured for input, no-pullup, // and configured to generate an interrupt with rising signal (when enabled)
The blue push button on a NUCLEO board has hardware filtering to help prevent "switch bounce".
A 4.7K external pull-Up, along with the .1uF capacitor create a good hardware debounce.
No software debounce should be necessary.
// These two lines of code configure interrupt priority and enable the interrupt: /* EXTI interrupt init*/ HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0, 0); HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
Now, every time the blue user button is pressed, an interrupt is generated.
Really? What's it do? Answer: Nothing. The function stub is empty. You need to write your own function.
// Need to over-ride the weak EXTI_Callback function stub with your own function. // Example: Have it toggle the LED2 LED Pin // Interrupt is generated on the release of the button, creating a rising edge signal. void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin); }
Just by adding the above function, over-ridding the stub function with the same name,
you should now see the LED toggle as the button is pressed.
Reference:
https://deepbluembedded.com/stm32-external-interrupt-example-lab/
Using On-board Flash for data storage
For most any processor with on-board program flash, this same flash can be used to store data. As an example, let's allocate some of this flash and use it as part of a LittleFS Flash File System.
ARM - STM32 Semihosting
See: https://developer.arm.com/documentation/dui0471/g/Bgbjjgij "Semihosting is a mechanism that enables code running on an ARM target to communicate and use the Input/Output facilities on a host computer that is running a debugger." This is a abstract functionality that ARM has provided target side hardware support for. The software that implements this is a mystery, implemented in a library without source code. The software needed on the host side is also a bit of a mystery. Here's a collection of links followed by a step by step document for Semihosting with STM32: https://developer.arm.com/documentation/101453/0100/uVision-Windows/Debug--printf--Viewer https://percepio.com/arm-itm/ https://blog.segger.com/getting-printf-output-from-target-to-debugger/ Segger implemented something called "RTT", Real Time Transfer, to support this
STM32 Semihosting Document
I found this great step by step document, for STM32, that's the answer to my quest: https://community.st.com/s/article/how-to-use-semihosting-with-stm32cubeide-and-stm32 At first, I was unable to get this to function. There appeared to be some issue with the Debugger Startup Command: “monitor arm semihosting enable” The debugger would start, connect, and after a little time a "Problem Occurred" dialog box would be displayed. Well, after contacting a buddy within STMicro, he performed the documented steps and replied "all is working". It appears I have trouble reading the words: Note: ST-LINK GDB server does not support semihosting. The "trick is to change the debugger from the default, "GDB", to "OpenOCD". This change is done via the "Debugger" tab within the "Debug Configurations" dialog. Change "Debug Probe" from "ST-LINK (ST-LINK DGB server)" to "ST-LINK (OpenOCD)" Note: This functionality has been tested with NUCLEO-C031C6 (STM32-C031C6). Works great!
Example Source Code
Morse Code
When only a single LED is available, a trained Boy Scout can read the short and long pulses associated with Morse Code.
morse_code.c
hexdump
Memory / register display routine
// Here's the output I want from a hexdump(address, 23) routine: //00000000 02 03 1f 00 0d 00 00 00 31 32 33 00 00 00 00 00 |........123.....| //00000010 00 00 00 00 00 00 00 |.......| void hexdump(void * address, int32_t count); hexdump.c