PWM 脉宽调制配置详解与完整SG90舵机驱动

一 PWM基本介绍

本章简单介绍PWM基本特征

1 PWM基础简介

PWM(脉冲宽度调制)是一种通过调整脉冲宽度来控制输出信号的技术。(常用于通过设置数字引脚的高低电平持续时间,来控制各种设备的运行状态,速度亮度声音等)

(1) 基本原理

  • PWM信号是一种周期固定、占空比可调的方波信号。其波形由一系列等幅不等宽的矩形脉冲组成,脉冲的宽度(即高电平持续时间)是可调节的,而周期保持不变
  • 产生原理:通过改变脉冲的宽度和周期来模拟不同的电压或功率级别。在一个周期内,通过改变高电平持续时间占整个周期的比例,即占空比,来实现对输出信号调制。例如,若占空比为 50%,则在一个周期内高电平时间和低电平时间相等,平均电压为电源电压的一半。

(2) 基础参数

  • 频率:指1秒内多少个完整的PWM周期,单位为赫兹(Hz)。它决定了PWM信号的更新速度,较高的频率可以使负载响应更平滑。
  • 周期:PWM输出一个完整信号的时长,为频率Hz的倒数,单位为
  • 占空比:表示高电平在一个周期内所占的比例,通常用百分比表示。例如,如果高电平持续时间为8ms,周期为10ms,那么占空比就是80%。通过调整占空比,可以控制输出信号的平均功率或电压(一般不需要直接设小数比例)
  • 实现原理:微控制器可以基于定时器,精确地控制数字引脚输出高电平或低电平的时间,例如在一个10ms的周期,高电平持续时间为2毫秒,低电平持续时间为8毫秒,依靠对定时器的设定,即可满足不同的PWM频率,控制不同外设。

(4) 应用领域

  • 电机控制:通过调整 PWM 信号的占空比,可以精确地控制电机的转速和扭矩。例如,在直流电机调速中,占空比越大,电机的平均电压越高,转速就越快。
  • LED 调光:利用 PWM 技术可以实现 LED 灯的亮度调节。通过改变 PWM 信号的占空比和频率,可以使 LED 灯以不同的亮度发光,并且可以避免闪烁现象。
  • 通信领域:在一些通信协议中,PWM 可用于信号的传输和编码。例如,通过改变 PWM 信号的占空比和频率来表示不同的数据信息。
  • 电力电子设备控制:用于控制逆变器、整流器等电力电子设备的输出电压和电流,实现对电网的电能质量控制和节能降耗。

频率区别
时钟频率50Hz:每秒可运算50次,MHz为百万次,GHz为十亿次
PWM频率50Hz:每秒完成50个完整的PWM周期,一个PWM周期时长:1/50=0.02s=20ms


2 定时器简介

