stm32利用GPIO中断模拟脉冲控制步进电机

使用STM32的通用定时器定时,控制步进电机正反转和启停。
学习目的:学习步进电机和步进电机驱动器的基本使用方法。

步进电机驱动器(共阳极接法):
    ENA+ <---> 3V3
    ENA- <---> PB12
    DIR+ <---> 3V3
    DIR- <---> PB14
    PUL+ <---> 3V3
    PUL- <---> PC6

编程要点
(1) 通用 GPIO 配置
(2) 步进电机、定时器中断初始化
(3) 在定时器中断翻转 IO 引脚
(4) 在 main 函数中编写轮询按键控制步进电机旋转的代码

头文件

#ifndef __BSP_STEP_MOTOR_INIT_H
#define __BSP_STEP_MOTOR_INIT_H

#include "stm32f1xx.h"
#include "stm32f1xx_hal.h"

#define MOTOR_PUL_TIM                   TIM2
#define MOTOR_PUL_CLK_ENABLE()          __HAL_RCC_TIM2_CLK_ENABLE()

#define MOTOR_PUL_IRQn                  TIM2_IRQn
#define MOTOR_PUL_IRQHandler            TIM2_IRQHandler

//引脚定义
//Motor 使能 
#define MOTOR_EN_PIN                    GPIO_PIN_12
#define MOTOR_EN_GPIO_PORT              GPIOB
#define MOTOR_EN_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOB_CLK_ENABLE()

//Motor 方向 
#define MOTOR_DIR_PIN                   GPIO_PIN_14
#define MOTOR_DIR_GPIO_PORT             GPIOB
#define MOTOR_DIR_GPIO_CLK_ENABLE()     __HAL_RCC_GPIOB_CLK_ENABLE()

//Motor 脉冲
#define MOTOR_PUL_PIN                   GPIO_PIN_6
#define MOTOR_PUL_GPIO_PORT             GPIOC
#define MOTOR_PUL_GPIO_CLK_ENABLE()     __HAL_RCC_GPIOC_CLK_ENABLE()


/************************************************************/
#define HIGH GPIO_PIN_SET       //高电平
#define LOW  GPIO_PIN_RESET     //低电平

#define ON  LOW                 //开
#define OFF HIGH                //关

#define CW  HIGH                //顺时针
#define CCW LOW                 //逆时针

//控制使能引脚
/* 带参宏,可以像内联函数一样使用 */
#define MOTOR_EN(x)         HAL_GPIO_WritePin(MOTOR_EN_GPIO_PORT,MOTOR_EN_PIN,x)
#define MOTOR_PUL(x)        HAL_GPIO_WritePin(MOTOR_PUL_GPIO_PORT,MOTOR_PUL_PIN,x)
#define MOTOR_DIR(x)        HAL_GPIO_WritePin(MOTOR_DIR_GPIO_PORT,MOTOR_DIR_PIN,x)
                          
#define MOTOR_PUL_T()       HAL_GPIO_TogglePin(MOTOR_PUL_GPIO_PORT,MOTOR_PUL_PIN);
  

extern TIM_HandleTypeDef TIM_TimeBaseStructure;
void TIMx_Configuration(void);
extern void stepper_Init(void);
extern void stepper_turn(int tim,float angle,float subdivide,uint8_t dir);
#endif /* __STEP_MOTOR_INIT_H */

定时器初始化配置

首先对定时器进行初始化,定时器模式配置函数主要就是对这结构体的成员进行初始化,然后通过相应的初始化函数把这些参数写入定时器的寄存器中。由于定时器坐在的 APB 总线不完全一致,所以说,定时器的时钟是不同的,在使能定时器时钟时必须特别注意,在这里使用的是定时器 2,通用定时器的总线频率为 84MHZ, 分频参数选择为(84-1),也就是当计数器计数到 1M 时为一个周期,计数累计到(300-1)时产生一个中断,使用向上计数方式。产生中断后翻转 IO 口电平即可。因为我们使用的是内部时钟,所以外部时钟采样分频成员不需要设置,重复计数器我们没用到,也不需要设置,然后调用 HAL_TIM_Base_Init初始化定时器并开启定时器更新中断。

