【STM32】USART串口(下)
【STM32】USART(下)
- 四、串口收发HEX数据包
- 4.1 HEX数据包
- 4.2 HEX数据包接收
- 五、串口收发文本数据包
- 5.1 文本数据包
- 5.2 文本数据包接收
- 六、串口收发数据包程序
- 6.1 串口收发HEX数据包
- 6.2 串口收发文本数据包
四、串口收发HEX数据包
4.1 HEX数据包
1. 固定包长,含包头包尾
包头:通常由特定的字节(如示例中的 0xFF)来标识数据包的开始,起到 “标记起始” 的作用,让接收方能够识别一个新数据包的开端。
固定包长:每个数据包包含的数据字节数量是固定的。比如图中 “数据包 1”,从包头 0xFF 开始,后面跟着固定数量的字节(0x01、0x02、0x03、0x04),再加上包尾 0xFE,整体长度固定。这种方式的优点是接收方可以很容易地预测每个数据包的长度,便于数据的解析和处理。
包尾:由特定字节(如示例中的 0xFE)标识数据包的结束,用于确认一个数据包的完整接收。
2.可变包长,含包头包尾
包头:同样由特定字节(如示例中的 0xFF)标识数据包的开始,功能与固定包长中的包头一致。
可变包长:每个数据包包含的数据字节数量不固定。如图中 “数据包 1” 和 “数据包 2” 的长度不同,“数据包 1” 包含较多字节,“数据包 2” 包含较少字节。这种方式的灵活性更高,适用于数据内容长度不固定的场景。
包尾:也由特定字节(如示例中的 0xFE)标识数据包的结束,确保接收方能够判断每个可变长度数据包的结束位置。
实际应用举例
- 固定包长:像自动售货机定期上传库存,每次传 “饮料种类、剩余数量、价格”3 类固定数据,数据包长度固定,方便后台按固定格式快速解析,不用判断长度。
- 可变包长:如外卖平台的订单消息,订单有时是 “单点一份餐”,有时是 “多商品 + 备注”,内容长度不定,用可变包长能灵活传输不同长度的订单信息,靠包头包尾确定每个订单的起止。
4.2 HEX数据包接收
状态机是程序中用于解析这类数据包的 “状态流转逻辑”—— 通过不同状态的切换,确保准确接收、解析数据包。
状态机的三个核心状态
状态机通过 S
(状态变量)的取值,区分等待包头、接收数据、等待包尾三个阶段,每个阶段对应不同的行为:
1. 状态 S = 0
:等待包头
- 行为:程序处于“监听包头”的状态,持续检查是否收到包头字节
0xFF
。 - 流转逻辑:
- 若收到非
0xFF
的“其他数据”:保持在S = 0
状态,继续等待包头。 - 若收到
0xFF
:切换到S = 1
(接收数据)状态,开始接收数据包的有效数据。
- 若收到非
2. 状态 S = 1
:接收数据
- 行为:程序开始接收数据包的“有效数据部分”(如图中包头
0xFF
后的0x01
、0x02
等字节)。 - 流转逻辑:
- 若未收够固定数量的有效数据(图中示例是“4个数据”,需结合具体场景定义数量):保持在
S = 1
状态,继续接收数据。 - 若收够固定数量的有效数据:切换到
S = 2
(等待包尾)状态,准备验证包尾。 - 若收到非有效数据的“其他数据”:可能触发错误处理(图中未细讲,实际场景中可能重置状态或报警)。
- 若未收够固定数量的有效数据(图中示例是“4个数据”,需结合具体场景定义数量):保持在
3. 状态 S = 2
:等待包尾
- 行为:程序处于“监听包尾”的状态,等待接收包尾字节
0xFE
,以确认数据包完整。 - 流转逻辑:
- 若收到非
0xFE
的“其他数据”:可能触发错误处理(如数据包不完整,需重置状态重新接收)。 - 若收到
0xFE
:认为当前数据包接收完整且有效,可以对数据进行后续处理(如图中“陀螺仪数据”的解析、使用);之后,状态机重置为S = 0
,开始下一个数据包的接收流程。
- 若收到非
五、串口收发文本数据包
5.1 文本数据包
1. 固定包长,含包头包尾
包头:由特定字符 @ 标识,作为每个数据包的 “起始标记”,让接收方知道 “新数据包开始了”。
固定包长:每个数据包的长度固定。如图中 “数据包 1”,从包头 @ 开始,包含固定数量的内容(A、B、C),再加上包尾 \r\n(回车 + 换行,是常见的文本结束标记),整体长度一致。
优点:接收方可以 “按固定长度切割数据”,解析简单,不易出错。
包尾:由 \r\n 标识,作为每个数据包的 “结束标记”,让接收方知道 “当前数据包结束了”。
2. 可变包长,含包头包尾
包头:同样由 @ 标识,功能与 “固定包长” 的包头一致(标记数据包起始)。
可变包长:每个数据包的长度不固定。如图中 “数据包 1” 包含 A、B、C,“数据包 2” 包含 D、E,长度明显不同。
优点:灵活性高,适合 “数据内容长度不固定” 的场景(比如有时传 3 个字符,有时传 2 个字符)。
包尾:同样由 \r\n 标识,确保接收方能够判断 “可变长度的数据包何时结束”。
实际应用举例
- 固定包长:比如 “每 10 秒传一次环境传感器数据,每次传温度、湿度、气压 3 个值”,数据长度固定,用固定包长更方便。
- 可变包长:比如 “用户输入的命令,有时是短命令(如help),有时是长命令(如configure device xxx)”,数据长度不固定,用可变包长更灵活。
5.2 文本数据包接收
状态机的三个核心状态
状态机通过 S
(状态变量)的取值,区分等待包头、接收数据等待包尾、等待包尾三个阶段,每个阶段对应不同的行为:
1. 状态 S = 0
:等待包头
- 行为:程序处于“监听包头
@
”的状态,持续检查是否收到包头字符@
。 - 流转逻辑:
- 若收到非
@
的“其他数据”:保持在S = 0
状态,继续等待包头。 - 若收到
@
:切换到S = 1
(接收数据等待包尾)状态,开始接收数据包的“有效数据部分”。
- 若收到非
2. 状态 S = 1
:接收数据等待包尾
- 行为:程序开始接收数据包的“有效数据”(如图中
@
后的A
、B
、C
或D
、E
等字符),同时等待包尾的第一个字符\r
。 - 流转逻辑:
- 若收到非
\r
的“其他数据”:保持在S = 1
状态,继续接收有效数据。 - 若收到
\r
:切换到S = 2
(等待包尾)状态,准备验证包尾的第二个字符\n
。
- 若收到非
3. 状态 S = 2
:等待包尾
- 行为:程序处于“监听包尾的第二个字符
\n
”的状态,确认数据包是否完整。 - 流转逻辑:
- 若收到非
\n
的“其他数据”:可能触发错误处理(比如数据包不完整,需重置状态重新接收)。 - 若收到
\n
:认为当前数据包接收完整且有效,可以对数据进行后续处理(比如提取ABC
或DE
做业务逻辑);之后,状态机重置为S = 0
,开始下一个数据包的接收流程。
- 若收到非
六、串口收发数据包程序
6.1 串口收发HEX数据包
采用固定包长,含包头包尾,其中包头为FF,固定4字节,包尾为FE
接线图
- Serial.h 模块代码
#ifndef __SERIAL_H
#define __SERIAL_H#include <stdio.h>extern uint8_t Serial_TxPacket[];
extern uint8_t Serial_RxPacket[];void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array, uint16_t Length);
void Serial_SendString(char *String);
void Serial_SendNumber(uint32_t Number, uint8_t Length);
void Serial_Printf(char *format, ...);void Serial_SendPacket(void);
uint8_t Serial_GetRxFlag(void);#endif
- Serial.c 模块代码
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include <stdarg.h>uint8_t Serial_TxPacket[4]; //定义发送数据包数组,数据包格式:FF 01 02 03 04 FE
uint8_t Serial_RxPacket[4]; //定义接收数据包数组
uint8_t Serial_RxFlag; //定义接收数据包标志位/*** 函 数:串口初始化* 参 数:无* 返 回 值:无*/
void Serial_Init(void)
{/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启USART1的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟/*GPIO初始化*/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); //将PA9引脚初始化为复用推挽输出GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA10引脚初始化为上拉输入/*USART初始化*/USART_InitTypeDef USART_InitStructure; //定义结构体变量USART_InitStructure.USART_BaudRate = 9600; //波特率USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制,不需要USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //模式,发送模式和接收模式均选择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_Init,配置USART1/*中断输出配置*/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外设/*USART使能*/USART_Cmd(USART1, ENABLE); //使能USART1,串口开始运行
}/*** 函 数:串口发送一个字节* 参 数:Byte 要发送的一个字节* 返 回 值:无*/
void Serial_SendByte(uint8_t Byte)
{USART_SendData(USART1, Byte); //将字节数据写入数据寄存器,写入后USART自动生成时序波形while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); //等待发送完成/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}/*** 函 数:串口发送一个数组* 参 数:Array 要发送数组的首地址* 参 数:Length 要发送数组的长度* 返 回 值:无*/
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{uint16_t i;for (i = 0; i < Length; i ++) //遍历数组{Serial_SendByte(Array[i]); //依次调用Serial_SendByte发送每个字节数据}
}/*** 函 数:串口发送一个字符串* 参 数:String 要发送字符串的首地址* 返 回 值:无*/
void Serial_SendString(char *String)
{uint8_t i;for (i = 0; String[i] != '\0'; i ++)//遍历字符数组(字符串),遇到字符串结束标志位后停止{Serial_SendByte(String[i]); //依次调用Serial_SendByte发送每个字节数据}
}/*** 函 数:次方函数(内部使用)* 返 回 值:返回值等于X的Y次方*/
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{uint32_t Result = 1; //设置结果初值为1while (Y --) //执行Y次{Result *= X; //将X累乘到结果}return Result;
}/*** 函 数:串口发送数字* 参 数:Number 要发送的数字,范围:0~4294967295* 参 数:Length 要发送数字的长度,范围:0~10* 返 回 值:无*/
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发送每位数字}
}/*** 函 数:使用printf需要重定向的底层函数* 参 数:保持原始格式即可,无需变动* 返 回 值:保持原始格式即可,无需变动*/
int fputc(int ch, FILE *f)
{Serial_SendByte(ch); //将printf的底层重定向到自己的发送字节函数return ch;
}/*** 函 数:自己封装的prinf函数* 参 数:format 格式化字符串* 参 数:... 可变的参数列表* 返 回 值:无*/
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); //串口发送字符数组(字符串)
}/*** 函 数:串口发送数据包* 参 数:无* 返 回 值:无* 说 明:调用此函数后,Serial_TxPacket数组的内容将加上包头(FF)包尾(FE)后,作为数据包发送出去*/
void Serial_SendPacket(void)
{Serial_SendByte(0xFF);Serial_SendArray(Serial_TxPacket, 4);Serial_SendByte(0xFE);
}/*** 函 数:获取串口接收数据包标志位* 参 数:无* 返 回 值:串口接收数据包标志位,范围:0~1,接收到数据包后,标志位置1,读取后标志位自动清零*/
uint8_t Serial_GetRxFlag(void)
{if (Serial_RxFlag == 1) //如果标志位为1{Serial_RxFlag = 0;return 1; //则返回1,并自动清零标志位}return 0; //如果标志位为0,则返回0
}/*** 函 数: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); //清除标志位}
}
- main.c 模块代码
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "Key.h"uint8_t KeyNum; //定义用于接收按键键码的变量int main(void)
{/*模块初始化*/OLED_Init(); //OLED初始化Key_Init(); //按键初始化Serial_Init(); //串口初始化/*显示静态字符串*/OLED_ShowString(1, 1, "TxPacket");OLED_ShowString(3, 1, "RxPacket");/*设置发送数据包数组的初始值,用于测试*/Serial_TxPacket[0] = 0x01;Serial_TxPacket[1] = 0x02;Serial_TxPacket[2] = 0x03;Serial_TxPacket[3] = 0x04;while (1){KeyNum = Key_GetNum(); //获取按键键码if (KeyNum == 1) //按键1按下{Serial_TxPacket[0] ++; //测试数据自增Serial_TxPacket[1] ++;Serial_TxPacket[2] ++;Serial_TxPacket[3] ++;Serial_SendPacket(); //串口发送数据包Serial_TxPacketOLED_ShowHexNum(2, 1, Serial_TxPacket[0], 2); //显示发送的数据包OLED_ShowHexNum(2, 4, Serial_TxPacket[1], 2);OLED_ShowHexNum(2, 7, Serial_TxPacket[2], 2);OLED_ShowHexNum(2, 10, Serial_TxPacket[3], 2);}if (Serial_GetRxFlag() == 1) //如果接收到数据包{OLED_ShowHexNum(4, 1, Serial_RxPacket[0], 2); //显示接收的数据包OLED_ShowHexNum(4, 4, Serial_RxPacket[1], 2);OLED_ShowHexNum(4, 7, Serial_RxPacket[2], 2);OLED_ShowHexNum(4, 10, Serial_RxPacket[3], 2);}}
}
6.2 串口收发文本数据包
采用不固定包长,含包头包尾,其中包头为@,包尾为\r,\n
接线图
- Serial.h 模块代码
#ifndef __SERIAL_H
#define __SERIAL_H#include <stdio.h>extern char Serial_RxPacket[];
extern uint8_t Serial_RxFlag;void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array, uint16_t Length);
void Serial_SendString(char *String);
void Serial_SendNumber(uint32_t Number, uint8_t Length);
void Serial_Printf(char *format, ...);#endif
- Serial.c 模块代码
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include <stdarg.h>char Serial_RxPacket[100]; //定义接收数据包数组,数据包格式"@MSG\r\n"
uint8_t Serial_RxFlag; //定义接收数据包标志位/*** 函 数:串口初始化* 参 数:无* 返 回 值:无*/
void Serial_Init(void)
{/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启USART1的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟/*GPIO初始化*/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); //将PA9引脚初始化为复用推挽输出GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA10引脚初始化为上拉输入/*USART初始化*/USART_InitTypeDef USART_InitStructure; //定义结构体变量USART_InitStructure.USART_BaudRate = 9600; //波特率USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制,不需要USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //模式,发送模式和接收模式均选择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_Init,配置USART1/*中断输出配置*/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外设/*USART使能*/USART_Cmd(USART1, ENABLE); //使能USART1,串口开始运行
}/*** 函 数:串口发送一个字节* 参 数:Byte 要发送的一个字节* 返 回 值:无*/
void Serial_SendByte(uint8_t Byte)
{USART_SendData(USART1, Byte); //将字节数据写入数据寄存器,写入后USART自动生成时序波形while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); //等待发送完成/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}/*** 函 数:串口发送一个数组* 参 数:Array 要发送数组的首地址* 参 数:Length 要发送数组的长度* 返 回 值:无*/
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{uint16_t i;for (i = 0; i < Length; i ++) //遍历数组{Serial_SendByte(Array[i]); //依次调用Serial_SendByte发送每个字节数据}
}/*** 函 数:串口发送一个字符串* 参 数:String 要发送字符串的首地址* 返 回 值:无*/
void Serial_SendString(char *String)
{uint8_t i;for (i = 0; String[i] != '\0'; i ++)//遍历字符数组(字符串),遇到字符串结束标志位后停止{Serial_SendByte(String[i]); //依次调用Serial_SendByte发送每个字节数据}
}/*** 函 数:次方函数(内部使用)* 返 回 值:返回值等于X的Y次方*/
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{uint32_t Result = 1; //设置结果初值为1while (Y --) //执行Y次{Result *= X; //将X累乘到结果}return Result;
}/*** 函 数:串口发送数字* 参 数:Number 要发送的数字,范围:0~4294967295* 参 数:Length 要发送数字的长度,范围:0~10* 返 回 值:无*/
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发送每位数字}
}/*** 函 数:使用printf需要重定向的底层函数* 参 数:保持原始格式即可,无需变动* 返 回 值:保持原始格式即可,无需变动*/
int fputc(int ch, FILE *f)
{Serial_SendByte(ch); //将printf的底层重定向到自己的发送字节函数return ch;
}/*** 函 数:自己封装的prinf函数* 参 数:format 格式化字符串* 参 数:... 可变的参数列表* 返 回 值:无*/
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); //串口发送字符数组(字符串)
}/*** 函 数: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 == '@' && 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); //清除标志位}
}
- main.c 模块代码
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "LED.h"
#include "string.h"int main(void)
{/*模块初始化*/OLED_Init(); //OLED初始化LED_Init(); //LED初始化Serial_Init(); //串口初始化/*显示静态字符串*/OLED_ShowString(1, 1, "TxPacket");OLED_ShowString(3, 1, "RxPacket");while (1){if (Serial_RxFlag == 1) //如果接收到数据包{OLED_ShowString(4, 1, " ");OLED_ShowString(4, 1, Serial_RxPacket); //OLED清除指定位置,并显示接收到的数据包/*将收到的数据包与预设的指令对比,以此决定将要执行的操作*/if (strcmp(Serial_RxPacket, "LED_ON") == 0) //如果收到LED_ON指令{LED1_ON(); //点亮LEDSerial_SendString("LED_ON_OK\r\n"); //串口回传一个字符串LED_ON_OKOLED_ShowString(2, 1, " ");OLED_ShowString(2, 1, "LED_ON_OK"); //OLED清除指定位置,并显示LED_ON_OK}else if (strcmp(Serial_RxPacket, "LED_OFF") == 0) //如果收到LED_OFF指令{LED1_OFF(); //熄灭LEDSerial_SendString("LED_OFF_OK\r\n"); //串口回传一个字符串LED_OFF_OKOLED_ShowString(2, 1, " ");OLED_ShowString(2, 1, "LED_OFF_OK"); //OLED清除指定位置,并显示LED_OFF_OK}else //上述所有条件均不满足,即收到了未知指令{Serial_SendString("ERROR_COMMAND\r\n"); //串口回传一个字符串ERROR_COMMANDOLED_ShowString(2, 1, " ");OLED_ShowString(2, 1, "ERROR_COMMAND"); //OLED清除指定位置,并显示ERROR_COMMAND}Serial_RxFlag = 0; //处理完成后,需要将接收数据包标志位清零,否则将无法接收后续数据包}}
}
有关【STM32】USART串口(下)就到这,希望对你有所帮助,感谢观看!