STM32F103教程——UART串口通信

 串口简介


 数据通信的基础概念


在单片机的应用中,数据通信是必不可少的一部分,比如:单片机和上位机、单片机和外围器件之间,它们都有数据通信的需求。由于设备之间的电气特性、传输速率、可靠性要求各不相同,于是就有了各种通信类型、通信协议,我们最常的有:USART、IIC、SPI、CAN、USB等。下面,我们先来学习数据通信的一些基础概念。


1.  数据通信方式


按数据通信方式分类,可分为串行通信和并行通信两种。串行和并行的对比如下图所示:


数据传输方式


串行通信的基本特征是数据逐位顺序依次传输,优点是传输线少、布线成本低、灵活度高等优点,一般用于近距离人机交互,特殊处理后也可以用于远距离,缺点就是传输速率低。而并行通信是数据各位可以通过多条线同时传输,优点是传输速率高,缺点就是布线成本高,抗干扰能力差因而适用于短距离、高速率的通信。


数据传输方向


根据数据传输方向,通信又可分为全双工、半双工和单工通信。全双工、半双工和单工通信的比如下图所示:


数据传输方式

单工是指数据传输仅能沿一个方向,不能实现反方向传输,如校园广播。半双工是指数据传输可以沿着两个方向,但是需要分时进行,如对讲机。全双工是指数据可以同时进行双向传输,日常的打电话属于这种情形。这里注意全双工和半双工通信的区别:半双工通信是共用一条线路实现双向通信,而全双工是利用两条线路,一条用于发送数据,另一条用于接收数据。


数据同步方式


根据数据同步方式,通信又可分为同步通信和异步通信。同步通信和异步通信比较如下图所示:


数据同步方式


同步通信要求通信双方共用同一时钟信号,在总线上保持统一的时序和周期完成信息传输。优点:可以实现高速率、大容量的数据传输,以及点对多点传输。缺点:要求发送时钟和接收时钟保持严格同步,收发双方时钟允许的误差较小,同时硬件复杂。异步通信不需要时钟信号,而是在数据信号中加入开始位和停止位等一些同步信号,以便使接收端能够正确地将每一个字符接收下来,某些通信中还需要双方约定传输速率。优点:没有时钟信号硬件简单,双方时钟可允许一定误差。缺点:通信速率较低,只适用点对点传输。


通信速率


在数字通信系统中,通信速率(传输速率)指数据在信道中传输的速度,它分为两种:传信率和传码率。传信率:每秒钟传输的信息量,即每秒钟传输的二进制位数,单位为 bit/s(即比特每秒),
因而又称为比特率。传码率:每秒钟传输的码元个数,单位为 Baud(即波特每秒),因而又称为波特率。比特率和波特率这两个概念又常常被人们混淆。比特率很好理解,我们来看看波特率,波
特率被传输的是码元,码元是信号被调制后的概念,每个码元都可以表示一定 bit 的数据信息量。举个例子,在 TTL 电平标准的通信中,用 0V 表示逻辑 0,5V 表示逻辑 1,这时候这个码元就可以表示两种状态。如果电平信号 0V、2V、4V 和 6V 分别表示二进制数 00、01、10、11,这时候每一个码元就可以表示四种状态。由上述可以看出,码元携带一定的比特信息,所以比特率和波特率也是有一定的关系的。
比特率和波特率的关系可以用以下式子表示:


比特率 = 波特率 * log 2 M


其中 M 表示码元承载的信息量。我们也可以理解 M 为码元的进制数。
举个例子:波特率为 100 Baud,即每秒传输 100 个码元,如果码元采用十六进制编码(即M=2,代入上述式子),那么这时候的比特率就是 400 bit/s。如果码元采用二进制编码(即 M=2,代入上述式子),那么这时候的比特率就是 100 bit/s。
可以看出采用二进制的时候,波特率和比特率数值上相等。但是这里要注意,它们的相等只是数值相等,其意义上不同,看波特率和波特率单位就知道。由于我们的所用的数字系统都是二进制的,所以有部分人久而久之就直接把波特率和比特率混淆了。

串口通信协议简介


