VS1053 Mp3 SPI problem

As I can’t play with micropython:
https://community.brainpad.com/t/install-micropython/191
I play with vs1503 mp3 decoder, but I have a problem with spi exception:

#### Exception System.InvalidOperationException - CLR_E_INVALID_OPERATION (1) ####
    #### Message: 
    #### GHIElectronics.TinyCLR.Devices.Spi.Provider.SpiControllerApiWrapper::SetActiveSettings [IP: 0000] ####
    #### GHIElectronics.TinyCLR.Devices.Spi.SpiController::SetActive [IP: 000b] ####
    #### GHIElectronics.TinyCLR.Devices.Spi.SpiDevice::TransferFullDuplex [IP: 000e] ####
    #### GHIElectronics.TinyCLR.Devices.Spi.SpiDevice::TransferFullDuplex [IP: 000e] ####
    #### TinyCLRApplicationVs1503.Vs1503B::GetRegister [IP: 002c] ####
    #### TinyCLRApplicationVs1503.Vs1503B::ClearBitInRegister [IP: 0018] ####
    #### TinyCLRApplicationVs1503.Vs1503B::StopSin [IP: 0058] ####
Exception levée : 'System.InvalidOperationException' dans GHIElectronics.TinyCLR.Devices.Spi.dll
Une exception non gérée du type 'System.InvalidOperationException' s'est produite dans GHIElectronics.TinyCLR.Devices.Spi.dll

My (relative simple) code is :slight_smile: :

using System;
using System.Threading;
using GHIElectronics.TinyCLR.Devices.Gpio;
using GHIElectronics.TinyCLR.Devices.Spi;
// ReSharper disable UnusedMethodReturnValue.Global
// ReSharper disable UnusedMember.Global
// ReSharper disable MemberCanBePrivate.Global

namespace TinyCLRApplicationVs1503
{
    public class Vs1503B
    {
        private readonly SpiDevice _sci;
        private readonly SpiDevice _sdi;
        private readonly GpioPin _dataRequested;

        readonly byte[] _sciBufferRead = new byte[4];
        readonly byte[] _sciBufferWrite = new byte[4];

        readonly byte[] _sdiTestBufferRead = new byte[8];
        readonly byte[] _sdiTestBufferWrite = new byte[8];

        private const byte Read = 0x3;
        private const byte Write = 0x2;

        public enum Register : byte
        {
            Mode = 0x0,
            Status = 0x1,
            Bass = 0x2,
            Clock = 0x3,
            DecodeTime = 0x4,
            AuData = 0x5,
            WRam = 0x6,
            WRamAddr = 0x7,
            HeaderData0 = 0x8,
            HeaderData1 = 0x9,
            ApplicationStartAddress = 0xA,
            Volume = 0xB
        }

        public enum Bit
        {
            SetMaskTest = 5,
            SetMaskReset = 2,
        }

        private DateTime _timeOutOrigin;
        public TimeSpan TimeOut = TimeSpan.FromMilliseconds(200);

        public Vs1503B(string spiBuses, int xcs, int dcs, int dataRequested)
        {
            var ctl = SpiController.FromName(spiBuses);
            SpiConnectionSettings sciSettings = new SpiConnectionSettings()
            {
                ChipSelectType = SpiChipSelectType.Gpio,
                ChipSelectLine = xcs,
                ClockFrequency = 4_000_000,
                DataBitLength = 8,
                ChipSelectSetupTime = TimeSpan.FromMilliseconds(1),
                Mode = SpiMode.Mode0
            };
            _sci = ctl.GetDevice(sciSettings);
            SpiConnectionSettings sdiSettings = new SpiConnectionSettings()
            {
                ChipSelectType = SpiChipSelectType.Gpio,
                ChipSelectLine = dcs,
                ClockFrequency = 4_000_000,
                DataBitLength = 8,
                ChipSelectSetupTime = TimeSpan.FromMilliseconds(1),
                Mode = SpiMode.Mode0
            };
            _sdi = ctl.GetDevice(sdiSettings);

            var gpioCtl = GpioController.GetDefault();
            _dataRequested = gpioCtl.OpenPin(dataRequested);
            _dataRequested.SetDriveMode(GpioPinDriveMode.Input);

            Initialization();
        }

        private void Initialization()
        {
            SetRegister(Register.Mode, 0x4800);
            SoftReset();
        }

