第06章 电机驱动控制
约 2318 字大约 8 分钟
2026-01-26
9.1 电机参数


9.2 电机驱动芯片
TOSHIBA(东芝)公司的TB67S109A芯片是一种配备PWM斩波器的两相双极步进电机驱动芯片。内置时钟解码器。采用BiCD工艺制作,额定值为50V/4.0A。允许全步、半步、1/4、1/8、1/16、1/32步(细分)运行,即细分最高为32。TB67S109A芯片的内部逻辑电路见图:

① 功能引脚
CLK 功能
| CLK 输入 | 功能 |
|---|---|
| 上升沿 | 改变每一步的电角 |
| 下降沿 | 电角的状态不变 |
ENABLE 功能
| ENABLE 输入 | 功能 |
|---|---|
| H | 输出级 ='ON'(正常运行模式) |
| L | 输出级 ='OFF'(高阻抗模式) |
方向功能
| CW/CCW 输入 | OUT (+) |
|---|---|
| H:顺时针方向运行 (CW)** | H |
| L:逆时针方向运行 (CCW)** | L |
细分设置功能
| DMODE0 | DMODE1 | DMODE2 | 功能 |
|---|---|---|---|
| L | L | L | 待机模式 (OSCM 被禁用,输出级被设置为 “OFF” 状态) |
| L | L | H | 全步分辨率 |
| L | H | L | 半步分辨率 (类型 (A)) |
| L | H | H | 四分之一步分辨率 |
| H | L | L | 半步分辨率 (类型 (B)) |
| H | L | H | 1/8 步分辨率 |
| H | H | L | 1/16 步分辨率 |
| H | H | H | 1/32 步分辨率 |
② 步进电机驱动器
步进电机驱动器是一款专业的两相步进电机驱动。可实现正反转控制,通过3 位拨码开关选择7档细分控制(1,2/A,2/B,4,8,16,32,),通过3位拨码开关选择8档电流控制(0.5A,1A,1.5A,2A,2.5A,2.8A,3.0A,3.5A)。适合驱动57、42型两相、四相混合式步进电机。能达到低振动、小噪声、高速度的效果驱动电机。
| 类别 | 信号名称 | 说明 |
|---|---|---|
| 信号输入端 | PUL+ | 脉冲信号输入正 |
| 信号输入端 | PUL- | 脉冲信号输入负 |
| 信号输入端 | DIR+ | 电机正、反转控制正 |
| 信号输入端 | DIR- | 电机正、反转控制负 |
| 信号输入端 | EN+ | 电机脱机控制正 |
| 信号输入端 | EN- | 电机脱机控制负 |
输入信号共有三路,它们是:①步进脉冲信号PUL+,PUL-;②方向电平信号DIR+,DIR-③脱机(关闭)信号EN+,EN-。输入信号接口有两种接法,用户可根据 需要采用共阳极接法或共阴极接法。
共阳极接法:分别将PUL+,DIR+,EN+连接到控制系统的电源正极上,如果此电源是+5V/3.3V则可直接接入,如果此电源大于+5V,则须外部另加限流电阻R,保证给驱动器内部光藕提供8—15mA 的驱动电流。脉冲输入信号通过PUL-接入,方向信号通过DIR-接入,使能信号通过EN-接入。
共阴极接法:分别将PUL-,DIR-,EN-连接到控制系统的地线(GND)上。脉冲输入信号通过PUL+接入,方向信号通过DIR+接入,使能信号通过EN+接入。如果此控制系统信号线是+5V/3.3V 则可直接接入,如果此信号电压大于+5V,则须外部另加限流电阻R,保证给驱动器内部光藕提供8—15mA 的驱动电流。
我们的例程选择共阳接法:
| 驱动器端子 | 功能 | 开发板引脚 |
|---|---|---|
| ENA- | 停机引脚,当 ENA - 接 GND,ENA + 接 5V 时电机停止旋转,通过控制 ENA - 高低电平控制电机是否旋转 | PXX |
| ENA+ | 配合 ENA - 控制电机旋转状态 | +5V |
| DIR- | 旋转方向控制,当 DIR + 接 + 5V 时,通过控制 DIR - 引脚高低电平控制电机旋转方向 | PXX |
| DIR+ | 配合 DIR - 控制电机旋转方向 | +5V |
| PUL- | 脉冲信号,每接收一个脉冲信号步进电机旋转一步 | PXX |
| PUL+ | 配合 PUL - 传输脉冲信号 | +5V |
③ 6位拨码开关
通过调整6位拨码开关调整: 步分辨率和驱动电流

