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

news2024/9/23 21:32:43

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

目录

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2105462.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ts 类型分类

目录 01 ts 相关指令 02 类型声明空间与变量声明空间 03 类型注解和类型推断 04 类型分类 , 联合类型与交叉类型 05 never类型 any类型 和unknown类型 01 ts 相关指令 全局安装 typescript 模块 npm i -g typescript 安装完成之后 可以将ts文件转换成js文件 tsc xxx.ts…

为什么越来越多的人选择开放式耳机?平价高品质蓝牙耳机推荐

越来越多的人选择开放式耳机&#xff0c;主要是因为其具有多方面的优势&#xff0c;具体如下&#xff1a; 佩戴舒适度高&#xff1a;开放式耳机不入耳&#xff0c;不堵塞耳道&#xff0c;避免了对耳道的压迫和摩擦&#xff0c;长时间佩戴也不易产生闷热感和不适感。例如&#x…

windows安装git

windows安装git 1.通过git官网下载 官网&#xff1a;https://git-scm.com/downloads 2.安装git &#xff08;1&#xff09;找到下载到的文件&#xff0c;进行双击进行安装 &#xff08;2&#xff09;安装使用说明点击Next &#xff08;3&#xff09;选择自己的安装地址&a…

大屏可视化:阿里 DataV 大屏怎么做自适应的?

你好&#xff0c;我是沐爸&#xff0c;欢迎点赞、收藏、评论和关注。 阿里 DataV 大屏是一款功能强大的数据可视化应用搭建工具&#xff0c;由阿里云提供&#xff0c;旨在帮助用户通过图形化的界面轻松搭建专业水准的可视化应用。 下面我们一起看下 DataV 大屏 是如何做自适应…

【嵌入式】uint32值写入到二进制文件中的存储详情

背景 项目预留100字节用于存储参数文件。现在有些数值一个字节表示不够&#xff0c;需要用4个字节。比如数值1144201745&#xff0c;对应的十六进制0x44332211,那么在二进制中存储是怎么样的呢&#xff1f;做个总结 分析 写了一个sample&#xff0c;把uint32_t u32Value1{0x…

【ThreadLocal源码】深入浅出ThreadLocal

【ThreadLocal源码】深入浅出ThreadLocal-CSDN博客

Linux之Prometheus

认识普罗米修斯 1、prometheus介绍 Prometheus(普罗米修斯)是一套开源的监控&报警&时间序列数据库的组合, 由go语言开发。 适合监控容器平台, 因为kubernetes(俗称k8s)的流行带动了prometheus的发展。 PS&#xff1a;文档使用prometheus监控物理服务器。 prometheus的…

数组程序基础知识

为了处理方便&#xff0c;把具有相同类型的变量按有序的形式组织起来。这些同类数据元素的集合称之为数组。数组可分为数值数组、字符数组、指针数组、结构数组等。 01--一维数组 在使用数组前需要先定义&#xff1a; 类型说明符 数组名[常量表达式]&#xff1b; 类型说明符…

因 Mysql root 密码过于简单导致 Mysql 连接失败的解决方法

问题&#xff1a; Access denied for user ‘root’‘192.168.xx.xx’ (using password: YES) 用户“root”“192.168.xx.xx”的访问被拒绝&#xff08;使用密码&#xff1a;YES&#xff09; 解决方法&#xff1a; 1、使用root用户登录mysql&#xff0c;通过下面的命令给ro…

数据管理能力成熟度评估模型DCMM

一、引言 DCMM&#xff08;Data Management Capability Maturity Assessment Model&#xff0c;数据管理能力成熟度评估模型&#xff09;是我国首个数据管理领域国家标准&#xff08;GB/T 36073-2018&#xff09;&#xff0c;由中国国家标准化管理委员会于2018年3月15日发布&am…

FreeRTOS基础入门——FreeRTOS信号量及二值信号量(十三)