        private void SoftReset()
        {
            SetBitInRegister(Register.Mode, Bit.SetMaskReset);
            WaitDataRequest();
        }

        public ushort GetRegister(Register register)
        {

            _sciBufferWrite[0] = Read;
            _sciBufferWrite[1] = (byte)register;
            _sciBufferWrite[2] = 0;
            _sciBufferWrite[3] = 0;
            _sci.TransferFullDuplex(_sciBufferWrite, _sciBufferRead);
            WaitDataRequest();
            return (ushort)(_sciBufferRead[2] << 8 | _sciBufferRead[3]);
        }

        public void SetRegister(Register register, ushort value)
        {
            _sciBufferWrite[0] = Write;
            _sciBufferWrite[1] = (byte)register;
            _sciBufferWrite[2] = (byte)(value >> 8);
            _sciBufferWrite[3] = (byte)(value & 0xff);
            _sci.TransferFullDuplex(_sciBufferWrite, _sciBufferRead);
            WaitDataRequest();
        }

        public void SetBitInRegister(Register register, Bit bit)
        {
            if (bit < 0 || (int)bit > 32) return;
            var value = GetRegister(register);
            var mask = (1 << (int)bit);
            var newValue = (ushort)(value | mask);
            SetRegister(register, newValue);
        }


        public void ClearBitInRegister(Register register, Bit bit)
        {
            if (bit < 0 || (int)bit > 32) return;
            var value = GetRegister(register);
            var mask = (ushort)(~(1 << (int)bit));
            var newValue = (ushort)(value & mask);
            SetRegister(register, newValue);
        }

        public bool WaitDataRequest()
        {
            _timeOutOrigin = DateTime.Now;
            while (_dataRequested.Read() == GpioPinValue.Low)
            {
                if ((DateTime.Now - _timeOutOrigin) > TimeOut) return false;
            }
            return true;
        }

        public bool CheckDataReady()
        {
            return _dataRequested.Read() == GpioPinValue.High;
        }

        public void StartSin()
        {
            SetBitInRegister(Register.Mode, Bit.SetMaskTest);
            _sdiTestBufferWrite[0] = 0x53;
            _sdiTestBufferWrite[1] = 0xef;
            _sdiTestBufferWrite[2] = 0x6e;
            _sdiTestBufferWrite[3] = 0xe5;
            for (int i = 4; i < 8; i++)
                _sdiTestBufferWrite[i] = 0x00;
            _sdi.TransferFullDuplex(_sdiTestBufferWrite, _sdiTestBufferRead);
            WaitDataRequest();
        }
        public void StopSin()
        {
            _sdiTestBufferWrite[0] = 0x45;
            _sdiTestBufferWrite[1] = 0x78;
            _sdiTestBufferWrite[2] = 0x69;
            _sdiTestBufferWrite[3] = 0x74;
            for (int i = 4; i < 8; i++)
                _sdiTestBufferWrite[i] = 0x00;
            _sdi.TransferFullDuplex(_sdiTestBufferWrite, _sdiTestBufferRead);
            WaitDataRequest();
            Thread.Sleep(2);
            ClearBitInRegister(Register.Mode, Bit.SetMaskTest);
        }
    }
}

and my program:

        static void Main()
        {
            var vs1503B = new Vs1503B(FEZPandaIII.SpiBus.Spi1, FEZPandaIII.GpioPin.D7, FEZPandaIII.GpioPin.D6, FEZPandaIII.GpioPin.D3);

            Debug.WriteLine($"Mode:   0x{vs1503B.GetRegister(Vs1503B.Register.Mode):X4}");
            Debug.WriteLine($"Status: 0x{vs1503B.GetRegister(Vs1503B.Register.Status):X4}");
            Debug.WriteLine($"Volume: 0x{vs1503B.GetRegister(Vs1503B.Register.Volume):X4}");
            Debug.WriteLine($"Mode:   0x{vs1503B.GetRegister(Vs1503B.Register.Mode):X4}");

            while (true)
            {
                vs1503B.StartSin();
                Thread.Sleep(1000);
                vs1503B.StopSin();
                Thread.Sleep(1000);

                Thread.Sleep(20);
            }
        }

Is it a bug in spi Library, or an error I have done ? I tried to see with logic analyzer, but all seems correct and it gives me no clue.

Can you share a smaller example that shows the error please?

