原文:https://decaday.github.io/blog/sifli-boot/
最近也是收到了sf32lb52开发板,低功耗蓝牙,大内存大Flash,STAR-MC1(Cortex-M33)还是很有吸引力的,可以好好玩一番。
这篇文章就来分析一下思澈芯片的二级Bootloader、FlashTable,顺便写一个Rust点灯程序。
repo:https://github.com/OpenSiFli/sifli-hal-rs
SiFli芯片简介
不是广告不是广告不是广告
SF32LB52x 芯片采用了基于Arm Cortex-M33 STAR-MC1 处理器的大小核架构,集成高性能2D/2.5D图形引擎,双模蓝牙5.3,以及音频codec。
芯片中大核性能处理器最高工作频率达192MHz,单 核性能达787CoreMark,支持动态频率功耗调节,功 耗效率最高可达4.8uA/CoreMark。
思澈的芯片已经在小米、华为、华强北手表上广泛使用。最近(25年初)他们拥抱开源社区,开源了SDK,发售了开发板(以后会和嘉立创合作出一个板子)。
启动流程
参考:
- SiFli-SDK boot_loader
- SiFli-SDK flash table中,flash bootloader的地址问题和宏覆盖问题 · Issue #10
- SiFli Docs 应用程序启动流程 - SiFli SDK编程指南 文档
- SiFli Docs 安全引导加载 - SiFli SDK编程指南 文档
Overview
SiFli SF32LB52x有两级Bootloader。
一级Bootloader固化在了芯片的ROM中,初始化Flash和调试串口,运行二级Bootloader。
二级Bootloader一般在启动Flash的开始位置+0x10000,比如0x1201 0000。它初始化PSRAM,并跳转用户程序并运行。
用户程序在启动Flash的开始位置+0x20000, 比如0x1202 0000.
Flash Table、二级Bootloader和用户程序一起编译和烧录。
地址空间
内置SRAM
HPSYS 总线上共有512KB SRAM,其中包括:
- 0x2000_0000-0x2001_FFFF,128KBzero-wait-cycle SRAM(与D-TCM共享),所有AHBmaster均可访问, 最高频率为192MHz。
- 0x2002_0000-0x2007_FFFF,384KBzero-wait-cycle SRAM,所有AHBmaster均可访问,最高频率为192MHz
外挂Flash或PSRAM
SiFli SF32LB52x使用MPI1和MPI2来挂载Flash或PSRAM。既可以合封,也能挂载到外部。
HPSYS支持合封四线和八线PSRAM,外挂NOR/NANDFLASH。
MPI1可挂PSRAM,地址0x6000_0000–0x61FF_FFFF,接口最高频率为DDR 144MHz,数据位宽为8-bit。
MPI1 可挂合封FLASH,地址0x6000_0000 - 0x61FF_FFFF (0x1000_0000 - 0x11FF_FFFF) 推荐使用频率为96MHz
MPI2 可挂外置FLASH,地址0x6200_0000 - 0x9FFF_FFFF (0x1200_0000 - 0x1FFF_FFFF) 推荐使用频率为60MHz
SF32LB52-MOD-1-N16R8
以我手头的SF32LB52-DevKit-LCD V1.2(板载SF32LB52-MOD-1-N16R8)为例:
MPI1挂了 8MB OPI-PSRAM,地址0x6000_0000,MPI2挂了16MB QSPI-NOR Flash,实际地址0x1200_0000。
FlashTable
FlashTable一般在Flash的其实地址(内部或者外部Flash,下文称为启动Flash),比如 (0x1200_0000 - 0x12001_FFFF,地址空间64K) ,用来指示二级Bootloader,HCPU,LCPU的代码位置和运行方式。
目前默认FlashTable的生成方式是:
- Scons构建脚本根据 ptab.json 分区表的
ftab
条目生成ftab.c - Scons构建脚本根据 ptab.json 分区表的
tags
条目生成ptab.h,这是一个undef的文件,覆盖定义mem_map.h 中的宏。(其中有一些宏依赖关系,所以不仅仅是ptab.h里的宏改变了。 - 然后编译ftab.c,不链接其它代码,直接编译为ftab.bin。
个人认为这个覆盖宏的流程,以及宏命名方式有点糟糕(小声)
我在过年的时候也尝试复刻了这个流程,来给Rust固件来用:sifli-flash-table。之后我想把烧录用的那一堆文件整理下,做个runner或者cli出来,但还没想好怎么做。
这是我写flash table时和工程师交流的issue,可供参考:Issue #10 · OpenSiFli/SiFli-SDK
一级Bootloader
一级Bootloader固化在了芯片的ROM中,其中断向量表地址为0。上电后会首先运行一级Bootloader,根据芯片封装类型,确定Flash分区表的位置,根据Flash分区表指示的二级Bootloader地址(必须在启动Flash上,地址为启动Flash开始的地址),拷贝二级Bootloader代码到RAM中,并跳转运行。
一级Bootloader会初始化SiFli那个既当Uart又当SWD的串口,发送SFBL
信息,检查是否设置了BOOT选项,以进入下载模式(这样我们就不怕自己的程序把芯片玩成砖了)
一级Bootloader阶段大核以上电默认的时钟频率运行,初始化启动Flash的IO配置。
源码:SiFli-SDK/example/boot_loader, BOOTROM
宏开启。
跳转方式为:
void run_img(uint32_t dest)
{
__asm("LDR SP, [%0]" :: "r"(dest));
__asm("LDR PC, [%0, #4]" :: "r"(dest));
}
它将SP(Stack Pointer)栈指针,也就是栈的最高地址,设为目的固件的第一个地址(stack_top)。
将PC(Program Counter)程序计数器,也就是下一条命令的地址,设为目的固件的第二个地址(ResetHandler)。这符合标准的cortex-m向量表格式。
二级Bootloader
二级Bootloader同样是有向量表和启动汇编的完整cortex-m程序。在SystemInit中会重定义向量表(设置SCB->VTOR寄存器)。
二级Bootloader根据芯片封装类型以及Flash分区表,加载应用程序并跳转执行。根据芯片封装类型,应用程序分为以下几种启动方式:
XIP(直接以NOR Flash地址执行代码,代码的存储地址与执行地址相同)
使用MPI内置或外挂NOR FLASH
非XIP(从Flash拷贝代码到RAM中执行,即代码的存储地址与执行地址不同)
内置PSRAM(MPI1),外置MP1 NAND FLASH或SDIO SD FLASH。
不论是哪种启动方式,应用程序与二级Bootloader均存放在同一个启动Flash上。
二级Bootloader会调整时钟设置,详见文档。
源码:SiFli-SDK/example/boot_loader, BOOTROM
宏关闭。
二级Bootloader的跳转函数和上述一级Bootloader相同。
用户固件
SiFli的用户固件同样还是有向量表和启动汇编的完整cortex-m程序。
事实上,两个Bootloader和用户固件用的是同一个startup.s和linker script。
根据文档,二级Bootloader不加载PMU的校准参数,仅修改所使用的存储相关的IO设置。Cache未使能,MPU未使能。在用户固件中还需要将这三个东西初始化。
Rust点灯
对于Cortex-M33 STAR-MC1(thumbv8m.main)有成熟的LLVM后端和Rust前端,那当然要上Rust呀。
Rust,启动!
对于Rust HAL的结构,这里不再赘述,请参考:
Rust 嵌入式开发中的外设寄存器访问:从 svd2rust 到 chiptool 和 metapac - 以 hpm-data 为例 | 猫·仁波切
embassy-rs/embassy: Modern embedded framework, using Rust and async.
我之后也会基于py32或sifli写一篇博客。
sifli-hal基本是复制了embassy/embassy-rp的项目结构,我自己加了一些codegen,比如中断表和外设singleton(单例)生成。
GPIO驱动
GPIO的代码并不复杂:sifli-hal-rs/sifli-hal/src/gpio.rs
思澈也对每一个用bit(一个IO用一个bit那种)的寄存器提供了三个入口,比如:
- DOER:数据输出开启寄存器(Data Output Set Register),RW,1打开,0关闭。
- DOESR: 数据输出开启置位寄存器(Data Output Enable Set Register),Write Only,对某位写1来开启。
- DOECR: 数据输出开启清除寄存器(Data Output Enable Clear Register)Write Only,对某位写1来关闭。
这就省去我们按位与、取反等等的麻烦。
要注意的是,不能复位PINMUX外设,因为有些引脚已经用作MPI引脚,复位它就崩了。
遇到的坑
但是我却被卡了两天,运行Rust固件就HardFault。而且使用cortex-debug产生HardFault就直接断了,SiFliUartServer的灯也灭了(查看文档是说Debug模式关闭,此时也无法烧录固件,无法再次连接调试,除非复位)
简单来说,是MSPLIM未设置的问题,但是不知道为什么出现的是UsageFault INVSTATE,而且故障发生后cortex-debug就会挂掉。
因为与文章无关,所以在这里的发布我就删除了我的排除过程。
我感觉是挺有意思的,可以查看原文:https://decaday.github.io/blog/sifli-boot/
总之,Rust点灯成功!
MSPLIM
MSPLIM: The stack limit registers limit the extent to which the MSP and PSP registers can descend respectively.
这个东西是好东西,Armv8-M(Cortex-M33,M23)才有的,用于设置栈底,当堆栈溢出时会产生HardFault(UsageFault STKOF),防止踩到.bss和.data段。有了这个东西,flip-link 都不用了。
也就是说,Bootloader已经设置了MSPLIM,而我没去重新设置它,导致的问题。
不过,为什么产生的是INVSTATE而不是STKOF,就不得而知了……
我给sifli-hal加上这些代码,用掉cortex-m-rt的__pre_init
符号,来设置msplim就OK了:
#[cfg(feature = "set-msplim")]
global_asm!(
".section .text._pre_init",
".global __pre_init",
".type __pre_init, %function",
".thumb_func",
"__pre_init:",
" ldr r0, =_stack_end",
" msr MSPLIM, r0",
" bx lr",
".size __pre_init, . - __pre_init"
);
同时,我也给cortex-m-rt拉了PR,但是他们会不会合,就说不定了:
最后
找到这个问题用了不短的时间,这还让我几乎学会了命令行gdb和arm汇编(盯着汇编看了好久),emmmm
不过总之也是跑通了,Cortex-M33就是好玩,那个太监Cortex-M0连中断原因都查不了。
对于sifli-hal-rs,可能会有更多的大佬参与进来(目前SiFli的SDK和工具链还不支持Linux/Mac)。我自己的时间和精力来写完一整个hal估计是不太够(Welcome!)
原文:https://decaday.github.io/blog/sifli-boot/
我的github: https://github.com/decaday
本文以CC-BY-NC许可发布,当您转载该文章时,需要署名,且不能用于商业用途。特别地,不能转载到C**N平台。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。