写这篇文章的目的主要是为了记录一下我在接触PCA9685时遇到的一些问题。
首先我在学习别人的文章时看到了很多开头初始化PCA,其实主要目的都是为了设置他的频率为50HZ,所以为了简洁方便,这里我的代码开头就直接设置频率,然后在while中周期性调整角度。值得注意的一点是,我在调整角度中发现调整角度过程中假如延时过短,那么就会在调整上一个角度的过程中转换成调整下一个角度,所以我选择了延时3s。
HAL库的配置我选择的是软件I2C通信的模式,所以选择了PB6作为时钟线,PB7作为数据线,还开启了一个串口方便调试。
首先开始一个舵控板的头文件,定义一些地址
#define mode1 0x00
#define mode2 0x01
#define LED0_ON_L 0x06
#define LED0_ON_H 0x07
#define LED0_OFF_L 0x08
#define LED0_OFF_H 0x09
#define address_write 0x80
#define address_read 0x81
#define Pre_scale 0xFE
然后在C文件中攥写代码
首先是基于I2C协议的关于读写的代码(关于其中I2C的底层逻辑代码我也不多赘述)
void Servo_Write(uint8_t address_config,uint16_t data)
{
if(I2C_Start() == 0)
{
I2C_Stop();
}
I2C_SendByte(address_write);
if(!I2C_WaitAck())
{
I2C_Stop();
}
I2C_SendByte(address_config);
if(!I2C_WaitAck())
{
I2C_Stop();
}
I2C_SendByte(data);
if(!I2C_WaitAck())
{
I2C_Stop();
}
I2C_Stop();
}
uint16_t Servo_Read(uint8_t address_config)
{
uint8_t data;
if(I2C_Start() == 0)
{
I2C_Stop();
}
I2C_SendByte(address_write);
if(!I2C_WaitAck())
{
I2C_Stop();
}
I2C_SendByte(address_config);
if(!I2C_WaitAck())
{
I2C_Stop();
}
if(I2C_Start() == 0)
{
I2C_Stop();
}
I2C_SendByte(address_read);
if(!I2C_WaitAck())
{
I2C_Stop();
}
data = I2C_ReceiveByte();
I2C_SendACK(1);
I2C_Stop();
return data;
}
接下来是舵控板的设置频率代码和设置OFF值代码(我们一般将ON设为0)
/*Mode1寄存器
D7(RESTART):写1复位,写完后此位自动清除。一定要在SLEEP位写0后至少500us后才能对此位写1进行复位
D6(EXTCLOCK):0-使用内部时钟。1-使用外部时钟引脚的时钟。修改此位前,一定要先SLEEP,
再修改此位(此时SLEEP位仍然写1),再退出SLEEP
D5(AI):0-内部地址读写后不自动增加。1-内部地址读写后自动增加
D4(SLEEP):0-退出SLEEP模式(退出后等待500us)。1-进入SLEEP模式(更改D6和频率必须go to sleep)
D3(SUB1) D2(SUB2) D1(SUB3)
D0(ALLCALL):0-不响应通用i2c地址。1-响应通用i2c地址(0x70)*/
//设置PWM频率
void PWM_SetFreq(uint8_t freq)
{
Servo_Write(mode1,0x00);
float Lprescal ;
uint8_t old_mode,now_mode,Nprescal;
freq *= 0.915; //误差
Lprescal = (float)25000000.0 / 4096 / freq - 1; //计算频率
Nprescal = floor(Lprescal + 0.5f); //向上取整
old_mode = Servo_Read(mode1); //读出mode1的值(0x00)
now_mode = (old_mode&0x7F)|0x10;
/*0x7F:0111 1111
0x10:0001 0000*/
Servo_Write(mode1,now_mode); //让舵控板睡觉
Servo_Write(Pre_scale,Nprescal); //设置PWM频率
Servo_Write(mode1,old_mode); //叫醒舵控板
HAL_Delay(5);
Servo_Write(mode1,old_mode|0xa1);
/*0xa1:1010 0001
将RESTART,AI,ALLCALL置1*/
}
//一般ON直接设0,使用OFF的值与4096的比例
void Set_Pwm(uint8_t num,uint32_t on,uint32_t off)
{
Servo_Write(LED0_ON_L + 4 * num,on); //写入ON低八位
Servo_Write(LED0_ON_H + 4 * num,on>>8); //写入ON高四位
Servo_Write(LED0_OFF_L + 4 * num,off); //写入OFF低八位
Servo_Write(LED0_OFF_H + 4 * num,off>>8); //写入OFF高四位
}
最后就是在main里面初始化频率以及设置角度
PWM_SetFreq(50);
/*90度脉宽=0.5ms+(90/180)*(2.5ms-0.5ms)=1.5ms
1.5ms/20ms=off/4096
off=307*/
Set_Pwm(0,0,239);
HAL_Delay(3000);
Set_Pwm(0,0,307);
HAL_Delay(3000);
其中存在的问题和不足希望读者在评论区加以点评。