一、STM32内部的USART外设
-
作用:
-
按照串口协议来产生和接收高低电平信号,实现串口通信。
-
USART 的定义
-
USART 是 Universal Synchronous/Asynchronous Receiver/Transmitter 的缩写,即通用同步 / 异步收发器。它是一种用于数据传输的硬件外设。
-
UART(51单片机的中的产串口外设):通用异步收发器,一般串口很少使用这个同步功能,所以USART和UART使用起来,也没有什么区别。
-
STM32的USART的同步模式,只是多了个时钟输出而已,它只支持时钟输出,不支持时钟输入,所以这个同步模式更多的是为了,兼容别的协议或者特殊用途而设计的,并不支持两个USART之间进行同步通信。所以学习串口主要还是异步通信。
-
USART 在 STM32 中的功能
-
数据处理与传输
-
串口主要就是靠收发这样的、约定好的波形来进行通信的,USART 是 STM32 内部集成的硬件外设,就是串口通信的硬件支持电路,其中大体可以分为两部分,一部分是发送,一部分是接收。
-
它能够根据数据寄存器的一个字节数据自动生成数据帧时序,然后从 TX 引脚发送出去。(发送部分)
-
同时,它也可以自动接收 RX 引脚的数据帧时序,将接收到的数据拼接为一个字节数据,并存储在数据寄存器里。(接收部分)
-
-
波特率发生器
-
USART 自带波特率发生器(配置波特率),最高可达 4.5 Mbits/s。波特率决定了数据传输的速度,这个特性使得 USART 能够在高速数据传输场景下使用。
-
它其实就是一个分频器,比如设置APB2总线给个72MHz的频率,然后波特率发生器进行一个分频,得到我们想要的波特率时钟,最后在这个时钟下,进行收发,就是我们指定的通信波特率。
-
-
可配置参数(通过库函数给结构体赋值即可配置)
-
数据位长度:可以配置数据位长度为 8 位或 9 位。不同的数据位长度适用于不同的数据传输需求。
-
停止位长度:停止位长度可以配置为 0.5、1(最常用)、1.5 或 2,在进行连续发送时,停止位长度决定了帧的间隔。停止位用于标识一个数据帧的结束。
-
校验位:可以选择校验位的类型,包括无校验、奇校验和偶校验。校验位用于检测数据传输过程中的错误。
-
常用参数:波特率,9600或者115200,数据位8位,停止位1位,无校验。
-
-
其他功能支持
-
同步模式:USART 支持同步模式,就是多了个时钟CLK的输出,即在数据传输时可以使用时钟信号进行同步,确保数据传输的准确性。
-
硬件流控制:能够实现硬件流控制,防止数据传输过程中出现数据丢失或溢出的情况。比如A设备有个TX向B设备的RX发送数据,A设备一直在发,发的太快了,B处理不过来,如果没有硬件流控制,那B就只能抛弃新数据或者覆盖原数据了,如果有硬件流控制,在硬件电路上,会多出一根线,如果B没准备好接收,就置高电平,如果准备好了,就置低电平,A接收到了B反馈的准备信号,就只会在B准备好的时候,才发数据,如果B没准备好,那数据就不会发送出去。
-
DMA:支持直接存储器访问(DMA),可以在不占用 CPU 资源的情况下进行数据传输,提高数据传输效率。
-
智能卡、IrDA、LIN:还支持智能卡、红外通信(IrDA)和本地互联网络(LIN)等多种通信协议,扩展了其应用范围。
-
-
STM32F103C8T6 的 USART 资源
-
STM32F103C8T6 这款芯片具有 USART1、USART2、USART3 三个 USART 资源,可供开发者在不同的应用场景下使用。其中这里USART1是APB2总线上的设备,剩下的都是APB1总线的设备。
二、USART结构框图
-
总体结构
-
数据流向
-
从图的左侧到右侧可以看到数据的传输路径。数据从 CPU 或 DMA(通过写操作 PWDATA)进入发送数据寄存器(TDR),经过一系列处理后从 TX 引脚发送出去。
-
接收数据时,数据从 RX 引脚进入,经过处理后存入接收数据寄存器(RDR),然后通过读操作 PRDATA 被 CPU 或 DMA 读取。
-
这里的SW_RX、IRDA_OUT/IN这些是智能卡和IrDA通信的引脚,不用这些协议,所以这些引脚就不用管。右边这个框框,IrDA、SIR这些东西也都不用管。
-
-
主要模块
-
发送模块:包括发送数据寄存器(TDR)、发送移位寄存器、发送器控制、发送器时钟等部分。
-
接收模块:包括接收数据寄存器(RDR)、接收移位寄存器、接收器控制、接收器时钟等部分。
-
波特率控制模块:包括 USART_BRR 寄存器,用于控制发送器和接收器的波特率。
-
中断控制模块:包括 USART 中断控制,用于处理各种中断事件。
-
-
详细模块介绍
-
发送模块
-
发送数据寄存器(TDR):
-
数据从 CPU 或 DMA 通过写操作 PWDATA 写入 TDR。
-
当发送移位寄存器准备好接收新数据时,TDR 中的数据会被转移到发送移位寄存器。
-
与接收数据寄存器占用同一个地址。在程序上,只表现为一个寄存器,就是数据寄存器DR(Data Register),但实际硬件中,是分成了两个寄存器,一个用于发送TDR,一个用于接收RDR,TDR是只写的,RDR是只读的,当你进行写操作时,数据就写入到TDR,当你进行读操作时,数据就是从RDR读出来的。
-
-
发送移位寄存器:
-
负责将并行数据转换为串行数据,把一个字节的数据一位一位地移出去正好对应串口协议的波形的数据位,并通过 TX 引脚发送出去。
-
其工作受发送器控制和发送器时钟的控制。
-
-
发送器控制(CR1、CR2、CR3 寄存器相关位):
-
CR1 寄存器中的一些位(如 TE - 发送使能)用于控制发送器的开启和关闭。
-
其他位(如 TXEIE - 发送缓冲区空中断使能)用于控制相关中断。
-
-
发送器时钟:
-
发送器时钟由系统时钟分频得到,用于控制发送移位寄存器的数据发送速度。
-
-
-
接收模块
-
接收数据寄存器(RDR):
-
接收移位寄存器将串行数据转换为并行数据后存入 RDR。
-
CPU 或 DMA 通过读操作 PRDATA 从 RDR 中读取数据。
-
与发送数据寄存器占用同一个地址。在程序上,只表现为一个寄存器,就是数据寄存器DR(Data Register),但实际硬件中,是分成了两个寄存器,一个用于发送TDR,一个用于接收RDR,TDR是只写的,RDR是只读的,当你进行写操作时,数据就写入到TDR,当你进行读操作时,数据就是从RDR读出来的。
-
-
接收移位寄存器:
-
从 RX 引脚一位一位的接收串行数据,并将其转换为并行数据。
-
其工作受接收器控制和接收器时钟的控制。
-
-
接收器控制(CR1、CR2、CR3 寄存器相关位):
-
CR1 寄存器中的 RE - 接收使能位用于控制接收器的开启和关闭。
-
其他位(如 RXNEIE - 接收缓冲区非空中断使能)用于控制相关中断。
-
-
接收器时钟:
-
接收器时钟同样由系统时钟分频得到,用于控制接收移位寄存器的数据接收速度。
-
-
-
波特率控制模块
-
USART_BRR 寄存器:
-
USARTDIV的分频系数包括 DIV_Mantissa(整数部分)和 DIV_Fraction(小数部分)两个字段。通过设置这两个字段,可以控制发送器和接收器的波特率。并且这里分频系数是支持小数点后4位的,分频就更加精准
-
波特率计算公式为:USARTDIV=DIV_Mantissa + (DIV_Fraction / 16)
-
波特率发生器根据 USARTDIV 的值产生合适的时钟信号用于发送和接收。
-
波特率发生器其实就是分频器,APB时钟进行分频,得到发送和接收移位的时钟。
-
这里时钟输入是fPCLKx(x=1或2),USART1挂载在APB2,所以就是PCLK2的时钟,一般是72M,其他的USART都挂载在APB1,所以是PCLK1的时钟,一般是36M。之后这个时钟进行一个分频,除一个USARTDIV的分频系数,分频完之后,还要再除个16,得到发送器时钟和接收器时钟,通向控制部分。
-
然后右边这里,如果TE(TXEnable)为1,就是发送器使能了,发送部分的波特率就有效;如果RE(RXEnable)为1,就是接收器使能了,接收部分的波特率就有效。
-
-
-
中断控制模块
-
USART 中断控制:
-
通过 CR1、CR2、CR3 寄存器中的相关位(如 TXEIE、RXNEIE 等)控制各种中断事件。
-
当发生中断事件时,CPU 可以根据中断标志进行相应的处理。
-
重要标志位:一个是TXE发送寄存器空,另一个是RXNE接收寄存器非空,这两个是判断发送状态和接收状态的必要标志位。
-
中断输出控制:就是配置中断是不是能通向NVIC。
-
-
-
硬件数据流控模块
-
硬件流控制,简称流控。
-
作用:如果发送设备发的太快,接收设备来不及处理,就会出现丢弃或覆盖数据的现象,那有了流控,就可以避免这个问题了,这里流控有两个引脚,一个是nRTS,一个是nCTS。
-
引脚:nRTS(RequestToSend)是请求发送,是输出脚,也就是告诉别人,我当前能不能接收;nCTS(ClearToSend)是清除发送,是清除发送,是输入脚,也就是用于接收别人nRTS的信号的,这里前面加个n意思是低电平有效
-
工作流程:找另一个支持流控的串口,它的TX接到这里的RX,然后这里的RTS要输出一个能不能接收的反馈信号,接到对方的 CTS,当能接收的时候,RTS就置低电平,请求对方发送,对方的CTS接收到之后,就可以一直发,当处理不过来时,比如接收数据寄存器一直没有读,又有新的数据过来了,现在就代表我没有及时处理,那RTS就会置高电平,对方CTS接收到之后,就会暂停发送,直到这里接收数据寄存器被读走,RTS置低电平,新的数据才会继续发送;那反过来,当这里的TX给对方发送数据时,我们CTS就要接到对方的RTS,用于判断对方,能不能接收。TX和CTS是一对的,RX和RTS是一对的,CTS和RTS也要交叉连接。
-
-
唤醒单元控制模块
-
作用:控制串口挂载多设备。因为串口是点对点通信,只支持两个设备互相通信,想发数据可以直接发;而多设备,在一条总线上,可以接多个从设备,每个设备分配一个地址,想跟某个设备通信,就先进行寻址,确定通信对象,在进行数据收发。
这个唤醒单元就可以用来实现多设备的功能,在USART地址这里,可以给串口分配一个地址,当发送指定地址时,此设备唤醒开始工作,当发送别的设备地址时,别的设备就唤醒工作,没收到地址就会保持沉默,这样就可以实现多设备的串口通信了。
-
-
其他控制和状态寄存器
-
GTPR 寄存器:
-
包含 GT(保护时间)、PSC(预分频器)、SCLK 控制等字段,用于控制 USART 的一些高级功能。
-
工作历程:这部分电路用于产生同步的时钟信号,它是配合发送移位寄存器输出的,发送寄存器每移位一次,同步时钟电平就跳变一个周期,时钟告诉对方,我移出去一位数据了,时钟信号用于指导接收数据的设备接收数据。这个时钟只支持输出,不支持输入,所以两个USART之间,不能实现同步的串回通信。
-
作用:第一个用途就是,兼容别的协议。比如串口加上时钟之后,就跟SPI协议特别像,所以有了时钟输出的串口,就可以兼容SPI。第二个用途这个时钟也可以做自适应波特率,比如接收设备不确定发送设备给的什么波特率,那就可以测量一下这个时钟的周期,然后再计算得到波特率,不过这就需要另外写程序来实现这个功能了。
-
-
状态寄存器(SR):
-
包含多个状态位(如 TXE - 发送缓冲区空、RXNE - 接收缓冲区非空等),用于反映 USART 当前的工作状态。
-
-
硬件电路的工作案例
1.发送端中发送数据寄存器和发送移位寄存器的工作流程:比如你在某时刻给TDR写入了0x55这个数据,在寄存器里就是二进制存储,01010101,那么此时,硬件检测到你写入数据了,它就会检查,当前移位寄存器是不是有数据正在移位,如果没有,这个0101010101就立刻全部移动到发送移位寄存器,准备发送,当数据从TDR移动到移位寄存器时,会置一个标志位,叫TXE (TX Empty),发送寄存器空,我们检查这个标志位,如果置1了,我们就可以在TDR写入下一个数据了,注意一下,当TXE标志位置1时,数据其实还没有发送出去,只要数据从TDR转移到发送移位寄存器了,TXE就会置1,我们就可以写入新的数据了,然后发送移位寄存器就会在下面这里的发生器控制的驱动下,向右移位,然后一位一位地,把数据输出到TX引脚,这里是向右移位的,所以正好和串口协议规定的低位先行,是一致的,当数据移位完成后,新的数据就会再次自动地从TDR转移到发送移位寄存器里来,如果当前移位寄存器移位还没有完成,TDR的数据就会进行等待,一但移位完成,就会立刻转移过来,了TDR和移位寄存器的双重缓存,可以保证连续发送数据的时候,数据帧之间不会有空闲,提高了工作效率。简单来说,就是你数据一但从TDR转移到移位寄存器了,管你有没有移位完成,我就立刻把下一个数据放在TDR等着,一但移完了,新的数据就会立刻跟上。
2. 接收端的接受数据寄存器和接受移位寄存器的工作流程:数据从RX引脚通向接收移位寄存器,在接收器控制的驱动下,位一位地读取RX电平,先放在最高位,然后向右移,移位8次之后,就能接收一个字节了,同样,因为串口协议规定是低位先行,所以接收移位寄存器是从高位往低位这个方向移动的,之后,当一个字节移位完成之后,这一个字节的数据就会整体地一下子转移到接收数据寄存器RDR里来,在转移的过程中,也会置一个标志位,叫RXNE (RX Not Empty) 接收数据寄存器非空,当我们检测到RXNE置1之后,就可以把数据读走了,同样,这里也是两个寄存器进行缓存,当数据从移位寄存器转移到RDR时,就可以直接移位接收下一帧数据了。
三、各个USART引脚对应的GPIO口:
-
USART2的TX是PA2口,RX是PA3口;
-
USART3的TX和RX分别是PB10和PB11;
-
USART1的TX和RX分别是PA9和PA10;
-
引脚的使用必须按照,引脚定义的规定来。
四、USART基本结构
-
总体结构
-
组成部分
-
整个 USART 基本结构由多个模块组成,包括波特率发生器、发送控制器、接收控制器、发送数据寄存器(TDR)、接收数据寄存器(RDR)、发送移位寄存器、接收移位寄存器,以及与外部连接的 GPIO(General - Purpose Input/Output)引脚(TX 和 RX)。
-
还有一个开关控制模块用于控制数据的发送和接收。
-
-
模块功能
-
波特率发生器
-
波特率发生器模块位于图的左侧,接收 PCLK2/1 时钟信号。
-
其主要功能是产生用于数据传输的约定好的时钟信号,以控制数据的发送和接收速率。
-
波特率发生器经过分频后输出连接到发送控制器和接收控制器,确保数据的同步传输。
-
-
发送控制器和接收控制器
-
发送控制器和接收控制器分别位于图的中间部分,颜色为橙色。
-
发送控制器的功能是管理数据从发送数据寄存器(TDR)到发送移位寄存器的传输过程。它控制数据的发送时序和逻辑。
-
接收控制器则负责管理数据从接收移位寄存器到接收数据寄存器(RDR)的传输过程。它处理接收数据的时序和逻辑。
-
-
数据寄存器和移位寄存器
-
发送数据寄存器(TDR)和接收数据寄存器(RDR)分别位于图的中间偏右部分,颜色为绿色。
-
TDR 用于存储待发送的数据,当发送控制器准备好后,数据从 TDR 传输到发送移位寄存器,置一个TEX标志位,通过判断标志位,可以判断是否写入下一个数据。
-
RDR 用于存储接收到的数据,当接收控制器处理完接收移位寄存器的数据后,数据被存储到 RDR 中,置一个RENX标志位,通过判断标志位,可以判断是否读取下一个数据。
-
发送移位寄存器和接收移位寄存器用于将并行数据转换为串行数据(发送时),或将串行数据转换为并行数据(接收时)。
-
-
GPIO 引脚(TX 和 RX)
-
TX 和 RX 引脚位于图的右侧,颜色为绿色。
-
TX 引脚用于将数据发送到外部设备,数据从发送移位寄存器通过 TX 引脚输出特定波形。
-
RX 引脚用于接收外部设备发送的特定波形,接收到的特定波形通过 RX 引脚进入接收移位寄存器(低位先行,所以从左边一位一位移入接收一位寄存器)。
-
-
开关控制
-
开关控制模块位于图的右下角,颜色为橙色。
-
它用于控制整个 USART 的工作状态,包括数据的发送和接收。通过开关控制,可以启动或停止数据传输过程。
-
-
数据传输流程
-
发送流程
-
数据首先被写入发送数据寄存器(TDR)。
-
发送控制器将 TDR 中的数据由低位一位一位传输到发送移位寄存器,当一个数据帧发送完成后,会置一个TXE标志位,可以通过判断这个标志位,知道是佛可以写下一个数据了。
-
发送移位寄存器将并行数据转换为串行数据,并通过 TX 引脚发送特定波形到外部设备。
-
-
接收流程
-
外部设备通过 RX 引脚发送串行数据(特定波形)。
-
接收移位寄存器接收串行数据并将其转换为并行数据,将特定波形一位一位从左边输入接受移位寄存器,移完一帧后,数据会统一转移到接收数据寄存器。
-
接收控制器将并行数据从接收移位寄存器传输到接收数据寄存器(RDR) ,并且置一个RENX标志位,通过检查这个标志位,知道是否可以继续读取数据。同时这个标志位也可以去申请中断,这样就可以在收到数据时,直接进入中断函数,然后快速地读取和保存数据。
-
最后,数据可以从 RDR 中被读取和处理。
-
图中右边实际上有四个寄存器,但是软件层面上,只有一个DR寄存器供我们读写,写入DR时,数据走上面这条路,进行发送;读取DR时,数据走下面这条路,进行接收。
五、数据帧
1. 不同位数的数据帧
-
总体概述
这张图比较了两种字长设置下的数据帧结构:9 位字长(设置了 M 位)和 8 位字长(未设置 M 位),每种字长都有 1 个停止位。图中详细展示了数据帧各个部分的时序关系。
-
9 位字长(设置了 M 位)
-
数据帧结构
-
起始位:数据帧的开始,标志着数据传输的起点。
-
位 0 - 位 8:9 位数据位,其中包括了 8 位数据和 1 位 M 位(可能用于奇偶校验或其他控制功能)。
-
停止位:数据帧的结束标志。
-
-
时序关系
-
时钟:提供数据传输的同步时钟信号,每个数据位在时钟的上升沿或下降沿进行传输。
-
空闲帧:在数据帧之间的空闲状态,没有数据传输。(局域网协议使用,串口不需要)
-
断开帧:在特定情况下用于表示连接断开的状态。(局域网协议使用,串口不需要)
-
LBCL 位控制最后一个数据的时钟脉冲:这表示最后一个数据位(位 8)的时钟脉冲受 LBCL 位控制,控制其是否输出,确保数据传输的准确性。
-
可能的奇偶校验位:在 9 位字长中,可能会有一个奇偶校验位用于数据校验。
-
下一个数据帧:在当前数据帧结束后,下一个数据帧的起始位开始。
-
-
8 位字长(未设置 M 位)
-
数据帧结构
-
起始位:同样标志着数据传输的开始。
-
位 0 - 位 7:8 位数据位,没有额外的 M 位。
-
停止位:数据帧的结束标志。
-
-
时序关系
-
时钟:与 9 位字长类似,提供同步时钟信号。
-
空闲帧:数据帧之间的空闲状态。(局域网协议使用,串口不需要)
-
断开帧:用于表示连接断开的状态。(局域网协议使用,串口不需要)
-
LBCL 位控制最后一个数据的时钟脉冲:同样,最后一个数据位(位 7)的时钟脉冲受 LBCL 位控制。
-
可能的奇偶校验位:在 8 位字长中,也可能会有一个奇偶校验位用于数据校验。
-
下一个数据帧:在当前数据帧结束后,下一个数据帧的起始位开始。
-
-
总结
-
对比
-
9 位字长和 8 位字长的数据帧结构主要区别在于数据位的数量,9 位字长多了一个 M 位。
-
两者都有起始位、停止位,并且在数据传输过程中都受时钟信号控制。
-
奇偶校验位在两种字长中都有可能存在,用于数据的错误检测,但我们一般在9字长,选择8位有效载荷+1位奇/偶校验位或者8字长,选择8位有效载荷+无校验。
-
-
应用场景
-
根据具体的应用需求,可以选择 8 位或 9 位字长的数据帧。例如,在需要额外控制位的情况下,可以选择 9 位字长;而在只需要传输 8 位数据的情况下,8 位字长就足够了。
-
2. 不同停止位长度的数据帧
-
总体概述
这张图主要讨论了 8 位字长(未设置 M 位)的数据帧在不同停止位配置下的结构。图中展示了四种不同的停止位配置:1 个停止位、1.5 个停止位、2 个停止位和 0.5 个停止位。
-
8 位字长(未设置 M 位)的数据帧结构
-
数据帧的基本组成部分
-
起始位:数据帧的开始,标志着数据传输的起点。
-
位 0 - 位 7:8 位数据位,没有额外的 M 位。
-
停止位:数据帧的结束标志,其数量在不同配置下有所不同。
-
时钟:提供数据传输的同步时钟信号,每个数据位在时钟的上升沿或下降沿进行传输。
-
空闲帧:在数据帧之间的空闲状态,没有数据传输。
-
断开帧:在特定情况下用于表示连接断开的状态。
-
可能的奇偶校验位:在数据帧中可能存在奇偶校验位用于数据校验。
-
下一个数据帧:在当前数据帧结束后,下一个数据帧的起始位开始。
-
-
不同停止位配置的详细分析
-
1 个停止位
-
数据帧结构:起始位 - 位 0 - 位 7 - 1 个停止位。
-
图中在 8 位数据位(位 0 - 位 7)之后紧接着是 1 个停止位,完成一个数据帧的传输。
-
-
1.5 个停止位
-
数据帧结构:起始位 - 位 0 - 位 7 - 1.5 个停止位。
-
图中在 8 位数据位之后有 1.5 个停止位,这意味着在 8 位数据传输完后,有一个额外的半位停止位。
-
-
2 个停止位
-
数据帧结构:起始位 - 位 0 - 位 7 - 2 个停止位。
-
图中在 8 位数据位之后有 2 个完整的停止位,用于确保数据传输的可靠性。
-
-
0.5 个停止位
-
数据帧结构:起始位 - 位 0 - 位 7 - 0.5 个停止位。
-
图中在 8 位数据位之后有一个半位停止位,这是一种特殊的配置。
-
-
总结
-
停止位的作用
-
停止位用于表示一个数据帧的结束,确保数据接收端能够正确识别数据帧的边界。不同数量的停止位可以提供不同程度的数据传输可靠性。
-
-
应用场景
-
在实际应用中,选择不同的停止位配置取决于具体的通信需求和设备的兼容性。例如,在噪声环境下,可能会选择更多的停止位来提高数据传输的准确性
-
六、 USART电路输入数据的策略
串口的输出TX应该是比输入RX简单很多,输出定时翻转TX引脚高低电平即可,但是输入不仅要保证,输入的采样频率和波特率一致,还要保证每次输入采样的位置,要正好处于每一位的正中间,只有在每一位的正中间采样,这样高低电平读进来,才是最可靠的,如果采样点过于靠前或靠后,那有可能高低电平还正在翻转,电平还不稳定,或者稍有误差,数据就采样错了,另外,输入最好还要对噪声有一定的判断能力,如果是噪声,最好能置个标志位提醒一下,这些就是输入数据时所面临的问题。
1. USART的起始位预测
-
总体概述
-
图中主要描述了在数据接收过程中如何侦测起始位。起始位是数据帧的开始标志,准确地侦测到起始位对于数据的正确接收至关重要。
-
当输入电路侦测到一个,数据帧的起始位后,就会以波特率的频率,连续采样一帧数据,同时,从起始位开始,采样位置就要对齐到位的正中间,只要第一位对齐了,后面就肯定都是对齐的,为了实现以上功能。
-
首先输入的这部分电路对采样时钟进行了细分,它会以波特率的16倍频率进行采样,也就是在一位的时间里,可以进行16次采样,最开始,空闲状态高电平,那采样就一直是1,在某个位置,突然采到一个0,那么就说明,在这两次采样之间,出现了下降沿,如果没有任何噪声,那之后就应该是起始位了,在起始位,会进行连续16次采样,没有噪声的话,这16次采样,肯定就都是0,但是,实际电路还是会存在一些噪声的,所以这里即使出现下降沿了,后续也要再采样几次,以防万一。根据手册描述,这个接收电路,还会在下降沿之后的第3次、5次、7次,进行一批采样,在第8次、9次、10次,再进行一批采样,且这两批采样,都要要求每3位里面至少应有2个0,没有噪声,那肯定全是0,满足情况,如果有轻微噪声导致这里3位里面,只有两个0,另一个是1,那也算是检测到了起始位,但是在状态寄存器里会置一个NE(Noise Error),噪声标志位,就是提醒你一下,数据我是收到了,但是有噪声,谨慎使用;如果3位里面,只有1个0,那就不算检测到了起始位,可能前面那个下降沿是噪声导致的,这时电路就忽略前面的数据,重新开始捕捉下降沿,以上就是STM32的串口,在接收过程中,对噪声的处理。
-
如果通过了起始位侦测,那接收状态就由空闲,变为接收起始位,同时,第8、9、10次采样的位置,就正好是起始位的正中间(由于对一个单位的数据进行十六次采样,低8.9.10次采样就是在这16次采样的中间),之后,接收数据位时,就都在第8、9、10次,进行采样,这样就能保证采样位置在位的正中间了,这就是起始位侦测和采样位置对齐的策略。
-
详细结构
-
接收状态
-
图的上方有一个 “接收状态” 的标识,表示当前数据接收的状态。
-
接收状态分为 “空闲” 和 “起始位” 两个阶段。在空闲阶段,没有数据传输;在起始位阶段,表示数据帧的开始。
-
-
接收信号线
-
接收信号线在图中以一个水平的长条形表示。
-
当接收信号线从高电平(空闲状态)转变为低电平(起始位)时,表示数据帧的开始。
-
-
理想的采样时钟与实际的采样时钟
-
理想的采样时钟:图中以垂直的箭头表示理想的采样时钟,均匀分布在时间轴上。理想的采样时钟是假设的理想情况下的数据采样时刻。
-
实际的采样时钟:以 “X” 标记的垂直箭头表示实际的采样时钟。实际的采样时钟可能会因为系统时钟的偏差等因素而与理想的采样时钟不一致。
-
-
数据采样
-
数据采样在图中通过垂直箭头与接收信号线的交叉点表示。
-
理想情况下,数据采样在每个采样时钟的上升沿进行,但实际情况可能会有所不同。
-
-
判断起始位的条件
-
图的下方列出了判断起始位的条件:
-
检测下降沿:当接收信号线从高电平变为低电平时,检测到下降沿,表示可能的起始位。
-
每 3 位里面至少应有 2 个 0:在起始位的检测过程中,每 3 个采样位中至少应有 2 个 0,才能确认是起始位。
-
-
图中以 “1” 和 “0” 表示采样位的值,通过检查这些值来判断是否满足起始位的条件。
-
-
一位的时间长度
-
图中标记了 “一位的时间长度”,表示一个数据位在时间轴上所占的长度。
-
这里有两个标记:“6/16” 和 “7/16”,表示在一位的时间长度内,采样时钟的位置可能会有偏差。
-
-
工作原理
-
起始位检测过程
-
当接收信号线出现下降沿时,系统开始检测是否为起始位。
-
在下降沿之后,系统对后续的采样位进行检查,确保每 3 位里面至少有 2 个 0,以此来确认起始位。
-
-
采样时钟偏差
-
由于实际的采样时钟可能与理想的采样时钟不一致,图中展示了这种偏差对起始位检测的影响。
-
通过检查采样位的值和判断条件,系统能够在存在时钟偏差的情况下准确地检测到起始位。
-
2. USART的数据采样流程
-
总体概述
图 中主要描述了在数据采样过程中如何处理和检测噪声。数据采样是从接收信号线中获取数据的过程,而噪声可能会干扰数据的准确采集。
-
详细结构
-
接收信号线
-
图的上方有一条水平的接收信号线,表示从外部接收到的数据信号。
-
接收信号线上有一些阴影区域,表示可能存在的噪声干扰。
-
-
采样时钟
-
图中有垂直的采样时钟线,标记为 1 到 16,这里是一个数据位的长度。
-
这些采样时钟线均匀分布,表示在每个时钟周期内进行一次数据采样。
-
在一个数据位,有16个采样时钟。
-
-
数据采样
-
由于起始位侦测已经对齐了采样时钟,所以,这里就直接在第8、9、10次采样数据位,为了保证数据的可靠性,这里是连续采样3次,没有噪声的理想情况下,这3次肯定全为1或者全为0,全为1,就认为收到了1,全为0,就认为收到了0,如果有噪声,导致3次采样不是全为1或者全为0,那它就按照2:1的规则来,2次为1,就认为收到了1;2次为0,就认为收到了0,在这种情况下,噪声标志位NE也会置1。
-
从采样时钟线出发的箭头指向接收信号线,表示在每个采样时钟的上升沿进行数据采样。
-
数据采样的位置可能会受到噪声的影响。
-
-
一个数据位的时间长度
-
图中标注了 “一个数据位的时间长度”,分为两部分:6/16 和 7/16。
-
这表示在一个数据位的时间内,采样时钟可能会在不同的位置进行采样。
-
-
工作原理
-
数据采样过程
-
在每个采样时钟的上升沿,系统会对接收信号线进行采样,获取数据位的值。
-
由于噪声的存在,采样的数据可能会受到干扰,导致数据不准确。
-
-
检测噪声
-
通过观察采样数据的变化,可以检测到噪声的存在。
-
例如,如果在连续的采样中数据值出现异常波动,可能是由于噪声干扰导致的。
-
-
采样位置的影响
-
图中显示了采样时钟在一个数据位的时间长度内可能的不同采样位置(6/16 和 7/16)。
-
不同的采样位置可能会导致对数据位的不同判断,尤其是在噪声存在的情况下。
-
七、波特率发生器(分频器)
-
波特率发生器概述
-
波特率确定方式:发送器和接收器的波特率由波特率寄存器(BRR)里的 DIV 确定。
-
计算公式:波特率 = fPCLK2/1 / (16 * DIV)
-
为什么这里多个16?因为它内部还有一个16倍波特率的采样时钟,所以这里输入时钟/DIV要等于16倍的波特率,最终计算波特率,自然要多除一个16了。
-
例子:比如要配置USART1为9600的波特率,代入公式,就是9600等于USART1的时钟是72M除16倍的DIV,解得,DIV=72M/9600/16=468.75,这是一个带小数的分频系数,最终写到寄存器还需要转换成二进制。最后写入寄存器为整数部分为111010100,前面多出来的补0,小数部分为11,后面多出来的补0
-
利用库函数配置的话,需要多少波特率直接写入即可。
-
-
-
波特率比率寄存器(USART_BRR)
-
地址和复位值
-
地址偏移:0x08
-
复位值:0x0000
-
-
寄存器位结构
-
该寄存器共有 32 位,从位 0 到位 31。
-
其中,位 16 到位 31 为保留位,硬件强制为 0。
-
位 0 到位 15 用于确定波特率。
31
30
29
28
27
26
25
24
23
22
21
20
19
18
17
16
保
留
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
DIV_Mantissa[11:0]
DIV_Fraction[3:0]
-
-
位段功能
-
位 15:4(DIV_Mantissa [11:0])
-
这 12 位定义了 USART 分频器除法因子(USARTDIV)的整数部分。
-
-
位 3:0(DIV_Fraction [3:0])
-
这 4 位定义了 USART 分频器除法因子(USARTDIV)的小数部分。
-
-
-
注意事项
-
如果 TE(发送使能)或 RE(接收使能)被分别禁止,波特计数器停止计数。
-
-
应用场景
在串行通信中,波特率是一个关键参数,决定了数据传输的速率。通过配置 USART_BRR 寄存器,可以精确地设置波特率,确保发送器和接收器之间的数据传输能够正确进行。
八、USB转串口模块的原理图
-
USB端口:
-
USB有4根线,GND、D+、D-、VCC,USB标准供电是5V,然后中间D+和D-是通信线。
-
-
GH340芯片
-
由于走的是USB协议,所以这里需要一个CH340芯片转换一下,转换之后输出的就是TXD和RXD,是串口协议。
-
-
CON6排针
-
之后TXD、RXD通过排针引出。
-
供电策略:所有的电都是从USB端口VCC+5V来的,然后VCC+5V,通过稳压管电路进行降压,得到VCC+3.3V,之后,VCC+5V和VCC+3.3V,都通过排针引出来了,所以这个第6脚和第4脚,是分别有5V和3.3V输出的。
-
其中的第五脚,板子上标的是VCC,通过原理图可以知道,它是通向CH340芯片上,所以这个第5脚,实际上是CH340的电源输入脚。
-
排针跳线帽:这个跳线帽需要插在4、5脚(短路3V3到VCC,CH340供电为3.3V,TTL电平为3.3V),或者5、6脚(短路 5V 到 VCC,CH340 供电为 5V,TTL 电平为 5V)上,所以这个跳线帽,是用来选择通信电平的,也是给CH340芯片供电的,如果拿掉相当于整个芯片没有供电。
-
九、串口发送&串口发送+接收代码详解
-
串口发送:
-
工程的建立:
-
复制“4-1 OLED显示屏”并修改名称为“9-1 串口发送”。
-
删去多余部分。
-
-
建立串口模块(Serial.c):
-
USART中需要使用的库函数
-
void USART_DeInit(USART_TypeDef* USARTx);
-
功能:
-
这个函数用于将指定的 USART(通用同步 / 异步收发器)外设复位到其默认状态。它会重置所有与 USART 相关的寄存器,包括控制寄存器、状态寄存器、数据寄存器等,使其恢复到上电复位后的初始配置。
-
-
参数:
-
USARTx
:这是一个指向USART_TypeDef
结构体的指针,用于指定要进行复位操作的 USART 外设。例如,在 STM32 系列中,可以是USART1
、USART2
等,具体取决于芯片型号和可用的 USART 资源。
-
-
-
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
-
功能:
-
此函数用于初始化指定的 USART 外设。它根据传入的初始化结构体中的参数来配置 USART 的各种特性,如波特率、数据位长度、停止位数量、奇偶校验位、硬件流控制以及工作模式(发送、接收或收发模式)等。
-
-
参数:
-
USARTx
:同USART_DeInit
函数中的参数,用于指定要初始化的 USART 外设。 -
USART_InitStruct
:这是一个指向USART_InitTypeDef
结构体的指针,该结构体包含了 USART 初始化所需的各种参数。例如:-
USART_InitStruct->USART_BaudRate
:设置波特率。 -
USART_InitStruct->USART_WordLength
:设置数据字长。 -
USART_InitStruct->USART_StopBits
:设置停止位数量。 -
USART_InitStruct->USART_Parity
:设置奇偶校验位。 -
USART_InitStruct->USART_HardwareFlowControl
:设置硬件流控制。 -
USART_InitStruct->USART_Mode
:设置工作模式。
-
-
-
-
void USART_StructInit(USART_InitTypeDef* USART_InitStruct);
-
功能:
-
这个函数用于将
USART_InitTypeDef
结构体中的成员变量初始化为默认值。这些默认值通常是在不进行特殊配置时的典型设置,例如波特率可能被初始化为 9600,数据字长为 8 位,无硬件流控制等。
-
-
参数:
-
USART_InitStruct
:这是一个指向USART_InitTypeDef
结构体的指针,该结构体的成员变量将被初始化。
-
-
-
void USART_ClockInit(USART_TypeDef* USARTx, USART_ClockInitTypeDef* USART_ClockInitStruct);
-
功能:
-
当 USART 工作在同步模式时,此函数用于初始化与 USART 相关的时钟参数。它配置时钟极性、相位以及是否使能时钟输出等特性。
-
-
参数:
-
USARTx
:指定要进行时钟初始化的 USART 外设。 -
USART_ClockInitStruct
:这是一个指向USART_ClockInitTypeDef
结构体的指针,该结构体包含了与 USART 时钟初始化相关的参数,例如时钟极性、时钟相位和时钟使能等设置。
-
-
-
void USART_ClockStructInit(USART_ClockInitTypeDef* USART_ClockInitStruct);
-
功能:
-
这个函数用于将
USART_ClockInitTypeDef
结构体中的成员变量初始化为默认值。这些默认值适用于在不进行特殊时钟配置时的情况。
-
-
参数:
-
USART_ClockInitStruct
:这是一个指向USART_ClockInitTypeDef
结构体的指针,该结构体的成员变量将被初始化。
-
-
-
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
-
功能:
-
此函数用于使能或禁用指定的 USART 外设。当传入
ENABLE
时,USART 将开始工作;当传入DISABLE
时,USART 将停止工作。
-
-
参数:
-
USARTx
:指定要进行操作的 USART 外设。 -
NewState
:这是一个枚举类型参数,可以是ENABLE
或DISABLE
,用于决定是使能还是禁用 USART 外设。
-
-
-
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
- 功能:
-
这个函数用于通过指定的 USART(通用同步 / 异步收发器)外设发送数据。它将一个 16 位的数据字发送到 USART 的发送数据寄存器,然后 USART 硬件会按照预先配置好的波特率和数据格式(如起始位、数据位、停止位和奇偶校验位等)将数据逐位发送出去。
-
-
参数:
-
USARTx
:这是一个指向USART_TypeDef
结构体的指针,用于指定要进行数据发送的 USART 外设。例如,在 STM32 系列中,可以是USART1
、USART2
等,具体取决于芯片型号和可用的 USART 资源。 -
Data
:这是一个 16 位无符号整数,表示要发送的数据。实际发送的数据长度可能会根据 USART 的配置(如数据字长设置为 8 位或 9 位)而有所不同,但函数接受 16 位数据,高位可能会被忽略(如果配置的数据字长小于 16 位)。
-
- 功能:
-
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);
-
功能:
-
此函数用于从指定的 USART 外设接收数据。当 USART 接收到数据并将其存储在接收数据寄存器后,调用这个函数可以获取接收到的数据。函数返回一个 16 位的数据字,包含了接收到的数据。
-
-
参数:
-
USARTx
:同USART_SendData
函数中的参数,是一个指向USART_TypeDef
结构体的指针,用于指定要从哪个 USART 外设接收数据。
-
-
-
串口初始化函数的编写:
第一步 开启时钟,把需要用的USART和GPIO的时钟打开
-
在 STM32 中,任何外设要工作都需要先开启其时钟。上述代码分别开启了 USART1 和 GPIOA 的时钟。
-
USART1 是将要使用的串口模块,而 GPIOA 的开启是因为串口的相关引脚(这里是 PA9)属于 GPIOA 端口。如果时钟未开启,后续对这些外设的操作都无法进行。
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
第二部 GPIO 初始化,把TX配置成复用输出,RX配置成输入
-
首先定义了一个
GPIO_InitTypeDef
类型的结构体变量GPIO_InitStructure
,用于存储 GPIO 初始化的参数。 -
GPIO_Mode = GPIO_Mode_AF_PP
:将 PA9 引脚的模式设置为复用推挽输出模式。对于串口通信,当作为发送引脚时,需要使用这种模式,以便能够正确地将数据发送出去。 -
GPIO_Pin = GPIO_Pin_9
:指定要初始化的引脚是 PA9。这是 USART1 的发送引脚(TX)。 -
GPIO_Speed = GPIO_Speed_50MHz
:设置引脚的输出速度为 50MHz,较高的速度可以满足大多数串口通信的数据传输速率要求。 -
最后通过
GPIO_Init(GPIOA, &GPIO_InitStructure);
函数将上述参数应用到 GPIOA 的 PA9 引脚,完成引脚的初始化。
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA9引脚初
这样就是把PA9配置为复用推挽输出,供USART1的TX使用
第三步 USART 初始化,直接使用一个结构体配置这里的所有参数
-
定义了一个
USART_InitTypeDef
类型的结构体变量USART_InitStructure
,用于配置 USART1 的参数。 -
USART_BaudRate = 9600
:设置串口的波特率为 9600bps,写完之后,这个lnit函数内部会自动算好9600对应的分频系数,然后写入到BRR寄存器。波特率决定了数据传输的速度,发送端和接收端的波特率必须一致才能正常通信。 -
USART_HardwareFlowControl = USART_HardwareFlowControl_None
:表示不需要硬件流控制。硬件流控制通常用于防止数据丢失,但在一些简单的应用场景中可能不需要,这样可以简化电路和软件设计。注意:像这种自己选取参数的结构体变量,可以使用Ctrl+Alt+空格提示一下选择哪个参数。 -
USART_Mode = USART_Mode_Tx
:将 USART1 的工作模式设置为发送模式。表明这个串口只用于发送数据,如果还需要接收数据,则需要设置为发送和接收模式(USART_Mode_Tx | USART_Mode_Rx
)。 -
USART_Parity = USART_Parity_No
:不使用奇偶校验。奇偶校验是一种简单的数据校验方法,如果对数据准确性要求不是特别高,或者有其他校验机制,可以不使用奇偶校验来简化数据传输过程。(可以选择No无校验、Odd奇校验、Even偶校验) -
USART_StopBits = USART_StopBits_1
:选择 1 位停止位。停止位用于表示一个数据字节传输的结束,1 位停止位是比较常见的设置。 -
USART_WordLength = USART_WordLength_8b
:设置数据字长为 8 位。8 位字长适合传输常见的字节数据,如 ASCII 码字符等。 -
最后通过
USART_Init(USART1, &USART_InitStructure);
函数将配置参数应用到 USART1,完成 USART1 的初始化。
/*USART初始化*/
USART_InitTypeDef USART_InitStructure; //定义结构体变量
USART_InitStructure.USART_BaudRate = 9600; //波特率
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制,不需要
USART_InitStructure.USART_Mode = USART_Mode_Tx; //模式,选择为发送模式
USART_InitStructure.USART_Parity = USART_Parity_No; //奇偶校验,不需要
USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位,选择1位
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长,选择8位
USART_Init(USART1, &USART_InitStructure); //将结构体变量交给USART_Init,配置USART1
此时串口的配置是9600波特率、8位字长、无校验、1位停止位、无流控、只有发送模式
第四步 USART 使能
-
这一步是使能 USART1。在完成了时钟开启、GPIO 初始化和 USART 初始化后,通过
USART_Cmd
函数使 USART1 开始运行,此时串口就可以按照之前配置好的参数进行数据发送了。
/*USART使能*/
USART_Cmd(USART1, ENABLE); //使能USART1,串口开始运行
-
串口发送数据函数的编写(发送一个字节):
-
函数功能
-
这个函数的功能是通过 USART1 实现串口发送一个字节的数据。调用这个函数,就可以从TX引脚发送一个字节数据
-
-
函数实现步骤
-
数据写入
-
USART_SendData(USART1, Byte);
-
在 STM32 中,USART(通用异步收发器)是用于串口通信的重要模块。当调用
USART_SendData
函数时,它会把要发送的字节数据Byte
(8 位无符号整数)写入到 USART1 的数据寄存器中。一旦数据被写入,USART 硬件会根据之前配置好的参数(如波特率、数据格式等)自动生成时序波形,将数据逐位发送出去。这些配置在之前的代码中已经通过类似USART_Init
函数完成。 -
转到定义看下函数内部,这里Byte传递给Data这个变量,之后Data&1FF,就是把无关的高位清零,然后直接赋值给DR寄存器,因为这是写入DR,所以数据最终通向TDR,发送数据寄存器,TDR再传递给发送移位寄存器,最后一位一位地把数据移出到TX引脚,完成数据的发送,
-
-
-
等待发送完成
-
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
-
在数据写入数据寄存器后,为了确保数据准确无误地发送出去,需要等待发送操作完成。这里通过循环检查 USART1 的发送数据寄存器空(
TXE
)标志位来实现。USART_GetFlagStatus
函数用于获取指定 USART(这里是 USART1)的标志位状态。USART_FLAG_TXE
标志位表示发送数据寄存器是否为空。当该标志位为RESET
(0)时,意味着数据还在发送过程中,尚未发送完毕,所以函数会一直停留在这个循环中等待。只有当标志位变为SET
(1)时,才表示数据已经发送完毕,循环结束,此时才能进行下一次的数据发送操作。
-
-
-
标志位处理
-
/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
-
这是一个注释,说明了在这种情况下不需要手动清除发送完成标志位的原因。在 STM32 的 USART 机制中,当再次向数据寄存器写入数据时,会自动清除
TXE
标志位,所以在这个函数中不需要额外编写代码来清除标志位。
-
-
-
/**
* 函 数:串口发送一个字节
* 参 数:Byte 要发送的一个字节
* 返 回 值:无
*/
void Serial_SendByte(uint8_t Byte)
{
USART_SendData(USART1, Byte); //将字节数据写入数据寄存器,写入后USART自动生成时序波形
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); //等待发送完成
/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}
-
对串口发送数据函数的封装(发送一个数组):
一、函数功能
Serial_SendArray
函数的目的是通过串口发送一个字节数组。它基于Serial_SendByte
函数实现,属于 STM32 串口通信相关操作。
二、函数实现细节
-
变量声明
-
uint16_t i;
-
声明了一个 16 位无符号整数变量
i
,用于作为循环计数器,在遍历数组时使用。
-
-
-
循环发送数组元素
-
for (i = 0; i < Length; i ++)
-
这是一个
for
循环,用于遍历要发送的字节数组。循环从i = 0
开始,每次循环i
自增 1,直到i
达到要发送数组的长度Length
。
-
-
Serial_SendByte(Array[i]);
-
在每次循环中,调用
Serial_SendByte
函数来发送数组中的一个字节数据。-
原理:
-
Serial_SendByte
函数在之前的代码中应该已经定义,其功能是通过 STM32 的 USART1 串口发送一个字节的数据。它将字节数据写入 USART1 的数据寄存器,然后等待数据发送完成(通过检查发送数据寄存器空标志TXE
)。
-
-
操作过程:
-
Array[i]
表示获取数组Array
中索引为i
的字节元素。在循环过程中,从数组的第一个元素(i = 0
时)开始,依次将每个字节数据通过Serial_SendByte
函数发送出去,直到整个数组发送完毕。
-
-
-
-
/**
* 函 数:串口发送一个数组
* 参 数:Array 要发送数组的首地址
* 参 数:Length 要发送数组的长度
* 返 回 值:无
*/
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
uint16_t i;
for (i = 0; i < Length; i ++) //遍历数组
{
Serial_SendByte(Array[i]); //依次调用Serial_SendByte发送每个字节数据
}
}
-
对串口发送数据函数的封装(发送一个字符串):
一、函数功能
Serial_SendString
函数的目的是通过串口发送一个字符串。它基于Serial_SendByte
函数实现,用于将字符串中的每个字符依次通过串口发送出去。
二、函数实现细节
-
变量声明
-
uint8_t i;
-
声明了一个 8 位无符号整数变量
i
,用于作为循环计数器,在遍历字符串时使用。
-
-
-
循环发送字符串字符
-
for (i = 0; String[i]!= '\0'; i ++)
-
这是一个
for
循环,用于遍历要发送的字符串。循环从i = 0
开始,每次循环i
自增 1,循环条件是字符串中当前位置的字符不等于字符串结束标志'\0'
(在 C 语言中,字符串以'\0'
作为结束标志)。
-
-
Serial_SendByte(String[i]);
-
在每次循环中,调用
Serial_SendByte
函数来发送字符串中的一个字符。-
原理:
-
Serial_SendByte
函数在之前的代码中应该已经定义,其功能是通过 STM32 的 USART1 串口发送一个字节的数据。它将字节数据写入 USART1 的数据寄存器,然后等待数据发送完成(通过检查发送数据寄存器空标志TXE
)。
-
-
操作过程:
-
String[i]
表示获取字符串String
中索引为i
的字符。在循环过程中,从字符串的第一个字符(i = 0
时)开始,依次将每个字符数据通过Serial_SendByte
函数发送出去,直到遇到字符串结束标志'\0'
,此时整个字符串发送完毕。
-
-
-
-
/**
* 函 数:串口发送一个字符串
* 参 数:String 要发送字符串的首地址
* 返 回 值:无
*/
void Serial_SendString(char *String)
{
uint8_t i;
for (i = 0; String[i] != '\0'; i ++)//遍历字符数组(字符串),遇到字符串结束标志位后停止
{
Serial_SendByte(String[i]); //依次调用Serial_SendByte发送每个字节数据
}
}
-
对串口发送数据函数的封装(发送一个数字):
一、函数功能
Serial_SendNumber
函数的目的是通过串口发送一个数字。该数字是一个 32 位无符号整数(uint32_t
),并且可以指定发送该数字的长度(Length
),将数字按位拆分后依次通过串口发送。
二、函数实现细节
-
变量声明
-
uint8_t i;
-
声明了一个 8 位无符号整数变量
i
,用于作为循环计数器,在遍历数字的每一位时使用。
-
-
-
循环发送数字的每一位
-
for (i = 0; i < Length; i ++)
-
这是一个
for
循环,用于根据指定的数字长度Length
来遍历数字的每一位。循环从i = 0
开始,每次循环i
自增 1,直到i
达到指定的数字长度Length
。
-
-
Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');
-
在每次循环中,调用
Serial_SendByte
函数来发送数字的一位。-
原理:
-
Serial_SendByte
函数应该是之前定义好的用于通过串口发送一个字节数据的函数。它会将字节数据写入相关的数据寄存器并完成发送操作。
-
-
操作过程:
-
这里通过数学运算获取数字的每一位。
Number / Serial_Pow(10, Length - i - 1)
是先将数字除以一个基于 10 的幂次方,幂次方的值取决于当前循环的位置(由Length
和i
决定),目的是将目标位移动到个位的位置。然后% 10
是取除以 10 的余数,得到当前位的数字值(0 - 9)。最后+ '0'
是将数字值转换为对应的 ASCII 码值,因为在字符表示中,数字 0 - 9 的 ASCII 码值是 48 - 57,通过给数字加上'0'
(ASCII 码值为 48)可以得到对应的数字字符的 ASCII 码,进而可以通过Serial_SendByte
发送该数字字符。
-
-
-
-
/**
* 函 数:串口发送数字
* 参 数:Number 要发送的数字,范围:0~4294967295
* 参 数:Length 要发送数字的长度,范围:0~10
* 返 回 值:无
*/
void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i ++) //根据数字长度遍历数字的每一位
{
Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0'); //依次调用Serial_SendByte发送每位数字
}
}
-
用于向串口发送数字数据是实现数字拆分的函数:
一、函数功能
Serial_Pow
函数的目的是计算一个数(X
)的另一个数(Y
)次方。该函数在计算Serial_SendNumber
函数中用于辅助将数字按位拆分。
二、函数实现细节
-
变量声明与初始化
-
uint32_t Result = 1;
-
声明了一个 32 位无符号整数变量
Result
,并将其初始化为 1。这是因为任何数的 0 次方都为 1,所以在开始计算次方时,将结果初始化为 1。
-
-
-
循环计算次方
-
while (Y --)
-
这是一个
while
循环,循环条件是Y
每次自减 1,直到Y
变为 0。循环执行的次数就是Y
的初始值。
-
-
Result *= X;
-
在每次循环中,将
Result
乘以X
。第一次循环时,Result
为 1,乘以X
后得到X
;第二次循环时,Result
(此时为X
)再乘以X
得到X
的 2 次方,以此类推。经过Y
次循环后,Result
的值就是X
的Y
次方。
-
-
-
返回结果
-
return Result;
-
当循环结束后,
Result
存储了X
的Y
次方的值,函数将这个结果返回。
-
-
/**
* 函 数:次方函数(内部使用)
* 返 回 值:返回值等于X的Y次方
*/
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1; //设置结果初值为1
while (Y --) //执行Y次
{
Result *= X; //将X累乘到结果
}
return Result;
}
-
printf函数的移植(重要):
一、 打开Use MicroLIB
使用printf之前,需要打开工程选项(魔术棒),把这个UseMicroLIB勾上,MicroLIB是Keil为嵌入式平台优化的一个精简库,等会儿要用的printf函数就可以用这个MicroLIB。
二、 printf的重定向
将printf函数打印的东西输出到串口,因为printf函数默认是输出到屏幕,我们单片机没有屏幕,所以要进行重定向。步骤:
-
在串口模块里最开始加上,#include<stdio.h>
-
重写fputc函数
-
函数功能概述
-
在 STM32 的编程环境中,当要使用
printf
函数时,通常需要对其底层函数进行重定向。上述fputc
函数就是用于实现printf
函数重定向的底层函数。这个fputc是printf函数的底层,printf函数在打印的时候,就是不断调用fputc函数一个个打印的,我们把fputc函数重定向到了串口,那printf自然就输出到串口了。
-
-
参数分析
-
int ch
:这是要输出的字符。在printf
函数执行过程中,对于要输出的每个字符,都会调用fputc
函数,并将字符作为ch
参数传递进来。 -
FILE *f
:这个参数在这种嵌入式环境下通常没有实际作用。它是从标准库中继承下来的参数形式,在重定向过程中保持原始格式即可。
-
-
函数体操作
-
Serial_SendByte(ch);
-
这里调用了
Serial_SendByte
函数。根据前文中对串口相关操作的介绍,Serial_SendByte
函数的功能是通过 STM32 的串口(是基于之前章节中配置好的 USART 串口, [9 - 1] 介绍的 USART 串口协议和 [9 - 2] 介绍的 USART 串口外设等相关知识)发送一个字节的数据。在这个fputc
函数中,就是将printf
函数要输出的字符(通过ch
参数传递)通过Serial_SendByte
函数发送出去,实现了printf
输出到串口的重定向。
-
-
return ch;
-
按照
fputc
函数的规范,返回输出的字符。这是为了与标准库中的函数行为保持一致,确保在调用printf
及其相关函数时,系统的运行逻辑不会出现错误。
-
-
-
/**
* 函 数:使用printf需要重定向的底层函数
* 参 数:保持原始格式即可,无需变动
* 返 回 值:保持原始格式即可,无需变动
*/
int fputc(int ch, FILE *f)
{
Serial_SendByte(ch); //将printf的底层重定向到自己的发送字节函数
return ch;
}
三、 封装sprintf函数(实现多个串口输出)
1. 函数功能
Serial_Printf
函数是一个自定义的类似于printf
的函数,用于将格式化后的字符串通过串口发送出去。sprintf可以把格式化字符输出到一个字符串里。
2. 变量声明
-
char String[100];
-
声明了一个长度为 100 的字符数组
String
。这个数组用于存储经过格式化后的字符串。
-
-
va_list arg;
-
声明了一个
va_list
类型的变量arg
。va_list
是 C 语言中用于处理可变参数列表的数据类型,在这个函数中用于接收和处理传递给Serial_Printf
函数的可变参数。
-
3. 可变参数处理
-
va_start(arg, format);
-
这一步初始化了可变参数列表。
va_start
宏用于在函数中开始处理可变参数。它告诉编译器从format
参数之后开始,将后续的可变参数收集到arg
变量中。
-
-
vsprintf(String, format, arg);
-
此函数用于将格式化字符串和可变参数组合并存储到
String
数组中。-
vsprintf
函数的第一个参数String
是目标字符数组,用于存储格式化后的字符串。 -
第二个参数
format
是格式化字符串,它定义了如何对可变参数进行格式化,类似于printf
函数中的格式化字符串(例如%d
表示整数,%f
表示浮点数等)。 -
第三个参数
arg
是包含可变参数的变量,vsprintf
函数会根据format
的格式要求从arg
中取出参数进行格式化处理,并将结果存储到String
数组中。
-
-
-
va_end(arg);
-
在处理完可变参数后,
va_end
宏用于结束对可变参数的处理。这是一个必要的步骤,用于确保程序的正确运行。
-
4. 字符串发送
-
Serial_SendString(String);
-
调用
Serial_SendString
函数将存储在String
数组中的格式化字符串通过串口发送出去。Serial_SendString
函数是用于逐个字符地将字符串通过 STM32 的串口发送给外部设备,实现了将格式化后的信息输出到串口的功能。
-
/**
* 函 数:自己封装的prinf函数
* 参 数:format 格式化字符串
* 参 数:... 可变的参数列表
* 返 回 值:无
*/
void Serial_Printf(char *format, ...)
{
char String[100]; //定义字符数组
va_list arg; //定义可变参数列表数据类型的变量arg
va_start(arg, format); //从format开始,接收参数列表到arg变量
vsprintf(String, format, arg); //使用vsprintf打印格式化字符串和参数列表到字符数组中
va_end(arg); //结束变量arg
Serial_SendString(String); //串口发送字符数组(字符串)
}
-
显示汉字的操作方法
1. 汉字编码格式,选的是UTF8,所以最终发送到串口,汉字会以UTF8的方式编码,最终串口助手,也得选择UTF8,才能解码正确。
2. 如何实现UTF8不乱码:
打开工程选项(魔术棒),C/C++,这里杂项控制栏(Misc Controls),写上--no-multibyte-chars,需要给编译器输入一个这样的参数,注意别写错了,此时在程序中输入汉字就不会报错
但有时候UTF8有些软件兼容性不好,所以可以选择GB2312编码。
3. 打开配置(扳手),Encoding选择GB2312,在串口助手这里,选择GBK编码,般Windows软件默认就是GBK的编码,GBK和GB2312一样,都是中文的编码,基本都是兼容的。
4. 总结:要么Keil和串口助手都选择UTF8,且Keil加上--no-multibyte-chars参数,要么都使用GB开头的中文编码格式,参数不用加的。
-
主函数代码的详解
-
头文件包含
-
stm32f10x.h
:这是 STM32F10x 系列芯片的标准头文件,包含了芯片相关的寄存器定义、数据类型定义等基础信息,是进行 STM32F10x 开发的基础。 -
Delay.h
:应该是自定义的延时函数头文件,在程序中可能用于各种需要延时操作的地方,例如在 OLED 显示操作、串口数据发送操作之间可能需要延时来确保操作的正确性。 -
OLED.h
:OLED 相关的头文件,可能包含了 OLED 初始化函数、显示函数等的声明,用于控制 OLED 显示屏进行数据显示。 -
Serial.h
:串口相关的头文件,包含了串口初始化、数据发送等函数的声明,用于实现串口通信操作。
-
main
函数中的操作
1. 模块初始化
OLED_Init()
:调用 OLED 初始化函数,根据OLED.h
中的声明,这个函数用于初始化 OLED 显示屏。可能会配置 OLED 的相关引脚(如数据线、时钟线等)为合适的模式,设置 OLED 的显示模式、初始化显示参数等,为后续的 OLED 显示操作做准备。-
Serial_Init()
:调用串口初始化函数,依据Serial.h
中的声明,该函数用于初始化 STM32 的串口。这可能涉及开启串口和相关 GPIO 的时钟,配置串口的波特率、数据位、停止位、奇偶校验位等参数,以及配置串口对应的 GPIO 引脚为合适的输入 / 输出模式(例如,将发送引脚配置为复用推挽输出模式)。
2. 串口基本函数操作
-
调用
Serial_SendByte
函数发送一个字节的数据0x41
(十六进制,对应的 ASCII 码字符是 'A')。这个函数可能会将数据写入串口的数据寄存器,然后等待数据发送完成(通过检查相关的发送标志位),确保数据通过串口正确发送出去。 -
首先定义了一个
uint8_t
类型(8 位无符号整数)的数组MyArray
,包含了 4 个元素0x42
('B')、0x43
('C')、0x44
('D')、0x45
('E')。 -
然后调用
Serial_SendArray
函数发送这个数组。该函数可能通过循环调用Serial_SendByte
函数来逐个发送数组中的元素,确保数组中的数据通过串口依次发送 -
调用
Serial_SendString
函数发送字符串"\r\nNum1="
。这个函数可能会逐个字符地将字符串中的字符通过串口发送出去,其中\r
是回车符,\n
是换行符,用于在接收端(例如终端软件)显示时实现换行操作。 - 调用
Serial_SendNumber
函数发送数字111
,且指定数字的长度为3
。该函数可能会将数字按位拆分,然后将每一位数字转换为对应的字符(通过加上'0'
的 ASCII 码值),再通过调用Serial_SendByte
函数逐个发送这些数字字符,确保数字以字符形式通过串口发送出去。
3. 实现printf
效果的三种方法
方法 1:直接重定向printf
-
直接使用
printf
函数来发送格式化字符串"\r\nNum2=%d"
,其中%d
是格式控制符,表示要输出一个十进制整数,后面的222
就是要输出的整数。 -
这种方法需要重定向
fputc
函数,因为在嵌入式系统中,printf
函数默认是将数据输出到标准输出设备(如控制台),而在 STM32 中,需要将其输出重定向到串口。同时,需要在工程选项里勾选Use MicroLIB
,这是因为重定向printf
函数在一些情况下依赖于微库(MicroLIB)来实现。
方法 2:使用sprintf
打印到字符数组,再用串口发送字符数组
-
首先定义了一个长度为
100
的字符数组String
。 -
然后使用
sprintf
函数将格式化字符串"\r\nNum3=%d"
(其中%d
由333
填充)打印到字符数组String
中。sprintf
函数类似于printf
,但它是将格式化后的字符串存储到指定的字符数组中,而不是输出到标准输出设备。 -
最后调用
Serial_SendString
函数将存储在字符数组中的字符串通过串口发送出去。
方法 3:将sprintf
函数封装起来,实现专用的printf
-
调用自定义的
Serial_Printf
函数来发送格式化字符串"\r\nNum4=%d"
(其中%d
由444
填充)。这个函数是对sprintf
和串口发送操作的封装。 -
接着再次调用
Serial_Printf
函数发送一个换行符"\r\n"
,确保在接收端显示时换行。
4. 主循环
-
主循环中没有其他操作,程序会一直在这里循环等待。在实际应用中,如果有其他需要实时处理的任务(如接收串口数据、处理传感器数据等),可以在这个循环中添加相应的代码。
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
Serial_Init(); //串口初始化
/*串口基本函数*/
Serial_SendByte(0x41); //串口发送一个字节数据0x41
uint8_t MyArray[] = {0x42, 0x43, 0x44, 0x45}; //定义数组
Serial_SendArray(MyArray, 4); //串口发送一个数组
Serial_SendString("\r\nNum1="); //串口发送字符串
Serial_SendNumber(111, 3); //串口发送数字
/*下述3种方法可实现printf的效果*/
/*方法1:直接重定向printf,但printf函数只有一个,此方法不能在多处使用*/
printf("\r\nNum2=%d", 222); //串口发送printf打印的格式化字符串
//需要重定向fputc函数,并在工程选项里勾选Use MicroLIB
/*方法2:使用sprintf打印到字符数组,再用串口发送字符数组,此方法打印到字符数组,之后想怎么处理都可以,可在多处使用*/
char String[100]; //定义字符数组
sprintf(String, "\r\nNum3=%d", 333);//使用sprintf,把格式化字符串打印到字符数组
Serial_SendString(String); //串口发送字符数组(字符串)
/*方法3:将sprintf函数封装起来,实现专用的printf,此方法就是把方法2封装起来,更加简洁实用,可在多处使用*/
Serial_Printf("\r\nNum4=%d", 444); //串口打印字符串,使用自己封装的函数实现printf的效果
Serial_Printf("\r\n");
while (1)
{
}
}
-
串口发送+接收
复制“9-1 串口发送”工程,再次工程基础上更改即可得到“9-2 串口发送+接收”
对于串口接受数据有查询和中断两种方式。
1. 初始化RX对应的GPIO引脚
-
参数含义
-
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU
:将 GPIO 的模式设置为上拉输入模式(Input Pull - Up)。在这种模式下,引脚默认处于高电平状态,当外部设备将引脚拉低时,STM32 可以检测到低电平输入。这种模式常用于连接外部按键等输入设备,防止引脚悬空导致的不确定状态。(这里的输入模式部分,不分什么普通输入、复用输入。一根线只能有一个输出到那时可以有多个输入,所以对于输入脚外设和GPIO都可以同时用,一般配置为浮空输入或者上拉输入。因为串口波形空闲状态是高电平,所以不使用下拉输入) -
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10
:指定要配置的引脚为 GPIOA 的第 10 引脚(PA10)。 -
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz
:设置引脚的输出速度为 50MHz。虽然这里是输入模式,但这个参数在某些情况下仍可能影响输入信号的采样等操作。
-
-
初始化操作
-
GPIO_Init(GPIOA, &GPIO_InitStructure);
:通过这个函数将上述配置参数应用到 GPIOA 的 PA10 引脚,完成引脚的初始化,使其成为上拉输入模式。
-
2. 更改模式参数
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //模式,发送模式和接收模式均选择
3. 串口接收中断配置
-
功能
-
用于开启 USART1 的接收数据寄存器非空(RXNE)中断。当 USART1 接收到数据并且数据存储到接收数据寄存器后,会触发这个中断。这使得 STM32 能够及时响应串口接收到的数据,进行后续处理。
-
/*中断输出配置*/
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //开启串口接收数据的中断
4. NVIC 中断分组配置
-
原理
-
NVIC(Nested Vectored Interrupt Controller,嵌套向量中断控制器)是 STM32 中用于管理中断优先级的模块。
NVIC_PriorityGroupConfig
函数用于配置中断优先级分组。这里将 NVIC 配置为分组 2。
-
-
分组 2 特点
-
在分组 2 模式下,中断优先级由抢占优先级和响应优先级组成。具体来说,高 4 位用于表示抢占优先级,低 4 位用于表示响应优先级。这种分组方式可以根据实际应用需求灵活地设置不同中断的优先级。
-
/*NVIC中断分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
5. NVIC 具体配置
-
结构体变量定义
-
NVIC_InitTypeDef NVIC_InitStructure;
:定义一个 NVIC 初始化结构体变量,用于存储 NVIC 的配置参数。
-
-
参数含义与配置
-
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn
:指定要配置的中断通道为 USART1 的中断。当 USART1 发生中断事件时(如接收数据中断),会通过这个通道向 NVIC 发送中断请求。 -
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE
:使能这个中断通道。只有当通道被使能时,对应的中断请求才能被 NVIC 处理。 -
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1
:设置这个中断通道的抢占优先级为 1。抢占优先级决定了当多个中断同时发生时,哪个中断能够先被处理。较高抢占优先级的中断可以打断正在执行的较低抢占优先级的中断服务程序。 -
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1
:设置这个中断通道的响应优先级为 1。响应优先级用于在抢占优先级相同的情况下,决定中断的响应顺序。
-
-
初始化操作
-
NVIC_Init(&NVIC_InitStructure);
:将配置好的结构体变量参数应用到 NVIC,完成对 USART1 中断在 NVIC 中的配置,确保 USART1 中断能够按照设定的优先级得到正确处理。
-
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //选择配置NVIC的USART1线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
6. 中断函数的编写
USART1_IRQHandler
函数
-
函数功能:
-
这是 USART1 的中断服务函数。当 USART1 发生接收数据中断等相关事件时,这个函数会被自动调用,用于处理接收数据等操作。
-
-
代码逻辑:
-
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
:首先,通过USART_GetITStatus
函数检查 USART1 的接收数据寄存器非空(RXNE
)中断标志位是否被设置。如果被设置,说明有数据已经到达 USART1 的数据接收寄存器,触发了接收中断。 -
Serial_RxData = USART_ReceiveData(USART1);
:当确认是接收中断后,使用USART_ReceiveData
函数从 USART1 的数据接收寄存器中读取接收到的数据,并将其存储到Serial_RxData
变量中。这个变量就是Serial_GetRxData
函数所返回的变量,用于保存接收到的数据。 -
Serial_RxFlag = 1;
:将Serial_RxFlag
变量设置为 1,表示已经接收到了数据。这个变量就是Serial_GetRxFlag
函数所检查的标志位。 -
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
:清除 USART1 的RXNE
标志位。虽然读取数据寄存器可能会自动清除此标志位,但为了确保万无一失,这里还是进行了清除操作。如果之前已经读取了数据寄存器,不执行此代码也可能不会产生问题,但加上这行代码可以保证标志位的正确处理。
-
/**
* 函 数:USART1中断函数
* 参 数:无
* 返 回 值:无
* 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
* 函数名为预留的指定名称,可以从启动文件复制
* 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
*/
void USART1_IRQHandler(void)
{
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) //判断是否是USART1的接收事件触发的中断
{
Serial_RxData = USART_ReceiveData(USART1); //读取数据寄存器,存放在接收的数据变量
Serial_RxFlag = 1; //置接收标志位变量为1
USART_ClearITPendingBit(USART1, USART_IT_RXNE); //清除USART1的RXNE标志位
//读取数据寄存器会自动清除此标志位
//如果已经读取了数据寄存器,也可以不执行此代码
}
}
7. 中断函数中两个变量的封装函数
Serial_GetRxFlag
函数
-
函数功能:
-
此函数用于获取串口接收标志位。其主要目的是让外部程序能够知道是否有数据已经被串口接收。
-
-
代码逻辑:
-
if (Serial_RxFlag == 1)
:这里检查一个名为Serial_RxFlag
的变量。这个变量应该是在其他地方定义的,用于表示串口接收状态。如果Serial_RxFlag
为 1,意味着有数据已经被接收。 -
Serial_RxFlag = 0; return 1;
:当发现Serial_RxFlag
为 1 时,先将其清零,然后返回 1,表示有数据被接收过。这样做的好处是,外部程序可以通过调用这个函数来获取接收状态,并且每次获取后标志位会自动重置,方便下次检测。 -
return 0;
:如果Serial_RxFlag
为 0,直接返回 0,表示没有接收到数据。
-
Serial_GetRxData
函数
-
函数功能:
-
该函数用于获取串口接收到的数据。
-
-
代码逻辑:
-
return Serial_RxData;
:简单地返回一个名为Serial_RxData
的变量。这个变量应该是在其他地方被赋值的,用于存储串口接收到的数据。
-
/**
* 函 数:获取串口接收标志位
* 参 数:无
* 返 回 值:串口接收标志位,范围:0~1,接收到数据后,标志位置1,读取后标志位自动清零
*/
uint8_t Serial_GetRxFlag(void)
{
if (Serial_RxFlag == 1) //如果标志位为1
{
Serial_RxFlag = 0;
return 1; //则返回1,并自动清零标志位
}
return 0; //如果标志位为0,则返回0
}
/**
* 函 数:获取串口接收的数据
* 参 数:无
* 返 回 值:接收的数据,范围:0~255
*/
uint8_t Serial_GetRxData(void)
{
return Serial_RxData; //返回接收的数据变量
}
8. 主函数的编写
1. 头文件包含
-
stm32f10x.h
:这是 STM32F10x 系列芯片的标准头文件,包含了芯片相关的寄存器定义、数据类型定义等基础信息,是进行 STM32F10x 开发的基础。 -
Delay.h
:自定义的延时函数头文件,在程序中可能用于 OLED 显示操作、串口数据发送操作等需要延时的地方,以确保操作的正确性。 -
OLED.h
:OLED 相关的头文件,可能包含了 OLED 初始化函数、显示函数等的声明,用于控制 OLED 显示屏进行数据显示。 -
Serial.h
:串口相关的头文件,包含了串口初始化、数据发送和接收相关函数的声明,用于实现串口通信操作。
2. 变量定义
-
定义了一个 8 位无符号整数变量
RxData
,用于存储从串口接收到的数据。
3. main
函数操作
3.1 模块初始化
-
调用
OLED_Init
函数对 OLED 显示屏进行初始化。根据视频中相关章节(如 [4 - 2] OLED 显示屏)的内容,这个初始化过程可能包括配置 OLED 的相关引脚(如数据线、时钟线等)为合适的模式,设置 OLED 的显示模式、初始化显示参数等,为后续的 OLED 显示操作做准备。
3.2 显示静态字符串
-
使用
OLED_ShowString
函数在 OLED 显示屏的第 1 行第 1 列开始显示字符串"RxData:"
。这是为了后续显示接收到的串口数据做一个标识。
3.3 串口初始化
-
调用
Serial_Init
函数对串口进行初始化。结合视频中 [9 - 3] 串口发送 & 串口发送 + 接收相关内容,这个初始化过程可能涉及开启串口和相关 GPIO 的时钟,配置串口的波特率、数据位、停止位、奇偶校验位等参数,以及配置串口对应的 GPIO 引脚为合适的输入 / 输出模式。
3.4 主循环操作
-
接收数据标志位检查:
-
if (Serial_GetRxFlag() == 1)
:通过调用Serial_GetRxFlag
函数来检查是否有串口数据被接收。如果函数返回 1,表示有数据被接收。
-
-
获取接收数据:
-
RxData = Serial_GetRxData();
:当检测到有数据被接收后,调用Serial_GetRxData
函数获取接收到的数据,并将其存储到RxData
变量中。
-
-
数据回传与显示:
-
Serial_SendByte(RxData);
:将接收到的数据通过串口回传回去。这一步可能用于测试串口的接收和发送功能是否正常。 -
OLED_ShowHexNum(1, 8, RxData, 2);
:在 OLED 显示屏的第 1 行第 8 列开始,以十六进制形式显示接收到的数据,且数据宽度为 2 个字符。这样可以直观地看到接收到的串口数据内容。
-
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
uint8_t RxData; //定义用于接收串口数据的变量
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
/*显示静态字符串*/
OLED_ShowString(1, 1, "RxData:");
/*串口初始化*/
Serial_Init(); //串口初始化
while (1)
{
if (Serial_GetRxFlag() == 1) //检查串口接收数据的标志位
{
RxData = Serial_GetRxData(); //获取串口接收的数据
Serial_SendByte(RxData); //串口将收到的数据回传回去,用于测试
OLED_ShowHexNum(1, 8, RxData, 2); //显示串口接收的数据
}
}
}