基于STM32F1的JY901惯性模块串口数据解码
JY901是一款集成了加速度、角速度、角度等多维度信息的惯性测量单元。本文介绍如何在STM32F103平台上通过异步串口捕获其数据流,并完成有效载荷的提取与转换。
物理层与协议层概要
模块与MCU之间采用TTL电平串口通信,典型接线如下:
| JY901引脚 | STM32F1对应引脚 | 功能说明 |
|---|---|---|
| VCC | 3.3V / 5V | 供电,依模块版本而定 |
| GND | GND | 共地 |
| TX | USART_RX(如PA10) | 模块发送,MCU接收 |
| RX | USART_TX(如PA9) | 可选,用于下发配置指令 |
JY901在自动输出模式下,按固定周期发送数据包。每包长度为11字节,结构定义如下:
| 偏移量 | 内容 | 含义 |
|---|---|---|
| 0 | 0x55 | 同步头 |
| 1 | 0x5X | 数据类别标识 |
| 2~9 | 数据体 | 8字节有效数据,小端排列 |
| 10 | 校验字节 | 累加和校验 |
常见数据类别标识包括:0x50(时间戳)、0x51(加速度)、0x52(角速度)、0x53(倾角)、0x54(磁场强度)等。
驱动代码实现
1. 串口底层配置
以下代码基于STM32标准库,配置USART1作为接收通道,波特率匹配JY901默认输出速率。
#include "stm32f10x.h"
#include <string.h>
#define PACKET_LEN 11
#define RX_BUF_SIZE 512
/* 传感器数据结构定义 */
#pragma pack(1)
typedef struct {
int16_t x;
int16_t y;
int16_t z;
int16_t temp;
} JY901_Accel, JY901_Gyro;
typedef struct {
int16_t roll;
int16_t pitch;
int16_t yaw;
int16_t temp;
} JY901_Angle;
#pragma pack()
volatile struct {
uint8_t pool[RX_BUF_SIZE];
uint16_t head;
uint16_t tail;
} g_ringBuf = {0};
static JY901_Accel s_accel;
static JY901_Gyro s_gyro;
static JY901_Angle s_angle;
void JY901_USART_Init(uint32_t baud)
{
GPIO_InitTypeDef gpio_cfg;
USART_InitTypeDef uart_cfg;
NVIC_InitTypeDef nvic_cfg;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
/* PA9 TX */
gpio_cfg.GPIO_Pin = GPIO_Pin_9;
gpio_cfg.GPIO_Mode = GPIO_Mode_AF_PP;
gpio_cfg.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpio_cfg);
/* PA10 RX */
gpio_cfg.GPIO_Pin = GPIO_Pin_10;
gpio_cfg.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &gpio_cfg);
USART_StructInit(&uart_cfg);
uart_cfg.USART_BaudRate = baud;
uart_cfg.USART_WordLength = USART_WordLength_8b;
uart_cfg.USART_StopBits = USART_StopBits_1;
uart_cfg.USART_Parity = USART_Parity_No;
uart_cfg.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
uart_cfg.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &uart_cfg);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
USART_Cmd(USART1, ENABLE);
nvic_cfg.NVIC_IRQChannel = USART1_IRQn;
nvic_cfg.NVIC_IRQChannelPreemptionPriority = 2;
nvic_cfg.NVIC_IRQChannelSubPriority = 2;
nvic_cfg.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvic_cfg);
}
2. 环形缓冲区与中断接收
采用环形缓冲区解耦中断与解析过程,避免在中断内执行复杂逻辑。
static inline uint16_t RingBuf_Count(void)
{
return (g_ringBuf.head - g_ringBuf.tail) & (RX_BUF_SIZE - 1);
}
static void RingBuf_Put(uint8_t byte)
{
uint16_t next = (g_ringBuf.head + 1) & (RX_BUF_SIZE - 1);
if (next != g_ringBuf.tail) {
g_ringBuf.pool[g_ringBuf.head] = byte;
g_ringBuf.head = next;
}
}
static uint8_t RingBuf_Get(uint8_t *out)
{
if (g_ringBuf.head == g_ringBuf.tail) return 0;
*out = g_ringBuf.pool[g_ringBuf.tail];
g_ringBuf.tail = (g_ringBuf.tail + 1) & (RX_BUF_SIZE - 1);
return 1;
}
void USART1_IRQHandler(void)
{
if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
uint8_t ch = (uint8_t)USART_ReceiveData(USART1);
RingBuf_Put(ch);
}
}
3. 帧解析引擎
状态机方式从字节流中定位完整数据包,并进行校验与分发。
static uint8_t CalcChecksum(const uint8_t *p, uint8_t len)
{
uint8_t sum = 0;
while (len--) sum += *p++;
return sum;
}
void JY901_ParseTask(void)
{
static uint8_t state = 0;
static uint8_t pktBuf[PACKET_LEN];
static uint8_t idx = 0;
uint8_t ch;
while (RingBuf_Get(&ch)) {
switch (state) {
case 0: /* 搜索同步头 */
if (ch == 0x55) {
pktBuf[idx++] = ch;
state = 1;
}
break;
case 1: /* 接收数据类别 */
if ((ch & 0xF0) == 0x50) {
pktBuf[idx++] = ch;
state = 2;
} else {
idx = 0;
state = 0;
}
break;
case 2: /* 接收剩余字节 */
pktBuf[idx++] = ch;
if (idx >= PACKET_LEN) {
/* 校验验证:第11字节为前10字节累加和取低8位 */
uint8_t cs = CalcChecksum(pktBuf, PACKET_LEN - 1);
if (cs == pktBuf[PACKET_LEN - 1]) {
/* 校验通过,按类型解析 */
switch (pktBuf[1]) {
case 0x51:
memcpy((void *)&s_accel, &pktBuf[2], 8);
break;
case 0x52:
memcpy((void *)&s_gyro, &pktBuf[2], 8);
break;
case 0x53:
memcpy((void *)&s_angle, &pktBuf[2], 8);
break;
default:
break;
}
}
idx = 0;
state = 0;
}
break;
default:
idx = 0;
state = 0;
break;
}
}
}
4. 数据转换与输出
原始数据为定点数,需根据量程转换为物理量。以角度为例,假设满量程对应±180°,16位有符号数。
float JY901_GetRoll(void)
{
return (float)s_angle.roll * 180.0f / 32768.0f;
}
float JY901_GetPitch(void)
{
return (float)s_angle.pitch * 180.0f / 32768.0f;
}
float JY901_GetYaw(void)
{
return (float)s_angle.yaw * 180.0f / 32768.0f;
}
/* 主循环示例 */
int main(void)
{
SystemInit();
JY901_USART_Init(115200);
while (1) {
JY901_ParseTask();
/* 每100ms输出一次姿态角 */
float r = JY901_GetRoll();
float p = JY901_GetPitch();
float y = JY901_GetYaw();
/* 此处调用打印或无线发送函数 */
// printf("R:%.2f P:%.2f Y:%.2f\r\n", r, p, y);
Delay_ms(100);
}
}
常见问题排查
- 数据错位:若出现解析值跳变或恒定为零,首先用逻辑分析仪或USB-TTL工具抓取原始波形,确认0x55同步头位置及波特率设置无误。
- 校验失败:部分早期批次模块校验算法存在差异,可尝试关闭校验仅观察数据变化趋势,或查阅对应批次手册。
- 数值漂移:静态时角度缓慢漂移属正常现象,可通过模块自带的校准指令或外部融合算法(如互补滤波、卡尔曼滤波)抑制。
- 波特率不匹配:JY901部分型号支持指令修改波特率,若修改后无法通信,需恢复默认115200或9600重新连接。