串口通信是一种设备间常用的串行通信方式,串口按位(bit)发送和接收字节。尽管比特字节(byte)的串行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。串口通信协议是指规定了数据包的内容,内容包含了起始位、主体数据、校验位及停止位,双方需要约定一致的数据包格式才能正常收发数据的有关规范。在串口通信中,常用的协议包括RS-232、RS-422 和 RS-485 等。
随着科技的发展,RS-232 在工业上还有广泛的使用,但是在商业技术上,已经慢慢的使用USB 转串口取代了 RS-232 串口。我们只需要在电路中添加一个 USB 转串口芯片,就可以实现USB 通信协议和标准 UART 串行通信协议的转换,而我们开发板上的 USB 转串口芯片是CH340C 这个芯片。关于 USB 转串口芯片的原理图请看 17.2 小节。下面我们来学习串口通信协议,这里主要学习串口通信的协议层。
串口通信的数据包由发送设备的 TXD 接口传输到接收设备的 RXD 接口。在串口通信的协议层中,规定了数据包的内容,它由起始位、主体数据、校验位以及停止位组成,通讯双方的数据包格式要约定一致才能正常收发数据,其组成如图 所示。

1.  波特率


本章主要讲解的是串口异步通信,异步通信是不需要时钟信号的,但是这里需要我们约定好两个设备的波特率。波特率表示每秒钟传送的码元符号的个数,所以它决定了数据帧里面每一个位的时间长度。两个要通信的设备的波特率一定要设置相同,我们常见的波特率是 4800、9600、115200 等。


2.  数据帧格式


数据帧格式需要我们提前约定好,串口通信的数据帧包括起始位、停止位、有效数据位以及校验位。
⚫  起始位和停止位
串口通信的一个数据帧是从起始位开始,直到停止位。数据帧中的起始位是由一个逻辑 0的数据位表示,而数据帧的停止位可以是 0.5、1、1.5 或 2 个逻辑 1 的数据位表示,只要双方约定一致即可。
⚫  有效数据位
数据帧的起始位之后,就接着是数据位,也称有效数据位,这就是我们真正需要的数据,有效数据位通常会被约定为 5、6、7 或者 8 个位长。有效数据位是低位(LSB)在前,高位(MSB)在后。
⚫  校验位
校验位可以认为是一个特殊的数据位。校验位一般用来判断接收的数据位有无错误,检验方法有:奇检验、偶检验、0 检验、1 检验以及无检验。下面分别介绍一下:奇校验是指有效数据为和校验位中“1”的个数为奇数,比如一个 8 位长的有效数据为:10101001,总共有 4 个“1”,为达到奇校验效果,校验位设置为“1”,最后传输的数据是 8 位的有效数据加上 1 位的校验位总共 9 位。偶校验与奇校验要求刚好相反,要求帧数据和校验位中“1”的个数为偶数,比如数据帧:11001010,此时数据帧“1”的个数为 4 个,所以偶校验位为“0”。0  校验是指不管有效数据中的内容是什么,校验位总为“0”,1  校验是校验位总为“1”。无校验是指数据帧中不包含校验位。我们一般是使用无校验的情况。

STM32F1  的串口简介


STM32F103 的串口资源相当丰富,功能也相当强劲。STM32F103ZET6 最多可提供 5 路串口,有分数波特率发生器、支持同步单线通信和半双工单线通讯、支持 LIN、支持调制解调器操作、智能卡协议和 IrDA SIR ENDEC 规范、具有 DMA 等。
STM32F1 的串口分为两种:USART(即通用同步异步收发器)和 UART(即通用异步收发器)。UART 是在 USART 基础上裁剪掉了同步通信功能,只剩下异步通信功能。简单区分同步和异步就是看通信时需不需要对外提供时钟输出,我们平时用串口通信基本都是异步通信。
STM32F1 有 3 个 USART 和 2 个 UART,其中 USART1 的时钟源来于 APB2 时钟,其最大频率为 72MHz,其他 4 个串口的时钟源可以来于 APB1 时钟,其最大频率为 36MHz。STM32 的串口输出的是 TTL 电平信号,如果需要 RS-232 标准的信号可使用 MAX3232 芯片进行转换,而本实验我们是通过 USB 转串口芯片 CH340C 来与电脑的上位机进行通信。


USART  框图


下面先来学习如图所示的 USART 框图,通过 USART 框图引出 USART 的相关知识,从而有了一个很好的整体掌握,对之后的编程也会有一个清晰的思路。

