使用STM32H7系列使用软件IIC控制PCA9685,但代码编译通过确无法在PCA9685输出PWM波,这是为什么,以下是源代码:
int main(void)
{
Cache_Enable(); //打开L1-Cache
HAL_Init(); //初始化HAL库
Stm32_Clock_Init(160,5,2,4); //设置时钟,400Mhz
delay_init(400);
PCA_MG9XX_Init(60,180);
delay_ms(500);
while(1)
{
pca_setpwm(0, 0, 2048);
pca_setpwm(1, 0, 2048);
delay_ms(500);
}
#ifndef __PCA9685_H
#define __PCA9685_H
//#include "sys.h"
#define pca_adrr 0x80
#define pca_mode1 0x0
#define pca_pre 0xFE
#define LED0_ON_L 0x6
#define LED0_ON_H 0x7
#define LED0_OFF_L 0x8
#define LED0_OFF_H 0x9
#define jdMIN 115 // minimum
#define jdMAX 590 // maximum
#define jd000 130 //0度对应4096的脉宽计数值
#define jd180 520 //180度对应4096的脉宽计算值
//IO方向设置
#define SDA_P_IN() {GPIOB->MODER&=~(3<<(5*2));GPIOB->MODER|=0<<5*2;} //PH5输入模式
#define SDA_P_OUT() {GPIOB->MODER&=~(3<<(5*2));GPIOB->MODER|=1<<5*2;} //PH5输出模式
//IO操作
#define IIC_P_SCL(n) (n?HAL_GPIO_WritePin(GPIOB,GPIO_PIN_6,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOB,GPIO_PIN_6,GPIO_PIN_RESET)) //SCL
#define IIC_P_SDA(n) (n?HAL_GPIO_WritePin(GPIOB,GPIO_PIN_7,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOB,GPIO_PIN_7,GPIO_PIN_RESET)) //SDA
#define READ_P_SDA HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_7) //输入SDA
void pca_write(u8 adrr,u8 data);
u8 pca_read(u8 adrr);
void PCA_MG9XX_Init(float hz,u8 angle);
void pca_setfreq(float freq);
void pca_setpwm(u8 num, u32 on, u32 off);
void PCA_MG9XX(u8 num,u8 start_angle,u8 end_angle,u8 mode,u8 speed);
//IIC所有操作函数
void IIC_P_Init(void); //初始化IIC的IO口
void IIC_P_Start(void); //发送IIC开始信号
void IIC_P_Stop(void); //发送IIC停止信号
void IIC_P_Send_Byte(u8 txd); //IIC发送一个字节
u8 IIC_Read_P_Byte(unsigned char ack);//IIC读取一个字节
u8 IIC_P_Wait_Ack(void); //IIC等待ACK信号
void IIC_P_Ack(void); //IIC发送ACK信号
void IIC_P_NAck(void); //IIC不发送ACK信号
u8 IIC_Read_P_Byte(unsigned char ack); //读1个字节,ack=1时,发送ACK,ack=0,发送nACK
#endif
#include "delay.h"
#include "pca9685.h"
#include "math.h"
void pca_write(u8 adrr,u8 data)//向PCA写数据,adrrd地址,data数据
{
IIC_P_Start();
IIC_P_Send_Byte(pca_adrr);
IIC_P_Wait_Ack();
IIC_P_Send_Byte(adrr);
IIC_P_Wait_Ack();
IIC_P_Send_Byte(data);
IIC_P_Wait_Ack();
IIC_P_Stop();
}
u8 pca_read(u8 adrr)//从PCA读数据
{
u8 data;
IIC_P_Start();
IIC_P_Send_Byte(pca_adrr);
IIC_P_Wait_Ack();
IIC_P_Send_Byte(adrr);
IIC_P_Wait_Ack();
IIC_P_Start();
IIC_P_Send_Byte(pca_adrr|0x01);
IIC_P_Wait_Ack();
data=IIC_Read_P_Byte(0);
IIC_P_Stop();
return data;
}
void pca_setfreq(float freq)//设置PWM频率
{
u8 prescale,oldmode,newmode;
double prescaleval;
//freq *= 0.92;
prescaleval = 25000000;
prescaleval /= 4096;
prescaleval /= freq;
prescaleval -= 1;
prescale =floor(prescaleval + 0.5f);
oldmode = pca_read(pca_mode1);
newmode = (oldmode&0x7F) | 0x10; // sleep
pca_write(pca_mode1, newmode); // go to sleep
pca_write(pca_pre, prescale); // set the prescaler
pca_write(pca_mode1, oldmode);
delay_ms(2);
pca_write(pca_mode1, oldmode | 0xa1);
}
void pca_setpwm(u8 num, u32 on, u32 off)
{
pca_write(LED0_ON_L+4*num,on);
pca_write(LED0_ON_H+4*num,on>>8);
pca_write(LED0_OFF_L+4*num,off);
pca_write(LED0_OFF_H+4*num,off>>8);
}
/*num:舵机PWM输出引脚0~15,on:PWM上升计数值0~4096,off:PWM下降计数值0~4096
一个PWM周期分成4096份,由0开始+1计数,计到on时跳变为高电平,继续计数到off时
跳变为低电平,直到计满4096重新开始。所以当on不等于0时可作延时,当on等于0时,
off/4096的值就是PWM的占空比。*/
/*
函数作用:初始化舵机驱动板
参数:1.PWM频率
2.初始化舵机角度
*/
void PCA_MG9XX_Init(float hz,u8 angle)
{
u32 off=0;
IIC_P_Init();
pca_write(pca_mode1,0x0);
pca_setfreq(hz);//设置PWM频率
off=(u32)(145+angle*2.4);
pca_setpwm(0,0,off);pca_setpwm(1,0,off);pca_setpwm(2,0,off);pca_setpwm(3,0,off);
pca_setpwm(4,0,off);pca_setpwm(5,0,off);pca_setpwm(6,0,off);pca_setpwm(7,0,off);
pca_setpwm(8,0,off);pca_setpwm(9,0,off);pca_setpwm(10,0,off);pca_setpwm(11,0,off);
pca_setpwm(12,0,off);pca_setpwm(13,0,off);pca_setpwm(14,0,off);pca_setpwm(15,0,off);
delay_ms(500);
}
/*
函数作用:控制舵机转动;
参数:1.输出端口,可选0~15;
2.起始角度,可选0~180;
3.结束角度,可选0~180;
4.模式选择
0 表示函数内无延时,调用时需要在函数后另外加延时函数,且不可调速,第五个参数可填任意值;
1 表示函数内有延时,调用时不需要在函数后另外加延时函数,且不可调速,第五个参数可填任意值;
2 表示速度可调,第五个参数表示速度值;
5.速度,可填大于 0 的任意值,填 1 时速度最快,数值越大,速度越小;
注意事项:模式 0和1 的速度比模式 2 的最大速度大;
*/
void PCA_MG9XX(u8 num,u8 start_angle,u8 end_angle,u8 mode,u8 speed)
{
u8 i;
u32 off=0;
switch(mode)
{
case 0:
off=(u32)(158+end_angle*2.2);
pca_setpwm(num,0,off);
break;
case 1:
off=(u32)(158+end_angle*2.2);
pca_setpwm(num,0,off);
if(end_angle>start_angle){delay_ms((u16)((end_angle-start_angle)*2.7));}
else{delay_ms((u16)((start_angle-end_angle)*2.7));}
break;
case 2:
if(end_angle>start_angle)
{
for(i=start_angle;i<=end_angle;i++)
{
off=(u32)(158+i*2.2);
pca_setpwm(num,0,off);
delay_ms(2);
delay_us(speed*250);
}
}
else if(start_angle>end_angle)
{
for(i=start_angle;i>=end_angle;i--)
{
off=(u32)(158+i*2.2);
pca_setpwm(num,0,off);
delay_ms(2);
delay_us(speed*250);
}
}
break;
}
}
//----------------
//初始化IIC
void IIC_P_Init(void)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOB_CLK_ENABLE(); //使能GPIOH时钟
//PH4,5初始化设置
GPIO_Initure.Pin=GPIO_PIN_6|GPIO_PIN_7;
GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FREQ_VERY_HIGH; //快速
HAL_GPIO_Init(GPIOB,&GPIO_Initure);
IIC_P_SDA(1);
IIC_P_SCL(1);
}
//产生IIC起始信号
void IIC_P_Start(void)
{
SDA_P_OUT(); //sda线输出
IIC_P_SDA(1);
IIC_P_SCL(1);
delay_us(4);
IIC_P_SDA(0);//START:when CLK is high,DATA change form high to low
delay_us(4);
IIC_P_SCL(0);//钳住I2C总线,准备发送或接收数据
}
//产生IIC停止信号
void IIC_P_Stop(void)
{
SDA_P_OUT();//sda线输出
IIC_P_SCL(1);
IIC_P_SDA(1); //STOP:when CLK is high DATA change form low to high
delay_us(4);
IIC_P_SCL(1);
IIC_P_SDA(1);//发送I2C总线结束信号
delay_us(4);
}
//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
u8 IIC_P_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_P_IN(); //SDA设置为输入
IIC_P_SDA(1);;delay_us(1);
IIC_P_SCL(1);delay_us(1);
while(READ_P_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_P_Stop();
return 1;
}
}
IIC_P_SCL(0);//时钟输出0
return 0;
}
//产生ACK应答
void IIC_P_Ack(void)
{
IIC_P_SCL(0);
SDA_P_OUT();
IIC_P_SDA(0);
delay_us(2);
IIC_P_SCL(1);
delay_us(2);
IIC_P_SCL(0);
}
//不产生ACK应答
void IIC_P_NAck(void)
{
IIC_P_SCL(0);
SDA_P_OUT();
IIC_P_SDA(1);
delay_us(2);
IIC_P_SCL(1);
delay_us(2);
IIC_P_SCL(0);
}
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答
void IIC_P_Send_Byte(u8 txd)
{
u8 t;
SDA_P_OUT();
IIC_P_SCL(0);//拉低时钟开始数据传输
for(t=0;t<8;t++)
{
IIC_P_SDA((txd&0x80)>>7);
txd<<=1;
delay_us(2); //对TEA5767这三个延时都是必须的
IIC_P_SCL(1);
delay_us(2);
IIC_P_SCL(0);
delay_us(2);
}
}
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK
u8 IIC_Read_P_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_P_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
IIC_P_SCL(0);
delay_us(2);
IIC_P_SCL(1);
receive<<=1;
if(READ_P_SDA)receive++;
delay_us(1);
}
if (!ack)
IIC_P_NAck();//发送nACK
else
IIC_P_Ack(); //发送ACK
return receive;
}
你好!根据你提供的代码,我看到你使用STM32H7系列控制PCA9685输出PWM波。但是你说代码编译通过后无法输出PWM波。我注意到你在主函数中使用了pca_setpwm函数来设置PWM波的输出,但是你没有提供pca_setpwm函数的实现。你能提供一下pca_setpwm函数的实现吗?这样我才能更好地帮助你解决问题。
不用初始化,直接设置。
设置后没有输出的话,可能i2c写入失败,刚开始遇到过这种情况,后来重新换了i2c库之后就可以了。还是主要i2c要正确使用。
/*freq:要设置的周期*/
void setPWMFreq(float freq);
/*
num:设置第num个输出口输出量变化
on与off:配合产生占空比 0~on为低电平 ,到on为高电平,到off转为低电平
*/
void setPWM(u8 num, u16 on, u16 off);
上面的用的不方便就封装为简单的 直接输入需要设置的占空比 周期50,off:15表示1.5ms控制舵机方便
void set_pwm(u8 num, u8 off);
void setPWMFreq(float freq) {
freq *= 0.9;
float prescaleval = 25000000;
prescaleval /= 4096;
prescaleval /= freq;
prescaleval -= 1;
uint8_t oldmode = read8(PCA9685_MODE1);
uint8_t newmode = (oldmode&0x7F) | 0x10; //准备进入sleep,设置时钟前必须先进入sleep模式
Delay_ms(5);
write8(PCA9685_MODE1, newmode);
Delay_ms(5);
write8(PCA9685_PRESCALE, prescaleval); p
Delay_ms(5);
oldmode &= 0xef; //清除sleep位
write8(PCA9685_MODE1, oldmode);
Delay_ms(5);
write8(PCA9685_MODE1, oldmode | 0xa1); // This sets the MODE1 register to turn on auto increment.
}
void setPWM(uint8_t num, uint16_t on, uint16_t off) {
write8(LED0_ON_L+4*num,on);
write8(LED0_ON_H+4*num,on>>8);
write8(LED0_OFF_L+4*num,off);
write8(LED0_OFF_H+4*num,off>>8);
}
void set_pwm(u8 num, u8 off)
{
setPWM(num, 0, (int)(off*20.48));
}
uint8_t read8(uint8_t reg_addr) {
u8 data=0;
I2C_BufferRead(PCA9685_SLAVE_ADDRESS,&data,reg_addr,1);
return data;
}
void write8(uint8_t reg_addr, uint8_t reg_dat) {
I2C_ByteWrite(PCA9685_SLAVE_ADDRESS,reg_dat,reg_addr);
}
由于经常使用i2c接口就写了一个库方便移植,以后每次使用i2c就可以只用稍加修改封装下,主要还是使用一下两个接口 I2C_BufferRead(PCA9685_SLAVE_ADDRESS,&data,reg_addr,1);与 I2C_ByteWrite(PCA9685_SLAVE_ADDRESS,reg_dat,reg_addr);