├── .gitignore
├── .project
├── .pydevproject
├── AUTHORS
├── DESCRIPTION
├── LICENSE
├── MANIFEST.in
├── README.md
├── canprog
├── __init__.py
├── file.py
├── logger.py
├── main.py
├── protocols
│ ├── __init__.py
│ ├── abstract.py
│ └── stm32.py
└── tests
│ └── __init__.py
├── setup.cfg
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 |
49 | # Translations
50 | *.mo
51 | *.pot
52 |
53 | # Django stuff:
54 | *.log
55 | local_settings.py
56 |
57 | # Flask stuff:
58 | instance/
59 | .webassets-cache
60 |
61 | # Scrapy stuff:
62 | .scrapy
63 |
64 | # Sphinx documentation
65 | docs/_build/
66 |
67 | # PyBuilder
68 | target/
69 |
70 | # Jupyter Notebook
71 | .ipynb_checkpoints
72 |
73 | # pyenv
74 | .python-version
75 |
76 | # celery beat schedule file
77 | celerybeat-schedule
78 |
79 | # SageMath parsed files
80 | *.sage.py
81 |
82 | # dotenv
83 | .env
84 |
85 | # virtualenv
86 | .venv
87 | venv/
88 | ENV/
89 |
90 | # Spyder project settings
91 | .spyderproject
92 | .spyproject
93 |
94 | # Rope project settings
95 | .ropeproject
96 |
97 | # mkdocs documentation
98 | /site
99 |
100 | # mypy
101 | .mypy_cache/
102 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | CAN-Prog
4 |
5 |
6 |
7 |
8 |
9 | org.python.pydev.PyDevBuilder
10 |
11 |
12 |
13 |
14 |
15 | org.python.pydev.pythonNature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.pydevproject:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | python 3.6
5 | python
6 |
7 | /${PROJECT_DIR_NAME}
8 |
9 |
10 |
--------------------------------------------------------------------------------
/AUTHORS:
--------------------------------------------------------------------------------
1 | Marcin Borowicz
2 | https://github.com/jakob-tsd
--------------------------------------------------------------------------------
/DESCRIPTION:
--------------------------------------------------------------------------------
1 | CAN-Prog
2 | ========
3 |
4 | Command-line tool to flashing devices by CAN-BUS.
5 |
6 | | Author: Marcin Borowicz
7 | | Contact: marcinbor85 at gmail com
8 | | Date: 2019/11/01
9 | | Version: 0.2.1
10 |
11 | License
12 | -------
13 |
14 | | The MIT License (MIT)
15 | |
16 | | Copyright (c) 2017 Marcin Borowicz
17 | |
18 | | Permission is hereby granted, free of charge, to any person obtaining a copy
19 | | of this software and associated documentation files (the "Software"), to deal
20 | | in the Software without restriction, including without limitation the rights
21 | | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
22 | | copies of the Software, and to permit persons to whom the Software is
23 | | furnished to do so, subject to the following conditions:
24 | |
25 | | The above copyright notice and this permission notice shall be included in
26 | | all copies or substantial portions of the Software.
27 | |
28 | | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
29 | | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
30 | | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
31 | | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
32 | | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
33 | | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
34 | | THE SOFTWARE.
35 |
36 | Main features
37 | -------------
38 |
39 | - Support STM32 CAN-BUS ROM bootloader
40 | - Easily expand with other CAN-BUS protocols
41 | - Support iHEX and binary format files
42 | - Object oriented architecture
43 | - Command-line interface
44 | - Socket-CAN driver for CAN-BUS low level operations
45 |
46 | Download
47 | --------
48 |
49 | * https://pypi.python.org/pypi/canprog
50 | * https://github.com/marcinbor85/can-Prog
51 |
52 | Install
53 | -------
54 |
55 | Install using pip (no separate download required):
56 |
57 | ``pip install canprog``
58 |
59 | Install from sources:
60 |
61 | ``python setup.py install``
62 |
63 | Source code
64 | -----------
65 |
66 | Source code on GitHub:
67 |
68 | https://github.com/marcinbor85/can-prog
69 |
70 | Get code with git:
71 |
72 | ``git clone https://github.com/marcinbor85/can-prog.git``
73 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 Marcin Borowicz
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include DESCRIPTION
2 | incluse LICENSE
3 | include AUTHORS
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # canprog
2 |
3 | Command-line tool to flashing devices by CAN-BUS.
4 |
5 | ## Main features
6 | - Support STM32 CAN-BUS ROM bootloader
7 | - Easily expand with other CAN-BUS protocols
8 | - Support iHEX and binary format files
9 | - Object oriented architecture
10 | - Command-line interface
11 | - Socket-CAN driver for CAN-BUS low level operations
12 |
13 | ## Todo
14 | - [ ] Other microcontroller protocols
15 | - [ ] Other CAN-BUS interfaces
16 | - [ ] Autocalculating sectors for erasing (for STM32)
17 | - [ ] Memory write protect/unprotect (form STM32)
18 | - [ ] TDD tests
19 |
20 | ## Requirements
21 | - Compatible PC CAN-BUS adapter
22 | - Linux + Python3
23 | - Enabled SocketCAN driver
24 | - Board with STM32 with CAN interface
25 |
26 | ### Driver instalation
27 | ```
28 | modprobe can
29 | modprobe can-raw
30 | modprobe slcan
31 | slcand -o -c -f -s4 /dev/ttyUSB0 slcan0
32 | ip link set up slcan0
33 | ```
34 |
35 | ### App instalation
36 | ```
37 | sudo pip install canprog
38 | ```
39 |
40 | ## Usage:
41 | ### General usage + configuration
42 | ```
43 | usage: canprog [-h] [--verbose] [--version] [-n NAME] [-i {socketcan}]
44 | [-f {hex,bin}]
45 | {stm32} ...
46 |
47 | Command-line tool to flashing devices by CAN-BUS.
48 |
49 | others:
50 | -h, --help show this help message and exit
51 | --verbose enable verbose output
52 | --version show program's version number and exit
53 |
54 | configuration:
55 | -n NAME interface name (default: slcan0)
56 | -i {socketcan} interface type (default: socketcan)
57 | -f {hex,bin} file format (default: hex)
58 |
59 | protocols:
60 | {stm32}
61 | stm32 STM32 ROM bootloader
62 | ```
63 | ### STM32 bootloader options
64 | ```
65 | usage: canprog stm32 [-h] {write,read,erase,go,lock,unlock} ...
66 |
67 | others:
68 | -h, --help show this help message and exit
69 |
70 | commands:
71 | {write,read,erase,go,lock,unlock}
72 | write write file to target memory
73 | read read target memory to file
74 | erase erase target memory
75 | go start program application
76 | lock enable readout protection
77 | unlock disable readout protection
78 | speed change the can baud rate the boot rom uses
79 | ```
80 | ### Usage examples:
81 | ```
82 | canprog stm32 write image.hex
83 | canprog -f bin stm32 write image.bin -a 0x08000000
84 | canprog stm32 read dump.hex -s 0x200
85 | canprog stm32 lock
86 | canprog stm32 erase -P 0 1 2 3
87 | ```
88 | ### Example output:
89 | ```
90 | [13:41:25.931] main INFO: Connecting target
91 | [13:41:25.935] stm32 INFO: Bootloader initialized
92 | [13:41:25.944] stm32 INFO: Bootloader version: 2.0
93 | [13:41:25.947] stm32 INFO: Read protection: 0x0000
94 | [13:41:25.950] stm32 INFO: Chip ID: 0x0413 - STM32F40xxx/41xxx
95 | [13:41:25.950] main INFO: Connected
96 | [13:41:25.958] main INFO: Writing memory at 0x08000000:6548
97 | [13:41:25.958] stm32 INFO: Progress: 0%
98 | [13:41:26.201] stm32 INFO: Progress: 11%
99 | [13:41:26.429] stm32 INFO: Progress: 23%
100 | [13:41:26.657] stm32 INFO: Progress: 35%
101 | [13:41:26.895] stm32 INFO: Progress: 46%
102 | [13:41:27.136] stm32 INFO: Progress: 58%
103 | [13:41:27.371] stm32 INFO: Progress: 70%
104 | [13:41:27.617] stm32 INFO: Progress: 82%
105 | [13:41:27.908] stm32 INFO: Progress: 93%
106 | [13:41:28.065] stm32 INFO: Progress: 100%
107 | [13:41:28.065] main INFO: Successful
108 | [13:41:28.065] main INFO: Writing memory at 0x08004000:16
109 | [13:41:28.065] stm32 INFO: Progress: 0%
110 | [13:41:28.074] stm32 INFO: Progress: 100%
111 | [13:41:28.074] main INFO: Successful
112 | [13:41:28.074] main INFO: Disconnecting target
113 | [13:41:28.074] main INFO: Disconnected
114 | ```
115 |
--------------------------------------------------------------------------------
/canprog/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # The MIT License (MIT)
3 | #
4 | # Copyright (c) 2017 Marcin Borowicz
5 | #
6 | # Permission is hereby granted, free of charge, to any person obtaining a copy
7 | # of this software and associated documentation files (the "Software"), to deal
8 | # in the Software without restriction, including without limitation the rights
9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | # copies of the Software, and to permit persons to whom the Software is
11 | # furnished to do so, subject to the following conditions:
12 | #
13 | # The above copyright notice and this permission notice shall be included in
14 | # all copies or substantial portions of the Software.
15 | #
16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | # THE SOFTWARE.
23 | #
24 |
25 | __version__ = '0.2.1'
26 | __appname__ = 'canprog'
27 | __description__ = 'Command-line tool to flashing devices by CAN-BUS.'
28 |
29 |
--------------------------------------------------------------------------------
/canprog/file.py:
--------------------------------------------------------------------------------
1 | #
2 | # The MIT License (MIT)
3 | #
4 | # Copyright (c) 2017 Marcin Borowicz
5 | #
6 | # Permission is hereby granted, free of charge, to any person obtaining a copy
7 | # of this software and associated documentation files (the "Software"), to deal
8 | # in the Software without restriction, including without limitation the rights
9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | # copies of the Software, and to permit persons to whom the Software is
11 | # furnished to do so, subject to the following conditions:
12 | #
13 | # The above copyright notice and this permission notice shall be included in
14 | # all copies or substantial portions of the Software.
15 | #
16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | # THE SOFTWARE.
23 | #
24 |
25 | import intelhex
26 |
27 | class FileManager(object):
28 | def __init__(self):
29 | self._ih = intelhex.IntelHex()
30 |
31 | def load(self, filename, fmt, address=0):
32 | if fmt == 'bin':
33 | self._ih.loadbin(filename, offset=address)
34 | elif fmt == 'hex':
35 | self._ih.loadhex(filename)
36 | else:
37 | raise ValueError('Unsupported file format type')
38 |
39 | def get_segments(self):
40 | for s in self._ih.segments():
41 | address = s[0]
42 | size = s[1]-address
43 | data = self._ih.tobinarray(start=address, size=size)
44 | yield (address, data,)
45 |
46 | def set_segment(self, address, data):
47 | for b in data:
48 | data = self._ih[address] = b
49 | address += 1
50 |
51 | def save(self, filename, fmt):
52 | if fmt == 'bin':
53 | self._ih.tobinfile(filename)
54 | elif fmt == 'hex':
55 | self._ih.write_hex_file(filename, write_start_addr=False)
56 | else:
57 | raise ValueError('Unsupported file format type')
58 |
--------------------------------------------------------------------------------
/canprog/logger.py:
--------------------------------------------------------------------------------
1 | #
2 | # The MIT License (MIT)
3 | #
4 | # Copyright (c) 2017 Marcin Borowicz
5 | #
6 | # Permission is hereby granted, free of charge, to any person obtaining a copy
7 | # of this software and associated documentation files (the "Software"), to deal
8 | # in the Software without restriction, including without limitation the rights
9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | # copies of the Software, and to permit persons to whom the Software is
11 | # furnished to do so, subject to the following conditions:
12 | #
13 | # The above copyright notice and this permission notice shall be included in
14 | # all copies or substantial portions of the Software.
15 | #
16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | # THE SOFTWARE.
23 | #
24 |
25 | import logging
26 |
27 | from canprog import __appname__
28 |
29 | def get_log(name):
30 | log = logging.getLogger(name)
31 | h = logging.StreamHandler()
32 | formatter = logging.Formatter('[%(asctime)s.%(msecs)03d] %(module)s %(levelname)s: %(message)s',datefmt='%H:%M:%S')
33 | h.setFormatter(formatter)
34 | log.addHandler(h)
35 | return log
36 |
37 | log = get_log(__appname__)
38 |
39 | def set_level(level):
40 | log.setLevel(level)
41 | for h in log.handlers:
42 | h.setLevel(level)
43 |
44 | set_level(logging.INFO)
45 |
46 |
--------------------------------------------------------------------------------
/canprog/main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | #
4 | # The MIT License (MIT)
5 | #
6 | # Copyright (c) 2017 Marcin Borowicz
7 | #
8 | # Permission is hereby granted, free of charge, to any person obtaining a copy
9 | # of this software and associated documentation files (the "Software"), to deal
10 | # in the Software without restriction, including without limitation the rights
11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | # copies of the Software, and to permit persons to whom the Software is
13 | # furnished to do so, subject to the following conditions:
14 | #
15 | # The above copyright notice and this permission notice shall be included in
16 | # all copies or substantial portions of the Software.
17 | #
18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | # THE SOFTWARE.
25 | #
26 |
27 | import argparse
28 | import can
29 | import sys
30 |
31 | from canprog import __version__, __appname__, __description__
32 | from canprog import protocols
33 | from canprog import file
34 | from canprog.logger import log
35 |
36 | import canprog.logger
37 |
38 | def config_parser():
39 |
40 | parser = argparse.ArgumentParser(prog=__appname__, description=__description__)
41 | parser._optionals.title = 'others'
42 |
43 | parser.add_argument('--verbose', action='store_true', default=False, help='enable verbose output')
44 | parser.add_argument('--version', action='version', version='%(prog)s {version}'.format(version=__version__))
45 |
46 | group = parser.add_argument_group('configuration')
47 | group.add_argument('-n', dest='name', action='store', default='slcan0', help='interface name (default: slcan0)')
48 | group.add_argument('-i', dest='interface', action='store', choices=('socketcan',), default='socketcan', help='interface type (default: socketcan)')
49 | group.add_argument('-f', dest='format', action='store', choices=('hex','bin'), default='hex', help='file format (default: hex)')
50 |
51 | subparsers = parser.add_subparsers(title='protocols', dest='protocol')
52 |
53 | subparsers.required = True
54 |
55 | parser_stm32 = subparsers.add_parser('stm32', help='STM32 ROM bootloader')
56 | parser_stm32._optionals.title = 'others'
57 |
58 | subparsers_stm32 = parser_stm32.add_subparsers(title='commands', dest='command')
59 | subparsers_stm32.required = True
60 |
61 | parser_command = subparsers_stm32.add_parser('write', help='write file to target memory')
62 | parser_command._optionals.title = 'others'
63 | group = parser_command.add_argument_group('options')
64 | group.add_argument('-e', dest='erase', action='store_true', default=False, help='erase memory before write')
65 | group.add_argument('-P', dest='pages', action='store', type=lambda x: int(x,0), default=[], nargs='+', help='list of pages to erase (default: all)')
66 | group.add_argument('-v', dest='verify', action='store_true', default=False, help='verify memory after write')
67 | group.add_argument('-g', dest='go', action='store_true', default=False, help='start application after write')
68 | group.add_argument('-a', dest='address', action='store', type=lambda x: int(x,0), default=0x08000000, help='start memory address (default: 0x08000000)')
69 | group = parser_command.add_argument_group('arguments')
70 | group.add_argument('input', action='store', help='input filename')
71 |
72 | parser_command = subparsers_stm32.add_parser('read', help='read target memory to file')
73 | parser_command._optionals.title = 'others'
74 | group = parser_command.add_argument_group('options')
75 | group.add_argument('-a', dest='address', action='store', type=lambda x: int(x,0), default=0x08000000, help='start memory address (default: 0x08000000)')
76 | group.add_argument('-s', dest='size', action='store', type=lambda x: int(x,0), default=0x8000, help='data size to read (default: 0x8000)')
77 | group = parser_command.add_argument_group('arguments')
78 | group.add_argument('output', action='store', help='output filename')
79 |
80 | parser_command = subparsers_stm32.add_parser('erase', help='erase target memory')
81 | parser_command._optionals.title = 'others'
82 | group = parser_command.add_argument_group('options')
83 | group.add_argument('-P', dest='pages', action='store', type=lambda x: int(x,0), default=[], nargs='+', help='list of pages to erase (default: all)')
84 |
85 | parser_command = subparsers_stm32.add_parser('go', help='start program application')
86 | parser_command._optionals.title = 'others'
87 | group = parser_command.add_argument_group('options')
88 | group.add_argument('-a', dest='address', action='store', type=lambda x: int(x,0), default=0x08000000, help='start memory address (default: 0x08000000)')
89 |
90 | parser_command = subparsers_stm32.add_parser('lock', help='enable readout protection')
91 | parser_command = subparsers_stm32.add_parser('unlock', help='disable readout protection')
92 |
93 | parser_command = subparsers_stm32.add_parser('speed', help='change the can baud rate the boot rom uses')
94 | group = parser_command.add_argument_group('arguments')
95 | group.add_argument('bps', action='store', type=lambda x: int(x), help='new baud rate')
96 |
97 | """
98 | parser_simple = subparsers.add_parser('simple', help='Simple bootloader')
99 | parser_simple._optionals.title = 'others'
100 |
101 | subparsers_simple = parser_simple.add_subparsers(title='commands', dest='command')
102 |
103 | subparsers_simple.required = True
104 |
105 | parser_command = subparsers_simple.add_parser('write', help='write file to target')
106 | group = parser_command.add_argument_group('read options')
107 | group.add_argument('input', action='store', help='input filename')
108 | """
109 |
110 | return parser
111 |
112 | def connect(protocol):
113 | try:
114 | log.info('Connecting target')
115 | protocol.connect()
116 | log.info('Connected')
117 | except TimeoutError as e:
118 | raise ConnectionError('Connecting error: '+str(e))
119 |
120 | def disconnect(protocol):
121 | try:
122 | log.info('Disconnecting target')
123 | protocol.disconnect()
124 | log.info('Disconnected')
125 | except TimeoutError as e:
126 | raise ConnectionError('Disconnecting error: '+str(e))
127 |
128 | def go(protocol, address):
129 | try:
130 | log.info('Starting application')
131 | protocol.go(address)
132 | log.info('Successful')
133 | except TimeoutError as e:
134 | raise ConnectionError('Starting error: '+str(e))
135 |
136 | def erase(protocol, pages):
137 | try:
138 | log.info('Erasing memory. Please wait...')
139 | protocol.erase(pages)
140 | log.info('Successful')
141 | except TimeoutError as e:
142 | raise ConnectionError('Erasing error: '+str(e))
143 |
144 | def read(protocol, address, size):
145 | try:
146 | log.info('Reading memory at 0x{address:08X}:{size}'.format(address=address, size=size))
147 | d = protocol.read(address, size)
148 | log.info('Successful')
149 | return d
150 | except TimeoutError as e:
151 | raise ConnectionError('Reading error: '+str(e))
152 |
153 | def write(protocol, address, data):
154 | try:
155 | log.info('Writing memory at 0x{address:08X}:{size}'.format(address=address, size=len(data)))
156 | protocol.write(address, data)
157 | log.info('Successful')
158 | except TimeoutError as e:
159 | raise ConnectionError('Writing error: '+str(e))
160 |
161 | def verify(protocol, address, data):
162 | try:
163 | log.info('Verifying memory at 0x{address:08X}:{size}'.format(address=address, size=len(data)))
164 | data_readed = protocol.read(address, len(data))
165 | if len(data_readed) != len(data):
166 | raise ValueError('Size mismatch {} != {}'.format(len(data_readed),len(data)))
167 | for (a, b), i in zip(zip(data_readed, data), range(address, address+len(data))):
168 | if a != b:
169 | raise ValueError('Mismatch at 0x{address:08X} 0x{:02X}!=0x{:02X}'.format(a, b, address=i))
170 | log.info('Successful')
171 | except TimeoutError as e:
172 | raise ConnectionError('Verifying error: '+str(e))
173 | except ValueError as e:
174 | raise ConnectionError('Verifying error: '+str(e))
175 |
176 | def lock(protocol):
177 | try:
178 | log.info('Enabling readout protection')
179 | protocol.lock()
180 | log.info('Successful')
181 | except TimeoutError as e:
182 | raise ConnectionError('Enabling error: '+str(e))
183 |
184 | def unlock(protocol):
185 | try:
186 | log.info('Disabling readout protection')
187 | protocol.unlock()
188 | log.info('Successful')
189 | except TimeoutError as e:
190 | raise ConnectionError('Disabling error: '+str(e))
191 |
192 | def speed(protocol, bps):
193 | try:
194 | log.info('Setting speed to %d bps' % (bps,))
195 | protocol.speed(bps)
196 | log.info('Command sent')
197 | except TimeoutError as e:
198 | raise ConnectionError('Writing error: '+str(e))
199 |
200 | def main():
201 |
202 | parser = config_parser()
203 |
204 | params = parser.parse_args()
205 |
206 | if params.verbose:
207 | canprog.logger.set_level(canprog.logger.logging.DEBUG)
208 |
209 | if params.interface == 'socketcan':
210 | iface = can.interface.Bus(channel=params.name, bustype='socketcan_native')
211 |
212 | datafile = file.FileManager()
213 |
214 | protocol_class = protocols.get_protocol_class_by_name(params.protocol)
215 | protocol = protocol_class(iface)
216 |
217 | try:
218 | connect(protocol)
219 |
220 | if params.command == 'go':
221 | go(protocol, params.address)
222 | elif params.command == 'erase':
223 | erase(protocol, params.pages)
224 | elif params.command == 'write':
225 | if params.erase:
226 | erase(protocol, params.pages)
227 |
228 | datafile.load(params.input, params.format, params.address)
229 | for address, data in datafile.get_segments():
230 | write(protocol, address, data)
231 |
232 | if params.verify:
233 | for address, data in datafile.get_segments():
234 | verify(protocol, address, data)
235 |
236 | if params.go:
237 | go(protocol, params.address)
238 | elif params.command == 'read':
239 | data = read(protocol, params.address, params.size)
240 | datafile.set_segment(params.address, data)
241 | datafile.save(params.output, params.format)
242 | elif params.command == 'lock':
243 | lock(protocol)
244 | elif params.command == 'unlock':
245 | unlock(protocol)
246 | elif params.command == 'speed':
247 | speed(protocol, params.bps)
248 | else:
249 | log.info('Nothing to do...')
250 |
251 | disconnect(protocol)
252 | sys.exit(0)
253 | except ValueError as e:
254 | log.error(e)
255 | except ConnectionError as e:
256 | log.error(e)
257 |
258 | if __name__ == '__main__':
259 | main()
260 | sys.exit(1)
261 |
--------------------------------------------------------------------------------
/canprog/protocols/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # The MIT License (MIT)
3 | #
4 | # Copyright (c) 2017 Marcin Borowicz
5 | #
6 | # Permission is hereby granted, free of charge, to any person obtaining a copy
7 | # of this software and associated documentation files (the "Software"), to deal
8 | # in the Software without restriction, including without limitation the rights
9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | # copies of the Software, and to permit persons to whom the Software is
11 | # furnished to do so, subject to the following conditions:
12 | #
13 | # The above copyright notice and this permission notice shall be included in
14 | # all copies or substantial portions of the Software.
15 | #
16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | # THE SOFTWARE.
23 | #
24 |
25 | from .abstract import AbstractProtocol
26 |
27 | from . import stm32
28 |
29 | def get_protocol_class_by_name(name):
30 | if name == 'stm32':
31 | return stm32.STM32Protocol
32 | else:
33 | raise NotImplementedError('unknown protocol type')
34 |
--------------------------------------------------------------------------------
/canprog/protocols/abstract.py:
--------------------------------------------------------------------------------
1 | #
2 | # The MIT License (MIT)
3 | #
4 | # Copyright (c) 2017 Marcin Borowicz
5 | #
6 | # Permission is hereby granted, free of charge, to any person obtaining a copy
7 | # of this software and associated documentation files (the "Software"), to deal
8 | # in the Software without restriction, including without limitation the rights
9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | # copies of the Software, and to permit persons to whom the Software is
11 | # furnished to do so, subject to the following conditions:
12 | #
13 | # The above copyright notice and this permission notice shall be included in
14 | # all copies or substantial portions of the Software.
15 | #
16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | # THE SOFTWARE.
23 | #
24 |
25 | import can
26 | import sys
27 | import time
28 |
29 | from canprog.logger import log
30 |
31 | if 'win' in sys.platform.lower():
32 | current_time = time.clock
33 | else:
34 | current_time = time.time
35 |
36 | def canframe_to_string(msg):
37 |
38 | array = []
39 |
40 | flags = ''
41 | if msg.is_remote_frame:
42 | flags += 'R'
43 | if msg.is_error_frame:
44 | flags += 'E'
45 |
46 | if flags != '':
47 | array.append(flags)
48 |
49 | if msg.is_extended_id:
50 | aid = '{:08X}'.format(msg.arbitration_id)
51 | else:
52 | aid = '{:03X}'.format(msg.arbitration_id)
53 |
54 | array.append(aid)
55 | array.append(str(msg.dlc))
56 |
57 | data = ''.join(['{:02X}'.format(b) for b in msg.data])
58 |
59 | if data != '':
60 | array.append(data)
61 |
62 | return ':'.join(array)
63 |
64 | class AbstractProtocol(object):
65 | '''
66 | classdocs
67 | '''
68 | RECV_TIMEOUT = 1.0
69 |
70 | def __init__(self, iface):
71 | if not isinstance(iface, can.BusABC):
72 | raise TypeError('canbus interface not compatible')
73 | self._iface = iface
74 |
75 | def _send(self, msg):
76 | try:
77 | self._iface.send(msg)
78 | log.debug('TX: '+canframe_to_string(msg))
79 | except can.CanError:
80 | raise IOError('Sending error')
81 |
82 | def _recv(self, timeout=None, checker=None):
83 | if timeout == None:
84 | t = self.RECV_TIMEOUT
85 | else:
86 | t = timeout
87 | time = current_time()
88 | response = None
89 | while current_time() - time < t:
90 | frame = self._iface.recv(0.0)
91 | if frame:
92 | if checker != None:
93 | if not checker(frame):
94 | continue
95 | response = frame
96 | break
97 |
98 | if not response:
99 | raise TimeoutError('Receiving timeout')
100 |
101 | log.debug('RX: '+canframe_to_string(response))
102 | return response
103 |
104 | def connect(self):
105 | try:
106 | self._connect()
107 | except AttributeError as e:
108 | raise NotImplementedError('Connect method not implemented')
109 |
110 | def disconnect(self):
111 | try:
112 | self._disconnect()
113 | except AttributeError as e:
114 | raise NotImplementedError('Disconnect method not implemented')
115 |
116 | def read(self, address, size):
117 | try:
118 | return self._read(address, size)
119 | except AttributeError as e:
120 | raise NotImplementedError('Read method not implemented')
121 |
122 | def write(self, address, data):
123 | try:
124 | self._write(address, data)
125 | except AttributeError as e:
126 | raise NotImplementedError('Write method not implemented')
127 |
128 | def erase(self, pages):
129 | try:
130 | self._erase(pages)
131 | except AttributeError as e:
132 | raise NotImplementedError('Erase method not implemented')
133 |
134 | def lock(self):
135 | try:
136 | self._lock()
137 | except AttributeError as e:
138 | raise NotImplementedError('Lock method not implemented')
139 |
140 | def unlock(self):
141 | try:
142 | self._unlock()
143 | except AttributeError as e:
144 | raise NotImplementedError('Unlock method not implemented')
145 |
146 | def go(self, address):
147 | try:
148 | self._go(address)
149 | except AttributeError as e:
150 | raise NotImplementedError('Go method not implemented')
151 |
152 | def speed(self, bps):
153 | try:
154 | self._speed(bps)
155 | except AttributeError as e:
156 | raise NotImplementedError('Speed method not implemented')
157 |
--------------------------------------------------------------------------------
/canprog/protocols/stm32.py:
--------------------------------------------------------------------------------
1 | #
2 | # The MIT License (MIT)
3 | #
4 | # Copyright (c) 2017 Marcin Borowicz
5 | #
6 | # Permission is hereby granted, free of charge, to any person obtaining a copy
7 | # of this software and associated documentation files (the "Software"), to deal
8 | # in the Software without restriction, including without limitation the rights
9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | # copies of the Software, and to permit persons to whom the Software is
11 | # furnished to do so, subject to the following conditions:
12 | #
13 | # The above copyright notice and this permission notice shall be included in
14 | # all copies or substantial portions of the Software.
15 | #
16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | # THE SOFTWARE.
23 | #
24 |
25 | import can
26 | import struct
27 |
28 | from . import AbstractProtocol
29 |
30 | from canprog.logger import log
31 |
32 | CMD_GET_COMMANDS = 0x00
33 | CMD_GET_VERSION = 0x01
34 | CMD_GET_ID = 0x02
35 | CMD_CHANGE_SPEED = 0x03
36 | CMD_READ_MEMORY = 0x11
37 | CMD_GO = 0x21
38 | CMD_WRITE_MEMORY = 0x31
39 | CMD_ERASE = 0x43
40 | CMD_WRITE_PROTECT = 0x63
41 | CMD_WRITE_UNPROTECT = 0x73
42 | CMD_READOUT_PROTECT = 0x82
43 | CMD_READOUT_UNPROTECT = 0x92
44 |
45 | MASSERASE_MAX_TIMEOUT = 30.0
46 | UNPROTECT_MAX_TIMEOUT = 2.0
47 |
48 | BYTE_ACK = 0x79
49 | BYTE_NOACK = 0x1F
50 | BYTE_INIT = 0x79
51 | BYTE_DATA = 0x04
52 |
53 | CHIP_ID = { 0x440: "STM32F05xxx & STM32F030x8",
54 | 0x444: "STM32F03xx4/6",
55 | 0x442: "STM32F030xC",
56 | 0x445: "STM32F04xxx & STM32F070x6",
57 | 0x448: "STM32F070xB & STM32F071xx/072xx",
58 | 0x442: "STM32F09xxx",
59 |
60 | 0x412: "STM32F10xxx Low-density",
61 | 0x410: "STM32F10xxx Medium-density",
62 | 0x414: "STM32F10xxx High-density",
63 | 0x420: "STM32F10xxx Medium-density VL",
64 | 0x428: "STM32F10xxx High-density VL",
65 | 0x418: "STM32F105xx/107xx",
66 | 0x430: "STM32F10xxx XL-density",
67 |
68 | 0x411: "STM32F2xxxx",
69 |
70 | 0x432: "STM32F373xx & STM32F378xx",
71 | 0x422: "STM32F302xB(C)/303xB(C) & STM32F358xx",
72 | 0x439: "STM32F301xx/302x4(6/8) & STM32F318xx",
73 | 0x438: "STM32F303x4(6/8)/334xx/328xx",
74 | 0x446: "STM32F302xD(E)/303xD(E) & STM32F398xx",
75 |
76 | 0x413: "STM32F40xxx/41xxx",
77 | 0x419: "STM32F42xxx/43xxx",
78 | 0x423: "STM32F401xB(C)",
79 | 0x433: "STM32F401xD(E)",
80 | 0x458: "STM32F410xx",
81 | 0x431: "STM32F411xx",
82 | 0x441: "STM32F412xx",
83 | 0x421: "STM32F446xx",
84 | 0x434: "STM32F469xx/479xx",
85 | 0x463: "STM32F413xx/423xx",
86 |
87 | 0x452: "STM32F72xxx/73xxx",
88 | 0x449: "STM32F74xxx/75xxx",
89 | 0x451: "STM32F76xxx/77xxx",
90 |
91 | 0x450: "STM32H74xxx/75xxx",
92 |
93 | 0x457: "STM32L01xxx/02xxx",
94 | 0x425: "STM32L031xx/041xx",
95 | 0x417: "STM32L05xxx/06xxx",
96 | 0x447: "STM32L07xxx/08xxx",
97 | 0x416: "STM32L1xxx6(8/B)",
98 | 0x429: "STM32L1xxx6(8/B)A",
99 | 0x427: "STM32L1xxxC",
100 | 0x436: "STM32L1xxxD",
101 | 0x437: "STM32L1xxxE",
102 |
103 | 0x435: "STM32L43xxx/44xxx",
104 | 0x462: "STM32L45xxx/46xxx",
105 | 0x415: "STM32L47xxx/48xxx",
106 | 0x461: "STM32L496xx/4A6xx",}
107 |
108 | def _check_support(cmd):
109 | def func_wrapper(function):
110 | def call_wrapper(*args):
111 | stm32 = args[0]
112 | command = stm32._supported_commands[cmd]
113 | if not command['support']:
114 | raise NotImplementedError('No support {name} command'.format(name=command['name']))
115 | return function(*args)
116 | return call_wrapper
117 | return func_wrapper
118 |
119 | class STM32Protocol(AbstractProtocol):
120 |
121 | def __init__(self, *args, **kwargs):
122 | super().__init__(*args, **kwargs)
123 | self._supported_commands = { CMD_GET_COMMANDS: {'name': 'GET', 'support': False},
124 | CMD_GET_VERSION: {'name': 'GET_VERSION', 'support': False},
125 | CMD_GET_ID: {'name': 'GET_ID', 'support': False},
126 | CMD_CHANGE_SPEED: {'name': 'CHANGE_SPEED', 'support': False},
127 | CMD_READ_MEMORY: {'name': 'READ_MEMORY', 'support': False},
128 | CMD_GO: {'name': 'GO', 'support': False},
129 | CMD_WRITE_MEMORY: {'name': 'WRITE_MEMORY', 'support': False},
130 | CMD_ERASE: {'name': 'ERASE', 'support': False},
131 | CMD_WRITE_PROTECT: {'name': 'WRITE_PROTECT', 'support': False},
132 | CMD_WRITE_UNPROTECT: {'name': 'WRITE_UNPROTECT', 'support': False},
133 | CMD_READOUT_PROTECT: {'name': 'READOUT_PROTECT', 'support': False},
134 | CMD_READOUT_UNPROTECT: {'name': 'READOUT_UNPROTECT', 'support': False} }
135 |
136 | def _check_response(self, arb_id, dlc=None, ack_bytes=None):
137 | def _check_message(msg):
138 | if msg.arbitration_id != arb_id:
139 | return False
140 | if dlc:
141 | if msg.dlc != dlc:
142 | return False
143 | if ack_bytes:
144 | if msg.data[0] in ack_bytes:
145 | return True
146 | return False
147 | return True
148 | return _check_message
149 |
150 | def _check_ack(self, arb_id):
151 | return self._check_response(arb_id, 1, (BYTE_ACK,))
152 |
153 | def _check_byte(self, arb_id):
154 | return self._check_response(arb_id, 1)
155 |
156 | def _check_ack_or_noack(self, arb_id):
157 | return self._check_response(arb_id, 1, (BYTE_ACK, BYTE_NOACK))
158 |
159 | def _wait_for_ack(self, cmd, timeout=None):
160 | self._recv(timeout=timeout, checker=self._check_ack(cmd))
161 |
162 | def _send_data(self, cmd, data=[]):
163 | self._send(can.Message(arbitration_id=cmd, data=data, extended_id=False))
164 |
165 | def _init(self):
166 | self._send_data(BYTE_INIT)
167 | self._recv(checker=self._check_ack_or_noack(BYTE_INIT))
168 |
169 | def _recv_data(self, cmd, size=None):
170 | msg = self._recv(checker=self._check_response(cmd, size))
171 | return msg.data
172 |
173 | def _get_commands(self):
174 | self._send_data(CMD_GET_COMMANDS)
175 |
176 | self._wait_for_ack(CMD_GET_COMMANDS)
177 | commands_num = self._recv_data(CMD_GET_COMMANDS, 1)[0]
178 | boot_version = self._recv_data(CMD_GET_COMMANDS, 1)[0]
179 |
180 | commands = []
181 | for _ in range(commands_num):
182 | cmd = self._recv_data(CMD_GET_COMMANDS, 1)[0]
183 | commands.append(cmd)
184 |
185 | self._wait_for_ack(CMD_GET_COMMANDS)
186 |
187 | for k, v in self._supported_commands.items():
188 | v['support'] = True if k in commands else False
189 |
190 | self._bootloader_version = '{}.{}'.format((boot_version>>4)&0x0F, (boot_version)&0x0F)
191 |
192 | @_check_support(CMD_GET_VERSION)
193 | def _get_version(self):
194 |
195 | self._send_data(CMD_GET_VERSION)
196 |
197 | self._wait_for_ack(CMD_GET_VERSION)
198 | self._recv_data(CMD_GET_VERSION, 1)
199 | option_msg = self._recv_data(CMD_GET_VERSION, 2)[0:2]
200 |
201 | self._wait_for_ack(CMD_GET_VERSION)
202 |
203 | self._read_protection_bytes = '0x{}'.format(''.join(['{:02X}'.format(i) for i in option_msg]))
204 |
205 | @_check_support(CMD_GET_ID)
206 | def _get_id(self):
207 | self._send_data(CMD_GET_ID)
208 |
209 | self._wait_for_ack(CMD_GET_ID)
210 | chip_id = self._recv_data(CMD_GET_ID)
211 | self._wait_for_ack(CMD_GET_ID)
212 |
213 | self._chip_id = '0x{}'.format(''.join(['{:02X}'.format(i) for i in chip_id]))
214 |
215 | def _connect(self):
216 | self._init()
217 | log.info('Bootloader initialized')
218 |
219 | self._get_commands()
220 |
221 | log.info('Bootloader version: {version}'.format(version=self._bootloader_version))
222 |
223 | try:
224 | self._get_version()
225 | log.info('Read protection: {bytes}'.format(bytes=self._read_protection_bytes))
226 | except NotImplementedError as e:
227 | pass
228 |
229 | try:
230 | self._get_id()
231 |
232 | try:
233 | name = CHIP_ID[int(self._chip_id,0)]
234 | except KeyError:
235 | name = 'Unknown CHIP ID'
236 |
237 | log.info('Chip ID: {hexid} - {name}'.format(hexid=self._chip_id, name=name))
238 | except NotImplementedError as e:
239 | pass
240 |
241 | def _disconnect(self):
242 | pass
243 |
244 | def _wait_ack(self, cmd, seconds=30):
245 | i = 1
246 | while True:
247 | try:
248 | self._wait_for_ack(cmd, timeout=1.0)
249 | break
250 | except TimeoutError as e:
251 | log.info('Waiting... {}s'.format(i))
252 | if i >= seconds:
253 | raise
254 | else:
255 | i += 1
256 |
257 | @_check_support(CMD_GO)
258 | def _go(self, address):
259 | self._send_data(CMD_GO, struct.pack(">I", address))
260 | self._wait_for_ack(CMD_GO)
261 |
262 | @_check_support(CMD_READOUT_PROTECT)
263 | def _lock(self):
264 | self._send_data(CMD_READOUT_PROTECT)
265 | self._wait_for_ack(CMD_READOUT_PROTECT)
266 | self._wait_ack(CMD_READOUT_PROTECT, UNPROTECT_MAX_TIMEOUT)
267 |
268 | @_check_support(CMD_READOUT_UNPROTECT)
269 | def _unlock(self):
270 | self._send_data(CMD_READOUT_UNPROTECT)
271 | self._wait_for_ack(CMD_READOUT_UNPROTECT)
272 | self._wait_ack(CMD_READOUT_UNPROTECT, MASSERASE_MAX_TIMEOUT)
273 |
274 | def _erase_page(self, p):
275 | self._send_data(CMD_ERASE, (p,))
276 | self._wait_for_ack(CMD_ERASE)
277 | self._wait_ack(CMD_ERASE, MASSERASE_MAX_TIMEOUT)
278 |
279 | @_check_support(CMD_ERASE)
280 | def _erase(self, pages):
281 | if len(pages) == 0:
282 | log.info('Mass erasing. Please wait..')
283 | self._erase_page(0xFF);
284 | else:
285 | for p in pages:
286 | log.info('Erasing sector {:02X}'.format(p))
287 | self._send_data(CMD_ERASE, (0,))
288 | self._wait_for_ack(CMD_ERASE)
289 | self._erase_page(p);
290 |
291 |
292 | @_check_support(CMD_WRITE_MEMORY)
293 | def _write(self, address, data):
294 | size = len(data)
295 |
296 | last_p = -1
297 | for i in range(0, size, 256):
298 | p = int(100.0 * i / size)
299 | if (last_p == -1) or (p - last_p >= 10):
300 | log.info('Progress: {}%'.format(p))
301 | last_p = p
302 | self._write_page(address+i, data[i:i+256])
303 | log.info('Progress: 100%')
304 |
305 | def _write_page(self, address, data):
306 | size = len(data)
307 | self._send_data(CMD_WRITE_MEMORY, struct.pack(">IB", address, size - 1))
308 | self._wait_for_ack(CMD_WRITE_MEMORY)
309 |
310 | for i in range(0, size, 8):
311 | self._send_data(BYTE_DATA, data[i:i+8])
312 | self._wait_for_ack(CMD_WRITE_MEMORY)
313 |
314 | self._wait_for_ack(CMD_WRITE_MEMORY)
315 |
316 | @_check_support(CMD_READ_MEMORY)
317 | def _read(self, address, size):
318 |
319 | total = size
320 | last_p = -1
321 | data = bytearray()
322 | while size > 0:
323 | p = int(100.0 * (total-size) / total)
324 | if (last_p == -1) or (p - last_p >= 10):
325 | log.info('Progress: {}%'.format(p))
326 | last_p = p
327 |
328 | to_read = min(256, size)
329 |
330 | page = self._read_page(address, to_read)
331 | data += page
332 |
333 | address += to_read
334 | size -= to_read
335 |
336 | log.info('Progress: 100%')
337 |
338 | return data
339 |
340 | def _read_page(self, address, size):
341 | self._send_data(CMD_READ_MEMORY, struct.pack(">IB", address, size - 1))
342 | self._wait_for_ack(CMD_READ_MEMORY)
343 |
344 | page = bytearray()
345 | for _ in range(0, size, 8):
346 | data = self._recv_data(CMD_READ_MEMORY)
347 | page += data
348 |
349 | self._wait_for_ack(CMD_READ_MEMORY)
350 |
351 | return page
352 |
353 | def _speed(self, bps):
354 | if bps == 125000:
355 | code = 1
356 | elif bps == 250000:
357 | code = 2
358 | elif bps == 500000:
359 | code = 3
360 | elif bps == 1000000:
361 | code = 4
362 | else:
363 | raise NotImplementedError('Unsupported speed %d bps' % (bps,))
364 |
365 | self._send_data(CMD_CHANGE_SPEED, struct.pack(">B", code))
366 |
--------------------------------------------------------------------------------
/canprog/tests/__init__.py:
--------------------------------------------------------------------------------
1 | #
2 | # The MIT License (MIT)
3 | #
4 | # Copyright (c) 2017 Marcin Borowicz
5 | #
6 | # Permission is hereby granted, free of charge, to any person obtaining a copy
7 | # of this software and associated documentation files (the "Software"), to deal
8 | # in the Software without restriction, including without limitation the rights
9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | # copies of the Software, and to permit persons to whom the Software is
11 | # furnished to do so, subject to the following conditions:
12 | #
13 | # The above copyright notice and this permission notice shall be included in
14 | # all copies or substantial portions of the Software.
15 | #
16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | # THE SOFTWARE.
23 | #
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | description-file = README.md
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | #
4 | # The MIT License (MIT)
5 | #
6 | # Copyright (c) 2017 Marcin Borowicz
7 | #
8 | # Permission is hereby granted, free of charge, to any person obtaining a copy
9 | # of this software and associated documentation files (the "Software"), to deal
10 | # in the Software without restriction, including without limitation the rights
11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | # copies of the Software, and to permit persons to whom the Software is
13 | # furnished to do so, subject to the following conditions:
14 | #
15 | # The above copyright notice and this permission notice shall be included in
16 | # all copies or substantial portions of the Software.
17 | #
18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | # THE SOFTWARE.
25 | #
26 |
27 | import os
28 | from setuptools import setup
29 |
30 | from canprog import __version__, __appname__, __description__
31 |
32 | def read(fname):
33 | with open(os.path.join(os.path.dirname(__file__), fname)) as f:
34 | content = f.read()
35 | return content
36 |
37 | setup(
38 | name = __appname__,
39 | version = __version__,
40 | author = "Marcin Borowicz",
41 | author_email = "marcinbor85@gmail.com",
42 | description = (__description__),
43 | license = "MIT",
44 | keywords = "bootloader socketcan can stm32",
45 | url = "https://github.com/marcinbor85/CAN-Prog",
46 | packages=['canprog', 'canprog.protocols', 'canprog.tests'],
47 | long_description=read('DESCRIPTION'),
48 | install_requires=[
49 | "intelhex", "python-can",
50 | ],
51 | classifiers=[
52 | "Development Status :: 3 - Alpha",
53 | "License :: OSI Approved :: MIT License",
54 | "Intended Audience :: Telecommunications Industry",
55 | "Environment :: Console",
56 | "Natural Language :: English",
57 | "Operating System :: POSIX :: Linux",
58 | "Programming Language :: Python :: 3.6",
59 | "Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator",
60 | "Topic :: Software Development :: Embedded Systems",
61 | ],
62 | entry_points={
63 | 'console_scripts': [
64 | 'canprog = canprog.main:main'
65 | ]
66 | },
67 | )
68 |
--------------------------------------------------------------------------------