-
Notifications
You must be signed in to change notification settings - Fork 0
/
pca9685.c
307 lines (253 loc) · 6.56 KB
/
pca9685.c
1
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
// SPDX-License-Identifier: GPL-2.0
/*
* Hacked from https://github.com/Reinbert/pca9685
* Copyright (c) 2014 Reinhard Sprung
*
* Copyright (c) 2020 Damien Le Moal.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include "pca9685.h"
#ifdef PCA_DEBUG
#define pca_dbg(fmt, ...) printf(fmt, ##__VA_ARGS__)
#else
#define pca_dbg(fmt, ...) {}
#endif
/**
* Get channel register
*/
static __u8 pca9685_channel_reg(int ch)
{
if (ch >= PCA9685_ALL_CHANNELS)
return PCA9685_ALL_CHANNELS_ON_L;
return PCA9685_CHANNEL0_ON_L + ch * 4;
}
static int pca9685_access(int fd, int read_write, __u8 reg,
int size, union i2c_smbus_data *data)
{
struct i2c_smbus_ioctl_data args;
int ret;
args.read_write = read_write;
args.command = reg;
args.size = size;
args.data = data;
ret = ioctl(fd, I2C_SMBUS, &args);
if (ret < 0) {
fprintf(stderr, "I2C_SMBUS %s ioctl failed %d (%s)\n",
read_write ? "read" : "write",
errno, strerror(errno));
return -1;
}
return 0;
}
static int pca9685_write8(struct pca9685 *pca, __u8 reg, __u8 val)
{
union i2c_smbus_data data = { .byte = val };
pca_dbg("pca9685_write8 reg 0x%02x, val 0x%02x\n",
(unsigned int) reg,
(unsigned int) val);
return pca9685_access(pca->fd, I2C_SMBUS_WRITE, reg, 2, &data);
}
static int pca9685_read8(struct pca9685 *pca, __u8 reg)
{
union i2c_smbus_data data;
int ret;
pca_dbg("pca9685_read8 reg 0x%02x\n",
(unsigned int) reg);
ret = pca9685_access(pca->fd, I2C_SMBUS_READ, reg, 2, &data);
if (ret < 0)
return ret;
return data.byte & 0x00ff;
}
static int pca9685_write16(struct pca9685 *pca, __u8 reg, __u16 val)
{
union i2c_smbus_data data = { .word = val };
pca_dbg("pca9685_write16 reg 0x%02x, val 0x%04x\n",
(unsigned int) reg,
(unsigned int) val);
return pca9685_access(pca->fd, I2C_SMBUS_WRITE, reg, 3, &data);
}
static int pca9685_read16(struct pca9685 *pca, __u8 reg)
{
union i2c_smbus_data data;
int ret;
pca_dbg("pca9685_read16 reg 0x%02x\n",
(unsigned int) reg);
ret = pca9685_access(pca->fd, I2C_SMBUS_READ, reg, 2, &data);
if (ret < 0)
return ret;
return data.word & 0x00ffff;
}
int pca9685_open(struct pca9685 *pca, int address, int freq)
{
int fd, mode;
pca_dbg("Opening device %s\n", pca->dev);
fd = open(pca->dev, O_RDWR);
if (fd < 0) {
fprintf(stderr, "Open i2c adapter %s failed %d (%s)\n",
pca->dev, errno, strerror(errno));
return -1;
}
/* Set 7-bits address */
if (ioctl(fd, I2C_TENBIT, 0) < 0) {
fprintf(stderr, "Set 7-bits address failed %d (%s)\n",
errno, strerror(errno));
goto err;
}
/* Connect pca */
pca_dbg("Set slave address 0x%x\n", address);
if (ioctl(fd, I2C_SLAVE, address) < 0) {
fprintf(stderr, "Connect to slave at 0x%x failed %d (%s)\n",
address, errno, strerror(errno));
goto err;
}
pca->fd = fd;
/* Setup the chip: enable auto-increment of registers */
mode = pca9685_read8(pca, PCA9685_MODE1);
if (pca9685_write8(pca, PCA9685_MODE1, (mode | 0x20) & 0x7F))
goto err;
/*
* Set frequency of PWM signals. Also ends sleep mode and starts
* PWM output.
*/
pca9685_set_pwm_freq(pca, freq);
return 0;
err:
close(fd);
return -1;
}
void pca9685_close(struct pca9685 *pca)
{
if (pca->fd > 0)
close(pca->fd);
pca->fd = -1;
}
/**
* Sets the frequency of PWM signals.
* Frequency will be capped to range [40..1000] Hertz. Try 50 for servos.
*/
void pca9685_set_pwm_freq(struct pca9685 *pca, int freq)
{
int prescale, sleep, wake, mode;
if (!freq)
return;
if (freq > PCA9685_FREQ_MAX)
freq = PCA9685_FREQ_MAX;
else if (freq < PCA9685_FREQ_MIN)
freq = PCA9685_FREQ_MIN;
/*
* To set pwm frequency we have to set the prescale register.
* The formula is:
* prescale = round(osc_clock / (4096 * frequency))) - 1
* where osc_clock = 25 MHz
* Further info here:
* http://www.nxp.com/documents/data_sheet/PCA9685.pdf Page 24
*/
prescale = (int)(25000000.0f / (4096 * freq) - 0.5f);
mode = pca9685_read8(pca, PCA9685_MODE1);
mode &= 0x7F;
sleep = mode | 0x10;
wake = mode & 0xEF;
/* Go to sleep, set prescale and wake up again */
pca9685_write8(pca, PCA9685_MODE1, sleep);
pca9685_write8(pca, PCA9685_PRESCALE, prescale);
pca9685_write8(pca, PCA9685_MODE1, wake);
/*
* wait a millisecond for the oscillator to finish stabilizing
* and restart PWM.
*/
usleep(1000);
pca9685_write8(pca, PCA9685_MODE1, wake | 0x80);
}
/**
* Set all channels back to default values (full off)
*/
void pca9685_reset(struct pca9685 *pca)
{
pca9685_write16(pca, PCA9685_ALL_CHANNELS_ON_L, 0x0000);
pca9685_write16(pca, PCA9685_ALL_CHANNELS_ON_L + 2, 0x1000);
}
/**
* Write on and off ticks manually to a channel
* (Deactivates any full-on and full-off)
*/
void pca9685_set_pwm(struct pca9685 *pca, int ch, int on, int off)
{
int reg = pca9685_channel_reg(ch);
/*
* Write to on and off registers and mask the 12 lowest bits of data
* to overwrite full-on and off.
*/
pca9685_write16(pca, reg, on & 0x0FFF);
pca9685_write16(pca, reg + 2, off & 0x0FFF);
}
/**
* Reads both on and off registers as 16 bit of data
* To get PWM: mask each value with 0x0FFF
* To get full-on or off bit: mask with 0x1000
*/
void pca9685_get_pwm(struct pca9685 *pca, int ch, int *on, int *off)
{
int reg = pca9685_channel_reg(ch);
if (on)
*on = pca9685_read16(pca, reg);
if (off)
*off = pca9685_read16(pca, reg + 2);
}
/**
* Enable/disable full-on
* tf = true: full-on
* tf = false: according to PWM
*/
void pca9685_set_full_on(struct pca9685 *pca, int ch, int tf)
{
int reg = pca9685_channel_reg(ch) + 1; // LEDX_ON_H
int state = pca9685_read8(pca, reg);
if (tf)
state |= 0x10;
else
state &= 0xEF;
pca9685_write8(pca, reg, state);
/* Set full-off to 0 as it has priority over full-on */
if (tf)
pca9685_set_full_off(pca, ch, 0);
}
/**
* Enable/disable full-off
* tf = true: full-off
* tf = false: according to PWM or full-on
*/
void pca9685_set_full_off(struct pca9685 *pca, int ch, int tf)
{
int reg = pca9685_channel_reg(ch) + 3; // LEDX_OFF_H
int state = pca9685_read8(pca, reg);
if (tf)
state |= 0x10;
else
state &= 0xEF;
pca9685_write8(pca, reg, state);
}
/**
* Simple PWM control which sets on-tick to 0 and off-tick to value.
* If value is <= 0, full-off will be enabled
* If value is >= 4096, full-on will be enabled
* Every value in between enables PWM output
*/
void pca9685_pwm(struct pca9685 *pca, int ch, int len)
{
if (len >= 4096) {
pca9685_set_full_on(pca, ch, 1);
return;
}
if (len <= 0) {
pca9685_set_full_off(pca, ch, 1);
return;
}
pca9685_set_pwm(pca, ch, 0, len);
}