尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32F407读取JY901九轴姿态模块:串口与IIC完整实现与避坑指南

STM32F407读取JY901九轴姿态模块:串口与IIC完整实现与避坑指南 简介面向需要快速实现 STM32F407 与 JY-901 九轴姿态传感器模块通信的嵌入式开发者压缩包提供了一套完整的串口与 IIC 接口工程适用于环境监测、智能家居等物联网场景。包内共 158 个文件约 620KB以 60 个 C 源码、64 个头文件为主另含 Keil 工程文件uvproj/uvopt、链接映射、文本说明及备份文件覆盖传感器初始化、寄存器配置、数据解析、AHRS 姿态解算等完整流程从底层驱动到上层应用均有对应源码目录结构清晰方便按需检索和快速移植。工程围绕 IMU_AHRS 展开实现了 JY-901 模块的加速度、角速度与姿态角数据读取串口与 IIC 两种通信方式均已实测可用可帮助开发者避开协议解析与外设配置中的常见陷阱直接获得稳定、可用于显示与控制的有效数据。目前已有 847 人浏览学习适合具备 STM32 基础并正在整合 JY-901 模块或需要参考 IMU 解算代码的嵌入式工程师。1. 用 F407 读 JY-901东西到手先别急着接串口JY-901 这颗九轴姿态模块在淘宝上一抓一大把标称串口和 IIC 都能读。但真正把它接到 STM32F407 上你会发现坑全在“以为能读”和“读得对”之间——模块上电默认波特率 9600而多数人手里的 F407 板子外部晶振是 8M 或 25MHAL 库初始化串口时稍有偏差收到的就是乱码更隐蔽的是模块默认从 0x55 0x51 输出加速度但固件版本不同、寄存器配置不同输出的数据类型和字节序会变。这个压缩包里的东西说白了就是一套Keil MDK 工程备份工程名 IMU_AHRS 暴露了它的定位把 JY-901 当成 IMU 数据源在 F407 上跑姿态解算。包里既有串口轮询读取的初始化代码也有 IIC 读取角度的寄存器配置还带了几份调试时的.uvgui窗口布局备份。适合刚接触 901 模块、手里正好是 F4 系列芯片、想把串口和 IIC 两条路都打通的人。下文按“工程文件结构 → JY-901 通信协议 → 串口解析实现 → IIC 寄存器读写 → 移植与排错”的顺序展开每步都给可直接抄的代码。2. 工程文件解剖.uvgui 和 .bak 到底是干什么的2.1 备份文件不一定都能删压缩包根目录下的文件分为几类先分清功能再动手不然全选加入工程会报一堆重复定义文件类型作用使用建议IMU_AHRS.uvproj.bakKeil 工程原始备份真正可编译的工程文件改名为.uvproj后双击打开IMU_AHRS.uvopt.bak工程选项备份调试器、Flash 下载算法等与.uvproj配套一并改名IMU_AHRS.uvgui_Administrator.bak窗口布局记录非源码删除不影响编译IMU_AHRS.uvgui_zw.bak另一个用户的窗口布局同上可删.uvgui这类文件记录的是“哪个窗口停靠在哪个位置、寄存器窗口打开了哪几个外设观察页”经常同步进 git 后导致团队成员打开工程布局错乱。建议从工程目录中直接排除只保留.uvproj、.uvopt和源码目录。2.2 Keil 工程内含的三个核心模块打开工程后Project窗口里大致能看到三组文件对应 JY-901 通信的三个层次串口驱动层usart.c / stm32f4xx_hal_uart.c负责初始化 UART配置 GPIO 复用为 USART 功能并实现单字节或帧中断接收。JY-901 的数据主动上报频率默认 100Hz串口中断每 10ms 触发一次解析数据量不大适合用中断方式而不是 DMA。IIC 驱动层i2c.c / soft_i2c.c如果选 IIC 通信常见实现是 GPIO 模拟 IIC 时序因为 JY-901 的 IIC 从机地址是 7 位地址 0x50硬件 IIC 在 F407 上要处理 NACK 和时钟延展反而不如模拟的稳定。代码里一般会提供IIC_ReadReg()和IIC_WriteReg()两个函数。姿态解析层imu.c / ahrs.c把串口或 IIC 取回的原始数据换算成物理量例如加速度原始值除以 32768 得到 g角速度除以 328 得到 °/s。如果做姿态解算还要在此基础上跑四元数更新和欧拉角转换。3. JY-901 通信协议一帧数据拆开来看3.1 串口帧格式0x55 开头校验和收尾JY-901 的串口协议固定以0x55为帧头第二字节 D1 指示数据类型后跟 D2~D4 的数据字段和 D5 校验和。六种常用数据类型对应关系如下数据类型标识含义数据内容0x51加速度Ax 低字节、Ax 高字节、Ay 低、Ay 高、Az 低、Az 高0x52角速度同上结构三轴角速度0x53角度欧拉角Roll、Pitch、Yaw 各 16 位0x54磁场三轴磁力计原始值0x55气压高度气压值、高度值0x59四元数Q0~Q3 各 16 位需固件版本 ≥ V1.6校验和算法是Sum 0x55 0x51 D2 D3 D4仅取低 8 位收到帧后做同样的累加与 D5 相等即认为帧有效。注意JY-901 没有帧尾标识所以解析时必须先找到 0x55再判断第二个字节是否为合法类型否则一帧的数据错位会导致后面所有帧解析失败。3.2 寄存器读写协议串口也能写寄存器很多人以为只有 IIC 才能配置 JY-901实际上串口同样能改寄存器。写入格式0xFF 0xAA 0x69 寄存器地址 数据低字节 数据高字节例如把串口输出内容配置为仅输出加速度和角度关闭磁场和四元数需要操作 0x02 寄存器输出内容寄存器。这种设计让调试阶段用串口助手就能完成配置不必先接 IIC。3.3 IIC 协议从机地址和寄存器映射JY-901 的 IIC 从机地址固定为0x507 位地址。寄存器地址与串口数据类型对应例如读地址0x34是角度寄存器连读 6 字节得 Roll、Pitch、Yaw。数据格式与串口完全一致16 位有符号整数先低字节后高字节。IIC 读取时需先写寄存器地址再发起读操作时序要求支持重复起始位repeated START。4. 串口读取完整实现HAL 库中断接收 状态机解析4.1 串口初始化与中断接收F407 上使用 USART1引脚 PA9/PA10 复用为 TX/RX。注意 F407 的 GPIO 复用必须调用HAL_GPIO_Init时指定Alternate为GPIO_AF7_USART1这一点和 F103 的“直接开复用推挽”不同也是从 F1 移植到 F4 最容易踩的坑。void MX_USART1_UART_Init(void) { huart1.Instance USART1; huart1.Init.BaudRate 9600; // JY-901 默认波特率 huart1.Init.WordLength UART_WORDLENGTH_8B; huart1.Init.StopBits UART_STOPBITS_1; huart1.Init.Parity UART_PARITY_NONE; huart1.Init.Mode UART_MODE_TX_RX; huart1.Init.HwFlowCtl UART_HWCONTROL_NONE; HAL_UART_Init(huart1); __HAL_UART_ENABLE_IT(huart1, UART_IT_RXNE); // 接收中断 HAL_UART_Receive_IT(huart1, (uint8_t *)uart1_rx_byte, 1); // 单字节接收 }代码逻辑说明HAL_UART_Receive_IT最后一个参数传1表示每收一个字节触发一次中断回调而不是收满一帧才回调。JY-901 每包数据 11 字节帧头 10 字节如果改用HAL_UART_Receive_IT(huart1, rx_buffer, 11)则必须保证两次回调之间恰好收到 11 字节一旦错位就永远凑不齐一帧。用单字节接收 状态机解析更稳妥。参数说明波特率必须与模块配置一致JY-901 出厂默认是 9600。如果之前被人改过波特率串口助手先发0xFF 0xAA 0x00 寄存器地址 波特率低 波特率高恢复默认。4.2 状态机接收与帧解析#define IMU_FRAME_HEAD1 0x55 #define IMU_ACCEL 0x51 #define IMU_ANGLE 0x53 #define IMU_QUAT 0x59 enum { WAIT_HEAD, WAIT_TYPE, WAIT_DATA, WAIT_SUM }; static uint8_t imu_state WAIT_HEAD; static uint8_t imu_buf[8]; static uint8_t imu_cnt 0; void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart-Instance USART1) { imu_parser_byte(uart1_rx_byte); HAL_UART_Receive_IT(huart1, (uint8_t *)uart1_rx_byte, 1); } } void imu_parser_byte(uint8_t data) { switch (imu_state) { case WAIT_HEAD: if (data IMU_FRAME_HEAD1) { imu_state WAIT_TYPE; imu_cnt 0; } break; case WAIT_TYPE: if (data IMU_ACCEL || data IMU_ANGLE || data IMU_QUAT) { imu_type data; imu_state WAIT_DATA; } else if (data ! IMU_FRAME_HEAD1) { imu_state WAIT_HEAD; } break; case WAIT_DATA: imu_buf[imu_cnt] data; if (imu_cnt 6) imu_state WAIT_SUM; break; case WAIT_SUM: { uint8_t sum 0x55 imu_type; for (int i 0; i 6; i) sum imu_buf[i]; if (sum data) imu_data_ready(imu_type, imu_buf); imu_state WAIT_HEAD; break; } default: imu_state WAIT_HEAD; } }代码逻辑说明状态机四个状态对应一帧数据的四个阶段。WAIT_HEAD 状态寻找帧头 0x55WAIT_TYPE 判断数据类型同时兼容“上一帧丢字节导致帧头连续出现”的情况——如果收到 0x55 则留在当前状态继续收类型否则回退到 WAIT_HEADWAIT_DATA 累计 6 个数据字节WAIT_SUM 做校验和比对通过后交给数据解析函数。参数说明数据长度写死为 6 字节因为加速度、角速度、角度都是三轴 × 16 位 6 字节。若开启四元数输出0x59同样 6 字节Q0~Q2 暂存Q3 需要读下一帧或改用 8 字节解析。若需要支持 0x54 磁场也是 6 字节可直接扩展imu_type的判断范围。4.3 数据换算与单位void imu_data_ready(uint8_t type, uint8_t *buf) { int16_t raw[3]; raw[0] (int16_t)(buf[0] | (buf[1] 8)); raw[1] (int16_t)(buf[2] | (buf[3] 8)); raw[2] (int16_t)(buf[4] | (buf[5] 8)); if (type IMU_ACCEL) { accel.x raw[0] / 32768.0f * 16.0f; // 量程 ±16g accel.y raw[1] / 32768.0f * 16.0f; accel.z raw[2] / 32768.0f * 16.0f; } else if (type IMU_ANGLE) { angle.roll raw[0] / 32768.0f * 180.0f; angle.pitch raw[1] / 32768.0f * 180.0f; angle.yaw raw[2] / 32768.0f * 180.0f; } }代码逻辑说明JY-901 的原始量程由模块内部寄存器决定默认加速度 ±16g、角速度 ±2000°/s、角度 ±180°。转换公式为物理量 原始值 / 32768 × 量程。之所以除以 32768 而非 32767是因为模块手册规定的满量程对应 32768按手册来避免边界误差。参数说明如果模块被配置为 ±2g 或 ±4g需要同步修改这里的量程常量。查看当前量程的方法是发0xFF 0xAA 0x27 0x00 0x00 0x00读加速度量程寄存器返回值的低两位对应量程配置。5. IIC 读角度寄存器直读与串口模式的取舍5.1 模拟 IIC 的 GPIO 定义与基础时序F407 硬件 I2C 有众所周知的 Bug——在检测到总线错误后需要手动复位外设否则 BUSY 位永远不释放。JY-901 这类从机应答节奏不确定硬件 I2C 很容易触发超时。项目里直接选软件 IIC 是常见做法也符合压缩包内代码的风格。#define IIC_SCL_PIN GPIO_PIN_8 #define IIC_SDA_PIN GPIO_PIN_9 #define IIC_GPIO_PORT GPIOB #define IIC_SDA_H() HAL_GPIO_WritePin(IIC_GPIO_PORT, IIC_SDA_PIN, GPIO_PIN_SET) #define IIC_SDA_L() HAL_GPIO_WritePin(IIC_GPIO_PORT, IIC_SDA_PIN, GPIO_PIN_RESET) #define IIC_SCL_H() HAL_GPIO_WritePin(IIC_GPIO_PORT, IIC_SCL_PIN, GPIO_PIN_SET) #define IIC_SCL_L() HAL_GPIO_WritePin(IIC_GPIO_PORT, IIC_SCL_PIN, GPIO_PIN_RESET) void IIC_Start(void) { IIC_SDA_H(); IIC_SCL_H(); delay_us(5); IIC_SDA_L(); // SCL 高电平期间 SDA 拉低 起始 delay_us(5); IIC_SCL_L(); }代码逻辑说明IIC 起始条件是在 SCL 高电平期间将 SDA 从高拉低。后面发送 8 位数据时要求 SDA 只能在 SCL 低电平时变化所以每次切换 SDA 状态前必须先拉低 SCL。GPIO 需配置为开漏输出并外接 4.7kΩ 上拉电阻F407 内部上拉约 40kΩ在 400kHz 的 IIC 时钟下驱动能力不足。参数说明delay_us(5)对应约 100kHz 的 IIC 时钟。JY-901 手册最高支持 400kHz实际使用时 100kHz 稳定性最好尤其是杜邦线较长或供电不稳时降速能明显减少应答失败。5.2 连续读角度寄存器uint8_t JY901_ReadAngle(float *roll, float *pitch, float *yaw) { uint8_t reg 0x34; // 角度寄存器起始地址 uint8_t buf[6]; IIC_Start(); if (IIC_WriteByte(0x50 1 | 0x00) ! 0) { IIC_Stop(); return 1; } // 写地址 写方向 if (IIC_WriteByte(reg) ! 0) { IIC_Stop(); return 1; } // 寄存器地址 IIC_Start(); // 重复起始位 if (IIC_WriteByte(0x50 1 | 0x01) ! 0) { IIC_Stop(); return 1; } // 写地址 读方向 for (int i 0; i 6; i) { buf[i] IIC_ReadByte(i 5); // 最后一字节回 NACK } IIC_Stop(); int16_t raw_r (int16_t)(buf[0] | (buf[1] 8)); int16_t raw_p (int16_t)(buf[2] | (buf[3] 8)); int16_t raw_y (int16_t)(buf[4] | (buf[5] 8)); *roll raw_r / 32768.0f * 180.0f; *pitch raw_p / 32768.0f * 180.0f; *yaw raw_y / 32768.0f * 180.0f; return 0; }代码逻辑说明读寄存器需要先发送设备地址写方向再发送寄存器地址然后发送重复起始位切换为读方向连续读取。最后一字节必须回 NACK 通知从机停止发送否则从机继续拉低 SDA总线会被卡住。读到的 6 字节依次是 Roll 低、Roll 高、Pitch 低、Pitch 高、Yaw 低、Yaw 高。参数说明0x50 1 | 0x00是把 7 位地址左移一位拼上 R/W 位。JY-901 的 7 位地址 0x50实际总线地址是 0xA0写/ 0xA1读。如果你的模块焊脚上 A0/A1 有跳到其他电平这两处地址需要同步改。5.3 串口和 IIC 怎么选对比项串口UARTIIC接线TX、RX、GND、VCC 四线SCL、SDA、GND、VCC 四线速率9600bps 默认可配 115200100kHz ~ 400kHz数据输出模块主动上报主控只管收主控发起读模块被动应答多设备每个模块占一个串口同总线挂多个地址不同应用场景输出频率固定适合直接做航姿需要按需读取、低功耗场景串口的优势在于模块主动输出MCU 不需要每次都发起事务100Hz 更新率下 CPU 占用率极低只有需要临时读取某个寄存器时才主动发指令。IIC 的优势是总线上可以挂其他传感器比如 BMP280 气压计但注意 JY-901 内部数据刷新率是 100Hz不管 IIC 读多快寄存器内容都是 10ms 才更新一次。6. 移植到其他 F4 工程的关键改动与避坑记录6.1 从 F103 移植到 F407 时必改的三处压缩包内工程若是按 F1 写的F407 上需要改三个地方。第一个是stm32f4xx_hal_conf.h里HSE_VALUEF407 开发板常见 8MHz 或 25MHz 晶振改为实际值后整个时钟树才会正确。第二步是 GPIO 初始化代码F1 的GPIO_Mode_AF_PP写法要替换为 F4 的GPIO_AF7_USART1复用配置GPIO_InitTypeDef GPIO_InitStruct {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitStruct.Pin GPIO_PIN_9 | GPIO_PIN_10; GPIO_InitStruct.Mode GPIO_MODE_AF_PP; GPIO_InitStruct.Pull GPIO_PULLUP; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate GPIO_AF7_USART1; HAL_GPIO_Init(GPIOA, GPIO_InitStruct);代码逻辑说明F407 的每个引脚可复用为多种外设功能必须通过Alternate字段指定选哪一套。USART1 对应GPIO_AF7I2C1 对应GPIO_AF4写错则引脚无输出或通信完全失败。前面提到的四开关 buck-boost 双向升降压数字电源、伺服电机 485 控制这类项目只要是 F407 平台这一点都是共通的。参数说明GPIO_SPEED_FREQ_VERY_HIGH对应 100MHz 翻转速率串口 9600 波特率完全用不到设成GPIO_SPEED_FREQ_MEDIUM反而能减少 EMI长线时会减少误码。第三处需要修改的是 HSE 时钟使能后等待超时时间外部晶振起振慢或未焊接时HAL_RCC_ClockConfig会一直卡在HSE_TIMEOUT_VALUE上调试时优先检查这里。6.2 常见失败现象与排查对照表现象最可能原因排查方法串口收到 0x00 或 0xFFTX/RX 接反或模块未上电交换杜邦线量 VCC 是否 5V数据乱码但能收到字节波特率不匹配模块被改过发 0xFF 0xAA 0x00 复位波特率到 9600一直收到 0x55 且后字节不是 0x51~0x59帧头查找逻辑被干扰检查是否开启了奇偶校验HAL 里需设为 NONEIIC 读返回全 0xFFSDA 上拉电阻缺失或地址错误确认是 0x50 而不是 0x55量 SDA 是否能被拉低角度数据跳动超过 ±5°模块未做水平校准或磁场干扰静止时发 0xFF 0xAA 0x01 0x01 0x00 0x00 做水平校准串口中断回调不执行没有调用HAL_UART_Receive_IT重新使能回调函数末尾必须再次调用一次6.3 一个值得留住的调试技巧把原始帧同时输出到另一个串口调试 JY-901 时最怕的是“数据好像有但不知对不对”。我一般会把 USART2 留作调试口在imu_parser_byte的 WAIT_SUM 分支做一次镜像输出case WAIT_SUM: if (sum data debug_enable) { uint8_t debug_frame[8] {0x55, imu_type, buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]}; HAL_UART_Transmit(huart2, debug_frame, sizeof(debug_frame), 100); } imu_state WAIT_HEAD; break;做法说明把原始帧镜像输出到调试串口后可以用 USB-TTL 接电脑在串口助手里看到模块到 MCU 的完整链路数据。哪一段丢了帧、校验和是否经常失败一目了然。JY-901 模块零漂校准也建议在此阶段完成模块静止水平放置连续采集 200 帧数据取均值作为零偏后续解算时减去该值比直接信任出厂校准更贴合当前板子的装配应力。本文还有配套的精品资源点击获取
返回列表