【SG90模拟舵机控制及PCA9685模块的使用】[通俗易懂]

2022-11-03 17:30:22 浏览数 (1)

一.模拟舵机控制

网上不乏对此种舵机的介绍,比如下面这篇文章: 浅谈用单片机控制SG90舵机(原理 编程)

1.简介

SG90模拟舵机在市面上十分常见,价格也比较便宜。常用于航模,机器人或智能小车等。 如上图所示,一个舵机有三条线:VCC、GND和信号线。只要通过信号线给予规定的控制信号即可实现舵机码盘的转动。

2.控制信号

对于此种模拟舵机的控制是通过向信号线持续发送PWM信号,直到舵机转到指定位置(对于数字舵机只需发送一次目标角度的信号)。透过蓝色的舵机外壳可以看到里面有一块很小的电路板,它便是用来将PWM信号转换成舵机的实际转动。

一般PWM信号的周期为20ms(50Hz的频率),想要控制角度只需控制一个周期中高电平持续的时间。以180°舵机为例,对应关系如下:

一个周期中高电平持续时间

舵机保持的角度

0.5ms

1ms

45°

1.5ms

90°

2ms

135°

2.5ms

180°

其他在0°~180°之间的角度通过上表类推即可。(但需要注意舵机的死区时间) 需要理解注意的是:对于舵机而言,所有提到的角度均为绝对角度,而非相对角度。即每个角度所对应的是一个固定的位置,并而不是像步进电机那样相对当前位置转动一定角度。

二.PCA9685模块

也可参考下面这篇博客: 16路12位PWM控制器 PCA9685 后续的介绍借鉴了此篇文章。

1.简介

通过之前对舵机的介绍会发现,控制一个舵机需要占用三个引脚,其中一个为单片机的I/O脚。当所需使用的舵机数量较多时,十分占用引脚资源。此时PCA9685模块便可以发挥作用。

PCA9685芯片设计之初是用来控制多路LED灯,后来发现在控制多路舵机上也可发挥很大作用。因此网上所购买的PCA9685模块大都是以控制多路舵机而设计的。如下图所示:

PCA9685芯片内置了25MHz的晶振,同时也提供外部晶振输入引脚(但是模块中一般不引出此脚,只能使用内部晶振)

2.模块接口介绍:

★1.PCA9685模块的通信使用的是IIC协议,如上图模块左右两侧的SDA数据线和SCL时钟线

★2.OE是芯片的使能引脚,低电平时使能芯片。模块中已经下拉保持低电平,因此使用时可不连接。

★3. 上图中红色线连接的V 引脚是舵机的驱动电压,与PCA9685芯片本身无关。可通过左右两侧的排针接到最小系统板的标准3.3V/5V。当发现驱动电压不够,舵机无法转动或者扭力不够时,可通过上方的接线端子接入外接电源,如锂电池。但需要注意,接入的最大电压不能大于左上角电解电容的耐压值,一般为10V

★4.绿色方框便是舵机的接口,一块PCA9685芯片最多可以控制16路舵机

3.模块器件地址

模块的器件地址构成如上。其中最高位固定为1,最低位为读/写控制位A0~A5决定了其硬件地址,当采用多个此模块时可借此用于分别的控制。模块中对于A0~A5的设置可见上图的右上角紫色方框。6个引脚通过下拉电阻接地,因此默认全为低电平(即器件地址默认0x80。有些地方说是0x40是因为不考虑读写位,也是正确的),如果焊上框中上方的连接点,便会接到VCC,从而变为高电平。

4.相关寄存器

PCA9685的寄存器较多,使用也相对复杂,此处仅介绍控制舵机需要用到的。

①寄存器总览

寄存器地址

名称

00H

MODE1

01H

MODE2

02H

SUBADR1

03H

SUBADR2

04H

SUBADR3

05H

ALLCALLADR

06H

LED0_ON_L

07H

LED0_ON_H

08H

LED0_OFF_L

09H

LED0_OFF_H

···

···

06H 4*X

LEDX_ON_L

06H 4*X 1

LEDX_ON_H

06H 4*X 2

LEDX_OFF_L

06H 4*X 3

LEDX_OFF_H

FA

ALL_LED_ON_L

FB

ALL_LED_ON_H

FC

ALL_LED_OFF_L

FD

ALL_LED_OFF_H

FE

PRE_SCALE

FF

TestMode

②MODE1模式配置寄存器1

SFR name

Address

bit

B7

B6

B5

B4

B3

B2

B1

B0

MODE1

00H

name

RESTART

EXTCLK

AI

SLEEP

SUB1

SUB2

SUB3

ALLCALL

RESTART:0—不复位,1—复位,完成复位后会自动清零;要在SLEEP置0后至少500us以后才能进行复位。

EXTCLK:0—使用内部时钟,1—使用外部时钟;修改此位时,需要先将SLEEP位置1

