STM32单片机学习笔记(十)-串口通讯(USART)(一)

写在前面:本系列内容均为自学笔记,参考资料为野火指南者开发板资料及芯片参考手册等,使用野火指南者开发板进行学习,该系列内容仅用于记录笔记,不做其他用途,笔记的内容可能会存在不准确或者错误等,如有大佬看到错误内容还望能够评论指正,感谢各位。
本节包括前几节的程序,请参考野火开发板资料,里面由更加清晰的教学,野火B站账号:野火官方B站账号链接。

参考资料

《STM32F10x芯片参考手册-中文版》、《STM32F10x芯片数据手册-中文版》、《STM32F10x Cortex-M3编程手册-英文版》、《CM3权威指南-中文版》、《野火开发板相关资料》等;
本文中理论知识的梳理主要参考《野火开发板相关资料》,当然芯片参考手册中也有关于USART的介绍,不过理论性较强,在本文中可能不会大量引用,主要引用野火的资料;

本文内容可能会存在错误或不严谨,发现后会及时改正,也希望各位能帮忙纠错,谢谢!

学习目标

1、简单了解通信的基本概念;
2、串口通讯(USART)相关内容;
3、程序:单片机收发码;

电路图

图10-1:USB转串口硬件设计图(图片来自野火资料,这里仅作参考)
STM32单片机学习笔记(十)-串口通讯(USART)(一)_第1张图片
图10-2:LED灯(图片来自野火资料,在本文中用于USART控制硬件点灯)
STM32单片机学习笔记(十)-串口通讯(USART)(一)_第2张图片

一、通信的基本概念

:这部分内容来自《野火开发板资料》,当然这部分内容也可以查看计算机相关内容;

1、串行通讯与并行通讯

图10-3:串行与并行示意图(自己画的,如有问题还请留言,谢谢)
STM32单片机学习笔记(十)-串口通讯(USART)(一)_第3张图片
通过上图可以看出:
并行通讯:有8根传输线,可以同时将8位数据传输给设备,这种方式相对较快,但是所需要的传输线比较多,这种通讯方式的抗干扰能力相对更好;
串行通讯:有1根传输线,一个8位数据在传输时只能一位一位的把数据传输给设备,这种方式相对较慢,但是所需要的传输线会比较少,这种通讯方式的抗干扰能力相对更差。
基于以上特点:并行通讯更适合短距离传输,毕竟需要的线比较多,传输距离太长,成本也会更高;而串行通讯则适合长距离通讯,需要的线少,成本相对可以低一点。

2、全双工、半双工及单工通讯

图10-4:全双工、半双工和单工通讯(自己画的,如有问题还请留言,谢谢)
STM32单片机学习笔记(十)-串口通讯(USART)(一)_第4张图片
全双工:在同一时刻,两个设备之间可以同时发送或者接受数据;
半双工:在同一时刻,两个设备之间可以进行数据的收发,但是在设备A向设备B发送数据的同时设备B不能向设备A发送数据(即,两设备同一时刻不能同时收发数据);
单工:任何时刻,两设备都只能向一个方向进行通讯,即只能设备A向设备B发送数据,设备B不能向设备A发送数据。

3、同步通讯和异步通讯

同步通讯:在同步通讯中收发双方除了数据信号外还会有时钟信号,双方在收发数据时会根据时钟信号来协调通讯,同步数据,比如规定双方在时钟信号的上升沿收发数据,对数据进行采样等,另外,同步通讯中,数据信号均为有效数据,详情请看图10-3:
图10-5:同步通讯(图片来自《野火开发板资料》)
STM32单片机学习笔记(十)-串口通讯(USART)(一)_第5张图片
异步通讯:异步通讯中收发双方只有数据信号,数据发送方在发送信号时,除了要将有效数据发送出去以外,还会将用于识别有效数据的信号一同发出,数据接收方可以根据这些识别信号来确认信号的正确性和完整性,详情请看图10-4:
图10-6:异步通讯(图片来自《野火开发板资料》)
STM32单片机学习笔记(十)-串口通讯(USART)(一)_第6张图片

4、比特率和波特率

比特率:单位时间内传输的二进制位数,单位为比特每秒(bit/s),比特率越高,单位时间内传输的数据就越多;
波特率:单位时间内传输的码元数,是衡量数据传输速率的指标,常用的波特率有4800、9600、115200等;
举个例子:(个人理解,可能有误)
 假设0V用0表示,5V用1表示,此时对比特率,二进制位数为2(0和1),对波特率,码元数为2(0V和5V);
 假设0V用00表示,3.3V用01表示,5V用10表示,则此时对比特率,二进制位数为6(0、0、0、1、1、0),对波特率,码元数为3(0V、3.3V和5V)。

二、通讯协议简介

注:这部分内容基本来自野火开发板资料
串口通讯:是一种设备间非常常用的串行通讯方式,因其简单便捷,因此大部分电子设备都支持该通讯方式;
通讯协议:可以将其简单分为物理层和协议层;
  物理层:规定通讯系统中具有机械、电子功能部分的特性,确保原始数据在物理媒体的传输,其实就是硬件部分;
  协议层:主要规定通讯逻辑,统一收发双方的数据打包、解包标准,其实就是软件部分;

1、物理层

①、RS232标准

图10-7:RS232标准串口通讯结构图(来自:野火开发板资料)
STM32单片机学习笔记(十)-串口通讯(USART)(一)_第7张图片
 在RS232标准下,两设备的通讯主要通过DB9接口的通讯线来实现,这种方式主要用于工业设备的通讯,其常用的电平转换芯片一般有MAX3232、SP3232等。
 在最初的应用中,RS-232 串口标准常用于计算机、路由与调制调解器(MODEN,俗称“猫”)之间的通讯 ,在这种通讯系统中,设备被分为数据终端设备 DTE(如:计算机、路由)和数据通讯设备DCE(如:调制调解器)。我们以这种通讯模型讲解它们的信号线连接方式及各个信号线的作用。
图10-8:DB9信号线实物图
STM32单片机学习笔记(十)-串口通讯(USART)(一)_第8张图片
上图中,接线口为针式引出引脚的为公头,以孔式引出引脚的为母头,计算机上的一般是公头;
图10-9:DB9通讯线公头及母头接法(来自:野火开发板资料)
STM32单片机学习笔记(十)-串口通讯(USART)(一)_第9张图片
下方表格是DB9各个引脚的含义:
表10-1:DB9各引脚含义,公头(DTE可以理解为计算机,DCE可以理解为要接在计算机上的设备(调制调节器等))

