USART of STM32

USE

USART (Universal Synchronous/Asynchronous Receiver/Transmitter) is a full-duplex universal synchronous/asynchronous serial transceiver, and the interface is a highly flexible serial communication device. Asynchronous communication is also commonly used for USART.

Full-duplex communication (telephony)

Communication allows data to be transmitted simultaneously in two directions, which is equivalent to the combination of two simplex communication methods in capability. Full-duplex refers to the two-way transmission of signals (A→B and B→A) that can be performed simultaneously (instantaneously). Refers to A→B and B→A at the same time, which is instantaneously synchronized. Communication allows data to be transmitted simultaneously in two directions, which is equivalent to the combination of two simplex communication methods in capability. Full-duplex refers to the two-way transmission of signals (A→B and B→A) that can be performed simultaneously (instantaneously). Refers to A→B and B→A at the same time, which is instantaneously synchronized.

USART main features

  • Full duplex asynchronous communication.

  • Single-wire half-duplex communication.

  • Separate transmitter and receiver enable bits.

  • Configurable multi-buffer communication using DMA.

  • Multiple flagged interrupt sources.

  • Programmable data word length (8 or 9 bits).

● Configurable stop bits (1 or 2 stop bits supported).

character frame

A character frame is also called a data frame. A frame of data consists of a start bit, a data bit, a parity bit, and a stop bit. For asynchronous communication, in order to be able to transmit data correctly, the communication parties must first agree on the rate and The organizational format of the transmitted data, that is, the baud rate and the format of the character frame.

  • Start bit: The sender sends out a logic "0" signal (low level) every time a communication is started, indicating the beginning of the transmission character.

  • Data bits: The data bits can be 5, 6, 7, 8, 9 bits, etc., forming a character (usually 8 bits). Such as ASCII code (7 digits), extended BCD code (8 digits). The lowest bit is sent first, and the highest bit is sent last, using low level to indicate '0' and high level to indicate '1' to complete the transmission of data bits.

  • Parity bit: Calculate the number of "1" in the data bit should be even (even parity) or odd (odd parity), in order to verify the correctness of data transmission, verification method:

  1. No parity (no parity).

  1. Odd parity: If the number of "1" in the data bit is even, the parity bit is "1", and if the number of "1" is odd, the parity bit is "0".

  1. Even parity (even parity): If the number of "1" in the data is even, the parity bit is "0", and if it is odd, the parity bit is "1".

  1. mark parity: The parity bit is always 1 (not commonly used).

  1. parity: The parity digit is always 0 (not commonly used).

  • Stop bit: The end mark of a character data. It can be a high level of 0.5 bit, 1 bit, 1.5 bit, or 2 bits.

In short, when using the UART serial port protocol to transmit data, it is necessary to stipulate that the transmission rate (baud rate) of both parties is consistent, and the data format (start bit, data bit, parity, stop bit) should also be consistent. For example, when character a is transmitted, one frame information of character a consists of start bit, data bit (character a), parity bit (optional), and stop bit.

Set the baud rate of USART

Generally, STM32CubeMX will automatically configure the baud rate without configuring it yourself.

USARTDIV = DIV_Mantissa + (DIV_Fraction / 16) , that is, when USARTDIV = 39.0625, DIV_Mantissa is 39 (ie 0x27), DIV_Fraction is 0.625 * 16 = 1 (ie 0x01).

  • DIV_Mantissa: Integer part of USARTDIV

  • DIV_Fraction: fractional part of USARTDIV * 16

Introduction to USART related registers

register

name

effect

USART_CR1

control register 1

Configure USART working parameters

USART_CR2

control register 2

Configure USART working parameters

USART_CR3

control register 3

Configure USART working parameters

USART_SR

status register

Query the current USART transmission status (mainly bits TC, RXNE)

USART_DR

data register

Store data to be sent and received data, consisting of two registers (TDR/RDR)

STM32's hal library about the USART structure

UART_HandleTypeDef

Handle structure.

typedef struct __UART_HandleTypeDef
{
  USART_TypeDef  *Instance;  /* USARTx */

  UART_InitTypeDef  Init;  /* USART初始化结构体:通信参数 */

} UART_HandleTypeDef;

