Open Model Railroad Network (OpenMRN)
Loading...
Searching...
No Matches
PCA9685PWM.hxx
Go to the documentation of this file.
1
34#include <fcntl.h>
35#include <unistd.h>
36#include "stropts.h"
37#include "i2c.h"
38#include "i2c-dev.h"
39
40#include "PWM.hxx"
41
42
43#include "os/OS.hxx"
44
45class PCA9685PWMBit;
46
48class PCA9685PWM : public OSThread
49{
50public:
52 static constexpr size_t NUM_CHANNELS = 16;
53
55 static constexpr size_t MAX_PWM_COUNTS = 4096;
56
59 : sem_(0)
60 , i2c_(-1)
61 , dirty_(0)
62 , i2cAddress_(0)
63 {
64 duty_.fill(0);
65 }
66
74 void init(const char * name, uint8_t i2c_address,
75 uint16_t pwm_freq = 200, int32_t external_clock_freq = -1)
76 {
77 uint32_t clock_freq = external_clock_freq >= 0 ? external_clock_freq :
78 25000000;
79 i2cAddress_ = i2c_address;
80
81 i2c_ = ::open(name, O_RDWR);
82 HASSERT(i2c_ >= 0);
83 ::ioctl(i2c_, I2C_SLAVE, i2cAddress_);
84
85 HASSERT(clock_freq <= 50000000);
86 HASSERT(pwm_freq < (clock_freq / (4096 * 4)));
87
88 Mode1 mode1;
89 mode1.aI = 1;
90 mode1.sleep = 1;
91 mode1.allCall = 0;
93
94 uint8_t prescaler = (clock_freq / (4096 * (uint32_t)pwm_freq)) - 1;
95 register_write(PRE_SCALE, prescaler);
96
97 if (external_clock_freq < 0)
98 {
99 /* if using internal clock */
100 mode1.sleep = 0;
101 register_write(MODE1, mode1.byte);
102 }
103
104 Mode2 mode2;
105 mode2.och = 1;
106 register_write(MODE2, mode2.byte);
107
108 /* start the thread at the highest priority in the system */
109 start(name, configMAX_PRIORITIES - 1, 1024);
110 }
111
114 {
115 }
116
117private:
120 {
121 MODE1 = 0,
123
125
126 PRE_SCALE = 254,
127 };
128
130 union Mode1
131 {
134 : byte(0x01)
135 {
136 }
137
138 uint8_t byte;
139 struct
140 {
141 uint8_t allCall : 1;
142 uint8_t sub3 : 1;
143 uint8_t sub2 : 1;
144 uint8_t sub1 : 1;
145 uint8_t sleep : 1;
146 uint8_t aI : 1;
147 uint8_t extClk : 1;
148 uint8_t restart : 1;
149 };
150 };
151
153 union Mode2
154 {
157 : byte(0x04)
158 {
159 }
160
161 uint8_t byte;
162 struct
163 {
164 uint8_t outNE : 2;
165 uint8_t outDrv : 1;
166 uint8_t och : 1;
167 uint8_t invert : 1;
168 uint8_t unused : 3;
169 };
170 };
171
173 struct LedCtl
174 {
175 union On
176 {
179 : word(0x0000)
180 {
181 }
182 uint16_t word;
183 struct
184 {
185 uint16_t counts : 12;
186 uint16_t fullOn : 1;
187 uint16_t unused : 3;
188 };
189 };
190 union Off
191 {
194 : word(0x1000)
195 {
196 }
197 uint16_t word;
198 struct
199 {
200 uint16_t counts : 12;
201 uint16_t fullOff : 1;
202 uint16_t unused : 3;
203 };
204 };
207 };
208
211 void *entry() override;
212
218 void bit_modify(Registers address, uint8_t data, uint8_t mask)
219 {
220 uint8_t addr = address;
221 uint8_t rd_data;
222 struct i2c_msg msgs[] =
223 {
224 {
225 .addr = i2cAddress_,
226 .flags = 0,
227 .len = 1,
228 .buf = &addr
229 },
230 {
231 .addr = i2cAddress_,
232 .flags = I2C_M_RD,
233 .len = 1,
234 .buf = &rd_data
235 }
236 };
237
238 struct i2c_rdwr_ioctl_data ioctl_data =
239 {.msgs = msgs, .nmsgs = ARRAYSIZE(msgs)};
240
241 ::ioctl(i2c_, I2C_RDWR, &ioctl_data);
242
243 rd_data &= ~mask;
244 rd_data |= (data & mask);
245
246 register_write(address, rd_data);
247 }
248
252 void set_pwm_duty(unsigned channel, uint16_t counts)
253 {
254 HASSERT(channel < NUM_CHANNELS);
255 duty_[channel] = counts;
256
257 portENTER_CRITICAL();
258 dirty_ |= 0x1 << channel;
259 portEXIT_CRITICAL();
260
261 sem_.post();
262 }
263
267 uint16_t get_pwm_duty(unsigned channel)
268 {
269 HASSERT(channel < NUM_CHANNELS);
270 return duty_[channel];
271 }
272
276 void write_pwm_duty(unsigned channel, uint16_t counts)
277 {
278 LedCtl ctl;
279 if (counts >= MAX_PWM_COUNTS)
280 {
281 ctl.on.fullOn = 1;
282 ctl.off.fullOff = 0;
283 }
284 else if (counts == 0)
285 {
286 ctl.on.fullOn = 0;
287 ctl.off.fullOff = 1;
288 }
289 else
290 {
291 // the "256" count offset is to help average the current accross
292 // all 16 channels when the duty cycle is low
293 ctl.on.counts = (channel * 256);
294 ctl.off.counts = (counts + (channel * 256)) % 0x1000;
295 }
296
297 htole16(ctl.on.word);
298 htole16(ctl.off.word);
299
300 Registers offset = (Registers)(LED0_ON_L + (channel * 4));
301 register_write_multiple(offset, &ctl, sizeof(ctl));
302 }
303
307 void register_write(Registers address, uint8_t data)
308 {
309 uint8_t payload[] = {address, data};
310 ::write(i2c_, payload, sizeof(payload));
311 }
312
317 void register_write_multiple(Registers address, void *data, size_t count)
318 {
319 uint8_t payload[count + 1];
320 payload[0] = address;
321 memcpy(payload + 1, data, count);
322 ::write(i2c_, payload, sizeof(payload));
323 }
324
327
330
332 int i2c_;
333
335 std::array<uint16_t, NUM_CHANNELS> duty_;
336
338 uint16_t dirty_;
339
341 uint8_t i2cAddress_;
342
344};
345
347class PCA9685PWMBit : public PWM
348{
349public:
353 PCA9685PWMBit(PCA9685PWM *instance, unsigned index)
354 : PWM()
355 , instance_(instance)
356 , index_(index)
357 {
359 }
360
363 {
364 }
365
366private:
369 void set_period(uint32_t counts) override
370 {
372 };
373
376 uint32_t get_period() override
377 {
379 }
380
383 void set_duty(uint32_t counts) override
384 {
385 instance_->set_pwm_duty(index_, counts);
386 }
387
390 uint32_t get_duty() override
391 {
393 }
394
397 uint32_t get_period_max() override
398 {
399 return get_period();
400 }
401
404 uint32_t get_period_min() override
405 {
406 return get_period();
407 }
408
411
413 unsigned index_;
414
416};
417
int ioctl(int fd, unsigned long int key,...)
Request and ioctl transaction.
Definition Fileio.cxx:452
This class provides a counting semaphore API.
Definition OS.hxx:243
void post()
Post (increment) a semaphore.
Definition OS.hxx:260
This class provides a threading API.
Definition OS.hxx:46
void start(const char *name, int priority, size_t stack_size)
Starts the thread.
Definition OS.hxx:78
Specialization of the PWM abstract interface for the PCA9685.
void set_period(uint32_t counts) override
Set PWM period.
PCA9685PWMBit(PCA9685PWM *instance, unsigned index)
Constructor.
PCA9685PWM * instance_
instance pointer to the whole chip complement
~PCA9685PWMBit()
Destructor.
unsigned index_
bit index within PCA9685
uint32_t get_period_max() override
Get max period supported.
uint32_t get_duty() override
Gets the duty cycle.
uint32_t get_period_min() override
Get min period supported.
uint32_t get_period() override
Get PWM period.
void set_duty(uint32_t counts) override
Sets the duty cycle.
Agragate of 16 PWM channels for a PCA9685PWM.
void register_write(Registers address, uint8_t data)
Write to an I2C register.
void set_pwm_duty(unsigned channel, uint16_t counts)
Set the pwm duty cycle.
static constexpr size_t MAX_PWM_COUNTS
maximum number of PWM counts supported by the PCA9685
friend PCA9685PWMBit
Allow access to private members.
static constexpr size_t NUM_CHANNELS
maximum number of PWM channels supported by the PCA9685
void bit_modify(Registers address, uint8_t data, uint8_t mask)
Bit modify to an I2C register.
OSSem sem_
Wakeup for the thread processing.
void write_pwm_duty(unsigned channel, uint16_t counts)
Set the pwm duty cycle.
Registers
Important device register offsets.
@ PRE_SCALE
clock prescale divider
@ MODE1
mode 1 settings
@ LED0_ON_L
first LED control register
@ MODE2
mode 2 settings
~PCA9685PWM()
Destructor.
std::array< uint16_t, NUM_CHANNELS > duty_
local cache of the duty cycles
uint16_t get_pwm_duty(unsigned channel)
Get the pwm duty cycle.
int i2c_
I2C file descriptor.
PCA9685PWM()
Constructor.
uint8_t i2cAddress_
I2C address of the device.
uint16_t dirty_
set if the duty_ value is updated (bit mask)
void * entry() override
User entry point for the created thread.
void init(const char *name, uint8_t i2c_address, uint16_t pwm_freq=200, int32_t external_clock_freq=-1)
Initialize device.
void register_write_multiple(Registers address, void *data, size_t count)
Write to multiple sequential I2C registers.
Abstract interface for a PWM driver.
Definition PWM.hxx:43
#define ARRAYSIZE(a)
Returns the number of elements in a statically defined array (of static size)
Definition macros.h:185
#define HASSERT(x)
Checks that the value of expression x is true, else terminates the current process.
Definition macros.h:138
#define DISALLOW_COPY_AND_ASSIGN(TypeName)
Removes default copy-constructor and assignment added by C++.
Definition macros.h:171
Representation of the replicative 4 LED control register.
On on
on registers instance
Off off
off registers instance
uint16_t word
full word data
uint16_t counts
turn off counts
uint16_t fullOff
set if full off
uint16_t counts
turn on counts
uint16_t fullOn
set if full on
uint16_t unused
unused
uint16_t word
full word data
Represent the mode 1 register.
uint8_t extClk
external clock
Mode1()
Constructor.
uint8_t byte
full byte value
uint8_t sleep
sleep enabled
uint8_t allCall
respond to "all call" address
uint8_t aI
auto increment
uint8_t sub1
sub-address 1
uint8_t sub3
sub-address 3
uint8_t sub2
sub-address 2
uint8_t restart
restart
Represent the mode 2 register.
uint8_t och
1 = updata on ack, 0 = update on stop
uint8_t invert
invert output
uint8_t unused
unused
Mode2()
Constructor.
uint8_t byte
full byte data
uint8_t outDrv
1 = push/pull, 0 = open drain
uint8_t outNE
output not enable