夜雨聆风学习资料网

ARTICLE · 1040334

综合电子系统设计(七) 串口通信学习文档,更新了六版

综合电子系统设计(七) 串口通信学习文档,更新了六版

这是我几年前辅导学生在课外学习的记录,遗憾的是学生只跟我学了几个月就“分道扬镳”了。本文的前半部分写于几年前,那时AI还很弱智。因为学生还在校学习,故一直没有发布。2026年,学生大四了,准备找工作,将之前的学习过程展示出来,也算是一种互勉吧。当然他可以把这些过程写成简历中的机能,这就是现在公布文档内容的原因之一。本人教学多年得到的体会就是一定要挑选真正想学和能持续学习的学生学,一直靠督促真的很难学好。通信工程专业的学习是有很高难度的,想学好真的要主动花很大气力的。毕业离校之后就没有人这么叮嘱你了。这次公布的内容是更新的第六版,早期写文档就要规范起来,让学生养成好习惯。

“大一下学期,我有幸能跟蔡老师学习。在我学习C语言的时候,老师提出我要好好学习MATLAB,后面要用的。MATLAB在我们的学校目前是没有正式课程的(选修课的教学不会有很强的专业性),所以想学好的方法就是紧紧跟着老师和自己努力尝试。可惜我不是一个有很强动力的人(其实比较“懒”,知道当下就业情况,但是不知道怎么努力,于是就只能随波逐流),对于MATLAB的学习也没有付出很多的精力。

后面老师开始带领我学习单片机(通信专业的核心课程之一),最开始是让我自己下好STM32Cubled软件,然后给我书本,让我自己去看网络课程(B站的正点原子)。在我对软件有一定了解后,老师给了我学习目标(打通串口通信),委托王学长给我单片机,学长也鼓励我要好好学下去(这一点,我很愧对老师和学长),老师也很关心我,给我带书本,常常关心我学习过程。

下面也是我串口通信的学习记录(局部):

3.串口通信HAL驱动函数

3.1.USART相关

1)HAL_UART_Transmit(huart,pData,Size,Timeout)/串口发送数据(轮询)

lhuart是指向一个包含了特定UART外设配置和状态信息的结构体指针

lpData指向要发送的数据的指针

lSize是要发送的数据的字节数。

lTimeout是超时时间

2)HAL_UART_Receive(huart,pData,Size,Timeout)/串口接收数据(轮询)

lhuart是指向一个包含了特定UART外设配置和状态信息的结构体指针

lpData指向要发送的数据的指针

lSize是要发送的数据的字节数。

lTimeout是超时时间

3.2.TIM相关

1)HAL_TIM_Init(&htimx);//用于设置定时器的基本参数,如预分器周期、自动重装载值等。

&htimx是指向TIM_HandleTypeDef类型变量的指针

2)HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)//中断回调函数

TIM_HandleTypeDef *htim:指向定时器句柄的指针,表示哪个定时器的周期结束了。

5.STM32里的USART和定时器

5.1.USART

STM32可以通过串口包括USART(通用同步/异步接收/发送器)、UART(通用异步接收/发送器)和SPI(串行外设接口)等和其他设备进行传输数据。对应的STM32引脚分别是RX和TX。

STM32的USART支持硬件流控制和软件流控制。

l硬件流控制:通过nRTS和nCTS引脚实现。当接收缓冲区满时,nRTS引脚被拉高,通知发送方停止发送;当接收缓冲区准备好接收新数据时,nCTS引脚被拉低,允许发送方继续发送。

l软件流控制:通过发送特定的控制字符(如XON/XOFF)来实现。这种方法不需要额外的硬件支持,但会增加数据传输的开销。

1)TX:发送数据输出引脚。

2)RX:接收数据输入引脚。

3)SW_RX:数据接收引脚,只用于单线和智能卡模式,属于内部引脚,没有具体外部引脚。

4)nRTS:请求发送(Request To Send),n 表示低电平有效。如果使能 RTS 流控制,当USART接收器准备好接收新数据时就会将 nRTS 变成低电平;当接收寄存器已满时, nRTS 将被设置为高电平。该引脚只适用于硬件流控制。

5)nCTS:清除以发送(Clear To Send),n 表示低电平有效。如果使能 CTS 流控制,发送器在发送下一帧数据之前会检测 nCTS 引脚,如果为低电平,表示可以发送数据,如果为高电平则在发送完当前数据帧之后停止发送。该引脚只适用于硬件流控制。

