单片机通用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

关于ISPIAPDFU和bootloader

PIC32单片机端C语言serial bootloader和PC端C#语言bootloader串口通信程序

STM32单片机bootloader扫盲

freescale飞思卡尔 HCS12 系列单片机bootloader详解

STM32 BootLoader升级固件