我们把整个框图分成几个部分来介绍。
① USART  信号引脚
TX:发送数据输出引脚
RX:接收数据输入引脚
SCLK:发送器时钟输出,适用于同步传输
SW_RX:数据接收引脚,属于内部引脚,用于智能卡模式
IrDA_RDI:IrDA 模式下的数据输入
IrDA_TDO:IrDA 模式下的数据输出
nRTS:发送请求,若是低电平,表示 USART 准备好接收数据
nCTS:清除发送,若是高电平,在当前数据传输结束时阻断下一次的数据发送
② 数据寄存器
USART_DR 包含了已发送或接收到的数据。由于它本身就是两个寄存器组成的,一个专门给发送用的(TDR),一个专门给接收用的(RDR),该寄存器具备读和写的功能。TDR 寄存器提供了内部总线和输出移位寄存器之间的并行接口。RDR 寄存器提供了输入移位寄存器和内部总线之间的并行接口。当进行数据发送操作时,往 USART_DR 中写入数据会自动存储在 TDR内;当进行读取操作时,向 USART_DR 读取数据会自动提去 RDR 数据。
USART 数据寄存器(USART_DR)低 9 位数据有效,其他数据位保留。USART_DR 的第9 位数据是否有效跟 USART_CR1 的 M 位设置有关,当 M 位为 0 表示 8 位数据字长;当 M 位为 1 时表示 9 位数据字长,一般使用 8 位数据字长。当使能校验位(USART_CR1 中 PCE 位被置位)进行发送时,写到 MSB 的值(根据数据的长度不同,MSB 是第 7 位或者第 8 位)会被后来的校验位取代。
③ 控制器
USART 有专门控制发送的发送器,控制接收的接收器,还有唤醒单元、中断控制等等,具体在后面讲解 USART 寄存器的时候细讲。
④时钟与波特率
这部分的主要功能就是为 USART 提供时钟以及配置波特率。波特率,即每秒钟传输的码元个数,在二进制系统中(串口的数据帧就是二进制的形式),波特率与波特率的数值相等,所以我们今后在把串口波特率理解为每秒钟传输的二进制位数。波特率通过以下公式得出:


𝑐𝑏𝑣𝑒 =𝑔𝑑𝑙/16 ∗ USARTDIV


fck 是给串口的时钟(USART2\3\3\4\5 的时钟源为 PCLK1,USART1 的时钟源为 PCLK2),
USARTDIV 是一个无符号的定点数,存放在波特率寄存器(USART_BRR)的低 16 位DIV_Man-
tissa[11:0]存放的是 USARTDIV 的整数部分,DIV_Fractionp[3:0]存放的是 USARTDIV 的小数部
分。
下面举个例子说明:
当串口 1 设置需要得到 115200 的波特率,fck = 72MHz,那么可得:


115200 =72000000/16 ∗ USARTDIV


得到 USARTDIV = 39.0625,分离 USARTDIV 的整数部分与小数部分,整数部分为 39,即
0x27,那么 DIV_Mantissa = 0x27;小数部分为 0.0625,转化为十六进制即 0.0625*16 = 1,所以DIV_Fractionp = 0x1,USART_BRR 寄存器应该赋值为 0x271,成功设置波特率为 115200。值得注意 USARTDIV 是允许有余数的,我们用四舍五入进行取整,这样会导致波特率会有所偏差,而这样的小误差是可以被允许的。


USART  寄存器


使用 STM32F103 的 USART 的配置步骤在《STM32F10xxx 参考手册_V10(中文版).pdf》中有列出,这里我们引用手册中的配置步骤:
1. 通过在 USART_CR1 寄存器上置位 UE 位来激活 USART。
2. 编程 USART_CR1 的 M 位来定义字长。

