stm32CubeMX, with Keil, uses the HAL library (or standard library) for serial communication

Introduction

Today we will learn the operation of the STM32CubeMX serial port and the configuration of the HAL library serial port. We will explain the use and specific functions of each module in detail, and implement the Printf function function redirection and USART interrupt reception based on the HAL library. This series of tutorials combines the HAL library with STM32CubeMX is explained together so that you can learn the use of each module more quickly

Article directory

  • introduction
  • Preface
  • 1. Introduction to serial communication
    • 1. Serial communication
    • 2. Classification of serial communication:
      • 2.1 Serial communication
      • 2.2 Parallel communication
    • 3. Serial communication
    • 4.STM32 serial communication interface
    • 5. Data transmission format/communication protocol
    • 6. USART usage steps
  • 2. Preparation tools
  • 3. Configure the project through STM32CubeMX
  • 4. Introduction to HAL library USART function library
    • 1. Serial port sending/receiving function
      • 1.1. Serial port sending data function
      • 1.2. Interrupt receiving data function
    • 2. Serial port interrupt function
      • 2.1 Serial port reception interrupt callback function
      • 2.2 Serial port interrupt processing function
    • 3. Serial port query function
    • 4. Redefine the printf function (take serial port UART1 as an example)
  • 5. Configure the code in keil5
    • 1. Open the project created through CubeMX
    • 2. Test the redirected printf function
    • 3. Test serial port interruption (send “*” to print, send “#” to stop)
      • 3.1 Requirements:
      • 3.2 Ideas:
      • 3.3 Operation effect
      • 3.4 Use Keil’s software simulation logic analyzer function to observe the serial port output waveform
  • Summarize
  • refer to

Foreword

For example: With the continuous development of artificial intelligence, machine learning technology is becoming more and more important. Many people have started learning machine learning. This article introduces the basic content of machine learning.

1. Introduction to serial communication

1. Serial communication

Serial communication refers to a communication method that transmits data bit by bit between peripherals and computers through data signal lines, ground lines, control lines, etc., such as SPI communication, USART communication, EEPROM communication, etc. To put it simply, serial communication realizes information exchange between the host computer (PC) and the slave computer (such as STM32).

The upper computer (PC) realizes data reception and transmission through serial port debugging assistant, etc.;

The lower computer (STM32) realizes the reception and transmission of characters or strings through functions such as printf() and getchar().

2. Classification of serial communication:

There are two ways for the processor to communicate with external devices:

2.1 Serial communication

Data is transmitted in bit order, such as 8 data bits, which is slow but takes up less pin resources.

? According to the direction of data transmission, it is divided into:

Simplex: Data transmission only supports data transmission in one direction. (Only receive but not send or only send but not receive, the mode is fixed)
Single-duplex: Allows data to be transmitted in both directions, but at a certain time, only allows data to be transmitted in one direction. (Can send and receive, but not at the same time)
Full-duplex: Allows data to be transmitted in both directions simultaneously. (Can send and receive, and can do it at the same time)

2.2 Parallel communication

Each bit of data is transmitted at the same time, such as 8 data bits are transmitted at the same time, occupying pin resources is slow, but the speed is fast.

3. Serial communication

Serial communication can be divided into:

Synchronous communication: Transmission of synchronous signals with clock, such as SPI, IIC communication, etc.
Asynchronous communication: without clock synchronization signal, such as UART (Universal Asynchronous Receiver Transmitter), single bus, etc.

4.STM32 serial communication interface

UART: Universal Asynchronous Receiver-Transmitter
USART: Universal synchronous/asynchronous receiver-transmitter (two modes switchable)
The STM32F103 series provides 5 serial ports, including 3 USARTs and 2 UARTs.

The pins of the serial port are as shown in the figure below:

5. Data transmission format/communication protocol

Serial communication must have a suitable communication protocol.

Communication protocol refers to a rule and agreement that must be followed by communicating parties to complete information exchange. For example, two people agree on when to communicate, whether to communicate in Chinese or English, and what content to communicate.

1. Starting position

? When no data is sent, the data line is in a logic “1” state; a logic “0” signal is first sent to indicate the start of character transmission.

2. Data bits

? Immediately after the start bit, the data bit represents the actual information to be sent or received, usually 8 or 9 digits.

3. Parity bit

? You can choose whether to add a parity bit at the end of the data bits to detect whether the data transmission is correct.

4. Stop position

? Flag bit representing the end of information transmission, which can be 1 bit, 1.5 bits or 2 bits. The more stop bits there are, the slower the data transmission rate is.

5. Baud rate setting

The baud rate represents the number of transmitted symbols per second and is an indicator of the data transmission rate. The unit is Baud. There is another term called bit rate, which represents the number of binary bits transmitted per second, in bit/s.

? Bit refers to one bit of information. When data is represented in binary, 0 is one bit and 1 is also one bit of information. It is fixed and one bit represents one bit in binary.

? Usually when describing code elements, we will say M-ary code elements. For example, octal system, we know that octal system contains eight kinds of data from 0 to 7, but the computer only recognizes two kinds of data: 0 and 1. If we want to send these eight kinds of data to the computer, we can use a group of 3 bits. means, that is, there are eight groups of 000,001,…,111, so an octal symbol means that it carries 3 bits, and the bit rate at this time is three times the baud rate. Then, an M-ary symbol carries log2 M bits.

6. USART usage steps

The general steps for serial port settings can be summarized as the following steps:

1) Serial port clock enable GPIO clock enable

