【CW32模块使用】16路舵机驱动模块

当你在一个项目中碰到了微控制器芯片的PWM输出引脚不够用的情况,那么这款PCA968516路舵机就能很快帮助您解决这个问题了。只要你的主控芯片具备了I2C通信,就能够让主控芯片和PCA9685通信,实现多个舵机的同时控制了。PCA9685 16路舵机是一个采用I2C通信,内置了PWM驱动器和一个时钟,这个意味着,这将和TLCG940系列有很大不同,你不需要不断发送信号占用你的单片机。它是5V的兼容,这意味你还可以用3.3V单片机控制并且安全地驱动到6V输出(当你想要控制白色或蓝色指示灯用3.4+正电压也是可以的)。地址选择引脚使你可以把62个驱动板挂在单个l2C总线上,总共有992路PWM输出,那将是非常庞大的资源,约1.6Khz可调频PWM输出,为步进电机准备输出12位分辨率,可配置的推拉输出或开路输出,输出使能引脚能够快速禁用所有输出。

一、模块来源

模块实物展示:


资料链接:https://pan.baidu.com/s/1FjoAuJm387bxaZxS6g9HEg
资料提取码:8888

二、规格参数

输入电压:3.3V~5V

额定电流:15mA

控制方式:IIC

尺寸:21(长)*21(宽)[单位:mm]

以上信息见厂家资料文件

三、移植过程

我们的目标是将例程移植至CW32F030C8T6开发板上【实现无线的数据传输的功能】。首先要获取资料,查看数据手册应如何实现读取数据,再移植至我们的工程。

3.1查看资料

IIC器件地址

PCA9685 是一个I2C 从设备,有个设备ID,或者叫从 地址。从地址是如下确定的:

Board 0: Address = 0×40 Offset = binary 00000 (默认)

Board 1: Address = 0×41 Offset = binary 00001 (A0接上拉)

Board 2: Address = 0×42 Offset = binary 00010 (接上A1上拉)

Board 3: Address = 0×43 Offset = binary 00011 (A0和A1上拉)

Board 4: Address = 0×44 Offset = binary 00100 (A2上拉)

以此类推;

PCA9685的I2C总线从地址如下图所示。为了节约电力,硬件可选地址引脚上没有内部上拉电阻,它们必须被拉高或拉低。但是我们使用的是模块,而模块上已经为我们接好了上拉电阻。

地址字节的最后一位定义要执行的操作。当设置为逻辑1时,将选择读操作,而逻辑0则选择写操作。 在原理图中,地址线全部接0,所以slave address是0x40。对应Fig 4上的位置,则为:

则IIC地址是 0x80 ,写入时是0x80,读取时是0x81。

设置PWM频率

舵机控制所需的 PWM 周期为20 ms. 在用 PCA9685 作为多舵机控制器时,需要将 其 PWM 输出周期设定为20 ms,即PWM 波的频率设定为50 Hz,PCA9685 输出频率与振荡器有关,频率的设置值refresh_rate见下面的公式;

其中,EXTCLK是PCA9685的内部时钟频率为25Mhz;prescale是要设置的频率,我们设置为50Hz;

refresh_rate = 25,000,000 /( 4096 * ( 50 + 1 ))

refresh_rate = 25,000,000 / 4096 / (50 + 1)

refresh_rate = 6,103.52 / (50 + 1)

refresh_rate = 6,103.52 / 51

refresh_rate = 119.68

所以我们需要设置的值是119.68,取整数就是120。

需要注意的是,频率的更改只能在 PCA9685 芯片处于休眠状态下进行。

以下加粗字体是数据手册内容:

要使用EXTCLK引脚,该位必须按以下顺序设置:

  1. 在mode1中设置SLEEP位。这就关闭了内部振荡器,使芯片处于休眠状态。
  2. 将逻辑1写入MODE1中的SLEEP和EXTCLK位。这样就转换完成了。外部时钟可以在切换期间处于活动状态,因为设置了SLEEP位。

这个位是一个“粘性位”,也就是说,它不能通过写入逻辑0来清除。EXTCLK位只能通过电源循环或软件重置来清除。 占空比或者脉冲宽度的设定

每个PWM引脚输出的开启时间和PWM的占空比可以通过LEDn_ONLEDn_OFF寄存器独立控制。

