stm32h7控制pca9685

使用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函数的实现吗?这样我才能更好地帮助你解决问题。

  • 给你找了一篇非常好的博客,你可以看看是否有帮助,链接:STC12C5A60S2的PCA模块输出PWM波
  • 除此之外, 这篇博客: PCA9685--16路 PWM模块舵机驱动板--STM32 IIC接口模块中的 外面调用的接口 部分也许能够解决你的问题, 你可以仔细阅读以下内容或跳转源博客中阅读:
  • 不用初始化,直接设置。

    设置后没有输出的话,可能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);