2) Serial port reset

3) GPIO port mode setting

4) Serial port parameter initialization

5) Enable interrupts and initialize NVIC (this step is only required if interrupts need to be enabled)

6) Enable serial port

7) Write an interrupt handling function

2. Preparation tools

1. STM32 development board (mine is STM32F103C8T6)
2. STM32CubeMx software, IDE: Keil5 software
3. STM32F1xxHAL library
4. Serial port: Use USART1 PA9, PA10

3. Configure the project through STM32CubeMX

(1) Open STM32CubeMX and click on the main interface: ACCESS TO MCU SELECTOR:

(2) Select the microcontroller model STM32F103C8T6 and click to start the project:

(3) Turn on the high-speed clock (HSE) under RCC and select the external crystal oscillator:

(4) Select the serial port line under SYS

(5) Select the serial port USART1 and set MODE to asynchronous communication (Asynchronous)
Basic parameters: baud rate 115200 Bits/s. The transmission data length is 8 Bit. Parity check is none, stop bit 1 is enabled for both reception and transmission.

(6) Set NVIC to enable receive interrupt

(7) Set the project name, storage path and select the IDE to be used, and finally create the project project file


4. Introduction to HAL library USART function library

1. Serial port sending/receiving function

HAL_UART_Transmit(): Serial port sends data, using timeout management mechanism;
HAL_UART_Receive(): The serial port receives data and uses the timeout management mechanism;
HAL_UART_Transmit_IT(): Serial port interrupt mode transmission;
HAL_UART_Receive_IT(): Serial port interrupt mode reception;
HAL_UART_Transmit_DMA(): Serial port DMA mode transmission;
HAL_UART_Transmit_DMA(): Serial port DMA mode reception;

The parameters of these functions are basically the same. Let’s choose two used in the code to explain in detail:

1.1. Serial port sending data function

Function Description:
HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);

Features:

The serial port sends data of specified length. If the transmission is not completed after the timeout, it will no longer be sent and the timeout flag (HAL_TIMEOUT) will be returned.

Example:
HAL_UART_Transmit( & amp;huart1, (uint8_t *)ZZX, 3, 0xffff);
The meaning of the above function is that the serial port USART1 sends three bytes of data, and the maximum transmission time is 0xffff

1.2. Interrupt receiving data function

Interrupt receiving data:
HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

Features:

The serial port interrupts reception and receives data of specified length in interrupt mode. The general process is to set the data storage location, receive the data length, and then enable the serial port reception interrupt. When data is received, the serial port interrupt will be triggered. Then, the serial port interrupt function processes until the specified length of data is received, then turns off the interrupt, enters the interrupt reception callback function, and no longer triggers the reception interrupt. (Only triggers an interrupt once)

Example:
HAL_UART_Receive_IT( & amp;huart1,(uint8_t *) & amp;value,1);
The meaning of the above function is to trigger an interrupt to receive a character and store it in value.

2. Serial port interrupt function

HAL_UART_IRQHandler(UART_HandleTypeDef *huart): Serial port interrupt handling function
HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart): Serial port transmission interrupt callback function
HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart): The serial port sends half of the interrupt callback function (less used)
HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart): Serial port reception interrupt callback function
HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart): The serial port receives half of the callback function (less used)
HAL_UART_ErrorCallback(): Serial port receiving error function

Choose two used in the code and explain them in detail:

2.1 Serial port reception interrupt callback function

Serial port reception interrupt callback:
HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);

Features:

After the interrupt of the HAL library is completed, it will not exit directly, but will enter the interrupt callback function, where the user can set the code. After the serial port interrupt reception is completed, it will enter this function. This function is an empty function and the user needs to Revise