序号 名称 符号 数据方向 说明
1 载波检测 DCD DTE→DCE Data Carrier Detect,数据载波检测,用于DTE告知对方,本机是否收到对方的载波信号
2 接收数据 RXD DTE←DCE Receive Data,数据接收信号,即输入
3 发送数据 TXD DTE→DCE Transmit Data,数据发送信号,即输出;两个设备之间的 TXD与RXD 应交叉相连
4 数据终端(DTE)就绪 DTR DTE→DCE Data Terminal Ready,数据终端就绪,用于DTE向对方告知本机是否已准备好
5 信号地 GND - 地线,两个通讯设备之间的地电位可能不一样,这会影响收发双方的电平信号,所以两个串口设备之间必须要使用地线连接,即共地
6 数据设备(DCE)就绪 DSR DTE←DCE Data Set Ready,数据发送就绪,用于 DCE告知对方本机是否处于待命状态
7 请求发送 RTS DTE→DCE Request To Send,请求发送,DTE请求DCE本设备向 DCE 端发送数据
8 允许发送 CTS DTE←DCE Clear To Send,允许发送,DCE回应对方的RTS发送请求,告知对方是否可以发送数据
9 响铃指示 RI DTE←DCE Ring Indicator,响铃指示,表示 DCE端与线路已接通

上表格是计算机端的DB9公头引脚说明,两设备之间进行通讯时,数据收发的信号线(TXD和RXD)应该交互,所以调制调节器端的DB9母头收发信号引脚接线与公头相反,接线图可参考下图:
图10-10:计算机与调制调节器的信号线连接示意图
STM32单片机学习笔记(十)-串口通讯(USART)(一)_第10张图片
 串口线中的RTS、CTS、DSR、DTR及DCD信号,使用逻辑1表示信号有效,逻辑0表示信号无效。例如,当计算机端控制DTR信号线表示为逻辑1时,它是为了告知远端的调制调解器,本机已准备好接收数据,0则表示还没准备就绪。
 在目前的其它工业控制使用的串口通讯中,一般只使用 RXD、TXD 以及 GND 三条信号线,直接传输数据信号,而RTS、CTS、DSR、DTR及DCD信号都被裁剪掉了。
图10-11:RS232电平和TTL电平的区别(来自:野火开发板资料)
STM32单片机学习笔记(十)-串口通讯(USART)(一)_第11张图片
RS232电平中,电平1一般表示-15V左右的电压,电平0一般表示+15V左右的电压;
TTL电平中,电平1一般表示+5V左右的电压,电平0一般表示0V左右的电压;
也可以通过下表来区分两电平标准:
表10-2:RS232电平和TTL电平标准(来自:野火开发板资料)

通讯标准 电平标准(发送端)
5V TTL 逻辑1:2.4V~5V
逻辑0:0~0.5V
RS232 逻辑1:-15V~-3V
逻辑0:+3~+15V

②、USB转TTL

图10-12:USB转串口通讯结构图(来自:野火开发板资料)
STM32单片机学习笔记(十)-串口通讯(USART)(一)_第12张图片
 USB转串口通讯一般用于设备与电脑之间的通信,如开发板根电脑通讯等;一般这种方式用到的电平转换芯片有:CH340、PL2303、CP2102、FT232等;这种通讯方式一般情况下都需要安装电平转化芯片的驱动(据说win10以上系统有自带,各位自己安装驱动之前可以先试试)。

③、原生的串口转串口

图10-13:原生的串口转串口通讯结构图(来自:野火开发板资料)
STM32单片机学习笔记(十)-串口通讯(USART)(一)_第13张图片
原生的串口通信主要是控制器跟串口的设备或者传感器通信,不需要经过电平转换芯片来转换电平,直接就用TTL电平通信;
用途:GPS模块、GSM模块、串口转WIFI模块、HC04蓝牙模块;

2、协议层:串口数据包的基本组成

图10-14:串口数据包的基本组成(来自:野火开发板资料)
STM32单片机学习笔记(十)-串口通讯(USART)(一)_第14张图片
起始位:由一个逻辑0的数据位表示;
停止位:可以由0.5、1、1.5、2个逻辑1的数据位表示;
有效数据:数据包的起始位后紧跟着的要传输的主体数据内容,有效数据长度一般有5~8位长;
校验位:该位主要是为了数据的抗干扰性,可选择校验方式,校验方式主要有以下几种:
  奇校验(odd):有效位和校验位中的“1”的个数为奇数,如有效数据为01110010,此时有4个1,那么如果使用奇校验,则校验位需要为1,此时1的个数为奇数,最后传输的数据一共9位;
  偶校验(even):有效位和校验位中的“1”的个数为偶数,如有效数据为01110010,此时有4个1,那么如果使用偶校验,则校验位需要为0,此时1的个数为偶数,最后传输的数据一共9位;
  0校验(space):无论有效数据的内容是什么,校验位始终为0;
  1校验(mark):无论有效数据的内容是什么,校验位始终为1;
  无校验(noparity):数据包中不包含校验位。

三、USART:通用同步异步收发器

 USART,大家可以简单理解为串口(虽然可能并不准确),下面简单介绍USART,详情请以《STM32F10x芯片参考手册》等官方资料为准。
 通用同步异步收发器(USART)提供了一种灵活的方法与使用工业标准NRZ异步串行数据格式的外部设备之间进行全双工数据交换。USART利用分数波特率发生器提供宽范围的波特率选择。它支持同步单向通信和半双工单线通信,也支持LIN(局部互连网),智能卡协议和IrDA(红外数据组织)SIR ENDEC规范,以及调制解调器(CTS/RTS)操作。它还允许多处理器通信。
 串行通信一般是以帧格式传输数据,即是一帧一帧的传输,每帧包含有起始信号、数据信息、停止信息,可能还有校验信息。USART就是对这些传输参数有具体规定,当然也不是只有唯一一个参数值,很多参数值都可以自定义设置,只是增强它的兼容性。
 使用多缓冲器配置的DMA方式,可以实现高速数据通信。
 与USART相对应的是UART,UART是通用异步收发器,UART与USART的区别就是USART可以提供时钟信号,以产生同步通信,而UART中不能产生时钟信号,所以没有同步通信。
 USART在STM32应用最多莫过于“打印”程序信息,一般在硬件设计时都会预留一个USART通信接口连接电脑,用于在调试程序是可以把一些调试信息“打印”在电脑端的串口调试助手工具上,从而了解程序运行是否正确、如果出错哪具体哪里出错等等;
 USART的主要特性可以参考《STM32F10x芯片参考手册》,主要比较多(写了一页多),这里不再放出,各位可以去参考手册观看。

1、USART寄存器

