1、什么是Flash Driver
考虑到汽车ECU升级过程中可能存在外部干扰导致跑飞情况,如果直接将flash擦除有关程序固化到bootloader中,有可能在汽车运行过程中导致单片机flash被擦除或篡改的可能性,严重影响汽车的功能安全,所以一般会将与flash相关的程序单独剥离出bootloader,打包作为flash driver通过上位机下发的方式给到单片机。同时,单片机在接收到完整的flash driver包并通过校验后,会将其放入ram段执行调用;当在IAP升级过程中出现异常掉电或完成升级跳转至app程序中时,在ram段中的flash driver就不复存在了。
2、实验过程
在仔细思考flash driver运行过程后,我发现想要实现功能主要有以下疑问点:①如何将driver部分代码从flash搬入ram段执行;②如何在代码中(这里指整个代码工程)没有driver代码的情况下给driver分配一块区域;③通过什么方式将driver代码写入到规定好的ram段中。
由于仅做实验用途,我选用了最简单的测试代码——LED闪烁,测试环境为AutoChips杰发的AC7840x系列单片机开发板(ARM核 编译器Keil5),将LED闪烁的代码从工程中剔除,再通过按键触发将LED闪烁代码写入ram段,实现这个测试,下面我将根据这三个疑问点分布展开:
2.1 如何将driver部分代码从flash搬入ram段执行
通过Keil5的Linker修改.sct文件实现,操作步骤如下:
2.1.1 修改c代码
在想要放入ram段执行的函数前后分别加入#pragma arm section code = "XXX"和#pragma arm section字段,其中XXX可以自命名。要注意,如果你包含的函数中存在函数的调用,建议将所有相关的函数均包含在section字段中间(初始化函数不用放入),方便我们后面进行实验。
#pragma arm section code = "LED_DRV"
/*!
* @brief GPIO翻转点亮函数
*
* @param[in] none
* @return none
*/
void LED_DRV_TOGGLE(void)
{
GPIOC->PIOR = (1<<6);
GPIOC->PIOR = (1<<7);
}
#pragma arm section
2.1.2 修改.sct文件
1、在勾选Use Memory Layout from Target Dialog的情况下先进行一次编译,编译器会根据你Target页的配置生成一个.sct文件
2、点击options魔术棒,选择linker
3、取消勾选Use Memory Layout from Target Dialog
3、点击Edit,即可弹出.sct文件
4、在单片机ram段处加入代码 *.o (LED_DRV),括号内的名字为前面在c代码中定义的section名称,这里大家根据自己用的单片机调下添加的地址即可,博主用的单片机SRAM起始地址为0x1FFF0000
LR_IROM1 0x00000000 0x00100000 { ; load region size_region
ER_IROM1 0x00000000 0x00100000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
}
RW_IRAM1 0x1FFF0000 0x00010000 { ; RW data
*.o (LED_DRV)
}
RW_IRAM2 0x20000000 0x00010000 { ; RW data
.ANY (+RW +ZI)
}
}
5、编译后查看map文件即可发现,编译器已经将flash的地址搬入ram中了,此时仿真程序也发现pc指针也指向的ram段
Execution Region LED_DRV (Exec base: 0x1fff0000, Load base: 0x00003008, Size: 0x00000018, Max: 0x00000018, ABSOLUTE)
Exec Addr Load Addr Size Type Attr Idx E Section Name Object
0x1fff0000 0x00003008 0x00000018 Code RO 104 LED_DRV gpio.o
2.2 如何在代码中(这里指整个代码工程)没有driver代码的情况下给driver分配一块区域
我们回到2.1中说到的.sct文件,之前添加的代码将LED_DRV这个section放在了RW_IRAM1下,并且通过map文件我们可以知道,咱们的LED_DRV占用了0x18个大小的字节,知道了以上信息,下面更改一下.sct文件,将LED_DRV从RW_IRAM1里分离出来就可以了。
LR_IROM1 0x00000000 0x00100000 { ; load region size_region
ER_IROM1 0x00000000 0x00100000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
}
RW_IRAM1 0x1FFF0000 0x00010000 { ; RW data
*.o (LED_DRV)
}
RW_IRAM2 0x20000000 0x00010000 { ; RW data
.ANY (+RW +ZI)
}
}
改动完的.sct文件如下:可以看到我们单独定义了一块区域专门用来放0x18大小的LED_DRV section,这样我们就在SRAM开辟了一块区域。
LR_IROM1 0x00000000 0x00100000 { ; load region size_region
ER_IROM1 0x00000000 0x00100000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
}
LED_DRV 0x1FFF0000 0x00000018 { ; LED_DRV code
*.o (LED_DRV)
}
RW_IRAM1 0x1FFF0018 0x0000FFE8 { ; RW data
}
RW_IRAM2 0x20000000 0x00010000 { ; RW data
.ANY (+RW +ZI)
}
}
有的朋友就要说了,不对啊,光改了sct文件你现在flash里还是存在原来咱们在LED_DRV里添加的函数代码啊。说的没问题,下面我们之间把函数里面的内容删除,就留下一个“空壳”函数不就完成了咱们需要的操作了吗?代码如下:至此咱们完成了两项疑问点的操作,就剩下一项了,把LED_DRV_TOGGLE这个函数里面的代码重新放回到ram里去执行。
#pragma arm section code = "LED_DRV"
/*!
* @brief GPIO翻转点亮函数
*
* @param[in] none
* @return none
*/
void LED_DRV_TOGGLE(void)
{
//空函数等待写入
// GPIOC->PIOR = (1<<6);
// GPIOC->PIOR = (1<<7);
}
#pragma arm section
2.3 通过什么方式将driver代码写入到规定好的ram段中
首先,既然要写到ram里,我们就需要有东西写进去,可以通过查看hex文件的方式了解到目标写入的内容。在2.1中还没有删除driver部分的代码,从map文件可以看到载入地址为0x00003008。
打开hex文件找到地址所在代码数据,从0x00003008后共查找24个字节,即为咱们要的led driver代码了。
那么,重点到用什么方式写入呢,这里想了一些方法还是决定把led driver代码放到数组里,再传输到ram上即可,至此就完成了搬运的操作了,实验的目的也达成了。下面贴一下主函数给大家参考,在正常上电后LED为熄灭状态,当开关按下后在ram段写入led闪烁的驱动代码,然后就可以看到led以1s的周期闪烁了。
uint8_t function_code[0x18] = {0x40, 0x20, 0x04, 0x49, 0x08, 0x60, 0x80, 0x20, 0x02, 0x49, 0x8C, 0x39, 0xC1, 0xF8, 0x8C, 0x00, 0x70, 0x47, 0x00, 0x00, 0x8C, 0x50, 0x08, 0x40};
int main(void)
{
uint8_t i;
SystemClock_Config(); //初始化时钟
GPIO_LedInit(); //初始化板载LED引脚
GPIO_KeyInit(); //初始化板载按键引脚
while (1)
{
if(1 == Get_Key4Value()) //按下开关后写入
{
for(i = 0; i<0x18; i++)
{
*(uint8_t *)(0x1fff0000 + i) = function_code[i];
}
}
LED_DRV_TOGGLE();
OSIF_TimeDelay(500);
}
}
3、小结
通过此实验发现了一些问题,这里提出来:1、所有driver相关的函数需要全部放入ram段,不然编译器在编译之后会在map文件中添加一些链接,相当于还有一部分代码还是在flash中,没有达到driver不放在flash中的目的;2、文章只是使用了总工程下的hex文件作为写入ram的代码参考,如何单独制作一个driver工程并使用其hex文件,还存在一些问题。