Wiznet W5500 Driver

Got it. Thanks.

Question about driver. Where is ISpiChannelManager Defined?

Ooops. Like I said ā€¦ extracted from another project. Hereā€™s the missing bits, below. You have two options: 1) just remove calls to the SpiChannelManager if you only have one SPI device. 2) if you are sharing your SPI bus across multiple devices and threads, then the SpiChannelManager will make sure everyone plays nice together.

If you do use this SPI arbitration manager code (below), then you wonā€™t have ā€˜IDriverā€™ nor the DiContainer. You can remove IDriver, DIContainer, and the Start() and Stop() routines and just declare and use SpiChannelManager as a static. The DIContainer is a Dependency Injection framework that I use a lot for runtime dynamic discovery and unit testing purposes. The Start/Stop/IDriver stuff is all part of the Verdant Driver/Agent framework, which you also wonā€™t have. (All Drivers get their Start function called at startup, and Stop at shutdown. Itā€™s all useful stuff, so I will package it and share it at some point, but removing those bits will allow you to use the SpiChannelManager without Verdant.

using System;
using System.Text;
using Microsoft.SPOT.Hardware;

namespace PervasiveDigital.Common
{
    public interface ISpiChannelManager
    {
        SPI this[SPI.SPI_module index] { get; set; }
        IDisposable ObtainExclusiveAccess(SPI.Configuration config);
    }
}

And the code that does the workā€¦

using System;
using System.Text;
using System.Threading;
using Microsoft.SPOT.Hardware;

using PervasiveDigital.Common;

namespace PervasiveDigital.Core.Drivers
{
    public class SpiChannelManager : IDriver, ISpiChannelManager
    {
        private readonly SPI[] _channels = new SPI[4];
        private readonly SpiLockObject[] _locks = { new SpiLockObject(), new SpiLockObject(), new SpiLockObject(), new SpiLockObject() };

        public SPI this[SPI.SPI_module index]
        {
            get { return _channels[(int)index]; }
            set { _channels[(int)index] = value; }
        }

        public IDisposable ObtainExclusiveAccess(SPI.Configuration config)
        {
            _locks[(int) config.SPI_mod].Enter();
            _channels[(int) config.SPI_mod].Config = config;
            return _locks[(int) config.SPI_mod];
        }

        public void Start()
        {
            // Register ourselves as also being a service
            DiContainer.Instance.Register(typeof(ISpiChannelManager), () => this);
        }

        public void Stop()
        {
        }
    }

    class SpiLockObject : IDisposable
    {
        private readonly object _lock = new object();
        private bool _locked = false;

        ~SpiLockObject()
        {
            Dispose();
        }

        public void Enter()
        {
            Monitor.Enter(_lock);
            _locked = true;
        }

        public void Dispose()
        {
            if (_locked)
            {
                _locked = false;
                Monitor.Exit(_lock);
            }
        }
    }
}

The IDisposable implementation for SpinLockObject does not call GC.SuppressFinalize when calling Dispose directly, I would be interested to know if that was intentional. To my knowledge, like the desktop framework, this will result in delayed reclamation of the memory due to the instance being processed from the finalization queue and moving to freachable etc.

1 Like

Weā€™ll there is no need to do it right away, esp on things like the G120. There is a lot of ram to spend.

@taylorza is right though, thatā€™s an important optimization. While these objects are small and you might have a lot of memory, they are created on each SPI transaction, so you could be churning memory quite a bit and triggering unnecessarily expensive GC cyclesā€¦ Using SuppressFinalize would improve the performance in a meaningful way.

Thanks for pointing that out!

2 Likes

Well I got the driver working on NETMF. In case anyone needs test code.

using System;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using GHI.Pins;
using System.Threading;
using PervasiveDigital.NETMF.Net.Sockets;

namespace TestMaina{
public class Program {
    static Thread _workThread = new Thread(Work);
    public static void Main() {
        _workThread.Start();
    }
    public static void Work() {
        PervasiveDigital.NETMF.W5500.Wiznet_W5500 _wiznet = new PervasiveDigital.NETMF.W5500.Wiznet_W5500(SPI.SPI_module.SPI3,
            (Cpu.Pin)GHI.Pins.G120.P0_3, (Cpu.Pin)GHI.Pins.G120.P0_2, (Cpu.Pin)GHI.Pins.G120.P0_25, false);

        _wiznet.PhysicalAddress = new byte[] { 0x00, 0x08, 0xDC, 0x1, 0x2, 0x3 };
        byte[] _mac = _wiznet.PhysicalAddress;
        _wiznet.EnableDhcp();

        Socket _socket = new Socket(_wiznet, AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        _socket.Bind(new PervasiveDigital.NETMF.Net.IPEndPoint(_wiznet.Address, 23));
        _socket.Listen();

        Socket _telnet = _socket.Accept();
        while (_telnet.Connected) {
            byte[] _data = new byte[255];
            int _read = _telnet.Receive(_data);
            if (_read > 0) {
                _telnet.Send(_data, _read, SocketFlags.None);
                Thread.Sleep(0);
            }
        }
    }
}
}
4 Likes

@mcalsyn, Ok new question. How do we send UDP messages?

Iā€™ve noticed the section in W5000.Send, which seems to only work for TCP sockets. Wonā€™t work for UDP because the value of status will be 0x22 (SOCK_UDP). Is this a bug or is there another way to send UDP messages?

if ((status != (byte)W5500Driver.Sn_SR.SOCK_ESTABLISHED) && (status != (byte)W5500Driver.Sn_SR.SOCK_CLOSE_WAIT)) {
                return 0;
            }

Here is the full method.

public int Send(int slot, byte[] buffer, int offset, int len, SocketFlags socketFlags, int timeout) {
        int result = len;

        if (len > _driver.SocketBufferSize)
            throw new Exception("Send exceeds buffer size");

        int freeSize = 0;
        do {
            freeSize = _driver.SocketRegisterReadUshort(W5500Driver.SocketRegisters.TX_FSR, slot);
            var status = _driver.SocketRegisterRead(W5500Driver.SocketRegisters.SR, slot);
            if ((status != (byte)W5500Driver.Sn_SR.SOCK_ESTABLISHED) && (status != (byte)W5500Driver.Sn_SR.SOCK_CLOSE_WAIT)) {
                return 0;
            }
        } while (freeSize < result);

        // copy data
        result = _driver.SocketSendData(slot, buffer, offset, len);
        _driver.SocketRegisterWrite(W5500Driver.SocketRegisters.CR, slot, (byte)W5500Driver.Sn_CR.SEND);
        // wait for the command to be acknowledged
        while (_driver.SocketRegisterRead(W5500Driver.SocketRegisters.CR, slot) != 0) ;

        while ((_driver.SocketRegisterRead(W5500Driver.SocketRegisters.IR, slot) & (byte)W5500Driver.Sn_IR.SEND_OK) != (byte)(byte)W5500Driver.Sn_IR.SEND_OK) {
            if (_driver.SocketRegisterRead(W5500Driver.SocketRegisters.SR, slot) == (byte)W5500Driver.Sn_SR.SOCK_CLOSED) {
                this.Close(slot);
                return 0;
            }
            if ((_driver.SocketRegisterRead(W5500Driver.SocketRegisters.IR, slot) & (byte)W5500Driver.Sn_IR.TIMEOUT) != 0) {
                _driver.SocketRegisterWrite(W5500Driver.SocketRegisters.IR, slot, (byte)(W5500Driver.Sn_IR.SEND_OK | W5500Driver.Sn_IR.TIMEOUT)); // clear SEND_OK & TIMEOUT
                this.Close(slot);
                return 0;
            }
        }
        _driver.SocketRegisterWrite(W5500Driver.SocketRegisters.IR, slot, (byte)W5500Driver.Sn_IR.SEND_OK);

        return result;
    }

SendTo() and ReceiveFrom() will send and receive UDP packets. These are the connectionless entry points.

Kewlio, I got it.

Hi mcalsyn, is there a way to email you directly? Itā€™s in regards to printhead drivers.
Iā€™m new here and not sure how to send you a message other than replying.

Thanks, Herb

Do you still have it? Can we put on TinyCLR?

1 Like

Item was deleted? We wanted it :)))

Yup - still got it.

2 Likes

we are talking about the code, not your dance skills, right?

4 Likes

Both of course :smiley: Iā€™ll submit a PR for the drivers repo if you want. I see what happened - I cleaned out my DropBox public shares. GitHub is the right place for this so I will see if I can spin up a driver in the drivers repo.

3 Likes

Drivers repo requires the driver to be a NuGet and with TinyCLR GHI namespaces. If you are okay with that then yes please do share.

1 Like

If it would be a driver, will it still work when using the C# network stuff like sockets and such? Or would it require the usage of different functions?

No you canā€™t and should not use the standard stuff. This is not secure and not fully supported. The user should be aware the is not the real deal. However, keeping a .NET friendly interface is welcomed.

1 Like

So things like VNC wouldnā€™t work with the W5500?

Since they use the.net tcp/udp functions?

Also, why wouldnā€™t this be a secure option? It could be implemented to be secure, right?