使用嵌入式 Rust 设置寄存器

Setting registers using embedded rust

所以...我一直在关注 embedded rust book... and I'm currently reading about registers. Now, the book does suggest that I use an STM32F303VC discovery to avoid issues, but I couldn't find one, due to which I got a Nucleo F303RE instead。货物的目标和内容保持不变。所以我认为不会有任何问题。

因此,我正在使用的 MCU 将 LED 连接到端口 A (0x48000000),它的 BSRR 偏移量为 0x18。现在,我在 datasheet 中读到端口 A 的默认值是 0xa8000000,我不明白为什么。但是当我尝试使用设置 LED 引脚时 ptr::write_volatile(PORTA_BSRR as *mut u32, 1 << 5); 没有任何反应。甚至我的 gdb 终端也没有反映出任何变化。所以我尝试按照原始教程的建议 (0x48001018) 使用 portE 检查。但即便如此,寄存器值也不会改变。我无法调试这个问题。

现在,我可以 运行 以前的教程,并且可以检查变量和东西。我的 stm 似乎没有任何问题,因为我可以使用 stmc32cubeide 很好地控制它。

这是代码,以备不时之需

编辑:所以,我阅读了@Ikolbly 的评论,并查看了 RCC_AHBENR 寄存器,我猜这就像在 arduino 中设置 pinMode(pin, HIGH),它打开端口。

我已经修改了设置那个位的代码,但似乎没有任何变化。我猜辅助代码已经为 portE 完成了,这就是为什么我不必为此做任何初始化......但即使更改 portE 的寄存器值也不起作用。

//#![deny(unsafe_code)]
#![no_main]
#![no_std]

use aux5::entry;
use core::ptr;

#[entry]
fn main() -> ! {


    const RCC_AHBENR: u32 = 0x48000014;

    const PORTA_BSRR: u32 = 0x48000018;
    let _y;
    let x = 42;
    _y = x;

    unsafe {
        // EDIT enabling portA
        ptr::write_volatile(RCC_AHBENR as *mut u32, 1 << 17);


        // Toggling pin PA5
        ptr::write_volatile(PORTA_BSRR as *mut u32, 1 << 5);

        // Toggling random shit to see if it works
        ptr::write_volatile(PORTA_BSRR as *mut u32, 1 << 6);
        ptr::write_volatile(PORTA_BSRR as *mut u32, 1 << 7);
        ptr::write_volatile(PORTA_BSRR as *mut u32, 1 << 8);
    }
    // infinite loop; just so we don't leave this stack frame
    loop {}
}

超过 99% 的裸机正在读取。

所以你从 nucleo 数据表中发现 D13 是 LD2,而在 F303 变体上是 PA5。端口 A 引脚 5.

在STM3F303R的参考手册中...

的基地址
RCC 0x40021000
GPIOA 0x48000000

评论中显示的 RCC 基础错误。

RCC_AHBENR 位于 rcc base + 0x14。位 17 是 IOPAEN(I/O 端口 A(时钟)启用)复位值 0x00000014 闪存和 sram 在复位时启用,启用是一件非常好的事情。

现在,要让这些 ST 端口使 LED 闪烁,您至少需要启用 gpio 端口,在这种情况下,需要设置 RCC_AHBENR 的第 17 位。然后设置 GPIOA_MODER 寄存器以将端口设置为输出,您不需要弄乱 pullup/down 和速度,并且输出类型寄存器已经设置为复位时输出。所以启用该端口,并使其成为推挽输出。然后用bsrr闪烁。

GPIOA_MODER 重置为 0xA8000000 重置后使 15,14,13 复用功能,你会发现这些是 jtag 寄存器,两个也是 SWD 数据和 I/O,你可以使用SWD 调试器(内置于 nucleo 板中)。将二进制文件复制到虚拟拇指驱动器比直接使用 swd 更容易(调试 mcu 然后使用 swd 写入目标 mcu 并重置它)。

并且如上所述 RCC_AHBENR 启用了 sram 和闪存。

作为一般规则(也有例外),您想要执行读取-修改-写入。只需将 1<<17 写入 RCC_AHBENR 即可启用 porta。现在您刚刚在睡眠模式下禁用了 sram 和 flash。现在,如果您不进入睡眠模式,从技术上讲您没问题,但您确实应该

x = read RCC_AHBENR
x|= 1<<17
write RCC_AHBENR = x

