当前位置: 首页 > news >正文

STM32学习笔记12-串口数据包收发FlyMcuST-LINK Utility

串口数据包收发的思路

数据包:由于通信的过程,需要让对方识别得懂我们的数据,而对方会随时接收,对方大概率信息是不完整且乱的,这时需要数据包进行将数据的分割,分割后再进行特定的协议去区分每个数据包。

HEX数据包:

                       本身数据就是本身

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

  • 可变包长,含包头包尾

  1. 包头包尾和数据载荷重复的问题:

包头定义为:FF;包尾定义为:FE,在传输数据中如果数据本身也是FF和FE,就会出现重复问题:解决方法:一、限制载荷数据范围;二、固定数据包长度;三、增加包头和包尾的数量

  1. 包头和包尾,实际上并不是每个都需要
  2. 对于HEX数据包,固定包长和可变包长的选择
  3. 各种数据转换为字节流

无论是什么类型,所占多少字节,都可以用一个指针把它们当作一个字节数组来发送就行

文本数据包

       本身数据,每个字节经过了一层编码和译码

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

  • 可变包长,含包头包尾


发送数据包:

        对应是数据包里填充数据,然后调用函数即可


接收数据包:

        以下操作,以此类推

        HEX数据包接收——固定数据包

在之前的接收代码中,我们每一个字节进行一次中断,中断后就结束了,是把每个字节给分开来了;但是图中表示的是以包头,包数据和包尾的三个不同状态,是一个整体;因此我们需要一个能记住不同状态的机制,在不同状态执行不同的操作,同时还要进行状态的合理转移——状态机(思维)

文本数据包接收——可变数据包

用状态机定义三个状态

接线图

9-3 串口收发HEX数据包