每个PWM引脚输出将有两个12位寄存器。这些寄存器将由用户编程。两个寄存器都将保存从0到4095的值。一个12位寄存器将保存ON时间的值,另一个12位寄存器将保存OFF时间的值。将ON和OFF时间与12位计数器的值进行比较,该计数器将从0000h持续运行到0FFFh(0到4095十进制)。

ON时间是可编程的,它是PWM输出ON的时间,OFF时间也是可编程的,它是PWM输出OFF的时间。这样相移就完全可编程了。相移的分辨率为目标频率的1 / 4096。表7列出了这些寄存器。

以下用一个例子说明如何计算要加载到这些寄存器中的值。

(假设使用LED0输出,(延时时间)+ (PWM占空比)<=100%)

延迟时间 = 10%;PWM占空比= 20% (LEDON电平= 20%;LEDOFF时间= 80%)。延迟时间= 10% = 4096 * 0.1 = 409.6 ~ 410,计数= 410(十进制) = 19Ah(十六进制)

因为计数器从0开始,到4095结束,我们将减去1,所以延迟时间 = 199h 个数。

LED0_ON_H = 1h;LED0_ON_L = 99h (LED开始打开后,这个延迟计数到409)

LED开机时间= 20% = 819.2 ~ 819次

LED关闭时间= 4CCh(十进制410 + 819-1 = 1228)

LED0_OFF_H = 4h;LED0_OFF_L = CCh(此计数到1228后LED开始关闭)

整个周期为4095, LED_ON 和 LED_OFF 2个的设定值确定脉宽,在后面的代码里,LED_ON 设为0, LED_OFF就是脉宽了。 这里都用2位字节来表示。

相关地址表

// 相关地址表
// 这里只截图了需要的地址,分别是:
#define PCA_Addr            0x80    //IIC地址
#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    //配置频率地址

3.2引脚选择

模块接线图

3.3移植至工程

移植步骤中的导入.c和.h文件与【CW32模块使用】DHT11温湿度传感器相同,只是将.c和.h文件更改为bsp_pca9685.c与bsp_pca9685.h。这里不再过多讲述,移植完成后面修改相关代码。

在文件bsp_pca9685.c中,编写如下代码。

/*
 * Change Logs:
 * Date           Author       Notes
 * 2024-06-25     LCKFB-LP    first version
 */

#include "bsp_pca9685.h"
#include "stdio.h"
#include <math.h>

/******************************************************************
 * 函 数 名 称:PCA9685_GPIO_Init
 * 函 数 说 明:PCA9685的引脚初始化
 * 函 数 形 参:无
 * 函 数 返 回:无
 * 作       者:LC
 * 备       注:无
******************************************************************/
void PCA9685_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct; // GPIO初始化结构体

    RCC_PCA9685_GPIO_ENABLE();        // 使能GPIO时钟

    GPIO_InitStruct.Pins = GPIO_SDA|GPIO_SCL;   // GPIO引脚
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; // 开漏输出
    GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;    // 输出速度高
    GPIO_Init(PORT_PCA9685, &GPIO_InitStruct);  // 初始化
}


/******************************************************************
 * 函 数 名 称:IIC_Start
 * 函 数 说 明:IIC起始时序
 * 函 数 形 参:无
 * 函 数 返 回:无
 * 作       者:LC
 * 备       注:无
******************************************************************/
void IIC_Start(void)
{
        SDA_OUT();

        SDA(1);
        delay_us(5);
        SCL(1);
        delay_us(5);

        SDA(0);
        delay_us(5);
        SCL(0);
        delay_us(5);

}
/******************************************************************
 * 函 数 名 称:IIC_Stop
 * 函 数 说 明:IIC停止信号
 * 函 数 形 参:无
 * 函 数 返 回:无
 * 作       者:LC
 * 备       注:无
******************************************************************/
void IIC_Stop(void)
{
        SDA_OUT();
        SCL(0);
        SDA(0);

        SCL(1);
        delay_us(5);
        SDA(1);
        delay_us(5);

}

/******************************************************************
 * 函 数 名 称:IIC_Send_Ack
 * 函 数 说 明:主机发送应答或者非应答信号
 * 函 数 形 参:0发送应答  1发送非应答
 * 函 数 返 回:无
 * 作       者:LC
 * 备       注:无
******************************************************************/
void IIC_Send_Ack(unsigned char ack)
{
        SDA_OUT();
        SCL(0);
        SDA(0);
        delay_us(5);
        if(!ack) SDA(0);
        else     SDA(1);
        SCL(1);
        delay_us(5);
        SCL(0);
        SDA(1);
}


