打印
[STM32F1]

基于STM32F103C8T6的双区bootloader学习总结

[复制链接]
130|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
wakayi|  楼主 | 2024-10-30 14:36 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
在微控制器(MCU)中,Bootloader(引导加载程序)是一个非常重要的组成部分,它负责在系统启动时执行一系列初始化操作,并为后续的应用程序提供一个合适的运行环境。

1. Bootloader介绍
Bootloader是微控制器启动时运行的第一个程序,它位于存储器的特定位置,通常是Flash存储器的开始部分。Bootloader的设计目的是在没有操作系统的情况下,独立完成一系列关键任务,为系统的进一步启动做好准备。

Bootloader的作用

硬件初始化:
初始化微控制器的硬件资源,包括但不限于时钟设置、复位外设、配置中断控制器等,以确保系统处于一个稳定且可预测的状态。
加载和执行应用程序:
将应用程序从非易失性存储器(如Flash)加载到RAM中,并跳转到应用程序的入口点(通常是main()函数),使应用程序得以执行。
固件更新:
提供一种机制,使得用户可以通过不同的通信接口(如UART、USB、CAN等)上传新的固件到MCU的Flash存储器中,从而实现远程更新或现场编程。
这一功能对于需要在生命周期内不断升级的设备尤为重要。
故障恢复:
在应用程序崩溃或出现错误时,Bootloader 可以充当一个恢复机制,例如重启系统或将系统恢复到一个已知良好的状态。
安全和认证:
现代Bootloader 可以实现安全启动,确保只有经过认证的固件才能被加载执行。这有助于保护系统免受恶意软件的攻击。
配置和设置:
提供用户界面或命令行工具,允许用户在启动时进行一些基本的系统配置,如选择不同的启动模式或设定启动参数。
Bootloader的分类

基本Bootloader

功能:

初始化硬件。
加载并启动用户应用程序。
特点:

简单且占用资源少。
不支持固件更新。
通常在上电或复位时运行。
应用场景:

固件更新不频繁的应用。
资源受限的低功耗设备。
代码示例:

#include "stm32f4xx.h"  // 示例使用STM32F4系列

void SystemClock_Config(void);
void JumpToApplication(uint32_t appAddress);

int main(void) {
    // 系统时钟配置
    SystemClock_Config();

    // 启用全局中断
    __enable_irq();

    // 检查是否有有效的应用程序
    if (*((uint32_t*)0x08008000) == 0x20000000) {
        // 跳转到应用程序
        JumpToApplication(0x08008000);
    } else {
        // 如果没有有效的应用程序,则保持在Bootloader中
        while (1) {
            // 可以添加错误处理代码
        }
    }
}

void JumpToApplication(uint32_t appAddress) {
    // 设置堆栈指针
    __set_MSP(*(__IO uint32_t*)appAddress);
    // 获取复位向量地址
    void (*app_entry)(void) = (void (*)(void))(*(uint32_t*)(appAddress + 4));
    // 跳转到应用程序
    app_entry();
}


可升级Bootloader

功能:

除了基本的启动功能外,还支持通过某种接口(如UART、USB、I2C、SPI等)进行固件更新。
特点:

支持在线编程(In-System Programming, ISP)或在应用编程(In-Application Programming, IAP)。
通常包含安全机制,防止未经授权的固件更新。
可以通过多种通信协议进行固件更新,如YModem、XModem、CAN、以太网等。
应用场景:

物联网设备,需要定期更新固件。
工业控制设备,需要远程维护和更新。
智能家居设备,需要持续的功能改进和安全更新。
代码示例:

#include "stm32f4xx.h"
#include "usart.h"  // 假设有一个USART库用于串口通信

void SystemClock_Config(void);
void JumpToApplication(uint32_t appAddress);
void UpdateFirmware(uint8_t *data, size_t length);

int main(void) {
    // 系统时钟配置
    SystemClock_Config();

    // 启用全局中断
    __enable_irq();

    // 初始化串口
    USART_Init();

    // 检查是否接收到固件更新请求
    if (USART_ReceiveCommand() == 'U') {
        // 接收新的固件数据
        uint8_t *newFirmware = (uint8_t *)malloc(length);
        USART_ReceiveData(newFirmware, length);

        // 更新固件
        UpdateFirmware(newFirmware, length);

        // 释放内存
        free(newFirmware);
    }

    // 检查是否有有效的应用程序
    if (*((uint32_t*)0x08008000) == 0x20000000) {
        // 跳转到应用程序
        JumpToApplication(0x08008000);
    } else {
        // 如果没有有效的应用程序,则保持在Bootloader中
        while (1) {
            // 可以添加错误处理代码
        }
    }
}

void UpdateFirmware(uint8_t *data, size_t length) {
    // 擦除Flash区域
    FLASH_EraseSector(FLASH_SECTOR_0, VOLTAGE_RANGE_3);
    // 写入新固件
    for (size_t i = 0; i < length; i += 2) {
        FLASH_ProgramHalfWord(0x08008000 + i, ((uint16_t)data << 8) | data[i+1]);
    }
}


双区Bootloader

功能:

使用两个独立的存储区域(通常是Flash),一个用于当前运行的应用程序,另一个用于新版本的固件。
特点:

支持无缝固件更新,即在更新过程中不会中断应用程序的运行。
更新完成后,Bootloader会切换到新的固件区域。
通常包括校验机制(如CRC校验)来确保新固件的完整性。
如果新固件验证失败,可以回滚到旧版本。
应用场景:

医疗设备,需要高可靠性。
关键任务系统,不能容忍停机时间。
需要连续运行的工业控制系统。
代码示例:

#include "stm32f4xx.h"
#include "usart.h"  // 假设有一个USART库用于串口通信

#define APP1_ADDR 0x08008000
#define APP2_ADDR 0x08010000
#define ACTIVE_APP_FLAG_ADDR 0x080FF000

void SystemClock_Config(void);
void JumpToApplication(uint32_t appAddress);
void UpdateFirmware(uint8_t *data, size_t length, uint32_t targetAddr);
uint32_t GetActiveAppAddress(void);
void SetActiveAppAddress(uint32_t addr);

int main(void) {
    // 系统时钟配置
    SystemClock_Config();

    // 启用全局中断
    __enable_irq();

    // 初始化串口
    USART_Init();

    // 检查是否接收到固件更新请求
    if (USART_ReceiveCommand() == 'U') {
        // 接收新的固件数据
        uint8_t *newFirmware = (uint8_t *)malloc(length);
        USART_ReceiveData(newFirmware, length);

        // 更新固件
        uint32_t activeAddr = GetActiveAppAddress();
        uint32_t targetAddr = (activeAddr == APP1_ADDR) ? APP2_ADDR : APP1_ADDR;
        UpdateFirmware(newFirmware, length, targetAddr);

        // 设置活动的应用程序地址
        SetActiveAppAddress(targetAddr);

        // 释放内存
        free(newFirmware);
    }

    // 获取活动的应用程序地址
    uint32_t appAddr = GetActiveAppAddress();

    // 检查是否有有效的应用程序
    if (*((uint32_t*)appAddr) == 0x20000000) {
        // 跳转到应用程序
        JumpToApplication(appAddr);
    } else {
        // 如果没有有效的应用程序,则保持在Bootloader中
        while (1) {
            // 可以添加错误处理代码
        }
    }
}

void UpdateFirmware(uint8_t *data, size_t length, uint32_t targetAddr) {
    // 擦除Flash区域
    FLASH_EraseSector((targetAddr - 0x08000000) / 0x5000, VOLTAGE_RANGE_3);
    // 写入新固件
    for (size_t i = 0; i < length; i += 2) {
        FLASH_ProgramHalfWord(targetAddr + i, ((uint16_t)data << 8) | data[i+1]);
    }
}

uint32_t GetActiveAppAddress(void) {
    return *(uint32_t*)ACTIVE_APP_FLAG_ADDR;
}

void SetActiveAppAddress(uint32_t addr) {
    *(uint32_t*)ACTIVE_APP_FLAG_ADDR = addr;
}


加密Bootloader

功能:

在基本或可升级Bootloader的基础上增加了加密和安全特性。
特点:

使用加密算法(如AES、RSA)保护固件免受篡改。
支持数字签名验证,确保固件来自可信源。
可能包含安全密钥管理和认证机制。
保护敏感数据和代码。
应用场景:

支付终端,需要高度的安全性。
智能卡,需要防止未授权访问和篡改。
安全通信设备,需要保护数据传输的安全性。
代码示例:

#include "stm32f4xx.h"
#include "usart.h"  // 假设有一个USART库用于串口通信
#include "aes.h"   // 假设有一个AES库用于加密/解密

#define AES_KEY {0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c}

void SystemClock_Config(void);
void JumpToApplication(uint32_t appAddress);
void UpdateFirmware(uint8_t *data, size_t length, uint32_t targetAddr);
uint32_t GetActiveAppAddress(void);
void SetActiveAppAddress(uint32_t addr);
bool VerifySignature(uint8_t *data, size_t length, uint8_t *signature, size_t sigLength);

int main(void) {
    // 系统时钟配置
    SystemClock_Config();

    // 启用全局中断
    __enable_irq();

    // 初始化串口
    USART_Init();

    // 检查是否接收到固件更新请求
    if (USART_ReceiveCommand() == 'U') {
        // 接收新的固件数据
        uint8_t *newFirmware = (uint8_t *)malloc(length);
        USART_ReceiveData(newFirmware, length);

        // 接收签名数据
        uint8_t *signature = (uint8_t *)malloc(sigLength);
        USART_ReceiveData(signature, sigLength);

        // 验证签名
        if (VerifySignature(newFirmware, length, signature, sigLength)) {
            // 更新固件
            uint32_t activeAddr = GetActiveAppAddress();
            uint32_t targetAddr = (activeAddr == APP1_ADDR) ? APP2_ADDR : APP1_ADDR;
            UpdateFirmware(newFirmware, length, targetAddr);

            // 设置活动的应用程序地址
            SetActiveAppAddress(targetAddr);
        }

        // 释放内存
        free(newFirmware);
        free(signature);
    }

    // 获取活动的应用程序地址
    uint32_t appAddr = GetActiveAppAddress();

    // 检查是否有有效的应用程序
    if (*((uint32_t*)appAddr) == 0x20000000) {
        // 跳转到应用程序
        JumpToApplication(appAddr);
    } else {
        // 如果没有有效的应用程序,则保持在Bootloader中
        while (1) {
            // 可以添加错误处理代码
        }
    }
}

bool VerifySignature(uint8_t *data, size_t length, uint8_t *signature, size_t sigLength) {
    // 使用AES或其他加密算法验证签名
    uint8_t calculatedSig[sigLength];
    AES_Encrypt(data, length, AES_KEY, calculatedSig);

    // 比较计算出的签名和接收到的签名
    for (size_t i = 0; i < sigLength; ++i) {
        if (calculatedSig != signature) {
            return false;
        }
    }
    return true;
}



多阶段Bootloader

功能:

分多个阶段执行,每个阶段完成特定的任务。
特点:

