Bootstrap

新手STM32:利用HAL库实现USART串口通信

一、串口通信协议

在串口通信中,数据是在发送端以二进制位的形式,通过一根通信线(可以是物理线路,如电线、光纤等),按照规定的速率(波特率)一位一位地发送到接收端。例如,要发送一个字节(8 位)的数据10101010,会先发送最高位1,然后依次发送后面的位。为了确保接收端能够正确识别数据,发送端和接收端需要遵循相同的波特率、数据位、停止位和奇偶校验等设置。

1.RS-232标准

RS - 232 是一种广泛使用的串口通信标准。它规定了接口的电气特性、机械特性和功能特性等。

电气特性方面,它使用负逻辑,逻辑 “1” 的电平范围是 - 3V 到 - 15V,逻辑 “0” 的电平范围是 + 3V 到 + 15V。这种电平标准可以提高抗干扰能力,但需要专门的电平转换芯片(如 MAX232)将 TTL 电平(一般微控制器输出的电平)转换为 RS - 232 电平,才能与外部设备通信。

机械特性方面,它通常采用 DB - 9 或 DB - 25 连接器。其中 DB - 9 连接器比较常见,它有 9 个引脚,不同的引脚定义了不同的功能,如数据发送(TXD)、数据接收(RXD)、地线(GND)等。应用场景包括计算机与调制解调器、鼠标、打印机等设备的通信。

2.串口通讯电平标准

TTL 电平:

电平定义:一般使用 + 5V 表示逻辑 1,0V 表示逻辑 0 。在实际应用中,高电平最小为 2.4V,低电平最大为 0.4V 也可被识别为有效电平.

特点:TTL 电平是数字电路中常见的电平标准,其优点是电路简单、成本低廉,适用于短距离通信,一般在同一块电路板上或芯片之间的通信,通信距离通常在一米之内.

应用场景:常见于微控制器、数字逻辑电路等芯片之间的串口通信,如 51 单片机、STM32 单片机等内部的 UART 串口通信通常采用 TTL 电平.

RS-232 电平:

电平定义:逻辑 1 的电平范围是 - 3V 到 - 15V,逻辑 0 的电平范围是 + 3V 到 + 15V.

特点:RS-232 电平标准的抗干扰能力强,因为其高低电平之间有较大的电压差值,即使信号受到一定干扰,仍能较准确地识别电平状态,从而保证通信的可靠性,通信距离可达 15 米左右.

应用场景:曾广泛应用于计算机与调制解调器、鼠标、打印机等设备的通信,是 IBM-PC 及其兼容机上的串行连接标准.

3.USB to TTL(以CH340芯片为例)

TXD:发送端,一般表示为自己的发送端。
RXD:接收端,一般表示为自己的接收端。
在与我们的STM32连接时,VCC与接地端与USB to TTL模块互连,但两者的TXD与RXD应当反接,也就是说,我们的TTL模块的TXD端与STM32的RXD端相连,反之亦然。

CH340驱动下载地址:
链接: https://pan.baidu.com/s/1MHQ4Jjjqtu8N9hvag8J1lw?pwd=q6xc 提取码: q6xc

(本篇文章使用有的工具软件都在里面) 

解压后找到CH340的文件夹,点击里面的.exe文件进行安装即可。

二、 通过USB to TTL在两台电脑间互传文件

通过两个USB to TTL将两台PC连接起来,两方需要在串口调试助手中打开自己的串口,硬件连接如下。

唯一值得注意的是,两个TTL的RX与TX应该反接。

发送方发送的图片为:

在发送时,发送方串口调试助手显示的界面应该为:

 

接收方的的串口界面为: 

 

接收完毕后,接收方点击串口调试助手的保存数据,串口助手会自动在安装位置生成一个.DAT文件,我们需要手动修改其文件后缀为.jpg还原成图片文件。

 

最后打开图片就能得到我们发送过来的图片了。 

三、HAL库实现串口通信

(一)、阻塞串口通信

阻塞串口通信是一种同步的通信方式。在这种方式下,当程序执行串口数据发送或接收操作时,它会一直等待该操作完成后才会继续执行下一个指令。就好像在一个只有一个窗口的办事大厅,工作人员(程序)在发送或接收串口数据(办理业务)时,必须等这个业务完全处理好才能去做别的事情。

