☰
STM32F103硬件I2C驱动DRV2605震动马达实战指南
2026/10/28 17:35:49 网站建设 项目流程

简介:本资源面向嵌入式开发初学者与有一定基础的工程师,提供STM32F103C8T6通过I2C总线驱动DRV2605振动马达驱动器的完整工程实践。内容涵盖硬件连接与上拉电阻配置、STM32的I2C外设初始化、基于HAL/LL库的通信代码编写、DRV2605寄存器配置与振动波形选择、马达强度与频率调节,以及通信错误处理和示波器调试思路,帮助读者掌握微控制器外设编程与马达驱动控制的软硬件交互方法。压缩包共247个文件,以c源码、h头文件、s汇编启动文件、o目标文件、d依赖文件及crf编译中间文件为主,另含bin、hex固件、htm/html说明页与uvprojx工程文件,整体约6.95MB,工程结构完整可直接编译参考。目前已有525人学习下载,适合希望快速上手I2C通信与振动反馈控制的开发者借鉴。

1. STM32F103 接 DRV2605 驱动震动马达:为什么 I2C 一上电就翻车

很多人第一次把 DRV2605 焊到 STM32F103 最小系统板上,通电后马达纹丝不动,示波器一挂 SDA 线发现一直被拉低,或者读 WHO_AM_I 寄存器返回 0x00 或 0xFF。这个标题讲的就是用 STM32F103 的硬件 I2C 去配置 DRV2605 这颗触觉驱动芯片,让它按指定波形驱动 ERM 或 LRA 震动马达。它解决的是“MCU 怎么用最少的线把震动效果做出来”的问题,适合做手持设备、穿戴提醒、游戏手柄震动的嵌入式工程师。DRV2605 内部带波形库,不用自己算 PWM 包络,I2C 只负责写寄存器,难点不在协议本身,而在初始化顺序、电源时序和 I2C 时序参数上。下面按我实际调通的顺序,从硬件到代码一步步拆。

2. 先搞清 DRV2605 的寄存器模型和 I2C 地址

2.1 DRV2605 的 7 位地址与写时序

DRV2605 的 7 位 I2C 地址由 ADDR 引脚决定,常见模块默认拉低,地址是 0x5A。STM32F103 的硬件 I2C 外设要求你把 7 位地址左移一位再或上读写位,所以写操作发送的字节是 0xB4,读操作是 0xB5。很多新手直接拿 0x5A 去发,HAL 库会把它当成 8 位地址处理,结果从机根本不响应。

DRV2605 的寄存器是 8 位地址、8 位数据,写时序是:START → 发送 0xB4 → 发送寄存器地址 → 发送数据 → STOP。读时序是:START → 发送 0xB4 → 发送寄存器地址 → 重复 START → 发送 0xB5 → 读一个字节 → NACK → STOP。这个“写地址-写寄存器-重复起始-读地址”的流程是 I2C 读寄存器的标准动作,DRV2605 没有例外。

提示:如果你用软件模拟 I2C,地址直接写 0x5A 和 0x5B 即可,因为软件模拟时你自己控制读写位。硬件 I2C 必须左移。

2.2 必须认识的几个关键寄存器

DRV2605 的寄存器不多,但有几个是初始化绕不开的。0x00 是状态寄存器,上电后读它应该返回 0x00 或 0x04,如果返回 0xFF 说明 I2C 根本没通。0x01 是模式寄存器,写 0x00 进入内部触发模式,写 0x05 进入 RTP 实时播放模式。0x02 是 RTP 数据寄存器,实时模式下往这里写 0-127 控制震动强度。0x03 是波形序列寄存器,内部触发模式下最多放 8 个波形编号。0x04 是波形序列控制,低 3 位是序列长度。0x16 是过驱电压,0x17 是持续电压,这两个决定马达的力度。0x1A 是反馈控制,影响 ERM 和 LRA 的识别。

