STM32控制PCA9685产生16路PWM波控制舵机

本文借鉴了这篇文章STM32--PCA9685驱动(16路舵机驱动模块)-CSDN博客,文章基础知识的介绍已经很详细了。本文采用江科大的软件I2C实现对于芯片的控制,具体代码如下所示:

MyI2C.c文件

cpp 复制代码
#include "stm32f10x.h"                  // Device header
#include "Delay.h"

/*引脚配置层*/

/**
  * 函    数:I2C写SCL引脚电平
  * 参    数:BitValue 协议层传入的当前需要写入SCL的电平,范围0~1
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SCL为低电平,当BitValue为1时,需要置SCL为高电平
  */
void MyI2C_W_SCL(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);		//根据BitValue,设置SCL引脚的电平
	Delay_us(10);												//延时10us,防止时序频率超过要求
}

/**
  * 函    数:I2C写SDA引脚电平
  * 参    数:BitValue 协议层传入的当前需要写入SDA的电平,范围0~1
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SDA为低电平,当BitValue为1时,需要置SDA为高电平
  */
void MyI2C_W_SDA(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);		//根据BitValue,设置SDA引脚的电平,BitValue要实现非0即1的特性
	Delay_us(10);												//延时10us,防止时序频率超过要求
}

/**
  * 函    数:I2C读SDA引脚电平
  * 参    数:无
  * 返 回 值:协议层需要得到的当前SDA的电平,范围0~1
  * 注意事项:此函数需要用户实现内容,当前SDA为低电平时,返回0,当前SDA为高电平时,返回1
  */
uint8_t MyI2C_R_SDA(void)
{
	uint8_t BitValue;
	BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);		//读取SDA电平
	Delay_us(10);												//延时10us,防止时序频率超过要求
	return BitValue;											//返回SDA电平
}

/**
  * 函    数:I2C初始化
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,实现SCL和SDA引脚的初始化
  */
void MyI2C_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);	//开启GPIOB的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);					//将PB10和PB11引脚初始化为开漏输出
	
	/*设置默认电平*/
	GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);			//设置PB10和PB11引脚初始化后默认为高电平(释放总线状态)
}

/*协议层*/

/**
  * 函    数:I2C起始
  * 参    数:无
  * 返 回 值:无
  */
void MyI2C_Start(void)
{
	MyI2C_W_SDA(1);							//释放SDA,确保SDA为高电平
	MyI2C_W_SCL(1);							//释放SCL,确保SCL为高电平
	MyI2C_W_SDA(0);							//在SCL高电平期间,拉低SDA,产生起始信号
	MyI2C_W_SCL(0);							//起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
}

/**
  * 函    数:I2C终止
  * 参    数:无
  * 返 回 值:无
  */
void MyI2C_Stop(void)
{
	MyI2C_W_SDA(0);							//拉低SDA,确保SDA为低电平
	MyI2C_W_SCL(1);							//释放SCL,使SCL呈现高电平
	MyI2C_W_SDA(1);							//在SCL高电平期间,释放SDA,产生终止信号
}

/**
  * 函    数:I2C发送一个字节
  * 参    数:Byte 要发送的一个字节数据,范围:0x00~0xFF
  * 返 回 值:无
  */
void MyI2C_SendByte(uint8_t Byte)
{
	uint8_t i;
	for (i = 0; i < 8; i ++)				//循环8次,主机依次发送数据的每一位
	{
		/*两个!可以对数据进行两次逻辑取反,作用是把非0值统一转换为1,即:!!(0) = 0,!!(非0) = 1*/
		MyI2C_W_SDA(!!(Byte & (0x80 >> i)));//使用掩码的方式取出Byte的指定一位数据并写入到SDA线
		MyI2C_W_SCL(1);						//释放SCL,从机在SCL高电平期间读取SDA
		MyI2C_W_SCL(0);						//拉低SCL,主机开始发送下一位数据
	}
}

/**
  * 函    数:I2C接收一个字节
  * 参    数:无
  * 返 回 值:接收到的一个字节数据,范围:0x00~0xFF
  */