UART_InitTypeDef

USART initialization structure.

typedef struct
{
  uint32_t BaudRate;  /* 波特率设置 */

  uint32_t WordLength;  /* 数据帧字长 */

  uint32_t StopBits;  /* 停止位设置 */

  uint32_t Parity;  /* 奇偶校验位控制 */

  uint32_t Mode;  /* UART模式选择 */

  uint32_t HwFlowCtl;  /* 硬件流控制选择 */

  uint32_t OverSampling;  /* 过采样选择 */

} UART_InitTypeDef;

STM32's hal library functions on USART

HAL_UART_Transmit()

Send the specified byte of data to the serial port in a blocking manner, set the specified time, and no longer send this data after the specified time.

原型:HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)

参数:
UART_HandleTypeDef *huart:串口句柄
uint8_t *pData:向串口发送的数据的地址
uint16_t Size:向串口发送的数据的大小
uint32_t Timeout:此次数据发送的时间

实例:HAL_UART_Transmit(&huart1,(uint8_t *)"jiangxiaoya\n",strlen("jiangxiaoya\n"),100);

HAL_UART_Receive()

以阻塞的方式从串口接收指定字节的数据,设定指定时间,超过指定时间后不再接收此次数据。

原型:HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)

参数:
UART_HandleTypeDef *huart:串口句柄
uint8_t *pData:从串口接收的数据的存放地址
uint16_t Size:从串口接收的数据的大小(留一个字符存放 '\0' )
uint32_t Timeout:此次数据接收的时间

实例:HAL_UART_Receive(&huart1,myData,sizeof(myData) - 1,100);

HAL_UART_Transmit_IT()

以中断的方式发送指定字节的数据。

原型:HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

参数:
UART_HandleTypeDef *huart:串口句柄
uint8_t *pData:向串口发送的数据的地址
uint16_t Size:向串口发送的数据的大小

HAL_UART_Receive_IT()

以中断的方式接收指定字节的数据。

原型:HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

参数:
UART_HandleTypeDef *huart:串口句柄
uint8_t *pData:从串口接收的数据的存放地址
uint16_t Size:从串口接收的数据的大小(留一个字符存放 '\0' )

HAL_UART_TxCpltCallback()

串口发送数据完成回调函数,也就是串口每发送完x(x的值由HAL_UART_Transmit_IT函数决定)个字节数据会产生执行该函数,该函数是一个虚函数,我们可以在main.c文件中完成它的函数体

原型:__weak void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)

参数:
UART_HandleTypeDef *huart:串口句柄

HAL_UART_RxCpltCallback()

串口接收数据完成回调函数,也就是串口每接收完x(x的值由HAL_UART_Receive_IT函数决定)个字节数据会产生执行该函数,该函数是一个虚函数,我们可以在main.c文件中完成它的函数体

原型:__weak void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)

参数:
UART_HandleTypeDef *huart:串口句柄

串口之非中断法

使用STM32CubeMX创建工程

配置SYS

配置RCC

配置串口信息

配置工程名称、工程路径

选择固件库

生成工程

使用MicroLIB库

main函数编写

/* 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"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

#include <string.h>
#include <stdio.h>

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

//重写stdio.c文件中的prinft()里的fputc()函数
int fputc(int my_data,FILE *p)
{
    unsigned char temp = my_data;
    //改写后,使用printf()函数会将数据通过串口1发送出去
    HAL_UART_Transmit(&huart1,&temp,1,0xffff);  //0xfffff为最大超时时间
    return my_data;
}
    
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
    uint8_t myData[20] = {0};
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */

  //串口以阻塞的方式发送数据,超过时间后不再发送此次数据
  HAL_UART_Transmit(&huart1,(uint8_t *)"jiangxiaoya\n",strlen("jiangxiaoya\n"),100);  //单片机复位后,会发送一个数据

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    //串口以阻塞的方式接收数据,超过时间后不再接收此次数据
    HAL_UART_Receive(&huart1,myData,sizeof(myData) - 1,100);  
        
    printf("%s\r\n",myData);  //向串口发送数据
    memset(myData,0,sizeof(myData));
        
  }
  /* USER CODE END 3 */
}

