本节主要记录自己学习Ardupilot stm32f1 Bootloader,欢迎批评指正!
int main(void)
{
unsigned timeout = 0;
/* 做板层初始化------do board-specific initialisation */
board_init(); //主要配置LED和串口2
#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
#ifdef INTERFACE_I2C
# error I2C bootloader detection logic not implemented
#endif
/*如果应用程序留下一个cookie说我们应该等待,然后等待---- if the app left a cookie saying we should wait, then wait */
//Cookie,有时也用其复数形式 Cookies,指某些网站为了辨别用户身份、进行 session 跟踪而储存在用户本地终端上的数据(通常经过加密)。
if (should_wait())
{
timeout = BOOTLOADER_DELAY;
}
#ifdef BOARD_FORCE_BL_PIN
/* if the force-BL pin state matches the state of the pin, wait in the bootloader forever */
//如果强制BL引脚状态与PIN的状态匹配,则在Bootloader中永远等待
if (BOARD_FORCE_BL_VALUE == gpio_get(BOARD_FORCE_BL_PORT, BOARD_FORCE_BL_PIN))
{
timeout = 0xffffffff;
}
#endif
/*在备份寄存器零点中查找Bootloader值中的魔法等待--- 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 */
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时钟---- configure the clock for bootloader activity */
clock_init();
/*启动接口,采用串口---- 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;
}
}
static void board_init(void)
{
/*初始化led----initialise LEDs */
rcc_peripheral_enable_clock(&BOARD_CLOCK_LEDS_REGISTER, BOARD_CLOCK_LEDS); //使能PB端口时钟
gpio_set_mode(BOARD_PORT_LEDS, //设置PB
GPIO_MODE_OUTPUT_50_MHZ,
GPIO_CNF_OUTPUT_PUSHPULL,
BOARD_PIN_LED_BOOTLOADER | BOARD_PIN_LED_ACTIVITY); //PB15,PB14
BOARD_LED_ON(BOARD_PORT_LEDS,BOARD_PIN_LED_BOOTLOADER | BOARD_PIN_LED_ACTIVITY); //点亮led
/*如果有一个,强制引导加载程序引脚---if we have one, enable the force-bootloader pin */
#ifdef BOARD_FORCE_BL_PIN //PB5
rcc_peripheral_enable_clock(&BOARD_FORCE_BL_CLOCK_REGISTER, BOARD_FORCE_BL_CLOCK_BIT);
gpio_set(BOARD_FORCE_BL_PORT, BOARD_FORCE_BL_PIN);
gpio_set_mode(BOARD_FORCE_BL_PORT,
GPIO_MODE_INPUT,
BOARD_FORCE_BL_PULL,
BOARD_FORCE_BL_PIN);
#endif
/*启用备份寄存器---- enable the backup registers */
rcc_peripheral_enable_clock(&RCC_APB1ENR, RCC_APB1ENR_PWREN | RCC_APB1ENR_BKPEN);
#ifdef INTERFACE_USART
/*配置串口引脚-------configure usart pins */
rcc_peripheral_enable_clock(&BOARD_USART_PIN_CLOCK_REGISTER, BOARD_USART_PIN_CLOCK_BIT);
gpio_set_mode(BOARD_PORT_USART,
GPIO_MODE_OUTPUT_50_MHZ,
GPIO_CNF_OUTPUT_ALTFN_PUSHPULL,
BOARD_PIN_TX); //对应----UART2
/*配置串口时钟使能----configure USART clock */
rcc_peripheral_enable_clock(&BOARD_USART_CLOCK_REGISTER, BOARD_USART_CLOCK_BIT);
#endif
#ifdef INTERFACE_I2C
# error I2C GPIO config not handled yet
#endif
}
总结:板层初始化主要完成LED初始化,串口初始化
void jump_to_app()
{
const uint32_t *app_base = (const uint32_t *)APP_LOAD_ADDRESS; //app的入口地址是0x08001000,也就是说有4k字节的地址给bootloader使用
/*
* 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.
*/
//我们拒绝程序的第一个字的应用程序,直到上传标记完成的主机。因此,如果不是0xFFFFFFF,我们应该尝试启动它。
if (app_base[0] == 0xffffffff)
{
return;
}
/*
* The second word of the app is the entrypoint; it must point within the
* flash area (or we have a bad flash).
*/
//应用程序的第二个字是入口点;它必须指向闪存区域内(或者我们的闪存不好)
if (app_base[1] < APP_LOAD_ADDRESS)
{
return;
}
if (app_base[1] >= (APP_LOAD_ADDRESS + board_info.fw_size)) //大于板子的APP flash大小=总FLASH去掉预留和bootloader
{
return;
}
/*flash短暂锁定闪存程序和擦除控制器用于防止对闪存的虚假写入---just for paranoia's sake */
flash_lock();
/*关闭系统中断和计数-----kill the systick interrupt */
systick_interrupt_disable();
systick_counter_disable();
/* 重新初始化串口和USB----deinitialise the interface */
cfini();
/*重置时钟---- reset the clock */
clock_deinit();
/*重置板层初始化----deinitialise the board */
board_deinit();
/*将异常处理程序切换到应用程序---- switch exception handlers to the application */
SCB_VTOR = APP_LOAD_ADDRESS; //切换到应用程序地址
/* extract the stack and entrypoint from the app vector table and go */
//从app的中断矢量表中,提取堆栈入口点
do_jump(app_base[0], app_base[1]);
}
static void do_jump(uint32_t stacktop, uint32_t entrypoint)
{
asm volatile(
"msr msp, %0 \n" //msr通用寄存器值传入特殊功能寄存器
"bx %1 \n"
: : "r"(stacktop), "r"(entrypoint) :); //
// just to keep noreturn happy
for (;;) ;
}
上面的代码等价于下面的两行汇编指令
msr msp stacktop
bx entrypoint
备注:asm violate (“movl %1,%0” : “=r” (result) : “m” (input));
“movl %1,%0"是指令模板;”%0"和"%1"代表指令的操作数,称为占位符,内嵌汇编靠它们将C 语言表达式与指令操作数相对应。指令模板后面用小括号括起来的是C语言表达式,本例中只有两个:“result"和"input”,他们按照出现的顺序分 别与指令操作数"%0","%1"对应;注意对应顺序:第一个C 表达式对应"%0";第二个表达式对应"%1",依次类推,操作数至多有10 个,分别用"%0","%1"…"%9"表示。在每个操作数前面有一个用引号括起来的字符串,字符串的内容是对该操作数的限制或者说要求。 “result"前面的限制字符串是”=r",其中"=“表示"result"是输出操作数,“r” 表示需要将"result"与某个通用寄存器相关联,先将操作数的值读入寄存器,然后在指令中使用相应寄存器,而不是"result"本身,当然指令执行 完后需要将寄存器中的值存入变量"result”,从表面上看好像是指令直接对"result"进行操作,实际上GCC做了隐式处理,这样我们可以少写一 些指令。“input"前面的"r"表示该表达式需要先放入某个寄存器,然后在指令中使用该寄存器参加运算。
C表达式或者变量与寄存器的关系由GCC自动处理,我们只需使用限制字符串指导GCC如何处理即可。限制字符必须与指令对操作数的要求相匹配,否则产生的 汇编代码将会有错,读者可以将上例中的两个"r”,都改为"m"(m表示操作数放在内存,而不是寄存器中),编译后得到的结果是:
movl input, result
很明显这是一条非法指令,因此限制字符串必须与指令对操作数的要求匹配。例如指令movl允许寄存器到寄存器,立即数到寄存器等,但是不允许内存到内存的操作,因此两个操作数不能同时使用"m"作为限定字符。
所以这里我们只需要设定飞控程序的入口地址是pc的初值就可以满足,Bootloader跳转到飞控应用程序。
注意:jump_to_app()函数为什么使用两次:可以这样理解:假如我们的飞控已经下载BOOTLOADER,现在正在烧写应用程序,就直接跳转到应用程序,但是如果我们只有Bootloader没有烧写应用程序,那代码一定要实时检查是否有应用程序到来。
void bootloader(unsigned timeout)
{
//这个类型的引导程序,无论从usb或串口,将决定什么类型的端口第一时间接收bootloader命令是有效的
bl_type = NONE; // The type of the bootloader, whether loading from USB or USART, will be determined by on what port the bootloader recevies its first valid command.
//上载前强制擦除
uint32_t address = board_info.fw_size; /* force erase before upload will work */
uint32_t first_word = 0xffffffff;
/* 重新)启动计时器系统(re)start the timer system */
systick_set_clocksource(STK_CSR_CLKSOURCE_AHB);
systick_set_reload(board_info.systick_mhz * 1000); /* 1ms tick, magic number */
systick_interrupt_enable();
systick_counter_enable();
/* 如果我们正在处理超时,请启动它运行if we are working with a timeout, start it running */
if (timeout)
{
timer[TIMER_BL_WAIT] = timeout;
}
/* make the LED blink while we are idle */
//使LED在我们空闲时闪烁
led_set(LED_BLINK);
while (true)
{
volatile int c;
int arg;
static union
{
uint8_t c[256];
uint32_t w[64];
} flash_buffer;
//等待命令字节---- Wait for a command byte
led_off(LED_ACTIVITY);
do {
/* if we have a timeout and the timer has expired, return now */
if (timeout && !timer[TIMER_BL_WAIT]) {
return;
}
/* try to get a byte from the host */
c = cin_wait(0);
} while (c < 0);
led_on(LED_ACTIVITY);
// handle the command byte
switch (c) {
// sync
//
// command: GET_SYNC/EOC
// reply: INSYNC/OK
//
case PROTO_GET_SYNC:
/* expect EOC */
if (!wait_for_eoc(2)) {
goto cmd_bad;
}
break;
// get device info
//
// command: GET_DEVICE//EOC
// BL_REV reply: /INSYNC/EOC
// BOARD_ID reply: /INSYNC/EOC
// BOARD_REV reply: /INSYNC/EOC
// FW_SIZE reply: /INSYNC/EOC
// VEC_AREA reply /INSYNC/EOC
// bad arg reply: INSYNC/INVALID
//
case PROTO_GET_DEVICE:
/* expect arg then EOC */
arg = cin_wait(1000);
if (arg < 0) {
goto cmd_bad;
}
if (!wait_for_eoc(2)) {
goto cmd_bad;
}
switch (arg) {
case PROTO_DEVICE_BL_REV:
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;
}
break;
// erase and prepare for programming
//
// command: ERASE/EOC
// success reply: INSYNC/OK
// erase failure: INSYNC/FAILURE
//
case PROTO_CHIP_ERASE:
/* expect EOC */
if (!wait_for_eoc(2)) {
goto cmd_bad;
}
#if defined(TARGET_HW_PX4_FMU_V4)
if (check_silicon()) {
goto bad_silicon;
}
#endif
// clear the bootloader LED while erasing - it stops blinking at random
// and that's confusing
led_set(LED_ON);
// erase all sectors
flash_unlock();
for (int i = 0; flash_func_sector_size(i) != 0; i++) {
flash_func_erase_sector(i);
}
// enable 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;
// resume blinking
led_set(LED_BLINK);
break;
// program bytes at current address
//
// command: PROG_MULTI///EOC
// success reply: INSYNC/OK
// invalid reply: INSYNC/INVALID
// readback failure: INSYNC/FAILURE
//
case PROTO_PROG_MULTI: // program bytes
// expect count
arg = cin_wait(50);
if (arg < 0) {
goto cmd_bad;
}
// sanity-check arguments
if (arg % 4) {
goto cmd_bad;
}
if ((address + arg) > board_info.fw_size) {
goto cmd_bad;
}
if (arg > sizeof(flash_buffer.c)) {
goto cmd_bad;
}
for (int i = 0; i < arg; i++) {
c = cin_wait(1000);
if (c < 0) {
goto cmd_bad;
}
flash_buffer.c[i] = c;
}
if (!wait_for_eoc(200)) {
goto cmd_bad;
}
if (address == 0) {
#if defined(TARGET_HW_PX4_FMU_V4)
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;
}
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;
}
break;
// fetch CRC of the entire flash area
//
// command: GET_CRC/EOC
// reply: /INSYNC/OK
//
case 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);
break;
// read a word from the OTP
//
// command: GET_OTP//EOC
// reply: /INSYNC/OK
case 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;
// read the SN from the UDID
//
// command: GET_SN//EOC
// reply: /INSYNC/OK
case PROTO_GET_SN:
// 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_sn(index));
}
break;
// read the chip ID code
//
// command: GET_CHIP/EOC
// reply: /INSYNC/OK
case PROTO_GET_CHIP: {
// expect EOC
if (!wait_for_eoc(2)) {
goto cmd_bad;
}
cout_word(get_mcu_id());
}
break;
// read the chip description
//
// command: GET_CHIP_DES/EOC
// reply: /INSYNC/OK
case 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);
}
break;
#ifdef BOOT_DELAY_ADDRESS
case 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;
#endif
// finalise programming and boot the system
//
// command: BOOT/EOC
// reply: INSYNC/OK
//
case PROTO_BOOT:
// expect EOC
if (!wait_for_eoc(1000)) {
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();
delay(100);
// quiesce and jump to the app
return;
case PROTO_DEBUG:
// XXX reserved for ad-hoc debugging as required
break;
default:
continue;
}
// we got a command worth syncing, so kill the timeout because
// we are probably talking to the uploader
timeout = 0;
// Set the bootloader port based on the port from which we received the first valid command
if (bl_type == NONE) {
bl_type = last_input;
}
// send the sync response for this command
sync_response();
continue;
cmd_bad:
// send an 'invalid' response but don't kill the timeout - could be garbage
invalid_response();
continue;
cmd_fail:
// send a 'command failed' response but don't kill the timeout - could be garbage
failure_response();
continue;
#if defined(TARGET_HW_PX4_FMU_V4)
bad_silicon:
// send the bad silicon response but don't kill the timeout - could be garbage
bad_silicon_response();
continue;
#endif
}
}