【单片机】基于STM32的UART串口通信

一、前言

简单讲解一下UART通信协议,以及UART能够实现的一些功能,还有有关使用STM32CubeMX来配置芯片的一些操作。实验内容基于正点原子精英板开发板,单片机芯片为STM32F103ZET6
在后面我会以我使用的STM32F429开发板来举例讲解(其他STM32系列芯片大多数都可以按照这些步骤来操作的),如有不足请多多指教。

二、UART相关知识

1、UART简介

嵌入式开发中,UART串口通信协议是我们常用的通信协议(UART、I2C、SPI等)之一,全称叫做通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),是异步串口通信协议的一种,工作原理是将传输数据的每个字符一位接一位地传输,它能将要传输的资料在串行通信与并行通信之间加以转换,能够灵活地与外部设备进行全双工数据交换。
类似的,USART(Universal Synchronous Asynchronous Receiver and Transmitter通用同步异步收发器)串口的,USART相当于UART的升级版,USART支持同步模式,因此USART 需要同步始终信号USART_CK(如STM32 单片机),通常情况同步信号很少使用,因此一般的单片机UART和USART使用方式是一样的,都使用异步模式。因为USART的使用方法上跟UART基本相同,所以在此就以UART来讲该通信协议了。

2、UART通信协议

在这里插入图片描述

  1. 起始位
    当未有数据发送时,数据线处于逻辑“1”状态;先发出一个逻辑“0”信号,表示开始传输字符。
  2. 数据位
    紧接着起始位之后。资料位的个数可以是4、5、6、7、8等,构成一个字符。通常采用ASCII码。从最低位开始传送,靠时钟定位。
  3. 奇偶校验位
    资料为加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验资料传送的正确性。
  4. 停止位
    它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。 由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。
  5. 空闲位或起始位
    处于逻辑“1”状态,表示当前线路上没有资料传送,进入空闲状态。
    处于逻辑“0”状态,表示开始传送下一数据段。
  6. 波特率
    表示每秒钟传送的码元符号的个数,是衡量数据传送速率的指标,它用单位时间内载波调制状态改变的次数来表示。
    常用的波特率有:9600、115200……
    时间间隔计算:1秒除以波特率得出的时间,例如,波特率为9600的时间间隔为1s / 9600(波特率) = 104us。

3、UART功能说明

接口通过三个引脚从外部连接到其它设备。任何 USART 双向通信均需要 至少两个引脚:接收数据输入引脚 (RX) 和发送数据引脚输出 (TX):
  RX:接收数据输入引脚就是串行数据输入引脚。过采样技术可区分有效输入数据和噪声,从而用于恢复数据。
  TX:发送数据输出引脚。如果关闭发送器,该输出引脚模式由其 I/O 端口配置决定。如果使 能了发送器但没有待发送的数据,则 TX 引脚处于高电平。在单线和智能卡模式下,该 I/O 用于发送和接收数据(USART 电平下,随后在 SW_RX 上接收数据)。

(1)正常 USART 模式下,通过这些引脚以帧的形式发送和接收串行数据:

  • 发送或接收前保持空闲线路
  • 起始位
  • 数据(字长 8 位或 9 位),最低有效位在前
  • 用于指示帧传输已完成的 0.5 个、1 个、1.5 个、2 个停止位
  • 该接口使用小数波特率发生器 - 带 12 位尾数和 4 位小数
  • 状态寄存器 (USART_SR)
  • 数据寄存器 (USART_DR)
  • 波特率寄存器 (USART_BRR) - 12 位尾数和 4 位小数
  • 智能卡模式下的保护时间寄存器 (USART_GTPR)

(2)在同步模式下连接时需要以下引脚:

  • SCLK:发送器时钟输出。该引脚用于输出发送器数据时钟,以便按照 SPI 主模式进行同步发送(起始位和结束位上无时钟脉冲,可通过软件向最后一个数据位发送时钟脉冲)。RX 上可同步接收并行数据。这一点可用于控制带移位寄存器的外设(如 LCD 驱动器)。时钟相位和极性可通过软件编程。在智能卡模式下,SCLK 可向智能卡提供时钟。在硬件流控制模式下需要以下引脚:
  • nCTS:“清除以发送”用于在当前传输结束时阻止数据发送(高电平时)。
  • nRTS:“请求以发送”用于指示 USART 已准备好接收数据(低电平时)。