例如,当使用阻塞方式发送一个字节的数据时,程序会停留在发送函数处,直到这个字节被成功地放入串口发送缓冲区并且开始发送出去,才会执行后续的程序代码

(1)CUBEMX配置

打开STM32CUBEMX,新建文件并选择我们F103C8T6的板子,然后打开配置界面。

首先配置SYS,因为我采用的STLink V2进行烧录,所以我选择Serial Wire,如下图所示。

选择JTAG烧录的话就选择下面这两个,记得按照自己的引脚来。

接下来配置RCC,配置为高速外部晶振即可,如下图所示。

接下来配置系统时钟树,按照下图的顺序修改就行了。

 然后配置我们的串口,在Connecttivity中找到我们的USART1,并在Mode中选择Asynchronous(异步,默认串口波特率为115200)。

 然后就可以进行我们的项目配置了。按照下图红框所示配置即可。

值得一提的是,编译器的版本应该与自己使用的Keil版本一致,也就是第一个红框中的部分。接下来点击右上角的GENERATE CODE,生成我们的代码,在接下来弹出的窗口中点击Open Project就能直接进入Keil进行代码部分的编写了 。

(2)代码编写

对于单纯的串口向上位机进行发送而言,代码部分较为简单,大家唯一比较生疏的应该就是需要使用HAL_UART_Transmit_IT这个函数。

这个函数用于制定串口发送固定长度的的数据。其中:

&huart :用来指定我们的串口,比如我们本次使用的时USART1,那我们调用函数的时候该部分就应该写为&huart1。

*pData:是我们发送数据的地址指针,对于数组而言,也就是数组名称。

Size:是我们所发数据的长度或者说个数。

对这个函数进行讲解了过后,我们就可以开始撰写代码了。首先我们定义一个固定长度的数组,书写数组的内容,然后通过这个函数进行串口输出,反映到代码上的格式为:

    uint8_t hello[20]="hello windows\n";
    HAL_UART_Transmit_IT(&huart1,hello,20);

(3)程序烧录

因为这次需要检查串口发送的数据,同时使用stlink和TTL有点麻烦,所以我使用了FLYMCU来进行代码的烧录。该软件可以直接使用TTL烧录一些较为简单的程序,有需要的小伙伴可以在前文CH340下载的链接中找到。

打开FLYMCU后,按照下图配置就好,记得要选择自己编译成功后的.hex文件。

注意: 当我们烧录时,需要将板子上的BOOT0键帽由0-->1,像下图一样。

接下来将TTL模块按照之前所说的方式连接,板子上的PA9对应我们的TX1,PA10对应我们的RX1,不是很清楚的小伙伴可以看下图。

然后接到电脑上就可以开始烧录了。点击FLYMCU的开始编程,如果长时间没烧录成功就点击我们板子上的复位键,还是不行的话就多点几次。

然后打开我们的串口调试助手,找到我们接入的端口(如果没安装CH340的驱动可能无法识别到),打开我们的串口就能接收到板子的数据了。

(二)中断串口通信

中断串口通信是一种异步通信方式。在这种方式下,当串口完成数据发送或接收等操作时,会向 CPU 发出一个中断请求信号。CPU 在收到这个信号后,会暂停当前正在执行的任务(如果允许中断),转而执行相应的中断服务程序(ISR)来处理串口相关的事务,处理完成后再返回原来被中断的任务继续执行。

这就好比你正在房间里做自己的事情(CPU 执行主程序),突然门铃响了(串口产生中断请求),你暂停手中的工作,去开门并处理来访者的事情(执行中断服务程序),处理完后再回来继续做之前的事情。

(1)CUBEMX配置

所有的配置都跟阻塞通信一致,唯一多的一步是配置USART1时,我们需要打开他的中断模式。

(2) 代码编写

相较于阻塞通信而言,我们需要实现的功能增加了接收上位机传来的数据和判断上位机数据的内容。

对于接收上位机数据,我们需要调用HAL_UART_Receive_IT这个函数。

其函数的三个变量含义与前文的HAL_UART_Transmit_IT一致,这里不再赘述。它的作用就是在指定的串口截取固定长度的上位机发来的数据,将其保存到我们所指定的地址当中。