3. 在 USART_CR2 中编程停止位的位数。
4. 如果采用多缓冲器通信,配置 USART_CR3 中的 DMA 使能位(DMAT)。按多缓冲器通信中的描述配置 DMA 寄存器。
5. 利用 USART_BRR 寄存器选择要求的波特率。
6. 设置 USART_CR1 中的 TE 位,发送一个空闲帧作为第一次数据发送。
7. 把要发送的数据写进 USART_DR 寄存器(此动作清除 TXE 位)。在只有一个缓冲器的情况下,对每个待发送的数据重复步骤 7。
8. 在 USART_DR 寄存器中写入最后一个数据字后,要等待 TC=1,它表示最后一个数据帧的传输结束。当需要关闭 USART 或需要进入停机模式之前,需要确认传输结束,避免破坏最后一次传输。我们按照上面的步骤配置就可以使用 STM32F103 的串口了:只要你开启了串口时钟,并设置相应 IO 口的模式,然后配置一下波特率,数据位长度,奇偶校验位等信息,就可以使用了。总结地来说,我们要学会配置 USART 对应的寄存器就可以使用串口功能了。下面,我们就简单介绍下这几个与串口基本配置直接相关的寄存器。
( (1 )串口时钟使能
串口作为 STM32F103 的一个外设,其时钟由外设时钟使能寄存器控制,这里我们使用的串口 1 是在 APB2ENR 寄存器的第 14 位。
注意:除了串口 1 的时钟使能在 APB2ENR 寄存器,其他串口的时钟使能位都在 APB1ENR寄存器,而 APB2(72M)的频率一般是 APB1(36M)的一倍。
( (2 )串口复位
一般系统刚开始配置外设的时候,都会先执行复位该外设的操作,可以使外设的对应寄存器恢复到默认值,方便我们进行配置。串口 1 的复位就是通过配置 APB2RSTR 寄存器的第 14位来实现的。APB2RSTR 寄存器的描述如图 17.1.3.2.1 所示:

( (3 )串口波特率设置
每个串口都有一个自己独立的波特率寄存器 USART_BRR,通过设置该寄存器就可以达到配置不同波特率的目的。在前面的 USART 框图部分描述过,为了让大家更好了解波特率寄存器,下面截取 USART_BRR 寄存器图,如图 17.1.3.2.2 所示:

 (4 )串口控制
STM32F103 每个串口都有 3 个控制寄存器 USART_CR1~3,串口的很多配置都是通过这 3个寄存器来设置的。USART_CR1 寄存器的描述如图 17.1.3.2.3 所示:

该寄存器的高 18 位没有用到,低 14 位用于串口的功能设置。我们在这里只介绍需要用到的一些位,其他位可以参考《STM32F10xxx 参考手册_V10(中文版).pdf》。UE 为串口使能位,通过该位置 1,使能串口。M 为字长,当该位为 0 的时候设置串口为 8 个字长外加 n 个停止位,停止位的个数(n)是根据 USART_CR2 的[13:12]位设置来决定的,默认为 0。PCE 为校验使能位,设置为 0,即禁止校验,否则使能校验。PS 为校验位选择,设置为 0 为偶校验,否则奇校验。TXIE 为发送缓冲区空中断使能位,设置该位为 1,当 USART_SR 中的 TXE 位为 1 时,将产生串口中断。TCIE 为发送完成中断使能位,设置该位为 1,当 USART_SR 中的 TC 位为 1时,将产生串口中断。RXNEIE 为接收缓冲区非空中断使能,设置该位为 1,当 USART_SR 中的 ORE 或者 RXNE 位为 1 时,将产生串口中断。TE 为发送使能位,设置为 1,将开启串口的发送功能。RE 为接收使能位,用法同 TE。
( (5 )数据发送与接收
STM32 的发送与接收是通过数据寄存器 USART_DR 来实现的,这是一个双寄存器,包含了 TDR 和 RDR。当向该寄存器写数据的时候,串口就会自动发送,当收到数据的时候,也是存在该寄存器内。在前面的 USART 框图已经对 USART_DR 有详细的介绍,大家可以自行查阅。下面看一下寄存器的各位描述如图

 (6 )串口状态
串口状态通过状态寄存器 USART_SR 读取。USART_SR 的各位描述如图 17.1.3.2.5 所示:

这里我们关注一下两个位,第 5、6 位 RXNE 和 TC。
RXNE(读数据寄存器非空),当该位被置 1 的时候,就是提示已经有数据被接收到了,并且可以读出来了。这时候我们要做的就是尽快去读取 USART_DR,通过读 USART_DR 可以将该位清零,也可以向该位写 0,直接清除。
TC(发送完成),当该位被置位的时候,表示 USART_DR 内的数据已经被发送完成了。如果置了这个位的中断,则会产生中断。该位也有两种清零方式:
1)读 USART_SR,写 USART_DR。
2)直接向该位写 0
通过以上一些寄存器的操作再加上 IO 口的配置,我们就可以达到串口最基本的配置了,关于串口更详细的介绍,请参考《STM32F10xxx 参考手册_V10(中文版).pdf》关于通用同步异步收发器这一章的相关知识。

GPIO  引脚复用功能


我们知道芯片有许多外设,而引脚的资源是很有限的,为了解决这个问题,方法就是引脚复用,这样使得引脚除了作为普通的 IO 口之外,还会与一些外设关联起来,作为第二功能使用,而且一个引脚不单单只有一种复用功能,而是拥有多个第二功能,但是一次只允许一个外设的复用功能,以确保共用同一个 IO 引脚的外设之间不会产生冲突。下面我们把之前没讲解的复用功能寄存器 AFIO 讲解一下。
AFIO 寄存器的作用就是复用功能 I/O 和调试配置的,STM32F103ZET6 共有 6 个 AFIO 的寄存器,事件控制寄存器 AFIO_EVCR、复用重映射和调试 I/O 配置寄存器 AFIO_MAPR、外部中断配置寄存器 AFIO_EXTICR1、外部中断配置寄存器 AFIO_EXTICR2、外部中断配置寄存器
AFIO_EXTICR3 和外部中断配置寄存器 AFIO_EXTICR4。在对这些寄存器进行读写操作前,应先打开 AFIO 时钟,该时钟在 RCC_APB2ENR 寄存器上的位 0 上配置,在位 0 上置 0 表示辅助功能 IO 时钟关闭;在位 0 上置 1 表示辅助功能 IO 时钟开启。
事件控制寄存器 AFIO_EVCR,用得比较少这里不作过多介绍。
AFIO_EXTICRx 寄存器,在中断章节再来详细讲解,本章节不涉及这些寄存器的配置。复用重映射和调试 I/O 配置寄存器 AFIO_MAPR 寄存器描述,如图 17.1.4.1 所示。

在对 AFIO_MAPR 寄存器某些位进行写入实现引脚的重新映射,这时候,复用功能不再映射到它们原始分配上。例如 AFIO_MAPR 寄存器位 2 是对 USART1 的重映射,置 0: 没有重映(TX/PA9,RX/PA10); 置 1: 重映像(TX/PB6,RX/PB7)。默认情况下,PA9 和 PA10 是作为串口 1 的引脚使用,假如 PA9 和 PA10 被用作其他地方,但还是需要用到串口 1,那么就可以在 AFIO_MAPR 的位 2 置 1,把串口 1 的引脚重映射到 PB6 和 PB7。这个串口初始化的过程,就有点变化,需要初始化 AFIO 时钟,和对 AFIO_MAPR 的第 2 位进行置 1 操作,其他与普通串口配置没有区别。串口 通讯 配置步骤
1)  串口参数初始化(波特率 、字长、奇偶校验 等)
HAL 库通过调用串口初始化函数 HAL_UART_Init 完成对串口参数初始化,详见例程源码注意:该函数会调用:HAL_UART_MspInit 函数来完成对串口底层的初始化,包括:串口及 GPIO 时钟使能、GPIO 模式设置、中断设置等。
2 )使能串口和 GPIO  口时钟
本实验用到 USART1 串口,使用 PA9 和 PA10 作为串口的 TX 和 RX 脚,因此需要先使能
USART1 和 GPIOA 时钟。参考代码如下:


__HAL_RCC_USART1_CLK_ENABLE(); /* 使能 USART1 时钟 */
__HAL_RCC_GPIOA_CLK_ENABLE(); /* 使能 GPIOA 时钟 */


3 )GPIO  模式 设置(速度 、 上下拉 、复用功能 等) )
GPIO 模式设置通过调用 HAL_GPIO_Init 函数实现,详见本例程源码。
4 )开启串口相关中断,配置串口中断优先级
本实验我们使用串口中断来接收数据。我们使用 HAL_UART_Receive_IT 函数开启串口中断接收,并设置接收 buffer 及其长度。通过 HAL_NVIC_EnableIRQ 函数使能串口中断,通过HAL_NVIC_SetPriority 函数设置中断优先级。
5 )编写中断服务函数
串口 1 中断服务函数为:USART1_IRQHandler,当发生中断的时候,程序就会执行中断服务函数。HAL 库为了使用方便,提供了一个串口中断通用处理函数 HAL_UART_IRQHandler,该函数在串口接收完数据后,又会调用回调函数 HAL_UART_RxCpltCallback ,用于给用户处理串口接收到的数据。因此我们需要在 HAL_UART_RxCpltCallback 函数实现数据接收处理,详见本例程源码。
6 )串口数据接收和发送
最后我们可以通过读写 USART_DR 寄存器,完成串口数据的接收和发送,HAL 库也给我们提供了:HAL_UART_Receive 和 HAL_UART_Transmit 两个函数用于串口数据的接收和发送。大家可以根据实际情况选择使用以上介绍的方式来收发串口数据。