这里放出芯片官方数据手册中关于USART各寄存器的描述,以便查看本文中所涉及到的部分寄存器的部分标志位,建议各位先大致读一下,里面部分内容后面会涉及到,如部分标志位在什么情况下会被清0等:

①、USART寄存器地址映像

图10-15:USART寄存器列表及其复位值(图片来自芯片参考手册)
STM32单片机学习笔记(十)-串口通讯(USART)(一)_第15张图片

②、状态寄存器(USART_SR)

图10-16:状态寄存器(USART_SR)(图片来自芯片参考手册)
STM32单片机学习笔记(十)-串口通讯(USART)(一)_第16张图片

③、数据寄存器(USART_DR)

图10-17:数据寄存器(USART_DR)(图片来自芯片参考手册)
STM32单片机学习笔记(十)-串口通讯(USART)(一)_第17张图片

④、波特比率寄存器(USART_BRR)

图10-18:波特比率寄存器(USART_BRR)(图片来自芯片参考手册)
STM32单片机学习笔记(十)-串口通讯(USART)(一)_第18张图片

⑤、控制寄存器1(USART_CR1)

图10-19:控制寄存器1(USART_CR1)(图片来自芯片参考手册)
STM32单片机学习笔记(十)-串口通讯(USART)(一)_第19张图片

⑥、控制寄存器2(USART_CR2)

图10-20:控制寄存器2(USART_CR2)(图片来自芯片参考手册)
STM32单片机学习笔记(十)-串口通讯(USART)(一)_第20张图片

⑦、控制寄存器3(USART_CR3)

图10-21:控制寄存器3(USART_CR3)(图片来自芯片参考手册)
STM32单片机学习笔记(十)-串口通讯(USART)(一)_第21张图片

⑧、保护时间和预分频寄存器(USART_GTPR)

图10-22:保护时间和预分频寄存器(USART_GTPR)(图片来自芯片参考手册)
STM32单片机学习笔记(十)-串口通讯(USART)(一)_第22张图片

2、USART功能框图

图10-23:USART功能框图(截选自野火资料,芯片参考手册中有此图,此处是略作修改)
STM32单片机学习笔记(十)-串口通讯(USART)(一)_第23张图片
 接口通过三个引脚与其他设备连接在一起(见图10-23)。任何USART双向通信至少需要两个脚:接收数据输入(RX)和发送数据输出(TX)。
RX:接收数据串行输。通过过采样技术来区别数据和噪音,从而恢复数据。
TX:发送数据输出。当发送器被禁止时,输出引脚恢复到它的I/O端口配置。当发送器被激活,并且不发送数据时,TX引脚处于高电平。在单线和智能卡模式里,此I/O口被同时用于数据的发送和接收。
● 总线在发送或接收前应处于空闲状态
● 一个起始位
● 一个数据字(8或9位),最低有效位在前
● 0.5,1.5,2个的停止位,由此表明数据帧的结束
● 使用分数波特率发生器 —— 12位整数和4位小数的表示方法。
● 一个状态寄存器(USART_SR)
● 数据寄存器(USART_DR)
● 一个波特率寄存器(USART_BRR),12位的整数和4位小数
● 一个智能卡模式下的保护时间寄存器(USART_GTPR)
上述各寄存器内容,请看后面的内容请参考前面第1小点(USART寄存器)的内容,或者直接参考芯片参考手册,其余内容请看以下介绍:

①、功能引脚

TX:发送数据输出引脚,通过过采样技术来区别数据和噪音,从而恢复数据。
RX:接收数据输入引脚,当发送器被禁止时,输出引脚恢复到它的I/O端口配置。当发送器被激活,并且不发送数据时,TX引脚处于高电平。在单线和智能卡模式里,此I/O口被同时用于数据的发送和接收。
SW_RX:数据接收引脚,只用于单线和智能卡模式,属于内部引脚,没有具体外部引脚。
nRTS:请求以发送(Request To Send),n 表示低电平有效。如果使能 RTS 流控制,当USART 接收器准备好接收新数据时就会将 nRTS 变成低电平;当接收寄存器已满时,nRTS 将被设置为高电平。该引脚只适用于硬件流控制
nCTS:清除以发送(Clear To Send),n 表示低电平有效。如果使能 CTS 流控制,发送器在发送下一帧数据之前会检测 nCTS 引脚,如果为低电平,表示可以发送数据,如果为高电平则在发送完当前数据帧之后停止发送。该引脚只适用于硬件流控制
SCLK:发送器时钟输出引脚,这个引脚仅适用于同步模式。
IRDA_OUT:IRDA模式下的输出引脚。
IRDA_IN:IRDA模式下的输入引脚。
USART的引脚在STM32F103ZET6中的分布如下表:
表10-3:USART引脚分布(STM32F103ZET6)

引脚 APB2 APB1
USART1 USART2 USART3 UART4 UART5
TX PA9 PA2 PB10 PC10 PC12
RX PA10 PA3 PB11 PC11 PD2
SCLK PA8 PA4 PB12 - -
nCTS PA11 PA0 PB13 - -
nRTS PA12 PA1 PB14 - -

上述表格中需要注意的是:
  在STM32F103ZET6中有3个USART(USART1、USART2、USART3),2个UART(UART4、UART5);
  USART1挂载在APB2上,其余4个USART均挂载在APB1上,APB2最大频率为72MHz,APB1最大频率为36MHz;
  USART1的速度要比其他4个快;
  STM32F103系列芯片,USART所在的引脚是一样的,比如VET6的USART引脚也是上述表格中的引脚号;

②、数据寄存器

USART数据寄存器(USART_DR)只有低9位(位0~位8)有效,并且第9位(位8)是否有效取决于USART控制寄存器(USART_CR1)的M位设置,当M位为0时表示8位字长,为1时表示9位字长,一般我们使用8位数据字长。
USART_DR包含了已经发送或者接收到的数据,它实际上是包含了两个寄存器,一个是专门用于发送的可写的TDR,另一个是专门用于接收的可读的RDR;当进行发送操作时,往USART_DR中写入的数据都将保存在TDR中,相应的进行读操作时,会自动提取RDR中的数据。
 TDR和RDR都是介于系统总线和移位寄存器之间。串行通信是一个位一个位传输的,发送时把TDR内容转移到发送移位寄存器,然后把移位寄存器数据每一位发送出去,接收时把接收到的每一位顺序保存在接收移位寄存器内然后才转移到RDR。

③、控制器

USART有专门控制发送的发送器、控制接收的接收器,还有唤醒单元、中断控制等等。使用USART之前需要向USART_CR1寄存器的UE位置1使能USART,UE位用来开启供给给串口的时钟。发送或者接收数据字长可选8位或9位,由USART_CR1的M位控制。

