目录

一、上电复位(Power-On Reset)

1.硬件自动行为:

2.startup.s的作用

3.底层原理

二、板级初始化(Board Initialization)

1.时钟初始化(以STM32为例):

2.GPIO初始化底层:

3.中断控制器(NVIC)初始化:

三、引导/升级系统(Bootloader)

1.应用程序跳转代码:

2.双Bank升级流程(防止变砖):

四、 RTOS初始化(以FreeRTOS为例)

1.任务栈结构:

2.PendSV中断:

五、多任务调度实例

六、终极总结:嵌入式软件启动全景图


一、上电复位(Power-On Reset)

核心问题:CPU如何找到第一条指令? 关键技术启动文件(startup.s)向量表(Vector Table)

1.硬件自动行为:

  • 上电瞬间,CPU的PC寄存器被强制设置为 复位向量地址(ARM Cortex-M是0x00000000,实际映射到Flash起始地址0x08000000)。

  • 该地址存放的是一个4字节的 栈指针初始值(SP) 和下一个4字节的 复位处理函数地址(Reset_Handler)

2.startup.s的作用

; 向量表定义
__Vectors:
    DCD     __initial_sp          ; 栈顶地址(由链接脚本定义)
    DCD     Reset_Handler         ; 复位处理函数
    DCD     NMI_Handler           ; NMI中断处理
    ...                           ; 其他中断向量
​
Reset_Handler:
    ; 1. 初始化.data段(从Flash拷贝到RAM)
    LDR r0, =_sdata     ; .data段起始地址(RAM)
    LDR r1, =_edata
    LDR r2, =_sidata    ; .data段的Flash源地址
    BL  memcpy          ; 拷贝初始化数据
​
    ; 2. 清零.bss段(未初始化全局变量)
    LDR r0, =_sbss
    LDR r1, =_ebss
    BL  zero_mem
​
    ; 3. 跳转到main()或SystemInit()
    B   SystemInit

关键操作

  • 初始化.data段:全局变量初始值存储在Flash中,需拷贝到RAM。

  • 清零.bss段:未初始化的全局变量所在内存区域清零。

  • 设置堆栈指针:从链接脚本获取__initial_sp值。

3.底层原理

  • 链接脚本(.ld文件) 定义了内存布局:

MEMORY {
    FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
    RAM (rwx)  : ORIGIN = 0x20000000, LENGTH = 128K
}
  • 编译器根据链接脚本生成_sdata_edata等符号地址。

二、板级初始化(Board Initialization)

核心问题:如何让芯片外设进入可用状态? 关键技术时钟树配置寄存器级编程

1.时钟初始化(以STM32为例):

  • 复位后时钟状态:默认使用内部HSI(8MHz),所有外设时钟关闭。

  • 配置流程

// 1. 使能外部晶振(HSE)
RCC->CR |= RCC_CR_HSEON;
while (!(RCC->CR & RCC_CR_HSERDY)); // 等待HSE就绪
​
// 2. 配置PLL(锁相环)
RCC->PLLCFGR = (RCC_PLLCFGR_PLLSRC_HSE |  // PLL输入源=HSE
                (8 << RCC_PLLCFGR_PLLM_Pos) |  // 分频系数M=8
                (336 << RCC_PLLCFGR_PLLN_Pos)); // 倍频系数N=336
​
// 3. 切换系统时钟到PLL
RCC->CFGR |= RCC_CFGR_SW_PLL;
while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL); // 等待切换完成
  • 关键点

    • 需严格遵循芯片参考手册的时钟树时序。

    • 错误配置会导致芯片“卡死”(如PLL未锁定就切换时钟)。

2.GPIO初始化底层:

  • 寄存器操作示例(配置PA5为推挽输出):

// 1. 使能GPIOA时钟
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
​
// 2. 配置PA5模式
GPIOA->MODER &= ~(3 << (5 * 2));      // 清零模式位
GPIOA->MODER |= (1 << (5 * 2));       // 设置为输出模式(01)
​
// 3. 配置输出类型
GPIOA->OTYPER &= ~(1 << 5);           // 推挽输出(0)

3.中断控制器(NVIC)初始化:

  • 设置优先级分组

NVIC_SetPriorityGrouping(3); // 4位抢占优先级,0位子优先级

  • 使能中断