/******************************************************************
 * 函 数 名 称:I2C_WaitAck
 * 函 数 说 明:等待从机应答
 * 函 数 形 参:无
 * 函 数 返 回:0有应答  1超时无应答
 * 作       者:LC
 * 备       注:无
******************************************************************/
unsigned char I2C_WaitAck(void)
{

        char ack = 0;
        unsigned char ack_flag = 10;
        SCL(0);
        SDA(1);
        SDA_IN();
        delay_us(5);
        SCL(1);
        delay_us(5);

        while( (SDA_GET()==1) && ( ack_flag ) )
        {
                        ack_flag--;
                        delay_us(5);
        }

        if( ack_flag <= 0 )
        {
                        IIC_Stop();
                        return 1;
        }
        else
        {
                        SCL(0);
                        SDA_OUT();
        }
        return ack;
}

/******************************************************************
 * 函 数 名 称:Send_Byte
 * 函 数 说 明:写入一个字节
 * 函 数 形 参:dat要写人的数据
 * 函 数 返 回:无
 * 作       者:LC
 * 备       注:无
******************************************************************/
void Send_Byte(uint8_t dat)
{
        int i = 0;
        SDA_OUT();
        SCL(0);//拉低时钟开始数据传输

        for( i = 0; i < 8; i++ )
        {
                SDA( (dat & 0x80) >> 7 );
                delay_us(1);
                SCL(1);
                delay_us(5);
                SCL(0);
                delay_us(5);
                dat<<=1;
        }
}

/******************************************************************
 * 函 数 名 称:Read_Byte
 * 函 数 说 明:IIC读时序
 * 函 数 形 参:无
 * 函 数 返 回:读到的数据
 * 作       者:LC
 * 备       注:无
******************************************************************/
unsigned char Read_Byte(void)
{
        unsigned char i,receive=0;
        SDA_IN();//SDA设置为输入
    for(i=0;i<8;i++ )
        {
                SCL(0);
                delay_us(5);
                SCL(1);
                delay_us(5);
                receive<<=1;
                if( SDA_GET() )
                {
                        receive|=1;
                }
                delay_us(5);
        }
        SCL(0);
        return receive;
}

/******************************************************************
 * 函 数 名 称:PCA9685_Write
 * 函 数 说 明:向PCA9685写命令或数据
 * 函 数 形 参:addr写入的寄存器地址    data写入的命令或数据
 * 函 数 返 回:无
 * 作       者:LC
 * 备       注:无
******************************************************************/
void PCA9685_Write(uint8_t addr,uint8_t data)
{
        IIC_Start();

        Send_Byte(PCA_Addr);
        I2C_WaitAck();

        Send_Byte(addr);
        I2C_WaitAck();

        Send_Byte(data);
        I2C_WaitAck();

        IIC_Stop();
}

/******************************************************************
 * 函 数 名 称:PCA9685_Read
 * 函 数 说 明:读取PCA9685数据
 * 函 数 形 参:addr读取的寄存器地址
 * 函 数 返 回:读取的数据
 * 作       者:LC
 * 备       注:无
******************************************************************/
uint8_t PCA9685_Read(uint8_t addr)
{
        uint8_t data;

        IIC_Start();

        Send_Byte(PCA_Addr);
        I2C_WaitAck();

        Send_Byte(addr);
        I2C_WaitAck();

        IIC_Stop();

        delay_us(10);


        IIC_Start();

        Send_Byte(PCA_Addr|0x01);
        I2C_WaitAck();

        data = Read_Byte();
        IIC_Send_Ack(1);
        IIC_Stop();

        return data;
}
/******************************************************************
 * 函 数 名 称:PCA9685_setPWM
 * 函 数 说 明:设置第num个PWM引脚,on默认为0,控制舵机旋转off角度
 * 函 数 形 参:num:设置第几个引脚输出,范围0~15
 *              on :默认为0
 *              off:舵机旋转角度,范围:0~180
 * 函 数 返 回:无
 * 作       者:LC
 * 备       注:无
******************************************************************/
void PCA9685_setPWM(uint8_t num,uint32_t on,uint32_t off)
{
        IIC_Start();

        Send_Byte(PCA_Addr);
        I2C_WaitAck();

        Send_Byte(LED0_ON_L+4*num);
        I2C_WaitAck();

        Send_Byte(on&0xFF);
        I2C_WaitAck();

        Send_Byte(on>>8);
        I2C_WaitAck();

        Send_Byte(off&0xFF);
        I2C_WaitAck();

        Send_Byte(off>>8);
        I2C_WaitAck();

        IIC_Stop();

}