业务代码如下,基于STM32_HAL库编写

main.c

int main(void)
{
    uint8_t len;
    uint16_t times = 0;

    HAL_Init();                             /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);     /* 设置时钟为72Mhz */
    delay_init(72);                         /* 延时初始化 */
    usart_init(115200);                     /* 串口初始化为115200 */
    led_init();                             /* 初始化LED */
    while (1)
    {
        if (g_usart_rx_sta & 0x8000)        /* 接收到了数据? */
        {
            len = g_usart_rx_sta & 0x3fff;  /* 得到此次接收到的数据长度 */
            printf("\r\n您发送的消息为:\r\n");

            HAL_UART_Transmit(&g_uart1_handle,(uint8_t*)g_usart_rx_buf, len, 1000);    /* 发送接收到的数据 */
            while(__HAL_UART_GET_FLAG(&g_uart1_handle, UART_FLAG_TC) != SET);          /* 等待发送结束 */
            printf("\r\n\r\n");             /* 插入换行 */
            g_usart_rx_sta = 0;
        }
        else
        {
            times++;

            if (times % 5000 == 0)
            {
                printf("\r\n正点原子 STM32开发板 串口实验\r\n");
                printf("正点原子@ALIENTEK\r\n\r\n\r\n");
            }

            if (times % 200 == 0) printf("请输入数据,以回车键结束\r\n");

            if (times % 30  == 0) LED0_TOGGLE(); /* 闪烁LED,提示系统正在运行. */

            delay_ms(10);
        }
    }
}

uart.c

/******************************************************************************************/
/* 加入以下代码, 支持printf函数, 而不需要选择use MicroLIB */

#if 1

#if (__ARMCC_VERSION >= 6010050)            /* 使用AC6编译器时 */
__asm(".global __use_no_semihosting\n\t");  /* 声明不使用半主机模式 */
__asm(".global __ARM_use_no_argv \n\t");    /* AC6下需要声明main函数为无参数格式,否则部分例程可能出现半主机模式 */

#else
/* 使用AC5编译器时, 要在这里定义__FILE 和 不使用半主机模式 */
#pragma import(__use_no_semihosting)

struct __FILE
{
    int handle;
    /* Whatever you require here. If the only file you are using is */
    /* standard output using printf() for debugging, no file handling */
    /* is required. */
};

#endif

/* 不使用半主机模式,至少需要重定义_ttywrch\_sys_exit\_sys_command_string函数,以同时兼容AC6和AC5模式 */
int _ttywrch(int ch)
{
    ch = ch;
    return ch;
}

/* 定义_sys_exit()以避免使用半主机模式 */
void _sys_exit(int x)
{
    x = x;
}

char *_sys_command_string(char *cmd, int len)
{
    return NULL;
}


/* FILE 在 stdio.h里面定义. */
FILE __stdout;

/* MDK下需要重定义fputc函数, printf函数最终会通过调用fputc输出字符串到串口 */
int fputc(int ch, FILE *f)
{
    while ((USART_UX->SR & 0X40) == 0);     /* 等待上一个字符发送完成 */

    USART_UX->DR = (uint8_t)ch;             /* 将要发送的字符 ch 写入到DR寄存器 */
    return ch;
}
#endif
/******************************************************************************************/

#if USART_EN_RX /*如果使能了接收*/

/* 接收缓冲, 最大USART_REC_LEN个字节. */
uint8_t g_usart_rx_buf[USART_REC_LEN];

/*  接收状态
 *  bit15,      接收完成标志
 *  bit14,      接收到0x0d
 *  bit13~0,    接收到的有效字节数目
*/
uint16_t g_usart_rx_sta = 0;

uint8_t g_rx_buffer[RXBUFFERSIZE];  /* HAL库使用的串口接收缓冲 */

