ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32驱动ADXL345:I2C配置、寄存器初始化与串口打印实战解析

STM32驱动ADXL345:I2C配置、寄存器初始化与串口打印实战解析 简介基于STM32单片机编写的ADXL345加速度传感器软件例程源码面向嵌入式入门开发者以及需要快速实现三轴加速度数据采集的工程师。工程采用标准库完成包含I2C接口驱动、传感器初始化、原始数据读取与串口打印可从main函数直接跟踪程序流程学习如何配置外设、查询设备状态并输出X/Y/Z轴加速度值。压缩包共82个文件以37个c与38个h源码文件为主同时附带Keil工程配置、启动文件、标准外设库、编译生成的hex文件以及LED、USART、AT24C02等硬件驱动模块整体约308KB目录规划清楚便于按模块查阅和二次移植。已有1100人学习下载。例程每100毫秒读取一次三轴数据并通过串口显示同时具备按键初始化交互为后续倾角测量、运动识别等应用提供可直接复用的底层代码与调试思路。1. 从寄存器到串口这套 ADXL345 例程到底能帮你省多少事拿到基于 STM32F103 标准库的 ADXL345 例程第一反应通常是“不就是读个加速度吗”但真正动手接传感器的时候坑都在细节里I2C 时序对不上、寄存器配置顺序错了导致测量值恒为 0、串口打印乱码分不清是波特率问题还是 printf 重定向没写好。这套源码的价值不在于把 ADXL345 的驱动写得多花哨而在于它把 STM32F10x 标准库、I2C 软件模拟、USART 串口打印、LED/按键外设全部串成了一条直接能跑通的链路非常适合做毕设、课程设计或者产品原型阶段的传感器选型验证。代码基于 STM32F103 系列工程目录里已经按 CORE、SYSTEM、HARDWARE、USER 分层CORTEX-M3 内核的芯片都能直接参考移植到 C8T6 或者 ZET6 最小系统板都只需要改引脚映射。有意思的是这套例程走的是软件模拟 I2C 而不是 STM32 硬件 I2C。用软件 I2C 的原因是它不挑引脚、不挑时钟配置GPIO 翻转时序完全由代码控制对 ADXL345 这种最高支持 400kHz 的传感器来说软件模拟的稳定性反而比硬件 I2C 更容易排查问题。串口部分已经做了 printf 重定向也就是说调试的时候直接printf(x:%d, x)就能往串口助手丢数据不管是看原始值还是换算后的角度都不用再自己封装格式化函数。这套工程里把 AT24C02EEPROM、ADXL345加速度传感器、按键、LED 全部集成在一个 HARDWARE 目录下意味着某个模块的驱动写法可以直接复制到其他项目里复用。2. ADXL345 软件例程的工程结构与数据链路拆解2.1 Keil 工程四层目录CORE、SYSTEM、HARDWARE、USER 各自干什么打开压缩包里的 Template.uvprojx你会看到标准库工程常见的四层结构。CORE 目录下是startup_stm32f10x_hd.s启动文件和core_cm3.h/.c内核抽象层startup_stm32f10x_hd.s是 STM32F103 高密度芯片的启动文件如果你的板子是 C8T6中等密度或者 ZET6高密度需要确认这个启动文件匹配不然上电后中断向量表对不上程序跑飞都查不到原因。startup_stm32f10x_hd.s对于 512KB Flash 以内的高密度型号是通用的c8t6 不属于 hd 系列但例程默认测试平台多为主流开发板如果你用 C8T6 出现程序跑飞或者进不了主循环优先换startup_stm32f10x_md.s。SYSTEM 目录是正点原子风格的延时、串口、系统时钟封装。delay.c提供delay_init()和delay_ms()这两个函数在整个例程里出现频率极高delay_init()内部会判断系统时钟主频来选择 SysTick 的装载值所以如果你的外部晶振不是 8MHzdelay_init()的参数要跟着改否则延时时间直接翻倍或者减半。usart.c里面做了串口 1 的初始化波特率默认是 115200同时实现了fputc重定向把printf的输出引到串口 1 的发送数据寄存器上。HARDWARE 目录下藏着 led、key、i2c、at24c02、usart、adxl345 共 6 组外设驱动。I2C_INIT()是软件模拟 I2C 的初始化入口它把两个 GPIO 引脚配置为开漏输出带上拉然后通过I2C_Start()、I2C_Stop()、I2C_Send_Byte()这几个基础函数拼出完整的读写时序。ADXL345_Init()负责配置传感器的寄存器ADX_GetXYZ_Value()负责读取 X、Y、Z 三轴加速度原始值这两个函数是你重点关注的对象。USER 目录就是业务逻辑层。main.c里完成了外设初始化和数据打印的主循环stm32f10x_it.c是中断服务函数集合目前没有用到中断。keilkilll.bat是个小工具脚本双击可以清理 Keil 编译产生的中间文件减小备份体积。2.2 I2C 通信主从关系与 ADXL345 的器件地址ADXL345 在 I2C 总线上是一个从设备7 位地址由ALT ADDRESS引脚电平决定。当该引脚接地时器件地址是0x1D7 位换算成 8 位写地址是0x3A读地址是0x3B当引脚接高电平时7 位地址变成0x538 位写地址是0xA6读地址是0xA7。这套例程的ADXL345_Init()里将 ADXL345 的器件地址定义为0xA6也就是说硬件上 ALT ADDRESS 引脚必须接高电平VDDIO如果你自己画板子或者用模块先检查模块上这个引脚有没有被拉高。I2C 读数据的时序分成两步第一步主机发送起始信号然后发送器件写地址0xA6再发送要读取的寄存器地址重复起始信号后发送器件读地址0xA7传感器才会把该寄存器的数据放到 SDA 上。整套流程里最容易错的是漏掉重复起始信号或者读地址发成了写地址导致读到的是 ACK 位而不是数据。软件模拟 I2C 的好处是每一拍都能打断点查波形比硬件 I2C 调试直观得多。2.3 串口数据链路printf 重定向到 USART1 的完整路径例程里printf_init()内部调用了 USART1 的初始化波特率 115200、8 位数据位、1 位停止位、无校验。printf 输出要能落到串口助手上需要在usart.c里实现fputc代码长这样int fputc(int ch, FILE *f) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); USART_SendData(USART1, (uint8_t)ch); return ch; }USART_FLAG_TXE是发送数据寄存器空标志位只有上一次数据已经从移位寄存器挪走新数据才能写进去。USART_SendData()把 ch 的低 8 位写入发送寄存器然后由外设自动完成移位输出。如果你用的是 MicroLib在 Keil 的 Options for Target - Target - Code Generation 勾选 Use MicroLIBfputc会被自动调用不勾选的话需要额外处理半主机模式semihosting问题否则程序会卡在printf的底层 I/O 请求上直接死机。这套例程默认使用了 MicroLIB如果你在移植过程中把 MicroLIB 勾掉了串口打印会变成只初始化但不输出任何字符。调试时如果发现串口助手收到的数据是乱码先看两个地方一是 USART1 的 GPIO 复用配置是否正确TXPA9要配置为 AF_PP复用推挽输出RXPA10配置为 IN_FLOATING 或者 IPU二是串口助手的波特率有没有选 115200。这套例程没有做 RX 中断只用了发送方向接收引脚悬空不影响整体流程运行。3. ADXL345 寄存器初始化实战量程、采样率与测量模式配置3.1 六个关键寄存器的功能与取值逻辑ADXL345 的寄存器映射里对初始化起决定性作用的有六个。0x31是 DATA_FORMAT控制量程和分辨率模式0x2C是 BW_RATE控制输出数据速率0x2D是 POWER_CTL控制测量模式0x38是 FIFO_CTLFIFO 模式配置本套例程用默认直通模式0x1E到0x20是数据偏移校准寄存器0x32到0x37是数据输出寄存器。本套例程的ADXL345_Init()中POWER_CTL 被写入 0x08即进入测量模式DATA_FORMAT 写入 0x0B含义是 ±16g 量程、13 位有效分辨率、右对齐输出。// DATA_FORMAT (0x31) // Bit 7: 自测位0 关闭 // Bit 6: SPI 模式0 为 4 线 // Bit 5: 中断极性0 为高电平有效 // Bit 4: 保留 // Bit 3: FULL_RES1 表示 13 位全分辨率模式 // Bit 2-1: 量程选择11 表示 ±16g // Bit 0: 对齐方式0 为右对齐 IIC_Write_One_Byte(ADXL345_Addr, ADXL345_DATA_FORMAT, 0x0B);0x0B拆开看FULL_RES1意味着无论量程选多少输出都以 13 位全分辨率呈现±16g 时的灵敏度是固定的 3.9mg/LSB量程切换为 ±2g、±4g、±8g 时灵敏度系数不变这对做角度解算很有用因为不同量程下换算公式不用改。如果你要更精细的小角度测量把这个值改成 0x08±2g 13 位分辨率灵敏度仍然是 3.9mg/LSB但量程变小后同一位数对应的角度变化更大。BW_RATE寄存器写入 0x0A也就是输出数据速率为 100Hz。这个值的低四位是速率编码0x0A对应 100Hz0x09对应 50Hz0x0B对应 200Hz。100Hz 的采样率在每秒打印 10 次的场景下足够而且功耗比 200Hz 更低。如果你的系统里对传感器数据做了滤波滤波截止频率要低于这里设置的数据速率的一半否则会产生混叠。3.2 初始化流程的顺序为什么不能乱ADXL345 的上电默认状态是待机模式内部寄存器全部是复位值这时候读取数据寄存器得到的是无效值。初始化顺序有明确要求先把 DATA_FORMAT 配好再配置 BW_RATE最后把 POWER_CTL 设为测量模式。如果先打开测量模式再设置量程传感器可能会在错误的数据格式下输出几个采样周期的数据后面的换算全部错位。例程里的初始化函数把ADXL345_Init()做成返回值函数内部先尝试读取 DEVID 寄存器地址0x00如果读到的值等于 0xE5说明 I2C 通信正常否则返回 1 让主循环打印加速度传感器错误。DEVID 是 ADXL345 的出厂标识固定为 0xE5这个机制能有效区分传感器硬件没焊好和通信时序错误。u8 ADXL345_Init(void) { u8 val; IIC_Write_One_Byte(ADXL345_Addr, ADXL345_POWER_CTL, 0x00); // 先进入待机模式 IIC_Write_One_Byte(ADXL345_Addr, ADXL345_DATA_FORMAT, 0x0B); // ±16g, 13位 IIC_Write_One_Byte(ADXL345_Addr, ADXL345_BW_RATE, 0x0A); // 100Hz 输出速率 IIC_Write_One_Byte(ADXL345_Addr, ADXL345_POWER_CTL, 0x08); // 进入测量模式 val IIC_Read_One_Byte(ADXL345_Addr, ADXL345_DEVID); // 读 DEVID 验证 if (val 0xE5) return 0; else return 1; }POWER_CTL先写 0x00 再写 0x08 的理由如果传感器之前处于睡眠或者测量模式直接改写测量位可能不会产生预期效果。先把所有模式位清零让器件进待机再写测量模式位状态机切换更可靠。如果你拿到的是二手模块或者之前被别人配置过这一步能避免状态残留。3.3 六个数据寄存器读取与合并高位左移八位逻辑数据输出寄存器从0x32开始每个轴占用两个连续字节低字节在前高字节在后。X 轴对应0x32DATA0和0x33DATA1Y 轴对应0x34和0x35Z 轴对应0x36和0x37。读取时不能只读低字节否则数据永远是 8 位精度数值跳动极其明显。例程里的读取逻辑如下void ADX_GetXYZ_Value(short *x, short *y, short *z) { u8 buf[6] {0}; // 从 0x32 开始连续读取 6 个字节 ADXL345_ReadBytes(ADXL345_Addr, ADXL345_DATAX0, buf, 6); *x (short)((buf[1] 8) | buf[0]); *y (short)((buf[3] 8) | buf[2]); *z (short)((buf[5] 8) | buf[4]); }buf[1] 8把高字节移动到短整型的高 8 位buf[0]做低 8 位按位或合成为一个 16 位数。由于 ADXL345 的加速度数据是二进制的补码形式short类型的符号位天然能表达正负加速度静止水平放置时 Z 轴读到约 256对应 1gX 和 Y 轴接近 0。如果你要访问完整寄存器表ADXL345 支持多字节突发读但本套例程的单字节读写实现也够用只是时序上多了几次 ACK 应答。4. 数据读取与串口打印主循环里的 100ms 采样逻辑完整跑通4.1 主循环的完整代码流程与分支条件解析main.c的主循环以 10ms 为基本调度单位变量t每轮循环自增t%100的成立频率是每秒 10 次也就是每 100ms 读取一次加速度三个轴的原始值并打印读取结果以short类型变量的地址传给ADX_GetXYZ_Value()函数内部写入数据主循环再用 printf 输出。10ms 的调度周期来自delay_ms(10)这个延时函数配合t完成了一个极简时间片轮询。int main(void) { u8 key; u8 t 0; short x, y, z; short angx, angy, angz; delay_init(); // 延时函数初始化 LED_Init(); // LED GPIO 初始化 KEY_Init(); // 按键 GPIO 初始化 printf_init(); // USART1 初始化绑定 printf I2C_INIT(); // 软件模拟 I2C 引脚初始化 while (ADXL345_Init()) { printf(加速度传感器错误\r\n); delay_ms(100); } while (1) { t; if (t % 10 0) // 每 100ms 读取一次 { ADX_GetXYZ_Value(x, y, z); printf(x:%d \n, x); printf(y:%d \n, y); printf(z:%d \n, z); } delay_ms(10); } }其中ADX_GetXYZ_Value()的参数类型是short *传入x后函数内部通过指针写值不需要返回值。printf(x:%d \n, x)里的%d格式符对应 16 位有符号整数内容是新一行。注意打印串里没有加\r部分串口助手对\n的兼容性不好显示会不换行修改方式是把\n换成\r\n。主循环里key变量虽然定义了但按键检测逻辑在例程中没有实际调用如果你要加按键控制采样频率或者量程切换就在KEY_Scan()返回值后加分支。4.2 原始值与物理量的换算公式以 ±16g 量程为例ADXL345 的原始值本身不是物理量要换算成重力加速度 g 需要乘灵敏度系数。在 13 位全分辨率模式下±16g 量程对应的灵敏度是 3.9mg/LSB也就是每 1 个 LSB 代表 3.9 毫 g。换算公式float x_g (float)x * 0.0039; float y_g (float)y * 0.0039; float z_g (float)z * 0.0039;乘以 0.0039 之后静止水平放置时 z_g 约等于 1.0左右翻转后 x_g 会体现重力分量。如果采用 ±2g 量程灵敏度是 0.004 左右对应DATA_FORMAT值改为 0x08换算时用 0.004 做系数。实际调试中发现打印的数值永远是一个固定范围不变化先确认模块有没有从待机模式切到测量模式再看POWER_CTL寄存器值是否被后续代码误写。4.3 串口打印乱码与数据跳变的调试手段串口打印乱码的排查优先级如下先确认波特率一致。例程默认 115200如果串口助手里选成 9600输出的内容全是乱码。再确认 GND 是否共地USB 转 TTL 模块和 STM32 板子必须共地否则电平参考点不一致数据位会错乱。最后用示波器或者逻辑分析仪抓 TX 引脚波形看空闲电平是否为高起始位是否为低脉冲。如果波形正常但乱码检查USART_Init()里的时钟配置是否开了RCC_APB2Periph_USART1没有开外设时钟的话串口寄存器写不进去。数据跳变严重则大概率是电源问题。ADXL345 的 VDD 引脚上必须加 0.1uF 去耦电容电源纹波大时数据输出噪声明显。软件层面加滑动平均滤波#define FILTER_N 5 short filter_buf[FILTER_N]; u8 filter_cnt 0; short ADXL345_Filter(short new_val) { long sum 0; u8 i; filter_buf[filter_cnt] new_val; if (filter_cnt FILTER_N) filter_cnt 0; for (i 0; i FILTER_N; i) sum filter_buf[i]; return (short)(sum / FILTER_N); }滤波窗口大小取 5 是兼顾响应速度和平滑度的经验值窗口太大时传感器快速转动会看到明显滞后。主循环里ADX_GetXYZ_Value()读完后调用ADXL345_Filter()处理每个轴再打印就能明显减少串口输出里的毛刺。5. 加速度转角度基于 atan2 的三轴姿态解算与 printf 输出扩展5.1 把加速度原始值换算为欧拉角的方法x、y、z 三个轴的加速度值在静止状态下可以解算出俯仰角Pitch和横滚角Roll。Z 轴加速度在水平时等于 1g当板子倾斜时重力加速度会按三角函数关系分配到 X、Y、Z 三个轴上。利用atan2函数可以避免分母为零的问题#include math.h float pitch atan2(-x_g, sqrt(y_g * y_g z_g * z_g)) * 57.29578f; float roll atan2(y_g, z_g) * 57.29578f;拿出计算器验证把板子水平放置pitch 接近 0°roll 接近 0°把板子绕 Y 轴旋转 90° 立起来此时 x_g 约等于 0z_g 约等于 0y_g 约等于 1pitch 的结果会趋向于 ±90°。这里的 57.29578f 是 180/π 的近似值把弧度换算成角度。需要说明的是仅用加速度计做姿态解算在动态情况下误差大因为运动加速度会叠加在重力分量上但静止或缓慢移动场景下足够正确测量倾角。5.2 在现有串口打印代码上扩展角度输出的改造方法要在 printf 里同时打印角度直接把角度值放进去printf(pitch:%.2f roll:%.2f\r\n, pitch, roll);%.2f只保留两位小数避免串口数据被一长串浮点数刷屏。注意此时需要确保项目里包含 libm在 Keil 的 Target 选项里勾选 Use MicroLIB 并且确认math.h已经被包含。atan2函数接收两个double类型参数如果你传入的是float编译器会自动隐式转换。printf输出浮点数依赖微库的格式化函数实现如果你没有勾选 Use MicroLIBprintf里的%f可能输出空字符串。5.3 模块方向变化时数据更新缓慢的排查技巧如果转动模块时串口打印的角度值变化很迟钝先确认 100ms 采样周期是否太慢把t%100改成t%2020ms 采样并缩短delay_ms()数据更新频率会提升。若数值跳动幅度变大说明采样频率接近传感器内部带宽极限这时候用BW_RATE寄存器把输出速率从 100Hz 提高到 400Hz写入 0x0D再在软件里做平均值滤波效果会好很多。角度输出超过 180° 跳变时检查atan2的参数符号是否反了不同安装方向下 X/Y 轴的极性不同可在初始化后加一段归一化校准水平放置时记录三个轴的偏移后续读数先减偏移再做计算。判断偏移是否合理的方法是观察静止时打印的原始值X、Y 轴应在 ±20 以内Z 轴应在 240~270 之间超出这个范围说明传感器贴装不水平或存在磁场干扰。本文还有配套的精品资源点击获取
返回列表