前言
要使用一个模块,首先最起码要有这个模块的原理图、(芯片寄存器)操作手册
一 PCA9685模块简介
PCA9685是一个基于IIC通信的16路PWM输出模块,可以在单片机资源不足的情况下进行扩展使用。
通俗点就是可以节约定时器资源,举个例子,要控制多个舵机的话,就是控制pwm输出,那么将定时器的psc和arr,即定时器的频率设置成一样会更加方便我们的控制。不然的话就要将多个定时器的频率都得设置成一样,那往后再想用定时器中断什么的,资源就少了。
设备地址
I2C设备地址一般选择7位,pca9685的从机地址由1+A0~A5决定,至于是不是A0-A5都可调,不确定,一般来说也用不上这么多,A0-A2是确认可调的,也就是一个I2C总线至少能控制2^316=128个pwm输出。(6个pca9685模块),如果a3,a4,a5都可调,那就是2的6次方个设备地址16=1024个pwm输出。(2的6次方个pca9685模块)
默认情况下,a0-a5都接地,这个要看pca9685的硬件原理图。
可以看到A0-A5都是接的GND,那么他的I2C设备地址是1000 000=0x40,那为什么代码I2C设备地址为什么是#define PCA9685_adrr 0x80
是因为设备地址一般是7位I2C地址+1位读写位。在pca9685.pdf第7章中有说明
当最后一位是0,是写。所以pca9685的I2C地址是1000 0000 = 0x80
在读的时候,发送的地址位是0x81。
接下来我们要做的就是通过IIC通讯对固定好的寄存器地址写入相应的数据来初始化PCA9685驱动板。
7.3的内容就是pca9685的一些寄存器描述。I2C也是要我们对寄存器进行操作。
我们使用PCA9685的目标很简单,就是可以set_pwm就是我们的目的,所以我们还得设置pwm的频率。
看手册
这里说了睡眠模式下PRE_SCALE是无效的。
二 代码解释
void SetPWMFreq(float freq)
设置SLEEP为1,即工作模式被禁用,这是为了PWM的稳定性,是一种保护机制。
如果在 PWM 信号生成过程中更改了预分频器的设置,可能会导致不稳定的输出,或者产生不期望的 PWM 信号。
pca9685的pwm有4096个计数单元,也就是arr自动重装载值是4096.主频25MHz(手册上有,硬件规定)
void SetPWMFreq(float freq)
{
uint32_t prescale,oldmode,newmode;
double prescaleval;
//freq *= 0.92f; // Correct for overshoot in the frequency setting
prescaleval = 25000000;
prescaleval /= 4096;
prescaleval /= freq;
prescaleval -= 1;
prescale = floor(prescaleval + 0.5);//使用 floor 函数将 prescaleval 四舍五入为最接近的整数,并将结果存储在 prescale 变量中。
oldmode = PCA9685_read(PCA9685_MODE1);//0x00
newmode = (oldmode&0x7F) | 0x10; //
PCA9685_write(PCA9685_MODE1, newmode); // go to sleep
PCA9685_write(PCA9685_PRESCALE, prescale); // set the prescaler设置前需要进入睡眠模式
PCA9685_write(PCA9685_MODE1, oldmode);
PCA9685_write(PCA9685_MODE1, oldmode | 0xa1);
}
prescale其实就是预分频系数,我们这里要设置。
这里的CLK就是pca9685的主频25MHz,period就是arr,我们已知主频,arr,f(频率自己设置),求psc
f=CLK/(pscarr)
psc=CLK/(arrf)=25000000/(4096*freq)
prescaleval -= 1;//因为 PCA9685 的预分频器的最小有效值是 0,而不是 1。
void PCA9685_Reset(void)
{
PCA9685_write(PCA9685_MODE1,0x00);//PCA9685_MODE1寄存器地址,写入0x00,相当于工作模式的一个初始化
}
void PCA9685_Go(void)
{
PCA9685_Reset();
}
代码解释:newmode = (oldmode&0x7F) | 0x10;
oldmode原本就是0,&0x7F的目的是为了把最高位清0。虽然在实际中oldmode的最高位(第7位)可能已经是0了,但是使用位与操作符将其清零可以更好地体现程序员的编程习惯和良好的代码风格,同时也可以避免由于不同的输入值导致的位运算错误。或上0x10是为了把bit4 SLEEP(第五位)置1,进入睡眠模式。(二进制中,位的位置通常是从右到左编号)
MODE1寄存器描述如下:
PCA9685_write(PCA9685_MODE1, oldmode);
PCA9685_write(PCA9685_MODE1, oldmode | 0xa1);
或上0xa1
1010 0001,将bit0,bit5,bit7,即ALLCALL,AI,RESTART.
AI是允许寄存器地址递增,这在后面的set_pwm中有用到。
RESTART置1是为了复位,设置完了频率要重新上电。
void SetPWM(uint32_t num,uint32_t on,uint32_t off)
void SetPWM(uint32_t num,uint32_t on,uint32_t off)
{
PCA9685_write(LED0_ON_L+4*num,on);
PCA9685_write(LED0_ON_H+4*num,on>>8);
PCA9685_write(LED0_OFF_L+4*num,off);
PCA9685_write(LED0_OFF_H+4*num,off>>8);
}
对16路对应的寄存器进行操作。
*4是因为他们的寄存器地址是每1路pwm就相差4
三 角度设置
void setAngle(uint8_t num,uint8_t angle)
void setAngle(uint8_t num,uint8_t angle)
{
uint32_t off = 0;
off = (uint32_t)(102+angle*2.276);
SetPWM(num,0,off);
}
pca9685的pwm计数单元是4096个,即arr的值是4096,上面已经设置了频率是20ms
以180度角度舵机为例,那么对应的控制关系是这样的:
0.5ms--------------0度;
1.0ms------------45度;
1.5ms------------90度;
2.0ms-----------135度;
2.5ms-----------180度;
那么比如要转45度,那么一个pwm周期要有1.0ms的高电平
那么需要设置的pwm值是4096*(1.0/20)=204.8
转动0度:4096*(0.5/20)=102.4
转动180度:4096*(2.5/20)=512
那么180度舵机,每1度对应的pwm值为(512-102.4)/angle+102.4
void servo_ctr(void)
void servo_ctr(void)
{
int angle_change = 0; // 初始角度为0度
// 逐渐增加角度
for (angle_change = 0; angle_change <= 180; angle_change += 10) {
setAngle(0, angle_change);
vTaskDelay(pdMS_TO_TICKS(500));
}
// 逐渐减小角度
for (angle_change = 180; angle_change >= 0; angle_change -= 10) {
setAngle(0, angle_change);
vTaskDelay(pdMS_TO_TICKS(500));
}
}
这里写了一段代码,实现每0.5秒10度的舵机角度变换。