JP's Website

RP2350 发布博客

发布于 2024-08-08

目录

Intro

今天发布了 Raspberry Pi 微控制器系列的最新产品 - RP2350 系列。 我已经有一段时间的原型单元,你今天就可以在它上面运行 Rust 代码。 据我所知,这是有史以来第一个开箱即用支持 Rust 的微控制器发布。 如果你还没看过其他地方的规格,让我来介绍一下: RP2040| RP235x ---|--- 主 CPU| 2x Arm Cortex-M0+ (Armv6-M)| 2x Arm Cortex-M33 with FPU (Armv8-M) 备选 CPU| None| 2x Hazard3 (RISC-V RV32IMAC) 原子比较和交换| No| Yes (Arm 和 RISC-V 模式) 双精度浮点协处理器| No| Yes (仅 Arm 模式) 标称时钟| 133 MHz| 150 MHz 片上 SRAM| 264 KiB| 520 KiB 内部 Flash| None| 要么 None (RP2350x),要么 2 MiB (RP2354x) 内部 OTP| None| 64 页,每页 64 个 24 位字(可读为 16 位 + ECC) 安全启动| No| Yes 外部存储器| 1x QSPI Flash (最大 16 MiB)| 2x QSPI Flash 和/或 PSRAM (每个最大 16 MiB) Flash 分区| 不支持| ROM 最多支持 16 个,带有地址转换 地址转换| None| 每个芯片选择四个 4 MiB 窗口,可映射到任何 4 KiB 偏移 GPIO| 30x 3.3V| 30x (xA) 或 48x (xB) 3.3V PIO 块| 2| 3 PWM 通道| 16| 24 ADC 通道| 4| 4 (xA) 或 8 (xB) DVI 输出| 软件实现 (使用 2x 超频)| 硬件 TMDS 编码器 & 高速双倍泵浦串行器 USB 设备| USB 1.1 全速| USB 1.1 全速 USB 主机| USB 1.1 低速/全速| USB 1.1 低速/全速 睡眠电流| 180 μA| 低至 10 μA 正如你所看到的,我们几乎拥有了一切 - 加上可以在启动时或运行时将两个 Cortex-M33 中的一个(或两个)换成开源[1] Hazard3 RISC-V 核心。 在我看来,唯一没有真正改善的是我们仍然卡在 USB 1.1 的 11 Mbps 全速 上,而不是升级到 USB 2.0 的 480 Mbps 高速 模式 - 这很可惜。 但是,嘿,它仍然只有一美元! 另外! 不再有单一 SKU - 而是有四种 RP235x 变体可供发布。 RP2040| RP2350A| RP2350B| RP2354A| RP2354B ---|---|---|---|--- 封装| QFN56| QFN60| QFN80| QFN60| QFN80 内部 Flash| None| None| None| 2 MiB| 2 MB GPIO| 30| 30| 48| 30| 48 ADC 引脚| 4| 4| 8| 4| 8 在这篇博客中,当我说 RP235x 时,我指的是 RP2350A、RP2350B、RP2354A 或 RP2354B 中的任何一个。 当我说 RP2350x 时,我指的是没有 flash 的部件之一。 当我说 RP235xAxA 时,我指的是任何 QFN60 RP235x 部件,对于 RP235xBxB,我指的是任何 QFN80 RP235x 部件。 所有 RP235x 都有相同的硅芯片,所以好消息是我们在考虑软件时可以很大程度上忽略这些。 即使是内部 Flash 实际上也是 封装中的 Flash,因此工作方式与外部 flash 完全相同。 由于焊盘在内部键合的方式,60 针封装上的 GPIO 实际上不是芯片上的前 30 个 GPIO,但是芯片内的重新映射功能意味着你可以假装它们是前 30 个 GPIO。 这仅仅是因为硅芯片勘误表意味着重新映射在 Arm 非安全模式下不起作用,但这没关系。 大多数代码都将在安全模式下运行。 我可以访问 RP2350A 的各种早期版本,这些版本安装在预生产的 Raspberry Pi Pico 2 上。

它能运行 Rust 吗?

当然,你可以为它编译 Rust 代码。 对于 Arm Cortex-M33 核心,使用 thumbv8m.main-none-eabihf。 对于 Hazard3 RV32IMAC 核心,使用 riscv32imac-unknown-none-elf。 我已经尝试过这两种方法,从编译器的角度来看没有任何问题。 我的实验性 HAL 位于 https://github.com/thejpster/rp-hal-rp2350-public。 截至今天,该存储库是公开的,但已存档。 我希望这些更改将在 https://github.com/rp-rs 上游采用,但我即将去度假,所以不会发送 PR。 此外,还有一些悬而未决的问题,我希望社区参与进来:

编辑: 好的,几个月后我回到了这里,a) 修复了一些示例中的错别字,以及 b) 让你知道 RP2350 HAL 支持现在位于 https://github.com/rp-rs/rp-hal,所以你应该去使用该版本。 没有太多变化,但可能有一些有用的错误修复。

先别管细节,给我看看 Demo

