接线注意(具体详情见手册)
第一份参考资料
- PU+ / PU-:脉冲信号(Pulse),控制电机走几步、走多快 。
- DR+ / DR-:方向信号(Direction),控制电机正转还是反转 。
- EN+ / EN-:使能信号(Enable),控制电机通电锁死还是断电松开 。
第二份参考资料
手册里明确画了两种接法,我们强烈推荐使用的是**"共阳极接法"** 。 所谓的"共阳"(共同的阳极/正极),意思就是把所有带 + 号的引脚,全部统一接到一个固定的正极电压上。 因为 UMLP57 支持 5V~24V 的宽电压信号 ,而 STM32 开发板上刚好有现成的 5V 引脚。
所以:
- 驱动器的 PU+ \rightarrow 接到 STM32 开发板的 5V 引脚。
- 驱动器的 DR+ \rightarrow 接到 STM32 开发板的 5V 引脚。
- (驱动器的 EN+ 和 EN- 悬空不接,手册上说不接的话默认就是使能工作状态 )。
第三份参考资料
1. 脉冲信号 PU- 为什么接 PA0? 在代码中,我们把产生脉冲的任务交给了定时器 2 的通道 1 (TIM2_CH1) 。 在 STM32F103C8T6 这颗芯片的内部硬件电路上,TIM2_CH1 这个通道是物理绑定在 PA0 引脚上 的(这需要查阅 STM32 的数据手册引脚定义表)。 所以,既然代码里用了 TIM2_CH1 来发脉冲,你就必须 把驱动器的 PU- 接到 PA0 上。
2. 方向信号 DR- 为什么接 PA1? 方向信号只是一个普通的高低电平开关。我们在代码里写了: GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; 这里我们人为选定 了 PA1(你选 PA2、PB5 都可以,只要代码里跟着改就行)。因为 PA1 就在 PA0 旁边,接线最方便。 所以,驱动器的 DR- 接到 PA1 上。
代码端
#include "stm32f10x.h"
int main(void)
{
// 1. 定义三个"配置清单"(结构体)
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
// 2. 核心供电:打开对应外设的时钟
// 给 GPIOA 和 AFIO (引脚复用功能) 通电 (它们在高速总线 APB2 上)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
// 给 TIM2 (定时器2) 通电 (它在低速总线 APB1 上)
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// 3. 配置引脚:PA0 (发脉冲) 和 PA1 (控制方向)
// 配置 PA0 连到驱动器的 PU-
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 复用开漏输出 (极其关键)
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置 PA1 连到驱动器的 DR-
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; // 普通开漏输出
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 默认给 PA1 输出高电平(决定了电机初始的正反转方向)
GPIO_SetBits(GPIOA, GPIO_Pin_1);
// 4. 配置定时器 TIM2:设定脉冲频率 (决定电机转速)
// 目标频率:24000 Hz
TIM_TimeBaseStructure.TIM_Prescaler = 30 - 1; // 预分频器 (相当于除以 30)
TIM_TimeBaseStructure.TIM_Period = 100 - 1; // 自动重装载值 (数 100 下为一个周期)
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
// 5. 配置 PWM 波形:塑造脉冲形状
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 允许输出
TIM_OCInitStructure.TIM_Pulse = 50; // 设为周期 100 的一半,产生 50% 占空比的方波
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM2, &TIM_OCInitStructure); // 把配置应用到 TIM2 的通道 1 (也就是 PA0)
// 使能预装载功能(让定时器更稳定)
TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM2, ENABLE);
// 6. 见证奇迹的时刻:按下定时器总开关
TIM_Cmd(TIM2, ENABLE);
// 7. 单片机进入死循环待命,此时底层硬件正在源源不断地自动发送脉冲
while(1)
{
}
}
第一部分:准备"空白申请表"
C
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
- 拆字解密:
Init= Initialization(初始化/启动配置)。TypeDef= Type Definition(类型定义,在C语言里通常指一个"结构体"或"类")。Structure= 结构体/表格。
- 大白话: STM32 的寄存器有成千上万个,直接去里面写 0 和 1 极其容易写错。所以,ST公司发明了"填表法"。这三行代码,就是你作为工厂老板,找秘书拿了三张空白的配置申请表 :
GPIO_...:一张用来分配引脚工人的任务表。TIM_TimeBase...:一张用来设定机器**运行节奏(心跳)**的表。TIM_OC...:一张用来设定发出的波形长什么样的表。
第二部分:核心供电局(RCC 与 APB)
C
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
这是你刚才提问的核心!为什么一个是 APB2,一个是 APB1?
- 拆字解密:
RCC= Reset and Clock Control(复位与时钟控制,即城市供电总局)。APB1/APB2= Advanced Peripheral Bus(高级外设总线)。你可以理解为低速供电网 (APB1) 和 极速供电网 (APB2)。Periph= Peripheral(外围设备/外设)。Clock= 时钟(数字电路里,给时钟就等于给电源动力)。Cmd= Command(下达命令)。
- 对比剖析:为啥要分 APB1 和 APB2?
- 高速网 APB2(最高72MHz): 专门接那些对反应速度要求极高的设备。比如引脚(GPIOA),它可能要在一秒钟内翻转几千万次,必须走极速网。还有
AFIO(引脚功能复用调度中心),它是直接管引脚的,所以也在 APB2 上。 - 低速网 APB1(最高36MHz): 专门接那些在后台默默干活的设备。比如定时器2(TIM2),它就是一个在后台自己数数的机器,不需要跟外部世界瞬间互动,所以为了省电,它被挂在了低速网 APB1 上。
- 高速网 APB2(最高72MHz): 专门接那些对反应速度要求极高的设备。比如引脚(GPIOA),它可能要在一秒钟内翻转几千万次,必须走极速网。还有
- 括号里的参数:
RCC_APB1Periph_TIM2, ENABLE是什么鬼?RCC_APB1Periph_TIM2:这就是供电局配电箱上的**"具体电闸编号"**。你不仅要找到低速网,还得告诉供电局具体合上哪个电闸。ENABLE:英语"使能"。就是**"把电闸推上去"**的意思。- 那个
|符号: 它是 C 语言里的"或"运算符。在这里它的作用就像个**"购物车"** 。GPIOA | AFIO意思是把这两个电闸打包,同时推上去。
第三部分:分配引脚工人(GPIO)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
这里我们开始填第一张表了。
- 拆字解密:
Pin_0:0号引脚。Mode:工作模式。AF_OD= Alternate Function (复用功能) + Open Drain (开漏)。
- 大白话:
- 你在表上写下:"让 PA0 员工上班,速度拉满到 50MHz。"
- 极其关键的
AF_OD:AF意思是老板(CPU)不管了,把 PA0 外包给机器(TIM2)直接控制。OD(开漏) 意思是,PA0 引脚内部变成一个接地的开关。因为我们的驱动器接了 5V,输出 0 时开关接地导通,输出 1 时开关断开,完美兼容 5V 且不会烧坏单片机。 GPIO_Init(GPIOA, &...): 填完表后,把这张表的内存地址(&)交到 GPIOA 车间主任的手里,让他照着表去设置底层的物理电路。
第四部分:设定发脉冲的频率(TIM_TimeBase)
TIM_TimeBaseStructure.TIM_Prescaler = 30 - 1;
TIM_TimeBaseStructure.TIM_Period = 100 - 1;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
这里开始填第二张表。为了让传送带跑到完美的 60转/分钟,我们需要一秒钟发送 24000 个脉冲。
- 拆字解密:
TimeBase= 时基(时间的基准节奏)。Prescaler= 预分频器(减速齿轮)。Period= 周期/自动重装载值(数多少下算一个完整的动作)。
- 大白话(算数时间):
- 单片机的心脏跳动是 72MHz(一秒 7200万下)。
Prescaler = 30 - 1:给心脏加一个 30 倍的减速齿轮。7200万 / 30 = 240万。现在机器一秒钟只跳 240万下了。Period = 100 - 1:规定机器每数 100 下,就算完成一个"脉冲周期"。240万 / 100 = 24000。- 完美!这就是为什么这台机器现在每秒钟能精准产生 24kHz 脉冲的底层数学逻辑。减 1 是因为计算机是从 0 开始数数的。
一步一步推导:
1. 机械层:传送带想要多快?
- 目标: 你想要减速箱的最终输出轴(连着传送带的那个轴),每分钟转 60 圈(60 RPM)。
- 换算成秒: 每分钟 60 圈,等于 每秒转 1 圈。
2. 电机层:电机本体需要多快?
- 物理结构: 买的是 1:15 的涡轮减速电机。意思是:电机本体要在里面狂转 15 圈,外面的输出轴才慢吞吞地转 1 圈。
- 计算: 输出轴每秒转 1 圈,所以电机本体需要:1 \times 15 = 15 圈/秒。
3. 驱动器层:一秒钟需要喂多少个脉冲?
- 硬件设置: 根据 UMLP57 手册,我们把拨码开关 SW3~SW6 设置成了 1600 细分 。意思是你得给驱动器发送 1600 个脉冲,电机本体才肯转 1 圈。
- 计算: 电机本体每秒要转 15 圈,所以每秒需要的总脉冲数(也就是目标频率 f): f = 15 \times 1600 = 24000\text{ Hz} 结论:只要单片机能精准发出 24000 Hz 的频率,传送带就是完美的 60 RPM!
4. 单片机层:怎么把 72MHz 变成 24kHz?
- 系统心跳: STM32 的主频是 72,000,000 Hz。我们要把它降到 24,000 Hz。
- 总降速倍数: 72000000 / 24000 = 3000 倍。
- 拆分给两个寄存器: 单片机规定,这个 3000 倍必须由
Prescaler(预分频) 和Period(周期) 两个齿轮乘起来共同完成。 - 分配方案: 我们把 3000 拆成 30 \times 100。
Prescaler负责承担 30 倍减速(代码里写 30 - 1 = 29)。Period负责承担 100 倍减速(代码里写 100 - 1 = 99)。
- 这就是
TIM_Prescaler = 29和TIM_Period = 99的由来。
第五部分:塑造波形的形状(TIM_OC)
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_Pulse = 50;
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
光有频率不行,驱动器(UMLP57)要求脉冲必须是一会儿高、一会儿低的"方波"。
TIM_OCMode_PWM1 为什么选 PWM1?
-
拆字解密:
OC= Output Compare(输出比较)。Mode= 模式。PWM1= 脉宽调制模式 1(STM32 还有个 PWM2 模式)。
-
大白话对比(PWM1 vs PWM2): 还记得我们定了一个周期是 100 下(0~99),并且把比较值
Pulse定为了 50 吗?机器在从 0 数到 99 的过程中,会不断和 50 这个数字作比较。- 选 PWM1(正常人的思维): 当数到的数字 小于 50 时,输出 高电平(有电) ;当数字 大于等于 50 时,输出 低电平(没电)。
- 选 PWM2(反人类的思维): 刚好反过来!当数字小于 50 时,没电;大于等于 50 时,反而有电。
-
为什么选 PWM1? 因为 PWM1 符合人类最直观的逻辑。我们把
Pulse设为 50,就是想要前面 50% 的时间有电(占空比 50%)。如果你想要脉冲变窄(比如占空比 20%),你只需要把Pulse改成 20 即可。所见即所得。 -
拆字解密:
OC= Output Compare(输出比较)。单片机底层有一个比较器,一边数数,一边和设定的值比较。Pulse= 脉冲宽度(决定占空比)。
-
大白话:
-
上一步我们规定了
Period是 100。也就是机器从 0 数到 99。 -
这里我们把
Pulse设为 50。 -
机器工作逻辑:当数到的数字小于 50(占了一半的时间)时,引脚输出导通;当数字大于等于 50 时,引脚输出断开。这就完美切出了一个 50% 时间通、50% 时间断的方波。
TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM2, ENABLE);
-
-
拆字解密:
Preload= 预装载。 -
大白话: 这相当于一个**"缓冲信箱"**。如果你以后想用代码动态改变电机的速度,新指令会先放在信箱里。等机器把当前这个周期(这100下)完整跑完,再在下一个周期使用新速度。防止机器刚跑到一半,你突然变卦,导致波形撕裂、电机咔哒作响。
TIM_ARRPreloadConfig 到底有什么用?
这里要把你之前可能混淆的两个东西分开: 代码里其实有两行长得很像的"预装载 (Preload)":
TIM_OC1PreloadConfig(这是给 OC,也就是 Pulse 脉冲宽度/占空比用的)TIM_ARRPreloadConfig(这是给 ARR,也就是 Period 周期/总频率用的)
`TIM_ARRPreloadConfig(TIM2, ENABLE);这个配置是防止电机在"动态调速"时发生灾难性抖动的"保命符"。
- 拆字解密:
ARR= Auto-Reload Register(自动重装载寄存器)。它就是我们代码里写的那个Period = 100 - 1,决定了脉冲的总频率(电机的绝对速度)。Preload= 预装载(影子寄存器缓冲机制)。
- 大白话:没有它会发生什么灾难? 假设你的传送带正在以 60 RPM 欢快地跑着(此时底层硬件正在从 0 数到 99,到了 99 就清零发一个脉冲)。 突然!你通过串口下达了一个紧急指令,要求速度瞬间翻倍!也就是周期时间要缩短一半,代码立刻把底层的 ARR 从 99 改成了 49。 致命瞬间来了: 如果在这个瞬间,底层的计数器刚好已经数到了 60 怎么办? 因为上限突然变成了 49,而现在已经是 60 了。机器一比对,发现 60 永远追不上 49,它就会彻底发疯!它会一路狂飙数到计算机的最大值(65535),然后再从 0 开始数到 49。 现象: 你的电机本该加速,结果却突然"咔"地卡顿停滞了长达几十毫秒,然后才开始疯狂加速。这不仅会把传送带上的东西甩飞,还极容易弄坏驱动器。
- 大白话:加上
ENABLE后有什么奇效? 开启了ARRPreload,就相当于给硬件加了一个**"排队等候室(影子寄存器)"** 。 当你紧急下令把上限改成 49 时,这个 49 不会立刻生效,而是先坐在等候室里喝茶。 底层机器依然按照原来的 99 把当下这一个完整的脉冲平稳地发完。等数到 99 归零的一瞬间(这叫更新事件),49 才会从等候室里跳出来,正式顶替原来的 99。 现象: 电机的调速过程丝滑,无论你怎么疯狂地下发改变速度的代码,电机绝不卡顿、绝不丢步!
第六部分:按下核按钮与袖手旁观
TIM_Cmd(TIM2, ENABLE);
-
拆字解密:
Cmd= 命令。 -
大白话: 表格全填完了,底层的物理电路也全按表格配置好了。这句代码就是按下 TIM2 这台全自动发脉冲机器的绿色启动按钮 (ENABLE)。
-
只要这一行执行完,PA0 引脚就会如同发电机一样,源源不断地向驱动器输出 24kHz 的方波。
while(1)
{
} -
大白话:
while(1)是一个永远出不来的死循环。 -
为什么里面什么都不写?因为发脉冲这个动作,在前面的
AF_OD配置中,已经被彻底外包给了 TIM2 硬件去执行。