uint8_t MyI2C_ReceiveByte(void)
{
	uint8_t i, Byte = 0x00;					//定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到
	MyI2C_W_SDA(1);							//接收前,主机先确保释放SDA,避免干扰从机的数据发送
	for (i = 0; i < 8; i ++)				//循环8次,主机依次接收数据的每一位
	{
		MyI2C_W_SCL(1);						//释放SCL,主机机在SCL高电平期间读取SDA
		if (MyI2C_R_SDA()){Byte |= (0x80 >> i);}	//读取SDA数据,并存储到Byte变量
													//当SDA为1时,置变量指定位为1,当SDA为0时,不做处理,指定位为默认的初值0
		MyI2C_W_SCL(0);						//拉低SCL,从机在SCL低电平期间写入SDA
	}
	return Byte;							//返回接收到的一个字节数据
}

/**
  * 函    数:I2C发送应答位
  * 参    数:Byte 要发送的应答位,范围:0~1,0表示应答,1表示非应答
  * 返 回 值:无
  */
void MyI2C_SendAck(uint8_t AckBit)
{
	MyI2C_W_SDA(AckBit);					//主机把应答位数据放到SDA线
	MyI2C_W_SCL(1);							//释放SCL,从机在SCL高电平期间,读取应答位
	MyI2C_W_SCL(0);							//拉低SCL,开始下一个时序模块
}

/**
  * 函    数:I2C接收应答位
  * 参    数:无
  * 返 回 值:接收到的应答位,范围:0~1,0表示应答,1表示非应答
  */
uint8_t MyI2C_ReceiveAck(void)
{
	uint8_t AckBit;							//定义应答位变量
	MyI2C_W_SDA(1);							//接收前,主机先确保释放SDA,避免干扰从机的数据发送
	MyI2C_W_SCL(1);							//释放SCL,主机机在SCL高电平期间读取SDA
	AckBit = MyI2C_R_SDA();					//将应答位存储到变量里
	MyI2C_W_SCL(0);							//拉低SCL,开始下一个时序模块
	return AckBit;							//返回定义应答位变量
}

MyI2C.h文件

cpp 复制代码
#ifndef __MYI2C_H
#define __MYI2C_H

void MyI2C_Init(void);
void MyI2C_Start(void);
void MyI2C_Stop(void);
void MyI2C_SendByte(uint8_t Byte);
uint8_t MyI2C_ReceiveByte(void);
void MyI2C_SendAck(uint8_t AckBit);
uint8_t MyI2C_ReceiveAck(void);

#endif

Pca9685.c

cpp 复制代码
#include "Pca9685.h"
#include "MyI2C.h"
#include "Delay.h"
#include "math.h"


#define PCA_ADDRESS 0x80

void PCA_WriteReg(uint8_t RegAddress, uint8_t Data)//向PCA写数据,adrrd地址,data数据
{ 
	MyI2C_Start();						//I2C起始
	MyI2C_SendByte(PCA_ADDRESS);	//发送从机地址,读写位为0,表示即将写入
	MyI2C_ReceiveAck();					//接收应答
	MyI2C_SendByte(RegAddress);			//发送寄存器地址
	MyI2C_ReceiveAck();					//接收应答
	MyI2C_SendByte(Data);				//发送要写入寄存器的数据
	MyI2C_ReceiveAck();					//接收应答
	MyI2C_Stop();						//I2C终止
}
 
uint8_t PCA_ReadReg(uint8_t RegAddress)
{
	uint8_t Data;
	
	MyI2C_Start();						//I2C起始
	MyI2C_SendByte(PCA_ADDRESS);	//发送从机地址,读写位为0,表示即将写入
	MyI2C_ReceiveAck();					//接收应答
	MyI2C_SendByte(RegAddress);			//发送寄存器地址
	MyI2C_ReceiveAck();					//接收应答
	
	MyI2C_Start();						//I2C重复起始
	MyI2C_SendByte(PCA_ADDRESS | 0x01);	//发送从机地址,读写位为1,表示即将读取
	MyI2C_ReceiveAck();					//接收应答
	Data = MyI2C_ReceiveByte();			//接收指定寄存器的数据
	MyI2C_SendAck(1);					//发送应答,给从机非应答,终止从机的数据输出
	MyI2C_Stop();						//I2C终止
	
	return Data;
}

 
 