我一般先读 0x00 确认通信,再读 0x1A 确认芯片有没有正确识别马达类型。如果 0x1A 的 bit7 是 1,说明自动识别完成;如果是 0,说明马达没接好或者反馈电阻不对。

2.3 硬件连接与上电时序

STM32F103 的 I2C1 默认引脚是 PB6(SCL)和 PB7(SDA),这两个脚需要 4.7k 上拉到 3.3V。DRV2605 的 VDD 是 3.3V,但马达的驱动电压可以到 5V,所以模块上通常有 VDD 和 VIN 两个电源脚。VDD 接 3.3V 给芯片逻辑供电,VIN 接 3.7V 或 5V 给马达供电。EN 引脚必须拉高,否则芯片一直处于关断状态,I2C 也不响应。

上电顺序有讲究:先给 VDD 上电,再给 VIN 上电,最后拉高 EN。如果 VIN 先于 VDD 上电,芯片内部可能进入异常状态,I2C 读出来全是 0xFF。我遇到过一批模块,VIN 和 VDD 接反了,结果芯片发热但 I2C 完全没反应,换芯片才解决。

3. 用 STM32CubeMX 配 I2C 并生成初始化代码

3.1 CubeMX 里 I2C 的参数怎么填

打开 CubeMX,选 STM32F103C8T6,在 Connectivity 里勾选 I2C1。Speed Mode 选 Standard Mode 或 Fast Mode,DRV2605 支持 400kHz,但如果你用杜邦线连接,建议先用 100kHz 调通再提速。Clock Speed 填 100000,Duty Cycle 选 2:1。其他参数保持默认,CubeMX 会自动算 CCR 和 TRISE。

生成代码时,Toolchain 选 MDK-ARM,勾选“Generate peripheral initialization as a pair of .c/.h files”。这样 I2C 的初始化会单独放在 i2c.c 里,方便你改。

3.2 生成的 I2C 初始化代码里看什么

CubeMX 生成的MX_I2C1_Init函数里,关键参数是I2C1_Init.ClockSpeed = 100000和I2C1_Init.DutyCycle = I2C_DUTYCYCLE_2。如果你后面要改速度,直接改 ClockSpeed 就行,CubeMX 会重新算 CCR。但要注意,STM32F103 的 I2C 外设有个已知问题:在某些情况下 BUSY 标志会卡住,导致 HAL_I2C_Master_Transmit 超时返回 HAL_BUSY。解决办法是在初始化后加一段复位逻辑,或者用软件 I2C 绕开。

