STM32:通过旋转计数器的计数控制舵机旋转的角度

#哪个编程工具让你的工作效率翻倍?#

声明:本博客为各模块之间结合的自主研究学习。

目录

一、按键操控舵机旋转(单向)

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);
	}
}

  • 21
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青山入墨雨如画

你的鼓励将是我创作的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值