STM32F4通过SPI驱动ICM20648六轴传感器:CubeMX配置与验证

发布时间:2026/9/16 16:56:53
STM32F4通过SPI驱动ICM20648六轴传感器:CubeMX配置与验证 简介一套完整的STM32F4通过SPI接口驱动ICM20648六轴惯性传感器IMU的工程源码面向嵌入式开发者、电子竞赛参赛者及需要快速完成运动数据采集的硬件工程师。工程基于STM32CubeMX HAL库搭建从SPI接口初始化、时钟与引脚配置到寄存器读取加速度计、陀螺仪原始数据形成了完整的传感器通信链路可直接移植至无人机、机器人、可穿戴设备等项目。压缩包共379个文件大小19.25MB以118个.h头文件和105个.c源文件为主包含HAL底层驱动库、用户应用代码并附Keil工程文件.uvprojx、链接脚本.sct、批处理脚本.bat以及编译生成的.hex固件和.map映射文件既可用于工程复现也便于烧录调试。已有609人学习下载代码结构清晰、模块划分明确对理解SPI时序、ICM20648寄存器操作和STM32F4外设配置均有很好的参考价值。1. 为什么 STM32F4 驱动 ICM20648 值得绕开 I2C 走 SPI把 ICM20648 接到 STM32F4 的 SPI 总线上表面上是换一种通信方式实际上把你从 I2C 的地址冲突、400kHz 上限和软件模拟时的时序抖动里解放出来。这颗六轴传感器自带 4KB FIFO 和片上数字运动处理器配合 STM32F4 的硬件 SPI可以按 1kHz 帧率连续产出数据CPU 只要在片选周期里做搬数据这件事。对于从 MPU6050 迁移过来的人ICM20648 的 banked 寄存器会让你第一次读 WHO_AM_I 就意识到这不是老一套对于刚用 CubeMX 生成工程的新手只要把 SPI 的模式和片选规则定对剩下的问题就集中在读到的数字是否可信。我下面写的连线、CubeMX 参数和最小验证代码就是顺着这条线展开的。2. SPI 引脚、时序模式与片选设计ICM20648 上板前先拨清楚的三件事2.1 先分清单片机引脚别把 SDIO 和 SDO 接反ICM20648 是六轴惯性传感器接口上只留了 SCLK、SDIO、SDO、CS 这四根 SPI 线。SDIO 是主机发命令和数据用的接到 STM32F4 的 MOSISDO 是从机输出数据用的接到 STM32F4 的 MISO。这块芯片不带硬件仲裁接反不会有任何反馈结果就是读 WHO_AM_I 永远抓不到有效字节。我在 STM32F4 上用 SPI1 做验证时引脚分配如下ICM20648 引脚STM32F4 引脚说明VDD3.3V / 1.8V 视批次逻辑电平不一致要加转换不能硬接GNDGND电源地平层靠近传感器布局SCLKPA5 / SPI1_SCK由 SPI1 外设控制SDIOPA7 / SPI1_MOSI写命令和写寄存器共用SDOPA6 / SPI1_MISO读寄存器输出数据CS任意 GPIO如 PB6用软件片选时由 GPIO 控制INT1任意 GPIO如 PB7数据就绪中断输出注意 SDO 在 I2C 模式下还是地址引脚 AD0。如果三极管或上拉电阻把它拉到一个固定电平SPI 的 MISO 方向不会被影响但 I2C 地址会被锁死。这个引脚电平在 SPI 模式下没有额外作用不过调试时最好别让它悬空省得逻辑分析仪上出现莫名其妙的噪声。建议在 PCB 或者杜邦线上把 SCLK 和 SDIO 留出一段短引线方便夹逻辑分析仪探头。惯性传感器对时序敏感SPI 时钟边沿上的过冲比数据线电平更容易掩盖问题我一般会把示波器探头夹在 SCLK 上而不是 SDO 上。2.2 SPI Mode 3 不是玄学CPOL/CPHA 与读取时序的对应关系ICM20648 的寄存器访问帧和普通 SPI Flash 不太一样CS 拉低后主机发出的第一个字节是高 7 位寄存器地址加最低位读写标志随后的 SCK 边沿才真正搬数据。如果 CPOL 和 CPHA 反了地址字节会被从机误采后面读回来的数据自然全错。这颗传感器最常见的接法是 SPI Mode 3也就是 CPOL1、CPHA1。CubeMX 里对应两个选项Clock Polarity (CPOL) 选 HighClock Phase (CPHA) 选 2 Edge。SCLK 空闲时保持高电平第一个边沿输出地址位第二个边沿采样 MISO 上的数据。这里不是让你背 MODE 数字而是记住一个判断方法先读 WHO_AM_I能返回 0x48 就说明时序模式对了返回全 0x00 或者全 0xFF 就去交换 CPOL 和 CPHA。SCK 时钟频率也要压住。STM32F4 的 SPI1 挂在 APB2 总线上CubeMX 里显示的分频数是相对于系统时钟算的。我习惯先把 Prescaler 设成 16跑出 5.25MHz 左右把链路跑通后再循序渐进地提高。软件模拟 SPI 可以慢慢读硬件 SPI 一旦频率上去线缆长度和上拉强度都会变成变量。2.3 硬件片选和软件片选怎么选ICM20648 要求 CS 在完整的寄存器访问期间保持低电平地址字节和数据字节共用一条 SCLK 时钟线CS 中间抖一下就会丢掉字节边界。STM32F4 的硬件 NSS 在连续传输多个字节时通常也能满足这个条件但配置多一个环节多一个出错点。对比项软件片选GPIO硬件片选NSS时序可控性高可手动控制拉低时机依赖 SPI 外设状态机调试难度低逻辑清楚要高NSS 配置错会一直读零多设备共享总线需要仔细防止总线争用硬件自动切换更省心推荐程度调试例程首选量产或 RTOS 多任务时考虑我写这颗传感器例程时一律用软件片选。CS 拉低前先把 GPIO 配成推挽输出传输完成后再拉高这样即使 SPI 外设处于空闲状态也不会把 CS 长期压在地上。icm20648_cs_low(); spi_write_byte(reg | 0x80); // 带读标记的寄存器地址 spi_write_byte(0x00); // 数据阶段MISO 上出现寄存器值 icm20648_cs_high();这段伪代码表达了第 3 章 HAL 函数底层的完整事务模型CS 低电平从命令字节开始到数据字节结束。实际工程里CS 拉低后最好加一个 100ns 左右的延时给从机地址采样准备时间但 STM32F4 的 HAL 库函数调用本身已经有足够开销通常不需要额外插入 nop。3. CubeMX 里把 SPI1 配置成 Mode 3再用 HAL 库读 WHO_AM_I 验证通信3.1 CubeMX 参数表先把 STM32F4 的 SPI1 打开引脚映射会自动出现在 PA5、PA6、PA7 上。CubeMX 左侧 Parameter Settings 里需要改动的参数不多照着下面这张表填就行参数名设置值理由ModeFull-Duplex MasterICM20648 需要主机发命令、从机回数据Hardware NSS SignalDisable使用软件片选避免 NSS 抢占 GPIOData Size8 bits寄存器地址和数据都是单字节不需要 16 位First BitMSB FirstInvenSense 寄存器协议固定 MSB 先发Clock Polarity (CPOL)High对应 SPI Mode 3Clock Phase (CPHA)2 Edge数据在第二个边沿采样Prescaler16约 5.25MHz留足时序余量CRCDisableSPI CRC 会污染单字节寄存器帧生成代码之前把片选引脚在 GPIO 标签里设成 Output Push Pull、High Level。上电默认 CS 高电平让芯片先停留在非选中状态等到第一笔 SPI 事务开始才拉低。有些人会把 FSYNC 也配一个 GPIO 输出低电平。实际上 ICM20648 的 FSYNC 不用时可以悬空但悬浮引脚在电噪声大的电机驱动板上有可能引入异常采样我会顺手在初始化阶段把 FSYNC 对应的 GPIO 设为 0。3.2 读 WHO_AM_I 的最小 HAL 函数下面是完整的读写原语。这段代码能直接放进 STM32CubeIDE 生成的工程里只需要把 hspi1 和片选引脚改成你自己工程里的句柄和宏即可。/* icm20648_spi.c */ #include main.h #include spi.h extern SPI_HandleTypeDef hspi1; #define ICM20648_CS_LOW() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET) #define ICM20648_CS_HIGH() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET) #define ICM20648_WHO_AM_I 0x00 /* ICM20648 的 Bank0 地址按手册调整 */ static void icm20648_write_reg(uint8_t reg, uint8_t value) { uint8_t tx[2]; tx[0] reg 0x7F; /* 写操作时最高位为 0 */ tx[1] value; ICM20648_CS_LOW(); HAL_SPI_Transmit(hspi1, tx, 2, HAL_MAX_DELAY); ICM20648_CS_HIGH(); } static uint8_t icm20648_read_reg(uint8_t reg) { uint8_t tx[2] { reg | 0x80, 0x00 }; uint8_t rx[2]; ICM20648_CS_LOW(); HAL_SPI_TransmitReceive(hspi1, tx, rx, 2, HAL_MAX_DELAY); ICM20648_CS_HIGH(); return rx[1]; /* 第二个字节才是从机回的数据 */ } uint8_t icm20648_check_id(void) { return icm20648_read_reg(ICM20648_WHO_AM_I); }HAL_SPI_TransmitReceive是同步发送和接收接口。发送数组第一个字节是带读标记的寄存器地址第二个字节是填充字节作用只是产生 SCLK 时钟从机在第二个字节对应的时钟周期内把寄存器值放到 SDO 上所以返回值取rx[1]。HAL_MAX_DELAY表示不超时。在调试阶段这样写最省事一旦 SPI 引脚没配对程序会卡死在等待 SCLK 完成的状态。量产代码里我会改成 100 毫秒超时并在返回出错状态时做重试。3.3 返回 0x00 或 0xFF 的排查顺序WHO_AM_I 是检验通信的第一道关卡。常见返回值有两种错误第一个是 0x00。看到全 0 先查 MISO 电平。STM32F4 的 GPIO 要复用为 SPI但 MISO 引脚本身默认有上下拉配置如果在 CubeMX 里把它设成了 OutputHAL 库根本采不到数据。第二个是 0xFF表示 SDO 始终被拉高多半是 CPOL/CPHA 配错了方向或者 IC 的 CS 根本没拉低。还有一个隐蔽问题从机地址和主机地址的字节序。ICM20648 的寄存器协议要求 MSB 先发CubeMX 默认也是 MSB First但如果之前跑过 W25Q64 之类的 SPI Flash把 First Bit 改成了 LSB First读回来的 ID 就会变成按位翻转后的值看着像乱码却不等于 0xFF。遇到这种中间状态先把工程里 SPIn 的其他参数截图和 CubeMX 默认值对比一下。4. 寄存器初始化与 14 字节批量读取把 ICM20648 的原始惯量数据搬回内存4.1 唤醒、复位与默认量程ICM20648 上电后默认处于低功耗态直接用 SPI 读加速度寄存器大概率读到全零。我的最小初始化顺序是先延时等待电源平稳再写 PWR_MGMT_1 复位最后清除睡眠位并确认 WHO_AM_I。注意这颗芯片的寄存器是分 bank 的REG_BANK_SEL决定当前访问哪个地址空间。默认停在 Bank 0PWR_MGMT_1 和 WHO_AM_I 都在 Bank 0 里所以初始化阶段不用切 bank。如果你拿到的寄存器手册是更早的版本地址定义可能有差异以下宏在实际移植时以自己手上的手册为准。#define ICM20648_PWR_MGMT_1 0x06 #define ICM20648_WHO_AM_I 0x00 uint8_t icm20648_init(void) { uint8_t id; HAL_Delay(50); /* 复位所有寄存器回到上电默认状态 */ icm20648_write_reg(ICM20648_PWR_MGMT_1, 0x80); HAL_Delay(100); /* 清除睡眠位选内部振荡器作为时钟源 */ icm20648_write_reg(ICM20648_PWR_MGMT_1, 0x01); HAL_Delay(10); id icm20648_read_reg(ICM20648_WHO_AM_I); if (id ! 0x48) { return 1; } return 0; }这里不修改 GYRO_CONFIG 和 ACCEL_CONFIG意味着陀螺仪量程是默认的 ±250dps加速度计量程是默认的 ±2g。先跑通再调量程比一开始就把所有寄存器都配置好更容易排错。如果你需要更高量程比如把陀螺仪调到 ±500dps记得后面的换算常数也要同步改。4.2 burst read 批量读取 14 字节原始数据连续读取比单字节读更实用。ICM20648 支持从某个起始寄存器地址开始自动递增输出所以我只要把 CS 拉低发出起始地址然后连续接收 14 个字节就能一次拿到加速度 X/Y/Z、温度和陀螺仪 X/Y/Z。typedef struct { int16_t accel[3]; int16_t temp; int16_t gyro[3]; } icm20648_raw_t; #define ICM20648_ACCEL_OUT 0x2D /* Bank0 中加速度首个输出寄存器 */ icm20648_raw_t icm20648_read_raw(void) { icm20648_raw_t raw; uint8_t tx_buf ICM20648_ACCEL_OUT | 0x80; uint8_t rx_buf[14]; ICM20648_CS_LOW(); HAL_SPI_Transmit(hspi1, tx_buf, 1, HAL_MAX_DELAY); HAL_SPI_Receive(hspi1, rx_buf, 14, HAL_MAX_DELAY); ICM20648_CS_HIGH(); raw.accel[0] (int16_t)((rx_buf[ 0] 8) | rx_buf[ 1]); raw.accel[1] (int16_t)((rx_buf[ 2] 8) | rx_buf[ 3]); raw.accel[2] (int16_t)((rx_buf[ 4] 8) | rx_buf[ 5]); raw.temp (int16_t)((rx_buf[ 6] 8) | rx_buf[ 7]); raw.gyro[0] (int16_t)((rx_buf[ 8] 8) | rx_buf[ 9]); raw.gyro[1] (int16_t)((rx_buf[10] 8) | rx_buf[11]); raw.gyro[2] (int16_t)((rx_buf[12] 8) | rx_buf[13]); return raw; }执行逻辑是先发送一个地址字节HAL_SPI_Receive内部会连续产生 14 个字节的 SCLK同时把从机 SDO 上输出的数据写进rx_buf。因为 CS 在整个过程中保持低电平从机会自动把地址递增下去直到读满 14 个字节才拉高片选。HAL_SPI_Transmit和HAL_SPI_Receive是两次独立的同步调用但它们之间共享 CS 低电平状态所以底层仍然是同一个 SPI 事务。要保证从机地址和后续数据之间没有多余延时STM32F4 的 HAL 库调用间隔很短正常调到 5.25MHz 时不会丢字节。4.3 原始值换算成 g 和 dps 的常数选择原始值是补码形式的有符号数。把两个相邻字节拼成一个int16_t后加速度按 16384 LSB/g 换算陀螺仪按 131 LSB/dps 换算。float ax_g (float)raw.accel[0] / 16384.0f; float gx_dps (float)raw.gyro[0] / 131.0f;这两个常数对应的是默认量程。改变量程后加速度常数会变成 8192、4096 等陀螺仪常数变成 65.5、32.8 等。换算结果不是浮点性能瓶颈但对于 STM32F4 做姿态解算的场景可以在读取后就地转成 float避免后续整型乘除反复溢出。如果你把传感器水平放在桌面上加速度 Z 轴读数应该在 16384 附近X/Y 轴接近 0。陀螺仪三个轴都接近 0但不会严格等于 0因为传感器本身有零偏这一点在第 5 章会用来验证数据链路。5. 静止校验法用 SPI-ICM20648 例程上电后的第一组数据抓住时序问题5.1 采集 1000 个静止样本驱动读通之后第一件事不是接 DMP而是把传感器放在水平桌面上连续打印原始数据。我用串口每 5 毫秒打印一行printf(%d,%d,%d,%d,%d,%d\r\n, raw.accel[0], raw.accel[1], raw.accel[2], raw.gyro[0], raw.gyro[1], raw.gyro[2]);采集大约 1000 行保存成imu.csv然后用 Python 在电脑上做一次统计。判断标准是加速度模长落在 1g 附近陀螺仪三个轴均值不超过每秒几度。import math import statistics rows [[int(v) for v in line.split(,)] for line in open(imu.csv)] acc_norm [math.sqrt(r[0]**2 r[1]**2 r[2]**2) / 16384.0 for r in rows] print(acc norm mean:, statistics.mean(acc_norm)) print(acc norm stddev:, statistics.pstdev(acc_norm)) gyro_offset [statistics.mean([r[3], r[4], r[5]]) / 131.0 for r in rows] print(gyro offset dps:, gyro_offset)加速度模长的标准差如果超过 0.02g说明数据里混入了明显抖动陀螺仪均值如果偏向同一方向超过 5dps可能是传感器没放平也可能是字节拼接时高低位反了。注意高字节在前、低字节在后如果拼反静止时数字会来回跳而不是恒定接近 0。5.2 校验结果对应到 SPI 层排查加速度模长正常、陀螺仪零偏也正常说明 SPI 的片选、SCLK 和数据线三件事都对了这时再去调量程和 DMP 也放心。如果出现两边数值都异常优先检查两处。第一处是 CS 有没有在整个读取期间保持低。用逻辑分析仪抓 CS 和 SCLK 的叠加波形如果 CS 在命令字节结束之后出现毛刺HAL_SPI_Receive的时序就会失效。第二处是时钟模式。Mode 3 下 SCLK 空闲为高HAL_SPI_TransmitReceive期间如果有上升沿抖动从机可能把地址采错。这套静止校验方法的好处是不依赖任何外部设备串口加一个水平桌面就能做。我每次移植新 PCB 上的 SPI-ICM20648 例程都会先跑这一步确认原始数据的统计特性无异常后再往上加 FIFO 和四元数融合。这样后面任何姿态误差都可以直接往算法层和传感器标定层去找不用回头怀疑 SPI 驱动本身。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询