1
+ #!/usr/bin/env python
2
+ # SparkFun Variable Loader
3
+ # Variable baud rate bootloader for Artemis Apollo3 modules
4
+
5
+ # Immediately upon reset the Artemis module will search for the timing character
6
+ # to auto-detect the baud rate. If a valid baud rate is found the Artemis will
7
+ # respond with the bootloader version packet
8
+ # If the computer receives a well-formatted version number packet at the desired
9
+ # baud rate it will send a command to begin bootloading. The Artemis shall then
10
+ # respond with the a command asking for the next frame.
11
+ # The host will then send a frame packet. If the CRC is OK the Artemis will write
12
+ # that to memory and request the next frame. If the CRC fails the Artemis will
13
+ # discard that data and send a request to re-send the previous frame.
14
+ # This cycle repeats until the Artemis receives a done command in place of the
15
+ # requested frame data command.
16
+ # The initial baud rate determination must occur within some small timeout. Once
17
+ # baud rate detection has completed all additional communication will have a
18
+ # universal timeout value. Once the Artemis has begun requesting data it may no
19
+ # no longer exit the bootloader. If the host detects a timeout at any point it
20
+ # will stop bootloading.
21
+
22
+ # Notes about PySerial timeout:
23
+ # The timeout operates on whole functions - that is to say that a call to
24
+ # ser.read(10) will return after ser.timeout, just as will ser.read(1) (assuming
25
+ # that the necessary bytes were not found)
26
+ # If there are no incoming bytes (on the line or in the buffer) then two calls to
27
+ # ser.read(n) will time out after 2*ser.timeout
28
+ # Incoming UART data is buffered behind the scenes, probably by the OS.
29
+
30
+ # ***********************************************************************************
31
+ #
32
+ # Imports
33
+ #
34
+ # ***********************************************************************************
35
+
36
+ import argparse
37
+ import serial
38
+ import serial .tools .list_ports as list_ports
39
+ import sys
40
+ import time
41
+ import math
42
+
43
+ # ***********************************************************************************
44
+ #
45
+ # Commands
46
+ #
47
+ # ***********************************************************************************
48
+ SVL_CMD_VER = 0x01 # version
49
+ SVL_CMD_BL = 0x02 # enter bootload mode
50
+ SVL_CMD_NEXT = 0x03 # request next chunk
51
+ SVL_CMD_FRAME = 0x04 # indicate app data frame
52
+ SVL_CMD_RETRY = 0x05 # request re-send frame
53
+ SVL_CMD_DONE = 0x06 # finished - all data sent
54
+
55
+
56
+ # ***********************************************************************************
57
+ #
58
+ # Compute CRC on a byte array
59
+ #
60
+ # ***********************************************************************************
61
+ def get_crc16 (data ):
62
+ # To perform the division perform the following:
63
+
64
+ # Load the register with zero bits.
65
+ # Augment the message by appending W zero bits to the end of it.
66
+ # While (more message bits)
67
+ # Begin
68
+ # Shift the register left by one bit, reading the next bit of the
69
+ # augmented message into register bit position 0.
70
+ # If (a 1 bit popped out of the register during step 3)
71
+ # Register = Register XOR Poly.
72
+ # End
73
+ # The register now contains the remainder.
74
+ register = 0x0000
75
+ poly = 0x8005
76
+
77
+ data = bytearray (data )
78
+ data .extend (bytearray (2 ))
79
+ bits = 8 * len (data )
80
+
81
+ def get_data_bit (bit ):
82
+ byte = int (bit / 8 )
83
+ if (data [byte ] & (0x80 >> (bit % 8 ))):
84
+ return 1
85
+ return 0
86
+
87
+ for bit in range (bits ):
88
+
89
+ c = 0
90
+ if (register & 0x8000 ):
91
+ c = 1
92
+
93
+ register <<= 1
94
+ register &= 0xFFFF
95
+
96
+ if (get_data_bit (bit )):
97
+ register |= 0x0001
98
+
99
+ if (c ):
100
+ register = (register ^ poly )
101
+
102
+ return register
103
+
104
+
105
+
106
+ # ***********************************************************************************
107
+ #
108
+ # Wait for a packet
109
+ #
110
+ # ***********************************************************************************
111
+ def wait_for_packet (ser ):
112
+
113
+ packet = {'len' :0 , 'cmd' :0 , 'data' :0 , 'crc' :1 , 'timeout' :1 }
114
+
115
+ n = ser .read (2 ) # get the number of bytes
116
+ if (len (n ) < 2 ):
117
+ return packet
118
+
119
+ packet ['len' ] = int .from_bytes (n , byteorder = 'big' , signed = False ) #
120
+ payload = ser .read (packet ['len' ])
121
+
122
+ if (len (payload ) != packet ['len' ]):
123
+ return packet
124
+
125
+ packet ['timeout' ] = 0 # all bytes received, so timeout is not true
126
+ packet ['cmd' ] = payload [0 ] # cmd is the first byte of the payload
127
+ packet ['data' ] = payload [1 :packet ['len' ]- 2 ] # the data is the part of the payload that is not cmd or crc
128
+ packet ['crc' ] = get_crc16 (payload ) # performing the crc on the whole payload should return 0
129
+
130
+ return packet
131
+
132
+ # ***********************************************************************************
133
+ #
134
+ # Send a packet
135
+ #
136
+ # ***********************************************************************************
137
+ def send_packet (ser , cmd , data ):
138
+ data = bytearray (data )
139
+ num_bytes = 3 + len (data )
140
+ payload = bytearray (cmd .to_bytes (1 ,'big' ))
141
+ payload .extend (data )
142
+ crc = get_crc16 (payload )
143
+ payload .extend (bytearray (crc .to_bytes (2 ,'big' )))
144
+
145
+ ser .write (num_bytes .to_bytes (2 ,'big' ))
146
+ ser .write (bytes (payload ))
147
+
148
+
149
+
150
+
151
+
152
+
153
+ # ***********************************************************************************
154
+ #
155
+ # Setup: signal baud rate, get version, and command BL enter
156
+ #
157
+ # ***********************************************************************************
158
+ def phase_setup (ser ):
159
+
160
+ baud_detect_byte = b'U'
161
+
162
+ print ('\n phase:\t setup' )
163
+
164
+ # Handle the serial startup blip
165
+ ser .reset_input_buffer ()
166
+ print ('\t cleared startup blip' )
167
+
168
+ ser .write (baud_detect_byte ) # send the baud detection character
169
+
170
+ packet = wait_for_packet (ser )
171
+ if (packet ['timeout' ] or packet ['crc' ]):
172
+ return 1
173
+
174
+ print ('\t Got SVL Bootloader Version: ' + str (int .from_bytes (packet ['data' ],'big' )))
175
+ print ('\t Sending \' enter bootloader\' command' )
176
+
177
+ send_packet (ser , SVL_CMD_BL , b'' )
178
+
179
+ # Now enter the bootload phase
180
+
181
+
182
+
183
+
184
+
185
+
186
+ # ***********************************************************************************
187
+ #
188
+ # Bootloader phase (Artemis is locked in)
189
+ #
190
+ # ***********************************************************************************
191
+ def phase_bootload (ser ):
192
+
193
+ frame_size = 512 * 4
194
+
195
+ print ('\n phase:\t bootload' )
196
+
197
+ with open (args .binfile , mode = 'rb' ) as binfile :
198
+ application = binfile .read ()
199
+ total_len = len (application )
200
+
201
+ total_frames = math .ceil (total_len / frame_size )
202
+ curr_frame = 0
203
+
204
+ verboseprint ('\t Length to send: ' + str (total_len ) + ' bytes, ' + str (total_frames ) + ' frames' )
205
+
206
+ bl_done = False
207
+ while (not bl_done ):
208
+
209
+ packet = wait_for_packet (ser ) # wait for indication by Artemis
210
+ if (packet ['timeout' ] or packet ['crc' ]):
211
+ print ('\n \t error receiving packet' )
212
+ print (packet )
213
+ print ('\n ' )
214
+ return 1
215
+
216
+ if ( packet ['cmd' ] == SVL_CMD_NEXT ):
217
+ # verboseprint('\tgot frame request')
218
+ curr_frame += 1
219
+ elif ( packet ['cmd' ] == SVL_CMD_RETRY ):
220
+ verboseprint ('\t \t retrying...' )
221
+ else :
222
+ print ('unknown error' )
223
+ return 1
224
+
225
+ if ( curr_frame <= total_frames ):
226
+ frame_data = application [((curr_frame - 1 )* frame_size ):((curr_frame - 1 + 1 )* frame_size )]
227
+ verboseprint ('\t sending frame #' + str (curr_frame )+ ', length: ' + str (len (frame_data )))
228
+
229
+ send_packet (ser , SVL_CMD_FRAME , frame_data )
230
+
231
+ else :
232
+ send_packet (ser , SVL_CMD_DONE , b'' )
233
+ bl_done = True
234
+
235
+ print ('\n \t Upload complete' )
236
+
237
+ exit ()
238
+
239
+
240
+
241
+
242
+
243
+
244
+ # ***********************************************************************************
245
+ #
246
+ # Help if serial port could not be opened
247
+ #
248
+ # ***********************************************************************************
249
+ def phase_serial_port_help ():
250
+ devices = list_ports .comports ()
251
+
252
+ # First check to see if user has the given port open
253
+ for dev in devices :
254
+ if (dev .device .upper () == args .port .upper ()):
255
+ print (dev .device + " is currently open. Please close any other terminal programs that may be using " +
256
+ dev .device + " and try again." )
257
+ exit ()
258
+
259
+ # otherwise, give user a list of possible com ports
260
+ print (args .port .upper () +
261
+ " not found but we detected the following serial ports:" )
262
+ for dev in devices :
263
+ if 'CH340' in dev .description :
264
+ print (
265
+ dev .description + ": Likely an Arduino or derivative. Try " + dev .device + "." )
266
+ elif 'FTDI' in dev .description :
267
+ print (
268
+ dev .description + ": Likely an Arduino or derivative. Try " + dev .device + "." )
269
+ elif 'USB Serial Device' in dev .description :
270
+ print (
271
+ dev .description + ": Possibly an Arduino or derivative." )
272
+ else :
273
+ print (dev .description )
274
+
275
+
276
+ # ***********************************************************************************
277
+ #
278
+ # Main function
279
+ #
280
+ # ***********************************************************************************
281
+ def main ():
282
+ try :
283
+ with serial .Serial (args .port , args .baud , timeout = args .timeout ) as ser :
284
+
285
+ t_su = 0.15 # startup time for Artemis bootloader (experimentally determined - 0.095 sec min delay)
286
+
287
+ print ('\n \n Artemis SVL Bootloader' )
288
+
289
+ time .sleep (t_su ) # Allow Artemis to come out of reset
290
+ phase_setup (ser ) # Perform baud rate negotiation
291
+
292
+ phase_bootload (ser ) # Bootload
293
+
294
+ except :
295
+ phase_serial_port_help ()
296
+
297
+ exit ()
298
+
299
+
300
+ # ******************************************************************************
301
+ #
302
+ # Main program flow
303
+ #
304
+ # ******************************************************************************
305
+ if __name__ == '__main__' :
306
+
307
+ parser = argparse .ArgumentParser (
308
+ description = 'SparkFun Serial Bootloader for Artemis' )
309
+
310
+ parser .add_argument ('port' , help = 'Serial COMx Port' )
311
+
312
+ parser .add_argument ('-b' , dest = 'baud' , default = 115200 , type = int ,
313
+ help = 'Baud Rate (default is 115200)' )
314
+
315
+ parser .add_argument ('-f' , dest = 'binfile' , default = '' ,
316
+ help = 'Binary file to program into the target device' )
317
+
318
+ parser .add_argument ("-v" , "--verbose" , default = 0 , help = "Enable verbose output" ,
319
+ action = "store_true" )
320
+
321
+ parser .add_argument ("-t" , "--timeout" , default = 0.50 , help = "Communication timeout in seconds (default 0.5)" ,
322
+ type = float )
323
+
324
+ if len (sys .argv ) < 2 :
325
+ print ("No port selected. Detected Serial Ports:" )
326
+ devices = list_ports .comports ()
327
+ for dev in devices :
328
+ print (dev .description )
329
+
330
+ args = parser .parse_args ()
331
+
332
+ # Create print function for verbose output if caller deems it: https://stackoverflow.com/questions/5980042/how-to-implement-the-verbose-or-v-option-into-a-script
333
+ if args .verbose :
334
+ def verboseprint (* args ):
335
+ # Print each argument separately so caller doesn't need to
336
+ # stuff everything to be printed into a single string
337
+ for arg in args :
338
+ print (arg , end = '' ),
339
+ print ()
340
+ else :
341
+ verboseprint = lambda * a : None # do-nothing function
342
+
343
+ main ()
0 commit comments