/* i2c.c 中生成的初始化片段 */ static void MX_I2C1_Init(void) { hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 100000; // 100kHz,杜邦线阶段先用低速 hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; // 标准模式占空比 hi2c1.Init.OwnAddress1 = 0; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 = 0; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; if (HAL_I2C_Init(&hi2c1) != HAL_OK) { Error_Handler(); } }

这段代码里NoStretchMode必须关掉,因为 DRV2605 在内部处理波形时可能会拉低 SCL 做时钟拉伸,如果 STM32 不允许拉伸,通信会出错。OwnAddress1填 0 是因为 STM32 做主机,不需要自己的地址。

3.3 写一个 DRV2605 的读写封装

在drv2605.c里封装两个函数:写寄存器和读寄存器。写寄存器用HAL_I2C_Master_Transmit,读寄存器用HAL_I2C_Mem_Read。注意HAL_I2C_Mem_Read的MemAddress参数是 8 位寄存器地址,MemAddSize填I2C_MEMADD_SIZE_8BIT。

/* drv2605.c 读写封装 */ #define DRV2605_ADDR (0x5A << 1) // 7位地址左移,写操作 uint8_t DRV2605_WriteReg(uint8_t reg, uint8_t data) { return HAL_I2C_Mem_Write(&hi2c1, DRV2605_ADDR, reg, I2C_MEMADD_SIZE_8BIT, &data, 1, 100); } uint8_t DRV2605_ReadReg(uint8_t reg, uint8_t *data) { return HAL_I2C_Mem_Read(&hi2c1, DRV2605_ADDR, reg, I2C_MEMADD_SIZE_8BIT, data, 1, 100); }

HAL_I2C_Mem_Write的第二个参数是设备地址,必须是左移后的 0xB4。第三个参数是寄存器地址,第四个参数告诉 HAL 寄存器地址是 8 位。超时设 100ms,太短了在低速下容易误判。如果返回HAL_ERROR,先查硬件上拉和地址,再查 EN 引脚。

4. 初始化序列和震动效果配置的完整流程

4.1 上电后的初始化顺序

DRV2605 的初始化不能乱写,顺序错了芯片不工作。我一般按这个流程走:先读 0x00 确认通信,如果返回 0xFF 就检查硬件;然后写 0x01 为 0x00 进入内部触发模式;接着写 0x1A 配置反馈控制,ERM 马达写 0x36,LRA 马达写 0xB6;再写 0x16 和 0x17 设置过驱和持续电压,一般 0x16 写 0x5A,0x17 写 0x3C;最后写 0x04 设置波形序列长度,写 0x03 开始放波形编号。

/* drv2605.c 初始化函数 */ uint8_t DRV2605_Init(void) { uint8_t id = 0; if (DRV2605_ReadReg(0x00, &id) != HAL_OK) return 1; // 通信失败 if (id == 0xFF || id == 0x00) return 2; // 芯片无响应 DRV2605_WriteReg(0x01, 0x00); // 内部触发模式 DRV2605_WriteReg(0x1A, 0x36); // ERM 马达反馈配置 DRV2605_WriteReg(0x16, 0x5A); // 过驱电压 DRV2605_WriteReg(0x17, 0x3C); // 持续电压 DRV2605_WriteReg(0x04, 0x01); // 序列长度 1 DRV2605_WriteReg(0x03, 0x01); // 波形编号 1,强短震 return 0; }

0x1A的值决定芯片怎么识别马达。ERM 马达的反馈电阻通常接在 OUT+ 和 OUT- 之间,写 0x36 让芯片按 ERM 模型驱动。如果你用的是 LRA 线性马达,写 0xB6,否则震动会很弱或者有异响。0x16和0x17的电压值不是越大越好,过驱电压太高会导致马达发热,我一般从 0x5A 开始试,不够再加。

4.2 用内部波形库触发震动

DRV2605 内部有 123 种波形,编号 1 到 123。编号 1 是强短震,编号 47 是长震,编号 14 是脉冲。你只需要往 0x03 写波形编号,再往 0x0C 写 0x01 触发。触发后芯片自动播放波形,播完自动停止。如果要连续震动,可以在 0x03 里放多个波形编号,0x04 写序列长度。

/* 触发一次震动 */ void DRV2605_PlayWave(uint8_t wave) { DRV2605_WriteReg(0x03, wave); // 波形编号 DRV2605_WriteReg(0x0C, 0x01); // GO 位,触发播放 }

0x0C的 bit0 是 GO 位,写 1 开始播放,播放完成后芯片自动清零。你不需要手动清。如果写 1 后没反应,读 0x00 看 bit0 是否变成 1,如果一直是 0,说明波形编号无效或者芯片没进入内部触发模式。

4.3 RTP 实时模式调震动强度

内部波形库够用,但如果你想根据传感器数据动态调震动强度,就要用 RTP 模式。写 0x01 为 0x05 进入 RTP 模式,然后往 0x02 写 0 到 127 的值,值越大震动越强。RTP 模式下芯片不自动停止,你写 0 就停。

/* RTP 模式设置强度 */ void DRV2605_SetRTP(uint8_t strength) { if (strength > 127) strength = 127; DRV2605_WriteReg(0x02, strength); }

RTP 模式适合做“震动随按键力度变化”的效果。但要注意,RTP 模式下马达一直通电,长时间写大值会发热。我一般加一个软件定时器,超过 500ms 自动写 0。

5. 避坑排查:I2C 读不到数据、马达不震、震动异常的 5 个血泪经验

5.1 读 WHO_AM_I 返回 0xFF

现象:上电后读 0x00 寄存器,返回 0xFF,换一块模块还是 0xFF。原因:I2C 总线上没有从机响应,SDA 被外部拉高。解决:先查 ADDR 引脚电平,确认地址是 0x5A 还是 0x5B;再查 EN 引脚是否拉高,DRV2605 的 EN 必须为高电平才响应 I2C;最后查上拉电阻,PB6/PB7 必须有 4.7k 上拉到 3.3V,很多最小系统板不带这个电阻。

5.2 HAL_I2C_Mem_Write 返回 HAL_BUSY

现象:第一次读写正常,第二次开始一直返回 HAL_BUSY,复位也不管用。原因:STM32F103 的 I2C 外设在总线受到干扰后,BUSY 标志会卡死,这是硬件已知问题。解决:在初始化后加一段 GPIO 模拟复位时序,把 SCL 拉低再拉高几次,然后重新初始化 I2C。或者直接用软件 I2C,彻底绕开这个坑。

5.3 马达不震但 I2C 通信正常

现象:能读到 0x00 返回 0x04,写波形也返回 HAL_OK,但马达不动。原因:VIN 没接或者 EN 没拉高,芯片逻辑部分工作但驱动部分没供电。解决:用万用表量 VIN 对地电压,应该在 3.7V 到 5V 之间;量 EN 对地电压,应该是 3.3V。如果 EN 是 0,检查代码里有没有把 EN 对应的 GPIO 拉高。

5.4 震动很弱或者有异响

现象:马达能震,但力度很小,或者发出“滋滋”声。原因:0x1A 的反馈配置和马达类型不匹配,ERM 写了 LRA 的值,或者 0x16/0x17 电压太低。解决:确认马达是 ERM 还是 LRA,ERM 写 0x36,LRA 写 0xB6;把 0x16 从 0x5A 加到 0x7F,0x17 从 0x3C 加到 0x5A,逐步试。

5.5 波形播放一次后不再触发

现象:第一次写 0x0C 触发成功,第二次写 0x0C 没反应。原因:内部触发模式下,波形播放完成后 0x0C 的 GO 位自动清零,但如果你在播放过程中重复写 0x0C,芯片会忽略。解决:每次触发前读 0x00 的 bit0,确认上一次播放已完成;或者加 10ms 延时再触发。

6. 进阶技巧:用 RTP 模式做力度跟随和低功耗唤醒

RTP 模式是我在实际产品里用得最多的,因为它能把震动强度和传感器数据绑在一起。比如做握力反馈,压力传感器读 0-4095,映射到 0-127 写 0x02,握得越紧震得越强。映射函数很简单,但要注意两点:一是 RTP 模式下马达持续通电,必须加超时保护;二是从 RTP 模式切回内部触发模式时,要先写 0x02 为 0 停震,再写 0x01 为 0x00,否则会有残留震动。

/* 压力映射到 RTP 强度,带 500ms 超时 */ void DRV2605_ForceFeedback(uint16_t pressure) { static uint32_t last_tick = 0; uint8_t strength = (uint8_t)((uint32_t)pressure * 127 / 4095); DRV2605_SetRTP(strength); last_tick = HAL_GetTick(); while (HAL_GetTick() - last_tick < 500) { // 等待或做其他事 } DRV2605_SetRTP(0); // 超时停震 }

低功耗场景下,DRV2605 可以进待机模式,写 0x01 为 0x40,电流降到微安级。唤醒时写 0x01 为 0x00,再重新初始化波形序列。我一般把 EN 引脚也接到 GPIO,不用的时候直接拉低 EN,比寄存器待机更省电。

验证方法很简单:用手机慢动作拍马达,看震动波形是不是和波形编号对应;或者串口打印 0x00 寄存器的值,确认每次触发后 bit0 从 1 变 0。我踩过最深的坑是 EN 引脚悬空,芯片时好时坏,查了两天才发现是 EN 没接。后来养成习惯,拿到新模块先量 EN 和 VIN,再写代码。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询