第一阶段通常是简单的Bootloader,负责初始化基本硬件并加载第二阶段的Bootloader。
第二阶段Bootloader可能更复杂,支持更多的功能,如固件更新、安全验证等。
适用于需要复杂初始化过程或高度定制化的应用。
应用场景:

复杂的嵌入式系统,需要分阶段初始化。
需要高度定制化和灵活性的应用。
高性能计算平台,需要复杂的启动过程。
代码示例:

第一阶段Bootloader (Stage1)

#include "stm32f4xx.h"

void SystemClock_Config(void);
void JumpToAddress(uint32_t address);

int main(void) {
    // 系统时钟配置
    SystemClock_Config();

    // 设置堆栈指针
    __set_MSP(*(__IO uint32_t*)0x20000000);  // 假设堆栈位于SRAM起始位置

    // 从外部Flash加载第二阶段Bootloader到内部SRAM
    // 这里仅是概念演示,实际代码需要根据具体的Flash通信协议编写
    // 例如使用SPI读取数据并写入到SRAM
    // ...

    // 假设第二阶段Bootloader已经加载到0x20001000
    JumpToAddress(0x20001000);
}

void JumpToAddress(uint32_t address) {
    void (*app_entry)(void) = (void (*)(void))(*(uint32_t*)address);
    app_entry();
}


第二阶段Bootloader (Stage2)

#include "stm32f4xx.h"
#include "usart.h"  // 假设有一个USART库用于串口通信

void SystemClock_Config(void);
void InitializePeripherals(void);
void JumpToApplication(uint32_t appAddress);
void UpdateFirmware(uint8_t *data, size_t length, uint32_t targetAddr);

int main(void) {
    // 系统时钟配置
    SystemClock_Config();

    // 初始化外设
    InitializePeripherals();

    // 检查是否接收到固件更新请求
    if (USART_ReceiveCommand() == 'U') {
        // 接收新的固件数据
        uint8_t *newFirmware = (uint8_t *)malloc(length);
        USART_ReceiveData(newFirmware, length);

        // 更新固件
        UpdateFirmware(newFirmware, length, 0x08010000);

        // 释放内存
        free(newFirmware);
    }

    // 检查是否有有效的应用程序
    if (*((uint32_t*)0x08008000) == 0x20000000) {
        // 跳转到应用程序
        JumpToApplication(0x08008000);
    } else {
        // 如果没有有效的应用程序,则保持在Bootloader中
        while (1) {
            // 可以添加错误处理代码
        }
    }
}

// 其他函数定义...



自定义Bootloader

功能:

根据具体应用需求定制的Bootloader。
特点:

功能和实现方式完全取决于应用需求。
可以结合上述各种类型的特性。
提供最大程度的灵活性和定制化。
应用场景:

特殊应用场景,如特定硬件平台或专有协议。
需要特定功能和高度定制化的应用。
科研项目或原型开发,需要灵活的启动和配置选项。
代码示例:

支持通过UART进行固件更新的自定义Bootloader,并且要求对固件进行CRC校验。

#include "stm32f4xx.h"
#include "usart.h"  // 假设有一个USART库用于串口通信
#include <string.h>

#define APP_START_ADDR 0x08008000
#define BOOTLOADER_START_ADDR 0x08000000
#define FLASH_PAGE_SIZE 0x500

uint32_t CalculateCRC32(uint8_t *data, size_t length);
void SystemClock_Config(void);
void InitializePeripherals(void);
void JumpToApplication(uint32_t appAddress);
void EraseFlashPage(uint32_t page_addr);
void WriteToFlash(uint32_t addr, uint16_t data);

int main(void) {
    // 系统时钟配置
    SystemClock_Config();

    // 初始化外设
    InitializePeripherals();

    // 检查是否接收到固件更新请求
    if (USART_ReceiveCommand() == 'U') {
        // 接收固件大小
        size_t firmware_size;
        USART_ReceiveSize(&firmware_size);

        // 分配缓冲区接收固件数据
        uint8_t *firmware_data = (uint8_t *)malloc(firmware_size);
        USART_ReceiveData(firmware_data, firmware_size);

        // 计算CRC32
        uint32_t crc = CalculateCRC32(firmware_data, firmware_size);

        // 接收预期的CRC32值
        uint32_t expected_crc;
        USART_ReceiveCRC(&expected_crc);

        // 校验CRC
        if (crc == expected_crc) {
            // 固件有效,开始更新
            for (size_t i = 0; i < firmware_size; i += 2) {
                uint32_t flash_addr = APP_START_ADDR + i;
                if (i % FLASH_PAGE_SIZE == 0) {
                    EraseFlashPage(flash_addr);
                }
                WriteToFlash(flash_addr, ((uint16_t)firmware_data << 8) | firmware_data[i+1]);
            }

            // 更新完成后,跳转到应用程序
            JumpToApplication(APP_START_ADDR);
        } else {
            // CRC校验失败
            while (1) {
                // 错误处理
            }
        }

        // 释放内存
        free(firmware_data);
    }

    // 检查是否有有效的应用程序
    if (*((uint32_t*)APP_START_ADDR) == 0x20000000) {
        // 跳转到应用程序
        JumpToApplication(APP_START_ADDR);
    } else {
        // 如果没有有效的应用程序,则保持在Bootloader中
        while (1) {
            // 可以添加错误处理代码
        }
    }
}

// 计算CRC32
uint32_t CalculateCRC32(uint8_t *data, size_t length) {
    // 实现CRC32计算
    // 这里只是一个占位符
    return 0;
}

// 其他函数定义...





Bootloader的实现

实现一个Bootloader通常需要完成以下步骤:

硬件初始化:
设置CPU寄存器、初始化时钟、配置中断等,确保硬件处于正确的工作状态。
设置堆栈和堆空间:
分配足够的内存空间作为堆栈和堆,以便程序能够正常运行。
加载应用程序:
从Flash读取应用程序代码并将其拷贝到RAM中,准备执行。
跳转到应用程序入口点:
跳转到应用程序的入口点(如main()函数),启动应用程序。
固件更新逻辑:
如果Bootloader支持固件更新,需要实现接收新固件、验证固件完整性、写入新固件到Flash等操作。
常见的MCU Bootloader实例

ST-Link Bootloader:
用于STM32系列MCU,支持通过SWD或JTAG接口进行编程。
USB DFU Bootloader:
利用USB设备固件更新(Device Firmware Update)协议,通过USB接口进行固件更新。
TinyUSB Bootloader:
基于TinyUSB库实现的Bootloader,支持通过USB接口更新固件。
Blinky Bootloader:
适用于AVR和ARM Cortex-M系列MCU的开源Bootloader。
本次bootloader学习是基于双区bootloader,即bootloader程序和app程序存放在不同区域,由bootloader程序跳转到app程序运行。

通过几个示例对不同场景不同bootloader用法的学习:

bootloader直接跳转到app运行:

应用场景:bootloader和app程序都存放在XIP类型的Flash上,即可以在Flash上运行程序。
示例说明:bootloader初始化UART后打印“bootloader”,然后跳转到app程序,打印app程序内容。



bootloader跳转到app后,app把自己复制到RAM中运行:

应用场景:bootloader和app程序都存放在XIP类型的Flash上,即可以在Flash上运行程序。(程序复制到内存中运行可以提高CPU运行效率,参考文章:28335在程序在FLASH里运行和在RAM里运行的区别?)。
示例说明:bootloader初始化UART后打印“bootloader”,然后跳转到app程序,app把自己复制到RAM中运行,打印app程序内容。



bootloader把app复制到RAM中,再跳转到RAM中的app运行:

应用场景:bootloader程序都存放在XIP类型的Flash上,即可以在Flash上运行程序,app程序存放在非XIP类型的Flash上(如MCU外挂的SPI Flash)
示例说明:bootloader初始化UART后打印“bootloader”,然后把app复制到内存中,最后跳转到内存中app的位置运行,打印app程序内容。



bootloader把app复制到RAM中,并重新设置vector(异常向量表),再跳转到RAM中的app运行:

应用场景:bootloader程序都存放在XIP类型的Flash上,即可以在Flash上运行程序,app程序存放在非XIP类型的Flash上(如MCU外挂的SPI Flash)。

注:前提是硬件支持重新设置vector,例如app是一个FreeRTOS程序,需要使用到Timer中断,即Timer异常,要想使app正常运行就需要先设置好vector。

示例说明:bootloader初始化UART后打印“bootloader”,然后把app复制到内存中并重新设置vector,最后跳转到内存中app的位置运行,打印app程序内容。



bootloader把app复制到RAM中,实现vector(异常向量表)跳转到app的vector功能,再跳转到RAM中的app运行:

应用场景:bootloader程序都存放在XIP类型的Flash上,即可以在Flash上运行程序,app程序存放在非XIP类型的Flash上(如MCU外挂的SPI Flash)。

注:前提是硬件不支持重新设置vector,例如app是一个FreeRTOS程序,需要使用到Timer中断,即Timer异常,要想使app正常运行就需要先设置好vector,但由于无法重新设置vector,这时就需要在原先vector中实现:触发异常时跳转到app vector对应的异常去运行的功能。

此示例参考:韦东山老师教程

示例说明:bootloader初始化UART后打印“bootloader”,然后把app复制到内存中并实现vector跳转到app vector的功能,最后跳转到内存中app的位置运行,打印app程序内容。



2. 补充-异常向量概念
异常向量是一组固定的内存地址,这些地址指向处理特定类型异常的函数或代码段。当处理器检测到一个异常时,它会自动跳转到相应的异常向量所指示的位置,执行在那里存放的异常处理程序。

异常向量的作用:

异常处理:
异常向量允许处理器在检测到异常时,立即跳转到预先定义好的处理程序中,从而快速响应并处理异常情况。
这些处理程序可以进行错误处理、日志记录、系统重启等操作,以保证系统的稳定性和可靠性。
硬件初始化:
在某些情况下,异常向量也可以用于硬件初始化。例如,在系统上电或复位时,处理器可能会首先跳转到一个固定的异常向量地址,执行一些基本的硬件初始化操作。
系统安全:
异常向量还可以用于实现安全机制。例如,当检测到非法访问或错误指令时,处理器可以跳转到异常向量处的安全处理程序,防止系统受到进一步的损害。
调试支持:
异常向量通常也是调试过程中非常有用的工具。当系统运行时发生异常,开发人员可以通过查看异常向量及其处理程序来定位问题的原因。
异常类型:

常见的异常类型包括但不限于:

复位异常(Reset Exception):
系统复位时发生的异常,通常用于初始化系统。
未定义指令异常(Undefined Instruction Exception):
当处理器遇到一条无法识别的指令时触发的异常。
软件中断异常(Software Interrupt Exception):
通过软件指令(如SWI)引发的异常,通常用于系统调用。
预取中止异常(Prefetch Abort Exception):
当处理器尝试从非法地址预取指令时触发的异常。
数据中止异常(Data Abort Exception):
当处理器尝试访问非法地址的数据时触发的异常。
IRQ异常(IRQ Interrupt Exception):
处理器接收到中断请求(Interrupt Request)时触发的异常。
FIQ异常(FIQ Interrupt Exception):
快速中断请求(Fast Interrupt Request)时触发的异常,通常用于高优先级的中断处理。
异常向量表(Exception Vector Table):