/**
  * @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_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&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_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != 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\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

疑难点分析

改写stdio.c中的fputc()函数,以便能使用printf()向串口发送数据

//重写stdio.c文件中的prinft()里的fputc()函数
int fputc(int my_data,FILE *p)
{
    unsigned char temp = my_data;
    //改写后,使用printf()函数会将数据通过串口一发送出去
    HAL_UART_Transmit(&huart1,&temp,1,0xffff);  //0xfffff为最大超时时间
    return my_data;
}

串口之中断法

串口中断流程

  • USART1_IRQHandler():由硬件调用,当串口收到中断就会调用此函数,此函数一般调用HAL_UART_IRQHandler()函数。

  • HAL_UART_IRQHandler():该函数用来判断是串口发送数据还是串口接收数据引起的中断,如果是串口接收数据引起的中断则调用UART_Receive_IT()函数。

  • UART_Receive_IT():此函数可以指定串口每接收几个字节数据调用一次回调函数,即HAL_UART_RxCpltCallback()函数。

  • HAL_UART_RxCpltCallback():串口接收完成回调函数,一般用来对串口接收的数据进行处理。

串口数据接收的状态标记变量

该变量可以自行命名,是一个uint16_t 类型的变量如uint16_t UART1_RX_STA = 0。串口每接收到一个数据时,该变量就自增1,当串口接收到回车(0x0d和0x0a),即串口的缓存数据全部接收完成,让该变量的bit16置1,表示数据全部接收完成。

uint16_t UART1_RX_STA

bit 15

bit 14

bit 13~0

接收数据完成标志

接收0x0d标志

接收到有效数据个数标志

串口接收缓存和接收缓冲

  • 接收缓存:存放串口每次接收的数据(uint8_t buf=0;

  • 接收缓冲:存放接收缓存(uint8_t UART1_RX_Buffer[USART_REC_LEN] = {'\0'};

串口每接收一个字节就会调用一次串口接收数据完成回调函数(HAL_UART_RxCpltCallback()),因此,需要在串口接收数据完成回调函数中对串口每一次接收的数据进行处理,即把数据存放到接收缓冲中。

使用STM32CubeMX创建工程

配置SYS

配置RCC

配置串口信息

配置NVIC

配置工程名称、工程路径

选择固件库

生成工程

usart.c文件编写

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    usart.c
  * @brief   This file provides code for the configuration
  *          of the USART instances.
  ******************************************************************************
  * @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 "usart.h"

/* USER CODE BEGIN 0 */

#include <stdio.h>
#include <string.h>

#define USART_REC_LEN 200

//串口接收缓存(1字节)
uint8_t buf=0;

uint8_t UART1_RX_Buffer[USART_REC_LEN];  //接收缓冲,串口接收的数据存放地点

//串口接收状态,16位
uint16_t UART1_RX_STA = 0;
//bit15: 如果是1表示接收完成
//bit14: 如果是1表示接收到回车(0x0d)
//bit13~bit0: 接收到的有效字节数目

/* USER CODE END 0 */

UART_HandleTypeDef huart1;

/* USART1 init function */

void MX_USART1_UART_Init(uint32_t my_baud)
{

  /* USER CODE BEGIN USART1_Init 0 */

  /* USER CODE END USART1_Init 0 */

  /* USER CODE BEGIN USART1_Init 1 */

  /* USER CODE END USART1_Init 1 */
  huart1.Instance = USART1;
  huart1.Init.BaudRate = my_baud;  /* 波特率可以自己传入 */
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART1_Init 2 */

    /* 开启串口1的接收中断 */
    HAL_UART_Receive_IT(&huart1,&buf,1);  /* 每接收一个串口数据调用一次串口接收完成回调函数 */
    
  /* USER CODE END USART1_Init 2 */

}