Of course ! I hope it is smaller:
Here is code that raises exception (I have test a version with fullTransfer, it doesn’t change anything):

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

namespace testBugSpi2Devices
{
    static class Program
    {
        private static GpioPin _dataRequested;
        private static SpiDevice _sci; // to send command to vs1053
        private static SpiDevice _sdi; // to send data to vs1053

        
        static void Main()
        {
            Setup();
            SendSciTestCommand(); //  use _sci device : it works
            SendSdiStartTest();//  use _sdi device : it works
            Thread.Sleep(1000);
            SendSdiStopTest(); // use _sdi device : it works 
            SendSciClearTestCommand(); // use _sci device: it raise exception

            while (true)
            {
                Thread.Sleep(20);
            }
            // ReSharper disable once FunctionNeverReturns
        }

        private static void SendSdiStopTest()
        {
            byte[] bufferWrite = new byte[8];
            bufferWrite[0] = 0x45;   // Write Operation
            bufferWrite[1] = 0x78;     // Register to write to
            bufferWrite[2] = 0x69;  // High value
            bufferWrite[2] = 0x74;  // Low value
            // All other bytes are 0
            _sdi.Write(bufferWrite);
        }

        private static void SendSdiStartTest()
        {
            byte[] bufferWrite = new byte[8];
            bufferWrite[0] = 0x53;   // Write Operation
            bufferWrite[1] = 0xEF;     // Register to write to
            bufferWrite[2] = 0x6E;  // High value
            bufferWrite[2] = 0b11101111;  // Low value
            // All other bytes are 0
            _sdi.Write(bufferWrite);

        }

        private static void SendSciTestCommand()
        {
            byte[] bufferWrite = new byte[4];
            bufferWrite[0] = 0x2;   // Write Operation
            bufferWrite[1] = 0;     // Register to write to
            bufferWrite[2] = 0x48;  // High value
            bufferWrite[2] = 0x20;  // Low value
            _sci.Write(bufferWrite);
            WaitEndOfOperation(); // Wait Dreq is high
        }

        private static void SendSciClearTestCommand()
        {
            byte[] bufferWrite = new byte[4];
            bufferWrite[0] = 0x2;   // Write Operation
            bufferWrite[1] = 0;     // Register to write to
            bufferWrite[2] = 0x48;  // High value
            bufferWrite[2] = 0x00;  // Low value

            /**********************************************************
             * Exception raised as it cannot use sci device anymore !!!
             **********************************************************/
            _sci.Write(bufferWrite);

            WaitEndOfOperation(); // Wait Dreq is high
        }

        private static void WaitEndOfOperation()
        {
            while (_dataRequested.Read() == GpioPinValue.Low)
            {
            }
        }

        private static void Setup()
        {
            // Test with FEZPAndaIII board and music maker shield from adafruit

            var gpioCtl = GpioController.GetDefault();
            var spiCtl = SpiController.FromName(FEZPandaIII.SpiBus.Spi1);

            var settingsSdi = new SpiConnectionSettings()
            {
                ChipSelectLine = FEZPandaIII.GpioPin.D6,
                ChipSelectType = SpiChipSelectType.Gpio,
                ClockFrequency = 1_000_000,
                DataBitLength = 8,
                Mode = SpiMode.Mode0
            };
            _sdi = spiCtl.GetDevice(settingsSdi);

            var settingsSci = new SpiConnectionSettings()
            {
                ChipSelectLine = FEZPandaIII.GpioPin.D7,
                ChipSelectType = SpiChipSelectType.Gpio,
                ClockFrequency = 1_000_000,
                DataBitLength = 8,
                Mode = SpiMode.Mode0
            };
            _sci = spiCtl.GetDevice(settingsSci);

            _dataRequested = gpioCtl.OpenPin(FEZPandaIII.GpioPin.D3);
            _dataRequested.SetDriveMode(GpioPinDriveMode.Input);
        }
    }
}

If I manage ChipSelect Pin manually, it can be a workaround (but with overhead time with all transfer):

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

namespace testBugSpi2DevicesWorkaround
{
    static class Program
    {
        private static GpioPin _dataRequested,_sdiCsPin,_sciCsPin;
        private static SpiDevice _spi; // use unique spi device