异常向量表是一个固定长度的数组,每个元素对应一个异常类型,并指向处理该异常的函数地址。在系统启动时,异常向量表通常被加载到一个特定的内存区域(通常是RAM的低地址部分)。处理器在检测到异常时,会根据异常类型查找相应的向量地址,并跳转到那里执行处理程序。

ARM Cortex-M 系列的异常向量:

以ARM Cortex-M系列为例,其异常向量表通常包含以下条目:

复位向量(Reset Vector)
NMI(Non-Maskable Interrupt)向量
硬故障(Hard Fault)向量
内存管理故障(Memory Management Fault)向量
总线故障(Bus Fault)向量
使用故障(Usage Fault)向量
保留(Reserved)向量
SVCall(System Call)向量
监控器(Monitor)向量
PendSV(Pending SV)向量
Systick(SysTick)向量
IRQ中断向量表
异常向量的配置:

在编写MCU程序时,通常需要配置异常向量表。这可以通过以下方式实现:

初始化代码:
在系统初始化代码中设置异常向量表,确保处理器能够正确响应各种异常。
链接器脚本:
使用链接器脚本定义异常向量表的内存布局,确保异常向量表被放置在正确的内存位置。
硬件配置:
在某些情况下,可能需要通过硬件配置(如寄存器设置)来指定异常向量表的位置。
3. 示例1-APP无异常向量
3.1 源码
bootloader工程中只有4个文件,start.s、main.c、uart.c、uart.h,各文件源码如下:

start.s:

                PRESERVE8 ;伪指令,用于指示编译器在接下来的代码中保留8位汇编语法。
                THUMB ;伪指令,用于指示编译器接下来的代码将以Thumb模式编译。Thumb模式是ARM架构的一种低功耗模式,使用16位指令集。


; AREA定义了一个新的段(section),RESET表示这个段的名字,DATA表示数据段,READONLY表示这个段是只读的。
; EXPORT声明了一个全局符号__Vectors,使得其他模块可以引用这个符号。
; __Vectors是异常向量表的起始地址。
; DCD是一个伪指令,用于定义一个32位的常量(双字)。
; 第一个DCD定义了一个初始堆栈指针(SP)的值,这里设置为0。在实际应用中,通常会设置一个有效的RAM地址作为初始堆栈指针。
; 第二个DCD定义了复位处理程序的地址,即Reset_Handler。

; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY
                                EXPORT  __Vectors
                                       
__Vectors       DCD     0                  
                DCD     Reset_Handler              ; Reset Handler

; AREA定义了一个新的段(section),.text表示代码段,CODE表示该段包含可执行代码,READONLY表示该段是只读的。
                                AREA    |.text|, CODE, READONLY

; Reset_Handler是复位处理程序的入口点。
; EXPORT声明了Reset_Handler为全局可见,[WEAK]表示这是一个弱符号,如果没有其他定义,这个定义将被使用。
; IMPORT声明了一个外部符号main,表示main是一个外部函数,将在链接时被解析。
; LDRSP,=(0x20000000+0x5000):这条指令设置了堆栈指针(SP),指向内存地址0x20000000+0x5000,即0x20005000。这通常是一个RAM区域,用于存储栈数据。
; BLmain:这条指令通过跳转(BranchandLink)调用main函数,main函数应该是应用程序的入口点。
; ENDP结束了Reset_Handler过程的定义。

; Reset handler
Reset_Handler   PROC
                                EXPORT  Reset_Handler             [WEAK]
                IMPORT  main

                                LDR SP, =(0x20000000+0x5000)
                                BL main

                ENDP

; END指令表示整个汇编文件的结束
                 END



main.c:

#include "uart.h"

void delay(int d) //简单的延时函数
{
        while(d--);
}

int main()
{
       
        void (*app)(void); //app地址
       
        uart_init(); //初始化串口

        putstr("bootloader\r\n"); // 串口打印bootloader
       
        /* start app */
        app = (void (*)(void))0x08008001; //将0x08008000地址赋给app,最低位等于1是Thumb指令集表示地址的特性
       
        app(); //跳转到app程序,实现bootloader程序跳转
       
        return 0;
}



uart.c(UART直接对寄存器操作,参考下面STM32F10XXX参考手册中截图):

#include "uart.h"

typedef unsigned int uint32_t;
typedef struct
{
  volatile uint32_t SR;    /*!< USART Status register, Address offset: 0x00 */
  volatile uint32_t DR;    /*!< USART Data register,   Address offset: 0x04 */
  volatile uint32_t BRR;   /*!< USART Baud rate register, Address offset: 0x08 */
  volatile uint32_t CR1;   /*!< USART Control register 1, Address offset: 0x0C */
  volatile uint32_t CR2;   /*!< USART Control register 2, Address offset: 0x10 */
  volatile uint32_t CR3;   /*!< USART Control register 3, Address offset: 0x14 */
  volatile uint32_t GTPR;  /*!< USART Guard time and prescaler register, Address offset: 0x18 */
} USART_TypeDef;


void uart_init(void)
{
        USART_TypeDef *usart1 = (USART_TypeDef *)0x40013800; //USART1寄存器地址
        volatile unsigned int *pReg;
        /* 使能GPIOA/USART1模块 */
        /* RCC_APB2ENR */
        pReg = (volatile unsigned int *)(0x40021000 + 0x18); //RRC寄存器地址
        *pReg |= (1<<2) | (1<<14); //使能APB2总线上USART1和GPIOA
       
        /* 配置引脚功能: PA9(USART1_TX), PA10(USART1_RX)
         * GPIOA_CRH = 0x40010800 + 0x04
         */
        pReg = (volatile unsigned int *)(0x40010800 + 0x04);
       
        /* PA9(USART1_TX) */
        *pReg &= ~((3<<4) | (3<<6)); //把GPIOA_CRH寄存器中MODE9和CNF9清零
        *pReg |= (1<<4) | (2<<6); //配置为复用功能推挽输出,最大输出速度为10MHz

        /* PA10(USART1_RX) */
        *pReg &= ~((3<<8) | (3<<10)); //把GPIOA_CRH寄存器中MODE10和CNF10清零
        *pReg |= (0<<8) | (1<<10);  //配置为浮空输入
       
        /* 设置波特率
         * 115200 = 8000000/16/USARTDIV
         * USARTDIV = 4.34
         * DIV_Mantissa = 4
         * DIV_Fraction / 16 = 0.34
         * DIV_Fraction = 16*0.34 = 5
         * 真实波特率:
         * DIV_Fraction / 16 = 5/16=0.3125
         * USARTDIV = DIV_Mantissa + DIV_Fraction / 16 = 4.3125
         * baudrate = 8000000/16/4.3125 = 115942
         */
#define DIV_Mantissa 4
#define DIV_Fraction 5
        usart1->BRR = (DIV_Mantissa<<4) | (DIV_Fraction);
       
        /* 设置数据格式: 8n1 */
        usart1->CR1 = (1<<13) | (0<<12) | (0<<10) | (1<<3) | (1<<2);       
        usart1->CR2 &= ~(3<<12);
       
        /* 使能USART1 */
}
       
int getchar(void)
{
        USART_TypeDef *usart1 = (USART_TypeDef *)0x40013800;
        while ((usart1->SR & (1<<5)) == 0);
        return usart1->DR;
}

int putchar(char c)
{
        USART_TypeDef *usart1 = (USART_TypeDef *)0x40013800;
        while ((usart1->SR & (1<<7)) == 0);
        usart1->DR = c;
       
        return c;
}

void putstr(char *str)
{
        while (*str)
        {
                putchar(*str);
                str++;
        }
}





uart.h:

#ifndef _UART_H
#define _UART_H
void uart_init(void);
int getchar(void);
int putchar(char c);
void putstr(char *str);
#endif


参考芯片手册中寄存器操作初始化串口:
















app工程中也有4个文件start.s、main.c、uart.c、uart.h,各文件源码如下。

start.s:

                PRESERVE8 ;伪指令,用于指示编译器在接下来的代码中保留8位汇编语法。
                THUMB ;伪指令,用于指示编译器接下来的代码将以Thumb模式编译。Thumb模式是ARM架构的一种低功耗模式,使用16位指令集。

; 以下注释说明了向量表的定义,但在实际代码中并未定义。向量表通常会放置在内存的最低地址处(通常是0x00000000),并在复位时指向复位处理程序。
; Vector Table Mapped to Address 0 at Reset
;                AREA    RESET, DATA, READONLY
;                                EXPORT  __Vectors
;
; DCD是一个伪指令,用于定义一个32位的常量(双字)。如果这段代码被启用,它将定义一个向量表,第一个条目为0(初始堆栈指针),第二个条目指向Reset_Handler。
;__Vectors       DCD     0                  
;                DCD     Reset_Handler              ; Reset Handler
;

; AREA定义了一个新的段(section),.text表示代码段,CODE表示该段包含可执行代码,READONLY表示该段是只读的。
                                AREA    |.text|, CODE, READONLY

; Reset_Handler是复位处理程序的入口点。
; EXPORT声明了Reset_Handler为全局可见,[WEAK]表示这是一个弱符号,如果没有其他定义,这个定义将被使用。
; IMPORT声明了一个外部符号mymain,表示mymain是一个外部函数,将在链接时被解析。
; LDRSP,=(0x20000000+0x5000):这条指令设置了堆栈指针(SP),指向内存地址0x20000000+0x5000,即0x200005000。这通常是一个RAM区域,用于存储栈数据。
; BLmymain:这条指令通过跳转(BranchandLink)调用mymain函数,mymain函数应该是应用程序的入口点。
; ENDP结束了Reset_Handler过程的定义。

; Reset handler
Reset_Handler   PROC
                                EXPORT  Reset_Handler             [WEAK]
                IMPORT  mymain

                                LDR SP, =(0x20000000+0x5000)  ; 0x08008000
                                BL mymain

                ENDP

; END指令表示整个汇编文件的结束
                 END





main.c:

#include "uart.h"

void delay(int d)
{
        while(d--);
}
/* 循环打印字母A-Z */
int mymain()
{
        char c = 'A';
       
        while (1)
        {
                putchar(c++);
                delay(1000000);
                if (c == 'Z')
                        c = 'A';
        }
       
        return 0;
}



注:app的main.c中如果把mymain改成main时无法正常跳转,使用mymain时编译的代码和使用main时编译的代码反汇编不同,在示例2-APP有异常向量中,无论使用mymain还是使用main都能成功跳转,猜测原因是没有设置异常向量表且函数未main时,系统会默认设置异常向量表;使用mymain时,系统不会主动设置异常向量表。

app的main.c使用main时的反汇编:

========================================================================

