STM32 - How To

From Embedded Workshop
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