SPI is not reliable above 8MHz

Hello,

I was playing with a FT810 chip on the SC20260 dev board and I’ve found that I could not go as fast as I should with this module.

It’s given for 30MHz max but, depending on the SPI bus I choose, I can only go at 12MHz max :frowning:

The FT810 has to be initialized with a SPI frequency below 10MHz. So I’m using 2MHz to be (really) safe during initialization.
Then, at the end of the init process, I raise the frequency to the maximum allowed, or not so far given the prescaler results. This should be somewhere around 21 or 24MHz, I think.

Anyway, I can only go to 12MHz on SPI5 (although it is given for 60MHz max) and 8Mhz on SPI2 & SPI3 (which are given for 24MHz max).

The following code does reproduce the issue. It simply reads the chip ID twice : once at the beginning of the init process and another time once the init process is over and I can raise the SPI frequency.

using GHIElectronics.TinyCLR.Devices.Gpio;
using GHIElectronics.TinyCLR.Devices.Spi;
using GHIElectronics.TinyCLR.Pins;
using System;
using System.Diagnostics;
using System.Threading;

namespace SPI_Issue
{
    class Program
    {
        static void Main()
        {
            new FT810(SC20260.SpiBus.Spi3);

            Thread.Sleep(Timeout.Infinite);
        }

    }

    public class FT810
    {
        private readonly SpiDevice _ft800;
        private readonly GpioPin _rst;
        private UInt32 RAM_REG;
        private readonly String _spiBus;

        private readonly Byte[] FT8XX_REG_PCLK = new Byte[] { 0x6C, 0x70 };
        private readonly Byte[] FT8XX_REG_PCLK_POL = new Byte[] { 0x68, 0x6C };
        private readonly Byte[] FT8XX_REG_CSPREAD = new Byte[] { 0x64, 0x68 };
        private readonly Byte[] FT8XX_REG_SWIZZLE = new Byte[] { 0x60, 0x64 };
        private readonly Byte[] FT8XX_REG_VSYNC1 = new Byte[] { 0x4C, 0x50 };
        private readonly Byte[] FT8XX_REG_VSYNC0 = new Byte[] { 0x48, 0x4C };
        private readonly Byte[] FT8XX_REG_VSIZE = new Byte[] { 0x44, 0x48 };
        private readonly Byte[] FT8XX_REG_VOFFSET = new Byte[] { 0x40, 0x44 };
        private readonly Byte[] FT8XX_REG_VCYCLE = new Byte[] { 0x3C, 0x40 };
        private readonly Byte[] FT8XX_REG_HSYNC1 = new Byte[] { 0x38, 0x3C };
        private readonly Byte[] FT8XX_REG_HSYNC0 = new Byte[] { 0x34, 0x38 };
        private readonly Byte[] FT8XX_REG_HSIZE = new Byte[] { 0x30, 0x34 };
        private readonly Byte[] FT8XX_REG_HOFFSET = new Byte[] { 0x2C, 0x30 };
        private readonly Byte[] FT8XX_REG_HCYCLE = new Byte[] { 0x28, 0x2C };
        private readonly Byte[] FT8XX_REG_GPIO = new Byte[] { 0x90, 0x94 };
        private readonly Byte[] FT8XX_REG_GPIO_DIR = new Byte[] { 0x8C, 0x90 };

        public FT810(String spiBus)
        {
            _spiBus = spiBus;
            // Initialize SPI at low speed for startup
            _ft800 = SpiController.FromName(spiBus).GetDevice(new SpiConnectionSettings()
            {
                ChipSelectType = SpiChipSelectType.Gpio,
                ChipSelectLine = GpioController.GetDefault().OpenPin(SC20260.GpioPin.PG12), // CS pin on Socket #1
                Mode = SpiMode.Mode0,
                ClockFrequency = 2000000
            });
            Debug.WriteLine($"{_spiBus} frequency set to {_ft800.ConnectionSettings.ClockFrequency:N0} Hz");

            // Initialize Reset pin
            _rst = GpioController.GetDefault().OpenPin(SC20260.GpioPin.PI8);    // RST pin on Socket #1
            _rst.SetDriveMode(GpioPinDriveMode.Output);
            _rst.Write(GpioPinValue.High);

            WakeUp();
            DetectChip();
            ConfigureDisplayTimings();
        }

