STM32&UART串口通信(使用CubeMX)

STM32 UART通信

令人感慨,大二上学期不知道这玩意的我,是怎么把广和通项目糊出来的?

写完咯 ~ 还没写完

参考链接

研究原理啥的还是看看参考的博客的好~

[Bonjour STM32] No.7-demo 4.串口通信 - Emoe-Studio

电子入门教程[04]-喂?zaima? - Emoe-Studio

串口通信与编程01:串口基础知识 - 依旧淡然 - 博客园 (cnblogs.com)

基于STM32之UART串口通信协议(一)详解 - LLLIN000 - 博客园 (cnblogs.com)

基于STM32之UART串口通信协议(二)发送 - LLLIN000 - 博客园 (cnblogs.com)

基于STM32之UART串口通信协议(三)接收 - LLLIN000 - 博客园 (cnblogs.com)

基于STM32之UART串口通信协议(四)Printf发送 - LLLIN000 - 博客园 (cnblogs.com)

STM32 printf 方法重定向到串口UART - Milton - 博客园 (cnblogs.com)

STM32 Uart 实现printf函数_ForeverIT的博客-CSDN博客_stm32 实现printf

串口通信

串口是串行接口(serial port)的简称,也称为串行通信接口或COM接口。

串口通信是指采用串行通信协议(serial communication)在一条信号线上将数据一个比特一个比特地逐位进行传输的通信模式。

串口按电气标准及协议来划分,包括RS-232-C、RS-422、RS485等。

在串行通信中,数据在1位宽的 单条线路 上进行传输,一个字节的数据要分为8次,由低位到高位按顺序一位一位的进行传送。

串行通信的数据是逐位传输的,发送方发送的每一位都具有固定的时间间隔,这就要求接收方也要按照发送方同样的时间间隔来接收每一位。不仅如此,接收方还必须能够确定一个信息组的开始和结束。

常用的两种基本串行通信方式包括同步通信和异步通信。

然后剩下的可以去参考链接继续了解了~

UART简介

嵌入式开发中,UART串口通信协议是我们常用的通信协议(UART、I2C、SPI等)之一,全称叫做通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),是 异步串口通信协议 的一种,工作原理是将传输数据的每个字符一位接一位地传输,它能将要传输的资料在串行通信与并行通信之间加以转换,能够灵活地与外部设备进行全双工数据交换。

USART 相当于UART的升级版,USART支持同步模式,因此USART 需要同步始终信号USART_CK(如STM32 单片机),通常情况同步信号很少使用,因此一般的单片机UART和USART使用方式是一样的,都使用异步模式。因为USART的使用方法上跟UART基本相同,所以在此就 以UART 来讲该通信协议了。

UART功能说明

接口通过三个引脚从外部连接到其它设备。任何 USART 双向通信均需要 至少两个引脚:接收数据输入引脚 (RX) 和发送数据引脚输出 (TX):

两个系统的GND必须连在一起(因为需要有相同的参考电位)

RX:接收数据输入引脚就是串行数据输入引脚。过采样技术可区分有效输入数据和噪声,从而用于恢复数据。
TX:发送数据输出引脚。如果关闭发送器,该输出引脚模式由其 I/O 端口配置决定。如果使 能了发送器但没有待发送的数据,则 TX 引脚处于高电平。在单线和智能卡模式下,该 I/O 用于发送和接收数据(USART 电平下,随后在 SW_RX 上接收数据)。


具体原理留个坑,先大概了解到这,这次主要是通过 CubeMX 使用UART进行通信~


波特率

波特率的单位是 Bits/s,很好理解, 每一位的传输时间 。这个参数决定了串口传输数据的速度,115200,这是串口常用的波特率里很快的一个速度了,但是计算一下,11.5kbits/s,除以8换算成字节,也就是不到2kBytes/s,每秒传输一千多个字节的数据,就是这样一个速度了——比较老的USB2.0的U盘拷贝文件的速度大概在几个MBytes到十几MBytes每秒之间,对比一下可见串口这玩意确实是一个在速度上十分落后的通信接口了。

