1. ADXL345传感器基础解析ADXL345是ADI公司推出的一款经典三轴数字加速度计我在多个运动检测项目中都使用过它。这款传感器最大的特点就是低功耗和高分辨率实测下来功耗确实能控制在手册标注的23μA左右对于电池供电的设备非常友好。传感器内部结构其实很有意思它采用微机电系统MEMS技术通过检测质量块在加速度作用下的位移来测量加速度值。我拆解过几个样品发现其内部包含三个独立的测量通道分别对应X、Y、Z三个轴向。每个通道都有一套完整的信号调理电路包括放大器、滤波器和ADC转换器。ADXL345支持±2g到±16g的量程范围这个参数选择很关键。我在做智能手环项目时发现对于日常活动监测±4g就足够了但如果是工业振动监测可能需要选择±16g量程。量程选择直接影响分辨率±2g时分辨率为10位±16g时可达13位通信接口方面ADXL345确实很灵活。我最早接触时用的是IIC接口后来在需要高速数据传输的场景改用了SPI。两种方式我都实测过IIC模式下最高时钟400kHzSPI模式下可达5MHz传感器的引脚定义需要特别注意特别是CS和SDO这两个关键引脚CS引脚控制通信模式高电平IIC/低电平SPISDO决定IIC地址高电平0x1D/低电平0x532. STM32硬件环境搭建我用的是STM32F103C8T6最小系统板也就是大家常说的蓝板。这个板子性价比很高但引脚资源有限所以接口选择要谨慎。根据我的经验建议这样分配引脚IIC模式接线方案PB6 - SCLPB7 - SDAVCC - 3.3VGND - GNDCS - VCC必须拉高SDO - 根据地址需求选择SPI模式接线方案PA4 - CSPA5 - SCKPA6 - MISOPA7 - MOSIVCC - 3.3VGND - GNDCS - GND必须拉低这里有个坑我踩过ADXL345的工作电压是2.0-3.6V而STM32的IO口是3.3V电平直接连接没问题。但如果用的是5V Arduino必须加电平转换电路否则容易烧坏传感器。电源滤波也很重要我在传感器VCC引脚附近加了0.1μF的陶瓷电容实测可以明显减少数据抖动。如果环境干扰较大建议再加一个10μF的钽电容。3. IIC通信配置详解STM32的硬件IIC一直是个争议话题很多人说它不好用。但我实测下来只要配置得当硬件IIC还是很稳定的。下面分享我的配置代码void I2C_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; I2C_InitTypeDef I2C_InitStructure; // 使能时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); // 配置GPIO GPIO_InitStructure.GPIO_Pin GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_OD; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); // IIC参数配置 I2C_InitStructure.I2C_Mode I2C_Mode_I2C; I2C_InitStructure.I2C_DutyCycle I2C_DutyCycle_2; I2C_InitStructure.I2C_OwnAddress1 0x00; // 主机地址可随意设置 I2C_InitStructure.I2C_Ack I2C_Ack_Enable; I2C_InitStructure.I2C_AcknowledgedAddress I2C_AcknowledgedAddress_7bit; I2C_InitStructure.I2C_ClockSpeed 400000; // 400kHz I2C_Init(I2C1, I2C_InitStructure); I2C_Cmd(I2C1, ENABLE); }关键点说明GPIO必须配置为开漏输出GPIO_Mode_AF_OD时钟速度不要超过传感器支持的400kHz上拉电阻很重要我一般用4.7kΩ读写函数是核心这里给出我优化过的版本uint8_t I2C_ReadByte(uint8_t devAddr, uint8_t regAddr) { while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); // 发送起始条件 I2C_GenerateSTART(I2C1, ENABLE); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); // 发送设备地址(写) I2C_Send7bitAddress(I2C1, devAddr, I2C_Direction_Transmitter); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); // 发送寄存器地址 I2C_SendData(I2C1, regAddr); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); // 发送重复起始条件 I2C_GenerateSTART(I2C1, ENABLE); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); // 发送设备地址(读) I2C_Send7bitAddress(I2C1, devAddr, I2C_Direction_Receiver); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); // 接收数据 I2C_AcknowledgeConfig(I2C1, DISABLE); I2C_GenerateSTOP(I2C1, ENABLE); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)); return I2C_ReceiveData(I2C1); }4. SPI通信模式切换当需要更高速度的数据采集时SPI是更好的选择。ADXL345的SPI模式支持最高5MHz时钟频率比IIC快很多。切换步骤硬件改动将CS引脚接地连接SPI四线SCK/MOSI/MISO/CS软件配置void SPI_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; // 使能时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); // 配置SCK/MOSI/CS为推挽输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // 配置MISO为浮空输入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); // SPI参数配置 SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode SPI_Mode_Master; SPI_InitStructure.SPI_DataSize SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA SPI_CPHA_1Edge; SPI_InitStructure.SPI_NSS SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_16; // 4.5MHz 72MHz SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial 7; SPI_Init(SPI1, SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); }SPI读写函数与IIC有很大不同uint8_t SPI_ReadByte(uint8_t regAddr) { uint8_t data; // 拉低CS GPIO_ResetBits(GPIOA, GPIO_Pin_4); // 发送寄存器地址(最高位设为1表示读) SPI_I2S_SendData(SPI1, regAddr | 0x80); while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); // 读取数据 while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) RESET); data SPI_I2S_ReceiveData(SPI1); // 拉高CS GPIO_SetBits(GPIOA, GPIO_Pin_4); return data; }5. 传感器初始化与数据采集无论采用哪种通信方式初始化流程都是类似的。根据我的经验这三个寄存器必须配置好DATA_FORMAT寄存器0x31设置量程±2g/±4g/±8g/±16g选择数据输出格式右对齐/左对齐是否启用自测功能BW_RATE寄存器0x2C设置输出数据速率ODR选择低功耗模式POWER_CTL寄存器0x2D启用测量模式选择睡眠模式参数我的初始化函数是这样的void ADXL345_Init(void) { // 设置数据速率100Hz I2C_WriteByte(ADXL345_ADDR, 0x2C, 0x0A); // 设置量程±4g右对齐数据 I2C_WriteByte(ADXL345_ADDR, 0x31, 0x01); // 启用测量模式 I2C_WriteByte(ADXL345_ADDR, 0x2D, 0x08); // 延时等待稳定 Delay_ms(10); }数据采集时要注意ADXL345的数据寄存器是6个连续的8位寄存器0x32-0x37分别对应X、Y、Z轴的低字节和高字节。建议使用突发读取模式一次性读取所有数据void ADXL345_ReadData(int16_t *x, int16_t *y, int16_t *z) { uint8_t buffer[6]; // 突发读取6个字节 I2C_ReadMulti(ADXL345_ADDR, 0x32, buffer, 6); // 组合成16位数据注意字节顺序 *x ((int16_t)buffer[1] 8) | buffer[0]; *y ((int16_t)buffer[3] 8) | buffer[2]; *z ((int16_t)buffer[5] 8) | buffer[4]; }6. 数据处理与校准技巧原始数据不能直接使用需要经过几个处理步骤转换为实际加速度值// 假设量程为±4g分辨率为10位 float g_x (float)x_raw * 0.0078; // 4g/512校准零点偏移将传感器水平静止放置采集100个样本求平均值保存为偏移量滤波处理 我推荐使用简单的移动平均滤波#define FILTER_SIZE 5 int16_t filter_buf[FILTER_SIZE]; int16_t MovingAverage(int16_t new_val) { static uint8_t index 0; int32_t sum 0; filter_buf[index] new_val; index (index 1) % FILTER_SIZE; for(int i0; iFILTER_SIZE; i){ sum filter_buf[i]; } return sum / FILTER_SIZE; }对于要求更高的场合可以尝试卡尔曼滤波。我在四轴飞行器项目中用过效果不错但计算量较大需要根据MCU性能权衡。7. 常见问题排查根据我的调试经验这些问题最常见读取数据全为0xFF或0x00检查CS引脚电平IIC必须拉高确认IIC地址是否正确用逻辑分析仪抓取波形数据抖动严重检查电源是否稳定添加滤波电容降低数据输出速率通信超时检查上拉电阻4.7kΩ-10kΩ降低IIC时钟速度确保SCL/SDA线没有接反有个特别隐蔽的坑STM32的硬件IIC在遇到总线错误后需要重新初始化才能恢复。建议在代码中添加超时判断和自动恢复机制。