AI:0—读写后寄存器地址不自动递增,1—读写后寄存器地址自动递增;一般设置自动递增

SLEEP:0—退出SLEEP模式,1—进入SLEEP模式。设置EXTCLK位和PRE_SCALE寄存器前都需先进入SLEEP模式

ALLCALL:0—不响应0x70通用IIC地址,1—相应0x70通用IIC地址。

③PRE_SCALE寄存器

p r e s c a l e v a l u e = r o u n d ( o s c _ c l o c k 4096 ∗ u p d a t e _ r a t e ) − 1 prescale ~value=round(frac{osc_clock}{4096*update_rate})-1 prescale value=round(4096∗update_rateosc_clock​)−1

osc_clock为选用的时钟频率,如使用内部25MHz时钟,即为25 000 000。 update_rate为PWM的频率,如前面说舵机PWM周期为20ms,则update_rate=50Hz。 4096是因为计数器是12位。

经过上式计算可发现,PRE_SCALE中所存的值实际是计数器ACK每自加1,需要的时钟脉冲个数。其实就是时钟分频后计数。和51或stm32的定时器原理类似。

④每个通道的四个寄存器

由之前的寄存器总览表中可看出:16个通道中,每个都有LEDX_ON_L、LEDX_ON_H、LEDX_OFF_L、LEDX_OFF_H 四个寄存器

芯片中12位的计数器ACK,会根据PRE_SCALE设置的值进行计数。 当LEDX_ON_H[3:0]:LEDX_ON_L < ACK < LEDX_OFF_H[3:0]:LEDX_OFF_L时,输出高电平; 当LEDX_OFF_H[3:0]:LEDX_OFF_L < ACK < 4096时,输出低电平;

实际应用中有误差,需要校准,把update_rate乘0.915

5.以stc15单片机为例代码

以上提到的一些要点均会体现在下方的代码中。

★需要注意的是:此模块同一时刻只能改变一个PWM输出,因此控制多个舵机时,只能依次控制,并不能实现多个同步控制。当然,如果使用上面寄存器表中给出的ALL_LED_ON/OFF_L/H四个寄存器,也可以实现所有舵机一起转动,但是转动角度只能是全部相同。