4、UART工作原理

(1)发送接收

发送逻辑对从发送FIFO 读取的数据执行“并→串”转换。控制逻辑输出起始位在先的串行位流,并且根据控制寄存器中已编程的配置,后面紧跟着数据位(注意:最低位 LSB 先输出)、奇偶校验位和停止位。
在检测到一个有效的起始脉冲后,接收逻辑对接收到的位流执行“串→并”转换。此外还会对溢出错误、奇偶校验错误、帧错误和线中止(line-break)错误进行检测,并将检测到的状态附加到被写入接收FIFO 的数据中。

(2)波特率产生

波特率除数(baud-rate divisor)是一个22 位数,它由16 位整数和6 位小数组成。波特率发生器使用这两个值组成的数字来决定位周期。通过带有小数波特率的除法器,在足够高的系统时钟速率下,UART 可以产生所有标准的波特率,而误差很小。

(3)数据收发

发送时,数据被写入发送FIFO。如果UART 被使能,则会按照预先设置好的参数(波特率、数据位、停止位、校验位等)开始发送数据,一直到发送FIFO 中没有数据。一旦向发送FIFO 写数据(如果FIFO 未空),UART 的忙标志位BUSY 就有效,并且在发送数据期间一直保持有效。BUSY 位仅在发送FIFO 为空,且已从移位寄存器发送最后一个字符,包括停止位时才变无效。即 UART 不再使能,它也可以指示忙状态。

在UART 接收器空闲时,如果数据输入变成“低电平”,即接收到了起始位,则接收计数器开始运行,并且数据在Baud16 的第8 个周期被采样。如果Rx 在Baud16 的第8 周期仍然为低电平,则起始位有效,否则会被认为是错误的起始位并将其忽略。
  
如果起始位有效,则根据数据字符被编程的长度,在 Baud16 的每第 16 个周期(即一个位周期之后)对连续的数据位进行采样。如果奇偶校验模式使能,则还会检测奇偶校验位。

最后,如果Rx 为高电平,则有效的停止位被确认,否则发生帧错误。当接收到一个完整的字符时,将数据存放在接收FIFO 中。

(4)中断控制

出现以下情况时,可使UART 产生中断:

  • FIFO 溢出错误
  • 线中止错误(line-break,即Rx 信号一直为0 的状态,包括校验位和停止位在内)
  • 奇偶校验错误
  • 帧错误(停止位不为1)
  • 接收超时(接收FIFO 已有数据但未满,而后续数据长时间不来)
  • 发送
  • 接收
    由于所有中断事件在发送到中断控制器之前会一起进行“或运算”操作,所以任意时刻 UART 只能向中断产生一个中断请求。通过查询中断状态函数,软件可以在同一个中断服务函数里处理多个中断事件(多个并列的if 语句)。

(5)FIFO操作

FIFO 是“First-In First-Out”的缩写,意为“先进先出”,是一种常见的队列操作。 Stellaris 系列ARM 的UART 模块包含有2 个16 字节的FIFO:一个用于发送,另一个用于接收。可以将两个FIFO 分别配置为以不同深度触发中断。可供选择的配置包括:1/8、 1/4、1/2、3/4 和7/8 深度。例如,如果接收FIFO 选择1/4,则在UART 接收到4 个数据时产生接收中断。
 
发送FIFO的基本工作过程: 只要有数据填充到发送FIFO 里,就会立即启动发送过程。由于发送本身是个相对缓慢的过程,因此在发送的同时其它需要发送的数据还可以继续填充到发送 FIFO 里。当发送 FIFO 被填满时就不能再继续填充了,否则会造成数据丢失,此时只能等待。这个等待并不会很久,以9600 的波特率为例,等待出现一个空位的时间在1ms 上下。发送 FIFO 会按照填入数据的先后顺序把数据一个个发送出去,直到发送 FIFO 全空时为止。已发送完毕的数据会被自动清除,在发送FIFO 里同时会多出一个空位。

接收FIFO的基本工作过程: 当硬件逻辑接收到数据时,就会往接收FIFO 里填充接收到的数据。程序应当及时取走这些数据,数据被取走也是在接收FIFO 里被自动删除的过程,因此在接收 FIFO 里同时会多出一个空位。如果在接收 FIFO 里的数据未被及时取走而造成接收FIFO 已满,则以后再接收到数据时因无空位可以填充而造成数据丢失。

