声明:本博客为各模块之间结合的自主研究学习。
目录
一、按键操控舵机旋转(单向)
1.1、实物图讲解
1.2、代码讲解
1.2.1、PWM.c
具体步骤:
完整代码:
1.2.2、PWM.h
1.2.3、Servo.c && Servo.h
1.2.4、main.c
二、按键操控舵机旋转(巡回)
三、旋转编码器的实现
3.1实物图讲解
3.2、代码讲解
3.2.1、Encoder.c
具体步骤:
完整代码:
3.2.2、Encoder.h
3.2.3、main.c
四、通过旋转编码器控制舵机角度
4.1、综述
4.2、功能描述
4.2.1、正常工作
4.2.2、异常停止
4.2.3、按键复位
4.3、实物图讲解
4.4、代码部分
4.4.1、Encoder.c && Encoder.h
4.4.2、PWM.c &&PWM.h
4.4.3、Servo.c && Servo.h
4.4.4、Encoder模块和PWM模块冲突判断与解决方案
4.4.5、main.c
具体步骤:
完整代码:
一、按键操控舵机旋转(单向)
1.1、实物图讲解
舵机型号:SG90。棕色线是舵机的GND,接在面包板的GND;红色线是舵机的5V正极,因为面包板的正极只能提供3.3V,所以红色线不能接在面包板上,而是接在STLINK的5V输出脚;橙色线是PWM引脚,接在了PA1引脚上。
在PB1接一个按键,用来控制舵机。
1.2、代码讲解
1.2.1、PWM.c
具体步骤:
第一步:打开RCC时钟。
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
第二步:配置GPIO端口。
其中,因为PWM引脚连在了PA1上,所以配置GPIO时引脚选择GPIO_Pin_1。
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
第三步: 配置时基单元。
PSC、ARR值的选取:SG90要求的频率是20ms,则72MHz / (PSC+1) / (ARR+1) = 1/0.02;这里PSC和ARR的参数是不固定的。经过多次尝试最终确定PSC=72-1,ARR=20k-1时,舵机旋转效果最好。
TIM_InternalClockConfig(TIM2);/*开启内部时钟*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseImitStructure;
TIM_TimeBaseImitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseImitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseImitStructure.TIM_Period = 20000 - 1;
TIM_TimeBaseImitStructure.TIM_Prescaler = 72 - 1;
TIM_TimeBaseImitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseImitStructure);
第四步:配置输出比较单元。
有关CRR:CCR设置成500就是0.5ms,设置成2500就是2.5ms。占空比:本舵机要求高电平时间是0.5ms~2.5ms。
初始化的通道:因为使用的是PA1,PA1对应通道2,所以应该是通道2的初始化。即:
TIM_OC2Init(TIM2,&TIM_OCInitStructure);
如果想要多个通道输出PWM,则:
TIM_OC1Init(TIM2,&TIM_OCInitStructure);
TIM_OC2Init(TIM2,&TIM_OCInitStructure);
TIM_OC3Init(TIM2,&TIM_OCInitStructure);
TIM_OC4Init(TIM2,&TIM_OCInitStructure);
这样就能同时使用多个通道来输出多个PWM了。
对于同一个定时器的不同通道输出的PWM,因为不同通道共用一个计数器,所以不同通道的频率一定相同,他们的占空比由各自的CCR决定,所以占空比可以各自设定。
对于相位,由于计数器更新,所有的PWM同时跳变,所以他们的相位是同步的。
这就是同一个定时器驱动不同通道输出PWM的特点。如果驱动多个舵机和直流电机,使用同一个定时器的不同通道的PWM即可。
故输出比较单元部分的代码:
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0; /*CCR*/
TIM_OC2Init(TIM2,&TIM_OCInitStructure);
第五步: 使能时钟。
TIM_Cmd(TIM2,ENABLE);
完整代码:
#include "stm32f10x.h" // Device header
void pwm_init(void)
{
/*第一步开启RCC时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
/*第二步:配置GPIO端口*/
GPIO_InitTypeDef GPIO_InitStructure_For_Servo;
GPIO_InitStructure_For_Servo.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure_For_Servo.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure_For_Servo.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure_For_Servo);
/*第三步:配置时基单元*/
TIM_InternalClockConfig(TIM2);/*开启内部时钟*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseImitStructure;
TIM_TimeBaseImitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseImitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseImitStructure.TIM_Period = 20000 - 1;
TIM_TimeBaseImitStructure.TIM_Prescaler = 72 - 1;
TIM_TimeBaseImitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseImitStructure);
/*第四步:配置输出比较单元*/
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0; /*CCR*/
TIM_OC2Init(TIM2,&TIM_OCInitStructure);
/*第五步:使能时钟*/
TIM_Cmd(TIM2,ENABLE);
}
void pwm_setcompare2(uint16_t compare)
{
TIM_SetCompare2(TIM2,compare);
}
1.2.2、PWM.h
#ifndef _PWM_H
#define _PWM_H
void pwm_init(void);
void pwm_setcompare2(uint16_t compare);
#endif
1.2.3、Servo.c && Servo.h
由于CCR从500到2500,对应SG90的0度到180度,人工换算不方便,所以添加一个舵机的c文件,用来更直观地面向需求。
/Servo.c*/
#include "stm32f10x.h" // Device header
#include "PWM.h"
void servo_init(void)
{
pwm_init();
}
void servo_set_angle(double angle)
{
pwm_setcompare2(angle / 180 * 2000 + 500);
}
对舵机的初始化就是对pwm的初始化;第二个函数是CCR和舵机转角的线性映射关系。
然后写头文件声明一下各个函数。
/*Servo.h*/
#ifndef _SERVO_H
#define _SERVO_H
void servo_init(void);
void servo_set_angle(double angle);
#endif
1.2.4、main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Servo.h"
#include "Key.h"
uint8_t key_num;
double angle;
int main(void)
{
OLED_Init();
servo_init();
Key_Init();
OLED_ShowString(1,1,"angle:");
while (1)
{
key_num = Key_GetNum();
if(key_num == 1)
{
angle += 30;
if(angle > 180)
angle = 0;
}
servo_set_angle(angle);
OLED_ShowNum(1,7,angle,3);
}
}
头文件应该包含Servo.h和Key.h。
定义两个变量:uint8_t key_num;按键键码;double angle;角度变量。
主函数中,首先对OLED显示屏初始化,之后对Servo和按键初始化。
主循环中,首先获取按键的键码。
如果按键了(key_num == 1),则angle += 30,舵机角度旋转30度。如果舵机旋转到了180度,则角度瞬间变为0度。
之后将角度变量传参到舵机角度设置的代码中servo_set_angle(angle);
最后加一下OLED显示。
二、按键操控舵机旋转(巡回)
因为上述代码中,只要按键6次,舵机旋转角度就会到180度,之后突然逆时针旋转180度回到0度的位置,不美观。
升级背景:按键操控舵机巡回。即,到达180度后,再按按键,舵机反向旋转30度;直到回到0度后,再按按键,舵机正向旋转30度……以此类推,实现巡回。
升级思路:
记从第1次到第6次按键为顺时针周期,第7次到第7到第12次为逆时针周期,以此类推。如果从“第0周期”开始,按时间先后顺序给每个周期编号,可以发现,所有顺时针周期都是偶数,即编号 % 2 == 0;所有逆时针周期都是奇数,即编号 % 2 == 1;
因此,只需定义周期计数变量,判断计数变量的奇偶,对偶数编号angle += 30,对奇数编号angle -= 30即可。
当angle == 0度或angle == 180度时,说明刚好过了一个周期,周期计数变量就自增一次。
#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"
#include "Servo.h"
#include "Key.h"
uint8_t key_num;
uint16_t counter;
double angle;
int main(void)
{
OLED_Init();
servo_init();
Key_Init();
OLED_ShowString(1,1,"angle:");
while (1)
{
key_num = Key_GetNum();
if(key_num == 1)
{
if(counter % 2 == 0)
angle += 30;
if(counter % 2 == 1)
angle -= 30;
if(angle == 0 || angle == 180)
counter += 1;
}
servo_set_angle(angle);
OLED_ShowNum(1,7,angle,3);
}
}
三、旋转编码器的实现
3.1实物图讲解
旋转编码器模块:VCC和GND接了面包板的正负极。
下面两个A、B向的输出引脚,分别接到STM32的PC13和PC14引脚。(上图是哔站课程的原图,接的是PB0和PB1引脚,本博客将其改为PC13和PC14引脚。)
3.2、代码讲解
3.2.1、Encoder.c
具体步骤:
第一步:打开RCC时钟。包括配置中断引脚选择的AFIO时钟和GPIO端口的GPIOC的时钟。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
第二步: 配置GPIO端口。引脚模式选择上拉输入(GPIO_Mode_IPU)。
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC,&GPIO_InitStructure);
第三步:配置AFIO终端引脚选择。我们选择的是PC13和PC14两个引脚。
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC,GPIO_PinSource13);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC,GPIO_PinSource14);
第四步:配置EXTI。
选择为“中断模式”,“下降沿触发”。
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line13 | EXTI_Line14;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructure);
第五步:配置NVIC(中断优先级)。这里要对两个通道分别设置优先级。
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
/*对两个通道分别设置优先级*/
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_Init(&NVIC_InitStructure);
第六步:定义计数变量,定义函数返回变量的值。
这里不直接返回encoder_count变量了,而是返回一个增减的变化值,用于主函数中外部加减一个变量。所以这里我们需要返回count,然后把count清零。
int16_t encoder_count;
int16_t encoder_get(void)
{
int16_t temp;
temp = encoder_count;
encoder_count = 0;
return temp;
}
第七步:编写两个中断的中断函数。
打开启动文件startup_stm32f10x_md.s。在第119行定义了EXTI15_10_IRQHandler函数。
DCD EXTI15_10_IRQHandler ; EXTI Line 15..10
A向和B向都触发中断。只有在B向下降沿和A向低电平是,才判断为正转。只有在A向下降沿和B向低电平时,才能判断为反转。每次encoder_count变化后,清楚标志位。
void EXTI15_10_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line13)==SET)
{
if( ! GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_14))
encoder_count++;
EXTI_ClearITPendingBit(EXTI_Line13);
}
if(EXTI_GetITStatus(EXTI_Line14)==SET)
{
if( ! GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_13))
encoder_count--;
EXTI_ClearITPendingBit(EXTI_Line14);
}
}
完整代码:
#include "stm32f10x.h" // Device header
int16_t encoder_count;
void encoder_init(void)
{
/*
A和B相都触发中断,
只有在B相下降沿和A相低电平时,才判断为正转;
只有在A相下降沿和B相低电平时,才判断为反转。
*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC,&GPIO_InitStructure);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC,GPIO_PinSource13);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC,GPIO_PinSource14);
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line13 | EXTI_Line14;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructure);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
/*对两个通道分别设置优先级*/
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_Init(&NVIC_InitStructure);
}
int16_t encoder_get(void)
{
int16_t temp;
temp = encoder_count;
encoder_count = 0;
return temp;
}
void EXTI15_10_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line13)==SET)
{
if( ! GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_14))
encoder_count++;
EXTI_ClearITPendingBit(EXTI_Line13);
}
if(EXTI_GetITStatus(EXTI_Line14)==SET)
{
if( ! GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_13))
encoder_count--;
EXTI_ClearITPendingBit(EXTI_Line14);
}
}
3.2.2、Encoder.h
#ifndef _ENCODER_H
#define _ENCODER_H
void encoder_init(void);
int16_t encoder_get(void);
#endif
3.2.3、main.c
#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"
#include "Encoder.h"
int16_t num;
int main(void)
{
OLED_Init();
encoder_init();
OLED_ShowString(1, 1, "count:");
while (1)
{
num+=encoder_get();
OLED_ShowSignedNum(1,8,num,5);
}
}
定义一个计数变量,在主循环中,每次通过encoder_get()函数获取一个变化值,就加减在主函数的计数变量上。然后在OLED屏幕上打印当前的计数。
四、通过旋转编码器控制舵机角度
4.1、综述
上面代码中,“按键”其实相当于舵机的“红绿灯”。按键是一个人为控制的脉冲信号,只要出现这个脉冲信号,舵机就可以工作一次。
所以,只要给舵机传递人为的脉冲信号,舵机就可以按照我们预设的方式进行运动了。
因此,可以将外部定时中断部分的旋转编码器迁移到这里来,将“人拧动旋转编码器”的事件作为脉冲输入,控制舵机的运转。
4.2、功能描述
4.2.1、正常工作
扭动旋转编码器给定角度,舵机跟随旋转编码器的变化而运动。OLED屏幕上显示舵机旋转的角度和计数器的计数。
4.2.2、异常停止
无效旋转时,舵机不动,OLED屏幕上的旋转角度和计数器计数不变。直到重新进行有效旋转,三者立刻进入工作状态。
无效旋转是指,当计数器计数到达0计数时,继续沿递减方向旋转编码器的操作,以及当计数器计数达到180计数时,继续沿递增方向旋转编码器的操作。
4.2.3、按键复位
按下电键后,舵机立即回到初始位置,OLED屏幕上,舵机旋转角度置0,计数器计数置0。
4.3、实物图讲解
没有现有的电路图。其实就是将上述两个电路图合并为一个。下面是实物图的样子。
4.4、代码部分
4.4.1、Encoder.c && Encoder.h
#include "stm32f10x.h" // Device header
int16_t encoder_count;
void encoder_init(void)
{
/*
A和B相都触发中断,
只有在B相下降沿和A相低电平时,才判断为正转;
只有在A相下降沿和B相低电平时,才判断为反转。
*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure_For_Encoder;
GPIO_InitStructure_For_Encoder.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStructure_For_Encoder.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14;
GPIO_InitStructure_For_Encoder.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC,&GPIO_InitStructure_For_Encoder);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC,GPIO_PinSource13);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC,GPIO_PinSource14);
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line13 | EXTI_Line14;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructure);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
/*对两个通道分别设置优先级*/
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_Init(&NVIC_InitStructure);
}
int16_t encoder_get(void)
{
int16_t temp;
temp = encoder_count;
encoder_count = 0;
return temp;
}
void EXTI15_10_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line13)==SET)
{
if( ! GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_14))
encoder_count++;
EXTI_ClearITPendingBit(EXTI_Line13);
}
if(EXTI_GetITStatus(EXTI_Line14)==SET)
{
if( ! GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_13))
encoder_count--;
EXTI_ClearITPendingBit(EXTI_Line14);
}
}
#ifndef _ENCODER_H
#define _ENCODER_H
void encoder_init(void);
int16_t encoder_get(void);
#endif
4.4.2、PWM.c &&PWM.h
#include "stm32f10x.h" // Device header
void pwm_init(void)
{
/*第一步开启RCC时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
/*第二步:配置GPIO端口*/
GPIO_InitTypeDef GPIO_InitStructure_For_Servo;
GPIO_InitStructure_For_Servo.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure_For_Servo.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure_For_Servo.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure_For_Servo);
/*第三步:配置时基单元*/
TIM_InternalClockConfig(TIM2);/*开启内部时钟*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseImitStructure;
TIM_TimeBaseImitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseImitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseImitStructure.TIM_Period = 20000 - 1;
TIM_TimeBaseImitStructure.TIM_Prescaler = 72 - 1;
TIM_TimeBaseImitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseImitStructure);
/*第四步:配置输出比较单元*/
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0; /*CCR*/
TIM_OC2Init(TIM2,&TIM_OCInitStructure);
/*第五步:使能时钟*/
TIM_Cmd(TIM2,ENABLE);
}
void pwm_setcompare2(uint16_t compare)
{
TIM_SetCompare2(TIM2,compare);
}
#ifndef _PWM_H
#define _PWM_H
void pwm_init(void);
void pwm_setcompare2(uint16_t compare);
#endif
4.4.3、Servo.c && Servo.h
#include "stm32f10x.h"
#include "PWM.h"
void servo_init(void)
{
pwm_init();
}
void servo_set_angle(double angle)
{
pwm_setcompare2(angle / 180 * 2000 + 500);
}
#ifndef _SERVO_H
#define _SERVO_H
void servo_init(void);
void servo_set_angle(double angle);
#endif
4.4.4、Encoder模块和PWM模块冲突判断与解决方案
Encoder模块开启的是APB2总线上的GPIOC和AFIO的时钟,采用的是GPIO -> AFIO -> EXTI -> NVIC线路;
PWM模块开启的是APB1总线上的TIM2时钟和APB2总线上的GPIOA时钟,采用的是GPIO -> TIM -> OC -> TIM_Cmd线路;
所以两个模块在开启时钟上并不冲突,但是在选择线路时均有配置GPIO模块的代码。配置GPIO模块时,需要定义GPIO的结构体。
因此,在Encoder和PWM的源文件中,应将
GPIO_InitTypeDef GPIO_InitStructure;
分别改为
GPIO_InitTypeDef GPIO_InitStructure_For_Encoder;
和
GPIO_InitTypeDef GPIO_InitStructure_For_Servo;
4.4.5、main.c
具体步骤:
第一步:添加头文件。
根据功能描述可知,头文件需包含OLED、Servo、Encoder和按键Key。
#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"
#include "Servo.h"
#include "Encoder.h"
#include "Key.h"
第二步:初始化。
主函数中,首先对上面四个头文件对应的模块初始化。
int main(void)
{
OLED_Init();
encoder_init();
servo_init();
Key_Init();
while (1)
{
}
}
第三步:定义全局变量。
定义三个变量,依次用于记录按键键码、旋转编码器计数值、舵机旋转角度值。
uint8_t key_num;
uint8_t encoder_num;
double angle;
第四步:获取实时参数。
主循环中,首先通过Key_GetNum()函数获取按键键码,之后通过encoder_num += encoder_get()得到最终的计数器计数值。
int main(void)
{
OLED_Init();
encoder_init();
servo_init();
Key_Init();
while (1)
{
key_num = Key_GetNum();
encoder_num += encoder_get();
}
}
第五步:判断异常。
判断异常:当encoder_num >= 180时,舵机就待在原位不动了,那么如果再向递增方向旋转编码器,encoder_num的值应该被强制固定在179。同理,如果encoder_num <= 0,,舵机 也应该待在原位不动了,如果再向递减方向旋转编码器,encoder_num的值应该被强制固定在1。
注意,一定是179和1,不能是180和0。因为到了180和0,舵机就会自动触发复位设置。
int main(void)
{
OLED_Init();
encoder_init();
servo_init();
Key_Init();
OLED_ShowString(1,1,"angle:");
OLED_ShowString(2,1,"count:");
while (1)
{
key_num = Key_GetNum();
encoder_num += encoder_get();
if(encoder_num >= 180) encoder_num = 179;
if(encoder_num <= 0) encoder_num = 1;
}
}
第六步:判断复位。
判断复位:如果key_num == 1,则encoder_num应该被立即强制赋值为0。
int main(void)
{
OLED_Init();
encoder_init();
servo_init();
Key_Init();
OLED_ShowString(1,1,"angle:");
OLED_ShowString(2,1,"count:");
while (1)
{
key_num = Key_GetNum();
encoder_num += encoder_get();
if(encoder_num >= 180) encoder_num = 179;
if(encoder_num <= 0) encoder_num = 1;
if(key_num == 1) encoder_num = 0;
}
}
第七步:显示。
将角度传递给舵机。并在显示屏上显示两个参数。
完整代码:
#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"
#include "Servo.h"
#include "Encoder.h"
#include "Key.h"
uint8_t key_num;
uint8_t encoder_num;
double angle;
int main(void)
{
OLED_Init();
encoder_init();
servo_init();
Key_Init();
OLED_ShowString(1,1,"angle:");
OLED_ShowString(2,1,"count:");
while (1)
{
key_num = Key_GetNum();
encoder_num += encoder_get();
if(encoder_num >= 180) encoder_num = 179;
if(encoder_num <= 0) encoder_num = 1;
if(key_num == 1) encoder_num = 0;
servo_set_angle(encoder_num % 180);
OLED_ShowNum(1,7,encoder_num,3);
OLED_ShowNum(2,7,encoder_num,5);
}
}