当前位置:首页 > 技术 > 正文内容

基于STM32F1的JY901惯性模块串口数据解码

访客 技术 2026年6月29日 1

JY901是一款集成了加速度、角速度、角度等多维度信息的惯性测量单元。本文介绍如何在STM32F103平台上通过异步串口捕获其数据流,并完成有效载荷的提取与转换。

物理层与协议层概要

模块与MCU之间采用TTL电平串口通信,典型接线如下:

JY901引脚STM32F1对应引脚功能说明
VCC3.3V / 5V供电,依模块版本而定
GNDGND共地
TXUSART_RX(如PA10)模块发送,MCU接收
RXUSART_TX(如PA9)可选,用于下发配置指令

JY901在自动输出模式下,按固定周期发送数据包。每包长度为11字节,结构定义如下:

偏移量内容含义
00x55同步头
10x5X数据类别标识
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重新连接。
标签: STM32F103

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。