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, 2019-2023 -- leonerd@leonerd.org.uk |
5
|
|
|
|
|
|
|
|
6
|
13
|
|
|
13
|
|
3637252
|
use v5.26; |
|
13
|
|
|
|
|
156
|
|
7
|
13
|
|
|
13
|
|
82
|
use warnings; |
|
13
|
|
|
|
|
29
|
|
|
13
|
|
|
|
|
477
|
|
8
|
13
|
|
|
13
|
|
8144
|
use Object::Pad 0.800 ':experimental(adjust_params)'; |
|
13
|
|
|
|
|
147669
|
|
|
13
|
|
|
|
|
1169
|
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
package Device::AVR::UPDI 0.14; |
11
|
|
|
|
|
|
|
class Device::AVR::UPDI :strict(params); |
12
|
|
|
|
|
|
|
|
13
|
13
|
|
|
13
|
|
5962
|
use Carp; |
|
13
|
|
|
|
|
32
|
|
|
13
|
|
|
|
|
756
|
|
14
|
|
|
|
|
|
|
|
15
|
13
|
|
|
13
|
|
7208
|
use Future::AsyncAwait; |
|
13
|
|
|
|
|
33068
|
|
|
13
|
|
|
|
|
72
|
|
16
|
13
|
|
|
13
|
|
1415
|
use Future::IO 0.03; # ->sysread_exactly |
|
13
|
|
|
|
|
13546
|
|
|
13
|
|
|
|
|
479
|
|
17
|
|
|
|
|
|
|
|
18
|
13
|
|
|
13
|
|
7291
|
use File::ShareDir qw( module_dir ); |
|
13
|
|
|
|
|
372116
|
|
|
13
|
|
|
|
|
903
|
|
19
|
13
|
|
|
13
|
|
6174
|
use YAML (); |
|
13
|
|
|
|
|
101186
|
|
|
13
|
|
|
|
|
824
|
|
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
my $SHAREDIR = module_dir( __PACKAGE__ ); |
22
|
|
|
|
|
|
|
|
23
|
13
|
|
|
13
|
|
7027
|
use Object::Pad::ClassAttr::Struct 0.04; |
|
13
|
|
|
|
|
15766
|
|
|
13
|
|
|
|
|
69
|
|
24
|
|
|
|
|
|
|
|
25
|
13
|
|
50
|
13
|
|
876
|
use constant DEBUG => $ENV{UPDI_DEBUG} // 0; |
|
13
|
|
|
|
|
35
|
|
|
13
|
|
|
|
|
1756
|
|
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
class Device::AVR::UPDI::_PartInfo :Struct { |
28
|
1
|
|
|
1
|
|
4
|
field $name; |
|
1
|
|
|
|
|
7
|
|
29
|
|
|
|
|
|
|
|
30
|
1
|
|
|
1
|
|
4
|
field $signature; |
|
1
|
|
|
|
|
5
|
|
31
|
27
|
|
|
27
|
|
56
|
field $baseaddr_nvmctrl; |
|
27
|
|
|
|
|
140
|
|
32
|
2
|
|
|
2
|
|
5
|
field $baseaddr_fuse; |
|
2
|
|
|
|
|
12
|
|
33
|
1
|
|
|
1
|
|
3
|
field $baseaddr_sigrow; |
|
1
|
|
|
|
|
5
|
|
34
|
|
|
|
|
|
|
|
35
|
4
|
|
|
4
|
|
13
|
field $baseaddr_flash; |
|
4
|
|
|
|
|
34
|
|
36
|
0
|
|
|
0
|
|
0
|
field $pagesize_flash; |
|
0
|
|
|
|
|
0
|
|
37
|
1
|
|
|
1
|
|
5
|
field $size_flash; |
|
1
|
|
|
|
|
5
|
|
38
|
|
|
|
|
|
|
|
39
|
4
|
|
|
4
|
|
11
|
field $baseaddr_eeprom; |
|
4
|
|
|
|
|
23
|
|
40
|
0
|
|
|
0
|
|
0
|
field $pagesize_eeprom; |
|
0
|
|
|
|
|
0
|
|
41
|
0
|
|
|
0
|
|
0
|
field $size_eeprom; |
|
0
|
|
|
|
|
0
|
|
42
|
|
|
|
|
|
|
|
43
|
1
|
|
|
1
|
|
4
|
field $fusenames; |
|
1
|
|
|
|
|
11
|
|
44
|
|
|
|
|
|
|
} |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
my %partinfos; |
47
|
|
|
|
|
|
|
{ |
48
|
|
|
|
|
|
|
while( readline DATA ) { |
49
|
|
|
|
|
|
|
m/^#/ and next; |
50
|
|
|
|
|
|
|
chomp; |
51
|
|
|
|
|
|
|
my ( $name, $signature, @fields ) = split m/\|/, $_; |
52
|
|
|
|
|
|
|
$signature = pack "H*", $signature; |
53
|
|
|
|
|
|
|
my $fuses = [ map { length $_ ? $_ : undef } split m/,/, pop @fields ]; |
54
|
|
|
|
|
|
|
m/^0x/ and $_ = hex $_ for @fields; |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
my $partinfo = Device::AVR::UPDI::_PartInfo->new_values( $name, $signature, @fields, $fuses ); |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
$partinfos{lc $name} = $partinfo; |
59
|
|
|
|
|
|
|
$partinfos{"m$1"} = $partinfo if $name =~ m/^ATmega(.*)$/; |
60
|
|
|
|
|
|
|
$partinfos{"t$1"} = $partinfo if $name =~ m/^ATtiny(.*)$/; |
61
|
|
|
|
|
|
|
} |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
close DATA; |
64
|
|
|
|
|
|
|
} |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
=head1 NAME |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
C - interact with an F microcontroller over F |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
=head1 DESCRIPTION |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
This module provides a class for interacting with an F microcontroller |
73
|
|
|
|
|
|
|
over the F programming and debug interface. This is used by chips in the |
74
|
|
|
|
|
|
|
newer F 0-series, or F 0-series or 1-series, or F or |
75
|
|
|
|
|
|
|
F families. |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
=head2 Hardware Interface |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
This code expects to find a serial port connected to the UPDI pin of the |
80
|
|
|
|
|
|
|
microcontroller as a shared single-wire interface. Suitable hardware to |
81
|
|
|
|
|
|
|
provide this can be created using a USB-UART adapter, connecting the C |
82
|
|
|
|
|
|
|
line directly to the MCU's C pin, and connecting C via a |
83
|
|
|
|
|
|
|
current-limiting resistor of 1kohm. |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
+------------+ +-------------------+ |
86
|
|
|
|
|
|
|
| RX-|-------------+ | | |
87
|
|
|
|
|
|
|
| USB-UART | +------|-UPDI | |
88
|
|
|
|
|
|
|
| TX-|---[ 1k ]---+ | ATmega or ATtiny | |
89
|
|
|
|
|
|
|
+------------+ +-------------------| |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
=cut |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
=head1 CONSTRUCTORS |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
=cut |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
=head2 new |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
$updi = Device::AVR::UPDI->new( ... ); |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
Constructs and returns a new C instance. |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
Takes the following named arguments: |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
=over 4 |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
=item dev => STRING |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
Path to the device node representing the serial port connection. |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
=item fh => IO |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
Alternative to C, provides an IO handle directly. This should be an |
114
|
|
|
|
|
|
|
instance of L, or at least, provide the same interface. |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
=item part => STRING |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
Name of the AVR chip to interact with. This is used to define parameters like |
119
|
|
|
|
|
|
|
memory size and location of internal peripherals. |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
Any of the following forms are accepted |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
part => "ATtiny814" | "attiny814" | "t814" |
124
|
|
|
|
|
|
|
part => "ATmega4809" | "atmega4809" | "m4809" |
125
|
|
|
|
|
|
|
part => "AVR64DA48" | "avr64da48" |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
=item baud => INT |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
Optional. Overrides the baud rate for communications. Defaults to 115200. |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
Lower numbers may be useful if communication is unreliable, for example over a |
132
|
|
|
|
|
|
|
long cable or with high capacitance or noise. |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
=back |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
After construction, the link must be initialised by calling L |
137
|
|
|
|
|
|
|
before any of the command methods are used. |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
=cut |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
field $_fh; |
142
|
7
|
|
|
7
|
1
|
20
|
field $_partinfo :reader; |
|
7
|
|
|
|
|
76
|
|
143
|
|
|
|
|
|
|
|
144
|
9
|
|
|
9
|
|
63
|
field $_nvm_version :writer(_set_nvm_version); |
|
9
|
|
|
|
|
205
|
|
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
ADJUST :params ( |
147
|
|
|
|
|
|
|
:$fh = undef, |
148
|
|
|
|
|
|
|
:$dev = undef, |
149
|
|
|
|
|
|
|
:$baud //= 115200, |
150
|
|
|
|
|
|
|
:$part, |
151
|
|
|
|
|
|
|
) { |
152
|
|
|
|
|
|
|
$_fh = $fh // do { |
153
|
|
|
|
|
|
|
require IO::Termios; |
154
|
|
|
|
|
|
|
IO::Termios->open( $dev ) or |
155
|
|
|
|
|
|
|
die "Unable to open $dev - $!\n"; |
156
|
|
|
|
|
|
|
}; |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
$_fh->cfmakeraw(); |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
# 8bits, Even parity, 2 stop |
161
|
|
|
|
|
|
|
$_fh->set_mode( "$baud,8,e,2" ); |
162
|
|
|
|
|
|
|
$_fh->setflag_clocal( 1 ); |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
$_fh->autoflush; |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
$_partinfo = $partinfos{lc $part} // |
167
|
|
|
|
|
|
|
croak "Unrecognised part name $part"; |
168
|
|
|
|
|
|
|
} |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
field $_reg_ctrla = 0; |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
=head1 ACCESSORS |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
=cut |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
=head2 partinfo |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
$partinfo = $updi->partinfo; |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
Returns the Part Info structure containing base addresses and other parameters |
181
|
|
|
|
|
|
|
which may be useful for interacting with the chip. |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
The returned structure provides the following fields |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
$name = $partinfo->name; |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
$sig = $partinfo->signature; |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
$addr = $partinfo->baseaddr_nvmctrl; |
190
|
|
|
|
|
|
|
$addr = $partinfo->baseaddr_fuse; |
191
|
|
|
|
|
|
|
$addr = $partinfo->baseaddr_flash; |
192
|
|
|
|
|
|
|
$addr = $partinfo->baseaddr_eeprom; |
193
|
|
|
|
|
|
|
$addr = $partinfo->baseaddr_sigrow; |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
$bytes = $partinfo->pagesize_flash; |
196
|
|
|
|
|
|
|
$bytes = $partinfo->pagesize_eeprom; |
197
|
|
|
|
|
|
|
$bytes = $partinfo->size_flash; |
198
|
|
|
|
|
|
|
$bytes = $partinfo->size_eeprom; |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
$fusenames = $partinfo->fusenames; |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
=cut |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
# generated accessor |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
=head2 fuseinfo |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
Returns a data structure containing information about the individual fuse |
209
|
|
|
|
|
|
|
fields defined by this device. |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
This is parsed directly from a shipped YAML file; see the files in the |
212
|
|
|
|
|
|
|
F directory for more details. |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
=cut |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
field $_fuseinfo; |
217
|
|
|
|
|
|
|
|
218
|
0
|
|
|
|
|
0
|
method fuseinfo () |
|
0
|
|
|
|
|
0
|
|
219
|
0
|
|
|
0
|
1
|
0
|
{ |
220
|
0
|
|
0
|
|
|
0
|
return $_fuseinfo //= do { |
221
|
0
|
|
|
|
|
0
|
my $yamlpath = "$SHAREDIR/${\ $self->partinfo->name }.yaml"; |
|
0
|
|
|
|
|
0
|
|
222
|
0
|
0
|
|
|
|
0
|
unless( -f $yamlpath ) { |
223
|
0
|
|
|
|
|
0
|
die "No YAML file found at $yamlpath\n"; |
224
|
|
|
|
|
|
|
} |
225
|
0
|
|
|
|
|
0
|
YAML::LoadFile( $yamlpath ); |
226
|
|
|
|
|
|
|
}; |
227
|
|
|
|
|
|
|
} |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
=head1 METHODS |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
The following methods documented in an C expression return L |
232
|
|
|
|
|
|
|
instances. |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
=cut |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
use constant { |
237
|
|
|
|
|
|
|
# SYNC byte |
238
|
13
|
|
|
|
|
107582
|
SYNC => "\x55", |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
# Instruction opcodes |
241
|
|
|
|
|
|
|
OP_LDS => 0x00, |
242
|
|
|
|
|
|
|
OP_STS => 0x40, |
243
|
|
|
|
|
|
|
OP_DATA8 => 0x00, |
244
|
|
|
|
|
|
|
OP_DATA16 => 0x01, |
245
|
|
|
|
|
|
|
OP_ADDR8 => 0x00, |
246
|
|
|
|
|
|
|
OP_ADDR16 => 0x01, |
247
|
|
|
|
|
|
|
OP_ADDR24 => 0x02, |
248
|
|
|
|
|
|
|
OP_LD => 0x20, |
249
|
|
|
|
|
|
|
OP_ST => 0x60, |
250
|
|
|
|
|
|
|
OP_PTR => 0x00, |
251
|
|
|
|
|
|
|
OP_PTRINC => 0x04, |
252
|
|
|
|
|
|
|
OP_PTRREG => 0x08, |
253
|
|
|
|
|
|
|
OP_LDCS => 0x80, |
254
|
|
|
|
|
|
|
OP_STCS => 0xC0, |
255
|
|
|
|
|
|
|
OP_REPEAT => 0xA0, |
256
|
|
|
|
|
|
|
OP_KEY => 0xE0, |
257
|
|
|
|
|
|
|
OP_KEY_READSIB => 0xE5, |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
# UPDI registers |
260
|
|
|
|
|
|
|
REG_STATUSA => 0x00, |
261
|
|
|
|
|
|
|
REG_STATUSB => 0x01, |
262
|
|
|
|
|
|
|
REG_CTRLA => 0x02, |
263
|
|
|
|
|
|
|
CTRLA_IBDLY => (1<<7), |
264
|
|
|
|
|
|
|
CTRLA_PARD => (1<<5), |
265
|
|
|
|
|
|
|
CTRLA_DTD => (1<<4), |
266
|
|
|
|
|
|
|
CTRLA_RSD => (1<<3), |
267
|
|
|
|
|
|
|
CTRLA_GTVAL_MASK => 0x07, |
268
|
|
|
|
|
|
|
REG_CTRLB => 0x03, |
269
|
|
|
|
|
|
|
CTRLB_NACKDIS => (1<<4), |
270
|
|
|
|
|
|
|
CTRLB_CCDETDIS => (1<<3), |
271
|
|
|
|
|
|
|
CTRLB_UPDIDIS => (1<<2), |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
REG_ASI_KEY_STATUS => 0x07, |
274
|
|
|
|
|
|
|
ASI_KEY_UROWWRITE => (1<<5), |
275
|
|
|
|
|
|
|
ASI_KEY_NVMPROG => (1<<4), |
276
|
|
|
|
|
|
|
ASI_KEY_CHIPERASE => (1<<3), |
277
|
|
|
|
|
|
|
REG_ASI_RESET_REQ => 0x08, |
278
|
|
|
|
|
|
|
ASI_RESET_REQ_SIGNATURE => 0x59, |
279
|
|
|
|
|
|
|
REG_ASI_CTRLA => 0x09, |
280
|
|
|
|
|
|
|
REG_ASI_SYS_CTRLA => 0x0A, |
281
|
|
|
|
|
|
|
REG_ASI_SYS_STATUS => 0x0B, |
282
|
|
|
|
|
|
|
ASI_SYS_STATUS_RSTSYS => (1<<5), |
283
|
|
|
|
|
|
|
ASI_SYS_STATUS_INSLEEP => (1<<4), |
284
|
|
|
|
|
|
|
ASI_SYS_STATUS_NVMPROG => (1<<3), |
285
|
|
|
|
|
|
|
ASI_SYS_STATUS_UROWPROG => (1<<2), |
286
|
|
|
|
|
|
|
ASI_SYS_STATUS_LOCKSTATUS => (1<<0), |
287
|
|
|
|
|
|
|
REG_ASI_CRC_STATUS => 0x0C, |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
# Keys |
290
|
|
|
|
|
|
|
KEY_CHIPERASE => "\x65\x73\x61\x72\x45\x4D\x56\x4E", |
291
|
|
|
|
|
|
|
KEY_NVMPROG => "\x20\x67\x6F\x72\x50\x4D\x56\x4E", |
292
|
13
|
|
|
13
|
|
34601
|
}; |
|
13
|
|
|
|
|
38
|
|
293
|
|
|
|
|
|
|
|
294
|
1
|
|
|
|
|
3
|
async method _break () |
|
1
|
|
|
|
|
3
|
|
295
|
1
|
|
|
|
|
4
|
{ |
296
|
1
|
|
|
|
|
11
|
my $was_baud = $_fh->getobaud; |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
# Writing a 0 at 300baud is sufficient to look like a BREAK |
299
|
1
|
|
|
|
|
1100
|
$_fh->setbaud( 300 ); |
300
|
1
|
|
|
|
|
1114
|
$_fh->print( "\x00" ); |
301
|
|
|
|
|
|
|
|
302
|
1
|
|
|
|
|
1145
|
await Future->wait_any( |
303
|
|
|
|
|
|
|
Future::IO->sysread( $_fh, 1 ), |
304
|
|
|
|
|
|
|
Future::IO->sleep( 0.05 ) |
305
|
|
|
|
|
|
|
->then_fail( "Timed out waiting for echo of BREAK - is this a UPDI programmer?\n" ) |
306
|
|
|
|
|
|
|
); |
307
|
|
|
|
|
|
|
|
308
|
1
|
|
|
|
|
3055
|
$_fh->setbaud( $was_baud ); |
309
|
1
|
|
|
1
|
|
3
|
} |
310
|
|
|
|
|
|
|
|
311
|
110
|
|
|
|
|
160
|
async method _op_writeread ( $write, $readlen ) |
|
110
|
|
|
|
|
188
|
|
|
110
|
|
|
|
|
151
|
|
|
110
|
|
|
|
|
148
|
|
312
|
110
|
|
|
|
|
242
|
{ |
313
|
110
|
|
|
|
|
145
|
printf STDERR "WR: => %v02X\n", $write if DEBUG > 1; |
314
|
110
|
|
|
|
|
444
|
await Future::IO->syswrite_exactly( $_fh, $write ); |
315
|
|
|
|
|
|
|
|
316
|
110
|
|
|
|
|
277204
|
my $buf = ""; |
317
|
110
|
|
|
|
|
379
|
my $len = length( $write ) + $readlen; |
318
|
|
|
|
|
|
|
|
319
|
110
|
|
|
|
|
305
|
while( length $buf < $len ) { |
320
|
110
|
50
|
|
|
|
307
|
my $what = ( length $buf >= length $write ) ? |
321
|
|
|
|
|
|
|
"chip response - is the chip present?" : |
322
|
|
|
|
|
|
|
"echo of command - is this a UPDI programmer?"; |
323
|
|
|
|
|
|
|
|
324
|
110
|
|
|
|
|
447
|
$buf .= await Future->wait_any( |
325
|
|
|
|
|
|
|
Future::IO->sysread( $_fh, $len - length $buf ), |
326
|
|
|
|
|
|
|
Future::IO->sleep( 0.1 ) |
327
|
|
|
|
|
|
|
->then_fail( "Timed out waiting for $what\n" ) |
328
|
|
|
|
|
|
|
); |
329
|
|
|
|
|
|
|
|
330
|
110
|
|
|
|
|
335851
|
my $got = substr( $buf, 0, length $write ); |
331
|
110
|
|
|
|
|
283
|
my $exp = substr( $write, 0, length $buf ); |
332
|
|
|
|
|
|
|
|
333
|
110
|
|
|
|
|
166
|
printf STDERR "RD: <= %v02X\n", $buf if DEBUG > 1; |
334
|
|
|
|
|
|
|
|
335
|
110
|
50
|
|
|
|
461
|
die "Received different bytes while waiting to receive echo of command - is this a UPDI programmer?\n" |
336
|
|
|
|
|
|
|
if $got ne $exp; |
337
|
|
|
|
|
|
|
} |
338
|
|
|
|
|
|
|
|
339
|
110
|
|
|
|
|
576
|
return substr( $buf, length( $write ) ); |
340
|
110
|
|
|
110
|
|
192
|
} |
341
|
|
|
|
|
|
|
|
342
|
29
|
|
|
|
|
41
|
method _pack_op_addr ( $op, $addr ) |
|
29
|
|
|
|
|
43
|
|
|
29
|
|
|
|
|
51
|
|
|
29
|
|
|
|
|
45
|
|
343
|
29
|
|
|
29
|
|
62
|
{ |
344
|
29
|
100
|
|
|
|
263
|
return $_nvm_version >= 2 |
345
|
|
|
|
|
|
|
? pack( "C S> 16 ) |
346
|
|
|
|
|
|
|
: pack( "C S<", $op|OP_ADDR16<<2, $addr ); |
347
|
|
|
|
|
|
|
} |
348
|
|
|
|
|
|
|
|
349
|
45
|
|
|
|
|
66
|
async method _op_write_expecting_ack ( $op, $write ) |
|
45
|
|
|
|
|
115
|
|
|
45
|
|
|
|
|
70
|
|
|
45
|
|
|
|
|
63
|
|
350
|
45
|
|
|
|
|
120
|
{ |
351
|
45
|
100
|
|
|
|
109
|
if( $_reg_ctrla & CTRLA_RSD ) { |
352
|
|
|
|
|
|
|
# No ACK expected |
353
|
6
|
|
|
|
|
21
|
await $self->_op_writeread( $write, 0 ); |
354
|
|
|
|
|
|
|
} |
355
|
|
|
|
|
|
|
else { |
356
|
39
|
|
|
|
|
134
|
my $ack = await $self->_op_writeread( $write, 1 ); |
357
|
39
|
50
|
|
|
|
3315
|
$ack eq "\x40" or croak "Expected ACK to $op"; |
358
|
|
|
|
|
|
|
} |
359
|
45
|
|
|
45
|
|
79
|
} |
360
|
|
|
|
|
|
|
|
361
|
10
|
|
|
|
|
18
|
async method stptr ( $addr ) |
|
10
|
|
|
|
|
19
|
|
|
10
|
|
|
|
|
30
|
|
362
|
10
|
|
|
|
|
23
|
{ |
363
|
10
|
100
|
|
|
|
98
|
my $cmd = $_nvm_version >= 2 |
364
|
|
|
|
|
|
|
? pack( "C S> 16 ) |
365
|
|
|
|
|
|
|
: pack( "C S<", OP_ST|OP_PTRREG|OP_ADDR16, $addr ); |
366
|
|
|
|
|
|
|
|
367
|
10
|
|
|
|
|
53
|
await $self->_op_write_expecting_ack( "ST PTR" => SYNC . $cmd ); |
368
|
10
|
|
|
10
|
0
|
21
|
} |
369
|
|
|
|
|
|
|
|
370
|
13
|
|
|
|
|
25
|
async method lds8 ( $addr ) |
|
13
|
|
|
|
|
24
|
|
|
13
|
|
|
|
|
23
|
|
371
|
13
|
|
|
|
|
33
|
{ |
372
|
13
|
|
|
|
|
45
|
my $ret = unpack "C", await |
373
|
|
|
|
|
|
|
$self->_op_writeread( SYNC . $self->_pack_op_addr( OP_LDS, $addr ), 1 ); |
374
|
|
|
|
|
|
|
|
375
|
13
|
|
|
|
|
1069
|
printf STDERR ">> LDS8[%04X] -> %02X\n", $addr, $ret if DEBUG; |
376
|
13
|
|
|
|
|
52
|
return $ret; |
377
|
13
|
|
|
13
|
0
|
28
|
} |
378
|
|
|
|
|
|
|
|
379
|
16
|
|
|
|
|
35
|
async method sts8 ( $addr, $val ) |
|
16
|
|
|
|
|
30
|
|
|
16
|
|
|
|
|
31
|
|
|
16
|
|
|
|
|
26
|
|
380
|
16
|
|
|
|
|
48
|
{ |
381
|
16
|
|
|
|
|
25
|
printf STDERR ">> STS8[%04X] = %02X\n", $addr, $val if DEBUG; |
382
|
|
|
|
|
|
|
|
383
|
16
|
|
|
|
|
72
|
await $self->_op_write_expecting_ack( STS8 => |
384
|
|
|
|
|
|
|
SYNC . $self->_pack_op_addr( OP_STS, $addr ) ); |
385
|
|
|
|
|
|
|
|
386
|
16
|
|
|
|
|
1276
|
await $self->_op_write_expecting_ack( "STS8 DATA" => |
387
|
|
|
|
|
|
|
pack( "C", $val ) ); |
388
|
16
|
|
|
16
|
0
|
28
|
} |
389
|
|
|
|
|
|
|
|
390
|
5
|
|
|
|
|
11
|
async method ld ( $addr, $len ) |
|
5
|
|
|
|
|
9
|
|
|
5
|
|
|
|
|
11
|
|
|
5
|
|
|
|
|
9
|
|
391
|
5
|
|
|
|
|
12
|
{ |
392
|
5
|
|
|
|
|
28
|
await $self->stptr( $addr ); |
393
|
|
|
|
|
|
|
|
394
|
5
|
|
|
|
|
842
|
my $ret = ""; |
395
|
|
|
|
|
|
|
|
396
|
5
|
|
|
|
|
27
|
while( $len ) { |
397
|
5
|
|
|
|
|
11
|
my $chunklen = $len; |
398
|
|
|
|
|
|
|
# REPEAT can only do at most 255 repeats |
399
|
5
|
50
|
|
|
|
25
|
$chunklen = 256 if $chunklen > 256; |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
await |
402
|
5
|
50
|
|
|
|
56
|
$self->_op_writeread( SYNC . pack( "C C", OP_REPEAT, $chunklen - 1 ), 0 ) if $chunklen > 1; |
403
|
5
|
|
|
|
|
395
|
$ret .= await |
404
|
|
|
|
|
|
|
$self->_op_writeread( SYNC . pack( "C", OP_LD|OP_PTRINC|OP_DATA8 ), $chunklen ); |
405
|
|
|
|
|
|
|
|
406
|
5
|
|
|
|
|
403
|
$len -= $chunklen; |
407
|
|
|
|
|
|
|
} |
408
|
|
|
|
|
|
|
|
409
|
5
|
|
|
|
|
13
|
printf STDERR ">> LD[%04X] -> %v02X\n", $addr, $ret if DEBUG; |
410
|
5
|
|
|
|
|
20
|
return $ret; |
411
|
5
|
|
|
5
|
0
|
12
|
} |
412
|
|
|
|
|
|
|
|
413
|
3
|
|
|
|
|
6
|
async method st8 ( $addr, $data ) |
|
3
|
|
|
|
|
8
|
|
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
5
|
|
414
|
3
|
|
|
|
|
11
|
{ |
415
|
3
|
|
|
|
|
8
|
printf STDERR ">> ST[%04X] = %v02X\n", $addr, $data if DEBUG; |
416
|
|
|
|
|
|
|
|
417
|
3
|
|
|
|
|
8
|
my $len = length( $data ); |
418
|
|
|
|
|
|
|
|
419
|
3
|
|
|
|
|
15
|
await $self->stptr( $addr ); |
420
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
await |
422
|
3
|
100
|
|
|
|
632
|
$self->_op_writeread( SYNC . pack( "C C", OP_REPEAT, $len - 1 ), 0 ) if $len > 1; |
423
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
await |
425
|
3
|
|
|
|
|
170
|
$self->_op_writeread( SYNC . pack( "C", OP_ST|OP_PTRINC|OP_DATA8 ), 0 ); |
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
# If we're in RSD mode we might as well just write all the data in one go |
428
|
3
|
100
|
|
|
|
239
|
if( $_reg_ctrla & CTRLA_RSD ) { |
429
|
2
|
|
|
|
|
10
|
await $self->_op_writeread( $data, 0 ) |
430
|
|
|
|
|
|
|
} |
431
|
|
|
|
|
|
|
else { |
432
|
1
|
|
|
|
|
13
|
foreach my $byte ( split //, $data ) { |
433
|
1
|
|
|
|
|
4
|
await $self->_op_write_expecting_ack( "STR data" => $byte ); |
434
|
|
|
|
|
|
|
} |
435
|
|
|
|
|
|
|
} |
436
|
3
|
|
|
3
|
0
|
6
|
} |
437
|
|
|
|
|
|
|
|
438
|
2
|
|
|
|
|
6
|
async method st16 ( $addr, $data ) |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
4
|
|
439
|
2
|
|
|
|
|
9
|
{ |
440
|
2
|
|
|
|
|
5
|
printf STDERR ">> ST[%04X] = %v02X\n", $addr, $data if DEBUG; |
441
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
# Count in 16bit words |
443
|
2
|
|
|
|
|
10
|
my $len = int( length( $data ) / 2 ); |
444
|
|
|
|
|
|
|
|
445
|
2
|
|
|
|
|
10
|
await $self->stptr( $addr ); |
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
await |
448
|
2
|
50
|
|
|
|
477
|
$self->_op_writeread( SYNC . pack( "C C", OP_REPEAT, $len - 1 ), 0 ) if $len > 1; |
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
await |
451
|
2
|
|
|
|
|
174
|
$self->_op_writeread( SYNC . pack( "C", OP_ST|OP_PTRINC|OP_DATA16 ), 0 ); |
452
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
# If we're in RSD mode we might as well just write all the data in one go |
454
|
2
|
50
|
|
|
|
177
|
if( $_reg_ctrla & CTRLA_RSD ) { |
455
|
2
|
|
|
|
|
13
|
await $self->_op_writeread( substr( $data, 0, $len*2 ), 0 ); |
456
|
|
|
|
|
|
|
} |
457
|
|
|
|
|
|
|
else { |
458
|
0
|
|
|
|
|
0
|
foreach my $word ( $data =~ m/.{2}/sg ) { |
459
|
0
|
|
|
|
|
0
|
await $self->_op_write_expecting_ack( "STR data" => $word ); |
460
|
|
|
|
|
|
|
} |
461
|
|
|
|
|
|
|
} |
462
|
|
|
|
|
|
|
|
463
|
2
|
50
|
|
|
|
179
|
if( length( $data ) % 2 ) { |
464
|
|
|
|
|
|
|
# Final byte |
465
|
2
|
|
|
|
|
10
|
my $byte = substr $data, 2 * $len, 1; |
466
|
|
|
|
|
|
|
await |
467
|
2
|
|
|
|
|
9
|
$self->_op_writeread( SYNC . pack( "C", OP_ST|OP_PTRINC|OP_DATA8 ), 0 ); |
468
|
|
|
|
|
|
|
|
469
|
2
|
|
|
|
|
496
|
await $self->_op_write_expecting_ack( "STR data" => $byte ); |
470
|
|
|
|
|
|
|
} |
471
|
2
|
|
|
2
|
0
|
6
|
} |
472
|
|
|
|
|
|
|
|
473
|
7
|
|
|
|
|
12
|
async method ldcs ( $reg ) |
|
7
|
|
|
|
|
14
|
|
|
7
|
|
|
|
|
9
|
|
474
|
7
|
|
|
|
|
24
|
{ |
475
|
7
|
|
|
|
|
43
|
my $ret = unpack "C", await |
476
|
|
|
|
|
|
|
$self->_op_writeread( SYNC . pack( "C", OP_LDCS | $reg ), 1 ); |
477
|
|
|
|
|
|
|
|
478
|
7
|
|
|
|
|
574
|
printf STDERR ">> LDCS[%02X] -> %02X\n", $reg, $ret if DEBUG; |
479
|
7
|
|
|
|
|
28
|
return $ret; |
480
|
7
|
|
|
7
|
0
|
14
|
} |
481
|
|
|
|
|
|
|
|
482
|
16
|
|
|
|
|
36
|
async method stcs ( $reg, $value ) |
|
16
|
|
|
|
|
27
|
|
|
16
|
|
|
|
|
25
|
|
|
16
|
|
|
|
|
26
|
|
483
|
16
|
|
|
|
|
35
|
{ |
484
|
16
|
|
|
|
|
29
|
printf STDERR ">> STCS[%02X] = %02X\n", $reg, $value if DEBUG; |
485
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
await |
487
|
16
|
|
|
|
|
153
|
$self->_op_writeread( SYNC . pack( "CC", OP_STCS | $reg, $value ), 0 ); |
488
|
16
|
|
|
16
|
0
|
33
|
} |
489
|
|
|
|
|
|
|
|
490
|
2
|
|
|
|
|
3
|
async method key ( $key ) |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
2
|
|
491
|
2
|
|
|
|
|
7
|
{ |
492
|
2
|
50
|
|
|
|
10
|
length $key == 8 or |
493
|
|
|
|
|
|
|
die "Expected 8 byte key\n"; |
494
|
|
|
|
|
|
|
|
495
|
2
|
|
|
|
|
4
|
printf STDERR ">> KEY %v02X\n", $key if DEBUG; |
496
|
|
|
|
|
|
|
|
497
|
|
|
|
|
|
|
await |
498
|
2
|
|
|
|
|
25
|
$self->_op_writeread( SYNC . pack( "C a*", OP_KEY, $key ), 0 ); |
499
|
2
|
|
|
2
|
0
|
4
|
} |
500
|
|
|
|
|
|
|
|
501
|
9
|
|
|
|
|
17
|
async method set_rsd ( $on ) |
|
9
|
|
|
|
|
17
|
|
|
9
|
|
|
|
|
15
|
|
502
|
9
|
|
|
|
|
48
|
{ |
503
|
9
|
100
|
|
|
|
39
|
my $val = $on ? $_reg_ctrla | CTRLA_RSD : $_reg_ctrla & ~CTRLA_RSD; |
504
|
|
|
|
|
|
|
|
505
|
9
|
100
|
|
|
|
68
|
await $self->stcs( REG_CTRLA, $_reg_ctrla = $val ) if $_reg_ctrla != $val; |
506
|
9
|
|
|
9
|
0
|
27
|
} |
507
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
=head2 init_link |
509
|
|
|
|
|
|
|
|
510
|
|
|
|
|
|
|
await $updi->init_link; |
511
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
Initialise the UPDI link for proper communication. |
513
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
This method must be invoked after the object is constructed, before using any |
515
|
|
|
|
|
|
|
of the other commands. |
516
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
=cut |
518
|
|
|
|
|
|
|
|
519
|
1
|
|
|
|
|
3
|
async method init_link () |
|
1
|
|
|
|
|
1
|
|
520
|
1
|
|
|
|
|
18
|
{ |
521
|
|
|
|
|
|
|
# Sleep 100msec before sending BREAK in case of attached UPDI 12V pulse hardware |
522
|
1
|
|
|
|
|
8
|
await Future::IO->sleep( 0.1 ); |
523
|
|
|
|
|
|
|
|
524
|
1
|
|
|
|
|
1564
|
await $self->_break; |
525
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
# We have to disable collision detection or else the chip won't respond |
527
|
|
|
|
|
|
|
# properly |
528
|
1
|
|
|
|
|
1306
|
await $self->stcs( REG_CTRLB, CTRLB_CCDETDIS ); |
529
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
# Set CTRLA to known state also |
531
|
1
|
|
|
|
|
158
|
await $self->stcs( REG_CTRLA, $_reg_ctrla = 0 ); |
532
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
# Read the SIB so we can determine what kind of NVM controller is required |
534
|
1
|
|
|
|
|
148
|
my $sib = await $self->read_sib; |
535
|
1
|
50
|
|
|
|
87
|
$sib->{nvm_version} =~ m/^P:(\d)/ or croak "Unrecognised NVM_VERSION string: $sib->{nvm_version}"; |
536
|
1
|
|
|
|
|
7
|
$_nvm_version = $1; |
537
|
1
|
|
|
1
|
1
|
9786
|
} |
538
|
|
|
|
|
|
|
|
539
|
|
|
|
|
|
|
=head2 read_updirev |
540
|
|
|
|
|
|
|
|
541
|
|
|
|
|
|
|
$rev = await $updi->read_updirev; |
542
|
|
|
|
|
|
|
|
543
|
|
|
|
|
|
|
Reads the C field of the C register. |
544
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
=cut |
546
|
|
|
|
|
|
|
|
547
|
1
|
|
|
|
|
2
|
async method read_updirev () |
|
1
|
|
|
|
|
2
|
|
548
|
1
|
|
|
|
|
4
|
{ |
549
|
1
|
|
|
|
|
5
|
return ( await $self->ldcs( REG_STATUSA ) ) >> 4; |
550
|
1
|
|
|
1
|
1
|
2001
|
} |
551
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
=head2 read_asi_sys_status |
553
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
Reads the C register. |
555
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
=cut |
557
|
|
|
|
|
|
|
|
558
|
0
|
|
|
|
|
0
|
async method read_asi_sys_status () |
|
0
|
|
|
|
|
0
|
|
559
|
0
|
|
|
|
|
0
|
{ |
560
|
0
|
|
|
|
|
0
|
return await $self->ldcs( REG_ASI_SYS_STATUS ); |
561
|
0
|
|
|
0
|
1
|
0
|
} |
562
|
|
|
|
|
|
|
|
563
|
|
|
|
|
|
|
=head2 read_sib |
564
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
$sib = await $updi->read_sib; |
566
|
|
|
|
|
|
|
|
567
|
|
|
|
|
|
|
Reads the System Information Block. |
568
|
|
|
|
|
|
|
|
569
|
|
|
|
|
|
|
This is returned in a HASH reference, containing four keys: |
570
|
|
|
|
|
|
|
|
571
|
|
|
|
|
|
|
{ |
572
|
|
|
|
|
|
|
family => "tinyAVR", |
573
|
|
|
|
|
|
|
nvm_version => "P:0", |
574
|
|
|
|
|
|
|
ocd_version => "D:0", |
575
|
|
|
|
|
|
|
dbg_osc_freq => 3, |
576
|
|
|
|
|
|
|
} |
577
|
|
|
|
|
|
|
|
578
|
|
|
|
|
|
|
=cut |
579
|
|
|
|
|
|
|
|
580
|
2
|
|
|
|
|
5
|
async method read_sib () |
|
2
|
|
|
|
|
3
|
|
581
|
2
|
|
|
|
|
10
|
{ |
582
|
2
|
|
|
|
|
9
|
my $bytes = await |
583
|
|
|
|
|
|
|
$self->_op_writeread( SYNC . pack( "C", OP_KEY_READSIB ), 16 ); |
584
|
2
|
|
|
|
|
151
|
printf STDERR ">> READSIB -> %v02X\n", $bytes if DEBUG; |
585
|
|
|
|
|
|
|
|
586
|
2
|
|
|
|
|
18
|
my ( $family, $nvm, $ocd, $dbgosc ) = unpack "A7 x A3 A3 x A1", $bytes; |
587
|
|
|
|
|
|
|
return { |
588
|
2
|
|
|
|
|
18
|
family => $family, |
589
|
|
|
|
|
|
|
nvm_version => $nvm, |
590
|
|
|
|
|
|
|
ocd_version => $ocd, |
591
|
|
|
|
|
|
|
dbg_osc_freq => $dbgosc, |
592
|
|
|
|
|
|
|
}; |
593
|
2
|
|
|
2
|
1
|
5900
|
} |
594
|
|
|
|
|
|
|
|
595
|
|
|
|
|
|
|
=head2 read_signature |
596
|
|
|
|
|
|
|
|
597
|
|
|
|
|
|
|
$signature = await $updi->read_signature; |
598
|
|
|
|
|
|
|
|
599
|
|
|
|
|
|
|
Reads the three signature bytes from the Signature Row of the device. This is |
600
|
|
|
|
|
|
|
returned as a plain byte string of length 3. |
601
|
|
|
|
|
|
|
|
602
|
|
|
|
|
|
|
=cut |
603
|
|
|
|
|
|
|
|
604
|
1
|
|
|
|
|
3
|
async method read_signature () |
|
1
|
|
|
|
|
3
|
|
605
|
1
|
|
|
|
|
5
|
{ |
606
|
|
|
|
|
|
|
# The ATtiny814 datasheet says |
607
|
|
|
|
|
|
|
# All Atmel microcontrollers have a three-byte signature code which |
608
|
|
|
|
|
|
|
# identifies the device. This code can be read in both serial and parallel |
609
|
|
|
|
|
|
|
# mode, also when the device is locked. The three bytes reside in a |
610
|
|
|
|
|
|
|
# separate address space. |
611
|
|
|
|
|
|
|
# So far no attempt at reading signature over UPDI from a locked device has |
612
|
|
|
|
|
|
|
# been successful. :( |
613
|
|
|
|
|
|
|
|
614
|
1
|
|
|
|
|
4
|
return await $self->ld( $_partinfo->baseaddr_sigrow, 3 ); |
615
|
1
|
|
|
1
|
1
|
5512
|
} |
616
|
|
|
|
|
|
|
|
617
|
|
|
|
|
|
|
=head2 request_reset |
618
|
|
|
|
|
|
|
|
619
|
|
|
|
|
|
|
await $updi->request_reset( $reset ); |
620
|
|
|
|
|
|
|
|
621
|
|
|
|
|
|
|
Sets or clears the system reset request. Typically used to issue a system |
622
|
|
|
|
|
|
|
reset by momentarilly toggling the request on and off again: |
623
|
|
|
|
|
|
|
|
624
|
|
|
|
|
|
|
await $updi->request_reset( 1 ); |
625
|
|
|
|
|
|
|
await $updi->request_reset( 0 ); |
626
|
|
|
|
|
|
|
|
627
|
|
|
|
|
|
|
=cut |
628
|
|
|
|
|
|
|
|
629
|
6
|
|
|
|
|
13
|
async method request_reset ( $reset ) |
|
6
|
|
|
|
|
13
|
|
|
6
|
|
|
|
|
9
|
|
630
|
6
|
|
|
|
|
32
|
{ |
631
|
6
|
|
|
|
|
32
|
await $self->stcs( REG_ASI_RESET_REQ, $reset ? ASI_RESET_REQ_SIGNATURE : 0 ); |
632
|
6
|
|
|
6
|
1
|
7599
|
} |
633
|
|
|
|
|
|
|
|
634
|
|
|
|
|
|
|
=head2 erase_chip |
635
|
|
|
|
|
|
|
|
636
|
|
|
|
|
|
|
await $updi->erase_chip; |
637
|
|
|
|
|
|
|
|
638
|
|
|
|
|
|
|
Requests a full chip erase, waiting until the erase is complete. |
639
|
|
|
|
|
|
|
|
640
|
|
|
|
|
|
|
After this, the chip will be unlocked. |
641
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
Takes an optional named argument: |
643
|
|
|
|
|
|
|
|
644
|
|
|
|
|
|
|
=over 4 |
645
|
|
|
|
|
|
|
|
646
|
|
|
|
|
|
|
=item no_reset => BOOL |
647
|
|
|
|
|
|
|
|
648
|
|
|
|
|
|
|
If true, does not issue a system reset request after loading the key. This |
649
|
|
|
|
|
|
|
allows you to load multiple keys at once before sending the reset, which |
650
|
|
|
|
|
|
|
may be required e.g. to recover from a bad C fuse setting. |
651
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
await $updi->erase_chip( no_reset => 1 ); |
653
|
|
|
|
|
|
|
await $updi->enable_nvmprog; |
654
|
|
|
|
|
|
|
|
655
|
|
|
|
|
|
|
=back |
656
|
|
|
|
|
|
|
|
657
|
|
|
|
|
|
|
=cut |
658
|
|
|
|
|
|
|
|
659
|
1
|
|
|
|
|
2
|
async method erase_chip ( %opts ) |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
2
|
|
660
|
1
|
|
|
|
|
5
|
{ |
661
|
1
|
|
|
|
|
4
|
await $self->key( KEY_CHIPERASE ); |
662
|
|
|
|
|
|
|
|
663
|
1
|
50
|
|
|
|
182
|
die "Failed to set CHIPERASE key\n" unless ASI_KEY_CHIPERASE & await $self->ldcs( REG_ASI_KEY_STATUS ); |
664
|
|
|
|
|
|
|
|
665
|
1
|
50
|
|
|
|
75
|
return if $opts{no_reset}; |
666
|
|
|
|
|
|
|
|
667
|
1
|
|
|
|
|
7
|
await $self->request_reset( 1 ); |
668
|
1
|
|
|
|
|
220
|
await $self->request_reset( 0 ); |
669
|
|
|
|
|
|
|
|
670
|
1
|
|
|
|
|
213
|
my $timeout = 50; |
671
|
1
|
|
|
|
|
6
|
while( --$timeout ) { |
672
|
2
|
100
|
|
|
|
1407
|
last if not ASI_SYS_STATUS_LOCKSTATUS & await $self->ldcs( REG_ASI_SYS_STATUS ); |
673
|
|
|
|
|
|
|
|
674
|
1
|
|
|
|
|
75
|
await Future::IO->sleep( 0.05 ); |
675
|
|
|
|
|
|
|
} |
676
|
1
|
50
|
|
|
|
77
|
die "Failed to unlock chip\n" if !$timeout; |
677
|
1
|
|
|
1
|
1
|
2695
|
} |
678
|
|
|
|
|
|
|
|
679
|
|
|
|
|
|
|
=head2 enable_nvmprog |
680
|
|
|
|
|
|
|
|
681
|
|
|
|
|
|
|
await $updi->enable_nvmprog; |
682
|
|
|
|
|
|
|
|
683
|
|
|
|
|
|
|
Requests the chip to enter NVM programming mode. |
684
|
|
|
|
|
|
|
|
685
|
|
|
|
|
|
|
=cut |
686
|
|
|
|
|
|
|
|
687
|
1
|
|
|
|
|
4
|
async method enable_nvmprog () |
|
1
|
|
|
|
|
2
|
|
688
|
1
|
|
|
|
|
3
|
{ |
689
|
1
|
|
|
|
|
5
|
await $self->key( KEY_NVMPROG ); |
690
|
|
|
|
|
|
|
|
691
|
1
|
50
|
|
|
|
183
|
die "Failed to set NVMPROG key\n" unless ASI_KEY_NVMPROG & await $self->ldcs( REG_ASI_KEY_STATUS ); |
692
|
|
|
|
|
|
|
|
693
|
1
|
|
|
|
|
76
|
await $self->request_reset( 1 ); |
694
|
1
|
|
|
|
|
223
|
await $self->request_reset( 0 ); |
695
|
|
|
|
|
|
|
|
696
|
1
|
|
|
|
|
216
|
my $timeout = 50; |
697
|
1
|
|
|
|
|
6
|
while( --$timeout ) { |
698
|
2
|
100
|
|
|
|
1367
|
last if ASI_SYS_STATUS_NVMPROG & await $self->ldcs( REG_ASI_SYS_STATUS ); |
699
|
|
|
|
|
|
|
|
700
|
1
|
|
|
|
|
93
|
await Future::IO->sleep( 0.05 ); |
701
|
|
|
|
|
|
|
} |
702
|
1
|
50
|
|
|
|
83
|
die "Timed out waiting for NVMPROG key to be accepted\n" if !$timeout; |
703
|
1
|
|
|
1
|
1
|
2843
|
} |
704
|
|
|
|
|
|
|
|
705
|
|
|
|
|
|
|
field $_nvmctrl; |
706
|
|
|
|
|
|
|
|
707
|
10
|
|
|
|
|
17
|
method nvmctrl () |
|
10
|
|
|
|
|
19
|
|
708
|
10
|
|
|
10
|
0
|
34
|
{ |
709
|
10
|
100
|
|
|
|
53
|
return $_nvmctrl if defined $_nvmctrl; |
710
|
|
|
|
|
|
|
|
711
|
6
|
50
|
|
|
|
24
|
defined $_nvm_version or |
712
|
|
|
|
|
|
|
croak "Must ->init_link before calling ->nvmctrl"; |
713
|
|
|
|
|
|
|
|
714
|
|
|
|
|
|
|
# ATtiny and ATmega chips claim "P:0" |
715
|
6
|
100
|
|
|
|
68
|
return $_nvmctrl = Device::AVR::UPDI::_NVMCtrlv0->new( updi => $self ) |
716
|
|
|
|
|
|
|
if $_nvm_version == 0; |
717
|
|
|
|
|
|
|
|
718
|
|
|
|
|
|
|
# AVR Dx chips claim "P:2" |
719
|
3
|
50
|
|
|
|
50
|
return $_nvmctrl = Device::AVR::UPDI::_NVMCtrlv2->new( updi => $self ) |
720
|
|
|
|
|
|
|
if $_nvm_version == 2; |
721
|
|
|
|
|
|
|
|
722
|
0
|
|
|
|
|
0
|
croak "Unrecognised NVM version $_nvm_version"; |
723
|
|
|
|
|
|
|
} |
724
|
|
|
|
|
|
|
|
725
|
|
|
|
|
|
|
=head2 read_flash_page |
726
|
|
|
|
|
|
|
|
727
|
|
|
|
|
|
|
$data = await $updi->read_flash_page( $addr, $len ); |
728
|
|
|
|
|
|
|
|
729
|
|
|
|
|
|
|
Reads a single flash page and returns the data. C<$addr> is within the flash |
730
|
|
|
|
|
|
|
address space. |
731
|
|
|
|
|
|
|
|
732
|
|
|
|
|
|
|
=cut |
733
|
|
|
|
|
|
|
|
734
|
2
|
|
|
|
|
5
|
async method read_flash_page ( $addr, $len ) |
|
2
|
|
|
|
|
6
|
|
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
5
|
|
735
|
2
|
|
|
|
|
8
|
{ |
736
|
2
|
|
|
|
|
11
|
return await $self->nvmctrl->read_flash_page( $addr, $len ); |
737
|
2
|
|
|
2
|
1
|
5154
|
} |
738
|
|
|
|
|
|
|
|
739
|
|
|
|
|
|
|
=head2 write_flash_page |
740
|
|
|
|
|
|
|
|
741
|
|
|
|
|
|
|
await $updi->write_flash_page( $addr, $data ); |
742
|
|
|
|
|
|
|
|
743
|
|
|
|
|
|
|
Writes a single flash page into the NVM controller in 16-bit word transfers. |
744
|
|
|
|
|
|
|
C<$addr> is within the flash address space. |
745
|
|
|
|
|
|
|
|
746
|
|
|
|
|
|
|
=cut |
747
|
|
|
|
|
|
|
|
748
|
2
|
|
|
|
|
7
|
async method write_flash_page ( $addr, $data ) |
|
2
|
|
|
|
|
6
|
|
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
4
|
|
749
|
2
|
|
|
|
|
11
|
{ |
750
|
2
|
|
|
|
|
26
|
await $self->nvmctrl->write_flash_page( $addr, $data ); |
751
|
2
|
|
|
2
|
1
|
25606
|
} |
752
|
|
|
|
|
|
|
|
753
|
|
|
|
|
|
|
=head2 read_eeprom_page |
754
|
|
|
|
|
|
|
|
755
|
|
|
|
|
|
|
$data = await $updi->read_eeprom_page( $addr, $len ); |
756
|
|
|
|
|
|
|
|
757
|
|
|
|
|
|
|
Reads a single EEPROM page and returns the data. C<$addr> is within the EEPROM |
758
|
|
|
|
|
|
|
address space. |
759
|
|
|
|
|
|
|
|
760
|
|
|
|
|
|
|
=cut |
761
|
|
|
|
|
|
|
|
762
|
2
|
|
|
|
|
4
|
async method read_eeprom_page ( $addr, $len ) |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
6
|
|
|
2
|
|
|
|
|
3
|
|
763
|
2
|
|
|
|
|
9
|
{ |
764
|
2
|
|
|
|
|
10
|
return await $self->nvmctrl->read_eeprom_page( $addr, $len ); |
765
|
2
|
|
|
2
|
1
|
4843
|
} |
766
|
|
|
|
|
|
|
|
767
|
|
|
|
|
|
|
=head2 write_eeprom_page |
768
|
|
|
|
|
|
|
|
769
|
|
|
|
|
|
|
Similar to L but issues a combined erase-and-write |
770
|
|
|
|
|
|
|
command and C<$addr> is within the EEPROM address space. |
771
|
|
|
|
|
|
|
|
772
|
|
|
|
|
|
|
=cut |
773
|
|
|
|
|
|
|
|
774
|
2
|
|
|
|
|
4
|
async method write_eeprom_page ( $addr, $data ) |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
7
|
|
775
|
2
|
|
|
|
|
10
|
{ |
776
|
2
|
|
|
|
|
31
|
await $self->nvmctrl->write_eeprom_page( $addr, $data ); |
777
|
2
|
|
|
2
|
1
|
22190
|
} |
778
|
|
|
|
|
|
|
|
779
|
|
|
|
|
|
|
=head2 write_fuse |
780
|
|
|
|
|
|
|
|
781
|
|
|
|
|
|
|
await $updi->write_fuse( $idx, $value ); |
782
|
|
|
|
|
|
|
|
783
|
|
|
|
|
|
|
Writes a fuse value. C<$idx> is the index of the fuse within the FUSES memory |
784
|
|
|
|
|
|
|
segment, from 0 onwards. |
785
|
|
|
|
|
|
|
|
786
|
|
|
|
|
|
|
=cut |
787
|
|
|
|
|
|
|
|
788
|
2
|
|
|
|
|
4
|
async method write_fuse ( $idx, $value ) |
|
2
|
|
|
|
|
6
|
|
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
3
|
|
789
|
2
|
|
|
|
|
8
|
{ |
790
|
2
|
|
|
|
|
11
|
await $self->nvmctrl->write_fuse( $idx, $value ); |
791
|
2
|
|
|
2
|
1
|
6588
|
} |
792
|
|
|
|
|
|
|
|
793
|
|
|
|
|
|
|
=head2 read_fuse |
794
|
|
|
|
|
|
|
|
795
|
|
|
|
|
|
|
$value = await $updi->read_fuse( $idx ); |
796
|
|
|
|
|
|
|
|
797
|
|
|
|
|
|
|
Reads a fuse value. C<$idx> is the index of the fuse within the FUSES memory |
798
|
|
|
|
|
|
|
segment, from 0 onwards. |
799
|
|
|
|
|
|
|
|
800
|
|
|
|
|
|
|
=cut |
801
|
|
|
|
|
|
|
|
802
|
0
|
|
|
|
|
0
|
async method read_fuse ( $idx ) |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
803
|
0
|
|
|
|
|
0
|
{ |
804
|
0
|
|
|
|
|
0
|
my $addr = $_partinfo->baseaddr_fuse + $idx; |
805
|
|
|
|
|
|
|
|
806
|
0
|
|
|
|
|
0
|
return await $self->lds8( $addr ); |
807
|
0
|
|
|
0
|
1
|
0
|
} |
808
|
|
|
|
|
|
|
|
809
|
|
|
|
|
|
|
role # hide from indexer |
810
|
|
|
|
|
|
|
Device::AVR::UPDI::_NVMCtrl { |
811
|
|
|
|
|
|
|
|
812
|
|
|
|
|
|
|
use constant { |
813
|
13
|
|
|
|
|
14013
|
NVMCTRL_CTRLA => 0, |
814
|
|
|
|
|
|
|
NVMCTRL_STATUS => 2, |
815
|
|
|
|
|
|
|
NVMCTRL_STATUS_FBUSY => (1<<0), |
816
|
13
|
|
|
13
|
|
1916
|
}; |
|
13
|
|
|
|
|
37
|
|
817
|
|
|
|
|
|
|
|
818
|
36
|
|
|
36
|
|
114
|
field $_updi :reader :param; |
|
36
|
|
|
36
|
|
135
|
|
|
|
|
|
36
|
|
|
|
819
|
37
|
|
|
37
|
|
70
|
field $_partinfo :reader; |
|
37
|
|
|
37
|
|
148
|
|
|
|
|
|
37
|
|
|
|
820
|
|
|
|
|
|
|
|
821
|
|
|
|
|
|
|
ADJUST |
822
|
|
|
|
|
|
|
{ |
823
|
|
|
|
|
|
|
$_partinfo = $_updi->partinfo; |
824
|
|
|
|
|
|
|
} |
825
|
|
|
|
|
|
|
|
826
|
11
|
|
|
|
|
23
|
async method nvmctrl_command ( $cmd ) |
|
11
|
|
|
|
|
22
|
|
|
11
|
|
|
|
|
17
|
|
827
|
11
|
|
|
|
|
32
|
{ |
828
|
11
|
|
|
|
|
35
|
await $self->updi->sts8( $self->partinfo->baseaddr_nvmctrl + NVMCTRL_CTRLA, $cmd ); |
829
|
11
|
|
|
11
|
|
27
|
} |
|
|
|
|
11
|
|
|
|
|
|
|
|
11
|
|
|
|
830
|
|
|
|
|
|
|
|
831
|
11
|
|
|
|
|
21
|
async method await_nvm_not_busy () |
|
11
|
|
|
|
|
19
|
|
832
|
11
|
|
|
|
|
43
|
{ |
833
|
11
|
|
|
|
|
23
|
my $timeout = 50; |
834
|
11
|
|
|
|
|
58
|
while( --$timeout ) { |
835
|
13
|
100
|
|
|
|
3021
|
last if not( NVMCTRL_STATUS_FBUSY & await $self->updi->lds8( |
836
|
|
|
|
|
|
|
$self->partinfo->baseaddr_nvmctrl + NVMCTRL_STATUS ) ); |
837
|
|
|
|
|
|
|
|
838
|
2
|
|
|
|
|
508
|
await Future::IO->sleep( 0.01 ); |
839
|
|
|
|
|
|
|
} |
840
|
11
|
|
|
11
|
|
38
|
} |
|
|
|
|
11
|
|
|
|
|
|
|
|
11
|
|
|
|
841
|
|
|
|
|
|
|
} |
842
|
|
|
|
|
|
|
|
843
|
|
|
|
|
|
|
class # hide from indexer |
844
|
|
|
|
|
|
|
Device::AVR::UPDI::_NVMCtrlv0 :does(Device::AVR::UPDI::_NVMCtrl) { |
845
|
|
|
|
|
|
|
|
846
|
13
|
|
|
13
|
|
1643
|
use Carp; |
|
13
|
|
|
|
|
37
|
|
|
13
|
|
|
|
|
1129
|
|
847
|
|
|
|
|
|
|
|
848
|
|
|
|
|
|
|
use constant { |
849
|
|
|
|
|
|
|
# Command values |
850
|
13
|
|
|
|
|
21881
|
NVMCTRL_CMD_WP => 1, |
851
|
|
|
|
|
|
|
NVMCTRL_CMD_ER => 2, |
852
|
|
|
|
|
|
|
NVMCTRL_CMD_ERWP => 3, |
853
|
|
|
|
|
|
|
NVMCTRL_CMD_PBC => 4, |
854
|
|
|
|
|
|
|
NVMCTRL_CMD_CHER => 5, |
855
|
|
|
|
|
|
|
NVMCTRL_CMD_EEER => 6, |
856
|
|
|
|
|
|
|
NVMCTRL_CMD_WFU => 7, |
857
|
|
|
|
|
|
|
NVMCTRL_CTRLB => 1, |
858
|
|
|
|
|
|
|
NVMCTRL_DATA => 6, |
859
|
|
|
|
|
|
|
NVMCTRL_ADDR => 8, |
860
|
13
|
|
|
13
|
|
95
|
}; |
|
13
|
|
|
|
|
64
|
|
861
|
|
|
|
|
|
|
|
862
|
1
|
|
|
|
|
2
|
async method read_flash_page ( $addr, $len ) |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
3
|
|
863
|
1
|
|
|
|
|
3
|
{ |
864
|
1
|
|
|
|
|
5
|
return await $self->updi->ld( $self->partinfo->baseaddr_flash + $addr, $len ); |
865
|
1
|
|
|
1
|
|
2
|
} |
866
|
|
|
|
|
|
|
|
867
|
1
|
|
|
|
|
2
|
async method read_eeprom_page ( $addr, $len ) |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
2
|
|
868
|
1
|
|
|
|
|
3
|
{ |
869
|
1
|
|
|
|
|
5
|
return await $self->updi->ld( $self->partinfo->baseaddr_eeprom + $addr, $len ); |
870
|
1
|
|
|
1
|
|
2
|
} |
871
|
|
|
|
|
|
|
|
872
|
2
|
|
|
|
|
6
|
async method _write_page ( $addr, $data, $wordsize, $cmd ) |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
6
|
|
873
|
2
|
|
|
|
|
6
|
{ |
874
|
2
|
|
|
|
|
9
|
my $updi = $self->updi; |
875
|
|
|
|
|
|
|
|
876
|
|
|
|
|
|
|
# clear page buffer |
877
|
2
|
|
|
|
|
10
|
await $self->nvmctrl_command( NVMCTRL_CMD_PBC ); |
878
|
2
|
|
|
|
|
452
|
await $self->await_nvm_not_busy; |
879
|
|
|
|
|
|
|
|
880
|
|
|
|
|
|
|
# Disable response sig for speed |
881
|
2
|
|
|
|
|
351
|
await $updi->set_rsd( 1 ); |
882
|
|
|
|
|
|
|
|
883
|
2
|
100
|
|
|
|
445
|
if( $wordsize == 8 ) { |
|
|
50
|
|
|
|
|
|
884
|
1
|
|
|
|
|
6
|
await $updi->st8( $addr, $data ); |
885
|
|
|
|
|
|
|
} |
886
|
|
|
|
|
|
|
elsif( $wordsize == 16 ) { |
887
|
1
|
|
|
|
|
7
|
await $updi->st16( $addr, $data ); |
888
|
|
|
|
|
|
|
} |
889
|
|
|
|
|
|
|
else { |
890
|
0
|
|
|
|
|
0
|
croak "Invalid word size"; |
891
|
|
|
|
|
|
|
} |
892
|
|
|
|
|
|
|
|
893
|
|
|
|
|
|
|
# Re-enable response sig again |
894
|
2
|
|
|
|
|
409
|
await $updi->set_rsd( 0 ); |
895
|
|
|
|
|
|
|
|
896
|
2
|
|
|
|
|
437
|
await $self->nvmctrl_command( $cmd ); |
897
|
2
|
|
|
|
|
433
|
await $self->await_nvm_not_busy; |
898
|
2
|
|
|
2
|
|
5
|
} |
899
|
|
|
|
|
|
|
|
900
|
1
|
|
|
|
|
2
|
async method write_flash_page ( $addr, $data ) |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
2
|
|
901
|
1
|
|
|
|
|
3
|
{ |
902
|
1
|
|
|
|
|
7
|
await $self->_write_page( $self->partinfo->baseaddr_flash + $addr, $data, 16, NVMCTRL_CMD_WP ); |
903
|
1
|
|
|
1
|
|
3
|
} |
904
|
|
|
|
|
|
|
|
905
|
1
|
|
|
|
|
3
|
async method write_eeprom_page ( $addr, $data ) |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
2
|
|
906
|
1
|
|
|
|
|
2
|
{ |
907
|
1
|
|
|
|
|
4
|
await $self->_write_page( $self->partinfo->baseaddr_eeprom + $addr, $data, 8, NVMCTRL_CMD_ERWP ); |
908
|
1
|
|
|
1
|
|
2
|
} |
909
|
|
|
|
|
|
|
|
910
|
1
|
|
|
|
|
2
|
async method write_fuse ( $idx, $value ) |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
2
|
|
911
|
1
|
|
|
|
|
2
|
{ |
912
|
1
|
|
|
|
|
3
|
my $updi = $self->updi; |
913
|
|
|
|
|
|
|
|
914
|
1
|
|
|
|
|
4
|
my $addr = $self->partinfo->baseaddr_fuse + $idx; |
915
|
|
|
|
|
|
|
|
916
|
1
|
|
|
|
|
3
|
my $baseaddr = $self->partinfo->baseaddr_nvmctrl; |
917
|
|
|
|
|
|
|
|
918
|
|
|
|
|
|
|
# Oddly, this works but an attempt at STS16 does not. Unsure why |
919
|
1
|
|
|
|
|
10
|
await $updi->sts8 ( $baseaddr + NVMCTRL_ADDR , $addr & 0xFF ); |
920
|
1
|
|
|
|
|
150
|
await $updi->sts8 ( $baseaddr + NVMCTRL_ADDR+1, $addr >> 8 ); |
921
|
|
|
|
|
|
|
|
922
|
1
|
|
|
|
|
196
|
await $updi->sts8 ( $baseaddr + NVMCTRL_DATA, $value ); |
923
|
|
|
|
|
|
|
|
924
|
1
|
|
|
|
|
160
|
await $self->nvmctrl_command( NVMCTRL_CMD_WFU ); |
925
|
|
|
|
|
|
|
|
926
|
1
|
|
|
|
|
223
|
await $self->await_nvm_not_busy; |
927
|
1
|
|
|
1
|
|
2
|
} |
928
|
|
|
|
|
|
|
} |
929
|
|
|
|
|
|
|
|
930
|
|
|
|
|
|
|
class # hide from indexer |
931
|
|
|
|
|
|
|
Device::AVR::UPDI::_NVMCtrlv2 :does(Device::AVR::UPDI::_NVMCtrl) { |
932
|
|
|
|
|
|
|
|
933
|
13
|
|
|
13
|
|
1680
|
use Carp; |
|
13
|
|
|
|
|
70
|
|
|
13
|
|
|
|
|
1295
|
|
934
|
|
|
|
|
|
|
|
935
|
|
|
|
|
|
|
use constant { |
936
|
|
|
|
|
|
|
# Command values |
937
|
13
|
|
|
|
|
24024
|
NVMCTRL_CMD_NOCMD => 0x00, |
938
|
|
|
|
|
|
|
NVMCTRL_CMD_FLWR => 0x02, |
939
|
|
|
|
|
|
|
NVMCTRL_CMD_EEERWR => 0x13, |
940
|
|
|
|
|
|
|
|
941
|
|
|
|
|
|
|
NVMCTRL_CTRLB => 1, |
942
|
13
|
|
|
13
|
|
131
|
}; |
|
13
|
|
|
|
|
49
|
|
943
|
|
|
|
|
|
|
|
944
|
2
|
|
|
|
|
3
|
async method _set_flmap ( $bank ) |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
4
|
|
945
|
2
|
|
|
|
|
4
|
{ |
946
|
2
|
|
|
|
|
7
|
await $self->updi->sts8( $self->partinfo->baseaddr_nvmctrl + NVMCTRL_CTRLB, $bank << 4 ); |
947
|
2
|
|
|
2
|
|
4
|
} |
948
|
|
|
|
|
|
|
|
949
|
1
|
|
|
|
|
2
|
async method read_flash_page ( $addr, $len ) |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
2
|
|
950
|
1
|
|
|
|
|
2
|
{ |
951
|
1
|
|
|
|
|
4
|
await $self->_set_flmap( $addr >> 15 ); |
952
|
1
|
|
|
|
|
223
|
$addr &= 0x7FFF; |
953
|
|
|
|
|
|
|
|
954
|
1
|
|
|
|
|
9
|
return await $self->updi->ld( $self->partinfo->baseaddr_flash + $addr, $len ); |
955
|
1
|
|
|
1
|
|
3
|
} |
956
|
|
|
|
|
|
|
|
957
|
1
|
|
|
|
|
2
|
async method read_eeprom_page ( $addr, $len ) |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
1
|
|
958
|
1
|
|
|
|
|
3
|
{ |
959
|
1
|
|
|
|
|
4
|
return await $self->updi->ld( $self->partinfo->baseaddr_eeprom + $addr, $len ); |
960
|
1
|
|
|
1
|
|
2
|
} |
961
|
|
|
|
|
|
|
|
962
|
3
|
|
|
|
|
8
|
async method _write_page ( $addr, $data, $wordsize, $cmd ) |
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
8
|
|
|
3
|
|
|
|
|
8
|
|
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
6
|
|
963
|
3
|
|
|
|
|
20
|
{ |
964
|
3
|
|
|
|
|
22
|
my $updi = $self->updi; |
965
|
|
|
|
|
|
|
|
966
|
|
|
|
|
|
|
# set page write mode |
967
|
3
|
|
|
|
|
19
|
await $self->nvmctrl_command( $cmd ); |
968
|
|
|
|
|
|
|
|
969
|
|
|
|
|
|
|
# Disable response sig for speed on long data |
970
|
|
|
|
|
|
|
# (no point on single-byte fuses) |
971
|
3
|
100
|
|
|
|
733
|
await $updi->set_rsd( 1 ) if length $data > 1; |
972
|
|
|
|
|
|
|
|
973
|
3
|
100
|
|
|
|
448
|
if( $wordsize == 8 ) { |
|
|
50
|
|
|
|
|
|
974
|
2
|
|
|
|
|
15
|
await $updi->st8( $addr, $data ); |
975
|
|
|
|
|
|
|
} |
976
|
|
|
|
|
|
|
elsif( $wordsize == 16 ) { |
977
|
1
|
|
|
|
|
5
|
await $updi->st16( $addr, $data ); |
978
|
|
|
|
|
|
|
} |
979
|
|
|
|
|
|
|
else { |
980
|
0
|
|
|
|
|
0
|
croak "Invalid word size"; |
981
|
|
|
|
|
|
|
} |
982
|
|
|
|
|
|
|
|
983
|
|
|
|
|
|
|
# Re-enable response sig again |
984
|
3
|
|
|
|
|
584
|
await $updi->set_rsd( 0 ); |
985
|
|
|
|
|
|
|
|
986
|
3
|
|
|
|
|
493
|
await $self->await_nvm_not_busy; |
987
|
|
|
|
|
|
|
|
988
|
|
|
|
|
|
|
# clear command |
989
|
3
|
|
|
|
|
446
|
await $self->nvmctrl_command( NVMCTRL_CMD_NOCMD ); |
990
|
3
|
|
|
|
|
656
|
await $self->await_nvm_not_busy; |
991
|
3
|
|
|
3
|
|
9
|
} |
992
|
|
|
|
|
|
|
|
993
|
1
|
|
|
|
|
2
|
async method write_flash_page ( $addr, $data ) |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
2
|
|
994
|
1
|
|
|
|
|
3
|
{ |
995
|
1
|
|
|
|
|
6
|
await $self->_set_flmap( $addr >> 15 ); |
996
|
1
|
|
|
|
|
210
|
$addr &= 0x7FFF; |
997
|
|
|
|
|
|
|
|
998
|
1
|
|
|
|
|
5
|
await $self->_write_page( $self->partinfo->baseaddr_flash + $addr, $data, 16, NVMCTRL_CMD_FLWR ); |
999
|
1
|
|
|
1
|
|
3
|
} |
1000
|
|
|
|
|
|
|
|
1001
|
1
|
|
|
|
|
1
|
async method write_eeprom_page ( $addr, $data ) |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
2
|
|
1002
|
1
|
|
|
|
|
3
|
{ |
1003
|
1
|
|
|
|
|
4
|
await $self->_write_page( $self->partinfo->baseaddr_eeprom + $addr, $data, 8, NVMCTRL_CMD_EEERWR ); |
1004
|
1
|
|
|
1
|
|
2
|
} |
1005
|
|
|
|
|
|
|
|
1006
|
1
|
|
|
|
|
2
|
async method write_fuse ( $idx, $value ) |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
2
|
|
1007
|
1
|
|
|
|
|
4
|
{ |
1008
|
|
|
|
|
|
|
# Fuses are written by pretending it's EEPROM |
1009
|
1
|
|
|
|
|
7
|
my $data = pack "C", $value; |
1010
|
|
|
|
|
|
|
|
1011
|
1
|
|
|
|
|
5
|
await $self->_write_page( $self->partinfo->baseaddr_fuse + $idx, $data, 8, NVMCTRL_CMD_EEERWR ); |
1012
|
1
|
|
|
1
|
|
2
|
} |
1013
|
|
|
|
|
|
|
} |
1014
|
|
|
|
|
|
|
|
1015
|
|
|
|
|
|
|
=head1 SEE ALSO |
1016
|
|
|
|
|
|
|
|
1017
|
|
|
|
|
|
|
=over 2 |
1018
|
|
|
|
|
|
|
|
1019
|
|
|
|
|
|
|
=item * |
1020
|
|
|
|
|
|
|
|
1021
|
|
|
|
|
|
|
"AVR UPDI Programming Cable" |
1022
|
|
|
|
|
|
|
|
1023
|
|
|
|
|
|
|
An adapter cable to flash firmware onto an AVR microcontroller chip via UPDI, |
1024
|
|
|
|
|
|
|
compatible with this module. |
1025
|
|
|
|
|
|
|
|
1026
|
|
|
|
|
|
|
L |
1027
|
|
|
|
|
|
|
|
1028
|
|
|
|
|
|
|
=back |
1029
|
|
|
|
|
|
|
|
1030
|
|
|
|
|
|
|
=cut |
1031
|
|
|
|
|
|
|
|
1032
|
|
|
|
|
|
|
=head1 AUTHOR |
1033
|
|
|
|
|
|
|
|
1034
|
|
|
|
|
|
|
Paul Evans |
1035
|
|
|
|
|
|
|
|
1036
|
|
|
|
|
|
|
=cut |
1037
|
|
|
|
|
|
|
|
1038
|
|
|
|
|
|
|
0x55AA; |
1039
|
|
|
|
|
|
|
|
1040
|
|
|
|
|
|
|
__DATA__ |