收发FIFO 主要是为了解决UART 收发中断过于频繁而导致CPU 效率不高的问题而引入的。在进行 UART 通信时,中断方式比轮询方式要简便且效率高。但是,如果没有收发 FIFO,则每收发一个数据都要中断处理一次,效率仍然不够高。如果有了收发FIFO,则可以在连续收发若干个数据(可多至14 个)后才产生一次中断然后一并处理,这就大大提高了收发效率。

完全不必要担心FIFO 机制可能带来的数据丢失或得不到及时处理的问题,因为它已经帮你想到了收发过程中存在的任何问题,只要在初始化配置UART 后,就可以放心收发了, FIFO 和中断例程会自动搞定一切。

(6)回环操作

UART 可以进入一个内部回环(Loopback)模式,用于诊断或调试。在回环模式下,从Tx 上发送的数据将被Rx 输入端接收。

三、STM32CubeMx配置

正常创建工程,启用USART1,相关设置如下:
在这里插入图片描述

四、UART发送

1、初始化说明

CubeMX生成的UART初始化代码(在usart.c中)

1 UART_HandleTypeDef huart1;
 2 
 3 /* USART1 init function */
 4 
 5 void MX_USART1_UART_Init(void)
 6 {
 7 
 8   huart1.Instance = USART1;
 9   huart1.Init.BaudRate = 115200;
10   huart1.Init.WordLength = UART_WORDLENGTH_8B;
11   huart1.Init.StopBits = UART_STOPBITS_1;
12   huart1.Init.Parity = UART_PARITY_NONE;
13   huart1.Init.Mode = UART_MODE_TX_RX;
14   huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
15   huart1.Init.OverSampling = UART_OVERSAMPLING_16;
16   if (HAL_UART_Init(&huart1) != HAL_OK)
17   {
18     Error_Handler();
19   }
20 
21 }
22 
23 void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
24 {
25 
26   GPIO_InitTypeDef GPIO_InitStruct = {0};
27   if(uartHandle->Instance==USART1)
28   {
29   /* USER CODE BEGIN USART1_MspInit 0 */
30 
31   /* USER CODE END USART1_MspInit 0 */
32     /* USART1 clock enable */
33     __HAL_RCC_USART1_CLK_ENABLE();
34   
35     __HAL_RCC_GPIOA_CLK_ENABLE();
36     /**USART1 GPIO Configuration    
37     PA9     ------> USART1_TX
38     PA10     ------> USART1_RX 
39     */
40     GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
41     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
42     GPIO_InitStruct.Pull = GPIO_PULLUP;
43     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
44     GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
45     HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
46 
47   /* USER CODE BEGIN USART1_MspInit 1 */
48 
49   /* USER CODE END USART1_MspInit 1 */
50   }
51 }
52 
53 void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
54 {
55 
56   if(uartHandle->Instance==USART1)
57   {
58   /* USER CODE BEGIN USART1_MspDeInit 0 */
59 
60   /* USER CODE END USART1_MspDeInit 0 */
61     /* Peripheral clock disable */
62     __HAL_RCC_USART1_CLK_DISABLE();
63   
64     /**USART1 GPIO Configuration    
65     PA9     ------> USART1_TX
66     PA10     ------> USART1_RX 
67     */
68     HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10);
69 
70   /* USER CODE BEGIN USART1_MspDeInit 1 */
71 
72   /* USER CODE END USART1_MspDeInit 1 */
73   }
74 } 
USART init

2、HAL库函数说明

HAL_UART_Transmit(在stm32f4xx_hal_uart.c中),该函数能够通过huart串口发送Size位pData数据。
参数说明:

  • huart :选择用来发送的UART串口
  • pData :指向将要发送的数据的指针
  • Size :发送数据的大小
  • Timeout:超时时间

3、代码实现UART发送

(1)直接发送

在main主函数中定义一个数组:

1   /* USER CODE BEGIN 1 */
2     unsigned char uTx_Data[5] = {0x41, 0x42, 0x43, 0x44, 0x45};    //数组内十六进制代表“ABCDE”
3   /* USER CODE END 1 */

在main主函数中的while循环中调用HAL库UART发送函数:

/* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
        /* UART发送 */
      HAL_UART_Transmit(&huart1, uTx_Data, sizeof(uTx_Data), 0xffff);
        /* 延迟1s */
        HAL_Delay(1000);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */

整体的main函数如下:

int main(void)
{
  /* USER CODE BEGIN 1 */
    unsigned char uTx_Data[5] = {0x41, 0x42, 0x43, 0x44, 0x45};    //数组内十六进制代表“ABCDE”
  /* 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 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
        /* UART发送 */
      HAL_UART_Transmit(&huart1, uTx_Data, sizeof(uTx_Data), 0xffff);
        /* 延迟1s */
        HAL_Delay(1000);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

通过编译下载,可在串口助手中显示发送的数据:
在这里插入图片描述

(2)字符串发送

前面的发送方式,不仅要传入句柄参数,还有数组、长度、超时时间参数。

为了简便发送,我们可以专门写一个字符串发送函数,可以直接传入一个数组即可发送,可以更简便地实现字符串发送。

优点是,发送数据更简便,能够一次性发送很长的数据数组。

但缺点就是不能控制发送的长度,会将整个数据数组发出。

在Uart.c中添加vUser_UART_SendString函数

/* USER CODE BEGIN 1 */
void vUser_UART_SendString(UART_HandleTypeDef* uartHandle, unsigned char * uData)
{
    /* -1- 判断数据是否发送完毕 */
    while(*uData)        //若为空即发送完毕,若不为空则还有数据
    {
        /* -2- 发送1Byte */
        HAL_UART_Transmit(uartHandle, uData, 1, 0xffff);
        /* -3- 移至下1Byte */
        uData++;
    }
}
/* USER CODE END 1 */

在Uart.h中声明一下vUser_UART_SendString函数(声明后就可以在别的地方调用该函数)

1 /* USER CODE BEGIN Prototypes */
2 extern void vUser_UART_SendString(UART_HandleTypeDef* uartHandle, unsigned char * uData);
3 /* USER CODE END Prototypes */

在main主函数中定义一个数组

1   /* USER CODE BEGIN 1 */
2     unsigned char uTx_Data[] = "\r\n Hallo World! 你好,世界!";
3   /* USER CODE END 1 */

在main主函数的while循环中调用字符串发送函数

/* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
        /* 字符串发送 */
      vUser_UART_SendString(&huart1, uTx_Data);
        /* 延迟1s */
        HAL_Delay(1000);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */

整个main函数如下:

int main(void)
{
  /* USER CODE BEGIN 1 */
    unsigned char uTx_Data[] = "\r\n Hallo World! 你好,世界!";
  /* 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 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
        /* UART发送 */
      vUser_UART_SendString(&huart1, uTx_Data);
        /* 延迟1s */
        HAL_Delay(1000);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

编译下载后在串口助手中显示如下:
在这里插入图片描述
这种发送方式就是相当于编写c语言的时候,在小黑框中打印自己想要打印的东西;通过printf发送,我们也可以在串口助手上实现一样的功能。

五、UART接收

1、初始化说明

UART接收在原本配置CubeMx的基础上,添加一些UART的中断配置来实现中断接收操作。

使能串口中断
在这里插入图片描述
设置中断优先级(如果没开启其他中断,那就默认即可,直接跳过)
在这里插入图片描述
重新生成代码

2、函数说明

(1)CubeMx生成的UART中断处理函数(在stm32f1xx_it.c中)

当USART1发生中断事件时,程序会进行该函数,所以我们会在这个函数编写好程序,来处理我们的中断事件。

/**
 * @brief This function handles USART1 global interrupt.
  */
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */

  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}

(2)HAL库函数HAL_UART_Transmit(在stm32f4xx_hal_uart.c中)

该函数能够通过huart串口发送Size位pData数据。
参数说明:

  • huart :选择用来发送的UART串口
  • pData :指向将要发送的数据的指针
  • Size :发送数据的大小
  • Timeout:超时时间

(3)HAL库函数HAL_UART_Receive(在stm32f4xx_hal_uart.c中)

  • huart :选择用来接收的UART串口
  • pData :指向将要存放数据的指针
  • Size :发送数据的大小
  • Timeout:超时时间

3、代码编写:实现UART接收

(1)直接接收(不建议)

1)在main主函数中定义一个变量,负责接收数据

1   /* USER CODE BEGIN 1 */
2     unsigned char uRx_Data = 0;
3   /* USER CODE END 1 */

2)在main主函数while循环中调用HAL库UART接收函数