/******************************************************************
 * 函 数 名 称:PCA9685_setFreq
 * 函 数 说 明:设置PCA9685的输出频率
 * 函 数 形 参:freq
 * 函 数 返 回:无
 * 作       者:LC
 * 备       注:
floor语法:
FLOOR(number, significance)
    Number必需。要舍入的数值。
    Significance必需。要舍入到的倍数。
说明
    将参数 number 向下舍入(沿绝对值减小的方向)为最接近的 significance 的倍数。
    如果任一参数为非数值型,则 FLOOR 将返回错误值 #VALUE!。
    如果 number 的符号为正,且 significance 的符号为负,则 FLOOR 将返回错误值 #NUM!
示例
    公式                                说明                                                                结果
    FLOOR(3.7,2)                将 3.7 沿绝对值减小的方向向下舍入,使其等于最接近的 2 的倍数                2
    FLOOR(-2.5, -2)                将 -2.5 沿绝对值减小的方向向下舍入,使其等于最接近的 -2 的倍数                -2
******************************************************************/
void PCA9685_setFreq(float freq)
{
        uint8_t prescale,oldmode,newmode;

        double prescaleval;

//        freq *= 0.9;  // Correct for overshoot in the frequency setting (see issue #11).

//        PCA9685的内部时钟频率是25Mhz
//        公式: presale_Volue = round( 25000000/(4096 * update_rate) ) - 1
//        round = floor();  floor是数学函数,需要导入 math.h 文件
//        update_rate = freq;
        prescaleval = 25000000;
        prescaleval /= 4096;
        prescaleval /= freq;
        prescaleval -= 1;
        prescale = floor(prescaleval+0.5f);

        //返回MODE1地址上的内容(保护其他内容)
        oldmode = PCA9685_Read(PCA_Model);

        //在MODE1中设置SLEEP位
        newmode = (oldmode&0x7F)|0x10;
        //将更改的MODE1的值写入MODE1地址,使芯片睡眠
        PCA9685_Write(PCA_Model,newmode);
        //写入我们计算的设置频率的值
        //PCA_Pre = presale 地址是0xFE,可以数据手册里查找到
        PCA9685_Write(PCA_Pre,prescale);
        //重新复位
        PCA9685_Write(PCA_Model,oldmode);
        //等待复位完成
        delay_1ms(5);
        //设置MODE1寄存器开启自动递增
        PCA9685_Write(PCA_Model,oldmode|0xa1);
}

//
/******************************************************************
 * 函 数 名 称:setAngle
 * 函 数 说 明:设置角度
 * 函 数 形 参:num要设置的PWM引脚     angle设置的角度
 * 函 数 返 回:无
 * 作       者:LC
 * 备       注:无
******************************************************************/
void setAngle(uint8_t num,uint8_t angle)
{
        uint32_t off = 0;

        off = (uint32_t)(158+angle*2.2);

        PCA9685_setPWM(num,0,off);
}

/******************************************************************
 * 函 数 名 称:PCA9685_Init
 * 函 数 说 明:PCA9685初始化,所有PWM输出频率配置与所有PWM引脚输出的舵机角度
 * 函 数 形 参:hz设置的初始频率  angle设置的初始角度
 * 函 数 返 回:无
 * 作       者:LC
 * 备       注:无
******************************************************************/
void PCA9685_Init(float hz,uint8_t angle)
{
        uint32_t off = 0;

        PCA9685_GPIO_Init();

        //在MODE1地址上写0x00
        PCA9685_Write(PCA_Model,0x00);        //这一步很关键,如果没有这一步PCA9685就不会正常工作。

//        pwm.setPWMFreq(SERVO_FREQ)函数主要是设置PCA9685的输出频率,
//        PCA9685的16路PWM输出频率是一致的,所以是不能实现不同引脚不同频率的。
//        下面是setPWMFreq函数的内容,主要是根据频率计算PRE_SCALE的值。
        PCA9685_setFreq(hz);
        //计算角度
        off = (uint32_t)(145+angle*2.4);

        //控制16个舵机输出off角度
        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_1ms(100);

}