        private void DetectChip()
        {
            RAM_REG = 0x302000;

            // Read chip model and revision
            var Id = BitConverter.GetBytes(Read32(0xC0000));

            // For FT810 chip, Id should contain [0x00, 0x01, 0x10, 0x08] 
            Debug.WriteLine($"Id = 0x{Id[0]:X2}, 0x{Id[1]:X2}, 0x{Id[2]:X2}, 0x{Id[3]:X2}\r\n");
        }

        private void WakeUp()
        {
            // Power-up
            _rst.Write(GpioPinValue.Low);
            Thread.Sleep(1);
            _rst.Write(GpioPinValue.High);
            Thread.Sleep(10);

            HostCommand(0x00);     //Wake-up
            HostCommand(0x44);     // Access address 0 to wake up the FT8XX
            HostCommand(0x62);     // Switch PLL output to 48MHz (FT80x) or 60MHz (FT81x)
            Thread.Sleep(300);
        }

        private void HostCommand(Byte command)
        {
            _ft800.Write(new Byte[] { (Byte)(command & 0x40), 0, 0 });
        }

        private void ConfigureDisplayTimings()
        {
            Write16(RAM_REG + FT8XX_REG_HCYCLE[1], 928);
            Write16(RAM_REG + FT8XX_REG_HOFFSET[1], 88);
            Write16(RAM_REG + FT8XX_REG_HSYNC0[1], 0);
            Write16(RAM_REG + FT8XX_REG_HSYNC1[1], 48);
            Write16(RAM_REG + FT8XX_REG_VCYCLE[1], 525);
            Write16(RAM_REG + FT8XX_REG_VOFFSET[1], 32);
            Write16(RAM_REG + FT8XX_REG_VSYNC0[1], 0);
            Write16(RAM_REG + FT8XX_REG_VSYNC1[1], 3);
            Write8(RAM_REG + FT8XX_REG_SWIZZLE[1], 0);
            Write8(RAM_REG + FT8XX_REG_PCLK_POL[1], 1);
            Write8(RAM_REG + FT8XX_REG_CSPREAD[1], 1);
            Write16(RAM_REG + FT8XX_REG_HSIZE[1], 800);
            Write16(RAM_REG + FT8XX_REG_VSIZE[1], 480);

            // Enable display
            Write8(RAM_REG + FT8XX_REG_GPIO_DIR[1], (Byte)(0x80 | Read8(RAM_REG + FT8XX_REG_GPIO_DIR[1])));
            Write8(RAM_REG + FT8XX_REG_GPIO[1], (Byte)(0x080 | Read8(RAM_REG + FT8XX_REG_GPIO[1])));
            Write8(RAM_REG + FT8XX_REG_PCLK[1], 0x02);

            Thread.Sleep(100);
            // Raise Spi speed
            _ft800.ConnectionSettings.ClockFrequency = 8_000_000;
            Debug.WriteLine($"{_spiBus} frequency set to {_ft800.ConnectionSettings.ClockFrequency:N0} Hz");
            DetectChip();
        }

        readonly Byte[] _addr = new Byte[4];

        public Byte Read8(UInt32 addr)
        {
            _addr[0] = (Byte)(addr >> 16);
            _addr[1] = (Byte)(addr >> 8);
            _addr[2] = (Byte)(addr & 0xFF);
            _ft800.TransferSequential(_addr, _addr);

            return _addr[0];
        }
        public UInt32 Read32(UInt32 addr)
        {
            _addr[0] = (Byte)(addr >> 16);
            _addr[1] = (Byte)(addr >> 8);
            _addr[2] = (Byte)(addr & 0xFF);
            _ft800.TransferSequential(_addr, _addr);

            return (UInt32)((_addr[0] << 24) + (_addr[1] << 16) + (_addr[2] << 8) + _addr[3]);
        }

        readonly Byte[] w8 = new Byte[4];
        readonly Byte[] w16 = new Byte[5];
        public void Write8(UInt32 addr, Byte val)
        {
            w8[0] = (Byte)(0x80 | (addr >> 16));
            w8[1] = (Byte)(addr >> 8);
            w8[2] = (Byte)(addr & 0xFF);
            w8[3] = val;
            _ft800.Write(w8);
        }

