正点原子bootloader代码(STM32F103ZET6)粗略解读

本文详细解释了如何使用STM32的IAP功能将应用程序写入闪存,包括代码接收、缓冲处理、地址设置以及新程序的加载流程,涉及UART通信、内存地址管理和中断向量表配置。
摘要由CSDN通过智能技术生成
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "stmflash.h"
#include "iap.h"
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK战舰STM32开发板
//IAP 代码	   
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2012/9/24
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved									  
//	

iapfun jump2app; 
u16 iapbuf[1024];   
//appxaddr:应用程序的起始地址
//appbuf:应用程序CODE.
//appsize:应用程序大小(字节).
void iap_write_appbin(u32 appxaddr,u8 *appbuf,u32 appsize)
{
	u16 t;
	u16 i=0;
	u16 temp;
	u32 fwaddr=appxaddr;//当前写入的地址
	u8 *dfu=appbuf;
	for(t=0;t<appsize;t+=2)
	{						    
		temp=(u16)dfu[1]<<8;
		temp+=(u16)dfu[0];	  
		dfu+=2;//偏移2个字节
		iapbuf[i++]=temp;	    
		if(i==1024)
		{
			i=0;
			STMFLASH_Write(fwaddr,iapbuf,1024);	
			fwaddr+=2048;//偏移2048  16=2*8.所以要乘以2.
		}
	}
	if(i)STMFLASH_Write(fwaddr,iapbuf,i);//将最后的一些内容字节写进去.  
}

//跳转到应用程序段
//appxaddr:用户代码起始地址.
void iap_load_app(u32 appxaddr)
{
	if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000)	//检查栈顶地址是否合法.
	{ 
		jump2app=(iapfun)*(vu32*)(appxaddr+4);		//用户代码区第二个字为程序开始地址(复位地址)		
		MSR_MSP(*(vu32*)appxaddr);					//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
		jump2app();									//跳转到APP.
	}
}		 

关于这个iap写bin文件的代码可以清晰的看见它是整了一个2K字节的缓冲数组用来将接收到的代码暂时放在里面,这里有一点很重要STMFLASH写函数是以半字的形式写,这里就要把每两个字节组成一个u16,地址自增2.如果代码量小于2K字节,也就是u16数组的1024个元素,

for(t=0;t<appsize;t+=2)
	{						    
		temp=(u16)dfu[1]<<8;
		temp+=(u16)dfu[0];	  
		dfu+=2;//偏移2个字节
		iapbuf[i++]=temp;	    
		if(i==1024)
		{
			i=0;
			STMFLASH_Write(fwaddr,iapbuf,1024);	
			fwaddr+=2048;//偏移2048  16=2*8.所以要乘以2.
		}
	}

这里这个if判断不会执行,直接跳出for循环后执行

if(i)STMFLASH_Write(fwaddr,iapbuf,i);//将最后的一些内容字节写进去.  

如果代码量大于2K字节,他会每2K字节进入if判断写这完整的缓存数组的内容,然后将数组偏移置零,地址偏移+2048也就是2K字节。

//跳转到应用程序段
//appxaddr:用户代码起始地址.
void iap_load_app(u32 appxaddr)
{
	if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000)	//检查栈顶地址是否合法.
	{ 
		jump2app=(iapfun)*(vu32*)(appxaddr+4);		//用户代码区第二个字为程序开始地址(复位地址)		
		MSR_MSP(*(vu32*)appxaddr);					//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
		jump2app();									//跳转到APP.
	}
}		 

 用户代码的第一个字存放的是堆栈指针地址,通过指针解引用的到值与0x2000 0000比较,符合条件就可以跳转。

//设置栈顶地址
//addr:栈顶地址
__asm void MSR_MSP(u32 addr) 
{
    MSR MSP, r0 			//set Main Stack value
    BX r14
}

因为使用串口来接收要升级的程序bin文件,所以串口接收程序也要做一定修改,保证接收的字节数要符合新程序的字节大小,最大值是55K


#define USART_REC_LEN              55*1024 //定义最大接收字节数 55K
#define EN_USART1_RX             1        //使能(1)/禁止(0)串口1接收
	if(USART1->SR&(1<<5))//接收到数据
	{	 
		res=USART1->DR; 
		if(USART_RX_CNT<USART_REC_LEN)
		{
			USART_RX_BUF[USART_RX_CNT]=res;
			USART_RX_CNT++;			 									     
		}
	}

对于接收的生成的新程序代码,要设置其在(以FLASH为例)中的地址偏移,未设地址偏移的时候,STM32是从栈顶0x0800 0000地址开始,一直到main的死循环里,如果没有中断来打断。有中断打断,就会跳到中断向量表地址0x0800 0004接着向下执行,形成一个闭环。新程序的代码要放在IAP程序的后面。前面这块内存作为IAP的中断向量表之类的配置,锁死。后面新程序的中断堆栈地址、向量表地址、main函数地址都要在其后面。