void PCA_setfreq(float freq)//设置PWM频率
{
		u8 prescale, oldmode, newmode;
		double prescaleval;
		freq *= 0.98; 
		prescaleval = 25000000;
		prescaleval /= 4096;
		prescaleval /= freq;
		prescaleval -= 1;
		prescale = floor(prescaleval + 0.5f);
	
		oldmode = PCA_ReadReg(PCA_MODE1);

		newmode = (oldmode&0x7F) | 0x10; // sleep
	
		PCA_WriteReg(PCA_MODE1, newmode); // go to sleep
		PCA_WriteReg(PCA_PRE, prescale); // set the prescaler
		PCA_WriteReg(PCA_MODE1, oldmode);
		Delay_ms(5);
	
		PCA_WriteReg(PCA_MODE1, oldmode | 0xa1); 
}
 
void PCA_setpwm(u8 channel, u32 on, u32 off)
{
	PCA_WriteReg(LED0_ON_L + 4 * channel, on & 0xFF);
	PCA_WriteReg(LED0_ON_H + 4 * channel, on >> 8);
	PCA_WriteReg(LED0_OFF_L + 4 * channel, off & 0xFF);
	PCA_WriteReg(LED0_OFF_H + 4 * channel, off >> 8);
}
/*
channel:舵机PWM输出引脚0~15;
on:PWM上升计数值0~4096;
off:PWM下降计数值0~4096;
一个PWM周期分成4096份,由0开始+1计数,计到on时跳变为高电平,继续计数到off时
跳变为低电平,直到计满4096重新开始。所以当on不等于0时可作延时,当on等于0时,
off/4096的值就是PWM的占空比。
*/
 
/*
	函数作用:初始化舵机驱动板
	参数:1.PWM频率
		  2.初始化舵机角度
*/
void PCA_MG9XX_Init(float hz, u16 angle)
{
	u32 off = 0;
	u8 i=0;
	MyI2C_Init();	
  PCA_WriteReg(PCA_MODE1, 0x00);    // 软件复位

	PCA_setfreq(hz);//设置PWM频率
	
	off = (u32)(102.4 + angle*1.14);

	for(i=0;i<=0;i++)
    {
        PCA_setpwm(i,0,off);
    }
	Delay_ms(500);
}
 

void PCA_MG9XX(u8 channel, u8 end_angle)
{
	u32 off = 0;

	off = (u32)(102.4 + end_angle * 1.14);
	PCA_setpwm(channel, 0, off);
}

Pca9685.h

cpp 复制代码
#ifndef __PCA9685_H
#define __PCA9685_H	
 
#include "stm32f10x.h"
 
 
#define PCA_MODE1 0x00
#define PCA_PRE 0xFE
 
#define LED0_ON_L 0x6
#define LED0_ON_H 0x7
#define LED0_OFF_L 0x8
#define LED0_OFF_H 0x9
 
 
void PCA_WriteReg(uint8_t RegAddress, uint8_t Data);
uint8_t PCA_ReadReg(uint8_t RegAddress);
void PCA_MG9XX_Init(float hz,u16 angle);
void PCA_setfreq(float freq);
void PCA_setpwm(u8 num, u32 on, u32 off);
void PCA_MG9XX(u8 channel, u8 end_angle);

 
#endif
相关推荐
亿道电子Emdoor4 小时前
【ARM】MDK-快捷键添加及修改
arm开发·stm32·单片机
heater4047 小时前
【STM32】stm32启动流程
stm32·单片机·嵌入式硬件
honey ball9 小时前
滤波器的主要参数
人工智能·单片机·嵌入式硬件·学习
浅陌pa9 小时前
RTC:实时时钟
c语言·stm32·单片机·嵌入式硬件
湘の子10 小时前
汇编语言与接口技术--跑马灯
汇编·stm32·单片机·嵌入式硬件·51单片机
佳心饼干-10 小时前
单片机-蜂鸣器实验
单片机·嵌入式硬件
honey ball12 小时前
大功率PCB设计
单片机·嵌入式硬件
黑果果的思考12 小时前
LED背光驱动芯片RT9293应用电路
嵌入式硬件
逝灮13 小时前
【蓝桥杯——物联网设计与开发】Part2:OLED
驱动开发·stm32·单片机·嵌入式硬件·物联网·蓝桥杯·oled
jjddzyh14 小时前
单片机串口控制
单片机·嵌入式硬件