** ELF Header Information

    File Name: Objects\led_c.axf

    Machine class: ELFCLASS32 (32-bit)
    Data encoding: ELFDATA2LSB (Little endian)
    Header version: EV_CURRENT (Current version)
    Operating System ABI: none
    ABI Version: 0
    File Type: ET_EXEC (Executable) (2)
    Machine: EM_ARM (ARM)

    Image Entry point: 0x08008081
    Flags: EF_ARM_HASENTRY + EF_ARM_ABI_FLOAT_SOFT (0x05000202)

    ARM ELF revision: 5 (ABI version 2)

    Conforms to Soft float procedure-call standard

    Built with
    Component: ARM Compiler 5.06 update 7 (build 960) Tool: armasm [4d35fa]
    Component: ARM Compiler 5.06 update 7 (build 960) Tool: armlink [4d3601]

    Header size: 52 bytes (0x34)
    Program header entry size: 32 bytes (0x20)
    Section header entry size: 40 bytes (0x28)

    Program header entries: 1
    Section header entries: 15

    Program header offset: 12608 (0x00003140)
    Section header offset: 12640 (0x00003160)

    Section header string table index: 14

========================================================================

** Program header #0 (PT_LOAD) [PF_X + PF_W + PF_R + PF_ARM_ENTRY]
    Size : 484 bytes (388 bytes in file)
    Virtual address: 0x08008000 (Alignment 4)


========================================================================

