File Coverage

blib/lib/Device/Chip/PCA9685.pm
Criterion Covered Total %
statement 70 76 92.1
branch 3 8 37.5
condition 2 5 40.0
subroutine 23 24 95.8
pod 7 8 87.5
total 105 121 86.7


line stmt bran cond sub pod time code
1             package Device::Chip::PCA9685;
2              
3 4     4   77871 use strict;
  4         6  
  4         86  
4 4     4   13 use warnings;
  4         4  
  4         130  
5              
6             our $VERSION = 'v0.9';
7              
8 4     4   20 use base qw/Device::Chip/;
  4         4  
  4         610  
9              
10 4     4   7817 use Future;
  4         4  
  4         66  
11 4     4   13 use Time::HiRes q/usleep/;
  4         3  
  4         17  
12              
13             =head1 NAME
14              
15             C - A C implementation for the PCA9685 chip
16              
17             =head1 DESCRIPTION
18              
19             This class implements a L interface for the PCA9685 chip, a 12-bit 16 channel PWM driver.
20              
21             =head1 SYNOPSIS
22              
23             use Device::Chip::PCA9685;
24             use Device::Chip::Adapter;
25              
26             my $adapter = Device::Chip::Adapter->new_from_description("LinuxKernel");
27              
28             my $chip = Device::Chip::PCA9685->new();
29             # This is the i2c bus on an RPI 2 B+
30             $chip->mount($adapter, bus => '/dev/i2c-1')->get;
31            
32             $chip->enable()->get;
33             $chip->set_frequency(400)->get; # 400 Hz
34            
35             $chip->set_channel_value(10, 1024)->get; # Set channel 10 to 25% (1024/4096)
36            
37             $chip->set_channel_full_value(10, 1024, 3192)->get; # Set channel 10 to ON at COUNTER=1024, and OFF at COUNTER=3192 (50% duty cycle, with 25% phase difference)
38              
39             =head1 METHODS
40              
41             =cut
42              
43             my %REGS = (
44             MODE1 => {addr => 0},
45             MODE2 => {addr => 1},
46             SUBADR1 => {addr => 2},
47             SUBADR2 => {addr => 3},
48             SUBADR3 => {addr => 4},
49             ALLCALLADR => {addr => 5},
50             ALL_CHAN_ON => {addr => 0xFA}, # 16bit
51             ALL_CHAN_OFF => {addr => 0xFC}, # 16bit
52             PRE_SCALE => {addr => 0xFE},
53             TEST_MODE => {addr => 0xFF},
54             );
55              
56             for my $n (0..15) {
57             $REGS{"CHAN${n}_ON"} = {addr => 0x06 + $n * 4}; # 16bit
58             $REGS{"CHAN${n}_OFF"} = {addr => 0x08 + $n * 4}; # 16bit
59             }
60              
61 4     4   1209 use utf8;
  4         12  
  4         14  
62              
63 4     4   78 use constant PROTOCOL => "I2C";
  4         3  
  4         1704  
64              
65             sub _read_u8 {
66 1     1   1 my $self = shift;
67 1         1 my ($register) = @_;
68            
69 1         3 my $regv = $REGS{$register}{addr};
70            
71             $self->protocol->write_then_read("\0", 1)->then( sub {
72 1     1   94 my ($value) = @_;
73 1         3 return Future->done(unpack("C", $value));
74 1         2 });
75             }
76              
77             sub _write_u8 {
78 5     5   46 my $self = shift;
79 5         8 my ($register, $value) = @_;
80              
81 5         10 my $regv = $REGS{$register}{addr};
82              
83 5         12 $self->protocol->write(pack("C C", $regv, $value));
84             }
85              
86             sub _write_u16 {
87 3     3   3 my $self = shift;
88 3         5 my ($register, @values) = @_;
89              
90 3         5 my $regv = $REGS{$register}{addr};
91              
92 3         7 $self->protocol->write(pack("C (S<)*", $regv, @values));
93             }
94              
95 3     3 0 496 sub I2C_options {my $self = shift; return (addr => 0x40, @_)}; # pass it through, but allow the address to change if passed in, should use a constructor instead
  3         11  
