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 结束。
大佬啊,我完全看不懂
看不懂,完全看不懂
这就是大佬吗