打印
[其他ST产品]

STM32收发HEX数据包

[复制链接]
261|9
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
4y1b3|  楼主 | 2024-3-31 15:07 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
    在实际应用中,STM32的串口通信都是以数据包格式进行收发,这个数据包一般都包含包头和包尾,表示一个数据包。源代码在文末给出

数据包格式:
固定长度,含包头包尾



可变包长,含包头包尾



问题1:当数据包传输时,里面有数据与包头包尾重复怎么办?

1:设置限幅,包头包尾设置为数据包无法超过的16进制数

2:如果无法避免重复,那么就采用固定长度,含包头包尾的包格式

3:增加包头包尾的数据,增强包头包尾的唯一性,例如可以设置两位包头:0xFF与0xFD,判断包头时,需要同时判断第二位是否为0xFD,包尾同理。

问题2:包头包尾是否可以去掉一个?

        是可以的,包头包尾并不是全都需要,例如当采用固定包长进行数据包收发时候,这时就可以只有一个包头,当程序检测到包头后开始接收数据,收够4个字节后,置标志位,一个数据包接收完成。但是这种方法存在很大的弊端,例如当设置包头为0xFF的时候,这时传输4个0xFF的时,程序就可能分不清哪个是包头。

问题3:如何发送字节流:

        在STM32中,数据包都是以一个字节一个字节进行发送的,当需要发送16位,或32位的数据时,如何发送?例如float,double,甚至是结构体。STM32支持这种发送方式,这种16位或32位的数据类型内部都是由多个字节组成的,发送时只需要使用uint8_t指针指向它,把它当成一个字节数组发送就可以发送整个数据。

总结:
        若数据包载荷不会和包头包尾重复,那么就可以选择可变包长,相反就选择固定包长。可变包长的灵活性很强,可以选择任意的数据包长度,发送16位或32位的变量时候使用uint8_t指针指向它,就可以发送整个数据。

使用特权

评论回复
沙发
4y1b3|  楼主 | 2024-3-31 15:08 | 只看该作者
HEX数据包与文本数据包:
        HEX数据包发送的就是16进制数据,而文本数据包是将数字译码后的数据,通常以换行作为包尾,文本载荷数据是一个字符串。

        HEX数据包传输最直接,解析数据非常简单,比较适合模块发送原始数据,例如传感器等,缺点是载荷容易和包头包尾重复,文本数据包数据适合输入指令进行人机交互,例如蓝牙模块的AT指令,缺点是解析效率低,例如发送数字100,文本数据就需要占用3个字节,而HEX只需要1个字节空间。 根据实际场景选择数据。

数据包发送:
HEX数据包:
        定义字符数组进行发送

文本数据包发送:
        定义字符串进行发送

数据包接收:
        每收到一个字节,程序都会进入一次中断,因此每拿到一个字节的数据都是独立的过程,对数据包来说,主要有包头,载荷,包尾这三种数据,那么需要设计一种能记住不同状态的程序和机制,在不同状态执行不同的操作,同时还要进行状态的合理转移,这种程序设计就叫做状态机(State Machine)。

使用特权

评论回复
板凳
4y1b3|  楼主 | 2024-3-31 15:08 | 只看该作者
HEX数据包接收:
如下图状态转移图:

        首先设置状态变量s = 0;表示等待包头,当接收到数据进入中断时,中断程序进行判断状态接收到的数据是否为包头,是的话将s置1,表示接收数据,这样在下一次中断时就会进行数据接收,并且还要在程序中设置一个数组,当接收到载荷的长度后,表示数据接收完成,这时将s置2,表示等待包尾,如果没有问题,那么下一次收到的数据就是包尾数据,这时将s置0,这样就完成了一次接收数据的循环。

        如果数据和包头重复,那么就说明数据判断错误,那么接收完成后包尾的位置就可能不是FE,这时就可以进入重复等待包尾的状态,直到接收到真正的包尾,这样能预防数据和包头重复的错误。

使用特权

评论回复
地板
4y1b3|  楼主 | 2024-3-31 15:08 | 只看该作者
  真正设计串口通信时,尽量避免包头与数据重复。


使用特权

评论回复
5
4y1b3|  楼主 | 2024-3-31 15:08 | 只看该作者
文本数据包接收:
如下图状态转移图:   

        与HEX不同的是,在s = 1的时候,每次接收数据都要判断是否为\r如果是,则不接受数据并进入下一个状态等待包尾\n,这里有两个包尾,如果只有一个包尾为\r的话那么当接收到\r时可以直接回到s=0了。

电路连接:
        连接显示屏与触摸模块,显示屏的SCL在B10,SDA在B11,触摸模块的输出引脚在A1(触摸时输出高电平)。

使用特权

评论回复
6
4y1b3|  楼主 | 2024-3-31 15:09 | 只看该作者
编写代码:
Serial.c
#include "stm32f10x.h"                  // Device header
#include "DELAY.h"
#include "OLED.h"
#include "Serial.h"
uint8_t RxData;
uint8_t KeyNum;