6)SCLK:发送器时钟输出引脚。这个引脚仅适用于同步模式。

图5-1 USART流程图

由图5-1 USART流程图,我们可以看到数据在USART中的传输过程。大致过程如下。

5.1.1.发送数据过程

1)CPU写入数据:CPU将要发送的数据写入USART的数据寄存器(DR),在硬件层面,这个操作实际上是将数据写入发送数据寄存器(TDR)。

2)USART控制器检测:USART控制器不断检测TDR(通过数据寄存器DR的状态来间接检测)的状态。当检测到TDR中有数据时,控制器准备开始发送数据。

3)数据移位发送:USART控制器从TDR中读取数据,并将数据一位一位地通过TX(发送)引脚串行发送出去。这个过程中,数据首先被移动到发送移位寄存器(这部分在图中可能未明确标出,但它是USART内部的一个组件),然后按照波特率设置的速度,一位接一位地通过TX引脚输出。

4)发送完成与中断:当整个数据帧发送完毕后,USART控制器会更新状态寄存器(SR),设置相应的标志位(如TC,表示发送完成)。如果配置了发送完成中断,USART控制器还会发送一个中断信号给中断控制器。

5)中断处理(如果配置了中断):CPU响应中断,执行中断服务程序。在中断服务程序中,CPU可以清除发送完成标志位(通过写入特定的值到状态寄存器SR),准备下一次发送,或者执行其他与发送完成相关的操作。

5.1.2.接收数据过程

1)起始位检测:USART控制器不断监测RX(接收)引脚的电平状态。当检测到低电平(起始位)时,控制器知道一个新的数据帧即将开始。

2)数据接收与移位:在起始位之后,USART控制器按照波特率设置的速度,一位接一位地从RX引脚接收数据,并将接收到的数据存储在接收移位寄存器中(同样,这部分在图中可能未明确标出)。当接收完所有的数据位后,控制器将接收移位寄存器中的数据移动到接收数据寄存器(RDR)。

3)接收完成与中断:当整个数据帧接收完毕后,USART控制器会更新状态寄存器(SR),设置相应的标志位(如RXNE,表示接收数据寄存器非空)。如果配置了接收完成中断,USART控制器还会发送一个中断信号给中断控制器。

4)CPU读取数据:CPU可以定期检查状态寄存器(SR),查看RXNE标志位是否被设置。如果RXNE被设置,表示RDR中有新的数据可供读取。CPU读取RDR中的数据,这个动作通常会自动清除RXNE标志位。

5)中断处理(如果配置了中断):CPU响应中断,执行中断服务程序。在中断服务程序中,CPU从RDR中读取数据,进行必要的处理,然后退出中断服务程序,准备接收下一个数据帧。

图5-2 数据帧图

串口的几个重要的数据设定:

1)波特率:串口通信的速率

2)起始位:标志一个数据帧的开始,固定为低电平。当数据开始发送时,产生一个下降沿。(空闲–>起始位)

3)数据位:发送数据帧,1为高电平,0为低电平。低位先行。

4)校验位:用于数据验证,根据数据位的计算得来。有奇校验,偶校验和无校验。

5)停止位:用于数据的间隔,固定为高电平。数据帧发送完成后,产生一个上升沿。(数据传输–>停止位)

串口通信是异步的,这意味着发送和接收的数据传输速率并不需要完全同步。发送和接收的时钟可以是独立的,数据传输过程中会包含足够的同步信息(如起始位和停止位),以确保数据的正确接收。这种异步特性使得串口通信适合于不同设备间的通信,即使时钟频率不同,也能够正常进行数据交换。

异步数据的数据发送过程:

1)空闲状态:在没有数据传输时,TX通常保持在高电平(逻辑1),这表示通信线路处于空闲状态。

2)起始位:当发送端准备发送数据时,它会首先将TX从高电平(空闲状态)切换到低电平(逻辑0),这个低电平的起始位标志着数据位的开始。

3)数据位:起始位之后是数据位的传输。数据位的顺序是从最低有效位(LSB,即D0)开始,按顺序一直到最高有效位(MSB)。数据位的数量通常是7位或8位。

4)奇偶校验位(如果使用):在数据位之后,如果通信协议中包含了奇偶校验,发送端会添加一个奇偶校验位。这个位用于确保数据位加上奇偶校验位的总数是奇数(奇校验)或偶数(偶校验)。

