Skip to content
This repository was archived by the owner on Jan 31, 2019. It is now read-only.

Commit 2a991c5

Browse files
committed
Merge branch 'flasher-tinkering' and release 2.5.0
Fixes #28 Fixes #25 Fixes #23 Fixes purduesigbots/pros-atom#28 Fixes purduesigbots/pros-atom#39
2 parents 527040e + 5478afc commit 2a991c5

File tree

8 files changed

+163
-131
lines changed

8 files changed

+163
-131
lines changed

Jenkinsfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ stage('Build') {
99
}
1010
stage('Dependencies') {
1111
tool 'python3'
12-
sh 'sudo apt-get install -y python3-pip'
12+
if(isUnix()) {
13+
sh 'sudo apt-get install -y python3-pip'
14+
}
1315
venv.create_virtualenv()
1416
venv.run 'pip3 install wheel twine'
1517
}

proscli/flasher.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import prosflasher.upload
88
import prosconfig
99
from proscli.utils import default_cfg, AliasGroup
10+
from proscli.utils import get_version
1011

1112

1213
@click.group(cls=AliasGroup)
@@ -24,13 +25,16 @@ def flasher_cli():
2425
help='Specifies a binary file, project directory, or project config file.')
2526
@click.option('-p', '--port', default='auto', metavar='PORT', help='Specifies the serial port.')
2627
@click.option('--no-poll', is_flag=True, default=False)
28+
@click.option('-r', '--retry', default=2,
29+
help='Specify the number of times the flasher should retry the flash when it detects a failure'
30+
' (default two times).')
2731
@default_cfg
2832
# @click.option('-m', '--strategy', default='cortex', metavar='STRATEGY',
2933
# help='Specify the microcontroller upload strategy. Not currently used.')
30-
def flash(ctx, save_file_system, y, port, binary, no_poll):
34+
def flash(ctx, save_file_system, y, port, binary, no_poll, retry):
3135
"""Upload binaries to the microcontroller. A serial port and binary file need to be specified.
3236
33-
By default, the port is automatically selected (if you want to be pendantic, 'auto').
37+
By default, the port is automatically selected (if you want to be pedantic, 'auto').
3438
Otherwise, a system COM port descriptor needs to be used. In Windows/NT, this takes the form of COM1.
3539
In *nx systems, this takes the form of /dev/tty1 or /dev/acm1 or similar.
3640
\b
@@ -39,6 +43,7 @@ def flash(ctx, save_file_system, y, port, binary, no_poll):
3943
By default, the CLI will look around for a proper binary to upload to the microcontroller. If one was not found, or
4044
if you want to change the default binary, you can specify it.
4145
"""
46+
click.echo(' ====:: PROS Flasher v{} ::===='.format(get_version()))
4247
if port == 'auto':
4348
ports = prosflasher.ports.list_com_ports()
4449
if len(ports) == 0:
@@ -87,7 +92,13 @@ def flash(ctx, save_file_system, y, port, binary, no_poll):
8792

8893
click.echo('Flashing ' + binary + ' to ' + ', '.join(port))
8994
for p in port:
90-
prosflasher.upload.upload(p, binary, no_poll, ctx)
95+
tries = 1
96+
while tries <= retry:
97+
code = prosflasher.upload.upload(p, y, binary, no_poll, ctx)
98+
if code or code == -1000:
99+
break
100+
click.echo('Retrying...')
101+
tries += 1
91102

92103

93104
def find_binary(path):

proscli/main.py

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
import click
22
#from pkg_resources import get_distribution
33
import proscli
4-
from proscli.utils import default_options
5-
import os.path
6-
import sys
7-
4+
from proscli.utils import default_options, get_version
85

96
def main():
107
# the program name should always be pros. don't care if it's not...
@@ -13,22 +10,6 @@ def main():
1310
except KeyboardInterrupt:
1411
click.echo('Aborted!')
1512

