當你在一個項目中碰到了微控制器芯片的PWM輸出引腳不夠用的情況,那么這款PCA968516路舵機就能很快幫助您解決這個問題了。只要你的主控芯片具備了I2C通信,就能夠讓主控芯片和PCA9685通信,實現(xiàn)多個舵機的同時控制了。
PCA9685 16路舵機是一個采用I2C通信,內置了PWM驅動器和一個時鐘,這個意味著,這將和TLCG940系列有很大不同,你不需要不斷發(fā)送信號占用你的單片機。它是5V的兼容,這意味你還可以用3.3V單片機控制并且安全地驅動到6V輸出(當你想要控制白色或藍色指示燈用3.4+正電壓也是可以的)。地址選擇引腳使你可以把62個驅動板掛在單個l2C總線上,總共有992路PWM輸出,那將是非常龐大的資源,約1.6Khz可調頻PWM輸出,為步進電機準備輸出12位分辨率,可配置的推拉輸出或開路輸出,輸出使能引腳能夠快速禁用所有輸出。
一模塊來源
模塊實物展示:
資料鏈接:https://pan.baidu.com/s/1FjoAuJm387bxaZxS6g9HEg
資料提取碼:8888
二規(guī)格參數(shù)
輸入電壓:3.3V~5V
額定電流:15mA
控制方式:IIC
尺寸:21(長)*21(寬)[單位:mm]
以上信息見廠家資料文件
三移植過程
我們的目標是將例程移植至CW32F030C8T6開發(fā)板上【實現(xiàn)無線的數(shù)據(jù)傳輸的功能】。首先要獲取資料,查看數(shù)據(jù)手冊應如何實現(xiàn)讀取數(shù)據(jù),再移植至我們的工程。
3.1查看資料
IIC器件地址
PCA9685 是一個I2C 從設備,有個設備ID,或者叫從 地址。從地址是如下確定的:
Board 0: Address = 0×40 Offset = binary 00000 (默認)
Board 1: Address = 0×41 Offset = binary 00001 (A0接上拉)
Board 2: Address = 0×42 Offset = binary 00010 (接上A1上拉)
Board 3: Address = 0×43 Offset = binary 00011 (A0和A1上拉)
Board 4: Address = 0×44 Offset = binary 00100 (A2上拉)
以此類推;
PCA9685的I2C總線從地址如下圖所示。為了節(jié)約電力,硬件可選地址引腳上沒有內部上拉電阻,它們必須被拉高或拉低。但是我們使用的是模塊,而模塊上已經(jīng)為我們接好了上拉電阻。
地址字節(jié)的最后一位定義要執(zhí)行的操作。當設置為邏輯1時,將選擇讀操作,而邏輯0則選擇寫操作。 在原理圖中,地址線全部接0,所以slave address是0x40。對應Fig 4上的位置,則為:
則IIC地址是 0x80 ,寫入時是0x80,讀取時是0x81。
設置PWM頻率
舵機控制所需的 PWM 周期為20 ms. 在用 PCA9685 作為多舵機控制器時,需要將 其 PWM 輸出周期設定為20 ms,即PWM 波的頻率設定為50 Hz,PCA9685 輸出頻率與振蕩器有關,頻率的設置值refresh_rate見下面的公式;
其中,EXTCLK是PCA9685的內部時鐘頻率為25Mhz;prescale是要設置的頻率,我們設置為50Hz;
refresh_rate = 25,000,000 /( 4096 * ( 50 + 1 ))
refresh_rate = 25,000,000 / 4096 / (50 + 1)
refresh_rate = 6,103.52 / (50 + 1)
refresh_rate = 6,103.52 / 51
refresh_rate = 119.68
所以我們需要設置的值是119.68,取整數(shù)就是120。
需要注意的是,頻率的更改只能在 PCA9685 芯片處于休眠狀態(tài)下進行。
以下加粗字體是數(shù)據(jù)手冊內容:
要使用EXTCLK引腳,該位必須按以下順序設置:
在mode1中設置SLEEP位。這就關閉了內部振蕩器,使芯片處于休眠狀態(tài)。
將邏輯1寫入MODE1中的SLEEP和EXTCLK位。這樣就轉換完成了。外部時鐘可以在切換期間處于活動狀態(tài),因為設置了SLEEP位。
這個位是一個“粘性位”,也就是說,它不能通過寫入邏輯0來清除。EXTCLK位只能通過電源循環(huán)或軟件重置來清除。 占空比或者脈沖寬度的設定
每個PWM引腳輸出的開啟時間和PWM的占空比可以通過LEDn_ON和LEDn_OFF寄存器獨立控制。
每個PWM引腳輸出將有兩個12位寄存器。這些寄存器將由用戶編程。兩個寄存器都將保存從0到4095的值。一個12位寄存器將保存ON時間的值,另一個12位寄存器將保存OFF時間的值。將ON和OFF時間與12位計數(shù)器的值進行比較,該計數(shù)器將從0000h持續(xù)運行到0FFFh(0到4095十進制)。
ON時間是可編程的,它是PWM輸出ON的時間,OFF時間也是可編程的,它是PWM輸出OFF的時間。這樣相移就完全可編程了。相移的分辨率為目標頻率的1 / 4096。表7列出了這些寄存器。
以下用一個例子說明如何計算要加載到這些寄存器中的值。
(假設使用LED0輸出,(延時時間)+ (PWM占空比)<=100%)
延遲時間 = 10%;PWM占空比= 20% (LEDON電平= 20%;LEDOFF時間= 80%)。延遲時間= 10% = 4096 * 0.1 = 409.6 ~ 410,計數(shù)= 410(十進制) = 19Ah(十六進制)
因為計數(shù)器從0開始,到4095結束,我們將減去1,所以延遲時間 = 199h 個數(shù)。
LED0_ON_H = 1h;LED0_ON_L = 99h (LED開始打開后,這個延遲計數(shù)到409)
LED開機時間= 20% = 819.2 ~ 819次
LED關閉時間= 4CCh(十進制410 + 819-1 = 1228)
LED0_OFF_H = 4h;LED0_OFF_L = CCh(此計數(shù)到1228后LED開始關閉)
整個周期為4095, LED_ON 和 LED_OFF 2個的設定值確定脈寬,在后面的代碼里,LED_ON 設為0, LED_OFF就是脈寬了。 這里都用2位字節(jié)來表示。
相關地址表
// 相關地址表
// 這里只截圖了需要的地址,分別是:
#define PCA_Addr 0x80 //IIC地址
#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 //配置頻率地址
3.2引腳選擇
模塊接線圖
3.3移植至工程
移植步驟中的導入.c和.h文件與【CW32模塊使用】DHT11溫濕度傳感器相同,只是將.c和.h文件更改為bsp_pca9685.c與bsp_pca9685.h。這里不再過多講述,移植完成后面修改相關代碼。
在文件bsp_pca9685.c中,編寫如下代碼。
/*
* Change Logs:
* Date Author Notes
* 2024-06-25 LCKFB-LP first version
*/
#include "bsp_pca9685.h"
#include "stdio.h"
#include <math.h>
/******************************************************************
* 函 數(shù) 名 稱:PCA9685_GPIO_Init
* 函 數(shù) 說 明:PCA9685的引腳初始化
* 函 數(shù) 形 參:無
* 函 數(shù) 返 回:無
* 作 者:LC
* 備 注:無
******************************************************************/
void PCA9685_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct; // GPIO初始化結構體
RCC_PCA9685_GPIO_ENABLE(); // 使能GPIO時鐘
GPIO_InitStruct.Pins = GPIO_SDA|GPIO_SCL; // GPIO引腳
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; // 開漏輸出
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH; // 輸出速度高
GPIO_Init(PORT_PCA9685, &GPIO_InitStruct); // 初始化
}
/******************************************************************
* 函 數(shù) 名 稱:IIC_Start
* 函 數(shù) 說 明:IIC起始時序
* 函 數(shù) 形 參:無
* 函 數(shù) 返 回:無
* 作 者:LC
* 備 注:無
******************************************************************/
void IIC_Start(void)
{
SDA_OUT();
SDA(1);
delay_us(5);
SCL(1);
delay_us(5);
SDA(0);
delay_us(5);
SCL(0);
delay_us(5);
}
/******************************************************************
* 函 數(shù) 名 稱:IIC_Stop
* 函 數(shù) 說 明:IIC停止信號
* 函 數(shù) 形 參:無
* 函 數(shù) 返 回:無
* 作 者:LC
* 備 注:無
******************************************************************/
void IIC_Stop(void)
{
SDA_OUT();
SCL(0);
SDA(0);
SCL(1);
delay_us(5);
SDA(1);
delay_us(5);
}
/******************************************************************
* 函 數(shù) 名 稱:IIC_Send_Ack
* 函 數(shù) 說 明:主機發(fā)送應答或者非應答信號
* 函 數(shù) 形 參:0發(fā)送應答 1發(fā)送非應答
* 函 數(shù) 返 回:無
* 作 者:LC
* 備 注:無
******************************************************************/
void IIC_Send_Ack(unsigned char ack)
{
SDA_OUT();
SCL(0);
SDA(0);
delay_us(5);
if(!ack) SDA(0);
else SDA(1);
SCL(1);
delay_us(5);
SCL(0);
SDA(1);
}
/******************************************************************
* 函 數(shù) 名 稱:I2C_WaitAck
* 函 數(shù) 說 明:等待從機應答
* 函 數(shù) 形 參:無
* 函 數(shù) 返 回:0有應答 1超時無應答
* 作 者:LC
* 備 注:無
******************************************************************/
unsigned char I2C_WaitAck(void)
{
char ack = 0;
unsigned char ack_flag = 10;
SCL(0);
SDA(1);
SDA_IN();
delay_us(5);
SCL(1);
delay_us(5);
while( (SDA_GET()==1) && ( ack_flag ) )
{
ack_flag--;
delay_us(5);
}
if( ack_flag <= 0 )
{
IIC_Stop();
return 1;
}
else
{
SCL(0);
SDA_OUT();
}
return ack;
}
/******************************************************************
* 函 數(shù) 名 稱:Send_Byte
* 函 數(shù) 說 明:寫入一個字節(jié)
* 函 數(shù) 形 參:dat要寫人的數(shù)據(jù)
* 函 數(shù) 返 回:無
* 作 者:LC
* 備 注:無
******************************************************************/
void Send_Byte(uint8_t dat)
{
int i = 0;
SDA_OUT();
SCL(0);//拉低時鐘開始數(shù)據(jù)傳輸
for( i = 0; i < 8; i++ )
{
SDA( (dat & 0x80) >> 7 );
delay_us(1);
SCL(1);
delay_us(5);
SCL(0);
delay_us(5);
dat<<=1;
}
}
/******************************************************************
* 函 數(shù) 名 稱:Read_Byte
* 函 數(shù) 說 明:IIC讀時序
* 函 數(shù) 形 參:無
* 函 數(shù) 返 回:讀到的數(shù)據(jù)
* 作 者:LC
* 備 注:無
******************************************************************/
unsigned char Read_Byte(void)
{
unsigned char i,receive=0;
SDA_IN();//SDA設置為輸入
for(i=0;i<8;i++ )
{
SCL(0);
delay_us(5);
SCL(1);
delay_us(5);
receive<<=1;
if( SDA_GET() )
{
receive|=1;
}
delay_us(5);
}
SCL(0);
return receive;
}
/******************************************************************
* 函 數(shù) 名 稱:PCA9685_Write
* 函 數(shù) 說 明:向PCA9685寫命令或數(shù)據(jù)
* 函 數(shù) 形 參:addr寫入的寄存器地址 data寫入的命令或數(shù)據(jù)
* 函 數(shù) 返 回:無
* 作 者:LC
* 備 注:無
******************************************************************/
void PCA9685_Write(uint8_t addr,uint8_t data)
{
IIC_Start();
Send_Byte(PCA_Addr);
I2C_WaitAck();
Send_Byte(addr);
I2C_WaitAck();
Send_Byte(data);
I2C_WaitAck();
IIC_Stop();
}
/******************************************************************
* 函 數(shù) 名 稱:PCA9685_Read
* 函 數(shù) 說 明:讀取PCA9685數(shù)據(jù)
* 函 數(shù) 形 參:addr讀取的寄存器地址
* 函 數(shù) 返 回:讀取的數(shù)據(jù)
* 作 者:LC
* 備 注:無
******************************************************************/
uint8_t PCA9685_Read(uint8_t addr)
{
uint8_t data;
IIC_Start();
Send_Byte(PCA_Addr);
I2C_WaitAck();
Send_Byte(addr);
I2C_WaitAck();
IIC_Stop();
delay_us(10);
IIC_Start();
Send_Byte(PCA_Addr|0x01);
I2C_WaitAck();
data = Read_Byte();
IIC_Send_Ack(1);
IIC_Stop();
return data;
}
/******************************************************************
* 函 數(shù) 名 稱:PCA9685_setPWM
* 函 數(shù) 說 明:設置第num個PWM引腳,on默認為0,控制舵機旋轉off角度
* 函 數(shù) 形 參:num:設置第幾個引腳輸出,范圍0~15
* on :默認為0
* off:舵機旋轉角度,范圍:0~180
* 函 數(shù) 返 回:無
* 作 者:LC
* 備 注:無
******************************************************************/
void PCA9685_setPWM(uint8_t num,uint32_t on,uint32_t off)
{
IIC_Start();
Send_Byte(PCA_Addr);
I2C_WaitAck();
Send_Byte(LED0_ON_L+4*num);
I2C_WaitAck();
Send_Byte(on&0xFF);
I2C_WaitAck();
Send_Byte(on>>8);
I2C_WaitAck();
Send_Byte(off&0xFF);
I2C_WaitAck();
Send_Byte(off>>8);
I2C_WaitAck();
IIC_Stop();
}
/******************************************************************
* 函 數(shù) 名 稱:PCA9685_setFreq
* 函 數(shù) 說 明:設置PCA9685的輸出頻率
* 函 數(shù) 形 參:freq
* 函 數(shù) 返 回:無
* 作 者:LC
* 備 注:
floor語法:
FLOOR(number, significance)
Number必需。要舍入的數(shù)值。
Significance必需。要舍入到的倍數(shù)。
說明
將參數(shù) number 向下舍入(沿絕對值減小的方向)為最接近的 significance 的倍數(shù)。
如果任一參數(shù)為非數(shù)值型,則 FLOOR 將返回錯誤值 #VALUE!。
如果 number 的符號為正,且 significance 的符號為負,則 FLOOR 將返回錯誤值 #NUM!
示例
公式 說明 結果
FLOOR(3.7,2) 將 3.7 沿絕對值減小的方向向下舍入,使其等于最接近的 2 的倍數(shù) 2
FLOOR(-2.5, -2) 將 -2.5 沿絕對值減小的方向向下舍入,使其等于最接近的 -2 的倍數(shù) -2
******************************************************************/
void PCA9685_setFreq(float freq)
{
uint8_t prescale,oldmode,newmode;
double prescaleval;
// freq *= 0.9; // Correct for overshoot in the frequency setting (see issue #11).
// PCA9685的內部時鐘頻率是25Mhz
// 公式: presale_Volue = round( 25000000/(4096 * update_rate) ) - 1
// round = floor(); floor是數(shù)學函數(shù),需要導入 math.h 文件
// update_rate = freq;
prescaleval = 25000000;
prescaleval /= 4096;
prescaleval /= freq;
prescaleval -= 1;
prescale = floor(prescaleval+0.5f);
//返回MODE1地址上的內容(保護其他內容)
oldmode = PCA9685_Read(PCA_Model);
//在MODE1中設置SLEEP位
newmode = (oldmode&0x7F)|0x10;
//將更改的MODE1的值寫入MODE1地址,使芯片睡眠
PCA9685_Write(PCA_Model,newmode);
//寫入我們計算的設置頻率的值
//PCA_Pre = presale 地址是0xFE,可以數(shù)據(jù)手冊里查找到
PCA9685_Write(PCA_Pre,prescale);
//重新復位
PCA9685_Write(PCA_Model,oldmode);
//等待復位完成
delay_1ms(5);
//設置MODE1寄存器開啟自動遞增
PCA9685_Write(PCA_Model,oldmode|0xa1);
}
//
/******************************************************************
* 函 數(shù) 名 稱:setAngle
* 函 數(shù) 說 明:設置角度
* 函 數(shù) 形 參:num要設置的PWM引腳 angle設置的角度
* 函 數(shù) 返 回:無
* 作 者:LC
* 備 注:無
******************************************************************/
void setAngle(uint8_t num,uint8_t angle)
{
uint32_t off = 0;
off = (uint32_t)(158+angle*2.2);
PCA9685_setPWM(num,0,off);
}
/******************************************************************
* 函 數(shù) 名 稱:PCA9685_Init
* 函 數(shù) 說 明:PCA9685初始化,所有PWM輸出頻率配置與所有PWM引腳輸出的舵機角度
* 函 數(shù) 形 參:hz設置的初始頻率 angle設置的初始角度
* 函 數(shù) 返 回:無
* 作 者:LC
* 備 注:無
******************************************************************/
void PCA9685_Init(float hz,uint8_t angle)
{
uint32_t off = 0;
PCA9685_GPIO_Init();
//在MODE1地址上寫0x00
PCA9685_Write(PCA_Model,0x00); //這一步很關鍵,如果沒有這一步PCA9685就不會正常工作。
// pwm.setPWMFreq(SERVO_FREQ)函數(shù)主要是設置PCA9685的輸出頻率,
// PCA9685的16路PWM輸出頻率是一致的,所以是不能實現(xiàn)不同引腳不同頻率的。
// 下面是setPWMFreq函數(shù)的內容,主要是根據(jù)頻率計算PRE_SCALE的值。
PCA9685_setFreq(hz);
//計算角度
off = (uint32_t)(145+angle*2.4);
//控制16個舵機輸出off角度
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_1ms(100);
}
在文件bsp_pca9685.h中,編寫如下代碼。
/*
* Change Logs:
* Date Author Notes
* 2024-06-25 LCKFB-LP first version
*/
#ifndef _BSP_PCA9685_H_
#define _BSP_PCA9685_H_
#include "board.h"
//端口移植
#define RCC_PCA9685_GPIO_ENABLE() __RCC_GPIOA_CLK_ENABLE()
#define PORT_PCA9685 CW_GPIOA
#define GPIO_SDA GPIO_PIN_5
#define GPIO_SCL GPIO_PIN_6
//設置SDA輸出模式
#define SDA_OUT() {
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pins = GPIO_SDA;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_Init(PORT_PCA9685, &GPIO_InitStruct);
}
//設置SDA輸入模式
#define SDA_IN() {
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pins = GPIO_SDA;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_Init(PORT_PCA9685, &GPIO_InitStruct);
}
//獲取SDA引腳的電平變化
#define SDA_GET() GPIO_ReadPin(PORT_PCA9685, GPIO_SDA)
//SDA與SCL輸出
#define SDA(x) GPIO_WritePin(PORT_PCA9685, GPIO_SDA, (x?GPIO_Pin_SET:GPIO_Pin_RESET) )
#define SCL(x) GPIO_WritePin(PORT_PCA9685, GPIO_SCL, (x?GPIO_Pin_SET:GPIO_Pin_RESET) )
#define PCA_Addr 0x80 //IIC地址
#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,uint8_t angle);
void setAngle(uint8_t num,uint8_t angle);
void PCA9685_setFreq(float freq);
void PCA9685_setPWM(uint8_t num,uint32_t on,uint32_t off);
#endif
四移植驗證
>>>
在自己工程中的main主函數(shù)中,編寫如下。
/*
* Change Logs:
* Date Author Notes
* 2024-06-25 LCKFB-LP first version
*/
#include "board.h"
#include "stdio.h"
#include "bsp_uart.h"
#include "bsp_pca9685.h"
int32_t main(void)
{
uint8_t i = 0;
board_init();
uart1_init(115200);
printf("startrn");
PCA9685_Init(60,0); //PCA9685--16路舵機初始化 頻率60Hz -- 0度
delay_ms(1000);
while(1)
{
i = ( i + 1 ) % 180;
setAngle(0,i);
delay_ms(10);
}
}
移植現(xiàn)象:0號接口的舵機從0度一直移動到180度后,又回到0度。
模塊移植成功案例代碼:
鏈接:https://pan.baidu.com/s/1UrA4XVIjnRYQAL4bSxNIfg?pwd=LCKF
提取碼:LCKF
END
往期回顧
REVIEW
【產(chǎn)品應用】CW32電動工具產(chǎn)品開源
【產(chǎn)品方案】基于CW32L010低成本電動工具方案
【產(chǎn)品應用】基于CW32的智能充電寶(方案開源)
【產(chǎn)品應用】CW-W88水泵通用控制板設計方案(已開源)
【產(chǎn)品應用】基于CW32的角磨機控制器產(chǎn)品方案
【產(chǎn)品方案】基于CW32F030C8的低壓無刷風機無感控制器
【產(chǎn)品方案】基于CW32的無刷直流空心杯電機有感控制驅動方案
【產(chǎn)品方案】基于CW32的無刷直流空心杯電機無感方波控制驅動方案
【產(chǎn)品方案】基于CW32F003E4P7的數(shù)字電壓電流表產(chǎn)品方案
【產(chǎn)品方案】CW32L010低成本工業(yè)儀表
CW32生態(tài)社區(qū)(WX)群
掃碼加入QQ群
4群| 478586307
獲取資料及“開發(fā)者扶持計劃”第一手資訊