File Coverage

blib/lib/Device/BusPirate/Chip/DS1307.pm
Criterion Covered Total %
statement 33 86 38.3
branch 0 4 0.0
condition 0 5 0.0
subroutine 11 39 28.2
pod 2 17 11.7
total 46 151 30.4


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2015 -- leonerd@leonerd.org.uk
5              
6             package Device::BusPirate::Chip::DS1307;
7              
8 1     1   429 use strict;
  1         1  
  1         27  
9 1     1   4 use warnings;
  1         1  
  1         25  
10 1     1   21 use 5.010;
  1         2  
  1         35  
11 1     1   5 use base qw( Device::BusPirate::Chip );
  1         1  
  1         422  
12              
13             our $VERSION = '0.01';
14              
15 1     1   318 use Carp;
  1         1  
  1         47  
16              
17 1     1   3 use constant CHIP => "DS1307";
  1         1  
  1         38  
18 1     1   3 use constant MODE => "I2C";
  1         1  
  1         27  
19              
20 1     1   3 use constant DEFAULT_ADDR => 0x68;
  1         1  
  1         26  
21              
22 1     1   546 use Future;
  1         7217  
  1         81  
23              
24             =head1 NAME
25              
26             C - use a F chip with C
27              
28             =head1 DESCRIPTION
29              
30             This L subclass provides specific communication to a
31             F F chip attached to the F via I2C.
32              
33             =cut
34              
35             sub new
36             {
37 0     0 1   my $class = shift;
38 0           my ( $bp, %opts ) = @_;
39              
40 0           my $self = $class->SUPER::new( @_ );
41              
42 0           $self->{$_} = $opts{$_} for qw( address );
43              
44 0   0       $self->{address} //= DEFAULT_ADDR;
45              
46 0           return $self;
47             }
48              
49             use constant {
50 1         80 REG_SECONDS => 0x00,
51             REG_MINUTES => 0x01,
52             REG_HOURS => 0x02,
53             REG_WDAY => 0x03,
54             REG_MDAY => 0x04,
55             REG_MONTH => 0x05,
56             REG_YEAR => 0x06,
57             REG_CONTROL => 0x07,
58 1     1   5 };
  1         1  
59              
60             use constant {
61             # REG_SECONDS
62 1         949 MASK_CLOCKHALT => 1<<7,
63              
64             # REG_HOURS
65             MASK_12H => 1<<6,
66             MASK_PM => 1<<5,
67              
68             # REG_CONTROL
69             MASK_OUT => 1<<7,
70             MASK_SQWE => 1<<4,
71             MASK_RS => 3<<0,
72 1     1   4 };
  1         0  
