当你在一个项目中碰到了微控制器芯片的PWM输出引脚不够用的情况,那么这款PCA968516路舵机就能很快帮助您解决这个问题了。只要你的主控芯片具备了I2C通信,就能够让主控芯片和PCA9685通信,实现多个舵机的同时控制了。PCA9685 16路舵机是一个采用I2C通信,内置了PWM驱动器和一个时钟,这个意味着,这将和TLCG940系列有很大不同,你不需要不断发送信号占用你的单片机。它是5V的兼容,这意味你还可以用3.3V单片机控制并且安全地驱动到6V输出(当你想要控制白色或蓝色指示灯用3.4+正电压也是可以的)。地址选择引脚使你可以把62个驱动板挂在单个l2C总线上,总共有992路PWM输出,那将是非常庞大的资源,约1.6Khz可调频PWM输出,为步进电机准备输出12位分辨率,可配置的推拉输出或开路输出,输出使能引脚能够快速禁用所有输出。
模块来源
规格参数
输入电压:3.3V~5V
额定电流:15mA
控制方式:IIC
尺寸: 21(长)*21(宽)[单位:mm]
以上信息见厂家资料文件
移植过程
查看资料
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引脚,该位必须按以下顺序设置:
- 在mode1中设置SLEEP位。这就关闭了内部振荡器,使芯片处于休眠状态。
- 将逻辑1写入MODE1中的SLEEP和EXTCLK位。这样就转换完成了。外部时钟可以在切换期间处于活动状态,因为设置了SLEEP位。
这个位是一个“粘性位”,也就是说,它不能通过写入逻辑0来清除。EXTCLK位只能通过电源循环或软件重置来清除。 占空比或者脉冲宽度的设定
每个PWM引脚输出的开启时间和PWM的占空比可以通过LEDn_ON和LEDn_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 //配置频率地址
2
3
4
5
6
7
8
9
引脚选择
这里选择的引脚见引脚接线表
代码移植
下载为大家准备的驱动代码文件夹,复制到自己工程中\luban-lite\application\rt-thread\helloworld\user-bsp
文件夹下
提示
如果未找到 user-bsp
这个文件夹,说明你未进行模块移植的前置操作。请转移到手册使用必要操作(点击跳转)中进行必要的配置操作!!!
接下来打开自己的工程,开始修改Kconfig文件。
1、在 VSCode 中打开 application\rt-thread\helloworld\Kconfig 文件
2、在该文件的 #endif
前面添加该模块的 Kconfig路径语句
# 16路舵机驱动模块
source "application/rt-thread/helloworld/user-bsp/16ch-servo-drive-module/Kconfig"
2
menuconfig操作
1、我们 双击 luban-lite
文件夹下的 win_env.bat
脚本打开env工具:
2、输入以下命令列出所有可用的默认配置:
scons --list-def
3、选择 d13x_JLC_rt-thread_helloworld
这个配置!这个是我们衡山派开发板的默认配置!输入以下命令即可:
scons --apply-def=7
或者
scons --apply-def=d13x_JLC_rt-thread_helloworld_defconfig
这两个命令作用是一样的,一个是 文件名 ,一个是 编号 !!!
4、输入以下命令进入menuconfig菜单
scons --menuconfig
进入以下界面:
5、选中 Porting code using the LCKFB module
按
Y
选中按
N
取消选中方向键
左右
调整 最下面菜单的选项方向键
上下
调整 列表的选项
回车
执行最下面菜单的选项
6、回车进入 Porting code using the LCKFB module
菜单
7、按方向键 上下
选中 Using 16-channel servo driver module
后按 Y
键,看到前面括号中出现一个 *
号,就可以下一步了。
8、按方向键 左右
选中 <Save>
然后一路回车
,然后 退出
即可
编译
我们 保存并退出menuconfig菜单 之后,输入以下命令进行编译:
scons
或
scons -j16
-j 用来选择参与编译的核心数: 我这里是选择16
大家可以根据自己的电脑来选择
核心越多编译越快
如果写的数量高于电脑本身,那么就自动按照最高可用的来运行!
镜像烧录
编译完成之后会在 \luban-lite\output\d13x_JLC_rt-thread_helloworld\images
文件夹下生成一个 d13x_JLC_v1.0.0.img
镜像文件!
然后我们烧录镜像,具体的教程请查看:镜像烧录(点击跳转🚀)
到这里完成了,请移步到 最后一节 进行移植验证。
工程代码解析
bsp_pca9685.c
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 文档网站:wiki.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 嘉立创社区问答:https://www.jlc-bbs.com/lckfb
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*/
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <getopt.h>
#include <sys/time.h>
#include <rtthread.h>
#include <inttypes.h>
#include <finsh.h>
#include "hal_adcim.h"
#include "rtdevice.h"
#include "aic_core.h"
#include "aic_log.h"
#include "hal_gpai.h"
#include "aic_hal_gpio.h"
#include "hal_i2c.h"
#include "bsp_pca9685.h"
#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 //配置频率地址
#define PCA9685_SCL_PIN_NAME "PE.14"
#define PCA9685_SDA_PIN_NAME "PE.12"
static int PCA9685_SCL_Pin = 0;
static int PCA9685_SDA_Pin = 0;
#define PCA9685_SET_SCL_LEVEL(x) rt_pin_write(PCA9685_SCL_Pin, x ? PIN_HIGH : PIN_LOW)
#define PCA9685_SET_SDA_LEVEL(x) rt_pin_write(PCA9685_SDA_Pin, x ? PIN_HIGH : PIN_LOW)
#define PCA9685_SDA_GET() rt_pin_read(PCA9685_SDA_Pin)
/* 设置SDA开漏输出模式 */
#define SDA_OUT() rt_pin_mode(PCA9685_SDA_Pin, PIN_MODE_OUTPUT_OD)
/* 设置SDA输入模式 */
#define SDA_IN() rt_pin_mode(PCA9685_SDA_Pin, PIN_MODE_INPUT_PULLUP)
static void delay_us(uint32_t us){ aicos_udelay(us); }
static void delay_ms(uint32_t ms){ rt_thread_mdelay(ms); }
/******************************************************************
* 函 数 名 称:PCA9685_GPIO_Init
* 函 数 说 明:初始化GPIO引脚
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LCKFB
* 备 注:无
******************************************************************/
static void PCA9685_GPIO_Init(void)
{
PCA9685_SCL_Pin = rt_pin_get(PCA9685_SCL_PIN_NAME);
PCA9685_SDA_Pin = rt_pin_get(PCA9685_SDA_PIN_NAME);
rt_pin_mode(PCA9685_SCL_Pin, PIN_MODE_OUTPUT_OD);
rt_pin_mode(PCA9685_SDA_Pin, PIN_MODE_OUTPUT_OD);
rt_pin_write(PCA9685_SCL_Pin, PIN_HIGH);
rt_pin_write(PCA9685_SDA_Pin, PIN_HIGH);
}
/******************************************************************
* 函 数 名 称:IIC_Start
* 函 数 说 明:IIC起始时序
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
static void IIC_Start(void)
{
SDA_OUT();
PCA9685_SET_SDA_LEVEL(1);
delay_us(5);
PCA9685_SET_SCL_LEVEL(1);
delay_us(5);
PCA9685_SET_SDA_LEVEL(0);
delay_us(5);
PCA9685_SET_SCL_LEVEL(0);
delay_us(5);
}
/******************************************************************
* 函 数 名 称:IIC_Stop
* 函 数 说 明:IIC停止信号
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
static void IIC_Stop(void)
{
SDA_OUT();
PCA9685_SET_SCL_LEVEL(0);
PCA9685_SET_SDA_LEVEL(0);
PCA9685_SET_SCL_LEVEL(1);
delay_us(5);
PCA9685_SET_SDA_LEVEL(1);
delay_us(5);
}
/******************************************************************
* 函 数 名 称:IIC_Send_Ack
* 函 数 说 明:主机发送应答或者非应答信号
* 函 数 形 参:0发送应答 1发送非应答
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
static void IIC_Send_Ack(unsigned char ack)
{
SDA_OUT();
PCA9685_SET_SCL_LEVEL(0);
PCA9685_SET_SDA_LEVEL(0);
delay_us(5);
if(!ack) PCA9685_SET_SDA_LEVEL(0);
else PCA9685_SET_SDA_LEVEL(1);
PCA9685_SET_SCL_LEVEL(1);
delay_us(5);
PCA9685_SET_SCL_LEVEL(0);
PCA9685_SET_SDA_LEVEL(1);
}
/******************************************************************
* 函 数 名 称:I2C_WaitAck
* 函 数 说 明:等待从机应答
* 函 数 形 参:无
* 函 数 返 回:0有应答 1超时无应答
* 作 者:LC
* 备 注:无
******************************************************************/
static uint8_t I2C_WaitAck(void)
{
char ack = 0;
unsigned char ack_flag = 10;
PCA9685_SET_SCL_LEVEL(0);
PCA9685_SET_SDA_LEVEL(1);
SDA_IN();
delay_us(5);
PCA9685_SET_SCL_LEVEL(1);
delay_us(5);
while( (PCA9685_SDA_GET()==1) && ( ack_flag ) )
{
ack_flag--;
delay_us(5);
}
if( ack_flag <= 0 )
{
IIC_Stop();
return 1;
}
else
{
PCA9685_SET_SCL_LEVEL(0);
SDA_OUT();
}
return ack;
}
/******************************************************************
* 函 数 名 称:Send_Byte
* 函 数 说 明:写入一个字节
* 函 数 形 参:dat要写人的数据
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
static void Send_Byte(uint8_t dat)
{
int i = 0;
SDA_OUT();
PCA9685_SET_SCL_LEVEL(0);//拉低时钟开始数据传输
for( i = 0; i < 8; i++ )
{
PCA9685_SET_SDA_LEVEL( (dat & 0x80) >> 7 );
delay_us(1);
PCA9685_SET_SCL_LEVEL(1);
delay_us(5);
PCA9685_SET_SCL_LEVEL(0);
delay_us(5);
dat<<=1;
}
}
/******************************************************************
* 函 数 名 称:Read_Byte
* 函 数 说 明:IIC读时序
* 函 数 形 参:无
* 函 数 返 回:读到的数据
* 作 者:LC
* 备 注:无
******************************************************************/
static uint8_t Read_Byte(void)
{
unsigned char i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
PCA9685_SET_SCL_LEVEL(0);
delay_us(5);
PCA9685_SET_SCL_LEVEL(1);
delay_us(5);
receive<<=1;
if( PCA9685_SDA_GET() )
{
receive|=1;
}
delay_us(5);
}
PCA9685_SET_SCL_LEVEL(0);
return receive;
}
/******************************************************************
* 函 数 名 称:PCA9685_Write
* 函 数 说 明:向PCA9685写命令或数据
* 函 数 形 参:addr写入的寄存器地址 data写入的命令或数据
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
static 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
* 备 注:无
******************************************************************/
static 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_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 文件
pdate_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_ms(5);
//设置MODE1寄存器开启自动递增
PCA9685_Write(PCA_Model,oldmode|0xa1);
}
/******************************************************************
* 函 数 名 称:PCA9685_SetPWM
* 函 数 说 明:初始化
* 函 数 形 参:Num:设置第几个引脚输出,范围0~15
* On :默认为0
* Angle:舵机旋转角度,范围:0~180
* 函 数 返 回:RT_EOK成功 -RT_ERROR失败
* 作 者:LCKFB
* 备 注:无
******************************************************************/
int PCA9685_SetPWM(uint8_t Num, uint32_t On, uint32_t Angle)
{
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(Angle&0xFF);
I2C_WaitAck();
Send_Byte(Angle>>8);
I2C_WaitAck();
IIC_Stop();
return RT_EOK;
}
/******************************************************************
* 函 数 名 称:PCA9685_SetAngle
* 函 数 说 明:设置角度
* 函 数 形 参:Num要设置的PWM引脚 Angle设置的角度
* 函 数 返 回:RT_EOK成功 -RT_ERROR失败
* 作 者:LCKFB
* 备 注:无
******************************************************************/
int PCA9685_SetAngle(uint8_t Num, uint8_t Angle)
{
uint32_t off = 0;
off = (uint32_t)(158+Angle*2.2);
PCA9685_SetPWM(Num,0,off);
return RT_EOK;
}
/******************************************************************
* 函 数 名 称:PCA9685_Init
* 函 数 说 明:初始化
* 函 数 形 参:Hz初始频率 Angle初始角度
* 函 数 返 回:RT_EOK成功 -RT_ERROR失败
* 作 者:LCKFB
* 备 注:无
******************************************************************/
int PCA9685_Init(uint32_t 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_ms(100);
return RT_EOK;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
bsp_pca9685.h
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 文档网站:wiki.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 嘉立创社区问答:https://www.jlc-bbs.com/lckfb
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*/
#ifndef __BSP_PCA9685_H__
#define __BSP_PCA9685_H__
#include "stdio.h"
int PCA9685_Init(uint32_t Hz, uint8_t Angle);
int PCA9685_SetPWM(uint8_t Num, uint32_t On, uint32_t Angle);
int PCA9685_SetAngle(uint8_t Num, uint8_t Angle);
#endif
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Kconfig
这个是一个menuconfig中的选项,如果在菜单中选中该选项,就会在rtconfig.h
中定义一个语句,用来if判断条件编译之类的。
config LCKFB_16_CH_SERVO_DRIVE_MODULE
bool "Using 16-channel servo driver module"
default n
help
More information is available at: https://wiki.lckfb.com/
2
3
4
5
6
SConscript
自动化构建文件,如果定义了 LCKFB_16_CH_SERVO_DRIVE_MODULE
和 USING_LCKFB_TRANSPLANT_CODE
就自动编译当前目录下的文件!!
Import('RTT_ROOT')
Import('rtconfig')
import rtconfig
from building import *
cwd = GetCurrentDir()
CPPPATH = [cwd]
src = []
if GetDepend('LCKFB_16_CH_SERVO_DRIVE_MODULE') and GetDepend('USING_LCKFB_TRANSPLANT_CODE'):
src = Glob(os.path.join(cwd, '*.c'))
group = DefineGroup('lckfb-16-ch-servo-drive-module', src, depend = [''], CPPPATH = CPPPATH)
Return('group')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
test_16ch_servo_drive_module.c
这个文件定义了一个处理PCA9685 16通道PWM驱动模块的线程,初始化了PCA9685模块的板级支持包(BSP),并设置了线程的优先级、栈大小和时间片。
线程的主要任务是控制PCA9685模块输出PWM信号,从而控制连接到PCA9685的舵机或其他PWM控制设备。在这个例子中,它将循环设置PCA9685的第一个通道的角度,从而实现舵机的角度控制。
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <getopt.h>
#include <sys/time.h>
#include <rtthread.h>
#include "rtdevice.h"
#include "aic_core.h"
#include "aic_hal_gpio.h"
#include "bsp_pca9685.h"
#define THREAD_PRIORITY 25 // 线程优先级
#define THREAD_STACK_SIZE 1024 // 线程大小
#define THREAD_TIMESLICE 20 // 时间片
static rt_thread_t pca9685_thread = RT_NULL; // 线程控制块
// 线程入口函数
static void pca9685_thread_entry(void *param)
{
int ret = 0;
int i = 0;
int while_count = 1; // 循环次数
/* PCA9685初始化 */
ret = PCA9685_Init(60, 0);
if(ret != RT_EOK)
{
LOG_E("failed to PCA9685_Init !!");
return;
}
rt_thread_mdelay(1000);
rt_kprintf("Start Loop !!\n");
while(while_count++)
{
i = ( i + 1 ) % 180;
PCA9685_SetAngle(0, i);
if(while_count >= 5000)
{
while_count = 1;
rt_kprintf("\nType [test_exit_pca9685_driver_module] command to exit PCA9685 to read data\n");
rt_kprintf("Note: Pressing [TAB] as you type will autocomplete the command\n");
rt_thread_mdelay(2000);
}
rt_thread_mdelay(50);
}
rt_kprintf("\nEND!!\n");
}
/* PCA9685启动函数 */
static void test_pca9685_driver_module(int argc, char **argv)
{
/* 创建线程,名称是 pca9685_thread,入口是 pca9685_thread_entry */
pca9685_thread = rt_thread_create("pca9685_thread1",
pca9685_thread_entry, RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIORITY, THREAD_TIMESLICE);
/* 如果获得线程控制块,启动这个线程 */
if (pca9685_thread != RT_NULL)
rt_thread_startup(pca9685_thread);
}
// 导出函数为命令
MSH_CMD_EXPORT(test_pca9685_driver_module, run PCA9685 16 ch driver module);
/* PCA9685退出函数 */
static void test_exit_pca9685_driver_module(void)
{
int ret = rt_thread_delete(pca9685_thread);
if(ret != RT_EOK)
{
LOG_E("failed to test_exit_pca9685_driver_module !!");
}
else
{
rt_kprintf("\n========PCA9685 exit successful !!========\n");
}
}
// 导出函数为命令
MSH_CMD_EXPORT(test_exit_pca9685_driver_module, quit PCA9685);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
线程入口函数逻辑
- 定义了几个变量用于存储返回状态、循环计数和角度。
- 调用
PCA9685_Init
函数初始化PCA9685模块,并设置PWM频率。 - 在一个无限循环中,通过
PCA9685_SetAngle
函数设置第一个通道的角度,角度从0度循环增加到180度,然后重新开始。 - 每当循环次数达到5000次时,提示用户可以通过输入命令来退出PCA9685的数据处理循环。
- 在每次循环结束时,线程会挂起一段时间,这里是50毫秒。
此外,该文件还定义了两个命令,test_pca9685_driver_module
和test_exit_pca9685_driver_module
,它们分别用于启动和退出PCA9685 16通道PWM驱动模块线程。
test_pca9685_driver_module
命令创建并启动一个名为pca9685_thread
的线程,该线程将执行pca9685_thread_entry
函数。test_exit_pca9685_driver_module
命令用于删除pca9685_thread
线程,从而退出PWM驱动模块的数据处理循环,并打印退出成功的消息。
以下是代码中的关键点:
PCA9685_Init
函数用于初始化PCA9685模块。PCA9685_SetAngle
函数用于设置PCA9685通道的角度。aicos_mdelay
函数用于实现毫秒级的延时。LOG_E
宏用于打印错误信息。MSH_CMD_EXPORT
宏用于将函数导出为命令行接口,使得用户可以在命令行中直接调用这些函数。
通过在命令行中输入test_pca9685_driver_module
,用户可以启动PCA9685 16通道PWM驱动模块的数据处理线程,并且每当角度更新时,都会在PCA9685模块上看到相应的PWM信号变化。输入test_exit_pca9685_driver_module
命令可以退出数据处理线程。
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <getopt.h>
#include <sys/time.h>
#include <rtthread.h>
#include "rtdevice.h"
#include "aic_core.h"
#include "aic_hal_gpio.h"
#include "bsp_pca9685.h"
#define THREAD_PRIORITY 25 // 线程优先级
#define THREAD_STACK_SIZE 1024 // 线程大小
#define THREAD_TIMESLICE 20 // 时间片
static rt_thread_t pca9685_thread = RT_NULL; // 线程控制块
// 线程入口函数
static void pca9685_thread_entry(void *param)
{
int ret = 0;
int i = 0;
int while_count = 1; // 循环次数
/* PCA9685初始化 */
ret = PCA9685_Init(60, 0);
if(ret != RT_EOK)
{
LOG_E("failed to PCA9685_Init !!");
return;
}
aicos_mdelay(1000);
rt_kprintf("Start Loop !!\n");
while(while_count++)
{
i = ( i + 1 ) % 180;
PCA9685_SetAngle(0, i);
if(while_count >= 5000)
{
while_count = 1;
rt_kprintf("\nType [test_exit_pca9685_driver_module] command to exit PCA9685 to read data\n");
rt_kprintf("Note: Pressing [TAB] as you type will autocomplete the command\n");
aicos_mdelay(2000);
}
aicos_mdelay(50);
}
rt_kprintf("\nEND!!\n");
}
/* PCA9685启动函数 */
static void test_pca9685_driver_module(int argc, char **argv)
{
/* 创建线程,名称是 pca9685_thread,入口是 pca9685_thread_entry */
pca9685_thread = rt_thread_create("pca9685_thread1",
pca9685_thread_entry, RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIORITY, THREAD_TIMESLICE);
/* 如果获得线程控制块,启动这个线程 */
if (pca9685_thread != RT_NULL)
rt_thread_startup(pca9685_thread);
}
// 导出函数为命令
MSH_CMD_EXPORT(test_pca9685_driver_module, run PCA9685 16 ch driver module);
/* PCA9685退出函数 */
static void test_exit_pca9685_driver_module(void)
{
int ret = rt_thread_delete(pca9685_thread);
if(ret != RT_EOK)
{
LOG_E("failed to test_exit_pca9685_driver_module !!");
}
else
{
rt_kprintf("\n========PCA9685 exit successful !!========\n");
}
}
// 导出函数为命令
MSH_CMD_EXPORT(test_exit_pca9685_driver_module, quit PCA9685);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
移植验证
我们使用串口调试,将 USB转TTL模块 连接到衡山派开发板上面!!
具体的教程查看:串口调试(点击跳转🚀)
串口波特率默认为
115200
我们在输入下面的命令运行该模块的线程:
输入的时候按下
TAB键
会进行命令补全!!
test_pca9685_driver_module