7.6.2.2. 第二种方式:使用GPIO中断模拟脉冲控制¶
编程要点
通用GPIO配置
步进电机、定时器中断初始化
在定时器中断翻转IO引脚
在main函数中编写轮询按键控制步进电机旋转的代码
宏定义
功能引脚相关宏定义¶
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22#define MOTOR_PUL_TIM TIM2
#define MOTOR_PUL_CLK_ENABLE() __TIM2_CLK_ENABLE()
#define MOTOR_PUL_IRQn TIM2_IRQn
#define MOTOR_PUL_IRQHandler TIM2_IRQHandler
//引脚定义
/*******************************************************/
//Motor 方向
#define MOTOR_DIR_PIN GPIO_PIN_1
#define MOTOR_DIR_GPIO_PORT GPIOE
#define MOTOR_DIR_GPIO_CLK_ENABLE() __HAL_RCC_GPIOE_CLK_ENABLE()
//Motor 使能
#define MOTOR_EN_PIN GPIO_PIN_0
#define MOTOR_EN_GPIO_PORT GPIOE
#define MOTOR_EN_GPIO_CLK_ENABLE() __HAL_RCC_GPIOE_CLK_ENABLE()
//Motor 脉冲
#define MOTOR_PUL_PIN GPIO_PIN_5
#define MOTOR_PUL_GPIO_PORT GPIOI
#define MOTOR_PUL_GPIO_CLK_ENABLE() __HAL_RCC_GPIOI_CLK_ENABLE()
使用宏定义非常方便程序升级、移植。如果使用不同的GPIO,定时器更换对应修改这些宏即可。
按键初始化配置
按键初始化¶
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45/**
* @brief 配置按键用到的I/O口
* @param 无
* @retval 无
*/
void Key_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/*开启按键GPIO口的时钟*/
KEY1_GPIO_CLK_ENABLE();
KEY2_GPIO_CLK_ENABLE();
KEY3_GPIO_CLK_ENABLE();
KEY4_GPIO_CLK_ENABLE();
KEY5_GPIO_CLK_ENABLE();
/*选择按键的引脚*/
GPIO_InitStructure.Pin = KEY1_PIN;
/*设置引脚为输入模式*/
GPIO_InitStructure.Mode = GPIO_MODE_INPUT;
/*设置引脚不上拉也不下拉*/
GPIO_InitStructure.Pull = GPIO_PULLDOWN;
/*使用上面的结构体初始化按键*/
HAL_GPIO_Init(KEY1_GPIO_PORT, &GPIO_InitStructure);
/*选择按键的引脚*/
GPIO_InitStructure.Pin = KEY2_PIN;
/*使用上面的结构体初始化按键*/
HAL_GPIO_Init(KEY2_GPIO_PORT, &GPIO_InitStructure);
/*选择按键的引脚*/
GPIO_InitStructure.Pin = KEY3_PIN;
/*使用上面的结构体初始化按键*/
HAL_GPIO_Init(KEY3_GPIO_PORT, &GPIO_InitStructure);
/*选择按键的引脚*/
GPIO_InitStructure.Pin = KEY4_PIN;
/*使用上面的结构体初始化按键*/
HAL_GPIO_Init(KEY4_GPIO_PORT, &GPIO_InitStructure);
/*选择按键的引脚*/
GPIO_InitStructure.Pin = KEY5_PIN;
/*使用上面的结构体初始化按键*/
HAL_GPIO_Init(KEY5_GPIO_PORT, &GPIO_InitStructure);
}
开启按键IO对应的时钟,并在主函数中设置按键轮询。当按键按下时,会进入并且执行相应代码。
定时器初始化配置
定时器初始化配置¶
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34/*
* 注意: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);
}
首先对定时器进行初始化,定时器模式配置函数主要就是对这结构体的成员进行初始化,
然后通过相应的初始化函数把这些参数写入定时器的寄存器中。
有关结构体的成员介绍请参考定时器详解章节。
由于定时器坐在的APB总线不完全一致,所以说,定时器的时钟是不同的,在使能定时器时钟时必须特别注意,
在这里使用的是定时器2,通用定时器的总线频率为84MHZ,分频参数选择为(84-1),也就是当计数器计数到1M时为一个周期,
计数累计到(300-1)时产生一个中断,使用向上计数方式。产生中断后翻转IO口电平即可。
因为我们使用的是内部时钟,所以外部时钟采样分频成员不需要设置,重复计数器我们没用到,也不需要设置,
然后调用HAL_TIM_Base_Init初始化定时器并开启定时器更新中断。
步进电机初始化
步进电机初始化¶
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42/**
* @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(OFF);
/*初始化定时器*/
TIMx_Configuration();
}
步进电机引脚使用必须选择相应的模式和设置对应的参数,使用GPIO之前都必须开启相应端口时钟。
初始化结束后可以先将步进电机驱动器的使能先关掉MOTOR_EN(OFF),需要旋转的时候,再将其打开即可。
最后需要初始化定时器,来反转引脚电平以达到模拟脉冲的目的。
主函数
主函数¶
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
int i=0,j=0;
int dir_val=0;
int en_val=0;
/* 初始化系统时钟为168MHz */
SystemClock_Config();
/*初始化USART 配置模式为 115200 8-N-1,中断接收*/
DEBUG_USART_Config();
printf("欢迎使用野火 电机开发板 步进电机 IO口模拟控制 例程\r\n");
printf("按下按键2可修改旋转方向,按下按键3可修改使能\r\n");
/*按键中断初始化*/
Key_GPIO_Config();
/*步进电机初始化*/
stepper_Init();
MOTOR_EN(0);
while(1)
{
if( Key_Scan(KEY2_GPIO_PORT,KEY2_PIN) == KEY_ON )
{
// LED2 取反
LED2_TOGGLE;
/*改变方向*/
dir_val=(++i % 2) ? CW : CCW;
MOTOR_DIR(dir_val);
}
if( Key_Scan(KEY3_GPIO_PORT,KEY3_PIN) == KEY_ON )
{
// LED1 取反
LED1_TOGGLE;
/*改变使能*/
en_val=(++j % 2) ? CW : CCW;
MOTOR_EN(en_val);
}
}
}
主函数中首先对系统和外设初始化,在while(1)里面是两个判断语句,主要作用是使能开关和方向的改变,在if语句中可以改变步进电机的状态。
当KEY2按下后,改变旋转方向, 转换方向 即CW,正转运行;相对地,CCW,反转运行 采用三元运算符 i是奇数是反转运行,i是偶数是正转运行。
与方式一不同的是,从延时模拟脉冲变成了中断翻转电平增加了脉冲的准确性。