forked from 74hc595/PIC16F1-USB-Bootloader
-
Notifications
You must be signed in to change notification settings - Fork 0
/
usb16f1prog
executable file
·272 lines (227 loc) · 8.9 KB
/
usb16f1prog
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
#!/usr/bin/env python
#
# usb16f1prog
# Copyright (c) 2015, Matt Sarnoff (msarnoff.org)
# v1.0, February 12, 2015
# Released under a 3-clause BSD license: see the accompanying LICENSE file.
#
# Uploads firmware to a PIC16F1xxx microcontroller programmed with
# Matt Sarnoff's USB bootloader.
#
# Accepts 16-bit Intel HEX files as input.
# Can only be used to write to program memory; the bootloader does not support
# writing the configuration words or user ID words, so values at those
# addresses in the input file are ignored.
#
# The programming protocol is very simplistic. There are three requests,
# distinguished by their length: Set Parameters (4 bytes), Write (64 bytes),
# and Reset (1 byte). After a Set Parameters or Write command, the host (e.g.
# this script) must wait for a 1-byte status response. If the response byte is
# 0x01, the operation succeeded. Otherwise, the host should abort. (See below
# for possible error values.)
#
# Set Parameters is 4 bytes long:
# - addressLowByte
# - addressHighByte
# - expectedChecksum
# - shouldErase
# addressLowByte/HighByte is the 16-bit word address, aligned to a 32-word
# boundary, where data should be written.
# expectedChecksum is the 8-bit checksum of the 32 words to be written at the
# specified address. (This is the 2's complement of the byte-wise sum mod 256
# of the upcoming 32 words.)
# If the shouldErase byte is 0x45 ('E'), the flash row at that address is
# erased. An erase is mandatory before a Write command.
#
# Write is 64 bytes long:
# - dataWord0LowByte
# - dataWord0HighByte
# ...
# - dataWord31LowByte
# - dataWord31HighByte
# In other words, exactly 32 words, little-endian. If less than 32 words are
# to be written, the sequence should be padded out with 0x3FFF (0xFF, 0x3F).
# The device may return an error if the checksum of the data does not match
# the value sent in the last Set Parameters command, or if the values in flash
# do not match the supplied data after the write. (The latter may happen if you
# attempt to write to an address outside the device's ROM space, or to the
# bootloader region.)
#
# Reset is 1 byte long. If it is 0x52 ('R'), the device is reset, and no status
# is returned.
import argparse, errno, os, shutil, sys
from serial import Serial
from intelhex import IntelHex16bit, IntelHexError
__version__ = '1.0'
__author__ = 'Matt Sarnoff (msarnoff.org)'
PORT_ENV_VAR = 'USB16F1PROG_PORT'
CONFIGURATION_WORD_RANGE = range(0x8000*2, 0x8009*2, 2)
FLASH_ROW_LEN = 32
BCMD_ERASE = 0x45
BCMD_RESET = 0x52
# Status codes
STATUS_OK = 1
STATUS_INVALID_COMMAND = 2
STATUS_INVALID_CHECKSUM = 3
STATUS_VERIFY_FAILED = 4
STATUS_MESSAGES = {
STATUS_INVALID_COMMAND: 'invalid command',
STATUS_INVALID_CHECKSUM: 'checksum failed; data not written',
STATUS_VERIFY_FAILED: 'write verification failed'
}
# Log levels
LOG_QUIET = 0
LOG_DEFAULT = 1
LOG_VERBOSE = 2
def low(n):
return n & 0xFF
def high(n):
return (n >> 8) & 0xFF
log_level = LOG_DEFAULT
def log(level, *args):
if log_level >= level:
print ' '.join(str(a) for a in args)
def error(msg):
print >> sys.stderr, 'error:', msg
def exit_with_error(code, msg):
error(msg)
sys.exit(code)
def warn(msg):
print >> sys.stderr, 'warning:', msg
def device_error(status):
error(STATUS_MESSAGES.get(status, 'unknown status code 0x%02x' % status))
def device_set_params(ser, wordaddr, checksum=None):
log(LOG_VERBOSE, 'Erasing %d words at %04x%s' % (
FLASH_ROW_LEN,
wordaddr,
(', checksum of data to be written is 0x%02x' % checksum) if checksum is not None else ''
))
ser.write(bytearray([low(wordaddr), high(wordaddr), checksum or 0, BCMD_ERASE]))
status = ord(ser.read(1)[0])
if status != STATUS_OK:
device_error(status)
return False
else:
return True
def device_write(ser, databytes):
log(LOG_DEFAULT, 'Writing %d bytes' % len(databytes))
ser.write(databytes)
status = ord(ser.read(1)[0])
if status != STATUS_OK:
device_error(status)
return False
else:
return True
def device_reset(ser):
log(LOG_DEFAULT, 'Resetting device')
ser.write(bytearray([BCMD_RESET]))
return True
def main(args):
global log_level
parser = argparse.ArgumentParser(
description='Uploads firmware (in Intel HEX format) to a PIC16F1xxx using Matt Sarnoff\'s USB bootloader.',
epilog='If no port is specified using -p, the '+PORT_ENV_VAR+' environment variable is consulted. '+
"""
The entire range of application program memory (excluding the bootloader region) is erased
during the programming process. To preserve the contents of High-Endurance Flash across reprograms,
use the -r option with the appropriate value. (e.g. for the PIC16F1454, which has 8K ROM and 128 bytes
of HEF, use '-r 8064'.)
""",
formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=35))
parser.add_argument('-v', '--version',
action='version',
version='%(prog)s version '+__version__+' by '+__author__)
parser.add_argument('-l', '--log-level',
metavar='LVL',
type=int,
default=1,
choices=[0,1,2],
help='specify logging level (0=errors only, 1=default, 2=verbose)')
parser.add_argument('-p', '--port',
metavar='PORT',
help='the serial port that the PIC is attached to')
parser.add_argument('-b', '--bootloader-size',
metavar='SIZE',
default=512,
type=int,
help='size of the bootloader in words, 512 or 4096 (defaults to 512)')
parser.add_argument('-r', '--rom-size',
metavar='SIZE',
default=8192,
type=int,
help='size of the device ROM in words up to 32768 (defaults to 8192)')
parser.add_argument('inputfile',
metavar='inputfile',
nargs=1,
help='16-bit Intel HEX input file')
args = parser.parse_args()
infile = args.inputfile[0]
log_level = args.log_level
if args.port is not None:
port = args.port
else:
port = os.getenv(PORT_ENV_VAR)
if not port:
exit_with_error(1, 'no serial port specified (use -p or set the '+PORT_ENV_VAR+' environment variable)')
min_address = args.bootloader_size
if min_address != 512 and min_address != 4096:
exit_with_error(2, 'bootloader size must be 512 or 4096')
max_address = args.rom_size
if max_address <= min_address or max_address > 32768:
exit_with_error(3, 'ROM size must be <= 32768 and > bootloader size')
# open the hex file
try:
ih = IntelHex16bit(infile)
except IOError, e:
exit_with_error(4, e)
except IntelHexError, e:
exit_with_error(5, e)
if ih.minaddr() < min_address:
exit_with_error(6, 'hex file starts at 0x%04x, but the minimum allowable start address is 0x%04x' % (
ih.minaddr(), min_address))
# check for configuration words and warn that they won't be written
# IntelHex has no "check if address is populated" method, so we have to use
# the dictionary representation
ihdict = ih.todict()
for byteaddr in CONFIGURATION_WORD_RANGE:
if ihdict.get(byteaddr) is not None:
wordaddr = byteaddr/2
warn('bootloader cannot write to address 0x%x in configuration space' % wordaddr)
# remove configuration words from hex
del ih[byteaddr:byteaddr+2]
ih.padding = 0x3fff
log(LOG_DEFAULT, 'Code range: 0x%04x-0x%04x (%d words)' % (ih.minaddr(), ih.maxaddr(), ih.maxaddr()-ih.minaddr()+1))
# open the serial port
log(LOG_DEFAULT, 'Opening serial port '+port)
try:
ser = Serial(port, 38400) # baud doesn't matter
except Exception, e:
exit_with_error(7, e)
failed = False
for wordaddr in range(min_address, max_address, FLASH_ROW_LEN):
row_in_range = wordaddr >= ih.minaddr() and wordaddr <= ih.maxaddr()
# compute the row checksum
checksum = None
if row_in_range:
words = ih.tobinarray(wordaddr, size=FLASH_ROW_LEN)
data = reduce(lambda arr,word: arr.extend([low(word), high(word)]) or arr, words, [])
checksum = -sum(data) & 0xff # two's complement of lower 8 bits of sum of bytes
# erase the row
if not device_set_params(ser, wordaddr, checksum):
failed = True
break
# if the row is within the range of the hex file, program the data
if row_in_range:
if not device_write(ser, data):
failed = True
break
if not failed:
device_reset(ser)
log(LOG_DEFAULT, 'Done.')
log(LOG_DEFAULT, 'Closing serial port '+port)
ser.close()
if failed:
exit_with_error(127, 'Programming failed')
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))