前言 本文主要记录FM33L02x在自己写的程序中通过某些途径获取到固件后,如何写入flash,以及从 bootloader引导程序 到 用户程序 的过程。 固件数据流的获取方法(如:串口、spi、以太网、wifi等)本文不作分析。 在 bootload 示例程序中有相关说明文档,并提供了三个示例:本地上电、远程备份、远程不备份。本文将以 “ 远程备份 ” 的例程改为自己实现的远程升级固件的程序,并记录开发过程中遇到的问题。 --------------------------------------------------------------------------------------------------------------------------------------- 一、原理我们先来看看地址空间分配:
我们可以看到,flash的存放地址在 0x0000_0000 ~ 0x0002_0000。 将存储空间分4区: 地址 | 名称 | 说明 | 0~16K | bootloader区 | 用于存放boot loader引导程序 | 16~68K | 用户代码区 | 存放用户程序 | 68~120K | 用户备份区 | 存放新用户程序 | 120~122K | 配置信息区 | 存放升级配置信息和标志 |
根据个人情况,可以适当调整各区空间大小分布。 正常启动流程: - 进入引导程序。
- 查看配置信息区标志,看是否需要升级固件。
- 从引导程序跳到用户代码区,进入用户程序。
升级固件流程: - 在用户程序分包接收新固件,并写入用户备份区。
- 写入升级配置信息到配置信息区。
- 重启。
- 进入引导程序,查看配置信息区标志。
- 将用户备份区复制到用户代码区。
- 跳到用户代码区,进入用户程序。
综上,我们写代码时要创建两个工程,一个是引导程序,一个是用户程序,分别烧写到不同的两个区域。
二、bootloader引导程序宏定义: - 1 #define APPLICATION_ADDRESS_OFFSET (0x4000) //用户程序起始地址 16K
- 2 #define BOOTLOAD_PAGE_SIZE (512) //页长度(字节)
- 3 #define BOOTLOAD_CODE_LEN_MAX (0xD000) //升级代码最大长度 52K
- 4 #define BOOTLOAD_USER_START_ADDRESS (0x4000) //用户程序起始地址 16K flash处
- 5 #define BOOTLOAD_BACKUP_START_ADDRESS (0x11000) //升级暂存程序起始地址68K flash处
- 6 #define BOOTLOAD_CONFIG_START_ADDRESS (0x1E000) //升级配置信息起始地址120K flash处
main函数: int main (void)
{
uint32 JumpAddress;
int i = 0; Init_System();
Init_CRC_CRC16CCITT(); // LED0 闪5下
for (i = 0; i < 5; ++i)
{
GPIO_ToggleBits(GPIOC, GPIO_Pin_0);
TicksDelayMs( 50, NULL );
}
if(BOOTLOAD_file() == 0)// 查看是否有需要bootload的文件
{
// 升级成功
}
TicksDelayMs( 100, NULL ); //这里不加延时会卡死
/* 跳转到应用程序位置 */
JumpAddress = *(__IO uint32*) (APPLICATION_ADDRESS_OFFSET + 4);
__set_MSP(*(__IO uint32*) APPLICATION_ADDRESS_OFFSET);
(*( void (*)( ) )JumpAddress) ();
}
如果需要其他功能可以自行添加。 【注意】刷固件后,要延迟100ms左右再跳到用户程序,不然程序会卡死。 三、用户程序
1、修改中断向量偏移既然整段程序都偏移了,那么中断向量也要偏移,不然用户程序里的中断就会跑到引导程序里了! 在项目中找到 system_FM33L0XX.c,在 SystemInit() 中添加 SCB->VTOR = 0x00004000;
【注意】我这里的项目选择的是 FM33L02X,所以修改的是 FM33L02X 文件夹中的 system_FM33L0XX.c,如果你选的是 FM33L01X,那就要改 FM33L01X 文件夹里的。 2、keil的相关配置设置用户程序的地址偏移:设置修改ROM,从 0x4000 开始,长度为 0x1C000。
设置按扇区擦除,不要全擦:
3、FLASH擦写库函数在例程的 flash.c 里提供了flash的擦写库函数。 1)FLASH擦扇区函数 1 | extern uint8_t Flash_Erase_Sector( uint16_t SectorNum ); //FLASH擦扇区
- SectorNum:被擦扇区号 。512个字节为一个扇区。
- 返回值: 0:成功;其他:发生错误
2)FLASH写入 1 |extern uint8_t Flash_Write_String( uint32_t prog_addr, uint32_t* prog_data, uint16_t Len);
- prog_addr:要写入的地址
- prog_data:要写入的数据
- Len: 数据长度(4字节的个数,以 uint32_t 为单位)
- 返回值: 0:成功;其他:发生错误
【注意】flash写前必须先擦除,不然程序卡死。 四、keil生成固件
1、hex文件与bin文件的区别
参考:hex文件和bin文件的区别和联系 简单来说,hex文件是将bin文件的16进制转ASCII字符串保存起来,所以bin文件才是真正的固件。 所以我们直接写入flash的是bin文件而不是hex文件。 2、keil生成bin文件使用 fromelf.exe 工具生成bin文件。 - 在Keil里:Option - User - After Build/Rebuild 中,勾选Run #1。
- Run #1 中填入:fromelf.exe --bin -o “@L.bin” “#L”
再次重新编译工程,bin文件会生成在.uvprojx文件所在文件夹中。 五、CRC-16/CCITT 算法实现在例程中,bootloader引导程序里是用CRC-16/CCITT来校验固件的完整性。为了不对file_bootloader.c作太大的改动,我们的校验也用CRC-16/CCITT。 CRC-16/CCITT的原理和算法网上有很多: - 按位计算:运算量大,占用空间小。
- 按字节计算:运算量小,占用空间大。
- 按半字节计算:运算量比按字节计算大1倍左右,占用空间是按位计算的1/16。
大家可以根据个人情况挑选适合自己算法。
下面是我在Qt上用的按位计算的算法: /**
* x16+x12+x5+1
* 多项式 POLY 0x1021
* 初始值 INIT 0x0000
* 结果异或值 XOROUT 0x0000
* 输入数据反转(REFIN)
* 输出数据反转(REFOUT)
*/
quint16 MainWindow::CRC16CCITT(quint8 *data, quint16 len)
{
quint16 crc_reg = 0;
quint8 index;
quint16 to_xor;
for (int i = 0; i < len; i++)
{
index = (crc_reg ^ data[i) & 0xff;
to_xor = index;
for (int j = 0; j < 8; j++)
{
if (to_xor & 0x0001)
to_xor = (to_xor >> 1) ^ 0x8408;
else
to_xor >>= 1;
}
crc_reg = (crc_reg >> 8) ^ to_xor;
}
return crc_reg;
}
crc16验证工具:CRC(循环冗余校验)在线计算 六、FM33L0xx软复位查看说明书:
 代码: 1| RCC_SOFTRST_Write(0x5C5CAABB); //软件复位
原帖:https://blog.csdn.net/qq_37388044/article/details/111057497【已征得作者同意】
|