(1) 基本概念
定时器是一种具有计数和定时功能的仪器组件,在芯片中主要负责精确测量时间间隔、产生定时中断以及提供时钟信号等功能(简单理解就是个可以控制间隔的计数器

(2) 分类

  • 高级定时器:如 TIM1、TIM8,通常具有更丰富的功能和更高的性能,适用于对时间精度和功能要求较高的应用,如电机控制、高精度脉冲输出等(可产生7路PWM)
  • 通用定时器:包括 TIM2-TIM5、TIM9-TIM14 等,其功能相对较为平衡,可满足一般的定时、计数、PWM 输出等需求。(可产生4路PWM)
  • 基本定时器:如 TIM6、TIM7,功能相对简单,主要用于一些基础的定时任务。
  • 系统定时器:SysTick 是一个特殊的定时器,位于 Cortex-M0 内核中,常用于操作系统的时钟节拍、延时函数等。

(3) 主要特点

  • 多种工作模式:包括定时器模式、计数器模式、PWM 模式、输入捕获模式、输出比较模式等,可根据不同的应用场景选择合适的工作模式。
  • 高精度:通过合理的配置计数器预分频器自动重载寄存器等参数,可以实现高精度的时间测量和控制,满足各种对时间精度要求较高的应用需求。
  • 可编程性:用户可以通过编写程序对定时器的各项参数进行灵活配置,如计数方向、计数周期、输出极性等,以实现不同的功能。
  • 中断和 DMA 支持:定时器可以产生中断请求,当定时器计数到达设定值或发生特定事件时,会触发中断,以便及时处理相应的任务。同时,定时器也可以与 DMA 配合使用,实现数据的自动传输和处理,提高系统的运行效率

二 PWM实现

本章重点介绍频率计算法,与定时器配置,并深入介绍PWM

1 定时器频率设定(重要)

(1) 频率计算原理

  • 定时器实际频率总线时钟频率/分频系数,就是降频,如 72Mhz/2=36MHz,这个分频系数就叫PSC。即每秒最大可计数3600万次,这只不过代表了计数能力,想要实现计时,还需要设置时间间隔。

  • 时间间隔:就是在该计数能力下,计数多少次才算一个间隔。就像表针每秒转一次,微控制器定时器的时间间隔,可以设的远远比1s更精确。假设在7200Hz下,间隔设为每计数72次完成一个间隔,那么72就是定时器的计数上限,即ARR。达到该上限后,计数清0,进入下一个时间间隔(因为日常理解都是以s为单位,所以易混淆)。

  • 定时器周期:还是以实际频率7200Hz,72次为计数上限为例,可知1s可计数7200次,那么计数72次需要时长为:72/7200*1=0.01s=10ms,这便是定时器周期,公式表示即:计数上限/(总时钟频率/分频次数)

以控制SG90舵机为例,舵机本身的PWM频率为50Hz,则周期为1/50=20ms,需要将定时器对应修改,可灵活修改:

时钟频率分频系数实际定时器频率计数上限定时器周期
7200 0000(即72MHz)72100 0000200002w/100w=20ms
72MHz720010000200200/1w=20ms

并且,计数上限越大,则可控精度便越高。显而易见,2w会比20控制更精确。顺便一提,SG90舵机本身的控制方式:

  • 一个PWM周期为20ms
  • 占空比为 0.5ms, 1ms,1.5ms,2ms, 2.5ms时
  • 对应的角度:0° 45° 90° 135° 180°
  • 也就是20ms内,前0.5m通高电平其余时间低电平,舵机转动到0°位置, 0.5ms~ 1ms之间对应0-45°,其余同理。(对没错,就是这么精准)

定时器周期计算化简为字母公式就是:定时器周期=(PSC+1)(ARR+1)/系统时钟频率,不过字母的不怎么好看,啧,一套又一套的。(+1是因为配置本身数值问题,无需纠结)

(2) 定时器配置步骤

  • 开RCC时钟,配置定时器时钟源
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);//开启TIM2的时钟
TIM_InternalClockConfig(TIM2);	    //选择TIM2为内部时钟
  • 时基单元初始化
名称介绍
TIM_Prescaler(PSC)分频系数设置定时器的预分频值,调整TIM_Prescaler的值,可以改变定时器的计数速度。如果TIM_Prescaler的值较大,定时器的计数速度就会变慢;反之,越小越快。除法计算
TIM_CounterMode设置定时器的计数方向,向上计数模式、向下计数模式和中心对齐计数模式等。在向上计数模式下,计数器从0开始计数,每次计数递增,达到最大值归计数0并进入下一个时间间隔
TIM_Period(ARR)计数上限一次时间间隔的 计数上限
TIM_ClockDivision设置定时器的时钟分频因子,库函数提供了几个固定参数,实现不同的计数精度和计数速率
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 20000;					//ARR的值,计数上限
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;				//预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);  

2 PWM 定时器比较模式设定

PWM需要使用定时器的输出比较模式,需要定时器在特定的时间点输出一个信号,通过定时器的输出比较模式,可以精确地设定这个时间点,当计数器的值与预设的比较值匹配时,输出相应的信号,从而实现占空比设置。

(1) 输出比较模式配置

  • TIM_OCMode:选择PWM模式
  • TIM_OCPolarity:定时器输出引脚输出高低电平设定
  • TIM_OutputState:是否开启比较
  • TIM_Pulse:其实就是初始化时的占空比

