提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
今天学习了STM32的串口通信,USART串口通信的目的是将一个设备的数据传送到另一个设备,扩展硬件系统。在学习串口通信之前我们还需要了解一点通信原理的知识。
一、通信基础知识
1、数据通信方式
按数据通信方式分类,可分为串行通信跟并行通信两种。串行和并行的对比如下图所示:
串行通信的基本特征是数据逐位顺序依次传输,优点是传输线少、布线成本低、灵活度高等优点,一般用于近距离人机交互,特殊处理后也可以用于远距离,缺点就是传输速率低。
而并行通信是数据各位可以通过多条线同时传输,优点是传输速率高,缺点就是布线成本高,抗干扰能力差因而适用于短距离、高速率的通信。
2、数据传输方向
根据数据传输方向,通信又可以分为全双工、半双工和单工通信。全双工、半双工、单工通信的比较如下图所示:
单工是指数据传输仅能沿一个方向,不能实现反方向传输,如校园广播。
半双工是指数据传输可以沿着两个方向,但是需要分时进行,如对讲机。
全双工是指数据可以同时进行双向传输,日常的打电话属于这种情形。
这里注意全双工和半双工通信的区别:半双工通信是共用一条线路实现双向通信,而全双
工是利用两条线路,一条用于发送数据,另一条用于接收数据。
3、数据同步方式
根据数据同步方式,通信又可分为同步通信和异步通信。同步通信和异步通信比较如下图所示:
同步通信要求通信双方共用同一时钟信号,在总线上保持统一的时序和周期完成信息传输。
优点:可以实现高速率、大容量的数据传输,以及点对多点传输。
缺点:要求发送时钟和接收时钟保持严格同步,收发双方时钟允许的误差较小,同时硬件复杂。
异步通信不需要时钟信号,而是在数据信号中加入开始位和停止位等一些同步信号,以便使接收端能够正确地将每一个字符接收下来,某些通信中还需要双方约定传输速率。
优点:没有时钟信号硬件简单,双方时钟可允许一定误差。
缺点:通信速率较低,只适用点对点传输。
4、通信速率
在数字通信系统中,通信速率(传输速率)指数据在信道中传输的速度,它分为两种:传信率和传码率。
传信率:每秒钟传输的信息量,即每秒钟传输的二进制位数,单位为 bit/s(即比特每秒),因而又称为比特率。
传码率:每秒钟传输的码元个数,单位为 Baud(即波特每秒),因而又称为波特率。
二、串口通信协议简介
串口通信是一种设备间常用的串行通信方式,串口按位(bit)发送和接收字节。尽管比特字节(byte)的串行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。串口通信协议是指规定了数据包的内容,内容包含了起始位、主体数据、校验位及停止位,双方需要约定一致的数据包格式才能正常收发数据的有关规范。在串口通信中,常用的协议包括RS-232、RS-422 和 RS-485 等
串口通信的数据包由发送设备的 TXD 接口传输到接收设备的 RXD 接口。在串口通信的协议层中,规定了数据包的内容,它由起始位、主体数据、校验位以及停止位组成,通讯双方的数据包格式要约定一致才能正常收发数据,其组成如图所示。低电平代表启动位,高电平代表停止位。
1、波特率
是串口异步通信,异步通信是不需要时钟信号的,但是这里需要我们约定好两个设备的波特率。波特率表示每秒钟传送的码元符号的个数,所以它决定了数据帧里面每一个位的时间长度。两个要通信的设备的波特率一定要设置相同。
2、数据帧格式
数据帧格式需要我们提前约定好,串口通信的数据帧包括起始位、停止位、有效数据位以及校验位。
3、起始位和停止位
串口通信的一个数据帧是从起始位开始,直到停止位。数据帧中的起始位是由一个逻辑 0 的数据位表示,而数据帧的停止位可以是 0.5、1、1.5 或 2 个逻辑 1 的数据位表示,只要双方约定一致即可。
4、有效数据位
数据帧的起始位之后,就接着是数据位,也称有效数据位,这就是我们真正需要的数据,有效数据位通常会被约定为 5、6、7 或者 8 个位长。有效数据位是低位(LSB)在前,高位(MSB)在后。
三、STM32F1的串口简介
1、串口介绍
STM32F1 的串口分为两种:USART(即通用同步异步收发器)和 UART(即通用异步收发器)。UART 是在 USART 基础上裁剪掉了同步通信功能,只剩下异步通信功能。简单区分同步和异步就是看通信时需不需要对外提供时钟输出,我们平时用串口通信基本都是异步通信。STM32F1 有 3 个 USART 和 2 个 UART,其中 USART1 的时钟源来于 APB2 时钟,其最大频率为 72MHz,其他 4 个串口的时钟源可以来于 APB1 时钟,其最大频率为 36MHz。
2、USART框图
3、USART信号引脚
TX:发送数据输出引脚
RX:接收数据输入引脚
SCLK:发送器时钟输出,适用于同步传输
SW_RX:数据接收引脚,属于内部引脚,用于智能卡模式
IrDA_RDI:IrDA 模式下的数据输入
IrDA_TDO:IrDA 模式下的数据输出
nRTS:发送请求,若是低电平,表示 USART 准备好接收数据
nCTS:清除发送,若是高电平,在当前数据传输结束时阻断下一次的数据发送。
4、数据寄存器
USART_DR 包含了已发送或接收到的数据。由于它本身就是两个寄存器组成的,一个专门给发送用的(TDR),一个专门给接收用的(RDR),该寄存器具备读和写的功能。当向该寄存器写数据的时候,串口就会自动发送,当收到数据的时候,也是存在该寄存器内。
5、串口初始化
第⼀步,开启时钟,把需要⽤的 USART 和 GPIO 的时钟打开
第⼆步, GPIO 初始化,把 TX 配置成复⽤输出, RX 配置成输⼊
第三步,配置 USART ,直接⽤⼀个结构体,就可以配置好所有参数
第四步,如果只需要发送的功能就直接开启 USART ,如果需要接收的功能,还需要再配置中断,在开启 USART 之前,加上 ITConfig 和 NVIC 的代码。
void USART_DeInit(USART_TypeDef* USARTx);//回复缺省值函数
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);//开启串口函数
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT,FunctionalState NewState);//开启串口中断函数
void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq,FunctionalState NewState);//开启USART到DMA的触发通道函数
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);//发送数据函数
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);//接收数据
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_tUSART_FLAG);//在中断函数外获取标志位函数
void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG);//在中断函数外清除标志位函数
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);//在中断函数内获取标志位函数
void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_tUSART_IT);//在中断函数内清除标志位函数
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include <stdarg.h>
void Serial_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
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);
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;//停⽌位⼀位
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8位1字
节
USART_Init(USART1, &USART_InitStructure);
USART_Cmd(USART1, ENABLE);//开启串⼝
}
void Serial_SendByte(uint8_t Byte)//发送字符
{
USART_SendData(USART1, Byte);
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]);
}
}
void Serial_SendString(char *String)//发送字符串
{
uint8_t i;
for (i = 0; String[i] != '\0'; i ++)
{
Serial_SendByte(String[i]);
}
}
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1;
while (Y --)
{
Result *= X;
}
return Result;
}
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');
}
}
int fputc(int ch, FILE *f)//重定向printf
{
Serial_SendByte(ch);
return ch;
}
void Serial_Printf(char *format, ...)//重定向printf多串⼝使⽤
{
char String[100];
va_list arg;
va_start(arg, format);
vsprintf(String, format, arg);
va_end(arg);
Serial_SendString(String);
}
6、串口收发数据包
数据包的作⽤是:把⼀个个单独的数据给打包起来,⽅便进⾏多字节的数据通信.因为接收⽅可能从任意的位置接收,所以可能出现数据错位的现象,我们需要⼀种⽅式把数据进⾏分割,数据分割开来,分割成⼀批批数据包,接收的时候就知道了对应的数据. 数据包的任务,就是把同⼀批的数据进⾏打包和分割。
串口数据包,通常使用的是额外添加包头包尾的这种方式。防⽌数据包包头包尾和数据重复的⽅法,第⼀种,限制载荷数据的范围,在发送的时候对数据进⾏限幅,第⼆种,尽量使⽤固定长度的数据包,第三种,增加包头包尾的数量,并且让它尽量呈现出载荷数据出现不了的状态。
文本数据包
数据包的收发流程:
void Serial_SendPacket(void)
{
Serial_SendByte(0xFF);
Serial_SendArray(Serial_TxPacket, 4);
Serial_SendByte(0xFE);
}
void USART1_IRQHandler(void)
{
static uint8_t RxState = 0;//状态机变量
static uint8_t pRxPacket = 0;//指示接收个数
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
{
uint8_t RxData = USART_ReceiveData(USART1);
if (RxState == 0)
{
if (RxData == 0xFF)
{
RxState = 1;
pRxPacket = 0;
}
}
else if (RxState == 1)
{
Serial_RxPacket[pRxPacket] = RxData;
pRxPacket ++;
if (pRxPacket >= 4)
{
RxState = 2;
}
}
else if (RxState == 2)
{
if (RxData == 0xFE)
{
RxState = 0;
Serial_RxFlag = 1;
}
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}