Ardupilot stm32f1 Bootloader学习

目录

文章目录

  • 目录
  • 摘要
  • 1.Bootloader代码执行
    • 1.board_init();
    • 2.jump_to_app();
    • 3.do_jump();
    • 4.bootloader(timeout)

摘要



本节主要记录自己学习Ardupilot stm32f1 Bootloader,欢迎批评指正!



1.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;
	}
}


1.board_init();



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
}

Ardupilot stm32f1 Bootloader学习_第1张图片

总结:板层初始化主要完成LED初始化,串口初始化



2.jump_to_app();



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]);
}



3.do_jump();



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"作为限定字符。


Ardupilot stm32f1 Bootloader学习_第2张图片

所以这里我们只需要设定飞控程序的入口地址是pc的初值就可以满足,Bootloader跳转到飞控应用程序。
注意:jump_to_app()函数为什么使用两次:可以这样理解:假如我们的飞控已经下载BOOTLOADER,现在正在烧写应用程序,就直接跳转到应用程序,但是如果我们只有Bootloader没有烧写应用程序,那代码一定要实时检查是否有应用程序到来。



4.bootloader(timeout)



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
	}
}

你可能感兴趣的:(ardupilot学习)