ADXL345三轴加速度计驱动代码详解:从IIC时序到计步算法

发布时间:2026/10/15 9:49:01
ADXL345三轴加速度计驱动代码详解:从IIC时序到计步算法 简介这是一份基于ADXL345三轴加速度计的驱动代码文档面向嵌入式开发者和物联网/穿戴设备学习者适用于运动检测、跌倒检测、步伐计数等场景解决通过IIC总线读取X/Y/Z三轴数据的关键问题。压缩包内仅含1个Word文档大小约63KB查阅和实验都较方便。目前已有473人学习。内容围绕ADXL345驱动展开对IIC通信协议进行拆解从IIC_Init初始化、IIC_Start/Stop时序信号到IIC_Wait_Ack应答处理、IIC_Send_Byte和IIC_Read_Byte等底层函数均有注释说明并配有计步算法思路与实际应用测试记录。代码经过多种环境验证可直接移植或二次开发能帮助读者缩短驱动调试周期、快速构建三轴加速度应用。1. 一套能计步的ADXL345三轴驱动代码从IIC时序到状态机做计步手环或跌倒检测时ADXL345三轴驱动代码是绕不开的地基。很多人的起点是某个示例工程里的adxl.c文件里塞了一整套软件模拟IIC收发函数和两套计步算法注释还算全但没人讲清楚哪条时序线能改、哪个寄存器别乱动、计步阈值到底怎么来的。这篇文章把这份驱动代码拆开讲透先解决IIC时序和寄存器配置再把两版计步滤波器的状态机逻辑摆到台面上最后给出移植和调参的实操顺序。适合刚把ADXL345点亮、却卡在计步不准或偶发读不到数据的开发者照着复现。2. 先啃底层软件模拟IIC时序函数的关键实现与改造2.1 GPIO初始化与总线空闲态推挽还是开漏这份代码的IIC_Init是经典ST库写法使能GPIOB时钟把PB10和PB11配置成50MHz推挽输出然后在总线上置高。void IIC_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_10 | GPIO_Pin_11; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; // 推挽输出 GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11); // 空闲时总线为高 }初始化代码的意图很明确让SCL和SDA在空闲状态下都处于高电平这是IIC协议的前提。推挽输出的好处是上升沿利落400kHz以内的时序都能靠delay_us撑起来坏处是SDA在输入采样时会短暂地和从机输出针锋相对。ADXL345的SDA本身是开漏输出所以更稳的做法是把SDA引脚改成开漏模式外部加4.7kΩ上拉电阻更接近芯片原厂电气要求。如果你在原工程基础上改把GPIO_Mode_Out_PP替换成GPIO_Mode_Out_OD即可其余不用动延时参数也不用跟着调。总线空闲态还有一个隐藏作用Start和Stop时序都以SCL高电平下的SDA跳变为准初始化后立刻置高相当于给后续IIC_Start一个确定的起点。实际移植时如果换引脚注意别只改GPIO_Pin宏必须把RCC_APB2PeriphClockCmd那一行也换到对应GPIO端口。这是新手最容易漏的地方换完引脚发现总线一直拉不高多半就是时钟没使能。SDA_IN和SDA_OUT两个宏负责切换SDA方向切换动作必须在SCL低电平期间完成。IIC_Start、IIC_Stop、IIC_Send_Byte里都在切完方向后立刻拉低SCL这个顺序不是随便写的——SCL高电平期间SDA方向翻转会造成总线竞争轻则读回错误电平重则把引脚拉坏。我在某块开发板上改过一版把SDA_IN()放到了SCL1之后结果IIC_Read_Byte读回来的数据偶尔整字节翻成0xFF查了一晚上最后才发现是方向切换时机的问题。2.2 起始、停止、应答时序里必须守住的三个波形要求IIC_Start和IIC_Stop是整套时序的地基原代码用delay_us(4)卡节奏void IIC_Start(void) { SDA_OUT(); // SDA切到输出模式 IIC_SDA 1; IIC_SCL 1; delay_us(4); IIC_SDA 0; // SCL高时SDA由1变0产生起始条件 delay_us(4); IIC_SCL 0; // 钳住总线准备传数据 } void IIC_Stop(void) { SDA_OUT(); IIC_SCL 0; IIC_SDA 0; delay_us(4); IIC_SCL 1; IIC_SDA 1; // SCL高时SDA由0变1产生停止条件 delay_us(4); }三个波形要求要记住SCL高电平期间SDA不允许变化数据位必须在SCL低电平时切换起始和停止条件必须独占一个SCL高电平窗口。这套代码里Start在SCL1时拉低SDAStop先拉低SCL再拉高SCL最后拉高SDA等价于在SCL高电平窗口内完成SDA的0到1跳变两个顺序都不能对调。IIC_Start最后把SCL拉低是为了钳住总线这个低电平状态会保持到第一个数据位写入IIC_Stop最后延时4us是为了让总线上所有器件都正确识别停止条件之后再释放总线。IIC_Wait_Ack是另一个容易翻车的点u8 IIC_Wait_Ack(void) { u8 ucErrTime 0; SDA_IN(); // SDA设为输入 IIC_SDA 1; delay_us(1); IIC_SCL 1; delay_us(1); while (READ_SDA) { // 等待SDA被从机拉低 ucErrTime; if (ucErrTime 250) { IIC_Stop(); return 1; // 无应答 } } IIC_SCL 0; return 0; }如果从机正常工作ACK应该在第9个时钟周期内被拉低。代码里的while循环最多等250次超时后执行IIC_Stop并返回1。但要注意调用方拿到1之后并没有统一的重试机制如果只是打印错误继续跑后续读写时序可能全部错位。我一般会在调用IIC_Wait_Ack的地方检查返回值失败时把SCL连续翻转9次做一次总线解锁再重新发起通信。这个细节放到第5章避坑里展开。delay_us(4)在72MHz主频下大约对应几百个NOP能撑起400kHz以内的IIC节奏。如果换到低主频MCU或者总线上还挂了其他器件导致总线电容变大4us可能不够实测中把延时加到510us并不会影响吞吐反而更稳。反之如果你把延时缩到1us以下正好踩在器件时序上限上会偶发应答失败。这份驱动里的延时是给三个器件主机、ADXL345、可能的总线扩展留的余量不是越小越好。2.3 字节收发与多字节读写连续读6字节是计步的地基字节级函数的高位在前由IIC_Send_Byte逐位发送IIC_Read_Byte在SCL高电平期间采样SDA。这两个函数本身不复杂但多字节读写是计步数据连续获取的关键入口void AD_Read_Multi_Byte(uint8 reg, uint8 *buf, uint8 len) { uint8 i; IIC_Start(); IIC_Send_Byte(WRITE_CMD); // 写方向地址 IIC_Wait_Ack(); IIC_Send_Byte(reg); // 先写寄存器起始地址 IIC_Wait_Ack(); IIC_Start(); // 重复起始切换读方向 IIC_Send_Byte(READ_CMD); // 读方向地址 IIC_Wait_Ack(); for (i 0; i len; i) { if (i ! (len - 1)) *(buf i) IIC_Read_Byte(1); // 非末尾字节回ACK else *(buf i) IIC_Read_Byte(0); // 末尾字节回NACK } IIC_Stop(); }这套读多字节的流程是标准模式先写寄存器地址再重复起始切读方向连续读回数据最后一个字节用NACK告诉从机不再读了。注意IIC_Read_Byte(0)触发的是IIC_NAck不是不回应答。如果len传1循环里ilen-1直接用NACK收尾符合单字节读的需求。主循环里调用AD_Read_Multi_Byte(DATAX0, buf, 6)把六个数据寄存器一次取回避免分三次单字节读造成数据不同步。IIC_Send_Byte内部每个bit都做了两次delay_us这个延时对应的是SCL半周期。换算下来一个字节大概需要8×4us32us加上应答和地址字节读一次6字节数据总的IIC占用时间在100us上下。采样率100Hz时每10ms读一次总线占用率只有1%余量非常充足。如果你在中断里读数据这个耗时是完全可以接受的如果你把它放在主循环while里记得给其他任务预留足够调度时间别让IIC的阻塞延时卡死了整个主循环。void AD_Write_Multi_Byte(uint8 reg, uint8 *data, uint8 len) { uint8 i; IIC_Start(); IIC_Send_Byte(WRITE_CMD); IIC_Wait_Ack(); IIC_Send_Byte(reg); IIC_Wait_Ack(); for (i 0; i len; i) { IIC_Send_Byte(*(data i)); IIC_Wait_Ack(); } IIC_Stop(); }多字节写在这份代码里实际用到的场景不多初始化时每个寄存器都是单字节写。保留它的意义在于后续如果要写FIFO或连续校准寄存器不用再造轮子。参数含义reg是起始寄存器地址data指向待写数据缓冲区len是数据长度。调试时如果怀疑写入失败可以把AD_Wtite_One_Byte里的步进拆分出来逐段检查ACK返回值很快就能定位到是地址没问应还是数据没问应。3. ADXL345初始化与三轴数据读取寄存器配置顺序与拼接坑3.1 初始化流程Device ID校验与四个关键寄存器AD_Initial的逻辑很清晰先IIC_Init再读0x00寄存器验证DEVID是否为0xE5通过后依次配置DATA_FORMAT、BW_RATE、POWER_CTL、INT_ENABLE、INT_MAP最后清三个轴的偏移寄存器。uint8 AD_Initial(void) { IIC_Init(); if (AD_Read_One_Byte(0) 0xe5) { // DEVID校验 AD_Wtite_One_Byte(DATA_FORMAT, 0x69); // 4g量程, 全分辨率 AD_Wtite_One_Byte(BW_RATE, 0x09); // 输出速率100Hz AD_Wtite_One_Byte(POWER_CTL, 0x28); // 测量模式 AD_Wtite_One_Byte(INT_ENABLE, 0x80); // 数据就绪中断 AD_Wtite_One_Byte(INT_MAP, 0x80); // 映射到INT2引脚 AD_Wtite_One_Byte(OFSX, 0x00); // X轴偏移清零 AD_Wtite_One_Byte(OFSY, 0x00); // Y轴偏移清零 AD_Wtite_One_Byte(OFSZ, 0x00); // Z轴偏移清零 return 0; } return 1; }关键在第一行的DEVID校验。这个if判断如果失败直接返回1主程序可以据此报硬件错误。查问题之前先确认这个校验是否通过能省掉一大半瞎查的时间。如果校验失败但总线波形看起来正常优先怀疑SDA/SCL接反或者芯片供电没到位。三个寄存器的配置参数值得逐一说明列成表看起来更清楚。寄存器写入值含义DATA_FORMAT (0x30)0x69量程±4g全分辨率模式FULL_RES1中断输出有效极性等一并配置BW_RATE (0x2C)0x09低四位为9对应输出数据速率100HzPOWER_CTL (0x2D)0x28测量模式BIT31但同时置了自测位BIT51INT_ENABLE (0x2E)0x80使能DATA_READY中断INT_MAP (0x2F)0x80DATA_READY中断映射到INT2引脚OFSX/OFSY/OFSZ0x00三个轴偏移量校准寄存器静止状态下按需写入POWER_CTL的0x28是个隐蔽的坑二进制展开是00101000BIT3置1进入测量模式BIT5置1是自测功能。自测位开启后芯片内部会施加静电力Z轴读数会明显偏移。如果初始化完发现Z轴数据比另外两轴大几百个LSB先查这一位。正常测量模式写成0x08就够了0x28更像是原开发者调试自测功能时留下的值。DATA_FORMAT的0x69在全分辨率位置1后±4g量程对应的灵敏度是4mg/LSB读到原始int16值乘以0.004就得到g值。计步算法不需要浮点换算直接用LSB做阈值判断省运算也省移植麻烦。3.2 三轴数据读取低字节在前与int16拼接ADXL345的六个数据寄存器是连续排列的DATAX0为起始地址依序是X低8位、X高8位、Y低8位、Y高8位、Z低8位、Z高8位。void Read_Axis(int16 *data) { uint8 buf[6] {0}; AD_Read_Multi_Byte(DATAX0, buf, 6); // 一次读X/Y/Z三轴共6字节 *data (int16)(((u16)buf[1] 8) buf[0]); // X低字节在前 *(data 1) (int16)(((u16)buf[3] 8) buf[2]); // Y *(data 2) (int16)(((u16)buf[5] 8) buf[4]); // Z }这个拼接顺序是典型翻车点。ADXL345的数据寄存器是低字节在前DATAX0是X轴低8位DATAX1是高8位。(u16)buf[1]8先给buf[1]做无符号提升把高字节移到位再和低字节相加。如果写成buf[0]8|buf[1]不仅高低字节对调而且buf[0]是uint8类型左移8位在标准C里会先整型提升结果把低字节的高位挤到符号位里读出来的数值完全不连续。原代码用(int16)括号做了一次带符号转换把补码形式正确变成int16_t这一步不可省。全分辨率模式下4g量程的灵敏度是4mg/LSB把读到的int16乘以0.004就换算成重力加速度g。测试时把这段printf打开看静止状态下三轴输出水平放置时Z轴应该接近256对应1gX和Y轴接近0。如果Z轴静止读数离这个值偏差过大要么是量程配置没生效要么是POWER_CTL自测位还开着再要么就是芯片没有真正水平。3.3 突变滤波Filter_Axis_Value区分运动与噪声Read_Axis拿到的是原始三轴数据直接送给计步算法会让阈值判断被高频抖动干扰。原代码在读取之后做了一道突变检测uint8 Filter_Axis_Value(int16 *data, int16 *reg) { uint8 loop_i; uint8 flag 0; int16 sub[3] {0}; Read_Axis(data); for (loop_i 0; loop_i 3; loop_i) { sub[loop_i] Cal_Abs_Value(*(data loop_i), *(reg loop_i)); } if ((sub[0] 10) || (sub[1] 10) || (sub[2] 10)) { flag 1; // 大幅变化认为是有效运动 } else { flag 0; // 小波动视为噪声 } for (loop_i 0; loop_i 3; loop_i) { *(reg loop_i) *(data loop_i); } return flag; }逻辑是三轴分别求当前值与上一次值的绝对差任一轴超过10个LSB就认为发生了明显运动否则当作静止噪声过滤掉。10这个阈值在4g全分辨率模式下对应40mg属于比较灵敏的门槛。换成±16g量程后同样的抖动在数据上会放大需要相应提高到20以上。flag的用途不在这份文件里但常见做法是让上层在flag为1时录入有效数据段为计步算法提供干净的输入。要注意reg数组必须由调用方维护静态存储并且调用间隔要和采样率一致。如果中断里每10ms调用一次reg保存的就是10ms前的值如果把间隔拉长到50ms突变检测的差值会变大阈值就可能被误触发。我见过有人在主循环里没有固定周期调用这段代码结果同一段动作有时计步有时不计步就是因为reg的更新周期不稳定。数据采集类代码一定要挂在固定采样节拍上。4. 计步算法核心从峰峰值检测到动态阈值和时间窗4.1 基础版Filter_AD_Data三条件判步的思路先看基础版的判定逻辑它把每个轴的加速度序列看作一条波形通过跟踪最小值和最大值来捕捉波形峰谷再叠加三个条件决定是否计一步。幅值条件峰峰值大于Axis_Precision[i]门值条件Axis_Threhold[i]落在最小值和最大值之间时间窗条件两次有效步间隔不小于采样点数阈值if (Step_P2P[i] Axis_Precision[i]) // 幅值够大 { if ((Step_Min[i] Axis_Threhold[i]) (Axis_Threhold[i] Step_Max[i])) // 波形跨过阈值线 { if (Sample_Timer 10) // 时间窗足够 { Judge_Show(); // 算一步 } } }代码里对X、Y、Z轴分别处理X和Y轴的时间窗是10个采样点Z轴是13个采样点。为什么Z轴更严因为走路时竖直方向的加速度是主信号如果时间窗太短每一步的二次冲击会被误判成新的一步。跑步场景下步频更快13个采样点对应130ms对快速跑可能偏严需要按实际步频做调整。这个版本的优点是计算量小每个轴只维护最大值和最小值两个状态量不需要额外数组。缺点是它不区分波形的上升沿和下降沿对缓慢摆动手臂造成的锯齿波容易多计。如果你的目标是做一个低功耗的简单计步器比如手环里的基础记步功能这个版本够用了。4.2 增强版Filter_AD_Data_1三态状态机与前后半段幅度比较增强版把这个过程细化成三态状态机每个轴用Step_P_Flag三个状态表示当前所处的波形位置0表示在找波谷起点1表示在找上升沿的最大值2表示在找下降沿的终点。if (Step_P_Flag[i] 0) // 状态0找最低点 { if (Axis_ResultA[i] Step_Startmin[i]) { Step_Startmin[i] Axis_ResultA[i]; // 还在下降更新谷值 } else { Step_Max[i] Axis_ResultA[i]; // 开始上升记录当前为峰值候选 Step_P_Flag[i] 1; // 切换到找上升沿最大值 } } else if (Step_P_Flag[i] 1) // 状态1找最高点 { if (Axis_ResultA[i] Step_Max[i]) { Step_Max[i] Axis_ResultA[i]; // 持续上升更新峰值 } else { Step_Endmin[i] Axis_ResultA[i]; // 开始下降记录当前为谷值候选 Step_P_Flag[i] 2; // 切换到找下降沿终点 } } else if (Step_P_Flag[i] 2) // 状态2找下降沿终点 { if (Axis_ResultA[i] Step_Endmin[i]) { Step_Endmin[i] Axis_ResultA[i]; // 还在下降更新谷值 } else { // 找完一个完整波形比较前后半段幅度 abs[0][i] Cal_Abs_Value(Step_Startmin[i], Step_Max[i]); // 前半段 abs[1][i] Cal_Abs_Value(Step_Endmin[i], Step_Max[i]); // 后半段 if (abs[1][i] Axis_Precision[i]) { if (abs[0][i] abs[1][i]) { Step_P2P[i] abs[0][i]; // 前半段冲击更大取前段峰峰值 } else { Step_P2P[i] abs[1][i]; // 后半段冲击更大取后段峰峰值 } if (i Axis_Flag) { Judge_the_Step_L(i); // 主轴上才做最终步长判断 } Step_Startmin[i] Axis_ResultA[i]; Step_P_Flag[i] 0; // 复位状态机 } else { Step_P_Flag[i] 1; // 后半段幅度不足留在上升沿状态 } } }这个状态机的关键改进在于完整识别一个波峰后分别计算前半段从起点波谷到峰顶和后半段从峰顶到终点波谷的幅度差取较大的那个作为本次波形的有效峰峰值。为什么这么做走路时脚着地的冲击往往出现在后半段如果只比较起点到峰顶的差值会低估着地冲击的强度导致慢走时漏步反过来取幅值较大的半段可以适应前后半段冲击强度不对称的步态对跑步、上下楼梯更友好。后半段幅度不足的细节值得注意代码里如果abs[1]不够大会把状态机置回1而不是0相当于把当前波形当作前一个上升沿的延续。这个分支是应对脚步离地时波形不完全闭合的情况防止状态机频繁复位导致漏检。路径分支多了调试时要学会用状态输出定位问题——原代码里三段printf被注释掉需要观察状态时可以临时打开把Step_P_Flag、Axis_ResultA和最终判步结果一起打出来。4.3 主轴选择与阈值学习配合为什么静止轴不参与计步两个版本里都有Select_Axis这个调用它的作用是从三个轴里选出一个主判轴。走路时X、Y、Z三个轴的加速度变化幅度不一样如果三个轴独立计步再相加同一个步伐会被重复计数。原代码的做法是选出变化最明显的那个轴只在该轴的波形满足条件时调用Judge_Show。void Filter_AD_Data(void) { Axis_Flag Select_Axis(Axis_ResultA); // 选主轴只判主轴 ... }阈值也不是拍脑袋定的。代码里的Judge_Extreme_Ad负责更新每个轴最近50个采样点的最值累计到50个点后调用Cal_Threhold_Precision计算动态阈值然后用Exchage_Extrem_Reg交换寄存器让下一个周期从新数据重新学习。这个机制的实质是让阈值跟着运动幅度自适应——大步快走时峰峰值大阈值自动抬高小步慢走时峰峰值小阈值自动降低。配合Sum_Time超过150个采样点无有效步就清零Sum_Step的机制停下来再启动时不会把旧步数带进新状态。我见过不少人把动态阈值部分直接删掉换成固定常数结果换个测试者就失灵。原因是不同人的步幅和步频差异很大固定的门限值只能适配一种步态。保留动态学习逻辑计步算法才具备跨场景适应性。这份代码里50点和150点这两个周期参数是调出来的50点对应0.5秒的学习窗口150点对应1.5秒的静默超时都是基于100Hz采样率设计的移植时如果改了采样率这两个数字必须同步缩放。5. 实战避坑指南ADXL345驱动在真实板子上的五个翻车点5.1 读DEVID返回0xFF先查硬件而非代码现象AD_Initial里if判断不通过读0x00寄存器返回0xFF。原因可能是SCL和SDA两根线接反或者SDA缺少上拉导致空闲时电平不稳。解决先用万用表量SCL和SDA在空闲状态下是不是高电平再检查IIC_Init里使能的GPIO端口和实际接线是否一致。如果线序没问题把SDA改成开漏模式加4.7kΩ上拉再试。这一步过了再怀疑代码逻辑。5.2 IIC_Wait_Ack超时返回1但调用方不处理总线卡死现象程序运行一段时间后Read_Axis读到的数据全部变成0xFF之后计步失效。原因某次通信过程中从机忙或干扰导致应答失败IIC_Wait_Ack执行了IIC_Stop但调用方没检查返回值下一次通信从错误的时序状态开始总线被SDA上的残余电平拖住。解决遍历代码里所有IIC_Wait_Ack调用点失败时不要继续往下执行而是连续翻转SCL 9次解锁总线再重新发起完整的Start→地址→寄存器流程。这层保护加上之后偶发读不到数据的频率会明显下降。5.3 三轴数据拼接错位高低字节顺序是玄学现象静止放置时三轴读数不是预期的0/0/256而是某轴数值跳变剧烈甚至出现千位数乱跳。原因拼接时写了buf[0]8|buf[1]把低字节挪到了高位。解决统一改成((u16)buf[1]8)buf[0]的形式三个轴都要改。改完把printf打开静止观察Z轴是否稳定在256附近晃动板子看数值能否连续变化。拼接顺序正确后数据曲线才能看出真实的波形形状。5.4 Z轴基准值异常偏大POWER_CTL的0x28带自测位现象初始化后Z轴静止读数不是预期值而是大了几百个LSBX/Y轴正常。原因POWER_CTL写入0x28时BIT5自测位被置1芯片内部持续施加静电力。解决把POWER_CTL改成0x08进入纯测量模式。这个坑的隐蔽性在于数据看起来是连续的、波形也在动只是Z轴静态基准偏移不对比数据手册很难发现。初始化完成后建议把POWER_CTL读回来确认实际写入的值和预期一致。5.5 计步忽多忽少阈值不能拍脑袋要看波形现象同样一段距离有时走一遍计数偏多再走一遍计数偏少且偏移方向不稳定。原因Axis_Precision或Axis_Threhold设置不当。解决把Read_Axis里的printf打开串口采集原始三轴数据画出波形观察真实峰峰值范围。走一步的典型波形幅度是峰峰值几百个LSB静止时噪声在10个LSB以内把幅值阈值设在两者之间靠上的位置门值阈值落在波形中段时间窗按实际步频调整。这个调试习惯比反复改常数更有效。6. 移植到自己的板子换引脚、看波形、动态阈值三件套移植这套驱动到新板子上我一般只改三处。第一处是IIC引脚定义原工程用PB10/PB11换成自己的引脚时把宏统一收口#define IIC_SCL_PIN GPIO_Pin_6 #define IIC_SDA_PIN GPIO_Pin_7 #define IIC_SCL_H() GPIO_SetBits(GPIOB, IIC_SCL_PIN) #define IIC_SCL_L() GPIO_ResetBits(GPIOB, IIC_SCL_PIN) #define IIC_SDA_H() GPIO_SetBits(GPIOB, IIC_SDA_PIN) #define IIC_SDA_L() GPIO_ResetBits(GPIOB, IIC_SDA_PIN) #define READ_SDA() GPIO_ReadInputDataBit(GPIOB, IIC_SDA_PIN) #define IIC_SCL_OUT() { GPIOB-CRL ~(0xF 24); GPIOB-CRL | (0x3 24); }改完引脚之后把DEVID校验跑一遍确认总线通了再谈计步。第二处是滤波和阈值参数。原工程的50点和150点时间窗是100Hz采样率下的值如果我把BW_RATE改成400Hz50点学习窗口就缩到125ms阈值学习的样本量不够需要按比例把50改成200、150改成600。第三处是Cal_Threhold_Precision的实现原工程没给出函数体常见做法是这样补void Cal_Threhold_Precision(int16 *thr, int16 *pre) { for (uint8_t i 0; i 3; i) { int16 maxv Axis_Max_Reg[i]; int16 minv Axis_Min_Reg[i]; thr[i] (int16)((maxv minv) / 2); // 门值取极值中位 pre[i] (int16)((maxv - minv) / 4); // 幅值下限取区间1/4 } }这段的逻辑是每50个采样点学习一次三轴极值门值取极值中位幅值下限取极值区间的四分之一。如果运动幅度稳定这个参数能让阈值自动贴到波形的中间位置如果运动幅度忽大忽小适当把pre的除数从4改成5或6让判定更宽容。调参时一定要配合串口波形看效果。我踩过的教训是换了一块板载电源噪声比较大的开发板静止时噪声直接超过10个LSB的滤波门槛计步器把自己抖出来的“步伐”也算进去了。后来我在滤波前加了一个滑动平均把10个采样点做累积再判断噪声才压下去。从那以后我每次换平台都强制先跑一遍原始数据波形确认噪声水平再动阈值这个习惯救过很多次。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

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

尧图内容编辑团队

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

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

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

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

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

网站改版的5个关键决策

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

获取专属建站方案

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

立即免费咨询