赞
踩
本次所采用的开发平台如下:
MCU :AT32F421K8U7
SRAM:16KB
Flash:64KB
软件开发平台:keil AC6
如果使用别的平台的单片机,原理上也是大同小异,明白原理就可以去设计自己的Bootloader和应用APP
我们知道Bootloader与应用APP是两个独立的程序被放置在MCU的Flash当中,系统复位之后先去执行Bootloader在再去跳转应用APP,故我们要对Bootlaoder的空间和应用APP的空间进行划分,使有足够的空间去存放两个程序代码。
这个图是AT32F421的地址映射,而实际上其他M3 和 M4芯片的地址映射也是差不多,无非是Flash空间和SRAM空间和外设空间的大小不一样罢了,但是还是对整个0x0000_0000到0xffff_ffff的4GB地址空间进行寻址。
当系统确定从Flash启动后,那么中断向量表就会从0x0000_0000处映射到0x0800_0000处,如果不知道中断量表是啥,就来看看篇 (设计自己的Bootloader(1)-------- Cortex M3与M4的上电启动过程_三石君啊的博客-CSDN博客)。
当确定这点以后,我们知道,代码都是从0x0800_0000里开始执行,那么Bootloader代码就从0x0800_0000处开始存放。
我这里对Bootloader和应用APP的空间划分就如下:
这个是程序大体上的流程,但是其中有很多要点,比如如何划分地址区域,Flash读写的设计,通信协议的设计,如何跳转应用APP,下来我们依次来写。
打开keil,点击魔术棒,对flash区域进行设计
接着对一下两个选项进行设置,根据自己划分的大小来设计
这样就做好了地址划分的操作。
通过官方的寄存器手册可以看到,AT32F421K8U7的Flash扇区大小是1KB,那么我每次就是对一个扇区的起始地址写入一个扇区的数据。
这个写入的操作很简单,对Flash解锁,擦除扇区,写入数据,对Flash上锁,相关的程序可以在各个平台的BSP的例程中找到。
以下就是我的flash写入操作,不是那么的严谨,比如没有对地址合法性进行判断。
void flash_8bit_write(uint32_t write_addr, uint8_t *pbuffer)
{
uint32_t index,write_data;
flash_unlock();
flash_sector_erase(write_addr);
for(index = 0; index < FLASH_SECTOR_SIZE; index++)
{
write_data = pbuffer[index];
flash_byte_program(write_addr, write_data);
write_addr += sizeof(uint8_t);
}
flash_lock();
}
这个是我的通信流程,很简单。
数据的组成如下:
typedef struct
{
uint8_t cmd_head;//帧头
uint8_t cmd_addr[4];//扇区首地址
uint8_t cmd_buf[FLASH_SECTOR_SIZE];//一个扇区的大小
uint8_t cmd_check[2];//crc16校验
} iap_data_group_type;
我的通信接口选择的串口,接收方式是 串口DMA空闲中断 。
在实际设计的时候要注意加入对接收信息的应答,超时机制,让上位机明白是哪里出错了,方便Debug排除错误。
应答数据:
typedef enum
{
back_ok,//请求下一帧
back_end,//IAP结束
back_error_cmd_head,//帧头错误
back_error_data,//数据内容错误
back_error_timeout,//超时错误
}iap_back_type;
在串口中对数据的长度进行判断
void USART1_IRQHandler() { if(usart_flag_get(USART1, USART_IDLEF_FLAG)) { usart_data_receive(USART1); dma_channel_enable(DMA1_CHANNEL3 , FALSE);//关闭dma通道 int length = sizeof(iap_data_group_type) - DMA1_CHANNEL3->dtcnt;//计算接收长度 if(length == sizeof(iap_data_group_type)) { iap_receive = 1; } else//接收长度有问题 { usart_set_dma((uint32_t)(&iap_data),sizeof(iap_data_group_type));//开启DMA接收 usart_back_data(back_error_data); } } }
超时机制
void TMR3_GLOBAL_IRQHandler(void)
{
if(tmr_flag_get(TMR3, TMR_OVF_FLAG) == SET)
{
tmr_flag_clear(TMR3, TMR_OVF_FLAG);
timeout_cnt++;
if(timeout_cnt > 5)//超时判断
{
tmr_counter_enable(TMR3, FALSE);
tmr_counter_value_set(TMR3,0);
}
}
}
Bootloader的通信
void iap_handle() { while(iap_receive == 0) { if(timeout_cnt > 5)//超时判断 { timeout_cnt = 0; tmr_counter_enable(TMR3, TRUE); usart_set_dma((uint32_t)(&iap_data),sizeof(iap_data_group_type));//开启DMA接收 usart_back_data(back_error_timeout); } } iap_receive = 0;//清空标志位 timeout_cnt = 0;//超时清空 uint8_t check_sum = 0; if(iap_data.cmd_head == CMD_HEAD)//帧头是否正确 { /* 检查数据内容 */ uint16_t crc = CalculateCRC16((uint8_t*)(&iap_data)); uint16_t check_data = (uint16_t)(iap_data.cmd_check[0]<<8) | iap_data.cmd_check[1]; if(check_data == crc)//数据正确 { uint32_t iap_address = 0; iap_address = (uint32_t)iap_data.cmd_addr[0]<<24 ; iap_address = iap_address | (((uint32_t)iap_data.cmd_addr[1]<<16) & 0xffff0000); iap_address = iap_address | (((uint32_t)iap_data.cmd_addr[2]<<8) & 0xffffff00); iap_address = iap_address | (uint32_t)iap_data.cmd_addr[3]; if(iap_address != 0xffffffff)//非结束地址 { memcpy((void*)src_flash_buf , iap_data.cmd_buf , FLASH_SECTOR_SIZE);//拷贝到flash缓冲 memset((void*)(&iap_data),0,sizeof(iap_data_group_type));//清空iap_data usart_set_dma((uint32_t)(&iap_data),sizeof(iap_data_group_type));//开启DMA接收 usart_back_data(back_ok);//返回正确,请求下一帧 flash_8bit_write(iap_address,src_flash_buf);//写入flash } else//为0xffffffff,IAP结束,跳转APP { memset((void*)src_flash_buf , 0 , FLASH_SECTOR_SIZE);//拷贝到flash缓冲 flash_8bit_write_sector(14,src_flash_buf); usart_back_data(back_end); crm_reset(); app_load(APP_START_ADDRESS); } } else//数据内容错误 { memset((void*)(&iap_data),0,sizeof(iap_data_group_type));//清空iap_data usart_set_dma((uint32_t)(&iap_data),sizeof(iap_data_group_type));//开启DMA接收 usart_back_data(back_error_data);//返回错误,重新请求该帧 } } else//帧头错误 { memset((void*)(&iap_data),0,sizeof(iap_data_group_type));//清空iap_data usart_set_dma((uint32_t)(&iap_data),sizeof(iap_data_group_type));//开启DMA接收 usart_back_data(back_error_cmd_head);//返回错误,重新请求该帧 } }
当IAP结束后,就是要跳转到我们的应用代码,这个时候就要思考,怎么跳转呢?
从汇编的角度思考,就是将应用APP起始地址的指令执行就可以了,就是将起始地址传递给 PC程序计数器 就可以了,但是要用到汇编,很麻烦?有无更加简单的方法呢?
回想一下,我们在调用函数的时候,其实也是做了类似的操作,当函数调用时候,就是对相关的寄存器进行压栈,然后将调用函数的地址传递给 PC程序计数器 ,所以我们就可以设置一个函数指针,将应用APP的地址传递给这个指针,然后调用即可。
但是还需要注意,由于我们跳转的应用APP,而应用APP的中断向量表仍然是Bootloader程序的中断向量表,一旦有相关中断发生,程序必然会跳回Bootloader中,带来难以想象的后果。所以我们在跳转前要关闭所有的中断,同时把堆栈指针的地址设置到应用APP中,才能放心跳转。
以下是app跳转的代码
void app_load(uint32_t app_addr) { /* 关闭时钟 */ crm_periph_clock_enable(CRM_TMR3_PERIPH_CLOCK, FALSE); crm_periph_clock_enable(CRM_USART1_PERIPH_CLOCK, FALSE); crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, FALSE); /* 关闭使用到的中断 */ nvic_irq_disable(USART1_IRQn); nvic_irq_disable(TMR3_GLOBAL_IRQn); __NVIC_ClearPendingIRQ(USART1_IRQn); __NVIC_ClearPendingIRQ(TMR3_GLOBAL_IRQn); /* 设置应用APP的Reset_Handler地址 */ jump_to_app = (iapjmp)*(uint32_t*)(app_addr + 4); /* 设置应用程序的栈顶指针地址 */ __set_MSP(*(uint32_t*)app_addr); jump_to_app();//跳转 }
到应用APP中已经非常简单了,总共操作如下:
操作和在Bootloader中差不多,也是点击魔法棒设置
在编译生成文件后,系统是产生axf,axf文件是编译默认生成的文件,不仅包含代码数据,而且还包含着调试信息,在MDK里进行debug调试用的就是这个文件,但是没法直接用于下载。
hex文件是包含地址信息,但是需要解析,也是比较麻烦。
bin文件最简单,就是一个二进制文件,只用将其中的二进制数据直接写入到对应的flash地址就可以了。
综上所述,我们直接选择bin文件就可以了,当然axf和hex也是可以,用上位机解析成bin文件就可以了。
但是keil无法直接生成bin文件,需要使用一个应用 fromelf.exe,这个是keil自带的一个应用,使用它和keil自己的自动化操作,就可以在编译结束后,把axf转化成bin了。
具体操作如下,点击魔法棒中的User选项卡。
在After Build/Rebuild勾选,在后面加入以下指令:
fromelf.exe --bin --output .\Listings\@L.bin !L
完成后,在你编译结束后,Listings 目录下就会出现bin文件了。
当Bootloader跳转到应用APP,假如响应中断,会响应Bootloader中的还是应用APP中的呢?
必然是Bootloader中。因为中断向量表的起始地址是0x0800_0000处,如果要响应APP中的中断,就必须要对中断向量表偏移一个Bootloader的程序大小的地址,这样就可以响应APP中的中断。
如何偏移呢?
在M3,M4内核中有个寄存器是专门负责中断向量的偏移。
当对这个寄存器设置后,那么所有的中断响应都是在0x0800_0000处的中断向量加上这个偏移寄存器的值作为地址,就是该地址上的存放的4字节值就是该中断函数的入口,这样就可以放心响应APP中的中断了!
但是在CMSIS中已经对该函数进行封装,我们直接调用就行
nvic_vector_table_set(NVIC_VECTTAB_FLASH, 0x4000);
第二个参数就是我们要偏移的地址。
这个就很简单了,我的做法是检测串口是否有收到符合条件的一组指令,有则在Bootloader的最后一个扇区进行相关代码的置位,然后软件复位,重新进入Bootloader进行升级
相关的代码:
void iap_communication_handle() { if(iap_receive == 1) { if(iap_buf[0] == 0xff && iap_buf[1] == 0xfe && iap_buf[2] == 0xfd && iap_buf[3] == 0xfc) { iap_flash_buf[0] = UPDATA; flash_8bit_write_sector(14 , iap_flash_buf); nvic_system_reset(); } else { iap_receive = 0; usart_set_dma((uint32_t)(&iap_buf),4); } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。