USART串口通信

前言

        串口通信就是将一个设备的数据传送到另一个设备,为了扩展硬件系统。在STM32中,里面集成了很多功能,例如AD采样、TIM定时器计数、PWM输出等功能。这些都是属于STM32芯片内部的功能,就相当于使用了对应的外设(例如PWM输出就是使用了TIM的输出比较功能),配置外设的寄存器都在芯片内。如果需要无法由STM32芯片实现的功能,例如蓝牙无线遥控功能、或者是陀螺仪测量加速度功能等,就需要对应的外挂上芯片,对应的外挂芯片回传数据的时候就需要通信来进行。

1.通信协议的介绍

        想要弄懂USART串口通信就需要先了解什么是通信协议

        通信的就是将一个设备的数据传送到另一个设备,扩展硬件系统,而通信协议:制定通信的规则通信双方按照协议规则进行数据收发。通信协议也就是指定通信的参数,例如双方波特率需要一致

        根据不同的通信协议参数配置(规则)就衍生出多种通信协议,其中常见的如下:

        可以看到,有USART、I2C、SPI、CAN、USB这五种常见的通信协议,对应的这五种的硬件电路也不同。从引脚名称就能看出来,该引脚只是该通信协议中最简单的部分引脚引出。

        接下来介绍这些通信协议的特征:

1.1双工

        然后是双工的分类,总共分为全双工、半双工以及单工(这里没有涉及到)

        全双工:一般有两根通信线,可以同时完成数据的发送和接收,互不影响,例如串口USART中的TX、RX(其他地方也叫做TXD、RXD)(Transmit Exchange、Receive Exchange)如下图:

        半双工:只有一根数据线,通过一根数据线完成数据的发送和接收操作,例如I2C中的SCL、SDA(Serial Clock、Serial Data),如下图:

        单工:也即只有一根通信线,只能单向传递数据,如下图:

1.2时钟(同步、异步)

        然后是时钟,决定同步还是异步,看到下图:

        其中,只有I2C和SPI属于同步的通信协议,也就是这两种通信协议通信时,是同步输入输出无延迟的,对应的都有一个同步时钟的引脚端口

        而异步,并没用来同步时钟的引脚端口,相对应的双方(设备1和设备2)需要规定一个采样频率,并且还需要添加一些帧头帧尾等来进行数据对齐操作。

1.3电平(单端、差分)

        电平分为单端和差分,单端也就是相对应GND的电压,例如TX、RX都是相对于GND的电压,对应的设备1和设备2的GND必须要连接到一起,这样才能够规定低电平GND来参考电压

        而差分,并不需要相对GND,而是两个引脚电压差即可。例如CAN通信,对应的电平输入、输出就是CNA_L和CAN_L的电压之差使用差分信号可以极大程度的克服干扰,所以一般差分信号一般会用于传输远距离的通信。

1.4设备(点对点、多设备)

        设备,分为点对点、多设备。其中点对点也就是一对一,一个设备对应一个设备。而多设备就是一个设备可以传输至多个设备之中,对应的在多设备通信协议,还需要有寻址(寻找对应设备)来进行传输操作

2.USART通信协议

        串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信

        单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大地扩展了单片机的应用范围,增强了单片机系统的硬件实力 。       

        下面为串口通信的一般结构图

        RX和TX是交叉连接的,设备1的TX发送到设备2的RX接收,设备1的RX接收到设备2的TX发送,这样就能实现最简单的串口通信

2.1电平标准

        在串口通信中,还需要注意电平标准,不同的电平标准之间不能正常地通信,需要通过相应的电平芯片来转换电平,进而实现通信的目的。

        电平标准是数据1和数据0(逻辑1和逻辑0)的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:

        TTL电平:+3.3V或+5V表示1,0V表示0

        RS232电平:-3~-15V表示1,+3~+15V表示0

        RS485电平:两线压差+2~+6V表示1,-2~-6V表示0(差分信号)

        在STM32单片机中一般使用的TTL电平,也就是低电压。而RS232电平3~15,一般用在电压较大的设备上。而RS485电平,传输的是差分信号,对应的应用在远距离传输的设备上。

