-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathwinbond.py
349 lines (296 loc) · 13 KB
/
winbond.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
"""
Implementation to interact with Winbond W25Q Flash with software reset.
Credits & kudos to crizeo
Taken from https://forum.micropython.org/viewtopic.php?f=16&t=3899
"""
from micropython import const
import time
class W25QFlash(object):
SECTOR_SIZE = const(4096)
BLOCK_SIZE = const(512)
PAGE_SIZE = const(256)
def __init__(self, spi, cs, baud=40000000, software_reset=True):
self.cs = cs
self.spi = spi
self.cs.init(self.cs.OUT, value=1)
# highest possible baudrate is 40 MHz for ESP-12
self.spi.init(baudrate=baud, phase=1, polarity=1)
self._busy = False
if software_reset:
self.reset()
# buffer for writing single blocks
self._cache = bytearray(self.SECTOR_SIZE)
# calc number of bytes (and makes sure the chip is detected and
# supported)
self.identify()
# address length (default: 3 bytes, 32MB+: 4)
self._ADR_LEN = 3 if (len(bin(self._CAPACITY-1))-2) <= 24 else 4
# setup address mode:
if self._ADR_LEN == 4:
if not self._read_status_reg(nr=16): # not in 4-byte mode
print("entering 4-byte address mode")
self._await()
self.cs(0)
self.spi.write(b'\xB7') # 'Enter 4-Byte Address Mode'
self.cs(1)
def reset(self) -> None:
"""
Reset the Winbond flash if the device has no hardware reset pin.
See datasheet section 7.2.43 Enable Reset (66h) and Reset (99h)
Because of the small package and the limitation on the number of pins,
the W25Q64FV provide a software Reset instruction instead of a
dedicated RESET pin. Once the Reset instruction is accepted, any
on-going internal operations will be terminated and the device will
return to its default power-on state and lose all the current volatile
settings, such as Volatile Status Register bits, Write Enable Latch
(WEL) status, Program/Erase Suspend status, Read parameter setting
(P7-P0), Continuous Read Mode bit setting (M7-M0) and Wrap Bit setting
(W6-W4).
"Enable Reset (66h)" and "Reset (99h)" instructions can be issued in
either SPI mode or QPI mode. To avoid accidental reset, both
instructions must be issued in sequence. Any other commands other than
"Reset (99h)" after the "Enable Reset (66h)" command will disable the
"Reset Enable" state. A new sequence of "Enable Reset (66h)" and
"Reset (99h)" is needed to reset the device. Once the Reset command is
accepted by the device, the device will take approximately tRST=30us
to reset. During this period, no command will be accepted.
Data corruption may happen if there is an on-going or suspended
internal Erase or Program operation when Reset command sequence is
accepted by the device. It is recommended to check the BUSY bit and
the SUS bit in Status Register before issuing the Reset command
sequence.
"""
if self._busy:
self._await()
self._busy = True
self.cs(0)
self.spi.write(b'\x66') # 'Enable Reset' command
self.cs(1)
self.cs(0)
self.spi.write(b'\x99') # 'Reset' command
self.cs(1)
time.sleep_us(30)
self._busy = False
# print('Reset performed')
def identify(self) -> None:
"""
Identify the Winbond chip.
Determine the manufacturer and device ID and raises an error if the
device is not detected or not supported.
The capacity variable is set to the number of blocks (calculated based
on the detected chip).
"""
self._await()
self.cs(0)
self.spi.write(b'\x9F') # 'Read JEDEC ID' command
# manufacturer id, memory type id, capacity id
mf, mem_type, cap = self.spi.read(3, 0x00)
self.cs(1)
self._CAPACITY = int(2**cap)
if not (mf and mem_type and cap): # something is 0x00
raise OSError("device not responding, check wiring. ({}, {}, {})".
format(hex(mf), hex(mem_type), hex(cap)))
if mf != 0xEF or mem_type not in [0x40, 0x60]:
# Winbond manufacturer, Q25 series memory (tested 0x40 only)
raise OSError("manufacturer ({}) or memory type ({}) unsupported".
format(hex(mf), hex(mem_type)))
print("manufacturer: {}".format(hex(mf))) # 0xef
print("mem_type: {}".format(mem_type))
print("device: {}".format(hex(mem_type << 8 | cap))) # 0x4016
print("capacity: {} bytes".format(self._CAPACITY)) # 4194304 bytes
# return self._CAPACITY # calculate number of bytes
def get_size(self) -> int:
"""
Get the flash chip size.
:returns: The flash size in byte.
:rtype: int
"""
return self._CAPACITY
def format(self) -> None:
"""
Format the Winbond flash chip by resetting all memory to 0xFF.
Important: Run "os.VfsFat.mkfs(flash)" to make the flash an accessible
file system. As always, you will then need to run
"os.mount(flash, '/MyFlashDir')" then to mount the flash
"""
self._wren()
self._await()
self.cs(0)
self.spi.write(b'\xC7') # 'Chip Erase' command
self.cs(1)
self._await() # wait for the chip to finish formatting
def _read_status_reg(self, nr) -> int:
"""
Read a status register.
:param nr: Register number to read
:type nr: int
:returns: The value (0 or 1) in status register (S0, S1, S2, ...)
:rtype: int
"""
reg, bit = divmod(nr, 8)
self.cs(0)
# 'Read Status Register-...' (1, 2, 3) command
self.spi.write((b'\x05', b'\x35', b'\x15')[reg])
stat = 2**bit & self.spi.read(1, 0xFF)[0]
self.cs(1)
return stat
def _await(self) -> None:
"""
Wait for device not to be busy
"""
self._busy = True
self.cs(0)
self.spi.write(b'\x05') # 'Read Status Register-1' command
# last bit (1) is BUSY bit in stat. reg. byte (0 = not busy, 1 = busy)
while 0x1 & self.spi.read(1, 0xFF)[0]:
pass
self.cs(1)
self._busy = False
def _sector_erase(self, addr) -> None:
"""
Resets all memory within the specified sector (4kB) to 0xFF
:param addr: The address
:type addr: int
"""
self._wren()
self._await()
self.cs(0)
self.spi.write(b'\x20') # 'Sector Erase' command
self.spi.write(addr.to_bytes(self._ADR_LEN, 'big'))
self.cs(1)
def _read(self, buf: list, addr: int) -> None:
"""
Read the length of the buffer bytes from the chip.
The buffer length has to be a multiple of self.SECTOR_SIZE (or less).
:param buf: The buffer
:type buf: list
:param addr: The start address
:type addr: int
"""
assert addr+len(buf) <= self._CAPACITY, \
"memory not addressable at %s with range %d (max.: %s)" % \
(hex(addr), len(buf), hex(self._CAPACITY-1))
# print("read {} bytes starting at {}".format(len(buf), hex(addr)))
self._await()
self.cs(0)
# 'Fast Read' (0x03 = default), 0x0C for 4-byte mode command
self.spi.write(b'\x0C' if self._ADR_LEN == 4 else b'\x0B')
self.spi.write(addr.to_bytes(self._ADR_LEN, 'big'))
self.spi.write(b'\xFF') # dummy byte
self.spi.readinto(buf, 0xFF)
self.cs(1)
def _wren(self) -> None:
"""
Set the Write Enable Latch (WEL) bit in the status register
"""
self._await()
self.cs(0)
self.spi.write(b'\x06') # 'Write Enable' command
self.cs(1)
def _write(self, buf: list, addr: int) -> None:
"""
Write the data of the given buffer to the address location
Writes the data from <buf> to the device starting at <addr>, which
has to be erased (0xFF) before. Last byte of <addr> has to be zero,
which means <addr> has to be a multiple of self.PAGE_SIZE (= start
of page), because wrapping to the next page (if page size exceeded)
is implemented for full pages only. Length of <buf> has to be a
multiple of self.PAGE_SIZE, because only full pages are supported at
the moment (<addr> will be auto-incremented).
:param buf: The data buffer to write
:type buf: list
:param addr: The starting address
:type addr: int
"""
assert len(buf) % self.PAGE_SIZE == 0, \
"invalid buffer length: {}".format(len(buf))
assert not addr & 0xf, \
"address ({}) not at page start".format(addr)
assert addr+len(buf) <= self._CAPACITY, \
("memory not addressable at {} with range {} (max.: {})".
format((hex(addr), len(buf), hex(self._CAPACITY-1))))
# print("write buf[{}] to {} ({})".format(len(buf), hex(addr), addr))
for i in range(0, len(buf), self.PAGE_SIZE):
self._wren()
self._await()
self.cs(0)
self.spi.write(b'\x02') # 'Page Program' command
self.spi.write(addr.to_bytes(self._ADR_LEN, 'big'))
self.spi.write(buf[i:i+self.PAGE_SIZE])
addr += self.PAGE_SIZE
self.cs(1)
def _writeblock(self, blocknum: int, buf: list) -> None:
"""
Write a data block.
To write a block, the sector (e.g. 4kB = 8 blocks) has to be erased
first. Therefore, a sector will be read and saved in cache first,
then the given block will be replaced and the whole sector written
back when
:param blocknum: The block number
:type blocknum: int
:param buf: The data buffer
:type buf: list
"""
assert len(buf) == self.BLOCK_SIZE, \
"invalid block length: {}".format(len(buf))
# print("writeblock({}, buf[{}])".format(blocknum, len(buf)))
sector_nr = blocknum // 8
sector_addr = sector_nr * self.SECTOR_SIZE
# index of first byte of page in sector (multiple of self.PAGE_SIZE)
index = (blocknum << 9) & 0xfff
self._read(buf=self._cache, addr=sector_addr)
self._cache[index:index+self.BLOCK_SIZE] = buf # apply changes
self._sector_erase(addr=sector_addr)
# addr is multiple of self.SECTOR_SIZE, so last byte is zero
self._write(buf=self._cache, addr=sector_addr)
def readblocks(self, blocknum: int, buf: list) -> None:
"""
Read a data block. The length has to be a multiple of self.BLOCK_SIZE
:param blocknum: The starting block number
:type blocknum: int
:param buf: The data buffer
:type buf: list
"""
assert len(buf) % self.BLOCK_SIZE == 0, \
'invalid buffer length: {}'.format(len(buf))
buf_len = len(buf)
if buf_len == self.BLOCK_SIZE:
self._read(buf=buf, addr=blocknum << 9)
else:
offset = 0
buf_mv = memoryview(buf)
while offset < buf_len:
self._read(buf=buf_mv[offset:offset+self.BLOCK_SIZE],
addr=blocknum << 9)
offset += self.BLOCK_SIZE
blocknum += 1
def writeblocks(self, blocknum: int, buf: list) -> None:
"""
Write a data block.The length has to be a multiple of self.BLOCK_SIZE
:param blocknum: The block number
:type blocknum: int
:param buf: The data buffer
:type buf: list
"""
assert len(buf) % self.BLOCK_SIZE == 0, \
'invalid buffer length: {}'.format(len(buf))
buf_len = len(buf)
if buf_len == self.BLOCK_SIZE:
self._writeblock(blocknum=blocknum, buf=buf)
else:
offset = 0
buf_mv = memoryview(buf)
while offset < buf_len:
self._writeblock(blocknum=blocknum,
buf=buf_mv[offset:offset+self.BLOCK_SIZE])
offset += self.BLOCK_SIZE
blocknum += 1
def count(self) -> int:
"""
Return the number of blocks available on the device
:returns: Number of blocks
:rtype: int
"""
return int(self._CAPACITY / self.BLOCK_SIZE)