您的位置:首页 > 健康 > 养生 > 云建站自动建站系统源码_金耀网站建设_seo交流博客_网络营销的应用研究论文

云建站自动建站系统源码_金耀网站建设_seo交流博客_网络营销的应用研究论文

2025/1/9 13:37:06 来源:https://blog.csdn.net/qq_19395823/article/details/144944714  浏览:    关键词:云建站自动建站系统源码_金耀网站建设_seo交流博客_网络营销的应用研究论文
云建站自动建站系统源码_金耀网站建设_seo交流博客_网络营销的应用研究论文

1. 数据包 

把一个个单独的数据打包,方便进行多字节的数据通信。

例如陀螺仪传感器,需要用串口发送数据到STM32。对于陀螺仪的数据,假设X、Y、Z轴各为一个字节,共计3个数据需要连续不断地发送。如果像XYZXYZ...连续发送时,接收方无法区分X、Y和Z轴的数据,因为接收方可能会从任意位置开始接收,所以会出现数据错位的现象。因此,需要将数据进行分割,把XYZ一批数据分开,分成一个个数据包。

数据包分割方法有多种,串口数据包通常使用的是额外添加包头包尾的方式。

1.1 HEX数据包 

包头包尾和数据载荷重复——这里定义FF为包头,FE为包尾,如果传输的数据本身就是FF和FE,有如下几种解决方法:

  1. 限制载荷数据的范围,避免和包头包尾重复;
  2. 如果无法避免载荷数据和包头包尾重复,尽量使用固定长度的数据包;
  3. 增加包头包尾的数量,并且使之尽量呈现出载荷数据出现不了的状态,比如使用FF、FE作为包头,FD、FC作为包尾

包头包尾并不是全部都需要的,比如可以只要一个包头,把包尾删掉。这样数据包的格式就是,一个包头FF加4个数据。当检测到FF开始接收,收够4个字节后,置标志位,一个数据包接收完成。不过这种情况下载荷和包头重复的问题会更加严重。

固定包长和可变包长的选择——对应HEX数据包来说,如果载荷会出现和包头包尾重复的情况,最好选择固定包长,这样可以避免接收错误。 

1.2 文本数据包

在HEX数据包中,数据均以原始字节数据呈现。在文本数据包中,每个字节经过了一层编码和译码,最终表现出来的就是文本格式。 

1.3 固定包长HEX数据包的接收

根据之前的代码,我们知道,每收到一个字节,程序都会进一遍中断,在中断函数中获取到这一字节,但获取之后就需要退出中断,所以,每获取到一个数据都是一个独立的过程。而对于数据包来说,很明显它具有前后关联性,包头之后是数据,数据之后是包尾。对于包头、数据和包尾这3种状态,需要有不同的处理逻辑,所以需要在程序中设计一个能记住不同状态的机制,在不同状态执行不同的操作,同时还要进行状态的合理转移,这种程序设计思维就叫做“状态机”。 

这里使用状态机的方法来接收一个数据包。对于上面这样一个固定包长HEX数据包来说,可以定义三个状态“等待包头、接收数据和等待包尾”,每个状态需要用一个变量标志,可以标志三个状态依次为"S=0、S=1和S=2"。执行的流程是

  1. 最开始S=0,收到一个数据后进入中断,根据S=0进入第一个状态的程序,判断数据是否为包头FF。如果是FF,则代表收到包头,之后置S=1,退出中断,结束。 这样下次再进入中断,根据S=1,就可以进行接收数据的程序。如果在第一个状态收到的不是FF,就证明数据包没有对齐,需要等待数据包包头的出现,这时状态仍然为0,下次进入中断还是判断包头的逻辑,直到出现FF才能转到下一个状态。
  2. 收到FF进入接收数据状态S=1。此时再收到数据就直接保存在数组中,另外再使用一个变量,用来记录接收数据的个数。如果未收够4个数据,就一直是接收状态,如果收够了,就置S=2,下次中断时即可进入下一个状态。
  3. 最后一个状态为等待包尾,判断数据是否是FE。正常情况下应该为FE,这样就可以置S=0,回到最初的状态,开始下一个轮回。也有可能这个数据不是FE,比如数据和包头重复,导致包头位置判断错误,此时就可以进入重复等待包尾的状态,直到接收到真正的包尾。