void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(uartHandle->Instance==USART1)
  {
  /* USER CODE BEGIN USART1_MspInit 0 */

  /* USER CODE END USART1_MspInit 0 */
    /* USART1 clock enable */
    __HAL_RCC_USART1_CLK_ENABLE();

    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**USART1 GPIO Configuration
    PA9     ------> USART1_TX
    PA10     ------> USART1_RX
    */
    GPIO_InitStruct.Pin = GPIO_PIN_9;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* USART1 interrupt Init */
    HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);  
    HAL_NVIC_EnableIRQ(USART1_IRQn);
  /* USER CODE BEGIN USART1_MspInit 1 */

  /* USER CODE END USART1_MspInit 1 */
  }
}

void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
{

  if(uartHandle->Instance==USART1)
  {
  /* USER CODE BEGIN USART1_MspDeInit 0 */

  /* USER CODE END USART1_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_USART1_CLK_DISABLE();

    /**USART1 GPIO Configuration
    PA9     ------> USART1_TX
    PA10     ------> USART1_RX
    */
    HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10);

    /* USART1 interrupt Deinit */
    HAL_NVIC_DisableIRQ(USART1_IRQn);
  /* USER CODE BEGIN USART1_MspDeInit 1 */

  /* USER CODE END USART1_MspDeInit 1 */
  }
}

/* USER CODE BEGIN 1 */

/* 重写stdio.h文件中的prinft()里的fputc()函数 */
int fputc(int my_data,FILE *p)
{
    unsigned char temp = my_data;
    //改写后,使用printf()函数会将数据通过串口一发送出去
    HAL_UART_Transmit(&huart1,&temp,1,0xffff);  //0xfffff为最大超时时间
    return my_data;
}

/* 串口接收完成回调函数 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    //判断中断是哪个串口触发的
    if(huart->Instance == USART1){
        
        //判断接收是否完成,即判断UART1_RX_STA的bit15是否为1
        if(!(UART1_RX_STA & 0x8000)){  //如果没接收完成就进入接收流程
            
            //判断是否接收到回车0x0d
            if(UART1_RX_STA & 0x4000){
                
                //判断是否接收到换行0x0a
                if(buf == 0x0a){
                    
                    //如果回车和换行都接收到了,则表示接收完成,即把bit15拉高
                    UART1_RX_STA |= 0x8000;
                }else{  //如果接收到回车0x0d没有接收到换行0x0a
                    
                    //则认为接收错误,重新开始接收
                    UART1_RX_STA = 0;
                }
            }else{  //如果没有接收到回车0x0d
                
                //则判断收到的这个字符是否是回车0x0d
                if(buf == 0x0d){  
                    
                    //如果这个字符是回车,则将将bit14拉高,表示接收到回车
                    UART1_RX_STA |= 0x4000;   
                }else{  //如果不是回车
                    
                    //则将这个字符存放到缓存数组中
                    UART1_RX_Buffer[UART1_RX_STA & 0x3ffff] = buf;
                    UART1_RX_STA++;
                    
                    //如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收
                    if(UART1_RX_STA > USART_REC_LEN - 1){
                        UART1_RX_STA = 0;
                    }
                }
            }
        }
        //如果接收完成则重新开启串口1的接收中断
        HAL_UART_Receive_IT(&huart1, &buf, 1);  
    }
}

/* 对串口接收数据的处理 */
void usart1_receive_data_handle()
{
  /* 判断判断串口是否接收完成 */
  if (UART1_RX_STA & 0x8000)
  {
    printf("接收完成\r\n");

    // 串口接收完数据后,对串口数据进行处理
    // 接收到open,点灯
    if (!strcmp((const char *)UART1_RX_Buffer, "open"))
    {
      HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);

      if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) == GPIO_PIN_RESET)
      {
        printf("%s\r\n", "开灯");
      }
    }
    // 接收到close,关灯
    else if (!strcmp((const char *)UART1_RX_Buffer, "close"))
    {
      HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);

      if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) == GPIO_PIN_SET)
      {
        printf("%s\r\n", "关灯");
      }
    }
    // 接收到其他数据,进行报错
    else
    {
      if (UART1_RX_Buffer[0] != '\0')
      {
        printf("%s\r\n", "输入错误");
      }
    }

    // 换行,重新开始下一次接收
    memset(UART1_RX_Buffer, 0, USART_REC_LEN);
    //printf("\r\n");
    UART1_RX_STA = 0;
  }
}

