PCA9685使用终极总结

一、硬件

在这里插入图片描述

在这里插入图片描述

  • 可以扩展出16路PWM波通道,这16路的PWM的频率是相同的,但是这16路的占空比是可以独立设定的。
  • 地址设定,A0~A4都浮空,地址就是0x40,实际使用会左移一位就是0x80。
  • 典型频率为24 Hz到1526 Hz,占空比的范围是0~4095(12位)。

PWM目标频率值的计算

芯片的频率为:25MHz 即 25000000Hz
在这里插入图片描述
这个写入寄存器的值怎么算出来的呢?

F r e = r o u n d ( 25000000 / ( 4095 ∗ H z ) ) − 1 Fre = round(25000000/(4095*Hz))-1 Fre=round(25000000/(4095Hz))1

25MHz除以4095乘以PWM的频率的积,对他们取最近整数,然后减1。
就是填入寄存器的频率值。

填入寄存器的占空比的值怎么计算?

V d u t y = 4095 ∗ ( D u t y / 100 ) Vduty = 4095*(Duty/100) Vduty=4095(Duty/100)

角度怎么计算?

设置好PWMd 频率为50Hz,才能用下面的公式:

V a n g l e = 4095 ∗ ( ( a n g l e / 180.0 ) ∗ 2.0 + 0.5 ) / 20.0 Vangle = 4095*((angle/180.0)*2.0+0.5)/20.0 Vangle=4095((angle/180.0)2.0+0.5)/20.0
它是基于下面的参数来的:

0.5ms-------------0度
1.0ms-------------45度
1.5ms-------------90度
2.0ms-------------135度
2.5ms-------------180度

二、控制舵机

2.1 SG90舵机的使用

(1).采用PWM控制的方式来进行舵机的操纵
(2).舵机的控制需要MCU产生一个20ms的脉冲信号,以0.5ms到2.5ms的高电平来控制舵机的角度
(3).数据:

0.5ms-------------0度; 2.5% 对应函数中占空比为250
1.0ms------------45度; 5.0% 对应函数中占空比为500
1.5ms------------90度; 7.5% 对应函数中占空比为750
2.0ms-----------135度; 10.0% 对应函数中占空比为1000
2.5ms-----------180度; 12.5% 对应函数中占空比为1250

三、单片机上的应用

  • STM32上面参考这个份代码的使用:https://github.com/github150620/stm32-pca9685-sg90/tree/main
  • 基于STM32F407VGT6

pca9685.c

#include "pca9685.h"
#include "stdio.h"

static void delay(int i) {
	while (i) {
		i--;
	}
}

void I2C2_Init() 
{
	GPIO_InitTypeDef GPIO_InitStructure;
	I2C_InitTypeDef  I2C_initStructure;

	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; 
	GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; //开漏
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	
	    // 配置引脚复用映射
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_I2C2);  // SCL
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource11, GPIO_AF_I2C2);  // SDA
	
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
//	GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);
	
	I2C_initStructure.I2C_ClockSpeed = 100000; // 100KHz
	I2C_initStructure.I2C_Mode = I2C_Mode_I2C;
	I2C_initStructure.I2C_DutyCycle = I2C_DutyCycle_2;
	I2C_initStructure.I2C_OwnAddress1 = 0x00;
	I2C_initStructure.I2C_Ack = I2C_Ack_Enable;
	I2C_initStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
	I2C_Init(I2C2, &I2C_initStructure);
	
	I2C_Cmd(I2C2, ENABLE);	
}

void PCA9685_WriteReg(uint8_t addr, uint8_t value) 
{

	I2C_AcknowledgeConfig(I2C2, ENABLE);
	//while (I2C_GetFlagStatus(I2C2, I2C_FLAG_BUSY));
	
	I2C_GenerateSTART(I2C2, ENABLE);
	while(!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT));

	I2C_Send7bitAddress(I2C2, (PCA9685_I2C_ADDR<<1)&0xfe, I2C_Direction_Transmitter);
	while(!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

	I2C_SendData(I2C2, addr);
	while(!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

	I2C_SendData(I2C2, value);
	while(!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

	I2C_GenerateSTOP(I2C2, ENABLE);	
}

void PCA9685_ReadReg(uint8_t addr, uint8_t *value) 
{
	I2C_AcknowledgeConfig(I2C2, ENABLE);
	
	I2C_GenerateSTART(I2C2, ENABLE);
	while(!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT));

	I2C_Send7bitAddress(I2C2, (PCA9685_I2C_ADDR<<1)&0xfe, I2C_Direction_Transmitter);
	while(!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

	I2C_SendData(I2C2, addr);
	while(!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED));	
	
	I2C_GenerateSTOP(I2C2, ENABLE);	
	
	I2C_GenerateSTART(I2C2, ENABLE);
	while(!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT));
	
	I2C_Send7bitAddress(I2C2, (PCA9685_I2C_ADDR<<1)|0x01, I2C_Direction_Receiver);
	while(!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));

	I2C_AcknowledgeConfig(I2C2, DISABLE);
	
	while(!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED));
	*value = I2C_ReceiveData(I2C2);

	I2C_GenerateSTOP(I2C2, ENABLE);	
}