** Section #1 'ER_RO' (SHT_PROGBITS) [SHF_ALLOC + SHF_EXECINSTR]
    Size   : 388 bytes (alignment 4)
    Address: 0x08008000

    $t
    !!!main
    __main
        0x08008000:    f000f802    ....    BL       __scatterload ; 0x8008008
        0x08008004:    f000f82c    ..,.    BL       __rt_entry ; 0x8008060
    !!!scatter
    __scatterload
    __scatterload_rt2
    __scatterload_rt2_thumb_only
        0x08008008:    a00a        ..      ADR      r0,{pc}+0x2c ; 0x8008034
        0x0800800a:    e8900c00    ....    LDM      r0,{r10,r11}
        0x0800800e:    4482        .D      ADD      r10,r10,r0
        0x08008010:    4483        .D      ADD      r11,r11,r0
        0x08008012:    f1aa0701    ....    SUB      r7,r10,#1
    __scatterload_null
        0x08008016:    45da        .E      CMP      r10,r11
        0x08008018:    d101        ..      BNE      0x800801e ; __scatterload_null + 8
        0x0800801a:    f000f821    ..!.    BL       __rt_entry ; 0x8008060
        0x0800801e:    f2af0e09    ....    ADR      lr,{pc}-7 ; 0x8008017
        0x08008022:    e8ba000f    ....    LDM      r10!,{r0-r3}
        0x08008026:    f0130f01    ....    TST      r3,#1
        0x0800802a:    bf18        ..      IT       NE
        0x0800802c:    1afb        ..      SUBNE    r3,r7,r3
        0x0800802e:    f0430301    C...    ORR      r3,r3,#1
        0x08008032:    4718        .G      BX       r3
    $d
        0x08008034:    00000140    @...    DCD    320
        0x08008038:    00000150    P...    DCD    336
    $t
    !!handler_zi
    __scatterload_zeroinit
        0x0800803c:    2300        .#      MOVS     r3,#0
        0x0800803e:    2400        .$      MOVS     r4,#0
        0x08008040:    2500        .%      MOVS     r5,#0
        0x08008042:    2600        .&      MOVS     r6,#0
        0x08008044:    3a10        .:      SUBS     r2,r2,#0x10
        0x08008046:    bf28        (.      IT       CS
        0x08008048:    c178        x.      STMCS    r1!,{r3-r6}
        0x0800804a:    d8fb        ..      BHI      0x8008044 ; __scatterload_zeroinit + 8
        0x0800804c:    0752        R.      LSLS     r2,r2,#29
        0x0800804e:    bf28        (.      IT       CS
        0x08008050:    c130        0.      STMCS    r1!,{r4,r5}
        0x08008052:    bf48        H.      IT       MI
        0x08008054:    600b        .`      STRMI    r3,[r1,#0]
        0x08008056:    4770        pG      BX       lr
    .ARM.Collect$$libinit$$00000000
    __rt_lib_init
        0x08008058:    b51f        ..      PUSH     {r0-r4,lr}
    .ARM.Collect$$libinit$$00000002
    .ARM.Collect$$libinit$$00000004
    .ARM.Collect$$libinit$$0000000A
    .ARM.Collect$$libinit$$0000000C
    .ARM.Collect$$libinit$$0000000E
    .ARM.Collect$$libinit$$00000011
    .ARM.Collect$$libinit$$00000013
    .ARM.Collect$$libinit$$00000015
    .ARM.Collect$$libinit$$00000017
    .ARM.Collect$$libinit$$00000019
    .ARM.Collect$$libinit$$0000001B
    .ARM.Collect$$libinit$$0000001D
    .ARM.Collect$$libinit$$0000001F
    .ARM.Collect$$libinit$$00000021
    .ARM.Collect$$libinit$$00000023
    .ARM.Collect$$libinit$$00000025
    .ARM.Collect$$libinit$$0000002C
    .ARM.Collect$$libinit$$0000002E
    .ARM.Collect$$libinit$$00000030
    .ARM.Collect$$libinit$$00000032
    .ARM.Collect$$libinit$$00000033
    __rt_lib_init_alloca_1
    __rt_lib_init_argv_1
    __rt_lib_init_atexit_1
    __rt_lib_init_clock_1
    __rt_lib_init_cpp_1
    __rt_lib_init_exceptions_1
    __rt_lib_init_fp_1
    __rt_lib_init_fp_trap_1
    __rt_lib_init_getenv_1
    __rt_lib_init_heap_1
    __rt_lib_init_lc_collate_1
    __rt_lib_init_lc_ctype_1
    __rt_lib_init_lc_monetary_1
    __rt_lib_init_lc_numeric_1
    __rt_lib_init_lc_time_1
    __rt_lib_init_preinit_1
    __rt_lib_init_rand_1
    __rt_lib_init_return
    __rt_lib_init_signal_1
    __rt_lib_init_stdio_1
    __rt_lib_init_user_alloc_1
        0x0800805a:    bd1f        ..      POP      {r0-r4,pc}
    .ARM.Collect$$libshutdown$$00000000
    __rt_lib_shutdown
        0x0800805c:    b510        ..      PUSH     {r4,lr}
    .ARM.Collect$$libshutdown$$00000002
    .ARM.Collect$$libshutdown$$00000004
    .ARM.Collect$$libshutdown$$00000006
    .ARM.Collect$$libshutdown$$00000009
    .ARM.Collect$$libshutdown$$0000000C
    .ARM.Collect$$libshutdown$$0000000E
    .ARM.Collect$$libshutdown$$00000011
    .ARM.Collect$$libshutdown$$00000012
    __rt_lib_shutdown_cpp_1
    __rt_lib_shutdown_fini_1
    __rt_lib_shutdown_fp_trap_1
    __rt_lib_shutdown_heap_1
    __rt_lib_shutdown_return
    __rt_lib_shutdown_signal_1
    __rt_lib_shutdown_stdio_1
    __rt_lib_shutdown_user_alloc_1
        0x0800805e:    bd10        ..      POP      {r4,pc}
    .ARM.Collect$$rtentry$$00000000
    .ARM.Collect$$rtentry$$00000002
    .ARM.Collect$$rtentry$$00000004
    __rt_entry
    __rt_entry_presh_1
    __rt_entry_sh
        0x08008060:    f000f814    ....    BL       __user_setup_stackheap ; 0x800808c
        0x08008064:    4611        .F      MOV      r1,r2
    .ARM.Collect$$rtentry$$00000009
    .ARM.Collect$$rtentry$$0000000A
    __rt_entry_li
    __rt_entry_postsh_1
        0x08008066:    f7fffff7    ....    BL       __rt_lib_init ; 0x8008058
    .ARM.Collect$$rtentry$$0000000C
    .ARM.Collect$$rtentry$$0000000D
    __rt_entry_main
    __rt_entry_postli_1
        0x0800806a:    f000f867    ..g.    BL       main ; 0x800813c
        0x0800806e:    f000f832    ..2.    BL       exit ; 0x80080d6
    .ARM.Collect$$rtexit$$00000000
    __rt_exit
        0x08008072:    b403        ..      PUSH     {r0,r1}
    .ARM.Collect$$rtexit$$00000002
    .ARM.Collect$$rtexit$$00000003
    __rt_exit_ls
    __rt_exit_prels_1
        0x08008074:    f7fffff2    ....    BL       __rt_lib_shutdown ; 0x800805c
    .ARM.Collect$$rtexit$$00000004
    __rt_exit_exit
        0x08008078:    bc03        ..      POP      {r0,r1}
        0x0800807a:    f000f853    ..S.    BL       _sys_exit ; 0x8008124
        0x0800807e:    0000        ..      MOVS     r0,r0
    .text
    Reset_Handler
        0x08008080:    f8dfd004    ....    LDR      sp,[pc,#4] ; [0x8008088] = 0x20005000
        0x08008084:    f000f85a    ..Z.    BL       main ; 0x800813c
    $d
        0x08008088:    20005000    .P.     DCD    536891392
    $t
    .text
    __user_setup_stackheap
        0x0800808c:    4675        uF      MOV      r5,lr
        0x0800808e:    f000f82b    ..+.    BL       __user_libspace ; 0x80080e8
        0x08008092:    46ae        .F      MOV      lr,r5
        0x08008094:    0005        ..      MOVS     r5,r0
        0x08008096:    4669        iF      MOV      r1,sp
        0x08008098:    4653        SF      MOV      r3,r10
        0x0800809a:    f0200007     ...    BIC      r0,r0,#7
        0x0800809e:    4685        .F      MOV      sp,r0
        0x080080a0:    b018        ..      ADD      sp,sp,#0x60
        0x080080a2:    b520         .      PUSH     {r5,lr}
        0x080080a4:    f000f824    ..$.    BL       __user_initial_stackheap ; 0x80080f0
        0x080080a8:    e8bd4020    .. @    POP      {r5,lr}
        0x080080ac:    f04f0600    O...    MOV      r6,#0
        0x080080b0:    f04f0700    O...    MOV      r7,#0
        0x080080b4:    f04f0800    O...    MOV      r8,#0
        0x080080b8:    f04f0b00    O...    MOV      r11,#0
        0x080080bc:    f0210107    !...    BIC      r1,r1,#7
        0x080080c0:    46ac        .F      MOV      r12,r5
        0x080080c2:    e8ac09c0    ....    STM      r12!,{r6-r8,r11}
        0x080080c6:    e8ac09c0    ....    STM      r12!,{r6-r8,r11}
        0x080080ca:    e8ac09c0    ....    STM      r12!,{r6-r8,r11}
        0x080080ce:    e8ac09c0    ....    STM      r12!,{r6-r8,r11}
        0x080080d2:    468d        .F      MOV      sp,r1
        0x080080d4:    4770        pG      BX       lr
    .text
    exit
        0x080080d6:    b510        ..      PUSH     {r4,lr}
        0x080080d8:    4604        .F      MOV      r4,r0
        0x080080da:    f3af8000    ....    NOP.W   
        0x080080de:    4620         F      MOV      r0,r4
        0x080080e0:    e8bd4010    ...@    POP      {r4,lr}
        0x080080e4:    f7ffbfc5    ....    B.W      __rt_exit ; 0x8008072
    .text
    __user_libspace
    __user_perproc_libspace
    __user_perthread_libspace
        0x080080e8:    4800        .H      LDR      r0,[pc,#0] ; [0x80080ec] = 0x20000000
        0x080080ea:    4770        pG      BX       lr
    $d
        0x080080ec:    20000000    ...     DCD    536870912
    $t
    .text
    __user_initial_stackheap
        0x080080f0:    b500        ..      PUSH     {lr}
        0x080080f2:    f04f0016    O...    MOV      r0,#0x16
        0x080080f6:    b085        ..      SUB      sp,sp,#0x14
        0x080080f8:    4669        iF      MOV      r1,sp
        0x080080fa:    aa01        ..      ADD      r2,sp,#4
        0x080080fc:    600a        .`      STR      r2,[r1,#0]
        0x080080fe:    beab        ..      BKPT     #0xab
        0x08008100:    9801        ..      LDR      r0,[sp,#4]
        0x08008102:    2800        .(      CMP      r0,#0
        0x08008104:    bf02        ..      ITTT     EQ
        0x08008106:    4805        .H      LDREQ    r0,_RW_Limit ; [0x800811c] = 0x20000060
        0x08008108:    1dc0        ..      ADDEQ    r0,r0,#7
        0x0800810a:    f0200007     ...    BICEQ    r0,r0,#7
        0x0800810e:    9903        ..      LDR      r1,[sp,#0xc]
        0x08008110:    9a02        ..      LDR      r2,[sp,#8]
        0x08008112:    9b04        ..      LDR      r3,[sp,#0x10]
        0x08008114:    b005        ..      ADD      sp,sp,#0x14
        0x08008116:    bd00        ..      POP      {pc}
    $d
        0x08008118:    00000009    ....    DCD    9
    _RW_Limit
        0x0800811c:    20000060    `..     DCD    536871008
    $t
    .text
    __I$use$semihosting
    __use_no_semihosting_swi
        0x08008120:    4770        pG      BX       lr
    .text
    __semihosting_library_function
        0x08008122:    0000        ..      MOVS     r0,r0
    .text
    _sys_exit
        0x08008124:    4901        .I      LDR      r1,[pc,#4] ; [0x800812c] = 0x20026
        0x08008126:    2018        .       MOVS     r0,#0x18
        0x08008128:    beab        ..      BKPT     #0xab
        0x0800812a:    e7fe        ..      B        0x800812a ; _sys_exit + 6
    $d
        0x0800812c:    00020026    &...    DCD    131110
    $t
    i.delay
    delay
        0x08008130:    bf00        ..      NOP      
        0x08008132:    1e01        ..      SUBS     r1,r0,#0
        0x08008134:    f1a00001    ....    SUB      r0,r0,#1
        0x08008138:    d1fb        ..      BNE      0x8008132 ; delay + 2
        0x0800813a:    4770        pG      BX       lr
    i.main
    main
        0x0800813c:    2441        A$      MOVS     r4,#0x41
        0x0800813e:    e00a        ..      B        0x8008156 ; main + 26
        0x08008140:    4620         F      MOV      r0,r4
        0x08008142:    1c62        b.      ADDS     r2,r4,#1
        0x08008144:    b2d4        ..      UXTB     r4,r2
        0x08008146:    f000f809    ....    BL       putchar ; 0x800815c
        0x0800814a:    4803        .H      LDR      r0,[pc,#12] ; [0x8008158] = 0xf4240
        0x0800814c:    f7fffff0    ....    BL       delay ; 0x8008130
        0x08008150:    2c5a        Z,      CMP      r4,#0x5a
        0x08008152:    d100        ..      BNE      0x8008156 ; main + 26
        0x08008154:    2441        A$      MOVS     r4,#0x41
        0x08008156:    e7f3        ..      B        0x8008140 ; main + 4
    $d
        0x08008158:    000f4240    @B..    DCD    1000000
    $t
    i.putchar
    putchar
        0x0800815c:    4904        .I      LDR      r1,[pc,#16] ; [0x8008170] = 0x40013800
        0x0800815e:    bf00        ..      NOP      
        0x08008160:    680a        .h      LDR      r2,[r1,#0]
        0x08008162:    f0020280    ....    AND      r2,r2,#0x80
        0x08008166:    2a00        .*      CMP      r2,#0
        0x08008168:    d0fa        ..      BEQ      0x8008160 ; putchar + 4
        0x0800816a:    6048        H`      STR      r0,[r1,#4]
        0x0800816c:    4770        pG      BX       lr
    $d
        0x0800816e:    0000        ..      DCW    0
        0x08008170:    40013800    .8.@    DCD    1073821696
    $d.realdata
    Region$$Table$$Base
        0x08008174:    08008184    ....    DCD    134250884
        0x08008178:    20000000    ...     DCD    536870912
        0x0800817c:    00000060    `...    DCD    96
        0x08008180:    0800803c    <...    DCD    134250556
    Region$$Table$$Limit

** Section #2 'ER_ZI' (SHT_NOBITS) [SHF_ALLOC + SHF_WRITE]
    Size   : 96 bytes (alignment 4)
    Address: 0x20000000


** Section #3 '.debug_abbrev' (SHT_PROGBITS)
    Size   : 1476 bytes


** Section #4 '.debug_frame' (SHT_PROGBITS)
    Size   : 508 bytes


** Section #5 '.debug_info' (SHT_PROGBITS)
    Size   : 1216 bytes


** Section #6 '.debug_line' (SHT_PROGBITS)
    Size   : 332 bytes


** Section #7 '.debug_loc' (SHT_PROGBITS)
    Size   : 100 bytes


** Section #8 '.debug_macinfo' (SHT_PROGBITS)
    Size   : 44 bytes


** Section #9 '.debug_pubnames' (SHT_PROGBITS)
    Size   : 85 bytes


** Section #10 '.symtab' (SHT_SYMTAB)
    Size   : 3120 bytes (alignment 4)
    String table #11 '.strtab'
    Last local symbol no. 124


** Section #11 '.strtab' (SHT_STRTAB)
    Size   : 3344 bytes


** Section #12 '.note' (SHT_NOTE)
    Size   : 24 bytes (alignment 4)


** Section #13 '.comment' (SHT_PROGBITS)
    Size   : 1760 bytes


** Section #14 '.shstrtab' (SHT_STRTAB)
    Size   : 156 bytes




app的main.c使用mymain时的反汇编:

========================================================================

** ELF Header Information

    File Name: Objects\led_c.axf

    Machine class: ELFCLASS32 (32-bit)
    Data encoding: ELFDATA2LSB (Little endian)
    Header version: EV_CURRENT (Current version)
    Operating System ABI: none
    ABI Version: 0
    File Type: ET_EXEC (Executable) (2)
    Machine: EM_ARM (ARM)

    Image Entry point: 0x08008001
    Flags: EF_ARM_HASENTRY + EF_ARM_ABI_FLOAT_SOFT (0x05000202)

    ARM ELF revision: 5 (ABI version 2)

    Conforms to Soft float procedure-call standard

    Built with
    Component: ARM Compiler 5.06 update 7 (build 960) Tool: armasm [4d35fa]
    Component: ARM Compiler 5.06 update 7 (build 960) Tool: armlink [4d3601]

    Header size: 52 bytes (0x34)
    Program header entry size: 32 bytes (0x20)
    Section header entry size: 40 bytes (0x28)

    Program header entries: 1
    Section header entries: 14

    Program header offset: 5868 (0x000016ec)
    Section header offset: 5900 (0x0000170c)

    Section header string table index: 13

========================================================================

** Program header #0 (PT_LOAD) [PF_X + PF_R + PF_ARM_ENTRY]
    Size : 80 bytes
    Virtual address: 0x08008000 (Alignment 4)


========================================================================

** Section #1 'ER_RO' (SHT_PROGBITS) [SHF_ALLOC + SHF_EXECINSTR]
    Size   : 80 bytes (alignment 4)
    Address: 0x08008000

    $t
    .text
    Reset_Handler
        0x08008000:    f8dfd004    ....    LDR      sp,[pc,#4] ; [0x8008008] = 0x20005000
        0x08008004:    f000f808    ....    BL       mymain ; 0x8008018
    $d
        0x08008008:    20005000    .P.     DCD    536891392
    $t
    i.delay
    delay
        0x0800800c:    bf00        ..      NOP      
        0x0800800e:    1e01        ..      SUBS     r1,r0,#0
        0x08008010:    f1a00001    ....    SUB      r0,r0,#1
        0x08008014:    d1fb        ..      BNE      0x800800e ; delay + 2
        0x08008016:    4770        pG      BX       lr
    i.mymain
    mymain
        0x08008018:    2441        A$      MOVS     r4,#0x41
        0x0800801a:    e00a        ..      B        0x8008032 ; mymain + 26
        0x0800801c:    4620         F      MOV      r0,r4
        0x0800801e:    1c62        b.      ADDS     r2,r4,#1
        0x08008020:    b2d4        ..      UXTB     r4,r2
        0x08008022:    f000f809    ....    BL       putchar ; 0x8008038
        0x08008026:    4803        .H      LDR      r0,[pc,#12] ; [0x8008034] = 0xf4240
        0x08008028:    f7fffff0    ....    BL       delay ; 0x800800c
        0x0800802c:    2c5a        Z,      CMP      r4,#0x5a
        0x0800802e:    d100        ..      BNE      0x8008032 ; mymain + 26
        0x08008030:    2441        A$      MOVS     r4,#0x41
        0x08008032:    e7f3        ..      B        0x800801c ; mymain + 4
    $d
        0x08008034:    000f4240    @B..    DCD    1000000
    $t
    i.putchar
    putchar
        0x08008038:    4904        .I      LDR      r1,[pc,#16] ; [0x800804c] = 0x40013800
        0x0800803a:    bf00        ..      NOP      
        0x0800803c:    680a        .h      LDR      r2,[r1,#0]
        0x0800803e:    f0020280    ....    AND      r2,r2,#0x80
        0x08008042:    2a00        .*      CMP      r2,#0
        0x08008044:    d0fa        ..      BEQ      0x800803c ; putchar + 4
        0x08008046:    6048        H`      STR      r0,[r1,#4]
        0x08008048:    4770        pG      BX       lr
    $d
        0x0800804a:    0000        ..      DCW    0
        0x0800804c:    40013800    .8.@    DCD    1073821696

** Section #2 '.debug_abbrev' (SHT_PROGBITS)
    Size   : 1476 bytes


** Section #3 '.debug_frame' (SHT_PROGBITS)
    Size   : 224 bytes


** Section #4 '.debug_info' (SHT_PROGBITS)
    Size   : 1220 bytes


** Section #5 '.debug_line' (SHT_PROGBITS)
    Size   : 332 bytes


** Section #6 '.debug_loc' (SHT_PROGBITS)
    Size   : 100 bytes


** Section #7 '.debug_macinfo' (SHT_PROGBITS)
    Size   : 44 bytes


** Section #8 '.debug_pubnames' (SHT_PROGBITS)
    Size   : 87 bytes


** Section #9 '.symtab' (SHT_SYMTAB)
    Size   : 304 bytes (alignment 4)
    String table #10 '.strtab'
    Last local symbol no. 13


** Section #10 '.strtab' (SHT_STRTAB)
    Size   : 248 bytes


** Section #11 '.note' (SHT_NOTE)
    Size   : 20 bytes (alignment 4)


** Section #12 '.comment' (SHT_PROGBITS)
    Size   : 1524 bytes


** Section #13 '.shstrtab' (SHT_STRTAB)
    Size   : 156 bytes




可以看到使用mymain时反汇编会‘干净’很多。

app工程中uart.c、uart.h和bootloader中uart.c、uart.h内容是一样的。

uart.c:

#include "uart.h"

typedef unsigned int uint32_t;
typedef struct
{
  volatile uint32_t SR;    /*!< USART Status register, Address offset: 0x00 */
  volatile uint32_t DR;    /*!< USART Data register,   Address offset: 0x04 */
  volatile uint32_t BRR;   /*!< USART Baud rate register, Address offset: 0x08 */
  volatile uint32_t CR1;   /*!< USART Control register 1, Address offset: 0x0C */
  volatile uint32_t CR2;   /*!< USART Control register 2, Address offset: 0x10 */
  volatile uint32_t CR3;   /*!< USART Control register 3, Address offset: 0x14 */
  volatile uint32_t GTPR;  /*!< USART Guard time and prescaler register, Address offset: 0x18 */
} USART_TypeDef;


void uart_init(void)
{
        USART_TypeDef *usart1 = (USART_TypeDef *)0x40013800; //USART1寄存器地址
        volatile unsigned int *pReg;
        /* 使能GPIOA/USART1模块 */
        /* RCC_APB2ENR */
        pReg = (volatile unsigned int *)(0x40021000 + 0x18); //RRC寄存器地址
        *pReg |= (1<<2) | (1<<14); //使能APB2总线上USART1和GPIOA
       
        /* 配置引脚功能: PA9(USART1_TX), PA10(USART1_RX)
         * GPIOA_CRH = 0x40010800 + 0x04
         */
        pReg = (volatile unsigned int *)(0x40010800 + 0x04);
       
        /* PA9(USART1_TX) */
        *pReg &= ~((3<<4) | (3<<6)); //把GPIOA_CRH寄存器中MODE9和CNF9清零
        *pReg |= (1<<4) | (2<<6); //配置为复用功能推挽输出,最大输出速度为10MHz

        /* PA10(USART1_RX) */
        *pReg &= ~((3<<8) | (3<<10)); //把GPIOA_CRH寄存器中MODE10和CNF10清零
        *pReg |= (0<<8) | (1<<10);  //配置为浮空输入
       
        /* 设置波特率
         * 115200 = 8000000/16/USARTDIV
         * USARTDIV = 4.34
         * DIV_Mantissa = 4
         * DIV_Fraction / 16 = 0.34
         * DIV_Fraction = 16*0.34 = 5
         * 真实波特率:
         * DIV_Fraction / 16 = 5/16=0.3125
         * USARTDIV = DIV_Mantissa + DIV_Fraction / 16 = 4.3125
         * baudrate = 8000000/16/4.3125 = 115942
         */
#define DIV_Mantissa 4
#define DIV_Fraction 5
        usart1->BRR = (DIV_Mantissa<<4) | (DIV_Fraction);
       
        /* 设置数据格式: 8n1 */
        usart1->CR1 = (1<<13) | (0<<12) | (0<<10) | (1<<3) | (1<<2);       
        usart1->CR2 &= ~(3<<12);
       
        /* 使能USART1 */
}
       
int getchar(void)
{
        USART_TypeDef *usart1 = (USART_TypeDef *)0x40013800;
        while ((usart1->SR & (1<<5)) == 0);
        return usart1->DR;
}

int putchar(char c)
{
        USART_TypeDef *usart1 = (USART_TypeDef *)0x40013800;
        while ((usart1->SR & (1<<7)) == 0);
        usart1->DR = c;
       
        return c;
}

void putstr(char *str)
{
        while (*str)
        {
                putchar(*str);
                str++;
        }
}



uart.h:

#ifndef _UART_H
#define _UART_H
void uart_init(void);
int getchar(void);
int putchar(char c);
void putstr(char *str);
#endif


3.2 程序烧录
根据STM32F103C8T6 (Flash 64K RAM 20K),64K = 0x10000,32K = 0x8000,20K = 0x5000。Flash的起始地址是0x8000000,RAM的起始地址是0x20000000。

希望将Flash平静分成2部分,前半部分烧录bootloader,后半部分烧录app。因此:

bootloader的起始地址是0x8000000,size是0x8000,RAM起始地址是0x20000000,size是0x5000(为什么RAM的size不是0x2800也可以,暂时没有深究)。
app的起始地址是0x8008000,size是0x8000,RAM起始地址是0x20000000,size是0x5000









烧录之后复位MCU:



从bootloader工程跳转到app工程成功!

4. 示例2-APP有异常向量
4.1 源码
bootloader工程中只有4个文件,start.s、main.c、uart.c、uart.h,其中只有main.c文件和示例1-APP无异常向量不同。

main.c:

#include "uart.h"

void delay(int d)
{
        while(d--);
}

int main()
{
        unsigned int *p = (unsigned int *)0x08008004;
        unsigned int val = *p;  /* 从app的异常向量表中获取第二项跳转,因为app的Reset_Handler地址是0x08008004,取出该地址的函数并执行 */
       
        void (*app)(void);
       
        uart_init();

        putstr("bootloader\r\n");
       
        /* start app */
        app = (void (*)(void))val;  
       
        app();
       
        return 0;
}



app工程也有4个文件,start.s、main.c、uart.c、uart.h,其中uart.c、uart.h文件和示例1-APP无异常向量相同。

start.s:

                PRESERVE8 ;伪指令,用于指示编译器在接下来的代码中保留8位汇编语法。
                THUMB ;伪指令,用于指示编译器接下来的代码将以Thumb模式编译。Thumb模式是ARM架构的一种低功耗模式,使用16位指令集。

;向量表通常会放置在内存的最低地址处(通常是0x00000000),并在复位时指向复位处理程序。
; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY
                                EXPORT  __Vectors

; DCD是一个伪指令,用于定义一个32位的常量(双字)。如果这段代码被启用,它将定义一个向量表,第一个条目为0(初始堆栈指针),第二个条目指向Reset_Handler。
__Vectors       DCD     0                  
                DCD     Reset_Handler              ; Reset Handler


; AREA定义了一个新的段(section),.text表示代码段,CODE表示该段包含可执行代码,READONLY表示该段是只读的。
                                AREA    |.text|, CODE, READONLY

; Reset_Handler是复位处理程序的入口点。
; EXPORT声明了Reset_Handler为全局可见,[WEAK]表示这是一个弱符号,如果没有其他定义,这个定义将被使用。
; IMPORT声明了一个外部符号mymain,表示mymain是一个外部函数,将在链接时被解析。
; LDRSP,=(0x20000000+0x5000):这条指令设置了堆栈指针(SP),指向内存地址0x20000000+0x5000,即0x200005000。这通常是一个RAM区域,用于存储栈数据。
; BLmymain:这条指令通过跳转(BranchandLink)调用mymain函数,mymain函数应该是应用程序的入口点。
; ENDP结束了Reset_Handler过程的定义。

; Reset handler
Reset_Handler   PROC
                                EXPORT  Reset_Handler             [WEAK]
                IMPORT  main

                                LDR SP, =(0x20000000+0x5000)  ; 0x08008000
                                BL main

                ENDP

; END指令表示整个汇编文件的结束
                 END



main.c:

#include "uart.h"

void delay(int d)
{
        while(d--);
}
/* 循环打印字母A-Z */
int main()
{
        char c = 'A';
       
        while (1)
        {
                putchar(c++);
                delay(1000000);
                if (c == 'Z')
                        c = 'A';
        }
       
        return 0;
}
]


示例2-APP有异常向量与示例1-APP无异常向量对比差异:








4.2 程序烧录
程序烧录配置和示例1-APP无异常向量也相同。



从bootloader工程跳转到app工程成功!

5. 示例3-使用汇编跳转
5.1 源码
bootloader工程中只有4个文件,start.s、main.c、uart.c、uart.h,其中main.c、start.s文件和示例1-APP无异常向量不同。

main.c

#include "uart.h"

/* 导入start_app函数 */
extern void start_app(unsigned int sp, unsigned int pc);

void delay(int d)
{
        while(d--);
}

int main()
{
        unsigned int *p = (unsigned int *)0x08008000;
        unsigned int sp = *p;  /* 将app地址的值赋给SP */
        unsigned int pc = *(p+1);  /* 将Reset_Handler赋给PC */
       
       
        uart_init();

        putstr("bootloader\r\n");
       
        /* start app */
        start_app(sp, pc); /* r0=sp, r1=pc  */
       
        return 0;
}



start.s:

                PRESERVE8 ;伪指令,用于指示编译器在接下来的代码中保留8位汇编语法。
                THUMB ;伪指令,用于指示编译器接下来的代码将以Thumb模式编译。Thumb模式是ARM架构的一种低功耗模式,使用16位指令集。

;向量表通常会放置在内存的最低地址处(通常是0x00000000),并在复位时指向复位处理程序。
; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY
                                EXPORT  __Vectors

; DCD是一个伪指令,用于定义一个32位的常量(双字)。如果这段代码被启用,它将定义一个向量表,第一个条目为0(初始堆栈指针),第二个条目指向Reset_Handler。
__Vectors       DCD     0                  
                DCD     Reset_Handler              ; Reset Handler


; AREA定义了一个新的段(section),.text表示代码段,CODE表示该段包含可执行代码,READONLY表示该段是只读的。
                                AREA    |.text|, CODE, READONLY

; Reset_Handler是复位处理程序的入口点。
; EXPORT声明了Reset_Handler为全局可见,[WEAK]表示这是一个弱符号,如果没有其他定义,这个定义将被使用。
; IMPORT声明了一个外部符号mymain,表示mymain是一个外部函数,将在链接时被解析。
; LDRSP,=(0x20000000+0x5000):这条指令设置了堆栈指针(SP),指向内存地址0x20000000+0x5000,即0x200005000。这通常是一个RAM区域,用于存储栈数据。
; BLmymain:这条指令通过跳转(BranchandLink)调用mymain函数,mymain函数应该是应用程序的入口点。
; ENDP结束了Reset_Handler过程的定义。

; Reset handler
Reset_Handler   PROC
                                EXPORT  Reset_Handler             [WEAK]
                IMPORT  main

                                LDR SP, =(0x20000000+0x5000)  ; 0x08008000
                                BL main

                ENDP

start_app   PROC
                                EXPORT  start_app ;导入start_app函数
                               
                                mov sp, r0 ;将寄存器r0的值赋给堆栈指针SP
                                BX r1 ;这是一条分支并交换指令,根据r1的内容切换到相应的处理器模式(ARM或Thumb)并跳转到r1所指向的地址,r1通常包含一个函数指针,指向要执行的代码,即app

                ENDP

; END指令表示整个汇编文件的结束
                 END



app工程也有4个文件,start.s、main.c、uart.c、uart.h,其中start.s文件和示例2-APP无异常向量不同,只是将栈SP的初始化改变了位置,即使使用示例2-APP无异常向量中的start.s也能正常执行。

start.s:

                PRESERVE8 ;伪指令,用于指示编译器在接下来的代码中保留8位汇编语法。
                THUMB ;伪指令,用于指示编译器接下来的代码将以Thumb模式编译。Thumb模式是ARM架构的一种低功耗模式,使用16位指令集。

;向量表通常会放置在内存的最低地址处(通常是0x00000000),并在复位时指向复位处理程序。
; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY
                                EXPORT  __Vectors

; DCD是一个伪指令,用于定义一个32位的常量(双字)。如果这段代码被启用,它将定义一个向量表,第一个条目为0x20000000+0x5000(初始堆栈指针),第二个条目指向Reset_Handler。
__Vectors       DCD     0x20000000+0x5000
                DCD     Reset_Handler              ; Reset Handler


; AREA定义了一个新的段(section),.text表示代码段,CODE表示该段包含可执行代码,READONLY表示该段是只读的。
                                AREA    |.text|, CODE, READONLY

; Reset_Handler是复位处理程序的入口点。
; EXPORT声明了Reset_Handler为全局可见,[WEAK]表示这是一个弱符号,如果没有其他定义,这个定义将被使用。
; IMPORT声明了一个外部符号mymain,表示mymain是一个外部函数,将在链接时被解析。
; LDRSP,=(0x20000000+0x5000):这条指令设置了堆栈指针(SP),指向内存地址0x20000000+0x5000,即0x200005000。这通常是一个RAM区域,用于存储栈数据。
; BLmymain:这条指令通过跳转(BranchandLink)调用mymain函数,mymain函数应该是应用程序的入口点。
; ENDP结束了Reset_Handler过程的定义。

; Reset handler
Reset_Handler   PROC
                                EXPORT  Reset_Handler             [WEAK]
                IMPORT  main

                                ;LDR SP, =(0x20000000+0x5000)  ; 0x08008000
                                BL main

                ENDP

; END指令表示整个汇编文件的结束
                 END



示例3-使用汇编跳转与示例2-APP有异常向量对比差异:







5.2 程序烧录
程序烧录配置和示例1-APP无异常向量也相同。



从bootloader工程跳转到app工程成功!

6. 示例4-重定位vector
6.1 源码
在bootloader中设置了异常向量,在跳转到应用工程中时,重新设置异常向量。

重定位Vector使用bootloader启动一个较为复杂的工程(包含FreeRTOS),bootloader工程与示例3-使用汇编跳转中main.c和start.s文件不同,uart.c和uart.h相同。

main.c:

#include "uart.h"

extern void start_app(unsigned int new_vector);

void delay(int d)
{
        while(d--);
}

int mymain()
{
        unsigned int new_vector = 0x08008000; //获取app工程的起始地址
       
        uart_init();

        putstr("bootloader\r\n");
       
        /* start app */
        start_app(new_vector); //在汇编中重定位vector
       
        return 0;
}



start.s:

                PRESERVE8 ;伪指令,用于指示编译器在接下来的代码中保留8位汇编语法。
                THUMB ;伪指令,用于指示编译器接下来的代码将以Thumb模式编译。Thumb模式是ARM架构的一种低功耗模式,使用16位指令集。

;向量表通常会放置在内存的最低地址处(通常是0x00000000),并在复位时指向复位处理程序。
; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY
                                EXPORT  __Vectors

; DCD是一个伪指令,用于定义一个32位的常量(双字)。如果这段代码被启用,它将定义一个向量表,第一个条目为0(初始堆栈指针),第二个条目指向Reset_Handler。
__Vectors       DCD     0                  
                DCD     Reset_Handler              ; Reset Handler


; AREA定义了一个新的段(section),.text表示代码段,CODE表示该段包含可执行代码,READONLY表示该段是只读的。
                                AREA    |.text|, CODE, READONLY

; Reset_Handler是复位处理程序的入口点。
; EXPORT声明了Reset_Handler为全局可见,[WEAK]表示这是一个弱符号,如果没有其他定义,这个定义将被使用。
; IMPORT声明了一个外部符号mymain,表示mymain是一个外部函数,将在链接时被解析。
; LDRSP,=(0x20000000+0x5000):这条指令设置了堆栈指针(SP),指向内存地址0x20000000+0x5000,即0x200005000。这通常是一个RAM区域,用于存储栈数据。
; BLmymain:这条指令通过跳转(BranchandLink)调用mymain函数,mymain函数应该是应用程序的入口点。
; ENDP结束了Reset_Handler过程的定义。

; Reset handler
Reset_Handler   PROC
                                EXPORT  Reset_Handler             [WEAK]
                IMPORT  main

                                LDR SP, =(0x20000000+0x5000)  ; 0x08008000
                                BL main

                ENDP

start_app   PROC
                                EXPORT  start_app ;导入start_app函数
                               
                                ; 设置vector基地址为0x08008000,可参考《Cortex_M3权威指南》
                                ldr r3, =0xE000ED08
                                str r0, [r3]           ; 将r0的内容存储到VTOR寄存器中,r0=0x08008000
                               
                                ldr sp, [r0]      ;初始化栈,从r0读取数据并将其设置为栈指针
                                ldr r1, [r0, #4]  ; 读取0x08008004地址的数据,这个地址是新向量表中的复位处理程序地址
                               
                                BX r1 ;这是一条分支并交换指令,根据r1的内容切换到相应的处理器模式(ARM或Thumb)并跳转到r1所指向的地址,r1通常包含一个函数指针,指向要执行的代码,即app

                ENDP

; END指令表示整个汇编文件的结束
                 END



app工程是一个带有FreeRTOS的工程,在启动时打印"Hello World!",然后在task中累加打印StartDefaultTask[n],源码参考附件。

示例4-重定位vector与示例3-使用汇编跳转对比差异:





6.2 程序烧录
程序烧录配置和示例1-APP无异常向量也相同。



从bootloader工程跳转到app工程成功!

7. 示例5-APP自我复制
7.1 源码
从bootloader跳转到app工程后,app将自己的代码复制到内存中运行。

示例5-APP自我复制的bootloader工程代码与示例4-重定位vector的bootloader工程完全相同。

app工程中main.c和start.s文件与示例3-使用汇编跳转不同,uart.c和uart.h相同。

main.c:

#include "uart.h"

static char buf[100] = "www.100ask.net";

void copy_myself(int *from, int *to, int len)
{
        // 从哪里到哪里, 多长 ?
        int i;
        for (i = 0; i < len/4+1; i++)
        {
                to = from;
        }
}

void delay(int d)
{
        while(d--);
}

int mymain()
{
        char c = 'A';
       
        int (*fp)(char c);
       
        fp = putchar;
       
        putstr(buf);

        while (1)
        {
                fp(c++);
                putchar(c++);
                delay(1000000);
                if (c == 'Z')
                        c = 'A';
        }
       
        return 0;
}



start.s:

                PRESERVE8 ;伪指令,用于指示编译器在接下来的代码中保留8位汇编语法。
                THUMB ;伪指令,用于指示编译器接下来的代码将以Thumb模式编译。Thumb模式是ARM架构的一种低功耗模式,使用16位指令集。

;向量表通常会放置在内存的最低地址处(通常是0x00000000),并在复位时指向复位处理程序。
; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY
                                EXPORT  __Vectors

; DCD是一个伪指令,用于定义一个32位的常量(双字)。如果这段代码被启用,它将定义一个向量表,第一个条目为0x20000000+0x5000(初始堆栈指针),第二个条目指向Reset_Handler,指令设置复位处理程序的地址为0x08008008。
__Vectors       DCD     0x20000000+0x5000               
                DCD     0x08008009 ;Reset_Handler              ; Reset Handler

                                AREA    |.text|, CODE, READONLY                                   ;定义一个只读代码段,名为 .text

; Reset handler
Reset_Handler   PROC
                                EXPORT  Reset_Handler             [WEAK]           ;导出 Reset_Handler 符号,并标记为弱符号,允许其他地方重定义
                IMPORT  mymain                                                                   ;导入外部符号 mymain, copy_myself 和 |Image$$ER_IROM1$$Length|
                                IMPORT  copy_myself
                                IMPORT |Image$$ER_IROM1$$Length|
                               
                                adr r0, Reset_Handler  ; r0=0x08008000
                                bic r0, r0, #0xff           ; 清除低8位,得到0x08008000
                               
                                ldr r1, =__Vectors ; r1=0x20000000
                                ldr r2, = |Image$$ER_IROM1$$Length|  ; LENGTH
                               
                                BL copy_myself        ; 调用 copy_myself 函数,实现APP自我复制程序到内存中

                                ;LDR SP, =(0x20000000+0x5000)
                                ;BL mymain
                                ldr pc, =mymain

                ENDP

                 END




散列文件app.sct:

; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************

LR_IROM1 0x20000000 0x5000  {    ; load region size_region 加载地址,即程序将被加载到的目标内存地址。
  ER_IROM1 0x20000000 0x5000  {  ; load address = execution address 执行地址,即程序实际运行时的地址。这里与加载地址相同,表示代码将在加载地址处直接执行。
   *.o (RESET, +First)
   .ANY (+RO)
   .ANY (+XO)
   .ANY (+RW +ZI)
  }
}



示例5-APP自我复制与示例3-使用汇编跳转对比差异:





7.2 程序烧录
Keil中程序烧录配置和示例1-APP无异常向量也相同。

bootloader工程固件可以直接使用Keil中的STLink烧录到STM32中,app工程固件使用Keil中的STLink烧录时会报错,需要使用STM32CubeProgrammer工具(或者其他烧录工具)使用STLink烧录到指定的0x08008000地址上,keil中需要加载散列文件:








从bootloader工程跳转到app工程成功!

8. 示例6-BootLoader根据头部信息复制APP
bootloader把app代码复制到内存中运行,比如芯片Flash太小,外挂一个非XIP类型的Flash存放app代码时,就需要bootloader帮忙把app的代码复制到内存中运行。

原理:

使用uboot中的mkimage.exe工具把app工程编译生成的app.bin文件增加一个头部生成app_with_uboot_header.bin文件
然后把app_with_uboot_header.bin文件通过STM32CubeProgrammer(或其他烧录工具)烧录到Flash的0x08008000地址上
bootloader读取0x08008000头部结构体中的加载地址和代码大小,把app代码复制到内存中
bootloader复制完app代码到内存后跳转到app代码运行
头部格式:

typedef unsigned int __be32;
typedef                unsigned char                uint8_t;

#define IH_MAGIC        0x27051956        /* Image Magic Number                */
#define IH_NMLEN        32        /* Image Name Length                */

typedef struct image_header {
        __be32                ih_magic;        /* Image Header Magic Number        */
        __be32                ih_hcrc;        /* Image Header CRC Checksum        */
        __be32                ih_time;        /* Image Creation Timestamp        */
        __be32                ih_size;        /* Image Data Size                */
        __be32                ih_load;        /* Data         Load  Address                */
        __be32                ih_ep;                /* Entry Point Address                */
        __be32                ih_dcrc;        /* Image Data CRC Checksum        */
        uint8_t                ih_os;                /* Operating System                */
        uint8_t                ih_arch;        /* CPU architecture                */
        uint8_t                ih_type;        /* Image Type                        */
        uint8_t                ih_comp;        /* Compression Type                */
        uint8_t                ih_name[IH_NMLEN];        /* Image Name                */
} image_header_t;


需要用到的ih_load(app的加载地址)和ih_size(app的代码大小)

mkimage.exe工具可以在gitbash中使用,例如将app.bin增加头部生成app_with_uboot_header.bin:

./mkimage.exe -n "stm32F103_app" -a 0x20000000 -e 0x20000008 -d app.bin app_with_uboot_header.bin
1
mkimage.exe用法:

$ ./mkimage.exe
Error: Missing output filename
Usage: /Users/Administrator/Desktop/1/mkimage -l image
          -l ==> list image header information
       /Users/Administrator/Desktop/1/mkimage [-x] -A arch -O os -T type -C comp -a addr -e ep -n name -d data_file[:data_file...] image
          -A ==> set architecture to 'arch'
          -O ==> set operating system to 'os'
          -T ==> set image type to 'type'
          -C ==> set compression type 'comp'
          -a ==> set load address to 'addr' (hex)
          -e ==> set entry point to 'ep' (hex)
          -n ==> set image name to 'name'
          -d ==> use image data from 'datafile'
          -x ==> set XIP (execute in place)
       /Users/Administrator/Desktop/1/mkimage [-D dtc_options] [-f fit-image.its|-f auto|-F] [-b <dtb> [-b <dtb>]] [-i <ramdisk.cpio.gz>] fit-image
           <dtb> file is used with -f auto, it may occur multiple times.
          -D => set all options for device tree compiler
          -f => input filename for FIT source
          -i => input filename for ramdisk file
Signing / verified boot not supported (CONFIG_FIT_SIGNATURE undefined)
       /Users/Administrator/Desktop/1/mkimage -V ==> print version information and exit
Use '-T list' to see a list of available image types





8.1 源码
示例6-BootLoader根据头部信息复制APP的app工程代码与示例5-APP自我复制的app工程相比仅屏蔽了start.s中复制app代码到内存的部分,main.c内容不变。

start.s:

                PRESERVE8 ;伪指令,用于指示编译器在接下来的代码中保留8位汇编语法。
                THUMB ;伪指令,用于指示编译器接下来的代码将以Thumb模式编译。Thumb模式是ARM架构的一种低功耗模式,使用16位指令集。

;向量表通常会放置在内存的最低地址处(通常是0x00000000),并在复位时指向复位处理程序。
; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY
                                EXPORT  __Vectors

; DCD是一个伪指令,用于定义一个32位的常量(双字)。如果这段代码被启用,它将定义一个向量表,第一个条目为0x20000000+0x5000(初始堆栈指针),第二个条目指向Reset_Handler,指令设置复位处理程序的地址为0x08008008。
__Vectors       DCD     0x20000000+0x5000               
                DCD     Reset_Handler              ; Reset Handler

                                AREA    |.text|, CODE, READONLY                                   ;定义一个只读代码段,名为 .text

; Reset handler
Reset_Handler   PROC
                                EXPORT  Reset_Handler             [WEAK]           ;导出 Reset_Handler 符号,并标记为弱符号,允许其他地方重定义
                IMPORT  mymain                                                                   ;导入外部符号 mymain, copy_myself 和 |Image$$ER_IROM1$$Length|
                                ;IMPORT  copy_myself
                                ;IMPORT |Image$$ER_IROM1$$Length|
                               
                                ;adr r0, Reset_Handler  ; r0=0x08008000
                                ;bic r0, r0, #0xff           ; 清除低8位,得到0x08008000
                               
                                ;ldr r1, =__Vectors ; r1=0x20000000
                                ;ldr r2, = |Image$$ER_IROM1$$Length|  ; LENGTH
                               
                                ;BL copy_myself        ; 调用 copy_myself 函数,实现APP自我复制程序到内存中

                                ;LDR SP, =(0x20000000+0x5000)
                                ;BL mymain
                                ldr pc, =mymain

                ENDP

                 END




主要修改在bootloader工程,除了start.s和main.c的修改外,在uart.c和uart.h中还增加了1个打印16进制数据的函数,用于打印地址。

uart.c中增加的内容:

const unsigned char hex_tab[]={'0','1','2','3','4','5','6','7',\
                                 '8','9','a','b','c','d','e','f'};

void puthex(unsigned int val)
{
        /* val: 0x12345678 */
        int i;
        int index;
        putstr("0x");
        for (i = 7; i >=0 ; i--)
        {
                index = (val >> (i*4)) & 0xf;
                putchar(hex_tab[index]);
        }
}


uart.h中增加的内容:

void puthex(unsigned int val);


main.c:

#include "uart.h"

typedef unsigned int __be32;
typedef                unsigned char                uint8_t;

#define IH_MAGIC        0x27051956        /* Image Magic Number                */
#define IH_NMLEN        32        /* Image Name Length                */

typedef struct image_header {
        __be32                ih_magic;        /* Image Header Magic Number        */
        __be32                ih_hcrc;        /* Image Header CRC Checksum        */
        __be32                ih_time;        /* Image Creation Timestamp        */
        __be32                ih_size;        /* Image Data Size                */
        __be32                ih_load;        /* Data         Load  Address                */
        __be32                ih_ep;                /* Entry Point Address                */
        __be32                ih_dcrc;        /* Image Data CRC Checksum        */
        uint8_t                ih_os;                /* Operating System                */
        uint8_t                ih_arch;        /* CPU architecture                */
        uint8_t                ih_type;        /* Image Type                        */
        uint8_t                ih_comp;        /* Compression Type                */
        uint8_t                ih_name[IH_NMLEN];        /* Image Name                */
} image_header_t;

unsigned int be32_to_cpu(unsigned int x)
{
        unsigned char *p = (unsigned char *)&x;
        unsigned int le;
        le = (p[0] << 24) + (p[1] << 16) + (p[2] << 8) + (p[3]);
        return le;
}

extern void start_app(unsigned int new_vector);

void delay(int d)
{
        while(d--);
}

void copy_app(int *from, int *to, int len)
{
        // 从哪里到哪里, 多长 ?
        int i;
        for (i = 0; i < len/4+1; i++)
        {
                to = from;
        }
}


void relocate_and_start_app(unsigned int pos)
{
        image_header_t *head;
        unsigned int load;
        unsigned int size;
        unsigned int new_pos = pos+sizeof(image_header_t);
       
        /* 读出头部 */
        head = (image_header_t *)pos;
       
        /* 解析头部 */
        load = be32_to_cpu(head->ih_load);
        size = be32_to_cpu(head->ih_size);
       
        putstr("load = ");
        puthex(load);
        putstr("\r\n");

        putstr("size = ");
        puthex(size);
        putstr("\r\n");
               
        /* 把程序复制到RAM */
        copy_app((int *)new_pos, (int *)load, size);
       
        /* 跳转执行 */
        start_app(new_pos);
}

int mymain()
{
        unsigned int app_pos = 0x08008000;       
       
        uart_init();

        putstr("bootloader\r\n");
       
        /* start app */
        relocate_and_start_app(app_pos);
       
        //start_app(new_vector);
       
        return 0;
}



start.s:

                PRESERVE8 ;伪指令,用于指示编译器在接下来的代码中保留8位汇编语法。
                THUMB ;伪指令,用于指示编译器接下来的代码将以Thumb模式编译。Thumb模式是ARM架构的一种低功耗模式,使用16位指令集。

;向量表通常会放置在内存的最低地址处(通常是0x00000000),并在复位时指向复位处理程序。
; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY
                                EXPORT  __Vectors

; DCD是一个伪指令,用于定义一个32位的常量(双字)。如果这段代码被启用,它将定义一个向量表,第一个条目为0(初始堆栈指针),第二个条目指向Reset_Handler。
__Vectors       DCD     0                  
                DCD     Reset_Handler              ; Reset Handler


; AREA定义了一个新的段(section),.text表示代码段,CODE表示该段包含可执行代码,READONLY表示该段是只读的。
                                AREA    |.text|, CODE, READONLY

; Reset_Handler是复位处理程序的入口点。
; EXPORT声明了Reset_Handler为全局可见,[WEAK]表示这是一个弱符号,如果没有其他定义,这个定义将被使用。
; IMPORT声明了一个外部符号mymain,表示mymain是一个外部函数,将在链接时被解析。
; LDRSP,=(0x20000000+0x5000):这条指令设置了堆栈指针(SP),指向内存地址0x20000000+0x5000,即0x200005000。这通常是一个RAM区域,用于存储栈数据。
; BLmymain:这条指令通过跳转(BranchandLink)调用mymain函数,mymain函数应该是应用程序的入口点。
; ENDP结束了Reset_Handler过程的定义。

; Reset handler
Reset_Handler   PROC
                                EXPORT  Reset_Handler             [WEAK]
                IMPORT  main

                                LDR SP, =(0x20000000+0x5000)  ; 0x08008000
                                BL main

                ENDP

start_app   PROC
                                EXPORT  start_app ;导入start_app函数
                               
                                ; 设置vector基地址为0x08008000,可参考《Cortex_M3权威指南》
                                ldr r3, =0xE000ED08
                                str r0, [r3]           ; 将r0的内容存储到VTOR寄存器中,r0=0x08008000
                               
                                ldr sp, [r0]      ; 从新向量表读值
                                ldr r1, [r0, #4]  ; 从新向量表+4读值
                               
                                BX r1 ;这是一条分支并交换指令,根据r1的内容切换到相应的处理器模式(ARM或Thumb)并跳转到r1所指向的地址,r1通常包含一个函数指针,指向要执行的代码,即app

                ENDP

; END指令表示整个汇编文件的结束
                 END




示例6-BootLoader根据头部信息复制APP与示例5-APP自我复制对比差异:











8.2 程序烧录
Keil中程序烧录配置和示例1-APP无异常向量也相同。

bootloader工程固件可以直接使用Keil中的STLink烧录到STM32中,app工程app.bin固件用mkimage.exe生成的app_with_uboot_header.bin固件使用Keil中的STLink烧录时会报错,需要使用STM32CubeProgrammer工具(或者其他烧录工具)使用STLink烧录到指定的0x08008000地址上,keil中需要加载散列文件:





从bootloader工程跳转到app工程成功!
————————————————

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

原文链接:https://blog.csdn.net/studyingdda/article/details/143265494

使用特权

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

本版积分规则

85

主题

4084

帖子

1

粉丝