2.2USART串口特征        

        USART(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步收发器,一般来说USART和USAT是一样的,只不过USART多了一个SCL时钟输出的功能(并不常用),在后续介绍结构会提到。

        USART是STM32内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可自动接收RX引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器里。

自带波特率发生器,最高达4.5Mbits/s

使用分数波特率发生器 —— 12位整数和4位小数的表示方法

可配置数据位长度(8/9)、停止位长度(0.5/1/1.5/2)

可选校验位(无校验/奇校验/偶校验)

支持同步模式、硬件流控制、DMA、智能卡、IrDA、LIN

STM32F103C8T6 USART资源: USART1、 USART2、 USART3

        上述为对USART串口外设的特征描述。

2.2.1波特率

        波特率就是每秒钟传输码元的个数,一般选择波特率为9600分数波特率发生器也就是通过选择分频系数,来产生对应的波特率,同时该参数分为12位正数和4位小数,这样就可以得到更为精确的波特率

        上图就为配置参数的寄存器BBR

        计算公式:

        根据不同的串口,选择不同的时钟,例如USART1就为APB2上的外设,此时f频率就为fPCLK2,同时上面的的计算公式在后续会提到如何得到的。

        发送器和接收器的波特率由波特率寄存器BRR里的DIV确定,例如此时需要产生9600的波特率,同时时钟为72MHz,待入计算公式,就可以得到一个带小数的数值。此时对该数值转换为16禁止,对应的就会产生小数部分,例如上述DIV结果为468.75,转换为二进制就为111010100.11,对应的就写入:

        换句话说,对BRR寄存器按上图配置(没写到的地方都为0),就可以产生一个为9600的波特率。

        下图为在不同主频下的误差

注: 1. CPU的时钟频率越低,则某一特定波特率的误差也越低。可以达到的波特率上限可以由这组数 据得到。
2. 只有USART1使用PCLK2(最高72MHz)。其它USART(USART2、3)使用PCLK1(最高36MHz)

2.2.2串口字符组成

        上图就为一个字符的组成,分为起始位,数据帧,检验位以及停止位。

        其中,起始位标志一个数据帧的开始,固定为低电平(空闲状态下就为高电平)。

        数据位:数据帧的有效载荷,1为高电平,0为低电平,低位先行。

        校验位:用于数据验证,根据数据位计算得来(一般分为奇、偶校验)。

        停止位:用于数据帧间隔,固定为高电平(一般为1位)。

        数据位一般为8位(位0到位7),特殊情况下可以将检验位也作为数据位,也即9位数据位。

        同时,在时钟的上升沿,才进行一个数据位的传输。

奇、偶校验
        偶校验:校验位使得一帧中的 78LSB数据以及校验位中’1’的个数为偶数。
例如:数据=00110100,有3’1’,这样选择了偶校验,就会在第9位(检验位)补上1,使得有偶数个1。
        
        奇校验:此校验位使得一帧中的78LSB数据以及校验位中’1’的个数为奇数。
例如:数据=00110101,有4个’1’,这样选择了奇校验,就会在第9位(检验位)补上1,使得有奇数个1。
        将经过奇偶校验后得到的数据传输至其他设备,在其他设备会对数据进行校验,也即检验数据1的个数奇偶,一般来说,在传输过程中受到干扰,发生误差会改变一位(0变为1),少数情况下会改变两次(这种情况就难以检测出数据误差),用下图表示就是:
        如果检验到1个个数不为奇或是偶(设定的检验),就不会接收该数据,会要求重新上传数据。
        如果发生两位波动(0变为1或是1变为0),就很难检测到问题。
        这里7或8个LSB就是数据位,通过控制CR1寄存器上的PCE位、M位

        上面是不同PCE位和M位的组合下的数据帧的分配情况,看到CR1寄存器:

        M位决定字长(数据帧的长度),PCE位则决定是否需要校验位,其中一个起始位和N个停止位是固定的,用来决定一个数据帧的开始和结束,其中N个停止位还需要另外设置,停止位越长,两个数据帧的间隔就越长。

        例如,M位为0(8为数据位长),PCE为0(禁止校验控制),结果就是8位(0~7位)全为数据传输,没有数据校验功能。

        一般情况下,需要校验位(PCE位为1),对应的M为就会为1,这样也会8位(0~7)数据位,对应的8位对应一个字节Byte。

        同时传输模式:如果USART_CR1PCE位被置位,写进数据寄存器的数据的MSB位被校验位替换后发送出去(如果选择偶校验偶数个’1’,如果选择奇校验奇数个’1’)。如果奇偶校验失败, USART_SR寄存器中的PE标志被置’1’,并且如果USART_CR1寄存器的PEIE在被预先设置的 话,中断产生

        这里类似ADC外设中的规则组、注入组转换完成,同样会产生标志位,根据自身需求,如果需要则使能对应的中断使能位,这样就可以在产生标志位后进入中断,执行我们需要的程序。

        同时在PE位描述中看到,在清除PE位前,软件必须等待RXNE标志位被置1:

        当RDR移位寄存器为空,对应的USART_DR寄存器就为非空,因为RDR移位寄存器会将数据转运至USART_DR寄存器(后续分析结构会提到),此时RXNE就置1,然后就可以完成PE标志位的清除。

        串口数据帧中还有n个停止位(高电平),用于数据帧间隔,固定为高电平:

        用两位STOP[1:0]来决定停止位的长度,也就是用来决定两个字符传输间隔的时长。

        上图就为一个完整数据帧的具体结构。

        在USART串口通信中,包含发送器和接收器,对应内部结构就是发送寄存器、接收寄存器(用来存储数据)以及发送、接收控制寄存器,接下来进行展开。

3.USART内部结构

3.1数据的接收和发送

     上图位USART的内部结构,最下面为之前讲过的波特率发生器,通过对BRR寄存器位设置,就可以得到不同大小的波特率,同时还能看到BRR寄存器控制的包含发送器波特率控制,还包括接收器波特率控制。根据TE、RE位的设置,就能实现BRR寄存器对对应操作的波特率进行控制。

        实际上就是,在发送、接收的过程,自动实现对发送、接收波特率的控制

        接下来重点看到发送TX和接收RX引脚的输入:

        TX引脚也就是输出(发送)引脚,对发送数据寄存器TDR进行写操作,然后通过发送移位寄存器转运至TX引脚,实现数据输出。

        同样,RX接收(输入)引脚,从RX中接收到数据传输至接收移位寄存器,然后存储至接收数据寄存器供用户读操作。

        上述位TX发送和RX接收的一般操作,其中包含一些细节,当发送使能位(TE)被设置时,发送移位寄存器中的数据在TX脚上输出,相应的时钟脉冲在CK(SCKL)脚上输出,这一输出也就是对应USART同步时序的功能,后续提到。

        TDR发送数据寄存器和RDR接收数据寄存器在外设内部只表现为1个数据寄存器:

        由于RDR接收数据寄存器是只读的,而TDR发送数据寄存器是只写的,所以,对USART_DR寄存器进行读、写操作,分别会对RDR、TDR执行对应的操作,这样就实现由一个寄存器实现两个寄存器的功能。

        简而言之就是,在物理层面上表现为两个寄存器(TDR、RDR),而在软件层上是通过同一个地址进行访问的,实际上在硬件层面是分离的,分别用于接送和发送数据。

3.1.1发送器

        发送器主要实现的就是字符(数据帧)的发送:

        在USART发送期间,在TX引脚上首先移出数据的最低有效位。在此模式里,USART_DR寄存 器包含了一个内部总线和发送移位寄存器之间的缓冲器
        每个字符之前都有一个低电平的起始位;之后跟着的停止位,其数目可配置。
        USART支持多种停止位的配置:0.511.52个停止位。

        字符发送是从最低为开始转移,用图表示就是:

        这样就能完成一整个发送过程。            

        这里看到下面两个注意:

1.在数据传输期间不能复位TE位,否则将破坏TX脚上的数据,因为波特率计数器停止计数。
正在传输的当前数据将丢失。
2 TE位被激活后将发送一个空闲帧。

        在数据传输过程中,TE位作为发送数据的使能端,不仅控制着发送器控制,同时还控制着发送器波特率的控制:

        对TE进行复位,就会同时复位这两个控制端,发送器波特率控制复位就会导致波特率计数器停止计数,导致当前传输的数据丢失。

        然后是TE位激活后将发送一个空闲帧,以下的解释:

  • 当TE位被激活(设置)时,USART的发送器被允许开始发送数据。
  • 如果此时发送缓冲区有数据,那么这些数据会按照USART的配置(如波特率、数据位、停止位等)被发送出去。
  • 当发送缓冲区中的所有数据都被发送完毕,包括最后一个字节的停止位之后,USART线路会自然进入空闲态,即发送了一个“空闲帧”。
        单字节通信
        
        清零TXE 位总是通过对数据寄存器的写操作来完成的。TXE位由硬件来设置,它表明:
● 数据已经从TDR移送到移位寄存器,数据发送已经开始
TDR寄存器被清空
● 下一个数据可以被写进USART_DR寄存器而不会覆盖先前的数据
       
        移位寄存器就相当于专门为TDR服务的DMA,目的也是防止数据被覆盖。
        下面看到 TXE位的描述:
        如果TXEIE 位被设置,此标志将产生一个中断。
        
        如果此时USART 正在发送数据,对 USART_DR 寄存器的写操作把数据存进 TDR 寄存器,并在当前传输结束时把该数据复制进移位寄存器。
        如果此时USART 没有在发送数据,处于空闲状态,对USART_DR寄存器的写操作直接把数据放进移位寄存器,TXE位立即被置起(因为一开始TDR寄存器就为0,TDR寄存器中的值已经被放进移位寄存器)。
        当一帧发送完成时(停止位发送后) 并且设置了 TXE TC 被置起,如果 USART_CR1 寄存器中的TCIE 被置起时,则会产生中断。
   
        在USART_DR 寄存器中写入了最后一个数据字后,在关闭USART模块之前或设置微控制器进入低功耗模式(详见下图)之前,必须先等待TC=1
使用下列软件过程清除TC位:
1.读一次USART_SR寄存器;
2.写一次USART_DR寄存器。
注: TC位也可以通过软件对它写’0’来清除。此清零方式只推荐在多缓冲器通信模式下使用。
        可以看到TXE为1的时候,也就是TDR寄存器空的情况,才开始写入数据,当数据传输完毕,TXE为0也就是TDR寄存器非空,此时最后一个数据,TXE就一直被拉起,直至数传输结束,然后置TC=0(表示传输结束)。这样进入到下一次传输就直接是空闲状态下的TXE标志。
空闲符号
        置位TE 将使得USART 在第一个数据帧前发送一空闲帧,对应前面提到TE激活产生一个空闲帧(高电平)
断开符号
        设置SBK 可发送一个断开符号。断开帧长度取决M。如果设置SBK=1,在完成当前数据发送后,将在TX线上发送一个断开符号。断开字符发送完成时(在断开符号的停止位时)SBK被硬件复位。USART在最后一个断开帧的结束处插入一逻辑’1’,以保证能识别下一帧的起始位。
注意如果在开始发送断开帧之前,软件又复位了 SBK位,断开符号将不被发送。如果要发送
两个连续的断开帧,SBK位应该在前一个断开符号的停止位之后置起。

 3.1.2接收器

        接收器就是通过串口来接收一个一个数据帧,对应的也就是发送器发出的数据。

        当CR1寄存器中的RE位被激活,此时就开始对起始位进行寻找

        起始位侦测
        在USART 中,如果辨认出一个特殊的采样序列,那么就认为侦测到一个起始位。
该序列为: 1 1 1 0 X 0 X 0 X 0 0 0 0,用图表示:

        在起始位侦测中,总共会有16位的侦测位检测。当在空闲模式下突然检测到了1110,则表示这时可能开始准备接收数据,然后接着检测,在第357位的第一次采样,和在第8910的第二次采样都为’0’,此时就说明接下来需要对数据进行接收操作。

        然而只需要在在第357位的第一次采样,和在第8910的第二次采样中至少有两位为0,就说明该侦测位有效(X表示无论为1还是0),此时会置NE噪声标志位为1,表明此时数据收到了干扰,是否需要使用根据自身需求来使用。这种序列是固定的在内部,只要检测到符合这种侦测序列,就会开始数据的接收。

        标志位NE会和RXNE标志位一起出现因为当开始接收时,移位寄存器一接收到数据,就会立刻转运到RDR接收数据寄存器中,此时RXNE标志位就为1。所以刚开始,NE几乎和RXNE标志位同时为1,所以,需要在NE检测到噪声产生中断时,可以使能RXNEIE使能中断。

        实际上,发送数据也是类似的,在第一次发送时(从空闲状态下突然发送),数据会直接到移位数据寄存器,跨过了发送数据寄存器TDR,同样的TXE也会置1,然后接收到对应TXE标志位,此时就会往TDR寄存器中补充数据(重装数据)。

帧错误
         当以下情况发生时检测到帧错误:
由于没有同步上或大量噪音的原因,停止位没有在预期的时间上接和收识别出来。
当帧错误被检测到时:
● FE位被硬件置起
● 无效数据从移位寄存器传送到USART_DR寄存器。
● 在单字节通信时,没有中断产生。然而,这个位和RXNE位同时置起,后者将产生中断。在
多缓冲器通信情况下,如果USART_CR3寄存器中EIE位被置位的话,将产生中断。
顺序执行对USART_SR和USART_DR寄存器的读操作,可复位FE位。

        同样会随着RXNE一同产生,需要产生中断同理使能RXNEIE,就可以使用RXNE标志位。

断开符号
        当接收到一个断开帧时,USART 像处理帧错误一样处理它。
空闲符号
        当一空闲帧被检测到时,其处理步骤和接收到普通数据帧一样,但如果IDLEIE 位被设置将产生一个中断。

        根据自己需要,在检测到总线空闲,执行对应的中断。

溢出错误
        如果RXNE 还没有被复位,又接收到一个字符,则发生溢出错误。数据只有当RXNE位被清零后才能从移位寄存器转移到RDR寄存器。RXNE标记是接收到每个字节后被置位的。如果下一个
数据已被收到或先前DMA请求还没被服务时,RXNE标志仍是置起的,溢出错误产生。

        溢出错误,又称为过载错误,在帧错误发生的同时,如果后续也发生过载错误,FE以及ORE这两个标志位都会被置起。

        RDR内容将不会丢失,读USART_DR寄存器仍能得到先前的数据。

        移位寄存器中以前的内容将被覆盖,随后接收到的数据都将丢失。

        在多缓冲器通信情况下,如果EIE使能,则会产生中断。

        顺序执行对USART_SRUSART_DR寄存器的读操作,可复位ORE位。
注意: 当ORE位置位时,表明至少有1个数据已经丢失。有两种可能性:
● 如果RXNE=1,上一个有效数据还在接收寄存器RDR上,可以被读出。
● 如果RXNE=0,这意味着上一个有效数据已经被读走,RDR已经没有东西可读。当上一个
有效数据在RDR中被读取的同时又接收到新的(也就是丢失的)数据时,此种情况可能发生。
在读序列期间(在USART_SR寄存器读访问和USART_DR读访问之间)接收到新的数据,此
种情况也可能发生。
停止位的检测
        停止位可以有0.5、1、1.5、2这四种停止位,通过设置STOP:
1. 0.5个停止位(智能卡模式中的接收):不对0.5个停止位进行采样。因此,如果选择0.5个
停止位则不能检测帧错误和断开帧。
2. 1个停止位:对1个停止位的采样在第8,第9和第10采样点上进行。
3. 1.5个停止位(智能卡模式):当以智能卡模式发送时,器件必须检查数据是否被正确的发
送出去。所以接收器功能块必须被激活(USART_CR1寄存器中的RE =1),并且在停止位
的发送期间采样数据线上的信号。如果出现校验错误,智能卡会在发送方采样NACK信
号时,即总线上停止位对应的时间内时,拉低数据线,以此表示出现了帧错误。FE在
1.5个停止位结束时和RXNE一起被置起。对1.5个停止位的采样是在第16,第17和第18
采样点进行的。1.5个的停止位可以被分成2部分:一个是0.5个时钟周期,期间不做任何
事情 。随后是1个时钟周期的停止位,在这段时间的中点处采样。详见第25.3.11节:智
能卡。
4. 2个停止位:对2个停止位的采样是在第一停止位的第8,第9和第10个采样点完成的。如
果第一个停止位期间检测到一个帧错误,帧错误标志将被设置。第二个停止位不再检查
帧错误。在第一个停止位结束时RXNE标志将被设置。

        正常一般使用1位停止位即可。

        上述1.5个停止位使用在智能卡模式下的,而只能卡是作为USART的扩展功能,同样的还有LIN局域互联网、红外IrDA,对应的接口:

        根据自己的需要,可以自行参考手册的描述。

        下面为对发送控制器、接收控制器的结构图:

        从上图就可以很直观看出,那些寄存器配置说明控制,例如发送器控制,由硬件数据流控,发送器时钟CR1寄存器的TE、SBK、PS、UE位进行控制。

3.2硬件数据流控

        硬件数据流控是为了防止数据收发不及时而导致数据被覆盖,造成数据丢失。

        如下图未两个设备用串口进行通信,且使用了硬件流控功能。

        当设备1发送数据到设备2时(TX1到RX2),nCTS1对应的接到nRTS2,具体作用根据时序来分析:

        上图为CTS流控制的时序,也就是发送的硬件流控制。当nCTS为低电平时(实际上是由nRTS2发送到nCTS1的低电平),就表示此时数据接收准备完毕,此时就开始实现数据由TX1到RX2的转运。

        如果nCTS一直为高电平(实际上是由nRTS2发送到nCTS1的高电平),表示此时设备2繁忙,此时还不能接收数据,此时就会置TX为空闲状态,直到nRTS2发送到nCTS1的电平为低电平时,此时才开始设备RX2数据的接收(对应设备1的发送TX1)。

        设备2的TX2到设备1的RX1也是同理,这里就不再赘述。

4.数据模式

        HEX模式/十六进制模式/二进制模式:以原始数据的形式显示

        文本模式/字符模式:以原始数据编码后的形式显示

        对应文本模式就是根据HEX等模式下的编码得到的字符:

        例如:DEC十进制模式下下65,表示的字符就为A,而在HEX模式下则为41,表示A。

        对应上图,为发送接收的具体过程,实际上是以HEX模式传输(根据你自己选的HEX、DEC模式)数据(0x41),接收方可以选择HEX数据接收,同样也可以选择译码后得到A。

        同样发送也是如此(两种发送选择)。

5.USART的配置

5.1发送配置

	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);	//开启USART1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
	
	/*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引脚初始化为复用推挽输出

        初始化,开启时钟等操作。

	/*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
	
	/*USART使能*/
	USART_Cmd(USART1, ENABLE);								//使能USART1,串口开始运行

        然后是串口初始化,对USART_InitStructure结构体进行配置。

        首先是波特率USART_BaudRate,一般情况下是9600,因为使用的标准库,而不需要在去通过公式来计算出分频大小。

        然后是硬件流控USART_HardwareFlowControl,一般应用在容易数据覆盖的情况,为了避免这种情况需要打开,一般正常情况不会出现数据覆盖,也就不需要。

        接着USART_Mode串口的模式,可以为发送TX以及接收RX:

        这里使用发送功能,即选择TX。

        然后是奇偶校验位USART_Parity,一般不需要,而且在特殊情况下(同时变更两位)没有作用。

        接着是停止位USART_StopBits,一般1位即可。

        最后是字长USART_WordLength,也就是对M位进行选择,8位还是9位数据位,一般都是8位,9位为1字节。

        然后将配置好的参数结构体传回初始化函数(Init)中,最后使能一下USART_Cmd即可。

        串口发送函数:

void Serial_SendByte(uint8_t Byte)
{
	USART_SendData(USART1, Byte);		//将字节数据写入数据寄存器,写入后USART自动生成时序波形
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);	//等待发送完成
	/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}

        通过上述函数,就可以实现发送一个字节的数据。

        如果需要发送一个数组,则使用下面函数:

void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
	uint16_t i;
	for (i = 0; i < Length; i ++)		//遍历数组
	{
		Serial_SendByte(Array[i]);		//依次调用Serial_SendByte发送每个字节数据
	}
}

        类似的,想要实现其他数据类型的发送,只需要写下对应的发送函数即可。

        归根结底是用到了函数USART_SendData:

5.2接收配置

	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);	//开启USART1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA10引脚初始化为上拉输入

        同样也是开启时钟,初始化对应的GPIO端口。

	/*USART初始化*/
	USART_InitTypeDef USART_InitStructure;					//定义结构体变量
	USART_InitStructure.USART_BaudRate = 9600;				//波特率
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;	//硬件流控制,不需要
	USART_InitStructure.USART_Mode =  USART_Mode_Rx;	//模式,接收模式
	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

        和发送类似,这里的一些参数需要和发送配置一致,除了USART_Mode选择接收模式。

	/*中断输出配置*/
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);			//开启串口接收数据的中断
	
	/*NVIC中断分组*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);			//配置NVIC为分组2
	
	/*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外设
	
	/*USART使能*/
	USART_Cmd(USART1, ENABLE);								//使能USART1,串口开始运行

        然后配置中断控制,因为不知道数据什么时候接收,所以需要通过中断来实现数据接收,防止数据未接收到的情况。

        这里要求不高,需要注意的是,中断通道USART1_IRQn。

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标志位
																//读取数据寄存器会自动清除此标志位
																//如果已经读取了数据寄存器,也可以不执行此代码
	}
}

        其中Serial_RxData为一个全局变量,用于存储接收的数据,而Serial_RxFlag为接收标志位,也是全局变量。

        对应的有函数:

uint8_t Serial_GetRxFlag(void)
{
	if (Serial_RxFlag == 1)			//如果标志位为1
	{
		Serial_RxFlag = 0;
		return 1;					//则返回1,并自动清零标志位
	}
	return 0;						//如果标志位为0,则返回0
}

        用于获取接收标志位,也就是RXNE,读取后自动清0。

uint8_t Serial_GetRxData(void)
{
	return Serial_RxData;			//返回接收的数据变量
}

        返回接收到的数据函数。

        在主函数中:

	while (1)
	{
		if (Serial_GetRxFlag() == 1)			//检查串口接收数据的标志位
		{
			RxData = Serial_GetRxData();		//获取串口接收的数据
			OLED_ShowHexNum(1, 8, RxData, 2);	//显示串口接收的数据
		}
	}
}

             每次当标志位Serial_RxFlag置1,就接收一次数据,将数据Serial_RxData传回RxData中存储。

        实际上用到的是USART_ReceiveData接收函数:

        这样就实现了对数据的接收。

6.小结

        本篇文章中尽管篇幅较长但还是有一些没有提及,例如USART的同步功能、IrDA、智能卡等,如有需要自行查阅手册,最后欢迎各位的讨论以及错误指针。

参考文章:[9-2] USART串口外设_哔哩哔哩_bilibili

  • 18
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
USART (Universal Synchronous/Asynchronous Receiver/Transmitter) 是一种通用的串行通信接口,常用于单片机与外部设备之间的通信。USART 可以以同步模式或异步模式进行数据传输。 在使用 USART 进行串口通信时,需要先设置 USART 的通信参数,包括波特率、数据位数、停止位数、奇偶校验等。然后将数据通过 USART 发送出去,或者从 USART 接收数据。 在发送数据时,需要将数据写入 USART 的数据寄存器中,USART 会自动将数据进行传输。在接收数据时,需要读取 USART 的数据寄存器,USART 会自动将接收到的数据存储在数据寄存器中。 通常情况下,我们可以使用 printf 函数来向串口发送数据,或使用 scanf 函数从串口接收数据。在使用这些函数之前,需要先初始化串口的通信参数。 下面是一个使用 USART 进行串口通信的例子,使用的是 STM32 单片机的 HAL 库: ```c #include "stm32f1xx_hal.h" UART_HandleTypeDef huart1; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_USART1_UART_Init(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); char buffer[20]; while (1) { printf("Enter a string: "); scanf("%s", buffer); printf("You entered: %s\n", buffer); HAL_Delay(1000); } } void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; __HAL_RCC_PWR_CLK_ENABLE(); __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } } static void MX_USART1_UART_Init(void) { huart1.Instance = USART1; huart1.Init.BaudRate = 115200; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart1) != HAL_OK) { Error_Handler(); } } void Error_Handler(void) { } ``` 在上面的例子中,我们先通过 MX_USART1_UART_Init 函数初始化了 USART1 的通信参数,然后在 while 循环中使用 printf 和 scanf 函数进行串口通信。注意,在使用 printf 函数前,需要在头文件中包含 stdio.h 和 usart.h 文件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值