Rlp spi

How does one go about sending data with SPI in RLP?

@ Reinhard Ostermeier - And you do the managed section inside a lock statement to ensure that no other threads change the SPI just before you make the call?

I do the one time setup in Managed code.
Then I do a single SPI WriteRead from managed code
After that I call an RLP function to read and store the registers which are affected by one the managed SPI WriteRead
These two steps should be in a lock or at an early state of your app where no concurrent access can happen.

The I call my actual RLP SPI WriteRead method, which restores the registers and makes the WriteRead.
Since this is a single RLP call, no lock is needed here.

I see if I can post some code tomorrow.

I’m going to lookup the registers that get written. If they are sequential then we hit the jackpot.

Will using RLP for spi increase the performance?

Not the performance within the SPI WriteRead.
But if you need to repeat the transaction often or in an reliable Intervall (like reading and ADC with several kHz) then you need to do it in RLP.
If you just wrap a single WriteRead in an RLP function you win nothing.

In my specific case I want to use RLP interrupts to execute the WriteRead function. CLR interrupts take too long and polling could take even longer depending on what the system is doing.

Edit: Also there is a lot of plumbing that is setup every time you call WriteRead. That takes clock cycles to do each and every time. I’m trying to avoid that as well.

Here is my Managed code:

Initializing SPI

// initialize SPI here, not in RLP
         var spiCfg = new SPI.Configuration((Cpu.Pin)csPin, !csInverse, 1, 0, false, true, clockRate, spiChannel);
         _spi = new SPI(spiCfg);
         // do a single reading from managed to have all registers set
         var inBuffer = new ushort[1];
         _spi.WriteRead(new ushort[] { 0x8400 }, inBuffer);

         _doneEvent = new ManualResetEvent(false);
         RlpLtc186X.SamplingDone += OnSamplingDone;

         if (!RlpLtc186X.InitSpi(csPin, spiPort, Is16Bit))
         {
            throw new HwException(String.Concat("HW-Device '", Name, ": Failed to init SPI " + spiPort));
         }

      private void OnSamplingDone()
      {
         _doneEvent.Set();
      }

Read an averaged value over a period of time:

public float ReadValue(int channel, float averageTime)
      {
         lock (this)
         {
            _doneEvent.Reset();
            int sampleCount = Math.Max(1, (int)Math.Round(AverageValueSampleRate * averageTime));
            RlpLtc186X.Start(channel, sampleCount, AverageValueSampleRate);
            _doneEvent.WaitOne();
            _doneEvent.Reset();
            return RlpLtc186X.GetFloatValue();
         }
   }

The RlpLtc186x class

using System;
using Microsoft.SPOT;
using RLP = GHI.Processor.RuntimeLoadableProcedures;

namespace remes.Core.Rlp
{
   public class RlpLtc186X
   {
      private static RLP.NativeFunction _initSpi;
      private static RLP.NativeFunction _getSpiRegs;
      private static RLP.NativeFunction _getValue;
      private static RLP.NativeFunction _getFloatValue;
      private static RLP.NativeFunction _start;
      private static RLP.NativeFunction _stop;
      private static RLP.NativeFunction _getSamplesDone;

      internal static void LoadFunctions(RLP.ElfImage elfImage)
      {
         _initSpi = elfImage.FindFunction("Ltc186xInitSpi");
         _getSpiRegs = elfImage.FindFunction("Ltc186xGetSpiRegs");
         _getValue = elfImage.FindFunction("Ltc186xGetValue");
         _getFloatValue = elfImage.FindFunction("Ltc186xGetFloatValue");
         
         _start = elfImage.FindFunction("Ltc186xStart");
         _stop = elfImage.FindFunction("Ltc186xStop");
         _getSamplesDone = elfImage.FindFunction("Ltc186xGetSamplesDone");

         DoneEventId = RlpLoader.GetEventId();
         RLP.NativeEvent += RlpOnNativeEvent;
      }

      private static void RlpOnNativeEvent(object sender, RLP.NativeEventEventArgs e)
      {
         if (e.Data == DoneEventId)
         {
            var handler = SamplingDone;
            if(handler != null)
            {
               handler();
            }
         }
      }

      public static uint DoneEventId { get; private set; }

      public static bool InitSpi(int chipSelectPin, int spiPort, bool is16Bit)
      {
         return _initSpi.Invoke(chipSelectPin, spiPort, (byte)(is16Bit ? 1 : 0)) != 0;
      }

      public static uint[] GetSpiRegs()
      {
         var regs = new uint[10];
         _getSpiRegs.Invoke(regs);
         return regs;
      }

      public static int GetValue()
      {
         return _getValue.Invoke();
      }

      public static float GetFloatValue()
      {
         var value = new float[1];
         if (_getFloatValue.Invoke(value) != 0)
            return value[0];
         return 0f;
      }