我已经将 https://github.com/rp-rs HAL 移植到 RP235x,以及一些示例:

rustup target add thumbv8m.main-none-eabihf
rustup target add riscv32imac-unknown-none-elf
git clone https://github.com/thejpster/rp-hal-rp2350-public
cd rp-hal-rp2350-public/rp235x-hal
# 它为 Arm 构建
cargo build --example pwm_blink --target thumbv8m.main-none-eabihf --all-features
picotool load -t elf ./target/thumbv8m.main-none-eabihf/debug/pwm_blink
# 一些示例(没有中断的那些)也为 RISC-V 构建!
cargo build --example pwm_blink --target riscv32imac-unknown-none-elf --all-features
picotool load -t elf ./target/riscv32imac-unknown-none-elf/debug/pwm_blink

你需要按照 Raspberry Pi 的说明,了解如何使用他们的 Pico SDK 编译 picotool。 遗憾的是,在有人添加 Arm Debug Interface v6 支持之前,标准的 Rust 工具(如 probe-rs)将无法与 RP2350 配合使用,这非常重要(并且远远超出了我的技能)。

RP2040 上 Rust 的回顾

RP2040 有两个 Arm Cortex-M0+ CPU,因此合适的 Rust 目标是 thumbv6m-none-eabi。 RP2040 有两个独立的 Rust 堆栈实现:

rp2040-hal 是我一直在使用的,但我听到了关于 embassy-rp 的好评 - 特别是如果你正在寻找为你的 RP2040 编写 Async Rust。 两者都让你为你的 RP2040 编写 Rust 代码,并使用所有集成的外围设备与漂亮的、高级的驱动程序。

今天 Rust 能做什么

RP235x 有两个 Arm Cortex-M33 CPU,因此 Arm 模式的合适 Rust 目标是 thumbv8m.main-none-eabihf。 RISC-V 模式的合适目标是 riscv32imac-unknown-none-elf。 到目前为止,我已经:

这些更改位于 rp-rs 存储库的一个分支中,我今天将其公开。 我可以确认,从应用程序的角度来看,可能只需要进行少量更改。 HAL 中最大的 API 更改是围绕删除 RTC 外围设备 - 最接近的功能现在是 POWMAN 外围设备的“始终在线定时器”,其具有 64 位 1 毫秒的滴答声。 还有现在有两个标准 Timer 外设,因此你必须明确你要使用哪一个。

RP2040 启动的回顾

RP2040 从其内部 Mask ROM 启动。 这个 ROM 查找并执行 flash 开头的特殊 256 字节 启动块,它在你的代码启动之前将外部 flash 控制器重新编程为高速 QSPI 模式。 然后,它使用位于启动块之后的向量表(位于 flash 地址 0x100 或内存地址 0x1000_0100)来启动你的应用程序,就像“常规”Arm CPU 一样。

启动 RP235x

这是我在移植我们现有的 Rust 代码以在 RP235x 上运行时面临的最大挑战,因此我想在此处详细介绍一下,以帮助那些沿着这条路跟随我的人。 基本上,RP235x 上的事情变得 有点复杂

  1. 有两个“核心”,每个“核心”可以是 Arm (Cortex-M33) 模式或 RISC-V (Hazard3) 模式。
  2. 此选择会在核心重置后记住。
  3. Arm Cortex-M33 核心可以在 安全模式非安全模式 下运行代码,并且外围设备/内存范围可以锁定为只能从 安全模式 访问。
  4. RISC-V Hazard3 核心可以在 机器模式用户模式 下运行代码,并且外围设备/内存范围可以锁定为只能从 机器模式 访问。
  5. 启动 ROM 是核心运行的第一件事。
  6. 因此,既有 RISC-V ROM,也有 Armv8-M ROM。 实际上,我认为 RISC-V ROM 运行一个微小的 Arm 模拟器来执行 Arm 版本的代码 - 或者至少在早期版本中是这样。
  7. ROM 需要弄清楚你的代码在哪里,以及你是否想在 Cortex-M33 上运行 Arm 代码,或者在 Hazard3 上运行 RISC-V 代码。
  8. ROM 还处理固件签名、分区表、安全/非安全模式、OTP 启动、UART 启动、从 SRAM 执行等
  9. 片上有 一次可编程 Flash,它可以通过包含以下内容来影响启动:
    • 哈希公钥,用于验证签名密钥是否有效,
    • 自定义引导加载程序,或
    • 自定义 QSPI flash 配置。

在 RP235x 上,256 字节的启动块消失了 - ROM 现在可以自动对外部 Flash 控制器进行编程,以使用各种常见的 QSPI flash(欢呼!)。 它有一个要尝试的命令和时钟分频器列表,但是如果你的 flash 芯片非常奇怪,ROM 无法使其工作,则可以将你的自定义 flash 配置放入 OTP 中。 虽然旧的启动块消失了,但你现在确实需要提供一个名为 Image Definition 的新数据结构,它位于 Flash 的前 4 KiB 内的某个位置。 这个 Image Definition 是称为 Block 的结构的特定形式。 Image Definition 告诉 ROM(或其他工具)关于你的应用程序的信息。 Block 必须形成为一个闭合的链表,称为 Block Loop。 如果你有一个 Block,它必须指向自身。