如何计算?比如我们的波特率是9600,那么每一位传输的时间是1/9600 s,意思就是说如果接连不断地发送数据帧,按照11bit长度计算,1秒钟可以发送9600/11=872.7,也就是差不多872个数据帧,也就是872字节。按照ASCII字母来说,872个英文字母/s,够直观了8~

CubeMX配置

本次使用 STM32F405RGT6

配置时钟

选择外部晶振,然后时钟拉到最高~

)用一张之前的图,意思差不多

4

设置调试接口

)照着参考链接来

配置串口

先启用,选择异步通信;然后波特率保持默认的 115200 就行

1

选择生成必要的代码

2

1.Copy all used libraries into the project folder

将所有使用过的库复制到项目文件夹中。

不管你用,还是没有用到,都拷贝到你工程目录下。这样一来,你工程下文件就比较多。

2.Copy only the necessary library files

只复制必要的库文件。

这个相比上一个减少了很多文件。比如你没有使用CAN、SPI…等外设,就不会拷贝相关库文件到你工程下。

3.Add necessary library files as reference in the toolchain project configuration file

在工具链项目配置文件中添加必要的库文件作为参考。

这里没有复制HAL库文件,只添加了必要文件(如main.c)。相比上面,没有Drivers相关文件。

Generate peripheral initialization as a pair of’.c/.h’ files per peripheral

每个外设生成独立的’.c/.h’文件

不勾:所有初始化代码都生成在main.c

勾选:初始化代码生成在对应的外设文件。 如UART初始化代码生成在uart.c中。

)其他配置啥的可以上网搜搜具体意思

UART 发送

主要使用到的函数为

stm32f4xx_hal_uart.c

HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
  • huart :选择用来发送的UART串口
  • pData :指向将要发送的数据的指针
  • Size :发送数据的大小
  • Timeout:超时时间

直接发送

  1. 定义一个数组

    //数组内十六进制代表“ABCDE”
    unsigned char uTx_Data[5] = {0x41, 0x42, 0x43, 0x44, 0x45};
  2. 调用函数发送

    while (1)
    {
        // UART发送,使用打开的第一个端口,发送定义的数组里的字符
        HAL_UART_Transmit(&huart1, uTx_Data, sizeof(uTx_Data), 0xffff);
        // 延迟1s
        HAL_Delay(1000);
    }

自己写发送函数

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

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

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

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

具体实现方式可以在参考链接中找到~

使用 printf() 函数发送

好好好好好好好好好好好好好好好好好好

首先打开 usart.c,然后添加头文件 #include "stdio.h"

再加入

/* USER CODE BEGIN 1 */
#ifdef __GNUC__
  /* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf
     set to 'Yes') calls __io_putchar() */
  #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
  #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
/**
  * @brief  Retargets the C library printf function to the USART.
  * @param  None
  * @retval None
  */
PUTCHAR_PROTOTYPE
{
  /* Place your implementation of fputc here */
  /* e.g. write a character to the EVAL_COM1 and Loop until the end of transmission */
  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);

  return ch;
}

/* USER CODE END 1 */

然后再到 main.c 中添加头文件 #include "stdio.h"

然后就能愉快的用 printf() 了~

printf("Hello World!\n\r");

具体效果

)用这个板子是因为上面已经有 CH340 这个芯片了,电脑上装好驱动可以直接用~

如果莫得的话,买一个 USB 转 TTL 模块 就行,几块钱~

主要代码

// 数组内十六进制代表 ABCDE
unsigned char uTx_Data[5] = {0x41, 0x42, 0x43, 0x44, 0x45};
while (1){
  // 直接发送
  HAL_UART_Transmit(&huart1, uTx_Data, sizeof(uTx_Data), 0xffff);
  HAL_Delay(1000);
  // 使用 printf 发送
  printf("Hello World!\n\r");
  HAL_Delay(1000);
}

然后连上,打开串口助手,打开串口

3

UART 接收

)等我学完

在之前文件的基础上,打开串口中断

4

如果开启了其他中断,要顺便设置一下中断优先级,如果没有默认即可

5

生成代码。