        static void Main()
        {
            Setup();
            SendSciTestCommand(); //  use _sci device : it works
            SendSdiStartTest();//  use _sdi device : it works
            Thread.Sleep(1000);
            SendSdiStopTest(); // use _sdi device : it works 
            SendSciClearTestCommand(); // use _sci device: it raise exception

            while (true)
            {
                Thread.Sleep(20);
            }
            // ReSharper disable once FunctionNeverReturns
        }

        private static void SendSdiStopTest()
        {
            byte[] bufferWrite = new byte[8];
            bufferWrite[0] = 0x45;   // Write Operation
            bufferWrite[1] = 0x78;     // Register to write to
            bufferWrite[2] = 0x69;  // High value
            bufferWrite[2] = 0x74;  // Low value
            // All other bytes are 0

            // SDI
            _sdiCsPin.Write(GpioPinValue.Low);
            _spi.Write(bufferWrite);
            _sdiCsPin.Write(GpioPinValue.High);
        }

        private static void SendSdiStartTest()
        {
            byte[] bufferWrite = new byte[8];
            bufferWrite[0] = 0x53;   // Write Operation
            bufferWrite[1] = 0xEF;     // Register to write to
            bufferWrite[2] = 0x6E;  // High value
            bufferWrite[2] = 0b11101111;  // Low value
            // All other bytes are 0

            // SDI
            _sdiCsPin.Write(GpioPinValue.Low);
            _spi.Write(bufferWrite);
            _sdiCsPin.Write(GpioPinValue.High);
        }

        private static void SendSciTestCommand()
        {
            byte[] bufferWrite = new byte[4];
            bufferWrite[0] = 0x2;   // Write Operation
            bufferWrite[1] = 0;     // Register to write to
            bufferWrite[2] = 0x48;  // High value
            bufferWrite[2] = 0x20;  // Low value

            // SCI
            _sciCsPin.Write(GpioPinValue.Low);
            _spi.Write(bufferWrite);
            _sciCsPin.Write(GpioPinValue.High);
            WaitEndOfOperation(); // Wait Dreq is high
        }

        private static void SendSciClearTestCommand()
        {
            byte[] bufferWrite = new byte[4];
            bufferWrite[0] = 0x2;   // Write Operation
            bufferWrite[1] = 0;     // Register to write to
            bufferWrite[2] = 0x48;  // High value
            bufferWrite[2] = 0x00;  // Low value

            /*******************************
             * No raised exception
             *******************************/

            // SCI
            _sciCsPin.Write(GpioPinValue.Low);
            _spi.Write(bufferWrite);
            _sciCsPin.Write(GpioPinValue.High);
            WaitEndOfOperation(); // Wait Dreq is high
        }

        private static void WaitEndOfOperation()
        {
            while (_dataRequested.Read() == GpioPinValue.Low)
            {
            }
        }

        private static void Setup()
        {
            // Test with FEZPAndaIII board and music maker shield from adafruit

            var gpioCtl = GpioController.GetDefault();
            var spiCtl = SpiController.FromName(FEZPandaIII.SpiBus.Spi1);

            var settingsSpi = new SpiConnectionSettings()
            {
                ChipSelectType = SpiChipSelectType.None,
                ClockFrequency = 1_000_000,
                DataBitLength = 8,
                Mode = SpiMode.Mode0
            };
            _spi = spiCtl.GetDevice(settingsSpi);

            _dataRequested = gpioCtl.OpenPin(FEZPandaIII.GpioPin.D3);
            _dataRequested.SetDriveMode(GpioPinDriveMode.Input);

            _sciCsPin = gpioCtl.OpenPin(FEZPandaIII.GpioPin.D7);
            _sciCsPin.SetDriveMode(GpioPinDriveMode.Output);

            _sdiCsPin = gpioCtl.OpenPin(FEZPandaIII.GpioPin.D6);
            _sdiCsPin.SetDriveMode(GpioPinDriveMode.Output);
        }
    }
}

I only need the lines that would raise an exception on spi. I think you are using spi wing but I need the specific lines to be sure please

I have put a comment in code (first one) where exception is raised. I think all code is needed because exception is raised after using device1 and device2 on spi, and go back to device1.
But I supposed GHI team is busy to do tinyclr 2.0, if it cannot be viewed soon, that not an issue.

Actually the reason I am asking is because I want to test it on 2.0 :grinning:

1 Like

@Bauland not that it would explain the exception but you are writing the 3rd byte of your buffers twice instead of setting lo and hi byte separately.