Funpack 3-3 X-NUCLEO-IKS4A1: QVAR 交互式传感数据展示
该项目使用了X-NUCLEO-IKS4A1、G0B1RE,实现了QVAR 交互式传感数据展示的设计,它的主要功能为:用户可通过 QVAR 触摸电极滑动切换数据视图,显示加速度计、陀螺仪、温湿度等数据。
标签
Funpack活动
X-NUCLEO-IKS4A1
scgummy
更新2024-07-08
37

项目介绍

本项目参与 Funpack 系列活动第 3 季第 3 期,在 G0B1RE 上 使用 X-NUCLEO-IKS4A1 扩展板,来实现一个利用 QVAR 触摸电极交互的数据展示系统.

image.png

组件介绍

X-NUCLEO-IKS4A1

X-NUCLEO-IKS4A1 是集成了许多 MEMS 传感器的板卡,其中包含

  • LSM6DSO16IS:MEMS 3轴加速度计、陀螺仪
  • LIS2MDL:MEMS 3轴磁感应强度计
  • LIS2DUXS12:超低功耗 MEMS 3轴加速度计
  • LPS22DF:低功耗高精度 MEMS 压强传感器
  • SHT40AD1B:高精度超低功耗相对湿度和温度传感器
  • STTS22H:低压超低功耗温度传感器
  • LSM6DSV16X:MEMS 3轴加速度计、陀螺仪,可实现数据融合
  • MKE001A:触摸电极副板

同时板卡上提供多种跳线选项,可为开发者评估不同方案提供便利.

设计与实现思路

架构

我们采用 Rust 来设计程序,利用 embassy 生态可以构造安全高效的嵌入式程序,但是这也意味着在裸机环境下,很多传感器库需要自己实现.

image.png

QVAR 状态机

连入 QVAR 传感器后,MCU 可以读到一个 i16 范围内的数值,我们需要跟踪数值的状态来判断用户的滑动方向. 代码如下所示,我们构造了一个 Swiper 状态机,其需要一个阈值和时间间隔作为状态跟踪的依据,通过与区间起点的值对比,就可以知道是向左滑动还是向右滑动

#[derive(Debug, Format)]
pub struct Swiper {
last: Option<(Instant, i16)>,
threshold: u16,
interval: Duration,
}

#[derive(Debug, Format)]
pub enum SwiperAction {
ClickLeft,
ClickRight,
SwipeLeft,
SwipeRight,
}

impl Swiper {
pub fn new(threshold: u16, interval: Duration) -> Self {
Self {
last: None,
threshold,
interval,
}
}
pub fn put(&mut self, value: i16) -> Option<SwiperAction> {
let now = Instant::now();
if value.checked_abs().map(|v| v as u16 >= self.threshold).unwrap_or(true) {
if let Some((last, last_value)) = self.last {
if now.duration_since(last) < self.interval {
if value.is_positive() == last_value.is_positive() {
return None
} else {
self.last = Some((now, value));
if value.is_positive() {
Some(SwiperAction::SwipeRight)
} else {
Some(SwiperAction::SwipeLeft)
}
}
} else {
self.last = Some((now, value));
None
}
} else {
self.last = Some((now, value));
None
}
} else {
None
}
}
}

传感器驱动

为了实现传感器的驱动,我们需要准备好所有相关设备的手册,然后阅读我们关心的部分.

image.png

一般来说 ST 的传感器最少的初始化标配,就是将相关功能的 CTRL 寄存器中有关 FS、ODR 字段进行设置,然后就是数值转换的因子以及 bias,整理了下列表格供参考.

R 为原始值

加速度 (mg)

陀螺仪 (mdps)

温度 (°C)

相对湿度 (%)

压强 (hPa)

磁强度 (mGs)

LSM6DSO16IS

R * 0.061
(FS=±2g)

R * 4.375
(FS=±125 dps)

25 + R / 256




LIS2MDL



25 + R / 8



R * 1.5

LIS2DUXS12

R * 0.061

(FS=±2g)


25 + R * 0.045




LPS22DF



R / 100


R / 4096


SHT40AD1B



-45 + 175 * R / 65535

-6 + 125 * R / 65535



STTS22H



R / 100




我们利用 bitfield 以及 embassy-stm32 库可以将 I2C 传感器手册中比较琐碎的寄存器映射给抽象成比较方便的视角,例如下面是一个 LSM6DSO16IS 的驱动库,完整的驱动库可以见附件中 src/sensors 目录下的模块.

