4. 陀螺仪数据采集:初始化配置、读取原始数据、数据校验、采样率设置、FIFO使用
各位同学好,我是老赵。今天咱们聊聊陀螺仪数据采集这件事。说实话,我在做OIS防抖的头两年,踩过最多的坑就是数据采集阶段。你想想看,算法再牛,如果源头数据是脏的,后面全是白搭。
这一节,我带你手把手把陀螺仪的数据通路打通。从寄存器配置到FIFO管理,咱们一条龙讲清楚。
4.1 初始化配置——别小看这一步
陀螺仪上电后,不是拿来就能用的。你得先跟它打个招呼,告诉它:「嘿,我要开始干活了」。这个打招呼的过程,就是初始化配置。
我个人习惯把初始化分成三步:
- 复位传感器——让内部状态回到已知的初始值
- 配置电源模式——选择正常模式还是低功耗模式
- 设置量程和带宽——决定你能测多大的角速度
以ICM-20602这颗常用芯片为例,我贴一段实际项目里用过的初始化代码:
// 初始化ICM-20602陀螺仪
void gyro_init(void) {
uint8_t who_am_i;
// 第一步:复位
spi_write_reg(REG_PWR_MGMT_1, 0x80); // 复位所有寄存器
delay_ms(100); // 等它缓过来
// 第二步:唤醒并选择时钟源
spi_write_reg(REG_PWR_MGMT_1, 0x01); // 用PLL时钟,退出休眠
// 第三步:配置量程为±2000dps
spi_write_reg(REG_GYRO_CONFIG, 0x18); // 满量程2000dps
// 第四步:配置数字低通滤波器,带宽约41Hz
spi_write_reg(REG_CONFIG, 0x03); // DLPF_CFG = 3
// 校验:读WHO_AM_I寄存器
who_am_i = spi_read_reg(REG_WHO_AM_I);
if (who_am_i != 0x12) {
// 我遇到过芯片焊接虚焊,读回来全是0xFF
// 这时候别慌,先检查硬件连接
error_handler("Gyro not found!");
}
}
4.2 读取原始数据——三轴数据怎么拿
初始化完了,就该读数据了。陀螺仪输出的是三个轴的角速度:X轴、Y轴、Z轴。每个轴16位,所以一次完整读取要拿6个字节。
这里有个关键点:数据是16位有符号数,高位在前(Big-Endian)。我见过有人直接拿两个字节拼成int16,结果符号位搞反了,出来的数据忽正忽负,查了半天。
// 读取三轴陀螺仪数据
typedef struct {
int16_t x;
int16_t y;
int16_t z;
} gyro_data_t;
gyro_data_t gyro_read_raw(void) {
uint8_t buf[6];
gyro_data_t data;
// 从0x43寄存器开始,连续读6个字节
spi_read_burst(REG_GYRO_XOUT_H, buf, 6);
// 拼成16位有符号数
data.x = (int16_t)((buf[0] << 8) | buf[1]);
data.y = (int16_t)((buf[2] << 8) | buf[3]);
data.z = (int16_t)((buf[4] << 8) | buf[5]);
return data;
}
读出来的原始值怎么转成物理单位?很简单:
角速度(dps) = 原始值 / 满量程灵敏度
比如量程是±2000dps,灵敏度是16.4 LSB/dps。那么:
实际角速度 = 原始值 / 16.4
| 量程 | 灵敏度 (LSB/dps) |
|---|---|
| ±250 dps | 131.0 |
| ±500 dps | 65.5 |
| ±1000 dps | 32.8 |
| ±2000 dps | 16.4 |
4.3 数据校验——别信数据,信校验
数据读回来了,你敢直接用吗?我不敢。SPI通信受干扰、时钟抖动、甚至线太长,都可能让数据出错。
我常用的校验手段有三个:
- WHO_AM_I校验——每次初始化后读一次,确认芯片活着
- 数据范围检查——如果读出来的值超过物理极限,直接丢弃
- 连续采样一致性——相邻两帧数据突变太大,大概率是异常
实战经验:我在做一款手机OIS时,发现偶尔会出现一个跳变很大的数据点。排查了很久,最后发现是SPI时钟频率太高(10MHz),线缆太长导致信号反射。把时钟降到5MHz后,问题消失。
// 数据校验示例
int gyro_data_check(gyro_data_t *data) {
// 检查范围:±2000dps对应的原始值范围
if (abs(data->x) > 32760 || abs(data->y) > 32760 || abs(data->z) > 32760) {
return -1; // 数据异常
}
// 检查与上一帧的差值(假设上一帧保存在prev中)
if (abs(data->x - prev.x) > 5000) {
return -2; // 突变过大
}
return 0; // 数据正常
}
4.4 采样率设置——快了不行,慢了也不行
采样率怎么选?说白了,取决于你的应用场景。
做OIS防抖,我一般用1kHz的采样率。为什么?因为手持抖动的频率主要在5-20Hz,根据奈奎斯特定理,采样率至少是最高频率的两倍。但实际中,为了留出余量,我们通常取10倍以上。
采样率通过配置内部时钟分频器来设置:
// 设置采样率为1kHz
// 内部时钟 = 32.768kHz
// 分频系数 = 32.768 / 1 = 32.768,取整为33
uint8_t sample_rate_div = 33 - 1; // 寄存器值 = 分频系数 - 1
spi_write_reg(REG_SMPLRT_DIV, sample_rate_div);
💡 小技巧:采样率不是越高越好。采样率太高,数据量暴增,MCU处理不过来,反而会丢帧。我一般控制在1kHz-2kHz之间,够用又不浪费算力。
4.5 FIFO使用——别让CPU累死
如果每次数据来了都去中断CPU,那CPU啥也别干了。这时候FIFO就派上用场了。
FIFO是个硬件缓冲区,陀螺仪自己把数据往里塞,你啥时候有空了,一次性读出来就行。
FIFO的使用流程:
- 使能FIFO,并选择要存入的数据类型(比如只存陀螺仪数据)
- 设置FIFO水印(Watermark)——当数据量达到这个值时,触发中断
- 在中断服务程序里,一次性读取FIFO中的所有数据
// 配置FIFO
void fifo_config(void) {
// 使能FIFO
spi_write_reg(REG_USER_CTRL, 0x40); // FIFO_EN = 1
// 选择存入FIFO的数据:只存陀螺仪
spi_write_reg(REG_FIFO_EN, 0x70); // GYRO_XOUT, GYRO_YOUT, GYRO_ZOUT
// 设置水印为30帧(30 * 6字节 = 180字节)
spi_write_reg(REG_FIFO_WM_TH, 30); // 水印值
}
// 读取FIFO数据
void fifo_read_data(uint8_t *buffer, uint16_t count) {
uint16_t fifo_count;
// 先读FIFO中还有多少数据
fifo_count = spi_read_reg(REG_FIFO_COUNT_H) << 8;
fifo_count |= spi_read_reg(REG_FIFO_COUNT_L);
if (fifo_count >= count) {
// 一次性读出所有数据
spi_read_burst(REG_FIFO_R_W, buffer, count);
}
}
4.6 本章知识体系
下面这张图,是我画的数据采集整体流程。你照着这个走,基本不会出大问题:
嗯,数据采集这块,说白了就是「配置好、读得对、验得准」。每一步都有坑,但只要你按照这个流程走,基本能稳。
下一节咱们会聊陀螺仪数据的预处理,包括去噪和温漂补偿。到时候我会分享一个我调了三个月的温漂补偿算法,保证让你少走弯路。