Example:
HAL_UART_RxCpltCallback( & amp;huart1){ //User-defined code }

2.2 Serial port interrupt processing function

Serial port interrupt handling:
HAL_UART_IRQHandler(UART_HandleTypeDef *huart);

Features:
Determine and process the received data to determine whether it is a sending interrupt or a receiving interrupt, then send and receive data, and use it in the interrupt service function. If data is received, the receiving interrupt processing function will be executed; if data is sent, the sending interrupt processing function will be executed.

3. Serial port query function

HAL_UART_GetState(): Determine whether UART reception is completed or whether sending data is busy

Example:
while(HAL_UART_GetState( & amp;huart4) == HAL_UART_STATE_BUSY_TX) //Detect the end of UART transmission

4. Redefine the printf function (taking serial port UART1 as an example)

Let’s take USART receiving and sending as an example for a brief explanation. First, we need to redirect the printf function. In the C language, we can directly call the printf printing function as long as it contains the library. However, for a microcontroller, even if it contains Without this library, we don’t know who the print output party is. At this time, we need to tell the microcontroller to print data to the computer PC. Therefore, we need to rewrite the printf function, and rewrite the fputc and fgetc functions:

//Rewrite fget and fput functions
#include <stdio.h>
/**
  * Function: Redirect c library function printf to DEBUG_USARTx
  * Input parameters: None
  * Return value: None
  * Description: None
  */
int fputc(int ch, FILE *f)
{<!-- -->
  HAL_UART_Transmit( & amp;huart1, (uint8_t *) & amp;ch, 1, 0xffff);
  return ch;
}
/**
  * Function: Redirect c library functions getchar and scanf to DEBUG_USARTx
  * Input parameters: None
  * Return value: None
  * Description: None
  */
int fgetc(FILE *f)
{<!-- -->
  uint8_t ch = 0;
  HAL_UART_Receive( & amp;huart1, & amp;ch, 1, 0xffff);
  return ch;
}

5. Configure code in keil5

1. Open the project created through CubeMX

2. Test the redirected printf function

Write the redirected printf function in the main function:

Then test in a while loop:

The test results are shown in the figure:

The USAT1 serial port prints out “hello windows!” every 1 second.

3. Test serial port interruption (send”*”print, send”#”stop)

3.1 Requirements:

When the host computer sends a character “#” to stm32, stm32 pauses sending “hello windows!”; after sending a character “*”, stm32 continues to send;

3.2 Ideas:

First, we set up a receive interrupt function HAL_UART_Receive_IT for STM32. This function is to receive the command order sent by the host computer. When the host computer sends the command, an interrupt is triggered. After STM32 receives the command in the interrupt mode, The interrupt will not be exited, but the interrupt rollback function HAL_UART_RxCpltCallback will be triggered. The inside of the interrupt rollback function function body is our user-defined code, so we can perform data here according to different instructions. Send or stop sending.

The final main function content is as follows:

/* USER CODE BEGIN Header */
/**
  *************************************************** ******************************
  * @file: main.c
  * @brief: Main program body
  *************************************************** ******************************
  * @attention
  *
  * Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  *************************************************** ******************************
  */
/* USER CODE END Header */
/* Includes ----------------------------------------------- ------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"
#include <String.h>



//Rewrite the fget and fput functions! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! !
#include <stdio.h>
/**
  * Function: Redirect c library function printf to DEBUG_USARTx
  * Input parameters: None
  * Return value: None
  * Description: None
  */
int fputc(int ch, FILE *f)
{<!-- -->
  HAL_UART_Transmit( & amp;huart1, (uint8_t *) & amp;ch, 1, 0xffff);
  return ch;
}
/**
  * Function: Redirect c library functions getchar and scanf to DEBUG_USARTx
  * Input parameters: None
  * Return value: None
  * Description: None
  */
int fgetc(FILE *f)
{<!-- -->
  uint8_t ch = 0;
  HAL_UART_Receive( & amp;huart1, & amp;ch, 1, 0xffff);
  return ch;
}



char order;//Instruction 0: Stop 1: Start
char message[]="hello windows!\\
";//Output message
char tips[]="CommandError\\
";//Tip 1
char tips1[]="Start.....\\
";//Tips 2
char tips2[]="Stop......\\
";//Tips 3
int flag=0;//Flag 0: Stop sending 1. Start sending



void SystemClock_Config(void);


void test()
{<!-- -->
printf("helloworld");
HAL_Delay(1000);
}

/**
  * @brief The application entry point.
  * @retval int
  */
int main(void)
{<!-- -->

  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
\t
\t
//Set up to accept interrupt
// The serial port interrupts reception and receives data of specified length in interrupt mode. The general process is to set the data storage location, receive the data length,
//Then enable the serial port receive interrupt. When data is received, the serial port interrupt will be triggered. Then, the serial port interrupt function is processed until the specified length data is received.
// Then turn off the interrupt, enter the interrupt reception callback function, and no longer trigger the reception interrupt. (Only triggers an interrupt once)
HAL_UART_Receive_IT( & amp;huart1, (uint8_t *) & amp;order, 1);

//When flag is 1, send information once per second
//When flag is 0, stop
  while (1)
  {<!-- -->
if(flag==1)
{<!-- -->
\t//send Message
HAL_UART_Transmit( & amp;huart1, (uint8_t *) & amp;message,, strlen(message),0xFFFF);
//delay
HAL_Delay(1000);
}
\t\t\t
// test();
  }
}


//After the reception interrupt of the HAL library is completed, it will not exit directly, but will enter the interrupt callback function, where the user can set the code,
//After the serial port interrupt reception is completed, this function will be entered. This function is an empty function and the user needs to modify it by himself.
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{<!-- -->
//When the input command is 0, send a prompt and change the flag
if(order=='#'){<!-- -->
flag=0;
HAL_UART_Transmit( & amp;huart1, (uint8_t *) & amp;tips2, strlen(tips2),0xFFFF);
}
\t
//When the input command is 1, send a prompt and change the flag
else if(order=='*'){<!-- -->
flag=1;
HAL_UART_Transmit( & amp;huart1, (uint8_t *) & amp;tips1, strlen(tips1),0xFFFF);
}
\t
//When the input command does not exist, send a prompt and change the flag
else {<!-- -->
flag=0;
HAL_UART_Transmit( & amp;huart1, (uint8_t *) & amp;tips, strlen(tips),0xFFFF);
}

//reset interrupt
HAL_UART_Receive_IT( & amp;huart1, (uint8_t *) & amp;order, 1);
}



/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{<!-- -->
  RCC_OscInitTypeDef RCC_OscInitStruct = {<!-- -->0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {<!-- -->0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  if (HAL_RCC_OscConfig( & amp;RCC_OscInitStruct) != HAL_OK)
  {<!-- -->
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig( & amp;RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
  {<!-- -->
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{<!-- -->
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {<!-- -->
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef USE_FULL_ASSERT
/**
  * @brief Reports the name of the source file and the source line number
  * where the assert_param error has occurred.
  * @param file: pointer to the source file name
  * @param line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{<!-- -->
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\\
", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

3.3 Operation effect

The computer sends “*” and the serial port prints “hello windows!”

The computer sends “#” and the serial port stops sending “hello windows!”

3.4 Use Keil’s software simulation logic analyzer function to observe the serial port output waveform

From the waveform diagram, it can be seen that the sending data cycle is about 1s:

When viewing the serial port communication waveform on the virtual oscilloscope, because the baud rate is very high, you can only see a slightly thicker line. To select it, use Ctrl + mouse wheel and zoom continuously to see the serial port frame data waveform, so that you can measure it. time, baud rate.

Summary

The difference between RS232 serial port standard and TTL level

RS232 is an asynchronous serial communication standard that defines the level and format of signals between communication devices. It uses a level of ±12V to represent binary 1 and 0, 1 represents -12V to -3V, and 0 represents +3V~+12V.

The TTL level standard adopts the 0-5V level range, 0V represents binary 0, and 3-5V represents binary 1.

Since most of the IO ports of microcontroller chips adopt the TTL standard with a low level of 0 and a high level of 1, they cannot be directly connected to the RS232 standard. Therefore, level conversion needs to be performed through the “USB/TTL to 232” module.

The CH340 module integrates the CH340G chip, which can complete the conversion between USB and UART. It converts the USB standard ±5V level to the RS232 standard ±12V level, or directly outputs the 3.3V TTL standard level, realizing the connection between USB and UART.

STM32 serial communication programming

1. Install the STM32CubeMX software, select USART1 as the serial port channel, set the baud rate to 115200bps, 1 stop bit, and no parity. Generate initialization code.

2. Open the stm32 project in Keil, add the HAL serial port driver header file, and implement the serial port sending and receiving functions.

3. Write the main function, initialize the serial port, and then enter the sending loop, sending the “hello windows!” string each time.

4. At the same time, enable the serial port reception interrupt, and judge the received character in the interrupt service program. If it is ‘#’, the transmission will be suspended. If it is ‘*’, the transmission will be resumed.

5. Compile and download to the development board, use the serial port assistant software to connect the PC and the development board, and test whether the serial port communication function is normal.

In short, the serial port initialization code is generated through STM32CubeMX, the HAL library function is used to implement serial port communication, and the sending process is controlled by receiving interrupts, thereby realizing condition-based serial port data sending.

Reference

STM32 USART-serial communication (including detailed analysis of serial port experiments)

[STM32] HAL library STM32CubeMX tutorial 11-DMA (serial port DMA sending and receiving)