这个新程序怎么生成呢?那就需要配置它的flash开始地址和其尺寸。

然后我们通过在 MDK 点击 Options for Target→User 选项卡,在 After Build/Rebuild 栏, 勾选 Run #1,并写入:D:\tools\mdk5.14\ARM\ARMCC\bin\fromelf.exe --bin -o ..\OBJ\RTC.bin ..\OBJ\RTC.axf,这个路径的前面是你安装mdk的地址,找到它写进去,后面的是你工程生成的二进制文件路径。

新的程序编写程序的时候就像正常程序一样。只不过存在芯片的闪存地址改变了。

在系统启动的时候,会首先调用 systemInit 函数初始化时钟系统同时 systemInit 还完成了中断向量表的设置,我们可以打开 systemInit 函数,看看函数体的结尾处有

 在新程序的工程内,我们看见在main函数内做了,VTOR 寄 存 器 存 放 的 是 中 断 向 量 表 的 起 始 地 址 。

新程序的中断向量表设置

也可以通过库函数配置 NVIC_SetVectorTable

在IAP程序内,整个程序的执行就是IAP程序启动,main函数内死循环,有没有中断请求,有的话回到IAP的中断向量表找它,然后执行完接着到main。如果符合main函数内的条件接收到了新程序。把新程序写入指定地址闪存空间内,然后继续执行IAP的一系列操作。

只有当用户需要执行新程序,在IAP程序的某个函数响应下,可能是个按键按下之类的操作,然后跳转到新程序的地址开始执行代码。新程序的中断向量表地址设置并不在IAP的地址范围内(IAP是一段位于闪存最前端的一小部分代码,且偏移是0x200即64K为基准),他会在这个新开辟的地址空间内做新程序的一切,像发生中断,跳到新程序中断向量表,他不会跳到IAP的中断向量表,然后执行下去到新程序的main函数,然后再发生新程序的中断。这样往复就完成了新程序的存储与运行。按键复位后它又从IAP程序开始运行,但是新程序还是存储在FLASH里的,你只需要达成一个条件,跳跃到新程序的起始地址执行就好。

FLASH_APP1_ADDR        0x08010000 

跳到0x0801 0000开始执行

新程序跟普通程序的配置没有区别就是在main函数内配置了中断向量表的跳转地址,有了它就不会再跳回0x0800 0000了

而IAP程序就是个普通程序,多了个接收新程序bin文件并将其写入FLASH内的功能。多了用户判定是否接收新程序的功能。其实能不能写完一个程序,在里面加后门,然后在新程序里同样配置好,然后不擦除前面的文件,在配置的地址开始下载新程序,不知道行不行,感觉可以达到类似的效果。

  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
