cubemx stm32 pca9685pw模块 16路PWM 可用于舵机驱动 驱动代码

20 篇文章 2 订阅
15 篇文章 0 订阅

资料

淘宝链接请点这里
淘宝资料资料:
链接:https://pan.baidu.com/s/1Kda-c7QdZdQ03FBMa0zeRA
提取码:1234

pca9685pw介绍

这个模块是 I2C 通信控制 16 路 PWM 的模块。
所有路的 频率 是统一设置的,所以每一路的频率都一样,但是每一路可以设置不同的占空比。

  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。

iic地址

硬件

硬件连接

  • OE,低电平有效输出使能。
  • SDA,I2C 的 SDA 引脚。
  • SCL,I2C 的 SCL 引脚。
  • V+,接入 5V 电源,若是驱动的 16 路全是舵机,这个电源的电流就要求很大了(不晓得这个模块到底能不能承受这么多舵机的电流)。
  • VCC,单片机 3V3 电源。
  • GND,对于 V+ 的地平面。

原理图截屏如下:
pca9685pw模块原理图

软件编写

pca9685pw.c

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

#define min(_X_, _Y_)	(_X_>=_Y_?_Y_:_X_)

static uint8_t read16(uint8_t addr);
static void write16(uint8_t addr, uint8_t d);


// 设置为true可打印一些调试消息,设置为false可禁用这些消息。
#define ENABLE_DEBUG_OUTPUT false

// 初始化。频率,转动角度(0~360)
void pca9685_init(float hz, uint16_t angle)
{
	uint32_t off = 0;
	// 这一步很关键,如果没有这一步PCA9685就不会正常工作。
	pca9685_reset();
	
	write16(PCA9685_MODE1,0x00);
	pca9685_setPWMFreq(hz);
	off = (uint32_t)(102.4+angle*1.14);
	
	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);
	
	HAL_Delay(100);
}

// 软件复位
void pca9685_reset(void)
{
	write16(PCA9685_MODE1, 0x0);
}

// 设置PCA9685的输出频率,
// PCA9685的16路PWM输出频率是一致的,
// 所以是不能实现不同引脚不同频率的。
void pca9685_setPWMFreq(float freq)
{
	//printf("Attempting to set freq ");
	//printf(freq);
	// 输出周期实际是有误差的,对于20ms(50Hz)的周期来说这里乘个0.94为20ms
	// 可以用示波器观看调试
	freq *= 0.942;
	double prescaleval = 25000000;
	prescaleval /= 4096;
	prescaleval /= freq;
	prescaleval -= 1;

	if (ENABLE_DEBUG_OUTPUT)
	{
		printf("Estimated pre-scale: %f\r\n", prescaleval);
	}
	// floor()总是返回小于等于一个给定数字的最大整数。
	// 这里是四舍五入
	uint8_t prescale = floor(prescaleval + 0.5);
	
	if (ENABLE_DEBUG_OUTPUT)
	{
		printf("Final pre-scale: %d\r\n", prescale);
	}

	uint8_t oldmode = read16(PCA9685_MODE1);
	uint8_t newmode = (oldmode&0x7F) | 0x10; // sleep
	write16(PCA9685_MODE1, newmode); // go to sleep
	write16(PCA9685_PRESCALE, prescale); // set the prescaler
	write16(PCA9685_MODE1, oldmode);
	HAL_Delay(5);
	write16(PCA9685_MODE1, oldmode | 0xa1);  //  This sets the MODE1 register to turn on auto increment.
										  // This is why the beginTransmission below was not working.
	//  printf("Mode now 0x"); printf(read16(PCA9685_MODE1), HEX);
}

// 输出PWM占空比的调节。通常on都设为0,改变off即可。
// 因为PCA9685是12位分辨率,所以off的值0~4096就代表了占空比0-100.
void pca9685_setPWM(uint8_t num, uint16_t on, uint16_t off)
{
	//printf("Setting PWM "); printf(num); printf(": "); printf(on); printf("->"); printf(off);
	uint8_t d[4] = {(on&0xFF), (on>>8), (off&0xFF), (off>>8)};
	
	HAL_I2C_Mem_Write(&PCA9685_HI2C, PCA9685_IIC_ADDR_W, (LED0_ON_L+4*num), 1, d, 4, 0xff);
}

// num:序号;angle:角度
void setAngle(uint8_t num, uint16_t angle)
{
	// 补误差
//	angle /= 2;
	uint32_t off = 0;

	// off范围是:0~4096
	// 舵机占空比范围是:0.5~2.5ms
	// 所以设置off在 102.4~512范围内
	// x/(512-102.4) = 1/360
	// 所以每一度步进值为:1.137777 约等于 1.14
	off = (uint32_t)(102.4+angle*1.14);
	pca9685_setPWM(num, 0, off);
}

static uint8_t read16(uint8_t addr)
{
	uint8_t d;
	HAL_I2C_Mem_Read(&PCA9685_HI2C, PCA9685_IIC_ADDR_R, addr, 1, &d, 1, 0xff);
	return d;
}

static void write16(uint8_t addr, uint8_t d)
{
	HAL_I2C_Mem_Write(&PCA9685_HI2C, PCA9685_IIC_ADDR_W, addr, 1, &d, 1, 0xff);
}

pca9685pw.h

#ifndef _PCA9685_PW_H
#define _PCA9685_PW_H

#include "stdbool.h"
#include "main.h"
#include "i2c.h"

// I2C 句柄,用户自己修改
#define PCA9685_HI2C	hi2c1
// I2C 地址
#define PCA9685_IIC_ADDR_W	0x80
#define PCA9685_IIC_ADDR_R	0x81


// 寄存器地址
#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



// 函数
void pca9685_reset(void);
void pca9685_setPWMFreq(float freq);
void pca9685_setPWM(uint8_t num, uint16_t on, uint16_t off);
void pca9685_setPin(uint8_t num, uint16_t val, bool invert);// =false


// 初始化。频率,转动角度(0~360)
void pca9685_init(float hz, uint16_t angle);
// num:序号;angle:角度(0~360)
void setAngle(uint8_t num,uint16_t angle);

#endif	/* _PCA9685_PW_H */

main

void main(void)
{
	uint16_t xxx = 0;
	// 初始50Hz,角度 180 度
	pca9685_init(50, 180);

	while(1)
	{
		// 设置 1 号口输出
		setAngle(1, xxx);
		xxx += 10;
		xxx>360?xxx=0:xxx;
		HAL_Delay(10);
	}
}

参考

PCA9685模块使用(Arduino和STM32)

淘宝上卖的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; }
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; }
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嵌入一下?

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值