这可以用 an 或 equal 来完成,作为我不推荐的习惯,但是对于这个寄存器的手动优化,它很好。我还不是铁锈专家(总有一天),所以不知道如何做到这一点。我认为您应该先尝试 asm 或 C 并成功,然后将这些知识应用到 rust。

对于 MODER 寄存器,读取-修改-写入的性质变得更加明显,因为它不止一位。

x = read GPIOA_MODER
x&=(~(3<<10))
x|=1<<10
write GPIOA_MODER = x

将是执行此操作的正确通用方法,现在查看文档并查看剩余值,从技术上讲,只需一次写入 0xA8000400,或者您可以执行读取-修改-写入

x = read
x |= 1<<10
write = x

不清除位 11。

现在有些stm32部分记录了这个,有些没有,大多数人很幸运,因为使用固定工具或读取 - 修改 - 写入习惯,逻辑与它有奇怪之处,特别是现代寄存器(可能使他们的代码工作)。

如果您要预先准备寄存器并进行背靠背写入

ldr r0,=0x40021014
ldr r1,=0x00020014
ldr r2,=0x48000000
ldr r3,=0xA8000400
str r1,[r0]
str r3,[r2]

假设我的位和地址正确(这不是 comment/issue 的重点)背靠背写入在所有 stm32 部件上都不起作用,你有一个竞争条件写入启用i/o 端口在与 i/o 端口通信之前需要延迟。现在即使在那些芯片上,这个

ldr r0,=0x40021014
ldr r1,=0x00020014
ldr r2,=0x48000000
str r1,[r0]
ldr r3,[r2]
modify
write

确实有效,我确定不是运气不好(它具有相同的竞争条件)。

当然,如果您将时钟从重置速度调整为更快的值,情况可能会变得更糟。

如果您使用高级语言进行背靠背写入,则无法保证它会生成上述内容,它可能会生成

ldr r0,=0x40021014
ldr r1,=0x00020014
ldr r2,=0x48000000
ldr r3,=0xA8000400
str r1,[r0]
one or both of the r2/r3 inserted here
str r3,[r2]

而且至少在我可以破坏的 stm32 部件上的重置时钟速度下,这是可行的。但是我已经看到一个编译器带有一组命令行选项,这些选项生成了错误的代码,而其他选项或其他选项并没有因为代码生成的性质而在两者之间插入一条指令(在这种情况下制作相同的 C 代码)工作和不工作都取决于运气)。

所以我建议你不要背靠背写信,and/or 你检查编译器的输出(你应该在任何新项目上这样做,尤其是你的第一个裸机 Rust 程序目前很难生成这样的裸机代码的语言)。 (非常可行,但说 C 的人的数量仍然是百万分之一,但对于锈病来说,这个星球的总人口中只有一小部分)。

您应该检查矢量 table、二进制文件中的位置、加载和存储以及地址和数据等内容。同样,当从任何二进制格式转换为需要复制的原始二进制格式时到 nucleo 板,还要检查该二进制文件,看看它是否以向量 table 开头,并且所需的任何填充都已到位。

所以....

修复你的rcc寄存器地址,我也会修复数据。

在 rcc 寄存器写入后添加读取或延迟,如果需要,可以对 gpio moder 进行简单的一次性读取,或者最好进行读取-修改-写入。

用位 11:10 写 moder 作为 0b01 使其成为通用输出

那么你可以弄乱 BSRR 中的位 5 和 16+5。

最后,根据 nucleo 文档,您甚至不必查看原理图。他们在 LD2 下很好地记录了

the I/O is HIGH value, the LED is on

所以你想SET PA5来打开LED并重置来关闭它。

写一个 1<<(0+5) 到 GPIOA_BSRR。在一个版本的代码中。然后在另一个写 1<<(16+5) 。第一个应该让 LED 亮起,第二个应该让 LED 熄灭。 (然后弄乱延迟并尝试使其闪烁)。

最后的无限循环只需要 bootstrap 或前主代码与 return 上的东西混淆,如果你自己写 bootstrap 那么你可以简单地让它在 main 的 return 上做一个无限循环(这就是我喜欢的方式,有时是一个 wfi/wfe 循环,尽管有些库会在 return 上做一些事情,因此这个习惯)。由于您应该非常了解任何裸机项目(供应商沙箱或您自己的)的启动过程和代码,因此您应该在编写 main 之前知道答案并知道要求是什么。也许您在这种情况下会这样做,只是没有在此处显示。