1   /* Infinite loop */
 2   /* USER CODE BEGIN WHILE */
 3   while (1)
 4   {
 5         /* 判断是否接收成功 */
 6         if(HAL_UART_Receive(&huart1, &uRx_Data, 1, 1000) == HAL_OK)
 7         {
 8             /* 将接收成功的数据通过串口发出来 */
 9             HAL_UART_Transmit(&huart1, &uRx_Data, 1, 0xffff);
10         }
11         
12     /* USER CODE END WHILE */
13 
14     /* USER CODE BEGIN 3 */
15   }
16   /* USER CODE END 3 */

整个main函数如下:

1 /**
 2   * @brief  The application entry point.
 3   * @retval int
 4   */
 5 int main(void)
 6 {
 7   /* USER CODE BEGIN 1 */
 8     unsigned char uRx_Data = 0;
 9   /* USER CODE END 1 */
10   
11 
12   /* MCU Configuration--------------------------------------------------------*/
13 
14   /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
15   HAL_Init();
16 
17   /* USER CODE BEGIN Init */
18 
19   /* USER CODE END Init */
20 
21   /* Configure the system clock */
22   SystemClock_Config();
23 
24   /* USER CODE BEGIN SysInit */
25 
26   /* USER CODE END SysInit */
27 
28   /* Initialize all configured peripherals */
29   MX_GPIO_Init();
30   MX_USART1_UART_Init();
31   /* USER CODE BEGIN 2 */
32 
33   /* USER CODE END 2 */
34 
35   /* Infinite loop */
36   /* USER CODE BEGIN WHILE */
37   while (1)
38   {
39         /* 判断是否接收成功 */
40         if(HAL_UART_Receive(&huart1, &uRx_Data, 1, 1000) == HAL_OK)
41         {
42             /* 将接收成功的数据通过串口发出来 */
43             HAL_UART_Transmit(&huart1, &uRx_Data, 1, 0xffff);
44         }
45         
46     /* USER CODE END WHILE */
47 
48     /* USER CODE BEGIN 3 */
49   }
50   /* USER CODE END 3 */
51 }

3)编译、下载烧写后实现效果如下
在这里插入图片描述
这种接收方式是直接在main函数里的while循环里不断接收,会严重占用程序的进程,且接收较长的数据时,会发生接收错误,如下:
在这里插入图片描述

(2)中断接收(接收并发送)(不推荐)

1)在HAL_UART_MspInit(在usart.c中)使能接收中断

1   /* USER CODE BEGIN USART1_MspInit 1 */
2     __HAL_UART_ENABLE_IT(uartHandle, UART_IT_RXNE);
3   /* USER CODE END USART1_MspInit 1 */

整个HAL_UART_MspInit函数如下:

1 void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
 2 {
 3 
 4   GPIO_InitTypeDef GPIO_InitStruct = {0};
 5   if(uartHandle->Instance==USART1)
 6   {
 7   /* USER CODE BEGIN USART1_MspInit 0 */
 8 
 9   /* USER CODE END USART1_MspInit 0 */
10     /* USART1 clock enable */
11     __HAL_RCC_USART1_CLK_ENABLE();
12   
13     __HAL_RCC_GPIOA_CLK_ENABLE();
14     /**USART1 GPIO Configuration    
15     PA9     ------> USART1_TX
16     PA10     ------> USART1_RX 
17     */
18     GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
19     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
20     GPIO_InitStruct.Pull = GPIO_PULLUP;
21     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
22     GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
23     HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
24 
25     /* USART1 interrupt Init */
26     HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
27     HAL_NVIC_EnableIRQ(USART1_IRQn);
28   /* USER CODE BEGIN USART1_MspInit 1 */
29     __HAL_UART_ENABLE_IT(uartHandle, UART_IT_RXNE);
30   /* USER CODE END USART1_MspInit 1 */
31   }
32 }

2)在USART1_IRQHandler(在stm32f4xx_it.c中)定义一个变量,负责接收数据

1 unsigned char uRx_Data = 0;

3)在USART1_IRQHandler(在stm32f4xx_it.c中)调用HAL库的UART接收函数以及发送函数

1     /* -1- 接收 */
2     HAL_UART_Receive(&huart1, &uRx_Data, 1, 1000);
3     /* -2- 将接收成功的数据通过串口发出去 */
4     HAL_UART_Transmit(&huart1, &uRx_Data, 1, 0xffff);