1.4 可变包长文本数据包的接收

同样也是利用状态机,定义3个状态。

  1. 等待包头S=0。判断收到的是否为规定的@符号。
  2. 接收数据等待包尾S=1。因为是可变包长,所以此状态需要兼具等待包尾的功能。收到一个数据,判断是否为\r。如果不是,则正常接收,如果是,则不接收,同时跳到下一个状态。
  3. 等待包尾S=2。因为数据包有两个包尾\r、\n,所以需要第三个状态等待包尾\n。如果只有一个包尾,那么出现包尾之后就可以直接回到初始状态了,只需两个状态即可,因为接收数据和等待包尾需要在一个状态内同时进行。

2. 串口收发HEX数据包 

2.1 接线图

按键按一下,发送一次数据包。同时在OLED上显示发送的数据包和接收的数据包。

2.2 代码

在“9-2 串口发送+接收”的程序上修改。

HEX数据包格式:固定包长,含包头包尾,其中包头为FF,包尾为FE,载荷数据固定4字节。

Serial.c

#include "stm32f10x.h"                  // Device header
#include <stdio.h>//  为了收发数据包,先定义两个缓存区的数组
uint8_t Serial_TxPacket[4];//   4个数据只存储发送或接收的载荷数据
uint8_t Serial_RxPacket[4];
uint8_t Serial_RxFlag;//    收到一个数据包,置RxFlagvoid 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);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);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;//    停止位USART_InitStructure.USART_WordLength = USART_WordLength_8b;//   字长。不需要校验,所以选8位即可USART_Init(USART1, &USART_InitStructure);USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//    开启RXNE标志位到NVIC的输出NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef NVIC_InitSturcture;NVIC_InitSturcture.NVIC_IRQChannel = USART1_IRQn;//    中断通道NVIC_InitSturcture.NVIC_IRQChannelCmd = ENABLE;NVIC_InitSturcture.NVIC_IRQChannelPreemptionPriority = 1;NVIC_InitSturcture.NVIC_IRQChannelSubPriority = 1;NVIC_Init(&NVIC_InitSturcture);USART_Cmd(USART1, ENABLE);
}//  发送字节
void Serial_SendByte(uint8_t Byte)
{USART_SendData(USART1, Byte);// 调用此库函数,Byte变量就写入TDR了,写完后需要等待TDR数据转移至移位寄存器。如果数据在TDR内再写入数据,会产生数据覆盖while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);//  发送数据寄存器(TDR)空标志位//  这里标志位置1后不需要手动清零,当下一次再SendData时,此标志位会自动清零
}//  发送数组
void Serial_SendArray(uint8_t *Array, uint16_t Length)// 指向待发送数组的首地址。由于数组无法判断是否结束,所以需要传递一个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++)//   循环条件用结束标志位来判断。这里数字0对应空字符,是字符串的结束标志位。如果不等于0,就是还没结束,进行循环//   这里数据0也可以写成字符形式,就是'\0',这就是空字符的转义字符表示形式for(i = 0; String[i] != '\0'; i++),和直接写0最终效果是一样的{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)
{//  需要把Number的个位、十位、百位等以十进制拆分开,然后转换成字符数字对应的数据,依次发送uint8_t i;for(i = 0; i < Length; i++){Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');//    最终要以字符的形式发送,所以最后要加上字符的偏移,根据ASCII码表,字符0对应的数据是0x30,也可以以字符的形式写'0'}
}int fputc(int ch, FILE *f)
{Serial_SendByte(ch);return ch;
}//  调用此函数后,TxPacket数组的4个数据会自动加上包头包尾发送出去
void Serial_SendPacket(void)
{Serial_SendByte(0xFF);//    发送包头Serial_SendArray(Serial_TxPacket, 4);// 发送载荷数据Serial_SendByte(0xFE);//    发送包尾
}//  实现Serial_RxFlag标志位读后自动清除
uint8_t Serial_GetRxFlag(void)
{if(Serial_RxFlag == 1){Serial_RxFlag = 0;return 1;}return 0;
}//  中断函数。用状态机执行接收逻辑
void USART1_IRQHandler(void)
{static uint8_t RxState = 0;//   标志当前状态的变量S//   这个静态变量类似于全局变量,函数进入只会初始化一次0,在函数退出后数据仍然有效static uint8_t pRxPacket = 0;// 指示接收到第几个数据if(USART_GetFlagStatus(USART1, USART_IT_RXNE) == SET){uint8_t RxData = USART_ReceiveData(USART1);if(RxState == 0)//  等待包头{if(RxData == 0xFF)//    收到包头{RxState = 1;//  转移状态RxState=1pRxPacket = 0;//    清零}}else if(RxState == 1)// 接收数据{Serial_RxPacket[pRxPacket] = RxData;pRxPacket++;if(pRxPacket >= 4){RxState = 2;//  转移状态RxState=2}}else if(RxState == 2)// 等待包尾{if(RxData == 0xFE){RxState = 0;//  回到最初的状态,同时,代表一个数据包收到了Serial_RxFlag = 1;//    置接收标志位1}}USART_ClearITPendingBit(USART1, USART_IT_RXNE);//   清除标志位}
}

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);
uint32_t Serial_Pow(uint32_t X, uint32_t Y);
void Serial_SendNumber(uint32_t Number, uint8_t Length);void Serial_SendPacket(void);
uint8_t Serial_GetRxFlag(void);#endif