这里给出一个简单的基于HAL库的STM32F103ZET6 bootloader代码示例: ```c #include "main.h" #define BOOTLOADER_VERSION "1.0" #define BOOTLOADER_ADDR 0x08000000 // Bootloader程序存储地址 #define APPLICATION_ADDR 0x08004000 // 应用程序存储地址 #define FLASH_PAGE_SIZE 2048 // flash每页大小 #define APP_MAGIC_CODE 0x5A5A // 应用程序标志码 // 函数声明 void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_USART1_UART_Init(void); static void MX_CRC_Init(void); static void JumpToApp(void); void FlashErase(uint32_t addr); void FlashWrite(uint32_t addr, uint8_t *data, uint32_t size); uint16_t CRC16(uint8_t *data, uint32_t size); // 全局变量 CRC_HandleTypeDef hcrc; UART_HandleTypeDef huart1; int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); MX_CRC_Init(); uint8_t buffer[256]; uint32_t size, addr, crc; uint16_t magic_code; // 检查是否需要进入bootloader模式 if (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_RESET) { // 进入bootloader模式 HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); // 发送欢迎信息 HAL_UART_Transmit(&huart1, (uint8_t *)"--------------------------------------\r\n", 44, HAL_MAX_DELAY); HAL_UART_Transmit(&huart1, (uint8_t *)"STM32F103ZET6 Bootloader\r\n", 29, HAL_MAX_DELAY); HAL_UART_Transmit(&huart1, (uint8_t *)"Version: ", 9, HAL_MAX_DELAY); HAL_UART_Transmit(&huart1, (uint8_t *)BOOTLOADER_VERSION, strlen(BOOTLOADER_VERSION), HAL_MAX_DELAY); HAL_UART_Transmit(&huart1, (uint8_t *)"\r\n", 2, HAL_MAX_DELAY); // 等待数据传输 while (1) { // 接收地址 HAL_UART_Receive(&huart1, (uint8_t *)&addr, 4, HAL_MAX_DELAY); // 接收大小 HAL_UART_Receive(&huart1, (uint8_t *)&size, 4, HAL_MAX_DELAY); // 接收校验码 HAL_UART_Receive(&huart1, (uint8_t *)&crc, 4, HAL_MAX_DELAY); // 接收数据 HAL_UART_Receive(&huart1, buffer, size, HAL_MAX_DELAY); // 计算数据校验码 if (CRC16(buffer, size) != crc) { // 发送错误信息 HAL_UART_Transmit(&huart1, (uint8_t *)"CRC error!\r\n", 12, HAL_MAX_DELAY); } else { // 写入flash FlashErase(addr); FlashWrite(addr, buffer, size); // 发送成功信息 HAL_UART_Transmit(&huart1, (uint8_t *)"Write success!\r\n", 16, HAL_MAX_DELAY); } // 接收标志码 HAL_UART_Receive(&huart1, (uint8_t *)&magic_code, 2, HAL_MAX_DELAY); // 检查标志码是否正确 if (magic_code == APP_MAGIC_CODE) { // 跳转到应用程序 JumpToApp(); } } } else { // 检查应用程序标志码 magic_code = *((uint16_t *)APPLICATION_ADDR); if (magic_code == APP_MAGIC_CODE) { // 跳转到应用程序 JumpToApp(); } } while (1) { } } void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct; RCC_ClkInitTypeDef RCC_ClkInitStruct; /**Initializes the CPU, AHB and APB busses clocks */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /**Initializes the CPU, AHB and APB busses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } } static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOC_CLK_ENABLE(); __HAL_RCC_GPIOD_CLK_ENABLE(); __HAL_RCC_AFIO_CLK_ENABLE(); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); /*Configure GPIO pin : PC13 */ GPIO_InitStruct.Pin = GPIO_PIN_13; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); } static void MX_CRC_Init(void) { hcrc.Instance = CRC; hcrc.Init.DefaultPolynomialUse = DEFAULT_POLYNOMIAL_ENABLE; hcrc.Init.DefaultInitValueUse = DEFAULT_INIT_VALUE_ENABLE; hcrc.Init.InputDataInversionMode = CRC_INPUTDATA_INVERSION_NONE; hcrc.Init.OutputDataInversionMode = CRC_OUTPUTDATA_INVERSION_DISABLE; hcrc.InputDataFormat = CRC_INPUTDATA_FORMAT_BYTES; if (HAL_CRC_Init(&hcrc) != HAL_OK) { Error_Handler(); } } static void MX_USART1_UART_Init(void) { huart1.Instance = USART1; huart1.Init.BaudRate = 115200; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart1) != HAL_OK) { Error_Handler(); } } void JumpToApp(void) { // 关闭中断 __disable_irq(); // 跳转到应用程序 uint32_t jump_address = *((volatile uint32_t *)(APPLICATION_ADDR + 4)); void (*application)(void) = (void (*)(void))jump_address; application(); } void FlashErase(uint32_t addr) { // 解锁flash HAL_FLASH_Unlock(); // 擦除flash FLASH_EraseInitTypeDef erase = { .TypeErase = FLASH_TYPEERASE_PAGES, .Banks = FLASH_BANK_1, .PageAddress = addr, .NbPages = 1}; uint32_t error; HAL_FLASHEx_Erase(&erase, &error); // 上锁flash HAL_FLASH_Lock(); } void FlashWrite(uint32_t addr, uint8_t *data, uint32_t size) { uint32_t i; // 解锁flash HAL_FLASH_Unlock(); // 写入flash for (i = 0; i < size; i += 2) { uint16_t value = (uint16_t)data[i] | ((uint16_t)data[i + 1] << 8); HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, addr + i, value); } // 上锁flash HAL_FLASH_Lock(); } uint16_t CRC16(uint8_t *data, uint32_t size) { uint32_t crc = 0xFFFFFFFF; for (uint32_t i = 0; i < size; i++) { crc ^= (uint32_t)(data[i]) << 24; for (uint32_t j = 0; j < 8; j++) { if (crc & 0x80000000) { crc = (crc << 1) ^ 0x04C11DB7; } else { crc <<= 1; } } } return (uint16_t)(crc >> 16) & 0xFFFF; } ``` 这个bootloader程序的功能比较简单,可以接收通过串口发送的数据,写入到flash中。同时,当接收到应用程序标志码时,会跳转到应用程序的入口地址。注意,这里的bootloader程序大小应该小于应用程序的大小。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值