资讯动态

MMA8452三轴加速度计驱动实战:STM32与51单片机I2C通信详解

发布时间:2026/10/15 13:07:07 来源:尧图企业网站定制
简介这份资源围绕MMA8452三轴加速度传感器展开面向嵌入式初学者与需要快速集成该传感器的开发者提供STM32与51单片机两套平台的驱动代码与配套资料可用于运动检测、倾斜角度测量、振动分析及屏幕自动旋转等场景。压缩包共390个文件约10.89MB以c源码、h头文件、o目标文件、d依赖文件为主另含pdf说明文档、txt笔记、hex固件、uvproj/uvopt工程文件及STARTUP.A51启动文件等覆盖从工程配置到编译产物的完整链路。目前已有805人学习下载。资料中包含角度测量相关程序与较完整的库函数、示例代码读者可据此理解I2C/SPI接口初始化、配置寄存器写入、XYZ轴数据读取、单位换算与滤波处理等关键环节并对照工程结构完成STM32或51平台的移植与调试适合作为入门练手与项目快速集成的参考。1. 三轴加速度计MMA8452的两种MCU驱动方案从寄存器手册到能跑的工程如果你手头有一块MMA8452模块想把它接到STM32或者51单片机上大概率会遇到一个尴尬模块卖家给的资料要么只有一份模糊的寄存器截图要么代码里I2C时序写得跟玄学一样换块板子就翻车。MMA8452是一颗三轴加速度计I2C接口12位分辨率带可编程中断工作电压1.95V到3.6V在姿态检测、振动采集、倾角测量这些场景里出场率很高。它本身不复杂复杂的是不同MCU平台的I2C实现差异——STM32有硬件I2C和软件模拟两条路51只能靠软件模拟时序延时、上拉电阻、地址位处理稍有偏差就读不出数据。这篇笔记把MMA8452的资料拆成两条可复现的驱动路径STM32一套、51一套讲清楚寄存器怎么配、I2C怎么写、参数怎么调、坑在哪让新手能照着跑通熟手能看到边界条件。2. 先搞懂MMA8452的寄存器地图和I2C通信约定2.1 器件地址与读写位0x1D和0x1C不是两个器件MMA8452的7位I2C从机地址由SA0引脚决定SA0接GND时地址是0x1C接VCC时是0x1D。很多资料里同时出现0x1D和0x1C新手容易以为是两个不同芯片。实际上这是同一个器件的两个可选地址模块上通常用跳线或焊盘选择。写操作时主机先发地址字节格式是7位地址左移一位再或上读写位写是(addr 1) | 0读是(addr 1) | 1。以0x1D为例写地址字节是0x3A读地址字节是0x3B。如果你在代码里直接写0x1D当写地址用通信必然失败这是最常见的翻车点之一。寄存器方面真正需要关心的不多。WHO_AM_I0x0D固定返回0x2A用来确认通信是否正常CTRL_REG10x2A控制工作模式和输出数据率XYZ_DATA_CFG0x0E设置量程STATUS0x00的bit3是ZYXDR表示新数据就绪OUT_X_MSB0x01开始连续6个字节是X/Y/Z的12位数据高字节在前低字节的高4位有效。读数据时建议从0x01开始连续读6字节用自动递增地址模式避免分次读造成三轴数据不同步。2.2 量程、数据率与分辨率之间的取舍XYZ_DATA_CFG的FS位设置量程0x00对应±2g0x01对应±4g0x02对应±8g。量程越小每LSB对应的加速度越小分辨率越高。±2g时12位数据每LSB约0.98mg±8g时每LSB约3.9mg。做倾角测量选±2g做振动或冲击检测选±8g。CTRL_REG1的DR位设置输出数据率从1.56Hz到800Hz。数据率越高功耗越大噪声也越大。一般姿态检测用50Hz或100Hz足够振动采集才需要400Hz以上。注意修改CTRL_REG1的DR位之前要先进入待机模式CTRL_REG1的bit0清零改完再激活否则设置不生效。这个细节很多简化代码里没写导致数据率死活改不动。2.3 上拉电阻与总线电容I2C波形不对先查这里I2C总线必须接上拉电阻典型值4.7kΩ。模块上一般自带上拉但如果你用裸芯片或者模块上拉被去掉就必须外接。上拉电阻太小低电平灌电流大可能超过器件承受能力太大上升沿变缓高速率下波形爬不到高电平。总线电容也关键线越长、挂的器件越多电容越大上升时间越长。400kHz速率下总线电容超过200pF就可能通信不稳定。排查时先用示波器看SCL和SDA的上升沿如果上升沿明显圆滑先减小上拉电阻或缩短走线。没有示波器的话把速率降到100kHz试试能通说明是上升沿问题。3. STM32驱动MMA8452硬件I2C与软件模拟的选型与实现3.1 硬件I2C还是软件模拟先看你的STM32型号和引脚STM32的硬件I2C外设在早期型号上有一些已知的时序问题尤其是F1系列在某些条件下会卡在总线忙状态。如果你用的是F4、G0、G4这些较新的系列硬件I2C基本可以放心用。判断标准很简单先写一个只发地址、不读数据的测试看能否收到ACK。如果硬件I2C反复失败换软件模拟通常能解决。软件模拟的代价是占用CPU时间但在100kHz速率下对大多数应用来说可以接受。我一般会先用硬件I2C试通了就用硬件不通就切软件模拟不在这上面耗太久。3.2 硬件I2C初始化与寄存器读写函数以STM32F4的HAL库为例初始化I2C1时钟100kHz7位地址模式。下面这段代码封装了单寄存器写、单寄存器读、多字节连续读三个函数。#include stm32f4xx_hal.h extern I2C_HandleTypeDef hi2c1; #define MMA8452_ADDR 0x1D // SA0接VCC时的7位地址 // 写单个寄存器 uint8_t MMA8452_WriteReg(uint8_t reg, uint8_t data) { // HAL库的Mem_Write会自动处理地址左移和读写位 if (HAL_I2C_Mem_Write(hi2c1, MMA8452_ADDR 1, reg, I2C_MEMADD_SIZE_8BIT, data, 1, 100) ! HAL_OK) { return 1; // 失败 } return 0; } // 读单个寄存器 uint8_t MMA8452_ReadReg(uint8_t reg, uint8_t *data) { if (HAL_I2C_Mem_Read(hi2c1, MMA8452_ADDR 1, reg, I2C_MEMADD_SIZE_8BIT, data, 1, 100) ! HAL_OK) { return 1; } return 0; } // 从OUT_X_MSB开始连续读6字节 uint8_t MMA8452_ReadXYZ(uint8_t *buf) { if (HAL_I2C_Mem_Read(hi2c1, MMA8452_ADDR 1, 0x01, I2C_MEMADD_SIZE_8BIT, buf, 6, 100) ! HAL_OK) { return 1; } return 0; }逻辑说明HAL_I2C_Mem_Write和HAL_I2C_Mem_Read内部会自动发送寄存器地址然后进行数据传输。注意第二个参数是地址左移一位后的值HAL库不帮你左移。超时参数100ms是保守值实际通信正常时几毫秒内完成。如果返回失败先查地址是否正确、上拉是否接好、总线是否被占用。3.3 初始化序列与数据读取的完整流程初始化顺序很重要先读WHO_AM_I确认通信再进入待机模式配置量程和数据率最后激活。uint8_t MMA8452_Init(void) { uint8_t who 0; // 1. 确认通信 if (MMA8452_ReadReg(0x0D, who) ! 0) return 1; if (who ! 0x2A) return 2; // WHO_AM_I不对 // 2. 进入待机模式 if (MMA8452_WriteReg(0x2A, 0x00) ! 0) return 3; // 3. 设置量程±2g if (MMA8452_WriteReg(0x0E, 0x00) ! 0) return 4; // 4. 设置数据率100Hz激活 // DR0x18对应100Hzbit01激活 if (MMA8452_WriteReg(0x2A, 0x19) ! 0) return 5; return 0; } // 读取三轴加速度返回0表示成功 uint8_t MMA8452_GetAccel(int16_t *x, int16_t *y, int16_t *z) { uint8_t buf[6]; uint8_t status 0; // 检查数据就绪 if (MMA8452_ReadReg(0x00, status) ! 0) return 1; if ((status 0x08) 0) return 2; // 数据未就绪 if (MMA8452_ReadXYZ(buf) ! 0) return 3; // 12位数据高字节在前低字节高4位有效 *x (int16_t)((buf[0] 8) | buf[1]) 4; *y (int16_t)((buf[2] 8) | buf[3]) 4; *z (int16_t)((buf[4] 8) | buf[5]) 4; // 符号扩展12位有符号数如果bit11为1需要补高位 if (*x 2047) *x - 4096; if (*y 2047) *y - 4096; if (*z 2047) *z - 4096; return 0; }参数说明CTRL_REG1写入0x19二进制是0001 1001bit01激活bit3:210对应100Hzbit5:401对应低噪声模式。如果你要改数据率查手册的DR编码表改bit5:3的值。符号扩展那一步不能省否则负加速度会变成很大的正数。读数据前检查STATUS的bit3确保读到的是新数据避免重复读旧值。4. 51单片机驱动MMA8452软件模拟I2C的时序与延时控制4.1 51的I2C模拟延时函数是核心51单片机没有硬件I2C必须用GPIO模拟。模拟I2C的关键是延时SCL高电平期间和低电平期间都要保持足够时间确保从机采样正确。标准100kHz下一个完整时钟周期10微秒高电平至少4微秒低电平至少4.7微秒。但51的指令周期跟晶振有关11.0592MHz晶振下一个_nop_约1微秒。实际延时用循环实现具体循环次数需要根据晶振调整。下面以11.0592MHz为例延时约5微秒。#include reg52.h #include intrins.h sbit SCL P1^0; sbit SDA P1^1; #define MMA8452_ADDR_W 0x3A // 0x1D1 | 0 #define MMA8452_ADDR_R 0x3B // 0x1D1 | 1 // 约5微秒延时11.0592MHz晶振 void I2C_Delay(void) { unsigned char i; for (i 0; i 5; i) { _nop_(); } } // 起始条件SCL高时SDA由高变低 void I2C_Start(void) { SDA 1; SCL 1; I2C_Delay(); SDA 0; I2C_Delay(); SCL 0; I2C_Delay(); } // 停止条件SCL高时SDA由低变高 void I2C_Stop(void) { SDA 0; SCL 1; I2C_Delay(); SDA 1; I2C_Delay(); } // 发送一个字节返回0表示收到ACK unsigned char I2C_SendByte(unsigned char dat) { unsigned char i, ack; for (i 0; i 8; i) { SDA (dat 0x80) ? 1 : 0; dat 1; I2C_Delay(); SCL 1; I2C_Delay(); SCL 0; I2C_Delay(); } // 第9个时钟读ACK SDA 1; // 释放SDA I2C_Delay(); SCL 1; I2C_Delay(); ack SDA; // 0表示ACK SCL 0; I2C_Delay(); return ack; } // 接收一个字节ack0发送ACKack1发送NACK unsigned char I2C_RecvByte(unsigned char ack) { unsigned char i, dat 0; SDA 1; // 释放SDA for (i 0; i 8; i) { SCL 1; I2C_Delay(); dat 1; if (SDA) dat | 0x01; SCL 0; I2C_Delay(); } // 发送ACK或NACK SDA ack ? 1 : 0; I2C_Delay(); SCL 1; I2C_Delay(); SCL 0; I2C_Delay(); SDA 1; // 释放SDA return dat; }逻辑说明起始和停止条件的时序不能颠倒SCL高电平期间SDA变化会被识别为起始或停止。发送字节时数据在SCL低电平期间变化高电平期间稳定。接收字节时主机先释放SDA从机驱动数据主机在SCL高电平期间采样。ACK位由接收方拉低SDA表示。延时函数是调参重点如果通信不稳定先加大延时通了再逐步减小。4.2 寄存器读写与初始化在模拟I2C基础上封装MMA8452的读写函数。// 写寄存器 unsigned char MMA8452_WriteReg(unsigned char reg, unsigned char dat) { I2C_Start(); if (I2C_SendByte(MMA8452_ADDR_W)) { I2C_Stop(); return 1; } if (I2C_SendByte(reg)) { I2C_Stop(); return 2; } if (I2C_SendByte(dat)) { I2C_Stop(); return 3; } I2C_Stop(); return 0; } // 读寄存器 unsigned char MMA8452_ReadReg(unsigned char reg, unsigned char *dat) { I2C_Start(); if (I2C_SendByte(MMA8452_ADDR_W)) { I2C_Stop(); return 1; } if (I2C_SendByte(reg)) { I2C_Stop(); return 2; } I2C_Start(); // 重复起始条件 if (I2C_SendByte(MMA8452_ADDR_R)) { I2C_Stop(); return 3; } *dat I2C_RecvByte(1); // 最后一个字节发NACK I2C_Stop(); return 0; } // 连续读6字节 unsigned char MMA8452_ReadXYZ(unsigned char *buf) { unsigned char i; I2C_Start(); if (I2C_SendByte(MMA8452_ADDR_W)) { I2C_Stop(); return 1; } if (I2C_SendByte(0x01)) { I2C_Stop(); return 2; } I2C_Start(); if (I2C_SendByte(MMA8452_ADDR_R)) { I2C_Stop(); return 3; } for (i 0; i 5; i) { buf[i] I2C_RecvByte(0); // 前5字节发ACK } buf[5] I2C_RecvByte(1); // 最后一字节发NACK I2C_Stop(); return 0; }参数说明读操作中先写地址和寄存器地址然后发重复起始条件再发读地址。连续读时前5个字节主机发ACK最后一个字节发NACK然后停止。如果发NACK后从机还在驱动SDA停止条件会失败所以发完NACK要先释放SDA再拉高SCL产生停止条件。初始化序列跟STM32一样只是底层读写换成了模拟I2C。4.3 51上的数据拼接与串口输出验证51没有方便的调试器验证数据是否正确最直接的方法是串口打印。把三轴数据转成字符串通过UART发到电脑。void UART_Init(void) { TMOD 0x20; // 定时器1模式2 TH1 0xFD; // 9600波特率11.0592MHz TL1 0xFD; SCON 0x50; // 模式1允许接收 TR1 1; } void UART_SendByte(unsigned char dat) { SBUF dat; while (!TI); TI 0; } void UART_SendStr(char *str) { while (*str) { UART_SendByte(*str); } } // 主循环中读取并打印 void main(void) { unsigned char buf[6]; int x, y, z; UART_Init(); MMA8452_Init(); while (1) { if (MMA8452_ReadXYZ(buf) 0) { x ((int)((buf[0] 8) | buf[1])) 4; y ((int)((buf[2] 8) | buf[3])) 4; z ((int)((buf[4] 8) | buf[5])) 4; if (x 2047) x - 4096; if (y 2047) y - 4096; if (z 2047) z - 4096; // 打印x/y/z这里省略数字转字符串函数 } // 延时 } }逻辑说明51的int是16位拼接时先把两个字节合成16位再右移4位取12位有效数据。符号扩展同样不能省。串口打印用于验证数据是否随姿态变化如果数值不变或跳变剧烈先查I2C通信是否稳定再查量程和数据率配置。5. 避坑与排查MMA8452调试中最容易翻车的五个点5.1 读出来全是0或全是0xFF现象WHO_AM_I读不到0x2A或者读出来是0x00或0xFF。原因通常是I2C通信根本没建立。先查地址SA0引脚电平决定0x1C还是0x1D代码里地址左移了吗再查上拉电阻SDA和SCL是否有4.7kΩ上拉用万用表测空闲时SDA和SCL是否都是高电平。如果上拉正常但读不到用示波器看起始条件后从机有没有拉低SDA发ACK。没有ACK说明从机没响应可能是供电问题——MMA8452的VDD范围1.95V到3.6V如果你用5V供电芯片可能已经损坏。5.2 数据率改了没反应现象修改CTRL_REG1的DR位后输出数据率不变。原因是修改DR位必须在待机模式下进行。CTRL_REG1的bit0是激活位写0进入待机改完DR再写1激活。很多简化代码直接写CTRL_REG1而不先清零bit0导致DR位被忽略。正确顺序先写0x00到CTRL_REG1再写目标值bit01两步之间不需要延时。5.3 三轴数据不同步或跳变现象X、Y、Z数据看起来不像是同一时刻的采样或者静止时数值跳动很大。原因是分次读取三个轴中间隔了其他操作。解决方法是连续读6个字节利用MMA8452的地址自动递增。另外如果数据率设得太高而读取间隔太长STATUS的ZYXDR位可能已经覆盖了多组数据读到的只是最新一组。静止时跳动大还可能是量程选得太大±8g时每LSB约3.9mg噪声相对明显换±2g会改善。5.4 51上通信时好时坏现象同样的代码有时候能读到数据有时候读不到。原因多半是延时不够或晶振频率不匹配。51的模拟I2C延时依赖晶振如果你用的是12MHz晶振但延时按11.0592MHz算时序会偏快。解决方法是加大延时循环次数先确保能通再逐步减小到稳定工作的最小值。另外51的P1口作为I2C引脚时需要确认没有其他外设占用且P1口有内部上拉51的P1口有弱上拉但驱动I2C可能不够建议外接4.7kΩ。5.5 中断引脚配置后没有触发现象配置了CTRL_REG4和CTRL_REG5使能中断但INT引脚没有输出。原因是中断触发条件没配对。比如你想用数据就绪中断需要使能CTRL_REG4的bit4DRDY同时CTRL_REG5的bit4要置1把中断路由到INT1或INT2引脚。另外中断引脚是开漏输出需要外接上拉电阻。如果用的是模块模块上通常已经有上拉但如果你直接接裸芯片别忘了这个上拉。排查时先读INT_SOURCE寄存器0x0C看中断源是否被触发再查引脚配置。6. 进阶技巧用FIFO和中断降低功耗并提高数据可靠性MMA8452有一个32样本的FIFO可以在MCU休眠时由传感器自己采集数据攒够一批再唤醒MCU读取。这在电池供电的振动记录或姿态追踪场景里很有用。配置FIFO需要设置F_SETUP0x09寄存器bit7:6是FIFO模式01是循环模式10是停止模式bit5:0是水位标记达到这个样本数触发中断。比如设置水位为10FIFO攒够10组数据后触发中断MCU醒来一次性读走30字节10组×3轴×2字节。读取时从OUT_X_MSB开始连续读地址自动递增会循环回FIFO起始位置。中断配置方面CTRL_REG4的bit6是FIFO中断使能CTRL_REG5的bit6决定路由到INT1还是INT2。INT引脚接MCU的外部中断下降沿触发。MCU进入中断后先读INT_SOURCE确认是FIFO中断再读FIFO数据最后读F_STATUS0x0B确认FIFO状态。注意FIFO溢出时F_STATUS的bit7会置1表示有数据被覆盖这时候读到的数据可能不连续需要在软件里处理。验证FIFO是否正常工作的一个简单方法静止放置模块配置FIFO水位为5触发中断后读出的5组数据应该基本一致只有噪声差异。如果5组数据差异很大说明FIFO配置有问题或者读取时序不对。我一般会先用轮询方式读STATUS确认数据就绪跑通后再切到FIFO加中断这样出问题时容易定位是FIFO配置错了还是中断没配好。最后一个血泪经验MMA8452的寄存器写入后不是立即生效的尤其是涉及模式切换的配置。改完CTRL_REG1后最好延时几毫秒再读数据给芯片内部状态机切换的时间。这个延时在数据手册里没有明确写但实测中不加延时偶尔会读到旧配置下的数据。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