步进电机初始化

步进电机引脚使用必须选择相应的模式和设置对应的参数,使用 GPIO 之前都必须开启相应端口时钟。初始化结束后可以先将步进电机驱动器的使能先关掉,需要旋转的时候,再将其打开即可。最后需要初始化定时器,来反转引脚电平以达到模拟脉冲的目的。

#include "./stepper/bsp_stepper_init.h"
#include "./led/bsp_led.h"   
#include "stm32f1xx.h"


TIM_HandleTypeDef TIM_TimeBaseStructure;
/**
  * @brief  通用定时器 TIMx,x[6,7]中断优先级配置
  * @param  无
  * @retval 无
  */
static void TIMx_NVIC_Configuration(void)
{
  /* 外设中断配置 */
  HAL_NVIC_SetPriority(MOTOR_PUL_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(MOTOR_PUL_IRQn);
}

/*
 * 注意:TIM_TimeBaseInitTypeDef结构体里面有5个成员,TIM6和TIM7的寄存器里面只有
 * TIM_Prescaler和TIM_Period,所以使用TIM6和TIM7的时候只需初始化这两个成员即可,
 * 另外三个成员是通用定时器和高级定时器才有.
 *-----------------------------------------------------------------------------
 * TIM_Prescaler         都有
 * TIM_CounterMode       TIMx,x[6,7]没有,其他都有(通用定时器)
 * TIM_Period            都有
 * TIM_ClockDivision     TIMx,x[6,7]没有,其他都有(通用定时器)
 * TIM_RepetitionCounter TIMx,x[1,8]才有(高级定时器)
 *-----------------------------------------------------------------------------
 */
static void TIM_Mode_Config(void)
{

  MOTOR_PUL_CLK_ENABLE();

  TIM_TimeBaseStructure.Instance = MOTOR_PUL_TIM;
  /* 累计 TIM_Period个后产生一个更新或者中断*/    
  //当定时器从0计数到4999,即为5000次,为一个定时周期
  TIM_TimeBaseStructure.Init.Period = 300-1;  
  // 通用控制定时器时钟源TIMxCLK = HCLK/2=84MHz 
  // 设定定时器频率为=TIMxCLK/(TIM_Prescaler+1)=1MHz
  TIM_TimeBaseStructure.Init.Prescaler = 84-1;
  // 计数方式
  TIM_TimeBaseStructure.Init.CounterMode=TIM_COUNTERMODE_UP;
  // 采样时钟分频
  TIM_TimeBaseStructure.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;
  // 初始化定时器TIMx, x[2,5] [9,14]
  HAL_TIM_Base_Init(&TIM_TimeBaseStructure);

  // 开启定时器更新中断
  HAL_TIM_Base_Start_IT(&TIM_TimeBaseStructure);  
}

/**
  * @brief  初始化通用定时器定时
  * @param  无
  * @retval 无
  */
void TIMx_Configuration(void)
{
  TIMx_NVIC_Configuration();  
  
  TIM_Mode_Config();
}

/**
  * @brief  引脚初始化
  * @retval 无
  */
void stepper_Init()
{
  /*定义一个GPIO_InitTypeDef类型的结构体*/
  GPIO_InitTypeDef  GPIO_InitStruct;

  /*开启Motor相关的GPIO外设时钟*/
  MOTOR_DIR_GPIO_CLK_ENABLE();
  MOTOR_PUL_GPIO_CLK_ENABLE();
  MOTOR_EN_GPIO_CLK_ENABLE();

  /*选择要控制的GPIO引脚*/                                 
  GPIO_InitStruct.Pin = MOTOR_DIR_PIN;  

  /*设置引脚的输出类型为推挽输出*/
  GPIO_InitStruct.Mode  = GPIO_MODE_OUTPUT_PP;  

  GPIO_InitStruct.Pull =GPIO_PULLUP;

  /*设置引脚速率为高速 */   
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;

  /*Motor 方向引脚 初始化*/
  HAL_GPIO_Init(MOTOR_DIR_GPIO_PORT, &GPIO_InitStruct); 

  /*Motor 脉冲引脚 初始化*/
  GPIO_InitStruct.Pin = MOTOR_PUL_PIN;  
  HAL_GPIO_Init(MOTOR_PUL_GPIO_PORT, &GPIO_InitStruct); 

  /*Motor 使能引脚 初始化*/
  GPIO_InitStruct.Pin = MOTOR_EN_PIN; 
  HAL_GPIO_Init(MOTOR_EN_GPIO_PORT, &GPIO_InitStruct);  

  /*关掉使能*/
  MOTOR_EN(LOW);
  /*初始化定时器*/
  TIMx_Configuration();
        
}

/**
  * @brief  定时器中断函数
  * @note   无
  * @retval 无
  */
void MOTOR_PUL_IRQHandler (void)
{
  HAL_TIM_IRQHandler(&TIM_TimeBaseStructure);   
}
/**
  * @brief  回调函数
  * @note   无
  * @retval 无
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if(htim==(&TIM_TimeBaseStructure))
  {
    MOTOR_PUL_T();//翻转IO口达到脉冲的效果
  }
}

按键头文件

#ifndef __KEY_H
#define	__KEY_H

#include "stm32f1xx.h"
#include "main.h"
//引脚定义
/*******************************************************/
#define KEY1_PIN                  GPIO_PIN_0                 
#define KEY1_GPIO_PORT            GPIOA                      
#define KEY1_GPIO_CLK_ENABLE()    __HAL_RCC_GPIOA_CLK_ENABLE()

#define KEY2_PIN                  GPIO_PIN_13                 
#define KEY2_GPIO_PORT            GPIOC                      
#define KEY2_GPIO_CLK_ENABLE()     __HAL_RCC_GPIOC_CLK_ENABLE()
/*******************************************************/

 /** 按键按下标置宏
	* 按键按下为高电平,设置 KEY_ON=1, KEY_OFF=0
	* 若按键按下为低电平,把宏设置成KEY_ON=0 ,KEY_OFF=1 即可
	*/
#define KEY_ON	1
#define KEY_OFF	0

void Key_GPIO_Config(void);
uint8_t Key_Scan(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin);

#endif /* __LED_H */

按键初始化

#include "./key/bsp_key.h" 
/**
  * @brief  配置按键用到的I/O口
  * @param  无
  * @retval 无
  */
void Key_GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    /*开启按键GPIO口的时钟*/
    KEY1_GPIO_CLK_ENABLE();
    KEY2_GPIO_CLK_ENABLE();
    /*选择按键的引脚*/	
    GPIO_InitStructure.Pin = KEY1_PIN; 

    /*设置引脚为输入模式*/
    GPIO_InitStructure.Mode = GPIO_MODE_INPUT; 

   /*设置引脚不上拉也不下拉*/
    GPIO_InitStructure.Pull = GPIO_NOPULL;
    /*使用上面的结构体初始化按键*/
    HAL_GPIO_Init(KEY1_GPIO_PORT, &GPIO_InitStructure);
    /*选择按键的引脚*/
    GPIO_InitStructure.Pin = KEY2_PIN; 

    /*使用上面的结构体初始化按键*/
    HAL_GPIO_Init(KEY2_GPIO_PORT, &GPIO_InitStructure);

}

