简介这份资源面向嵌入式开发初学者与需要快速接入三轴加速度传感器的工程师提供ADXL345驱动代码的完整实现与知识点梳理可用于运动检测、跌倒检测与计步等场景的二次开发。压缩包内共1个doc文件约63KB以文档形式集中呈现驱动源码与函数说明便于对照阅读和移植。资源已有474人学习下载具备一定的参考基础。内容围绕软件IIC时序展开逐一讲解IIC_Init引脚初始化、Start与Stop起止信号、Wait_Ack应答等待、Ack与NAck应答控制以及Send_Byte和Read_Byte的逐位收发实现并给出向ADXL345寄存器写入单字节与多字节的封装函数同时涉及基于加速度信号的计步算法思路且代码经过实际应用测试可稳定运行于多种环境。读者可据此掌握IIC底层时序编写方法理解传感器寄存器读写流程并获取可直接复用的驱动框架与计步实现参考。1. 软件模拟 IIC 驱动 ADXL345一份能直接跑计步的三轴加速度代码包拿到一份只有单个adxl.c的驱动代码很多人第一反应是“就这”。但真正把它烧进板子、接上 ADXL345、跑出稳定的三轴数据和计步结果中间隔着的坑比想象中多。这份代码包的核心价值不在于“驱动”两个字而在于它把软件模拟 IIC 的时序、ADXL345 的寄存器配置、以及一套基于峰峰值判断的计步算法全部揉进了一个文件里拿来就能编译改改引脚就能跑。它适合正在做可穿戴设备、运动检测 Demo、或者单纯想搞明白“计步到底怎么算”的嵌入式开发者。三轴加速度计的数据读取本身不复杂难的是从噪声里把“一步”抠出来这份代码给了一个可用的起点。2. 软件模拟 IIC 的时序底子为什么不用硬件外设2.1 软件模拟 IIC 的选型理由与引脚约束ADXL345 支持 IIC 和 SPI 两种接口这份代码选了 IIC而且是软件模拟的方式。原因很实际很多低成本 MCU 的硬件 IIC 外设存在各种玄学问题比如某些型号的硬件 IIC 在从机拉低时钟时会产生总线死锁或者多主机场景下仲裁逻辑有 bug。软件模拟 IIC 把时序控制权完全交给代码引脚随便挑两个普通 GPIO 就能用移植成本极低。代码里IIC_Init把 PB10 和 PB11 配置为推挽输出初始输出高电平。这里有个细节值得注意推挽输出模式下引脚既能输出高也能输出低但 ADXL345 的 SDA 线在应答阶段需要从机把线拉低所以代码里用SDA_IN()和SDA_OUT()两个宏在输入和输出模式之间切换。如果忘记切回输出模式就发数据SDA 线会处于高阻态波形直接翻车。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); // 空闲态拉高 }这段初始化只做了两件事开 GPIOB 时钟、把两个引脚设成推挽输出并拉高。参数上唯一需要根据实际硬件改的是GPIO_Pin和对应的端口如果你把传感器接在 PB6/PB7 上改这两行就行。GPIO_Speed_50MHz对 IIC 来说绰绰有余IIC 标准模式才 100kHz快速模式 400kHz50MHz 的翻转速度足够覆盖。2.2 起始、停止与应答三个最容易写错的时序点IIC 协议里起始信号的定义是SCL 为高电平时SDA 从高变低。停止信号反过来SCL 为高时SDA 从低变高。代码里IIC_Start先拉高 SDA 和 SCL延时 4 微秒再拉低 SDA再延时 4 微秒最后拉低 SCL 钳住总线。这个顺序不能乱如果先拉低 SCL 再动 SDA从机根本不会识别为起始条件。void IIC_Start(void) { SDA_OUT(); IIC_SDA 1; IIC_SCL 1; delay_us(4); IIC_SDA 0; // SCL 高时 SDA 下降沿 起始 delay_us(4); IIC_SCL 0; // 钳住总线 }应答等待IIC_Wait_Ack是另一个高频翻车点。它先把 SDA 切成输入模式释放总线让从机控制然后拉高 SCL在 SCL 高电平期间读 SDA。如果读到低电平说明从机应答了读到高电平说明没应答。代码里加了一个 250 次的超时计数超时后主动发停止信号并返回 1。这个超时保护很关键没有它的话传感器没接好或者地址写错程序会死循环卡在while(READ_SDA)里表现为“程序跑飞了但看不出原因”。提示delay_us的具体实现依赖你的系统时钟。如果延时函数不准比如实际延时远大于标称值IIC 速率会掉到几 kHz虽然大概率还能通信但读取三轴数据的刷新率会明显下降。用示波器看一眼 SCL 波形是最稳妥的验证方式。2.3 单字节与多字节读写寄存器地址的两种访问模式ADXL345 的寄存器访问分单字节和多字节两种。单字节读写的流程是起始 → 发写命令WRITE_CMD→ 等应答 → 发寄存器地址 → 等应答 → 起始 → 发读命令READ_CMD→ 等应答 → 读数据 → 发 NACK → 停止。多字节读则是在读完一个字节后发 ACK 而不是 NACK让从机继续输出下一个寄存器的值直到最后一个字节才发 NACK。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(); }Read_Axis函数就是靠这个多字节读一次性把DATAX0到DATAZ1六个寄存器读出来然后拼成三个 16 位有符号数。注意拼接顺序是低字节在前、高字节在后((u16)buf[1] 8) buf[0]得到 X 轴原始值。如果高低字节搞反读出来的数据会大得离谱或者符号完全错误这是新手最容易犯的错之一。3. ADXL345 寄存器配置量程、速率与数据格式的取舍3.1 初始化流程与器件 ID 校验AD_Initial函数的第一件事不是写配置而是读0x00寄存器判断返回值是不是0xE5。这是 ADXL345 的器件 ID固定值。如果读不到0xE5说明 IIC 通信根本没通后面写再多配置都是白费。这个校验步骤看起来简单但省掉了大量“配置写了怎么没反应”的排查时间。uint8 AD_Initial(void) { IIC_Init(); if(AD_Read_One_Byte(0) 0xE5) { AD_Wtite_One_Byte(DATA_FORMAT, 0x69); // ±4g全分辨率 11 位 AD_Wtite_One_Byte(BW_RATE, 0x09); // 输出速率 50Hz AD_Wtite_One_Byte(POWER_CTL, 0x28); // 测量模式 AD_Wtite_One_Byte(INT_ENABLE, 0x80); // 关中断 AD_Wtite_One_Byte(INT_MAP, 0x80); 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; }DATA_FORMAT写0x69拆开看bit6 和 bit5 是0b11对应 ±4g 量程bit3 是1开启全分辨率模式bit2 是0表示数据右对齐。全分辨率模式下输出分辨率固定为 11 位量程越大每 LSB 对应的重力加速度越大。±4g 量程下每 LSB 约等于 3.9mg对计步场景够用。如果你做的是跌落检测可能需要 ±16g如果做的是极慢速的姿态变化检测±2g 能换来更细的分辨率。BW_RATE写0x09对应输出数据速率 50Hz。这个值决定了你多久能读到一次新数据计步算法里Sample_Timer的计数节奏和它直接相关。50Hz 意味着每 20ms 一个采样点对于步行这种低频运动通常 1~3Hz来说采样率足够又不会让数据量过大。POWER_CTL写0x28是让芯片进入测量模式。bit3 置 1 开启测量bit5 置 1 开启自动休眠bit2 置 0 关闭休眠。如果这个寄存器没写对芯片会停留在待机模式读出来的三轴数据全是零现象就是“IIC 通了但数据不动”。3.2 偏移寄存器与数据对齐的实操细节OFSX、OFSY、OFSZ三个偏移寄存器代码里全写0x00意思是先不做硬件偏移补偿。实际使用中传感器焊接在板子上难免有安装倾斜或者零点漂移静止时读出来的值可能不是理想的(0, 0, 256)附近Z 轴受重力影响约 256 LSB。如果发现静止状态下 X 或 Y 轴有几十个 LSB 的固定偏差可以通过写这三个寄存器做粗调。偏移量的单位是每 LSB 约 15.6mg写之前最好先采集几百个静止样本取平均再换算成寄存器值。注意偏移寄存器是 8 位有符号数范围 -128 到 127。如果偏差超过这个范围说明要么传感器坏了要么量程选得不对靠偏移寄存器救不回来。数据对齐方面代码用的是右对齐模式12 位数据放在 16 位寄存器的低 12 位高 4 位是符号扩展。Read_Axis里直接强转int16就能拿到正确的有符号值。如果改成左对齐拼接方式要跟着改否则数值会差 16 倍。4. 计步算法拆解峰峰值判断与动态阈值4.1 三轴选择与动态阈值更新机制计步算法的核心逻辑在Filter_AD_Data和Filter_AD_Data_1两个函数里。第一步是Select_Axis选出一个当前变化最剧烈的轴作为计步参考轴因为手机或手环的佩戴方向不固定不能写死用 Z 轴。选轴之后算法维护一个 50 次采样的窗口在窗口内不断更新每个轴的最大值和最小值窗口结束时调用Cal_Threhold_Precision计算动态阈值和精度门限然后交换极值寄存器准备下一个窗口。if(Decide_Sample_No 49) { Judge_Extreme_Ad(); // 更新窗口内最值 Decide_Sample_No; } else { Cal_Threhold_Precision(Axis_Threhold, Axis_Precision); Exchage_Extrem_Reg(); // 交换最值准备下个周期 Decide_Sample_No 0; }这个动态阈值的思路很实用人的步频和步幅会变固定阈值在慢走时漏计、快跑时多计。用最近 50 个采样点的峰峰值来推算当前的运动强度阈值跟着数据走适应性好很多。50 次采样在 50Hz 下是 1 秒相当于每秒更新一次判断基准。4.2 波峰检测状态机与时间窗约束每个轴独立维护一个Step_P_Flag状态机三个状态分别对应“找最小值”“找上升沿最大值”“找下降沿最小值”。当检测到一个完整的波峰-波谷组合后计算前半段和后半段的幅值差取变化更大的那一段作为Step_P2P再和精度门限比较。只有幅值超过门限、且最小值到最大值之间跨过了阈值线才认为可能是一步。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(); } } }Sample_Timer 10这个条件是用来防抖的。没有它的话一次剧烈晃动可能被拆成好几个“步”。10 个采样点在 50Hz 下是 200ms对应最快步频 5 步/秒正常人跑步也很难超过这个频率。Z 轴的时间窗更严格要求Sample_Timer 13因为 Z 轴受重力影响噪声模式和其他两轴不同。Sum_Time和Sum_Step配合实现“有效计步间隔”如果 150 个采样周期3 秒内没有新的步被确认计步累加器清零重新进入搜索模式。这个机制防止了长时间静止后突然一个动作被误判为连续步伐。4.3 噪声过滤与大幅变化标记Filter_Axis_Value函数做了一层简单的差分滤波把当前读数和上一次读数逐轴求绝对差如果任一轴的差值超过 10 个 LSB标记flag 1表示“大幅变化”否则标记为噪声。这个 flag 可以给上层逻辑用来判断当前数据是否可信比如在flag 1时跳过计步判断避免把碰撞或甩动当成步伐。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 是个经验值对应约 39mg 的加速度变化。如果你发现静止时 flag 频繁置 1说明传感器噪声偏大可以适当调高如果轻微走动都触发不了说明阈值太高调到 5 左右试试。这个参数没有标准答案跟具体传感器的焊接质量、电源纹波都有关系。5. 避坑排查五个让计步结果对不上的典型问题5.1 读不到器件 ID 0xE5现象AD_Initial返回 1串口打印初始化失败后续所有数据为零。原因通常是 IIC 引脚接反、上拉电阻缺失、或者从机地址不对。ADXL345 的 7 位地址是0x53左移一位后写命令是0xA6读命令是0xA7。代码里WRITE_CMD和READ_CMD如果定义错了起始信号发出去从机根本不应答。解决先用万用表确认 SDA 和 SCL 空闲时都是高电平再用示波器抓起始信号后的第一个字节看是不是0xA6。5.2 三轴数据全是零或恒定不变现象ID 读到了0xE5但Read_Axis读出来的值始终是零或者某个固定值。原因大概率是POWER_CTL没写对芯片还在待机模式。0x28这个值里 bit3 是测量使能位如果写成0x20就只开了自动休眠没开测量。另一个可能是DATA_FORMAT的 bit3 没置 1全分辨率没开数据寄存器不更新。解决用AD_Read_One_Byte把POWER_CTL和DATA_FORMAT读回来打印确认写入生效。5.3 计步数只增不减或者疯狂累加现象静止放在桌上步数自己往上涨或者走一步计了五六步。原因分两种前者是动态阈值算得太低噪声被当成了步伐检查Cal_Threhold_Precision里阈值下限有没有设保护后者是时间窗Sample_Timer判断失效可能是Sample_Timer的递增位置在中断里被重复调用导致计数节奏乱了。解决把Step_P2P、Axis_Threhold、Sample_Timer三个值通过串口打出来走几步看波形正常一步应该对应一个明显的峰谷和 10 以上的Sample_Timer。5.4 换一块板子计步就不准了现象同一份代码A 板子计步正常B 板子偏差很大。原因是 ADXL345 的安装方向和焊接应力不同导致静止零点偏移和轴间灵敏度差异。代码里偏移寄存器全写零没有做个体校准。解决在初始化后加一段静止采样每个轴采集 200 个点取平均把平均值换算成偏移量写入OFSX/OFSY/OFSZ。注意 Z 轴要减去 2561g 对应的 LSB 数因为静止时 Z 轴承受重力。5.5 多字节读数据高低字节错位现象X 轴读数在静止时约等于 256但 Y 和 Z 轴数值异常大或者符号跳变。原因是AD_Read_Multi_Byte读出来的 buf 数组里高低字节的拼接顺序搞反了。ADXL345 的数据寄存器是低字节在前DATAX0在低地址高字节在后。如果写成(buf[0] 8) buf[1]数值会完全错乱。解决对照数据手册确认寄存器地址顺序DATAX00x32、DATAX10x33拼接时buf[1]是高字节。6. 从能跑到好用计步参数的整定方法与验证习惯代码能编译、能读出三轴数据只是第一步。真正让计步结果可信靠的是对着真实数据调参数。我一般会先写一个临时测试函数把Read_Axis读到的原始值、Step_P2P、Axis_Threhold、Axis_Precision四个量以 CSV 格式从串口吐出来然后用串口助手存成文件丢进表格里画折线图。走 20 步、跑 20 步、上下楼各一趟分别看峰峰值分布落在什么区间。整定的顺序有讲究先调Axis_Precision的下限保证静止和轻微晃动不触发再调Axis_Threhold的系数让正常步行能跨过阈值线最后调时间窗Sample_Timer的门限把一步拆成多步的情况压下去。这三个参数互相耦合改一个要回头验证另外两个别想一次调完。参数代码位置典型调整方向验证方法Axis_Precision下限Cal_Threhold_Precision静止误计多则调高静置 1 分钟看步数是否为零Axis_Threhold系数Cal_Threhold_Precision漏计多则调低慢走 20 步对比实际步数Sample_Timer门限Filter_AD_Data内一步多计则调高快跑 20 步看是否翻倍差分阈值 10Filter_Axis_Value噪声大则调高静止时 flag 是否频繁置 1还有一个容易被忽略的点Filter_AD_Data_1和Filter_AD_Data是两套不同的峰检测实现前者带更细的上升沿/下降沿分段判断后者是简化的峰峰值比较。代码里两个函数都存在但只调用了其中一个如果你发现当前使用的版本对慢走不敏感可以换另一个试试两者的参数体系是共通的但状态机行为有差异切换后要重新整定时间窗。从那以后我每次拿到这类传感器驱动代码都强制先做三件事读 ID 确认通信、打印原始三轴值确认数据在动、用 CSV 记录一段真实运动数据再调算法参数。跳过任何一步后面调计步都是在猜。希望这份拆解能帮你在接 ADXL345 的时候少走几个弯路。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
高校水电表缴费系统设计与微服务架构实践 1. 项目背景与选题价值高校水电表缴费系统这个选题源于我在研究生期间担任宿舍楼长的实际经历。每次月底统计各寝室水电用量时,总能看到楼管阿姨抱着一摞纸质表格挨个寝室敲门抄表,然后再手工计算费用。这种传统方式不仅效率低下,还经常出现数… · 2026/11/3 3:40:36
行式存储与列式存储:原理对比与业务场景选型 1. 存储模型的基础概念与业务需求在数据存储领域,行式存储(Row-based Storage)和列式存储(Column-based Storage)是两种根本不同的数据组织方式。行存将同一行的数据连续存储在磁盘上,而列存则将同一列的数… · 2026/11/3 3:39:36
Linux命令速查手册:从基础操作到实战避坑指南 简介:面向Linux初学者与运维入门者的命令速查手册,聚焦日常系统管理与文件操作场景,帮助读者摆脱对图形界面的依赖、建立命令行操作的基本功。资源为单个PDF文档,压缩包约130KB,篇幅精炼、便于随查随用,适合… · 2026/11/3 3:39:36
2026智能课堂笔记系统:教育数字化转型实践 1. 项目背景与核心价值"2026课堂笔记"这个看似简单的标题背后,实际上蕴含着教育数字化转型的前沿趋势。作为一名长期关注教育科技领域的从业者,我观察到传统纸质笔记正在经历一场革命性的变革。到2026年,课堂笔记将不再是简单的文字… · 2026/11/3 5:20:58
从零启动一个buzz:社群与内容传播的完整实操框架 1. 从一个词说起:为什么“buzz”值得单独拎出来聊“buzz”这个词,第一次看到的人多半会愣一下——它既不是某个具体产品的名字,也不像是一个明确的技术栈缩写。但如果你在互联网内容圈、社群运营圈或者产品圈待过一段时间,就会发现… · 2026/11/3 5:20:58
6-sol与6.1-sol实测:单价涨40%,账单只涨30%值不值 上周为了搞清楚一件事,我把整个周末搭进去了:新版 6.1-sol 到底比 6-sol 贵多少,贵出来的那部分值不值。这个问题在群里被问了不下十次,但大多数回答都停在单价对比——输入贵 40%、输出贵 40%,算完就结束了。我总觉得… · 2026/11/3 5:20:58
JavaScript异步编程:从回调到Promise再到async/await 1. 异步编程的演进历程在JavaScript的世界里,异步编程经历了从回调地狱到Promise再到async/await的进化过程。早期的回调函数虽然解决了同步阻塞的问题,但嵌套层级过深时就会形成著名的"回调地狱"。我曾在一个电商项目中处理订单状态变更时&am… · 2026/11/3 5:20:58
Git与GDB协同开发:从基础配置到高级调试技巧 1. Git与GDB基础概念解析"11.git_gbd"这个看似简单的文件名,实际上暗含了两个开发者日常使用的重要工具组合:Git版本控制系统和GDB调试器。这种命名方式在开发社区中很常见,通常表示某个与Git和GDB相关的配置文件、脚本或项目文档。… · 2026/11/3 5:19:58
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18
STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18