int main() {
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
        GPIO_InitTypeDef GPIO_InitStructure;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//下拉输入IPD,上拉输入IPU;
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//这一行可以不写,在输入模式下这一行不起作用
        GPIO_Init(GPIOA,  &GPIO_InitStructure);
        OLED_Init();
        Serial_Init();
        OLED_ShowString(1, 1, "TxData:");
        OLED_ShowString(3, 1, "RxData:");

        Serial_TxPacket[0] = 0x01;
        Serial_TxPacket[1] = 0x02;
        Serial_TxPacket[2] = 0x03;
        Serial_TxPacket[3] = 0x04;

        while(1){
                if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) == 1){//按键按下,这一位为1
                        while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) == 1);//如果一直不松手那么程序就卡在这里
                        KeyNum = 1;//返回按键的值
                }
                if(KeyNum == 1) {
                        Serial_TxPacket[0] ++;
                        Serial_TxPacket[1] ++;
                        Serial_TxPacket[2] ++;
                        Serial_TxPacket[3] ++;
                       
                        Serial_SendPacket();
                       
                        OLED_ShowHexNum(2,1, Serial_TxPacket[0], 2);//RxPacket数组是同时读写的数组,
                        OLED_ShowHexNum(2,4, Serial_TxPacket[1], 2);
                        OLED_ShowHexNum(2,7, Serial_TxPacket[2], 2);
                        OLED_ShowHexNum(2,10, Serial_TxPacket[3], 2);
                        KeyNum = 0;
                }
                if(Serial_GetRxFlag() == 1) {
                        OLED_ShowHexNum(4,1, Serial_RxPacket[0], 2);//RxPacket数组是同时读写的数组,
                        //当读取速度慢的时候,后面的数据可能会刷新为下一个数据包的数据,可能会造成数据冲突
                        OLED_ShowHexNum(4,4, Serial_RxPacket[1], 2);
                        //若在这里(读取的过程中)进入中断,那么数组的数据就可能被覆盖
                        OLED_ShowHexNum(4,7, Serial_RxPacket[2], 2);
                        OLED_ShowHexNum(4,10, Serial_RxPacket[3], 2);
                }
        }
}

使用特权

评论回复
7
4y1b3|  楼主 | 2024-3-31 15:09 | 只看该作者
Serial.h
#ifndef __SERIAL_H
#define __SERIAL_H
#include <stdio.h>
extern uint8_t Serial_TxPacket[];//外部可调用,如果模块里有数组需要外部调用,一般使用Get,Set函数进行封装,使用指针传递
extern uint8_t Serial_RxPacket[];

void Serial_Init();
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,...);
uint8_t Serial_GetRxFlag();
void Serial_SendPacket();


#endif

使用特权

评论回复
8
4y1b3|  楼主 | 2024-3-31 15:09 | 只看该作者
main.c
#include "stm32f10x.h"                  // Device header
#include "DELAY.h"
#include "OLED.h"
#include "Serial.h"
uint8_t RxData;
uint8_t KeyNum;

int main() {
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
        GPIO_InitTypeDef GPIO_InitStructure;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//下拉输入IPD,上拉输入IPU;
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//这一行可以不写,在输入模式下这一行不起作用
        GPIO_Init(GPIOA,  &GPIO_InitStructure);
        OLED_Init();
        Serial_Init();
        OLED_ShowString(1, 1, "TxData:");
        OLED_ShowString(3, 1, "RxData:");

        Serial_TxPacket[0] = 0x01;
        Serial_TxPacket[1] = 0x02;
        Serial_TxPacket[2] = 0x03;
        Serial_TxPacket[3] = 0x04;

        while(1){
                if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) == 1){//按键按下,这一位为1
                        while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) == 1);//如果一直不松手那么程序就卡在这里
                        KeyNum = 1;//返回按键的值
                }
                if(KeyNum == 1) {
                        Serial_TxPacket[0] ++;
                        Serial_TxPacket[1] ++;
                        Serial_TxPacket[2] ++;
                        Serial_TxPacket[3] ++;
                       
                        Serial_SendPacket();
                       
                        OLED_ShowHexNum(2,1, Serial_TxPacket[0], 2);//RxPacket数组是同时读写的数组,
                        OLED_ShowHexNum(2,4, Serial_TxPacket[1], 2);
                        OLED_ShowHexNum(2,7, Serial_TxPacket[2], 2);
                        OLED_ShowHexNum(2,10, Serial_TxPacket[3], 2);
                        KeyNum = 0;
                }
                if(Serial_GetRxFlag() == 1) {
                        OLED_ShowHexNum(4,1, Serial_RxPacket[0], 2);//RxPacket数组是同时读写的数组,
                        //当读取速度慢的时候,后面的数据可能会刷新为下一个数据包的数据,可能会造成数据冲突
                        OLED_ShowHexNum(4,4, Serial_RxPacket[1], 2);
                        //若在这里(读取的过程中)进入中断,那么数组的数据就可能被覆盖
                        OLED_ShowHexNum(4,7, Serial_RxPacket[2], 2);
                        OLED_ShowHexNum(4,10, Serial_RxPacket[3], 2);
                }
        }
}

使用特权

评论回复
9
4y1b3|  楼主 | 2024-3-31 15:09 | 只看该作者
程序现象:
        触摸开关触摸一次,发送一个数据包,在串口助手中,发送区域填写FF 12 34 56 78 FE发送数据,STM32接收到数据。

使用特权

评论回复
10
中国龙芯CDX| | 2024-3-31 22:50 | 只看该作者
STM32的串口通信都是以数据包格式进行收发,这个数据包一般都包含包头和包尾,表示一个数据包。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

21

主题

124

帖子

0

粉丝