发送器

 当发送使能位(TE)被设置,发送移位寄存器中的数据将在TX脚输出,相应的如果需要时钟脉冲信号则会在CK脚输出;USART发送数据期间,在TX引脚上的数据将首先输出数据的最低有效位;
 一个字符帧的发送需要3个部分:起始位+有效数据帧+停止位,其中起始位在有效数据前,它是一个位周期为全0的低电平信号(正常情况的位周期为1个低电平+1个高电平),停止位在有效数据后,它可以设置为0.5、1、1.5和2个停止位,一般情况下我们选用1个停止位,下方是关于上述4种停止位的介绍:
  1个停止位:停止位位数的默认值
  2个停止位:可用于常规USART模式、单线模式以及调制解调器模式
  0.5个停止位:在智能卡模式下接收数据时使用
  1.5个停止位:在智能卡模式下发送和接收数据时使用
下图是在选用8个有效数据字长的前提下,4种停止位下的发送字符的时序图:
图10-24:字符发送时序图(来自芯片参考手册)
STM32单片机学习笔记(十)-串口通讯(USART)(一)_第24张图片
 当发送使能位 TE 置 1 之后,发送器开始会先发送一个空闲帧(一个数据帧长度的高电平),接下来就可以往 USART_DR 寄存器写入要发送的数据。在写入最后一个数据后,需要等待 USART 状态寄存器(USART_SR)的 TC 位为 1,表示数据传输完成,如果USART_CR1 寄存器的 TCIE 位置 1,将产生中断。
下表是在发送数据时,编程中几个比较重要的标志位:
表10-4:发送数据的重要标志位

名称 描述
TE 发送使能
TXE 发送寄存器为空,发送单个字节的时候使用
TC 发送完成,发送多个字节数据的时候使用
TXIE 发送完成中断使能

 在芯片参考手册中还介绍了单字节通信,不过个人感觉可以直接看表10-4中的介绍,TXE在发送单字节数据时使用,主要通过判断相应寄存器是否为空来确认数据是否发送完成,而TC则是在发送多个字节时使用的,这个标志位在数据发送完成后会发生改变,通过判断表示位状态是否改变来确认数据是否发送完成,这两个在后面的程序中都会介绍并用到,关于单字节通信的介绍这里不再放出;
在芯片参考手册中还给出了发送数据的配置步骤,具体如下,各位可以参考一下:
  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或需要进入停机模式之前,需要确认传输结束,避免破坏最后一次传输。

接收器

这部分内容在芯片参考手册中的介绍比较繁杂,这里采用野火的资料中的介绍:
 如果将USART_CR1寄存器的RE位置1,使能USART接收,使得接收器在RX线(这个应该指的就是RX接收端口)开始搜索起始位。在确定到起始位后就根据 RX 线电平状态把数据存放在接收移位寄存器内。接收完成后就把接收移位寄存器数据移到RDR内,并把USART_SR寄存器的RXNE位置1,同时如果USART_CR2寄存器的RXNEIE置1的话可以产生中断。
 在接收数据时,编程的时候有几个比较重要的标志位我们来总结下:
表10-5:接收数据的重要标志位

名称 描述
RE 接收使能
RXNE 读数据寄存器非空
RXNEIE 发送完成中断使能

④、小数波特率生成

 波特率指数据信号对载波的调制速率,它用单位时间内载波调制状态改变次数来表示,单位为波特。比特率指单位时间内传输的比特数,单位bit/s(bps)。对于 USART 波特率与比特率相等,以后不区分这两个概念。波特率越大,传输速率越快。
USART 的发送器和接收器使用相同的波特率。计算公式如下:
T x / R x 波 特 率 = f c k ( 16 ∗ U S A R T D I V ) Tx/Rx波特率=\frac{f_{ck}}{(16*USARTDIV)} Tx/Rx=(16USARTDIV)fck
其中:
  fck:是USART的时钟,这里要注意区分APB1和APB2总线,APB1最大为36MHz,APB2最大为72MHz;
  USARTDIV:是一个存放在波特率寄存器(USART_BRR)的一个无符号定点数,其中该寄存器的DIV_Fraction[3:0]用于定义小数部分,DIV_Mantissa[11:0]定义整数部分.
 例如:DIV_Mantissa=24(0x18),DIV_Fraction=10(0x0A),此时USART_BRR值为0x18A;那么USARTDIV的小数位10/16=0.625;整数位24,最终USARTDIV的值为24.625。
 如果知道USARTDIV值为 27.68,那么DIV_Fraction=16*0.68=10.88,最接近的正整数为11,所以DIV_Fraction[3:0]为 0xB;DIV_Mantissa=整数(27.68)=27,即为0x1B。
 通过上述两个例子可以看出,公式在实际使用时,USARTDIV的整数部分并不需要乘16,只有小数部分需要乘16,且小数部分乘16后,如果还有小数,则找与之最接近的整数。
 简单举例介绍如何通过定义USARTDIV来获取想要的波特率,以USART1为例,假设要定义的波特率为115200bps,那么:
115200 = 72000000 ( 16 ∗ U S A R T D I V ) 115200=\frac{72000000}{(16*USARTDIV)} 115200=(16USARTDIV)72000000
此时USARTDIV=39.0625,那么DIV_Mantissa[11:0]=39=0x17,DIV_Fraction[3:0]=0.0625*16=0x01,所以USART_BRR的值设置为0x171。

⑤、校验控制

 在前面部分有简单介绍过校验,这里主要针对寄存器的校验控制位进行说明;在STM32F103单片机中USART支持奇偶校验,当使用校验位时,串口传输的长度将是8位的数据帧加上 1 位的校验位总共9位,此时USART_CR1寄存器的M位需要设置为1,即9数据位。将USART_CR1寄存器的PCE位置1就可以启动奇偶校验控制,奇偶校验由硬件自动完成。启动了奇偶校验控制之后,在发送数据帧时会自动添加校验位,接收数据时自动验证校验位。接收数据时如果出现奇偶校验位验证失败,会见USART_SR寄存器的PE位置1,并可以产生奇偶校验中断。
 使能了奇偶校验控制后,每个字符帧的格式将变成:起始位+数据帧+校验位+停止位。

⑥、中断控制

 USART中断请求标志位和使能控制位如下表所示:
表10-6中断请求相关标志位