┏━━━━━━━━━━━━━━━━━━━┓
┃ 0xffffded3    ┃ 魔术 32 位起始值              } 标头
┣━━━━━━━━━━━━━━━━━━━┫
┃ 项目 0      ┃
┣━━━━━━━━━━━━━━━━━━━┫
┃ ...        ┃
┣━━━━━━━━━━━━━━━━━━━┫
┃ 项目 N━1     ┃
┣━━━━━━━━━━━━━━━━━━━┫
┃ 字数       ┃ 此块中有多少个字              }
┣━━━━━━━━━━━━━━━━━━━┫                     }
┃ 下一个块偏移     ┃ 到列表中下一个块的相对偏移              } 页脚
┣━━━━━━━━━━━━━━━━━━━┫                     }
┃ 0xab123579    ┃ 魔术 32 位结束值               }
┗━━━━━━━━━━━━━━━━━━━┛

支持的项目包括(但不限于):

为了简单起见,我认为你需要启动 RP235x 的最小 Image Definition 是:

┏━━━━━━━━━━━━━━━┓
┃ 0xffff_ded3  ┃ 魔术 32 位起始值              } 标头
┣━━━━━━━━━━━━━━━┫
┃ 0x1021_0142  ┃ 以 Arm 安全模式启动
┣━━━━━━━━━━━━━━━┫
┃ 0x0000_01ff  ┃ 此块长 1 个字             }
┣━━━━━━━━━━━━━━━┫                    }
┃ 0x0000_0000  ┃ 到下一个块的偏移量(指向自身)          } 页脚
┣━━━━━━━━━━━━━━━┫                    }
┃ 0xab12_3579  ┃ 魔术 32 位结束值               }
┗━━━━━━━━━━━━━━━┛

这些 32 位值应以小端格式存储。 单个块通过偏移量零指向自身,但你可能想在 flash 映像的末尾添加第二个 Block,其中包含该映像的哈希或签名。 实际上,picotool 可以 密封 ELF 文件或 UF2 文件,并为你添加这样一个新的 Block,将其连接到先前仅在 flash 开头包含 Image DefinitionBlock Loop 中。 要使用哈希 密封 文件,你可以执行以下操作:

picotool seal \
  --verbose \
  --major 1 --minor 0 \
  --hash \
  ./target/thumbv8m.main-none-eabihf/release/examples/rom_funcs -t elf \
  ./target/rom_funcs_hashed.elf

要使用签名(受密码保护的哈希)密封 文件,你可以执行以下操作:

# 创建一个椭圆曲线密钥对
openssl ecparam -name secp256k1 -genkey -noout -out ec-secp256k1-priv-key.pem
# 使用我刚刚创建的密钥对二进制文件进行签名
picotool seal \
  --verbose \
  --major 1 --minor 0 \
  --sign \
  ./target/thumbv8m.main-none-eabihf/release/examples/rom_funcs -t elf \
  ./target/rom_funcs_signed.elf \
  ./ec-secp256k1-priv-key.pem \
  ./target/rom_funcs_signed_otp.json

第二个命令同时生成一个签名文件和一个需要设置的 OTP 值的 JSON 列表(picotool 可以为你执行此操作)。 只有当 RP235x 处于 安全启动 模式时才会检查这些签名,并且该模式由 OTP 中的一位控制。 我还没有足够勇敢地打开我唯一的 Pico 2 板上的那个位。 除了 Image Definitions 之外,你还可以创建一个 Partition Table,这是另一种 BlockPartition Table 可以描述 Flash 中最多 16 个分区,并控制它们是用于代码还是数据、它们是 A 分区还是 B 分区(用于 OTA 升级和先试后买),以及它们是用于 Arm 安全模式、Arm 非安全模式还是 RISC-V 代码。 你不必哈希你的 Partition Table,但这可能是一个好主意,这样你的 RP235x 就永远不会尝试使用损坏的分区表。 并且请注意,安全模式代码始终可以做任何它喜欢的事情并覆盖分区表,但非安全模式代码和 ROM 例程将遵守它,picotool 也会遵守它。 在运行时,你当前的的 Partition Table 位于它自己的特殊 RAM 片段中(我认为在 0x4000_0000 附近)。 如果你有一个 Partition Table,那么当你将 UF2 文件拖放到 USB 大容量存储接口时,它将根据 UF2 系列 ID 自动写入到相应的分区。 让我们使用 picotool 将此分区表添加到 Pico 2:

{
 "version": [1, 0],
 "unpartitioned": {
  "families": ["absolute"],
  "permissions": {
   "secure": "rw",
   "nonsecure": "rw",
   "bootloader": "rw"
  }
 },
 "partitions": [
  {
   "name": "Armageddon",
   "id": 0,
   "size": "2044K",
   "families": ["rp2350-arm-s"],
   "permissions": {
    "secure": "rw",