Pico裸机9(bootrom_func)

1 bootrom的内容

在Pico中,Bootrom基本就等于PC的bios。整个Bootrom是16K,内容如下:

源代码是在:https://github.com/raspberrypi/pico-bootrom-rp2040

Pico的启动过程分3级

1 Bootrom,将打包出来的elf中的头256字节放入SRAM。

2 Boot2,启动XIP,将Flash映射到内存地址空间,之后跳到0x10000100也就是向量表。

3 Main,读取Reset Vector,执行初始化(搬运数据段.data到RAM,清空.bss),最后跳到main。

Pico (RP2040/RP2350) 的Bootrom很类似PC的BIOS,不过Bootrom是直接刻死在芯片内部的硅片上的(ROM)。所以无法修改它,也无法刷坏。PC的BIOS存储在主板的Flash芯片里。启动后的第一件事都是检查硬件,Pico的Bootrom检查系统时钟、堆栈指针和基本的外部存储(Flash)。之后Pico 的Bootrom寻找外部SPI Flash里的用户程序。

Pico的Bootrom比较特别的是具有UF2模式,按住板子上的Bootsel,就会被认为是一个U盘,确实这样刷机方便了很多。盲猜是实现了usb storage和UF2格式的解析。

最后Pico的Bootrom 比PC BIOS多了一个很有趣的设计:它内部固化了一些高度优化的数学函数(比如浮点运算、除法、三角函数)。因为RP2040 硬件本身没有浮点运算单元(FPU),用户的代码在调用 sin() 或 cos() 时,SDK 实际上会通过一个查表机制去调用 Bootrom 里的这些固化函数。这节省了宝贵的 Flash 空间(不用自己写算法库),而且运行速度极快。

实际上只要用标准的 math.h,Pico SDK就会自动帮你连接到 Bootrom,对于一般的用户基本上感觉不到。

官方介绍是在2.8.3.1

是有好几类函数:

这些函数由树莓派的大神进行了高度优化并且保存在读取最快的ROM中,根据树莓派官方的基准测试:

在处理浮点数(float/double)运算时,使用 Bootrom 优化的库通常比标准的软件模拟库(如旧版的编译器自带库)快 2倍到10倍。在处理整数除法时,由于 Bootrom 配合了硬件加速逻辑,速度提升更为显著。

再完整的看官方手册吧。。。

2 调用

如果是在SDK下手动调用,大概是这样的:

查找

cpp 复制代码
void *rom_func_lookup(uint32_t code) {
    13 return rom_func_lookup_inline(code);
}

void *rom_data_lookup(uint32_t code) {
    return rom_data_lookup_inline(code);
}

code就源于之前的表,算法是

cpp 复制代码
uint32_t rom_table_code(char c1, char c2) {
    return (c2 << 8) | c1;
}

学习代码还是来自:https://github.com/carlosftm/RPi-Pico-Baremetal/blob/main/14_bootrom_func_data/bootrom_func_data.c