UART_HandleTypeDef g_uart1_handle;  /* UART句柄 */

/**
 * @brief       串口X初始化函数
 * @param       baudrate: 波特率, 根据自己需要设置波特率值
 * @note        注意: 必须设置正确的时钟源, 否则串口波特率就会设置异常.
 *              这里的USART的时钟源在sys_stm32_clock_init()函数中已经设置过了.
 * @retval      无
 */
void usart_init(uint32_t baudrate)
{
    /*UART 初始化设置*/
    g_uart1_handle.Instance = USART_UX;                                       /* USART_UX */
    g_uart1_handle.Init.BaudRate = baudrate;                                  /* 波特率 */
    g_uart1_handle.Init.WordLength = UART_WORDLENGTH_8B;                      /* 字长为8位数据格式 */
    g_uart1_handle.Init.StopBits = UART_STOPBITS_1;                           /* 一个停止位 */
    g_uart1_handle.Init.Parity = UART_PARITY_NONE;                            /* 无奇偶校验位 */
    g_uart1_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;                      /* 无硬件流控 */
    g_uart1_handle.Init.Mode = UART_MODE_TX_RX;                               /* 收发模式 */
    HAL_UART_Init(&g_uart1_handle);                                           /* HAL_UART_Init()会使能UART1 */

    /* 该函数会开启接收中断:标志位UART_IT_RXNE,并且设置接收缓冲以及接收缓冲接收最大数据量 */
    HAL_UART_Receive_IT(&g_uart1_handle, (uint8_t *)g_rx_buffer, RXBUFFERSIZE); 
}

/**
 * @brief       UART底层初始化函数
 * @param       huart: UART句柄类型指针
 * @note        此函数会被HAL_UART_Init()调用
 *              完成时钟使能,引脚配置,中断配置
 * @retval      无
 */
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
    GPIO_InitTypeDef gpio_init_struct;

    if (huart->Instance == USART_UX)                            /* 如果是串口1,进行串口1 MSP初始化 */
    {
        USART_TX_GPIO_CLK_ENABLE();                             /* 使能串口TX脚时钟 */
        USART_RX_GPIO_CLK_ENABLE();                             /* 使能串口RX脚时钟 */
        USART_UX_CLK_ENABLE();                                  /* 使能串口时钟 */

        gpio_init_struct.Pin = USART_TX_GPIO_PIN;               /* 串口发送引脚号 */
        gpio_init_struct.Mode = GPIO_MODE_AF_PP;                /* 复用推挽输出 */
        gpio_init_struct.Pull = GPIO_PULLUP;                    /* 上拉 */
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;          /* IO速度设置为高速 */
        HAL_GPIO_Init(USART_TX_GPIO_PORT, &gpio_init_struct);
                
        gpio_init_struct.Pin = USART_RX_GPIO_PIN;               /* 串口RX脚 模式设置 */
        gpio_init_struct.Mode = GPIO_MODE_AF_INPUT;    
        HAL_GPIO_Init(USART_RX_GPIO_PORT, &gpio_init_struct);   /* 串口RX脚 必须设置成输入模式 */
        
#if USART_EN_RX
        HAL_NVIC_EnableIRQ(USART_UX_IRQn);                      /* 使能USART1中断通道 */
        HAL_NVIC_SetPriority(USART_UX_IRQn, 3, 3);              /* 组2,最低优先级:抢占优先级3,子优先级3 */
#endif
    }
}

/**
 * @brief       串口数据接收回调函数
                数据处理在这里进行
 * @param       huart:串口句柄
 * @retval      无
 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART_UX)                    /* 如果是串口1 */
    {
        if ((g_usart_rx_sta & 0x8000) == 0)             /* 接收未完成 */
        {
            if (g_usart_rx_sta & 0x4000)                /* 接收到了0x0d(即回车键) */
            {
                if (g_rx_buffer[0] != 0x0a)             /* 接收到的不是0x0a(即不是换行键) */
                {
                    g_usart_rx_sta = 0;                 /* 接收错误,重新开始 */
                }
                else                                    /* 接收到的是0x0a(即换行键) */
                {
                    g_usart_rx_sta |= 0x8000;           /* 接收完成了 */
                }
            }
            else                                        /* 还没收到0X0d(即回车键) */
            {
                if (g_rx_buffer[0] == 0x0d)
                    g_usart_rx_sta |= 0x4000;
                else
                {
                    g_usart_rx_buf[g_usart_rx_sta & 0X3FFF] = g_rx_buffer[0];
                    g_usart_rx_sta++;

                    if (g_usart_rx_sta > (USART_REC_LEN - 1))
                    {
                        g_usart_rx_sta = 0;             /* 接收数据错误,重新开始接收 */
                    }
                }
            }
        }

        HAL_UART_Receive_IT(&g_uart1_handle, (uint8_t *)g_rx_buffer, RXBUFFERSIZE);
    }
}