5)停止位:在奇偶校验位之后(如果没有奇偶校验位,则在数据位之后),发送端会发送一个或两个高电平(逻辑1)的停止位,以表明数据传输的结束。

6)返回空闲状态:发送完停止位后,TX线返回到高电平(逻辑1),通信线路再次处于空闲状态,准备下一次数据的发送。

7)等待或发送更多数据:如果发送队列中有更多数据需要发送,发送端将重复上述步骤。如果没有更多数据,发送端将保持TX线在高电平,直到下一次数据传输的需求出现。

5.2.定时器

STM32F030CCT6中共有9个定时器,分为基本定时器、通用定时器和高级定器。

1)基础定时器TIM6/7是一个16位的只能向上计数的定时器,只能定时,有外部IO。

2)通用定时器TIM2/3/4/5是一个 16位的可以向上/下计数的定时器,可以定时,可以输出比较,可以输入捕捉,每个定时器有四个外部IO。

3)高级定时器TIM1是一个16位的可以向上/下计数的定时器,可以定时,可以输出比较,可以输入捕捉,还可以有三相电机互补输出信号,每个定时器有8个外部IO。

图5-3 基础定时器结构图

对于图5-3 基础定时器的工作流程,详细分析如下:

l内部时钟(CK_INT):定时器的工作始于来自RCC的TIMCLK时钟信号,该信号被输入到触发控制器,作为定时器的内部时钟源(INTCK)。_

l预分频器(PSC):为了调整定时器的输入时钟频率,时钟信号会经过预分频器(PSC)处理,产生CK_PSC信号。预分频器允许用户根据需求设置不同的分频系数。

l自动重装载寄存器:CK_PSC信号随后被输入到自动重装载寄存器中,用于设置定时器的周期或延时时间。当计数器达到预设值时,自动重装载寄存器会重新加载预设值,确保定时器能够持续工作。

l触发控制器:触发控制器不仅接收来自内部时钟(CK_INT)的信号,还负责触发自动重装载寄存器的装载操作。此外,它还具备复位、使能和计数功能,确保定时器按照预设的工作模式运行。

lCNT计数器:自动重装载寄存器的输出信号(UI)会输入到CNT计数器,用于进行计数操作。当计数器达到预设值时,会触发相应的中断或DMA输出,实现定时功能。

本文使用了TIM3,对于如何设置目标频率:

首先,我们需要计算预分频后的时钟频率,使其满足每秒一个计数周期的要求。

公式:T(溢出时间)=(自动重装载值+1)*( 预分频系数+1)/ 时钟频率

6.串口通信类型

6.1.轮询法

6.1.1.接口引脚配置

图6-1-1-1 接口引脚配置图

我们使用了PB6、7作为串口通信的接口,PA6、7作为控制二极管RLED和GLED的串口。PA13是单线调试接口数据线(SWDIO),PA14用于控制数据传输的时序,并提供时钟信号以进行芯片的调试或编程。

6.1.2.系统时钟配置

如图6-1-1-2,我们对系统时钟采用外部高速晶振

图6-1-1-2 系统时钟配置图

STM32单片机的系统时钟使用外部晶振的好处主要包括以下几点:

1)频率稳定性:外部晶振通常比内部RC振荡器具有更高的频率稳定性,这有助于提高系统时钟的准确性。

2)频率范围:外部晶振可以提供比内部振荡器更宽的频率范围,允许用户根据需要选择不同的工作频率。

3)低抖动:高质量的外部晶振可以提供更低的相位抖动,这对于需要高精度时钟的应用非常重要。

4)低功耗:某些外部晶振设计用于低功耗应用,可以在保持高精度的同时减少能耗。

外部晶振作为STM32单片机的时钟源也存在一些缺点:

1)成本增加:外部晶振通常比内部振荡器成本更高,这可能会增加整个项目的成本。

2)电路板空间:外部晶振需要在电路板上占据额外的空间,这可能限制了设计灵活性,尤其是在空间受限的应用中。

3)信号完整性问题:外部晶振可能会引入信号完整性问题,如反射、串扰等,这需要在设计时进行适当的布线和屏蔽。

4)电源噪声:外部晶振可能会受到电源波动的影响,导致时钟信号不稳定。

6.1.3.时钟树配置

图6-1-1-3 时钟树配置图

串口通信在轮询模式下对时间树的配置要求不高,本文配置HCLK为48MHz,其余保持默认配置。

