STM32F1控制步进电机

一、基础知识

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公用的,比如引脚的初始化,这里我没有再去划分。