├── .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 | --------------------------------------------------------------------------------