├── .gitignore ├── LICENSE ├── README.md ├── arduino_coldfire_bdm ├── arduino-coldfire-bdm.ino ├── bdm_interface.py ├── command_line.py ├── control_registers.py └── serial_interface.py ├── pyproject.toml ├── requirements.txt └── 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 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 Peter Sobot 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # arduino-coldfire-bdm 2 | 3 | An interface to Motorola® Coldfire processors' Background Debug Interface (BDM) using an Arduino. 4 | 5 | ``` 6 | pip install arduino-coldfire-bdm 7 | ``` 8 | 9 | ### Usage 10 | 11 | 1. Ensure you have a [working Python 3 installation](https://python.org/download). 12 | 2. Run `pip install arduino-coldfire-bdm` on the command line to install this package. 13 | 3. Connect an Arduino to a Motorola Coldfire via its debug pins. See [arduino-coldfire-bdm.ino](https://github.com/psobot/arduino-coldfire-bdm/blob/main/arduino_coldfire_bdm/arduino-coldfire-bdm.ino) for suggested pin mappings. (Ensure the Coldfire board is powered up independently; the Arduino does not supply power to the Coldfire.) 14 | 4. Use [the Arduino IDE](https://arduino.cc/en/software) to compile and upload [arduino-coldfire-bdm.ino](https://github.com/psobot/arduino-coldfire-bdm/blob/main/arduino_coldfire_bdm/arduino-coldfire-bdm.ino) to your Arduino. 15 | - Note that you may need to change the pin numbers at the top of the script depending on your Arduino and the pins you used to connect to the Coldfire. 16 | 5. On the command line (Terminal.app, Command Prompt on Windows, etc), run `arduino-coldfire-bdm` (or, if that doesn't work, `python3 -m arduino_coldfire_bdm.command_line`) to invoke the command line program. You should see the following help text: 17 | 18 | ``` 19 | usage: arduino-coldfire-bdm [-h] [--dry-run] [--show-commands] [--serial-port SERIAL_PORT] [--baud-rate BAUD_RATE] {dump_memory,trace_execution,load_flash,sram_test} ... 20 | 21 | Communicate with an attached Coldfire V3 board (and maybe other versions too) to act as a simple debugger, via an Arduino serial connection. 22 | 23 | options: 24 | -h, --help show this help message and exit 25 | --dry-run If passed, don't actually connect to a serial port; just queue up commands that would have otherwise been sent to an attached Arduino. 26 | --show-commands If passed, print out a listing of every command sent to the Arduino. 27 | --serial-port SERIAL_PORT 28 | The file path of the serial port to connect to. 29 | --baud-rate BAUD_RATE 30 | The baud rate to use when connecting to the Arduino over serial. Must match what the Arduino expects. 31 | 32 | commands: 33 | {dump_memory,trace_execution,load_flash,sram_test} 34 | dump_memory Dump memory. 35 | trace_execution Begin execution and print out the program counter and registers before every instruction. 36 | load_flash Erase an attached Flash chip and load in new contents from a file. 37 | sram_test Test SRAM attached to the Coldfire. Expects exactly 1MB of RAM, attached via chip-select port 1, mapped at 0x00200000. 38 | ``` 39 | 40 | 6. Select the appropriate `--serial-port` to use to connect to your Arduino, and run one of the commands. 41 | 42 | ## What? 43 | 44 | A long long time ago (the mid-1990s), Motorola created a series of CPUs derived from the 68k architecture, called the Coldfire. These processors are largely obsolete today, but are still found in certain industrial equipment and embedded devices released around that time; including some vintage synthesizers, like [the Alesis A6 Andromeda](https://www.alesis.com/products/view/a6-andromeda). 45 | 46 | This repository contains two things: 47 | - an Arduino sketch, which allows connecting pretty much any Arduino to a Coldfire processor's Background Debug Mode (BDM) port to send and receive data 48 | - a Python library to connect to an Arduino over USB serial, to allow running high-level debugging commands, like: 49 | - tracing execution (i.e.: like GDB or LLDB) 50 | - dumping the contents of memory to a file 51 | - erasing and re-flashing an attached flash memory chip 52 | - testing attached RAM chips 53 | - whatever else you want; it's Python! You can script it. 54 | 55 | This library, when paired with an Arduino, performs many of the same functions as [PEMicro's _Multilink_ debugging probes](https://www.pemicro.com). This library is free, runs wherever you can run an Arduino and Python code (rather than just on Windows), and requires no drivers. However, this library is way slower, is missing a ton of functions, and has no IDE support. 56 | 57 | ## Why? 58 | 59 | I bought a broken Alesis A6 Andromeda, and wanted to try fixing it by re-flashing its firmware without doing any soldering, because I'm bad at soldering. Read about that journey [on my blog](http://blog.petersobot.com/preview/c5fGNB81gvwoJvi7SchKEK/). 60 | 61 | ## How? 62 | 63 | I read the [MCF5307 data sheet](https://www.nxp.com/docs/en/data-sheet/MCF5307BUM.pdf) (484 pages) very carefully. It documents how to connect to a Coldfire BDM port from first principles. 64 | 65 | See the top of [arduino-coldfire-bdm.ino](https://github.com/psobot/arduino-coldfire-bdm/blob/main/arduino_coldfire_bdm/arduino-coldfire-bdm.ino) to figure out which pins to connect between the Arduino and your Coldfire's debug port. 66 | 67 | ## Can I use this for other things? 68 | 69 | Probably. There are certain assumptions made (especially around interfacing with Flash or RAM) that apply only to the Andromeda, and may not be useful for other devices. Pull requests to make this code more generic would be welcomed. 70 | 71 | ## What if I brick my device? 72 | 73 | Here's the license; I take no responsibility if this software is misused. You should probably read the software carefully first. 74 | 75 | Also; I tested this with a 5V Arduino on a 3.3V Coldfire CPU. It seemed to work just fine. That might fry your device; your mileage may vary. 76 | 77 | ``` 78 | Copyright (c) 2022 Peter Sobot 79 | 80 | Permission is hereby granted, free of charge, to any person obtaining 81 | a copy of this software and associated documentation files (the 82 | "Software"), to deal in the Software without restriction, including 83 | without limitation the rights to use, copy, modify, merge, publish, 84 | distribute, sublicense, and/or sell copies of the Software, and to 85 | permit persons to whom the Software is furnished to do so, subject to 86 | the following conditions: 87 | 88 | The above copyright notice and this permission notice shall be 89 | included in all copies or substantial portions of the Software. 90 | 91 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 92 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 93 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 94 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 95 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 96 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 97 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 98 | ``` 99 | -------------------------------------------------------------------------------- /arduino_coldfire_bdm/arduino-coldfire-bdm.ino: -------------------------------------------------------------------------------- 1 | #pragma GCC optimize("-O3") 2 | #pragma GCC push_options 3 | 4 | // Coldfire BDM Serial Bridge 5 | // by Peter Sobot (@psobot), November 8, 2022 6 | 7 | // Two inputs to the Coldfire: 8 | int DSCLK = 2; 9 | int DSI = 3; 10 | 11 | // Trigger a breakpoint by pulling this low: 12 | int BKPT = 4; // Low = 1, High = 0 13 | 14 | // One output from the Coldfire, which is visible a couple CPU clock cycles 15 | // after the fall of DSCLK. In practice, this means we only need to wait ~30 16 | // nanoseconds between fall of DSCLK and seeing the correct value on DSO. 17 | int DSO = 5; 18 | int RESET = 7; 19 | 20 | // Serial data is sent in 17-bit packets: 21 | // Receive (by the Coldfire): [status] [16 bits of data] 22 | // Send (to the Coldfire): [0] [16 bits of data] 23 | 24 | struct Packet { 25 | uint8_t status; 26 | uint16_t data; 27 | }; 28 | 29 | // The protocol here is technically full-duplex; commands can be sent and 30 | // received simultaneously. 31 | bool sendAndReceiveBit(uint8_t bitToSend) { 32 | digitalWrite(DSI, bitToSend); 33 | digitalWrite(DSCLK, HIGH); 34 | digitalWrite(DSCLK, LOW); 35 | return digitalRead(DSO); 36 | } 37 | 38 | bool receiveBit() { return sendAndReceiveBit(0); } 39 | 40 | void sendBit(uint8_t bit) { sendAndReceiveBit(bit); } 41 | 42 | Packet receivePacket() { return sendAndReceivePacket(0); } 43 | 44 | Packet sendAndReceivePacket(uint16_t dataToSend) { 45 | Packet packet; 46 | 47 | packet.status = 0; 48 | packet.data = 0; 49 | 50 | packet.status = sendAndReceiveBit(0); 51 | for (int i = 15; i >= 0; i--) { 52 | packet.data |= sendAndReceiveBit((dataToSend >> i) & 1) << i; 53 | } 54 | 55 | return packet; 56 | } 57 | 58 | void sendPacket(uint16_t data) { 59 | sendBit(0); 60 | 61 | for (int i = 15; i >= 0; i--) { 62 | char singleBit = (data >> i) & 1; 63 | sendBit(singleBit); 64 | } 65 | } 66 | 67 | void enterDebugMode(bool reset) { 68 | digitalWrite(BKPT, LOW); 69 | pinMode(BKPT, OUTPUT); 70 | delay(50); 71 | 72 | if (reset) { 73 | digitalWrite(RESET, LOW); 74 | pinMode(RESET, OUTPUT); 75 | delay(50); 76 | 77 | pinMode(RESET, INPUT); 78 | delay(50); 79 | } 80 | 81 | pinMode(BKPT, INPUT); 82 | delay(50); 83 | } 84 | 85 | void setup() { 86 | Serial.begin(1000000); 87 | Serial.println("Motorola Coldfire Debug Interface by Peter Sobot"); 88 | 89 | pinMode(DSCLK, OUTPUT); 90 | pinMode(DSI, OUTPUT); 91 | pinMode(DSO, INPUT); 92 | 93 | Serial.println("Ready."); 94 | } 95 | 96 | uint16_t getNextTwoBytesFromUSB() { 97 | while (!Serial.available()) { 98 | } 99 | uint16_t data = ((uint16_t)Serial.read()) << 8; 100 | while (!Serial.available()) { 101 | } 102 | data |= Serial.read(); 103 | return data; 104 | } 105 | 106 | void loop() { 107 | if (Serial.available() > 0) { 108 | int command = Serial.read(); 109 | 110 | switch (command) { 111 | case 'P': // for Ping 112 | Serial.println("PONG"); 113 | break; 114 | case 'B': // for Breakpoint 115 | enterDebugMode(false); 116 | break; 117 | case 'R': // for Reset 118 | enterDebugMode(true); 119 | break; 120 | case 'S': { // for Send-and-Receive 121 | uint16_t data = getNextTwoBytesFromUSB(); 122 | Packet packet = sendAndReceivePacket(data); 123 | Serial.print(packet.status ? "Y" : "N"); 124 | Serial.write((char *)&packet.data, sizeof(packet.data)); 125 | break; 126 | } 127 | case 's': { // for Send 128 | sendPacket(getNextTwoBytesFromUSB()); 129 | break; 130 | } 131 | case 'r': { // for Receive 132 | Packet packet = receivePacket(); 133 | Serial.print(packet.status ? "Y" : "N"); 134 | Serial.write((char *)&packet.data, sizeof(packet.data)); 135 | break; 136 | } 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /arduino_coldfire_bdm/bdm_interface.py: -------------------------------------------------------------------------------- 1 | import time 2 | import random 3 | from typing import List, Iterable 4 | from arduino_coldfire_bdm.serial_interface import ColdfireSerialInterface 5 | 6 | 7 | NUM_ADDRESS_REGISTERS = 8 8 | NUM_DATA_REGISTERS = 8 9 | 10 | 11 | def format_hex_list(values: List[int]) -> str: 12 | return f"[{', '.join([f'0x{v:08x}' for v in values])}]" 13 | 14 | 15 | class ConfigurationStatusRegister: 16 | def __init__(self, data: int): 17 | self.data = data 18 | 19 | def __repr__(self) -> str: 20 | return f"" 21 | 22 | @property 23 | def single_step_mode(self) -> bool: 24 | return bool((self.data >> 4) & 0x1) 25 | 26 | 27 | class BDMCommandInterface: 28 | """ 29 | A wrapper around a ColdfireSerialInterface that provides 30 | BDM (Background Debug Mode) commands. 31 | """ 32 | 33 | def __init__(self, interface: ColdfireSerialInterface): 34 | self.interface = interface 35 | self.current_step_mode = False 36 | 37 | def _send(self, *commands): 38 | """ 39 | Send one or more 16-bit packets (`commands`) while receiving (and discarding) any 40 | responses from previously-sent commands. This method will still throw exceptions 41 | if any of the previous commands failed. 42 | """ 43 | for command in commands: 44 | self.interface.send_and_receive_packet(command) 45 | 46 | def _send_then_receive(self, *commands, response_size_words: int = 1) -> int: 47 | """ 48 | Send one or more 16-bit packets (`commands`) then receive `response_size_words` words. 49 | Returns a single integer, which is the response data. 50 | """ 51 | for command in commands: 52 | self.interface.send_and_receive_packet(command) 53 | responses = [self.interface.receive_packet() for _ in range(response_size_words)] 54 | response_data = responses[0].data 55 | if len(responses) == 2: 56 | response_data <<= 16 57 | response_data |= responses[1].data 58 | return response_data 59 | 60 | def noop(self): 61 | """ 62 | Perform no operation; may be used as a null command. 63 | """ 64 | return self._send_then_receive(0x0000) 65 | 66 | def go(self): 67 | """ 68 | Resume execution from the current program counter. 69 | Execution proceeds forward without single-step mode; 70 | this is the equivalent of GDB/LLDB's "continue" command. 71 | """ 72 | self.set_single_step_mode(False) 73 | return self._send_then_receive(0x0C00) 74 | 75 | def step(self): 76 | """ 77 | Resume execution from the current program counter, stopping 78 | after the next instruction has been executed. This is the 79 | equivalent of GDB/LLDB's "step" command. 80 | """ 81 | self.set_single_step_mode(True) 82 | return self._send_then_receive(0x0C00) 83 | 84 | def read_byte(self, address: int) -> int: 85 | """ 86 | Read a single byte from the provided memory address. 87 | """ 88 | return self._send_then_receive(0x1900, address >> 16, address & 0xFFFF) & 0xFF 89 | 90 | def read_word(self, address: int) -> int: 91 | """ 92 | Read a single word (16 bits) from the provided memory address. 93 | """ 94 | return self._send_then_receive(0x1940, address >> 16, address & 0xFFFF) 95 | 96 | def read_longword(self, address: int) -> int: 97 | """ 98 | Read a longword (32 bits) from the provided memory address. 99 | """ 100 | return self._send_then_receive( 101 | 0x1980, address >> 16, address & 0xFFFF, response_size_words=2 102 | ) 103 | 104 | def read_address_register(self, register_number: int) -> int: 105 | """ 106 | Read the 32-bit contents of the given address register. 107 | """ 108 | if register_number > (NUM_ADDRESS_REGISTERS - 1) or register_number < 0: 109 | raise ValueError( 110 | f"Can't read register {register_number:,} - Coldfire only has" 111 | f" {NUM_ADDRESS_REGISTERS} address registers!" 112 | ) 113 | return self._send_then_receive(0x2188 | register_number, response_size_words=2) 114 | 115 | def read_data_register(self, register_number) -> int: 116 | """ 117 | Read the 32-bit contents of the given data register. 118 | """ 119 | if register_number > (NUM_DATA_REGISTERS - 1) or register_number < 0: 120 | raise ValueError( 121 | f"Can't read register {register_number:,} - Coldfire only has" 122 | f" {NUM_DATA_REGISTERS} data registers!" 123 | ) 124 | return self._send_then_receive(0x2180 | register_number, response_size_words=2) 125 | 126 | def read_control_register(self, register_encoding: int) -> int: 127 | """ 128 | Read the contents of one of the system control registers. 129 | See the Coldfire manual for these control registers. 130 | """ 131 | if register_encoding < 0 or register_encoding > 4095: 132 | raise ValueError("Coldfire only has a 12-bit control register field!") 133 | return self._send_then_receive(0x2980, 0, register_encoding & 0xFFF, response_size_words=2) 134 | 135 | def read_debug_configuration_status_register(self) -> ConfigurationStatusRegister: 136 | """ 137 | Read the contents of the Configuration Status Register (CSR) used for debugging. 138 | This register stores data including (but not limited to) the processor step mode, 139 | useful for stepping through instruction-by-instruction. 140 | """ 141 | return ConfigurationStatusRegister(self._send_then_receive(0x2D80, response_size_words=2)) 142 | 143 | def write_debug_configuration_status_register(self, new_value: int): 144 | """ 145 | Write the contents of the Configuration Status Register (CSR) used for debugging. 146 | This register stores data including (but not limited to) the processor step mode, 147 | useful for stepping through instruction-by-instruction. 148 | """ 149 | return self._send_then_receive(0x2C80, new_value >> 16, new_value & 0xFFF) 150 | 151 | def set_single_step_mode(self, enabled=True): 152 | """ 153 | Set the processor's single-step mode to the provided value. 154 | This controls the execution of the processor between instructions; if enabled, 155 | the processor will halt after every instruction, allowing debugging of the system. 156 | """ 157 | if self.current_step_mode == enabled: 158 | return 159 | csr = self.read_debug_configuration_status_register() 160 | new_register = csr.data 161 | new_register &= 0xFFFFFFEF 162 | if enabled: 163 | new_register |= 0b10000 164 | self.write_debug_configuration_status_register(new_register) 165 | self.current_step_mode = enabled 166 | 167 | def dump_words(self, base_address: int, num_words: int) -> Iterable[int]: 168 | """ 169 | Dump successive 16-bit words from the provided base address in an efficient manner. 170 | This is about three times faster than reading individual words, as the address of 171 | each word does not need to be specified. 172 | """ 173 | self.interface.send_packet(0x1940) 174 | self.interface.send_packet(base_address >> 16) 175 | self.interface.send_packet(base_address & 0xFFFF) 176 | num_words -= 1 177 | for _ in range(num_words): 178 | yield self.interface.send_and_receive_packet(0x1D40).data 179 | yield self.interface.receive_packet().data 180 | 181 | def write_byte(self, address: int, data: int): 182 | """ 183 | Write the provided byte to the provided address in memory. 184 | """ 185 | if data > 255 or data < 0: 186 | raise ValueError("Cannot write byte out of range for byte!") 187 | return self._send(0x1800, address >> 16, address & 0xFFFF, data & 0xFF) 188 | 189 | def write_word(self, address: int, data: int): 190 | """ 191 | Write the provided 16-bit word to the provided address in memory. 192 | """ 193 | if data > 0xFFFF or data < 0: 194 | raise ValueError("Cannot write byte out of range for word!") 195 | return self._send(0x1840, address >> 16, address & 0xFFFF, data) 196 | 197 | def write_longword(self, address: int, data: int): 198 | """ 199 | Write the provided 32-bit longword to the provided address in memory. 200 | """ 201 | return self._send(0x1880, address >> 16, address & 0xFFFF, data >> 16, data & 0xFFFF) 202 | 203 | def write_control_register(self, register: int, data: int): 204 | """ 205 | Write the provided 32-bit longword to the provided control register. 206 | """ 207 | return self._send(0x2880, 0, register & 0xFFFF, data >> 16, data & 0xFFFF) 208 | 209 | def write_address_register(self, register_number: int, data: int): 210 | """ 211 | Write the provided 32-bit longword to the provided address register. 212 | """ 213 | if register_number > (NUM_ADDRESS_REGISTERS - 1) or register_number < 0: 214 | raise ValueError( 215 | f"Can't write register {register_number:,} - Coldfire only has" 216 | f" {NUM_ADDRESS_REGISTERS} address registers!" 217 | ) 218 | return self._send_then_receive(0x2088 | register_number) 219 | 220 | def write_data_register(self, register_number: int, data: int): 221 | """ 222 | Write the provided 32-bit longword to the provided data register. 223 | """ 224 | if register_number > (NUM_DATA_REGISTERS - 1) or register_number < 0: 225 | raise ValueError( 226 | f"Can't write register {register_number:,} - Coldfire only has" 227 | f" {NUM_DATA_REGISTERS} data registers!" 228 | ) 229 | return self._send_then_receive(0x2080 | register_number) 230 | 231 | def send_flash_write_enable(self): 232 | """ 233 | If the attached system has a Flash boot ROM mapped at 0x00000000, 234 | this command unlocks that Flash chip for writing. The next write 235 | (using `write_byte`, `write_word`, or `write_longword`) will succeed. 236 | 237 | Note that when writing Flash memory, `0` bits can not be programmed 238 | to the value `1`; an entire flash chip erase may be necessary first 239 | to initialize all bits to `1` first. 240 | """ 241 | # This is the flash programming unlock sequence for a single word of memory, 242 | # assuming a word-addressable Flash memory attached at as Boot ROM. 243 | self.write_word(0x555 << 1, 0xAA) 244 | self.write_word(0x2AA << 1, 0x55) 245 | self.write_word(0x555 << 1, 0xA0) 246 | 247 | def enter_flash_unlock_bypass(self): 248 | """ 249 | If the attached system has a Flash boot ROM mapped at 0x00000000, 250 | this command puts that Flash chip into "unlock bypass mode," 251 | enabling faster writes by sending only a pair of words per 252 | word written. This command must be paired with 253 | `exit_flash_unlock_bypass` when the writes are complete. 254 | """ 255 | self.write_word(0x555 << 1, 0xAA) 256 | self.write_word(0x2AA << 1, 0x55) 257 | self.write_word(0x555 << 1, 0x20) 258 | self.in_unlock_bypass_mode = True 259 | 260 | def send_unlock_bypassed_flash_write(self, address: int, data: int): 261 | """ 262 | If the attached system has a Flash boot ROM mapped at 0x00000000, 263 | and `enter_flash_unlock_bypass` has been called, 264 | this command writes a single word to the Flash. 265 | 266 | Note that when writing Flash memory, `0` bits can not be programmed 267 | to the value `1`; an entire flash chip erase may be necessary first 268 | to initialize all bits to `1` first. 269 | """ 270 | if not self.in_unlock_bypass_mode: 271 | raise RuntimeError( 272 | "To use this method, ensure that `enter_flash_unlock_bypass` has been called first." 273 | ) 274 | self.write_word(0x00, 0xA0) 275 | self.write_word(address, data) 276 | 277 | def exit_flash_unlock_bypass(self): 278 | """ 279 | If the attached system has a Flash boot ROM mapped at 0x00000000, 280 | this command tells that Flash chip to exit "unlock bypass mode," 281 | returning to normal operation (that is: disallowing writes). 282 | """ 283 | self.write_word(0x00, 0x90) 284 | self.write_word(0x00, 0x00) 285 | self.in_unlock_bypass_mode = False 286 | 287 | def send_flash_chip_erase(self): 288 | """ 289 | Send the required commands to erase an entire attached flash chip. 290 | Note that this takes up to 30 seconds, and internally sleeps for that long. 291 | """ 292 | self.write_word(0x555 << 1, 0xAA) 293 | self.write_word(0x2AA << 1, 0x55) 294 | self.write_word(0x555 << 1, 0x80) 295 | self.write_word(0x555 << 1, 0xAA) 296 | self.write_word(0x2AA << 1, 0x55) 297 | self.write_word(0x555 << 1, 0x10) 298 | # TODO: Read words from the attached Flash to figure out how long to wait for. 299 | time.sleep(30) 300 | self.write_word(0x555 << 1, 0xF0) 301 | 302 | def consistency_check(self): 303 | """ 304 | Perform a consistency check on the attached Coldfire by reading and 305 | writing to all of the processor's registers, and ensuring that the 306 | values stick. This will raise a ValueError if any communication or 307 | consistency errors are detected. 308 | """ 309 | address_contents = [self.read_address_register(i) for i in range(NUM_ADDRESS_REGISTERS)] 310 | data_contents = [self.read_data_register(i) for i in range(NUM_DATA_REGISTERS)] 311 | 312 | # Read the address and data registers again and ensure consistency: 313 | address_contents_again = [ 314 | self.read_address_register(i) for i in range(NUM_ADDRESS_REGISTERS) 315 | ] 316 | if address_contents_again != address_contents: 317 | raise ValueError( 318 | f"Read all {NUM_ADDRESS_REGISTERS} address registers twice in a row, but found" 319 | " different results. This could indicate that the attached Coldfire processor is" 320 | " still running (i.e.: not halted), not properly connected to the debug port, or" 321 | " that the processor may be faulty. Inital values were:" 322 | f" {format_hex_list(address_contents)}, second read resulted in:" 323 | f" {format_hex_list(address_contents_again)}" 324 | ) 325 | data_contents_again = [self.read_data_register(i) for i in range(NUM_DATA_REGISTERS)] 326 | if data_contents_again != data_contents: 327 | raise ValueError( 328 | f"Read all {NUM_DATA_REGISTERS} data registers twice in a row, but found different" 329 | " results. This could indicate that the attached Coldfire processor is still" 330 | " running (i.e.: not halted), not properly connected to the debug port, or that" 331 | " the processor may be faulty. Inital values were:" 332 | f" {format_hex_list(data_contents)}, second read resulted in:" 333 | f" {format_hex_list(data_contents_again)}" 334 | ) 335 | 336 | # Write new random values to the address and data registers: 337 | expected_address_contents = [ 338 | int(random.random() * 0xFFFFFFFF) for _ in range(NUM_ADDRESS_REGISTERS) 339 | ] 340 | expected_data_contents = [ 341 | int(random.random() * 0xFFFFFFFF) for _ in range(NUM_DATA_REGISTERS) 342 | ] 343 | for i, value in enumerate(expected_address_contents): 344 | self.write_address_register(i, value) 345 | for i, value in enumerate(expected_data_contents): 346 | self.write_data_register(i, value) 347 | 348 | # Check to ensure that the values "stuck": 349 | address_contents_again = [ 350 | self.read_address_register(i) for i in range(NUM_ADDRESS_REGISTERS) 351 | ] 352 | if address_contents_again != expected_address_contents: 353 | raise ValueError( 354 | f"Wrote to all {NUM_ADDRESS_REGISTERS} address registers, but read different" 355 | " results. This could indicate that the attached Coldfire processor is still" 356 | " running (i.e.: not halted), not properly connected to the debug port, or that" 357 | " the processor may be faulty. Written values were:" 358 | f" {format_hex_list(expected_address_contents)}, but read-back resulted in:" 359 | f" {format_hex_list(address_contents_again)}" 360 | ) 361 | data_contents_again = [self.read_data_register(i) for i in range(NUM_DATA_REGISTERS)] 362 | if data_contents_again != expected_data_contents: 363 | raise ValueError( 364 | f"Wrote to all {NUM_DATA_REGISTERS} data registers twice in a row, but read" 365 | " different results. This could indicate that the attached Coldfire processor is" 366 | " still running (i.e.: not halted), not properly connected to the debug port, or" 367 | " that the processor may be faulty. Written values were:" 368 | f" {format_hex_list(expected_data_contents)}, but read-back resulted in:" 369 | f" {format_hex_list(data_contents_again)}" 370 | ) 371 | 372 | # Write the original values back to the processor to be a nice person: 373 | for i, value in enumerate(address_contents): 374 | self.write_address_register(i, value) 375 | for i, value in enumerate(data_contents): 376 | self.write_data_register(i, value) 377 | -------------------------------------------------------------------------------- /arduino_coldfire_bdm/command_line.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import argparse 3 | from contextlib import nullcontext 4 | import serial 5 | import time 6 | from tqdm import tqdm 7 | 8 | from arduino_coldfire_bdm.serial_interface import ( 9 | ColdfireSerialInterface, 10 | MockColdfireSerialInterface, 11 | ) 12 | from arduino_coldfire_bdm.bdm_interface import BDMCommandInterface 13 | from arduino_coldfire_bdm.control_registers import ControlRegisters 14 | 15 | 16 | PADDR_OFFSET = 0x244 17 | PADAT_OFFSET = 0x248 18 | 19 | 20 | def dump_memory_to_ascii( 21 | bdm, 22 | ofile, 23 | base: int = 0x00200000, 24 | length_in_bytes: int = 0x100, 25 | words_per_line: int = 8, 26 | binary: bool = False, 27 | ): 28 | for i, word in enumerate(bdm.dump_words(base, length_in_bytes // 2)): 29 | try: 30 | address = base + (i * 2) 31 | if (address - base) % (words_per_line * 4) == 0: 32 | if address > base: 33 | ofile.write("\n") 34 | ofile.write(f"0x{address:08x}: ") 35 | if binary: 36 | ofile.write(f"{word >> 8:08b} {word & 0xFF:08b} ") 37 | else: 38 | ofile.write(f"{word:04x}") 39 | if ((address - base) + 2) % 4 == 0: 40 | ofile.write(f" ") 41 | except KeyboardInterrupt: 42 | ofile.write("\n") 43 | break 44 | print() 45 | 46 | 47 | def register_dump_command(subparsers): 48 | subparser = subparsers.add_parser("dump_memory", help="Dump memory.") 49 | subparser.add_argument( 50 | "--starting-address", 51 | type=lambda s: int(s, 0), 52 | default=0x0, 53 | help="The starting address to dump memory from.", 54 | ) 55 | subparser.add_argument( 56 | "--num-bytes", 57 | type=lambda s: int(s, 0), 58 | default=0x1000000, 59 | help="The number of bytes to dump.", 60 | ) 61 | subparser.add_argument("--output-format", choices=["xxd", "raw", "binary"]) 62 | subparser.add_argument("--output-filename", default="stdout") 63 | 64 | def dump(args, bdm): 65 | if args.output_filename == "stdout": 66 | if args.output_format == "raw": 67 | ofile = sys.stdout.buffer 68 | else: 69 | ofile = sys.stdout 70 | else: 71 | if args.output_format == "raw": 72 | ofile = open(args.output_filename, "wb") 73 | else: 74 | ofile = open(args.output_filename, "w") 75 | if args.output_format == "raw": 76 | for word in bdm.dump_words(args.starting_address, args.num_bytes // 2): 77 | ofile.write((word >> 8).to_bytes(1, byteorder="big")) 78 | ofile.write((word & 0xFF).to_bytes(1, byteorder="big")) 79 | else: 80 | dump_memory_to_ascii( 81 | bdm, 82 | ofile, 83 | args.starting_address, 84 | args.num_bytes, 85 | binary=args.output_format == "binary", 86 | ) 87 | 88 | subparser.set_defaults(func=dump) 89 | 90 | 91 | def register_trace_command(subparsers): 92 | subparser = subparsers.add_parser( 93 | "trace_execution", 94 | help=( 95 | "Begin execution and print out the program counter and registers before every" 96 | " instruction." 97 | ), 98 | ) 99 | subparser.add_argument( 100 | "--starting-address", 101 | type=lambda s: int(s, 0), 102 | help="The starting address to begin execution from.", 103 | default=0x400, 104 | ) 105 | subparser.add_argument( 106 | "--num-instructions", 107 | type=lambda s: int(s, 0), 108 | default=0x10000000, 109 | help="The number of instructions to execute.", 110 | ) 111 | subparser.add_argument( 112 | "--stop-on-zero", 113 | action="store_true", 114 | help=( 115 | "If passed, stop tracing if the program counter goes to 0x0 (probably indicating a" 116 | " crash or reset)." 117 | ), 118 | ) 119 | 120 | def trace(args, bdm): 121 | print(f"Starting execution from 0x{args.starting_address:08x}...") 122 | bdm.write_control_register(ControlRegisters.PC, args.starting_address) 123 | 124 | print( 125 | "\t".join( 126 | ["PC ", "SP "] 127 | + [f"D{i} " for i in range(4)] 128 | + [f"A{i} " for i in range(4)] 129 | + ["MBAR ", "PADDR ", "PADAT "] 130 | ) 131 | ) 132 | for _ in range(0, args.num_instructions): 133 | try: 134 | pc = bdm.read_control_register(ControlRegisters.PC) 135 | sp = bdm.read_address_register(7) 136 | data_registers = [bdm.read_data_register(i) for i in range(4)] 137 | address_registers = [bdm.read_address_register(i) for i in range(4)] 138 | mbar = bdm.read_control_register(ControlRegisters.MBAR) 139 | 140 | values = ( 141 | [pc, sp] 142 | + data_registers 143 | + address_registers 144 | + [ 145 | mbar, 146 | bdm.read_word((mbar & 0xFFFFFFFE) + PADDR_OFFSET), 147 | bdm.read_word((mbar & 0xFFFFFFFE) + PADAT_OFFSET), 148 | ] 149 | ) 150 | print("\t".join([f"0x{v:08x}" for v in values])) 151 | if pc == 0x0 and args.stop_on_zero: 152 | print(f"Program counter is 0; exiting, the processor has probably crashed.") 153 | break 154 | bdm.step() 155 | except KeyboardInterrupt: 156 | break 157 | 158 | subparser.set_defaults(func=trace) 159 | 160 | 161 | def register_load_command(subparsers): 162 | subparser = subparsers.add_parser( 163 | "load_flash", 164 | help="Erase an attached Flash chip and load in new contents from a file.", 165 | ) 166 | subparser.add_argument("input_file", help="The path to the input file to load into memory.") 167 | subparser.add_argument( 168 | "--skip-erase", 169 | action="store_true", 170 | help=( 171 | "If passed, skip erasing the chip. This may result in an incorrect load if the Flash" 172 | " chip is not already filled with 0xFFFF." 173 | ), 174 | ) 175 | subparser.add_argument( 176 | "--max-bytes", 177 | type=lambda s: int(s, 0), 178 | default=0x10000000, 179 | help=( 180 | "The maximum number of bytes to load from the provided filename. Must be a number" 181 | " divisible by two." 182 | ), 183 | ) 184 | 185 | def load(args, bdm): 186 | start = time.time() 187 | with open(args.input_file, "rb") as f: 188 | data = f.read() 189 | print(f"Read {len(data):,} bytes from {args.input_file} to load into Flash.") 190 | if args.max_bytes < len(data): 191 | if args.max_bytes % 2 == 1: 192 | raise ValueError( 193 | f"--max-bytes {args.max_bytes} was passed, but the number of bytes to load" 194 | " must be divisible by two." 195 | ) 196 | data = data[: args.max_bytes] 197 | print(f"Only loading the first {args.max_bytes:,} bytes.") 198 | 199 | if not args.skip_erase: 200 | print("Sending full chip erase...") 201 | bdm.send_flash_chip_erase() 202 | print("Testing that chip was erased...") 203 | words = [(i, bdm.read_word(i)) for i in range(0, 16, 2)] 204 | if not all([word == 0xFFFF for _, word in words]): 205 | raise RuntimeError( 206 | "Flash chip was not erased! Expected all words to be 0xFFFF, but the first 8" 207 | f" were: {[word for _, word in words]}" 208 | ) 209 | else: 210 | print(f"Skipping full chip erase.") 211 | 212 | print(f"Unlocking Flash for writing...") 213 | bdm.send_flash_unlock_bypass() 214 | try: 215 | with tqdm(total=len(data), unit_scale=True, unit="b") as pbar: 216 | for i in range(0, len(data), 2): 217 | v = (data[i] << 8) | data[i + 1] 218 | bdm.send_unlock_bypassed_flash_write(i, v) 219 | pbar.update(2) 220 | finally: 221 | print(f"Locking Flash to prevent unexpected writes...") 222 | bdm.exit_flash_unlock_bypass() 223 | end = time.time() 224 | print(f"Load complete! Loaded {len(data):,} bytes in {end-start:.2f} seconds.") 225 | 226 | subparser.set_defaults(func=load) 227 | 228 | 229 | def register_sram_test_command(subparsers): 230 | subparser = subparsers.add_parser( 231 | "sram_test", 232 | help=( 233 | "Test SRAM attached to the Coldfire. Expects exactly 1MB of RAM, attached via" 234 | " chip-select port 1, mapped at 0x00200000." 235 | ), 236 | ) 237 | subparser.add_argument( 238 | "--base-address", 239 | type=lambda s: int(s, 0), 240 | help="The base address at which the SRAM is attached and mapped.", 241 | default=0x00200000, 242 | ) 243 | subparser.add_argument( 244 | "--max-bytes", 245 | type=lambda s: int(s, 0), 246 | default=0x100000, 247 | help="The maximum number of bytes to test.", 248 | ) 249 | 250 | def test(args, bdm): 251 | base = args.base_address 252 | end = base + args.max_bytes 253 | 254 | # First 8 bits of MBAR is ignored; used for mask status bits. 255 | mbar_value = 0x10000000 256 | bdm.write_control_register(ControlRegisters.MBAR, mbar_value + 1) 257 | 258 | if args.base_address != 0x00200000: 259 | raise NotImplementedError( 260 | "This script only knows how to access a single SRAM chip on chip select 1, mapped" 261 | " at base address 0x00200000." 262 | ) 263 | print(f"Mapping SRAM for access at 0x00200000...") 264 | # TODO: Make these constants, so that we can be more flexible 265 | # and map any chip-select-attached RAM to any address for testing. 266 | # All values from https://www.nxp.com/docs/en/data-sheet/MCF5307BUM.pdf 267 | bdm.write_word(mbar_value + 0x8C, 0x20) # Base address 268 | bdm.write_word(mbar_value + 0x96, 0x120) # Chip select configuration 269 | bdm.write_longword(mbar_value + 0x90, 0xF0001) # Size of mapping, plus `1` to indicate OK 270 | 271 | print(f"Writing 0xFFFFFFFF to start of SRAM and polling to ensure value remains constant:") 272 | bdm.write_longword(base, 0xFFFFFFFF) 273 | for _ in tqdm(range(200)): 274 | result = bdm.read_longword(base) 275 | if result != 0xFFFFFFFF: 276 | raise ValueError( 277 | "Value faded! SRAM does not appear to be holding its value; either SRAM does" 278 | " not exist at address 0x{args.base_address:08x}, or the attached SRAM is" 279 | " faulty." 280 | ) 281 | print(f"SRAM appears to retain values properly.") 282 | 283 | print( 284 | f"Testing RAM from 0x{base:08x} to 0x{end:08x} ({end-base:,} bytes) by writing" 285 | " low byte of address to each position." 286 | ) 287 | 288 | for address in range(base, end, 4): 289 | value = address 290 | bdm.write_longword(address, value) 291 | saved_value = bdm.read_longword(address) 292 | if saved_value != value: 293 | raise ValueError( 294 | f"SRAM check failed! Wrote 0x{value:08x} to 0x{address:08x}, but read back" 295 | f" 0x{saved_value:08x}! This may indicate that SRAM is faulty." 296 | ) 297 | print(f"SRAM test passed!") 298 | 299 | subparser.set_defaults(func=test) 300 | 301 | 302 | def main(): 303 | parser = argparse.ArgumentParser( 304 | description=( 305 | "Communicate with an attached Coldfire V3 board (and maybe other versions too) to act" 306 | " as a simple debugger, via an Arduino serial connection." 307 | ) 308 | ) 309 | parser.add_argument( 310 | "--dry-run", 311 | action="store_true", 312 | help=( 313 | "If passed, don't actually connect to a serial port; just queue up commands that would" 314 | " have otherwise been sent to an attached Arduino." 315 | ), 316 | ) 317 | parser.add_argument( 318 | "--show-commands", 319 | action="store_true", 320 | help="If passed, print out a listing of every command sent to the Arduino.", 321 | ) 322 | parser.add_argument( 323 | "--serial-port", 324 | default=None, 325 | help="The file path of the serial port to connect to.", 326 | ) 327 | parser.add_argument( 328 | "--baud-rate", 329 | type=int, 330 | default=1000000, 331 | help=( 332 | "The baud rate to use when connecting to the Arduino over serial. Must match what the" 333 | " Arduino expects." 334 | ), 335 | ) 336 | 337 | subparsers = parser.add_subparsers(title="commands") 338 | register_dump_command(subparsers) 339 | register_trace_command(subparsers) 340 | register_load_command(subparsers) 341 | register_sram_test_command(subparsers) 342 | args = parser.parse_args() 343 | 344 | if args.dry_run: 345 | print("In --dry-run mode; not connecting to serial port.") 346 | context = nullcontext() 347 | else: 348 | if not args.serial_port: 349 | parser.print_help() 350 | raise SystemExit(1) 351 | print(f"Connecting to serial port at {args.serial_port} at {args.baud_rate:,} baud...") 352 | context = serial.Serial(args.serial_port, args.baud_rate) 353 | 354 | with context as ser: 355 | if args.dry_run: 356 | interface = MockColdfireSerialInterface() 357 | else: 358 | interface = ColdfireSerialInterface(ser) 359 | bdm = BDMCommandInterface(interface) 360 | 361 | print(f"Entering debug mode...") 362 | interface.enter_debug_mode(True) 363 | 364 | if not args.dry_run: 365 | print(f"Performing consistency check on attached Coldfire processor...") 366 | bdm.consistency_check() 367 | 368 | if not hasattr(args, "func"): 369 | print("No command provided; doing nothing. (Pass -h to see available commands.)") 370 | else: 371 | args.func(args, bdm) 372 | 373 | if args.dry_run and args.show_commands: 374 | print(f"Commands sent to Arduino:") 375 | for command in interface.commands_sent: 376 | print(f"\t{repr(command)}") 377 | 378 | 379 | if __name__ == "__main__": 380 | main() 381 | -------------------------------------------------------------------------------- /arduino_coldfire_bdm/control_registers.py: -------------------------------------------------------------------------------- 1 | class ControlRegisters: 2 | CACR = 0x0002 3 | CacheControlRegister = 0x0002 4 | 5 | ACR0 = 0x0004 6 | AccessControlRegister0 = 0x0004 7 | 8 | ACR1 = 0x0005 9 | AccessControlRegister1 = 0x0005 10 | 11 | VBR = 0x801 12 | VectorBaseRegister = 0x801 13 | 14 | MACSR = 0x804 15 | MACStatusRegister = 0x804 16 | 17 | MASK = 0x805 18 | MACMaskRegister = 0x805 19 | 20 | ACC = 0x806 21 | MACAccumulator = 0x806 22 | 23 | SR = 0x80E 24 | StatusRegister = 0x80E 25 | 26 | PC = 0x80F 27 | ProgramCounter = 0x80F 28 | 29 | RAMBAR = 0xC04 30 | RAMBaseAddressRegister = 0xC04 31 | 32 | MBAR = 0xC0F 33 | ModuleBaseAddressRegister = 0xC0F 34 | -------------------------------------------------------------------------------- /arduino_coldfire_bdm/serial_interface.py: -------------------------------------------------------------------------------- 1 | import os 2 | import serial 3 | import struct 4 | 5 | PATH_TO_ARDUINO_SKETCH = os.path.join(os.path.dirname(__file__), "arduino-coldfire-bdm.ino") 6 | 7 | DEBUG_MESSAGE = ( 8 | "Ensure" 9 | " the Arduino is properly connected on the correct serial port, or try" 10 | f" re-compiling the Arduino sketch (from '{PATH_TO_ARDUINO_SKETCH}') and" 11 | " re-uploading with the Arduino IDE." 12 | ) 13 | 14 | 15 | class Response: 16 | """ 17 | A Coldfire debug response packet, with a status bit and 16-bit data word. 18 | This is pretty much just a plain old data container, but it also checks 19 | for invalid responses from the Coldfire. 20 | """ 21 | 22 | def __init__(self, status: int, data: int): 23 | self.status = status 24 | self.data = data 25 | 26 | if self.status == 1: 27 | if self.data == 0x0001: 28 | raise ValueError("Coldfire responded to last-sent command with Error response") 29 | elif self.data == 0xFFFF: 30 | raise ValueError("Coldfire responded to last-sent command with Illegal Command") 31 | 32 | 33 | class ColdfireSerialInterface: 34 | def __init__(self, serial: serial.Serial): 35 | """ 36 | Wrap a pySerial object with methods to communicate with a Coldfire BDM port, 37 | via an Arduino serial bridge to provide clocking. 38 | 39 | This class is only useful for communicating with an attached Arduino - 40 | see arduino-coldfire-bdm.ino for the Arduino sketch to upload. 41 | """ 42 | self.serial = serial 43 | 44 | first_line = self.serial.readline().decode("utf-8").strip() 45 | if not "Motorola Coldfire Debug Interface" in first_line: 46 | raise RuntimeError( 47 | "Tried to connect to Arduino, but got unexpected first line:" 48 | f" {first_line}\n{DEBUG_MESSAGE}" 49 | ) 50 | second_line = self.serial.readline().decode("utf-8").strip() 51 | if not "Ready" in second_line: 52 | raise RuntimeError( 53 | "Tried to connect to Arduino, but got unexpected second line:" 54 | f" {second_line}\n{DEBUG_MESSAGE}" 55 | ) 56 | 57 | def test_connection(self): 58 | self.serial.write(b"P") 59 | response = self.serial.read(4) 60 | if response != b"PONG": 61 | raise RuntimeError( 62 | "Sent ping to Arduino and expected to receive PONG, but got" 63 | f" {repr(response)}.\n{DEBUG_MESSAGE}" 64 | ) 65 | 66 | def send_packet(self, data: int): 67 | """ 68 | Send a 16-bit data packet to the attached Coldfire BDM via an Arduino bridge. 69 | The response will not be read automatically; use `receive_packet` to get the subsequent response. 70 | """ 71 | self.serial.write(b"s" + struct.pack(">H", data)) 72 | 73 | def receive_packet(self) -> Response: 74 | """ 75 | Receive a 17-bit data packet from the attached Coldfire BDM via an Arduino bridge. 76 | This implicitly sends a packet full of zeros (a "noop") to the Coldfire, necessary 77 | to receive a packet in response. This is slower than needed, though; use 78 | send_and_receive_packet to send a packet while receiving the response from the 79 | last packet. 80 | """ 81 | self.serial.write(b"r") 82 | return self._receive_packet() 83 | 84 | def _receive_packet(self) -> Response: 85 | response = self.serial.read(3) 86 | # The Arduino serial bridge translates the 17-bit packet into 87 | # 24 bits for us: 8 bits for the status bit, and then the original 88 | # 16 bits of the data word. 89 | status = int(response[0] == b"N") 90 | data = struct.unpack(" Response: 94 | """ 95 | Send a 16-bit data packet to the attached Coldfire BDM, while also receiving 96 | the 17-bit data packet that consitutes a response from the last command. This 97 | can be used to speed up sequences of commands where the response from the last 98 | command isn't required before sending the next command. 99 | """ 100 | self.serial.write(b"S" + struct.pack(">H", data)) 101 | return self._receive_packet() 102 | 103 | def enter_debug_mode(self, reset=False): 104 | """ 105 | Pull the Coldfire into BDM (Background Debug Mode) by asserting its BDM pin. 106 | If `reset` is True, the processor will also be reset during this operation, 107 | which may reset the program counter and status register. 108 | """ 109 | if reset: 110 | # R for Reset 111 | self.serial.write(b"R") 112 | else: 113 | # B for Breakpoint 114 | self.serial.write(b"B") 115 | 116 | 117 | class MockColdfireSerialInterface: 118 | def __init__(self): 119 | """ 120 | Provide a fake serial interface to use for dry runs and testing. 121 | """ 122 | self.commands_sent = [] 123 | 124 | def test_connection(self): 125 | self.commands_sent.append(b"P") 126 | 127 | def send_packet(self, data: int): 128 | """ 129 | Send a 16-bit data packet to the attached Coldfire BDM via an Arduino bridge. 130 | The response will not be read automatically; use `receive_packet` to get the subsequent response. 131 | """ 132 | self.commands_sent.append(b"s" + struct.pack(">H", data)) 133 | 134 | def receive_packet(self) -> Response: 135 | self.commands_sent.append(b"r") 136 | return self._receive_packet() 137 | 138 | def _receive_packet(self) -> Response: 139 | return Response(0, 0xFFFF) 140 | 141 | def send_and_receive_packet(self, data: int) -> Response: 142 | self.commands_sent.append(b"S" + struct.pack(">H", data)) 143 | return self._receive_packet() 144 | 145 | def enter_debug_mode(self, reset=False): 146 | if reset: 147 | # R for Reset 148 | self.commands_sent.append(b"R") 149 | else: 150 | # B for Breakpoint 151 | self.commands_sent.append(b"B") 152 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61.0"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pySerial 2 | tqdm -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | import pathlib 3 | 4 | here = pathlib.Path(__file__).parent.resolve() 5 | 6 | # Get the long description from the README file 7 | long_description = (here / "README.md").read_text(encoding="utf-8") 8 | 9 | setup( 10 | name="arduino_coldfire_bdm", 11 | version="0.1.0", 12 | description=( 13 | "An interface to Motorola® Coldfire processors' Background Debug Interface (BDM) using an" 14 | " Arduino." 15 | ), 16 | long_description=long_description, 17 | long_description_content_type="text/markdown", 18 | url="https://github.com/psobot/arduino_coldfire_bdm", 19 | author="Peter Sobot", 20 | author_email="github@petersobot.com", 21 | classifiers=[ 22 | "Development Status :: 3 - Alpha", 23 | "Intended Audience :: Developers", 24 | "Topic :: Software Development :: Debuggers", 25 | "License :: OSI Approved :: MIT License", 26 | "Programming Language :: Python :: 3", 27 | "Programming Language :: Python :: 3.7", 28 | "Programming Language :: Python :: 3.8", 29 | "Programming Language :: Python :: 3.9", 30 | "Programming Language :: Python :: 3.10", 31 | "Programming Language :: Python :: 3.11", 32 | "Programming Language :: Python :: 3 :: Only", 33 | ], 34 | keywords="coldfire, bdm, debugger, 68k, motorola, arduino", 35 | package_dir={"": "."}, 36 | packages=["arduino_coldfire_bdm"], 37 | python_requires=">=3.6, <4", 38 | install_requires=["pySerial", "tqdm"], 39 | package_data={"arduino_coldfire_bdm": ["arduino-coldfire-bdm.ino"]}, 40 | entry_points={ 41 | "console_scripts": ["arduino-coldfire-bdm=arduino_coldfire_bdm.command_line:main"] 42 | }, 43 | ) 44 | --------------------------------------------------------------------------------