配置好后,需要打开对应引脚的通道即可(通用定时器可支持4路PWM输出,高级定时器支持7路PWM输出)

	//用于设置定时器的输出比较模式
	TIM_OCInitTypeDef TIM_OCInitStructure;
	TIM_OCStructInit(&TIM_OCInitStructure); 								//结构体初始化.给结构体所有成员都赋一个默认值
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;			//输出比较模式,选择PWM模式1
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;	//当计数器的值与 捕获/比较寄存器的值相等时,定时器的输出引脚是输出高电平还是低电平
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;	//比较通道是否打开
	TIM_OCInitStructure.TIM_Pulse = 0;		     								 //初始化占空比
	
	//配置TIM2的输出比较通道
	TIM_OC1Init(TIM2, &TIM_OCInitStructure);		    //将结构体变量交给TIM_OC1Init通道
	TIM_OC2Init(TIM2, &TIM_OCInitStructure);        //1、2分别对应PA0、PA1
       
	/*TIM使能*/
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行

(2) 占空比设定

通过TIM_SetCompareX函数,直接设置对应通道占空比。注意这里并非填入一个比例值得,而是根据计数上限ARR设定:

如:ARR=20000,一个定时周期为20ms,前0.5m要设置高电平。

则 compare的值为:0.5 / 20 * 20000=500

//设置占空比函数
void PWM_SetCompare1(uint16_t Compare)
{
	TIM_SetCompare1(TIM2, Compare);
}

由上述例子可得出,在SG90舵机的例子中,Compare=500对应舵机的0°,同理可计算出180°:2.5/20*20000=2500,故该频率和计数上限下:500~2500可表示舵机的0-180°,按比例对应皆可控制舵机角度。

3 深入剖析PWM

虽然数字引脚本身只有高和低两种状态,但PWM通过快速切换这两种状态并调整持续时间,可以模拟出不同的输出电平效果,从而实现调控输出的电平大小的效果。基与原理如下:

(1) 冲量相等效果相同原理
当冲量(即窄脉冲的面积)相等而形状不同的窄脉冲加在具有惯性的环节上时,其效果基本相同。在PWM(脉冲宽度调制)控制技术中,这一原理被广泛应用。通过对一系列脉冲的宽度进行调制,来等效地获得所需要的波形(包括形状和幅值)。

(2)信号等效原理
在某些特定条件下,一个复杂的信号可以用另一个 简单的信号来等效或替代,而这种替代不会对系统或分析产生实质性的影响。

LED的有趣特性
在PWM控制LED亮度场景中,而由于人眼的视觉残留效应,人眼就会感觉不到高频的LED闪烁,看到的是一个稳定的常亮效果,类似于一种“惯性”效果。但当你通过修改定时器,把PWM实际频率调为60hz,占空比哪怕只设为1/ARR,用手机摄像头录像就可以捕捉到,其实LED是在高速闪烁,本身亮度没有衰弱。

总结:
<1> 通过改变高电平持续的时间,就可以控制负载在一个周期内接收的平均电流能量,从而改变其亮度或工作状态。
<2> 表现:高电平持续时间较长,那么负载就会接收到更多的能量,表现得更亮或工作更强烈;反之,如果高电平持续时间较短,负载就会接收到较少的能量,表现得较暗或工作较弱。


三 代码实现

本章为几个简单实例,含基础PWM驱动,与ServoSG90舵机驱动。本章阅读及建议目录跳转

1 PWM基本驱动

(1)头文件

//PWM.h
#ifndef __PWM_H
#define __PWM_H
/*
Tim2 4 通道可控制4路PWM信号,使用2例
PWM输出引脚:A0 A1
*/

void PWM_Init(void);
void PWM_SetCompare1(uint16_t Compare);//A0
void PWM_SetCompare2(uint16_t Compare);//A1

#endif

(2)实现文件

//PWM.c
#include "stm32f10x.h"                  // Device header
#include "PWM.h"