整个USART1_IRQHandler(在stm32f4xx_it.c中)函数如下:

1 /**
 2   * @brief This function handles USART1 global interrupt.
 3   */
 4 void USART1_IRQHandler(void)
 5 {
 6   /* USER CODE BEGIN USART1_IRQn 0 */
 7     unsigned char uRx_Data;
 8     
 9     /* -1- 接收 */
10     HAL_UART_Receive(&huart1, &uRx_Data, 1, 1000);
11     /* -2- 将接收成功的数据通过串口发出去 */
12     HAL_UART_Transmit(&huart1, &uRx_Data, 1, 0xffff);
13     
14   /* USER CODE END USART1_IRQn 0 */
15   HAL_UART_IRQHandler(&huart1);
16   /* USER CODE BEGIN USART1_IRQn 1 */
17 
18   /* USER CODE END USART1_IRQn 1 */
19 }

4)编译、下载烧写实现效果如下
在这里插入图片描述
相对于前面的直接接收方式,该中断接收方式就显得特别人性化了,在没有什么特别事件的时候,单片机会按照原本的程序运行着,等到有数据从UART串口发送过来时,会马上进入UART串口的中断处理函数中,完成相应的中断处理操作,完成后会退出中断函数,并继续原本在进行的程序,这样就不会占用单片机程序太多的进程了。

但仍会发生前面直接接收方式的接收异常状况,主要原因是,在中断处理函数中,我们在接收了数据后并紧接着作出发送的操作,这会出现一个状况,还没来得及将上一次接收到的数据发送出去,就进入下一次接收的中断,然而导致失去了一些数据了。

(3)中断接收(先接收完,后处理)(推荐)

这种接收方式,是在方式2的基础上稍作改进的,较于前两种接收方式,是更好的一种接收方式,不会给原本的程序进程造成太大影响。还可以先接收全部数据(提示:通过定义一个较大的数组来存储),再将数据进行处理,这样能确保接收数据的完整性,并能将数据进行有效的处理、分析。

既然这种方式明显会好一点,那为什么一开始不用这个方式呢?因为通过前面两种方法,可以更容易明白UART接收的操作。

而这次就只要在方式2的基础上作出一些简单的修改就可以了。

1)在HAL_UART_MspInit(在usart.c中)使能接收中断(与方式2相同)

1   /* USER CODE BEGIN USART1_MspInit 1 */
2     __HAL_UART_ENABLE_IT(uartHandle, UART_IT_RXNE);
3   /* USER CODE END USART1_MspInit 1 */

整个HAL_UART_MspInit(在usart.c中)函数如下:

1 void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
 2 {
 3 
 4   GPIO_InitTypeDef GPIO_InitStruct = {0};
 5   if(uartHandle->Instance==USART1)
 6   {
 7   /* USER CODE BEGIN USART1_MspInit 0 */
 8 
 9   /* USER CODE END USART1_MspInit 0 */
10     /* USART1 clock enable */
11     __HAL_RCC_USART1_CLK_ENABLE();
12   
13     __HAL_RCC_GPIOA_CLK_ENABLE();
14     /**USART1 GPIO Configuration    
15     PA9     ------> USART1_TX
16     PA10     ------> USART1_RX 
17     */
18     GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
19     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
20     GPIO_InitStruct.Pull = GPIO_PULLUP;
21     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
22     GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
23     HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
24 
25     /* USART1 interrupt Init */
26     HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
27     HAL_NVIC_EnableIRQ(USART1_IRQn);
28   /* USER CODE BEGIN USART1_MspInit 1 */
29     __HAL_UART_ENABLE_IT(uartHandle, UART_IT_RXNE);
30   /* USER CODE END USART1_MspInit 1 */
31   }
32 }

2)在USART1_IRQHandler(在stm32f4xx_it.c中)定义三个静态变量

1     static unsigned char     uRx_Data[1024] = {0}     ;    //存储数组
2     static unsigned char  *  pRx_Data       = uRx_Data;    //指向存储数组将要存储数据的位
3     static unsigned char     uLength        =  0  

3)在USART1_IRQHandler(在stm32f4xx_it.c中)调用HAL库的UART接收函数以及发送函数

