using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace SPI_Sample
{
    class Program
    {
        public const ushort PROLIFIC_VID = 0x67B;
        public const int ERROR_SUCCESS = 0;
        public const int ERROR_INSUFFICIENT_BUFFER = -1;
        public const int ERROR_READ_REGISTER_FAIL = -2;
        public const int ERROR_WRITE_REGISTER_FAIL = -3;
        public const int ERROR_READ_DATA_FAIL = -4;
        public const int ERROR_READ_TIMEOUT = -5;
        public const int ERROR_WRITE_DATA_FAIL = -6;
        public const int ERROR_WRITE_TIMEOUT = -7;
        public const int ERROR_DEVICE_NOT_EXIST = -8;
        public const int ERROR_NOT_GPIO_PIN = -9;
        public const int ERROR_DEVICE_OPEN_FAIL = -10;
        public const int ERROR_DATA_LENGTH_TOO_LARGE = -11;
        public const int ERROR_OTHER_FAIL = -12;
        public const int ERROR_I2C_BUS_BUSY = -13;
        public const int ERROR_I2C_ADDRESS_NACK = -14;
        public const int ERROR_I2C_DATA_NACK = -15;
        public const int ERROR_I2C_PROCESSING = -16;

        public enum SPI_SELECT
        {
            SPI_SELECT0 = 0x0,
            SPI_SELECT1 = 0x10
        }

        public enum SPI_MODE
        {
            SPI_MODE0 = 0x0,
            SPI_MODE1 = 0x10,
            SPI_MODE2 = 0x20,
            SPI_MODE3 = 0x30
        }

        [DllImport("HidDeviceSdk.dll", CallingConvention = CallingConvention.Winapi)]
        public static extern int GetSDKVersion(out uint SDKVersion);

        [DllImport("HidDeviceSdk.dll", CallingConvention = CallingConvention.Winapi)]
        public static extern int EnumDeviceByVid(ref uint HidDeviceCount, ushort VID);

        [DllImport("HidDeviceSdk.dll", CallingConvention = CallingConvention.Winapi)]
        public static extern int OpenDeviceHandle(uint DeviceIndex, ref IntPtr hDeviceHandle);

        [DllImport("HidDeviceSdk.dll", CallingConvention = CallingConvention.Winapi)]
        public static extern int CloseDeviceHandle(IntPtr hDeviceHandle);

        [DllImport("HidDeviceSdk.dll", CallingConvention = CallingConvention.Winapi)]
        public static extern int SetSPIFrequency(IntPtr hDeviceHandle, byte FreqDiv, SPI_MODE spiMode);

        [DllImport("HidDeviceSdk.dll", CallingConvention = CallingConvention.Winapi)]
        public static extern int SPIRead(IntPtr hDeviceHandle, SPI_SELECT SPISelect, ref byte ReadBuffer,
                                        ushort NumberOfBytesToRead, ref ushort NumberOfBytesRead, uint TimeOutms);

        [DllImport("HidDeviceSdk.dll", CallingConvention = CallingConvention.Winapi)]
        public static extern int SPIWrite(IntPtr hDeviceHandle, SPI_SELECT SPISelect, ref byte WriteBuffer,
                                        ushort NumberOfBytesToWrite, ref ushort NumberOfBytesWritten, uint TimeOutms);

        [DllImport("HidDeviceSdk.dll", CallingConvention = CallingConvention.Winapi)]
        public static extern int SPIWriteRead(IntPtr hDeviceHandle, SPI_SELECT SPISelect, ref byte WriteBuffer,
                                        ushort NumberOfBytesToWrite, ref byte ReadBuffer, ushort NumberOfBytesToRead,
                                        ref ushort NumberOfBytesUse, uint TimeOutms);

        [DllImport("HidDeviceSdk.dll", CallingConvention = CallingConvention.Winapi)]
        public static extern int SPIReset(IntPtr hDeviceHandle);

        static void Main(string[] args)
        {
            uint nHidDeviceCount = 0;
            ushort nVid = 0x067B;
            System.IntPtr hDeviceHandle = System.IntPtr.Zero;
            int nRet = 0;
            // Step1: Enum Device (&067B ==> USB VID)
            nRet = EnumDeviceByVid(ref nHidDeviceCount, nVid);
            if (ERROR_SUCCESS == nRet)
            {
                if (nHidDeviceCount > 0)
                {
                    // Step2: Open Hid Device Array index, ex: 0
                    nRet = OpenDeviceHandle(0, ref hDeviceHandle);
                    if (ERROR_SUCCESS == nRet)
                    {
                        // Step3: Set SPI Frequency(MAX 4.8 MHZ)
                        // nSPIFreqDiv = 4, SPI Frequency(KHz) = 24000/(4+1) = 4.8 Mhz(Max)
                        // nSPIFreqDiv = 254, SPI Frequency(KHz) = 24000/(254+1) = 94 Khz(Min)
                        byte nSPIFreqDiv = 19;    // //SPI Frequency(KHz) = 24000/(19+1) = 1,200 KHz
                        nRet = SetSPIFrequency(hDeviceHandle, nSPIFreqDiv, SPI_MODE.SPI_MODE3);

                        SPI_SELECT nSpiSelect = SPI_SELECT.SPI_SELECT0;
                        byte nSPICommand;
                        ushort nRealDataLen;
                        uint nTimeOutMs = 1000;  // Unit: ms

                        ushort nNumberOfBytesToRead;
                        // Send Read Identification(RDID) 0x9F Command
                        byte[] byReadBuffer = new byte[11];
                        nSPICommand = 0x9F;
                        nNumberOfBytesToRead = 3;
                        nRealDataLen = 0;
                        nRet = SPIWriteRead(hDeviceHandle, nSpiSelect, ref nSPICommand, 0x1, ref byReadBuffer[0], 
                                            nNumberOfBytesToRead, ref nRealDataLen, nTimeOutMs);
                        if (ERROR_SUCCESS == nRet)
                            Console.WriteLine("ManufactureId=0x{0:X}, MemoryType=0x{1:X}, MemoryDenisty=0x{2:X}", 
                                            byReadBuffer[0], byReadBuffer[1], byReadBuffer[2]);
                        else
                            Console.WriteLine("SPI Read Identification(RDID) Command Fail!");

                        // Send Read Status Register(RDSR) 0x05 Command
                        nSPICommand = 0x5;
                        nNumberOfBytesToRead = 1;
                        nRealDataLen = 0;
                        nRet = SPIWriteRead(hDeviceHandle, nSpiSelect, ref nSPICommand, 0x1, ref byReadBuffer[0], 
                                            nNumberOfBytesToRead, ref nRealDataLen, nTimeOutMs);
                        if (ERROR_SUCCESS == nRet)
                            Console.WriteLine("SPI Status=0x{0:X}", byReadBuffer[0]);
                        else
                            Console.WriteLine("SPI Read Status Register(RDSR) Command Fail!");

                        // Send write Enable(WREN) 0x06 Command
                        nSPICommand = 0x6;       // SPI Write Enable Command
                        nNumberOfBytesToRead = 1;
                        nRealDataLen = 0;
                        nRet = SPIWrite(hDeviceHandle, nSpiSelect, ref nSPICommand, 0x1, ref nRealDataLen, nTimeOutMs);
                        // SPIWrite Sample (Send SPI Write Enable Command)
                        if (ERROR_SUCCESS == nRet)
                            Console.WriteLine("SPI Write Enable Command Success!");
                        else
                            Console.WriteLine("SPI Write Enable Command Fail!");

                        // Send Read Status Register(RDSR) 0x05 Command
                        nSPICommand = 0x5;
                        nNumberOfBytesToRead = 1;
                        nRealDataLen = 0;
                        const Int32 STATUS_WEL_WRITE_ENABLE = 0x2;    // WEL
                        for (int n = 0; n <= 9; n++)
                        {
                            nRet = SPIWriteRead(hDeviceHandle, nSpiSelect, ref nSPICommand, 0x1, ref byReadBuffer[0], 
                                                nNumberOfBytesToRead, ref nRealDataLen, nTimeOutMs);
                            if (ERROR_SUCCESS == nRet)
                                Console.WriteLine("SPI Status=0x{0:X}", byReadBuffer[0]);
                            else
                                Console.WriteLine("SPI Read Status Register(RDSR) Command Fail!");

                            // check Write Enable (WEL bit1 = 1)
                            if (STATUS_WEL_WRITE_ENABLE == (byReadBuffer[0] & STATUS_WEL_WRITE_ENABLE))
                            {
                                Console.WriteLine("SPI Write Enable Success!");
                                break;
                            }
                            else
                                System.Threading.Thread.Sleep(100);
                        }

                        // Send Erase Command and Page Program Command

                        // Send Read Status Register(RDSR) 0x05 Command and Wait WIP bit0 = 0

                        // Send read data(Read) 0x03 Command
                        byte[] byWriteBuffer = new byte[10];
                        byWriteBuffer[0] = 0x3;  // Read Data Command
                        byWriteBuffer[1] = 0;    // AD1
                        byWriteBuffer[2] = 0;    // AD2
                        byWriteBuffer[3] = 0;    // AD3

                        nNumberOfBytesToRead = 10;
                        nRealDataLen = 0;
                        nRet = SPIWriteRead(hDeviceHandle, nSpiSelect, ref byWriteBuffer[0], 0x4, ref byReadBuffer[0], nNumberOfBytesToRead, ref nRealDataLen, nTimeOutMs);
                        if (ERROR_SUCCESS == nRet)
                        {
                            Console.Write("Read Data: ");
                            for (int n = 0; n <= 9; n++)
                                Console.Write("0x{0:X} ", byReadBuffer[n]);
                            Console.WriteLine("");
                        }
                        else
                            Console.WriteLine("SPI Read Data Command Fail!");
                    }
                    else
                        Console.WriteLine("Open device Fail!");
                    // Step6: Close Hid Device
                    nRet = CloseDeviceHandle(hDeviceHandle);
                }
                else
                    Console.WriteLine("No match Hid device!");
            }
            else
                Console.WriteLine("EnumDeviceByVid fail!");
            Console.ReadLine();
        }
    }
}