单片机通用Bootloader框架-优化
Posted aron566
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单片机通用Bootloader框架-优化相关的知识,希望对你有一定的参考价值。
单片机通用Bootloader框架-优化
这篇文章是对之前写的Bootloader的一次优化,之前博文
单片机通用Bootloader框架-优化
此次优化是看到这篇公众号推的(公众号写的真的太好了,每次必看):安富莱原文,主要讲了BL阶段需要跳转到APP程序执行前,如何省去,一些反初始化外设,那些复杂的操作,直接干净的跳转到APP区执行,文章中提到了RAM区一个段,不进行初始化的段,当单片机没有掉电的情况下,仅是软复位操作,不会改变这个段里面变量的数值,利用这个段去做一些事情,如BL需要的标志位,程序更新完毕了,改下这个段里面变量的标志位,复位一下在执行初始化硬件前
,根据标志位的情况去执行跳转
,免去由原来的跳转前反初始化外设(即恢复外设关闭中断这些操作)
另外片上一般都有BKP(即后备存储区),之前都是想到用来存放RTC是否是第一次配置的标志位,用它可以
比如下面的RTC配置,利用了RTC_BKP_DR0
数据区域存储标志位,只要MCU不掉电,这个数据一直在,而且不编程(动它)不会被改变
/**
* @file RTC_Port.c
*
* @date 2022年08月09日 11:20:01 星期二
*
* @author aron566
*
* @copyright Copyright (c) 2021 aron566 <aron566@163.com>.
*
* @brief RTC时钟接口.
*
* @details 需要将bool RTC_Port_Is_First_Config(void) 在初始化RTC硬件时调用判断是否需要设置时间.
*
* @version V1.0
*/
/** Includes -----------------------------------------------------------------*/
#include <time.h>
/* Private includes ----------------------------------------------------------*/
#include "RTC_Port.h"
#include "main.h"
#include "rtc.h"
/** Use C compiler -----------------------------------------------------------*/
#ifdef __cplusplus ///< use C compiler
extern "C"
#endif
/** Private macros -----------------------------------------------------------*/
#define RTC_FIRST_CONFIG_FLAG 0x0566/**< RTC首次配置标志 */
/** Private typedef ----------------------------------------------------------*/
/** Private constants --------------------------------------------------------*/
/** Public variables ---------------------------------------------------------*/
/** Private variables --------------------------------------------------------*/
static RTC_TIME_DATE_Typedef_t Time_Date;
/** Private function prototypes ----------------------------------------------*/
/** Private user code --------------------------------------------------------*/
/** Private application code -------------------------------------------------*/
/*******************************************************************************
*
* Static code
*
********************************************************************************
*/
/** Public application code --------------------------------------------------*/
/*******************************************************************************
*
* Public code
*
********************************************************************************
*/
/**
* @brief Alarm A callback.
* @param hrtc RTC handle
* @retval None
*/
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
/* Prevent unused argument(s) compilation warning */
UNUSED(hrtc);
/* NOTE : This function should not be modified, when the callback is needed,
the HAL_RTC_AlarmAEventCallback could be implemented in the user file
*/
/**
* @brief 关闭闹钟
*
*/
void RTC_Port_Set_Alarm_Close(void)
/* 默认关闭闹钟 */
HAL_RTC_DeactivateAlarm(&hrtc, RTC_ALARM_A);
/**
* @brief 设置闹钟
*
* @param H 时间
* @param Min 分钟
* @param S 秒
* @param W 星期几 1 - 7
* @param Mask 屏蔽字段 @ref RTC_AlarmMask_Definitions
* @param Week_Day_Sel 1 选择 Week 0 选择 Day
*/
void RTC_Port_Set_Alarm(uint8_t H, uint8_t Min, uint8_t S, uint8_t W, uint32_t Mask, uint8_t Week_Day_Sel)
RTC_AlarmTypeDef sAlarm = 0;
sAlarm.AlarmTime.Hours = H;
sAlarm.AlarmTime.Minutes = Min;
sAlarm.AlarmTime.Seconds = S;
sAlarm.AlarmTime.SubSeconds = 0;
sAlarm.AlarmTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
sAlarm.AlarmTime.StoreOperation = RTC_STOREOPERATION_RESET;
sAlarm.AlarmMask = Mask;
sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_ALL;
if(Week_Day_Sel == 1)
sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_WEEKDAY;
else
sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE;
sAlarm.AlarmDateWeekDay = W;
sAlarm.Alarm = RTC_ALARM_A;
if(HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN) != HAL_OK)
printf("Set Alarm Error\\n");
/**
* @brief 获取该时间下周几 1 - 7
*
* @param y 年 2000 + y
* @param m 月 1 - 12
* @param d 日 1 - 31
* @return int32_t 周几 1 - 7
*/
int32_t RTC_Port_GetWeekDay(int y, int m, int d)
int year = y + 2000;
int month = m;
int date = d;
if(month == 1) month = 13, year--;
if(month == 2) month = 14, year--;
int weekdey = (date + 2 * month + 3 * (month + 1) / 5 + year + year / 4 - year / 100 + year / 400) % 7;
return weekdey + 1;
/**
* @brief 获取至返回自 1970 年 1 月 1 日以来持续时间的秒数,格林威治时间GMT UTC世界标准时间(原子钟)+8小时就是东八区时间(北京时间)
*
* @return uint32_t秒数,Unix时间戳
*/
uint32_t RTC_Port_Get_TimeStamp(void)
struct tm tm_data;
const RTC_TIME_DATE_Typedef_t *T = RTC_Port_Get_Time_Date();
tm_data.tm_year = (int)T->Year + 2000 - 1900; /**< 自1900起的年数 */
tm_data.tm_mon = (int)T->Month - 1; /**< 月份,范围从0到11 */
tm_data.tm_mday = (int)T->Day; /**< 1 - 31 */
tm_data.tm_hour = (int)T->Hour; /**< 0-23 */
tm_data.tm_min = (int)T->Minute; /**< 0-59 */
tm_data.tm_sec = (int)T->Second; /**< 0-59 */
return (uint32_t)mktime(&tm_data);
/**
* @brief 获取时间日期
*
* @return const RTC_TIME_DATE_Typedef_t*
*/
const RTC_TIME_DATE_Typedef_t *RTC_Port_Get_Time_Date(void)
RTC_TimeTypeDef sTime = 0;
RTC_DateTypeDef sDate = 0;
/* 先获取时间,再获取日期,否则有时延 */
if(HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)
printf("rtc get time error.\\r\\n");
return &Time_Date;
Time_Date.Hour = sTime.Hours;
Time_Date.Minute = sTime.Minutes;
Time_Date.Second = sTime.Seconds;
if(HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK)
printf("rtc get date error.\\r\\n");
return &Time_Date;
Time_Date.Year = 2000 + sDate.Year;
Time_Date.Month = sDate.Month;
Time_Date.Day = sDate.Date;
Time_Date.Weekday = sDate.WeekDay;
return &Time_Date;
/**
* @brief 设置时间日期
*
* @param H 小时
* @param Min 分钟
* @param S 秒
* @param D 多少号
* @param Month 月
* @param Y 年
* @param W 星期 1 - 7
*/
void RTC_Port_Set_Time_Date(uint8_t H, uint8_t Min, uint8_t S, uint8_t D, uint8_t Month, uint8_t Y, uint8_t W)
RTC_TimeTypeDef sTime = 0;
RTC_DateTypeDef sDate = 0;
sTime.Hours = H;
sTime.Minutes = Min;
sTime.Seconds = S;
sDate.Date = D;
sDate.Month = Month;
sDate.Year = Y;
sDate.WeekDay = W;
sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
sTime.StoreOperation = RTC_STOREOPERATION_RESET;
if(HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)
printf("Set Time Error\\n");
if(HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK)
printf("Set Date Error\\n");
/**
* @brief 检测是否是首次配置RTC
*
* @return true 是
* @return false 不是
*/
bool RTC_Port_Is_First_Config(void)
/* 检测是否是首次配置 */
if(HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR0) != RTC_FIRST_CONFIG_FLAG)
return true;
return false;
/**
* @brief RTC接口初始化
*
*/
void RTC_Port_Init(void)
/* 检测是否是首次配置 */
if(RTC_Port_Is_First_Config() == true)
/* 设置默认时间参数 */
RTC_Port_Set_Time_Date(12, 12, 12, 9, 8, 22, 2);
/* 默认关闭闹钟 */
RTC_Port_Set_Alarm_Close();
/* 写入已配置标识 */
HAL_PWR_EnableBkUpAccess();
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR0, RTC_FIRST_CONFIG_FLAG);
printf("RTC Port First Config.\\r\\n");
return;
printf("RTC Port Init ok.\\r\\n");
#ifdef __cplusplus ///<end extern c
#endif
/******************************** End of file *********************************/
定义一个标志位变量存储在不初始化的段
类型定义
/* BL跳转指示 */
typedef enum
BL_UNKNOW_FLAG = 0,
BL_JUMP_APP = 0x0566,
BL_JUMP_BL = 0x0577,
BL_PORT_STEP_Typedef_t;
typedef struct
BL_PORT_STEP_Typedef_t Flag;
uint32_t App_Addr;
uint32_t Stack_Base_Addr;
BL_PORT_JUMP_Typedef_t;
在IAR中定义
我们打开IAR的分散加载文件***.icf
,找到不初始化的段名.noinit
static BL_PORT_JUMP_Typedef_t BL_Static_Jump_Data MATH_PORT_SECTION(".noinit");
MATH_PORT_SECTION是为了指定该变量分配到哪个段中,宏定义如下:不同的编译器不同关键字实现
#ifndef MATH_PORT_SECTION
#if defined (__CC_ARM) /* ARM Compiler */
#if ENABLE_SECTION_SPACE
#define MATH_PORT_SECTION(x) __attribute__((section(x)))
#else
#define MATH_PORT_SECTION(x)
#endif
#define MATH_PORT_USED __attribute__((used))
#define MATH_PORT_UNUSED __attribute__((unused))
#elif defined (__IAR_SYSTEMS_ICC__) /* for IAR Compiler */
#if ENABLE_SECTION_SPACE
#define MATH_PORT_SECTION(x) @ x
#else
#define MATH_PORT_SECTION(x)
#endif
#define MATH_PORT_USED __root
#define MATH_PORT_UNUSED
#elif defined (__GNUC__) /* GNU GCC Compiler */
#if ENABLE_SECTION_SPACE
#define MATH_PORT_SECTION(x) __attribute__((section(x)))
#else
#define MATH_PORT_SECTION(x)
#endif
#define MATH_PORT_USED __attribute__((used))
#define MATH_PORT_UNUSED __attribute__((unused))
#else
#define MATH_PORT_SECTION(x)
#define MATH_PORT_USED
#define MATH_PORT_UNUSED
#endif /* __CC_ARM */
/** @*/
#ifndef SECTION
#ifdef __CC_ARM /* ARM Compiler */
#define SECTION(x) __attribute__((section(x)))
#define USED __attribute__((used))
#define UNUSEDX __attribute__((unused))
#elif defined (__IAR_SYSTEMS_ICC__) /* for IAR Compiler */
#define SECTION(x) @ x
#define USED __root
#elif defined (__GNUC__) /* GNU GCC Compiler */
#define SECTION(x) __attribute__((section(x)))
#define USED __attribute__((used))
#define UNUSED __attribute__((unused))
#else
#error not supported tool chain
#endif /* __CC_ARM */
#endif
#endif
找到map文件,可以看到变量被分配的地址及段:
在MDK中定义
为了统一都用.noinit
段
在***.sct
分散加载文件中,修改如下:关键字UNINIT
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
;LR_IROM1 0x08020000 0x001E0000 ; load region size_region
; ER_IROM1 0x08020000 0x001E0000 ; load address = execution address
LR_IROM1 0x08040000 0x001C0000 ; load region size_region
ER_IROM1 0x08040000 0x001C0000 ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
RW_RAM1 0x00000000 0x00010000 ; 64KB ITCM Code Data 400MHz
*(USE_ITCM_SPACE)
*(.text.aidis.ro)
*(.text.aidis.math)
.ANY (+RO)
.ANY (+XO)
RW_IRAM1 0x20000000 UNINIT 0x0000000C ; 12Bytes DTCM Data 400MHz
*(.noinit)
.ANY (+RW +ZI)
RW_IRAM1 0x2000000C 0x0001FFF4 ; 128KB-12Bytes DTCM Data 400MHz
*(USE_DTCM_SPACE)
.ANY (+RW +ZI)
RW_IRAM2 0x24000000 0x00080000 ; 512KB Main Data 200MHz
.ANY (+RW +ZI)
RW_IRAM3 0x30000000 0x00020000 ; 128KB D2 DMA Data 200MHz
*(USE_LCD_DMA_BUF_SPACE)
RW_IRAM4 0x30020000 0x00020000 ; 128KB D2 DMA Data 200MHz
*(USE_IDEL1_DMA_BUF_SPACE)
RW_IRAM5 0x30040000 0x00008000 ; 32KB D2 DMA Data 200MHz
*(USE_IDEL2_DMA_BUF_SPACE)
.ANY (+RW +ZI)
RW_IRAM6 0x38000000 0x00010000 ; 64KB D3 DMA Data 200MHz
*(USE_DMA_BUF_SPACE)
RW_IRAM7 0x38800000 0x00001000 ; 4KB Low Power Save Data 200MHz
*(USE_BACKUP_BUF_SPACE)
修改跳转接口
原来跳转的地方,改为直接复位,这样在BL想要跳转到APP分区时,将APP分区的信息给到标志变量,然后软复位,软复位后先检查是否需要跳转,需要跳转直接跳转到APP分区,不需要进行硬件恢复操作
/**
******************************************************************
* @brief 跳转至应用程序分区
* @param [in]App_Addr app程序起始地址
* @param [in]Stack_Base_Addr 栈底地址
* @return None.
* @author aron566
* @version v1.0
* @date 2022/11/11
******************************************************************
*/
static void Jump_To_Application(uint32_t App_Addr, uint32_t Stack_Base_Addr)
uint32_t JumpAddress = 0;
printf("jump to : %08X, StackBase : %08X\\r\\n", App_Addr, Stack_Base_Addr);
printf("read top address of stack : %08X\\r\\n", (*(__IO uint32_t *)App_Addr));
printf("check stack in:%08X\\r\\n\\n", ((*(__IO uint32_t *)App_Addr) & STACK_ADDR_MASK));
/* Test if user code is programmed starting from address "APPLICATION_ADDRESS" */
if(((*(__IO uint32_t *)App_Addr) & STACK_ADDR_MASK) == Stack_Base_Addr)
/*Jump to user application*/
JumpAddress = *(__IO uint32_t *)(App_Addr + 4);
JumpToApplication = (pJumpFunction)JumpAddress;
if(App_Addr == USER_BL_ADDR)
BL_Static_Jump_Data.Flag = BL_JUMP_BL;
else
BL_Static_Jump_Data.Flag = BL_JUMP_APP;
BL_Static_Jump_Data.App_Addr = App_Addr;
BL_Static_Jump_Data.Stack_Base_Addr = Stack_Base_Addr;
printf("System Reset.\\r\\n");
/* 软复位 */
HAL_NVIC_SystemReset();
return;
#if 0
/* 关闭全局中断 */
__disable_irq();
/*关闭滴答定时器,复位到默认值*/
SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;
/*设置所有时钟到默认状态,使用HSI时钟*/
HAL_RCC_DeInit();
/*所有开启的外设恢复默认状态,否则产生中断标志在APP阶段导致硬件错误*/
HAL_DeInit();
Serial_Port_DeInit();
/*关闭所有中断,清除所有中断挂起标志 */
for(int i = 0; i < 8; i++)
NVIC->ICER[i] = 0xFFFFFFFF;
NVIC->ICPR[i] = 0xFFFFFFFF;
/*使能全局中断*/
__enable_irq();以上是关于单片机通用Bootloader框架-优化的主要内容,如果未能解决你的问题,请参考以下文章
PIC32MZ Live update bootloader
PIC32单片机端C语言serial bootloader和PC端C#语言bootloader串口通信程序