/**
 * @brief       串口1中断服务函数
 * @param       无
 * @retval      无
 */
void USART_UX_IRQHandler(void)
{
#if SYS_SUPPORT_OS                          /* 使用OS */
    OSIntEnter();    
#endif

    HAL_UART_IRQHandler(&g_uart1_handle);   /* 调用HAL库中断处理公用函数 */

#if SYS_SUPPORT_OS                          /* 使用OS */
    OSIntExit();
#endif

}

#endif

 uart.h

/******************************************************************************************/
/* 引脚 和 串口 定义 
 * 默认是针对USART1的.
 * 注意: 通过修改这几个宏定义,可以支持USART1~UART5任意一个串口.
 */
#define USART_TX_GPIO_PORT                  GPIOA
#define USART_TX_GPIO_PIN                   GPIO_PIN_9
#define USART_TX_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)   /* PA口时钟使能 */

#define USART_RX_GPIO_PORT                  GPIOA
#define USART_RX_GPIO_PIN                   GPIO_PIN_10
#define USART_RX_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)   /* PA口时钟使能 */

#define USART_UX                            USART1
#define USART_UX_IRQn                       USART1_IRQn
#define USART_UX_IRQHandler                 USART1_IRQHandler
#define USART_UX_CLK_ENABLE()               do{ __HAL_RCC_USART1_CLK_ENABLE(); }while(0)  /* USART1 时钟使能 */

/******************************************************************************************/

#define USART_REC_LEN               200         /* 定义最大接收字节数 200 */
#define USART_EN_RX                 1           /* 使能(1)/禁止(0)串口1接收 */
#define RXBUFFERSIZE   1                        /* 缓存大小 */

extern UART_HandleTypeDef g_uart1_handle;       /* HAL UART句柄 */

extern uint8_t  g_usart_rx_buf[USART_REC_LEN];  /* 接收缓冲,最大USART_REC_LEN个字节.末字节为换行符 */
extern uint16_t g_usart_rx_sta;                 /* 接收状态标记 */
extern uint8_t g_rx_buffer[RXBUFFERSIZE];       /* HAL库USART接收Buffer */


void usart_init(uint32_t bound);                /* 串口初始化函数 */

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
STM32F103是意法半导体公司推出的32位Cortex-M3内核的微控制器系列,其中包含UART2通信功能。 UART(通用异步收发器)是一种常用的串行通信接口,用于将数据从一个设备传输到另一个设备。STM32F103系列的微控制器具有多个UART接口,其中之一是UART2。 UART2接口在STM32F103系列的不同芯片上可能会有一些差异,但基本原理相同。UART2通信主要涉及到以下几个要点: 1. 配置:首先,需要配置UART2的工作参数,如波特率、数据位数、校验位、停止位等。可以使用寄存器来进行配置。 2. 传输数据:可以使用UART2接口进行数据传输的两种方式——中断驱动和轮询模式。 - 中断驱动模式:启用UART2接收和发送中断,通过中断服务程序(ISR)来处理接收和发送数据。 - 轮询模式:通过检查状态寄存器,判断接收/发送缓冲区是否为空,然后相应地读取/写入数据。 3. 数据传输完成检测:在数据传输完成后,需要检测是否成功传输。可以通过读取状态寄存器中的标志位来判断传输是否完成。 4. 错误处理:在UART2通信中,可能会遇到一些错误,如串口帧错误、奇偶校验错误等。需要根据具体情况进行适当的处理。 需要注意的是,UART2通信涉及到多个寄存器和配置参数,因此在使用之前需要仔细阅读STM32F103的参考手册和相关资料,以确保正确配置和操作UART2接口。 综上所述,STM32F103UART2通信具有一定的配置和操作步骤,可以通过配置寄存器和相应的中断服务程序或轮询模式来实现数据的收发。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值