板卡:Nucleo-L412
平台:macbook pro
工具:vscode stm32cubemx stm32cubeProgramer cmake toolchain
L412 自带128K的flash,所以我们可以这样分区,
printf(“|============ flash pration table ============|\r\n”);
printf(“| name | offset | size |\r\n”);
printf(“|---------------------------------------------|\r\n”);
printf(“| bootloader | 0x08000000 | 0x00005000 20K | \r\n”);
printf(“| setting | 0x08005000 | 0x00002000 8K |\r\n”);
printf(“| App | 0x08007000 | 0x0000C800 50K |\r\n”);
printf(“| download | 0x08013800 | 0x0000C800 50K |\r\n”);
bootloader从0x08000000-0x08005000 大小20K
setting 从0x08005000-0x08007000 大小08K
App 从0x08007000-0x08013800大小50K
Download 从0x08013800-0x08020000大小50k
Bootloader从0x08000000地址运行,而App程序从地址0x8007000运行。
App运行时,可通过iic或者uart接收到上位机升级的指令。App接收到要升级的程序,存在Download分区,并要setting区升级标志位置为1,重启。bootloader从Setting读取升级标志位,如果升级位为1,从Download分区copy数据到App区,copy完成后跳转到0x08007000地址,实现App的升级。
1、设置时钟源跟配置时钟树,这个根据自己的平台配置即可。
2、配置一路串口,用作打印用。
3、配置一路定时器,用于改变led灯闪烁频率,来区分正常启动还是升级的指示灯。
4、生成代码选择Makefile,因为一直做linux开发,实在用不惯keil。
1、开机打印分区信息
void print_text_message(void)
{
#ifdef USER_APPLICATION_CODE
printf("|------------- Enter Application -------------|\r\n");
#endif
#ifdef USER_BOOTLOADER_CODE
printf("|------------- Enter BootLoader --------------|\r\n");
#endif
printf("\r\n");
printf("Version: %s %s\r\n",APPCATION_VERSION_DATE,APPCATION_VERSION_TIME);
printf("\r\n");
printf("|----------L412 flash toal size 128K----------|\r\n");
printf("\r\n");
printf("|============ flash pration table ============|\r\n");
printf("| name | offset | size |\r\n");
printf("|---------------------------------------------|\r\n");
printf("| bootloader | 0x08000000 | 0x00005000 20K | \r\n");
printf("| setting | 0x08005000 | 0x00002000 8K |\r\n");
printf("| App | 0x08007000 | 0x0000C800 50K |\r\n");
printf("| download | 0x08013800 | 0x0000C800 50K |\r\n");
printf("|=============================================|\r\n");
}
其实IAP升级是两个工程,两个工程里都有自己的Makefile。而有一部分代码是两个工程共用的,所以我们可以将共用代码放在另外一个目录里,在Makefile里引用或者编译成静态库即可。
整个工程的代码架构是这样的。Common里即是boot跟app共用的代码。
2、重定向printf函数 main.c里添加
#ifdef USE_UART_PRINTF //Uart使用
int _write(int fd, char *ch, int len)
{
HAL_UART_Transmit(&huart1, (uint8_t*)ch, len, 0xFFFF);
return len;
}
#endif
#ifdef USE_SVO_PRINTF //Swv log使用
int _write(int file, char *ptr, int len)
{
int DataIdx;
for (DataIdx = 0; DataIdx < len; DataIdx++)
{
ITM_SendChar(*ptr++); // 把printf函数重定向到ITM_SendChar
}
return len;
}
#endif
3、定时器
定时器开启函数 : HAL_TIM_Base_Start_IT(&htim2);
定时器回调函数:
led_freq为LED_TOGGLE_100_MS时 100ms闪烁一次
LED_TOGGLE_500_MS 时500ms闪烁一次
int led_cnt = 0;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == htim2.Instance)
{
if (led_freq == LED_TOGGLE_100_MS)
{
HAL_GPIO_TogglePin(UserLed_GPIO_Port,UserLed_Pin);
led_cnt = 0;
}
else if (led_freq == LED_TOGGLE_500_MS)
{
if (led_cnt == LED_TOGGLE_500_MS)
{
HAL_GPIO_TogglePin(UserLed_GPIO_Port,UserLed_Pin);
led_cnt = 0;
}
}
led_cnt++;
}
}
3、读取升级标志位:
system_info_get_update_flag(&update_flag);
MI_BOOL system_info_get_update_flag(MI_U8 *flag)
{
MI_SystemInfo system_info;
memset(&system_info,0,sizeof(system_info));
stm32_flash_read(SYSTEM_INFO_START_ADDR,(uint8_t *)&system_info,sizeof(system_info));
*flag = system_info.update_flag;
return MI_TRUE;
}
升级标志位,是结构体形式写进0x08005000地址的,所以读的时候按一个字节从0x08005000以结构体形式再读出来即可。
/**
* 系统信息结构体
*/
typedef struct system_info
{
/* data */
MI_CHAR model_name[64];
MI_CHAR band_name[12];
MI_CHAR soft_version[36];
MI_CHAR settings_version[36];
MI_CHAR app_version[36];
MI_CHAR boot_version[36];
MI_U32 build_date;
MI_U32 build_time;
MI_U8 update_flag;
MI_GainMuteInfo gain_mute_info;
}MI_SystemInfo;
MI_BOOL stm32_flash_read(MI_U32 dest_addr, MI_U8* buff, MI_U32 Len)
{
MI_U32 i;
for(i = 0; i < Len; i++){
buff[i] = *(__IO MI_U8*)(dest_addr + i);
}
/* Return a valid address to avoid HardFault */
return 0;
}
main.c读取时代码如下:
system_info_get_update_flag(&update_flag);
//create_setting_data();
if (update_flag == SYSTEM_UPDATE)
{
// new Copy Application bin and update
HAL_TIM_Base_Start_IT(&htim2);
printf("Now Need copy Application bin and Update\r\n");
HAL_Delay(1000);
printf("\r\n");
led_freq = LED_TOGGLE_100_MS;
copy_download_to_app();
printf("\r\n");
printf("update success,and go to Application\r\n");
//升级成功后,将升级标志位清0
system_info_set_update_flag(SYSTEM_NO_UPGRADE);
led_freq = LED_TOGGLE_500_MS;
HAL_Delay(4000);
jumpApplication();
}
else if (update_flag == SYSTEM_NO_UPGRADE)
{
printf("\r\n");
printf("Now go to Application ,please wait! \r\n");
printf("\r\n");
HAL_Delay(1000);
jumpApplication();
}
Copy数据代码
MI_BOOL copy_download_to_app(void)
{
MI_U8 buffer[1024] = {0}; //每次从download读1K字节,然后写入1K
MI_U32 w_count = 0;
MI_U32 w_len = 0;
w_count = APP_SECTOR_SIZE / sizeof(buffer);
w_len = sizeof(buffer);
// 将App区域擦除
stm32_erase_flash(APP_START_SECTOR_ADDR,APP_END_SECTOR_ADDR);
for (int i = 0;i < w_count;i++)
{
stm32_flash_read(DOWNLOAD_START_SECTOR_ADDR + (w_len * i),buffer,w_len);
HAL_Delay(500);
stm32_flash_write(APP_START_SECTOR_ADDR + (w_len * i),buffer,w_len);
printf("update.............................%03d [100] \r\n",((i+1) * 100 /w_count));
}
return MI_TRUE;
}
4、App跳转
MI_VOID jumpApplication(MI_VOID)
{
HAL_GPIO_DeInit(UserLed_GPIO_Port,UserLed_Pin);
HAL_UART_MspDeInit(&huart1);
HAL_TIM_Base_MspDeInit(&htim2);
jump_app(APP_START_SECTOR_ADDR);
}
uint8_t jump_app(uint32_t app_addr)
{
uint32_t jump_addr;
jump_callback cb;
if (((*(__IO uint32_t*)app_addr) & 0x2FFE0000 ) == 0x20000000)
{
jump_addr = *(__IO uint32_t*) (app_addr + 4);
cb = (jump_callback)jump_addr;
__set_MSP(*(__IO uint32_t*)app_addr);
//__set_CONTROL(0);
cb();
return 1;
}
return 0;
}
5、修改STM32L412RBTxP_FLASH.ld
/* Specify the memory areas */
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 40K
RAM2 (xrw) : ORIGIN = 0x10000000, LENGTH = 8K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 20K
}
因为bootloader从0x08000000启动,大小为20K,故修改如上。
6、编译完成后,用stm32cubemxProgramer将Bootloader.bin烧录进板卡,地址填0x08000000.
整体代码在
https://download.csdn.net/download/weixin_43932857/87693349