/**
  * @brief   检测是否有按键按下
  * @param   具体的端口和端口位
  *		@arg GPIOx: x可以是(A...G) 
  *		@arg GPIO_PIN 可以是GPIO_PIN_x(x可以是1...16)
  * @retval  按键的状态
  *		@arg KEY_ON:按键按下
  *		@arg KEY_OFF:按键没按下
  */
uint8_t Key_Scan(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin)
{			
	/*检测是否有按键按下 */
	if(HAL_GPIO_ReadPin(GPIOx,GPIO_Pin) == KEY_ON )  
	{	 
		/*等待按键释放 */
		while(HAL_GPIO_ReadPin(GPIOx,GPIO_Pin) == KEY_ON);   
		return 	KEY_ON;	 
	}
	else
		return KEY_OFF;
}

开启按键 IO 对应的时钟,并在主函数中设置按键轮询。当按键按下时,会进入并且执行相应代码。

主函数 

主函数中首先对系统和外设初始化,在 while(1) 里面是两个判断语句,主要作用是使能开关和方向的改变,在 if 语句中可以改变步进电机的状态。与方式一不同的是,从延时模拟脉冲变成了中断翻转电平增加了脉冲的准确性。

#include "main.h"
#include <stdlib.h>
#include "./stepper/bsp_stepper_init.h"
#include "./usart/bsp_debug_usart.h"
#include "./led/bsp_led.h"
#include "./key/bsp_key.h" 