拨片向上表示闭合,拨片向下表示断开。
步分辨率:
默认旋转一圈是200个步长(每个步长1.8度),芯片可以通过调整电流做进一步的提高精度,这里有7个精度档次。
| DMODE0 | DMODE1 | DMODE2 | 功能 |
|---|---|---|---|
| L | L | L | 待机模式 (OSCM 被禁用,输出级被设置为 “OFF” 状态) |
| L | L | H | 全步分辨率 |
| L | H | L | 半步分辨率 (类型 (A)) |
| L | H | H | 四分之一步分辨率 |
| H | L | L | 半步分辨率 (类型 (B)) |
| H | L | H | 1/8 步分辨率 |
| H | H | L | 1/16 步分辨率 |
| H | H | H | 1/32 步分辨率 |
开关断开(拨片向下)则高电平,开关闭合(拨片向上)则低电平。

驱动电流:
SW4、SW5、SW6负责控制驱动电流,对负载要求越高电流要求就越高。

根据开关控制电流: 由于教学环境没有什么负载,可以设置最小电流。
下图的ON OFF指SW4 SW5 SW6 的开关状态,不是指电平。

9.3 开发实现
① 原理图


② CubeMX 设置
设置 TIM1_Channel1 输出比较模式为翻转模式:

开启 TIM1的更新中断:

设置电机启动引脚和方向控制引脚:

