使用PCA9685模块控制Arduino与STM32

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、PCA9685简介
  • 二、Arduino使用PCA9685
  • 1.硬件连接
  • 2.Adafruit库安装
  • 3.示例程序解析
  • 三、STM32使用PCA9685
  • 总结(程序代码下载)

  • 前言

    最近要用到PCA9685控制多路舵机,就买了一块PCA9685模块试验,刚开始参照淘宝店铺给的例程写代码,结果PCA9685完全没反应,经过几天的摸索终于搞明白PCA9685的用法,在这里给大家分享一下PCA9685的使用方法。


    提示:以下是本篇文章正文内容,下面案例可供参考

    一、PCA9685简介

    PCA9685是一个基于IIC通信的16路PWM输出模块,可以在单片机资源不足的情况下进行扩展使用。
    在使用PCA9685的时候需要注意以下几点:

    1.PCA9685的分辨率是12位,即占空比控制时,0-4096对应的占空比为0-100,在控制舵机的时候,控制信号是0.5ms-2.5ms,周期20ms,所以控制舵机角度不会有太高的分辨率,对舵机控制精度较高的地方不建议使用。

    2.PCA9685地址位和很多描述的不一样,根据芯片手册,地址位的寄存器一共8位,其中最高位固定是1,A0-A5这六位是用户可更改的,而其中最关键的一位是R/W位,这一位主要是决定了读还是写,置1时为读,置0时为写,所以我们在写程序的时候,PCA9685的地址应把R/W位加上,是0x80,而不是0x40,在写的时候,发送地址位是0x80,在读的时候,发送的地址位是0x81。
    PCA9685地址位

    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);		//在这里会将地址位的末位置1
    	IIC_NAck();
    	
    	data = IIC_Read_Byte(0);
    	
    	IIC_Stop();
    	
    	return data;
    	
    }
    

    二、Arduino使用PCA9685

    Arduino使用PCA9685模块主要参考大佬的方法,链接如下:

    https://blog.csdn.net/u010841775/article/details/99701182
    

    1.硬件连接

    Arduino和PCA9685主要以IIC方式连接,接线顺序如下:
    Arduino PCA9685
    5V —–> VCC
    SDA —–> SDA
    SCL —–> SCL
    GND —–> GND
    根据模块的电路原理图,可以看到模块上芯片的供电VCC和舵机驱动引脚的供电5V是分开的,我们开发板的5V功率较小,无法带动多个舵机,我们可以通过5V接口外接供电电路。在试验的时候为了方便接线,我就直接把VCC和5V引脚用接线帽短接了。
    PCA9685原理图
    Arduino本身自带IIC接口,但不同的板子接口不一样,Arduino UNO的是A4和A5两个引脚,我用的MEGA2560,它的IIC引脚就不是A4和A5了。不过板子上右上角都明确标出来了。

    Arduino IIC引脚示意

    2.Adafruit库安装

    PC9685模块对应的库是由Adafruit提供的外部库。在打开Arduino IDE点击“项目–>加载库–>管理库”,在搜索框里输入:adafruit pwm,将该库安装即可。
    Adafruit库安装

    安装完成库之后,打开示例程序,程序中已经写好,可以直接编译下载。
    示例程序

    3.示例程序解析

    Arduino的示例程序里面主要有三个函数,下面一次对其进行解析:

    pwm.begin();
    

    pwm.begin()函数主要是对IIC引脚的初始化配置,在配置完成后对PCA9685进行重置,即在MODE1地址上写0x00。这一步很关键,如果没有这一步PCA9685就不会正常工作。

    void Adafruit_PWMServoDriver::begin(void) {
     WIRE.begin();
     reset();
    }
    
    
    void Adafruit_PWMServoDriver::reset(void) {
     write8(PCA9685_MODE1, 0x0);
    }
    
    pwm.setPWMFreq(SERVO_FREQ);  // Analog servos run at ~50 Hz updates
    
    

    pwm.setPWMFreq(SERVO_FREQ)函数主要是设置PCA9685的输出频率,PCA9685的16路PWM输出频率是一致的,所以是不能实现不同引脚不同频率的。下面是setPWMFreq函数的内容,主要是根据频率计算PRE_SCALE的值。

    void Adafruit_PWMServoDriver::setPWMFreq(float freq) {
      //Serial.print("Attempting to set freq ");
      //Serial.println(freq);
      freq *= 0.9;  // Correct for overshoot in the frequency setting (see issue #11).
      float prescaleval = 25000000;
      prescaleval /= 4096;
      prescaleval /= freq;
      prescaleval -= 1;
    
      uint8_t oldmode = read8(PCA9685_MODE1);
      uint8_t newmode = (oldmode&0x7F) | 0x10; // sleep
      write8(PCA9685_MODE1, newmode); // go to sleep
      write8(PCA9685_PRESCALE, prescale); // set the prescaler
      write8(PCA9685_MODE1, oldmode);
      delay(5);
      write8(PCA9685_MODE1, oldmode | 0xa1);  //  This sets the MODE1 register to turn on auto increment.
                                              // This is why the beginTransmission below was not working.
      //  Serial.print("Mode now 0x"); Serial.println(read8(PCA9685_MODE1), HEX);
    }
    

    通过逻辑分析仪对其信号进行了解析,方便后续STM32程序的编写。
    设置频率

    设置频率

    pwm.setPWM(pwmnum, on, off );
    

    pwm.setPWM(pwmnum, on, off )函数主要是对输出PWM占空比的调节。通常on都设为0,改变off即可。因为PCA9685是12位分辨率,所以off的值0~4096就代表了占空比0-100.

    void Adafruit_PWMServoDriver::setPWM(uint8_t num, uint16_t on, uint16_t off) {
      //Serial.print("Setting PWM "); Serial.print(num); Serial.print(": "); Serial.print(on); Serial.print("->"); Serial.println(off);
    
      WIRE.beginTransmission(_i2caddr);
      WIRE.write(LED0_ON_L+4*num);
      WIRE.write(on);
      WIRE.write(on>>8);
      WIRE.write(off);
      WIRE.write(off>>8);
      WIRE.endTransmission();
    }
    

    设置占空比

    三、STM32使用PCA9685

    STM32使用PCA9685需要自己写的内容就比较多了,IIC通讯的程序我采用的是正点原子的bsp。IIC通讯的板级支持包内容如下(注释乱码,凑合看看,文末有Gitee链接):
    myiic.h文件:

    #ifndef __MYIIC_H
    #define __MYIIC_H
    #include "sys.h"
    	   		   
    //IO·½ÏòÉèÖÃ
    #define SDA_IN()  {GPIOA->CRH&=0XFFFF0FFF;GPIOA->CRH|=8<<12;}
    #define SDA_OUT() {GPIOA->CRH&=0XFFFF0FFF;GPIOA->CRH|=3<<12;}
    
    //IO²Ù×÷º¯Êý	 
    #define IIC_SCL    PAout(12) //SCL
    #define IIC_SDA    PAout(11) //SDA	 
    #define READ_SDA   PAin(11)  //ÊäÈë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
    

    myiic.c文件

    #include "myiic.h"
    #include "delay.h"
    
    //³õʼ»¯IIC
    void IIC_Init(void)
    {					     
    	GPIO_InitTypeDef GPIO_InitStructure;
    	//RCC->APB2ENR|=1<<4;//ÏÈʹÄÜÍâÉèIO PORTCʱÖÓ 
    	RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOA, ENABLE );	
    	   
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12|GPIO_Pin_11;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;   //ÍÆÍìÊä³ö
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &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(2);	   
    	IIC_SCL=1;delay_us(2);	 
    	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ÕâÈý¸öÑÓʱ¶¼ÊDZØÐëµÄ
    		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的板级支持包,还需要自己写PCA9685的驱动程序,主要程序如下:
    PCA9685.h文件:

    #include "stm32f10x.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,u8 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,u8 angle);
    

    PCA9685.c文件:

    #include "bsp_PCA9685.h"
    #include "myiic.h"
    #include "delay.h"
    #include <math.h>
    
    void PCA9685_Init(float hz,u8 angle)
    {
    	u32 off = 0;
    	IIC_Init();
    	PCA9685_Write(PCA_Model,0x00);
    	PCA9685_setFreq(hz);
    	off = (u32)(145+angle*2.4);
    	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.92;
    	prescaleval = 25000000;
    	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,u8 angle)
    {
    	u32 off = 0;
    	off = (u32)(158+angle*2.2);
    	PCA9685_setPWM(num,0,off);
    }
    
    

    main()函数相对就简单了:

    #include "stm32f10x.h"
    #include "delay.h"
    #include "sys.h"
    #include "bsp_PCA9685.h"
    
    
     int main(void)
     {	
    	delay_init();
    	PCA9685_Init(60,180);
    	while(1)
    	{
    		PCA9685_setPWM(0,0,2048);
    		delay_ms(500);
    	}
     }
    
    

    总结(程序代码下载)

    STM32的程序我放在Gitee上了,大家可以去下载。

    https://gitee.com/qi-zezhong/pca9685-stm32
    
    物联沃分享整理
    物联沃-IOTWORD物联网 » 使用PCA9685模块控制Arduino与STM32

    发表评论