个人名片&#xff1a; &#x1f393;作者简介&#xff1a;嵌入式领域优质创作者&#x1f310;个人主页&#xff1a;妄北y &#x1f4de;个人QQ&#xff1a;2061314755 &#x1f48c;个人邮箱&#xff1a;[mailto:2061314755qq.com] &#x1f4f1;个人微信&#xff1a;Vir2025WB…

智联云采 SRM2.0 autologin 身份认证绕过漏洞复现

0x01 产品简介 智联云采是一款针对企业供应链管理难题及智能化转型升级需求而设计的解决方案,针对企业供应链管理难题,及智能化转型升级需求,智联云采依托人工智能、物联网、大数据、云等技术,通过软硬件系统化方案,帮助企业实现供应商关系管理和采购线上化、移动化、智能…

【王树森】RNN模型与NLP应用(4/9):LSTM模型(个人向笔记)

前言 LSTM是对Simple RNN的改进&#xff0c;可以避免梯度消失的问题&#xff0c;能够有更长的记忆力。 LSTM 1. LSTM:Conveyor Belt 过去的信息 C t − 1 C_{t-1} Ct−1​ 通过一个传输带直接输送到下一个状态 C t C_t Ct​&#xff0c;不会发生太大的变化&#xff0c;由此…

激活函数 Sigmod 及其导数

文章目录 1. Sigmod 函数2. 取值3. 图像4. 导数 1. Sigmod 函数 Sigmod 函数是神经网络中最常用的激活函数之一&#xff0c;其形式如下&#xff1a; sigmod ( x ) f ( x ) 1 1 e − x . \text{sigmod}(x) f(x) \frac{1}{1 e^{-x}}. sigmod(x)f(x)1e−x1​. 2. 取值 分…

大模型理论基础

大模型理论基础与学习路径 1.大模型的理论基础&#xff0c;包括深度学习、预训练语言模型和大语言模型。 2.学习大模型开发的路径&#xff0c;包括理论学习、实践操作和项目应用。 3.如何通过理论学习提升上限&#xff0c;为深入学习大模型奠定基础。 GPT模型家族技术发展 1.GP…

数据结构——堆排序

目录 引言 堆排序 1.算法思想 2.算法步骤 3.代码实现 3.1 构建堆 (1)小堆 (2)大堆 3.2 交换与调整 3.3 重复上述过程 4.复杂度分析 5.完整代码 5.1算法实现代码 5.2 示例 6.堆排序的优势 结束语 引言 本篇博客&#xff0c;我们将利用堆结构实现的高效排序算法…

版本控制的核心:Git中的哈希与默克尔树解析

Git是最常用的代码版本控制工具。它帮助我们跟踪代码的更改、管理代码版本&#xff0c;同时保证代码库的完整性和安全性。我们知道 Git 中有一些基本的操作&#xff0c;比如commit、merge、rebase等&#xff0c;但这些操作的底层机制是如何实现的呢&#xff1f;哈希函数和默克尔…

深度学习|模型推理:端到端任务处理

文章目录 引言端到端的能力任务与模型简介手写数字识别数据准备训练集与测试集模型介绍 推理过程前向传播权重参数推理与评估 结语 引言 通过前文「深度学习&#xff5c;感知机&#xff1a;神经网络之始」中 XOR Gate 的示例&#xff0c;我们知道叠加层可以增强感知机的表达能…

单向链表排序及双向链表

单向链表的优缺点 优点&#xff1a;存储空间没有上限&#xff0c;插入删除效率高 缺点&#xff1a;修改和查找效率低&#xff0c;只能单向的向后遍历后续节点&#xff0c;不能向前遍历前驱节点 单向链表快慢指针法查找&#xff1a; 链表的排序 双向链表 由于单向链表只能通…

Linux 性能调优:策略与实践

引言 随着云计算和虚拟化技术的发展&#xff0c;Linux 已经成为企业和个人用户的首选操作系统。Linux 性能调优不仅有助于提高系统资源利用率&#xff0c;还能确保应用程序的高效运行。本文将探讨 Linux 性能调优的基本原则、常用工具和方法&#xff0c;以及实际案例分析。 一…