置于判断数据内容,我们只需要使用if条件判断语句即可。那么代码编写的脉络就比较清晰了。

首先定义一个变量或者数组用来接收我们上位机发送来的数据,然后接收上位机的数据并赋值给我们定义的变量,接下来进行条件判断,如果上位机发送的是“#”,停止串口发送,如果是“*”则继续发送。

反映到代码上为:

  uint8_t rcData='*';
  while (1)
  {
    /* USER CODE END WHILE */
    //接收中断使能
  	HAL_UART_Receive_IT(&huart1,&rcData,1);
  	if(rcData == '*')//接收*
  	{   
  		
  		uint8_t hello[20]="hello world\n";
  		HAL_UART_Transmit_IT(&huart1,hello,20);
  		HAL_Delay(500);
        
  	}
  	
  	else if(rcData == '#')//接收#
  	{				 
  		
  	}

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

这里的while循环,是主函数的固定语句,大家记得注意一下。 

值得一提的是,在进行串口调试的时候我发现,输入“#”后再次输入“*”会导致中断只发生一次,也就是只发送一句“hello windows!”于是我在接收“*”的部分增添了一句rcData='*';这样就不会发生上述的情况了。

修改后的代码如下:

  uint8_t rcData='*';
  while (1)
  {
    /* USER CODE END WHILE */
    //接收中断使能
  	HAL_UART_Receive_IT(&huart1,&rcData,1);
  	if(rcData == '*')//接收*
  	{   
  		
  		uint8_t hello[20]="hello world\n";
  		HAL_UART_Transmit_IT(&huart1,hello,20);
  		HAL_Delay(500);
        rcData = '*';
  	}
  	
  	else if(rcData == '#')//接收#
  	{				 
  		
  	}


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

(3)程序烧录

烧录过程与阻塞通信一致,这里不再赘述,直接展示串口调试结果。

(三)DMA串口通信

DMA 是一种可以在不经过 CPU 干预的情况下,实现数据在存储器(如内存)与外部设备(如串口)之间直接传输的技术。它通过专门的 DMA 控制器来管理数据传输,从而减轻 CPU 的负担,提高数据传输的效率。

以一个简单的类比来说,就好像有一个专门的 “搬运工”(DMA 控制器),可以直接把货物(数据)从一个仓库(内存)搬运到另一个地方(串口发送缓冲区),而不需要 “管理员”(CPU)每次都亲自指挥搬运的每个细节。

(1)CUBEMX配置

和中断串口通信的配置一致,唯一多的一部分是我们需要打开DMA的通道,如下图所示。

我们点击Add,然后将RX和TX添加进去,有关DMA的配置就完成了,接下来就可以生成代码进行编写了。

(2)代码编写

和阻塞通信的步骤一致,我们先定义一个数组或者变量,然后将这个数组通过串口发送给我们的上位机 。DMA的发送函数为HAL_UART_Transmit_DMA。

其包含的三个参数与上述一致,不再一一赘述。

将我们的操作反映到代码上显示为:

  uint8_t hello[20]="hello windows!\n";
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
    HAL_UART_Transmit_DMA(&huart1,hello,20);
    HAL_Delay(500);
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

 (3)程序烧录

实物连接和前面的方式一致,这里直接展示结果。

四、Keil仿真观察波形

 点击魔术棒,在Debug选项卡中按照下图的方式进行配置。

然后点击快捷工作栏的Debug选项 ,在下图列表中找到逻辑分析仪

或者在快捷工作栏找到。

然后再弹出的逻辑分析仪窗口的左上角找到Set up选项,在其中添加USART1_SR,如下图所示。

点击colse保存关闭后,就能得到如下的波形。 

五、总结

在本次的学习过程中,我们系统且深入地探索了串口通信领域的相关知识。首先,着重对串口通信的电平协议展开了详细的学习与研究。串口通信的电平协议作为数据传输的基础规范,有着至关重要的意义。其中涵盖了如 TTL 电平,其以较为简洁的电平逻辑,通常运用 +5V 表示逻辑 1,0V 表示逻辑 0,这种电平标准在芯片内部或短距离通信场景中发挥着关键作用,因其成本低廉且电路设计相对简易;还有 RS - 232 电平,它具有独特的电气特性,逻辑 1 的电平范围处于 - 3V 至 - 15V 之间,逻辑 0 的电平范围则在 + 3V 至 + 15V 之间,这使得它在抗干扰能力方面表现卓越,曾在计算机与外部设备(如调制解调器、鼠标、打印机等)的通信连接中被广泛应用;以及 RS - 485 电平,它采用差分信号传输模式,通过两线压差的变化来表示逻辑状态,例如 + 2V 至 + 6V 表示逻辑 1, - 2V 至 - 6V 表示逻辑 0,这种方式极大地增强了抗干扰性能,并且支持多节点连接,在工业控制领域的长距离、多设备通信场景中占据着主导地位。

在深入理解了串口通信电平协议的基础之上,我们进一步开展了实践操作,巧妙地运用 USB to TTL 模块成功实现了两台电脑之间文件的相互传输。这一过程不仅让我们对串口通信的实际应用有了更为直观的感受,同时也使我们对电平协议在数据传输过程中的具体作用有了更深刻的领悟。

随后,我们将学习重点聚焦于基于 HAL 库的串口通信编程实现方面,深入探究了三种极具代表性的通信方式,即阻塞串口通信、中断串口通信和 DMA 串口通信。

对于阻塞串口通信,我们从软件配置、代码编写以及实物演示三个维度进行了全面且细致的阐述。在软件配置环节,我们精心设置了串口的各项参数,包括波特率、数据位、停止位以及奇偶校验位等,确保串口能够按照预期的要求进行稳定可靠的数据传输。在代码编写方面,我们深入剖析了其实现原理,例如在数据发送函数中,通过循环查询串口发送缓冲区的状态,只有当缓冲区为空时,才将待发送的数据放入其中并启动发送流程;在数据接收函数中,同样采用循环查询的方式,持续检查接收缓冲区是否有数据到达,一旦检测到数据,便立即进行读取和后续处理。在实物演示过程中,我们借助 Keil 开发环境强大的调试功能,成功观察到了串口发出的波形变化。通过对波形的细致分析,我们将原本抽象的电平变化转化为具体可感的视觉信号,从而更加清晰地理解了数据在串口通信过程中的传输时序以及电平转换规律。

中断串口通信作为一种高效且灵活的通信方式,我们同样从软件配置、代码编写和实物演示三个方面进行了深入探讨。在软件配置过程中,我们着重对中断向量表进行了精准的设置,确保串口中断发生时,CPU 能够准确无误地找到对应的中断服务程序入口地址。同时,对串口中断相关的优先级等参数也进行了合理的配置,以保障系统在多中断源环境下能够稳定运行。在代码编写环节,我们精心编写了中断服务程序,例如在接收中断服务程序中,当串口接收缓冲区收到数据并触发中断后,中断服务程序会迅速响应,及时从缓冲区读取数据,并根据实际需求进行数据的存储、处理或转发操作。在实物演示方面,我们搭建了相应的测试环境,通过实际的数据传输测试,充分展示了中断串口通信在实时性和高效利用 CPU 资源方面的显著优势。

DMA 串口通信方式则以其能够实现高速批量数据传输且无需 CPU 过度干预的特点而备受关注。在软件配置阶段,我们对 DMA 控制器进行了全面细致的设置,包括精确指定源地址(通常为内存中的数据存储区域)、目的地址(串口发送或接收缓冲区)、传输数据的长度以及传输模式(如单次传输、循环传输等)等关键参数,确保 DMA 能够按照预定的规则高效地进行数据传输。在代码编写过程中,我们合理地协调了 DMA 与串口之间的交互操作,使得数据能够在两者之间流畅地传输。在实物演示环节,我们通过传输大量的数据文件,直观地展现了 DMA 串口通信在提高数据传输效率方面的卓越性能,它能够极大地减轻 CPU 的负担,使 CPU 能够将更多的时间和资源投入到其他重要的任务处理中。

通过对这三种基于 HAL 库的串口通信方式的深入学习与实践,我们全面掌握了串口通信在不同应用场景下的编程实现技巧,为今后在嵌入式系统开发、工业自动化控制以及数据通信等领域的深入研究和实际应用奠定了坚实的基础。

;