96              
97             =head2 set_channel_value
98              
99             $chip->set_channel_value($channel, $time_on, $offset = 0)->get
100            
101             Sets a channel PWM time based on a single value from 0-4095. Starts the channel to turn on at COUNTER = 0, and off at $time_on.
102             C<$offset> lets you stagger the time that the channel comes on and off. This lets you vary the times that channels go on and off
103             to reduce noise effects and power supply issues from large loads all coming on at once.
104              
105             C<$on_time> := 0; C<$off_time> := $time_on;
106              
107             =cut
108              
109             sub set_channel_value {
110 1     1 1 78 my $self = shift;
111 1         1 my ($chan, $time_on, $offset) = @_;
112 1   50     5 $offset //= 0;
113            
114             # set the high parts first, we shouldn't ever have backtracking then
115              
116 1 50 33     7 if ($time_on < 0 || $time_on >= 4096) {
117 0 0       0 $time_on = $time_on >= 4096 ? 4095 : 0;
118 0         0 warn "Channel outside allowed value, clamping: $chan, $time_on\n";
119             }
120              
121 1         2 $offset %= 4096; # wrap the offset around, that way you can increment it by any amount and have it work as expected
122 1         1 $time_on = ($time_on + $offset) % 4096; # wrap it around based on the offset.
123            
124 1         3 $self->set_channel_full_value($chan, $offset, $time_on);
125             }
126              
127             =head2 set_channel_full_value
128              
129             $chip->set_channel_full_value($channel, $on_time, $off_time)->get
130            
131             Set a channel value, on and off time. Lets you control the full on and off time based on the 12 bit counter on the device.
132              
133             =cut
134              
135             sub set_channel_full_value {
136 1     1 1 2 my ($self, $chan, $on_t, $off_t) = @_;
137              
138 1         5 $self->_write_u16("CHAN${chan}_ON" => ($on_t & 0x0FFF), ($off_t & 0x0FFF));
139             }
140              
141             =head2 set_channel_on
142              
143             $chip->set_channel_on($channel)->get
144            
145             Set a channel to full on. No off time at all.
146              
147             =cut
148              
149             sub set_channel_on {
150 1     1 1 1072 my ($self, $chan) = @_;
151            
152             # Set bit 4 of ON high, this is the bit that sets the channel to full on
153 1         4 $self->_write_u16("CHAN${chan}_ON" => 0x1000, 0x0000);
154             }
155              
156             =head2 set_channel_off
157              
158             $chip->set_channel_off($channel)->get
159            
160             Set a channel to full off. No on time at all.
161              
162             =cut
163              
164             sub set_channel_off {
165 1     1 1 806 my ($self, $chan) = @_;
166            
167             # Set bit 4 of OFF high, this is the bit that sets the channel to full off
168 1         4 $self->_write_u16("CHAN${chan}_ON" => 0x0000, 0x1000);
169             }
170              
171             =head2 set_default_mode
172              
173             $chip->set_default_mode()->get
174            
175             Reset the default mode back to the PCA9685.
176              
177             =cut
178              
179             sub set_default_mode {
180 0     0 1 0 my $self = shift;
181             # Sets all the mode registers to the chip defaults
182 0         0 Future->needs_all(
183             $self->_write_u8(MODE1 => 0b0000_0001),
184             $self->_write_u8(MODE2 => 0b000_00100),
185             );
186             }
187              
188             =head2 set_frequency
189              
190             $chip->set_frequency()
191            
192             Set the prescaler to the desired frequency for PWM. Returns the real frequency due to rounding.
193              
194             =cut
195              
196             sub set_frequency {
197 1     1 1 106 my $self = shift;
198 1         1 my ($freq) = @_;
199 4     4   4010 use Data::Dumper;
  4         17882  
  4         857  
200              
201 1         6 my $divisor = int( ( 25000000 / ( 4096 * $freq ) ) + 0.5 ) - 1;
202 1 50       2 if ($divisor < 3) { die "PCA9685 forces the scaler to be at least >= 3 (1526 Hz)." };
  0         0  
203 1 50       3 if ($divisor > 255) { die "PCA9685 forces the scaler to be <= 255 (24Hz)." };
  0         0  
204              
205 1         1 my $realfreq = 25000000 / (($divisor + 1)*(4096));
206            
207 1         1 my $old_mode1;
208             $self->_read_u8("MODE1")->then( sub {
209 1     1   57 ( $old_mode1 ) = @_;
210              
211 1         2 my $new_mode1 = ($old_mode1 & 0x7f) | 0x10; # Set the chip to sleep, make sure reset is disabled while we do this to avoid noise/phase differences
212            
213 1         4 $self->_write_u8(MODE1 => $new_mode1);
214             })->then( sub {
215 1     1   92 Future->needs_all(
216             $self->_write_u8(PRE_SCALE => $divisor),
217             $self->_write_u8(MODE1 => $old_mode1),
218             );
219             })->then( sub {
220 1     1   5248 usleep(5000);
221 1         7 $self->_write_u8(MODE1 => $old_mode1 | 0x80); # turn on the external clock, should this be optional?
222             })->then( sub {
223 1     1   109 return Future->done( $realfreq );
224 1         4 });
225             }
226              
227             =head2 enable
228              
229             $chip->enable()->get
230              
231             Enable the device. Must be the first thing done with the device.
232              
233             =cut
234              
235             sub enable {
236 1     1 1 78 my $self = shift;
237              
238             # 0x20 == AI, auto-increment addresses during register transfer
239             # Useful for 16bit read/write
240 1         3 $self->_write_u8(MODE1 => 0x20);
241             }
242              
243             =head1 AUTHOR
244              
245             Ryan Voots,
246             Paul 'LeoNerd' Evans
247              
248             =cut
249              
250             1;