在文件bsp_pca9685.h中,编写如下代码。

/*
 * Change Logs:
 * Date           Author       Notes
 * 2024-06-25     LCKFB-LP    first version
 */

#ifndef _BSP_PCA9685_H_
#define _BSP_PCA9685_H_

#include "board.h"


//端口移植
#define RCC_PCA9685_GPIO_ENABLE()   __RCC_GPIOA_CLK_ENABLE()
#define PORT_PCA9685                CW_GPIOA

#define GPIO_SDA                    GPIO_PIN_5
#define GPIO_SCL                    GPIO_PIN_6

//设置SDA输出模式
#define SDA_OUT()   {        \
                        GPIO_InitTypeDef GPIO_InitStruct;                \
                        GPIO_InitStruct.Pins = GPIO_SDA;                 \
                        GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;      \
                        GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;         \
                        GPIO_Init(PORT_PCA9685, &GPIO_InitStruct);       \
                     }
//设置SDA输入模式
#define SDA_IN()    {        \
                        GPIO_InitTypeDef GPIO_InitStruct;                \
                        GPIO_InitStruct.Pins = GPIO_SDA;                 \
                        GPIO_InitStruct.Mode = GPIO_MODE_INPUT;          \
                        GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;         \
                        GPIO_Init(PORT_PCA9685, &GPIO_InitStruct);       \
                    }
//获取SDA引脚的电平变化
#define SDA_GET()       GPIO_ReadPin(PORT_PCA9685, GPIO_SDA)
//SDA与SCL输出
#define SDA(x)          GPIO_WritePin(PORT_PCA9685, GPIO_SDA, (x?GPIO_Pin_SET:GPIO_Pin_RESET) )
#define SCL(x)          GPIO_WritePin(PORT_PCA9685, GPIO_SCL, (x?GPIO_Pin_SET:GPIO_Pin_RESET) )

#define PCA_Addr              0x80        //IIC地址
#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,uint8_t angle);
void setAngle(uint8_t num,uint8_t angle);
void PCA9685_setFreq(float freq);
void PCA9685_setPWM(uint8_t num,uint32_t on,uint32_t off);

#endif

四、移植验证

在自己工程中的main主函数中,编写如下。

/*
 * Change Logs:
 * Date           Author       Notes
 * 2024-06-25     LCKFB-LP    first version
 */
#include "board.h"
#include "stdio.h"
#include "bsp_uart.h"
#include "bsp_pca9685.h"

int32_t main(void)
{
    uint8_t i = 0;

    board_init();

    uart1_init(115200);

    printf("start\r\n");

    PCA9685_Init(60,0);        //PCA9685--16路舵机初始化  频率60Hz -- 0度
    delay_ms(1000);
    while(1)
    {
        i = ( i + 1 ) % 180;
        setAngle(0,i);
        delay_ms(10);
    }
}

移植现象:0号接口的舵机从0度一直移动到180度后,又回到0度。

动图封面

模块移植成功案例代码:

链接:https://pan.baidu.com/s/1UrA4XVIjnRYQAL4bSxNIfg?pwd=LCKF

提取码:LCKF

