一、基础知识
1. 步进电机控制方式
脉冲+方向控制(最常见)
控制信号:
DIR方向:高低电平决定正转或反转;
STEP脉冲:每个脉冲电机前进一步(可通过端口拉高拉低来模拟脉冲,或使用pwm来生成脉冲);
方法 定义说明 GPIO 模拟脉冲 用软件代码控制 GPIO 引脚"高/低"来模拟脉冲信号,通常通过 delay_us()
等手动延时PWM 脉冲(定时器) 使用硬件定时器自动生成一定频率和占空比的脉冲波形,直接输出到 GPIO 引脚
2. GPIO模拟脉冲和PWM生成脉冲两者的区别
| 项目 | GPIO 模拟脉冲 | PWM 定时器输出 |
| 控制方式 | 纯软件控制:代码中手动翻转引脚 | 硬件自动输出,CPU不再关心 |
| 精度/频率稳定性 | 受 CPU、延时函数精度影响(不稳定) | 非常精准(定时器硬件级别) |
| CPU 占用率 | 高:CPU 要一直跑在 delay 上 | 低:设置一次,自动输出 |
| 适合任务 | 简单、低速、临时用 | 实时、高速、精确的脉冲控制 |
| 速度上限 | 通常 <5kHz,超了容易乱 | 可达几十 kHz 或更高 |
| 支持加减速控制 | 复杂,需要自己调节 delay 变量 | 更容易调节频率,甚至可做变频波(配合 ARR) |
控制灵活性 灵活(逐步手动控制每一脉冲) 受限于定时器结构,但效率高
3. 驱动器细分档
驱动器上的"细分挡位"是用于设置步进电机细分数(microstepping)的开关,它可以控制电机每收到一个STEP脉冲时的实际转动角度,以实现更平滑、更高精度的运动控制。
普通步进电机的步距角通常是:1.8°(即每转动一圈需要200个整步)。
细分数 每微步角度(1.8°电机) 每圈脉冲数 1(全步) 1.8° 200 2(半步) 0.9° 400 4 0.45° 800 8 0.225° 1600 16 0.1125° 3200 32 0.05625° 6400 例如下面的驱动器,可以调节M1、M2、M3来控制细分档位。
二、实验
1. GPIO模拟脉冲方式
实验现象:正转2圈,延时,反转1圈。
main.c
cpp
#include "stm32f10x.h"
#include "usart1.h"
#include "delay.h"
#include "string.h"
#include "systick.h"
#include "led.h"
#include "motor.h"
#include "key.h"
int main(void)
{
SystemInit();
delay_init();
StepMotor_GPIO_Init();
StepMotor_Enable(); // 开启电机
LED_Init();
Key_Init();
StepMotor_SetDirection(1); // 设置方向
while (1) {
// if (GPIO_ReadInputDataBit(KEY_GPIO, KEY1_PIN) == Bit_RESET) // 按键1按下(低电平有效)
// {
// StepMotor_SetDirection(1); // 顺时针
// StepMotor_StepPulse(200, 500); // 200 步,800us 间隔(约625Hz)
// LED_ON();
// }
// else if (GPIO_ReadInputDataBit(KEY_GPIO, KEY2_PIN) == Bit_RESET) // 按键2按下
// {
// StepMotor_SetDirection(0); // 顺时针
// StepMotor_StepPulse(200, 500); // 200 步,800us 间隔(约625Hz)
// LED2_ON();
// }else{
// LED_OFF();
// LED2_OFF();
// }
StepMotor_TurnOneCircle(1, 2, 500); // 正转2圈,500us间隔(约1666Hz)
delay_ms(8000);
StepMotor_TurnOneCircle(0, 1, 500); // 反转一圈
delay_ms(8000);
}
}
motor.c
cpp
#include "motor.h"
void StepMotor_GPIO_Init(void) {
RCC_APB2PeriphClockCmd(STEPMOTOR_RCC, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = STEP_PIN | DIR_PIN | EN_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(STEPMOTOR_GPIO, &GPIO_InitStructure);
GPIO_ResetBits(STEPMOTOR_GPIO, STEP_PIN);
GPIO_ResetBits(STEPMOTOR_GPIO, DIR_PIN);
GPIO_SetBits(STEPMOTOR_GPIO, EN_PIN); // 默认使能(高电平)
}
void StepMotor_Enable(void) {
GPIO_SetBits(STEPMOTOR_GPIO, EN_PIN); // 高电平 = 使能
}
void StepMotor_Disable(void) {
GPIO_ResetBits(STEPMOTOR_GPIO, EN_PIN); // 低电平 = 失能(断电)
}
void StepMotor_SetDirection(uint8_t dir) {
if (dir)
GPIO_SetBits(STEPMOTOR_GPIO, DIR_PIN);
else
GPIO_ResetBits(STEPMOTOR_GPIO, DIR_PIN);
}
void StepMotor_StepPulse(uint32_t steps, uint32_t delay_us_val) {
for (uint32_t i = 0; i < steps; i++) {
GPIO_SetBits(STEPMOTOR_GPIO, STEP_PIN); // STEP 上升沿
delay_us(delay_us_val);
GPIO_ResetBits(STEPMOTOR_GPIO, STEP_PIN); // 下降沿
delay_us(delay_us_val);
}
}
void StepMotor_TurnOneCircle(uint8_t dir, uint16_t circles, uint32_t speed_us)
{
StepMotor_SetDirection(dir); // 设置方向(1=正转,0=反转)
for (uint32_t i = 0; i < circles*1600; i++) { // 1600步 = 一圈(8细分)
GPIO_SetBits(GPIOA, STEP_PIN); // STEP高
delay_us(speed_us);
GPIO_ResetBits(GPIOA, STEP_PIN); // STEP低
delay_us(speed_us);
}
}
// 简单延时函数(可使用SysTick或Timer)
void delay_us_function(uint32_t us)
{
for (uint32_t i = 0; i < us * 8; i++)
__NOP();
}
motor.h
cpp
#ifndef __MOTOR_H
#define __MOTOR_H
#include "stm32f10x.h"
// 步进电机引脚定义(你已说明:PA1 -> STEP,PA2 -> DIR,PA3 -> EN)
// GPIO 配置
#define STEPMOTOR_GPIO GPIOA
#define STEPMOTOR_RCC RCC_APB2Periph_GPIOA
#define STEP_PIN GPIO_Pin_1
#define DIR_PIN GPIO_Pin_2
#define EN_PIN GPIO_Pin_3
void StepMotor_GPIO_Init(void);
void StepMotor_SetDirection(uint8_t dir);
void StepMotor_Enable(void);
void StepMotor_Disable(void);
void StepMotor_StepPulse(uint32_t steps, uint32_t delay_us_val);
void StepMotor_TurnCircle(uint8_t dir, uint16_t circles, uint32_t speed_us);
void delay_us(uint32_t us);
#endif // __STEPMOTOR_H
key.c
cpp
#include "key.h"
static uint8_t key1_last = 1;
static uint8_t key2_last = 1;
void Key_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitTypeDef gpio;
// KEY1/KEY2 输入,带上拉
gpio.GPIO_Pin = KEY1_PIN | KEY2_PIN;
gpio.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(KEY_GPIO, &gpio);
}
uint8_t Key_Scan(void)
{
uint8_t key1_now = GPIO_ReadInputDataBit(KEY_GPIO, KEY1_PIN); //读取当前电平,0为按下。
uint8_t key2_now = GPIO_ReadInputDataBit(KEY_GPIO, KEY2_PIN);
uint8_t result = KEY_NONE; //0,没有按键
// 按键1:检测下降沿
if (key1_last == 1 && key1_now == 0) {
delay_ms(20); // 消抖
if (GPIO_ReadInputDataBit(KEY_GPIO, KEY1_PIN) == Bit_RESET)
result = KEY1_PRESSED; //result =1,表示按键1按下。
}
// 按键2:检测下降沿
if (key2_last == 1 && key2_now == 0) {
delay_ms(20); // 消抖
if (GPIO_ReadInputDataBit(KEY_GPIO, KEY2_PIN) == Bit_RESET)
result = KEY2_PRESSED; //result =2,表示按键1按下。
}
key1_last = key1_now;
key2_last = key2_now;
return result;
}
key.h
cpp
#ifndef __KEY_H
#define __KEY_H
#include "stm32f10x.h"
#define KEY1_PIN GPIO_Pin_1 // 正转按键 //PC1
#define KEY2_PIN GPIO_Pin_3 // 反转按键 //PC3
#define KEY_GPIO GPIOC
// 返回值定义(按下事件)
#define KEY_NONE 0
#define KEY1_PRESSED 1
#define KEY2_PRESSED 2
void Key_Init(void);
uint8_t Key_Scan(void);
#endif
2. pwm输出脉冲方式
实验现象:按下按键1电机正转2圈,按下按键2电机正转1圈。
tim2_pwm.c
cpp
#include "tim2_pwm.h"
volatile uint32_t step_count;
volatile uint32_t target_steps;
void StepMotor_PWM_Init(uint32_t freq_hz)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef gpio;
gpio.GPIO_Pin = GPIO_Pin_1;
gpio.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
gpio.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpio);
TIM_TimeBaseInitTypeDef tim;
uint16_t prescaler = 72 - 1; // 假设主频72MHz → 1MHz定时器
uint16_t period = 1000000 / freq_hz - 1; // 计算周期
tim.TIM_Prescaler = prescaler;
tim.TIM_CounterMode = TIM_CounterMode_Up;
tim.TIM_Period = period;
tim.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM2, &tim);
TIM_OCInitTypeDef oc;
oc.TIM_OCMode = TIM_OCMode_PWM1;
oc.TIM_OutputState = TIM_OutputState_Enable;
oc.TIM_Pulse = period / 2; // 50% 占空比
oc.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC2Init(TIM2, &oc); // PA1 = CH2
TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); // 开启更新中断
NVIC_InitTypeDef nvic;
nvic.NVIC_IRQChannel = TIM2_IRQn;
nvic.NVIC_IRQChannelPreemptionPriority = 1;
nvic.NVIC_IRQChannelSubPriority = 1;
nvic.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvic);
}
void StepMotor_RunCircles(uint32_t circles)
{
step_count = 0;
target_steps = circles*1600;
TIM_Cmd(TIM2, ENABLE); // 启动PWM
}
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
step_count++;
if (step_count >= target_steps)
{
TIM_Cmd(TIM2, DISABLE); // 关闭PWM
step_count = 0;
}
}
}
main.c
cpp
#include "stm32f10x.h"
#include "usart1.h"
#include "delay.h"
#include "string.h"
#include "systick.h"
#include "led.h"
#include "motor.h"
#include "key.h"
#include "tim2_pwm.h"
int main(void)
{
SystemInit();
delay_init();
StepMotor_GPIO_Init();
LED_Init();
Key_Init();
StepMotor_PWM_Init(1000); // 设置1kHz频率
StepMotor_Enable();
while (1) {
uint8_t key = Key_Scan();
if (key == KEY1_PRESSED) {
StepMotor_SetDirection(1);
StepMotor_RunCircles(2);
}
else if (key == KEY2_PRESSED) {
StepMotor_SetDirection(0);
StepMotor_RunCircles(1);
}
}
}
注意:pwm输出这里有些函数是和GPIO公用的,比如引脚的初始化,这里我没有再去划分。