下位机直接控制57步进电机匀速转动

接线注意(具体详情见手册)

第一份参考资料

  • 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公司发明了"填表法"。这三行代码,就是你作为工厂老板,找秘书拿了三张空白的配置申请表 :
    1. GPIO_...:一张用来分配引脚工人的任务表。
    2. TIM_TimeBase...:一张用来设定机器**运行节奏(心跳)**的表。
    3. 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 上。
  • 括号里的参数: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)":

  1. TIM_OC1PreloadConfig (这是给 OC,也就是 Pulse 脉冲宽度/占空比用的)
  2. 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 硬件去执行。

相关推荐
聆思科技AI芯片1 小时前
【AI硬件开发上手教程】怎么使用AI编程实现小聆AI语音控制舵机
人工智能·嵌入式硬件·开源·ai编程·智能硬件
abc light2 小时前
ARM 裸机开发(6)——时钟树与定时器
arm开发·单片机·fpga开发
吠品12 小时前
智谱开源 ZCode 之后,真正的考试才开始
单片机·嵌入式硬件·1024程序员节
Chery114013 小时前
高速ADC与精密DAC怎么选?DP9230D与DP8532D应用解析
嵌入式硬件·物联网·射频工程
小李不想当小白16 小时前
SPI通信外设与W25Q64 (硬件SPI)(STM32标准库学习笔记)
笔记·stm32·单片机·嵌入式硬件·学习·嵌入式
LCG米16 小时前
STM32驱动W5500以太网模块实测:TCP吞吐瓶颈定位、TX指针回绕陷阱与断线重连保活落地
stm32·单片机·嵌入式硬件·tcp/ip
尼喃18 小时前
662K/65Z5三脚稳压芯片规格书—PW6566B33/B30引脚功能
嵌入式硬件
创界工坊工作室18 小时前
63-基于STC89C52霍尔传感器的智能化电能表设计
stm32·单片机·嵌入式硬件·51单片机·proteus
智购科技自动售货机厂家19 小时前
让AI经营自动售货机:从Project Vend看工具权限与业务规则~YH
大数据·服务器·单片机·嵌入式硬件·深度学习