SPI bus not behaving as expected on the timing

Hello.

We are using IR communication through an SPI bus, but we are getting some weird behavior on the SPI clock. It looks like the following picture:

We can’t really understand why there is a pause every 8 clock pulse. Is there any obvious reason why there is a delay?

We are using a Verdin imx8m-mini and it is running a python software using the SpiDev library.

Hi @edmirsuljicOIM !

Could you please share more information?

  • Which exact Verdin iMX8M Mini are you using? Also its version.
  • Which carrier board are you using? If it is a carrier board from Toradex, please share its version as well.
  • How long is the pause? Does it always have the same length?
  • For completeness, what the pause causes on your IR device?
  • If you do not use the Python SpiDev library, do you still face the same issue?

Best regards,

  • We are using the V1.1B
  • We are using a custom board with SPI_1
  • The pause is around 125 micro seconds a changes when we change the baud rate but we cannot make it disappear really.
  • We are trying to read a continuous data stream which gets unreadable with the delay.
  • I wouldn’t know how to use any other library for python to communicate with the SPI interface

One suggestion we saw somewhere is to start looking at the device trees. Is there any chance that the device tree has a delay for the SPI?
We are currently using a custom verdin-imx8mm-wifi-dahlia device tree.

Hi @edmirsuljicOIM, how are you?

Can you please share us a few more details?

  • We’d need the full name of the module to reproduce it on our side. Can you please share it with us? e.g. MiniQuad 2GB WB…
  • Does the same behaviour happens with one of our carrier boards such as the Verdin Development Board?
  • On which baud rate was this detected?
  • Can you please share and highlight the changes that you made to the device tree?

Lastly, are you using which OS on your module? Is this TorizonCore? Can you please share the full version? Did you do any changes to the OS?

Best regards,

I’m good how are you? Sorry for not being clear.

  • Verdin IMX8MM DL 1GB WB IT
  • Yes the same behavior is detected on the dev board.
  • On 38000 Hz
  • The changes we did to the device tree are not related to the SPI at all actually but only opened up some pins to GPIO, and we tried the default device tree and the same behavior occurred.

However we have found some variables that can be used for the SPI in other threads.

  • |||fsl,spi-cs-sck-delay = <100>;

  • |||fsl,spi-sck-cs-delay = <50>;

Could these help our issue?

Hi @edmirsuljicOIM,

I’m also good, thanks for asking :smiley:

Thanks for the updates, we have some additional questions.

  • Are you using spidev directly or did you add a driver for a specific spi device?
  • If you’re using spidev, could you please share with us a sample code so that we can try to reproduce it on our side?

Were these threads found in our community? Could you please link them?
Also, we found the kernel documentation the following file: spi-fsl-dspi.txt « spi « bindings « devicetree « Documentation - linux-toradex.git - Linux kernel for Apalis, Colibri and Verdin modules
It explains what these two timings are for. However, you can see here that the SPI on the Verdin iMX8MM doesn’t have the same compatible as the one described in the previous file (imx8mm.dtsi « freescale « dts « boot « arm64 « arch - linux-toradex.git - Linux kernel for Apalis, Colibri and Verdin modules). Therefore, you may not be able to use these settings as a workaround for your issue.

Best regards,

  • Yes we are using spidev directly, this is how we use the Infrared. Might as well share the whole file:
    The run method in a different file calls get_ir_signal() in a loop and returns something if the code matches.
        self._spi = SpiDev(1, 0)
from spidev import SpiDev
from configparser import ConfigParser
import time

SEND_IR = 1
READ_IR = 2
CONF_IR_TIMER = 10


