打印
[STM32F4]

STM32F4 bootloader程序

[复制链接]
774|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
tpgf|  楼主 | 2024-5-23 11:37 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
  STM32的Bootloader主要负责在设备上电或复位后初始化硬件,然后加载并启动主应用程序。它还可以提供一些额外的功能,如通过串口、USB或其他接口进行程序烧录,支持固件升级,以及在开发过程中的调试。

      本文章的bootloader实现以下功能:

        v1.0.0 正常bootloader启动,升级
        v1.0.1 增加备份功能,每次升级之前,把旧的代码备份,以防升级失败无法启动系统,只需在等待bootloader启动期间通过串口发送use backup system即可启用备份的系统
        v1.0.2 可在等待bootloader启动期间通过串口发送downloadfile即可发送升级.bin文件升级  

        stm32有3种启动方式,当boot0置0时,将从单片机内部flash中启动,也就是0x08000000处。本文章用的就是这种方式。



        所以我们可以把bootloader程序下载到0x08000000处,把应用程序放到flash的其他位置。再通过bootloader程序跳转到应用程序,或者升级程序。

        这里用flash的前20KB存储bootloader程序,扇区5存储应用程序,扇区6存储待升级程序,扇区7存储备份的应用程序。



        完整代码在这,设置的0积分,不允许动态调整积分,应该是免费的吧【免费】STM32F4bootloader程序资源-CSDN文库     

        以下是bootloader程序主要代码

bootloader.c

#include "qingtianluoluo_ota.h"
#include "main.h"
#include "stdio.h"

#define VERSION "v 1.0.2"

uint8_t g_use_backup_system = 0;

/**
* @bieaf 写若干个数据
*
* @param addr       写入的地址
* @param buff       写入数据的起始地址
* @param word_size  长度
* @return
*/
static void WriteFlash(uint32_t addr, uint32_t * buff, int word_size)
{       
        /* 解锁FLASH*/
        FLASH_Unlock();
       
        for(int i = 0; i < word_size; i++)       
        {
                /* 对FLASH烧写*/
        FLASH_ProgramWord(addr + 4 * i, buff);       
        }

        /* 锁住FLASH*/
        FLASH_Lock();
}



/**
* @bieaf 读若干个数据
*
* @param addr       读数据的地址
* @param buff       读出数据的数组指针
* @param word_size  长度
* @return
*/
static void ReadFlash(uint32_t addr, uint32_t * buff, uint16_t word_size)
{
        for(int i =0; i < word_size; i++)
        {
                buff = *(__IO uint32_t*)(addr + 4 * i);
        }
        return;
}


/* 读取启动模式 */
unsigned int Read_Start_Mode(void)
{
        unsigned int mode = 0;
        ReadFlash((Application_2_Addr + Application_Size - 4), &mode, 1);
        return mode;
}



/**
* @bieaf  进行程序的覆盖
* @detail 1.擦除备份区域代码
*         2.备份旧代码
*         3.擦除目的地址
*         4.源地址的代码拷贝到目的地址
*         5.擦除源地址
*
* @param  搬运的源地址
* @param  搬运的目的地址
* @param  APP区域的总大小,不是代码大小
* @return
*/
void MoveCode(unsigned int src_addr, unsigned int des_addr, unsigned int byte_size)
{
        /*1.擦除备份地址*/
        printf("> Start erase backup flash......\r\n> ...\r\n");
    FLASH_Unlock();
    FLASH_EraseSector(FLASH_Sector_7, VoltageRange_3);
    FLASH_Lock();
        printf("> Erase backup flash down......\r\n\r\n");
        unsigned int temp[256];

    /*2.开始备份*/       
    printf("> start backup......\r\n> ...\r\n");
        for(int i = 0; i < byte_size/1024; i++)
        {
                ReadFlash((des_addr + i*1024), temp, 256);
                WriteFlash((Backup_Addr + i*1024), temp, 256);
        }
        printf("> backup down......\r\n\r\n");

    /*3.擦除目的地址*/
    printf("> Start erase des flash......\r\n> ...\r\n");
    FLASH_Unlock();
    FLASH_EraseSector(FLASH_Sector_5, VoltageRange_3);
    FLASH_Lock();
    printf("> Erase des flash down......\r\n");

        /*4.开始拷贝*/       
        printf("> Start copy......\r\n> ...\r\n");
        for(int i = 0; i < byte_size/1024; i++)
        {
                ReadFlash((src_addr + i*1024), temp, 256);
                WriteFlash((des_addr + i*1024), temp, 256);
        }
        printf("> Copy down......\r\n\r\n");

    /*5.擦除APP2区地址*/
    printf("> Start erase src flash......\r\n> ...\r\n");
    FLASH_Unlock();
    FLASH_EraseSector(FLASH_Sector_6, VoltageRange_3);
    FLASH_Lock();
    printf("> Erase src flash down......\r\n");

}