main.c

#include "stm32f10x.h"                  // Device 
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "Key.h"uint8_t KeyNum;int main(void)
{OLED_Init();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){//  按键按下后,数组数据加1发送KeyNum = Key_GetNum();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);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);}}
}

其他引用的头文件和c代码可在此处查阅:OLED.h(【江协STM32】4 OLED调试工具)、 Delay.h(【江协STM32】3-2 LED闪烁&LED流水灯&蜂鸣器,第1.3节)、 Key.h(【江协STM32】3-4 按键控制LED&光敏传感器控制蜂鸣器,第1.3节)

3. 串口收发文本数据包

3.1 接线图

3.2 代码

在上节程序的基础上进行修改。

文本数据包格式:可变包长,含包头包尾,以@符号为包头,以\r\n两个符号为包尾,载荷字符数量不固定。

Serial.c

#include "stm32f10x.h"                  // Device header
#include <stdio.h>//  为了接收数据包,先定义缓存区的数组
char Serial_RxPacket[100];//    设置单条指令最长不能超过100个字符
uint8_t Serial_RxFlag;//    收到一个数据包,置RxFlagvoid 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);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);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;//    停止位USART_InitStructure.USART_WordLength = USART_WordLength_8b;//   字长。不需要校验,所以选8位即可USART_Init(USART1, &USART_InitStructure);USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//    开启RXNE标志位到NVIC的输出NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef NVIC_InitSturcture;NVIC_InitSturcture.NVIC_IRQChannel = USART1_IRQn;//    中断通道NVIC_InitSturcture.NVIC_IRQChannelCmd = ENABLE;NVIC_InitSturcture.NVIC_IRQChannelPreemptionPriority = 1;NVIC_InitSturcture.NVIC_IRQChannelSubPriority = 1;NVIC_Init(&NVIC_InitSturcture);USART_Cmd(USART1, ENABLE);
}//  发送字节
void Serial_SendByte(uint8_t Byte)
{USART_SendData(USART1, Byte);// 调用此库函数,Byte变量就写入TDR了,写完后需要等待TDR数据转移至移位寄存器。如果数据在TDR内再写入数据,会产生数据覆盖while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);//  发送数据寄存器(TDR)空标志位//  这里标志位置1后不需要手动清零,当下一次再SendData时,此标志位会自动清零
}//  发送数组
void Serial_SendArray(uint8_t *Array, uint16_t Length)// 指向待发送数组的首地址。由于数组无法判断是否结束,所以需要传递一个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++)//   循环条件用结束标志位来判断。这里数字0对应空字符,是字符串的结束标志位。如果不等于0,就是还没结束,进行循环//   这里数据0也可以写成字符形式,就是'\0',这就是空字符的转义字符表示形式for(i = 0; String[i] != '\0'; i++),和直接写0最终效果是一样的{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)
{//  需要把Number的个位、十位、百位等以十进制拆分开,然后转换成字符数字对应的数据,依次发送uint8_t i;for(i = 0; i < Length; i++){Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');//    最终要以字符的形式发送,所以最后要加上字符的偏移,根据ASCII码表,字符0对应的数据是0x30,也可以以字符的形式写'0'}
}int fputc(int ch, FILE *f)
{Serial_SendByte(ch);return ch;
}//  中断函数。用状态机执行接收逻辑
void USART1_IRQHandler(void)
{static uint8_t RxState = 0;//   标志当前状态的变量S//   这个静态变量类似于全局变量,函数进入只会初始化一次0,在函数退出后数据仍然有效static uint8_t pRxPacket = 0;// 指示接收到第几个数据if(USART_GetFlagStatus(USART1, USART_IT_RXNE) == SET)// RXNE:当RDR移位寄存器中的数据被转移到USART_DR寄存器中,该位被硬件置位。{uint8_t RxData = USART_ReceiveData(USART1);if(RxState == 0)//  等待包头{if(RxData == '@' && Serial_RxFlag == 0)//    收到包头。等待每次处理完成之后再开始接收下一个数据包,Serial_RxFlag==0才执行接收,否则就是发送太快,还没处理完成{RxState = 1;//  转移状态RxState=1pRxPacket = 0;//    清零}}else if(RxState == 1)// 接收数据{if(RxData == '\r'){RxState = 2;}else{Serial_RxPacket[pRxPacket] = RxData;pRxPacket++;}}else if(RxState == 2)// 等待包尾{if(RxData == '\n'){RxState = 0;//  回到最初的状态,同时,代表一个数据包收到了Serial_RxPacket[pRxPacket] = '\0';//    在字符数组最后加字符串结束标志位\0,方便对字符串进行处理。否则使用ShowString时由于没有结束标志位,无法判断字符串长度Serial_RxFlag = 1;//    置接收标志位1}}USART_ClearITPendingBit(USART1, USART_IT_RXNE);//   清除标志位}
}

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);
uint32_t Serial_Pow(uint32_t X, uint32_t Y);
void Serial_SendNumber(uint32_t Number, uint8_t Length);#endif