NVIC_EnableIRQ(USART1_IRQn); // 使能USART1中断

三、引导/升级系统(Bootloader)

核心问题:如何安全跳转到应用程序? 关键技术向量表重定向Flash编程

1.应用程序跳转代码:

typedef void (*AppEntry)(void);
void JumpToApp(uint32_t appAddress) {
    AppEntry startApp = (AppEntry)(*(volatile uint32_t*)(appAddress + 4));
    __set_MSP(*(volatile uint32_t*)appAddress); // 设置主栈指针
    startApp(); // 跳转到应用程序
}
  • 关键点

    • appAddress + 4 是应用程序的Reset_Handler地址。

    • 需先关闭所有中断再跳转。

2.双Bank升级流程(防止变砖):

Bank1(运行中) | Bank2(新固件)
-----------------|-----------------
1. 擦除Bank2     |
2. 写入新固件到Bank2 |
3. 设置标志位指向Bank2 |
4. 复位后Bootloader检查标志位跳转

四、 RTOS初始化(以FreeRTOS为例)

核心问题:如何创建第一个任务? 关键技术任务栈初始化上下文切换

1.任务栈结构:

  • ARM Cortex-M的任务栈需手动模拟异常返回时的栈帧:

StackType_t pxStack[1024];
pxStack[1023] = 0x01000000;  // xPSR(Thumb模式)
pxStack[1022] = (StackType_t)TaskFunction; // PC
pxStack[1021] = (StackType_t)0xFFFFFFFE;   // LR(异常返回特殊值)
  • vTaskStartScheduler() 会触发 SVC异常,切换到第一个任务。

2.PendSV中断:

  • 上下文切换通过 PendSV异常 实现(最低优先级中断):

PendSV_Handler:
    MRS r0, PSP                 ; 获取当前任务栈指针
    STMDB r0!, {r4-r11}         ; 保存寄存器
    BL vTaskSwitchContext       ; 选择下一个任务
    LDMIA r0!, {r4-r11}         ; 恢复寄存器
    MSR PSP, r0                 ; 更新栈指针
    BX lr                       ; 返回后自动恢复剩余寄存器

五、多任务调度实例

场景:传感器采集 + 无线传输

void SensorTask(void *pv) {
    while (1) {
        float temp = ReadTemperature(); // 阻塞式读取
        xQueueSend(xDataQueue, &temp, portMAX_DELAY);
        vTaskDelay(pdMS_TO_TICKS(100)); // 100ms周期
    }
}
​
void CommsTask(void *pv) {
    while (1) {
        float temp;
        xQueueReceive(xDataQueue, &temp, portMAX_DELAY);
        SendToCloud(temp); // 通过Wi-Fi发送
    }
}

调度器行为

  1. SensorTask 调用 vTaskDelay 后进入阻塞状态,触发任务切换。

  2. 调度器选择 CommsTask 运行(若队列中有数据)。

  3. SysTick 中断每1ms检查是否需要任务切换。

六、终极总结:嵌入式软件启动全景图

上电
│
├─ 硬件复位 → CPU读取0x00000000获取SP和Reset_Handler
│   │
│   └─ startup.s:初始化.data/.bss → 跳转到SystemInit()
│
├─ 板级初始化:
│   ├─ 时钟树配置(HSI→HSE→PLL)
│   ├─ GPIO/USART/DMA外设使能
│   └─ 中断向量表重定位(SCB->VTOR)
│
├─ Bootloader:
│   ├─ 检查升级标志 → 擦写Flash → 跳转App
│   └─ 应用程序起始地址需对齐到0x100(Cortex-M要求)
│
├─ RTOS初始化:
│   ├─ 创建空闲任务 → 初始化任务链表 → 启动SysTick
│   └─ 第一个任务通过SVC异常启动
│
└─ 多任务运行:
    ├─ 任务切换由PendSV处理(保存R4-R11)
    └─ 同步机制:队列/信号量/事件组

关键原则

  1. 启动阶段:严格遵循芯片手册的初始化顺序(如先时钟后外设)。

  2. RTOS阶段:理解任务栈和上下文切换的汇编级实现。

  3. 调试技巧:若卡死,检查:

    • 向量表地址是否正确(VTOR)。

    • 栈是否溢出(填充0xDEADBEEF检测)。

    • 时钟配置是否成功(测量HSI/HSE频率)。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