/**
* @brief  使用之前的系统启动
* @param  void
* @return void
* @NOTE   
*/
void use_backup_system(void)
{
    printf("> use backup system\r\n");
    /*擦除目的地址*/
    printf("> Start erase des flash......\r\n> ...\r\n");
    FLASH_Unlock();
    FLASH_EraseSector(FLASH_Sector_5, VoltageRange_3);
    FLASH_Lock();
    printf("> Erase des flash down......\r\n");
    unsigned int temp[256];

        /*开始拷贝*/       
        printf("> Start copy......\r\n> ...\r\n");
        for(int i = 0; i < Application_Size/1024; i++)
        {
                ReadFlash((Backup_Addr + i*1024), temp, 256);
                WriteFlash((Application_1_Addr + i*1024), temp, 256);
        }
        printf("> Copy down......\r\n\r\n");
}

/* 采用汇编设置栈的值 */
__asm void MSR_MSP (uint32_t ulAddr)
{
    MSR MSP, r0                                            //set Main Stack value
    BX r14
}



/* 程序跳转函数 */
typedef void (*Jump_Fun)(void);
void IAP_ExecuteApp (uint32_t App_Addr)
{
        Jump_Fun JumpToApp;

    /* 检查栈顶地址是否合法 */
        if ( ( ( * ( __IO uint32_t * ) App_Addr ) & 0x2FFE0000 ) == 0x20000000 )
        {
        printf("\r\njump to the app...\r\n\r\n\r\n");

        /* 禁用所有中断,记得在APP程序打开中断 */
        INTX_DISABLE();
        /* 用户代码区第二个字为程序开始地址(复位地址) */
                JumpToApp = (Jump_Fun) * ( __IO uint32_t *)(App_Addr + 4);       
        /* 初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址) */        
                MSR_MSP( * ( __IO uint32_t * ) App_Addr );
        /* 跳转到APP */
                JumpToApp();   
        }
    printf("jump to app failed\r\n");
}


extern void ota_update(void);
extern uint8_t g_updata_flag;
/**
* @bieaf 进行BootLoader的启动
*
* @param none
* @return none
*/
void Start_BootLoader(void)
{
        /*==========打印消息==========*/  
       
    printf("\r\n\r\nbootloader by qingtian-luoluo\r\n");
    printf("***********************************\r\n");
    printf("*                                 *\r\n");
    printf("*            BootLoader           *\r\n");
    printf("*                                 *\r\n");
    printf("***********************************\r\n");
    printf("bootloader version: %s\r\n\r\n", VERSION);


    extern uint16_t g_wait_start_ms;
    g_wait_start_ms = 0;
    printf("waitting for start bootloader\r\n");
    while(g_wait_start_ms < 5000)
    {
        if(g_wait_start_ms % 500 == 0)
        {
            g_wait_start_ms++;
            printf("- ");
        }
        /* 等待期间处理调试串口的消息 */
        User_uart_handle();
        if(g_updata_flag || g_use_backup_system)
        {
            break;
        }
    }
    TIM_DeInit(TIM4);

    /* 通过串口下载升级程序,并检验无误后进行升级 */
    if(g_updata_flag)
    {
        ota_update();
    }
        /* 使用备份的程序,每次升级都会备份之前的程序 */
    if(g_use_backup_system)
    {
        use_backup_system();
    }
    else
    {
        switch(Read_Start_Mode())//读取是否启动应用程序
        {
            case Startup_Normol://正常启动
            {
                printf("> Normal start......\r\n");
                break;
            }
            case Startup_Update://升级再启动
            {
                printf("> Start update......\r\n");               
                MoveCode(Application_2_Addr, Application_1_Addr, Application_Size);
                printf("> Update down......\r\n");
                break;
            }
            case Startup_Reset://恢复出厂设置 目前没使用
            {
                printf("> Restore to factory program......\r\n");
                break;                       
            }
            default://启动失败
            {
                printf("> Error:%X!!!......\r\n", Read_Start_Mode());
                return;                       
            }
        }
    }
       
        /* 跳转到应用程序 */
        printf("> Start up......\r\n\r\n");       

        IAP_ExecuteApp(Application_1_Addr);
}