use bitfield::bitfield;
use embassy_stm32::i2c::{I2c, Instance, Error as I2cError};
use embassy_stm32::mode::{Mode, Async};

use super::EndianRead;

pub struct Device<'a, 't, T: Instance> {
i2c: &'a mut I2c<'t, T, Async>,
address: u8,
}

#[derive(Debug)]
pub enum Error {
IdMismatch,
I2c(I2cError)
}

impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Error::IdMismatch => write!(f, "id mismatch"),
Error::I2c(e) => write!(f, "i2c error: {:?}", e),
}
}
}

impl From<I2cError> for Error {
fn from(error: I2cError) -> Self {
Self::I2c(error)
}
}

type Result<T> = core::result::Result<T, Error>;

bitfield! {
/// e
#[derive(Default)]
pub struct Ctrl1(u8);
impl Debug;
odr, set_odr: 7, 4;
fs, set_fs: 3, 2;
}

bitfield! {
#[derive(Default)]
pub struct Ctrl2(u8);
impl Debug;
odr, set_odr: 7, 4;
fs, set_fs: 3, 2;
fs_125, set_fs_125: 1;
}

impl<'a, 't, T: Instance> Device<'a, 't, T> {
pub const ID: u8 = 0x22;

pub const REGISTER_WHO_AM_I: u8 = 0x0f;

pub const REGISTER_CTRL1: u8 = 0x10;
pub const REGISTER_CTRL2: u8 = 0x11;

pub const REGISTER_OUT_TEMP_L: u8 = 0x20;
pub const REGISTER_OUT_TEMP_H: u8 = 0x21;

pub const REGISTER_OUTX_L_G: u8 = 0x22;
pub const REGISTER_OUTX_H_G: u8 = 0x23;
pub const REGISTER_OUTY_L_G: u8 = 0x24;
pub const REGISTER_OUTY_H_G: u8 = 0x25;
pub const REGISTER_OUTZ_L_G: u8 = 0x26;
pub const REGISTER_OUTZ_H_G: u8 = 0x27;

pub const REGISTER_OUTX_L_A: u8 = 0x28;
pub const REGISTER_OUTX_H_A: u8 = 0x29;
pub const REGISTER_OUTY_L_A: u8 = 0x2a;
pub const REGISTER_OUTY_H_A: u8 = 0x2b;
pub const REGISTER_OUTZ_L_A: u8 = 0x2c;
pub const REGISTER_OUTZ_H_A: u8 = 0x2d;

pub const TEMPERATURE_MIN: i32 = -40;
pub const TEMPERATURE_MAX: i32 = 85;
pub const TEMPERATURE_ZERO: i32 = 25;
pub const TEMPERATURE_FACTOR: i32 = 256;

pub async fn new(i2c: &'a mut I2c<'t, T, Async>, address: u8) -> Result<Self> {
let mut instance = Self {
i2c,
address,
};
if instance.whoami().await? != Self::ID {
Err(Error::IdMismatch)
} else {
Ok(instance)
}
}

pub fn new_unchecked(i2c: &'a mut I2c<'t, T, Async>, address: u8) -> Self {
Self {
i2c,
address,
}
}

async fn read_le<R: EndianRead>(&mut self, addresses: &[u8]) -> Result<R> {
let mut buf = R::Array::default();
for (i, address) in addresses.iter().enumerate() {
self.i2c.write(self.address, &[*address]).await?;
self.i2c.read(self.address, &mut buf.as_mut()[i..i+1]).await?;
}
Ok(R::from_le_bytes(buf))
}

pub async fn whoami(&mut self) -> Result<u8> {
let mut buf = [0; 1];
self.i2c.write(self.address, &[Self::REGISTER_WHO_AM_I]).await?;
self.i2c.read(self.address, &mut buf).await?;
Ok(buf[0])
}

pub async fn write_ctrl1(&mut self) -> Result<()> {
let mut ctrl = Ctrl1::default();
ctrl.set_odr(0b1010);
ctrl.set_fs(0b00);
self.i2c.write(self.address, &[Self::REGISTER_CTRL1, ctrl.0]).await?;
Ok(())
}

pub async fn write_ctrl2(&mut self) -> Result<()> {
let mut ctrl = Ctrl2::default();
ctrl.set_odr(0b1010);
ctrl.set_fs_125(true);
self.i2c.write(self.address, &[Self::REGISTER_CTRL2, ctrl.0]).await?;
Ok(())
}

pub async fn read_accelerometer(&mut self) -> Result<(f64, f64, f64)> {
let sensitivity: f64 = 0.061;
let x: i16 = self.read_le(&[Self::REGISTER_OUTX_L_A, Self::REGISTER_OUTX_H_A]).await?;
let y: i16 = self.read_le(&[Self::REGISTER_OUTY_L_A, Self::REGISTER_OUTY_H_A]).await?;
let z: i16 = self.read_le(&[Self::REGISTER_OUTZ_L_A, Self::REGISTER_OUTZ_H_A]).await?;
Ok((x as f64 * sensitivity, y as f64 * sensitivity, z as f64 * sensitivity))
}

pub async fn read_gyroscope(&mut self) -> Result<(f64, f64, f64)> {
let sensitivity: f64 = 4.375;
let x: i16 = self.read_le(&[Self::REGISTER_OUTX_L_G, Self::REGISTER_OUTX_H_G]).await?;
let y: i16 = self.read_le(&[Self::REGISTER_OUTY_L_G, Self::REGISTER_OUTY_H_G]).await?;
let z: i16 = self.read_le(&[Self::REGISTER_OUTZ_L_G, Self::REGISTER_OUTZ_H_G]).await?;
Ok((x as f64 * sensitivity, y as f64 * sensitivity, z as f64 * sensitivity))
}

pub async fn read_temperature(&mut self) -> Result<f64> {
let value: i16 = self.read_le(&[Self::REGISTER_OUT_TEMP_L, Self::REGISTER_OUT_TEMP_H]).await?;
Ok(Self::TEMPERATURE_ZERO as f64 + value as f64 / Self::TEMPERATURE_FACTOR as f64)
}
}

