Skip to content
This repository was archived by the owner on Sep 30, 2019. It is now read-only.

Commit 1f974c7

Browse files
author
mpratt14
authored
Add SPI method: bulkread
Designed for communication with SPI Flash memory chips. Single-use or combinations of the existing methods are not efficient or functional for the use case of NOR/NAND flash chip reading and writing. It is essential that between reading and writing the Chip Select remains asserted (High) for the entire period. Usage of the `write` and `read` methods is therefore not possible in combination because each of them de-asserts CS at the end of the method. There is no need for that behavior to change though. The `transfer` method is inefficient because it sends a command to the FT232H that not only allows Full-Duplex but actually REQUIRES it for a command to complete. This is due to the design of the MPSSE. If I use the same FT232H command and attempt to read more than I am writing, a timeout occurs. In other words, the FT232H only allows communication at all during a Full-duplex command when there is data to be handled in Full-duplex, the amount you read MUST BE equal to the amount you write. For example, an SPI read of a NOR flash chip: After instructing the FT232H to open the connection to the flash chip, you write the command to the slave as 1 byte, then the address as 3 bytes and the chip responds AFTER the write, not DURING, for as long as CS is asserted. In order to read only 1 of the 64 blocks of a 4 MB flash chip using the `transfer` method, I would have to append 65,532 zeros to the array that I am writing to the chip to keep the connection alive for each iteration of the loop, which is mandated both by the slave (NOR Flash) with CS, and by the master (FT232H) with the full-duplex command. Again, half-duplex read then write is a very simple matter, as with any SPI slave, that would be treated as two separate commands, `read` and then `write`. But on many SPI slaves, half-duplex write then read is almost always in the same instruction, where CS assertion must not be interrupted between commands of the master (FT232H), and this is a very popular implementation. I have also designed this method to solve the issue #94 that I posted For a while, I thought it should work with a single write command and two poll reads ...not true... in parallel order, double read still doesnt fix it: ``` spi._ft232h._write(str(bytearray((commandR, len_lowR, len_highR)))) spi._ft232h._write(str(bytearray((commandR, len_lowR, len_highR)))) payload1 = spi._ft232h._poll_read(lengthR) payload2 = spi._ft232h._poll_read(lengthR) ``` but in series order, double read does give clean output: ``` spi._ft232h._write(str(bytearray((commandR, len_lowR, len_highR)))) payload1 = spi._ft232h._poll_read(lengthR) spi._ft232h._write(str(bytearray((commandR, len_lowR, len_highR)))) payload2 = spi._ft232h._poll_read(lengthR) ``` This is tested both in situations where the expected output is all ff, the expected output is all 00, or the expected output is real life data, confirmed with CRC32. My take on this is basically sacrificing a little more python environment memory, to save the chips buffer from suffering. I am convinced that the problem is NOT with the poll_read function. I am not sure if it is just my chip with this problem or many more, but this surely prevents it from appearing during heavy use, at practically any frequency. I have tried setting my clock as low as 1 MHz. Therefore I propose this method, bulkread (I can't think of a better name, maybe readblock) with the following features... - both the array to write and the length to read back are optional (one or the other), configurable, and with defaults - allows a read up to the limit of the FT232H capabilities, both hard limit in the datasheet (64 KB) by ensuring input is within the range... - and soft limit handling from data corruption issue observed in my real life testing by simply performing two separate poll reads (see #94) - Warning the user about the data length limits - Deasserting CS is the VERY LAST action sent to the FT232H - the buffer flush command (0x87) is not used, in my testing, it makes no difference, less talking to the master means more talking to the slave. - a very simple mode setting, to allow writing only with the same method, not having to change methods by setting readmode to 0 - all math is moved to the top, before anything is pushed to the device Similar logic should be applied to every other method in the library, to inform the user before errors occur, and prevent errors due to (most likely) hardware defects. @ladyada @tdicola Please help me out with the logger.debug line...I'm not sure about the syntax or purpose or usage of it
1 parent ede3d7d commit 1f974c7

File tree

1 file changed

+45
-0
lines changed

1 file changed

+45
-0
lines changed

Adafruit_GPIO/FT232H.py

+45
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,51 @@ def read(self, length):
504504
self._deassert_cs()
505505
# Read response bytes.
506506
return bytearray(self._ft232h._poll_read(length))
507+
508+
def readblock(self, data = [], lengthR = 'None', readmode = 1):
509+
"""Half-duplex SPI write then read. Send command and payload to slave as bytearray
510+
then consequently read out response from the slave for length in bytes.
511+
Designed for use with NOR or NAND flash chips, and possibly SD cards...etc...
512+
Read command is cut in half and performed twice in series to prevent single byte errors.
513+
Hardware limits per command are enforced before doing anything.
514+
Read length is an optional argument, so that it can function similar to transfer
515+
but still half-duplex.
516+
For reading without writing, one can send a blank array or skip that argument.
517+
"""
518+
#check for hardware limit of FT232H and similar MPSSE chips
519+
if (1 > lengthR > 65536)|(len(data) > 65536):
520+
print 'the FTDI chip is limited to 65536 bytes (64 KB) of input/output per command!'
521+
print 'use for loops for larger reads'
522+
exit(1)
523+
#default mode is to act like `transfer` but half-duplex
524+
if (lengthR == 'None')&(readmode == 1):
525+
lengthR = len(data)
526+
#command parameters definition and math
527+
#MPSSE engine sees length 0 as 1 byte, so - 1 lengths
528+
commandW = 0x10 | (spi.lsbfirst << 3) | spi.write_clock_ve
529+
lengthW = len(data) - 1
530+
len_lowW = (lengthW) & 0xFF
531+
len_highW = ((lengthW) >> 8) & 0xFF
532+
commandR = 0x20 | (spi.lsbfirst << 3) | (spi.read_clock_ve << 2)
533+
lengthR = lengthR/2
534+
len_lowR = (lengthR-1) & 0xFF
535+
len_highR = ((lengthR-1) >> 8) & 0xFF
536+
#logger debug info
537+
#logger.debug('SPI bulkread with write command {0:2X}.'.format(commandW))
538+
#logger.debug('and read command {0:2X}.'.format(commandR))
539+
#begin command set
540+
spi._assert_cs()
541+
#write command, these have to be separated due to TypeError
542+
spi._ft232h._write(str(bytearray((commandW, len_lowW, len_highW))))
543+
spi._ft232h._write(str(bytearray(data)))
544+
#read command, which is now divided into two commands
545+
spi._ft232h._write(str(bytearray((commandR, len_lowR, len_highR))))
546+
payload1 = spi._ft232h._poll_read(lengthR)
547+
spi._ft232h._write(str(bytearray((commandR, len_lowR, len_highR))))
548+
payload2 = spi._ft232h._poll_read(lengthR)
549+
#end command set
550+
spi._deassert_cs()
551+
return bytearray(payload1 + payload2)
507552

508553
def transfer(self, data):
509554
"""Full-duplex SPI read and write. The specified array of bytes will be

0 commit comments

Comments
 (0)