函数说明

  1. 中断处理函数 void USART1_IRQHandler(void) ,在 stm32f4xx_it.c 中;

  2. HAL库函数 HAL_UART_Transmit ,用来发送数据;

  3. HAL库函数 HAL_UART_Receive ,用来接收函数

    该函数能够通过huart串口接收Size位pData数据。

    参数说明:

    • huart :选择用来接收的UART串口

    • pData :指向将要存放数据的指针

    • Size :接收数据的大小

    • Timeout :超时时间

接收方式

接收方式大概有三种,大致是

  1. 直接接收

    这种方式为在 while 循环中不断判断是否接收成功,会严重占用程序的进程 ,且接收较长的数据时,会发生接收错误。

  2. 中断接收,接收一位发送一位

    使用中断可以很大程度的避免不必要的资源浪费。

    在没有什么特别事件的时候,单片机会按照原本的程序运行着,等到有数据从UART串口发送过来时,会马上进入UART串口的中断处理函数中,完成相应的中断处理操作,完成后会退出中断函数,并继续原本在进行的程序,这样就不会占用单片机程序太多的进程了。

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

  3. 中断接收,全部接收完后再发送

    这个方式以一定的资源换取了一定程度数据的完整。

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

以上 1, 2 两种方式可以在参考博客中找到实现方法,这里主要关注方法 3

中断接收,全部接收完后再发送

使能接收中断

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

void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
  // 省略掉的前面的一些配置
  /* USER CODE BEGIN USART1_MspInit 1 */
    __HAL_UART_ENABLE_IT(uartHandle, UART_IT_RXNE);
  /* USER CODE END USART1_MspInit 1 */
  }
}

编写接收中断服务程序

USART1_IRQHandler (在 stm32f4xx_it.c 中)

定义三个静态变量

// 存储数组
static unsigned char uRx_Data[1024] = {0};
// 指向存储数组将要存储数据的位
static unsigned char * pRx_Data = uRx_Data;
// 接收数据长度
static unsigned char uLength = 0;

调用HAL库的UART接收函数以及发送函数

2、3步都可以根据自身要求进行改进

  • 第2步:判断接收结束条件,这个可以根据自己想要接收何种类型的数据而定。本次使用 回车 作为判断条件
  • 第3步:数据处理,可以在这一步执行自己想要对数据做的一些操作。本次为将接收到的数据重新发送出去。
// 1. 接收数据
HAL_UART_Receive(&huart1, pRx_Data, 1, 1000);
// 2. 判断数据结尾
if(*pRx_Data == '\n' || *pRx_Data == '\r') {
  // 3. 将接收成功的数据通过串口发出去
  HAL_UART_Transmit(&huart1, uRx_Data, uLength, 0xffff);
  // 4. 初始化指针和数据长度
  pRx_Data = uRx_Data; //重新指向数组起始位置   uLength  = 0; //长度清零
}
// 5. 若未结束,指针往下一位移动,长度自增1
else {
  ++pRx_Data,++uLength;
}

完整中断服务程序

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */

  // 存储数组
  static unsigned char uRx_Data[1024] = {0};
  // 指向存储数组将要存储数据的位
  static unsigned char * pRx_Data = uRx_Data;
  // 接收数据长度
  static unsigned char uLength = 0;
  // 接收数据
  HAL_UART_Receive(&huart1, pRx_Data, 1, 1000);
  // 判断数据结尾
  if(*pRx_Data == '\n' || *pRx_Data == '\r') {
    // 将接收成功的数据通过串口发出去
    HAL_UART_Transmit(&huart1, uRx_Data, uLength, 0xffff);
    // 初始化指针和数据长度
    pRx_Data = uRx_Data; //重新指向数组起始位置
    uLength  = 0; //长度清零
  }
  // 若未结束,指针往下一位移动,长度自增1
  else {
    ++pRx_Data,++uLength;
  }
  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}

查看效果

编译下载,使用串口查看

默认是不断发送之前的 Hello World ! ,在受到我们发送的数据后,将其再发送,结束后之前运行不受影响。(中断的好处)

6

发送与接收都完成咯,撒花 ~

)这算是搞明白了当时是如何操纵 Stm32 使用 AT 指令与广和通 L610 通信了 ~

(当时没搞明白,对着例程xjb抄,发送功能糊出来了结果最后没有接收功能……令人感慨。