太原网站建设联系方式多备份wordpress
太原网站建设联系方式,多备份wordpress,怎样解除拦截网站设置,做试试彩网站人员前言#xff1a; 本文属于软件部分#xff0c;具体的串口硬件部分可见http://t.csdnimg.cn/afh48#xff0c;对于串口的工作原理以及各个寄存器工作流程的记录十分详细。
一、接线图
二、stm32发送-电脑串口助手接收
1.USART初始化流程图
1.开启时钟 把需要使用的USART和…前言 本文属于软件部分具体的串口硬件部分可见http://t.csdnimg.cn/afh48对于串口的工作原理以及各个寄存器工作流程的记录十分详细。
一、接线图
二、stm32发送-电脑串口助手接收
1.USART初始化流程图
·1.开启时钟 把需要使用的USART和GPIO的时钟打开
·2.GPIO初始化 把TX配置成复用输出RX配置成输入
·3.配置USART 直接使用一个结构体即可将所有参数配置完成
·4.开关控制 如果需要仅发送的功能就直接开启USART初始化到此结束 如果还需要接收的功能可能还需要配置中断那么就在开启USART之前加上IT_Config和NVIC的代码即可。
2.代码-发送字节数据 在Serial.c部分输入以下代码并将两个函数放到头文件声明
#include stm32f10x.hvoid Serial_Init()
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); // USART1是APB2的外设RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //引脚是PA9和PA10根据表需开启时钟GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; //TX引脚是USART外设控制的输出引脚要用复用推挽输出GPIO_InitStructure.GPIO_Pin GPIO_Pin_9;GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA,GPIO_InitStructure); USART_InitTypeDef USART_InitStruture;USART_InitStruture.USART_BaudRate 9600;USART_InitStruture.USART_HardwareFlowControl USART_HardwareFlowControl_None;//不使用流控选择noneUSART_InitStruture.USART_Mode USART_Mode_Tx;//如果既需要发送又接收就 TX | RXUSART_InitStruture.USART_Parity USART_Parity_No;//无需校验位USART_InitStruture.USART_StopBits USART_StopBits_1; //一位停止位USART_InitStruture.USART_WordLength USART_WordLength_8b; //无需校验位USART_Init(USART1,USART_InitStruture);USART_Cmd(USART1,ENABLE);
}void Serial_sendByte(uint8_t Byte)
{USART_SendData(USART1,Byte);while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) RESET);
} 然后到main.c包含头文件#include Serial.h 。main部分的代码为
#include stm32f10x.h
#include Delay.h
#include OLED.h
#include Serial.hint main()
{OLED_Init();Serial_Init();Serial_sendByte(0x11);while(1){}
}接下来需要打开串口助手注意串口助手内的参数要和代码一致。切记按下打开串口以及选择正确的串口号。 在接收区将会受到11烧录程序的时候自动复位会发送一次后续手动按下复位键也会发送11.
3.数据模式 ·HEX模式有的地方也称为十六进制模式或二进制模式这些称呼都是一个意思他表示的都是以原始数据的形式显示收到什么数据就把这个数据本身显示出来在这种模式下只能显示一个个的十六进制数比如11 7A 8B 33不能显示文本比如helloworld 和各种符号。等 ·如果要显示文本就要对一个个的数据进行编码了。这就叫文本模式或字符模式是以原始数据编码后的形式显示在这种模式下每一个字节数据通过查找字符集编码成一个字符。图中做下角的表就是ASCII码字符集 ·右方模式图描述的是字符和数据在发送和接收的转换关系
4.代码-发送数组 在Serial.c部分加入一下代码记得在Serial.h里面进行声明
void Serial_SendArray(uint8_t *Array,uint16_t Length) //数组的传递需要指针
{uint16_t i;for(i 0;i Length;i) //对数组进行遍历{Serial_sendByte(Array[i]); //一次取出数组的每一项通过sendbyte进行发送}
}同时在main函数加入以下函数
main
{ uint8_t MyArray[] {0x42,0x43,0x44,0x450x46};Serial_SendArray(MyArray,5);while1{}
} 编译烧录得到以下结果 5.代码-发送字符串 同上在Serial.c部分加入一下代码记得在Serial.h里面进行声明
void Serial_SendString(char *String) //字符串自带一个结束标志位所以不需要再传递长度参数
{uint8_t i;for(i 0; String[i] ! 0;i) //这里的0对应空字符是字符串结束标志位 如果不等于0就还没结束{ //也可以写成字符的形式 \0 Serial_sendByte(String[i]);}
}在main的函数如下 main
{ Serial_SendString(helloword!!\r\n); //在写完这个字符串之后编译器会自动补上结束标志位//所以字符串的存储空间会比字符大一//如果要执行换行操作要使用 \r\n 两个转义字符 都是不可见的控制字符while1{}
} 在串口助手记得选上文本模式。第一行的数字是我选择了hex模式时出现的不正常显示的现象
6.代码-发送数字 在Serial.c部分加入一下代码记得在Serial.h里面进行声明
void Serial_SendNumber(uint32_t Number, uint8_t Length)
{ //需要将Number的个位十位百位以十进制拆分开依次变成字符数字对应的数据发送出去uint8_t i;for(i 0;i Length;i ) //参数会以十进制由高位向低位依次发送{
// 由于最终是以字符的形式显示所以要根据ASCII表进行偏移 0x30 或 0Serial_sendByte(Number / Serial_Pow(10,Length - i - 1) %10 0);}
}举个例子假设取的数字是12345那么取万位就是12345 / 10000 % 10 1
取千位就是 12345 / 1000 %10 2 取百位就是 12345 / 100 % 10 3以此类推。也就是取某一位就是将数字除以10的位数次方这是为了去掉这一位数右边的数再对10取余这是去掉这一位左边的数。 所以需要写一个次方函数得到X的Y次方 uint32_t Serial_Pow(uint32_t X,uint32_t Y) //计算数字的某一位对应的数位百位或千位等
{uint32_t result 1;while(Y --){result * X;}return result;
}在main函数写下如下内容 main
{ Serial_SendNumber(12345,5);while1{}
} 得到运行结果如下 三、printf函数的移植 1、准备工作
使用printf之前需要先打开工程选项把use microLIB选项打开。microlib是keil为嵌入式平台优化的一个精简库本文使用到的printf将会用到这个microlib。 2、对printf进行重定向 将printf打印的东西输出到串口由于printf默认输出到屏幕但是单片机没有屏幕所以要进行重定向。 a.在串口的c文件里加上#includestdio.h b.在后面重写fputc函数
int fputc(int ch, FILE *f) //参数按此配置即可
{ //将fputc重定向到串口Serial_sendByte(ch); return ch;
} c.fputc和printf之间的联系 fputc是printf的底层printf函数在打印的时候就是不断调用fputc一个一个打印的。我们把fputc重定向到串口那么printf自然就输出到串口。 d.main函数中调用 经过上面的步骤printf已经移植完成。在主函数输入下面内容
#include stm32f10x.h
#include Delay.h
#include OLED.h
#include Serial.hint main()
{OLED_Init();Serial_Init();printf(date:%d\r\n,20240312);while(1){}
} 程序烧录后单片机将会直接在串口输出 date:20240312 这行内容并自动进行换行处理\r\n。 3、多串口使用printf 使用sprintfsprintf可以把格式化字符输出到一个字符串里面。 char string[100];sprintf(string,date:%d\r\n,2024031266);Serial_SendString(string); //把字符串string通过串口发送出去//因为sprintf可以指定打印位置不涉及重定向的东西所以每个串口都可以使用sprintf进行格式化打印
4、封装sprintf 由于printf这类函数比较特殊支持可变参数。
首先在串口的头文件里添加#includestdarg.h,然后在最末尾处对printf函数进行封装。
void Serial_printf(char *format,...) //第一个参数用来接收格式化字符串 三个点用来接收可变参数列表
{char string[100];va_list arg; //定义一个参数列表变量 va_list是类型名 arg是变量名va_start(arg,format); // 从format位置开始接收参数表放在arg里面vsprintf(string,format,arg);//这里的sprintf要改成vsprintf 前者只能接收直接写的参数 对于封装格式要用vsprintfva_end(arg);//释放参数表Serial_SendString(string);//把string发送出去
}在main部分进行调用
Serial_printf(date:%d\r\n,20240312); 输出结果如下
5、printf显示汉字的方法 在keil里面我们选择的汉字编码格式是utf8所以发送到串口的时候汉字会以utf8的方式编码。在串口助手也得选择utf8才能解码正确为了防止写入中文的时候编译器报错需要先在小魔术棒里面的c/c处输入下述参数。这个步骤是针对于使用utf8的用户 --no-multibyte-chars 由于我编译器使用的是GB2312编码所以串口助手处要使用GBK解码。如图正确解码得到“你好世界”中间的乱码部分是因为我选择了UTF8进行解码。
四、串口接收数据 1.代码-接收字节数据
#include stm32f10x.h
#include stdio.h
#include stdarg.hvoid Serial_Init()
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); // USART1是APB2的外设RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //引脚是PA9和PA10根据表需开启时钟GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; //TX引脚是USART外设控制的输出引脚要用复用推挽输出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; //RX PA10初始化GPIO_InitStructure.GPIO_Pin GPIO_Pin_10;GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA,GPIO_InitStructure);USART_InitTypeDef USART_InitStruture;USART_InitStruture.USART_BaudRate 9600;USART_InitStruture.USART_HardwareFlowControl USART_HardwareFlowControl_None;//不使用流控选择noneUSART_InitStruture.USART_Mode USART_Mode_Tx | USART_Mode_Rx;//既发送又接收USART_InitStruture.USART_Parity USART_Parity_No;//无需校验位USART_InitStruture.USART_StopBits USART_StopBits_1; //一位停止位USART_InitStruture.USART_WordLength USART_WordLength_8b; //无需校验位USART_Init(USART1,USART_InitStruture);//中断USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel USART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1;NVIC_InitStructure.NVIC_IRQChannelSubPriority 1;NVIC_Init(NVIC_InitStructure);USART_Cmd(USART1,ENABLE);
}void Serial_sendByte(uint8_t Byte)
{USART_SendData(USART1,Byte);while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) RESET);
}void Serial_SendArray(uint8_t *Array,uint16_t Length) //数组的传递需要指针
{uint16_t i;for(i 0;i Length;i) //对数组进行遍历{Serial_sendByte(Array[i]); //一次取出数组的每一项通过sendbyte进行发送}
}void Serial_SendString(char *String) //字符串自带一个结束标志位所以不需要再传递长度参数
{uint8_t i;for(i 0; String[i] ! 0;i) //这里的0对应空字符是字符串结束标志位 如果不等于0就还没结束{ //也可以写成字符的形式 \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 ) //参数会以十进制由高位向低位依次发送{ // 由于最终是以字符的形式显示所以要根据ASCII表进行偏移 0x30 或 0Serial_sendByte(Number / Serial_Pow(10,Length - i - 1) %10 0);}
}int fputc(int ch, FILE *f) //参数按此配置即可
{ //将fputc重定向到串口Serial_sendByte(ch); return ch;
}void Serial_printf(char *format,...) //第一个参数用来接收格式化字符串 三个点用来接收可变参数列表
{char string[100];va_list arg; //定义一个参数列表变量 va_list是类型名 arg是变量名va_start(arg,format); // 从format位置开始接收参数表放在arg里面vsprintf(string,format,arg);//这里的sprintf要改成vsprintf 前者只能接收直接写的参数 对于封装格式要用vsprintfva_end(arg);//释放参数表Serial_SendString(string);//把string发送出去
} 这部分代码和串口发送数据部分没什么本质区别仅仅添加了开启引脚PA10以及或上了RX部分。 提个醒自己的stm32接收不到数据但是程序没有任何问题接线也正常的情况下应该是stm32内部的ttl和串口转ttl模块冲突了可参考http://t.csdnimg.cn/q08TD这篇文章。由于我也出现了这种情况所以暂时没有实验现象。
2.代码-中断接收字节数据并回传
在serial.c里面添加两个定义
uint8_t Serial_RXData;
uint8_t Serial_RXFlag;
并在末尾部分添加如下代码
//实现读后自动清除的功能
//意思是返回这个标志位1就返回1,0就返回0但给这个1复位一下使得查一次就能复位1次
uint8_t Serial_GetRxFlag(void)
{if(Serial_RXFlag 1){Serial_RXFlag 0;return 1;}return 0;
}uint8_t Serial_GetRxData(void)
{return Serial_RXData;
}void USART1_IRQHandler(void)
{if(USART_GetITStatus(USART1,USART_IT_RXNE) SET){Serial_RXData USART_ReceiveData(USART1);Serial_RXFlag 1;USART_ClearITPendingBit(USART1,USART_IT_RXNE);}
}
接下来在main部分函数添加
#include stm32f10x.h // Device header
#include Delay.h
#include OLED.h
#include Serial.huint8_t RXData;int main()
{OLED_Init();Serial_Init();OLED_ShowString(1,1,RxData:);while(1){if(Serial_GetRxFlag() 1){RXData Serial_GetRxData();Serial_sendByte(RXData);OLED_ShowHexNum(1,8,RXData,2);}}
}
烧录后在发送区输入数据后将会显示到单片机连接的OLED显示屏上边同时会返回一份数据到接收区 五、数据包的定义 ·串口数据包通常使用的是额外添加包头包尾的这种方式 ·在HEX数据包里面数据都是以原始的字节数据本身呈现的而在文本数据包里面每个字节就经过了一层编码和译码最终表现出来的就是文本格式但是实际上每个文本字节的背后都还是一个HEX数据。 ·优缺点 HEX数据包优点传输最直接解析数据非常简单比较适合一些模块发送原始的数据 缺点灵活性不足容易和包头包尾重复 文本数据包优点数据直观易理解非常灵活比较适合一些输入指令进行人机交互的场合比如蓝牙模块常用的AT指令CNC和3D打印机常用的G代码都是文本数据包的格式。 缺点解析效率低比如发送一个100HEX数据包就是一个字节100但是文本数据包就得是三个字节的字符‘1’‘0’‘0’收到之后还要把字符转化成数据才能得到100
串口收发hex数据包 ·固定包长含包头包尾每个数据包的长度都固定不变数据包前面是包头后面是包尾 ·可变包长含包头包尾每一个数据包的长度可以是不一样的前面是包头后面是包尾。他的数据包格式可以根据用户需求自己规定。 ·包头包尾和载荷重复解决办法 ·如果数据含有FF和FE和包头包尾重复了怎么办会引起误判对于这个问题也有相应的几种解决方法 1.限制在和数据的范围如果可以的话可以在发送的时候对数据进行限幅。比如X、Y、Z三个数据的范围是0-100那么可以在载荷中只发送0-100的数据以此防止和包头包尾重复。 2.如果无法避免载荷数据和包头包尾重复则尽量使用固定长度的数据包由于载荷数据是固定的只要通过包头包尾对齐了数据我们就可以严格知道那个数据是包头包尾哪个数据是载荷数据。 在接收载荷数据的时候我们并不会判断他是不是包头包尾但是在判断包头包尾的时候会判断他是不是确实是包头包尾用于数据对齐在经过几个数据包对齐之后剩下的数据包就不会出现问题了。 3.增加包头包尾的数量并且尽量让他呈现载荷数据出现不了的状态。比如我们使用FF、FE作为包头FD、FC作为包尾这样也可以避免包头包尾和载荷数据重复的情况发生。 ·并不是所有的包头包尾都需要可以只要一个包头把包尾删掉这样数据包的格式就是一个包头FF加四个数据。当监测到FF开始接收当收够四个字节后置一个标志位一个数据包接收完成。不过这样会加重载荷和包头重复的问题。最坏的情况下载荷全是FF包头也是FF。如果加上了包尾FE无论数据怎么变化都是可以分辨出包头包尾的。 ·固定包长和可变包长的选择 对于HEX来说如果载荷会出现包头和包尾重复的情况最好是选择固定包长以避免接收错误。如果重复还选择可变包长数据容易乱套。如果包头包尾不会和载荷重复可以选择可变包长 ·关于各种数据转换为字节流的问题。 数据包都一个字节一个字节组成的如果想发送16、32位的整型数据float、double甚至是结构体都没问题因为内部是由一个字节一个字节组成的仅需要用一个uint8_t的指针指向他并把他们当做一个字节数组发送即可。
串口收发文本数据包 ·由于数据译码成了字符形式这样就会存在大量的字符可作为包头包尾可以有效的避免载荷和包头包尾重复的问题。比如以 作为包头以\r\n这两个换行字符作为包尾在载荷数据中间可以出现除了包头包尾的任意字符。文本数据包基本不用担心包头包尾和载荷重复的问题可变包长、各种符号、字母、数据都可以随意使用 ·当接收到载荷数据之后得到的就是一个字符串在软件中对字符串进行操作和判断就可以实现各种指令控制的功能而且字符串数据包表达的意义很明显可以把字符串数据包直接打印到串口助手上各种指令和数据都可以一眼看清。 ·文本数据包通常会以换行作为包尾在打印的时候就可以一行一行显示比较方便。 六、数据包的收发流程
HEX数据包接收固定包长 在接收的时候每收到一个字节程序都会进一遍中断在中断函数里面可以拿到这一个字节在拿到数据之后就得退出中断了每拿到一个数据都是一个独立的过程。对于数据包来说很明显有一个前后关联性包头之后是数据数据之后是包尾。对于包头数据包尾这三种不同的状态我们需要有不同的处理逻辑在程序中需设计一个记住不同状态的机制在不同状态执行不同操作同时还要进行状态的合理转移这种程序设计的思想叫做状态机接下来将使用状态机的方法来接收一个数据包。
执行流程 最开是S 0收到一个数据进中断根据S 0进第一个状态的程序判断包头是不是FF如果是代表收到包头之后置S 1退出中断结束。这样一来下次再进中断S 1就可以进行接收数据的程序了。 在第一个状态如果收到的不是FF证明数据包没有对齐我们应该等待数据包包头的出现这是状态仍然是0下次进中断还是判断包头的逻辑直到出现FF才能转入下一个状态进入下一个状态时收到数据将存入数组中另外再用一个变量记录接收数据的个数如果没接收够4个数据就一直处于接收状态如果收够了就置S 2。下次进中断就进入下一个状态。 最后的状态是等待包尾判断数据是不是FE如果是的话就可以置S 0回到最初的状态开始下一个轮回。这个数据也可能不是FE比如数据和包头重复导致包头位置判断错误那么这个包尾位置有可能不是FE这时进入重复等待包尾的状态直到接收到真正的包尾。这样的判断更能预防因数据和包头重复造成的错误。
使用状态机的基本步骤 1.先根据项目要求定义状态画几个圈然后考虑好各个状态在什么情况下会进行转移如何转移画好线和转移条件最后根据图编程。比如做个菜单就可以用到状态机的思维按什么键切换什么菜单执行什么程序还有一些芯片内部逻辑也会用到状态机比如什么情况下进入待机状态什么情况进入工作状态。
文本数据包接收 接收流程 第一个状态等待包头判断是不是我们规定的符号如果收到就进入接收状态在这个状态下依次接受数据同时这个状态还应该要兼具等待接收包尾的功能。因为这个是可变包长接收数据的同时要时刻监视是否收到了包尾一旦收到了包尾就立刻结束。这个状态的逻辑就是判断接收的一个数据是不是\r如果不是就正常接收如果是则不接收同时跳到下一个状态等待包尾\n因为数据包有两个包尾\r\n所以需要第三个状态如果只有一个包尾那么在出现一个包尾之后就可以直接回到初始状态即只需要两个状态即可。因为接收数据和等待包尾需要在一个状态里同时进行由于串口的包头包尾不会出现在数据中所以基本不会出现数据错位的现象
七、串口收发hex数据包
接线图 HEX数据包格式 定义如同PPT的一样固定包长含包头FF包尾FE载荷数据固定四个字节。为了收发数据包定义两个缓冲区的数组代码部分删去Serial_GetRxData()这个函数以及中断里面的部分内容编写函数Serial_SendPacket()
代码-发送数据包 添加这俩部分在serial.c并进行声明
uint8_t Serial_TxPacket[4]; //这四个数据只储存发送或接收的载荷数据包头包尾不存
uint8_t Serial_RxPacket[4];
void Serial_SendPacket(void)
{//第一步发送包头Serial_sendByte(0XFF);//依次将四个数据发送出去Serial_SendArray(Serial_TxPacket,4);//第三布发送包尾Serial_sendByte(0XFE);
}在main.c部分加入
int main()
{OLED_Init();Serial_Init();Serial_TxPacket[0] 0x01;Serial_TxPacket[1] 0x02;Serial_TxPacket[2] 0x03;Serial_TxPacket[3] 0x04;Serial_SendPacket();while(1){}
}烧录后得到实验结果 代码-接收数据包 接收数据包的缓存区和标志位已经定义好在中断函数里需要用状态机来执行接收逻辑接收数据包将数据存在Rxpacket数组里面 注意使用状态机在进行状态转移的时候要用 if else if 或者 switch case保障每次进入程序只执行其中一个状态的代码。如果使用三个并列的 if 在状态转移的时候可能会出现问题比如在状态0想转移到状态1就置RxState 1结果会造成下面状态1的条件立马满足会出现两个if都同时成立的情况。 中断函数内添加的内容如下
void USART1_IRQHandler(void)
{ static uint8_t RxState 0; // RxState 当做静态变量 S//这个静态变量类似于全局变量函数进入只会初始化一次0在函数退出之后数据仍然有效//但 与全局变量不同的是 静态变量只能在本函数使用static uint8_t pRxPacket 0;if(USART_GetITStatus(USART1,USART_IT_RXNE) SET){uint8_t RxData USART_ReceiveData(USART1);if(RxState 0) //进入等待包头的程序{if(RxData 0XFF){RxState 1;pRxPacket 0;//提前清0}}else if(RxState 1) //进入接收数据的程序{Serial_RxPacket[pRxPacket] RxData;//将rxdata存在接收数组里pRxPacket;//移动到下一个位置//每进一次接收状态 数据就转存一次缓存数组 同时存的位置if(pRxPacket 4){RxState 2;}}else if(RxState 2) //进入等待包尾的程序{if(RxData 0XFE){RxState 0;Serial_RXFlag 1;}}USART_ClearITPendingBit(USART1,USART_IT_RXNE);}
}main函数中添加内容如下 while(1){if(Serial_GetRxFlag() 1){OLED_ShowHexNum(1,1,Serial_RxPacket[0],2);OLED_ShowHexNum(1,4,Serial_RxPacket[1],2);OLED_ShowHexNum(1,7,Serial_RxPacket[2],2);OLED_ShowHexNum(1,10,Serial_RxPacket[3],2);}}
在电脑串口助手输入在oled小显示屏上面也显示出了 11 22 33 44。如果说输入的数据是FF FF FF 22 FE FE呢因为程序在接收载荷数据的时候并不会判断包头包尾这时即使载荷和包头包尾重复也干扰不到。 这个程序隐藏着一个小问题这个RxPackrt数组是同时被读出和写入的数组在中断函数里会依次写入在主函数又会依次读出这会造成数据包可能混在一起。如果读出的过程太慢了前面两个数据读出来等待一会才往后继续读取那么后面的数据有可能会刷新为下一个数据包的数据也就是读出的数据可能一部分属于上一个数据一部分属于下一个数据包。解决办法是在接收部分加入一个判断在每一个数据包接收处理完之后再接收下一个数据包。很多情况下可能不进行处理比如传输各种传感器的每个独立数据比如陀螺仪的x、y、z数据温湿度数据相邻的数据包之间的数据具有连续性即使相邻数据包混在一起也没关系这种情况下则不需要关心这个问题。 最终程序现象 main.c代码部分
#include stm32f10x.h // Device header
#include Delay.h
#include OLED.h
#include Serial.h
#include Key.huint8_t KeyNum;int main()
{OLED_Init();GPIO_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){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);}}
} 按下按键的时候oled屏幕显示01 02 03 04再次按下显示02 03 04 05每按一下每个数都增加1但是在串口助手 显示的是FF 01 02 03 04 FE,然后是FF 02 03 04 05 FE依次递增。 在发送区按包头载荷包尾然后OLED显示屏将会显示载荷部分的内容 八、串口收发文本数据包
接线图 代码-点灯 这部分代码加上了防止数据包错位的功能连续发送数据包程序处理不及时会导致数据包错位 main.c部分
#include stm32f10x.h // Device header
#include Delay.h
#include OLED.h
#include Serial.h
#include Key.h
#include LED.h
#include string.huint8_t KeyNum;int main()
{OLED_Init();GPIO_Key_Init();Serial_Init();LED_Init();OLED_ShowString(1,1,TxPacket);OLED_ShowString(3,1,RxPacket);while(1){
// if(Serial_GetRxFlag() 1)if(Serial_RXFlag 1){OLED_ShowString(4,1, );//相当于进行擦除处理OLED_ShowString(4,1,Serial_RxPacket);if(strcmp(Serial_RxPacket,LED_ON) 0){LED1_ON();Serial_SendString(LED_ON\r\n);OLED_ShowString(2,1, );OLED_ShowString(2,1,LED_ON_OK);}else if(strcmp(Serial_RxPacket,LED_OFF) 0){LED1_OFF();Serial_SendString(LED_OFF\r\n);OLED_ShowString(2,1, );OLED_ShowString(2,1,LED_OFF_OK);}else {Serial_SendString(ERROR_COMMAND\r\n);OLED_ShowString(2,1, );OLED_ShowString(2,1,ERROR_COMMAND);}Serial_RXFlag 0;}}
} serial.c部分代码
#include stm32f10x.h
#include stdio.h
#include stdarg.hchar Serial_RxPacket[100];uint8_t Serial_RXFlag; //如果收到一个数据包就置一个RxFlagvoid Serial_Init()
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); // USART1是APB2的外设RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //引脚是PA9和PA10根据表需开启时钟GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; //TX引脚是USART外设控制的输出引脚要用复用推挽输出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; //RX PA10初始化GPIO_InitStructure.GPIO_Pin GPIO_Pin_10;GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA,GPIO_InitStructure);USART_InitTypeDef USART_InitStruture;USART_InitStruture.USART_BaudRate 9600;USART_InitStruture.USART_HardwareFlowControl USART_HardwareFlowControl_None;//不使用流控选择noneUSART_InitStruture.USART_Mode USART_Mode_Tx | USART_Mode_Rx;//既发送又接收USART_InitStruture.USART_Parity USART_Parity_No;//无需校验位USART_InitStruture.USART_StopBits USART_StopBits_1; //一位停止位USART_InitStruture.USART_WordLength USART_WordLength_8b; //无需校验位USART_Init(USART1,USART_InitStruture);//中断USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel USART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1;NVIC_InitStructure.NVIC_IRQChannelSubPriority 1;NVIC_Init(NVIC_InitStructure);USART_Cmd(USART1,ENABLE);
}void Serial_sendByte(uint8_t Byte)
{USART_SendData(USART1,Byte);while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) RESET);
}void Serial_SendArray(uint8_t *Array,uint16_t Length) //数组的传递需要指针
{uint16_t i;for(i 0;i Length;i) //对数组进行遍历{Serial_sendByte(Array[i]); //一次取出数组的每一项通过sendbyte进行发送}
}void Serial_SendString(char *String) //字符串自带一个结束标志位所以不需要再传递长度参数
{uint8_t i;for(i 0; String[i] ! 0;i) //这里的0对应空字符是字符串结束标志位 如果不等于0就还没结束{ //也可以写成字符的形式 \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 ) //参数会以十进制由高位向低位依次发送{ // 由于最终是以字符的形式显示所以要根据ASCII表进行偏移 0x30 或 0Serial_sendByte(Number / Serial_Pow(10,Length - i - 1) %10 0);}
}int fputc(int ch, FILE *f) //参数按此配置即可
{ //将fputc重定向到串口Serial_sendByte(ch); return ch;
}void Serial_printf(char *format,...) //第一个参数用来接收格式化字符串 三个点用来接收可变参数列表
{char string[100];va_list arg; //定义一个参数列表变量 va_list是类型名 arg是变量名va_start(arg,format); // 从format位置开始接收参数表放在arg里面vsprintf(string,format,arg);//这里的sprintf要改成vsprintf 前者只能接收直接写的参数 对于封装格式要用vsprintfva_end(arg);//释放参数表Serial_SendString(string);//把string发送出去
}//实现读后自动清除的功能
//意思是返回这个标志位1就返回1,0就返回0但给这个1复位一下使得查一次就能复位1次
//uint8_t Serial_GetRxFlag(void)
//{
// if(Serial_RXFlag 1)
// {
// Serial_RXFlag 0;
// return 1;
// }
// return 0;
//}//void Serial_SendPacket(void)
//{
// //第一步发送包头
// Serial_sendByte(0XFF);
// //依次将四个数据发送出去
// Serial_SendArray(Serial_TxPacket,4);
// //第三布发送包尾
// Serial_sendByte(0XFE);
//}void USART1_IRQHandler(void)
{ static uint8_t RxState 0; // RxState 当做静态变量 S//这个静态变量类似于全局变量函数进入只会初始化一次0在函数退出之后数据仍然有效//但 与全局变量不同的是 静态变量只能在本函数使用static uint8_t pRxPacket 0;if(USART_GetITStatus(USART1,USART_IT_RXNE) SET){uint8_t RxData USART_ReceiveData(USART1);if(RxState 0) //进入等待包头的程序{if(RxData Serial_RXFlag 0){RxState 1;pRxPacket 0;//提前清0}}else if(RxState 1) //进入接收数据的程序{if(RxData \r) //判断是不是包尾{RxState 2;}else //不是包尾才接收数据{Serial_RxPacket[pRxPacket] RxData;//将rxdata存在接收数组里pRxPacket;//移动到下一个位置//每进一次接收状态 数据就转存一次缓存数组 同时存的位置}}else if(RxState 2) //进入等待包尾的程序{if(RxData \n){RxState 0;Serial_RxPacket[pRxPacket] \0;Serial_RXFlag 1;//接收到之后还需要给这个字符数组的最后加一个字符串结束标志位\0 方便后续对字符串的处理//不然在showstring没有结束标志位不知道这个字符串具体长度}}USART_ClearITPendingBit(USART1,USART_IT_RXNE);}
}
serial.h部分代码
#ifndef __SERIAL_H
#define __SERIAL_H#include stdio.hextern char Serial_RxPacket[];
extern uint8_t Serial_RXFlag;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);
uint32_t Serial_Pow(uint32_t X,uint32_t Y);
void Serial_printf(char *format,...);
uint8_t Serial_GetRxFlag(void);#endif 实验现象 如果在串口助手输入LED_ON打上回车之后发送将会点亮LED灯。LED_OFF将会熄灭LED灯
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/86639.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!