作者: shufue

  • [Rust_emb]外设(Peripherals)

    什么是外设

    MCU不是只有CPU,Flash,Ram。也包含传感器,显示器等帮助MCU和外界交互的设备这些统一称之为外设Peripherals。

    外设的作用

    外设可以允许开发者将任务处理的工作卸载在外设从而减少CPU的负载或者节省电量。类似于PC将图形处理任务卸载到GPU上

    Singletons

    不建议使用全局变量

    我们可以将内容设为公共全局静态,就像这样

    static mut THE_SERIAL_PORT: SerialPort = SerialPort;
    
    fn main() {
        let _ = unsafe {
            THE_SERIAL_PORT.read_speed();
        };
    }

    但是意味着在多线程或者多程序中可能会出现对公共全局变量的读写竞争,因为不可能所有对公共全局变量都是原子操作。所以这样是不安全的,无法跟踪变量的引用权和所有权。

    我们应该如何做在Rust

    与其将外设设为全局变量,我们可以选择创建一个结构体,这里称之为 PERIPHERALS ,其中包含每个外设的 Option<T> 。

    struct Peripherals {
        serial: Option<SerialPort>,
    }
    impl Peripherals {
        fn take_serial(&mut self) -> SerialPort {
            let p = replace(&mut self.serial, None);
            p.unwrap()
        }
    }
    static mut PERIPHERALS: Peripherals = Peripherals {
        serial: Some(SerialPort),
    };

    现有库的支持

    尽管上面创建了 Peripherals 结构体,但为你的代码并非必要这样做。 cortex_m 库包含一个名为 singleton!() 的宏,为你执行这个操作。

    use cortex_m::singleton;
    
    fn main() {
        // OK if `main` is executed only once
        let x: &'static mut bool =
            singleton!(: bool = false).unwrap();
    }
    • let x : & ‘static mut bool 声明x是一个引用,引用的是’static mut bool这个格式的
    • singleton! 创建一个全局唯一的变量,第一次调用就返回 Some(&'static mut bool)
    • (: bool =false) 声明创建的变量是布尔值初始化为false。
    • 总结:创建一个全局唯一的布尔值,初始值是 false,并返回它的可写引用给 x。”

    此外,如果你使用 cortex-m-rtic ,抽象化定义和获取这些外设,你将得到一个 Peripherals 结构体,其中包含你定义的所有项的非 Option<T> 版本。

    // cortex-m-rtic v0.5.x
    #[rtic::app(device = lm3s6965, peripherals = true)]
    const APP: () = {
        #[init]
        fn init(cx: init::Context) {
            static mut X: u32 = 0;
             
            // Cortex-M peripherals
            let core: cortex_m::Peripherals = cx.core;
            
            // Device specific peripherals
            let device: lm3s6965::Peripherals = cx.device;
        }
    }

  • [Rust_emb]基础认识

    使用STM32F3DISCOVERY

    一个 STM32F303VCT6 微控制器。这个微控制器具有

    • 一个单核 ARM Cortex-M4F 处理器,支持单精度浮点运算,最高时钟频率为 72 MHz。
    • 256 KiB 的“Flash”内存。(1 KiB = 1024 字节)
    • 48 KiB 的 RAM。
    • 各种集成外设,如定时器、I2C、SPI 和 USART。
    • 通用输入输出 (GPIO) 以及其他类型的引脚,可通过板子两侧的两排接口访问。
    • 可通过标有“USB USER”的 USB 端口访问的 Mini-USB 接口。
    •  LSM303DLHC 芯片有加速度计,磁力计,陀螺仪。
    • 8 个用户 LED,排列成指南针形状。
    • 第二个微控制器:STM32F103。这个微控制器实际上是板载编程器/调试器的一部分,并连接到名为“USB ST-LINK”的 Mini-USB 端口。

    为了获取更详细的功能列表和板卡的进一步规格,请访问 STMicroelectronics 网站。

    #![no_std]

    嵌入式编程这一术语涵盖了广泛的不同类别的编程。从仅使用几 KB RAM 和 ROM 编程的 8 位微控制器(如 ST72325xx),到拥有 32/64 位 4 核 Cortex-A53 @ 1.4 GHz 和 1GB RAM 的系统如 Raspberry Pi (Model B 3+)。根据你的目标和使用场景不同,编写代码时将面临不同的限制/局限性。

    Hosted Environments宿主环境

    环境接近于普通 PC 环境。这意味着将获得一个系统接口帮,例如 POSIX,它为你提供了与各种系统交互的基础设施,如文件系统、网络、内存管理、线程等。标准库通常会依赖这些基础设施来实现其功能。你可能有某种形式的 sysroot,以及 RAM/ROM 使用的限制,或许还有一些特殊的硬件或 I/O。总的来说,感觉就像在特殊用途的 PC 环境中编程。

    简单来说就是有些最基础的事情不用自己考虑由宿主系统代为管理,如内存分配/调度,核心线程任务调度等。只需使用宿主系统给的接口就能基于系统来避免做“重复造轮子”的任务来减少开发负荷。

    Bare Metal Environments  裸机环境

    在一个裸机环境中,在您的程序之前没有加载任何代码。没有操作系统提供的软件,无法加载标准库。相反,程序及其使用的 crates 只能使用硬件(裸机)来运行。为了防止 rust 加载标准库,使用 no_std 。标准库的平台无关部分通过 libcore 提供。libcore还排除了在嵌入式环境中并非指的使用的内容。其中之一是用于动态内存分配的内存分配器。如果您需要这个或其他任何功能,通常有 crates提供这些功能。

    The libstd

    所以libstd是Rust的标准库,提供在有操作系统支持下开发应用所需的的基础功能的集合。默认链接标准库,封装操作系统能力并提供通用数据结构与工具(如 网络 std::net)。

    所以在我们使用裸机环境(#![no_std])时是无法调用listd

    总结

    #![no_std] 是一个 crate 级别的属性,表示该 crate 将链接到 core-crate 而不是 std-crate。libcore crate 本身是 std crate 的平台无关子集,它不对程序运行的系统做任何假设。它提供了用于语言基本类型(如浮点数、字符串和切片)的 API,以及用于暴露处理器特性的 API(如原子操作和 SIMD 指令)。但它缺乏涉及平台集成的 API。因为这些特性,no_std 和 libcore 代码可用于任何类型的引导(阶段 0)代码,如引导加载程序、固件或内核。

    featureno_stdstd
    heap (dynamic memory)*
    collections (Vec, BTreeMap, etc)**
    stack overflow protection
    runs init code before main
    libstd available
    libcore available
    writing firmware, kernel, or bootloader code

    * 仅当你使用 alloc crate 并使用合适的分配器(如 alloc-cortex-m)时。

    ** 只有在使用 collections crate 并配置全局默认分配器时才可用。

    ** 由于缺少安全的随机数生成器,HashMap 和 HashSet 无法使用。

  • [Rust_emb]开始实践

    使用cargo-generate创建模版

    首先安装cargo-generate

    cargo install cargo-generate

    生成一个项目

    cargo generate --git https://github.com/knurling-rs/app-template
     Project Name: app
     Creating project called `app`...
     Done! New project created /tmp/app
    cd app

    配置文件

    第一步在 .cargo/config.toml 中设置一个默认的编译目标。

    # Pick ONE of these compilation targets
    # target = "thumbv6m-none-eabi"    # Cortex-M0 and Cortex-M0+
    # target = "thumbv7m-none-eabi"    # Cortex-M3
    # target = "thumbv7em-none-eabi"   # Cortex-M4 and Cortex-M7 (no FPU)
    target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)

    第二步是将内存区域信息输入到 memory.x 文件中。

    /* Linker script for the STM32F303VCT6 */
    MEMORY
    {
      /* NOTE 1 K = 1 KiBi = 1024 bytes */
      FLASH : ORIGIN = 0x08000000, LENGTH = 256K
      RAM : ORIGIN = 0x20000000, LENGTH = 40K
    }

    注意:如果你在首次build特定构建目标后,出于某种原因更改了 memory.x 文件,那么在执行 cargo build 之前要先执行 cargo clean ,因为 cargo build 可能无法及时跟踪 memory.x 的更新。

    在 bin/hello.rs 中检查内

    与原文差异原因
    说明
    hprintln旧版 cortex-m 的打印宏,新版本已移除
    defmt::println新版推荐使用的打印宏(通过 RTT 输出)

    你的 Cargo.toml 已经包含 defmt-rtt,所以使用 defmt::println 是正确选择。

    #![no_main]
    #![no_std]
    
    use app as _; // global logger + panicking-behavior + memory layout
    use cortex_m_rt::entry;
    
    #[entry]
    fn main() -> ! {
        defmt::println!("Hello, world!");
    
        loop {}
    }

    Memory Mapped Registers 内存映射寄存器

    内存映射寄存器(MMIO):把硬件设备“抽象成内存”,让 CPU 用普通的读写指令就能控制外设及通过“对特定地址的读写”来进行控制。

                ┌──────────────────────────────┐
                │         应用层 (App)          │
                │  main.rs / 业务逻辑 / RTOS    │
                └────────────▲─────────────────┘
                             │ 调用 API
                ┌────────────┴─────────────────┐
                │        HAL 层 (硬件抽象)      │
                │  stm32f3xx-hal               │
                │  GPIO / UART / SPI / I2C     │
                └────────────▲─────────────────┘
                             │ 调用寄存器
                ┌────────────┴─────────────────┐
                │      PAC 层 (寄存器映射)      │
                │  stm32f3 (svd2rust 生成)     │
                │  RCC / GPIOx / USARTx        │
                └────────────▲─────────────────┘
                             │ CPU控制 / 指令
                ┌────────────┴─────────────────┐
                │ Micro-architecture 层        │
                │ cortex-m / cortex-m-rt       │
                │ NVIC / SCB / asm / 启动逻辑  │
                └────────────▲─────────────────┘
                             │
                ┌────────────┴─────────────────┐
                │        硬件层 (MCU)          │
                │  Cortex-M4 + Flash + RAM     │
                └──────────────────────────────┘

    Micro-architecture crate(CPU 层)

    是底层对CPU的低级访问,是关于CPU基础行为定义

    cortex-m crate

    cortex-m-rt crate

    Peripheral Access Crate (PAC)

    地址 + 偏移 的类型安全封装

    stm32f3 crate

    HAL Crate

    把对寄存器直接的操作抽象成为易于理解的API

    stm32f3xx-hal crate

    例子
    let led = gpioa.pa5.into_push_pull_output();
    led.set_high();

    Board Crate

     比 HAL Crate 更进一步,通过预先配置各种外设和 GPIO 引脚以适应你使用的特定开发者套件或板,例如用于 STM32F3DISCOVERY 板的 stm32f3-discovery。

    Semihosting半主机模式

    个人感觉不实用可以用defmt 代替

    Panicking

    是Rust语言的核心,是内置在operation或index中的用来保证内存安全,当尝试越界时回返回panic。

    当 panic 发生时,Rust 会回溯当前线程的调用栈(逐层退出函数并执行清理),除非你选择让程序在 panic 时直接终止(abort)。

    因为嵌入式系统的需求差异很大(从可以崩溃到绝不能崩溃),所以 panic 的处理方式没有统一标准,但常见策略(停机、打印、重启等)已经被封装成 crate#[panic_handler],你只需要选择合适的 panic handler 即可。

    panic-abort . 一个panic会导致执行中止指令直接停止。

    panic-halt . 一个panic会导致程序或当前线程通过进入无限循环而停止。

    panic-probe . panic信息通过RTT(内存通道)进行输出日志记录。

    panic-itm . panic信息通过 ITM(ARM Cortex-M 特定外设)进行记录。

    panic-semihosting . painc信息通过semihosting技术记录到主机上。

    你可以在 crates.io 上搜索 panic-handler 关键词,或许能找到更多 crate。

    Exceptions不想看了

    异常和中断是处理器提供的一种硬件机制,用来处理异步事件(比如外设中断)和致命错误(比如非法指令)。当这些事件发生时,CPU 会抢占当前执行流程,跳转到对应的异常处理函数(handler)来处理。

    Interrupts不想看了

  • [Rust]6更多集合和更多错误处理

    本章梗概

    • Other collections—more complex and interesting ones this time
    • The question mark operator—just type ? to handle errors
    •  When panic and unwrap are good
    (更多…)
  • Certbot申请cloudflare证书并自动续签

    安装certbot和插件

    apt update 
    apt upgrade 
    apt install certbot python3-certbot-dns-cloudflare

    配置Cloudflare API密钥

    1. 登录到 Cloudflare 控制台
    2. 进入 API Tokens 页面:
      • 点击右上角的用户图标 -> My Profile -> API Tokens。
      • 点击 Create Token 创建一个新密钥。
    3. 创建一个 自定义 Token,为它设置适当的权限:
      • 选择 Zone.Zone、Zone.DNS 权限,这样 Certbot 可以管理 DNS 记录。
      • 设置为 “Edit” 权限。
    4. 生成 API Token 后,记下 Token,这将在配置 Certbot 时用到。

    编辑配置文件

    vim /etc/letsencrypt/cloudflare.ini
    # Cloudflare API token used by Certbot
    dns_cloudflare_api_token = 0123456789abcdef0123456789abcdef01234567
    

    更改权限为只读

    chmod 600 /etc/letsencrypt/cloudflare.ini

    申请证书

    sudo certbot certonly \
     --dns-cloudflare \
     --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
     --key-type ecdsa \
     -d "shufue.top,*.shufue.top"

    配置自动续签

    Certbot会默认安装证书时创建一个自动续签任务,但我们可以手动确认或者设置续签任务。

    测试自动续签

    certbot renew --dry-run

    设置定时cron任务检查过期续签

    crontab -e
    0 0 * * * certbot renew --quiet --deploy-hook "/usr/bin/systemctl reload nginx"


    这个任务会每天运行一次,检查证书是否需要续签。如果需要,它会自动续签,并在完成后重载 Nginx

  • 世界,您好!

    欢迎使用 WordPress。这是您的第一篇文章。编辑或删除它,然后开始写作吧!