以STM32F103为基础。
实现的简单思路:
IAP实际就是自己构建两个程序。将程序分割成两部分保存下来。一部分实现bootloader的功能,一部分是实际程序。
BootLoader部分实现:
其所有配置跟正常程序一致,不需要额外设置。功能方面只需要判断是否需要更新固件,如果不需要则直接跳转运行,如果需要就等待数据发送过来,发送完成后将其保存,最后跳转运行。
//判断持续按下3秒。进入更新固件的状态下。
uint8_t count = 0;
while(WKUP() == 1)//按键按下
{
delay_ms(500);
if((++count) >= 6)
{ //3s
printf("进入IAP更新模式,请发送相关bin文件......");
Mode = MYIAP;
break;
}
}
以常用的串口方式接收数据,此时定义一个大数组,用于保存数据,且指定起始地址,方便后面使用。
#define RX_BuffSize 1024*40//最大数据量 即能够更新的最大程序
uint32_t rxlength = 0;//记录数据长度
uint8_t rx_table[RX_BuffSize] __attribute__ ((at(0x20001000))); //0x20000000到0x20001000是烧录算法 所以采用0x20001000为起始地址
//0x20000000为SRAM的起始地址,接受的临时数据都存储在SRAM中
void Bsp_Receiverdata(UART_DEF *Uart) //接收数据函数
{
uint8_t data = USART_ReceiveData(Uart->USARTx);//串口接收
rx_table[rxlength] = data;//转存
rxlength++;//数据长度
}
接收完数据后,将数据保存到指定的地址,方便用于使用。此处将数据保存在flash中,且由于本人使用的STM32F103一页为2K,所以一次存入2K的数据,方便计算。但是在保存数据前,可以简单的判断一下数据是否正确。
//判断是否为0x08xxxxxx
//0X20001000为前面指定的数据地址,但是第一个数据是栈顶地址第二个地址才是flash地址
if(((*(uint32_t*)(0X20001000 + 4)) & 0xff000000) == 0x08000000)
//addr:写入的起始地址 注:此地址要与固件地址一致,以及跳转运行时的地址一致
//iapbuff:保存的数据
//iaplength:数据长度
bool Iap_WriteFlashFunction(uint32_t addr,uint8_t *iapbuff,uint32_t iaplength)
{
bool flag = false;
uint32_t count = 0,temp = 0;
uint32_t writebuff[512];//2K内容 4*512
while(iaplength)
{
//发送来的数据是bin文件,其数据是小端模式,所以在此处转化为实际数据
temp = (uint32_t)iapbuff[3] << 24;
temp |= (uint32_t)iapbuff[2] << 16;
temp |= (uint32_t)iapbuff[1] << 8;
temp |= (uint32_t)iapbuff[0];
iapbuff += 4;//地址偏移
iaplength -= 4;//剩余数据长度
writebuff[count++] = temp;//转存
if(count == 512)//满足一页内容后去保存
{
count = 0;//清零
flag = Flash_Write(addr,writebuff,512);//2K内容一次存入 一页为2K
if(!flag)
{
return flag;
}
addr += 2048;
}
}
if(count != 0)///剩余不足2K
{
flag = Flash_Write(addr,writebuff,count);
}
return flag;
}
保存数据后,可以通过简单的操作跳转运行固件程序,在此之前则可以简单判断一下固件是否争取。
//FLASH_APP_BASE_ADDR为写入flash的起始地址,其指向栈顶地址
//其本质与前面保存数据前的判定一个道理
if(((*(uint32_t*)(FLASH_APP_BASE_ADDR+4))&0xFF000000)==0x08000000)//判断是否为0x08xxxxxx
//不支持汇编 内联
//设置栈顶地址
//addr:栈顶地址
__asm void MSR_MSP(u32 addr)
{
MSR MSP, r0 //set Main Stack value
BX r14
}
//跳转到用户程序运行
//addr:用户程序的起始地址,也是前面保存数据时的地址
void Iap_Load_App(uint32_t addr)
{
if(((*(uint32_t *)addr) & 0x2FFE0000) == 0x20000000)//STM32程序起始地址处存的是栈顶地址 0x2FFE0000是由于RAM最大为0x20010000
{
MSR_MSP(*(uint32_t*)(addr));//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
void (*function)(void);//定义一个函数指针
function = (void (*)(void))*(uint32_t*)(addr + 4);//用户代码区第二个字为程序开始地址(复位地址)
function();
}
}
//function = (void (*)(void))*(uint32_t*)(addr + 4);
//function 为函数指针
//(void (*)(void))用于强转
//(uint32_t*)(addr + 4)表示一个地址
//*(uint32_t*)(addr + 4)其地址对应的值(由于存的是bin文件,所以其值本质也是一个地址,所以赋值给函数指针)
指针问题可以参考这里
用于更新的固件程序:
对于固件程序,跟其他正常程序基本一致,区别在于KEIL的配置,以及改变基地址。
//在main函数第一行加入此地址偏移
//偏移的0x10000是用于固件程序从此处开始
SCB->VTOR = FLASH_BASE | 0x10000;
在魔术棒Target中设置IROM1为0x8010000,此处0x8010000就是前面BootLoader中提到的写入地址,也是基础地址偏移0x10000的原因。此0x10000是为BootLoader程序预留的64K的内存。
由于发送的是bin文件,所以需要使用MDK生成.bin
文件。即在魔术棒User中设置。
红框内容为
//此为一个相对路径
//iap.bin为文件名
//Objects\iap.bin为在当前工程路径下的Objects生成iap.bin
//如果需要上一层文件可以改为..\BIN\iap.bin:此为在工程路径的上一层的BIN文件夹中生成iap.bin
//文件路径操作大致如Linux
$K\ARM\ARMCC\bin\fromelf.exe --bin --output=Objects\iap.bin !L
如此编译后将会生成.bin
文件。通过按键的方式让它处于固件更新的状态下,将.bin
文件通过串口助手发送即可完成更新。
具体demo下载点击这里