73              
74             sub _read
75             {
76 0     0     my $self = shift;
77 0           my ( $reg ) = @_;
78              
79             $self->mode->send_then_recv( $self->{address}, chr $reg, 1 )->then( sub {
80 0     0     Future->done( ord $_[0] );
81 0           });
82             }
83              
84             sub _write
85             {
86 0     0     my $self = shift;
87 0           my ( $reg, $value ) = @_;
88              
89 0           $self->mode->send( $self->{address}, chr( $reg ) . chr( $value ) );
90             }
91              
92 0     0     sub _unpack_bcd { ( $_[0] >> 4 )*10 + ( $_[0] % 16 ) }
93 0     0     sub _pack_bcd { int( $_[0] / 10 ) << 4 | ( $_[0] % 10 ) }
94              
95             sub _read_bcd
96             {
97 0     0     my $self = shift;
98 0           my ( $reg ) = @_;
99 0     0     $self->_read( $reg )->then( sub { Future->done( _unpack_bcd $_[0] ) } );
  0            
100             }
101              
102             sub _write_bcd
103             {
104 0     0     my $self = shift;
105 0           my ( $reg, $value ) = @_;
106 0           $self->_write( $reg, _pack_bcd( $value ) );
107             }
108              
109             =head1 METHODS
110              
111             The following methods documented with a trailing call to C<< ->get >> return
112             L instances.
113              
114             =cut
115              
116             =head2 $v = $ds->read_I->get
117              
118             Reads a timekeeping field and returns a decimal integer. The following fields
119             are recognised:
120              
121             seconds minutes hours wday mday month year
122              
123             The C field is always returned in 24-hour mode, even if the chip is in
124             12-hour ("AM/PM") mode.
125              
126             =cut
127              
128             # REG_SECONDS also contains the CLOCK HALTED flag
129             sub read_seconds {
130             shift->_read( REG_SECONDS )->then( sub {
131 0     0     my ( $v ) = @_;
132 0           $v &= ~MASK_CLOCKHALT;
133 0           Future->done( _unpack_bcd $v );
134 0     0 0   });
135             }
136              
137 0     0 0   sub read_minutes { shift->_read_bcd( REG_MINUTES ) }
138              
139             # REG_HOURS is either in 12 or 24-hour mode.
140             sub read_hours {
141             shift->_read( REG_HOURS )->then( sub {
142 0     0     my ( $v ) = @_;
143 0 0         if( $v & MASK_12H ) {
144 0           my $pm = $v & MASK_PM;
145 0           $v &= ~(MASK_12H|MASK_PM);
146 0           Future->done( _unpack_bcd( $v ) + 12*$pm );
147             }
148             else {
149 0           Future->done( _unpack_bcd $v );
150             }
151 0     0 0   });
152             }
153              
154 0     0 0   sub read_wday { shift->_read ( REG_WDAY ) }
155 0     0 0   sub read_mday { shift->_read_bcd( REG_MDAY ) }
156 0     0 0   sub read_month { shift->_read_bcd( REG_MONTH ) }
157 0     0 0   sub read_year { shift->_read_bcd( REG_YEAR ) }
158              
159             =head2 $ds->write_I->get
160              
161             Writes a timekeeping field as a decimal integer. The following fields are
162             recognised:
163              
164             seconds minutes hours wday mday month year
165              
166             The C field is always written back in 24-hour mode.
167              
168             =cut
169              
170 0     0 0   sub write_seconds { $_[0]->_write_bcd( REG_SECONDS, $_[1] ) }
171 0     0 0   sub write_minutes { $_[0]->_write_bcd( REG_MINUTES, $_[1] ) }
172 0     0 0   sub write_hours { $_[0]->_write_bcd( REG_HOURS, $_[1] ) }
173 0     0 0   sub write_wday { $_[0]->_write ( REG_WDAY, $_[1] ) }
174 0     0 0   sub write_mday { $_[0]->_write_bcd( REG_MDAY, $_[1] ) }
175 0     0 0   sub write_month { $_[0]->_write_bcd( REG_MONTH, $_[1] ) }
176 0     0 0   sub write_year { $_[0]->_write_bcd( REG_YEAR, $_[1] ) }
177              
178             =head2 @tm = $ds->read_time->get
179              
180             Returns a 7-element C-compatible list of values by reading the
181             timekeeping registers, suitable for passing to C, etc... Note
182             that the returned list does not contain the C or C fields.
183              
184             Because the F only stores a 2-digit year number, the year is presumed
185             to be in the range C<2000>-C<2099>.
186              
187             This method presumes C-compatible semantics for the C field
188             stored on the chip; i.e. that 0 is Sunday.
189              
190             This method performs an atomic reading of all the timekeeping registers as a
191             single I2C transaction, so is preferrable to invoking multiple calls to
192             individual read methods.
193              
194             =cut
195              
196             sub read_time
197             {
198 0     0 0   my $self = shift;
199              
200             $self->mode->send_then_recv( $self->{address}, chr( REG_SECONDS ), 7 )->then( sub {
201 0     0     my ( $bcd_sec, $bcd_min, $bcd_hour,
202             $wday, $bcd_mday, $bcd_mon, $bcd_year ) = unpack "C7", $_[0];
203              
204 0           Future->done(
205             _unpack_bcd( $bcd_sec ),
206             _unpack_bcd( $bcd_min ),
207             _unpack_bcd( $bcd_hour ),
208             _unpack_bcd( $bcd_mday ),
209             _unpack_bcd( $bcd_mon ) - 1,
210             _unpack_bcd( $bcd_year ) + 100,
211             $wday,
212             );
213 0           });
214             }
215              
216             =head2 $ds->write_time( @tm )->get
217              
218             Writes the timekeeping registers from a 7-element C-compatible list
219             of values. This method ignores the C and C fields, if present.
220              
221             Because the F only stores a 2-digit year number, the year must be in
222             the range C<2000>-C<2099> (i.e. numerical values of C<100> to C<199>).
223              
224             This method performs an atomic writing of all the timekeeping registers as a
225             single I2C transaction, so is preferrable to invoking multiple calls to
226             individual write methods.
227              
228             =cut
229              
230             sub write_time
231             {
232 0     0 1   my $self = shift;
233 0           my ( $sec, $min, $hour, $mday, $mon, $year, $wday ) = @_;
234              
235 0 0 0       $year >= 100 and $year <= 199 or croak "Invalid year ($year)";
236              
237 0           $self->mode->send( $self->{address}, pack "C8", REG_SECONDS,
238             _pack_bcd( $sec ),
239             _pack_bcd( $min ),
240             _pack_bcd( $hour ),
241             _pack_bcd( $wday ),
242             _pack_bcd( $mday ),
243             _pack_bcd( $mon + 1 ),
244             _pack_bcd( $year - 100 ),
245             );
246             }
247              
248             =head1 AUTHOR
249              
250             Paul Evans
251              
252             =cut
253              
254             0x55AA;