中断事件 事件标志 使能控制位
发送数据寄存器为空 TXE TXEIE
CTS 标志 CTS CTSIE
发送完成 TC TCIE
准备好读取接收到的数据 RXNE RXNEIE
检测到上溢错误 ORE RXNEIE
检测到空闲线路 IDLE IDLEIE
奇偶校验错误 PE PEIE
断路标志 LBD LBDIE
多缓冲通信中的噪声标志、上溢错误和帧错误 NF/ORE/FE EIE

3、USART部分固件库程序简介

①、USART初始化结构体

部分释义在下放程序种已经备注:

typedef struct
{
  uint32_t USART_BaudRate;            /*!< 用于配置USART通信波特率,下面是一个与波特率相关的公式,个人感觉直接看上文中提到的那个公式就行:
                                            - IntegerDivider = ((PCLKx) / (16 * (USART_InitStruct->USART_BaudRate)))
                                            - FractionalDivider = ((IntegerDivider - ((u32) IntegerDivider)) * 16) + 0.5 */

  uint16_t USART_WordLength;          /*!< 指定在一帧数据中发送或接收的数据的数据位数(字长), 这个数据位数,官方定义了两个,8位和9位 */

  uint16_t USART_StopBits;            /*!< 指定传输的停止位位数,官方定义4种选项:0.5、1、1.5、2,
  											一般选用1位,它设定USART_CR2寄存器的 STOP[1:0]位的值 */

  uint16_t USART_Parity;              /*!< 指定奇偶校验,可选3种:不校验USART_Parity_No,偶校验USART_Parity_Even,奇校验USART_Parity_Odd,
 											 它设定USART_CR1寄存器的PCE位和PS位的值 */
 
  uint16_t USART_Mode;                /*!< 指定是否启用或禁用接收或发送模式,有USART_Mode_Rx和USART_Mode_Tx,允许使用逻辑或运算选择两个,
  											它设定USART_CR1寄存器的RE位和TE位 */

  uint16_t USART_HardwareFlowControl; /*!< 启用或禁用硬件流控模式,
  											只有在硬件流控制模式才有效,可选有⑴使能RTS、⑵使能CTS、⑶同时使能RTS和CTS、⑷不使能硬件流 */
} USART_InitTypeDef;

stm32f10x.usart.h部分参数的声明如下:

/** @defgroup USART_Word_Length 
  * @{
  */ 
  
#define USART_WordLength_8b                  ((uint16_t)0x0000)
#define USART_WordLength_9b                  ((uint16_t)0x1000)
                                    
#define IS_USART_WORD_LENGTH(LENGTH) (((LENGTH) == USART_WordLength_8b) || \
                                      ((LENGTH) == USART_WordLength_9b))
/**
  * @}
  */ 

/** @defgroup USART_Stop_Bits 
  * @{
  */ 
  
#define USART_StopBits_1                     ((uint16_t)0x0000)
#define USART_StopBits_0_5                   ((uint16_t)0x1000)
#define USART_StopBits_2                     ((uint16_t)0x2000)
#define USART_StopBits_1_5                   ((uint16_t)0x3000)
#define IS_USART_STOPBITS(STOPBITS) (((STOPBITS) == USART_StopBits_1) || \
                                     ((STOPBITS) == USART_StopBits_0_5) || \
                                     ((STOPBITS) == USART_StopBits_2) || \
                                     ((STOPBITS) == USART_StopBits_1_5))
/**
  * @}
  */ 

/** @defgroup USART_Parity 
  * @{
  */ 
  
#define USART_Parity_No                      ((uint16_t)0x0000)
#define USART_Parity_Even                    ((uint16_t)0x0400)
#define USART_Parity_Odd                     ((uint16_t)0x0600) 
#define IS_USART_PARITY(PARITY) (((PARITY) == USART_Parity_No) || \
                                 ((PARITY) == USART_Parity_Even) || \
                                 ((PARITY) == USART_Parity_Odd))
/**
  * @}
  */ 

/** @defgroup USART_Mode 
  * @{
  */ 
  
#define USART_Mode_Rx                        ((uint16_t)0x0004)
#define USART_Mode_Tx                        ((uint16_t)0x0008)
#define IS_USART_MODE(MODE) ((((MODE) & (uint16_t)0xFFF3) == 0x00) && ((MODE) != (uint16_t)0x00))
/**
  * @}
  */ 

/** @defgroup USART_Hardware_Flow_Control 
  * @{
  */ 
#define USART_HardwareFlowControl_None       ((uint16_t)0x0000)
#define USART_HardwareFlowControl_RTS        ((uint16_t)0x0100)
#define USART_HardwareFlowControl_CTS        ((uint16_t)0x0200)
#define USART_HardwareFlowControl_RTS_CTS    ((uint16_t)0x0300)
#define IS_USART_HARDWARE_FLOW_CONTROL(CONTROL)\
                              (((CONTROL) == USART_HardwareFlowControl_None) || \
                               ((CONTROL) == USART_HardwareFlowControl_RTS) || \
                               ((CONTROL) == USART_HardwareFlowControl_CTS) || \
                               ((CONTROL) == USART_HardwareFlowControl_RTS_CTS))
/**
  * @}
  */ 

这里注意,上述声明中并没有关于USART_BaudRate的声明,波特率一般设置位2400、9600、115200等,波特率需要自己进行声明;

②、USART时钟初始化结构体

typedef struct
{

  uint16_t USART_Clock;   /*!< 同步模式下,指定USART时钟是启用还是禁用 */

  uint16_t USART_CPOL;    /*!< 同步模式下,指定时钟极性,高电平或低电平 */

  uint16_t USART_CPHA;    /*!< 同步模式下,指定时钟相位 */

  uint16_t USART_LastBit; /*!< 指定与最后传输数据位(MSB)对应的时钟脉冲是否必须以同步模式在SCLK引脚上输出 */
} USART_ClockInitTypeDef;

这部分内容可以参考《野火资料》中的解释:
   USART_Clock:同步模式下SCLK引脚上时钟输出使能控制,可选禁止时钟输出(USART_Clock_Disable)或开启时钟输出(USART_Clock_Enable);如果使用同步模式发送,一般都需要开启时钟。它设定USART_CR2寄存器的CLKEN位的值。
   USART_CPOL:同步模式下SCLK引脚上输出时钟极性设置,可设置在空闲时SCLK引脚为低电平(USART_CPOL_Low)或高电平(USART_CPOL_High)。它设定 USART_CR2寄存器的 CPOL 位的值。
   USART_CPHA:同步模式下 SCLK引脚上输出时钟相位设置,可设置在时钟第一个变化沿捕获数据(USART_CPHA_1Edge)或在时钟第二个变化沿捕获数据。它设定USART_CR2寄存器的CPHA位的值。USART_CPHA与USART_CPOL配合使用可以获得多种模式时钟关系。
   USART_LastBit:选择在发送最后一个数据位的时候时钟脉冲是否在SCLK引脚输 出 , 可 以是不输出脉冲(USART_LastBit_Disable) 、 输出脉冲(USART_LastBit_Enable)。它设定USART_CR2寄存器的LBCL位的值。
