line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package VIC::PIC::Functions::ECCP; |
2
|
31
|
|
|
31
|
|
19964
|
use strict; |
|
31
|
|
|
|
|
987
|
|
|
31
|
|
|
|
|
2719
|
|
3
|
31
|
|
|
31
|
|
117
|
use warnings; |
|
31
|
|
|
|
|
41
|
|
|
31
|
|
|
|
|
3212
|
|
4
|
31
|
|
|
31
|
|
122
|
use bigint; |
|
31
|
|
|
|
|
33
|
|
|
31
|
|
|
|
|
923
|
|
5
|
|
|
|
|
|
|
our $VERSION = '0.31'; |
6
|
|
|
|
|
|
|
$VERSION = eval $VERSION; |
7
|
31
|
|
|
31
|
|
19717
|
use Carp; |
|
31
|
|
|
|
|
1900
|
|
|
31
|
|
|
|
|
3789
|
|
8
|
31
|
|
|
31
|
|
882
|
use POSIX (); |
|
31
|
|
|
|
|
39
|
|
|
31
|
|
|
|
|
2017
|
|
9
|
31
|
|
|
31
|
|
104
|
use Moo::Role; |
|
31
|
|
|
|
|
37
|
|
|
31
|
|
|
|
|
1646
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
#FIXME: C2OUT and P1B may be conflicting. check datasheet |
12
|
|
|
|
|
|
|
sub _pwm_details { |
13
|
6
|
|
|
6
|
|
14
|
my ($self, $pwm_frequency, $duty, $type, @pins) = @_; |
14
|
6
|
50
|
|
|
|
13
|
return unless $self->doesrole('Chip'); |
15
|
6
|
50
|
|
|
|
27
|
unless (exists $self->registers->{CCP1CON}) { |
16
|
0
|
|
|
|
|
0
|
carp $self->type, " does not have CCP1CON for ECCP features"; |
17
|
0
|
|
|
|
|
0
|
return; |
18
|
|
|
|
|
|
|
} |
19
|
31
|
|
|
31
|
|
14588
|
no bigint; |
|
31
|
|
|
|
|
42
|
|
|
31
|
|
|
|
|
881
|
|
20
|
|
|
|
|
|
|
#pulse_width = $duty / $pwm_frequency; |
21
|
|
|
|
|
|
|
# timer2 prescaler |
22
|
6
|
|
|
|
|
10
|
my $prescaler = 1; # can be 1, 4 or 16 |
23
|
|
|
|
|
|
|
# Tosc = 1 / Fosc |
24
|
6
|
|
|
|
|
21
|
my $f_osc = $self->f_osc; |
25
|
6
|
|
|
|
|
53
|
my $pr2 = POSIX::ceil(($f_osc / 4) / $pwm_frequency); # assume prescaler = 1 here |
26
|
6
|
50
|
|
|
|
18
|
if (($pr2 - 1) <= 0xFF) { |
27
|
0
|
|
|
|
|
0
|
$prescaler = 1; # prescaler stays 1 |
28
|
|
|
|
|
|
|
} else { |
29
|
6
|
|
|
|
|
17
|
$pr2 = POSIX::ceil($pr2 / 4); # prescaler is 4 or 16 |
30
|
6
|
50
|
|
|
|
14
|
$prescaler = (($pr2 - 1) <= 0xFF) ? 4 : 16; |
31
|
|
|
|
|
|
|
} |
32
|
6
|
|
|
|
|
10
|
my $t2con = q{b'00000100'}; # prescaler is 1 or anything else |
33
|
6
|
50
|
|
|
|
13
|
$t2con = q{b'00000101'} if $prescaler == 4; |
34
|
6
|
50
|
|
|
|
14
|
$t2con = q{b'00000111'} if $prescaler == 16; |
35
|
|
|
|
|
|
|
# readjusting PR2 as per supported pre-scalers |
36
|
6
|
|
|
|
|
15
|
$pr2 = POSIX::ceil((($f_osc / 4) / $pwm_frequency) / $prescaler); |
37
|
6
|
|
|
|
|
8
|
$pr2--; |
38
|
6
|
|
|
|
|
6
|
$pr2 &= 0xFF; |
39
|
6
|
|
|
|
|
18
|
my $ccpr1l_ccp1con54 = POSIX::ceil(($duty * 4 * ($pr2)) / 100.0); |
40
|
6
|
|
|
|
|
9
|
my $ccp1con5 = ($ccpr1l_ccp1con54 & 0x02); #bit 5 |
41
|
6
|
|
|
|
|
7
|
my $ccp1con4 = ($ccpr1l_ccp1con54 & 0x01); #bit 4 |
42
|
6
|
|
|
|
|
8
|
my $ccpr1l = ($ccpr1l_ccp1con54 >> 2) & 0xFF; |
43
|
6
|
|
|
|
|
25
|
my $ccpr1l_x = sprintf "0x%02X", $ccpr1l; |
44
|
6
|
|
|
|
|
9
|
my $pr2_x = sprintf "0x%02X", ($pr2 - 1); ##HACK |
45
|
6
|
100
|
|
|
|
16
|
my $p1m = '00' if $type eq 'single'; |
46
|
6
|
100
|
|
|
|
14
|
$p1m = '01' if $type eq 'full_forward'; |
47
|
6
|
100
|
|
|
|
13
|
$p1m = '10' if $type eq 'half'; |
48
|
6
|
100
|
|
|
|
9
|
$p1m = '11' if $type eq 'full_reverse'; |
49
|
6
|
50
|
|
|
|
15
|
$p1m = '00' unless defined $p1m; |
50
|
6
|
|
|
|
|
17
|
my $ccp1con = sprintf "b'%s%d%d1100'", $p1m, $ccp1con5, $ccp1con4; |
51
|
6
|
|
|
|
|
31
|
my %str = (CCP1 => 0, P1D => 0, P1C => 0, P1B => 0, P1A => 0); # default all are port pins |
52
|
6
|
|
|
|
|
8
|
my %trisc = (); |
53
|
6
|
|
|
|
|
10
|
foreach my $pin (@pins) { |
54
|
16
|
50
|
|
|
|
166
|
unless (exists $self->pins->{$pin}) { |
55
|
0
|
|
|
|
|
0
|
carp "$pin is not a valid pin on the microcontroller. Ignoring\n"; |
56
|
0
|
|
|
|
|
0
|
next; |
57
|
|
|
|
|
|
|
} |
58
|
16
|
|
|
|
|
26
|
my $pinno = $self->pins->{$pin}; |
59
|
16
|
|
|
|
|
26
|
my $allpins = $self->pins->{$pinno}; |
60
|
16
|
|
|
|
|
11
|
my $pwm_pin; |
61
|
16
|
|
|
|
|
22
|
foreach (@$allpins) { |
62
|
48
|
100
|
|
|
|
80
|
next unless exists $self->eccp_pins->{$_}; |
63
|
16
|
|
|
|
|
14
|
$pwm_pin = $_; |
64
|
16
|
|
|
|
|
16
|
last; |
65
|
|
|
|
|
|
|
} |
66
|
16
|
50
|
|
|
|
23
|
next unless defined $pwm_pin; |
67
|
|
|
|
|
|
|
# the user may use say RC5 instead of CCP1 and we still want the |
68
|
|
|
|
|
|
|
# CCP1 name which should really be returned as P1A here |
69
|
|
|
|
|
|
|
# pulse steering only needed in Single mode |
70
|
16
|
|
|
|
|
13
|
my ($p0, $trisp, $portpin) = @{$self->eccp_pins->{$pwm_pin}}; |
|
16
|
|
|
|
|
34
|
|
71
|
16
|
100
|
|
|
|
22
|
$str{$pwm_pin} = 1 if $type eq 'single'; |
72
|
16
|
|
|
|
|
30
|
$trisc{$portpin} = $trisp; |
73
|
|
|
|
|
|
|
} |
74
|
6
|
|
33
|
|
|
22
|
my $p1a = $str{P1A} || $str{CCP1}; |
75
|
6
|
|
|
|
|
18
|
my $pstrcon = sprintf "b'0001%d%d%d%d'", $str{P1D}, $str{P1C}, $str{P1B}, $p1a; |
76
|
6
|
|
|
|
|
7
|
my $trisc_bsf = ''; |
77
|
6
|
|
|
|
|
8
|
my $trisc_bcf = ''; |
78
|
6
|
|
|
|
|
23
|
foreach (sort (keys %trisc)) { |
79
|
16
|
|
|
|
|
15
|
my $trisp = $trisc{$_}; |
80
|
16
|
|
|
|
|
30
|
$trisc_bsf .= "\tbsf $trisp, $trisp$_\n"; |
81
|
16
|
|
|
|
|
24
|
$trisc_bcf .= "\tbcf $trisp, $trisp$_\n"; |
82
|
|
|
|
|
|
|
} |
83
|
6
|
|
|
|
|
5
|
my $pstrcon_code = ''; |
84
|
6
|
100
|
|
|
|
14
|
if ($type eq 'single') { |
85
|
3
|
|
|
|
|
7
|
$pstrcon_code = << "..."; |
86
|
|
|
|
|
|
|
\tbanksel PSTRCON |
87
|
|
|
|
|
|
|
\tmovlw $pstrcon |
88
|
|
|
|
|
|
|
\tmovwf PSTRCON |
89
|
|
|
|
|
|
|
... |
90
|
|
|
|
|
|
|
} |
91
|
|
|
|
|
|
|
return ( |
92
|
|
|
|
|
|
|
# actual register values |
93
|
6
|
|
|
|
|
95
|
CCP1CON => $ccp1con, |
94
|
|
|
|
|
|
|
PR2 => $pr2_x, |
95
|
|
|
|
|
|
|
T2CON => $t2con, |
96
|
|
|
|
|
|
|
CCPR1L => $ccpr1l_x, |
97
|
|
|
|
|
|
|
PSTRCON => $pstrcon, |
98
|
|
|
|
|
|
|
PSTRCON_CODE => $pstrcon_code, |
99
|
|
|
|
|
|
|
# no ECCPAS |
100
|
|
|
|
|
|
|
PWM1CON => '0x80', # default |
101
|
|
|
|
|
|
|
# code to be added |
102
|
|
|
|
|
|
|
TRISC_BSF => $trisc_bsf, |
103
|
|
|
|
|
|
|
TRISC_BCF => $trisc_bcf, |
104
|
|
|
|
|
|
|
# general comments |
105
|
|
|
|
|
|
|
CCPR1L_CCP1CON54 => $ccpr1l_ccp1con54, |
106
|
|
|
|
|
|
|
FOSC => $f_osc, |
107
|
|
|
|
|
|
|
PRESCALER => $prescaler, |
108
|
|
|
|
|
|
|
PWM_FREQUENCY => $pwm_frequency, |
109
|
|
|
|
|
|
|
DUTYCYCLE => $duty, |
110
|
|
|
|
|
|
|
PINS => \@pins, |
111
|
|
|
|
|
|
|
TYPE => $type, |
112
|
|
|
|
|
|
|
); |
113
|
|
|
|
|
|
|
} |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
sub _pwm_code { |
116
|
5
|
|
|
5
|
|
8
|
my $self = shift; |
117
|
5
|
|
|
|
|
31
|
my %details = @_; |
118
|
5
|
|
|
|
|
8
|
my @pins = @{$details{PINS}}; |
|
5
|
|
|
|
|
11
|
|
119
|
5
|
|
|
|
|
101
|
return << "..."; |
120
|
|
|
|
|
|
|
;;; PWM Type: $details{TYPE} |
121
|
|
|
|
|
|
|
;;; PWM Frequency = $details{PWM_FREQUENCY} Hz |
122
|
|
|
|
|
|
|
;;; Duty Cycle = $details{DUTYCYCLE} / 100 |
123
|
|
|
|
|
|
|
;;; CCPR1L:CCP1CON<5:4> = $details{CCPR1L_CCP1CON54} |
124
|
|
|
|
|
|
|
;;; CCPR1L = $details{CCPR1L} |
125
|
|
|
|
|
|
|
;;; CCP1CON = $details{CCP1CON} |
126
|
|
|
|
|
|
|
;;; T2CON = $details{T2CON} |
127
|
|
|
|
|
|
|
;;; PR2 = $details{PR2} |
128
|
|
|
|
|
|
|
;;; PSTRCON = $details{PSTRCON} |
129
|
|
|
|
|
|
|
;;; PWM1CON = $details{PWM1CON} |
130
|
|
|
|
|
|
|
;;; Prescaler = $details{PRESCALER} |
131
|
|
|
|
|
|
|
;;; Fosc = $details{FOSC} |
132
|
|
|
|
|
|
|
;;; disable the PWM output driver for @pins by setting the associated TRIS bit |
133
|
|
|
|
|
|
|
\tbanksel TRISC |
134
|
|
|
|
|
|
|
$details{TRISC_BSF} |
135
|
|
|
|
|
|
|
;;; set PWM period by loading PR2 |
136
|
|
|
|
|
|
|
\tbanksel PR2 |
137
|
|
|
|
|
|
|
\tmovlw $details{PR2} |
138
|
|
|
|
|
|
|
\tmovwf PR2 |
139
|
|
|
|
|
|
|
;;; configure the CCP module for the PWM mode by setting CCP1CON |
140
|
|
|
|
|
|
|
\tbanksel CCP1CON |
141
|
|
|
|
|
|
|
\tmovlw $details{CCP1CON} |
142
|
|
|
|
|
|
|
\tmovwf CCP1CON |
143
|
|
|
|
|
|
|
;;; set PWM duty cycle |
144
|
|
|
|
|
|
|
\tmovlw $details{CCPR1L} |
145
|
|
|
|
|
|
|
\tmovwf CCPR1L |
146
|
|
|
|
|
|
|
;;; configure and start TMR2 |
147
|
|
|
|
|
|
|
;;; - clear TMR2IF flag of PIR1 register |
148
|
|
|
|
|
|
|
\tbanksel PIR1 |
149
|
|
|
|
|
|
|
\tbcf PIR1, TMR2IF |
150
|
|
|
|
|
|
|
\tmovlw $details{T2CON} |
151
|
|
|
|
|
|
|
\tmovwf T2CON |
152
|
|
|
|
|
|
|
;;; enable PWM output after a new cycle has started |
153
|
|
|
|
|
|
|
\tbtfss PIR1, TMR2IF |
154
|
|
|
|
|
|
|
\tgoto \$ - 1 |
155
|
|
|
|
|
|
|
\tbcf PIR1, TMR2IF |
156
|
|
|
|
|
|
|
;;; enable @pins pin output driver by clearing the associated TRIS bit |
157
|
|
|
|
|
|
|
$details{PSTRCON_CODE} |
158
|
|
|
|
|
|
|
;;; disable auto-shutdown mode |
159
|
|
|
|
|
|
|
\tbanksel ECCPAS |
160
|
|
|
|
|
|
|
\tclrf ECCPAS |
161
|
|
|
|
|
|
|
;;; set PWM1CON if half bridge mode |
162
|
|
|
|
|
|
|
\tbanksel PWM1CON |
163
|
|
|
|
|
|
|
\tmovlw $details{PWM1CON} |
164
|
|
|
|
|
|
|
\tmovwf PWM1CON |
165
|
|
|
|
|
|
|
\tbanksel TRISC |
166
|
|
|
|
|
|
|
$details{TRISC_BCF} |
167
|
|
|
|
|
|
|
... |
168
|
|
|
|
|
|
|
} |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
sub pwm_single { |
171
|
2
|
|
|
2
|
0
|
14
|
my ($self, $pwm_frequency, $duty, @pins) = @_; |
172
|
2
|
50
|
|
|
|
10
|
return unless $self->doesrole('ECCP'); |
173
|
2
|
50
|
|
|
|
16
|
unless (exists $self->eccp_pins->{P1A}) { |
174
|
0
|
0
|
|
|
|
0
|
if (exists $self->eccp_pins->{CCP1}) { |
175
|
|
|
|
|
|
|
# override the pins to CCP1 |
176
|
0
|
|
|
|
|
0
|
@pins = qw(CCP1); |
177
|
|
|
|
|
|
|
} |
178
|
|
|
|
|
|
|
} |
179
|
2
|
|
|
|
|
10
|
my %details = $self->_pwm_details($pwm_frequency, $duty, 'single', @pins); |
180
|
|
|
|
|
|
|
# pulse steering automatically taken care of |
181
|
2
|
|
|
|
|
12
|
return $self->_pwm_code(%details); |
182
|
|
|
|
|
|
|
} |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
sub pwm_halfbridge { |
185
|
1
|
|
|
1
|
0
|
9
|
my ($self, $pwm_frequency, $duty, $deadband, @pins) = @_; |
186
|
1
|
50
|
|
|
|
3
|
return unless $self->doesrole('ECCP'); |
187
|
1
|
50
|
33
|
|
|
10
|
if (exists $self->eccp_pins->{P1A} and exists $self->eccp_pins->{P1B}) { |
188
|
|
|
|
|
|
|
# we ignore the @pins that comes in |
189
|
1
|
|
|
|
|
3
|
@pins = qw(P1A P1B); |
190
|
|
|
|
|
|
|
} else { |
191
|
0
|
|
|
|
|
0
|
carp $self->type, " has no Enhanced PWM capabilities"; |
192
|
0
|
|
|
|
|
0
|
return; |
193
|
|
|
|
|
|
|
} |
194
|
1
|
|
|
|
|
4
|
my %details = $self->_pwm_details($pwm_frequency, $duty, 'half', @pins); |
195
|
|
|
|
|
|
|
# override PWM1CON |
196
|
1
|
50
|
33
|
|
|
8
|
if (defined $deadband and $deadband > 0) { |
197
|
1
|
|
|
|
|
71
|
my $fosc = $details{FOSC}; |
198
|
1
|
|
|
|
|
5
|
my $pwm1con = $deadband * $fosc / 4e6; # $deadband is in microseconds |
199
|
1
|
|
|
|
|
90
|
$pwm1con &= 0x7F; # 6-bits only |
200
|
1
|
|
|
|
|
103
|
$pwm1con |= 0x80; # clear PRSEN bit |
201
|
1
|
|
|
|
|
84
|
$details{PWM1CON} = sprintf "0x%02X", $pwm1con; |
202
|
|
|
|
|
|
|
} |
203
|
1
|
|
|
|
|
18
|
return $self->_pwm_code(%details); |
204
|
|
|
|
|
|
|
} |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
sub pwm_fullbridge { |
207
|
2
|
|
|
2
|
0
|
16
|
my ($self, $direction, $pwm_frequency, $duty, @pins) = @_; |
208
|
2
|
50
|
|
|
|
7
|
return unless $self->doesrole('ECCP'); |
209
|
2
|
50
|
33
|
|
|
12
|
if (defined $direction and ref $direction eq 'HASH') { |
210
|
2
|
|
|
|
|
5
|
$direction = $direction->{string}; |
211
|
|
|
|
|
|
|
} |
212
|
2
|
|
|
|
|
5
|
my $type = 'full_forward'; |
213
|
2
|
100
|
|
|
|
14
|
$type = 'full_reverse' if $direction =~ /reverse|backward|no?|0/i; |
214
|
2
|
50
|
33
|
|
|
27
|
if (exists $self->eccp_pins->{P1A} and exists $self->eccp_pins->{P1B} and |
|
|
|
33
|
|
|
|
|
|
|
|
33
|
|
|
|
|
215
|
|
|
|
|
|
|
exists $self->eccp_pins->{P1C} and exists $self->eccp_pins->{P1D}) { |
216
|
|
|
|
|
|
|
# we ignore the @pins that comes in |
217
|
2
|
|
|
|
|
6
|
@pins = qw(P1A P1B P1C P1D); |
218
|
|
|
|
|
|
|
} else { |
219
|
0
|
|
|
|
|
0
|
carp $self->type, " has no Enhanced PWM capabilities"; |
220
|
0
|
|
|
|
|
0
|
return; |
221
|
|
|
|
|
|
|
} |
222
|
2
|
|
|
|
|
8
|
my %details = $self->_pwm_details($pwm_frequency, $duty, $type, @pins); |
223
|
2
|
|
|
|
|
13
|
return $self->_pwm_code(%details); |
224
|
|
|
|
|
|
|
} |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
sub pwm_update { |
227
|
1
|
|
|
1
|
0
|
6
|
my ($self, $pwm_frequency, $duty) = @_; |
228
|
1
|
50
|
|
|
|
3
|
return unless $self->doesrole('ECCP'); |
229
|
|
|
|
|
|
|
# hack into the existing functions to update only what we need |
230
|
1
|
|
|
|
|
2
|
my @pins = qw(CCP1); |
231
|
1
|
50
|
33
|
|
|
27
|
if (exists $self->eccp_pins->{P1A} and exists $self->eccp_pins->{P1B} and |
|
|
|
33
|
|
|
|
|
|
|
|
33
|
|
|
|
|
232
|
|
|
|
|
|
|
exists $self->eccp_pins->{P1C} and exists $self->eccp_pins->{P1D}) { |
233
|
|
|
|
|
|
|
# we ignore the @pins that comes in |
234
|
1
|
|
|
|
|
130
|
@pins = qw(P1A P1B P1C P1D); |
235
|
|
|
|
|
|
|
} |
236
|
1
|
|
|
|
|
4
|
my %details = $self->_pwm_details($pwm_frequency, $duty, 'single', @pins); |
237
|
1
|
|
|
|
|
2
|
my ($ccp1con5, $ccp1con4); |
238
|
1
|
|
|
|
|
5
|
$ccp1con4 = $details{CCPR1L_CCP1CON54} & 0x0001; |
239
|
1
|
|
|
|
|
116
|
$ccp1con5 = ($details{CCPR1L_CCP1CON54} >> 1) & 0x0001; |
240
|
1
|
50
|
|
|
|
151
|
if ($ccp1con4) { |
241
|
1
|
|
|
|
|
19
|
$ccp1con4 = "\tbsf CCP1CON, DC1B0"; |
242
|
|
|
|
|
|
|
} else { |
243
|
0
|
|
|
|
|
0
|
$ccp1con4 = "\tbcf CCP1CON, DC1B0"; |
244
|
|
|
|
|
|
|
} |
245
|
1
|
50
|
|
|
|
2
|
if ($ccp1con5) { |
246
|
0
|
|
|
|
|
0
|
$ccp1con5 = "\tbsf CCP1CON, DC1B1"; |
247
|
|
|
|
|
|
|
} else { |
248
|
1
|
|
|
|
|
14
|
$ccp1con5 = "\tbcf CCP1CON, DC1B1"; |
249
|
|
|
|
|
|
|
} |
250
|
1
|
|
|
|
|
10
|
return << "..."; |
251
|
|
|
|
|
|
|
;;; updating PWM duty cycle for a given frequency |
252
|
|
|
|
|
|
|
;;; PWM Frequency = $details{PWM_FREQUENCY} Hz |
253
|
|
|
|
|
|
|
;;; Duty Cycle = $details{DUTYCYCLE} / 100 |
254
|
|
|
|
|
|
|
;;; CCPR1L:CCP1CON<5:4> = $details{CCPR1L_CCP1CON54} |
255
|
|
|
|
|
|
|
;;; CCPR1L = $details{CCPR1L} |
256
|
|
|
|
|
|
|
;;; update CCPR1L and CCP1CON<5:4> or the DC1B[01] bits |
257
|
|
|
|
|
|
|
$ccp1con4 |
258
|
|
|
|
|
|
|
$ccp1con5 |
259
|
|
|
|
|
|
|
\tmovlw $details{CCPR1L} |
260
|
|
|
|
|
|
|
\tmovwf CCPR1L |
261
|
|
|
|
|
|
|
... |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
} |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
1; |
267
|
|
|
|
|
|
|
__END__ |