        public void Write16(UInt32 addr, UInt16 val)
        {
            w16[0] = (Byte)(0x80 | (addr >> 16));
            w16[1] = (Byte)(addr >> 8);
            w16[2] = (Byte)(addr & 0xFF);
            w16[3] = (Byte)(val & 0xFF);
            w16[4] = (Byte)((val >> 8) & 0xFF);
            _ft800.Write(w16);
        }
    }
}

If you change the frequency at the end of the ConfigureDisplayTimings() method, then you will see that the Id array does not show the same values.

To give an example with SPI3. At 2MHz, Id values are ok.

Correct behaviour @ 8MHz :

GHIElectronics.TinyCLR.NativeApis.STM32H7.SpiController\2 frequency set to 2,000,000 Hz
Id = 0x00, 0x01, 0x10, 0x08

GHIElectronics.TinyCLR.NativeApis.STM32H7.SpiController\2 frequency set to 8,000,000 Hz
Id = 0x00, 0x01, 0x10, 0x08

Bad behaviour @ 16 MHz :

GHIElectronics.TinyCLR.NativeApis.STM32H7.SpiController\2 frequency set to 2,000,000 Hz
Id = 0x00, 0x01, 0x10, 0x08

GHIElectronics.TinyCLR.NativeApis.STM32H7.SpiController\2 frequency set to 16,000,000 Hz
Id = 0xFF, 0xFF, 0xFF, 0xFF

Other bad behaviour @ 12MHz:

GHIElectronics.TinyCLR.NativeApis.STM32H7.SpiController\2 frequency set to 2,000,000 Hz
Id = 0x00, 0x01, 0x10, 0x08

GHIElectronics.TinyCLR.NativeApis.STM32H7.SpiController\2 frequency set to 12,000,000 Hz
Id = 0xFF, 0xFF, 0xFF, 0xFF

This is near impossible for us to troubleshoot. It may even be wiring or power issue. Can you take the FT out of the equation and tell us what the problem is on SPI please?

If I remove the FT810, then I have no issue. But I only have the FT810 attached… No thermometer, no fever.

Problem is that I can read correct/reliable values from the device only if I am using 8MHz max (or 12MHz on SPI5).
The FT810 is given for 30MHz max and SPI on SC20260 from 24MHz to 60MHz, depending on which bus I choose. 8MHz is at best a third of the max value.

Wire length is roughly 5". External power applied. Same behaviour on the SC20260 dev board and on our RAM board (SC20260E SOM).

Maybe you have the wrong spi mode?

Do you mean higher 8MHz (Spi3), there is no communication? Or you set 12 or 15MHz SPI3 still 8MHz?

What I mean is (using SPI3, for example) : as long as I stay at 8MHz or below, I can realiably read values from the FT810 registers (in my previous post, it was the chip Id). If I try to set higher frequencies, results are either 0x00, 0xFF or random values but not the expected ones.
If I set the frequency back to 8MHz or below, everything is fine again, values read are those expected.

@Gus_Issa : Since everything is working fine at 8Mhz and below, I can say that SPI mode is correct. The code snippet shown here to reproduce the issue is an excerpt of a bigger code that is also working fine if I stay at 8MHz and below. (or 12MHz and below for SPI5). So, again, I’m sure of the SPI mode.

@Dat_Tran : Higher than 8MHz frequencies are set correctly, as far as I can tell. Otherwise I think I would not see a different behaviour.

Is it on a PCB or breadboarded with loose wires? Are the wires all the same length?

All wires are the same length, roughly 20cm each, connected from the MikroBus socket to the FT810 board.

Edit : I would have thought at this as well (cable length) if the behaviour between SPI3 and SPI5 was identical. But SPI5 is reliable at 12MHz when SPI3 is not.

I would offer to test but I don’t have any easy way to do a test. My latest board only uses SPI 2 and SPI 5. I don’t have SPI3 connected to anything. I have run SPI 2 at 24Mhz with no issues. Have not tested SPI 5 yet.

Just quick test on SPI3 at 12MHz, look correctly to me at 12MHz.

1 Like