2、传感器数据采集基础:I2C/SPI接口的加速度计(如LSM6DSOX)数据读取,使用STM32CubeMX配置DMA传输,将数据通过串口打印到PC。
好,咱们进入第二章。这一章,说白了就是让传感器开口说话。
你想想看,一个加速度计焊在板子上,它感知到了重力、振动、倾斜,但这些信息怎么变成我们能看懂的数值?靠的就是通信接口。我个人习惯用I2C,因为连线少,两根线搞定。但如果你追求速度,SPI是更好的选择。今天咱们以LSM6DSOX为例,把数据从传感器里“掏”出来,再通过串口扔到PC上。
2.1 硬件连接与接口选择
先看硬件。LSM6DSOX这颗芯片,我特别喜欢——它内置了机器学习核,但那是后面章节的事。现在咱们只当它是一个普通的加速度计+陀螺仪。
接线其实很简单:
- I2C模式:SCL接PB6,SDA接PB7(这是STM32F4的默认I2C1引脚)。
- SPI模式:SCK、MOSI、MISO、CS各接一个GPIO。
我个人建议新手先用I2C。为什么?因为调试方便,逻辑分析仪一看波形就知道对不对。SPI虽然快,但CS时序搞错了,数据全乱套。
2.2 STM32CubeMX配置:DMA传输是关键
好,打开STM32CubeMX。咱们要干三件事:配I2C、配DMA、配串口。
第一步:配置I2C1
- 选择I2C1,Mode选I2C。
- 参数默认即可:速度100kHz(标准模式)。
- 别忘了使能内部上拉电阻——我踩过坑,忘了开上拉,I2C一直卡在ACK阶段。
第二步:配置DMA
这里我要多说两句。DMA,直接存储器访问,说白了就是让数据自己跑,不占用CPU。你想想看,如果每次读传感器数据都要CPU去等I2C传输完,那CPU啥也别干了。
配置步骤:
- 在DMA Settings选项卡里,添加I2C1_RX的DMA通道。
- Direction选Peripheral To Memory(外设到内存)。
- Mode选Circular(循环模式)——这样DMA会一直自动读取传感器数据,你只需要在回调里处理。
第三步:配置串口
- USART2,波特率115200,8N1。
- 同样使能DMA发送(USART2_TX),Mode选Normal。
生成代码。嗯,这里要注意:CubeMX生成的DMA初始化顺序可能不对。我习惯手动检查一下HAL_DMA_Init和HAL_I2C_Init的调用顺序——必须先初始化I2C,再初始化DMA。
2.3 代码实现:从寄存器到物理量
代码分三块:初始化传感器、启动DMA读取、串口打印。
2.3.1 初始化LSM6DSOX
传感器上电后,默认是睡眠模式。你得先唤醒它,配置量程和输出数据速率。
// 唤醒传感器,设置量程为±2g,ODR为104Hz
uint8_t ctrl1_xl = 0x60; // 104Hz, ±2g, 400Hz抗混叠
HAL_I2C_Mem_Write(&hi2c1, LSM6DSOX_ADDR, 0x10, I2C_MEMADD_SIZE_8BIT, &ctrl1_xl, 1, 100);
这里有个坑:LSM6DSOX的寄存器地址是8位的,但有些库用了16位地址。我刚开始就搞混了,读出来的数据全是0xFF。后来查数据手册才发现——嗯,地址长度不对。
2.3.2 启动DMA循环读取
配置好DMA后,启动传输只需要一行代码:
// 启动DMA读取,每次读6字节(X、Y、Z各2字节)
uint8_t rx_buffer[6];
HAL_I2C_Mem_Read_DMA(&hi2c1, LSM6DSOX_ADDR, 0x28, I2C_MEMADD_SIZE_8BIT, rx_buffer, 6);
等等,这里有个问题:DMA是自动运行的,但你怎么知道数据更新了?用回调函数。
void HAL_I2C_MemRxCpltCallback(I2C_HandleTypeDef *hi2c)
{
if (hi2c->Instance == I2C1)
{
// 数据已更新,可以处理了
process_sensor_data(rx_buffer);
}
}
我个人习惯在回调里设一个标志位,主循环里检查标志位再处理。为什么?因为回调里不能做耗时操作,比如串口打印——我曾经在回调里直接printf,结果系统卡死。
2.3.3 数据转换与串口打印
原始数据是16位有符号整数,需要转换成物理量。LSM6DSOX在±2g量程下,灵敏度是0.061 mg/LSB。
void process_sensor_data(uint8_t *raw)
{
int16_t acc_x = (int16_t)(raw[1] << 8 | raw[0]);
int16_t acc_y = (int16_t)(raw[3] << 8 | raw[2]);
int16_t acc_z = (int16_t)(raw[5] << 8 | raw[4]);
float ax = acc_x * 0.061f; // 单位:mg
float ay = acc_y * 0.061f;
float az = acc_z * 0.061f;
char msg[64];
sprintf(msg, "X:%.2f mg, Y:%.2f mg, Z:%.2f mg\r\n", ax, ay, az);
HAL_UART_Transmit_DMA(&huart2, (uint8_t*)msg, strlen(msg));
}
这里用HAL_UART_Transmit_DMA而不是printf,因为printf会阻塞。DMA发送是非阻塞的,CPU可以继续干别的事。
2.4 避坑指南与调试技巧
做这个实验,我遇到过几个典型问题,分享给你:
- I2C卡死在Busy状态:多半是SDA/SCL没上拉。用示波器看波形,如果电平拉不到3.3V,就是上拉电阻没接。
- DMA只传输一次就停了:检查DMA的Mode是不是Circular。如果是Normal,传输完一次就停了。
- 串口打印乱码:波特率不匹配,或者串口DMA和I2C DMA优先级冲突。把串口DMA优先级调低一点。
好了,这一章的内容就这些。你动手试试,把LSM6DSOX的数据打印到串口助手,看看静止时Z轴是不是接近1000 mg(1g)。如果是,恭喜你,传感器调通了!