提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
最近要用到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); //在这里会将地址位的末位置1
IIC_NAck();
data = IIC_Read_Byte(0);
IIC_Stop();
return data;
}
Arduino使用PCA9685模块主要参考大佬的方法,链接如下:
https://blog.csdn.net/u010841775/article/details/99701182
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了。不过板子上右上角都明确标出来了。

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

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

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需要自己写的内容就比较多了,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
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h