传感器初始化与读取

我们在主程序中这样初始化设备,这样确保对应地址上的设备的 ID 与驱动中的 ID 匹配,并且将有关寄存器进行预配置.

async fn init<'a, 'b, T: Instance>(i2c: &'a mut I2c<'b, T, Async>) {
let mut device = sensors::lsm6dso16is::Device::new(i2c, 0x6a).await.unwrap();
device.write_ctrl1().await.unwrap();
device.write_ctrl2().await.unwrap();
let mut device = sensors::lis2mdl::Device::new(i2c, 0x1e).await.unwrap();
device.write_cfg_a().await.unwrap();
let mut device = sensors::lis2duxs12::Device::new(i2c, 0x19).await.unwrap();
device.write_ctrl5().await.unwrap();
let mut device = sensors::lps22df::Device::new(i2c, 0x5d).await.unwrap();
device.write_ctrl1().await.unwrap();
let mut device = sensors::sht40ad1b::Device::new(i2c, 0x44).await.unwrap();
let mut device = sensors::stts22h::Device::new(i2c, 0x38).await.unwrap();
device.write_ctrl().await.unwrap();
let mut device = sensors::lsm6dsv16x::Device::new(i2c, 0x6b).await.unwrap();
device.write_ctrl1().await.unwrap();
device.write_ctrl2().await.unwrap();
device.write_ctrl7().await.unwrap();
}

因为在设计驱动时,考虑到每一次都需要重新检查 ID 比较迂腐,因此在后续读取时将直接使用 new_unchecked 来避免不必要的总线数据传输.

fn writeln_3d_tuple_f64<const N: usize>(s: &mut String<N>, t: (f64, f64, f64)) -> fmt::Result {
writeln!(s, "[{:.3},{:.3},{:.3}]", t.0, t.1, t.2)
}
let mut device = sensors::lsm6dso16is::Device::new_unchecked(i2c, 0x6a);
let accelerometer = device.read_accelerometer().await.unwrap();
let gyroscope = device.read_gyroscope().await.unwrap();
let temperature = device.read_temperature().await.unwrap();
write!(&mut s, "A=");
writeln_3d_tuple_f64(&mut s, accelerometer);
write!(&mut s, "G=");
writeln_3d_tuple_f64(&mut s, gyroscope);
writeln!(&mut s, "T={}", temperature);

效果演示

image.png

附件下载
embedded.tgz
团队介绍
个人
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号