#embedded-hal-driver #sound #audio #embedded-hal-i2c #i2c-driver #no-std

no-std pa-spl

PCB Artists SPL模块的嵌入式-hal I2C驱动程序

1 个不稳定版本

0.1.0 2024年8月5日

#373嵌入式开发

Download history 91/week @ 2024-08-01 19/week @ 2024-08-08

每月 110 次下载

MIT/Apache

36KB
439

PCB Artists I2C SPL模块嵌入式HAL驱动程序

Build Status

嵌入式-hal编写的Rust no_std 驱动程序,用于PCB Artists I2C 声级模块

功能

  • 读取时间窗口(TAVG 寄存器)内平均的当前SPL值,范围从35 dB到120 dB(±2 dB),频率从30 Hz到8 kHz。
  • 可调整的时间窗口(TAVG 寄存器)以从10 ms到10,000 ms平均SPL值。
  • 读取在电源周期或复位之间感应到的最小/最大SPL值。

用法

此示例使用STM32F3 Discovery开发板和USB-TTL转换器与SPL模块。

请参阅示例项目以获取完整示例。

点击显示Cargo.toml。

[package]
name = "example-read-decibel-value"
description = "Example of reading SPL with the pa-spl driver, PCB Artists SPL module, and STM32F3 Discovery"
authors = ["Jason Scott <>"]
edition = "2021"
publish = false
readme = "README.md"
version = "0.1.0"

[dependencies]
cortex-m = "0.7.7"
cortex-m-rt = "0.7.3"
cortex-m-semihosting = "0.5.0"
panic-halt = "0.2.0"
stm32f3xx-hal = { version = "0.10.0", features = ["ld", "rt", "stm32f303xc"] }
pa-spl = "0.1.0"

[[bin]]
name = "example-read-decibel-value"
test = false
bench = false

[profile.release]
codegen-units = 1
debug = true
lto = true
// Reads the latest decibel value and prints it to UART4.

#![no_main]
#![no_std]

use cortex_m::asm;
use cortex_m_rt::entry;

// Use halt as the panicking behavior.
//
// A breakpoint can be set on `rust_begin_unwind` to catch panics.
//
use pa_spl::PaSpl;
use panic_halt as _;
use stm32f3xx_hal::{delay::Delay, i2c::I2c, pac, prelude::*, serial::config, serial::Serial};

use core::fmt::Write;

// Provide an implementation of a buffer writer in order to use the write!
// macro.
//
struct BufWriter<'a> {
    buf: &'a mut [u8],
    pos: usize,
}

impl<'a> BufWriter<'a> {
    pub fn new(buf: &'a mut [u8]) -> Self {
        BufWriter { buf, pos: 0 }
    }

    pub fn as_str(&self) -> &str {
        core::str::from_utf8(&self.buf[..self.pos]).unwrap()
    }

    pub fn reset(&mut self) {
        self.pos = 0;
        self.buf.fill(0);
    }
}

// Provide implementation of write_str in order to use the buffer writer with
// the write! formatting macro.
//
impl<'a> core::fmt::Write for BufWriter<'a> {
    fn write_str(&mut self, s: &str) -> core::fmt::Result {
        let bytes = s.as_bytes();
        let len = bytes.len();

        if self.pos + len > self.buf.len() {
            return Err(core::fmt::Error);
        }

        self.buf[self.pos..self.pos + len].copy_from_slice(bytes);
        self.pos += len;
        Ok(())
    }
}