/*
功能:PWM初始化
*/
void PWM_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA0引脚初始化为复用推挽输出	
																			
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 20000;					//即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;				//预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
	
	//用于设置定时器的输出比较模式
	TIM_OCInitTypeDef TIM_OCInitStructure;
	TIM_OCStructInit(&TIM_OCInitStructure); //结构体初始化.给结构体所有成员都赋一个默认值
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;				//输出比较模式,选择PWM模式1
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;	//当计数器的值与 捕获/比较寄存器的值相等时,定时器的输出引脚是输出高电平还是低电平
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;	//比较通道是否打开
	TIM_OCInitStructure.TIM_Pulse = 0;		      //初始化占空比
	
	//配置TIM2的输出比较通道
	TIM_OC1Init(TIM2, &TIM_OCInitStructure);		//将结构体变量交给TIM_OC1Init,
	TIM_OC2Init(TIM2, &TIM_OCInitStructure);        //1、2分别对应PA0、PA1
       
 
	
	/*TIM使能*/
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}

/*
功能:TIM2通道1,占空比 (改变每个周期内高电平或低电平的持续时间)
参数:Compare要写入的CCR的值,范围:0~20000 ,即为ARR范围
返回值:无
注意:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
占空比: CCR/(ARR+1)
*/
void PWM_SetCompare1(uint16_t Compare)
{
	TIM_SetCompare1(TIM2, Compare);		//设置CCR1的值,精确地设置高电平持续时间
}
/*
功能:TIM2通道2,占空比
-同上-
*/
void PWM_SetCompare2(uint16_t Compare)
{
	TIM_SetCompare2(TIM2, Compare);
}
 

(3)main函数测试文件

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"

//简易测试
void servo(int arr){
	int r=1.0*arr/180*2000+500;
   	PWM_SetCompare1(r);
}

void LED(int arr){
	int r=1.0*arr/1024*20000;
   	PWM_SetCompare2(r);
}

/*
舵机A0
LED A1
*/
int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	PWM_Init();			//PWM初始化
	
	/**/
	servo(140);
	while (1)
	{
		/*
		舵机测试 与 LED测试
		*/
		for(int i=0;i<=180;i++){
			servo(i);
			OLED_ShowNum(1,1,i,3);
			Delay_ms(15);
		}
		for(int i=0;i<=1024;i++){
			LED(i);
			Delay_ms(5);
		}
		Delay_ms(500);
		
	}
}


2 ServoSG90舵机驱动

简易实现舵机度数设定增加获取角度功能,2路舵机。
(1)头文件

//ServoSG90.h
#ifndef __SERVOSG90_H
#define __SERVOSG90_H
/*
ServoSG90输出引脚:A0 A1

*/
extern int Servo1_angle; //舵机1角度
extern int Servo2_angle; //舵机2角度

void Servo_Init(void);					//所有舵机初始化
void Servo1_Set(double Compare);//设置舵机1角度
void Servo1_Add(int angle);     //增加舵机1角度
int Get_Servo1(void);           //获取舵机1角度

void Servo2_Set(double Compare);//设置舵机2角度
void Servo2_Add(int angle);     //增加舵机2角度
int Get_Servo2(void);           //获取舵机2角度

#endif

(2)实现文件

//ServoSG90.c
#include "stm32f10x.h"                  // Device header
#include "ServoSG90.h"

int Servo1_angle=0;
int Servo2_angle=0;

/*
功能:Servo初始化
使用A0 A1引脚
*/
void Servo_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);	
																			
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM2);
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;							//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 20000;										//即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;								//预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
	
	//用于设置定时器的输出比较模式
	TIM_OCInitTypeDef TIM_OCInitStructure;
	TIM_OCStructInit(&TIM_OCInitStructure); 								//结构体初始化.给结构体所有成员都赋一个默认值
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;				//输出比较模式,选择PWM模式
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;	//当计数器的值与 捕获/比较寄存器的值相等时,定时器的输出引脚是输出高电平还是低电平
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;	//比较通道是否打开
	TIM_OCInitStructure.TIM_Pulse = 0;		     								 //初始化占空比
	
	//配置TIM2的输出比较通道
	TIM_OC1Init(TIM2, &TIM_OCInitStructure);		    //将结构体变量交给TIM_OC1Init通道
	TIM_OC2Init(TIM2, &TIM_OCInitStructure);        //1、2分别对应PA0、PA1
       
	/*TIM使能*/
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}