/* USER CODE END 1 */

usart.h文件编写

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    usart.h
  * @brief   This file contains all the function prototypes for
  *          the usart.c file
  ******************************************************************************
  * @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 */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __USART_H__
#define __USART_H__

#ifdef __cplusplus
extern "C" {
#endif

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

extern UART_HandleTypeDef huart1;

/* USER CODE BEGIN Private defines */

/* USER CODE END Private defines */

void MX_USART1_UART_Init(uint32_t my_baud);

/* USER CODE BEGIN Prototypes */

void usart1_receive_data_handle(void);

/* USER CODE END Prototypes */

#ifdef __cplusplus
}
#endif

#endif /* __USART_H__ */

main.c文件编写

/* 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"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init(115200);  /* 传入波特率115200 */
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
        
        usart1_receive_data_handle();
        HAL_Delay(40);
    }
  /* USER CODE END 3 */
}

/**
  * @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_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&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_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != 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\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

疑难点分析

fputc()函数分析

改写stdio.c中的fputc()函数,以便能使用printf()向串口发送数据

//重写stdio.c文件中的prinft()里的fputc()函数
int fputc(int my_data,FILE *p)
{
    unsigned char temp = my_data;
    //改写后,使用printf()函数会将数据通过串口一发送出去
    HAL_UART_Transmit(&huart1,&temp,1,0xffff);  //0xfffff为最大超时时间
    return my_data;
}

串口接收回调函数分析

当在main.c串口初始化后调用HAL_UART_Receive_IT()开始串口接收第一个字节数据,当接收到一个字节(即接收到接收缓存buf中)后就会产生中断进入接收回调函数对接收的数据进行处理

  • 如果该字节不是0x0d或0x0a则存到接收缓冲中

  • 如果该字节是0x0d,则串口数据接收的状态标记变量不再增加,等待接收到0x0a。

  1. 如果没有接收到0x0a则认为此次接收失败,则重新开始下一次接收。如果在接下来的100个字符内一直接收不到0x0a,则将UART1_RX_STA的bit14置0,表示没有接收到0x0d。

  1. 如果接收到了0x0a,则UART1_RX_STA的bit15置1表示数据全部接收完成,并等待该位被其他程序清除,重新开始下一次的串口接收。

  • 如果迟迟没有接收到0x0d,那么在接收字节超过USART_REC_LEN时,摈弃前面的数据,重新开始接收数据。

/* 串口接收完成回调函数 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    //判断中断是哪个串口触发的
    if(huart->Instance == USART1){
        
        //判断接收是否完成,即判断UART1_RX_STA的bit15是否为1
        if(!(UART1_RX_STA & 0x8000)){  //如果没接收完成就进入接收流程
            
            //判断是否接收到回车0x0d
            if(UART1_RX_STA & 0x4000){
                
                //判断是否接收到换行0x0a
                if(buf == 0x0a){
                    
                    //如果回车和换行都接收到了,则表示接收完成,即把bit15拉高
                    UART1_RX_STA |= 0x8000;
                }else{  //如果接收到回车0x0d没有接收到换行0x0a
                    
                    //则认为接收错误,重新开始接收
                    UART1_RX_STA = 0;
                }
            }else{  //如果没有接收到回车0x0d
                
                //则判断收到的这个字符是否是回车0x0d
                if(buf == 0x0d){  
                    
                    //如果这个字符是回车,则将将bit14拉高,表示接收到回车
                    UART1_RX_STA |= 0x4000;   
                }else{  //如果不是回车
                    
                    //则将这个字符存放到缓存数组中
                    UART1_RX_Buffer[UART1_RX_STA & 0x3ffff] = buf;
                    UART1_RX_STA++;
                    
                    //如果接收数据大于UART1_REC_LEN(200字节),则重新开始接收
                    if(UART1_RX_STA > USART_REC_LEN - 1){
                        UART1_RX_STA = 0;
                    }
                }
            }
        }
        //如果接收完成则重新开启串口1的接收中断
        HAL_UART_Receive_IT(&huart1, &buf, 1);  
    }
}

Guess you like

Origin blog.csdn.net/weixin_54076783/article/details/129323283