目录
通信接口1 USART
串口的通信协议
硬件部分:
软件部分:
字节数据的传递:
stm32内部的USART外设
串口发送
串口发送+接收
Hex数据包
文本数据包
数据包的收发流程
串口收发Hex数据包
串口收发文本数据包
通信接口1 USART
USART----- USB转串口 -----电脑
USART是STM32内部集成的硬件外设,usb转串口是连接stm32和电脑的
usb转串口:usb协议 ch340芯片转换 输出TXD RXD(串口协议)
通信的目的:将一个设备的数据传送到另一个设备
通信协议:制定通信的规则,通信双方按照协议规则进行数据收发
USART 引脚:TX(数据发送脚)、RX(数据接收脚)
这里全双工,就是指通信双方能够同时进行双向通信
有单独时钟线,同步,接收方可在时钟信号的指引下进行采样
无单独时钟线,异步,需双方约定一个采样频率,采样位置对齐
单端通信接GND
差分信号抗干扰
在USART、I2C、SPI、CAN、USB这几个通信协议中,USART、I2C、SPI、USB都属于串口通信,而CAN虽然也用于串行通信,但其通常不被直接归类为传统的串口通信协议,而是作为一种特定的串行通信协议存在。
串口的通信协议
硬件部分:
串口:单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信
串口接线(硬件电路)
TTL电平:+3.3V或+5V表示1,0V表示0
软件部分:
发送的一个字节的数据,串口中,每一个字节都装载在一个数据帧里面
每个数据帧都由起始位、数据位和停止位组成
字节数据的传递:
TX引脚输出定时翻转的高低电平
RX引脚定时读取引脚的高低电平
每个字节的数据加上起始位、停止位、可选的校验位,打包为数据帧,依次输出在TX引脚,另一端RX引脚依次接收
stm32内部的USART外设
(按照串口协议来产生和接收高低电平信号)
是串口通信的硬件支持电路
一个字节数据 波形
STM32F103C8T6 USART资源: USART1(APB2)、 USART2(APB1)、 USART3(APB1)
所以这个跳线帽。是用来选择通信电平的,也是给CH340芯片供电的
数据显示
串口发送
接线:共地
1.开启时钟(USART,GPIO)
2.GPIO初始化,TX复用输出,RX输入(这个程序仅输出可以先只初始化一个pa9 把PA9配置为复用推挽输出,供USART1的TX使用)
3.配置USART(发送:直接开启USART并初始化)(接收:配置中断,ITConfig,NVIC,开启USART)
4.发送函数,接收函数,获取发送和接收的状态,就调用获取标志位的函数
引脚定义表,USART1的TX是PA9,RX是PA10(上拉输入,浮空输入)
\r\n实现换行
//Serial.c
void Serial_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //1
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //2
//1
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);
//2
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_Cmd(USART1, ENABLE); //使能USART1,串口开始运行
}
//发送数据(从TX引脚发送一个字节数据)/
void Serial_SendByte(uint8_t Byte)
{
USART_SendData(USART1, Byte);//将字节数据写入数据寄存器,写入后USART自动生成时序波形
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);//等待发送完成(等着置1)
//下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位//
}
//发送数组
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]);
}
}
//返回X的Y次方
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1; //设置结果初值为1
while (Y --) //执行Y次
{
Result *= X; //将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'); //依次调用Serial_SendByte发送每位数字
}
}
///
int fputc(int ch, FILE *f)
{
Serial_SendByte(ch);//将printf的底层重定向到自己的发送字节函数
return ch;
}
void Serial_Printf(char *format, ...)
{
char String[100]; //定义字符数组
va_list arg; //定义可变参数列表数据类型的变量arg
va_start(arg, format); //从format开始,接收参数列表到arg变量
vsprintf(String, format, arg); //使用vsprintf打印格式化字符串和参数列表到字符数组中
va_end(arg); //结束变量arg
Serial_SendString(String); //串口发送字符数组(字符串)
}
///
串口发送+接收
初始化GPIO PA9 PA10
上拉输入/浮空输入
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //模式,发送模式和接收模式均选择
串口接收来说,可以使用查询和中断两种方法
查询的流程是,在主函数里不断判断RXNE标志位,如果置1了,就说明收到数据了,那再调用ReceiveData,读取DR寄存器
while (1)
if (USART_GetFlagStatus (USART1, USART_FLAG_RXNE) == SET)//检查串口接收数据的标志位
{
RxData = USART_ReceiveData (USART1);
OLED_ShowHexNum(1, 1, RxData, 2);|
}
}
中断方法
开启RXNE标志位到NVIC的输出
配置NVIC
/*中断输出配置*/
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外设
中断函数接收数据
uint8_t Serial_GetRxFlag(void)
{
if (Serial_RxFlag == 1) //如果标志位为1
{
Serial_RxFlag = 0;
return 1; //则返回1,并自动清零标志位
}
return 0; //如果标志位为0,则返回0
}
uint8_t Serial_GetRxData(void)
{
return Serial_RxData; //返回接收的数据变量
}
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标志位
//读取数据寄存器会自动清除此标志位
//如果已经读取了数据寄存器,也可以不执行此代码
}
}
以上仅能支持一个字节的接收
USART串口数据包
大量数据,以数据包的形式
数据包的作用是,把一个个单独的数据给打包起来,方便我们进行多字节的数据通信
数据包分割方法:额外添加包头包尾
在连续不断的数据流中分割出数据包(抱团的部分 有特殊联系)
数据转换为字节流
载荷和包头包尾重复
Hex数据包
HEX数据包里面,数据都是以原始的字节数据本身呈现的
文本数据包
文本数据包里面。每个字节就经过了一层编码和译码
每个文本字符背后,还是一个字节的HEX数据
所以这个文本数据包,通常会以换行作为包尾,打印时一行一行显示
数据包的收发流程
发送
接收
状态机:我们需要设计一个能记住不同状态的机制,在不同状态执行不同的操作,同时还要进行状态的合理转移
串口收发Hex数据包
uint8_t Serial_TxPacket[4]; //定义发送数据包数组,数据包格式:FF 01 02 03 04 FE
//需要在.h文件声明一下(extern uint8_t Serial_TxPacket[];因为需要在main文件进行一个内容填充)
uint8_t Serial_RxPacket[4];//定义接收数据包数组(不含包头包尾)
uint8_t Serial_RxFlag;//定义 接收数据包标志位
······
//Serial_TxPacket数组的内容将加上包头(FF)包尾(FE)后,并发送出去
void Serial_SendPacket(void)
{
Serial_SendByte(0xFF);
Serial_SendArray(Serial_TxPacket, 4);
Serial_SendByte(0xFE);
}
//USART1中断函数
void USART1_IRQHandler(void)
{
static uint8_t RxState = 0; //定义表示当前状态机状态的静态变量
static uint8_t pRxPacket = 0;//定义表示当前接收数据位置的静态变量
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)//判断是否是USART1的接收事件触发的中断
{
uint8_t RxData = USART_ReceiveData(USART1); //读取数据寄存器,存放在接收的数据变量
/*使用状态机的思路,依次处理数据包的不同部分*/
//当前状态为0,接收数据包包头
if (RxState == 0)
{
if (RxData == 0xFF) //如果数据确实是包头
{
RxState = 1; //置下一个状态
pRxPacket = 0; //数据包的位置归零
}
}
//当前状态为1,接收数据包数据
else if (RxState == 1)
{
Serial_RxPacket[pRxPacket] = RxData; //将数据存入数据包数组的指定位置
pRxPacket ++; //数据包的位置自增
if (pRxPacket >= 4) //如果收够4个数据
{
RxState = 2; //置下一个状态
}
}
//当前状态为2,接收数据包包尾
else if (RxState == 2)
{
if (RxData == 0xFE) //如果数据确实是包尾部
{
RxState = 0; //状态归0
Serial_RxFlag = 1; //接收数据包标志位置1,成功接收一个数据包
}
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE); //清除标志位
}
}
static uint8_t RxState = 0; (S)
静态变量类似全局变量,函数进入只会初始化一次0,函数退出后,数据仍有效
串口收发文本数据包
void USART1_IRQHandler(void)
{
static uint8_t RxState = 0; //定义表示当前状态机状态的静态变量
static uint8_t pRxPacket = 0; //定义表示当前接收数据位置的静态变量
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) //判断是否是USART1的接收事件触发的中断
{
uint8_t RxData = USART_ReceiveData(USART1); //读取数据寄存器,存放在接收的数据变量
/*使用状态机的思路,依次处理数据包的不同部分*/
/*当前状态为0,接收数据包包头*/
if (RxState == 0)
{
if (RxData == '@' && Serial_RxFlag == 0) //如果数据确实是包头,并且上一个数据包已处理完毕
{
RxState = 1; //置下一个状态
pRxPacket = 0; //数据包的位置归零
}
}
/*当前状态为1,接收数据包数据,同时判断是否接收到了第一个包尾*/
else if (RxState == 1)
{
if (RxData == '\r') //如果收到第一个包尾
{
RxState = 2; //置下一个状态
}
else //接收到了正常的数据
{
Serial_RxPacket[pRxPacket] = RxData; //将数据存入数据包数组的指定位置
pRxPacket ++; //数据包的位置自增
}
}
/*当前状态为2,接收数据包第二个包尾*/
else if (RxState == 2)
{
if (RxData == '\n') //如果收到第二个包尾
{
RxState = 0; //状态归0
Serial_RxPacket[pRxPacket] = '\0'; //将收到的字符数据包添加一个字符串结束标志
Serial_RxFlag = 1; //接收数据包标志位置1,成功接收一个数据包
}
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE); //清除标志位
}
}