16-
17-
def get_version():
18-
try:
19-
if os.path.isfile(os.path.join(__file__, '../../version')):
20-
return open(os.path.join(__file__, '../../version')).read().strip()
21-
except Exception:
22-
pass
23-
try:
24-
if getattr(sys, 'frozen', False):
25-
import BUILD_CONSTANTS
26-
return BUILD_CONSTANTS.CLI_VERSION
27-
except Exception:
28-
pass
29-
return None # Let click figure it out
30-
31-
3213
@click.command('pros',
3314
cls=click.CommandCollection,
3415
context_settings=dict(help_option_names=['-h', '--help']),

proscli/terminal.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import click
22
import proscli.serial_terminal
33
import prosflasher.ports
4+
import serial
45
import signal
56
import sys
67
import time
@@ -28,7 +29,7 @@ def terminal(port):
2829
click.echo('No ports were found.')
2930
click.get_current_context().abort()
3031
sys.exit()
31-
ser = prosflasher.ports.create_serial(port)
32+
ser = prosflasher.ports.create_serial(port, serial.PARITY_NONE)
3233
term = proscli.serial_terminal.Terminal(ser)
3334
signal.signal(signal.SIGINT, term.stop)
3435
term.start()

proscli/utils.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,25 @@
1+
import os
2+
13
import click
4+
import sys
5+
26
from proscli.state import State
37

48
pass_state = click.make_pass_decorator(State)
59

10+
def get_version():
11+
try:
12+
if os.path.isfile(os.path.join(__file__, '../../version')):
13+
return open(os.path.join(__file__, '../../version')).read().strip()
14+
except Exception:
15+
pass
16+
try:
17+
if getattr(sys, 'frozen', False):
18+
import BUILD_CONSTANTS
19+
return BUILD_CONSTANTS.CLI_VERSION
20+
except Exception:
21+
pass
22+
return None # Let click figure it out
623

724
def verbosity_option(f):
825
"""

prosflasher/bootloader.py

Lines changed: 78 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,8 @@ def debug_response(command, response, fmt='STM BL RESPONSE TO 0x{}: {}'):
1919

2020
def send_bootloader_command(port, command, response_size=1):
2121
port.write([command, 0xff - command])
22-
port.flush()
22+
time.sleep(0.01)
2323
response = port.read(response_size)
24-
debug_response(command, response)
2524
return response
2625

2726

@@ -44,22 +43,32 @@ def compute_address_commandable(address):
4443

4544

4645
def prepare_bootloader(port):
47-
click.echo('Preparing bootloader...', nl=False)
48-
prosflasher.upload.configure_port(port, serial.PARITY_EVEN)
49-
port.flush()
50-
port.write([0x7f])
51-
response = port.read(1)
52-
debug_response(0x07f, response)
53-
if response is None or len(response) != 1 or response[0] != ACK:
54-
click.echo('failed')
55-
return False
56-
click.echo('complete')
57-
if click.get_current_context().obj.verbosity > 1:
58-
click.echo('Extra commands:')
59-
send_bootloader_command(port, 0x00, 15)
60-
send_bootloader_command(port, 0x01, 5)
61-
send_bootloader_command(port, 0x02, 5)
62-
return True
46+
click.echo('Preparing bootloader... ', nl=False)
47+
# for _ in range(0, 3):
48+
# response = send_bootloader_command(port, 0x00, 15)
49+
# if response is not None and len(response) == 15 and response[0] == ACK and response[-1] == ACK:
50+
# click.echo('complete')
51+
# return True
52+
time.sleep(0.01)
53+
port.rts = 0
54+
time.sleep(0.01)
55+
for _ in range(0,3):
56+
port.write([0x7f])
57+
response = port.read(1)
58+
debug_response(0x7f, response)
59+
if not (response is None or len(response) != 1 or response[0] != ACK):
60+
time.sleep(0.01)
61+
response = send_bootloader_command(port, 0x00, 15)
62+
debug_response(0x00, response)
63+
if response is None or len(response) != 15 or response[0] != ACK or response[-1] != ACK:
64+
click.echo('failed (couldn\'t verify commands)')
65+
return False
66+
# send_bootloader_command(port, 0x01, 5)
67+
# send_bootloader_command(port, 0x02, 5)
68+
click.echo('complete')
69+
return True
70+
click.echo('failed')
71+
return False
6372

6473

6574
def read_memory(port, start_address, size=0x100):
@@ -95,59 +104,73 @@ def read_memory(port, start_address, size=0x100):
95104

96105

97106
def erase_flash(port):
98-
click.echo('Erasing user flash...', nl=False)
99-
prosflasher.upload.configure_port(port, serial.PARITY_EVEN)
107+
click.echo('Erasing user flash... ', nl=False)
108+
port.flush()
100109
response = send_bootloader_command(port, 0x43, 1)
101-
if response is None or response[0] != 0x79:
110+
if response is None or len(response) < 1 or response[0] != 0x79:
102111
click.echo('failed')
103112
return False
113+
time.sleep(0.01)
104114
response = send_bootloader_command(port, 0xff, 1)
105-
if response is None or response[0] != 0x79:
106-
click.echo('failed')
115+
debug_response(0xff, response)
116+
if response is None or len(response) < 1 or response[0] != 0x79:
117+
click.echo('failed (address unacceptable)')
107118
return False
108119
click.echo('complete')
109120
return True
110121

111122

112-
def write_flash(port, start_address, data):
123+
def write_flash(port, start_address, data, retry=2):
113124
data = bytearray(data)
114125
if len(data) > 256:
115126
click.echo('Tried writing too much data at once! ({} bytes)'.format(len(data)))
116127
return False
117128
port.read_all()
118-
start_address = compute_address_commandable(start_address)
119-
debug('Writing {} bytes to {}'.format(len(data), adr_to_str(start_address)))
129+
c_addr = compute_address_commandable(start_address)
130+
debug('Writing {} bytes to {}'.format(len(data), adr_to_str(c_addr)))
120131
response = send_bootloader_command(port, 0x31)
121-
if response is None or response[0] != ACK:
132+
if response is None or len(response) < 1 or response[0] != ACK:
122133
click.echo('failed (write command not accepted)')
123134
return False
124-
port.write(start_address)
135+
port.write(c_addr)
125136
port.flush()
126137
response = port.read(1)
127-
debug_response(adr_to_str(start_address), response)
128-
if response is None or response[0] != ACK:
129-
click.echo('failed (address not accepted)')
130-
return False
138+
debug_response(adr_to_str(c_addr), response)
139+
if response is None or len(response) < 1 or response[0] != ACK:
140+
if retry > 0:
141+
debug('RETRYING PACKET')
142+
write_flash(port, start_address, data, retry=retry - 1)
143+
else:
144+
click.echo('failed (address not accepted)')
145+
return False
131146
checksum = len(data) - 1
132147
for x in data:
133148
checksum ^= x
134-
data.insert(0, len(data) - 1)
135-
data.append(checksum)
136-
port.write(data)
149+
send_data = data
150+
send_data.insert(0, len(send_data) - 1)
151+
send_data.append(checksum)
152+
port.write(send_data)
137153
time.sleep(0.005)
138154
response = port.read(1)
139-
if response is None or response[0] != ACK:
140-
port.write(data)
141-
time.sleep(20)
142-
response = port.read(1)
143-
if response is None or response[0] != ACK:
155+
debug('STM BL RESPONSE TO WRITE: {}'.format(response))
156+
if response is None or len(response) < 1 or response[0] != ACK:
157+
if retry > 0:
158+
debug('RETRYING PACKET')
159+
write_flash(port, start_address, data, retry=retry - 1)
160+
else:
144161
click.echo('failed (could not complete upload)')
145162
return False
146163
port.flush()
147164
port.reset_input_buffer()
148165
return True
149166

150167

168+
def chunks(l, n):
169+
"""Yield successive n-sized chunks from l."""
170+
for i in range(0, len(l), n):
171+
yield l[i:i + n]
172+
173+
151174
def upload_binary(port, file):
152175
address = 0x08000000
153176
with open(file, 'rb') as f:
@@ -161,18 +184,23 @@ def upload_binary(port, file):
161184
return True
162185

163186

164-
def send_go_command(port, address):
165-
click.echo('Executing binary...', nl=False)
166-
address = compute_address_commandable(address)
167-
debug('Executing binary at {}'.format(adr_to_str(address)))
187+
def send_go_command(port, address, retry=3):
188+
click.echo('Executing user code... ', nl=False)
189+
c_addr = compute_address_commandable(address)
190+
debug('Executing binary at {}'.format(adr_to_str(c_addr)))
168191

169192
response = send_bootloader_command(port, 0x21, 1)
170-
if response is None or response[0] != ACK:
193+
debug_response(0x21, response)
194+
if response is None or len(response) < 1 or response[0] != ACK:
171195
click.echo('failed (execute command not accepted)')
172196
return False
173-
port.write(address)
174-
port.flush()
175-
click.echo('complete')
197+
time.sleep(0.01)
198+
port.write(c_addr)
199+
time.sleep(0.01)
200+
response = port.read(1)
201+
debug_response(adr_to_str(c_addr), response)
202+
if response is None or len(response) < 1 or response[0] != ACK:
203+
click.echo('user code might not have started properly. May need to restart Cortex')
204+
else:
205+
click.echo('complete')
176206
return True
177-
178-

prosflasher/ports.py

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,42 +16,38 @@ def list_com_ports():
1616
return [x for x in serial.tools.list_ports.comports() if x.vid is not None and (x.vid in USB_VID or 'vex' in x.product.lower())]
1717

1818

19-
def create_serial(port):
19+
def create_serial(port, parity):
2020
"""
2121
Creates and/or configures a serial port to communicate with the Cortex Microcontroller
2222
:param port: A serial.Serial object, a device string identifier will create a corresponding serial port.
23-
Anything elsse will create a default serial port with no device assigned.
23+
Anything else will create a default serial port with no device assigned.
2424
:return: Returns a correctly configured instance of a serial.Serial object, potentially with a correctly configured
2525
device iff a correct port value was passed in
2626
"""
27+
# port_str = ''
2728
if isinstance(port, str):
2829
try:
29-
port = serial.Serial(port)
30+
# port_str = port
31+
port = serial.Serial(port, baudrate=BAUD_RATE, bytesize=serial.EIGHTBITS, parity=parity, stopbits=serial.STOPBITS_ONE)
3032
except serial.SerialException as e:
3133
click.echo('WARNING: {}'.format(e))
32-
port = serial.Serial()
34+
port = serial.Serial(baudrate=BAUD_RATE, bytesize=serial.EIGHTBITS, parity=parity, stopbits=serial.STOPBITS_ONE)
3335
elif not isinstance(port, serial.Serial):
34-
port = serial.Serial()
36+
click.echo('port was not string, send help')
37+
port = serial.Serial(baudrate=BAUD_RATE, bytesize=serial.EIGHTBITS, parity=parity, stopbits=serial.STOPBITS_ONE)
3538

3639
assert isinstance(port, serial.Serial)
3740

38-
port.baudrate = BAUD_RATE
39-
port.bytesize = serial.EIGHTBITS
40-
port.parity = serial.PARITY_EVEN
41-
port.stopbits = serial.STOPBITS_ONE
42-
port.timeout = 5.0
43-
port.xonxoff = False
44-
port.rtscts = False
45-
port.dsrdtr = False
41+
# port.port = port_str if port_str != '' else None
42+
port.timeout = 0.5
4643
# port.write_timeout = 5.0
47-
# port.inter_byte_timeout = 0.005 # todo make sure this is seconds
48-
44+
port.inter_byte_timeout = 0.1 # todo make sure this is seconds
4945
return port
5046

5147

5248
def create_port_list(verbose=False):
5349
"""
54-
Returns a formatted string of all COM ports we believe are valid Cortex ports, delimted by \n
50+
Returns a formatted string of all COM ports we believe are valid Cortex ports, delimited by \n
5551
:param verbose: If True, then the hwid will be added to the end of each device
5652
:return: A formatted string for printing describing the COM ports
5753
"""

0 commit comments

Comments
 (0)