void PCA9685_SetChannelDuty(uint8_t channel, uint32_t on, uint32_t off) 
{
	I2C_AcknowledgeConfig(I2C2, ENABLE);
	
	I2C_GenerateSTART(I2C2, ENABLE);
	while(!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT));

	I2C_Send7bitAddress(I2C2, (PCA9685_I2C_ADDR<<1)&0xfe, I2C_Direction_Transmitter);
	while(!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

	I2C_SendData(I2C2, LED0_ON_L+4*channel);
	while(!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

	I2C_SendData(I2C2, on&0xff);
	while(!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

	I2C_SendData(I2C2, (on>>8)&0xff);
	while(!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

	I2C_SendData(I2C2, off&0xff);
	while(!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
	
	I2C_SendData(I2C2, (off>>8)&0xff);
	while(!I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
	
	I2C_GenerateSTOP(I2C2, ENABLE);
}

void PCA9685_Init() 
{
	uint8_t value;
	
	I2C2_Init();
	
	// The PRE_SCALE register can only be set when the SLEEP bit of MODE1 register is set to logic 1.
	PCA9685_WriteReg(MODE1, MODE1_SLEEP);
	PCA9685_WriteReg(PRE_SCALE, 121); // (25000000/4096/50)-1
	PCA9685_WriteReg(MODE1, 0);
	PCA9685_WriteReg(MODE1, MODE1_RESTART|MODE1_AI|MODE1_ALLCALL);

	while (1) 
	{
		PCA9685_ReadReg(MODE1, &value);
		if ((value&MODE1_RESTART)==0)
		{	// Restart OK.
			printf("restart OK!\r\n");
			break;
		}
		printf("value: 0x%02x\r\n", value);
		delay(100000);
	}
}

void SG90_SetAngle(uint8_t id, uint8_t angle) 
{
	PCA9685_SetChannelDuty(id, 0, (uint32_t)(4095*((angle/180.0)*2.0+0.5)/20.0));
}

pca9685.h

#ifndef __PCA9685_H__
#define __PCA9685_H__

#include "stdint.h"

#define PCA9685_I2C_ADDR 0x40

#define MODE1       0x00
#define MODE2       0x01
#define SUBADR1     0x02
#define SUBADR2     0x03
#define SUBADR3     0x04
#define ALLCALLADR  0x05
#define LED0_ON_L   0x06
#define LED0_ON_H   0x07
#define LED0_OFF_L  0x08
#define LED0_OFF_H  0x09
#define LED1_ON_L   0x0A
#define LED1_ON_H   0x0B
#define LED1_OFF_L  0x0C
#define LED1_OFF_H  0x0D
#define LED2_ON_L   0x0E
#define LED2_ON_H   0x0F
#define LED2_OFF_L  0x10
#define LED2_OFF_H  0x11
#define LED3_ON_L   0x12
#define LED3_ON_H   0x13
#define LED3_OFF_L  0x14
#define LED3_OFF_H  0x15
#define LED4_ON_L   0x16
#define LED4_ON_H   0x17
#define LED4_OFF_L  0x18
#define LED4_OFF_H  0x19
#define LED5_ON_L   0x1A
#define LED5_ON_H   0x1B
#define LED5_OFF_L  0x1C
#define LED5_OFF_H  0x1D
#define LED6_ON_L   0x1E
#define LED6_ON_H   0x1F
#define LED6_OFF_L  0x20
#define LED6_OFF_H  0x21
#define LED7_ON_L   0x22
#define LED7_ON_H   0x23
#define LED7_OFF_L  0x24
#define LED7_OFF_H  0x25
#define LED8_ON_L   0x26
#define LED8_ON_H   0x27
#define LED8_OFF_L  0x28
#define LED8_OFF_H  0x29
#define LED9_ON_L   0x2A
#define LED9_ON_H   0x2B
#define LED9_OFF_L  0x2C
#define LED9_OFF_H  0x2D
#define LED10_ON_L  0x2E
#define LED10_ON_H  0x2F
#define LED10_OFF_L 0x30
#define LED10_OFF_H 0x31
#define LED11_ON_L  0x32
#define LED11_ON_H  0x33
#define LED11_OFF_L 0x34
#define LED11_OFF_H 0x35
#define LED12_ON_L  0x36
#define LED12_ON_H  0x37
#define LED12_OFF_L 0x38
#define LED12_OFF_H 0x39
#define LED13_ON_L  0x3A
#define LED13_ON_H  0x3B
#define LED13_OFF_L 0x3C
#define LED13_OFF_H 0x3D
#define LED14_ON_L  0x3E
#define LED14_ON_H  0x3F
#define LED14_OFF_L 0x40
#define LED14_OFF_H 0x41
#define LED15_ON_L  0x42
#define LED15_ON_H  0x43
#define LED15_OFF_L 0x44
#define LED15_OFF_H 0x45

#define PRE_SCALE  0xFE

#define MODE1_RESTART 0x80
#define MODE1_EXTCLK  0x40
#define MODE1_AI      0x20
#define MODE1_SLEEP   0x10
#define MODE1_ALLCALL 0x01

void PCA9685_Init();

// channel: 0 ~ 15
//      on: 0 ~ 4095
//     off: 0 ~ 4095
void PCA9685_SetChannelDuty(uint8_t channel, uint32_t on, uint32_t off);

void SG90_SetAngle(uint8_t id, uint8_t angle);


#endif

main.c

#include "pca9685.h"

int main(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
	delay_init(168);     //初始化延时函数
	uart_init(115200);	//初始化串口波特率为115200
	
	printf("\r\n STM32F407开发板 PCA9685测试\r\n");
	
	LED_Init();					//初始化LED
	PCA9685_Init();
	
	SG90_SetAngle(0, 180);
	SG90_SetAngle(1, 180);
	
	while(1)
	{
	}
}

这份代码直接将舵机的控制转为角度,直接设置指定的那路舵机转动多少度。

四、嵌入式Linux上的应用

4.0 嵌入式Linux

基于Luckfox pico,芯片是Rockchip的RV1103。

内核

在这里插入图片描述
内核打开上面的开关后就可以出现节点:

# ls /sys/bus/i2c/drivers/pca9685-pwm/3-0040/pwm/pwmchip0
device     export     npwm       power      subsystem  uevent     unexport

不过这种PWM控制方式有问题,后面会有阐述。

参考

  • https://www.faschingbauer.me/trainings/material/soup/linux/hardware/pwm/topic.html
  • https://www.acmesystems.it/pca9685

设备树

&i2c3 {
	status = "okay";
	pinctrl-0 = <&i2c3m1_xfer>;
	clock-frequency = <100000>;

	pca9685: pca9685@40 {
		compatible = "nxp,pca9685-pwm";
		reg = <0x40>; 
		#pwm-cells = <2>;
		open-drain;
		invert;
	};
};

&uart5 {
 	status = "disabled";
};

4.1 分析–多种操作方式

PCA9685作为一个IIC设备,在Linux上面有多种应用实现的方式:

  • 简陋版:硬件连接OK,IIC地址能够扫描出来,直接使用i2cset命令(文件系统需要i2ctools软件工具支持)来进行寄存器读写操作。
  • 基础版:编写一个内核模块(PCA9685.ko),将PCA9685作为一个PWM设备来操作
    • 导出:echo 0 > /sys/class/pwm/pwmchip0/export
    • 设置频率:echo 1000000 > /sys/class/pwm/pwmchip0/pwm0/period
    • 设置占空比:echo 500000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
    • 使能:echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable
  • 进阶版:编写一个内核模块(PCA9685.ko),在文件系统中挂载一个设备节点/dev/pca9685,写应用程序对这个设备节点进行读写操作。

4.2 I2C设备之i2ctools软件工具使用基础

  • 查看所有的I2C总线:i2cdetect -l
  • 扫描第3号I2C总线上的设备:i2cdetect -r -y 3
    • UU:代表该地址被内核模块使用
    • 数字:代表该地址被扫描到
    • –:无设备
  • 查看目标设备所有寄存器的值:i2cdump -f -y 3 0x40
  • 设置单个寄存器的值:i2cset -f -y 3 0x40 0x00 0xA0
    。。。

4.3 linux kernel上的PCA9685内核驱动有问题

按照正常的流程:

  • 导出:echo 0 > /sys/class/pwm/pwmchip0/export
  • 设置频率:echo 1000000 > /sys/class/pwm/pwmchip0/pwm0/period
  • 设置占空比:echo 500000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
  • 使能:echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable

按照上面的流程输出PWM正常来说应该是没有问题的,但是,有问题!

  • 随机可以输出PWM
  • 使用echo 0 > /sys/class/pwm/pwmchip0/pwm0/enable之后就再也不能输出了!!
  • 重新上电都不能输出PWM。

问题分析过程

通过对比,单片机下和Linux下的设定寄存器:
单片机设置的,有波形的寄存器配置

# i2cdump -f -y 3 0x40
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00: 21 04 e2 e4 e8 e0 00 00 ff 01 00 00 ff 01 00 00    !?????...?...?..
10: 00 10 00 00 00 10 00 00 00 10 00 00 00 10 00 00    .?...?...?...?..
20: 00 10 00 00 00 10 00 00 00 10 00 00 00 10 00 00    .?...?...?...?..
30: 00 10 00 00 00 10 00 00 00 10 00 00 00 10 00 00    .?...?...?...?..
40: 00 10 00 00 00 10 XX XX XX XX XX XX XX XX XX XX    .?...?XXXXXXXXXX
50: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
60: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
70: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
80: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
90: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
a0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
b0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
c0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
d0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
e0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
f0: XX XX XX XX XX XX XX XX XX XX 00 00 00 00 79 00    XXXXXXXXXX....y.

Linux下设置的,没有波形的寄存器配置

# i2cdump -f -y 3 0x40
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00: 20 10 e2 e4 e8 e0 00 00 00 08 00 00 00 00 00 00     ?????...?......
10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
40: 00 00 00 00 00 00 XX XX XX XX XX XX XX XX XX XX    ......XXXXXXXXXX
50: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
60: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
70: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
80: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
90: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
a0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
b0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
c0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
d0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
e0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
f0: XX XX XX XX XX XX XX XX XX XX 00 00 00 00 05 00    XXXXXXXXXX....?.

找到Linux 驱动的问题

在这里插入图片描述
在这里插入图片描述
驱动绝对有问题!!
解释:

  • 前提知识点:09h的第4位,是控制全关的。它只要置1,通道0就不会有PWM输出。
  • 我在可以输出波形的配置基础上运行命令:echo 0 > /sys/class/pwm/pwmchip0/pwm0/enable目的是关闭通道0的PWM输出。OK,可以关闭通道0的PWM。
  • 然后我再运行命令:echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable目的是打开通道0的PWM输出。失败!
  • 通过查看寄存器,可以知道,当运行echo 0 > /sys/class/pwm/pwmchip0/pwm0/enable时,整个09h寄存器被赋值0x10(这意味着09h寄存器的0~3位数值被清零了)。当运行echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable时,整个09h寄存器可能被直接赋值0x00来把第四位清零。这个开关操作中,09h寄存器的0~3位数值被清零了,没有被被保留。这就意味着驱动有问题!

4.4 简陋版–修成正果

由于内核驱动的问题,导致后续基于内核驱动的操作都没法进行。所以就只能在简陋版操作上继续进行了。

初始化–寄存器直接写死

# 前六个寄存器的值直接写死
i2cset -f -y 3 0x40 0x00 0x21
i2cset -f -y 3 0x40 0x01 0x04
i2cset -f -y 3 0x40 0x02 0xe2
i2cset -f -y 3 0x40 0x03 0xe4
i2cset -f -y 3 0x40 0x04 0xe8
i2cset -f -y 3 0x40 0x05 0xe0

# 频率设置
i2cset -f -y 3 0x40 0xfe 0x05  // 1Khz
# 占空比
i2cset -f -y 3 0x40 0x05 0xe0

修改频率–有一套固定的流程

i2cset -f -y 3 0x40 0x00 0x10  # 停止输出波形,进入睡眠模式
i2cset -f -y 3 0x40 0xfe 0x79  # 修改频率
i2cset -f -y 3 0x40 0x00 0x00  # 重启
i2cset -f -y 3 0x40 0x00 0xa1  # 开始输出波形

修改占空比–随时可以改

i2cset -f -y 3 0x40 0x09 0x05 # 通道0,占空比

写了一个可以使用的C语言代码

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdbool.h>
#include <math.h>

#define PCA9685_I2C_ADDR 0x40
#define IIC_BUS_INDEX    0X03

#define MODE1       0x00
#define MODE2       0x01
#define SUBADR1     0x02
#define SUBADR2     0x03
#define SUBADR3     0x04
#define ALLCALLADR  0x05
#define LED0_ON_L   0x06
#define LED0_ON_H   0x07
#define LED0_OFF_L  0x08
#define LED0_OFF_H  0x09
#define LED1_ON_L   0x0A
#define LED1_ON_H   0x0B
#define LED1_OFF_L  0x0C
#define LED1_OFF_H  0x0D
#define LED2_ON_L   0x0E
#define LED2_ON_H   0x0F
#define LED2_OFF_L  0x10
#define LED2_OFF_H  0x11
#define LED3_ON_L   0x12
#define LED3_ON_H   0x13
#define LED3_OFF_L  0x14
#define LED3_OFF_H  0x15
#define LED4_ON_L   0x16
#define LED4_ON_H   0x17
#define LED4_OFF_L  0x18
#define LED4_OFF_H  0x19
#define LED5_ON_L   0x1A
#define LED5_ON_H   0x1B
#define LED5_OFF_L  0x1C
#define LED5_OFF_H  0x1D
#define LED6_ON_L   0x1E
#define LED6_ON_H   0x1F
#define LED6_OFF_L  0x20
#define LED6_OFF_H  0x21
#define LED7_ON_L   0x22
#define LED7_ON_H   0x23
#define LED7_OFF_L  0x24
#define LED7_OFF_H  0x25
#define LED8_ON_L   0x26
#define LED8_ON_H   0x27
#define LED8_OFF_L  0x28
#define LED8_OFF_H  0x29
#define LED9_ON_L   0x2A
#define LED9_ON_H   0x2B
#define LED9_OFF_L  0x2C
#define LED9_OFF_H  0x2D
#define LED10_ON_L  0x2E
#define LED10_ON_H  0x2F
#define LED10_OFF_L 0x30
#define LED10_OFF_H 0x31
#define LED11_ON_L  0x32
#define LED11_ON_H  0x33
#define LED11_OFF_L 0x34
#define LED11_OFF_H 0x35
#define LED12_ON_L  0x36
#define LED12_ON_H  0x37
#define LED12_OFF_L 0x38
#define LED12_OFF_H 0x39
#define LED13_ON_L  0x3A
#define LED13_ON_H  0x3B
#define LED13_OFF_L 0x3C
#define LED13_OFF_H 0x3D
#define LED14_ON_L  0x3E
#define LED14_ON_H  0x3F
#define LED14_OFF_L 0x40
#define LED14_OFF_H 0x41
#define LED15_ON_L  0x42
#define LED15_ON_H  0x43
#define LED15_OFF_L 0x44
#define LED15_OFF_H 0x45

#define PRE_SCALE  0xFE

#define MODE1_RESTART 0x80
#define MODE1_EXTCLK  0x40
#define MODE1_AI      0x20
#define MODE1_SLEEP   0x10
#define MODE1_ALLCALL 0x01


#define GO_FORWARD     0 // 前进
#define GO_BACKWARD    1 // 后退
#define TURN_STRAIGHT  2 // 直行
#define TURN_RIGHT     3 // 右转
#define TURN_LEFT      4 // 左转

void cmd_send(uint8_t addr, uint8_t reg, uint8_t value)
{
	uint8_t cmd[100] = {0};
	
	sprintf(cmd ,"i2cset -f -y %d 0x%02x 0x%02x 0x%02x" , IIC_BUS_INDEX, addr, reg, value);
	
	system(cmd);
}

void cmds_send(uint8_t addr, uint8_t reg, uint8_t value1, uint8_t value2, uint8_t value3, uint8_t value4)
{
	uint8_t cmd[100] = {0};
	
	sprintf(cmd ,"i2cset -f -y %d 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x i" , \
		IIC_BUS_INDEX, addr, reg, value1, value2, value3, value4);
	
	system(cmd);
}

void frequency_set(uint32_t hz)
{
	uint8_t value = 0;
	
	value = round(25000000/(4095*hz))-1;
	
	cmd_send(PCA9685_I2C_ADDR, MODE1, 0x10);
	cmd_send(PCA9685_I2C_ADDR, PRE_SCALE, value);
	cmd_send(PCA9685_I2C_ADDR, MODE1, 0x00);
	cmd_send(PCA9685_I2C_ADDR, MODE1, 0xa1);
}

void init_pwm(void)
{
	cmd_send(PCA9685_I2C_ADDR, MODE1, 0x21);
	cmd_send(PCA9685_I2C_ADDR, MODE2, 0x04);
	cmd_send(PCA9685_I2C_ADDR, SUBADR1, 0xe2);
	cmd_send(PCA9685_I2C_ADDR, SUBADR2, 0xe4);
	cmd_send(PCA9685_I2C_ADDR, SUBADR3, 0xe8);
	cmd_send(PCA9685_I2C_ADDR, ALLCALLADR, 0xe0);
}

void pwm_duty_set(uint8_t channel, uint32_t on, uint32_t off)
{
	cmds_send(PCA9685_I2C_ADDR, LED0_ON_L+4*channel, on&0xff, (on>>8)&0xff, off&0xff, (off>>8)&0xff);
}

void pwm_enable(uint8_t channel, bool state)
{
	if(state == true)
	{
		cmd_send(PCA9685_I2C_ADDR, LED0_OFF_H+4*channel, 0x10);
	}
	else
	{
		cmd_send(PCA9685_I2C_ADDR, LED0_OFF_H+4*channel, 0x00);
	}
}

void SG90_SetAngle(uint8_t channel, uint8_t angle) 
{
	pwm_duty_set(channel, 0, (uint32_t)(4095*((angle/180.0)*2.0+0.5)/20.0));
}

void Motor_SetDuty(uint8_t channel, uint8_t duty)
{
	pwm_duty_set(channel, 0, (uint32_t)(4095*(duty/100.0)));
}

void Car_Control(uint8_t go, uint8_t turn, uint8_t speed)
{
	uint8_t duty = speed;
	// 全速直行
	
	if(go == GO_FORWARD)
	{
		switch(turn)
		{
		case TURN_STRAIGHT:
		{
			// 正转
			pwm_duty_set(0, 0, (uint32_t)(4095*(duty/100.0)));
			pwm_duty_set(1, 0, 0);
			
			pwm_duty_set(2, 0, (uint32_t)(4095*(duty/100.0)));
			pwm_duty_set(3, 0, 0);
			
			pwm_duty_set(4, 0, (uint32_t)(4095*(duty/100.0)));
			pwm_duty_set(5, 0, 0);
			
			pwm_duty_set(6, 0, (uint32_t)(4095*(duty/100.0)));
			pwm_duty_set(7, 0, 0);	
		}break;
		case TURN_RIGHT:
		{
			// 正转
			pwm_duty_set(0, 0, (uint32_t)(4095*(duty/100.0)));
			pwm_duty_set(1, 0, 0);
			
			pwm_duty_set(2, 0, (uint32_t)(4095*(duty/100.0)));
			pwm_duty_set(3, 0, 0);
			
			pwm_duty_set(4, 0, (uint32_t)(4095*(duty/100.0))-10);
			pwm_duty_set(5, 0, 0);
			
			pwm_duty_set(6, 0, (uint32_t)(4095*(duty/100.0))-10);
			pwm_duty_set(7, 0, 0);
		}break;
		case TURN_LEFT:
		{
			// 正转
			pwm_duty_set(0, 0, (uint32_t)(4095*(duty/100.0))-10);
			pwm_duty_set(1, 0, 0);
			
			pwm_duty_set(2, 0, (uint32_t)(4095*(duty/100.0))-10);
			pwm_duty_set(3, 0, 0);
			
			pwm_duty_set(4, 0, (uint32_t)(4095*(duty/100.0)));
			pwm_duty_set(5, 0, 0);
			
			pwm_duty_set(6, 0, (uint32_t)(4095*(duty/100.0)));
			pwm_duty_set(7, 0, 0);
		}break;
		default:
			break;
		}
	}
	else if(go == GO_BACKWARD)
	{
		switch(turn)
		{
		case TURN_STRAIGHT:
		{
			// 反转
			pwm_duty_set(0, 0, 0);
			pwm_duty_set(1, 0, (uint32_t)(4095*(duty/100.0)));
			
			pwm_duty_set(2, 0, 0);
			pwm_duty_set(3, 0, (uint32_t)(4095*(duty/100.0)));
			
			pwm_duty_set(4, 0, 0);
			pwm_duty_set(5, 0, (uint32_t)(4095*(duty/100.0)));
			
			pwm_duty_set(6, 0, 0);
			pwm_duty_set(7, 0, (uint32_t)(4095*(duty/100.0)));
	
		}break;
		case TURN_RIGHT:
		{
			// 反转
			pwm_duty_set(0, 0, 0);
			pwm_duty_set(1, 0, (uint32_t)(4095*(duty/100.0)));
			
			pwm_duty_set(2, 0, 0);
			pwm_duty_set(3, 0, (uint32_t)(4095*(duty/100.0)));
			
			pwm_duty_set(4, 0, 0);
			pwm_duty_set(5, 0, (uint32_t)(4095*(duty/100.0))-10);
			
			pwm_duty_set(6, 0, 0);
			pwm_duty_set(7, 0, (uint32_t)(4095*(duty/100.0))-10);

		}break;
		case TURN_LEFT:
		{
			// 反转
			pwm_duty_set(0, 0, 0);
			pwm_duty_set(1, 0, (uint32_t)(4095*(duty/100.0))-10);
			
			pwm_duty_set(2, 0, 0);
			pwm_duty_set(3, 0, (uint32_t)(4095*(duty/100.0))-10);
			
			pwm_duty_set(4, 0, 0);
			pwm_duty_set(5, 0, (uint32_t)(4095*(duty/100.0)));
			
			pwm_duty_set(6, 0, 0);
			pwm_duty_set(7, 0, (uint32_t)(4095*(duty/100.0)));
		}break;
		default:
			break;
		}
	}
}

int main(int argc, char *argv[])
{

	init_pwm();
	
	frequency_set(50);

	//Motor_SetDuty(0, 50);

	Car_Control(GO_FORWARD, TURN_STRAIGHT, 10);
	
	while(1)
	{
		
	}
	
	return 0;
}

4.5 后记之更多调试记录

寄存器设置

预分频寄存器的设定(0xFE)

PRE_SCALE寄存器(0xFE)只能在MODE1寄存器的SLEEP位设置为逻辑1时设置。
预分频值是怎么计算的,如果你想要50Hz:

round(25000000/(4095*50))-1 = 121

round:取最近的整数

模式1寄存器的设定(0x00)
#define MODE1_RESTART 0x80 // 重启
#define MODE1_EXTCLK  0x40 // 使用外部时钟
#define MODE1_AI      0x20 // 寄存器自动增加
#define MODE1_SLEEP   0x10 // 1是低功耗模式,0是正常模式
#define MODE1_ALLCALL 0x01 // IIC设备地址响应,0就是不响应IIC,1就是响应

在这里插入图片描述

执行命令与I2C通信数据逐字节分析

上电的通信数据分析
0x80 0x01 # 写,模式2寄存器,接后面
0x81 0x10 # 读出模式2寄存器里面的数据是0x10
0x80 0x01 0x10 # 写,模式2寄存器,数据0x10

0x80 0x00 # 写,模式1寄存器,接后面
0x81 0x00 # 读出模式1寄存器里面的数据是0x00
0x80 0x00 0x00 # 写,模式1寄存器,数据0x00

0x80 0xFC 0x00 # 写,所有通道关寄存器(低寄存器),数据0
0x80 0xFD 0x00 # 写,所有通道关寄存器(高寄存器),数据0

0x80 0x00 # 写,模式1寄存器,接后面
0x81 0x00 # 读出模式1寄存器里面的数据是0x00
0x80 0x00 0x00 # 写,模式1寄存器,数据0x00

0x80:写
0x81:读

执行命令:echo 0 > /sys/class/pwm/pwmchip0/export
0x80 0x00 # 写,模式1寄存器,接后面
0x81 0x10 # 读出模式1寄存器里面的数据是0x10
0x80 0x00 0x00 # 写,模式1寄存器,数据0x00

0x80:写
0x81:读

执行命令:echo 1000000 > /sys/class/pwm/pwmchip0/pwm0/period
0x80 0x00 # 写,模式1寄存器,接后面
0x81 0x00 # 读出模式1寄存器里面的数据是0x00
0x80 0x00 0x10 # 写,模式1寄存器,数据0x10

0x80 0xFE 0x05 # 写,预分频寄存器,数据0x05

0x80 0x00 # 写,模式1寄存器,接后面
0x81 0x10 # 读出模式1寄存器里面的数据是0x10
0x80 0x00 0x00 # 写,模式1寄存器,数据0x00

0x80 0x09 0x10 # 写,通道0关寄存器(高寄存器),数据0x10

0x80:写
0x81:读

执行命令:echo 500000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
0x80 0x08 0x00 # 写,通道0关寄存器(低寄存器),数据0x00

0x80 0x09 0x08 # 写,通道0关寄存器(高寄存器),数据0x08

0x80 0x07 0x00 # 写,通道0开寄存器(高寄存器),数据0x00

0x80:写
0x81:读

执行命令:echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable
0x80 0x06 0x00 # 写,通道0开寄存器(低寄存器),数据0
0x80 0x07 0x00 # 写,通道0开寄存器(高寄存器),数据0
0x80 0x09 # 写,通道0关寄存器(高寄存器),接后面
0x81 0x08 # 读出0x09寄存器里面的数据是0x08

0x80:写
0x81:读

执行命令:echo 0 > /sys/class/pwm/pwmchip0/pwm0/enable
0x80 0x09 0x10 # 写,通道0关寄存器(高寄存器),数据0x10
0x80 0x08 0x00 # 写,通道0关寄存器(低寄存器),数据0

0x80:写
0x81:读


单片机初始化对比数据

void PCA9685_Init() 
{
	uint8_t value;
	
	I2C2_Init();
	
	// The PRE_SCALE register can only be set when the SLEEP bit of MODE1 register is set to logic 1.
	PCA9685_WriteReg(MODE1, MODE1_SLEEP);
	PCA9685_WriteReg(PRE_SCALE, 121); // (25000000/4096/50)-1
	PCA9685_WriteReg(MODE1, 0);
	PCA9685_WriteReg(MODE1, MODE1_RESTART|MODE1_AI|MODE1_ALLCALL);

	while (1) 
	{
		PCA9685_ReadReg(MODE1, &value);
		if ((value&MODE1_RESTART)==0)
		{	// Restart OK.
			printf("restart OK!\r\n");
			break;
		}
		printf("value: 0x%02x\r\n", value);
		delay(100000);
	}
}

void SG90_SetAngle(uint8_t id, uint8_t angle) 
{
	PCA9685_SetChannelDuty(id, 0, (uint32_t)(4095*((angle/180.0)*2.0+0.5)/20.0));
}

int main(void)
{
	PCA9685_Init();
	SG90_SetAngle(0, 180);
	SG90_SetAngle(1, 180);
	while(1)
	{}
}

通信内容:

0x80 0x00 0x10 # 写,模式1寄存器,数据0x10, 进入睡眠模式,便于设置参数
0x80 0xFE 0x79 # 写,预分频寄存器,数据0x79,设置分频系数,121
0x80 0x00 0x00 # 写,模式1寄存器,数据0x00
0x80 0x00 0xA1 # 写,模式1寄存器,数据0xA1
0x80 0x00 # 写,模式1寄存器,接后面
0x81 0x21 # 读出模式1寄存器里面的数据是0x21
0x80 0x06 0x00 0x00 0xFF 0x01 # 写,通道0开寄存器(低位),数据:0x00 0x00 0xff 0x01
0x80 0x0A 0x00 0x00 0xFF 0x01 # 写,通道1开寄存器(低位),数据:0x00 0x00 0xff 0x01

五、I2C软件调试工具使用记录

5.1 查看系统所有的I2C总线

i2cdetect -l

# i2cdetect -l
i2c-3   i2c             rk3x-i2c                                I2C adapter
i2c-4   i2c             rk3x-i2c                                I2C adapter

5.2 查看各个总线上挂载的设备

# i2cdetect -r -y 3
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: UU -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
# i2cdetect -r -y 4
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
#

UU:代表该地址被内核模块使用
数字:代表该地址被扫描到
–:无设备

5.3 查看器件所有寄存器的值

用i2cdump查看器件所有寄存器的值,这个命令可以查看器件所有寄存器的值,在实际测试时很好用
命令:

i2cdump -f -y 3 0x40

其中 :

  • 3: 表示I2C-3这个总线;
  • 0x40:表示总线上 设备地址为0x40的这个设备;
# i2cdump -f -y 3 0x40
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00: b0 10 e2 e4 e8 e0 00 00 00 00 00 00 00 00 00 00    ??????..........
10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
40: 00 00 00 00 00 00 XX XX XX XX XX XX XX XX XX XX    ......XXXXXXXXXX
50: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
60: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
70: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
80: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
90: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
a0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
b0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
c0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
d0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
e0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
f0: XX XX XX XX XX XX XX XX XX XX 00 00 00 00 79 00    XXXXXXXXXX....y.

5.4 设置单个寄存器的值

用i2cset来设置单个寄存器值
命令:

i2cset -f -y 3 0x40 0x00 0xA0   //复位器件

其中 :
3: 表示I2C-3这个总线;
0x40:表示总线上 设备地址为0x40的这个设备;
0x00:操作的寄存器地址
0xA0:当前操作的寄存器到写入的值

5.5 给多个寄存器各写一个数据(寄存器地址自增)

用i2cset来给寄存器里面写值,地址自增

i2cset -f -y 3 0x40 0x08 0x0fff w
i2cset -f -y 3 0x40 0x08 0x0f 0xff w
i2cset -f -y 3 0x40 0x08 0x0f 0xff i
i2cset -f -y 3 0x40 0x08 0x0f 0xff 0x0f 0xff i # 设置了0x08 0x09 0x0a 0x0b地址的值

3: 表示I2C-3这个总线;
0x40:表示总线上 设备地址为0x40的这个设备;
0x08:操作的寄存器地址
0x0fff:当前操作的寄存器到写入的值
w:2字节数据
i:块数据

命令格式:

i2cset -f -y <bus> <slave> <word value>

Usage: i2cset [-fy] [-m MASK] BUS CHIP-ADDRESS DATA-ADDRESS [VALUE] ... [MODE]

Set I2C registers

        I2CBUS  I2C bus number
        ADDRESS 0x03-0x77
MODE is:
        c       Byte, no value
        b       Byte data (default)
        w       Word data
        i       I2C block data
        s       SMBus block data
        Append p for SMBus PEC

        -f      Force access
        -y      Disable interactive mode
        -r      Read back and compare the result
        -m MASK Mask specifying which bits to write

5.6 获取单个寄存器的值

用i2cget来获取单个寄存器值
命令:

i2cget -f -y 3 0x40 0x00

其中 :
3: 表示I2C-3这个总线;
0x40:表示总线上 设备地址为0x40的这个设备;
0x00:操作的寄存器地址

参考

https://blog.csdn.net/dylanZheng/article/details/73864844

https://blog.csdn.net/ch122633/article/details/120316124

https://blog.csdn.net/wangjie36/article/details/134406899

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值