main.c

#include "stm32f10x.h"                  // Device 
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "LED.h"
#include <string.h> //  判断字符串时使用int main(void)
{OLED_Init();LED_Init();Serial_Init();OLED_ShowString(1, 1, "TxPacket");OLED_ShowString(3, 1, "RxPacket");while(1){if(Serial_RxFlag == 1)//    代表接收到数据包{OLED_ShowString(4, 1, "                ");//    擦除第4行OLED_ShowString(4, 1, Serial_RxPacket);if(strcmp(Serial_RxPacket, "LED_ON") == 0)// 判断两个字符串是否相等。相等则返回0{LED1_ON();Serial_SendString("LED_ON_OK\r\n");OLED_ShowString(2, 1, "                ");//    擦除第4行OLED_ShowString(2, 1, "LED_ON_OK");}else if(strcmp(Serial_RxPacket, "LED_OFF") == 0)// 判断两个字符串是否相等。相等则返回0{LED1_OFF();Serial_SendString("LED_ON_OFF\r\n");OLED_ShowString(2, 1, "                ");//    擦除第4行OLED_ShowString(2, 1, "LED_OFF_OK");}else{Serial_SendString("ERROR_COMMAND\r\n");OLED_ShowString(2, 1, "                ");//    擦除第4行OLED_ShowString(2, 1, "ERROR_COMMAND");}Serial_RxFlag = 0;//    清零}}
}

其他引用的头文件和c代码可在此处查阅:OLED.h(【江协STM32】4 OLED调试工具,第5节)、Delay.h(【江协STM32】3-2 LED闪烁&LED流水灯&蜂鸣器,第1.3节)、LED.h(【江协STM32】3-4 按键控制LED&光敏传感器控制蜂鸣器,第1.3节)

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com