这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 过程帖|用I²C读LIS3DH三轴加速度

共1条 1/1 1 跳转至页

过程帖|用I²C读LIS3DH三轴加速度

菜鸟
2026-11-08 20:25:09     打赏

任务二要把加速度计用起来,先能稳定读出三轴数据,再按统一格式导出,供后面的模型训练使用。

传感器挂在 I²C1 上,SDA 用 PB7、SCL 用 PB6,也就是排针上的 D14 和 D15。这两根是板子引出来的 I²C1,官方例程也这么接。

ed8de00eff5d4645cbbe12c835554e51_720.png

I²C 的引脚要配成开漏加内部上拉,复用功能选 AF4。H5 系列的 I²C 用的是 TIMINGR 寄存器,值跟内核时钟强相关。这一项不能照抄例程:例程给的 0x10901A4F 是按别的时钟算的,放到 PCLK1=250MHz 下,PRESC=1、SCLH=26、SCLL=79,SCL 周期只有 856 纳秒,也就是 1.17 MHz,超出 LIS3DH 的 400 kHz 上限。按 250 MHz 重算,取 PRESC=1(时间单位 8 纳秒)、SCLH=142、SCLL=169,周期正好 2.5 微秒,对应 400 kHz,SCLDEL 和 SDADEL 分别取 15 和 4 满足建立保持时间。算错这一步不会报错,读数会一直是同一帧,很难往总线速度上想。

GPIO_InitStruct.Pin       = GPIO_PIN_6 | GPIO_PIN_7;   /* PB6=SCL PB7=SDA */
GPIO_InitStruct.Mode      = GPIO_MODE_AF_OD;
GPIO_InitStruct.Pull      = GPIO_PULLUP;
GPIO_InitStruct.Speed     = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

hi2c1.Instance             = I2C1;
hi2c1.Init.Timing          = 0x10F48EA9;   /* 400kHz @ PCLK1 250MHz */
hi2c1.Init.AddressingMode  = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.NoStretchMode   = I2C_NOSTRETCH_ENABLE;
HAL_I2C_Init(&hi2c1);

模块的 SA0 可能被拉高也可能拉低,所以上电先扫一遍地址,从 0x18 和 0x19 里找到能应答 WHO_AM_I 且返回 0x33 的那个,手上这块是 0x19。配置只用写四个寄存器:CTRL_REG1 写 0x77,把 ODR 设成 400 Hz 并使能三轴;CTRL_REG4 写 0x88,打开 BDU 防止高低字节撕裂、量程 ±2g、高分辨率 12 位;CTRL_REG5 和 FIFO_CTRL 都写 0,这一版不用 FIFO。顺序上先定数据格式,最后才启动 ODR,避免配置到一半先出一批格式不对的数据。

static uint8_t lis_reg_write(uint8_t reg, uint8_t val)
{
  return HAL_I2C_Mem_Write(s_i2c, s_addr, reg, I2C_MEMADD_SIZE_8BIT, &val, 1, 20);
}

(void)lis_reg_write(0x24, 0x00);   /* CTRL_REG5 关 FIFO */
(void)lis_reg_write(0x2E, 0x00);   /* FIFO 旁路 */
(void)lis_reg_write(0x22, 0x00);   /* 不用 INT1/INT2 */
(void)lis_reg_write(0x23, 0x88);   /* BDU + ±2g + 12bit */
(void)lis_reg_write(0x20, 0x77);   /* ODR=400Hz + XYZ 使能 */

读取用直接读输出寄存器,不经过 FIFO。资料里通常推荐开 FIFO 攒一批再搬,但手上这块模块的 FIFO 只在最初填满一次,按单帧读能弹出、抽空之后就再也不进数据,一次读 32 帧的长突发还会把它彻底卡住,此后 FIFO_SRC 一直是 0。直接读反而稳:一帧 6 个字节在 400 kHz 下约 140 微秒,远小于 2.5 毫秒的采样周期,配合 BDU 不会读到高低字节跨帧的脏数据。

uint8_t raw[6];
HAL_I2C_Mem_Read(s_i2c, s_addr, 0x28 | 0x80, I2C_MEMADD_SIZE_8BIT, raw, 6, 60);

/* ±2g / 12bit 下 1 LSB = 1 mg,但数据是左对齐的,要除以 16 */
xyz[0] = (int16_t)((int16_t)(raw[0] | (raw[1] << 8)) / 16);
xyz[1] = (int16_t)((int16_t)(raw[2] | (raw[3] << 8)) / 16);
xyz[2] = (int16_t)((int16_t)(raw[4] | (raw[5] << 8)) / 16);

采样节拍得自己造。传感器 ODR 是 400 Hz,每 2.5 毫秒一帧,而 HAL_GetTick 只有 1 毫秒分辨率,取不出 2.5 这个间隔,所以用 DWT 周期计数器按机器周期数拍:250 MHz 除以 400 Hz 得 625000 个周期一帧。Cortex-M33 的 DWT 在可锁定区,先往 LAR 写解锁字再开 CYCCNT。

CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
*(volatile uint32_t *)0xE0001FB0 = 0xC5ACCE55;   /* DWT->LAR 解锁 */
DWT->CYCCNT = 0;
DWT->CTRL  |= DWT_CTRL_CYCCNTENA_Msk;

#define SAMPLE_CYC (SystemCoreClock / 400)        /* 625000 周期 = 2.5ms */

static void sample_wait(void)
{
  s_tick_next += SAMPLE_CYC;
  while ((int32_t)(DWT->CYCCNT - s_tick_next) < 0) { }
}

image.png

采满 512 点(400 Hz 下是 1.28 秒)之后从串口按 CSV 打出来,一行一组 x,y,z,单位毫克,首尾各有一行注释标记,方便脚本切分。开头那行会带上实际耗时和折算出来的采样率,便于核对节拍有没有跑偏。这份数据既是任务二的验收结果,也是后面给 NanoEdge AI Studio 的训练素材。

uart_puts("# capture 512 samples in ... ms  = ... Hz\r\n");
uart_puts("# x_mg,y_mg,z_mg  @400Hz, 512 lines\r\n");
for (i = 0; i < 512; i++)
{
  uart_i32(s_win[0][i]); uart_putc(',');
  uart_i32(s_win[1][i]); uart_putc(',');
  uart_i32(s_win[2][i]); uart_puts("\r\n");
}
uart_puts("# end\r\n");

烧进去之后先看静态值:板子平放不动,Z 轴应该稳定在 1000 毫克左右,正好是 1g 重力,X 和 Y 在 0 附近小幅波动。手指敲一下桌面,三轴会同时跳一下。如果读到的是 16000 多,说明忘了除 16;如果全零或者一直不变,先查 SDA 和 SCL 有没有接反、模块有没有供电,再核一下 I²C 的 TIMINGR 值是不是按当前主频算的。

串口输入 c 触发一次采集,会打印 512 行数据。日常看数值用实时流更方便:输入 v 打开,芯片一直在后台取样,每隔一段时间把最新一帧打一行 x,y,z 出来,再按一次 v 关闭;间隔默认 200 毫秒,输入 t 可以在 50、100、200、500 毫秒之间切换。这样不用等整窗采满,拿着板子边动边看,数值变化是跟着手的动作走的。

任务二到这里完成。下一步把电机驱动起来,让振动源可控。



共1条 1/1 1 跳转至页

回复

匿名不能发帖!请先 [ 登陆 注册 ]