③ 硬件接口层代码
Int_Motor.h
#ifndef __INT_MOTOR_H__
#define __INT_MOTOR_H__
#include <math.h>
#include "gpio.h"
#include "tim.h"
#include "Com_Global.h"
#include "Com_Debug.h"
/**
* @brief 启动电机
*
*/
void Int_Motor_Start(void);
/**
* @brief 停止电机
*
*/
void Int_Motor_Stop(void);
/**
* @brief 设置电机速度
*
*/
void Int_Motor_SetSpeed(void);
/**
* @brief 获取已经旋转的角度
*
*/
void Int_Motor_GetCurrentAngle(void);
/**
* @brief TIM1更新中断触发后被调用的函数
*
*/
void Int_Motor_UpdateCallback(void);
#endif /* __INT_MOTOR_H__ */Int_Motor.c
#include "Int_Motor.h"
// 定义静态全局变量:保存电机已经旋转角度对应的ITM1更新周期数
static uint32_t gs_current_angle_update_periods = 0;
// 定义静态全局变量:保存电机目标旋转角度对应的TIM1更新周期数
static uint32_t gs_target_angle_update_periods = 0;
/**
* @brief 启动电机
*
*/
void Int_Motor_Start(void)
{
// 1. 设置电机的旋转方向,目标角度是正数,设置为顺时针;目标角度是负数,设置为逆时针
if (g_target_angle > 0)
{
// 设置 MOTOR_DIR 为高电平
HAL_GPIO_WritePin(MOTOR_DIR_GPIO_Port, MOTOR_DIR_Pin, GPIO_PIN_SET);
}
else
{
// 设置 MOTOR_DIR 为低电平
HAL_GPIO_WritePin(MOTOR_DIR_GPIO_Port, MOTOR_DIR_Pin, GPIO_PIN_RESET);
}
// 2. 设置电机速速
Int_Motor_SetSpeed();
// 3. 计算目标角度对应的TIM1更新周期数
gs_target_angle_update_periods = fabs(g_target_angle) / 360 * 3200;
// 4. 清零已经旋转的角度对应的ITM1更新周期数
gs_current_angle_update_periods = 0;
// 5. 启动电机,设置 MOTOR_EN 为高电平
HAL_GPIO_WritePin(MOTOR_EN_GPIO_Port, MOTOR_EN_Pin, GPIO_PIN_SET);
// 6. 启动TIM1定时器的更新中断和TIM1_Channel1输出比较
HAL_TIM_Base_Start_IT(&htim1);
HAL_TIM_OC_Start_IT(&htim1, TIM_CHANNEL_1);
}
/**
* @brief 停止电机
*
*/
void Int_Motor_Stop(void)
{
// 停止电机,设置 MOTOR_EN 为低电平
HAL_GPIO_WritePin(MOTOR_EN_GPIO_Port, MOTOR_EN_Pin, GPIO_PIN_RESET);
// 停止TIM1定时器的更新中断和TIM1_Channel1输出比较
HAL_TIM_Base_Stop_IT(&htim1);
HAL_TIM_OC_Stop_IT(&htim1, TIM_CHANNEL_1);
}
/**
* @brief 设置电机速度
*
*/
void Int_Motor_SetSpeed(void)
{
/*
定时器溢出(更新)2次,产生1个上升沿
转1圈,需要需要200*8个上升沿;合计需要3200个更新周期
TIM1
PSC=71; 1us计1个数
根据转速计算ARR值,转速用rpm表示(转速:转/分钟)
一圈所用时间: (1 / rpm) * 60 * 1000 * 1000 us
定时器1次更新(溢出)所用时间: (1 / rpm) * 60 * 1000 * 1000 / 3200 us
定时器ARR的值:(1 / rpm) * 60 * 1000 * 1000 / 3200 - 1
*/
// 设置定时器ARR值
__HAL_TIM_SET_AUTORELOAD(&htim1, (1 / g_target_speed) * 60 * 1000 * 1000 / 3200 - 1);
}
/**
* @brief 获取已经旋转的角度
*
*/
void Int_Motor_GetCurrentAngle(void)
{
// 根据周期数计算已经旋转的角度
g_current_angle = gs_current_angle_update_periods / 3200.0f * 360.0f;
// 如果是逆时针转,将g_current_angle转为负数
if (g_target_angle < 0)
{
g_current_angle = -g_current_angle;
}
}
/**
* @brief TIM1更新中断触发后被调用的函数
*
*/
void Int_Motor_UpdateCallback(void)
{
// 增加已经旋转角度对应的ITM1更新周期数
gs_current_angle_update_periods++;
// 如果已经旋转角度对应的ITM1更新周期数大于等于目标角度对应的TIM1更新周期数
if (gs_current_angle_update_periods >= gs_target_angle_update_periods)
{
// 停止电机
Int_Motor_Stop();
}
}④ 应用层代码
App_KeyControl.h
添加头文件
#include "Int_Motor.h"App_KeyControl.c
修改如下:
/* 代码省略 ... */
void App_KeyControl_Run(void)
{
// 检测按键
switch (Int_Key_IsDetect())
{
case 1:
/* 代码省略 ... */
// 如果是第1页,目标速度增加
else if (g_current_page == 1)
{
g_target_speed += SPEED_STEP;
if (g_target_speed > MAX_TARGET_SPEED)
{
g_target_speed = MAX_TARGET_SPEED;
}
// 设置电机目标速度
DEBUG_PRINTLN("目标速度设置为: %f", g_target_speed);
Int_Motor_SetSpeed();
}
/* 代码省略 ... */
break;
case 2:
/* 代码省略 ... */
// 如果是第1页,目标速度减少
else if (g_current_page == 1)
{
g_target_speed -= SPEED_STEP;
if (g_target_speed < MIN_TARGET_SPEED)
{
g_target_speed = MIN_TARGET_SPEED;
}
// 设置电机目标速度
DEBUG_PRINTLN("目标速度设置为: %f", g_target_speed);
Int_Motor_SetSpeed();
}
/* 代码省略 ... */
break;
case 3:
// 检测到SW3,启动操作
DEBUG_PRINTLN("SW3 pressed, 启动电机! ");
// 启动电机
Int_Motor_Start();
break;
/* 代码省略 ... */
}
}App_Task.h
添加头文件:
#include "Int_Motor.h"App_Task.c
修改如下:
/* 代码省略 ... */
// 显示任务函数的实现
void disaply_task_callback(void *pvParameters)
{
printf("显示任务启动... \n");
// 初始化显示模块
App_Display_Init();
while (1)
{
// 如果是第0页,获取当前已经旋转的角度
if (g_current_page == 0)
{
// 获取当前已经旋转的角度
Int_Motor_GetCurrentAngle();
}
// 进入临界区,防止I2C通信被打断
taskENTER_CRITICAL();
// 更新显示
App_Display_Update();
// 退出临界区
taskEXIT_CRITICAL();
// 延时100ms
vTaskDelay(100);
}
}
/* 代码省略 ... */