6.1.4.配置串口通信

图6-1-1-4 USART配置图

如图6-1-1-4,本文中USART串口开启异步模式,采取了115200的波特率,8位数据位,0位奇偶校验位,1位停止位。

6.1.5.USART初始化

我们选择Core—>Scr—>main.c文件打开main.c。用usart.h、usart.c完成了USART串口的初始化。

图6-1-1-5 USART初始化图

这段代码用于初始化UART(通用异步收发器)通信接口,特别是USART1。代码首先包含了usart.h头文件,该文件包含了UART相关的定义和声明。接着,定义了一个名为MX_USART1_UART_Init的函数,该函数没有参数,并返回void类型。

在函数内部,首先配置了一个UART句柄huart1的多个参数,这些参数定义了USART通信的各个方面,包括:

波特率设置为115200,决定了数据传输的速率。

数据位长度设置为8位,定义了每个字符的位数。

停止位设置为1位,用于标识字符的结束。

奇偶校验设置为无,不进行额外的数据校验。

工作模式设置为发送和接收,允许双向通信。

硬件流控制设置为无,不使用硬件信号来控制数据传输。

过采样模式设置为16倍,影响数据的采样精度。

单采样位设置为禁用,不使用单采样位模式。

高级特性初始化设置为不初始化,表示不使用UART的高级特性。

完成配置后,代码调用了HAL_UART_Init函数来初始化UART接口。如果初始化过程失败(即返回值不是HAL_OK),则会调用Error_Handler函数来处理错误情况。

6.1.6.主函数

图6-1-1-6 主程序代码图

在图6-1-1-6 主程序代码图可见定义了message、message1、message2三个数组发送数据,同时通过控制RLED的亮、灭判断数据是否发送。

l数字和字母的传输是直接传输。

l中文字符在UTF-8编码中通常占用3个字节,需要确保这3个字节作为一个整体被传输。

下图是实验结果

图6-1-1-7 轮询接收图

如图6-1-1-7 轮询接收图,我们采购接收到了单片机发来的数据。

6.2.定时器中断法

6.2.1.接口引脚配置

图6-2-1-1 接口引脚配置图

如图6-2-1-1 接口引脚配置图,我们使用了PB6、7作为串口通信的接口,PA3作为接收AD采样通道IN3的串口。PA13是单线调试接口数据线(SWDIO),PA14用于控制数据传输的时序,并提供时钟信号以进行芯片的调试或编程。

6.2.2.系统时钟配置

如图6-2-1-2 系统时钟配置图,我们采用外部高速晶振作为系统时钟的信号源。

图6-2-1-2 系统时钟配置图

为了确保精度,我们采用了外部时钟。并打开了TIM3的内部时钟通道。

6.2.3.时钟树配置

图6-2-1-3 时钟树配置图

如图6-2-1-3 时钟树配置图,我们配置HCLK为48MHz,其余保持默认配置。并选择HSE通道和PLLCLK,同时将定时器分频48MHz。

6.2.4.定时器配置

图6-2-1-4 定时器配置图

如图6-2-1-4 定时器配置图,我们打开内部时钟为定时器提供计时。为了达到50Hz的采样率,我们将自动重装载值设为48-1,预分频值为20000-1,通过公式T=(48-1)*(20000-1)/48000000Hz=0.02s。

6.2.5.定时器初始化

图6-2-1-5 定时器配置图

这段代码是专门用于配置和初始化微控制器上的TIM3定时器。函数名为MX_TIM3_Init,它执行了以下关键步骤:

1)初始化TIM3定时器:

设置了预分频器(Prescaler)为48-1,这意味着计数器的时钟频率是微控制器时钟频率的1/48。

选择了向上计数模式(CounterMode),即计数器从0开始递增。

设置了定时器周期(Period)为20000-1,意味着当计数器达到这个值时,将产生一个更新事件(通常是中断)。

选择了时钟分频为1(ClockDivision),即不进行额外的时钟分频。

禁用了自动重载预装载(AutoReloadPreload),这意味着在更新事件发生时,新的周期值将立即加载到计数器中。

2)配置时钟源:

使用了内部时钟源(ClockSource),这意味着TIM3定时器将基于系统内部时钟运行。

3)配置主从模式:

禁用了主从模式(MasterSlaveMode),这意味着TIM3定时器将独立运行,不与其他定时器同步。

设置了主输出触发器(MasterOutputTrigger)为复位(Reset),这通常用于与其他外设或定时器进行同步。