注:
  如下的第2、3步都可以根据自身要求进行改进。

  • 第2步:判断接收结束条件,这个可以根据自己想要接收何种类型的数据而定。

  • 第3步:数据处理,大家可以在这一步执行自己想要对数据做的一些操作,我这里只是将接收到的数据重新发送出去而已。

1     /* -1- 接收数据 */
 2     HAL_UART_Receive(&huart1, pRx_Data, 1, 1000);
 3     
 4     /* -2- 判断数据结尾 */
 5     if(*pRx_Data == '\n')
 6     {
 7         /* -3- 将接收成功的数据通过串口发出去 */
 8         HAL_UART_Transmit(&huart1, uRx_Data, uLength, 0xffff);
 9         
10         /* -4- 初始化指针和数据长度 */
11         pRx_Data = uRx_Data;  //重新指向数组起始位置
12         uLength  = 0;         //长度清零
13     }
14     /* -5- 若未结束,指针往下一位移动,长度自增一 */
15     else
16     {
17         pRx_Data++;
18         uLength++;
19     }

整个USART1_IRQHandler(在stm32f4xx_it.c中)函数如下:

1 /**
 2   * @brief This function handles USART1 global interrupt.
 3   */
 4 void USART1_IRQHandler(void)
 5 {
 6   /* USER CODE BEGIN USART1_IRQn 0 */
 7     static unsigned char   uRx_Data[1024] = {0}     ;    //存储数组
 8     static unsigned char * pRx_Data       = uRx_Data;    //指向存储数组将要存储数据的位
 9     static unsigned char   uLength        =  0      ;    //接收数据长度
10     
11     /* -1- 接收数据 */
12     HAL_UART_Receive(&huart1, pRx_Data, 1, 1000);
13     
14     /* -2- 判断数据结尾 */
15     if(*pRx_Data == '\n')
16     {
17         /* -3- 将接收成功的数据通过串口发出去 */
18         HAL_UART_Transmit(&huart1, uRx_Data, uLength, 0xffff);
19         
20         /* -4- 初始化指针和数据长度 */
21         pRx_Data = uRx_Data;   //重新指向数组起始位置
22         uLength  = 0;          //长度清零
23     }
24     /* -5- 若未结束,指针往下一位移动,长度自增一 */
25     else
26     {
27         pRx_Data++;
28         uLength++;
29     }
30     
31     
32   /* USER CODE END USART1_IRQn 0 */
33   HAL_UART_IRQHandler(&huart1);
34   /* USER CODE BEGIN USART1_IRQn 1 */
35 
36   /* USER CODE END USART1_IRQn 1 */
37 }

4)编译、下载烧写后实现效果如下
在这里插入图片描述
除了上面的方法,还有DMA接收方法没介绍。

  • 7
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
基于STM32串口通信课程设计可以通过以下步骤实现: 1. 确定硬件平台:选择STM32F103单片机作为主控芯片,同时选择串口通信模块和LED模块作为外设。 2. 确定通信协议:选择虚拟串口软件实现串口助手与STM32通信,可以使用常见的串口通信协议如UART、SPI等。 3. 实现LED控制:通过串口助手分别发送'A'、'B'、'C'控制三个LED亮;分别发送'a'、'b'、'c'控制三个LED灭。 4. 实现OLED显示:OLED显示通过串口接收到的字符数据,并且将收到的数据通过串口发送至串口助手端,实现串口双向通信。 5. 实现LoRa数据收发:使用Ra-01SC模组和LLCC68芯片实现LoRa数据的收发。 以下是一个基于STM32串口通信课程设计的代码示例: ```c #include "stm32f10x.h" #include "stdio.h" void USART1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); USART_Cmd(USART1, ENABLE); } void LED_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOC, &GPIO_InitStructure); } void USART1_SendChar(char ch) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); USART_SendData(USART1, (uint8_t)ch); } void USART1_SendString(char* str) { while (*str) { USART1_SendChar(*str++); } } char USART1_ReceiveChar(void) { while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET); return (char)USART_ReceiveData(USART1); } int main(void) { char ch; USART1_Init(); LED_Init(); while (1) { ch = USART1_ReceiveChar(); switch (ch) { case 'A': GPIO_SetBits(GPIOC, GPIO_Pin_13); break; case 'B': GPIO_ResetBits(GPIOC, GPIO_Pin_13); break; case 'C': USART1_SendString("Hello World!\r\n"); break; default: break; } } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值