stm32f10x.usart.h中部分声明如下:

/** @defgroup USART_Clock 
  * @{
  */ 
#define USART_Clock_Disable                  ((uint16_t)0x0000)
#define USART_Clock_Enable                   ((uint16_t)0x0800)
#define IS_USART_CLOCK(CLOCK) (((CLOCK) == USART_Clock_Disable) || \
                               ((CLOCK) == USART_Clock_Enable))
/**
  * @}
  */ 

/** @defgroup USART_Clock_Polarity 
  * @{
  */
  
#define USART_CPOL_Low                       ((uint16_t)0x0000)
#define USART_CPOL_High                      ((uint16_t)0x0400)
#define IS_USART_CPOL(CPOL) (((CPOL) == USART_CPOL_Low) || ((CPOL) == USART_CPOL_High))

/**
  * @}
  */ 

/** @defgroup USART_Clock_Phase
  * @{
  */

#define USART_CPHA_1Edge                     ((uint16_t)0x0000)
#define USART_CPHA_2Edge                     ((uint16_t)0x0200)
#define IS_USART_CPHA(CPHA) (((CPHA) == USART_CPHA_1Edge) || ((CPHA) == USART_CPHA_2Edge))

/**
  * @}
  */

/** @defgroup USART_Last_Bit
  * @{
  */

#define USART_LastBit_Disable                ((uint16_t)0x0000)
#define USART_LastBit_Enable                 ((uint16_t)0x0100)
#define IS_USART_LASTBIT(LASTBIT) (((LASTBIT) == USART_LastBit_Disable) || \
                                   ((LASTBIT) == USART_LastBit_Enable))
/**
  * @}
  */ 

③、部分USART函数简介

下方函数中的USARTx均可以设置为USART1, USART2, USART3, UART4 or UART5,下面是部分函数的简单解释,详情请观看相关固件库文件:

void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
 用于初始化USART,需要根据情况通过结构体USART_InitTypeDef中的参数进行设置。

void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);
  用于使能USART中断,本文中选择使能USART_IT_RXNE(接收中断)。

void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
  用于使能USART,第二个形参只有ENABLE和DISABLE两个选项。

void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
  用于发送数据,其中形参Data是要传输的数据。

FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
 用于检查是否设置了指定的USART标志,可以用于判断USART是否发送数据完成,形参USART_FLAG可以设置的参数比较多,在本文中基本只用以下三种:
   USART_FLAG_TXE:传输数据寄存器空标志,用于检测是否成功发送了一个字节
   USART_FLAG_TC:传输完成标志,用于检测是否成功发送了多个字节
   USART_FLAG_RXNE:接收数据寄存器不为空标志
 注意,该函数是读取USART_SR寄存器相应位的状态的,对于上述第三个标志位,当读取它们的状态后,它们将被清0(这部分内容需要熟读官方对于寄存器的描述,里面有讲解(或者看上面第三大点的第1小点也可以,那部分内容截图自芯片参考手册))

uint16_t USART_ReceiveData(USART_TypeDef* USARTx);
 用于接收数据。

void USART_DeInit(USART_TypeDef* USARTx);
 用于USART的复位,可将USART各寄存器恢复到默认状态。

四、程序

 本文中的程序需要使用串口调试助手的辅助,各位可以找一个串口调试助手然后按照下述方式进行设置:
  识别开发板串口并正确选择端口;
  波特率选择115200(本文中将使用这个波特率);
  校验位选择None(即不需要校验位);
  数据位选择8位;
  停止位选择1位;
  最后根据情况选择打开或者关闭16进制显示或发送。

1、程序设计步骤简介

 本次程序涉及到的内容较多,这里统一展示程序思路,在后续的程序中直接写到一起进行展示:
下面的程序会涉及到寄存器USART_SR种的TXE、TC、RXNE三个位,这三个标志位默认为0,当完成发送或接收后将被置1,此时可以使用下面的方式清零:
对TC位(两种方式):
读寄存器USART_SR,然后写寄存器USART_DR
与USART相关的初始化:
  ●复位USART,使用USART_DeInit();(这一步在本文的程序中不写也可以)
  ●打开需要用到的GPIO和USART时钟
  ●初始化接收和发送端口,这个是对GPIO的初始化
  ●初始化USART,使用USART_Init();
  ●初始化NVIC中断,使用NVIC_Init();(做接收实验时会用到中断,所以这里进行初始化)
  ●使能串口接收中断,使用USART_ITConfig();
  ●使能串口,使用USART_Cmd();
  注意:需要声明或者定义的要进行声明及定义,如波特率的声明、部分变量的定义等,另外上述步骤涉及到了一些函数,这里简写了的函数在上面有简单介绍过(除了NVIC的);
初始化后,各实验实现步骤:
发送一个字节:
  自定义一个函数,设置2个形参:一个串口,一个数据(8位):
   ●使用USART_SendData();,用于发送数据
   ●判断标志位,检验是否发送完成,使用USART_GetFlagStatus();,判断USART_FLAG_TXE是否被置位,如果被置位则发送完毕
发送两个字节:
  自定义一个函数,设置2个形参:一个串口,一个数据(16位):
   ●定义两个变量,用于存放需要发送数据的高低位
   ●将需要发送数据的高8位和低8位分别存放到两个变量中
   ●首先发送高8位数据,并检验标志位(同发送一个字节)
   ●然后将低8位数据发送,并检验标志位(同发送一个字节)
发送一个数据位数为8的数组:
  自定义一个函数,设置3个形参:一个串口,一个指针(8位,用于表示数组),一个数据个数(8位):
   ●自定义一个变量,用于后面的循环
   ●使用for循环,循环中设定自定义的变量从0开始且小于数组的数据个数(自加),以此设定循环次数(for循环的用法)
   ●在for循环中,使用上面发送一个字节的函数进行数据的发送,注意此时该函数中的数据其实是数组
   ●判断标志位,检验是否发送完成,使用USART_GetFlagStatus();,判断USART_FLAG_TC是否被置位,如果被置位则发送完毕
发送一个字符串:
  自定义一个函数,设置两个参数:一个串口,一个指针(8位):
   ●自定义一个变量,并初始化变量为0,用于后续的循环
   ●使用do..while()循环,使用上面发送一个字节的函数进行数据的发送(注意此时该函数中的数据是指针),自定义变量自加,while中判断发送是否检测到'\0',检测到则发送结束(do..while()循环的用法)
   ●判断标志位(同发送数组),确认是否发送完成
