目录
通信接口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); //1RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //2//1GPIO_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); //2USART_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; //设置结果初值为1while (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; //定义可变参数列表数据类型的变量argva_start(arg, format); //从format开始,接收参数列表到arg变量vsprintf(String, format, arg); //使用vsprintf打印格式化字符串和参数列表到字符数组中va_end(arg); //结束变量argSerial_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线路的抢占优先级为1NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1NVIC_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; //置接收标志位变量为1USART_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; //状态归0Serial_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; //状态归0Serial_RxPacket[pRxPacket] = '\0'; //将收到的字符数据包添加一个字符串结束标志Serial_RxFlag = 1; //接收数据包标志位置1,成功接收一个数据包}}USART_ClearITPendingBit(USART1, USART_IT_RXNE); //清除标志位}
}