├── .flake8 ├── LICENSE ├── README.md ├── spi_flash_programmer.ino └── spi_flash_programmer_client.py /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 120 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | 3 | Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | 3. Public License Fallback. Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | 4. Limitations and Disclaimers. 94 | 95 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 96 | surrendered, licensed or otherwise affected by this document. 97 | 98 | b. Affirmer offers the Work as-is and makes no representations or warranties 99 | of any kind concerning the Work, express, implied, statutory or otherwise, 100 | including without limitation warranties of title, merchantability, fitness 101 | for a particular purpose, non infringement, or the absence of latent or 102 | other defects, accuracy, or the present or absence of errors, whether or not 103 | discoverable, all to the greatest extent permissible under applicable law. 104 | 105 | c. Affirmer disclaims responsibility for clearing rights of other persons 106 | that may apply to the Work or any use thereof, including without limitation 107 | any person's Copyright and Related Rights in the Work. Further, Affirmer 108 | disclaims responsibility for obtaining any necessary consents, permissions 109 | or other rights required for any use of the Work. 110 | 111 | d. Affirmer understands and acknowledges that Creative Commons is not a 112 | party to this document and has no duty or obligation with respect to this 113 | CC0 or use of the Work. 114 | 115 | For more information, please see 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | SPI Flash programmer 3 | ==================== 4 | 5 | This is a very simple Arduino sketch and Python 3 client to program SPI flash chips. It's probably not very nice or tolerant, but it does at least have error correction and fast verification. 6 | 7 | The requirements are [pySerial](https://github.com/pyserial/pyserial) and [clint](https://github.com/kennethreitz/clint). Both modules can be installed with [pip](https://pip.pypa.io/en/stable/installing/): 8 | 9 | ```bash 10 | python3 -m pip install pyserial clint 11 | ``` 12 | 13 | Usage 14 | ----- 15 | 16 | - Program the Arduino with sketch 17 | - Connect the SPI flash chip as described 18 | - Run python client on PC to talk to programmer 19 | 20 | Connecting a chip 21 | ----------------- 22 | 23 | Connect the chip as follows, assuming you have an 3.3V 8-pin SSOP Flash chip. 24 | You will need an Arduino running at 3.3V logic. See [3.3V Conversion](https://learn.adafruit.com/arduino-tips-tricks-and-techniques/3-3v-conversion) to convert your Arduino to 3.3V. 25 | 26 | Or use one of the following devices running at 3.3V: 27 | 28 | - [Arduino 101 / Genuino 101](https://store.arduino.cc/genuino-101) 29 | - [Arduino Zero / Genuino Zero](https://store.arduino.cc/genuino-zero) 30 | - [Arduino Due](https://store.arduino.cc/arduino-due) 31 | - [Arduino M0](https://store.arduino.cc/arduino-m0) 32 | - [Arduino M0 Pro](https://store.arduino.cc/arduino-m0-pro) 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
Chip pinArduino pin
1 /SS10
2 MISO12
3 /WP+3.3V
4 GNDGND
5 MOSI11
6 SCK13
7 /HOLD+3.3V
8 VDD+3.3V
45 | 46 | Commands 47 | ------- 48 | 49 | ```bash 50 | # Listing serial ports 51 | > python3 spi_flash_programmer_client.py ports 52 | 0: COM15 53 | 1: /dev/ttyS1 54 | 2: /dev/cu.usbserial 55 | Done 56 | 57 | # Read flash 58 | > python3 spi_flash_programmer_client.py \ 59 | > -d COM1 -l 4096 -f dump.bin read 60 | Connected to 'SPI Flash programmer v1.0' 61 | .... 62 | 63 | # Write flash (sectors are erased automatically) 64 | > python3 spi_flash_programmer_client.py \ 65 | > -d /dev/ttyS1 -l 4096 -f dump.bin write 66 | Connected to 'SPI Flash programmer v1.0' 67 | .... 68 | 69 | # Verify flash 70 | > python3 spi_flash_programmer_client.py \ 71 | > -d /dev/cu.usbserial -l 4096 -f dump.bin verify 72 | Connected to 'SPI Flash programmer v1.0' 73 | .... 74 | 75 | # Erase flash 76 | > python3 spi_flash_programmer_client.py \ 77 | > -d COM1 -l 4096 erase 78 | Connected to 'SPI Flash programmer v1.0' 79 | [########### ] 383/1024 - 00:01:13 80 | 81 | # Set IO Pin value 82 | # Example: IO pin 0x2, set to LOW 83 | > python3 spi_flash_programmer_client.py \ 84 | > -d COM1 --io 0x2 --value 0x0 set-output 85 | 86 | # Override ChipSelect pin 87 | # Example: use IO pin 13/0xd 88 | > python3 spi_flash_programmer_client.py \ 89 | > -d COM1 --io 0xd set-cs-io 90 | 91 | # Help text 92 | > python3 spi_flash_programmer_client.py -h 93 | usage: spi_flash_programmer_client.py [-h] [-d DEVICE] [-f FILENAME] 94 | [-l LENGTH] [--rate BAUD_RATE] 95 | [--flash-offset FLASH_OFFSET] 96 | [--file-offset FILE_OFFSET] [--pad PAD] 97 | [--debug {off,normal,verbose}] [--io IO] 98 | [--value VALUE] 99 | {ports,write,read,verify,erase,enable-protection,disable-protection,check-protection,status-register,id-register,set-cs-io,set-output} 100 | 101 | Interface with an Arduino-based SPI flash programmer 102 | 103 | positional arguments: 104 | {ports,write,read,verify,erase,enable-protection,disable-protection,check-protection,status-register,id-register,set-cs-io,set-output} 105 | command to execute 106 | 107 | optional arguments: 108 | -h, --help show this help message and exit 109 | -d DEVICE serial port to communicate with 110 | -f FILENAME file to read from / write to 111 | -l LENGTH length to read/write in bytes, use -1 to write entire 112 | file 113 | --rate BAUD_RATE baud-rate of serial connection 114 | --flash-offset FLASH_OFFSET 115 | offset for flash read/write in bytes 116 | --file-offset FILE_OFFSET 117 | offset for file read/write in bytes 118 | --pad PAD pad value if file is not algined with SECTOR_SIZE 119 | --debug {off,normal,verbose} 120 | enable debug output 121 | --io IO IO pin used for set-cs-io and set-output 122 | --value VALUE value used for set-output 123 | ``` 124 | 125 | Troubleshooting 126 | --------------- 127 | 128 | * Try reducing the serial speed from 115200 to 57600. You'll have to edit the value in both the .ino and the .py. 129 | * Play with the SPCR setting in the .ino according to the datasheet. 130 | 131 | License [CC0][http://creativecommons.org/publicdomain/zero/1.0/] 132 | ---------------------------------------------------------------- 133 | 134 | To the extent possible under law, the authors below have waived all copyright and related or neighboring rights to spi-flash-programmer. 135 | 136 | - Leonardo Goncalves 137 | - Nicholas FitzRoy-Dale, United Kingdom 138 | - Tobias Faller, Germany 139 | 140 | 141 | Flashing a 16MB wr703n Flash chip 142 | ================================= 143 | I used this to write a 16MB flash chip for the wr703n router running OpenWRT. Recent versions of OpenWRT detect the larger Flash and automatically use it, so you don't need to do any patching. U-Boot still thinks the chip is 4MB large, but Linux doesn't seem to care. So all you need to do is copy the image and write the ART (wireless firmware) partition to the right spot, which is right at the end of Flash. 144 | 145 | I guess if you do a system upgrade which puts the kernel image somewhere after the first 4MB you might be in trouble, so upgrade u-boot before doing that. 146 | 147 | 1. Connect the original chip and dump it: 148 | 149 | python3 spi_flash_programmer_client.py -s 4096 -f wr703n.orig.bin read 150 | 151 | 2. Connect the new chip and write it: 152 | 153 | python3 spi_flash_programmer_client.py -s 4096 -f wr703n.orig.bin write 154 | 155 | 3. Verify the write. 156 | 157 | python3 spi_flash_programmer_client.py -s 4096 -f wr703n.orig.bin verify 158 | 159 | 3. Write the ART partition to the final 64k of the chip (the magic numbers are 16M-64K and 4M-64K respectively). 160 | 161 | python3 spi_flash_programmer_client.py -f wr703n.orig.bin --flash-offset 16711680 --file-offset 4128768 write 162 | 163 | 4. Verify the ART partition. 164 | 165 | python3 spi_flash_programmer_client.py -f wr703n.orig.bin --flash-offset 16711680 --file-offset 4128768 verify 166 | 167 | 5. Solder the new chip in. 168 | 169 | If you try this, let me know! 170 | 171 | Flashing iCE40HX8K-EVB from Olimex 172 | ================================== 173 | This example uses the OLIMEXINO-32U4 to flash a Olimex iCE40HX8K-EVB. The steps should also work with a iCE40HX1K-EVB. 174 | 175 | The board is connected using the UEXT connector. 176 | ```bash 177 | # Set iCE40-CRESET LOW - PIN 0x2 178 | > python3 spi_flash_programmer_client.py -d COM1 --io 0x2 --value 0x0 set-output 179 | # Set CS/SS to PIN 13/0xd 180 | > python3 spi_flash_programmer_client.py -d COM1 --io 0xd set-cs-io 181 | # power cycle the EVB, check if ID register is readable 182 | > python3 spi_flash_programmer_client.py -d COM1 id-register 183 | # program the bitmap 184 | > python3 spi_flash_programmer_client.py -d COM1 -l -1 --pad 0xff -f toplevel_bitmap.bin write 185 | ``` 186 | -------------------------------------------------------------------------------- /spi_flash_programmer.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | // Commands always use other characters than 0-9 a-f A-F 8 | // since the command data is always encoded in HEX 9 | #define COMMAND_HELLO '>' 10 | #define COMMAND_HELP '?' 11 | #define COMMAND_BUFFER_CRC 'h' 12 | #define COMMAND_BUFFER_LOAD 'l' 13 | #define COMMAND_BUFFER_STORE 's' 14 | #define COMMAND_FLASH_READ 'r' 15 | #define COMMAND_FLASH_WRITE 'w' 16 | #define COMMAND_FLASH_ERASE_SECTOR 'k' 17 | #define COMMAND_FLASH_ERASE_ALL 'n' 18 | #define COMMAND_WRITE_PROTECTION_ENABLE 'p' 19 | #define COMMAND_WRITE_PROTECTION_DISABLE 'u' 20 | #define COMMAND_WRITE_PROTECTION_CHECK 'x' 21 | #define COMMAND_STATUS_REGISTER_READ 'y' 22 | #define COMMAND_ID_REGISTER_READ 'i' 23 | #define COMMAND_ERROR '!' 24 | #define COMMAND_SET_CS '*' 25 | #define COMMAND_SET_OUTPUT 'o' 26 | 27 | #define WRITE_PROTECTION_NONE 0x00 28 | #define WRITE_PROTECTION_PARTIAL 0x01 29 | #define WRITE_PROTECTION_FULL 0x02 30 | #define WRITE_PROTECTION_UNKNOWN 0x03 31 | 32 | #define WRITE_PROTECTION_CONFIGURATION_NONE 0x00 33 | #define WRITE_PROTECTION_CONFIGURATION_PARTIAL 0x01 34 | #define WRITE_PROTECTION_CONFIGURATION_LOCKED 0x02 35 | #define WRITE_PROTECTION_CONFIGURATION_UNKNOWN 0x03 36 | 37 | #define VERSION "SPI Flash programmer v1.0" 38 | 39 | // Configure for your chip and transfer rates accordingly 40 | #define SECTOR_SIZE 4096 41 | #define PAGE_SIZE 256 42 | #define SERIAL_RATE 115200 43 | // Use maximum speed with F_CPU / 2 44 | #define SPI_SPEED F_CPU/2 45 | 46 | void dump_buffer(void); 47 | void dump_buffer_crc(void); 48 | int8_t read_into_buffer(void); 49 | 50 | void erase_all(void); 51 | void erase_sector(uint32_t address); 52 | void read_page(uint32_t address); 53 | void write_page(uint32_t address); 54 | 55 | uint32_t crc_buffer(void); 56 | void wait_for_write_enable(void); 57 | 58 | int8_t read_nibble(void); 59 | int8_t read_hex_u8(uint8_t *value); 60 | int8_t read_hex_u16(uint16_t *value); 61 | int8_t read_hex_u32(uint32_t *value); 62 | void write_hex_u8(uint8_t value); 63 | void write_hex_u16(uint16_t value); 64 | 65 | void impl_enable_write(void); 66 | void impl_erase_chip(void); 67 | void impl_erase_sector(uint32_t addressSPI); 68 | void impl_read_page(uint32_t address); 69 | void impl_write_page(uint32_t address); 70 | void impl_status_register_read(void); 71 | void impl_write_protection_enable(void); 72 | void impl_write_protection_disable(void); 73 | void impl_write_protection_check(void); 74 | void impl_wait_for_write_enable(void); 75 | void impl_jedec_id_read(void); 76 | 77 | uint8_t buffer [PAGE_SIZE]; 78 | uint8_t nCsIo; 79 | 80 | void setup() 81 | { 82 | nCsIo = SS; 83 | 84 | // Setup SPI 85 | SPISettings settingsA(SPI_SPEED, MSBFIRST, SPI_MODE0); 86 | uint16_t i; 87 | 88 | for (i = 0; i < PAGE_SIZE; i += 4) 89 | { // Initialize buffer with 0xDEADBEEF 90 | buffer[i + 0] = 0xDE; 91 | buffer[i + 1] = 0xAD; 92 | buffer[i + 2] = 0xBE; 93 | buffer[i + 3] = 0xEF; 94 | } 95 | 96 | Serial.begin(SERIAL_RATE); 97 | 98 | SPI.begin(); // Initialize pins 99 | SPI.beginTransaction(settingsA); 100 | pinMode(nCsIo, OUTPUT); 101 | digitalWrite(nCsIo, HIGH); // disable flash device 102 | 103 | delay(10); 104 | } 105 | 106 | void loop() 107 | { 108 | uint32_t address; 109 | uint8_t tmp8; 110 | uint16_t tmp16; 111 | 112 | // Wait for command 113 | while(Serial.available() == 0) { 114 | ; // Do nothing 115 | } 116 | 117 | int cmd = Serial.read(); 118 | switch(cmd) { 119 | case COMMAND_HELLO: 120 | Serial.print(COMMAND_HELLO); // Echo OK 121 | Serial.println(VERSION); 122 | Serial.print(COMMAND_HELLO); // Echo 2nd OK 123 | break; 124 | 125 | case COMMAND_FLASH_ERASE_ALL: 126 | erase_all(); 127 | Serial.print(COMMAND_FLASH_ERASE_ALL); // Echo OK 128 | break; 129 | 130 | case COMMAND_FLASH_ERASE_SECTOR: 131 | if (!read_hex_u32(&address)) { 132 | Serial.print(COMMAND_ERROR); // Echo Error 133 | break; 134 | } 135 | 136 | erase_sector(address); 137 | Serial.print(COMMAND_FLASH_ERASE_SECTOR); // Echo OK 138 | break; 139 | 140 | case COMMAND_FLASH_READ: 141 | if (!read_hex_u32(&address)) { 142 | Serial.print(COMMAND_ERROR); // Echo Error 143 | break; 144 | } 145 | 146 | read_page(address); 147 | Serial.print(COMMAND_FLASH_READ); // Echo OK 148 | dump_buffer_crc(); 149 | break; 150 | 151 | case COMMAND_FLASH_WRITE: 152 | if (!read_hex_u32(&address)) { 153 | Serial.print(COMMAND_ERROR); // Echo Error 154 | break; 155 | } 156 | 157 | write_page(address); 158 | Serial.print(COMMAND_FLASH_WRITE); // Echo OK 159 | break; 160 | 161 | case COMMAND_BUFFER_LOAD: 162 | Serial.print(COMMAND_BUFFER_LOAD); // Echo OK 163 | dump_buffer(); 164 | Serial.println(); 165 | break; 166 | 167 | case COMMAND_BUFFER_CRC: 168 | Serial.print(COMMAND_BUFFER_CRC); // Echo OK 169 | dump_buffer_crc(); 170 | Serial.println(); 171 | break; 172 | 173 | case COMMAND_BUFFER_STORE: 174 | if (!read_into_buffer()) { 175 | Serial.print(COMMAND_ERROR); // Echo Error 176 | break; 177 | } 178 | 179 | Serial.print(COMMAND_BUFFER_STORE); // Echo OK 180 | dump_buffer_crc(); 181 | break; 182 | 183 | case COMMAND_WRITE_PROTECTION_CHECK: 184 | Serial.print(COMMAND_WRITE_PROTECTION_CHECK); // Echo OK 185 | impl_write_protection_check(); 186 | break; 187 | 188 | case COMMAND_WRITE_PROTECTION_ENABLE: 189 | Serial.print(COMMAND_WRITE_PROTECTION_ENABLE); // Echo OK 190 | impl_write_protection_enable(); 191 | break; 192 | 193 | case COMMAND_WRITE_PROTECTION_DISABLE: 194 | Serial.print(COMMAND_WRITE_PROTECTION_DISABLE); // Echo OK 195 | impl_write_protection_disable(); 196 | break; 197 | 198 | case COMMAND_STATUS_REGISTER_READ: 199 | Serial.print(COMMAND_STATUS_REGISTER_READ); // Echo OK 200 | impl_status_register_read(); 201 | break; 202 | 203 | case COMMAND_ID_REGISTER_READ: 204 | Serial.print(COMMAND_ID_REGISTER_READ); // Echo OK 205 | impl_jedec_id_read(); 206 | break; 207 | 208 | case COMMAND_SET_CS: 209 | if(!read_hex_u8(&tmp8)) { 210 | Serial.print(COMMAND_ERROR); // Echo Error 211 | break; 212 | } 213 | if (tmp8 != nCsIo) { 214 | if (nCsIo != SS) 215 | pinMode(nCsIo, INPUT); 216 | nCsIo=tmp8; 217 | pinMode(nCsIo, OUTPUT); 218 | digitalWrite(nCsIo, HIGH); // disable flash device 219 | } 220 | 221 | Serial.print(COMMAND_SET_CS); // Echo OK 222 | break; 223 | 224 | case COMMAND_SET_OUTPUT: 225 | if(!read_hex_u16(&tmp16)) { 226 | Serial.print(COMMAND_ERROR); // Echo Error 227 | break; 228 | } 229 | pinMode(tmp16>>8, OUTPUT); 230 | if (tmp16 & 0xf0) { 231 | if (tmp16 & 0xf) { 232 | digitalWrite(tmp16>>8, HIGH); 233 | } 234 | else { 235 | digitalWrite(tmp16>>8, LOW); 236 | } 237 | } 238 | 239 | Serial.print(COMMAND_SET_OUTPUT); // Echo OK 240 | break; 241 | 242 | case COMMAND_HELP: 243 | Serial.println(VERSION); 244 | Serial.println(" n : erase chip"); 245 | Serial.println(" kXXXXXXXX : erase 4k sector XXXXXXXX (hex)"); 246 | Serial.println(); 247 | Serial.println(" rXXXXXXXX : read a page XXXXXXXX (hex) to buffer"); 248 | Serial.println(" wXXXXXXXX : write buffer to a page XXXXXXXX (hex)"); 249 | Serial.println(); 250 | Serial.println(" p : enable write protection"); 251 | Serial.println(" u : disable write protection"); 252 | Serial.println(" x : check write protection"); 253 | Serial.println(" y : read status register"); 254 | Serial.println(" i : read id register"); 255 | Serial.println(); 256 | Serial.println(" h : print buffer CRC-32"); 257 | Serial.println(" l : display the buffer (in hex)"); 258 | Serial.println(" sBBBBBBBB : load the buffer with a page size of data BBBBBBBB..."); 259 | Serial.println(); 260 | Serial.println(" *XX : set IO XX as CS/SS"); 261 | Serial.println(" oXXYZ : set IO XX as output, set value Z if Y!=0"); 262 | Serial.println(); 263 | Serial.println("Examples:"); 264 | Serial.println(" r00003700 read data from page 0x3700 into buffer"); 265 | Serial.println(" scafe...3737 load the buffer with a page of data, first byte is 0xca ..."); 266 | break; 267 | } 268 | 269 | Serial.flush(); 270 | } 271 | 272 | void read_page(uint32_t address) 273 | { 274 | // Send read command 275 | digitalWrite(nCsIo, LOW); 276 | impl_read_page(address); 277 | 278 | // Release chip, signal end transfer 279 | digitalWrite(nCsIo, HIGH); 280 | } 281 | 282 | void write_page(uint32_t address) 283 | { 284 | digitalWrite(nCsIo, LOW); 285 | impl_enable_write(); 286 | digitalWrite(nCsIo, HIGH); 287 | delay(10); 288 | 289 | digitalWrite(nCsIo, LOW); 290 | impl_write_page(address); 291 | digitalWrite(nCsIo, HIGH); 292 | delay(1); // Wait for 1 ms 293 | 294 | impl_wait_for_write_enable(); 295 | } 296 | 297 | void erase_all() 298 | { 299 | digitalWrite(nCsIo, LOW); 300 | impl_enable_write(); 301 | digitalWrite(nCsIo, HIGH); 302 | delay(10); // Wait for 10 ms 303 | 304 | digitalWrite(nCsIo, LOW); 305 | impl_erase_chip(); 306 | digitalWrite(nCsIo, HIGH); 307 | delay(1); // Wait for 1 ms 308 | 309 | impl_wait_for_write_enable(); 310 | } 311 | 312 | void erase_sector(uint32_t address) 313 | { 314 | digitalWrite(nCsIo, LOW); 315 | impl_enable_write(); 316 | digitalWrite(nCsIo, HIGH); 317 | delay(10); 318 | 319 | digitalWrite(nCsIo, LOW); 320 | impl_erase_sector(address); 321 | digitalWrite(nCsIo, HIGH); 322 | 323 | impl_wait_for_write_enable(); 324 | } 325 | 326 | void dump_buffer(void) 327 | { 328 | uint16_t counter; 329 | 330 | for(counter = 0; counter < PAGE_SIZE; counter++) { 331 | write_hex_u8(buffer[counter]); 332 | } 333 | } 334 | 335 | void dump_buffer_crc(void) 336 | { 337 | uint32_t crc = crc_buffer(); 338 | write_hex_u16((crc >> 16) & 0xFFFF); 339 | write_hex_u16(crc & 0xFFFF); 340 | } 341 | 342 | int8_t read_into_buffer(void) 343 | { 344 | uint16_t counter; 345 | uint8_t tmp; 346 | 347 | for(counter = 0; counter < PAGE_SIZE; counter++) { 348 | if (!read_hex_u8(&tmp)) { 349 | return 0; 350 | } 351 | 352 | buffer[counter] = (uint8_t) tmp; 353 | } 354 | 355 | return 1; 356 | } 357 | 358 | int8_t read_nibble(void) 359 | { 360 | int16_t c; 361 | 362 | do { 363 | c = Serial.read(); 364 | } while(c == -1); 365 | 366 | if (c >= '0' && c <= '9') { 367 | return (c - '0') + 0; 368 | } else if (c >= 'a' && c <= 'f') { 369 | return (c - 'a') + 10; 370 | } else if (c >= 'A' && c <= 'F') { 371 | return (c - 'A') + 10; 372 | } else { 373 | return -1; 374 | } 375 | } 376 | 377 | int8_t read_hex_u16(uint16_t *value) 378 | { 379 | int8_t i, tmp; 380 | uint16_t result = 0; 381 | 382 | for (i = 0; i < 4; i++) { 383 | tmp = read_nibble(); 384 | if (tmp == -1) { 385 | return 0; 386 | } 387 | 388 | result <<= 4; 389 | result |= ((uint8_t) tmp) & 0x0F; 390 | } 391 | 392 | (*value) = result; 393 | 394 | return 1; 395 | } 396 | 397 | int8_t read_hex_u8(uint8_t *value) 398 | { 399 | int8_t i, tmp; 400 | uint8_t result = 0; 401 | 402 | for (i = 0; i < 2; i++) { 403 | tmp = read_nibble(); 404 | if (tmp == -1) { 405 | return 0; 406 | } 407 | 408 | result <<= 4; 409 | result |= ((uint8_t) tmp) & 0x0F; 410 | } 411 | 412 | (*value) = result; 413 | 414 | return 1; 415 | } 416 | 417 | int8_t read_hex_u32(uint32_t *value) 418 | { 419 | int8_t i, tmp; 420 | uint32_t result = 0; 421 | 422 | for (i = 0; i < 8; i++) { 423 | tmp = read_nibble(); 424 | if (tmp == -1) { 425 | return 0; 426 | } 427 | 428 | result <<= 4; 429 | result |= ((uint32_t) tmp) & 0x0F; 430 | } 431 | 432 | (*value) = result; 433 | 434 | return 1; 435 | } 436 | 437 | void write_nibble(uint8_t value) 438 | { 439 | if (value < 10) { 440 | Serial.write(value + '0' - 0); 441 | } else { 442 | Serial.write(value + 'A' - 10); 443 | } 444 | } 445 | 446 | void write_hex_u8(uint8_t value) 447 | { 448 | uint8_t i; 449 | 450 | for (i = 0; i < 2; i++) { 451 | write_nibble((uint8_t) ((value >> 4) & 0x0F)); 452 | value <<= 4; 453 | } 454 | } 455 | 456 | void write_hex_u16(uint16_t value) 457 | { 458 | uint8_t i; 459 | 460 | for (i = 0; i < 4; i++) { 461 | write_nibble((uint8_t) ((value >> 12) & 0x0F)); 462 | value <<= 4; 463 | } 464 | } 465 | 466 | // Via http://excamera.com/sphinx/article-crc.html 467 | static const uint32_t crc_table[16] = { 468 | 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 469 | 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 470 | 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 471 | 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c 472 | }; 473 | 474 | uint32_t crc_update(uint32_t crc, uint8_t data) 475 | { 476 | uint8_t tbl_idx; 477 | 478 | tbl_idx = crc ^ (data >> (0 * 4)); 479 | crc = crc_table[tbl_idx & 0x0f] ^ (crc >> 4); 480 | 481 | tbl_idx = crc ^ (data >> (1 * 4)); 482 | crc = crc_table[tbl_idx & 0x0f] ^ (crc >> 4); 483 | 484 | return crc; 485 | } 486 | 487 | uint32_t crc_buffer(void) 488 | { 489 | uint16_t i; 490 | uint32_t crc = ~0L; 491 | 492 | for(i = 0; i < PAGE_SIZE; i++) { 493 | crc = crc_update(crc, buffer[i]); 494 | } 495 | 496 | crc = ~crc; 497 | 498 | return crc; 499 | } 500 | 501 | // --------------------------------------------------------------------------- 502 | // Chip implementation specific code 503 | // --------------------------------------------------------------------------- 504 | 505 | // SPI opcodes 506 | #define WREN 0x06 507 | #define WRDI 0x04 508 | #define RDSR 0x05 509 | #define RDSR2 0x35 510 | #define RDSR3 0x15 511 | #define WRSR 0x01 512 | #define WRSR2 0x31 513 | #define WRSR3 0x11 514 | #define READ 0x03 515 | #define WRITE 0x02 516 | #define SECTOR_ERASE 0x20 517 | #define CHIP_ERASE 0xC7 518 | #define JEDECIDR 0x9F 519 | 520 | #define WPS 0x040000 521 | #define CP 0x000400 522 | #define SRP 0x000180 523 | #define SRP1 0x000100 524 | #define SRP0 0x000080 525 | #define BP 0x00001C 526 | 527 | void impl_enable_write(void) 528 | { 529 | SPI.transfer(WREN); // write enable 530 | } 531 | 532 | void impl_erase_chip(void) 533 | { 534 | SPI.transfer(CHIP_ERASE); 535 | } 536 | 537 | void impl_erase_sector(uint32_t address) 538 | { 539 | SPI.transfer(SECTOR_ERASE); // sector erase instruction 540 | SPI.transfer((address & 0x0FF0) >> 4); // bits 23 to 16 541 | SPI.transfer((address & 0x000F) << 4); // bits 15 to 8 542 | SPI.transfer(0); // bits 7 to 0 543 | } 544 | 545 | void impl_read_page(uint32_t address) 546 | { 547 | uint16_t counter; 548 | 549 | SPI.transfer(READ); // read instruction 550 | SPI.transfer((address >> 8) & 0xFF); // bits 23 to 16 551 | SPI.transfer(address & 0xFF); // bits 15 to 8 552 | SPI.transfer(0); // bits 7 to 0 553 | 554 | // Transfer a dummy page to read data 555 | for(counter = 0; counter < PAGE_SIZE; counter++) { 556 | buffer[counter] = SPI.transfer(0xff); 557 | } 558 | } 559 | 560 | void impl_write_page(uint32_t address) 561 | { 562 | uint16_t counter; 563 | 564 | SPI.transfer(WRITE); // write instruction 565 | SPI.transfer((address >> 8) & 0xFF); // bits 23 to 16 566 | SPI.transfer(address & 0xFF); // bits 15 to 8 567 | SPI.transfer(0); // bits 7 to 0 568 | 569 | for (counter = 0; counter < PAGE_SIZE; counter++) { 570 | SPI.transfer(buffer[counter]); 571 | } 572 | } 573 | 574 | void impl_wait_for_write_enable(void) 575 | { 576 | uint8_t statreg = 0x1; 577 | 578 | while((statreg & 0x1) == 0x1) { 579 | // Wait for the chip 580 | digitalWrite(nCsIo, LOW); 581 | SPI.transfer(RDSR); 582 | statreg = SPI.transfer(RDSR); 583 | digitalWrite(nCsIo, HIGH); 584 | } 585 | } 586 | 587 | void impl_write_protection_check(void) 588 | { 589 | uint32_t statusRegister; 590 | 591 | // Read status register 1 592 | digitalWrite(nCsIo, LOW); 593 | SPI.transfer(RDSR); 594 | statusRegister = ((uint32_t) SPI.transfer(RDSR)); 595 | digitalWrite(nCsIo, HIGH); 596 | 597 | // Read status register 2 598 | digitalWrite(nCsIo, LOW); 599 | SPI.transfer(RDSR2); 600 | statusRegister |= ((uint32_t) SPI.transfer(RDSR2)) << 8; 601 | digitalWrite(nCsIo, HIGH); 602 | 603 | // Read status register 3 604 | digitalWrite(nCsIo, LOW); 605 | SPI.transfer(RDSR3); 606 | statusRegister |= ((uint32_t) SPI.transfer(RDSR3)) << 16; 607 | digitalWrite(nCsIo, HIGH); 608 | 609 | if (statusRegister & SRP1) { 610 | write_hex_u8(WRITE_PROTECTION_CONFIGURATION_LOCKED); 611 | } else { 612 | write_hex_u8(WRITE_PROTECTION_CONFIGURATION_NONE); 613 | } 614 | 615 | if (statusRegister & WPS) { 616 | write_hex_u8(WRITE_PROTECTION_PARTIAL); 617 | return; 618 | } 619 | 620 | // Complement protect 621 | if (statusRegister & CP) { 622 | // Protection is inverted 623 | if ((statusRegister & BP) == BP) { 624 | write_hex_u8(WRITE_PROTECTION_NONE); 625 | return; 626 | } 627 | 628 | write_hex_u8((statusRegister & BP) 629 | ? WRITE_PROTECTION_PARTIAL : WRITE_PROTECTION_FULL); 630 | } else { 631 | // Protection is not inverted 632 | if ((statusRegister & BP) == BP) { 633 | write_hex_u8(WRITE_PROTECTION_FULL); 634 | return; 635 | } 636 | 637 | write_hex_u8((statusRegister & BP) 638 | ? WRITE_PROTECTION_PARTIAL : WRITE_PROTECTION_NONE); 639 | } 640 | } 641 | 642 | void impl_write_protection_disable(void) 643 | { 644 | uint8_t statusRegister; 645 | uint8_t statusRegister2; 646 | 647 | // Read status register 1 648 | digitalWrite(nCsIo, LOW); 649 | SPI.transfer(RDSR); 650 | statusRegister = SPI.transfer(RDSR); 651 | digitalWrite(nCsIo, HIGH); 652 | 653 | // Read status register 2 654 | digitalWrite(nCsIo, LOW); 655 | SPI.transfer(RDSR2); 656 | statusRegister2 = SPI.transfer(RDSR2); 657 | digitalWrite(nCsIo, HIGH); 658 | 659 | // Set chip as writable 660 | digitalWrite(nCsIo, LOW); 661 | SPI.transfer(WREN); // Write enable 662 | digitalWrite(nCsIo, HIGH); 663 | delay(10); 664 | 665 | digitalWrite(nCsIo, LOW); 666 | SPI.transfer(WRSR); // Write register instruction 667 | SPI.transfer(statusRegister & ~BP); // Force SR1 to XXX000XX 668 | digitalWrite(nCsIo, HIGH); 669 | 670 | // Set chip as writable 671 | digitalWrite(nCsIo, LOW); 672 | SPI.transfer(WREN); // Write enable 673 | digitalWrite(nCsIo, HIGH); 674 | delay(10); 675 | 676 | digitalWrite(nCsIo, LOW); 677 | SPI.transfer(WRSR2); // Write register 2 instruction 678 | SPI.transfer(statusRegister2 & ~(CP >> 8)); // Force SR2 to X0XXXXXX 679 | digitalWrite(nCsIo, HIGH); 680 | delay(1); 681 | 682 | impl_wait_for_write_enable(); 683 | } 684 | 685 | void impl_write_protection_enable(void) 686 | { 687 | uint8_t statusRegister; 688 | uint8_t statusRegister2; 689 | 690 | // Read status register 1 691 | digitalWrite(nCsIo, LOW); 692 | SPI.transfer(RDSR); 693 | statusRegister = SPI.transfer(RDSR); 694 | digitalWrite(nCsIo, HIGH); 695 | 696 | // Read status register 2 697 | digitalWrite(nCsIo, LOW); 698 | SPI.transfer(RDSR2); 699 | statusRegister2 = SPI.transfer(RDSR2); 700 | digitalWrite(nCsIo, HIGH); 701 | 702 | // Set chip as writable 703 | digitalWrite(nCsIo, LOW); 704 | SPI.transfer(WREN); // Write enable 705 | digitalWrite(nCsIo, HIGH); 706 | delay(10); 707 | 708 | digitalWrite(nCsIo, LOW); 709 | SPI.transfer(WRSR); // Write register instruction 710 | SPI.transfer(statusRegister | BP); // Force SR1 to XXX111XX 711 | digitalWrite(nCsIo, HIGH); 712 | 713 | // Set chip as writable 714 | digitalWrite(nCsIo, LOW); 715 | SPI.transfer(WREN); // Write enable 716 | digitalWrite(nCsIo, HIGH); 717 | delay(10); 718 | 719 | digitalWrite(nCsIo, LOW); 720 | SPI.transfer(WRSR2); // Write register 2 instruction 721 | SPI.transfer(statusRegister2 & ~(CP >> 8)); // Force SR2 to X0XXXXXX 722 | digitalWrite(nCsIo, HIGH); 723 | delay(1); 724 | 725 | impl_wait_for_write_enable(); 726 | } 727 | 728 | void impl_status_register_read(void) 729 | { 730 | uint8_t statusRegister; 731 | uint8_t statusRegister2; 732 | uint8_t statusRegister3; 733 | 734 | // Read status register 1 735 | digitalWrite(nCsIo, LOW); 736 | SPI.transfer(RDSR); 737 | statusRegister = SPI.transfer(RDSR); 738 | digitalWrite(nCsIo, HIGH); 739 | 740 | // Read status register 2 741 | digitalWrite(nCsIo, LOW); 742 | SPI.transfer(RDSR2); 743 | statusRegister2 = SPI.transfer(RDSR2); 744 | digitalWrite(nCsIo, HIGH); 745 | 746 | // Read status register 3 747 | digitalWrite(nCsIo, LOW); 748 | SPI.transfer(RDSR3); 749 | statusRegister3 = SPI.transfer(RDSR3); 750 | digitalWrite(nCsIo, HIGH); 751 | 752 | // Send status register length 753 | write_hex_u8(0x03); 754 | 755 | // Write register content 756 | write_hex_u8(statusRegister); 757 | write_hex_u8(statusRegister2); 758 | write_hex_u8(statusRegister3); 759 | } 760 | 761 | void impl_jedec_id_read(void) 762 | { 763 | digitalWrite(nCsIo, LOW); 764 | SPI.transfer(JEDECIDR); 765 | write_hex_u8(0x03); 766 | write_hex_u8(SPI.transfer(0x0)); 767 | write_hex_u8(SPI.transfer(0x0)); 768 | write_hex_u8(SPI.transfer(0x0)); 769 | digitalWrite(nCsIo, HIGH); 770 | } 771 | -------------------------------------------------------------------------------- /spi_flash_programmer_client.py: -------------------------------------------------------------------------------- 1 | #!/bin/python3 2 | 3 | import time 4 | import serial 5 | import argparse 6 | import binascii 7 | 8 | import serial.tools.list_ports as list_ports 9 | 10 | COMMAND_HELLO = '>' 11 | 12 | COMMAND_BUFFER_CRC = 'h' 13 | COMMAND_BUFFER_LOAD = 'l' 14 | COMMAND_BUFFER_STORE = 's' 15 | 16 | COMMAND_FLASH_READ = 'r' 17 | COMMAND_FLASH_WRITE = 'w' 18 | COMMAND_FLASH_ERASE_SECTOR = 'k' 19 | 20 | COMMAND_WRITE_PROTECTION_ENABLE = 'p' 21 | COMMAND_WRITE_PROTECTION_DISABLE = 'u' 22 | COMMAND_WRITE_PROTECTION_CHECK = 'x' 23 | COMMAND_STATUS_REGISTER_READ = 'y' 24 | COMMAND_ID_REGISTER_READ = 'i' 25 | 26 | COMMAND_SET_CS_IO = '*' 27 | COMMAND_SET_OUTPUT = 'o' 28 | 29 | WRITE_PROTECTION_NONE = 0x00 30 | WRITE_PROTECTION_PARTIAL = 0x01 31 | WRITE_PROTECTION_FULL = 0x02 32 | WRITE_PROTECTION_UNKNOWN = 0x03 33 | 34 | WRITE_PROTECTION_CONFIGURATION_NONE = 0x00 35 | WRITE_PROTECTION_CONFIGURATION_PARTIAL = 0x01 36 | WRITE_PROTECTION_CONFIGURATION_LOCKED = 0x02 37 | WRITE_PROTECTION_CONFIGURATION_UNKNOWN = 0x03 38 | 39 | DEFAULT_FLASH_SIZE = 4096 * 1024 40 | DEFAULT_SECTOR_SIZE = 4096 41 | DEFAULT_PAGE_SIZE = 256 42 | 43 | ENCODING = 'iso-8859-1' 44 | 45 | DEBUG_NORMAL = 1 46 | DEBUG_VERBOSE = 2 47 | 48 | 49 | class bcolors: 50 | MAGENTA = '\033[95m' 51 | BLUE = '\033[94m' 52 | CYAN = '\033[96m' 53 | GREEN = '\033[92m' 54 | YELLOW = '\033[93m' 55 | RED = '\033[91m' 56 | ENDC = '\033[0m' 57 | BOLD = '\033[1m' 58 | UNDERLINE = '\033[4m' 59 | 60 | 61 | def decorademsg(s: str, color): 62 | print(color + s + bcolors.ENDC) 63 | 64 | 65 | def logMessage(text): 66 | print(text) 67 | 68 | 69 | def logOk(text): 70 | decorademsg(text, bcolors.GREEN) 71 | 72 | 73 | def logError(text): 74 | decorademsg(text, bcolors.RED) 75 | 76 | 77 | def logDebug(text, type): 78 | if type == DEBUG_NORMAL: 79 | 80 | decorademsg(text, bcolors.CYAN) 81 | else: # DEBUG_VERBOSE 82 | decorademsg(text, bcolors.MAGENTA) 83 | 84 | 85 | class progress(): 86 | def __init__(self, max) -> None: 87 | self.max = max 88 | self.act = 0 89 | print("\r\n") 90 | 91 | def show(self, cnt): 92 | self.print_delete_line() 93 | self.act = cnt 94 | print(str(self.act) + " of " + str(self.max)) 95 | 96 | def show_bar(self, cnt): 97 | self.cnt = cnt 98 | percent = int(round(100.0 * self.cnt / self.max)) 99 | barlen = 40 100 | bar = "[" 101 | for i in range(barlen): 102 | if (i < (percent / (100 / barlen))): 103 | bar = bar + "#" 104 | else: 105 | bar = bar + " " 106 | bar = bar + "] " + str(self.cnt) + " of " + str(self.max) 107 | self.print_delete_line() 108 | print(bar) 109 | 110 | def print_delete_line(self): 111 | print("\033[A\033[A") # to clear the previous print 112 | 113 | 114 | class SerialProgrammer: 115 | 116 | def __init__(self, port, baud_rate, debug='off', sector_size=DEFAULT_SECTOR_SIZE, page_size=DEFAULT_PAGE_SIZE): 117 | self.sector_size = sector_size 118 | self.page_size = page_size 119 | self.pages_per_sector = self.sector_size // self.page_size 120 | 121 | if debug == 'normal': 122 | self.debug = DEBUG_NORMAL 123 | elif debug == 'verbose': 124 | self.debug = DEBUG_VERBOSE 125 | else: # off 126 | self.debug = 0 127 | 128 | self._debug('Opening serial connection') 129 | self.sock = serial.Serial(port, baud_rate, timeout=1) 130 | self._debug('Serial connection opened successfully') 131 | 132 | time.sleep(2) # Wait for the Arduino bootloader 133 | 134 | def _debug(self, message, level=DEBUG_NORMAL): 135 | if self.debug >= level: 136 | logDebug(message, level) 137 | 138 | def _readExactly(self, length, tries=3): 139 | """Read exactly n bytes or return None""" 140 | data = b'' 141 | _try = 0 142 | while len(data) < length and _try < tries: 143 | new_data = self.sock.read(length - len(data)) 144 | if new_data == b'': 145 | _try += 1 146 | 147 | data += new_data 148 | 149 | if len(data) != length: 150 | return None 151 | 152 | return data 153 | 154 | def _waitForMessage(self, text, tries=3, max_length=100): 155 | """Wait for the expected message and return True or return False""" 156 | self._debug('Waiting for \'%s\'' % text, DEBUG_VERBOSE) 157 | 158 | data = text.encode(ENCODING) 159 | return self._waitFor(len(data), lambda _data: data == _data, tries, max_length) 160 | 161 | def _waitFor(self, length, check, tries=3, max_length=100): 162 | """Wait for the expected message and return True or return False""" 163 | data = b'' 164 | 165 | _try = 0 166 | while _try < tries: 167 | new_data = self.sock.read(max(length - len(data), 1)) 168 | if new_data == b'': 169 | _try += 1 170 | 171 | max_length -= len(new_data) 172 | if max_length < 0: 173 | return False 174 | 175 | self._debug('Recv: \'%s\'' % new_data.decode(ENCODING), DEBUG_VERBOSE) 176 | 177 | data = (data + new_data)[-length:] 178 | if check(data): 179 | return True 180 | 181 | return False 182 | 183 | def _getUntilMessage(self, text, tries=3, max_length=100): 184 | """Wait for the expected message and return the data received""" 185 | self._debug('Reading until \'%s\'' % text, DEBUG_VERBOSE) 186 | 187 | data = text.encode(ENCODING) 188 | return self._getUntil(len(data), lambda _data: data == _data, tries, max_length) 189 | 190 | def _getUntil(self, length, check, tries=3, max_length=1000): 191 | """Wait for the expected message and return the data received""" 192 | data = b'' 193 | message = b'' 194 | 195 | _try = 0 196 | while _try < tries: 197 | new_data = self.sock.read(max(length - len(message), 1)) 198 | if new_data == b'': 199 | _try += 1 200 | 201 | max_length -= len(new_data) 202 | if max_length < 0: 203 | return None 204 | 205 | self._debug('Recv: \'%s\'' % new_data.decode(ENCODING), DEBUG_VERBOSE) 206 | 207 | message = (message + new_data)[-length:] 208 | data += new_data 209 | if check(message): 210 | return data[:-len(message)] 211 | 212 | return None 213 | 214 | def _dump(self, data_str): 215 | for offset, data_row in [(i, data_str[i:i+16]) for i in range(0, len(data_str), 16)]: 216 | logMessage('%08x: %s' % (offset, ' '.join([data_row[i:i+2] for i in range(0, 16, 2)]))) 217 | return 218 | 219 | def _sendCommand(self, command): 220 | self._debug('Send: \'%s\'' % command, DEBUG_VERBOSE) 221 | 222 | self.sock.write(command.encode(ENCODING)) 223 | self.sock.flush() 224 | 225 | def _eraseSector(self, sector): 226 | self._debug('Command: ERASE_SECTOR %d' % sector) 227 | 228 | self._sendCommand('%s%08x' % (COMMAND_FLASH_ERASE_SECTOR, sector)) 229 | return self._waitForMessage(COMMAND_FLASH_ERASE_SECTOR) 230 | 231 | def _readCRC(self): 232 | self._debug('Command: BUFFER_CRC') 233 | 234 | # Write crc check 235 | self._sendCommand(COMMAND_BUFFER_CRC) 236 | 237 | # Wait for crc start 238 | if not self._waitForMessage(COMMAND_BUFFER_CRC): 239 | self._debug('Invalid / no response for BUFFER_CRC command') 240 | return None 241 | 242 | crc = self._readExactly(8).decode(ENCODING) 243 | if crc is None: 244 | self._debug('Invalid / no CRC response') 245 | return None 246 | 247 | try: 248 | return int(crc, 16) 249 | except ValueError: 250 | self._debug('Could not decode CRC') 251 | return None 252 | 253 | def _loadPageOnce(self, page, tries=3): 254 | """Read a page into the internal buffer""" 255 | self._debug('Command: FLASH_READ %d' % page) 256 | 257 | # Reads page 258 | self._sendCommand('%s%08x' % (COMMAND_FLASH_READ, page)) 259 | 260 | # Wait for read acknowledge 261 | if not self._waitForMessage(COMMAND_FLASH_READ): 262 | self._debug('Invalid / no response for FLASH_READ command') 263 | return None 264 | 265 | crc = self._readExactly(8).decode(ENCODING) 266 | if crc is None: 267 | self._debug('Invalid / no CRC response') 268 | return None 269 | 270 | try: 271 | return int(crc, 16) 272 | except ValueError: 273 | self._debug('Could not decode CRC') 274 | return None 275 | 276 | def _loadPageMultiple(self, page, tries=3): 277 | """Read a page into the internal buffer 278 | 279 | Keeps reading until we get two page reads the same checksum. 280 | """ 281 | self._debug('Command: FLASH_READ_MULTIPLE %d' % page) 282 | 283 | crc_list = [] 284 | _try = 0 285 | 286 | while _try < tries: 287 | crc = self._loadPageOnce(page, tries) 288 | if crc is None: 289 | _try += 1 290 | continue 291 | 292 | if len(crc_list) >= 1: 293 | if crc in crc_list: 294 | self._debug('CRC is valid') 295 | return crc 296 | else: 297 | _try += 1 298 | 299 | crc_list.append(crc) 300 | 301 | self._debug('CRC reads did not match once') 302 | return None 303 | 304 | def _readPage(self, page, tries=3): 305 | """Read a page from the flash and receive it's contents""" 306 | self._debug('Command: FLASH_READ_PAGE %d' % page) 307 | 308 | # Load page into the buffer 309 | crc = self._loadPageMultiple(page, tries) 310 | 311 | for _ in range(tries): 312 | # Dump the buffer 313 | self._sendCommand(COMMAND_BUFFER_LOAD) 314 | 315 | # Wait for data start 316 | if not self._waitForMessage(COMMAND_BUFFER_LOAD): 317 | self._debug('Invalid / no response for BUFFER_LOAD command') 318 | continue 319 | 320 | # Load successful -> read sector with 2 nibbles per byte 321 | page_data = self._readExactly(self.page_size * 2) 322 | if page_data is None: 323 | self._debug('Invalid / no response for page data') 324 | continue 325 | 326 | try: 327 | data = binascii.a2b_hex(page_data.decode(ENCODING)) 328 | if crc == binascii.crc32(data): 329 | self._debug('CRC did match with read data') 330 | return data 331 | else: 332 | self._debug('CRC did not match with read data') 333 | continue 334 | 335 | except TypeError: 336 | self._debug('CRC could not be parsed') 337 | continue 338 | 339 | self._debug('Page read tries exceeded') 340 | return None 341 | 342 | def _writePage(self, page, data): 343 | """Write a page into the buffer and instruct a page write operation 344 | 345 | This operation checks the written data with a generated checksum. 346 | """ 347 | assert len(data) == self.page_size, (len(data), data) 348 | 349 | # Write the page and verify that it was written correctly. 350 | expected_crc = binascii.crc32(data) 351 | encoded_data = binascii.b2a_hex(data) 352 | 353 | self._sendCommand(COMMAND_BUFFER_STORE + encoded_data.decode(ENCODING)) 354 | if not self._waitForMessage(COMMAND_BUFFER_STORE): 355 | self._debug('Invalid / no response for BUFFER_STORE command') 356 | return False 357 | 358 | # This shouldn't fail if we're using a reliable connection. 359 | crc = self._readExactly(8).decode(ENCODING) 360 | if crc is None: 361 | self._debug('Invalid / no CRC response for buffer write') 362 | return None 363 | 364 | try: 365 | if int(crc, 16) != expected_crc: 366 | return None 367 | except ValueError: 368 | self._debug('Could not decode CRC') 369 | return None 370 | 371 | # Write page 372 | self._sendCommand('%s%08x' % (COMMAND_FLASH_WRITE, page)) 373 | time.sleep(.2) # Sleep 200 ms 374 | 375 | if not self._waitForMessage(COMMAND_FLASH_WRITE): 376 | self._debug('Invalid / no response for FLASH_WRITE command') 377 | return False 378 | 379 | # Read back page 380 | # Fail if we can't read what we wrote 381 | read_crc = self._loadPageMultiple(page) 382 | if read_crc is None: 383 | self._debug('Invalid / no CRC response for flash write') 384 | return False 385 | 386 | return (read_crc == expected_crc) 387 | 388 | def _writeSectors(self, offset, data, tries=3): 389 | """Write one or more sectors with data 390 | 391 | This method clears the sectors before writing to them and checks 392 | for valid data via reading each page and comparing the checksum. 393 | """ 394 | assert offset % self.sector_size == 0 395 | pages_offset = offset // self.page_size 396 | sectors_offset = offset // self.sector_size 397 | 398 | assert len(data) % self.sector_size == 0 399 | page_count = len(data) // self.page_size 400 | sector_count = len(data) // self.sector_size 401 | 402 | sector_write_attempt = 0 403 | sector = 0 404 | 405 | p = progress(page_count) 406 | 407 | while sector < sector_count: 408 | sector_index = sectors_offset + sector 409 | 410 | p.show_bar(sector * self.pages_per_sector) 411 | 412 | # Erase sector up to 'tries' times 413 | for _ in range(tries): 414 | if self._eraseSector(sector_index): 415 | break 416 | else: # No erase was successful 417 | logError('Could not erase sector 0x%08x' % sector_index) 418 | return False 419 | 420 | for page in range(self.pages_per_sector): 421 | page_data_index = sector * self.pages_per_sector + page 422 | data_index = page_data_index * self.page_size 423 | page_index = pages_offset + page_data_index 424 | 425 | if self._writePage(page_index, data[data_index: data_index + self.page_size]): 426 | p.show_bar(page_data_index + 1) 427 | continue 428 | 429 | sector_write_attempt += 1 430 | if sector_write_attempt < tries: 431 | break # Retry sector 432 | 433 | logError('Could not write page 0x%08x' % page_index) 434 | return False 435 | 436 | else: # All pages written normally -> next sector 437 | sector += 1 438 | 439 | return True 440 | 441 | def _eraseSectors(self, offset, length, tries=3): 442 | """Clears one or more sectors""" 443 | assert offset % self.sector_size == 0 444 | sectors_offset = offset // self.sector_size 445 | 446 | assert length % self.sector_size == 0 447 | sector_count = length // self.sector_size 448 | 449 | p = progress(sector_count) 450 | for sector in range(sector_count): 451 | sector_index = sectors_offset + sector 452 | 453 | p.show(sector) 454 | 455 | # Erase sector up to 'tries' times 456 | for _ in range(tries): 457 | if self._eraseSector(sector_index): 458 | break 459 | else: # No erase was successful 460 | logError('Could not erase sector %08x' % sector_index) 461 | return False 462 | 463 | p.show(sector_count) 464 | 465 | return True 466 | 467 | def _hello(self): 468 | """Send a hello message and expect a version string""" 469 | self._debug('Command: HELLO') 470 | 471 | # Write hello 472 | self._sendCommand(COMMAND_HELLO) 473 | 474 | # Wait for hello response start 475 | if not self._waitForMessage(COMMAND_HELLO): 476 | self._debug('Invalid / no response for HELLO command') 477 | return None 478 | 479 | message = self._getUntilMessage(COMMAND_HELLO) 480 | if message is None: 481 | self._debug('No termination for HELLO command') 482 | return None 483 | 484 | return message.decode(ENCODING) 485 | 486 | def _read_register(self, cmd, name): 487 | """Generic read register function, send cmd and read a response""" 488 | self._sendCommand(cmd) 489 | if not self._waitForMessage(cmd): 490 | self._debug('Invalid / no response for %s command' % (name,)) 491 | logError('Invalid response') 492 | return None 493 | 494 | length_str = self._readExactly(2).decode(ENCODING) 495 | if length_str is None: 496 | self._debug('Invalid / no response for %s length' % (name,)) 497 | logError('Invalid response') 498 | return None 499 | 500 | try: 501 | length = int(length_str, 16) 502 | except ValueError: 503 | self._debug('Could not decode %s length' % (name,)) 504 | logError('Invalid register length') 505 | return None 506 | 507 | data_str = self._readExactly(length * 2).decode(ENCODING) 508 | if data_str is None: 509 | self._debug('Invalid / no response for %s check' % (name,)) 510 | logError('Invalid response') 511 | return None 512 | 513 | try: # Check if valid data 514 | decoded_data = binascii.a2b_hex(data_str) 515 | except TypeError: 516 | self._debug('Could not decode %s content' % (name)) 517 | logError('Invalid response') 518 | return None 519 | 520 | return data_str 521 | 522 | def hello(self): 523 | """Send a hello message and print the retrieved version string""" 524 | version = self._hello() 525 | if version is None: 526 | logError('Connected to unknown device') 527 | return False 528 | else: 529 | logMessage('Connected to \'%s\'' % version.strip()) 530 | return True 531 | 532 | def writeFromFile(self, filename, flash_offset=0, file_offset=0, length=DEFAULT_SECTOR_SIZE, pad=None): 533 | """Write the data from file to the flash""" 534 | if pad == None: 535 | if (length != -1) and (length % self.sector_size != 0): 536 | logError('length must be a multiple of the sector size %d' % self.sector_size) 537 | return False 538 | 539 | if flash_offset % self.sector_size != 0: 540 | logError('flash_offset must be a multiple of the sector size %d' % self.sector_size) 541 | return False 542 | elif not ((0x0 <= pad) and (pad <= 0xff)): 543 | logError('pad must be in range 0x00--0xff') 544 | return False 545 | 546 | if file_offset < 0: 547 | logError('file_offset must be a positive value or 0') 548 | return False 549 | 550 | data = None 551 | try: 552 | with open(filename, 'rb') as file: 553 | file.seek(file_offset) 554 | data = file.read(length) 555 | except IOError: 556 | logError('Could not read from file \'%s\'' % filename) 557 | return True 558 | 559 | if (length != -1) and (len(data) != length): 560 | logError('File is not large enough to read %d bytes' % length) 561 | return True 562 | 563 | if pad != None: 564 | pad_value = b'%c' % (pad&0xff) 565 | self._debug("Length of data before padding 0x%x" % (len(data),)) 566 | 567 | pad_pre = flash_offset % self.sector_size 568 | self._debug("Pad 0x%x bytes before data" % (pad_pre,)) 569 | data = pad_value*(flash_offset % self.sector_size) + data 570 | 571 | post_pad = self.sector_size - (len(data) % self.sector_size) 572 | if post_pad == self.sector_size: 573 | post_pad = 0x0 574 | self._debug("Pad 0x%x bytes after data" % (post_pad,)) 575 | data = data + pad_value*(post_pad) 576 | 577 | flash_offset = flash_offset & (self.sector_size-0x1) 578 | elif (length == -1) and (len(data) % self.sector_size != 0): 579 | logError('file size must be a multiple of the sector size %d, use --pad' % self.sector_size) 580 | return False 581 | 582 | if not self._writeSectors(flash_offset, data): 583 | logError('Aborting') 584 | else: 585 | logOk('Done') 586 | 587 | return True 588 | 589 | def readToFile(self, filename, flash_offset=0, length=DEFAULT_FLASH_SIZE): 590 | """Read the data from the flash into the file""" 591 | if length % self.page_size != 0: 592 | logError('length must be a multiple of the page size %d' % self.page_size) 593 | return False 594 | 595 | if flash_offset % self.page_size != 0: 596 | logError('flash_offset must be a multiple of the page size %d' % self.page_size) 597 | return False 598 | 599 | page_count = length // self.page_size 600 | pages_offset = flash_offset // self.page_size 601 | 602 | try: 603 | with open(filename, 'wb') as file: 604 | 605 | p = progress(page_count) 606 | for page in range(page_count): 607 | 608 | p.show(page) 609 | 610 | page_index = pages_offset + page 611 | data = self._readPage(page_index) 612 | if data is not None: 613 | file.write(data) 614 | continue 615 | 616 | # Invalid data 617 | logError('Could not read page 0x%08x' % page_index) 618 | return True 619 | 620 | p.show(page_count) 621 | 622 | logOk('Done') 623 | return True 624 | except IOError: 625 | logError('Could not write to file \'%s\'' % filename) 626 | return True 627 | 628 | def verifyWithFile(self, filename, flash_offset=0, file_offset=0, length=DEFAULT_FLASH_SIZE): 629 | """Verify the flash content by checking against the file 630 | 631 | This method only uses checksums to verify the data integrity. 632 | """ 633 | if length % self.page_size != 0: 634 | logError('length must be a multiple of the page size %d' % self.page_size) 635 | return False 636 | 637 | if flash_offset % self.page_size != 0: 638 | logError('flash_offset must be a multiple of the page size %d' % self.page_size) 639 | return False 640 | 641 | page_count = length // self.page_size 642 | pages_offset = flash_offset // self.page_size 643 | 644 | try: 645 | with open(filename, 'rb') as file: 646 | file.seek(file_offset) 647 | 648 | p = progress(page_count) 649 | for page in range(page_count): 650 | 651 | p.show(page) 652 | 653 | data = file.read(self.page_size) 654 | 655 | page_index = pages_offset + page 656 | crc = self._loadPageMultiple(page_index) 657 | if crc is None: 658 | logError('Could not read page 0x%08x' % page_index) 659 | return True 660 | 661 | if crc == binascii.crc32(data): 662 | logOk('Page 0x%08x OK' % page_index) 663 | else: 664 | logError('Page 0x%08x invalid' % page_index) 665 | 666 | p.show(page_count) 667 | 668 | logOk('Done') 669 | return True 670 | except IOError: 671 | logError('Could not write to file \'%s\'' % filename) 672 | return True 673 | 674 | def erase(self, flash_offset=0, length=DEFAULT_FLASH_SIZE): 675 | """Write the data in the file to the flash""" 676 | if length % self.sector_size != 0: 677 | logError('length must be a multiple of the sector size %d' % self.sector_size) 678 | return False 679 | 680 | if flash_offset % self.sector_size != 0: 681 | logError('flash_offset must be a multiple of the sector size %d' % self.sector_size) 682 | return False 683 | 684 | if not self._eraseSectors(flash_offset, length): 685 | logError('Aborting') 686 | else: 687 | logOk('Done') 688 | 689 | return True 690 | 691 | def set_write_protection(self, enable=False): 692 | """Set or clear the write protection of the flash""" 693 | self._debug('Command: WITE_PROTECTION %s' % enable) 694 | 695 | # Write command 696 | if enable: # Enable 697 | self._sendCommand(COMMAND_WRITE_PROTECTION_ENABLE) 698 | if not self._waitForMessage(COMMAND_WRITE_PROTECTION_ENABLE): 699 | self._debug('Invalid / no response for WITE_PROTECTION command') 700 | logError('Invalid response') 701 | return True 702 | else: # Disable 703 | self._sendCommand(COMMAND_WRITE_PROTECTION_DISABLE) 704 | if not self._waitForMessage(COMMAND_WRITE_PROTECTION_DISABLE): 705 | self._debug('Invalid / no response for WITE_PROTECTION command') 706 | logError('Invalid response') 707 | return True 708 | 709 | logOk('Done') 710 | return True 711 | 712 | def check_write_protection(self): 713 | """Check the write protection of the flash""" 714 | self._debug('Command: WITE_PROTECTION_CHECK') 715 | 716 | self._sendCommand(COMMAND_WRITE_PROTECTION_CHECK) 717 | if not self._waitForMessage(COMMAND_WRITE_PROTECTION_CHECK): 718 | self._debug('Invalid / no response for WRITE_PROTECTION_CHECK command') 719 | logError('Invalid response') 720 | return True 721 | 722 | protection = self._readExactly(4).decode(ENCODING) 723 | if protection is None: 724 | self._debug('Invalid / no response for protection check') 725 | logError('Invalid response') 726 | return True 727 | 728 | try: 729 | configuration_protection = int(protection[0:2], 16) 730 | write_protection = int(protection[2:4], 16) 731 | 732 | if configuration_protection == WRITE_PROTECTION_CONFIGURATION_NONE: 733 | logMessage('Configuration is unprotected') 734 | elif configuration_protection == WRITE_PROTECTION_CONFIGURATION_PARTIAL: 735 | logMessage('Configuration is partially protected') 736 | elif configuration_protection == WRITE_PROTECTION_CONFIGURATION_FULL: 737 | logMessage('Configuration is fully protected') 738 | elif configuration_protection == WRITE_PROTECTION_CONFIGURATION_UNKNOWN: 739 | logMessage('Configuration protection is unknown') 740 | else: 741 | logError('Unknown configuration protection status') 742 | 743 | if write_protection == WRITE_PROTECTION_NONE: 744 | logMessage('Flash content is unprotected') 745 | elif write_protection == WRITE_PROTECTION_PARTIAL: 746 | logMessage('Flash content is partially protected') 747 | elif write_protection == WRITE_PROTECTION_FULL: 748 | logMessage('Flash content is fully protected') 749 | elif write_protection == WRITE_PROTECTION_UNKNOWN: 750 | logMessage('Flash content protection is UNKNOWN') 751 | else: 752 | logError('Unknown flash protection status') 753 | 754 | except ValueError: 755 | self._debug('Could not decode protection status') 756 | logError('Invalid protection status') 757 | return True 758 | 759 | logOk('Done') 760 | return True 761 | 762 | def read_status_register(self): 763 | """Reads the status register contents""" 764 | self._debug('Command: STATUS_REGISTER') 765 | data = self._read_register(COMMAND_STATUS_REGISTER_READ, 'STATUS_REGISTER') 766 | if data==None: 767 | return True 768 | 769 | self._dump(data) 770 | return True 771 | 772 | def read_id_register(self): 773 | """Reads the id register contents""" 774 | self._debug('Command: ID_REGISTER') 775 | data = self._read_register(COMMAND_ID_REGISTER_READ, 'ID_REGISTER') 776 | if data==None: 777 | return True 778 | 779 | self._dump(data) 780 | return True 781 | 782 | def set_cs_io(self, io): 783 | """Overrides the CS/SS IO of Arduino""" 784 | self._debug('Command: SET_CS_IO') 785 | 786 | self._sendCommand('%s%02x' % (COMMAND_SET_CS_IO, io)) 787 | if not self._waitForMessage(COMMAND_SET_CS_IO): 788 | self._debug('Invalid / no response for SET_CS_IO command') 789 | logError('Invalid response') 790 | return True 791 | 792 | return True 793 | 794 | def set_output(self, io, value): 795 | """Set IO pin to OUTPUT""" 796 | self._debug('Command: SET_OUTPUT') 797 | 798 | if value==None: 799 | value=0x00 800 | else: 801 | value=value&0xf 802 | if value&0xf!=0x0: 803 | value=0x1 804 | value=value|0x10 805 | 806 | self._sendCommand('%s%02x%02x' % (COMMAND_SET_OUTPUT, io, value)) 807 | if not self._waitForMessage(COMMAND_SET_OUTPUT): 808 | self._debug('Invalid / no response for SET_OUTPUT command') 809 | logError('Invalid response') 810 | return True 811 | 812 | return True 813 | 814 | 815 | def printComPorts(): 816 | logMessage('Available COM ports:') 817 | 818 | for i, port in enumerate(list_ports.comports()): 819 | logMessage('%d: %s' % (i+1, port.device)) 820 | 821 | logOk('Done') 822 | 823 | 824 | def main(): 825 | def hex_dec(x): 826 | # use auto detect mode, supports 0bYYYY=binary, 0xYYYY=hex, YYYY=decimal 827 | return int(x,0) 828 | 829 | parser = argparse.ArgumentParser(description='Interface with an Arduino-based SPI flash programmer') 830 | parser.add_argument('-d', dest='device', default='COM1', 831 | help='serial port to communicate with') 832 | parser.add_argument('-f', dest='filename', default='flash.bin', 833 | help='file to read from / write to') 834 | parser.add_argument('-l', type=hex_dec, dest='length', default=DEFAULT_FLASH_SIZE, 835 | help='length to read/write in bytes, use -1 to write entire file') 836 | 837 | parser.add_argument('--rate', type=int, dest='baud_rate', default=115200, 838 | help='baud-rate of serial connection') 839 | parser.add_argument('--flash-offset', type=hex_dec, dest='flash_offset', default=0, 840 | help='offset for flash read/write in bytes') 841 | parser.add_argument('--file-offset', type=hex_dec, dest='file_offset', default=0, 842 | help='offset for file read/write in bytes') 843 | parser.add_argument('--pad', type=hex_dec, default=None, 844 | help='pad value if file is not algined with SECTOR_SIZE') 845 | parser.add_argument('--debug', choices=('off', 'normal', 'verbose'), default='off', 846 | help='enable debug output') 847 | parser.add_argument('--io', type=hex_dec, default=None, 848 | help="IO pin used for set-cs-io and set-output") 849 | parser.add_argument('--value', type=hex_dec, default=None, 850 | help="value used for set-output") 851 | 852 | parser.add_argument('command', choices=('ports', 'write', 'read', 'verify', 'erase', 853 | 'enable-protection', 'disable-protection', 'check-protection', 854 | 'status-register', 'id-register', 'set-cs-io', 'set-output'), 855 | help='command to execute') 856 | 857 | args = parser.parse_args() 858 | if args.command == 'ports': 859 | printComPorts() 860 | return 861 | 862 | try: 863 | programmer = SerialProgrammer(args.device, args.baud_rate, args.debug) 864 | except serial.SerialException: 865 | logError('Could not connect to serial port %s' % args.device) 866 | return 867 | 868 | def write(args, prog): 869 | return prog.writeFromFile(args.filename, args.flash_offset, args.file_offset, args.length, args.pad) 870 | 871 | def read(args, prog): 872 | return prog.readToFile(args.filename, args.flash_offset, args.length) 873 | 874 | def verify(args, prog): 875 | return prog.verifyWithFile(args.filename, args.flash_offset, args.file_offset, args.length) 876 | 877 | def erase(args, prog): 878 | return prog.erase(args.flash_offset, args.length) 879 | 880 | def enable_protection(args, prog): 881 | return prog.set_write_protection(True) 882 | 883 | def disable_protection(args, prog): 884 | return prog.set_write_protection(False) 885 | 886 | def check_protection(args, prog): 887 | return prog.check_write_protection() 888 | 889 | def read_status_register(args, prog): 890 | return prog.read_status_register() 891 | 892 | def read_id_register(args, prog): 893 | return prog.read_id_register() 894 | 895 | def set_cs_io(args, prog): 896 | return prog.set_cs_io(args.io) 897 | 898 | def set_output(args, prog): 899 | return prog.set_output(args.io, args.value) 900 | 901 | commands = { 902 | 'write': write, 903 | 'read': read, 904 | 'verify': verify, 905 | 'erase': erase, 906 | 'enable-protection': enable_protection, 907 | 'disable-protection': disable_protection, 908 | 'check-protection': check_protection, 909 | 'status-register': read_status_register, 910 | 'id-register': read_id_register, 911 | 'set-cs-io': set_cs_io, 912 | 'set-output': set_output 913 | } 914 | 915 | if args.command not in commands: 916 | logError('Invalid command \'%d\'' % args.command) 917 | parser.print_help() 918 | return 919 | 920 | if not programmer.hello(): 921 | # Unrecognized device 922 | parser.print_help() 923 | return 924 | 925 | if not commands[args.command](args, programmer): 926 | # Command got invalid arguments 927 | parser.print_help() 928 | 929 | 930 | if __name__ == '__main__': 931 | main() --------------------------------------------------------------------------------