从零开始手写BootLoader–STM32L073/IAR/HAL库
一、测试平台: MCU:STM32L073V8T6(Cortex-M0+) 固件库:HAL+STM Cube IDE:IAR For STM32 二、实验目的 使用BootLoader通过串口将程序bin文件烧录至单片机内,然后跳转到烧录位置运行程序。 三、基础知识 STM32L073V8T6的启动方式有三种:内置FLASH启动、内置SRAM启动、系统存储器ROM启动,通过BOOT0和BOOT1引脚的设置可以选择从哪中方式启动,这里选择内置的FLASH启动。 四、准备 1 基础功能代码:使用STMCube代码生成器生成,在本实验中开启按键输入、LED引脚与串口4,配置时钟为MSI(4.194MHz)。此部分代码不予演示。 2 App程序:用户最终使用的应用程序(以下用App代替),复位后LED点亮,在while中循环发送字符串。
#define CODE_OFFSET 0x8000
int main(void)
{
__disable_irq();
SCB->VTOR = FLASH_BASE | CODE_OFFSET ;
__enable_irq();
HAL_Init();
System_HSIClock_Config();
System_Port_Config();
HAL_UART_Receive_IT(&huart4,&Uart4_RX_Char,1);
while(1)
{
char str[] = "test\r\n";
HAL_UART_Transmit(&huart4,str,strlen(str),1000);
HAL_Delay(500);
LED_Pulse;
}
}
相较于普通程序,App程序需要做以下两点修改 (1)在程序的起始阶段重定位中断向量表
__disable_irq();
SCB->VTOR = FLASH_BASE | CODE_OFFSET ;
__enable_irq();
(2)在IAR中配置烧录位置 这里将烧录位置设置为0x0800 8000 编译生成bin文件
3 BootLoader程序:引导App程序运行。
五、BootLoader 1 分配程序空间 通过芯片手册得知STM32L073V8T6内部flash为64K 注意:这里的参考手册给出的是L0x3系列flash空间的最大值,需要再查具体芯片的型号规格,如果意外写入到错误的地址,程序崩溃。 本次实验使用的是STM32L073V8T6芯片,使用者可根据芯片型号的不同自行定义flash空间。 可以看到flash的地址空间为0x8000 0000~0x8000 FFFF(32K)。 接下来是重要的一步:定义App的烧录位置。此时一定需要确保BootLoader程序的代码不能和App程序冲突。烧录位置需要根据BootLoader程序的大小进行确定,这里仅仅为了方便起见将flash均分为两个32K空间,实际使用时需要根据BootLoader程序的大小灵活调整。
#define FLASH_USER_START_ADDR 0x08008000
#define FLASH_USER_END_ADDR 0x0800FFFF
初始化
HAL_Init();
System_HSIClock_Config();
MX_USART4_UART_Init();
串口中断回调函数
uint8_t Uart4_RX_Char;
__no_init u8 USART_RX_BUF[10240]@0X20002000;
u16 USART_RX_STA=0;
u32 USART_RX_CNT=0;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance==USART4)
{
USART_RX_CNT++;
USART_RX_BUF[USART_RX_STA]=Uart4_RX_Char ;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))
USART_RX_STA=0;
HAL_UART_Receive_IT(&huart4,&Uart4_RX_Char,1);
}
}
从SRAM将代码复制到片内flash
void Write_Flash(uint32_t address, uint8_t *pData,uint32_t length)
{
uint32_t Address = 0, PAGEError = 0;
uint32_t* ptr32;
Address = address;
EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
EraseInitStruct.PageAddress = FLASH_USER_START_ADDR;
EraseInitStruct.NbPages = ((FLASH_USER_END_ADDR - FLASH_USER_START_ADDR) / FLASH_PAGE_SIZE);
HAL_FLASH_Unlock();
if (HAL_FLASHEx_Erase(&EraseInitStruct, &PAGEError) != HAL_OK)
{
while (1)
{
}
}
ptr32 = (uint32_t*)pData;
while (Address < address +length)
{
data32 = (uint32_t)*ptr32;
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, Address, data32) == HAL_OK)
{
Address = Address + 4;
ptr32++;
}
else
{
while (1)
{
}
}
}
HAL_FLASH_Lock();
}
这里注意写flash是以u32类型写入的(FLASH_TYPEPROGRAM_WORD),即4个字节,需要指针转换操作(data32 = (uint32_t)*ptr32;),每写一次地址+4(Address = Address + 4;),由于ptr32已经是u32指针,+1即可(ptr32++;)。
程序跳转
typedef void (*iapfun)(void);
void iap_load_app(u32 appxaddr)
{
if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000)
{
jump2app=(iapfun)*(vu32*)(appxaddr+4);
__set_MSP(*(__IO uint32_t*) appxaddr);
jump2app();
}
}
根据<<Cortex-M0权威指南>>,修改栈顶和跳转函数入口地址
主函数
#define KEY0 HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin)
#define KEY0_PRES 1
void Write_Flash(uint32_t address, uint8_t *pData,uint32_t length);
u8 key;
u8 KEY_Scan(u8 mode)
{
static u8 key_up=1;
if(mode==1)
key_up=1;
if(key_up&&(KEY0==0))
{
HAL_Delay(1);
key_up=0;
if(KEY0==0)
return 1;
}
else
key_up=1;
return 0;
}
int main(void)
{
HAL_Init();
System_HSIClock_Config();
System_USART4_Init();
USART_SendString(&huart4,"Warship STM32\r\n",strlen("Warship STM32\r\n"));
USART_SendString(&huart4,"IAP TEST\r\n",strlen("IAP TES\r\nT"));
while(1)
{
key=KEY_Scan(0);
if(key==KEY0_PRES)
{
USART_SendString(&huart4,"开始执行用户代码!!\r\n",strlen("开始执行用户代码!!\r\n"));
Write_Flash(FLASH_APP1_ADDR,USART_RX_BUF,USART_RX_CNT);
iap_load_app(FLASH_APP1_ADDR);
}
}
}
工作流程 1 将BootLoader程序烧录至单片机中 2 上电启动 3 使用串口工具向单片机发送App程序的bin文件 4 按下按键,程序跳转到App程序中执行
关键点 1 根据芯片手册、App文件大小、BootLoader文件大小计算计算各个部分在flash、SRAM中占用的空间 2 使用串口下载App的bin文件至SRAM区域 3 将SRAM中的文件搬运至片内flash中 4 跳转到App的flash中运行
总结 本程序只是实现了最简单的程序下载、烧录、跳转,用户可根据自身需求做出修改,其他的附加功能如擦除、校验、选择启动方式、下载位置等功能待完善。
参考 正点原子IAP实验程序 HAL库官方例程 《Cortex-M0权威指南》 《STM32L0x3参考手册》 https://www.cnblogs.com/wanghuaijun/p/7810182.html
附件 正点原子串口IAP例程 HAL库官方例程 串口助手
|