文章目录
一、硬件
- 可以扩展出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/(4095∗Hz))−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