/**
  * @brief  主函数
  * @param  无
  * @retval 无
  */
int main(void) 
{
  int i=0,j=0;
  int dir_val=0;
  int en_val=0;

  /* 初始化系统时钟为72MHz */
  SystemClock_Config();
  /*初始化USART 配置模式为 115200 8-N-1,中断接收*/
  DEBUG_USART_Config();
  printf("按下按键1可修改旋转方向,按下按键2可修改使能\r\n");
  /*LED初始化*/	
	LED_GPIO_Config();
  /*按键初始化*/	
  Key_GPIO_Config();  
  /*步进电机初始化*/
  stepper_Init(); 

  MOTOR_EN(HIGH);

  while(1)
  {     
    if( Key_Scan(KEY1_GPIO_PORT,KEY1_PIN) == KEY_ON  )
    {
      // LED1 取反    
      LED1_TOGGLE;
      
      /*改变方向*/
      dir_val=(++i % 2) ? CW : CCW;
      MOTOR_DIR(dir_val);
    }
    if( Key_Scan(KEY2_GPIO_PORT,KEY2_PIN) == KEY_ON  )
    {
      // LED2 取反    
      LED2_TOGGLE;

      /*改变使能*/
      en_val=(++j % 2) ? CW : CCW;
      MOTOR_EN(en_val);
    }
  }
}   


