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

资讯详情

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

基于VHDL与IIC的ADXL372撞击力测量设计

基于VHDL与IIC的ADXL372撞击力测量设计 简介一套面向ADXL372高动态范围加速度计的嵌入式驱动源码适用于冲击力检测与振动测量场景也可为FPGA/ASIC开发者提供寄存器配置与通信时序参考。覆盖传感器初始化、I²C/SPI双模式读写、带宽与数据速率配置以及撞击事件捕获等关键环节适合汽车安全、工业状态监测和跌落检测等应用。包内共4个文件由3个C源文件和1个头文件组成整包仅13KB代码精简、无工程框架冗余便于直接移植到STM32等单片机工程中。源码区分I²C与SPI控制函数开发者可在两种总线下快速完成驱动集成再根据实际传感器参数调整配置。目前已有389人学习适合正在开发ADXL372相关产品或希望缩短驱动编写时间的工程师参考。1. ADXL372 撞击力测量为什么值得用 IIC 重新写一遍如果手里有一颗 ADXL372又同时看到 VHDL 和 IIC 这两个词基本可以判断你在做撞击力或冲击检测方向的板级开发。ADXL372 是 ADI 专门为短时大加速度场景设计的 ±200g 三轴加速度计常见的网上下载的 adxl372.rar 工程包里默认驱动代码往往是 SPI 版本解压之后对着 IIC 引脚发现读不到数据是很多工程师踩过同一个坑。用 VHDL 在 FPGA 里实现一套 IIC 主机驱动比依赖 MCU 上的硬件 IIC 外设更可控时钟频率、ACK 处理、重启动Repeated Start逻辑都能按 ADXL372 手册的时序图调配合峰值检测和 FIFO撞击力测量这条链路的实时性可以完全放在硬件里闭环。这篇内容从寄存器模型、VHDL 状态机、撞击力换算到调试方法逐一讲清适合正在调板子或者准备审定 .rar 工程包里那堆代码的人。2. ADXL372 的 IIC 时序与上拉电阻先啃寄存器再写代码2.1 ADXL372 的 IIC 通信协议基础ADXL372 支持两种数字接口IIC 和 SPI具体用哪一种由 BIND 引脚的电平决定。做撞击力测量时IIC 相比 SPI 的优势不只是少两根线IIC 协议自带从机地址和 ACK 应答逻辑分析仪一抓就能看到每一个字节的流向排错成本低而且 ADXL372 的 IIC 从机地址固定不需要像 SPI 那样为每一帧拼命令字节和读写位。从 IIC 通信协议的角度看ADXL372 是标准的 IIC 从机。7 位地址为 0x1D加上读写位之后写地址是 0x3A读地址是 0x3B。这里最容易犯的错是把 SPI 时代的寄存器地址偏移带进来IIC 模式下寄存器地址就是一个 8 位字节写数据时直接跟在地址后面读取时需要先写寄存器地址再发 Repeated Start然后切到读方向。这与 SPI 的“地址字节高位带读写标志”完全不同。ADXL372 的 IIC 时序不支持 SMBus 的 PEC 校验也没有超时复位机制所以主机侧需要自己保证时序正确。手册标注的最高 SCL 频率一般是 400kHz但工程上我一般先用 100kHz 起步把通信调通后再提频率。这样一旦出现 ACK 丢帧至少可以排除时序裕量不足的因素。2.2 寄存器模型设备 ID 和三轴数据寄存器写 IIC 驱动之前先把要操作的寄存器表列清楚。ADXL372 中几个关键寄存器地址在几乎所有版本的手册里保持一致可以直接作为验证代码的锚点寄存器地址读回值作用DEVID_AD0x000xAD设备 ID 高字节用于验证 IIC 通路DEVID_MST0x010x1D设备 ID 中字节确认是 ADI 系列PART_ID0x020x44器件型号标识DATA_X_H/L0x08/0x09三轴 X12 位加速度原始值高字节在前DATA_Y_H/L0x0A/0x0B三轴 Y同上DATA_Z_H/L0x0C/0x0D三轴 Z同上POWER_CTL0x2D可读写电源模式控制默认值需恢复后才可写RESET0x2C可写软复位寄存器写任意值触发复位三轴数据是高字节在前还是低字节在前取决于手册中 DATA_FORMAT 的配置。ADXL372 默认输出 12 位左对齐格式取数据时建议把高低字节合并成 16 位数然后算术右移 4 位得到带符号的 12 位码值。初始化序列不能只看这几个寄存器。ADXL372 在上电后默认处于待机模式直接读加速度数据得到的是无效值必须先经过软复位、等待 2ms、配置滤波带宽、进入测量模式这几个步骤。这也是很多网上的 .rar 工程代码“看起来能跑但数据全是零”的根源复位寄存器没清干净后续写入被忽略而 IIC 从机仍然回 ACK。2.3 IIC 上拉电阻取多大按速度反推IIC 是开漏总线ADXL372 的 SDA 和 SCL 引脚都需要外部上拉电阻。上拉电阻取多大不能只看原理图模板要按总线上拉能力和时钟频率算。总线电容常见值在 10pF 到 100pF 之间取决于 PCB 走线长度、过孔数量、排线连接器的寄生电容。SCL 为 100kHz 时要求上升时间不超过 1000ns400kHz 时上升时间要求不超过 300ns。按 RC 充电公式估算上拉电阻与总线电容的乘积决定了上升沿斜率所以“电阻越小上升沿越陡”。推荐值如下表参考 3.3V 的 VDDIO 电平SCL 频率总线电容约 20pF总线电容约 100pF100kHz4.7kΩ 到 10kΩ2.2kΩ 到 4.7kΩ400kHz2.2kΩ 到 4.7kΩ1kΩ 到 2.2kΩ下限受 IIC 规范中低电平输出电流通常 3mA约束电阻太小会让 SDA 拉低时电流过大。上限受上升时间约束电阻太大会导致 SCL 沿过缓特别是排线连接到 FPGA 开发板时SCL 波形变成斜坡逻辑分析仪仍然能采到但 ADXL372 内部的输入施密特触发器可能不认。另一个容易忽略的点是 VDDIO 与 FPGA 侧 I/O 电压必须一致。ADXL372 的 SDA/SCL 电平与 VDDIO 挂钩如果 VDDIO 是 1.8V 而 FPGA 侧是 3.3V需要在两者之间增加电平转换不能直接用 3.3V I/O 去接 1.8V 电压域器件否则轻则读不到 ACK重则长期工作损坏传感器。2.3.1 上电时序对 IIC 建立连接的影响ADXL372 在上电期间处于复位状态此刻 SCL 上有干扰脉冲IIC 从机会认为主机发起了无效 START。常见做法是在 FPGA 配置完成后对 ADXL372 执行一次软复位先给 SCL 连续产生 9 个时钟脉冲把可能的半字节状态清掉再向 RESET 寄存器写值。这个操作在 IIC 通信协议里没有对应定义是工程经验补充的恢复手段。具体实现方式把 SCL 拉高SDA 拉高然后在 SCL 上输出 9 个脉冲。完成后重新初始化寄存器。3. 用 VHDL 语言实现 ADXL372 的 IIC 主机读取状态机3.1 状态机结构从 START 到 STOPVHDL 语言写 IIC 主机最核心的是状态机设计。IIC 时序本质上是“SCL 高电平期间 SDA 跳变代表 START/STOPSCL 低电平期间 SDA 保持稳定代表数据位”的规则所以状态机里每个状态都要考虑 SCL 相位。一个读单字节的最小状态机需要覆盖这些状态IDLE、START、发送设备地址写方向、等待 ACK、发送寄存器地址、等待 ACK、Repeated START、发送设备地址读方向、等待 ACK、读取 8 位数据、发送 NACK、STOP。撞击力场景如果只需要周期性读取三轴数据把三轴六个字节读完再 STOP 即可如果需要用中断引脚配合阈值检测则状态机中要额外插入中断状态寄存器的读取分支。如下是一个 VHDL 示例完成“写寄存器地址 读单个字节”的基本功能library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity adxl372_iic_read is generic( CLK_FREQ : integer : 50_000_000; IIC_FREQ : integer : 100_000 ); port( clk : in std_logic; rst_n : in std_logic; start : in std_logic; reg_addr : in std_logic_vector(7 downto 0); rd_data : out std_logic_vector(7 downto 0); valid : out std_logic; busy : out std_logic; scl : out std_logic; sda_in : in std_logic; sda_out : out std_logic; sda_oe : out std_logic ); end adxl372_iic_read; architecture rtl of adxl372_iic_read is type state_t is (IDLE, START_1, START_2, ADDR_W, ACK_W, REG_ADDR, ACK_REG, RSTART_1, RSTART_2, ADDR_R, ACK_R, READ_DATA, SEND_NACK, STOP_1, STOP_2, DONE); signal state : state_t : IDLE; signal bit_cnt : integer range 0 to 7 : 0; signal tick : std_logic : 0; signal half_tick : integer range 0 to 511 : 0; signal sreg : std_logic_vector(7 downto 0); signal data_buf : std_logic_vector(7 downto 0); constant IIC_ADDR : std_logic_vector(6 downto 0) : 0011101; -- 0x1D begin -- 分频器产生半个位周期宽度的时钟使能 process(clk, rst_n) begin if rst_n 0 then half_tick 0; tick 0; elsif rising_edge(clk) then if half_tick (CLK_FREQ / IIC_FREQ) / 2 - 1 then half_tick 0; tick 1; else half_tick half_tick 1; tick 0; end if; end if; end process; process(clk, rst_n) begin if rst_n 0 then state IDLE; bit_cnt 0; sreg (others 0); data_buf (others 0); rd_data (others 0); valid 0; busy 0; scl 1; sda_out 1; sda_oe 0; elsif rising_edge(clk) then if tick 1 then valid 0; case state is when IDLE if start 1 then busy 1; state START_1; end if; when START_1 sda_oe 1; sda_out 0; -- SDA 提前拉低 state START_2; when START_2 scl 0; -- 进入总线占用 bit_cnt 7; sreg IIC_ADDR 0; -- 写方向 state ADDR_W; when ADDR_W scl 0; sda_out sreg(bit_cnt); -- 先放数据 if bit_cnt 0 then bit_cnt bit_cnt - 1; else sda_oe 0; -- 释放 SDA等 ACK state ACK_W; end if; when ACK_W scl 1; if sda_in 0 then -- 从机 ACK scl 0; bit_cnt 7; sreg reg_addr; state REG_ADDR; else state IDLE; -- NACK结束本次操作 busy 0; end if; when REG_ADDR scl 0; sda_oe 1; sda_out sreg(bit_cnt); if bit_cnt 0 then bit_cnt bit_cnt - 1; else sda_oe 0; state ACK_REG; end if; when ACK_REG scl 1; if sda_in 0 then scl 0; state RSTART_1; else state IDLE; busy 0; end if; when RSTART_1 sda_oe 1; sda_out 1; -- SDA 拉高 state RSTART_2; when RSTART_2 scl 1; sda_out 0; -- SCL 高期间 SDA 下降沿 state ADDR_R; when ADDR_R scl 0; bit_cnt 7; sreg IIC_ADDR 1; -- 读方向 state ADDR_R; when ADDR_R scl 0; sda_out sreg(bit_cnt); if bit_cnt 0 then bit_cnt bit_cnt - 1; else sda_oe 0; state ACK_R; end if; when ACK_R scl 1; if sda_in 0 then scl 0; bit_cnt 7; state READ_DATA; else state IDLE; busy 0; end if; when READ_DATA scl 0; sda_oe 0; data_buf data_buf(6 downto 0) sda_in; if bit_cnt 0 then bit_cnt bit_cnt - 1; else sda_oe 1; sda_out 1; -- NACK表示只读一个字节 state SEND_NACK; end if; when SEND_NACK scl 1; -- 在高电平期间采样 NACK state STOP_1; when STOP_1 scl 0; sda_out 0; state STOP_2; when STOP_2 sda_out 1; -- SCL 高电平期间 SDA 上升沿 rd_data data_buf; valid 1; busy 0; state DONE; when DONE if start 0 then state IDLE; end if; end case; end if; end if; end process; end rtl;这段 VHDL 代码里SCL 和 SDA 的电平切换逻辑都基于分频后的 tick 信号所以每个状态的持续时间都是固定的半位周期。需要注意ADDR_R状态在 case 语句里重复出现两次前一段代码示例中的时序设计需要在实际工程里合并为单状态上电后先用仿真波形验证整条状态转移路径不要直接上板。分频器部分CLK_FREQ / IIC_FREQ算出的是单个位周期包含多少个系统时钟周期再除以 2 得到半周期。如果系统时钟 50MHz目标 IIC 频率 100kHz则半周期计数为 249 个时钟周期。_FREQ作为 generic 参数传入便于在不同主频的板卡间复用。3.2 VHDL 中常量的定义与参数化设计VHDL 中常量的定义有两大类一类是constant在架构声明区定义只能在当前模块内使用另一类是generic在 entity 中定义可以在例化时从上位模块覆盖。IIC 主机驱动里至少要把CLK_FREQ、IIC_FREQ、设备地址三个值参数化。constant适合定义状态码和编译期固定值例如把三轴寄存器地址定义为一组常量避免状态机里写魔法数。generic适合定义时钟频率、上电复位等待周期数等需要跨工程复用的参数。撞击力测量往往要挂多个 ADXL372每个传感器地址可以通过 ADDR 引脚微调此时设备地址就应该做成in端口而非固定常量保证同一份 VHDL 代码可以驱动多个从机。使用 VHDL 的numeric_std库时注意std_logic_vector与unsigned之间的转换。IIC 状态机里发送地址时把 7 位地址与读写位拼接成 8 位向量这里使用IIC_ADDR 0的写法直接把读写位附在 LSB避免额外做一次resize。3.3 地址位与 ACK 的边界条件调试 IIC 驱动时最常遇到的是 ACK 异常。ACK 在 SCL 高电平期间采样所以状态机里在 SCL 拉高前必须先把 SDA 释放为输入方向否则从机拉低 SDA主机也拉着 SDA总线会一直保持低电平ACK 判断永远读到 0。另一个边界是 Repeated START 与 STOP 之间的间隔。ADXL372 手册对两次 START 之间的总线空闲时间有最小要求VHDL 状态机里如果直接从 ACK_REG 跳到 RSTART没有留出 SCL 低电平等待时间从机可能不识别。常见做法是在进入 RSTART 前额外插入一个 SCL 低电平保持状态或者在 ACK_REG 状态里让 SCL 保持低电平多一个 tick。4. 从原始码值到撞击力ADXL372 数据处理与阈值标定4.1 12 位原始码值换算 g 值ADXL372 的数据寄存器是 16 位格式但有效数据只占 12 位。读取 X 轴高字节和低字节后合并需要先把(high 8) | low得到 16 位有符号数再算术右移 4 位。如果不移位低字节的噪声位会把有效值淹没撞击瞬间的峰值码值会被错误放大或缩小。换算到 g 值的过程中比例因子是关键参数。ADXL372 在不同量程下的 LSB 权重不同常见的 ±200g 模式下1g 约对应 10.24 LSB也就是每个 LSB 约为 98mg。但不同工程包固件、不同滤波设置下这个值可能有差异最可靠的做法是两点校准把传感器分别保持 1g 和 -1g 方向记录两组输出码值然后根据码值差计算实际比例因子。下面是一段 C# 解析代码适用于 FPGA 通过串口把原始数据送上位机后再处理的场景short raw (short)((high 8) | low); raw (short)(raw 4); // 12位有效数据右移得到带符号值 double lsb_per_g 10.24; // 约98mg/LSB建议用实测校准值 double g_value raw / lsb_per_g; double mass_kg 0.145; // 被测物质量单位kg double force_newton mass_kg * g_value * 9.80665;这段代码中raw 4是对 16 位有符号数做算术右移C# 中short类型右移保留符号位。对 12 位数据来说如果最高位是符号位右移后得到的值域正好对应 ±200g 满量程。lsb_per_g如果按 10.24 计算输出 g 值范围约 ±1000g 吗不会因为 12 位码值最大约 ±2047除以 10.24 后约 ±200g符合量程。4.2 撞击力测量中的阈值寄存器配置撞击力应用很少持续关注三轴数值通常只需要在加速度超过某个阈值时记录峰值。ADXL372 内部有阈值比较逻辑当任意轴的加速度绝对值超过设定阈值时可以触发中断把撞击事件锁存到 FIFO 中。阈值寄存器是 16 位无符号值配置时要用“目标 g 值 × 每 g 对应的 LSB 数”换算。按 10.24 LSB/g 计算若要检测 5g 撞击阈值码值 5 × 10.24 51.2取整为 51写入时把 51 拆成高字节和低字节分别写入阈值寄存器对应的高字节和低字节地址。注意 ADXL372 的阈值比较通常取绝对值所以不需要考虑方向正负 5g 都会触发。阈值寄存器初始化顺序有讲究先写阈值再使能中断映射最后进入测量模式。如果先进入测量模式再改阈值部分器件版本会忽略阈值更新需要重新复位后才能生效。4.3 常见撞击场景的参数组合撞击力测量有两种典型场景。第一种是跌落或碰撞检测加速度峰值可以超过 50g但持续时间只有几个毫秒第二种是振动或冲击计数峰值较小但高频分量多。按场景推荐参数组合如下场景量程输出速率阈值中断说明跌落检测±200g400Hz10g 到 20g激活中断强调低漏报桌面撞击±200g800Hz 或以上30g 到 50gFIFO 采样强调高采样率连续冲击计数±100g200Hz2g 到 5g激活中断防止振动脉冲重复触发这里“输出速率”指的是 ADXL372 内部的数据更新率IIC 的采样频率不需要等于它。撞击发生时如果 IIC 主机正忙于其他任务依赖 FIFO 把撞击前后的数据缓冲下来。4.4 上位机接收 IIC 数据的格式约定FPGA 把 ADXL372 的 IIC 数据读出后通过 UART 或以太网送给上位机数据格式需要自己定义。最简单的方式是每帧 9 字节帧头 设备 ID 三轴高字节 三轴低字节 校验和。C# 写 IIC 通信类库时常用类似思路本质上与这里解析寄存器一致只是传输介质不同。帧格式示例| 帧头 0xAA | 设备 0x1D | X_H | X_L | Y_H | Y_L | Z_H | Z_L | 校验 |校验和把前面 8 个字节相加取低 8 位。上位机解析时发现校验错误直接丢弃整帧不做重传因为撞击数据是实时时序数据重传毫无意义。这个约定与 IIC 通信协议中主从机的数据处理定位不同IIC 负责物理链路自定义帧格式负责应用层数据完整性。5. 验证 ADXL372 IIC 驱动DEVID 与 IIC pattern 快速排查拿到一段 VHDL 代码或者从 .rar 工程包移植完驱动后第一步永远不是直接读加速度数据而是回读 DEVID_AD 寄存器。ADXL372 的 DEVID_AD 固定返回 0xADIIC 通路只要正确读到的值就一定是 0xAD。如果读不到问题基本集中在器件地址、上拉电阻、上电时序三个方向。器件地址排查时先用万用表量 SCL 和 SDA 对地电压正常情况下静止状态都是 VDDIO 电平。如果 SDA 一直为低说明有器件在独占总线常见原因是复位脉冲把从机状态机打乱或 FPGA 侧的 sda_oe 信号没释放干净。用逻辑分析仪抓 IIC pattern 是效率最高的手段。设置触发条件为 SDA 下降沿抓一轮完整通信检查以下位置START 是否在 SCL 高电平期间产生发送 0x3A 后第 9 个时钟是否出现 ACK 低电平寄存器地址字节是否与预期一致读数据阶段的 Repeated START 是否被识别。IIC pattern 中如果 ACK 位置出现高电平说明从机没有响应此时重点查地址和供电。示波器看 IIC 波形时除了检查上升沿时间还要看 SCL 低电平期间 SDA 是否有跳变。这种毛刺在排线较长时经常出现会让从机把数据位误判成 START 或 STOP。处理办法是在 SDA 和 SCL 对地并联 10pF 到 33pF 的电容或者把 IIC 频率降到 100kHz 以下。电容不宜过大否则 400kHz 模式上升沿不达标。验证的最后一个动作是让传感器在断电状态下手动敲击 PCB观察是否有峰值数据跳变。把逻辑分析仪探针同时挂在 ADXL372 的中断引脚和 SCL 上触发条件设为中断下降沿可以确认中断事件与 IIC 读取之间的时序关系。如果中断引脚拉低后IIC 状态机没有快速响应多半是主机轮询间隔太长把中断锁存数据错过了。把 SCL 探头移到 SDA 旁抓一拍 STOP 前的建立时间再决定是否需要调整状态机中的空闲等待周期数。本文还有配套的精品资源点击获取
返回列表