一、基础知识
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个整步)。
例如下面的驱动器,可以调节M1、M2、M3来控制细分档位。
细分数 每微步角度(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
二、实验
1. GPIO模拟脉冲方式
实验现象:正转2圈,延时,反转1圈。
main.c
#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
#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);elseGPIO_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
#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_3void 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
#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
#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
#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 = CH2TIM_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); // 关闭PWMstep_count = 0;}}
}
main.c
#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公用的,比如引脚的初始化,这里我没有再去划分。