/**
  * @brief  System Clock Configuration
  *         The system Clock is configured as follow : 
  *            System Clock source            = PLL (HSE)
  *            SYSCLK(Hz)                     = 72000000
  *            HCLK(Hz)                       = 72000000
  *            AHB Prescaler                  = 1
  *            APB1 Prescaler                 = 2
  *            APB2 Prescaler                 = 1
  *            HSE Frequency(Hz)              = 8000000
  *            HSE PREDIV1                    = 2
  *            PLLMUL                         = 9
  *            Flash Latency(WS)              = 0
  * @param  None
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_ClkInitTypeDef clkinitstruct = {0};
  RCC_OscInitTypeDef oscinitstruct = {0};
  
  /* Enable HSE Oscillator and activate PLL with HSE as source */
  oscinitstruct.OscillatorType  = RCC_OSCILLATORTYPE_HSE;
  oscinitstruct.HSEState        = RCC_HSE_ON;
  oscinitstruct.HSEPredivValue  = RCC_HSE_PREDIV_DIV1;
  oscinitstruct.PLL.PLLState    = RCC_PLL_ON;
  oscinitstruct.PLL.PLLSource   = RCC_PLLSOURCE_HSE;
  oscinitstruct.PLL.PLLMUL      = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&oscinitstruct)!= HAL_OK)
  {
    /* Initialization Error */
    while(1); 
  }

  /* Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2 
     clocks dividers */
  clkinitstruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2);
  clkinitstruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  clkinitstruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  clkinitstruct.APB2CLKDivider = RCC_HCLK_DIV1;
  clkinitstruct.APB1CLKDivider = RCC_HCLK_DIV2;  
  if (HAL_RCC_ClockConfig(&clkinitstruct, FLASH_LATENCY_2)!= HAL_OK)
  {
    /* Initialization Error */
    while(1); 
  }
}

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是基于STM32控制步进电机的代码示例: ```c #include "stm32f4xx.h" // 定义IO口 #define STEP_GPIO_PORT GPIOA #define DIR_GPIO_PORT GPIOB #define STEP_GPIO_PIN GPIO_Pin_8 #define DIR_GPIO_PIN GPIO_Pin_10 // 定义步距角 #define STEP_ANGLE 1.8f // 定义步进电机控制参数 #define STEPS_PER_REV 200 // 每转步数 #define SPEED 5000 // 转速,单位为step/s #define ACCELERATION 5000 // 加速度,单位为step/s^2 // 定义计数器 volatile uint32_t step_count = 0; volatile uint32_t step_goal = 0; volatile uint32_t accel_count = 0; // 定时器中断处理函数 void TIM2_IRQHandler(void) { if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { // 清除中断标志 TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 判断是否加速 if(step_count < accel_count) { // 计算加速度 uint32_t speed_new = (uint32_t)(sqrtf(2.0f * ACCELERATION * step_count * STEP_ANGLE / 360.0f) * STEPS_PER_REV); // 更新定时器计数值 TIM2->ARR = (uint16_t)(SystemCoreClock / speed_new / 2); } else if(step_count >= step_goal - accel_count) { // 计算减速度 uint32_t speed_new = (uint32_t)(sqrtf(2.0f * ACCELERATION * (step_goal - step_count) * STEP_ANGLE / 360.0f) * STEPS_PER_REV); // 更新定时器计数值 TIM2->ARR = (uint16_t)(SystemCoreClock / speed_new / 2); } // 更新步进电机控制口状态 if(step_count < step_goal) { // 输出脉冲信号 GPIO_SetBits(STEP_GPIO_PORT, STEP_GPIO_PIN); GPIO_ResetBits(STEP_GPIO_PORT, STEP_GPIO_PIN); // 更新计数器 step_count++; } } } // 初始化GPIO口和定时器 void Init_StepMotor(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; NVIC_InitTypeDef NVIC_InitStructure; // 使能GPIO时钟和定时器时钟 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 初始化GPIOGPIO_InitStructure.GPIO_Pin = STEP_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(STEP_GPIO_PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = DIR_GPIO_PIN; GPIO_Init(DIR_GPIO_PORT, &GPIO_InitStructure); // 初始化定时器 TIM_TimeBaseStructure.TIM_Period = SystemCoreClock / SPEED / 2 - 1; // 定时器周期,单位为us TIM_TimeBaseStructure.TIM_Prescaler = 0; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); // 配置定时器中断 NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); // 启动定时器 TIM_Cmd(TIM2, ENABLE); } // 控制步进电机转动 void StepMotor_Run(int32_t steps) { if(steps > 0) { // 向正方向旋转 GPIO_SetBits(DIR_GPIO_PORT, DIR_GPIO_PIN); } else { // 向负方向旋转 GPIO_ResetBits(DIR_GPIO_PORT, DIR_GPIO_PIN); steps = -steps; } // 计算目标步数和加速时间 step_count = 0; step_goal = (uint32_t)(steps * STEP_PER_REV / 360.0f / STEP_ANGLE); accel_count = (uint32_t)(sqrtf(2.0f * step_goal * ACCELERATION * STEP_ANGLE / 360.0f) * STEPS_PER_REV); // 等待步进电机停止 while(step_count < step_goal); } int main(void) { // 初始化步进电机控制口和定时器 Init_StepMotor(); // 控制步进电机旋转1000步 StepMotor_Run(1000); while(1); return 0; } ``` 这段代码使用TIM2定时器控制脉冲信号的输出和步进电机的旋转方向。在定时器中断处理函数中,根据加速度和减速度的公式计算定时器的计数周期,并且输出脉冲信号来驱动步进电机。在控制步进电机旋转时,先计算目标步数和加速时间,然后等待步进电机旋转到目标位置。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值