[Tutor] Help with building bytearray arrays

Chip Wachob wachobc at gmail.com
Sun Sep 9 17:06:57 EDT 2018


Cameron, et al.

First off, thank you for being patient with me.  I'm not used to the
email list communication style.

Since Cameron's response was the one that raised the most questions /
comments, I'm going to reply to it.

Inline.. now that I know that this is the preferred method...

Before I jump in, the 1000 foot view is I have to send an array of 512
bytes down the SPI loop, and read back 512 bytes that were latched in
from a control interface.  Unfortunately, there's a glitch in the FTDI
part and I can't just send the 512 bytes.. the part times out and and
causes the script to terminate early...  So, my solution was to
'chunk' the data into smaller groups which the part _can_ handle.
This works fine until I come to the point where I concatenate the
'chunks' into my 512 byte array..  which other functions will then
process.

The libraries that I'm using are from the project link below.  I'm
learning from all of you how much code to post, I didn't want to post
the entire library, but the way it's set up it is hard not to.. as has
been pointed out.. methods of classes and then there's other files
that contain some of the low-level workings..

https://github.com/adafruit/Adafruit_Python_GPIO

Anyhow, on with the 'show'..


On Sat, Sep 8, 2018 at 6:49 AM, Cameron Simpson <cs at cskk.id.au> wrote:
> On 08Sep2018 20:01, Cameron Simpson <cs at cskk.id.au> wrote:
>>>
>>> So, if I'm understanding the transfer() function correctly, the
>>> function takes and returns a bytearray type.
>>
>>
>> It would be good to see the specification for the transfer function. They
>> we can adhere to its requirements. Can you supply a URL?
>
>
> I see you supplied the whole transfer function in your first message :-(

Yes, I realize now that I left out important information.  I have my
own transfer function which is the supplied transfer function with a
bunch of GPIO wrapped around it, and the 'chunking'.  This way I can
call it when I need to send and receive data from the loop, which is
often.  In the end, I will have to talk with several different SPI
devices, all of which will have to have different GPIO line wiggling
going on before and after they call the spi.transfer.

You're walk-through is very helpful.

>
> I'll recite it here and walk through its contents to explain:
>
>    def transfer(self, data):
>
> Ok, this is a method, a particular type of function associated with a class
> instance. (All objects are class instances, and the class is their type.)
>
> So to call this you would normally have a control object of some kind. [...]
> Ah, it looks like you should have an SpiDev instance, inferring from this
> code:
>
>
> https://github.com/adafruit/Adafruit_Python_GPIO/blob/master/Adafruit_GPIO/SPI.py
>
> So suppose you've got such an object and a variable referring to it, let's
> say it is named "spi". You'd normally call the transfer function like this:
>
>  spi.transfer(some_data)
>
> instead of calling transfer() "bare", as it were.

Again, I was generalizing and also being somewhat lazy and not typing
it all... my bad.

>
> When you call a Python method, the instance itself is implicitly passed as
> the parameter "self" (well, the first parameter - in Python we always call
> this "self" like C++ uses "this").

Ah, the one 'thorn' in my side is "this".  I have considerable
difficulty with the 'this' concept.  That probably means that my code
could be 'better', but I tend to avoid "this" like the plague.

>
>        """Full-duplex SPI read and write.  The specified array of bytes will
> be
>        clocked out the MOSI line, while simultaneously bytes will be read
> from
>        the MISO line.  Read bytes will be returned as a bytearray object.
>        """
>
> This is a docstring. It is like a comment but it gets attached to the
> function and can be inspected. Not your problem.
>
>        # Build command to read and write SPI data.
>        command = 0x30 | (self.lsbfirst << 3) | (self.read_clock_ve << 2) |
> self.write_clock_ve
>
> This constructs a value just as you would in C.
>
>        logger.debug('SPI transfer with command {0:2X}.'.format(command))
>
> Write a debugging message.

I saw this, but I don't know how to turn it 'on' so I could see the
log messages.  I wouldn't mind doing this with my code as well.
Perhaps creating my own log?  Right now, I have a verbose flag that I
pass to all my functions.. called disp.  Then for each place where I
want to see a value, etc, I have a line that reads:  if(disp): print "
What data is this ", data

>
>        # Compute length low and high bytes.
>        # NOTE: Must actually send length minus one because the MPSSE engine
>        # considers 0 a length of 1 and FFFF a length of 65536
>        length = len(data)
>        len_low  = (length-1) & 0xFF
>        len_high = ((length-1) >> 8) & 0xFF
>
> All just like C.
>
>        # Send command and length.
>        self._assert_cs()
>
> I would guess that this raises a control signal. Ah, RS-232? So
> clear-to-send then.

This is actually a chip select line that electrically wiggles a pin on
a device telling it 'Hey, I'm talking to YOU".

>
>        self._ft232h._write(str(bytearray((command, len_low, len_high))))
>
> Ok, it looks like this is Python 2, not Python 3. Let's unpack it.

Yes, Adafruit insists that this work has to be done in Python 2.7.  I
started working on this and tried to use Python 3.x but things were
failing all over the place.  Unfortunately, at the time I didn't
understand that Python 3 wasn't backward compatible with Python 2.x,
1.x, etc..  Lesson learned.

>
> "(command, len_low, len_high)" is a tuple of 3 values. A tuple is like a
> read only list. We're passing that to the bytearray() constructor, which
> will accept an iterable of values and make a bytes buffer. And we want to
> write that to "self._ft232h". Which expects a str, which is why I think this
> is Python 2. In Python 2 there's no "bytes" type and str is an immutable
> array of 8-bit character values. So writing bytes uses str.

Okay, so in Python 2 (the world I'm left to live within) if I want to
handle bytes, they actually have to be strings?  This sort of makes
sense.  When I try to display the contents of my arrays via a print
statement, I will get \xnn for my values, but at some point the nn
becomes what appears to be ASCII characters.  Sometimes I get, what I
would call, extended ASCII 'art' when I try to print the arrays.
Interesting art, but not very helpful in the information department.

>
> So this tabkes a tuple of values, to be bytes, makes those into a bytearray
> and makes that into a str, and writes that str to the serial line.
>
>        self._ft232h._write(str(bytearray(data)))
>
> We write that data itself.
>
>        self._ft232h._write('\x87')
>        self._deassert_cs()
>
> And here we lower the control signal.
>
>        # Read response bytes.
>        return bytearray(self._ft232h._poll_read(length))
>
> This calls the _poll_read method, presumably requesting "length" bytes, the
> same length as the supplied data. I presume we get a str back: we make a new
> bytearray with those bytes in it and return it.
>
> So you do get a new bytearray back from each call, so we can accumulate them
> into a list and then join them together later to make a single bytearray.

faffing is a new term, but given the context I'm guessing it is
equivalent to 'mucking about' or more colorful wording which I won't
even attempt to publish here.

>
> Why this faffing about with str and bytearray? Probably for Python 2/3
> compatibility, and because you want to deal with bytes (small ints) instead
> of characters. Ignore it: we're dealing with bytes.

More questions in the next installment since the reconstruction
methods are discussed there..

>
> Cheers,
> Cameron Simpson <cs at cskk.id.au>

Thank you all.


More information about the Tutor mailing list