├── .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 | | Chip pin | Arduino pin |
36 | | 1 /SS | 10 |
37 | | 2 MISO | 12 |
38 | | 3 /WP | +3.3V |
39 | | 4 GND | GND |
40 | | 5 MOSI | 11 |
41 | | 6 SCK | 13 |
42 | | 7 /HOLD | +3.3V |
43 | | 8 VDD | +3.3V |
44 |
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()
--------------------------------------------------------------------------------