#[entry]
fn main() -> ! {
    // Get peripherals.
    //
    // take() returns an Option, which requires handling the possibility of the
    // return of an Err or None instead of the desired value, which is of type
    // pac::Peripherals in this case.
    //
    // Since this is an embedded application, it's not as simple as writing to
    // stdout. This is a minimal example, so we'll drop into an inifinite loop
    // to allow a debugger to find the failure.
    //
    let device_periphs = pac::Peripherals::take().unwrap_or_else(|| {
        loop {
            // Failed to take cortex_m::Peripherals.
            asm::nop(); // If real app, replace with actual error handling.
        }
    });

    // Get RCC peripheral and configure clocks.
    //
    // The constrain() method is used here to provide a higher-level abstraction
    // of the peripheral rather than raw register access. The method consumes
    // the raw peripheral and returns an instance of the RCC peripheral with
    // higher-level safe abstractions provided by the HAL, which is of type Rcc,
    // while setting the system clock frequency.
    //
    let mut rcc = device_periphs.RCC.constrain();
    let mut flash = device_periphs.FLASH.constrain();
    let clocks = rcc.cfgr.sysclk(48.MHz()).freeze(&mut flash.acr);

    // Set up delay capability.
    //
    // Use the same unwrap method to get the core periphs, then
    // create a delay abstraction using SysTick (SYST).
    //
    let core_periphs = cortex_m::Peripherals::take().unwrap_or_else(|| {
        loop {
            // Failed to take cortex_m::Peripherals.
            asm::nop(); // If real app, replace with actual error handling.
        }
    });
    let mut delay = Delay::new(core_periphs.SYST, clocks);

    // Get GPIO Ports B and C.
    //
    // The split method here splits out the functionality of the GPIO Port B/C
    // while taking a mutable borrow of an "enabler" that enables the clock for
    // the port at the same time. The mutable borrow allows modification of the
    // borrowed value while ensuring exclusive access.
    //
    let mut gpiob = device_periphs.GPIOB.split(&mut rcc.ahb);
    let mut gpioc = device_periphs.GPIOC.split(&mut rcc.ahb);

    // Configure pins PB6 as SCL and PB7 as SDA for I2C1.
    //
    let mut scl =
        gpiob
            .pb6
            .into_af_open_drain(&mut gpiob.moder, &mut gpiob.otyper, &mut gpiob.afrl);
    let mut sda =
        gpiob
            .pb7
            .into_af_open_drain(&mut gpiob.moder, &mut gpiob.otyper, &mut gpiob.afrl);
    scl.internal_pull_up(&mut gpiob.pupdr, true);
    sda.internal_pull_up(&mut gpiob.pupdr, true);

    // Create an instance of I2C1 with the pins.
    //
    let i2c = I2c::new(
        device_periphs.I2C1,
        (scl, sda),
        100.kHz().try_into().unwrap(),
        clocks,
        &mut rcc.apb1,
    );

    // Configure GPIO pins PC10 as TX and PC11 as RX for UART4.
    //
    let tx_pin = gpioc
        .pc10
        .into_af_push_pull(&mut gpioc.moder, &mut gpioc.otyper, &mut gpioc.afrh);
    let rx_pin = gpioc
        .pc11
        .into_af_push_pull(&mut gpioc.moder, &mut gpioc.otyper, &mut gpioc.afrh);

    // Create an instance of UART4 with the pins.
    //
    let mut uart4 = Serial::new(
        device_periphs.UART4,
        (tx_pin, rx_pin),
        config::Config::default().baudrate(115_200.Bd()),
        clocks,
        &mut rcc.apb1,
    );

    // Use the I2C1 instance to create an instance of PaSpl.
    //
    let mut pa_spl = PaSpl::new(i2c);

    // Create a buffer able to be converted to a string.
    // 
    let mut buffer: [u8; 8] = [0; 8];
    let mut buf_writer = BufWriter::new(&mut buffer);

    // Algo delay in milliseconds.
    //
    const ALGO_DELAY_MS: u16 = 500;

    loop {
        // Reset the buffer at the start of each iteration
        //
        buf_writer.reset();

        // Get SPL value from the sensor.
        //
        let spl = pa_spl.get_latest_decibel().unwrap();

        // Format string with SPL value, then covert to string.
        //
        write!(buf_writer, "SPL: {}\r", spl).unwrap();
        let spl_str = buf_writer.as_str();

        // Write the string out to the UART.
        //
        uart4.write_str(spl_str).unwrap_or_else(|_| {
            loop {
                // Failed to write to UART4.
                asm::nop(); // If real app, replace with actual error handling.
            }
        });

        // Limit algorithm to (1000 * (1 / UART_WRITE_DELAY_MS)) Hz.
        //
        delay.delay_ms(ALGO_DELAY_MS);
    }
}

测试

包括两个测试套件 - 一个包含在驱动程序源中的离标单元测试套件,该套件使用嵌入式-hal-mock,以及一个硬件在环(HIL)测试套件,该套件作为嵌套cargo项目包含,遵循模式,该模式由Ferrous Systems描述,用于测试驱动程序存储库。

要运行离标测试

cargo test

HIL测试项目位于target-tests目录中,并配置为使用probe-rs自动构建测试、闪存目标、运行测试并报告结果。

要运行HIL测试,根据仓库中提供的STM32F3 Discovery示例连接硬件(不包括TTL-USB转换器)与PCB Artists传感器的频谱分析版本,固件版本0x32或0x33(从VERSION寄存器读取的数字),然后

cd target-tests
cargo test

CI配置在每次提交和PR中运行针对Rust 1.65、稳定版、beta版和nightly版的测试。由于将本地GitHub Actions运行器附加到公开存储库存在安全问题,因此HIL测试是私下运行的,但任何人都可以在自己的硬件上本地运行它们。

最低支持的 Rust 版本 (MSRV)

本软件包保证在稳定 Rust 1.65 及以上版本上编译。它可能可以用较老版本编译,但这一点可能会在任何新的补丁版本中发生变化。

有关如何升级 MSRV 的详细信息,请参阅此处

最低支持的嵌入式 HAL 版本

TL;DR:此初始版本仅支持嵌入式-hal 0.2,后续版本中将添加对 1.0 版本的支持。

此软件包依赖于嵌入式-hal软件包,因为它是用于嵌入式-hal 的驱动程序。嵌入式版本通常比主流版本更新速度慢得多,因此存储库中的许多软件包仍然依赖于嵌入式-hal 的0.2版本,而不是最近的 1.0 版本。因此,此软件包的最低支持的嵌入式-hal 版本是 0.2,1.0 版本在此初始版本中尚不支持。将在后续版本中添加对嵌入式-hal 1.0 版本的支持。

术语表

  • API:应用程序编程接口。
  • HAL:硬件抽象层。
  • PCB:印刷电路板。
  • I2C:集成电路互连协议。
  • SPL:声压级。

许可

根据您的选择,本软件包受Apache 许可证 2.0 版MIT 许可证的许可。
除非您明确声明,否则根据 Apache-2.0 许可证定义,您有意提交给 pa-spl 的任何贡献都应按照上述方式双许可,而不附加任何额外条款或条件。

贡献

除非您明确声明,否则根据 Apache-2.0 许可证定义,您有意提交给本作品的所有贡献都应按照上述方式双许可,而不附加任何额外条款或条件。

依赖关系

~0.6-1.1MB
~24K SLoC