PCA9685 擁有16路PWM,通過 IIC 與 STM32 進行通信,以下驅動代碼已通過測試,你可以進行更多代碼優化
#include "pca9685.h"// 向 PCA9685 寫入一個字節數據
static void PCA9685_write8( uint8_t addr, uint8_t d) {while (I2C_GetFlagStatus(I2C2, I2C_FLAG_BUSY)); // 等待總線空閑I2C_GenerateSTART(I2C2, ENABLE); // 產生起始信號while (!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT)); // 等待起始信號發送完成I2C_Send7bitAddress(I2C2, PCA9685_ADRESS << 1, I2C_Direction_Transmitter); // 發送從機地址while (!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); // 等待地址發送完成I2C_SendData(I2C2, addr); // 發送寄存器地址while (!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); // 等待寄存器地址發送完成I2C_SendData(I2C2, d); // 發送數據while (!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); // 等待數據發送完成I2C_GenerateSTOP(I2C2, ENABLE); // 產生停止信號delay_ms(1); // 延時一段時間
}// 從 PCA9685 讀取一個字節數據
static uint8_t PCA9685_read8( uint8_t addr) {uint8_t data;while (I2C_GetFlagStatus(I2C2, I2C_FLAG_BUSY)); // 等待總線空閑I2C_GenerateSTART(I2C2, ENABLE); // 產生起始信號while (!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT)); // 等待起始信號發送完成I2C_Send7bitAddress(I2C2, PCA9685_ADRESS << 1, I2C_Direction_Transmitter); // 發送從機地址while (!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); // 等待地址發送完成I2C_SendData(I2C2, addr); // 發送寄存器地址while (!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); // 等待寄存器地址發送完成I2C_GenerateSTART(I2C2, ENABLE); // 再次產生起始信號while (!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT)); // 等待起始信號發送完成I2C_Send7bitAddress(I2C2, PCA9685_ADRESS << 1 | 0x01, I2C_Direction_Receiver); // 發送從機地址(讀模式)while (!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); // 等待地址發送完成I2C_AcknowledgeConfig(I2C2, DISABLE); // 關閉應答I2C_GenerateSTOP(I2C2, ENABLE); // 產生停止信號while (!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED)); // 等待數據接收完成data = I2C_ReceiveData(I2C2); // 讀取數據return data;
}// 初始化 PCA9685 結構體
void PCA9685_Init() {GPIO_InitTypeDef GPIO_InitStructure;I2C_InitTypeDef I2C_InitStructure;// 使能 I2C2 和 GPIOB 時鐘RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);// 配置 I2C2 引腳(PB10 - SCL, PB11 - SDA)GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);// 配置 I2C2I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;I2C_InitStructure.I2C_OwnAddress1 = 0x00;I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;I2C_InitStructure.I2C_ClockSpeed = 100000;I2C_Init(I2C2, &I2C_InitStructure);// 使能 I2C2I2C_Cmd(I2C2, ENABLE);delay_ms(5);PCA9685_begin();
}// 開始使用 PCA9685
void PCA9685_begin() {PCA9685_reset();
}// 復位 PCA9685
void PCA9685_reset() {PCA9685_write8(PCA9685_MODE1, 0x0);
}// 設置 PWM 頻率
void PCA9685_setPWMFreq( float freq) {freq *= 0.9; // 校正頻率設置中的過沖float prescaleval = 25000000;prescaleval /= 4096;prescaleval /= freq;prescaleval -= 1;uint8_t prescale = (uint8_t)floor(prescaleval + 0.5);uint8_t oldmode = PCA9685_read8( PCA9685_MODE1);uint8_t newmode = (oldmode & 0x7F) | 0x10; // 進入睡眠模式PCA9685_write8( PCA9685_MODE1, newmode); // 進入睡眠模式PCA9685_write8( PCA9685_PRESCALE, prescale); // 設置預分頻器PCA9685_write8( PCA9685_MODE1, oldmode);delay_ms(5);PCA9685_write8( PCA9685_MODE1, oldmode | 0xa1); // 開啟自動遞增模式
}// 設置單個通道的 PWM 值 4095
void PCA9685_setPWM( uint8_t num, uint16_t on, uint16_t off) {PCA9685_write8( LED0_ON_L + 4 * num, on & 0xFF);PCA9685_write8( LED0_ON_H + 4 * num, on >> 8);PCA9685_write8( LED0_OFF_L + 4 * num, off & 0xFF);PCA9685_write8( LED0_OFF_H + 4 * num, off >> 8);
}// 工具函數,根據占空比設置 PWM
// channel: 0- 15 dutyCycle:0-1.0
void setPCA9685PWMWithDutyCycle(uint8_t channel, float dutyCycle) {// 檢查占空比是否在有效范圍內if (dutyCycle < 0.0) {dutyCycle = 0.0;} else if (dutyCycle > 1.0) {dutyCycle = 1.0;}// PCA9685 的 PWM 分辨率是 12 位,最大值為 4095const uint16_t pwmMax = 4095;uint16_t offValue = (uint16_t)(dutyCycle * pwmMax);// 調用 PCA9685_setPWM 函數設置 PWMPCA9685_setPWM(channel, 0, offValue);
}// 設置引腳的 PWM 值
void PCA9685_setPin( uint8_t num, uint16_t val, uint8_t invert) {// 限制值在 0 到 4095 之間if (val > 4095) val = 4095;if (invert) {if (val == 0) {// 信號全高的特殊值PCA9685_setPWM(num, 4096, 0);} else if (val == 4095) {// 信號全低的特殊值PCA9685_setPWM( num, 0, 4096);} else {PCA9685_setPWM( num, 0, 4095 - val);}} else {if (val == 4095) {// 信號全高的特殊值PCA9685_setPWM( num, 4096, 0);} else if (val == 0) {// 信號全低的特殊值PCA9685_setPWM(num, 0, 4096);} else {PCA9685_setPWM(num, 0, val);}}
}
#ifndef PCA9685_H
#define PCA9685_H
#include "stm32f10x.h"
#include <math.h>
#include "delay.h"// PCA9685 寄存器地址
#define PCA9685_SUBADR1 0x2
#define PCA9685_SUBADR2 0x3
#define PCA9685_SUBADR3 0x4#define PCA9685_ADRESS 0x40#define PCA9685_MODE1 0x0
#define PCA9685_PRESCALE 0xFE#define LED0_ON_L 0x6
#define LED0_ON_H 0x7
#define LED0_OFF_L 0x8
#define LED0_OFF_H 0x9#define ALLLED_ON_L 0xFA
#define ALLLED_ON_H 0xFB
#define ALLLED_OFF_L 0xFC
#define ALLLED_OFF_H 0xFD// 函數聲明
void PCA9685_Init(void);
void PCA9685_begin(void);
void PCA9685_reset(void);
void PCA9685_setPWMFreq(float freq);
void PCA9685_setPWM( uint8_t num, uint16_t on, uint16_t off);
void PCA9685_setPin( uint8_t num, uint16_t val, uint8_t invert);
void setPCA9685PWMWithDutyCycle(uint8_t channel, float dutyCycle);
#endif