STM32 串口 ISP 攻略
1 STM32 串口 ISP 原理
STM32 单片机系统存储器中有一段 Bootloader 代码,他的主要作用为通过串口下载程序代码到单片机内部 Flash 中。该过程称为串口 ISP,也就是说串口与 Bootloader 进行通信,就可以实现代码下载功能了,下面详细讲解与 Bootloader 的通信规则。
2 Bootloader 通信规则
2.1 启动 bootloader
与 Bootloader 通信前,需要先启动他,也就是需要让单片机进入到 Bootloader 到代码中执行。通过 STM32 的两个引脚可以实现配置。如下图所示:
如上图所示,可以配置成三种模式,第二种为启动 Bootloader 方式,即 BOOT0=1,BOOT1=0。
2.2 Bootloader 编码序列
当 STM32F10xx 系列单片机进入 Bootloader 程序后,单片机开始检测 PA10(串口 1 接收引脚)是否收到串口数据 0x7F,并且要求串口数据格式为 1bit 起始 +8bit 数据 +1bit 偶校验 +1bit 停止位。
收到 0x7F 后,单片机将内部串口的波特率设置和发送数据方相同,并且给主机回复应答信号 0x79。为方便描述,我们这里将发送数据方称为主机,STM32 单片机方称为从机。回复完应答信号 0x79 后,表示从机已经准备好接收主机的指令了。
2.3 波特率选择
为保证数据传输的正确性,一般选择在 1200bps 至 115200bps 之间。
2.4 Bootloader 指令集
完整指令集如下图,但并不是单片机同时支持所有的指令,后续将逐一介绍。
2.4.1 Get 命令
该指令允许主机获取从机的 Bootloader 版本号以及支持哪些命令。当从机收到主机发来的 Get 命令后,从机将 Bootloader 版本及所支持的命令发送给主机。发送 Get 命令的主机端时序如下图所示。
代码实现如下:
char GetCMD(DeviceInfo_t *DInfo) { char error,len; //step1 发送指令0x00,0xFF,并等待ACK SendByte(0x00); SendByte(0xFF); error = ACK();if(!error) return 0; //step2 接收数据 len = GetByte(); DInfo->cmd_count = len;//第一个字节为命令数量N DInfo->bootloaderversion = GetByte();//第二个字节为版本号 for(int i =0;i<len;i++) DInfo->cmd[i] = GetByte();//获取CMD error = ACK();if(!error) return 0; //step3 处理数据,并打印版本号,命令等 //打印数据,并清空串口缓存 return 1; }
2.4.2 Get ID 命令
该命令用于获取单片机的的产品 ID,主机端时序如下图所示。
代码实现如下:
char GetID(DeviceInfo_t *DInfo) { char len,error; //step1 发送序列0x02,0xfd,并等待ACK SendByte(0x02); SendByte(0xFD); error = ACK();if(!error) return 0; //step2 接收PID,并等待ACK len = GetByte();//第一个字节为ID长度N-1 for(int i =0;i<=len;i++) DInfo->PID[i] = GetByte();//获取ID error = ACK();if(!error) return 0; return 1; //step3 清空接收缓存,并打印PID }
2.4.3 Read Memory 命令
读内存命令用来读取从机单片机内部 RAM,FLASH,信息块(包括系统存储区和选项字节)中的数据。主机端时序如下所示。
代码实现如下:
///////////////////////////////////////////////// // //读取数据 //与写数据步骤类似 //addr必须能被4整除,len发送数据长度-1,单次不能超过256B ///////////////////////////////////////////////// char ReadMem(unsigned char *data, unsigned int addr, unsigned char len) { unsigned char temp[4],error; //保存addr的四个字节 int i; temp[0] = ((addr>>24) & 0xFF); temp[1] = ((addr>>16) & 0xFF); temp[2] = ((addr>> 8) & 0xFF); temp[3] = ((addr ) & 0xFF); //step1 发送序列0x11,0xEE,并等待ACK SendByte(0x11); SendByte(0xEE); error = ACK();if(!error) return 0; //step2 发送地址,先发高字节 SendByte(temp[0]); SendByte(temp[1]); SendByte(temp[2]); SendByte(temp[3]); //step3 发送地址校验,并等待ACK SendByte(CheckSum(temp, 4)); error = ACK();if(!error) return 0; //step4,发送len及校验,并等待ACK SendByte(len); SendByte(~len); error = ACK();if(!error) return 0; //step4 接收长度为len+1的数据 for(i=0;i<=len;i++) data[i] = GetByte(); return 1; }
2.4.4 Write Memory 命令
写内存命令用来往从机单片机内部 RAM,FLASH,信息块(包括系统存储区和选项字节)中写入数据,主机端时序如下所示。
往选择字区写入数据时,开始地址必须为 0x1FFFF800。
代码实现如下:
///////////////////////////////////////////////// // //写入数据块,从*data处,往stm32的addr处,写入len+1字节数据 // ///////////////////////////////////////////////// char WriteMem(unsigned char *data, unsigned int addr, unsigned char len) { unsigned char temp[4],error; //保存addr的四个字节 int i; temp[0] = ((addr>>24) & 0xFF); temp[1] = ((addr>>16) & 0xFF); temp[2] = ((addr>>8 ) & 0xFF); temp[3] = ((addr ) & 0xFF); //step1 发送序列0x31,0xCE,并等待ACK SendByte(0x31); SendByte(0xCE); error = ACK();if(!error) return 0; //step2 发送地址,先发高字节 SendByte(temp[0]); SendByte(temp[1]); SendByte(temp[2]); SendByte(temp[3]); //step3 发送地址校验,并等待ACK SendByte(CheckSum(temp, 4)); error = ACK();if(!error) return 0; //step4 发送len SendByte(len); //step5 连续发送数据,最后字节为校验,并等待ACK for(i=0;i<=len;i++) { SendByte(data[i]); } SendByte(len ^ CheckSum(data, len+1)); //delay_msec(3000); error = ACK();if(!error) return 0; //清空串口缓存,并延时1s. return 1; }
2.4.5 Erase Memory 命令
擦除从机单片机内部 FLASH 命令,仅单片机 Bootloader 版本 3.0 以下支持该命令。主机端时序如下所示。
全片擦除代码实现如下:
///////////////////////////////////////////////// // //全片擦除,bootloader V3.0以下有效 // step1 发送序列0x43,0xbc,并等待ACK // step2 发送序列0xff,0x00,并等待ACK // step3 清空串口接收缓冲 ///////////////////////////////////////////////// char EraseAll() { char error; SendByte(0x43);// SendByte(0xBC); error = ACK();if(!error) return 0; SendByte(0xFF); SendByte(0x00); //不同的产品,全片擦除的时间长短不一,500ms的时间不一定够, //因此不能用现成的ACK函数,需重写如下: //error = ACK();if(!error) return 0; while(!MyComRevBUff.size()) { delay_msec(1);//出让线程 } if(0x79 == GetByte()) return 1; else return 0; }
2.4.6 Extended Erase Memory 命令
仅单片机 Bootloader 版本 3.0 及以上支持该命令。主机端时序如下所示,以 Erase Memory 命令相比,该命令为双字节命令,即 Erase cmd 为双字节,例如 0xFFFF。
全片擦除代码实现如下:
///////////////////////////////////////////////// // //全片擦除,bootloader V3.0及以上有效 // step1 发送序列0x43,0xbc,并等待ACK // step2 发送序列0xff,0x00,并等待ACK // step3 清空串口接收缓冲 ///////////////////////////////////////////////// char ExtendedEraseAll() { char error; SendByte(0x44);// SendByte(0xBB); error = ACK();if(!error) return 0; unsigned char EraseCMD[2]; EraseCMD[0] = 0xFF; EraseCMD[1] = 0xFF; SendByte(0xFF);//写入地址0xFFFF SendByte(0xFF); SendByte(CheckSum(EraseCMD, 2));//双字节校验 //不同的产品,全片擦除的时间长短不一,500ms的时间不一定够, //因此不能用现成的ACK函数,需重写如下: //error = ACK();if(!error) return 0; while(!MyComRevBUff.size()) { delay_msec(1);//出让线程 } if(0x79 == GetByte()) return 1; else return 0; }
2.4.7 GO 命令
跳转到从机的指定地址开始执行程序。G0 命令主机时序如下。
代码实现如下:
///////////////////////////////////////////////// // //跳转执行指令,下载完成后,跳转到RAM或内部FLASH执行 // // ///////////////////////////////////////////////// char CMDGo(unsigned int addr) { unsigned char temp[4],error; //保存addr的四个字节 int i; temp[0] = ((addr>>24) & 0xFF); temp[1] = ((addr>>16) & 0xFF); temp[2] = ((addr>>8 ) & 0xFF); temp[3] = ((addr ) & 0xFF); //step1 发送序列0x21,0xDE,并等待ACK SendByte(0x21); SendByte(0xDE); error = ACK();if(!error) return 0; //step2 发送地址,先发高字节 SendByte(temp[0]); SendByte(temp[1]); SendByte(temp[2]); SendByte(temp[3]); //step3 发送地址校验,并等待ACK SendByte(CheckSum(temp, 4)); error = ACK();if(!error) return 0; return 1; }
3 STM32 串口 ISP 操作参考步骤
- step1 主机发送 0x7F,启动从机并等待接收主机命令;
- step2 主机发送 Get 命令,获取从机 Bootloader 版本号及所支持的命令等;
- step3 主机发送 Get ID 命令,获取从机产品 ID;
- step4 主机发送擦除命令,擦除从机内部 FLASH,为写数据做准备;
- step5 主机发送 Write Memory 命令,将代码写入从机器;
- step6 主机发送 Read Memory 命令,读取从机代码并与写入的数据进行对比校验;
- step7 主机发送 Go 命令,从机跳转至程序开始处执行代码,观察现象是否与程序一致。
- step8 结束。
大佬啊,我完全看不懂
看不懂,完全看不懂
这就是大佬吗