接收一个字节,并控制LED灯
  自定义一个函数,函数名需要根据startup_stm32f10x_hd.s中264~323行进行命名,无需形参
   ●自定义一个变量,用于存放接收到的数据
   ●判断是否接收到,如果接收到则将收到的数据赋值给自定义的变量,并将其发送出去
   ●switch语句,用于控制LED灯,通过判断自定义的变量来控制LED灯的亮灭

2、程序

 本文所有程序都在下面了,基本都是按照上面的步骤来写的,这里就不按照实验分开来写了,直接全都展示出来,基本都有注释,想看哪个实验,就把其他的屏蔽就行,最终效果就不展示了,主要是因为部分实验需要16进制显示,但有部分又不需要,放在一起展示不便。
 另外在下面bsp_usart.c中,最后的两个函数其实是c语言的内容,这里将其重新定义出来只是方便在程序中调用printfgetchar等c语言函数。

①、发送程序

程序10-1:bsp_usart.c

#include "bsp_usart.h"

static void NVIC_Config(void)
{
	NVIC_InitTypeDef NVIC_InitStruct;
	//设置优先级分组
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	NVIC_InitStruct.NVIC_IRQChannel = DEBUG_USART_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	//初始化NVIC
	NVIC_Init(&NVIC_InitStruct);
}

void Usart_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	USART_InitTypeDef USART_InitStruct;
	//复位USART
	//USART_DeInit(DEBUG_USARTx);
	//打开GPIO时钟
	DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
	//打开USART时钟
	DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);
	//初始化TX端口,推挽复用输出
	GPIO_InitStruct.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(DEBUG_USART_TX_GPIO_PROT, &GPIO_InitStruct);
	//初始化RX端口,浮空输入
	GPIO_InitStruct.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(DEBUG_USART_TX_GPIO_PROT, &GPIO_InitStruct);
	//配置串口
	//设定串口波特率
	USART_InitStruct.USART_BaudRate = DEBUG_USART_BaudRate;
	//设定传输的数据的字长
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;
	//设定停止位
	USART_InitStruct.USART_StopBits = USART_StopBits_1;
	//设定校验位
	USART_InitStruct.USART_Parity = USART_Parity_No;
	//设定工作模式,收发一起
	USART_InitStruct.USART_Mode = USART_Mode_Tx|USART_Mode_Rx;
	//初始化串口,完成配置
	USART_Init(DEBUG_USARTx, &USART_InitStruct);
	//NVIC中断初始化
	NVIC_Config();
	//使能串口接收中断
	USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);
	//使能串口
	USART_Cmd(DEBUG_USARTx, ENABLE);
	
}

//发送一个字节
void Usart_SendByte(USART_TypeDef* pUSARTx,uint8_t data)
{
	//发送数据
	USART_SendData(pUSARTx, data);
	//检验是否发送完毕,发送完毕标志位置1
	while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}

//发送两个字节
void Usart_SendHalfWord(USART_TypeDef* pUSARTx,uint16_t data)
{
	uint8_t data_h,data_l;
	data_h = (data&0xFF00) >> 8;	//取高8位
	data_l = data&0xFF;						//取低8位
	//发送高8位数据
	USART_SendData(pUSARTx, data_h);
	//检验是否发送完毕,发送完毕标志位置1
	while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
	//发送低8位数据
	USART_SendData(pUSARTx, data_l);
	//检验是否发送完毕,发送完毕标志位置1
	while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}

//发送一个8位数据的数组
void Usart_SendArray(USART_TypeDef* pUSARTx,uint8_t *array,uint8_t num)
{
	uint8_t i;
	for(i=0;i<num;i++)
	{
		Usart_SendByte(pUSARTx, array[i]);//一个字节一个字节的发送,而不是直接调用发送函数
	}
	while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TC) == RESET);
}

//发送一个字符串
void Usart_SendStr(USART_TypeDef* pUSARTx,uint8_t *str)
{
	uint8_t i=0;
	do
	{
		Usart_SendByte(pUSARTx, *(str+i));
		i++;
	}while(*(str+i) != '\0');	//当没检测到\0时则一直发送
	while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TC) == RESET);
}

//重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
		/* 发送一个字节数据到串口 */
		USART_SendData(DEBUG_USARTx, (uint8_t) ch);
		
		/* 等待发送完毕 */
		while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);		
	
		return (ch);
}

//重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{
		/* 等待串口输入数据 */
		while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);

		return (int)USART_ReceiveData(DEBUG_USARTx);
}

程序10-2:bsp_usart.h

#ifndef __BSP_USART_H
#define __BSP_USART_H

#include "stm32f10x.h"
#include "stdio.h"

//对USART相关内容进行声明
#define DEBUG_USARTx 							USART1
#define DEBUG_USART_BaudRate 					115200	//波特率定义为115200
//USART时钟声明
#define DEBUG_USART_CLK    						RCC_APB2Periph_USART1
#define DEBUG_USART_APBxClkCmd    				RCC_APB2PeriphClockCmd
//串口NVIC中断声明
#define DEBUG_USART_IRQn    					USART1_IRQn
#define DEBUG_USART_IRQHandler					USART1_IRQHandler	//这个要看startup_stm32f10x_hd.s中264~323行
//GPIO端口声明,TX和RX
#define DEBUG_USART_TX_GPIO_PROT 				GPIOA
#define DEBUG_USART_TX_GPIO_PIN					GPIO_Pin_9
#define DEBUG_USART_RX_GPIO_PROT 				GPIOA
#define DEBUG_USART_RX_GPIO_PIN 				GPIO_Pin_10
//GPIO时钟声明
#define DEBUG_USART_GPIO_CLK    				RCC_APB2Periph_GPIOA
#define DEBUG_USART_GPIO_APBxClkCmd    			RCC_APB2PeriphClockCmd

void Usart_Config(void);
void Usart_SendByte(USART_TypeDef* pUSARTx,uint8_t data);
void Usart_SendHalfWord(USART_TypeDef* pUSARTx,uint16_t data);
void Usart_SendArray(USART_TypeDef* pUSARTx,uint8_t *array,uint8_t num);
void Usart_SendStr(USART_TypeDef* pUSARTx,uint8_t *str);

#endif /*__BSP_USART_H*/

程序10-3:main.c

#include "stm32f10x.h"   // 相当于51单片机中的  #include 
#include "bsp_led.h"
#include "bsp_usart.h"

uint8_t uRecData;

