stm32—串口
1. 串口
什么是串口?
UART:Universal Asynchronous Receiver / Transmitter
通用异步收发器
USART:Universal Synchronous Asynchronous Receiver / Transmitter
通用同步异步收发器
串口是单片机中属于最常见,最简单的串行数据传输协议
串口是用来实现 模块与模块 之间的通信问题
通信?
(1) 物理媒介(硬件层面)
(2) 协议(软件层面)
模块通信:上位机 下位机模块通信一般来说,有多方(多个模块),人为把这些通信模块分为"上位机" "下位机"
上位机:把处理性能强的机子称为上位机,数据大部分处理都在上位机完成
下位机:把数据采集的终端,处理性能一般,功能单一的机子称为下位机
那什么是同步?什么是异步?通信双方有没有共同的时钟线用来作为同步时钟使用
如果有那么就是同步通信,如果没有那么就是异步通信
时钟信号可以作为通信时的同步信号,比如:
- 在时钟信号为低跳变的时候,改变信号 ---> 发送方发送数据
- 在时钟信号为高跳变的时候,采样信号 ---> 接收方接收数据
举个例子:
同步就相当于A叫B吃饭,B听到了就和A一起去吃饭,但是如果没有听到的话,A就不停的叫,直到B告诉A听到了,并和A一起去吃饭。此乃一对好基友
异步就是A叫B吃饭,叫完B一声之后就不管了,不管B有没有听到,也不管B到底去不去,A自己就去吃饭了,有点像心机Boy
所以要是你想请我吃饭,你就用同步的方式,要是我请你吃饭,我就用异步的方式
总结:同步:发送方发送数据之后,等接收方发回响应以后,才发下一个数据包的通讯方式
异步:发送方发送数据之后,不等接收方发回响应,接着发送下一个数据包的通讯方式
串口:串行数据传输协议
只需要两根数据线,就可以实现全双工串行通信
两根数据线分别为:
Tx:发送数据端,用于向对方发送数据
Rx:接收数据端,用于从对方那接收数据
比如两个设备之间要通讯的话:
设备A 设备B
Tx ----------> Rx
Rx <---------- Tx
全双工通信:两个设备的接收端和发送端是相互独立的,互不干扰的,接收数据不会干扰到发送数据,所以两个设备可以同时收/发
还有其他的通信方式:如单工(设备只能收或者发,只有一根数据线)、半双工(设备有两根数据线,但是不能够同时收/发)
串行:数据的传输只有一根”电线”,每次就只能传输1bits数据,当多个数据发送时,也只能1bit 1bit的发送比如:发送 0x5C
0101 1100
就是把这一个字节的每一个Bit一个bit接一个bit的发送出去。那么我们发送一个bit是怎么发送的呢?比如要发送一个0,我们就只需要将Tx 这根线的电平状态拉低一段时间就可以了,要发送1,就需要将Tx这根线的电平状态拉高一段时间就可以了
但是大家想一下,我的Tx这根线,就算什么都不往外发,是不是也会有一个电平状态,那么这个时候对面怎么知道你这个是不是要发送过来的数据呢?
这个时候我们就需要一个协议了,协议就是双发约定好的传输方式,串口也有协议
2. UART协议
UART Protocol 串口协议
作用:规定了串口发送和接收数据的方式,必须以帧(Frame)为单位
1帧(Frame) = 1 start bit(起始位) + 5~9bits数据位 + 0/1校验位 + (0.5 / 1 / 1.5 / 2)stop bits(停止位)
起始位:一个周期的低电平信号所以串口的数据线Tx在空闲的时候应该要永远保持高电平
5~9数据位:通信的正文,具体是发送多少bits,由双方协商
并且最先发送最低位(LSB),最后传送最高位(MSB)1个校验位:表示是否需要校验
0bit 没有校验位
1bit 有校验D0 D1 D2 D3 ...... Dn x
奇校验: 要保证前面的数据位加上校验位的1的个数是奇数个偶校验: 要保证前面的数据位加上校验位的1的个数是偶数个
比如:9bit发送0x5c (0101 1100)
奇校验:0 0 1 1 1 0 1 0 x=1
偶校验:0 0 1 1 1 0 1 0 x=0
0.5~2个停止位:高电平
具体持续多久高电平,由双方约定
0.5个周期 / 1个周期 / 1.5个周期 / 2个周期
但是由于UART异步串口,没有时钟进行同步,因此光靠帧格式传输数据还是不能准确收发,因此我们有另外一个东西来确保准确收发Baudrate波特率:UART的传输速率,决定1Frame的传输周期
常见的波特率有:9600bps(Bits per second) ,115200bps---> F = 9600 Hz T = (1 / 9600)s
---> F = 115200 Hz T = (1 / 115200)s
波特率的设置收发双方必须一致
UART协议: 帧格式 + 波特率
3. 物理层标准
串口有不同的分类:
TTL level UART : TTL电平串口Tx 数据发送端口
Rx 数据接收端口
VCC 电源端口(+),给外部模块供电,如果外部模块有电,可以不需要
GND 接地端口(-),通信双方的GND必须连接在一起,必须共地
先接GND
还有常见的分类比如:RS-232、RS-422、RS-485RS-485 <转换电路> <===相连===> TTL
不同电气标准的串口,引脚的个数也不一样,但是数据线Tx / Rx是一定存在的不同电气标准的串口的区别如下:
单端信号是指用一根线传输的信号,一根线没有参考点怎么会有信号呢?
参考点就是地。也就是说单端信号是在一根导线上传输的与地之间的电平差
差分信号指的是用两根线传输的信号,传输的是两根信号之间的电平差
4. STM32F4xx 串口控制器
STM32F4xx UART控制器的寄存器
参考<STM32F4xx参考手册.pdf>678页<USART框图>
从图来看,串口不仅有Tx和Rx两根线,还有两根用于硬件流控的RTS和CTS线,这两根线存在的意义在于,有时候发送方通过Tx往外发送数据时,如果对方还没有准备好,发送的数据将会被丢弃,所以在硬件上加两根硬件流控的信号RTS:Request To Send 请求发送信号
终端告诉对方我已经准备好了,你可以向我传输数据了
CTS:Clear To Send 清除发送信号
对方告诉终端,我要向你发送数据了
接线情况如下:
A B
RTS --------> CTS
CTS <-------- RTS
如果采用硬件流控的话,B已经准备好接收数据啦,B往A的CTS传输一个电平(请求发送信号),在A往B发送数据前,先清掉CTS表示我要向你发送数据啦
当然,并不一定需要使用RTS/CTS
RDR:Receiver Data Register 接收数据寄存器对方Tx ---> Rx -----> 接收移位寄存器 ----> RDR
CPU从RDR把接收到的数据读走(要及时读走,否则会被下一次接收的数据给覆盖掉)
TDR:Transmiterr Data Register 发送数据寄存器
CPU把要发送的数据写到 TDR ----> 发送移位寄存器 ----> Tx -----> Rx(对方)
在图的下方有一个USART_BRR(波特率发生器),主要是用来控制数据的收发速率
另外在图的中部有两个寄存器:
CR(Control Reigster):控制寄存器,用来控制串口的一些行为
SR(Status Register):状态寄存器,用来指示串口控制器的一些状态
a. 从读的角度来说
外部设备将数据往Rx引脚上发送,因为串口协议规定数据是一个bit一个bit发送的,所以每来一个bit,需要进行位移位或操作后先将其保存在接收移位寄存器中,当数据到齐之后,再将其挪入接收数据寄存器(Receiver Data Register),此时CPU就应该及时将RDR寄存器中接收到的数据及时读走,否则会被下一次发过来的数据给覆盖掉
那么我们怎么知道数据什么时候到齐该去读取了呢?
就需要用到SR寄存器中的标志位RXNE了
RXNE:Rx data Register Not Empty 接收数据寄存器非空标志
如果RXNE被设置,说明RDR中已经存入数据(从对方接收来的), CPU就可以去读取RDR以获取接收到的数据
b. 从发的角度来说我们只需将需要发送出去的数据存放至发送数据寄存器(TDR)中,发送控制器将要发送的数据自动填充到发送移位寄存器中去,在设定好的波特率影响下,将数据一个bit一个bit的通过Tx线发送出去
需要注意的是只有当一个数据发送完成后,才能接着发送下一个数据
那么我们怎么知道数据已经发送完了?
通过以下标志位来识别:
TXE:Tx data Register Empty 发送数据寄存器为空标志
如果TXE被设置(为1),说明TDR中的数据被发送出去了,此时CPU可以往TDR发送数据了
但是,并不表示数据已经发送完成,因为TDR的数据可能只是被转移到了发送移位寄存器中
TC:Transmit Complete 发送完成标志
表示发送移位寄存器中的数据,都已经从Tx引脚发送出去了
TDR(发送数据寄存器) ---> 发送移位寄存器 ---> Rx(对方)
TC == 1
表示 TXE == 1
并且 发送移位寄存器 也为空
注意:发送数据之前,必须确保TDR寄存器为空(TXE被设置),否则,上一个发送的数据可能没有发送完成,那么就会被覆盖。接收数据需要等 RXNE标志被设置,才能去接收。因此,一般使用串口中断来完成串口数据接收
另外还有一些在CR寄存器中的中断使能位:
TXEIE:TXE中断使能位,这个标志位当串口TDR为空时会触发的中断
如果TXEIE被设置为1,即当TXE=1(发送数据寄存器为空)时,就会产生一个串口中断
所以 IE -->Interrupt Enable 中断使能,控制是否产生一个串口中断
TCIE:发送完成,中断使能位
如果TCIE被设置,当串口数据都发送完成(经过Tx引脚),就会触发串口中断
RXNEIE:串口接收数据寄存器不为空 中断使能位
如果RXNEIE == 1,当RDR寄存器不为空时,就会触发串口中断
一个串口控制器,只对应一个中断,但是串口的多个标志,都可以引起串口的中断,因此,在你的串口中断处理函数中,要加以区分,以作不同的处理
5. STM32F4xx 串口代码流程
串口配置步骤:
根据原理图了解GEC_M4串口
-----> USART的Tx和Rx引脚是GPIO的功能复用而来
串口编程流程:
(1) 串口 GPIO 配置Tx / Rx 引脚都是由GPIO引脚复用的
a. 使能GPIO分组的时钟
RCC_AHB1PeriphClockCmd();
b. 初始化GPIO
GPIO_Init();===> AF 模式
c. 配置GPIO复用功能
GPIO_PinAFConfig();
比如:PA9 作为 USART1_TX
GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
(2) USART 配置
a. 使能USART时钟
RCC_APB2PeriphClockCmd(RCC_APB1Periph_USART1, ENABLE);or
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
根据具体配置哪个串口,不同的串口位于不同的总线上
b. 初始化配置USART
void USART_Init(USART_TypeDef *USARTx, USART_InitTypeDef *USART_InitStruct);@USARTx:指定串口编号USART1、USART2、USART3...@USART_InitStruct:指向串口初始化信息结构体typedef struct{uint32_t USART_BaudRate;指定串口的通信波特率,单位为bps:bits per second 是一个整数(通信双方必须一样),常见的有9600/115200等uint16_t USART_WordLength;指定数据帧数据位,也就是传输字长在STM32中传输字长 = 数据位数+ 校验位数USART_WordLength_8b 一般无校验用8bit数据位8bits数据位 + 0bit校验位7bits数据位 + 1bit校验位(奇/偶校验)USART_WordLength_9b 有校验用9bit数据位8bits数据位 + 1bit校验位(奇/偶校验)9bits数据位 + 0bit校验位uint16_t USART_StopBits;指定停止位长度USART_StopBits_0_5USART_StopBits_1 1个周期的停止位USART_StopBits_1_5USART_StopBits_2uint16_t USART_Parity;指定校验方式USART_Parity_No 不要校验USART_Parity_Even 偶校验USART_Parity_Odd 奇校验uint16_t USART_Mode;指定串口模式USART_Mode_Rx 接收模式 只接收数据USART_Mode_Tx 发送模式 只发送数据USART_Mode_Rx | USART_Mode_Tx 收发模式(全双工)uint16_t USART_HardwareFlowControl; 指定硬件控制流USART_HardwareFlowControl_None 不要硬件流控USART_HardwareFlowControl_RTS 请求发送当你准备好接收数据 接收流控制USART_HardwareFlowControl_CTS CTS当你要发送数据时通知对方 发送流控制USART_HardwareFlowControl_RTS_CTS发送和接收都用流控制} USART_InitTypeDef;(3) 中断配置
a. 中断源的控制
产生串口中断的事件或标志有很多,如:
TxE ----> 串口中断
TC ----> 串口中断
RXNE ----> 串口中断
...
这些事件需要”中断控制位使能”才能产生中断
USART_ITConfig:用来设置哪些状态会触发串口中断
void USART_ITConfig(USART_TypeDef *USARTx, uint16_t USART_IT, FunctionalState NewState);@USARTx:指定具体的串口编号USART1、USART2...@USART_IT:指定触发中断的标志USART_IT_RXNEIE RDR中存入数据后会触发中断---> CPU可以接收1字节数据USART_IT_TXEIE、USART_IT_TCIE...@NewState:ENALBE 相应的事件产生串口中断DISABLE 相应的事件不产生串口中断b. NVIC控制
串口中断使能(需要配置NVIC)配置NVIC:void NVIC_Init(NVIC_InitTypeDef *NVIC_InitStruct);
(4) 使能串口void USART_Cmd(USART_TypeDef *USARTx, FunctionalState NewState);@USARTx:指定串口编号USART1、USART2、USART3...@NewState:ENABLE 使能,开启串口DISABLE(5) 串口数据收发
一般的,在串口通信中,接收部分几乎都是由中断来完成
那么串口中断服务函数名经查中断向量表可知:void USART1_IRQHandler(void) { // 一般串口中断服务函数的实现如下// 有多个事件(TXE,TC,RXNE...)可以引起串口中断// 所以,在串口中断处理函数中一般要判断是什么事件引起的串口中断if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) { // RXNE事件产生// 去读取数据USART_ReceiveData// 清除中断标志USART_ClearITPendingBit} }获取串口中断标志: FlagStatus USART_GetFlagStatus(USART_TypeDef *USARTx, uint16_t USART_FLAG);ITStatus USART_GetITStatus(USART_TypeDef *USARTx, uint16_t USART_IT);@USARTx:指定串口,如:USART1...@USART_IT:指定串口事件,如:USART_IT_TXE、USART_IT_TC、USART_IT_RXNE...@返回值:SET :1 指定的事件产生RESET :0 指定的事件没产生清除串口中断标志: void USART_ClearITPendingBit(USART_TypeDef *USARTx, uint16_t USART_IT); void USART_ClearFlag(USART_TypeDef *USARTx, uint16_t USART_FLAG);串口接收数据:从指定的串口接收1个字节数据
uint16_t USART_ReceiveData(USART_TypeDef *USARTx);@USARTx:指定串口编号USART1、USART2、USART3...返回值:从串口接收到的数据串口发送数据:
void USART_SendData(USART_TypeDef *USARTx, uint16_t Data);@USARTx:指定串口编号USART1、USART2、USART3...@Data: 要发送的数据,1个字节注意:发送数据之前,必须确保TDR为空(TXE被设置),如果没有,则不能发送下一个字节 接收是被动的,我们并不知道什么时候有数据过来需要接收,所以在中断服务函数中接收 而发送是主动的,由我们自己决定什么时候发送数据出去,所以不用中断实现数据发送, 而需要自定义串口发送函数:// 发送一个字节 void USART1_SendByte(uint16_t data) {// 发送USART_SendData(USART1, data);// 等待发送寄存器为空事件产生while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) != SET);// 清除发送寄存器为空事件标志USART_ClearFlag(USART1, USART_FLAG_TXE); }// 发送一个字符串 void USART1_SendDatas(const char *str) {const char *s = str;while (*s) {USART1_SendByte(*s);s++;} }