51单片机 驱动16模块 PWM/ 舵机驱动板 控制器 机器人 IIC PCA9685 部分程序如下 #include <reg52.h> #include <intrins.h> #include <stdio.h> #include <math.h> typedef unsigned char uchar; typedef unsigned int uint; sbit scl=P1^3; //时钟输入线 sbit sda=P1^4; //数据输入/输出端 sbit KEY1=P2^0; sbit KEY2=P2^1; #define PCA9685_adrr 0x80// 1+A5+A4+A3+A2+A1+A0+w/r //片选地址,将焊接点置1可改变地址, // 当IIC总 呱嫌 多片PCA9685或相同地址时才需焊接 // #define PCA9685_SUBADR1 0x2 // #define PCA9685_SUBADR2 0x3 // #define PCA9685_SUBADR3 0x4 #define PCA9685_MODE1 0x0 #define PCA9685_PRESCALE 0xFE #define LED0_ON_L 0x6 #define LED0_ON_H 0x7 #define LED0_OFF_L 0x8 #define LED0_OFF_H 0x9 // #define ALLLED_ON_L 0xFA // #define ALLLED_ON_H 0xFB // #define ALLLED_OFF_L 0xFC // #define ALLLED_OFF_H 0xFD #define SERVOMIN 115 // this is the 'minimum' pulse length count (out of 4096) #define SERVOMAX 590 // this is the 'maximum' pulse length count (out of 4096) #define SERVO000 130 //0度对应4096的脉宽计数值 #define SERVO180 520 //180度对应4096的脉宽计算值,四个值可根据不同舵机修改 /**********************函数的声明*********************************/ /*--------------------------------------------------------------- 毫秒延时函数 ----------------------------------------------------------------*/ void delayms(uint z) { uint x,y; for(x=z;x>0;x--) for(y=148;y>0;y--); } /*--------------------------------------------------------------- IIC总线所需的通用函数 ----------------------------------------------------------------*/ /*--------------------------------------------------------------- 微妙级别延时函数 大于4.7us ----------------------------------------------------------------*/ void delayus() { _nop_(); //在intrins.h文件里 _nop_(); _nop_(); _nop_(); _nop_(); } /*--------------------------------------------------------------- IIC总线初始化函数 ----------------------------------------------------------------*/ void init() { sda=1; //sda scl使用前总是被拉高 delayus(); scl=1; delayus(); } /*--------------------------------------------------------------- IIC总线启动信号函数 ----------------------------------------------------------------*/ void start() { sda=1; delayus(); scl=1; //scl拉高时 sda突然来个低电平 就启动了IIC总线 delayus(); sda=0; delayus(); scl=0; delayus(); } /*--------------------------------------------------------------- IIC总线停止信号函数 ----------------------------------------------------------------*/ void stop() { sda=0; delayus(); scl=1; //scl拉高时 sda突然来个高电平 就停止了IIC总线 delayus(); sda=1; delayus(); } /*--------------------------------------------------------------- IIC总线应答信号函数 ----------------------------------------------------------------*/ void ACK() { uchar i; scl=1; delayus(); while((sda=1)&&(i<255)) i++; scl=0; delayus(); } /*--------------------------------------------------------------- 写一个字节,无返回值,需输入一个字节值 ----------------------------------------------------------------*/ void write_byte(uchar byte) { uchar i,temp; temp=byte; for(i=0;i<8;i++) { temp=temp<<1; scl=0; delayus(); sda=CY; delayus(); scl=1; delayus(); } scl=0; delayus(); sda=1; delayus(); } /*--------------------------------------------------------------- 读一个字节函数,有返回值 ----------------------------------------------------------------*/ uchar read_byte() { uchar i,j,k; scl=0; delayus(); sda=1; delayus(); for(i=0;i<8;i++) { delayus(); scl=1; delayus(); if(sda==1) { j=1; } else j=0; k=(k<< 1)|j; scl=0; } delayus(); return k; } /*--------------------------------------------------------------- 有关PCA9685模块的函数 ----------------------------------------------------------------*/ /*--------------------------------------------------------------- 向PCA9685里写地址,数据 ----------------------------------------------------------------*/ void PCA9685_write(uchar address,uchar date) { start(); write_byte(PCA9685_adrr); //PCA9685的片选地址 ACK(); write_byte(address); //写地址控制字节 ACK(); write_byte(date); //写数据 ACK(); stop(); } /*--------------------------------------------------------------- 从PCA9685里的地址值中读数据(有返回值) ----------------------------------------------------------------*/ uchar PCA9685_read(uchar address) { uchar date; start(); write_byte(PCA9685_adrr); //PCA9685的片选地址 ACK(); write_byte(address); ACK(); start(); write_byte(PCA9685_adrr|0x01); //地址的第八位控制数据流方向,就是写或读 ACK(); date=read_byte(); stop(); return date; }
淘宝上卖的16PWM舵机驱动模块的51单片机程序 部分程序如下 #include #include #include #include typedef unsigned char uchar; typedef unsigned int uint; sbit scl=P1^3; //时钟输入线 sbit sda=P1^4; //数据输入/输出端 sbit KEY1=P2^0; sbit KEY2=P2^1; #define PCA9685_adrr 0x80// 1+A5+A4+A3+A2+A1+A0+w/r //片选地址,将焊接点置1可改变地址, // 当IIC总 呱嫌 多片PCA9685或相同地址时才需焊接 // #define PCA9685_SUBADR1 0x2 // #define PCA9685_SUBADR2 0x3 // #define PCA9685_SUBADR3 0x4 #define PCA9685_MODE1 0x0 #define PCA9685_PRESCALE 0xFE #define LED0_ON_L 0x6 #define LED0_ON_H 0x7 #define LED0_OFF_L 0x8 #define LED0_OFF_H 0x9 // #define ALLLED_ON_L 0xFA // #define ALLLED_ON_H 0xFB // #define ALLLED_OFF_L 0xFC // #define ALLLED_OFF_H 0xFD #define SERVOMIN 115 // this is the 'minimum' pulse length count (out of 4096) #define SERVOMAX 590 // this is the 'maximum' pulse length count (out of 4096) #define SERVO000 130 //0度对应4096的脉宽计数值 #define SERVO180 520 //180度对应4096的脉宽计算值,四个值可根据不同舵机修改 /**********************函数的声明*********************************/ /*--------------------------------------------------------------- 毫秒延时函数 ----------------------------------------------------------------*/ void delayms(uint z) { uint x,y; for(x=z;x>0;x--) for(y=148;y>0;y--); } /*--------------------------------------------------------------- IIC总线所需的通用函数 ----------------------------------------------------------------*/ /*--------------------------------------------------------------- 微妙级别延时函数 大于4.7us ----------------------------------------------------------------*/ void delayus() { _nop_(); //在intrins.h文件里 _nop_(); _nop_(); _nop_(); _nop_(); } /*--------------------------------------------------------------- IIC总线初始化函数 ----------------------------------------------------------------*/ void init() { sda=1; //sda scl使用前总是被拉高 delayus(); scl=1; delayus(); } /*--------------------------------------------------------------- IIC总线启动信号函数 ----------------------------------------------------------------*/ void start() { sda=1; delayus(); scl=1; //scl拉高时 sda突然来个低电平 就启动了IIC总线 delayus(); sda=0; delayus(); scl=0; delayus(); } /*--------------------------------------------------------------- IIC总线停止信号函数 ----------------------------------------------------------------*/ void stop() { sda=0; delayus(); scl=1; //scl拉高时 sda突然来个高电平 就停止了IIC总线 delayus(); sda=1; delayus(); } /*--------------------------------------------------------------- IIC总线应答信号函数 ----------------------------------------------------------------*/ void ACK() { uchar i; scl=1; delayus(); while((sda=1)&&(i<255)) i++; scl=0; delayus(); } /*--------------------------------------------------------------- 写一个字节,无返回值,需输入一个字节值 ----------------------------------------------------------------*/ void write_byte(uchar byte) { uchar i,temp; temp=byte; for(i=0;i<8;i++) { temp=temp<<1; scl=0; delayus(); sda=CY; delayus(); scl=1; delayus(); } scl=0; delayus(); sda=1; delayus(); } /*--------------------------------------------------------------- 读一个字节函数,有返回值 ----------------------------------------------------------------*/ uchar read_byte() { uchar i,j,k; scl=0; delayus(); sda=1; delayus(); for(i=0;i<8;i++) { delayus(); scl=1; delayus(); if(sda==1) { j=1; } else j=0; k=(k<< 1)|j; scl=0; } delayus(); return k; } /*--------------------------------------------------------------- 有关PCA9685模块的函数 ----------------------------------------------------------------*/ /*--------------------------------------------------------------- 向PCA9685里写地址,数据 ----------------------------------------------------------------*/ void PCA9685_write(uchar address,uchar date) { start(); write_byte(PCA9685_adrr); //PCA9685的片选地址 ACK(); write_byte(address); //写地址控制字节 ACK(); write_byte(date); //写数据 ACK(); stop(); } /*--------------------------------------------------------------- 从PCA9685里的地址值中读数据(有返回值) ----------------------------------------------------------------*/ uchar PCA9685_read(uchar address) { uchar date; start(); write_byte(PCA9685_adrr); //PCA9685的片选地址 ACK(); write_byte(address); ACK(); start(); write_byte(PCA9685_adrr|0x01); //地址的第八位控制数据流方向,就是写或读 ACK(); date=read_byte(); stop(); return date; }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值