      public static bool Start(int channel, int sampleCount, int sampleRate)
      {
         return _start.Invoke(channel, sampleCount, sampleRate, DoneEventId) != 0;
      }

      public static bool Stop()
      {
         return _stop.Invoke() != 0;
      }

      public static int GetSamplesDone()
      {
         return _getSamplesDone.Invoke();
      }

      public static event Ltc186XSamplingDoneEventHandler SamplingDone;
   }

   public delegate void Ltc186XSamplingDoneEventHandler();
}

And finally my RLP Code (For G120)

#include "RLP.h"
 #include "LPC178x.h"

unsigned int g_Ltc186xValue;
int g_Ltc186xSampleCount;
int g_Ltc186xChannel;
unsigned int g_Ltc186xDoneEventId;

int g_Ltc186xChipSelectPin;

RLP_Task g_Ltc186xTask;
int g_Ltc186xTimeOffset;

int g_Ltc186xSamplesDone;

LPC_SSP_TypeDef* g_Ltc186xSpi;

unsigned int g_Ltc186xRegisters[4] = { 0, 0, 0, 0 };
unsigned short g_Ltc186xControlValues16[8] = { 0x8400, 0xC400, 0x9400, 0xD400, 0xA400, 0xE400, 0xB400, 0xF400 };
unsigned short g_Ltc186xControlValues[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };

// privates

 #define SSP_TFE        (1 << 0) // transmit fifo empty
 #define SSP_TNF        (1 << 1) // transmit fifo not full
 #define SSP_RNE        (1 << 2) // receive fifo empty
 #define SSP_RFF        (1 << 3) // receive fifo full
 #define SSP_BSY        (1 << 4) // SSP0 busy


int Ltc186xReadValue()
{
    RLP->GPIO.Writepin(g_Ltc186xChipSelectPin, RLP_FALSE);

    while (!(g_Ltc186xSpi->SR & SSP_TNF));
    (g_Ltc186xSpi->DR) = g_Ltc186xControlValues[g_Ltc186xChannel];

    while (!(g_Ltc186xSpi->SR & SSP_RNE));
    int value = (g_Ltc186xSpi->DR);

    RLP->GPIO.Writepin(g_Ltc186xChipSelectPin, RLP_TRUE);

    return value;
}

void Ltc186xTaskCallback(void *arg)
{
    if (g_Ltc186xSampleCount > 1)
    {
        // setup next task 1st
        RLP->Task.ScheduleTimeOffset(&g_Ltc186xTask, g_Ltc186xTimeOffset);
    }

    int value = Ltc186xReadValue();
    if(value != 0)
    {
        g_Ltc186xValue += value;
        ++g_Ltc186xSamplesDone;
    }
    if (g_Ltc186xSampleCount < 0x7fffffff)
    {
        --g_Ltc186xSampleCount;
    }

    if (g_Ltc186xSampleCount <= 0)
    {
        // done
        RLP->Task.Abort(&g_Ltc186xTask);
        RLP->PostManagedEvent(g_Ltc186xDoneEventId);
    }
}


// publics

int Ltc186xInitSpi(void **args)
{
    g_Ltc186xChipSelectPin = *(int*)args[0];
    int spiPort = *(int*)args[1];
    unsigned char is16Bit = *(unsigned char*)args[2];

    /*RLP->GPIO.EnableOutputMode(g_Ltc186xChipSelectPin, RLP_TRUE);

    if(spiPort == 1)
    {
        LPC_IOCON->P0_15 = 0x02; // clock
        LPC_IOCON->P0_17 = 0x02; // MISO
        LPC_IOCON->P0_18 = 0x02; // MOSI
    }
    else if(spiPort == 3)
    {
        LPC_IOCON->P1_0 = 0x14; // clock
        LPC_IOCON->P1_1 = 0x10; // MOSI (for some strange reason this pin must not be assigned to SSP2!)
        LPC_IOCON->P1_4 = 0x14; // MISO
    }
    else
    {
        return 0; // error
    }*/

    if(spiPort == 1)
    {
        // power on
        //LPC_SC->PCONP |= (1UL << 21);

        //g_Ltc186xSpi = LPC_SSP0;
    }
    else if(spiPort == 3)
    {
        // power on
        //LPC_SC->PCONP |= (1UL << 20);

        g_Ltc186xSpi = LPC_SSP2;
    }
    else
    {
        return 0; // error
    }

    /*g_Ltc186xSpi->CR0 = 0x0f;       // set DSS data to 16-bit, Frame format SPI, CPOL = 0, CPHA = 0;
    g_Ltc186xSpi->CPSR = 12;        // clk prescalar (PCLK = 60Mhz)
    g_Ltc186xSpi->CR1 = 0x02;       // enable in master mode, normal operation
    g_Ltc186xSpi->IMSC = 0x00;      // disable interrupts
*/

    // set correct bit count
    int n = 0;
    if (is16Bit)
    {
        g_Ltc186xSpi->CR0 = (g_Ltc186xSpi->CR0 & 0xf0) | 0x0f;
        for(; n < 8; ++n)
        {
            g_Ltc186xControlValues[n] = g_Ltc186xControlValues16[n];
        }
    }
    else
    {
        g_Ltc186xSpi->CR0 = (g_Ltc186xSpi->CR0 & 0xf0) | 0x0b;
        for(; n < 8; ++n)
        {
            g_Ltc186xControlValues[n] = g_Ltc186xControlValues16[n] >> 4;
        }
    }

    g_Ltc186xRegisters[0] = g_Ltc186xSpi->CR0;
    g_Ltc186xRegisters[1] = g_Ltc186xSpi->CPSR;
    g_Ltc186xRegisters[2] = g_Ltc186xSpi->CR1;
    g_Ltc186xRegisters[3] = g_Ltc186xSpi->IMSC;

    g_Ltc186xChannel = -1;
    g_Ltc186xValue = -1;

    return 1;
}