bootloader.h

#ifndef __LEAF_OTA_H_
#define __LEAF_OTA_H_



/*=====用户配置(根据自己的分区进行配置)=====*/
#define BootLoader_Size                 0x5000U                        ///< BootLoader的大小 20K
#define Application_Size                0x20000U            ///< 应用程序的大小 128K

#define Application_1_Addr                0x08020000U            ///< 应用程序1的首地址
#define Application_2_Addr                0x08040000U                ///< 应用程序2的首地址
#define Backup_Addr                        0x08060000U                ///< 备份程序的首地址
/*==========================================*/



/* 启动的步骤 */
#define Startup_Normol 0xFFFFFFFF        ///< 正常启动
#define Startup_Update 0xAAAAAAAA        ///< 升级再启动
#define Startup_Reset  0x5555AAAA        ///< ***恢复出厂 目前没使用***



void Start_BootLoader(void);

#endif


        在bootloader程序里,跳转应用程序之前关闭所有中断,以防跑飞



        然后就是下载bootloader程序,烧录的时候注意起始位置和大小



        OK,到这里,一个bootloader程序就几乎是完成了。接下来就是应用程序。

        由于在bootloader程序跳转的时候关闭了所有中断,所以第一时间肯定是要开启中断的。然后就是设置中断向量表偏移,可以选择直接修改VECT_TAB_OFFSET宏,也可以直接修改寄存器。



        再然后就是修改程序运行地址,由于这里的应用程序是扇区5(0x08020000),最大限制为一个扇区128KB,所以要设置下载位置跟大小



        再再然后就是把应用程序下载到芯片上,记得修改下载位置跟大小



        OK,到这里就已经实现了通过bootloader去启动一个应用程序。接下来就是升级程序,升级程序要用到的是.bin文件,但是keil不能直接生成.bin文件,所以要进行一些小小的设置。



D:\keil5\ARM\ARMCC\bin\fromelf.exe --bin --output ..\APP_Code.bin ..\Output\STM32F4_Temp.axf
        其实就是利用了keil自带的一个功能来生成

        其中D:\keil5是keil5的安装目录
        \ARM\ARMCC\bin\fromelf.exe是安装路径下的一个可执行文件
        --bin --output输出 .bin文件
        ..\APP_Code.bin 在.uvprojx项目文件所在目录的上一级目录生成APP_Code.bin文件
        ..\Output\STM32F4_Temp.axf 从.uvprojx文件所在目录的上一级目录的\Output目录里的\STM32F4_Temp.axf文件生成。这里是STM32F4_Temp.axf,具体名称取决于keil5设置的输出文件名称。
        以上路径,可以根据实际需要进行修改。

        再编译一下,就得到了.bin文件,然后再自己各显神通,把.bin文件写到单片机内部flash的扇区6。并且把扇区7-4的地址,也就是(0x08060000 - 0x04),在这写入0xAAAAAAAA,再重新上电,bootloader程序检查到这里被置0xAAAAAAAA之后,就会备份,升级,然后启动新应用程序。
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/weixin_51077191/article/details/139093817

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

1923

主题

15596

帖子

11

粉丝