main.c
#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "key.h"
//串口收发HEX数据包
uint8_t RxData;
uint8_t Keynum;
int main(void) {OLED_Init();    // 初始化OLEDSerial_Init();  // 初始化串口Key_Init();OLED_ShowString(1,1,"Serial_TxPacket:");OLED_ShowString(3,1,"Serial_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_SendPakcet();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);}//通过函数读取RxFlag来获取数据包if(Serial_GetSerial_Flag()==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);//uint8_t Serial_RxPacket[4]是仅要写入又要读入,花费的时间就长了,如果旧的数据还没处理完新的数据就加入则会数据混乱,所以需要在中断函数中的每个数据包读取完再接收一下数据包的地方进行控时(一般的,独立数据收发不影响)}}
}
Serial.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include <stdio.h>
#include <stdarg.h>
uint8_t Serial_Rxdata,Serial_Flag;
//定义收发数据包——4个
uint8_t Serial_TxPacket[4];
uint8_t Serial_RxPacket[4];
uint8_t RxFlag;//收到一个数据包,就置RxFlag
void Serial_Init(void){//开启时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//初始化GPIO引脚GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//这个只用TX,作为输入模式,此实验只需发送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对应的引脚PA10GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化USARTUSART_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;//宽度USART_Init(USART1,&USART_InitStructure);//中断USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//配置NVIC//分组NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//初始化NVICNVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;NVIC_Init(&NVIC_InitStructure);//启动以后,会调用对应的启动函数(固定的)——USART1_IRQHandler//启动USARTUSART_Cmd(USART1,ENABLE);//对于串口接收:1.可以使用查询——初始化结束;2.可以使用中断——开启中断,配置NVIC//查询:在主函数中不断判断RXNE标志位,若置1,就说明收到数据了,再调用ReceiveData,读取DR寄存器,即可}//发送数据的函数
void Serial_SendByte(uint8_t Byte){USART_SendData(USART1, Byte);//此时发送的数据会存到ADC->DR中,之后再从DR转到发送寄存器中,并且需要等待发送完,不然会对原数据直接覆盖//获取USART的TXE标志位,直到为SER就载入新的数据,又由手册得知TXE当对DR写操作时,会被清零while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); // 等待DR寄存器空while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);   // 等待发送完成}
//发送数组
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{uint16_t i;for (i = 0; i < Length; i ++)		//遍历数组{Serial_SendByte(Array[i]);		//依次调用Serial_SendByte发送每个字节数据}
}//实现读后清出标志位
uint8_t Serial_GetSerial_Flag(void){if(RxFlag==1){RxFlag=0;return 1;}return 0;
}//中断函数
void USART1_IRQHandler(void){static uint8_t RxState=0;//状态变量S//用变量记录次数static uint8_t pRxPacket=0;if(USART_GetFlagStatus(USART1, USART_IT_RXNE) == SET){//状态机:执行接收逻辑//1.定义一个标志当前状态的变量S//2.根据状态变量的不同,进入不同的处理程序uint8_t RxData=USART_ReceiveData(USART1);if(RxState==0){//当RxData==0xFF,说明读到报头了if(RxData==0xFF){RxState=1;pRxPacket=0;}}else if(RxState==1){Serial_RxPacket[pRxPacket]=RxData;pRxPacket++;if(pRxPacket>=4){RxState=2;}}else if(RxState==2){//判断包尾if(RxData==0xFE){RxState=0;RxFlag=1;}}//定期清理标志位USART_ClearITPendingBit(USART1, USART_IT_RXNE);}
}//让数组调用函数后,自动加上包头包尾发送出去
void Serial_SendPakcet(void){Serial_SendByte(0xFF);Serial_SendArray(Serial_TxPacket,4);Serial_SendByte(0xFE);
}

9-4 串口收发文本数据包

main.c
#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "LED.h"
#include <string.h>
//串口收发文本数据包+LED测试
uint8_t RxData;int main(void) {OLED_Init();    // 初始化OLEDSerial_Init();  // 初始化串口LED_Init();OLED_ShowString(1,1,"Serial_TxPacket:");OLED_ShowString(3,1,"Serial_RxPacket:");while (1) {if(RxFlag==1){//在显示之前,需要清除第4行,因为字符串长度不确定,如果一个是长的,一个是小的,那大的会露出来OLED_ShowString(4,1,"										");OLED_ShowString(4,1,Serial_RxPacket);//调用字符串函数,对字符串进行操作//判断两个字符串是否相对等if(strcmp(Serial_RxPacket,"LED_ON")==0){LED1_ON();Serial_SendString("LED_ON_OK\r\n");OLED_ShowString(2,1,"										");OLED_ShowString(2,1,Serial_RxPacket);}else if(strcmp(Serial_RxPacket,"LED_OFF")==0){LED1_OFF();Serial_SendString("LED_OFF_OK\r\n");OLED_ShowString(2,1,"										");OLED_ShowString(2,1,Serial_RxPacket);}else{Serial_SendString("Error_Command\r\n");OLED_ShowString(2,1,"										");OLED_ShowString(2,1,"Error_Command");}RxFlag=0;}}
}
Serial.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include <stdio.h>
#include <stdarg.h>
uint8_t Serial_Rxdata,Serial_Flag;
//由于本实验是可变的数组,所以发送,不能像之前的那样一个个更改,所以发送就直接在主函数里调用或printf
//定义收数据包——4个
char Serial_RxPacket[100];
uint8_t RxFlag;//收到一个数据包,就置RxFlagvoid Serial_Init(void){//开启时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//初始化GPIO引脚GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//这个只用TX,作为输入模式,此实验只需发送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对应的引脚PA10GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化USARTUSART_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;//宽度USART_Init(USART1,&USART_InitStructure);//中断USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//配置NVIC//分组NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//初始化NVICNVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;NVIC_Init(&NVIC_InitStructure);//启动以后,会调用对应的启动函数(固定的)——USART1_IRQHandler//启动USARTUSART_Cmd(USART1,ENABLE);//对于串口接收:1.可以使用查询——初始化结束;2.可以使用中断——开启中断,配置NVIC//查询:在主函数中不断判断RXNE标志位,若置1,就说明收到数据了,再调用ReceiveData,读取DR寄存器,即可}//发送数据的函数
void Serial_SendByte(uint8_t Byte){USART_SendData(USART1, Byte);//此时发送的数据会存到ADC->DR中,之后再从DR转到发送寄存器中,并且需要等待发送完,不然会对原数据直接覆盖//获取USART的TXE标志位,直到为SER就载入新的数据,又由手册得知TXE当对DR写操作时,会被清零while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); // 等待DR寄存器空while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);   // 等待发送完成}
//发送数组
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{uint16_t i;for (i = 0; i < Length; i ++)		//遍历数组{Serial_SendByte(Array[i]);		//依次调用Serial_SendByte发送每个字节数据}
}//中断函数
void USART1_IRQHandler(void){static uint8_t RxState=0;//状态变量S//用变量记录次数static uint8_t pRxPacket=0;if(USART_GetFlagStatus(USART1, USART_IT_RXNE) == SET){//状态机:执行接收逻辑//1.定义一个标志当前状态的变量S//2.根据状态变量的不同,进入不同的处理程序uint8_t RxData=USART_ReceiveData(USART1);if(RxState==0){//当RxData==0xFF,说明读到报头了if(RxData=='@'&& Serial_Flag==0) //做一个等待的操作{RxState=1;pRxPacket=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;//在字符结尾,需要手动加一个'\0',方便后续字符串操作Serial_RxPacket[pRxPacket]='\0';RxFlag=1;}}//定期清理标志位USART_ClearITPendingBit(USART1, USART_IT_RXNE);}
}//发送字符串
void Serial_SendString(char *String)
{for(uint8_t i=0;String[i]!='\0';i++){Serial_SendByte(String[i]);Delay_ms(10);}
}

两个软件的使用:

FlyMcu程序烧录软件:

可以通过串口给STM32下载程序;

必须使用USART1,接线图

为执行串口下载,我们需要配置工程,生产一个HEX文件

勾选即可,重新编译,从文件夹中可以看出.Hex文件

再进软件,进行配置

然后从浏览选项,找到.hex文件

在开始编译之前,需要去设置Boot引脚,执行BootLoader程序

不然,会一直卡死

修改Boot:

通过STM32芯片中的黄色两个跳线,这两个跳线就是配置Boot引脚;

将上面的跳线帽,配置到另一端,如图,此时Boot配置为1

再按一下复位键,让程序重新运行,因为Boot程序只有在STM32的复位中才能运行;

此时系统就进入BootLoader程序了:不断接收USART1的数据,刷新到主闪存;

此时就可以进入软件,点击开始编程

此时的LED程序就是刷新到了主闪存这里了,LED就不亮了,换回来就行

1.Boot引脚是干嘛的?为什么这么配置?BootLoader又是什么?串口下载的原理是什么?

0800地址的存储的程序代码的,也就是写了什么,就存什么的地方;当通过串口下载程序到驱动,刷新后到0800的一块位置——接收并转存数据本身也是程序,如何利用程序实现自我更新:STM实现自我更新的时候,是无法自我达成先不要主程序,再更新主程序,然后重新加载回来,这个时候需要BootLoader来先执行更新主程序的操作,然后进行替换;

BootLoader(自我程序):是ST公司写好的一段代码,存储位置在ROM区的最后1FFF F000

相当于我们所常见的刷机模式,是辅助主程序进行自更新的。用途是程序自我更新,串口下载:在更新过程中,BootLoader接收USART1数据,刷新到程序存储器中,此时主程序处于瘫痪状态,更好后再启动主程序,执行新程序

启动配置:

2.每次下载程序,都要拔插两遍跳线帽,有没有优化方法?

首先明确,当需要程序自我更新,就必须由Boot0引脚和RST复位引脚存在高低电平变化。所以我们可以通过接线,通过程序自动进行配置

通常用这两根作为配置Boot0引脚和Reset引脚的接口,相关配置要去根据STM32一键下载配置来选

在软件中的也是

本次的芯片并没有一键下载电路,但是有个另招

点相关配置,成图上,运行后发现,配置成功了,此时查看旁边的日志得:

在软件上,人工加入了一条跳转指令,但是只是一次性的,一旦复位就复原

读Flash功能:

读取芯片程序

 

.bin:是没有地址信息的原始数据文件,里面存的从0800开始的存储的程序数据

清除芯片:

可以把主程序区域全部擦除

这里就是选项字节中的那些配置参数

STLINK Utility:

配合STLINK使用的工具,通过STLINK对STM32进行配置

接线图同上

下载:

连接STM32

随后会展现一些数据,从0800来的程序数据

对.hex和.bin文件都支持,载入到此软件中,可以方便查看

点击编程

进入下载界面

选项字节配置:

内容都差不多

但是这里直接修改,应用就会生效,不像FlyMcu程序烧录软件,必须下载程序顺便更新选项字节

ST-Link固件更新

http://www.dtcms.com/a/333024.html

相关文章:

  • Shortest Routes II(Floyd最短路)
  • 管家婆辉煌系列试用版/期限说明
  • Shader开发(十三)理解片元插值
  • 淘米自动签到脚本
  • 大气负氧离子自动监测站:解密空气的科技密码
  • 有红帽认证证书可以0元置换华为openEuler-HCIA/HCIP认证
  • OpenSCA开源社区每日安全漏洞及投毒情报资讯|13th Aug. , 2025
  • MyBatis StatementHandler核心原理详解
  • Nginx反向代理Tomcat实战指南
  • mysql-DDLy语句案例
  • 基于asp.net#C##VUE框架的独居老人物资配送系统的设计与实现#sql server#visual studio
  • OpenZeppelin Contracts 架构分层分析
  • 基于机器学习的赌博网站识别系统设计与实现
  • 【计算机视觉与深度学习实战】02基于形态学的权重自适应图像去噪系统
  • 【车联网kafka】常用参数及其命令总结(第八篇)
  • 【展厅多媒体】数字展厅小知识:实物识别桌是什么?
  • 杂记 02
  • Java研学-SpringCloud(四)
  • YOLO12 改进、魔改|幅度感知线性注意力MALA,提升小目标、遮挡的检测能力
  • FDBus CBaseWork运行在当前线程
  • AKShare开源金融数据接口库 | 1、介绍
  • 驱动-总线bus注册流程分析
  • QT开发中QString如何截取字符串
  • 怎样使用数据度量测试
  • Leetcode SQL基础50题
  • 旋钮键盘项目---foc讲解(开环)
  • 转换一个python项目到moonbit,碰到报错输出:编译器对workflow.mbt文件中的类方法要求不一致的类型注解,导致无法正常编译
  • 如何将堡塔云WAF迁移到新的服务器
  • 高精度标准钢卷尺优质厂家、选购建议
  • leetcode 342. 4的幂 简单