大家好,欢迎来到IT知识分享网。
PX4 BootLoader原理简析
一、引言
看完本文,您会了解PX4 BootLoader中几个重要文件以及作用,掌握PX4 BootLoader的基本功能及原理。
PX4 BootLoader是一个涉及多系列主板的成熟BootLoader,它涉及非常多的文件和库。本节我们将挑选几个比较的重要文件进行介绍。这些文件名称及其简要作用如下:
文件名 | 主要作用 |
---|---|
Makefile | 编译主文件。包含PX系列各种设备的编译信息。 |
hw_config.h | 主配置文件。包含PX系列各种设备的配置信息。 |
main_f1.c | 主函数文件。不同系列的芯片有不同的主函数文件。其中,f1代表stm32f1系列。文件夹中还有main_f3.c、main_f4.c、main_f7.c。分别代表stm32f3系列的主函数文件、stm32f4系列的主函数文件和stm32f7系列的主函数文件。 |
bl.c | 公共函数文件。包含PX系列各种设备都会使用的公共函数。 |
二、Makefile文件简析
Makefile文件主要用于配置最终固件的编译。
首先,PX系列不同设备所使用的stm32芯片各不相同。而Makefile文件可以帮助我们筛选出使用stm32f1、stm32f3、stm32f4、stm32f7系列的PX设备。这样,我们可以很快的找到目标设备所对应的配置文件和主函数文件。
我们可以观察PX系列在编译时所链接的文件名称,即LINKER_FILE所对应的文件名。它揭示了该款设备使用的是什么系列的芯片。
其次,PX4有多种多样的设备。默认情况下,使用make命令会编译所有设备的固件,编译时间较长。因此,我们在确定使用哪个PX系列的设备后,可以通过Makefile文件进行调整。从而只编译该设备的固件,进而缩短编译时间,提高工作效率。具体来说,我们可以删除导出目录的对应设备名称,同时删除该设备的编译命令,即可在编译时取消编译该设备的固件。
三、hw_config.h文件简析
hw_config.h文件主要包含PX系列各类设备的配置信息。我们在Makefile中挑选好目标设备后,可以在这里查看该设备的配置文件。
该文件由上到下可以分为几部分。首先是接口信息和配置信息部分、第二是LED灯部分、第三是USART配置部分、第四是强制启用BootLoader部分、第五是flash相关配置部分。
一般情况下,PX4的开发者们已经对各种系列的设备做了完善的配置,我们并不需要修改hw.config.h配置文件中的内容。但是,如果我们有更高级的需求,就需要我们对照所要部署平台的stm32芯片使用手册来对照相应的芯片信息和管脚信息,从而实现高级的自定义修改。PX4 BootLoader中不同芯片的相关定义文件十分完善,我们可以善用全局搜索功能找到对应系列的定义文件,从而确认我们可以怎么修改。
下面以PX4_PIO_V1设备为例,简要介绍一下配置文件中各个设置的功能。以下为配置信息及其注释:
#elif defined(TARGET_HW_PX4_PIO_V1) || defined(TARGET_HW_PX4_PIO_V2) # define APP_LOAD_ADDRESS 0x0 // 程序固件的烧录地址。 # define APP_SIZE_MAX 0xf000 // 程序固件的最大大小。如果不使用PX的BootLoader烧录程序固件的话可以不修改。 # define BOOTLOADER_DELAY 200 // BootLoader在200毫秒后自行启动。如果想执行BootLoader中的命令,最好修改得大一点。或者在BootLoader中重置该值。 # define BOARD_PIO # define INTERFACE_USB 0 // 使能USB接口,此处为0表示不开启USB端口。 # define INTERFACE_USART 1 // 使能USART接口,此处为1表示开启USART端口。 # define USBDEVICESTRING "" // USB设备描述符,由于不开启USB端口,设备描述符可为空。 # define USBPRODUCTID -1 // USB产品ID,由于不开启USB端口,产品ID为-1。 # define OSC_FREQ 24 // 晶振频率。 # define BOARD_PIN_LED_ACTIVITY GPIO14 // 定义LED灯引脚。 # define BOARD_PIN_LED_BOOTLOADER GPIO15 // 定义LED灯引脚。 # define BOARD_PORT_LEDS GPIOB // 定义LED灯引脚。 # define BOARD_CLOCK_LEDS_REGISTER RCC_APB2ENR // 使能LED灯相关寄存器引脚。 # define BOARD_CLOCK_LEDS RCC_APB2ENR_IOPBEN // 使能LED灯相关IO引脚。 # define BOARD_LED_ON gpio_clear // 重定义开启LED灯。 # define BOARD_LED_OFF gpio_set // 重定义关闭LED灯。 # define BOARD_USART USART2 // 定义USART协议。 # define BOARD_USART_CLOCK_REGISTER RCC_APB1ENR // 使能USART相关寄存器的引脚。 # define BOARD_USART_CLOCK_BIT RCC_APB1ENR_USART2EN // 使能USART2相关比特率的引脚。 # define BOARD_PORT_USART GPIOA // 定义USART引脚。 # define BOARD_PIN_TX GPIO_USART2_TX // 定义USART发送引脚。 # define BOARD_PIN_RX GPIO_USART2_RX // 定义USART接收引脚。 # define BOARD_USART_PIN_CLOCK_REGISTER RCC_APB2ENR # define BOARD_USART_PIN_CLOCK_BIT RCC_APB2ENR_IOPAEN # define BOARD_FORCE_BL_PIN GPIO5 // 强制进入BootLoader的一些配置。一般不会用到,不做介绍 # define BOARD_FORCE_BL_PORT GPIOB # define BOARD_FORCE_BL_CLOCK_REGISTER RCC_APB2ENR # define BOARD_FORCE_BL_CLOCK_BIT RCC_APB2ENR_IOPBEN # define BOARD_FORCE_BL_PULL GPIO_CNF_INPUT_FLOAT // depend on external pull # define BOARD_FORCE_BL_VALUE BOARD_FORCE_BL_PIN # define BOARD_FLASH_SECTORS 60 // 板载闪存扇区数。 # define BOARD_TYPE 10 // 主板类型标识符。 # define FLASH_SECTOR_SIZE 0x400 // 板载闪存扇区大小。 # define NO_OTP_SN_CHIP 1 // 有无OTP芯片。
同时,hw_config.h文件末尾还有一些所有设备都会使用的公共配置信息。其中最重要就是定义USART所使用的波特率。可以观察到,没有自行定义时波特时默认是。
// 定义USART所使用的波特率 #if defined(OVERRIDE_USART_BAUDRATE) # define USART_BAUDRATE OVERRIDE_USART_BAUDRATE #else # define USART_BAUDRATE #endif
四、main_f1.c文件简析
main_f1.c是主函数文件,它是stm32f1芯片启动时的主函数。其中,f1代表stm32f1系列。文件夹中还有main_f3.c、main_f4.c、main_f7.c,分别代表stm32f3系列的主函数文件、stm32f4系列的主函数文件和stm32f7系列的主函数文件。
如果我们选择的开发板使用的是stmn32f1系列的芯片,那么开发板在上电后BootLoader程序会从main_f1.c最下面的main函数开始执行。因此,我们将以main_f1.c文件中的main函数为例对main函数做详细介绍。下面是对该函数详细的注释:
int main(void) {
unsigned timeout = 0; /* 主板初始化函数。主要配置LED灯光、USART通信接口和I2C通信接口相关的时钟、引脚。*/ board_init(); /*判断是否启用BootLoader等待。如果启用,程序会先进入BootLoader,同时计时器启动,等待通过USART通信接口或USB通信接口与用户交互。如果在执行完一条命令后到达等待时间,则会离开BootLoader,开始执行用户程序。一般来说,USART通信接口或USB通信接口两者总有一个有定义。因此BootLoader等待在大多数情况下都是有效的。*/ #if defined(INTERFACE_USART) || defined (INTERFACE_USB) /* XXX sniff for a USART connection to decide whether to wait in the bootloader? */ timeout = BOOTLOADER_DELAY; #endif /* Bootloader不支持stm32f1系列的开发板的I2C接口。因此如果遇到想要启用I2C接口,程序会报错。*/ #ifdef INTERFACE_I2C # error I2C bootloader detection logic not implemented #endif /* if the app left a cookie saying we should wait, then wait */ if (should_wait()) {
timeout = BOOTLOADER_DELAY; } /* 如果启用了强制等待BootLoader功能并且该引脚也有信号,那么不会再自动离开BootLoader。 */ #ifdef BOARD_FORCE_BL_PIN /* if the force-BL pin state matches the state of the pin, wait in the bootloader forever */ if (BOARD_FORCE_BL_VALUE == gpio_get(BOARD_FORCE_BL_PORT, BOARD_FORCE_BL_PIN)) {
timeout = 0xffffffff; } #endif /* look for the magic wait-in-bootloader value in backup register zero */ /* if we aren't expected to wait in the bootloader, try to boot immediately */ /* 如果在hw_config.h中定义的BOOTLOADER_DELAY为0,即没有BootLoader等待时间,那么尝试直接跳转到用户程序。跳转成功会直接执行用户程序,跳转失败会进入BootLoader并一直待在里面。 */ if (timeout == 0) {
/* try to boot immediately */ jump_to_app(); /* if we returned, there is no app; go to the bootloader and stay there */ timeout = 0; } /* 初始化BootLoader需要用到的时钟。如果定义了USB接口,那么输出的时钟为48mhz。反之,输出的时钟为24mhz。 */ /* configure the clock for bootloader activity */ clock_init(); /* 初始化主板上的USART通信接口。 */ /* start the interface */ cinit(BOARD_INTERFACE_CONFIG, USART); /* 我觉得英文注释就够了,对......吧? */ while (1) {
/* run the bootloader, possibly coming back after the timeout */ bootloader(timeout); /* look to see if we can boot the app */ jump_to_app(); /* boot failed; stay in the bootloader forever next time */ timeout = 0; } }
五、bl.c文件简析
bl.c是公共函数文件,它包含PX系列各种设备都会使用的公共函数。在之前对main_f1.c文件的分析中我们已经发现,main_f1.c文件中最重要的函数是main()函数,而main()函数中比较最重要的函数又是jump_to_app()函数和bootloader()函数。这两个函数其实就是来自bl.c文件。main_f1.c文件会通过引用bl.h从而使用bl.c文件中的函数。因此,本节主要介绍jump_to_app()函数和bootloader()函数。
(一)jump_to_app()函数简析
我们首先介绍jump_to_app()函数。该函数的主要作用如下:
从逻辑图中我们可以看到,该函数主要有两个功能:一是校验固件,二是跳转到固件。其中,校验固件是否有效又分为两步:校验固件是否烧写完成和校验固件的跳转地址是否有效。
1.校验固件功能简析
我们先来介绍它是怎么校验固件是否有效的。PX4的BootLoader烧写固件时,会先将除BootLoader外的部分擦写为0xffffffff(处理的最小单位是32位,4个字节)。然后,在烧写固件时,会最后烧写固件前32位的值。因此,如果固件成功完成烧写,那么固件的前32位就不会是0xffffffff。反之,如果固件没有完成烧写,那么固件的前32位就是0xffffffff。jump_to_app()函数也就依此来判断固件是否烧写完成。
2.跳转到固件功能简析
然后,我们来介绍它是怎么校验固件的跳转地址是否有效的。首先我们要明白,APP_LOAD_ADDRESS是固件的烧写地址,即固件烧写时从这个地址开始烧写。但固件开始执行的地址并不是APP_LOAD_ADDRESS,即固件不是从烧写地址开始执行(有data segment和stack segment,汇编知识)。我们约定,数据段的第一个32位,即固件最开始的32位,存放堆栈段地址;数据段的第二个32位,即固件的32位-64位,存放固件执行地址。从下面的例图中我们可以看到,固件的前32位为0x(小端序),即使用的堆栈段地址为0x;固件的32位-64位为0x0,即固件开始执行时的地址为0x0。
因此,jump_to_app()函数会依据固件的跳转地址,即固件的32位-64位判断这个固件是否能成功跳转。只有这个地址在APP_LOAD_ADDRESS(跳转地址不可能跑到固件烧写地址之前)到APP_LOAD_ADDRESS+board_info.fw_size(boar_info.fw_size是hw_config.h中APP_SIZE_MAX,代表固件的最大大小)之间时,该固件才可以成功跳转,即固件有效。
3.函数详细注释
最后是对jump_to_app()函数的详细注释:
void jump_to_app() // 跳转函数 {
// unsigned int 32位 代表四个字节,即为 int 类型; // 0x 0800 4000 4*8=32位 const uint32_t *app_base = (const uint32_t *)APP_LOAD_ADDRESS; // 0x0 飞控固件的起始地址,定义在hw_config.h中 const uint32_t *vec_base = (const uint32_t *)app_base; /* * We refuse to program the first word of the app until the upload is marked * complete by the host. So if it's not 0xffffffff, we should try booting it. */ //1. 根据飞控固件的烧写约定,检测固件是否有效 // 飞控固件烧写时,我们特意约定最后烧写固件的首地址(飞控固件使用的堆栈首地址)。 //若固件首地址为0xffffffff,表明固件烧写未完成(或固件无效)无法跳转,函数直接返回 */ if (app_base[0] == 0xffffffff) {
return; } /* 关于安全启动的代码,可以不用理会 */ #ifdef SECURE_BTL_ENABLED ...... #endif /* * The second word of the app is the entrypoint; it must point within the * flash area (or we have a bad flash). */ //飞控固件的第二个32位代表飞控固件的入口地址。 //若此地址未在固件指定的地址范围内,则表示固件有问题无法跳转,函数直接返回 if (app_base[1] < APP_LOAD_ADDRESS) {
return; } // //烧写内容太大返回 if (app_base[1] >= (APP_LOAD_ADDRESS + board_info.fw_size)) {
return; } /* 2. 现在飞控固件有效,反向初始化以便把外设控制权交给飞控固件 */ /* just for paranoia's sake */ arch_flash_lock(); /* kill the systick interrupt */ arch_systic_deinit(); /* deinitialise the interface */ cfini(); /* reset the clock */ clock_deinit(); /* deinitialise the board */ board_deinit(); /* 3. 更改向量表地址至飞控固件的向量表 */ /* SCB_VTOR:寄存器,向量表偏移地址,地址0xE000ED08 */ /* APP_LOAD_ADDRESS:飞控固件起始地址。0x0(主控FMU),0x0(IO协处理器) */ // SCB_VTOR = APP_LOAD_ADDRESS; /* switch exception handlers to the application */ arch_setvtor((uint32_t)vec_base); /* extract the stack and entrypoint from the app vector table and go */ ///* 4. 重置堆栈并跳转至飞控固件 */ do_jump(app_base[0], app_base[1]); }
(二)bootloader()函数简析
接着,我们介绍bootloader()函数。该函数的主要功能如下:
还记得我们在main_f1.c文件中调用bootloader时传递的timeout参数吗?这里就派上了用场!在刚进入bootloader()函数时,它会启动一个定时器,然后尝试从USART、USB、I2C等通信端口读取数据并执行对应的命令。每次执行完命令后,它都会对比定时器和timeout的值。一旦定时器的值超过了timeout,bootloader()函数就会强制返回。
我们与BootLoader交互均采用16进制的字节数据。在bl.c文件头部可以看到这些字节数据所代表的意思。我们向BootLoader发送命令时结尾必须加上0x20,即PROTO_EOC,代表命令输入结束。
下面,我们分别来介绍bootloader()函数的各个功能。
1.同步功能简析
首先是同步功能,其命令为:0x21 0x20。这个功能主要是指定bootload()函数输出数据时所使用的通信端口。在main_f1.c文件的main()函数中,我们已经初始化了很多通信端口(本文中举例的设备型号只初始化了USART端口)。那么,bootloader()是怎么知道我们通信使用的具体是USB端口还是USART端口呢?
当我们通过某一接口连接到主板,进入BootLoader后,BootLoader会监听设备上的所有通信端口。此时,我们可以通过某一端口向BootLoader发送0x21和0x20两个字节的数据(0x21是PROTO_GET_SYNC代表同步命令;0x20是PROTO_EOC,代表结束命令,表示本次数据传送完毕。)。然后,bootloader()函数就会使用我们发送同步命令时所使用的端口与我们进行通信。
因此,我们也可以看出,同步功能是非常重要的。我们在进入BootLoader后一定要先发送0x21和0x20命令,这样我们才能与BootLoader正确的通信。
假如我们在刚进入BootLoader时不发送同步命令,而是直接发送其他命令,那么我们很有可能无法收到BootLoader给我们的响应数据。但是,PX4 BootLoader在设计时已经考虑了这一情况。事实上,我们在刚进入BootLoader后,无论发送的是什么数据,PX4的BootLoader都会记录接收到数据的端口。下一次直接使用该端口与我们进行通信。因此,如果我们不执行同步命令,直接执行其他命令,很有可能会出现第一次执行命令后没有响应,但第二次执行命令及以后成功获取到响应的情况。
在与BootLoader通信时我们需要勾选上16进制发送和16进制接收。可以观察到,我们向BootLoader发送了0x21和0x20,BootLoader向我们返回了0x12和0x10。结合上面的命令字节对照表,我们可以知道这是PROTO_INSYNC和PROTO_OK,即状态同步字节和“ok”字节。
// sync // // command: GET_SYNC/EOC // reply: INSYNC/OK // case PROTO_GET_SYNC: // 若为同步命令PROTO_GET_SYNC /* expect EOC */ if (!wait_for_eoc(2)) {
goto cmd_bad; } SET_BL_FIRST_STATE(STATE_PROTO_GET_SYNC); break;
2.获取设备ID功能简析
第二是获取设备ID功能,其命令为:0x22 <选项> 0x20。通过这个功能,我们可以获取到设备的BootLoader修订号、主板型号、主板修订号、固件最大大小、保留扇区中的内容。具体来说,我们需要先发送0x22表明我们即将要获取设备信息。紧接着,我们需要选择获取什么设备信息。各个选项如下:设备的BootLoader修订号(0x1)、主板型号(0x2)、主板修订号(0x3)、固件最大大小(0x4)、保留扇区中的内容(0x5)。最后,我们需要补上结束命令0x20。
// get device info // // command: GET_DEVICE/<arg:1>/EOC // BL_REV reply: <revision:4>/INSYNC/EOC // BOARD_ID reply: <board type:4>/INSYNC/EOC // BOARD_REV reply: <board rev:4>/INSYNC/EOC // FW_SIZE reply: <firmware size:4>/INSYNC/EOC // VEC_AREA reply <vectors 7-10:16>/INSYNC/EOC // bad arg reply: INSYNC/INVALID // case PROTO_GET_DEVICE: // 若为获取设备ID命令PROTO_GET_DEVICE 输入命令 0x22 、 继续输入 1 2 3 4 5 /* expect arg then EOC */ arg = cin_wait(1000); // 1个字节 if (arg < 0) {
goto cmd_bad; } if (!wait_for_eoc(2)) {
goto cmd_bad; } switch (arg) {
case PROTO_DEVICE_BL_REV: // 1 // uart_cout(buf, len); cout((uint8_t *)&bl_proto_rev, sizeof(bl_proto_rev)); break; case PROTO_DEVICE_BOARD_ID: cout((uint8_t *)&board_info.board_type, sizeof(board_info.board_type)); break; case PROTO_DEVICE_BOARD_REV: cout((uint8_t *)&board_info.board_rev, sizeof(board_info.board_rev)); break; case PROTO_DEVICE_FW_SIZE: cout((uint8_t *)&board_info.fw_size, sizeof(board_info.fw_size)); break; case PROTO_DEVICE_VEC_AREA: for (unsigned p = 7; p <= 10; p++) {
uint32_t bytes = flash_func_read_word(p * 4); cout((uint8_t *)&bytes, sizeof(bytes)); } break; default: goto cmd_bad; } SET_BL_STATE(STATE_PROTO_GET_DEVICE); break;
3.擦写Flash准备烧写固件功能简析
第三是擦写Flash准备烧写固件功能,其命令为:0x23 0x20。正如之间校验固件功能简析中所提到的,使用BootLoader烧录固件时,需要先使用擦写Flash命令将固件所在区域重置为0xffffffff。具体来说,该功能会先检查配置文件中是否允许擦写固件区域,然后配置led灯使其常亮,接着解锁固件区域并进行擦写,然后熄灭led灯,通过读取固件区域中的数据和0xffffffff进行对比校验,校验完成后将led灯设置为闪烁。
// erase and prepare for programming // // command: ERASE/EOC // success reply: INSYNC/OK // erase failure: INSYNC/FAILURE // case PROTO_CHIP_ERASE: // 若为擦除flash与准备烧写飞控固件指令PROTO_CHIP_ERASE /* expect EOC */ if (!wait_for_eoc(2)) {
goto cmd_bad; } #if defined(TARGET_HW_PX4_FMU_V4) || defined(TARGET_HW_UVIFY_CORE) if (check_silicon()) {
goto bad_silicon; } #endif if ((bl_state & STATE_ALLOWS_ERASE) != STATE_ALLOWS_ERASE) {
goto cmd_bad; } // clear the bootloader LED while erasing - it stops blinking at random // and that's confusing led_set(LED_ON); // erase all sectors arch_flash_unlock(); for (int i = 0; flash_func_sector_size(i) != 0; i++) {
flash_func_erase_sector(i); // 擦除flash } // disable the LED while verifying the erase led_set(LED_OFF); // verify the erase for (address = 0; address < board_info.fw_size; address += 4) if (flash_func_read_word(address) != 0xffffffff) {
goto cmd_fail; } address = 0; SET_BL_STATE(STATE_PROTO_CHIP_ERASE); // resume blinking led_set(LED_BLINK); break;
4.烧写固件功能简析
第四是烧写固件功能,其命令为:0x27 <固件大小> <固件> <0x20>。我们需要先发送烧写固件命令0x27,然后发送固件长度,紧接着发送固件文件,最后以终止命令结束。
具体来说,它首先会检查固件长度是否符合要求,小于0、不为整数、大于最大固件长度均不符合要求。然后,将接收到的固件放入缓冲区(缓冲区默认为256字节,大小可能需要根据自编译的固件大小进行调整,如果使用PX官方固件不存在这个问题)。最后将固件写入闪存,并通过对比读取出的闪存和缓冲区中的数据来进行校验。
正如我们之前在校验固件功能中介绍的,烧写需要先保存固件第一个32位的值,最后再进行写入,为检查固件是否烧写完成提供依据。因此,执行完该功能后,固件实际并没有烧写完毕,固件中第一个32位的值还没有进行烧写。只有在我们执行“跳转到固件”功能后,固件中第一个32位的值才会正常烧写。因此,这个烧写固件的功能实际上是烧写不完全的。
// program bytes at current address // // command: PROG_MULTI/<len:1>/<data:len>/EOC // success reply: INSYNC/OK // invalid reply: INSYNC/INVALID // readback failure: INSYNC/FAILURE // case PROTO_PROG_MULTI: // program bytes 若为flash烧写命令PROTO_PROG_MULTI // expect count arg = cin_wait(50); // 输入 arg 收到的第一个参数(长度arg) if (arg < 0) {
goto cmd_bad; } // sanity-check arguments if (arg % 4) {
// //必须是整数 goto cmd_bad; } // 若当前编写地址(address)+第一个参数(长度arg)超出飞控固件最大范围board_info.fw_size if ((address + arg) > board_info.fw_size) {
goto cmd_bad; } // 若收到的第一个参数(长度arg)超出缓冲区长度,按照错误命令处理 if ((unsigned int)arg > sizeof(flash_buffer.c)) {
goto cmd_bad; } // 根据长度arg逐字节接收数据,并存放在缓冲区flash_buffer中;若其中任何1个字节超时1s,按照错误命令处理, // flash_buffer:共同体,代表接收缓冲区(256字节或32字) for (int i = 0; i < arg; i++) {
c = cin_wait(1000); // 一直输入 if (c < 0) {
goto cmd_bad; } flash_buffer.c[i] = c; //flash 空间 c[256] } // 接收完有效数据后,若200ms内未接收到命令终止字(PROTO_EOC),按照错误命令处理, if (!wait_for_eoc(200)) {
goto cmd_bad; } // 若当前编写地址address为0,则将烧写缓冲区flash_buffer首个32位保存在变量first_word中,并将缓冲区首改为0xFFFFFFFF */ if (address == 0) {
#if defined(TARGET_HW_PX4_FMU_V4) || defined(TARGET_HW_UVIFY_CORE) if (check_silicon()) {
goto bad_silicon; } #endif // save the first word and don't program it until everything else is done first_word = flash_buffer.w[0]; // replace first word with bits we can overwrite later flash_buffer.w[0] = 0xffffffff; } // 逐字烧写flash地址,若写入与读出不一致,进行命令失败处理,回告结构:(PROTO_INSYNC+PROTO_FAILED) */ arg /= 4; for (int i = 0; i < arg; i++) {
// program the word flash_func_write_word(address, flash_buffer.w[i]); // do immediate read-back verify // //判断写的和读取的是否一致 if (flash_func_read_word(address) != flash_buffer.w[i]) {
// 回读验证 goto cmd_fail; } address += 4; } SET_BL_STATE(STATE_PROTO_PROG_MULTI); break;
5.CRC校验功能简析
第五是CRC校验功能,其命令为:0x29 0x20。该功能会计算固件区域的CRC值并返回。
// fetch CRC of the entire flash area // // command: GET_CRC/EOC // reply: <crc:4>/INSYNC/OK // case PROTO_GET_CRC: // 若为CRC32校验命令PROTO_GET_CRC // expect EOC if (!wait_for_eoc(2)) {
goto cmd_bad; } // compute CRC of the programmed area uint32_t sum = 0; for (unsigned p = 0; p < board_info.fw_size; p += 4) {
uint32_t bytes; if ((p == 0) && (first_word != 0xffffffff)) {
bytes = first_word; } else {
bytes = flash_func_read_word(p); } sum = crc32((uint8_t *)&bytes, sizeof(bytes), sum); } cout_word(sum); SET_BL_STATE(STATE_PROTO_GET_CRC); break;
6.读取OTP区域功能简析
第六是读取OTP区域功能,其命令为:0x2a <地址> 0x20。该功能可以读取OTP指定地址的数据。
// read a word from the OTP // // command: GET_OTP/<addr:4>/EOC // reply: <value:4>/INSYNC/OK case PROTO_GET_OTP: // 若为读取OTP区域命令PROTO_GET_OTP // expect argument {
uint32_t index = 0; if (cin_word(&index, 100)) {
goto cmd_bad; } // expect EOC if (!wait_for_eoc(2)) {
goto cmd_bad; } cout_word(flash_func_read_otp(index)); } break;
7.获取MCU的UDID功能简析
第七是获取MCU的UDID功能,其命令为:0x2b <地址> 0x20。该功能可以获取在指定区域存储的芯片的UDID。芯片UDID所在地址需要用户自己指定。
// read the SN from the UDID // // command: GET_SN/<addr:4>/EOC // reply: <value:4>/INSYNC/OK case PROTO_GET_SN: // 若为获取MCU的UDID (Unique Device ID,或称为序列号) 0x2b // expect argument {
uint32_t index = 0; if (cin_word(&index, 100)) {
goto cmd_bad; } // expect EOC if (!wait_for_eoc(2)) {
goto cmd_bad; } // expect valid indices 0, 4 ...ARCH_SN_MAX_LENGTH-4 if (index % sizeof(uint32_t) != 0 || index > ARCH_SN_MAX_LENGTH - sizeof(uint32_t)) {
goto cmd_bad; } cout_word(flash_func_read_sn(index)); } SET_BL_STATE(STATE_PROTO_GET_SN); break;
8.获取芯片ID功能简析
第八是获取芯片ID功能,其命令为:0x2c 0x20。该功能可以获取芯片ID并返回。
// read the chip ID code // // command: GET_CHIP/EOC // reply: <value:4>/INSYNC/OK case PROTO_GET_CHIP: {
// 若为获取芯片ID和版本信息PROTO_GET_CHIP // expect EOC if (!wait_for_eoc(2)) {
goto cmd_bad; } // //获取芯片独特的ID cout_word(get_mcu_id()); SET_BL_STATE(STATE_PROTO_GET_CHIP); } break;
9.获取芯片描述信息功能简析
第九是获取芯片描述信息,其命令为:0x2e 0x20。该功能可以获取芯片中的描述信息。
// read the chip description // // command: GET_CHIP_DES/EOC // reply: <value:4>/INSYNC/OK case PROTO_GET_CHIP_DES: {
// 若为获取芯片描述信息PROTO_GET_CHIP_DES uint8_t buffer[MAX_DES_LENGTH]; unsigned len = MAX_DES_LENGTH; // expect EOC if (!wait_for_eoc(2)) {
goto cmd_bad; } len = get_mcu_desc(len, buffer); cout_word(len); cout(buffer, len); SET_BL_STATE(STATE_PROTO_GET_CHIP_DES); } break;
10.设置固件启动延时功能简析
第十是设置固件启动延时功能,其命令为:0x2d <延时时间> 0x20。该功能可以设定固件启动延时,即之前在hw_config_h中提到过的BOOTLOADER_DELAY。一旦程序进入BootLoader后的时间大于该值,程序就会自动退出BootLoader并尝试启动固件。延时时间最多为255秒。
case PROTO_SET_DELAY: {
// 若为设置飞控固件启动延时PROTO_SET_DELAY /* Allow for the bootloader to setup a boot delay signature which tells the board to delay for at least a specified number of seconds on boot. */ int v = cin_wait(100); if (v < 0) {
goto cmd_bad; } uint8_t boot_delay = v & 0xFF; if (boot_delay > BOOT_DELAY_MAX) {
goto cmd_bad; } // expect EOC if (!wait_for_eoc(2)) {
goto cmd_bad; } uint32_t sig1 = flash_func_read_word(BOOT_DELAY_ADDRESS); uint32_t sig2 = flash_func_read_word(BOOT_DELAY_ADDRESS + 4); if (sig1 != BOOT_DELAY_SIGNATURE1 || sig2 != BOOT_DELAY_SIGNATURE2) {
goto cmd_bad; } uint32_t value = (BOOT_DELAY_SIGNATURE1 & 0xFFFFFF00) | boot_delay; flash_func_write_word(BOOT_DELAY_ADDRESS, value); if (flash_func_read_word(BOOT_DELAY_ADDRESS) != value) {
goto cmd_fail; } } break;
11.跳转到固件功能简析
第十一是跳转到固件功能,其命令为0x30 0x20。它与固件烧写功能紧密连接。在前面的介绍中我们已经知道,固件烧写功能不会烧写固件的前32位值。而该功能完成的就是固件烧写功能最后的工作,即烧写固件的前32位值并跳转到固件开始执行。
// finalise programming and boot the system // // command: BOOT/EOC // reply: INSYNC/OK // case PROTO_BOOT: // 若为完成烧写并启动飞控固件PROTO_BOOT // expect EOC if (!wait_for_eoc(1000)) {
goto cmd_bad; } // 若变量first_word内容有效,烧写飞控固件首字并判断是否烧写成功 if (first_word != 0xffffffff && (bl_state & STATE_ALLOWS_REBOOT) != STATE_ALLOWS_REBOOT) {
goto cmd_bad; } // program the deferred first word if (first_word != 0xffffffff) {
flash_func_write_word(0, first_word); if (flash_func_read_word(0) != first_word) {
goto cmd_fail; } // revert in case the flash was bad... first_word = 0xffffffff; } // send a sync and wait for it to be collected sync_response(); // 运行正常发送PROTO_INSYNC+PROTO_OK delay(100); // 等待100ms,函数返回 // quiesce and jump to the app return;
12.Debug功能简析
最后一个功能是Debug功能,其命令为0x31。事实上该功能是留给用户自定义的。用户可以利用BootLoader中的代码自行编写Debug代码从而实现调试功能。
case PROTO_DEBUG: // XXX reserved for ad-hoc debugging as required break;
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/147254.html