4)错误处理:

如果任何步骤失败(即函数返回非HAL_OK),则调用Error_Handler()函数来处理错误。

5)用户代码段:

在初始化代码的不同位置,提供了用户代码段(USER CODE BEGIN ... USER CODE END),允许开发者在标准初始化代码前后添加自定义代码。

6.2.6.执行函数

图6-2-1-6 定时器中断图

图6-2-1-7 串口发送程序图

如图6-2-1-6 定时器中断图和图6-2-1-7 串口发送程序图,我们调用定时器中断函数确保定时启动,在内部确定信号来自定时器TIM3后,进行AD采样并将变量sign赋值为1。通过get_value函数发送数据到while循环中同时判断sign确保数据是AD转换完后的准确数值。

图6-2-1-8是结果显示,其中266和290是相差0.024s,290和308是相差0.018s,由此可看出时间差约为0.02s。

图6-2-1-8 定时器中断接收图

7.调试总结

7.1.串口接收错误

现象:从图7-1-1 错误现象图,使用UartAssist串口接受助手测试时,窗口无法正常接受数据,大部分是无接收信息,通过短接GND和RX可以接收乱码。极少时间内可以接受正常数据。

图7-1-1 错误现象图

修正:我们发现打开多个串口助手会导致串口被占用

7.2.与Matlab串口通信错误

现象:如图7-2-1,在stm串口与计算机通信测试成功后,与Matlab的连接出现错误。多次调试程序均失败。提示无法找到端口。

图7-2-1 错误现象图

修正:可能为接口号没对上、重启MATLAB。”

好了,就展示到这吧。当年的学生对知识点进行了总结,可惜不能坚持下去。当年AI没有现在这么智能,很多知识的学习靠看书和资料,学的辛苦自然也就学的扎实。唯有学习,命运才能掌握在自己手里。每一次培训的背后包含着多少老师的辛劳?也许学生根本不在意。毕业以后记不住很正常,关键在校期间还不珍惜,那就是自己的损失了。我现在帮扶的原则是不扶烂泥,但对每一届学生都会努力再努力,直到功败垂成。之前学生没有完成在MATLAB中编写程序接收串口数据,那就让老师来吧,准确的说是让AI来!

周四中午,没有休息,面对着两位大二学生正在测试学习的数字平台进行了讲解,串口通信和AD采样将是他们这两个月学习的重头戏,加油啊!时不我待!

接下来本人又要演示什么?

依旧把自己当成大二学生,测试两种模式下的串口通信,然后写个MATLAB程序替换掉串口助手。先看看串口通信在查询模式和中断模式下的差异。不懂的地方就问,AI 或者老师都可以。现在的学生就是不喜欢问,这源于自己心里没底,那就多看看书啊!书看多了,底气就来了。

对单片机串口(UART/USART)来说,查询模式中断模式的本质区别是:

  • 查询模式:CPU 主动、反复检查串口状态寄存器标志位,条件不满足就原地等待。

  • 中断模式:CPU 不等待,串口事件发生时由硬件触发中断,CPU 跳进中断服务函数处理。

查询模式是 CPU 主动等串口,简单但阻塞、低效;中断模式是串口通知 CPU,复杂但高效、实时。如果是产品级、连续接收、高波特率、多任务、低功耗,优先用中断模式。

之前是让元宝帮助写开发板上的程序,给出的答案的操作性较差,这次换成豆包试一试。

那就开始跟着做呗!新建工程!

配置完毕。

明明是用cube,居然出现keil,这就是目前豆包的智商。

代码写好了。

这个步骤则是纯粹瞎讲了。按照cube里面的步骤进行了。

准备连设备调试一下!说实话,对这个程序一点都不担心,AI 在这种难度层面的编程能力毋庸置疑。不过我会先测试一下MATLAB程序,看能不能接收到对方发来的数据?和学生写的串口通信程序对接!

看看代码吧!

程序已经发给学生了。下次课我会把我写的中断方式下的串口程序和她写的MATLAB程序进行收发测试。大家想参加我的单片机培训课程吗?留言给我!

这些过程弄好以后那就开始用普通的GPIO口模拟串口的收发过程了,然后写AD采样程序了,有了现在的基础可以将AD采样数据保存下来供后续研究使用。学习不能停,加油,老师和你们一起进步。未完,待续。

修订记录

20260919 完成内容;

相关学习资料