cpp 复制代码
    /* 1. Read and print the copyright string */
    volatile int* pRomTableLookup = ROM_TABLE_LOOKUP;                                       // pointer to rom_table_lookup() function
    uint32_t ( *romTableLookup )( uint16_t* dataTable, uint32_t code ) = *pRomTableLookup;  // function pointer assignment
    uint16_t *pDataTable = ( uint16_t* ) rom_hword_as_ptr( ROM_DATA_TABLE );                // pointer to the rom_data_table
    int code = genCode('C', 'R');                                                           // generate the code for the copyright_string
    char* copyrightString = (*romTableLookup)(pDataTable, code);                            // get the pointer to the copyright_string

    uartTxStr( "Copyright string from bootrom:\r\n") ;
    while(*copyrightString != 0)
    {
        uartTx(*copyrightString);
        copyrightString++;
    }
    uartTxStr("\r\n");

    /* 2. Call the bootrom popcount32() function */
    uint16_t *pFuncTable = ( uint16_t* ) rom_hword_as_ptr( ROM_FUNC_TABLE );                // pointer to the rom_func_table
    code = genCode('P', '3');                                                               // generate the code for the popcount32() function
    uint32_t* pFunction = ( *romTableLookup )( pFuncTable, code );                          // get pointer to the popcount32() function
    uint32_t ( *popCount32 )( uint32_t value ) = pFunction;                                 // function pointer assignment
    uint32_t result1s = ( *popCount32 )( 0xFF000001 );                                      // call popcount32() function

    uartTxStr( "\n\rCalculate number of binary 1 on 0xFF00001: \r\n");
    uartPrintDW( result1s );

    /* 3. Call the bootrom memset() function to initialize a 64byte memory block */
    unsigned char charArray[64];
    *pFuncTable = ( uint16_t* ) rom_hword_as_ptr( ROM_FUNC_TABLE );                         // pointer to the rom_func_table
    code = genCode('M', 'S');                                                               // generate the code for the memset() function
    pFunction = ( *romTableLookup )( pFuncTable, code );                                    // get pointer to the memset() function
    uint8_t (*_memset)(uint8_t *ptr, uint8_t c, uint32_t n) = pFunction;                    // function pointer assignment

    unsigned char initChar = 'a';
    while( initChar < 'd' )
    {
        (*_memset)(&charArray[0], initChar++, 64);                                          // call memset() function
        for ( int cnt = 0; cnt < 64; cnt++ )
        {
            uartTx(charArray[cnt]);
        }
        uartTxStr( "\n\r");
        SIO->GPIO_OUT_XOR_b.GPIO_OUT_XOR = ( 1 << 25 );     // XOR the LED pin
        delaySec( 1 );
    }

这里的代码好像也没用逻辑的寄存器位置,也是C的写法。

如果是使用SDK中,可以用hardware/bootrom.h。比如:

printf

cpp 复制代码
#include "hardware/bootrom.h"

typedef void (*rom_printf_fn)(const char *, ...);

void rom_debug(void) {
    rom_printf_fn rp = (rom_printf_fn)rom_func_lookup(ROM_FUNC_PRINTF);
    rp("Hello from Boot ROM!\n");
}

memcpy/memset

cpp 复制代码
#include "hardware/bootrom.h"

typedef void (*memcpy_fn)(void *, const void *, size_t);

void *rom_memcpy(void *dst, const void *src, size_t n) {
    memcpy_fn fn = (memcpy_fn)rom_func_lookup(ROM_FUNC_MEMCPY);
    return fn(dst, src, n);
}

3 小结

树莓派Pico这种玩法确实以前也没见过,说实话,重写这些库也要极高的水平。也算是有趣的一个地方。关键就是用途。我感觉用途有这几个。

1 Boot和裸片。Pico的Boot1是肯定没法写的,256字节的Boot2倒是可能魔改,这时候就要用这个。此外裸片的时候也只能用这个。

2 SDK。SDK应该也是封装了不少,高度定制了runtime lib,这个也是使用最多的场景。当然,这个时候对最终用户基本上也是无感的。

3 应用的时候手动调用。这个就见仁见智了,有说这样弄是高手,有说这样弄破坏封装结构的。只有具体涉及到的时候再看了。。。

相关推荐
雨疏风骤12401 天前
ROM与RAM,储存地址、链接地址以及运行地址
linux·stm32·嵌入式·linux嵌入式
不脱发的程序猿1 天前
SPI、DSPI、QSPI技术对比
单片机·嵌入式硬件·嵌入式
Channon_2 天前
专题五:实时系统的生死线——中断安全与优先级管理
嵌入式·优先级·中断安全
大聪明-PLUS2 天前
Linux进程间通信(IPC)指南 - 第3部分
linux·嵌入式·arm·smarc
技术小泽2 天前
MQTT从入门到实战
java·后端·kafka·消息队列·嵌入式
应该会好起来的2 天前
基于定时器中断的多任务轮询架构
嵌入式
切糕师学AI3 天前
NuttX RTOS是什么?
嵌入式·rtos
冤大头编程之路3 天前
FreeRTOS/RT-Thread双教程:嵌入式开发者入门到实战(2025版)
嵌入式
大聪明-PLUS3 天前
一个简单高效的 C++ 监控程序,带有一个通用的 Makefile
linux·嵌入式·arm·smarc