/*
功能:设置舵机1度数,
参数:0-180
*/
void Servo1_Set(double Compare)
{
	
	if(Compare>=180)
		Servo1_angle=180;
	if(Compare<=0)
		Servo1_angle=0;
	Servo1_angle=Compare;  //记录角度
	
	double tem=Compare/180*2000+500;
	TIM_SetCompare1(TIM2, tem);		//设置CCR1的值,精确地设置高电平持续时间
}

/*
功能:增加舵机1度数,
参数:0-180,越界会自动归与边界
*/
void Servo1_Add(int angle){
	Servo1_Set(Servo1_angle+angle);
}
/*
功能:获取舵机1度数,
参数:0-180,越界会自动归与边界
*/
int Get_Servo1(void){
	return Servo1_angle;
}


/*
功能:设置舵机2度数,
参数:0-180
*/
void Servo2_Set(double Compare)
{
	if(Compare>=180)
		Servo2_angle=180;
	if(Compare<=0)
		Servo2_angle=0;
	Servo2_angle=Compare;  //记录角度
	
	double tem=Compare/180*2000+500;
	TIM_SetCompare2(TIM2, tem);		//设置CCR1的值,精确地设置高电平持续时间
}

/*
功能:增加舵机2度数,
参数:0-180,越界会自动归与边界
*/
void Servo2_Add(int angle){
	Servo2_Set(Servo2_angle+angle);
}
/*
功能:获取舵机2度数,
参数:0-180,越界会自动归与边界
*/
int Get_Servo2(void){
	return Servo2_angle;
}

(3)main函数测试文件

#include "stm32f10x.h"  // USE_STDPERIPH_DRIVER
#include "delay.h"      //--no-multibyte-chars
#include "OLED.h"
#include "Serial.h"
#include "ServoSG90.h"
/*
1 串口连接:RX-A9 TX-A10
2 OLED连接:SCL-B8 SDA-B9
3 SercoSG90舵机:A0 A1
*/
int main(void)
{
	Serial_Init();
	OLED_Init();
	OLED_ShowString(1,1,"Servo TEST");
	Servo_Init();
	
	int degree=3;
	while (1)
	{
		/*舵机1  Set 遍历法
		for(int i=0;i<=180;i++){
			Servo1_Set(i);
			OLED_ShowNum(2,1,Get_Servo1(),4);
			Delay_ms(25);
		}
		*/
		
		/*舵机1 Add遍历法 */
		Servo1_Add(degree);  
		int t=Get_Servo1();
		OLED_ShowString(2,1,"Servo1 angle:");
		OLED_ShowNum(2,14,t,3);
		Delay_ms(50);
		
		if(t>=180) degree=-3;  //角度增加-3
		if(t<=0)   degree=3;  //角度增加3
	 
		
		/*舵机2 测试
		Servo2_Add(degree);  
		int t=Get_Servo2();
		OLED_ShowString(3,1,"Servo2 angle:");
		OLED_ShowNum(3,14,t,3);
		Delay_ms(50);
		if(t>=180) degree=-3;  //角度增加-3
		if(t<=0)   degree=3;  //角度增加3
		*/
	}
}


3 AD控制舵机

此处为结合AD模数转换PWM控制舵机,只需配置AD和PWM引脚:电位器控制舵机只需几行代码

    AD_Init();  //A2
	Servo_Init(); //A0 A1
	while (1)
	{
		int degree=AD_GetValue(ADC_Channel_2);//采集模拟引脚值,比例转化为度数
		degree=(degree+1)/4096.0*180;  
		Servo1_Set(degree);   //PWM控制舵机
		
		OLED_ShowString(2,1,"Servo angle:");
		OLED_ShowNum(2,14,degree,3);

		
	}

实际效果图:

在这里插入图片描述


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值