使用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); } }
stm32利用GPIO中断模拟脉冲控制步进电机
最新推荐文章于 2024-05-30 11:25:02 发布