6.6.2.1. 编程要点¶
定时器 IO 配置
定时器时基结构体TIM_TimeBaseInitTypeDef配置
定时器输出比较结构体TIM_OCInitTypeDef配置
封装一个舵机角度控制函数
在main函数中编写按键舵机控制代码
定时器复用功能引脚初始化¶
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24/*宏定义*/
#define GENERAL_TIM TIM4
#define GENERAL_TIM_GPIO_AF GPIO_AF2_TIM4
#define GENERAL_TIM_CLK_ENABLE() __TIM4_CLK_ENABLE()
#define PWM_CHANNEL_1 TIM_CHANNEL_1
//#define PWM_CHANNEL_2 TIM_CHANNEL_2
//#define PWM_CHANNEL_3 TIM_CHANNEL_3
//#define PWM_CHANNEL_4 TIM_CHANNEL_4
/* 累计 TIM_Period个后产生一个更新或者中断*/
/* 当定时器从0计数到PWM_PERIOD_COUNT,即为PWM_PERIOD_COUNT+1次,为一个定时周期 */
#define PWM_PERIOD_COUNT 999
/* 通用控制定时器时钟源TIMxCLK = HCLK/2=84MHz */
/* 设定定时器频率为=TIMxCLK/(PWM_PRESCALER_COUNT+1) */
#define PWM_PRESCALER_COUNT 1679
/*PWM引脚*/
#define GENERAL_TIM_CH1_GPIO_PORT GPIOD
#define GENERAL_TIM_CH1_PIN GPIO_PIN_12
//#define GENERAL_TIM_CH2_GPIO_PORT GPIOD
//#define GENERAL_TIM_CH2_PIN GPIO_PIN_13
使用宏定义非常方便程序升级、移植。如果使用不同的定时器IO,修改这些宏即可。
定时器复用功能引脚初始化
定时器复用功能引脚初始化¶
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21static void TIMx_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
/* 定时器通道功能引脚端口时钟使能 */
__HAL_RCC_GPIOA_CLK_ENABLE();
/* 定时器通道1功能引脚IO初始化 */
/*设置输出类型*/
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
/*设置引脚速率 */
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
/*设置复用*/
GPIO_InitStruct.Alternate = GENERAL_TIM_GPIO_AF;
/*选择要控制的GPIO引脚*/
GPIO_InitStruct.Pin = GENERAL_TIM_CH1_PIN;
/*调用库函数,使用上面配置的GPIO_InitStructure初始化GPIO*/
HAL_GPIO_Init(GENERAL_TIM_CH1_GPIO_PORT, &GPIO_InitStruct);
}
定时器通道引脚使用之前必须设定相关参数,这选择复用功能,并指定到对应的定时器。
使用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
35TIM_HandleTypeDef TIM_TimeBaseStructure;
static void TIM_PWMOUTPUT_Config(void)
{
TIM_OC_InitTypeDef TIM_OCInitStructure;
/*使能定时器*/
GENERAL_TIM_CLK_ENABLE();
TIM_TimeBaseStructure.Instance = GENERAL_TIM;
/* 累计 TIM_Period个后产生一个更新或者中断*/
//当定时器从0计数到PWM_PERIOD_COUNT,即为PWM_PERIOD_COUNT+1次,为一个定时周期
TIM_TimeBaseStructure.Init.Period = PWM_PERIOD_COUNT;
// 通用控制定时器时钟源TIMxCLK = HCLK/2=84MHz
// 设定定时器频率为=TIMxCLK/(PWM_PRESCALER_COUNT+1)
TIM_TimeBaseStructure.Init.Prescaler = PWM_PRESCALER_COUNT;
/*计数方式*/
TIM_TimeBaseStructure.Init.CounterMode = TIM_COUNTERMODE_UP;
/*采样时钟分频*/
TIM_TimeBaseStructure.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;
/*初始化定时器*/
HAL_TIM_Base_Init(&TIM_TimeBaseStructure);
/*PWM模式配置*/
TIM_OCInitStructure.OCMode = TIM_OCMODE_PWM1; // 配置为PWM模式1
TIM_OCInitStructure.Pulse = 0.5/20.0*PWM_PERIOD_COUNT; // 默认占空比
TIM_OCInitStructure.OCFastMode = TIM_OCFAST_DISABLE;
/*当定时器计数值小于CCR1_Val时为高电平*/
TIM_OCInitStructure.OCPolarity = TIM_OCPOLARITY_HIGH;
/*配置PWM通道*/
HAL_TIM_PWM_ConfigChannel(&TIM_TimeBaseStructure, &TIM_OCInitStructure, PWM_CHANNEL_1);
/*开始输出PWM*/
HAL_TIM_PWM_Start(&TIM_TimeBaseStructure,PWM_CHANNEL_1);
}
首先定义两个定时器初始化结构体,定时器模式配置函数主要就是对这两个结构体的成员进行初始化,然后通过相
应的初始化函数把这些参数写入定时器的寄存器中。有关结构体的成员介绍请参考定时器详解章节。
不同的定时器可能对应不同的APB总线,在使能定时器时钟是必须特别注意。通用控制定时器属于APB1,
定时器内部时钟是84MHz。
在时基结构体中我们设置定时器周期参数为PWM_PERIOD_COUNT(999),频率为50Hz,使用向上计数方式。
因为我们使用的是内部时钟,所以外部时钟采样分频成员不需要设置,重复计数器我们没用到,也不需要设置,
然后调用HAL_TIM_Base_Init初始化定时器。
在输出比较结构体中,设置输出模式为PWM1模式,通道输出高电平有效,设置默认脉宽为PWM_PERIOD_COUNT,
PWM_PERIOD_COUNT是我们定义的一个宏,用来指定占空比大小,实际上脉宽就是设定比较寄存器CCR的值,
用于跟计数器CNT的值比较。然后调用HAL_TIM_PWM_ConfigChannel初始化PWM输出。
最后使用HAL_TIM_PWM_Start函数让计数器开始计数和通道输出。
设置定时器占空比¶
1
2
3
4
5
6
7
8
9
10
11
12void set_steering_gear_dutyfactor(uint16_t dutyfactor)
{
#if 1
{
/* 对超过范围的占空比进行边界处理 */
dutyfactor = 0.5/20.0*PWM_PERIOD_COUNT > dutyfactor ? 0.5/20.0*PWM_PERIOD_COUNT : dutyfactor;
dutyfactor = 2.5/20.0*PWM_PERIOD_COUNT < dutyfactor ? 2.5/20.0*PWM_PERIOD_COUNT : dutyfactor;
}
#endif
TIM2_SetPWM_pulse(PWM_CHANNEL_1, dutyfactor);
}
封装一个舵机占空比设置函数,接收一个参数用于设置PWM的占空比,并对输入的参数进行合法性检查,将脉冲宽度限制
在0.5~2.5ms之间。
设置舵机角度¶
1
2
3
4
5
6void set_steering_gear_angle(uint16_t angle_temp)
{
angle_temp = (0.5 + angle_temp / 180.0 * (2.5 - 0.5)) / 20.0 * PWM_PERIOD_COUNT; // 计算角度对应的占空比
set_steering_gear_dutyfactor(angle_temp); // 设置占空比
}
该函数用于设置舵机角度,传入角度值然后计算占空比,最后条用set_steering_gear_dutyfactor()来设置占空比。
串口控制¶
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
46void deal_serial_data(void)
{
int angle_temp=0;
//接收到正确的指令才为1
char okCmd = 0;
//检查是否接收到指令
if(receive_cmd == 1)
{
if(UART_RxBuffer[0] == 'a' || UART_RxBuffer[0] == 'A')
{
//设置速度
if(UART_RxBuffer[1] == ' ')
{
angle_temp = atoi((char const *)UART_RxBuffer+2);
if(angle_temp>=0 && angle_temp <= 180)
{
printf("\n\r角度: %d\n\r", angle_temp);
angle_temp = (0.5 + angle_temp / 180.0 * (2.5 - 0.5)) / 20.0 * PWM_PERIOD_COUNT;
ChannelPulse = angle_temp; // 同步按键控制的比较值
set_steering_gear_angle(angle_temp);
okCmd = 1;
}
}
}
else if(UART_RxBuffer[0] == '?')
{
//打印帮助命令
show_help();
okCmd = 1;
}
//如果指令有无则打印帮助命令
if(okCmd != 1)
{
printf("\n\r 输入有误,请重新输入...\n\r");
show_help();
}
//清空串口接收缓冲数组
receive_cmd = 0;
uart_FlushRxBuffer();
}
}
以上为串口接收处理函数,接收正确的指令后将字符串计算出正确的角度值,判断角度值是否是在有效范围内,
同步按键调节的占空比防止按钮调节时转动范围过大。
main函数¶
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
47int main(void)
{
/* HAL 库初始化 */
HAL_Init();
/* 初始化系统时钟为168MHz */
SystemClock_Config();
/* 初始化按键GPIO */
Key_GPIO_Config();
/* 初始化串口 */
DEBUG_USART_Config();
/* 通用定时器初始化并配置PWM输出功能 */
TIMx_Configuration();
printf("野火舵机控制实验\r\n");
show_help();
while(1)
{
/* 处理数据 */
if (Key_Scan(KEY1_GPIO_PORT, KEY1_PIN) == KEY_ON)
{
ChannelPulse -= 10; // 减少占空比
ChannelPulse = 0.5/20.0*PWM_PERIOD_COUNT > ChannelPulse ? 0.5/20.0*PWM_PERIOD_COUNT : ChannelPulse; // 检查占空比的合法性
set_steering_gear_dutyfactor(ChannelPulse); // 设置占空比
}
/* 处理数据 */
if (Key_Scan(KEY2_GPIO_PORT, KEY2_PIN) == KEY_ON)
{
ChannelPulse += 10; // 增加占空比
ChannelPulse = (2.5/20.0*PWM_PERIOD_COUNT) < ChannelPulse ? (2.5/20.0*PWM_PERIOD_COUNT) : ChannelPulse; // 检查占空比的合法性
set_steering_gear_dutyfactor(ChannelPulse); // 设置占空比
}
/* 串口处理 */
deal_serial_data();
}
}
初始化串口、定时器输出PWM和按键等外设,最后在循环里面处理按键和串口接收的数据。当KEY1按下后,
减少占空比,并检查占空比是否在有效范围内,然后设置占空比,当KEY2按下后,增加占空比,并检查占空比
是否在有效范围内,然后设置占空比。最后调用deal_serial_data()来处理串口接收的函数。