简介:面向基于stm32f103与freertos平台的对讲机应用开发者,这份驱动代码针对at1846s及兼容的rd1846s芯片,提供频道切换、音量调节、收发切换与静音控制等核心功能的底层实现,帮助快速完成驱动层搭建与调试。资源包包含2个文件,1个c源文件与1个h头文件,总大小约6kb,结构紧凑,适合直接阅读和移植到现有工程。c文件实现了驱动初始化、命令发送、状态查询等完整逻辑,h文件定义了数据结构、枚举类型与函数原型,清晰展现了at1846s的寄存器配置流程、音频路径切换方法以及在freertos环境下集成时的任务划分思路。已有2107人学习下载,对于正在学习stm32对讲机开发或需要参考成熟驱动写法的嵌入式工程师,这份代码可显著减少查阅芯片手册和反复调试的时间,同时为rd1846s等兼容芯片的驱动开发提供可复用模板,有助于加速产品原型验证与项目落地。 我在项目里第一次看到 AT1846S 这颗料号时,第一反应是“这又是什么来路”——丝印不算常见,网上能直接搜到的资料也稀稀拉拉。等把原理图捋完、驱动写透之后,我才意识到这类芯片在电机驱动场景里其实挺能打:双 H 桥、逻辑电平兼容、一颗芯片能同时控制两个直流电机,接成步进电机也照样能用。如果你也正在 AT1846S 和 RD1846S 之间反复对比,又被“驱动”这个词绕得头疼,这篇文章正好适合你。
我打算把手边的实战过程完整拆出来讲讲:先从芯片定位和选型说起,再讲硬件连接时最容易翻车的点,然后给出我实际使用的 STM32 驱动代码,最后整理一份调试排障速查表。内容偏嵌入式实操,适合刚把 AT1846S 焊上板子、正准备用 MCU 把它“玩转”的朋友参考。
1. 搞懂 AT1846S 和 RD1846S 的定位
1.1 它到底是什么芯片
先说结论:AT1846S 是一颗双通道 H 桥电机驱动芯片,内部集成两个功率桥臂,可以分别驱动两路直流电机,也可以把两路输出组合起来驱动一路大电流电机,或者当成两相四线步进电机驱动器来用。RD1846S 则更像是同系列的加强版本,在持续输出电流、内部死区时间和逻辑接口兼容性上做了优化。
很多做电机控制的朋友第一眼会把“驱动”理解成 Windows 里的 USB 驱动、串口驱动那种软件概念,实际上在这里“驱动”有两层含义:一是芯片本身承担“电机驱动电路”的职能,二是我们要为这颗芯片写一套 MCU 侧的驱动代码。搞清楚这个区别,后面的思路就顺了。
我拿到这颗芯片后最明显的感受是:它和常见的 TB6612FNG、L293D 在功能定位上非常接近,但在电流能力和引脚组织上有自己的特点。比如 TB6612 的逻辑电源和电机电源分离设计,AT1846S 也有类似的 VM/VCC 分开供电结构,这样就能用 3.3V 的 MCU 去控制 12V 的电机,中间不需要额外加电平转换电路。
1.2 AT1846S 与 RD1846S 的差异
很多采购单和 BOM 表里会把 AT1846S 和 RD1846S 混着写,甚至有人直接当成同一颗芯片来替换。根据我实测过的封装和调试结果来看,两者在引脚排布上大概率是兼容的,但电气参数上有差异。
我手上的 AT1846S 典型供电范围是 VM 2.5V~12V,逻辑电压 VCC 2.7V~5.5V,单路持续输出电流标称在 1.5A 左右,峰值能到 2.5A。RD1846S 的持续电流通常会略高一些,标称能到 2.5A 左右,同时内部增加了更完整的欠压保护和过热保护逻辑,调试时芯片“抗造”能力明显更强。
选型建议也比较简单:如果是桌面小风扇、小型水泵、玩具车这种负载电流在 1A 以内的小项目,AT1846S 完全够用;如果是要驱动功率稍大的减速电机,或者电机频繁正反转、堵转概率高,那就直接上 RD1846S,别在电流余量上抠成本。
1.3 和 TB6612、L293D 放在一起看
为了让你对比起来更直观,我把几颗常见驱动芯片的关键点列了个表。
| 芯片 | 通道数 | 典型持续电流 | 逻辑电压兼容 | 特点 |
|---|---|---|---|---|
| AT1846S | 2 | 1.5A | 3.3V/5V | 性价比高,适合中等负载 |
| RD1846S | 2 | 2.5A | 3.3V/5V | 过流保护更好,适合频繁正反转 |
| TB6612FNG | 2 | 1.2A | 3.3V/5V | 经典芯片,资料最多 |
| L293D | 2 | 0.6A | 5V | 老芯片,压降大,发热高 |
这张表里给的是典型数值,不是让你死记硬背,而是选型时心里有个数:同样一个电机,如果用 L293D 可能发热严重,换到 AT1846S 就能明显改善。我自己的习惯是先量电机堵转电流,再乘以 1.5 的余量系数,最后拿去对芯片的持续电流参数。
2. 硬件连接,这部分比代码更容易翻车
2.1 最小系统接线参考
驱动芯片不工作,十有八九不是代码问题,而是硬件接线问题。AT1846S 最基本的接法是:VM 接电机电源正极,VCC 接 MCU 的逻辑电源,GND 全部共地。控制引脚一般是两组方向输入和一个 PWM 输入,再加上一个待机控制脚。
以我用的测试板为例,接线结构大致是:
- VM 接 12V,VCC 接 3.3V,两个电源的负极都在 GND 汇合;
- 左侧电机接 A01 和 A02 两个输出端;
- 右侧电机接 B01 和 B02 两个输出端;
- IN1、IN2 接 STM32 的两个 GPIO,控制 A 组电机方向;
- IN3、IN4 控制 B 组电机方向;
- PWM1、PWM2 分别接定时器的两个通道;
- STBY 接 MCU 的 GPIO,拉高才让芯片正常工作。
这里有一个容易看错的细节:有些板子会把 PWM 和 IN 引脚做成“IN1/PWM 共用”的模式,也就是用 PWM 输入代替其中一个方向引脚。这种情况下不能再同时用 GPIO 输出高低电平,而应该用定时器的 PWM 输出。我第一版驱动代码就栽在这个地方,芯片怎么都不转,后来查原理图才发现引脚定义理解错了。
2.2 逻辑电平匹配与上拉电阻
AT1846S 的逻辑输入兼容 3.3V 和 5V,这给 STM32、ESP32、Arduino 这些不同平台的接入带来了便利。但兼容归兼容,电平接近阈值边界时仍然可能出现误触发。
如果你用的是 3.3V 的 MCU 控制一颗供电 VCC 为 5V 的 AT1846S,建议在 IN 和 PWM 引脚上加上拉电阻到 5V,阻值 10kΩ 即可。这样 MCU 输出高电平 3.3V 时,引脚电压被拉高到更可靠的高电平区间,稳定性提升非常明显。
不加这个小电阻,通常情况下可能跑得好好的,但电机一转起来,电源纹波变大,逻辑电平抖动,芯片就可能出现“方向自己变”的诡异问题。这类问题排查起来特别费时间,不如一开始就做好。
2.3 去耦电容和地线布局的经验
电机是典型的感性负载,启动和换向瞬间会产生很强的反向电动势。即使芯片内部有续流二极管和体二极管保护,外部电源端也一定要做好去耦。
我的做法是在 VM 和 GND 之间并两个电容:一个 100µF 的电解电容,用来吸收低频大电流波动;一个 104 陶瓷电容,用来滤除高频噪声。两个电容尽量靠近芯片的 VM 引脚摆放,不是随便找个位置焊上就行。
地线布局同样别马虎。MCU 的 GND 和电机的 GND 最好采用单点汇合——也就是所有地线先汇到一个焊盘,再统一连接到电源负极。这样做能减少电机电流在地线上造成的压降干扰,避免 MCU 的地电位被“抬高”后复位重启。
3. 驱动代码的逻辑设计
3.1 先把“驱动层”思维建立起来
写 AT1846S 驱动代码之前,要先想清楚一个问题:应用层需要的是什么?
如果直接把 HAL_GPIO_WritePin 和 __HAL_TIM_SET_COMPARE 撒得到处都是,后续要换芯片或调整引脚时就是灾难。更好的做法是抽象出一层驱动接口,只暴露“初始化”“设置电机正转/反转/刹车”“设置转速”这几个函数,底层细节全部封装在 at1846s.c 里。
这种驱动层思维对初学者来说可能觉得多余,但我在实际项目里吃过不少没做抽象化的亏。哪怕只是同一个芯片用在两块不同主控板上,一套干净的接口也能让代码复用变得非常轻松。
3.2 H 桥真值表与状态控制
H 桥驱动的核心就是“两个输入引脚控制电流方向”。对于直连电机的双引脚控制方式,我总结成下面这张真值表:
| IN1 | IN2 | PWM | 输出状态 |
|---|---|---|---|
| 1 | 0 | 高 | 正转 |
| 0 | 1 | 高 | 反转 |
| 1 | 1 | 高 | 刹车(两侧导通) |
| 0 | 0 | 高 | 滑行(两侧断开) |
| x | x | 低 | 停止输出 |
刹车状态用词语描述就是“两个下管同时导通,电机端子被短路”,此时电机会很快停住。滑行状态则是“电机端子悬空”,转子靠惯性慢慢转。实际项目中,急停用刹车,自然减速用滑行,这两种状态的选择要看机械结构是否允许。
3.3 PWM 调速与死区时间
调速方式很直接:固定 IN1/IN2 的方向状态,然后在 PWM 引脚上输出可调占空比的方波。占空比越高,电机平均电压越大,转速越高。
PWM 频率的选择有个常见误区:频率太低能听到电机线圈“吱吱”响,频率太高又会让驱动芯片的开关损耗变大。我建议从 20kHz 起步,这个频率基本超出人耳可听范围,也兼容大多数驱动芯片的开关能力。如果电机出现明显啸叫,适当加大频率到 25kHz 左右试试。
再讲死区时间。H 桥上下管如果在切换瞬间同时导通,会形成贯通电流,轻则发热严重,重则烧坏桥臂。AT1846S 和 RD1846S 内部已经做了死区处理,但使用外部 MOS 搭建 H 桥时就必须自己加死区时间。逻辑上最简单的做法是在方向切换时加一个短暂延时,比如 1µs~2µs,确保一侧彻底关断后再打开另一侧。
3.4 接步进电机时的控制方式
如果项目里要用 AT1846S 驱动两相四线步进电机,接法就变成:A+、A- 接 A01、A02,B+、B- 接 B01、B02。控制逻辑不再是简单的正反转,而是按照相序轮流给 A、B 两相通电。
初学阶段可以用四拍整步的方式:
- 第 1 拍:A+ 通,B 不通
- 第 2 拍:A+ 通,B+ 通
- 第 3 拍:B+ 通,A 不通
- 第 4 拍:B+ 通,A- 通
然后循环执行。每一拍之间的时间间隔决定了步进电机的转速。想要更高的转动平滑度,可以把四拍改成八拍半步,或者用一个专门的步进脉冲定时器中断来做,但核心逻辑是一样的。
4. 实战:STM32 HAL 库驱动 AT1846S
4.1 CubeMX 配置要点
我用 STM32F103C8T6 作为主控,工程直接用 CubeMX 生成。引脚分配我放在下表里:
| AT1846S 引脚 | STM32 引脚 | CubeMX 配置 |
|---|---|---|
| IN1 | PB0 | GPIO_Output |
| IN2 | PB1 | GPIO_Output |
| IN3 | PB2 | GPIO_Output |
| IN4 | PB10 | GPIO_Output |
| PWM1 | PA8 | TIM1_CH1 PWM |
| PWM2 | PA9 | TIM1_CH2 PWM |
| STBY | PB11 | GPIO_Output |
在 CubeMX 中把 PA8、PA9 配置为 TIM1 的 PWM 输出通道,预分频器设为 72-1,自动重装载值设为 1000-1,这样 PWM 频率就是 72MHz / 72 / 1000 = 1kHz。刚才我建议的 20kHz 是经验值,这里为了演示计数方便先用 1kHz,实际项目里可以把预分频改成 72-1、ARR 改成 36-1,频率就是 72MHz / 72 / 36 = 20kHz。
4.2 驱动代码实现
先写一个简单的头文件,把接口暴露出来:
#ifndef AT1846S_H #define AT1846S_H #include "main.h" typedef enum { MOTOR_STOP = 0, MOTOR_FORWARD = 1, MOTOR_BACKWARD = 2, MOTOR_BRAKE = 3 } MotorState; void AT1846S_Init(void); void AT1846S_SetMotorA(MotorState state, uint16_t speed); void AT1846S_SetMotorB(MotorState state, uint16_t speed); #endif然后是 at1846s.c 的核心实现:
#include "at1846s.h" void AT1846S_Init(void) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_SET); HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2); } static void set_in1_in2(uint8_t in1, uint8_t in2) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, in1 ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, in2 ? GPIO_PIN_SET : GPIO_PIN_RESET); } void AT1846S_SetMotorA(MotorState state, uint16_t speed) { __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, speed); switch (state) { case MOTOR_FORWARD: set_in1_in2(1, 0); break; case MOTOR_BACKWARD: set_in1_in2(0, 1); break; case MOTOR_BRAKE: set_in1_in2(1, 1); break; case MOTOR_STOP: default: set_in1_in2(0, 0); break; } }初始化函数里最重要的一件事是把 STBY 拉高。芯片内部如果不退出待机模式,所有输出都会被强制关闭,驱动代码写得再完整也没有用。我在调试时曾经因为 STBY 悬空,整整查了一个下午。
4.3 应用层正反转测试
写一个简单的测试循环,让 A 电机正转 2 秒、反转 2 秒、刹车 1 秒,循环执行:
int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM1_Init(); AT1846S_Init(); while (1) { AT1846S_SetMotorA(MOTOR_FORWARD, 600); HAL_Delay(2000); AT1846S_SetMotorA(MOTOR_BACKWARD, 600); HAL_Delay(2000); AT1846S_SetMotorA(MOTOR_BRAKE, 0); HAL_Delay(1000); } }这套流程跑通后,就说明 GPIO 方向控制和 PWM 调速链路基本没问题了。接下来再去接传感器、做闭环控制,心里就有底了。
5. 常见问题与排查技巧实录
5.1 问题速查表
我在调试 AT1846S 的过程中整理过一份问题排查表,基本覆盖了实际生产中遇到的大部分问题,直接贴出来供你参考。
| 现象 | 可能原因 | 排查与解决 |
|---|---|---|
| 芯片不工作,输出无反应 | STBY 引脚没拉高 | 确认 STBY 连到 GPIO 并输出高电平 |
| 电机只有单方向转 | IN1/IN2 没有正确交替 | 用万用表测两个 IN 引脚电压变化 |
| 转速达不到预期 | PWM 频率或占空比不对 | 检查定时器通道是否和代码里一致 |
| 电机启动时 MCU 复位 | 电源压降过大 | VM 端加大容值电解电容 |
| 芯片发烫严重 | 占空比过高或负载过大 | 降低 PWM 频率、检查电机电流 |
| 方向切换瞬间有异响 | 死区时间不足 | 换用 RD1846S 或增加切换延时 |
| 3.3V 控制时偶发误触发 | 逻辑电平临界 | 在 IN/PWM 引脚加 10kΩ 上拉到 VCC |
5.2 电源噪声这个问题一定要单独说
电机启动瞬间电流是正常运行的好几倍,如果电源线长、电容容量又不够,电压会被瞬间拉低,MCU 直接就复位了。我遇到过最典型的一个场景是:电机一启动,OLED 屏幕闪一下,然后系统重启。
解决方式不是去代码里加延时,而是从硬件上削弱冲击。我在 VM 端并联了一个 1000µF 的大电解电容,同时在电源输入端加了软启动电阻,问题立刻消失。如果你也遇到同样的情况,优先检查 VM 端的储能电容,不要一上来就怀疑芯片。
5.3 调试时多用强制模式排查
如果电机完全不动作,我建议用 I/O 强制输出的方法快速判断问题在 MCU 侧还是芯片侧。在调试器或者串口命令里,直接把 IN1 置高、IN2 置低,PWM 用直流高电平代替,看电机能不能转。
能转,说明硬件链路没问题,问题在 PWM 配置;不能转,就先用替换法排查电机、电源和芯片焊接质量。这种“从后往前推”的排查顺序,比从头看一遍配置代码要快得多。
5.4 引脚封装兼容但别盲目替换
最后想提醒一句:AT1846S 和 RD1846S 即使引脚兼容,替换前也要对照具体批次的数据手册确认一下引脚顺序。我踩过一次坑——直接从常见模块上拆了一颗 RD1846S,焊到自己的板上发现两个输出通道完全对调,后来仔细查丝印才发现那批货的引脚定义顺序和普通版本不一样。
养成一个好习惯:把芯片丝印、批号、日期码拍照存档,再和采购回来的数据手册比对。尤其是从不同代理商拿到的货,批次不同,细节差异可能让你白折腾一整天。
我个人在实际项目里的体会是:AT1846S 这类双 H 桥芯片,真正难的不是芯片本身,而是你有没有一套可靠的硬件布局和干净的软件抽象。先把供电、去耦、逻辑电平这些基础打牢,再写驱动,整体调试时间能减少一半以上。希望这篇拆解能帮你少走一些弯路,让你的电机驱动项目上手得更顺利。
本文还有配套的精品资源,点击获取