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

文章目录

  • 前言
  • 一、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。

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);        //在这里会将地址位的末位置1IIC_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引脚用接线帽短接了。

Arduino本身自带IIC接口,但不同的板子接口不一样,Arduino UNO的是A4和A5两个引脚,我用的MEGA2560,它的IIC引脚就不是A4和A5了。不过板子上右上角都明确标出来了。

2.Adafruit库安装

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

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

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; // sleepwrite8(PCA9685_MODE1, newmode); // go to sleepwrite8(PCA9685_PRESCALE, prescale); // set the prescalerwrite8(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 highdelay_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();//·¢ËÍnACKelseIIC_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 0xFEvoid 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

PCA9685模块使用(Arduino和STM32)相关推荐

  1. Arduino uno使用PCA9685模块实现16路舵机控制

    PCA9685模块 PCA9685是16路12位PWM信号发生器,可用于控制舵机.led.电机等设备,采用I2C通信.主机只需要I2C接口即可实现16路舵机控制. PCA9685的I2C地址默认0x4 ...

  2. Arduino处理STM32中的多个串口通讯问题

    简 介: 对于在Arduino下开发STM32的程序,对于STM32所具有的三个硬件USART进行测测试.结果显示可以使用这些串口完成相应的数据的输入与输出.但是涉及到以下两个问题,还没有得到解决:问 ...

  3. 基础篇010.2 STM32驱动RC522 RFID模块之二:STM32硬件SPI驱动RC522

    目录 1. 实验硬件及原理图 1.1 RFID硬件 1.2 硬件原理图 2. 单片机与RFID硬件模块分析 3. 利用STM32CubeMX创建MDK工程 3.1 STM32CubeMX工程创建 3. ...

  4. 【SG90模拟舵机控制及PCA9685模块的使用】

    一.模拟舵机控制 网上不乏对此种舵机的介绍,比如下面这篇文章: 浅谈用单片机控制SG90舵机(原理+编程) 1.简介 SG90模拟舵机在市面上十分常见,价格也比较便宜.常用于航模,机器人或智能小车等. ...

  5. wemosD1_WIFI模块 与 arduino

    参考:wemosD1_WIFI模块 与 arduino 作者:枕上 发布时间:2021-07-19 21:49:55 网址:https://blog.csdn.net/jinchi_boke/arti ...

  6. arduino和stm32哪个更好学?

    信很多电子爱好者面对arduino和stm32都会有个难题,那就是不知道如何选择使用?arduino一直处在火热状态,但是STM32接口多性能强,速度也较快?为什么大家有时候还是喜欢arduino做一 ...

  7. 树莓派控制16路PWM输出的PCA9685模块

    树莓派控制16路PWM输出的PCA9685模块 1.PCA9685 1.1简介 采用I2C通信,内置了PWM驱动器和一个时钟,不需要不断发送信号占用单片机资源 支持16路PWM输出,每路12位分辨率( ...

  8. 树莓派4B通过16路PWM输出的PCA9685模块控制舵机(机械臂)附完整代码

    树莓派4B通过16路PWM输出的PCA9685模块控制舵机(机械臂)附完整代码 文章目录 树莓派4B通过16路PWM输出的PCA9685模块控制舵机(机械臂)附完整代码 一. Servo Driver ...

  9. 【DX-BT24蓝牙模块连接Arduino与手机透传教程】

    [DX-BT24蓝牙模块连接Arduino与手机透传教程] 1. 前言 2. 接线 3. 程序设计详解 4. 演示效果 5. 小结 1. 前言 大夏龙雀科技DX-BT24&BT24-S& ...

最新文章

  1. 【原】自定义UINavigationItem的两种方法以及相应的隐藏方法
  2. android 闪烁发光动画,androidview动画发光效果在imageview上
  3. ASP.NET 2.0 XML 系列(2): XML技术
  4. 【NetApp】IO读写和WAFL的工作原理
  5. Reachable Numbers
  6. wxWidgets:操作档案manipulate archives的示例应用程序
  7. 免费下载 | 全景揭秘阿里文娱智能算法
  8. mongoose操作mongodb
  9. mpvue tabBar设定 app.json
  10. 201771010120 苏浪浪 《面向对象程序设计(java)》第二周学习总结
  11. Reactor三种线程模型与Netty线程模型
  12. js保存当前html,JavaScript保存当前页面
  13. 强化学习案例_强化学习实践案例!携程如何利用强化学习提高酒店推荐排序质量...
  14. HashMap内部存储实现及HashTable比较
  15. 如何做职业规划并进行求职准备(持续更新)
  16. Java根据信用卡号区分国际常用的五大信用卡卡种:VISA,Master,AE,DC,JCB.
  17. 实现从oss(阿里云)服务器批量下载文件
  18. R语言入门——一文讲明白attach与detach
  19. 【python PDF解析】python 读取PDF文件内容
  20. 货郎问题与计算复杂性

热门文章

  1. Linux vi编辑器中出现很多^M的解决办法
  2. 百变星君---文章模块(1)
  3. 从编程角度揭示病毒感染原理--之百变星君(病毒易容术)(下)
  4. stm32f103 usb 修改成自定义复合设备教程
  5. 人类的历史,不过是浪花中的一点泡沫。
  6. ajax局部刷新不出来,关于Ajax局部刷新未出来数据的问题
  7. [已解决]多网卡情况下Seata1.5.2分布式事务注册到nacos时ip错误
  8. Java面向对象之全集
  9. Spark+AI Summit Europe 2019 超清视频PPT下载
  10. javaScript蓝桥杯-----平地起高楼