class Infrared:
    """
    Class for sending receiving infrared signals
    """

    def __init__(self):
        """
        Imports IR codes from config file and initiates SPI bus for IR interface
        """
        self.parser = ConfigParser()
        self.parser.read("config.ini")
        self._spi = SpiDev(1, 0)
        self._spi.mode = 0b01
        self._read_toggle = "0"
        self._write_toggle = "0"
        self._last_code = ""

    def _read_write(self, code=None):
        """
        Read write function. SPI interface reads everytime it writes. If code is none means we want to read.
        For reading a zero message is generated and sent just to read from the bus.
        For sending signal a message is extracted from hex code provided and sent over SPI bus. System will read
        incoming signals even when sending.
        :param code: IR code as hex string
        :return: data read from SPI bus
        """
        speed = 40000
        if code is None:
            message = self._get_zero_message()
        else:
            message = self._hex_to_spi_output(code)

        data = self._spi.xfer2(message, speed)
        return data

        # TODO: lägg in något stopp för längd på meddelande

    def _get_raw_ir_data(self):
        """
        Reads and decodes raw IR data from SPI bus. A buffer from SPI bus is read and recoded. If no data is reveived
        all data in buffer will be 255 and ignored. If there are zeros in buffer it will be decoded.
        Values < 125 = 0, values >= 125 = 1.
        The infrared signal will look like 0000111100001111 with 4 ones/zeros representing low/high. This is the way
        infrared protocols work. One bit in the RC5 protocol is represented by two bits, a one is 01 and a zero is 10
        For more info read up on Infrared RTC5 protocol.
        The filtering will read 3 ro more ones/zeros in a row as one bit. 00001111000111 = 0101
        Six or more ones/zeros in a row will be read as two bits. 00011100000001111 = 01001
        Data will be read until length of message is greater than 28 bits.
        If there are more than 9 ones in a row the message is incomplete and None is returned.
        :return: list of 28 integers representing the RC5 14 bit signal. None if incomplete.
        """
        start_bit_read = False
        zeros_added = ones_added = 0
        zeros = ones = 0
        bits = "1"
        data = self._read_write()
        read = data.count(0) > 0
        while read:
            for d in data:
                if d < 125:
                    start_bit_read = True
                    zeros += 1
                    if zeros_added == 0 or (zeros > 5 and zeros_added < 2):
                        bits += '0'
                        zeros_added += 1
                    ones = 0
                    ones_added = 0
                else:
                    if start_bit_read:
                        ones += 1
                        if ones_added == 0 or (ones > 5 and ones_added < 2):
                            bits += '1'
                            ones_added += 1
                        zeros = 0
                        zeros_added = 0
                        if ones > 9:
                            if len(bits) > 28:
                                bits = bits[0:28]
                                print(bits)
                                return bits
                            else:
                                return None
            data = self._read_write()
        return None

    def _filter_raw_ir_signal(self, raw):
        """
        Filers 28 integer list to RC5 14 integers signal.
        Converts 01 -> 1 and 10 -> 0.
        :param raw: unfiltered data
        :return: RC5 code, toggle status of message
        """
        toggle_bit = ""
        rc5_code = None
        if raw is not None and len(raw) >= 28:
            signal = ""
            for i in range(0, 28, 2):
                signal += raw[i + 1]
            if len(signal) != 14:
                print("wrong signal", signal, len(signal))
                print("raw", raw)
                return None, ""
            data = signal[len(signal) - 11:len(signal)]
            # data = signal[3:14]
            toggle_bit = signal[2]
            rc5_code = hex(int(data, 2))
        return rc5_code, toggle_bit

    def _hex_to_spi_output(self, hex_string):
        """
        Converts RC5 hex code to SPI data to be sent
        0 -> [255, 255, 255, 255, 0, 0, 0, 0]
        1 -> [0, 0, 0, 0, 255, 255, 255, 255]
        :param hex_string: RC5 code
        :return: Buffer for SPI bus
        """
        scale = 16
        num_of_bits = 11
        self._write_toggle = str(int(not int(self._write_toggle)))
        bit_string = "00" + self._write_toggle
        bit_string += bin(int(hex_string, scale))[2:].zfill(num_of_bits)
        spi_output = []
        for bit in bit_string:
            if bit == "0":
                spi_output += [255, 255, 255, 255, 0, 0, 0, 0]
            else:
                spi_output += [0, 0, 0, 0, 255, 255, 255, 255]
        return spi_output

    def _get_zero_message(self):
        """
        Return zero buffer to be sent on SPI bus
        :return: SPI buffer full of zeros
        """
        message = []
        for i in range(32):
            message += [0, 0, 0, 0]
        return message

    def get_ir_signal(self):
        """
        Decode RC5 code and find which key the code represents
        :return: key representation, RC5 code
        """
        key_pressed = None
        raw = self._get_raw_ir_data()
        rc5_code, toggle_bit = self._filter_raw_ir_signal(raw)

        if rc5_code is not None and (rc5_code != self._last_code or self._read_toggle != toggle_bit):
            for key, value in self.parser.items("rc5_read"):
                if value == rc5_code:
                    key_pressed = key
            self._read_toggle = toggle_bit
            self._last_code = rc5_code
            self._read_write()
            print("key_pressed ", key_pressed, "rc_5 ", rc5_code)
        return key_pressed, rc5_code

    def send_ir_signal(self, key, group):
        """
        Send IR signal for key pressed
        :param key: Key pressed
        """
        parser_key = f"rc5_send_{group}"
        value = self.parser[parser_key][str(key)]
        print("send key group", key, group, value)
        self._read_write(value)
        time.sleep(0.5)

Here is the community thread we found it in:

But yeah I guess you are right since the workaround didn’t really help. We have also tried filling the SPI buffer with a lot of bytes to reduce the amount of time delays but that did not work either, also it comes with another set of problems.

Hi @edmirsuljicOIM,

As @gclaudino.tx already pointed out, you have different SPI controller on imx8mm. DSPI on VF family allows continuous stream of bits, just add more and more data before FIFO buffer drains out. On eCSPI, which you have, you are limited to 4kbits. Gap every 8bits is caused by Linux driver and its DMA transfer implementation. Until transfer sizes of ~64 bytes DMA is not involved and you may see uniform SCK, above that DMA is used and you get a gap each 8/16/32 bits. Bits amount depends on transfer settings, see -b command line switch of spidev_test. You may try disabling SPI DMA in DT or via sysfs:

echo N > /sys/module/spi_imx/parameters/use_dma

Regarding your initial request. Those gaps are not violation of SPI protocol. SPI has additional SCK clock and those gaps not cause any problems in normal applications. As I understand your application keeps sending 32*4=128 bytes continuously and checks received data to emulate IR receiver HW. But start of new transfer will introduce pause any way. Even SPI interrupt due to interrupt latency may introduce quite long pause between SPI bursts. If you need really continuous stream you should look at I2S or something like that.

Update: What about sending more data at 2x 4x or faster rate, so that gap is much shorter than bit of your IR stream? You just need to update your patterns to check in received stream.

1 Like

Hi @edmirsuljicOIM ,

Hope you’re doing well.

Is there any news on this issue from your side? Did the suggestions of @gclaudino.tx and @Edward change anything about the situation?

Thanks, @Edward for your help on that one.

Best Regards
Kevin

Hi,

Well what we did is that we changed the SPI buffer size to 8 bytes instead of 128 and that seems to have done the trick with maybe a 80% success rate, as in the software manages to read the IR data and decode it correctly. Which is quite odd but we couldn’t figure out why exactly that worked, but it did…

Hi @edmirsuljicOIM,

Thanks for the update! So do you consider it’s working on your side now? If so, can you tag the post as solved?

Best regards,

Yes go ahead!

1 Like