int Ltc186xGetSpiRegs(void **args)
{
    unsigned int* regs = (unsigned int*)(args[0]);

    regs[0] = LPC_IOCON->P1_0;
    regs[1] = LPC_IOCON->P1_1;
    regs[2] = LPC_IOCON->P1_4;
    regs[3] = LPC_IOCON->P1_8;
    regs[4] = LPC_SC->PCONP;
    regs[5] = LPC_SSP2->CR0;
    regs[6] = LPC_SSP2->CPSR;
    regs[7] = LPC_SSP2->CR1;
    regs[8] = LPC_SSP2->IMSC;

    return 1;
}


int Ltc186xGetSamplesDone(void **args)
{
    return g_Ltc186xSamplesDone;
}

int Ltc186xGetValue(void **args)
{
    return (int)g_Ltc186xValue;
}

int Ltc186xGetFloatValue(void ** args)
{
    float *value = (float*)args[0];

    if (g_Ltc186xSamplesDone <= 0)
    {
        value[0] = 0.0f;
        return 0;
    }
    value[0] = (float)g_Ltc186xValue / g_Ltc186xSamplesDone;
    return 1;
}

int Ltc186xStart(void **args)
{
    g_Ltc186xChannel = *(int*)args[0];
    g_Ltc186xSampleCount = *(int*)args[1];
    int sampleRate = *(int*)args[2];
    g_Ltc186xDoneEventId = *(unsigned int*)args[3];

    // restore registers
    g_Ltc186xSpi->CR0 = g_Ltc186xRegisters[0];
    g_Ltc186xSpi->CPSR = g_Ltc186xRegisters[1];
    g_Ltc186xSpi->CR1 = g_Ltc186xRegisters[2];
    g_Ltc186xSpi->IMSC = g_Ltc186xRegisters[3];

    // dummy read to setup ADC and trigger next sample
    Ltc186xReadValue();
    RLP->Delay(4); // microseconds

    if (g_Ltc186xSampleCount <= 1)
    {
        g_Ltc186xValue = Ltc186xReadValue();
        g_Ltc186xSampleCount = 0;
        g_Ltc186xSamplesDone = 1;

        RLP->PostManagedEvent(g_Ltc186xDoneEventId);
    }
    else
    {
        g_Ltc186xSamplesDone = 0;
        g_Ltc186xValue = 0;

        g_Ltc186xTimeOffset = 1000000 / sampleRate; // offset is in micro seconds
        if (g_Ltc186xTimeOffset < 1)
        {
            return 0;
        }
        // if sample rate is too high, reduce by factor
        // reading takes nearly 10 micro seconds, so we have a maximum load of 25% here
        if (g_Ltc186xTimeOffset < 40)
        {
            g_Ltc186xSampleCount = (((g_Ltc186xSampleCount + 1) * g_Ltc186xTimeOffset) / 40) - 1;
            g_Ltc186xTimeOffset = 40;
        }
        g_Ltc186xTimeOffset -= 12; // reduce by overhead, which seams to be more or less constant

        RLP->Task.Initialize(&g_Ltc186xTask, Ltc186xTaskCallback, &g_Ltc186xSampleCount, RLP_TRUE); // use kernel mode to be accurate in timing
        RLP->Task.Schedule(&g_Ltc186xTask);
    }

    return 1;
}

int Ltc186xStop(void **args)
{
    g_Ltc186xSampleCount = 0;
    return 1;
}

With this I have managed to sample the ADC with up to 40kHz over a longer period of time without blocking the managed part completely.

This method consumes 25% of resources; as mentioned in the RLP code?

EDIT: As in your 40Khz ADC sample rate uses 25%

Actually this code limits the sample rate to 25kHz (40 microseconds interval)
Since the SPI transaction takes about 10 microseconds it should consume about 25% resources at 25kHz.
I think the 40kHz is the maximum I tried and worked as expected.
but at 40kHz I would expect about 40% CPU time for SPI.