(二)ARM 单片机 bootloader


一、在系统编程ISP

芯片系统区 ROM(0x1FFFF000) 一般会有一个厂家固化好的升级程序,允许用户对芯片进行烧录和升级。但是有几个缺点:

  • 进入这个系统区程序需要在上电前控制BOOT引脚,即需要设备预留额外硬件接口;
  • 通信协议固定、通信接口固定,使用上不自由;

所以,单片机程序一般还需要自行准备一个 bootloader 来应对各种升级需求。

二、升级方案

  • 不支持对 bootloader 自身的升级。除了不安全外(bootloader自身升级失败就会彻底无解),这个功能需要把 bootloader 定位到 RAM 中跑,增加了复杂度和对编程工具的依赖,当代码(向其他编程工具)移植时很不友好;
  • 不支持app地址无关或者可重定位,即不支持app既能在A地址上跑也能在B地址上跑,否则一样造成复杂度高、依赖性强、移植性差。(因为有一个升级方案就是若A地址程序在跑就升级到B地址,若B地址程序在跑就升级到A地址);
  • 如果有需要断电保存的参数可以放在某个分区的末尾扇区,但至少要占据一个扇区,否则app对该区域擦写时可能会因为擦掉自己导致死机。

在以上几个特征的基础上,分别有以下方案:

1、方案1

  • 文件下载功能放在 bootloader 里;
  • 除去 bootloader,剩下的flash空间都给app使用。

优点:

  • app可用空间足够大。

缺点:

  • 上电后,bootloader 可能需要等待几秒钟尝试与主机连接;
  • 需要升级时,主机需要将ARM单片机单独重启一次;
  • 升级时会破坏正常的app区域,若失败后,app无法运行,直到升级成功为止。

2、方案2

  • 文件下载功能放在 app 里;
  • 除去 bootloader,剩下的flash空间一半给app使用,一半用来存储下载文件。

优点:

  • bootloader 足够简单足够小,上电后 bootloader 无需等待主机连接,可以立即启动app;
  • 运行期间可以随时下载升级文件,而不影响正常功能;
  • 升级成败与否,都不影响app的正常运行;

缺点:

  • app的可用空间缩小一半。

flash 分区布局

bootloader 区app 运行区app 参数区升级文件存储区
4K(128K - 4K) / 2 - 1K1K(128K - 4K) / 2

如此,app大小就是 61K = (128K - 4K) / 2 - 1K。
方案2的 bootloader 足够简单,以下代码就以该方案为例。

三、程序设计(方案2)

1、分区检查

bootloader 运行后,是否需要复制升级文件到运行区,决定于运行区和文件存储区的内容合法性,合法性校验典型的就是CRC校验。

分区定义

#define FLASH_PART_BOOT			0   		// bootloader 分区
#define FLASH_PART_RUN 			1   		// 运行分区
#define FLASH_PART_PARAM		2   		// 参数分区
#define FLASH_PART_UPGRADE		3   		// 升级文件分区

#define FLASH_TOTAL_SIZE	   (128*1024)   // flash总大小 128K
#define FLASH_BOOT_SIZE	       (4*1024)     // bootloader 大小 4K
#define FLASH_PARAM_SIZE	   (1*1024)     // 需要断电保存的参数大小 1K
                                            // app程序大小 61K
#define FLASH_APP_SIZE	      ((FLASH_TOTAL_SIZE - FLASH_BOOT_SIZE)/2 - FLASH_PARAM_SIZE)

分区数据合法性检查

uint16_t CRC16_CCIT_Calc(const void *data, uint32_t len);
uint32_t Boot_PartAddrGet(int partId);

// 校验一个分区是否合法,成功返回0
int Boot_PartCheck(int partId)
{
	return (CRC16_CCIT_Calc((void*)Boot_PartAddrGet(partId), FLASH_APP_SIZE) == 0) ? 0:-1;
}

升级文件末尾的CRC值,可以由PC工具在制作bin文件时完成,也可以由发送端程序得到bin文件后自动添加。

一块数据连同它的CRC一起,如果再次对其进行CRC计算,那么结果应该是0。这里的CRC是16位的,所以就有一个大小端问题,即2字节的CRC值应该以怎样的字节序放入数据块的尾部。例如下面这个CRC算法:

uint16_t CRC16_CCIT_Calc(const void *data, uint32_t len)
{
    if (data == 0 || len == 0) {
        return 0xffff;
    }

    uint16_t 	crc = 0xffff;
    uint16_t 	poly = 0x1021;
    uint8_t 	byte = 0;
    uint8_t    *buf = (uint8_t*)data;

    while (len-- > 0) {
        byte = *(buf++);
        crc ^= (byte << 8);
        for (int i=0; i<8; i++) {
            if (crc & 0x8000)
                crc = (crc << 1) ^ poly;
            else
                crc = crc << 1;
        }
    }
    return crc;
}

上述程序中,出现 crc & 0x8000,可以看出算法是先算高字节,所以CRC值应该以大端方式参与校验,即高字节在低地址处,低字节在高地址处。

2、升级检查

升级条件判定

// 比较两个分区里指定长度的内容是否相等,相等返回0
int Boot_PartCompare(int partIdx, int partIdy, uint32_t len);

// 检查是否需要升级,成功返回0
int Boot_UpgradeCheck(void)
{
	if (Boot_PartCheck(FLASH_PART_UPGRADE) == 0) {
		// 运行分区不合法,或者运行分区合法但与文件存储区不一样,就表示需要升级
		if (Boot_PartCheck(FLASH_PART_RUN) != 0 || 
		    Boot_PartCompare(FLASH_PART_RUN, FLASH_PART_UPGRADE, FLASH_APP_SIZE) != 0) {
		    return 0;
		}
	}
	return -1;
}

3、升级app

升级函数

int Boot_FlashWrite(uint32_t addr, const void *data, uint32_t len);

// 升级,成功返回0
int Boot_Upgrade(void)
{
	Boot_FlashWrite(		Boot_PartAddrGet(FLASH_PART_RUN), 
					 (void*)Boot_PartAddrGet(FLASH_PART_UPGRADE), 
							FLASH_APP_SIZE);
	if (Boot_PartCompare(FLASH_PART_RUN, FLASH_PART_UPGRADE, FLASH_APP_SIZE) != 0) {
		return -1;
	}
	return 0;
}

4、运行app

#define U32_READ(x)		(*((uint32_t*)(x)))

int Board_DeInit(void);

void Boot_AppRun(void)
{
	uint32_t sp = U32_READ(Boot_PartAddrGet(FLASH_PART_RUN));
	uint32_t rstHandler;
	
	// 检查sp指向的RAM地址是否合法
	if ((sp & 0x20000000) != 0x20000000) {
		return;
	}
	
	// 解除某些外设的初始化。例如bootloader打开了串口中断,如果忘记关闭,app里就可能意外触发串口中断导致死机
	Board_DeInit();
	
	__set_PRIMASK(1);		// 禁止中断
    __set_MSP(sp));			// 设置MSP
    __set_CONTROL(0);		// 设置特权模式,且使用MSP而不是PSP
    
    // 跳转到app的复位向量处
    rstHandler = U32_READ(Boot_PartAddrGet(FLASH_PART_RUN) + 4);
    ( (void (*)(void))rstHandler )();
}

5、主程序结构

int Board_Init(void);
int Boot_AppLost(void);

int main(void)
{
	Board_Init();
	
	if (Boot_UpgradeCheck() == 0) {
		Boot_Upgrade();
	}
	Boot_AppRun();
	
	Boot_AppLost();
	for (;;);
	return 0;
}
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值