int main(void)
{// 来到这里的时候,系统的时钟已经被配置成72M。
	uint8_t a[10]={1,2,3,4,5,6,7,8,9,10};	//定义一个数组
	uint8_t ch;
	//LED_GPIO_Config();	//GPIO初始化
	Usart_Config();	//串口初始化
	LED_GPIO_Config();//LED初始化
	//使用printf输出
	printf("这是正在进行的串口测试:");
	//发送一个字节
	Usart_SendByte(DEBUG_USARTx,100);
	printf("\n");	//回车,后面内容显示到下一行
	//发送两个字节
	Usart_SendHalfWord(DEBUG_USARTx,0xAE86);
	printf("\n");
	//发送一个8位数据的数组
	Usart_SendArray(DEBUG_USARTx,a,10);
	printf("\n");
	//发送一个字符串
	Usart_SendStr(DEBUG_USARTx,"测试发送字符串\n");
	while (1)
	{//这个while循环没啥用,可以直接屏蔽
	}
}

②、接收程序(接收并控制LED灯)

程序10-4:stm32f10x_it.c
在原本的程序中加入以下程序:

#include "bsp_usart.h"
#include "bsp_led.h"

void DEBUG_USART_IRQHandler(void)
{
	uint8_t uRecData;
	if(USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) != RESET)
	{
		uRecData = USART_ReceiveData(DEBUG_USARTx);
		USART_SendData(DEBUG_USARTx, uRecData);
		switch(uRecData)
		{
			case '1':LED_GREEN;break;
			case '2':LED_BLUE;break;
			case '3':LED_RED;break;
			default:LED_RGBOFF;break;
		}
	}
}

③、LED初始化程序

程序10-5:bsp_led.c

#include "bsp_led.h"

void LED_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;		//定义变量,方便赋值
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);	//打开APB2时钟,GPIO挂载在APB2
	//GPIO_PIN的部分用或将用到的管脚初始化为一个十六进制数据,原本是三个十六进制
	GPIO_InitStruct.GPIO_Pin = (LED_G_GPIO_PIN|LED_B_GPIO_PIN|LED_R_GPIO_PIN);						//设置需要用到的管脚,LED_G_GPIO_PIN看.h文件
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;				//设置输出模式为推挽输出
	GPIO_InitStruct.GPIO_Speed = LED_G_GPIO_CLK;				//设置输出速率为50MHz,LED_G_GPIO_CLK看.h文件
	
	GPIO_Init(LED_GPIO_PORT, &GPIO_InitStruct);					//加上&,方便取值	//初始化GPIO
	
	//关灯
	GPIO_SetBits(LED_GPIO_PORT,LED_G_GPIO_PIN);
	GPIO_SetBits(LED_GPIO_PORT,LED_B_GPIO_PIN);
	GPIO_SetBits(LED_GPIO_PORT,LED_R_GPIO_PIN);
}

程序10-6:bsp_led.h

#ifndef _BSP_LED_H
#define _BSP_LED_H

#include "stm32f10x.h"		//要包含固件库的.h文件

#define LED_G_GPIO_PIN						GPIO_Pin_0		//定义绿灯管脚号
#define LED_B_GPIO_PIN						GPIO_Pin_1		//定义蓝灯管脚号
#define LED_R_GPIO_PIN						GPIO_Pin_5		//定义红灯管脚号
#define LED_GPIO_PORT						GPIOB			//定义用到的GPIO
#define LED_G_GPIO_CLK						GPIO_Speed_50MHz	//定义GPIO初始化速率
//下面用到的“\”符号为续行符,其后面不能由任何东西,意为这行下面的一行跟这行是一起的,分行写看起来比较清晰
//使用标准固件库控制LED灯的亮灭
#define ON									1
#define OFF									0		
#define LED1(a)								if(a==1) \
												GPIO_ResetBits(LED_GPIO_PORT,LED_G_GPIO_PIN); \
											else \
												GPIO_SetBits(LED_GPIO_PORT,LED_G_GPIO_PIN);
#define LED2(a)								if(a==2) \
												GPIO_ResetBits(LED_GPIO_PORT,LED_B_GPIO_PIN); \
											else \
												GPIO_SetBits(LED_GPIO_PORT,LED_B_GPIO_PIN);
#define LED3(a)								if(a==3) \
												GPIO_ResetBits(LED_GPIO_PORT,LED_R_GPIO_PIN); \
											else \
												GPIO_SetBits(LED_GPIO_PORT,LED_R_GPIO_PIN);

//直接操作寄存器控制LED灯的亮灭
#define digitalHi(p,i)						{p->BSRR=i;}	//输出高电平
#define digitalLo(p,i)						{p->BRR=i;}		//输出低电平
#define digitalToggle(p,i)					{p->ODR=i;}		//输出反转电平

//定义控制IO的宏
#define LED1_TOGGLE							digitalToggle(LED_GPIO_PORT,LED_G_GPIO_PIN)
#define LED1_OFF							digitalHi(LED_GPIO_PORT,LED_G_GPIO_PIN)
#define LED1_ON								digitalLo(LED_GPIO_PORT,LED_G_GPIO_PIN)

#define LED2_TOGGLE							digitalToggle(LED_GPIO_PORT,LED_B_GPIO_PIN)
#define LED2_OFF							digitalHi(LED_GPIO_PORT,LED_B_GPIO_PIN)
#define LED2_ON								digitalLo(LED_GPIO_PORT,LED_B_GPIO_PIN)

#define LED3_TOGGLE							digitalToggle(LED_GPIO_PORT,LED_R_GPIO_PIN)
#define LED3_OFF							digitalHi(LED_GPIO_PORT,LED_R_GPIO_PIN)
#define LED3_ON								digitalLo(LED_GPIO_PORT,LED_R_GPIO_PIN)

#define LED_GREEN							LED1_ON\
											LED2_OFF\
											LED3_OFF

#define LED_BLUE							LED1_OFF\
											LED2_ON\
											LED3_OFF
																	
#define LED_RED								LED1_OFF\
											LED2_OFF\
											LED3_ON

#define LED_RGBOFF							LED1_OFF\
											LED2_OFF\
											LED3_OFF

void LED_GPIO_Config(void);		//.c文件中的函数声明

#endif	/*_BSP_LED_H*/

以上内容还有很多不足之处,笔者会进行进一步的学习,等再有新的心得会再次更新相关内容,同时如果笔者发现上述内容有错误,也会及时进行更改,感谢观看。

以上仅为笔记记录,不作教学等用途,感谢观看。

你可能感兴趣的:(STM32单片机学习笔记,笔记,单片机,stm32,学习)