一、程序升级流程
1、MCU 的APP程序区接收到上位机“升级指令”,(上电直接从BL区启动)
2、MCU 跳转到BL程序区,应答上位机,“是否要升级”,一直发送
3、上位机应答“需要升级”,下发给MCU
4、MCU若果200ms没有收到“需要升级”,跳转到APP中
5、MCU若果200ms收到“需要升级”,应答上位机“开始升级”
6、上位机收到后,发送bin文件
7、MCU接收bin文件,存储到Flash,
8、MCU接收过程中,如果存在接收失败,返回上位机“接收失败”
9、上位机收到“接收失败”,重新开始发送bin文件
二、代码和参数设置
1、keil中 bin文件自动生成
$K\ARM\ARMCC\bin\fromelf.exe --bin --output=Bin\@L.bin !L
2、APP区启动程序地址设定
APP程序启动的地址:0x04000
3、BL程序区跳转地址设置
4、跳转代码
5、CANPro上位机发送bin文件
三、程序调试中注意细节
1、由于stm32f103c8t6,RAM只有20k,所以采用一边接收,一边写入Flash,CAN总线接收中断服务程序中完成
2、flash擦写规则
-
最小擦除单位:扇区
-
可选择擦除单位:扇区、块、全片
-
最大编程(写入)单位:页( 256 Byte),大于256 Byte则需要循环写入。
-
最小编程(写入)单位:1 Byte,即一次可写入 1~256 Byte的任意长度字节。
-
未写入时FLASH里面的数据为全1,即0xFF。
-
只能由 1 —> 0 写入,不能由 0 —> 1 写入,即如果已经写入过了,则需要先擦除(擦除后数据变为全1)再写入。
-
示例:0xF0(1111 0000),即高4位可写入,低4位不可写入。
3、由于flash擦除 需要时间,所以数据发送间隔20-40ms。时间太短否则容易出现擦写超时
4、BL程序区,跳转前关闭中断;在APP程序区要允许中断打开
5、bin文件大小 : Code+RO-data+RW-data
Program Size: Code=15128 RO-data=268 RW-data=164 ZI-data=2140
四、代码部分
1、BL程序主函数:
int main(void)
{
u8 key;
delay_init(); //延时初始化
LED_Init(); //初始化与LED连接的硬件接口
KEY_Init(); //初始化按键
InitCAN_GPIO();
CAN_Config();
USART_RX_CNT = 0;
uIAP.Step = 0;
uIAP.Wait=0;
uIAP.addr_off=0;
uIAP.FlashWriteOK = 0;
BootLoadSendMsg();
while(1)
{
delay_ms(200);
GPIOC->ODR ^= GPIO_Pin_13; //程序升级指示灯
key=KEY_Scan(0);
switch (uIAP.Step)
{
case 0: // *1*收到上位机 IAP 指令,准备升级 *2*没有收到IAP指令直接跳转
if(++uIAP.Wait >= 5*4) //等待3秒自动进入APP
{
uIAP.Step = 4;
}
if(key==KEY0_PRES) //testing 第一次按 BL区前3秒 准备接受bin文件
{
uIAP.Step = 1;
uIAP.Wait = 0;
}
else if(uIAP.PC_Request == 1) // 按键或者 上位机请求信号
{
uIAP.Step = 1;
uIAP.Wait = 0;
}
break;
case 1: //bin文件接收处理 flash处理完立刻启动
if(++uIAP.Wait >= 5*180) //等待180秒自动进入APP
{
uIAP.Step = 4;
}
else if(uIAP.FlashWriteOK ==1) // 收到上位机 发送完毕的信号
{
uIAP.Step = 4;
}
break;
case 4:
uIAP.Wait = 0;
//if(key==KEY0_PRES)
{
uIAP.addr_off=0;
uIAP.FlashWriteOK = 0;
USART_RX_CNT = 0;
if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000)//判断是否为0X08XXXXXX.
{
// printf("执行FLASH APP代码!\r\n");
iap_load_app(FLASH_APP1_ADDR);//关闭中断 执行FLASH APP代码
}
else
{
// printf("非FLASH应用程序,无法执行!\r\n");
uIAP.Step = 0;
}
}
break;
default:
break;
}
}
}
2、 CAN接收中断和写入flash
void GetCanData(CanRxMsg * rxMsg)
{
uint8_t length = 0,i=0;
switch (rxMsg->StdId) //判断帧标识符
{
case 0xB1:
;
break;
case 0xB2:
;
break;
case 0xBD: //bin文件解析
length = rxMsg->DLC;
USART_RX_BUF[USART_RX_CNT++] = rxMsg->Data[0];
USART_RX_BUF[USART_RX_CNT++] = rxMsg->Data[1];
USART_RX_BUF[USART_RX_CNT++] = rxMsg->Data[2];
USART_RX_BUF[USART_RX_CNT++] = rxMsg->Data[3];
USART_RX_BUF[USART_RX_CNT++] = rxMsg->Data[4];
USART_RX_BUF[USART_RX_CNT++] = rxMsg->Data[5];
USART_RX_BUF[USART_RX_CNT++] = rxMsg->Data[6];
USART_RX_BUF[USART_RX_CNT++] = rxMsg->Data[7];
iap_write_appbin(FLASH_APP1_ADDR+uIAP.addr_off,USART_RX_BUF,length);//更新FLASH代码
uIAP.addr_off+=USART_RX_CNT;
if(USART_RX_CNT%8==0)
USART_RX_CNT=0;
break;
default:
break;
}
}
void USB_LP_CAN1_RX0_IRQHandler(void)
{
if (CAN_MessagePending(CAN1, CAN_FIFO0) != 0)
{
CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);
GetCanData(&RxMessage);
}
}
3、写flash程序
void iap_write_appbin(u32 appxaddr,u8 *appbuf,u32 appsize)
{
u16 t;
u16 i=0;
u16 temp;
u32 fwaddr=appxaddr;//当前写入的地址
u8 *dfu=appbuf;
for(t=0;t<appsize;t+=2)
{
temp=(u16)dfu[1]<<8;
temp+=(u16)dfu[0];
dfu+=2;//偏移2个字节
iapbuf[i++]=temp;
if(i==1024)
{
i=0;
STMFLASH_Write(fwaddr,iapbuf,1024);
fwaddr+=2048;//偏移2048 16=2*8.所以要乘以2.
}
}
if(i)STMFLASH_Write(fwaddr,iapbuf,i);//将最后的一些内容字节写进去.
}
void STMFLASH_Write(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)
{
u32 secpos; //扇区地址
u16 secoff; //扇区内偏移地址(16位字计算)
u16 secremain; //扇区内剩余地址(16位字计算)
u16 i;
u32 offaddr; //去掉0X08000000后的地址
if(WriteAddr<STM32_FLASH_BASE||(WriteAddr>=(STM32_FLASH_BASE+1024*STM32_FLASH_SIZE)))return;//非法地址
FLASH_Unlock(); //解锁
offaddr=WriteAddr-STM32_FLASH_BASE; //实际偏移地址.
secpos=offaddr/STM_SECTOR_SIZE; //扇区地址 0~127 for STM32F103RBT6
secoff=(offaddr%STM_SECTOR_SIZE)/2; //在扇区内的偏移(2个字节为基本单位.)
secremain=STM_SECTOR_SIZE/2-secoff; //扇区剩余空间大小
if(NumToWrite<=secremain)secremain=NumToWrite;//不大于该扇区范围
while(1)
{
STMFLASH_Read(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//读出整个扇区的内容
for(i=0;i<secremain;i++)//校验数据
{
if(STMFLASH_BUF[secoff+i]!=0XFFFF)break;//需要擦除 不是1
}
if(i<secremain)//需要擦除
{
FLASH_ErasePage(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE);//擦除这个扇区
for(i=0;i<secremain;i++)//复制
{
STMFLASH_BUF[i+secoff]=pBuffer[i];
}
STMFLASH_Write_NoCheck(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//写入整个扇区
}
else
STMFLASH_Write_NoCheck(WriteAddr,pBuffer,secremain);//写已经擦除了的,直接写入扇区剩余区间.
if(NumToWrite==secremain)
break;//写入结束了
else//写入未结束
{
secpos++; //扇区地址增1
secoff=0; //偏移位置为0
pBuffer+=secremain; //指针偏移
WriteAddr+=secremain; //写地址偏移
NumToWrite-=secremain; //字节(16位)数递减
if(NumToWrite>(STM_SECTOR_SIZE/2))
secremain=STM_SECTOR_SIZE/2;//下一个扇区还是写不完
else
secremain=NumToWrite;//下一个扇区可以写完了
}
};
FLASH_Lock();//上锁
}