代码语言:javascript复制
#include <stc15.h> 
#include <intrins.h> 
#include <stdio.h>
#include <math.h>
typedef  unsigned char  u8;        
typedef  unsigned int   u16;        
sbit SCL=P2^0;                   //时钟线
sbit SDA=P2^1;                   //数据线
#define DELAY_TIME 5 //延时时间
#define PCA9685_adrr 0x80 //1 A5 A4 A3 A2 A1 A0 w/r 
#define PCA9685_SUBADR1 0x02
#define PCA9685_SUBADR2 0x03
#define PCA9685_SUBADR3 0x04
#define PCA9685_MODE1 0x00 //MODE1寄存器地址
#define PCA9685_PRESCALE 0xFE //PRE_SCALE寄存器地址
#define LED0_ON_L 0x06 //通道0的四个控制寄存器
#define LED0_ON_H 0x07
#define LED0_OFF_L 0x08
#define LED0_OFF_H 0x09
#define ALLLED_ON_L 0xFA //全部通道的四个控制寄存器
#define ALLLED_ON_H 0xFB //舵机控制时一般不用
#define ALLLED_OFF_L 0xFC
#define ALLLED_OFF_H 0xFD
#define SERVO000 130 //0度对应4096的脉宽计数值
#define SERVO180 520 //180度对应4096的脉宽计算值
//每个舵机都会有一定差异,需要实际测试。
/*-----------------------IIC协议-------------------------*/
/********************************************************* 函数功能:毫秒延时函数 *********************************************************/
void delayms(u16 z)
{ 

u16 x,y;
for(x=z;x>0;x--)
for(y=148;y>0;y--);
}
/********************************************************* 函数功能:IIC微秒延时函数,晶振12M,指令周期1T *********************************************************/
void IIC_Delay(unsigned char i)
{ 

do
{ 

_nop_();_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();_nop_();
_nop_();_nop_();_nop_();_nop_();_nop_();
}while(i--);      
}
/********************************************************* 函数功能:IIC启动 *********************************************************/
void IIC_Start(void)
{ 

SDA = 1;
SCL = 1;				
IIC_Delay(DELAY_TIME);
SDA = 0;
IIC_Delay(DELAY_TIME);
SCL = 0;	
}
/********************************************************* 函数功能:IIC停止 *********************************************************/
void IIC_Stop(void)
{ 

SDA = 0;
SCL = 1;				
IIC_Delay(DELAY_TIME);
SDA = 1;
}
/********************************************************* 函数功能:等待从机应答 *********************************************************/
bit IIC_WaitAck(void)
{ 
 
SDA = 1;
IIC_Delay(DELAY_TIME);
SCL = 1;
IIC_Delay(DELAY_TIME);
if(SDA)    
{ 
   
SCL = 0;
IIC_Stop();
return 0;
}
else  
{ 
 
SCL = 0;
return 1;
}
}
/********************************************************* 函数功能:IIC发送一个字节 *********************************************************/
void IIC_SendByte(unsigned char byt)
{ 

unsigned char i;
for(i=0;i<8;i  )
{ 
   
if(byt&0x80) 
{ 
	
SDA = 1;
}
else 
{ 

SDA = 0;
}
IIC_Delay(DELAY_TIME);
SCL = 1;
byt <<= 1;
IIC_Delay(DELAY_TIME);
SCL = 0;
}
}
/********************************************************* 函数功能:IIC接收一个字节 *********************************************************/
unsigned char IIC_RecByte(void)
{ 

unsigned char da;
unsigned char i;
for(i=0;i<8;i  )
{ 
   
SCL = 1;
IIC_Delay(DELAY_TIME);
da <<= 1;
if(SDA) 
da |= 0x01;
SCL = 0;
IIC_Delay(DELAY_TIME);
}
return da;
}
/*-----------------------PCA9685模块相关函数-------------------------*/
/********************************************************* 函数功能:向PCA9685的一个地址写数据 *********************************************************/
void PCA9685_write(u8 address,u8 date)
{ 

IIC_Start();
IIC_SendByte(PCA9685_adrr);   //PCA9685的片选地址
IIC_WaitAck();                          
IIC_SendByte(address);  	  //写地址控制字节
IIC_WaitAck();
IIC_SendByte(date);           //写数据
IIC_WaitAck();
IIC_Stop();
}
/********************************************************* 函数功能:从PCA9685的一个地址读数据 *********************************************************/
u8 PCA9685_read(u8 address)
{ 

u8 date;
IIC_Start();
IIC_SendByte(PCA9685_adrr); //PCA9685的片选地址
IIC_WaitAck();
IIC_SendByte(address);
IIC_WaitAck();
IIC_Start();
IIC_SendByte(PCA9685_adrr|0x01);        //地址的第八位控制数据流方向,就是写或读
IIC_WaitAck();
date=IIC_RecByte();
IIC_Stop();
return date;
}
/********************************************************* 函数功能:PCA9685的MODE1寄存器清零 *********************************************************/
void reset(void) 
{ 

PCA9685_write(PCA9685_MODE1,0x00);
}
/********************************************************* 函数功能:PCA9685频率修改 入口参数:freq-输出PWM频率 *********************************************************/
void setPWMFreq(float freq) 
{ 

u16 prescale,oldmode,newmode;
float prescaleval;
freq *= 0.92;   							//纠正频率设置中的过冲,进行校准 
prescaleval = 25000000;						//根据公式计算prescale的值
prescaleval /= 4096;						//prescaleval=round(osc_cloc/4096/freq)-1;
prescaleval /= freq;
prescaleval -= 1;
prescale = floor(prescaleval   0.5);		
oldmode = PCA9685_read(PCA9685_MODE1);		  //获得MODE1寄存器值
newmode = (oldmode&0xEF) | 0x10; 			  //SLEEP位 置1
PCA9685_write(PCA9685_MODE1, newmode);  	  //进入SLEEP模式
PCA9685_write(PCA9685_PRESCALE, prescale);    //设置频率
PCA9685_write(PCA9685_MODE1, oldmode);		  //退出SLEEP模式
delayms(2);
PCA9685_write(PCA9685_MODE1, oldmode | 0xa1); //RESTART、AI、ALLCALL三个位 置1
}
/********************************************************* 函数功能:改变通道PWM占空比 输入参数:num-使用的通道0~15 on-高电平开始时计数器ACK值 off-高电平结束时计数器ACK值 *********************************************************/
void setPWM(u16 num, u16 on, u16 off) 
{ 

PCA9685_write(LED0_ON_L 4*num,on);		//LED0_ON_L保存on低8位
PCA9685_write(LED0_ON_H 4*num,on>>8);	//LED0_ON_H保存on高4位
PCA9685_write(LED0_OFF_L 4*num,off);
PCA9685_write(LED0_OFF_H 4*num,off>>8);
}
/*-----------------------主函数-------------------------*/
void main()
{ 

reset();
setPWMFreq(50);  //设置频率50Hz
//以转动到60°位置为例:
//60度对应的脉宽=0.5ms (60/180)*(2.5ms-0.5ms)=1.1666ms
//利用占空比=1.1666ms/20ms=off/4096,off=239,50hz对应周期20ms
//setPWM(num,0,239);
while(1) 
{ 

setPWM(0, 0, 239);
setPWM(1, 0, SERVO000);
}                
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/203716.html原文链接:https://javaforall.cn

0 人点赞