大家好,欢迎来到IT知识分享网。
目录
PCA9685接线:
| 序号 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 指代 | GND | OE(模块使能端口) | SCL | SDA | VCC | V+ | 电源输入端口 | PCA9685 | pwm通道编号 | 舵机接口 |
OE使能端口:低电平使能,内部已经拉低,可以不用接线。
SCL和SDA端口:IIC接口。
V+端口:如果使用少量舵机(两三个舵机),可以通过V+接入电源。(Max电压:6V)
电源输入端口:如果使用多个舵机(五六个舵机),或者大扭矩舵机,最好使用电源输入端口单独
供电不要使用V+端口供电。(Max电压:6V)
PCA9685简介:
PCA9685 是一种常用的 PWM(脉冲宽度调制)驱动器芯片,通常用于控制舵机、电机和其他需要精确控制的设备。该芯片可以通过 I2C 总线与微控制器或单片机通信,以实现对多路 PWM 信号的生成和控制。
16 路 PWM 输出:PCA9685 可以同时控制最多 16 路 PWM 输出,每路输出的占空比都可以独立设置,但是16路PWM频率一样。
12 位分辨率:PCA9685 提供了 12 位分辨率的 PWM 输出,可以实现精细的输出控制。
内部振荡器:芯片内部集成了振荡器,可以产生稳定25MHz的时钟信号,无需外部晶振。
可编程频率:可以通过配置寄存器来设置 PWM 输出的频率,范围从 24 Hz 到 1526 Hz。
I2C 接口:使用标准的 I2C 串行总线接口与主控设备通信,方便集成到各种微控制器系统中。
输出驱动能力:每路 PWM 输出都具有较强的驱动能力,可以直接驱动舵机或者其他负载。
PCA9685地址位寄存器:
地址位的寄存器一共8位,通过地址寄存器来设置芯片的I2C地址。
| 位7 | 位6 | 位5 | 位4 | 位3 | 位2 | 位1 | 位0 |
| 1 | A5 | A4 | A3 | A2 | A1 | A0 | R/W |
| 固定值 “ 1 ” | 可定义 | 可定义 | 可定义 | 可定义 | 可定义 | 可定义 | 读/写控制 |
往PCA9685写程序的时候,发送的地址位是0x80,bit[7~0]:1000 0000 位0置“ 0 ” 表示写入;
读程序的时候,发送的地址位是0x81,bit[7~0]:1000 0001 位0置“ 1 ” 表示读取;
MODE1寄存器,地址0x00,可读、可写:
寄存器地址:
照片引用地址:PCA9685–16路 PWM模块舵机驱动板–STM32 IIC接口模块 (baidu.com)
PCA9685代码注解:
1.PCA9685数据写入:
void PCA9685_Write(u8 addr,u8 data) // addr 表示要写入数据的寄存器地址,data 表示要写入的数据 { IIC_Start(); // 发送 I2C 起始信号,开始 I2C 通信。 IIC_Send_Byte(PCA_Addr); // 发送 PCA_Addr = 0x80 ,告诉设备我们要写入数据 IIC_NAck(); // 发送不应答信号,表示主控器不需要从设备接收更多数据。 IIC_Send_Byte(addr); // 发送要写入数据的寄存器地址。 IIC_NAck(); // 发送不应答信号。 IIC_Send_Byte(data); // 发送要写入的数据。 IIC_NAck(); // 发送不应答信号。 IIC_Stop(); // 发送 I2C 停止信号,结束本次通信。 }
2.PCA9685数据读取:
u8 PCA9685_Read(u8 addr) // addr 表示要读取数据的寄存器地址 { u8 data; // 声明一个无符号 8 位整数变量 data,用于存储读取到的数据。 IIC_Start(); // 发送 I2C 起始信号,开始 I2C 通信。 IIC_Send_Byte(PCA_Addr); // 发送 PCA_Addr = 0x80 ,告诉设备我们要写入数据 IIC_NAck(); // 发送不应答信号,表示主控器不需要从设备接收更多数据。 IIC_Send_Byte(addr); // 发送要读取数据的寄存器地址。 IIC_NAck(); // 发送不应答信号。 IIC_Stop(); // 发送 I2C 停止信号,结束本次通信。 delay_us(10); // 延时 10 微秒,等待芯片准备好数据。 IIC_Start(); // 发送 I2C 起始信号,开始另一次 I2C 通信。 IIC_Send_Byte(PCA_Addr|0x01); // 发送 PCA9685 的地址,并设置最低位为 1, // PCA_Addr|0x01 = 0x81 表示要进行读取操作。 IIC_NAck(); // 发送不应答信号。 data = IIC_Read_Byte(0); // 通过 I2C 从 PCA9685 读取一个字节的数据,并存储到变量 data 中。 IIC_Stop(); // 发送 I2C 停止信号,结束本次通信。 return data; // 返回读取到的数据。 }
3.PCA9685的频率设置:
void PCA9685_setFreq(float freq) { u8 prescale,oldmode,newmode; //定义了三个无符号 8 位整型变量 用于存储预分频器值、旧的模式寄存器值和新的模式寄存器值 double prescaleval; //定义了一个双精度浮点型变量 prescaleval,用于计算预分频器的值。 freq *= 0.98; //将传入的频率值乘以 0.98,这是为了微调频率值以适应 PCA9685 的实际需求 prescaleval = ; //这是 PCA9685 内部振荡器的频率 prescaleval /= 4096; //每个周期从0计数到4095,除以 4096,得到每个计数器周期的时间, prescaleval /= freq; //除以所需的频率值,得到预分频器的值。 prescaleval -= 1; //减去 1,得到最终的预分频器值 prescale = floor(prescaleval+0.5f); //将计算得到的预分频器值四舍五入取整,并将其赋值给 prescale 变量。 oldmode = PCA9685_Read(PCA_Model); //通过调用 PCA9685_Read 函数读取当前 PCA9685 寄存器中的模式值,并将其存储在 oldmode 变量中。 newmode = (oldmode&0x7F)|0x10; //根据旧的模式值计算出新的模式值,将最高位清零(bit 7)并将第 5 位设为1(bit 4),表示将 PCA9685 设置为睡眠模式。 PCA9685_Write(PCA_Model,newmode); //将新的模式值写入 PCA9685 的模式寄存器。 PCA9685_Write(PCA_Pre,prescale); //将计算得到的预分频器值写入 PCA9685 的预分频器寄存器。 PCA9685_Write(PCA_Model,oldmode); //恢复旧的模式值。 delay_ms(5); // 延时 5 毫秒,等待 PCA9685 完全启动。 PCA9685_Write(PCA_Model,oldmode|0xa1); //将模式值的最高位和第 1 位设为1,表示将 PCA9685 设置为正常工作模式。 }
理论计算(预分配值):prescaleval = ( / 4096 / 50 )- 1 = 121 .0
实际计算(预分配值):prescaleval = ( / 4096 / 50 *0.98)- 1 = 118.
double prescaleval; //定义了一个双精度浮点型变量 prescaleval,用于计算预分频器的值。 freq *= 0.98; //将传入的频率值乘以 0.98,这是为了微调频率值以适应 PCA9685 的实际需求 prescaleval = ; //这是 PCA9685 内部振荡器的频率 prescaleval /= 4096; //每个周期从0计数到4095,除以 4096,得到每个计数器周期的时间, prescaleval /= freq; //除以所需的频率值,得到预分频器的值。 prescaleval -= 1; //减去 1,得到最终的预分频器值
内部振荡器的精度限制,设置预分频器值时无法完全达到所需的输出频率,将传入的频率值freq 乘以0.98,是为了微调频率值,以弥补 PCA9685 内部振荡器的偏差,使最终输出的频率更接近预期值。(0.98是经验值,实际使用中可自行调整)
freq *= 0.98; //将传入的频率值乘以 0.98,这是为了微调频率值以适应 PCA9685 的实际需求
对预分频值prescaleval,进行四舍五入。
例如prescaleval=118.3,则 prescale = floor(prescaleval+0.5f); = floor(118.3+0.5)=118
prescaleval=118.8,则 prescale = floor(prescaleval+0.5f); = floor(118.8+0.5)=119
prescaleval+0.5f 是为了模拟四舍五入的效果
floor () 函数对浮点数进行向下取整操作,返回不大于该浮点数的最大整数部分。
prescale = floor(prescaleval+0.5f); //将计算得到的预分频器值四舍五入取整,并将其赋值给 prescale 变量。
oldmode = PCA9685_Read(PCA_Model); //通过调用 PCA9685_Read 函数读取当前 PCA9685 寄存器中的模式值,并将其存储在 oldmode 变量中。 newmode = (oldmode&0x7F)|0x10; //根据旧的模式值计算出新的模式值,将最高位清零(bit 7)并将第 5 位设为1(bit 4),表示将 PCA9685 设置为睡眠模式。 PCA9685_Write(PCA_Model,newmode); //将新的模式值写入 PCA9685 的模式寄存器。 PCA9685_Write(PCA_Pre,prescale); //将计算得到的预分频器值写入 PCA9685 的预分频器寄存器。PCA_Pre = 0xFE PCA9685_Write(PCA_Model,oldmode); //恢复旧的模式值。 delay_ms(5); // 延时 5 毫秒,等待 PCA9685 完全启动。 PCA9685_Write(PCA_Model,oldmode|0xa1); //将模式值的最高位和第 1 位设为1,表示将 PCA9685 设置为正常工作模式。
4.PCA9685的PWM设置:
void PCA9685_setPWM(u8 num,u32 on,u32 off) //num 表示 PWM 通道号,on 表示 PWM 的起始位置,off 表示 PWM 的结束位置(即从高电平切换到低电平的时刻) { IIC_Start(); //发送 I2C 起始信号,开始 I2C 通信。 IIC_Send_Byte(PCA_Addr); //发送 PCA9685 的地址,告诉设备我们要和 PCA9685 进行通信。 IIC_Wait_Ack(); //等待应答信号,确保设备准备好接收数据。 IIC_Send_Byte(LED0_ON_L+4*num); //发送 LED 寄存器的地址,根据 PWM 通道号计算出相应的寄存器地址。 IIC_Wait_Ack(); // IIC_Send_Byte(on&0xFF); //发送 PWM 的起始位置低 8 位。 IIC_Wait_Ack(); //等待应答信号。 IIC_Send_Byte(on>>8); //发送 PWM 的起始位置高 8 位。 IIC_Wait_Ack(); //等待应答信号。 IIC_Send_Byte(off&0xFF); //发送 PWM 的结束位置低 8 位。 IIC_Wait_Ack(); //等待应答信号。 IIC_Send_Byte(off>>8); //发送 PWM 的结束位置高 8 位。 IIC_Wait_Ack(); //等待应答信号。 IIC_Stop(); //发送 I2C 停止信号,结束本次通信。 }
void PCA9685_setPWM(u8 num,u32 on,u32 off)函数传入:通道编号“ num”、“ on ”的值,“ off ”的值
当PCA9685的12位计数ACK,与“ on ”值进行比较,等于“ on ”值输出高电平
与“ off ”值进行比较,等于“ off ”值输出低电平
每路 PWM 有 4 个 8 位控制寄存器, LEDX_ON_L、LEDX_ON_H、LEDX_OFF_L、LEDX_OFF_H 四个寄存器。
通道0的LED0_ON_L:0x06,LED0_ON_H:0x07,LED0_OFF_L:0x08,LED0_OFF:0x09
所以输出通道的起始地址为:0x06+4*X (X为通道号)
舵机转动角度置换:
可以先了解一下:舵机驱动原理
PCA9685,每个周期都是从0计数到4095;设置 “ on ” = 0 , “ off ” = ?
| 脉冲宽度 | on | off |
| 0.5ms | 0 | 0.5/20*4096=102.4 |
| 1.5ms | 0 | 1.5/20*4096=307.2 |
| 2.5ms | 0 | 2.5/20*4096=512 |
取0.5ms时,off=102 ; 2.5ms时,off=512
| 舵机类型 | 每转动“ 1 ”度,计数个数 |
| 90 | 410/90 = 4.56 |
| 180 | 410/180 = 2.28 |
| 270 | 410/270 = 1.52 |
| 360 | 410/360 = 1.14 |
个人觉得转化为角度,驱动舵机转动偏差较大,所以直接输入“ off ”值控制舵机转动就好;
因为PCA9685精准度有限,所以理论计算出的的数值与实际存在一定偏差。
所以void setAngle(u8 num,u16 angle)函数和void PCA9685_Init(float hz,u16 angle)函数
根据个人意愿修改参数。
void setAngle(u8 num,u16 angle) { u32 off = 0; off = (u32)(103+angle*1.13); //360度舵机,每转动一度=1.14 0.5ms -180度起始位置:103 PCA9685_setPWM(num,0,angle); } void PCA9685_Init(float hz,u16 angle) { u32 off = 0; IIC_Init(); PCA9685_Write(PCA_Model,0x00); PCA9685_setFreq(hz); off = (u32)(103+angle*1.14); //360度舵机,每转动一度=1.14 0.5ms -180度起始位置:103 PCA9685_setPWM(0,0,off); PCA9685_setPWM(1,0,off); PCA9685_setPWM(2,0,off); PCA9685_setPWM(3,0,off); PCA9685_setPWM(4,0,off); PCA9685_setPWM(5,0,off); PCA9685_setPWM(6,0,off); PCA9685_setPWM(7,0,off); PCA9685_setPWM(8,0,off); PCA9685_setPWM(9,0,off); PCA9685_setPWM(10,0,off); PCA9685_setPWM(11,0,off); PCA9685_setPWM(12,0,off); PCA9685_setPWM(13,0,off); PCA9685_setPWM(14,0,off); PCA9685_setPWM(15,0,off); delay_ms(100); }
PCA9685驱动代码(stm32f103C8T6):
main.c
#include "sys.h" // Device header #include "delay.h" #include "myiic.h" #include "PCA9685.h" #include "usart.h" int main(void) { delay_init(); //初始化延时函数 PCA9685_Init(50,180); uart_init();//初始化串口波特率为 while(1) { printf("yes_ok\r\n"); PCA9685_setPWM(0,0,50); delay_ms(1000); PCA9685_setPWM(1,0,150); delay_ms(1000); PCA9685_setPWM(2,0,250); delay_ms(1000); PCA9685_setPWM(3,0,350); delay_ms(1000); PCA9685_setPWM(4,0,450); delay_ms(1000); } }
IIC文件
#ifndef __MYIIC_H #define __MYIIC_H #include "sys.h" //IO方向设置 #define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;} #define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;} //IO操作函数 #define IIC_SCL PBout(6) //SCL #define IIC_SDA PBout(7) //SDA #define READ_SDA PBin(7) //输入SDA //IIC所有操作函数 void IIC_Init(void); //初始化IIC的IO口 void IIC_Start(void); //发送IIC开始信号 void IIC_Stop(void); //发送IIC停止信号 void IIC_Send_Byte(u8 txd); //IIC发送一个字节 u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节 u8 IIC_Wait_Ack(void); //IIC等待ACK信号 void IIC_Ack(void); //IIC发送ACK信号 void IIC_NAck(void); //IIC不发送ACK信号 void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data); u8 IIC_Read_One_Byte(u8 daddr,u8 addr); #endif #include "myiic.h" #include "delay.h" //初始化IIC void IIC_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE ); //使能GPIOB时钟 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7); //PB6,PB7 输出高 } //产生IIC起始信号 void IIC_Start(void) { SDA_OUT(); //sda线输出 IIC_SDA=1; IIC_SCL=1; delay_us(4); IIC_SDA=0;//START:when CLK is high,DATA change form high to low delay_us(4); IIC_SCL=0;//钳住I2C总线,准备发送或接收数据 } //产生IIC停止信号 void IIC_Stop(void) { SDA_OUT();//sda线输出 IIC_SCL=0; IIC_SDA=0;//STOP:when CLK is high DATA change form low to high delay_us(4); IIC_SCL=1; IIC_SDA=1;//发送I2C总线结束信号 delay_us(4); } //等待应答信号到来 //返回值:1,接收应答失败 // 0,接收应答成功 u8 IIC_Wait_Ack(void) { u8 ucErrTime=0; SDA_IN(); //SDA设置为输入 IIC_SDA=1;delay_us(1); IIC_SCL=1;delay_us(1); while(READ_SDA) { ucErrTime++; if(ucErrTime>250) { IIC_Stop(); return 1; } } IIC_SCL=0;//时钟输出0 return 0; } //产生ACK应答 void IIC_Ack(void) { IIC_SCL=0; SDA_OUT(); IIC_SDA=0; delay_us(2); IIC_SCL=1; delay_us(2); IIC_SCL=0; } //不产生ACK应答 void IIC_NAck(void) { IIC_SCL=0; SDA_OUT(); IIC_SDA=1; delay_us(2); IIC_SCL=1; delay_us(2); IIC_SCL=0; } //IIC发送一个字节 //返回从机有无应答 //1,有应答 //0,无应答 void IIC_Send_Byte(u8 txd) { u8 t; SDA_OUT(); IIC_SCL=0;//拉低时钟开始数据传输 for(t=0;t<8;t++) { IIC_SDA=(txd&0x80)>>7; txd<<=1; delay_us(2); //对TEA5767这三个延时都是必须的 IIC_SCL=1; delay_us(2); IIC_SCL=0; delay_us(2); } } //读1个字节,ack=1时,发送ACK,ack=0,发送nACK u8 IIC_Read_Byte(unsigned char ack) { unsigned char i,receive=0; SDA_IN();//SDA设置为输入 for(i=0;i<8;i++ ) { IIC_SCL=0; delay_us(2); IIC_SCL=1; receive<<=1; if(READ_SDA)receive++; delay_us(1); } if (!ack) IIC_NAck();//发送nACK else IIC_Ack(); //发送ACK return receive; }
PCA9685驱动文件:
#ifndef __PCA9685_H #define __PCA9685_H #include "sys.h" #define PCA_Addr 0x80 #define PCA_Model 0x00 #define LED0_ON_L 0x06 #define LED0_ON_H 0x07 #define LED0_OFF_L 0x08 #define LED0_OFF_H 0x09 #define PCA_Pre 0xFE void PCA9685_Init(float hz,u16 angle); void PCA9685_Write(u8 addr,u8 data); u8 PCA9685_Read(u8 addr); void PCA9685_setPWM(u8 num,u32 on,u32 off); void PCA9685_setFreq(float freq); void setAngle(u8 num,u16 angle); #endif #include "PCA9685.h" #include "myiic.h" #include "delay.h" #include <math.h> #include "usart.h" void PCA9685_Init(float hz,u16 angle) { u32 off = 0; IIC_Init(); PCA9685_Write(PCA_Model,0x00); PCA9685_setFreq(hz); off = (u32)(103+angle*1.14); //360度舵机,每转动一度=1.14 0度起始位置:103 PCA9685_setPWM(0,0,off); PCA9685_setPWM(1,0,off); PCA9685_setPWM(2,0,off); PCA9685_setPWM(3,0,off); PCA9685_setPWM(4,0,off); PCA9685_setPWM(5,0,off); PCA9685_setPWM(6,0,off); PCA9685_setPWM(7,0,off); PCA9685_setPWM(8,0,off); PCA9685_setPWM(9,0,off); PCA9685_setPWM(10,0,off); PCA9685_setPWM(11,0,off); PCA9685_setPWM(12,0,off); PCA9685_setPWM(13,0,off); PCA9685_setPWM(14,0,off); PCA9685_setPWM(15,0,off); delay_ms(100); } void PCA9685_Write(u8 addr,u8 data) // addr 表示要写入数据的寄存器地址,data 表示要写入的数据 { IIC_Start(); // 发送 I2C 起始信号,开始 I2C 通信。 IIC_Send_Byte(PCA_Addr); // 发送 PCA9685 的地址,告诉设备我们要和 PCA9685 进行通信。 IIC_NAck(); // 发送不应答信号,表示主控器不需要从设备接收更多数据。 IIC_Send_Byte(addr); // 发送要写入数据的寄存器地址。 IIC_NAck(); // 发送不应答信号。 IIC_Send_Byte(data); // 发送要写入的数据。 IIC_NAck(); // 发送不应答信号。 IIC_Stop(); // 发送 I2C 停止信号,结束本次通信。 } u8 PCA9685_Read(u8 addr) // addr 表示要读取数据的寄存器地址 { u8 data; // 声明一个无符号 8 位整数变量 data,用于存储读取到的数据。 IIC_Start(); // 发送 I2C 起始信号,开始 I2C 通信。 IIC_Send_Byte(PCA_Addr); // 发送 PCA9685 的地址,告诉设备我们要和 PCA9685 进行通信。 IIC_NAck(); // 发送不应答信号,表示主控器不需要从设备接收更多数据。 IIC_Send_Byte(addr); // 发送要读取数据的寄存器地址。 IIC_NAck(); // 发送不应答信号。 IIC_Stop(); // 发送 I2C 停止信号,结束本次通信。 delay_us(10); // 延时 10 微秒,等待芯片准备好数据。 IIC_Start(); // 发送 I2C 起始信号,开始另一次 I2C 通信。 IIC_Send_Byte(PCA_Addr|0x01); // 发送 PCA9685 的地址,并设置最低位为 1,表示要进行读取操作。 IIC_NAck(); // 发送不应答信号。 data = IIC_Read_Byte(0); // 通过 I2C 从 PCA9685 读取一个字节的数据,并存储到变量 data 中。 IIC_Stop(); // 发送 I2C 停止信号,结束本次通信。 return data; // 返回读取到的数据。 } void PCA9685_setPWM(u8 num,u32 on,u32 off) //num 表示 PWM 通道号,on 表示 PWM 的起始位置,off 表示 PWM 的结束位置(即从高电平切换到低电平的时刻) { IIC_Start(); //发送 I2C 起始信号,开始 I2C 通信。 IIC_Send_Byte(PCA_Addr); //发送 PCA9685 的地址,告诉设备我们要和 PCA9685 进行通信。 IIC_Wait_Ack(); //等待应答信号,确保设备准备好接收数据。 IIC_Send_Byte(LED0_ON_L+4*num); //发送 LED 寄存器的地址,根据 PWM 通道号计算出相应的寄存器地址。 IIC_Wait_Ack(); // IIC_Send_Byte(on&0xFF); //发送 PWM 的起始位置低 8 位。 IIC_Wait_Ack(); //等待应答信号。 IIC_Send_Byte(on>>8); //发送 PWM 的起始位置高 8 位。 IIC_Wait_Ack(); //等待应答信号。 IIC_Send_Byte(off&0xFF); //发送 PWM 的结束位置低 8 位。 IIC_Wait_Ack(); //等待应答信号。 IIC_Send_Byte(off>>8); //发送 PWM 的结束位置高 8 位。 IIC_Wait_Ack(); //等待应答信号。 IIC_Stop(); //发送 I2C 停止信号,结束本次通信。 } void PCA9685_setFreq(float freq) { u8 prescale,oldmode,newmode; //定义了三个无符号 8 位整型变量 用于存储预分频器值、旧的模式寄存器值和新的模式寄存器值 double prescaleval; //定义了一个双精度浮点型变量 prescaleval,用于计算预分频器的值。 freq*= 0.98; //将传入的频率值乘以 0.98,这是为了微调频率值以适应 PCA9685 的实际需求 prescaleval = ; //这是 PCA9685 内部振荡器的频率 prescaleval /= 4096; //每个周期从0计数到4095,除以 4096,得到每个计数器周期的时间, prescaleval /= freq; //除以所需的频率值,得到预分频器的值。 prescaleval -= 1; //减去 1,得到最终的预分频器值 prescale = floor(prescaleval+0.5f); //将计算得到的预分频器值四舍五入取整,并将其赋值给 prescale 变量。 oldmode = PCA9685_Read(PCA_Model); //通过调用 PCA9685_Read 函数读取当前 PCA9685 寄存器中的模式值,并将其存储在 oldmode 变量中。 newmode = (oldmode&0x7F)|0x10; //根据旧的模式值计算出新的模式值,将最高位清零(bit 7)并将第 5 位设为1(bit 4),表示将 PCA9685 设置为睡眠模式。 PCA9685_Write(PCA_Model,newmode); //将新的模式值写入 PCA9685 的模式寄存器。 PCA9685_Write(PCA_Pre,prescale); //将计算得到的预分频器值写入 PCA9685 的预分频器寄存器。 PCA9685_Write(PCA_Model,oldmode); //恢复旧的模式值。 delay_ms(5); // 延时 5 毫秒,等待 PCA9685 完全启动。 PCA9685_Write(PCA_Model,oldmode|0xa1); //将模式值的最高位和第 1 位设为1,表示将 PCA9685 设置为正常工作模式。 } void setAngle(u8 num,u16 angle) { u32 off = 0; off = (u32)(102+angle*1.13); //360度舵机,每转动一度=1.14 0度起始位置:103 PCA9685_setPWM(num,0,angle); }
PCA9685驱动代码(stm32f407zgt6):
main.c程序:
#include "sys.h" #include "delay.h" #include "myiic.h" #include "PCA9685.h" int main(void) { delay_init(168); //初始化延时函数 PCA9685_Init(50,360); while(1) { setAngle(4,210); // 输入pwm通道号、舵机转动角度 delay_ms(500); //添加延时,确定舵机运动到指定位置 setAngle(4,120); // 输入pwm通道号、舵机转动角度 delay_ms(500); //添加延时,确定舵机运动到指定位置 } /*适用于stm32f407zgt6*/
IIC驱动:(正点原子IIC实验代码)
IIC.c文件
#include "myiic.h" #include "delay.h" //初始化IIC void IIC_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB时钟 //GPIOB8,B9初始化设置 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉 GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化 IIC_SCL=1; IIC_SDA=1; } //产生IIC起始信号 void IIC_Start(void) { SDA_OUT(); //sda线输出 IIC_SDA=1; IIC_SCL=1; delay_us(4); IIC_SDA=0;//START:when CLK is high,DATA change form high to low delay_us(4); IIC_SCL=0;//钳住I2C总线,准备发送或接收数据 } //产生IIC停止信号 void IIC_Stop(void) { SDA_OUT();//sda线输出 IIC_SCL=0; IIC_SDA=0;//STOP:when CLK is high DATA change form low to high delay_us(4); IIC_SCL=1; IIC_SDA=1;//发送I2C总线结束信号 delay_us(4); } //等待应答信号到来 //返回值:1,接收应答失败 // 0,接收应答成功 u8 IIC_Wait_Ack(void) { u8 ucErrTime=0; SDA_IN(); //SDA设置为输入 IIC_SDA=1;delay_us(1); IIC_SCL=1;delay_us(1); while(READ_SDA) { ucErrTime++; if(ucErrTime>250) { IIC_Stop(); return 1; } } IIC_SCL=0;//时钟输出0 return 0; } //产生ACK应答 void IIC_Ack(void) { IIC_SCL=0; SDA_OUT(); IIC_SDA=0; delay_us(2); IIC_SCL=1; delay_us(2); IIC_SCL=0; } //不产生ACK应答 void IIC_NAck(void) { IIC_SCL=0; SDA_OUT(); IIC_SDA=1; delay_us(2); IIC_SCL=1; delay_us(2); IIC_SCL=0; } //IIC发送一个字节 //返回从机有无应答 //1,有应答 //0,无应答 void IIC_Send_Byte(u8 txd) { u8 t; SDA_OUT(); IIC_SCL=0;//拉低时钟开始数据传输 for(t=0;t<8;t++) { IIC_SDA=(txd&0x80)>>7; txd<<=1; delay_us(2); //对TEA5767这三个延时都是必须的 IIC_SCL=1; delay_us(2); IIC_SCL=0; delay_us(2); } } //读1个字节,ack=1时,发送ACK,ack=0,发送nACK u8 IIC_Read_Byte(unsigned char ack) { unsigned char i,receive=0; SDA_IN();//SDA设置为输入 for(i=0;i<8;i++ ) { IIC_SCL=0; delay_us(2); IIC_SCL=1; receive<<=1; if(READ_SDA)receive++; delay_us(1); } if (!ack) IIC_NAck();//发送nACK else IIC_Ack(); //发送ACK return receive; }
IIC.h文件
#ifndef __MYIIC_H #define __MYIIC_H #include "sys.h" //IO方向设置 #define SDA_IN() {GPIOB->MODER&=~(3<<(9*2));GPIOB->MODER|=0<<9*2;} //PB9输入模式 #define SDA_OUT() {GPIOB->MODER&=~(3<<(9*2));GPIOB->MODER|=1<<9*2;} //PB9输出模式 //IO操作函数 #define IIC_SCL PBout(8) //SCL #define IIC_SDA PBout(9) //SDA #define READ_SDA PBin(9) //输入SDA //IIC所有操作函数 void IIC_Init(void); //初始化IIC的IO口 void IIC_Start(void); //发送IIC开始信号 void IIC_Stop(void); //发送IIC停止信号 void IIC_Send_Byte(u8 txd); //IIC发送一个字节 u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节 u8 IIC_Wait_Ack(void); //IIC等待ACK信号 void IIC_Ack(void); //IIC发送ACK信号 void IIC_NAck(void); //IIC不发送ACK信号 void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data); u8 IIC_Read_One_Byte(u8 daddr,u8 addr); #endif
PCA9685.c配置:
(代码引用于GItcode开源社区)
PCA9685模块使用(Arduino和STM32)_stm32_阿中廋不了-GitCode 开源社区 (csdn.net)
#include "PCA9685.h" #include "myiic.h" #include "delay.h" #include <math.h> #include "led.h" #include "usart.h" void PCA9685_Init(float hz,u16 angle) { u32 off = 0; IIC_Init(); PCA9685_Write(PCA_Model,0x00); PCA9685_setFreq(hz); off = (u32)(103+angle*1.14); //360度舵机,每转动一度=1.14 0度起始位置:103 PCA9685_setPWM(0,0,off); PCA9685_setPWM(1,0,off); PCA9685_setPWM(2,0,off); PCA9685_setPWM(3,0,off); PCA9685_setPWM(4,0,off); PCA9685_setPWM(5,0,off); PCA9685_setPWM(6,0,off); PCA9685_setPWM(7,0,off); PCA9685_setPWM(8,0,off); PCA9685_setPWM(9,0,off); PCA9685_setPWM(10,0,off); PCA9685_setPWM(11,0,off); PCA9685_setPWM(12,0,off); PCA9685_setPWM(13,0,off); PCA9685_setPWM(14,0,off); PCA9685_setPWM(15,0,off); delay_ms(100); } void PCA9685_Write(u8 addr,u8 data) { IIC_Start(); IIC_Send_Byte(PCA_Addr); IIC_NAck(); IIC_Send_Byte(addr); IIC_NAck(); IIC_Send_Byte(data); IIC_NAck(); IIC_Stop(); } u8 PCA9685_Read(u8 addr) { u8 data; IIC_Start(); IIC_Send_Byte(PCA_Addr); IIC_NAck(); IIC_Send_Byte(addr); IIC_NAck(); IIC_Stop(); delay_us(10); IIC_Start(); IIC_Send_Byte(PCA_Addr|0x01); IIC_NAck(); data = IIC_Read_Byte(0); IIC_Stop(); return data; } void PCA9685_setPWM(u8 num,u32 on,u32 off) { IIC_Start(); IIC_Send_Byte(PCA_Addr); IIC_Wait_Ack(); IIC_Send_Byte(LED0_ON_L+4*num); IIC_Wait_Ack(); IIC_Send_Byte(on&0xFF); IIC_Wait_Ack(); IIC_Send_Byte(on>>8); IIC_Wait_Ack(); IIC_Send_Byte(off&0xFF); IIC_Wait_Ack(); IIC_Send_Byte(off>>8); IIC_Wait_Ack(); IIC_Stop(); } void PCA9685_setFreq(float freq) { u8 prescale,oldmode,newmode; double prescaleval; freq *= 0.98; prescaleval = ; prescaleval /= 4096; prescaleval /= freq; prescaleval -= 1; prescale = floor(prescaleval+0.5f); oldmode = PCA9685_Read(PCA_Model); newmode = (oldmode&0x7F)|0x10; PCA9685_Write(PCA_Model,newmode); PCA9685_Write(PCA_Pre,prescale); PCA9685_Write(PCA_Model,oldmode); delay_ms(5); PCA9685_Write(PCA_Model,oldmode|0xa1); } void setAngle(u8 num,u16 angle) { u32 off = 0; off = (u32)(103+angle*1.13); //360度舵机,每转动一度=1.14 0度起始位置:103 PCA9685_setPWM(num,0,angle); }
PCA9685.h配置:
#ifndef __PCA9685_H #define __PCA9685_H #include "sys.h" #define PCA_Addr 0x80 #define PCA_Model 0x00 #define LED0_ON_L 0x06 #define LED0_ON_H 0x07 #define LED0_OFF_L 0x08 #define LED0_OFF_H 0x09 #define PCA_Pre 0xFE void PCA9685_Init(float hz,u16 angle); void PCA9685_Write(u8 addr,u8 data); u8 PCA9685_Read(u8 addr); void PCA9685_setPWM(u8 num,u32 on,u32 off); void PCA9685_setFreq(float freq); void setAngle(u8 num,u16 angle); #endif
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/120448.html







