Article count:10 Read by:28157

Featured Content
Account Entry

How to safely operate MCU registers in Rust

Latest update time:2024-10-08
    Reads:

In embedded development, it is inevitable to operate the peripheral registers of the MCU. In , C/C++ a series of register structures and bit field offset macros, bit field mask macros, etc. are usually defined according to the peripheral register list in the chip manual. C/C++ Although the register operation interface of makes the execution efficiency very high and easy to write, it is usually necessary to carefully check the valid range of register offset, bit field offset, and bit field value. Therefore, driver engineers need to be very careful to define a large number of hardware-related interfaces by comparing with the chip manual, which is a test of patience and carefulness.

Generally speaking, the interfaces of peripheral registers are generally consistent, and the only differences are the register names, offset values, bit field definitions, etc. Defining these interfaces requires a lot of repetitive work.

typedef struct 
{
uint32_t Pin; /*!< Specifies the GPIO pins to be configured.
This parameter can be any value of @ref GPIO_pins */
uint32_t Mode; /*!< Specifies the operating mode for the selected pins.
This parameter can be a value of @ref GPIO_mode */
uint32_t Pull; /*!< Specifies the Pull-Up or Pull-Down activation for the selected pins.
This parameter can be a value of @ref GPIO_pull */
uint32_t Speed; /*!< Specifies the speed for the selected pins.
This parameter can be a value of @ref GPIO_speed */
uint32_t Alternate; /*!< Peripheral to be connected to the selected pins
This parameter can be a value of @ref GPIOEx_Alternate_function_selection */
} GPIO_InitTypeDef;

#define GPIO_MODE_INPUT (0x00000000u) /*!< Input Floating Mode */
#define GPIO_MODE_OUTPUT_PP (0x00000001u) /*!< Output Push Pull Mode */
#define GPIO_MODE_OUTPUT_OD (0x00000011u) /*!< Output Open Drain Mode */
#define GPIO_MODE_AF_PP (0x00000002u) /*!< Alternate Function Push Pull Mode */
#define GPIO_MODE_AF_OD (0x00000012u) /*!< Alternate Function Open Drain Mode */
#define GPIO_MODE_ANALOG (0x00000003u) /*!< Analog Mode */

So, Rust is it also necessary to define a large number of structures and constant macros in the development of MCU drivers? Of course not!

Rust It is a high-level language. Its biggest feature is security. It is also very suitable for system-level development and can directly operate the underlying memory. Rust It can C/C++ operate raw pointers like , such as Systick The driver can be defined as follows

use volatile_register::{RW, RO}; 

pub struct SystemTimer { 
    p: &'static mut RegisterBlock 
}

#[repr(C)]  
struct RegisterBlock {  
    pub csr: RW<u32>,  
    pub rvr: RW<u32>,  
    pub cvr: RW<u32>,  
    pub calib: RO<u32>,  
}

impl SystemTimer {  
    pub fn new() -> SystemTimer {  
        SystemTimer {  
            p: unsafe { &mut *(0xE000_E010 as *mut RegisterBlock) }
        }
    }

    pub fn get_time(&self) -> u32 {  
        self.p.cvr.read()   
    }

    pub fn set_reload(&mut self, reload_value: u32) {
        unsafe { self.p.rvr.write(reload_value) }   
    }
}

pub fn example_usage() -> String {   
    let mut st = SystemTimer::new();   
    st.set_reload(0x00FF_FFFF);   
    format!("Time is now 0x{:08x}", st.get_time())   
}

This method is very similar to the way of writing drivers in C/C++ by directly operating the peripheral register address. Obviously, it can be seen that there are a lot of unsafe marked statements, indicating that the driver may call too many unsafe interfaces.

Rust officially recommends using another more elegant and convenient way to operate registers.

As shown in the figure above, add a ( peripheral access library) Rust between the MCU register and the hal library , and use to describe the MCU and peripheral register abstract interface layer separately. This layer does not need to be manually written, and is automatically generated by the tool . PAC will provide the operation interface of all registers, automatically generate interfaces according to the read-only, write-only, and read-write permissions of the registers, and generate the attributes of the bit field and the corresponding read or write interface. Ensure that the software layer does not exceed the constraints of the hardware, thereby avoiding undefined behavior. PAC Peripheral Access Crate PAC svd2rust

Take the STM32 peripheral clock enable register as an example:

pac The provided interfaces are used as follows:

let dp = pac::Peripherals::take().unwrap();
dp.RCC
  .ahb1enr
  .write(|w| w.gpioaen().set_bit().gpiocen().set_bit());
loop {
    // Read PC13 Input Value
    if !dp.GPIOC.idr.read().idr13().bit() {
        // Code if PC13 Low
    } else {
        // Code if PC13 High
    }
}

This method can be called very directly without paying attention to the commonly used shift, AND, OR, and non-equal operations in register operations. hal When writing , you only need to pay attention to the register name and bit field name.

While providing a convenient interface, the execution efficiency of the compiled binary code is almost the same as that of the assembly code.

refer to

STM32F4 Embedded Rust at the PAC: svd2rust (theembeddedrustacean.com)

PACs - Comprehensive Rust ???? (google.github.io)

Rust Embedded terminology - Discovery (rust-embedded.org)


 
EEWorld WeChat Subscription

 
EEWorld WeChat Service Number

 
AutoDevelopers

About Us Customer Service Contact Information Datasheet Sitemap LatestNews

Room 1530, Zhongguancun MOOC Times Building,Block B, 18 Zhongguancun Street, Haidian District,Beijing, China Tel:(010)82350740 Postcode:100190

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号