chipKIT® Development Platform

Inspired by Arduino™

Throughput limitations of ChipKit Wi-Fire

Created Tue, 03 Nov 2015 13:20:27 +0000 by Demian


Demian

Tue, 03 Nov 2015 13:20:27 +0000

Hi,

I'm using a ChipKit Wi-Fire board (with production silicon MCU chip) as a wireless datalogger over UDP (I know UDP gives no guarantee of packet reception, but that's not the issue here). However, I'm having some problems getting the throughput I want, so I'm wondering if the bottleneck is my code or the WiFi module attached to the Wi-Fire board itself. I'm using a 1 channel, 8 bit effective resolution, external ADC (LTC1196) over an SPI channel which runs at 8 MHz (still clocking 16 cycles for each sample since the ADC requires 12 anyway). If I sample at 100 ksps (meaning I need a continous 800 kbps data throughput) I typically receive 100/100 packets in Wireshark. If I increase the sampling rate to 200 ksps (which is what I ideally want) I receive no packets whatsoever. So something is being clogged up. If I use a sampling frequency somewhere in between, I can oftentimes still receive most of the packets, but I notice that they are received in Wireshark with a big delay and reception is going slowly.

I've posted the code below (written in Mpide 0150), and it basically works as follows: I use Timer3 as an interrupt to do the sampling (which changes the state to SAMPLE). I do a double buffering between buffer_even and buffer_odd, so when one of them is filled up, after 1024 samples, I switch to the other buffer for the next 1024 samples at the same time as I send the first buffer of data to the udpClient.writeDatagram() function (by changing the state to WRITE). In between samples I change the state to WAIT, which does nothing. After 100 packets, I end the program.

To rule out the possibility that it's the external ADC SPI communication that causes the problem, I also try to just load the buffers with a dummy number like this: packet_even[sample_counter] = sample_counter; instead of doing the SPI routine. Still I can't get a single packet through at 200 ksps. I'm not very good at writing good code, so that's why I was wondering where the bottleneck could be. If it's my style of coding, or if I'm basically limited by the WiFi module on the board and how it communicates with the MCU. Is there a way to set the SPI clock between the MCU and the WiFi module?

Here's the full code:

/*
 SPI channel:
 
 CS: pin 10
 MOSI: pin 11
 MISO: pin 12
 SCK: pin 13
 */

#include <MRF24G.h>
#include <DEIPcK.h>
#include <DEWFcK.h>

#include <DSPI.h>
#include <xc.h>             /* contains Vector Name/Number Macros */
#include <sys/attribs.h>    /* contains __ISR() Macros */

const char * szIPServer = "10.100.1.100";    // server to connect to
uint16_t portServer = DEIPcK::iPersonalPorts44 + 400;  // port 44400

// Specify the SSID
const char * szSsid = "Linksys05424";
const char * szPassPhrase = "digilent";
#define WiFiConnectMacro() deIPcK.wfConnect(szSsid, szPassPhrase, &status)
  

typedef enum
{
    NONE = 0,
    CONNECT,
    RESOLVEENDPOINT,
    WRITE,
    SAMPLE,
    WAIT,
    CLOSE,
    DONE,
} STATE;

STATE state = CONNECT;

// must have a datagram cache
UDPSocket udpClient;

IPSTATUS    status = ipsSuccess;
IPEndPoint  epRemote;

byte packet_even[1024];
byte packet_odd[1024];
int packet_size = 1024;
volatile uint8_t buffer_flag = 0; // flag for whether to use odd (1) or even (0) buffer. 

volatile uint16_t numPackets = 100;

volatile uint16_t sample_counter;
volatile uint16_t packet_counter;

const uint8_t testPin = 4;

// SPI configuration
DSPI0 spi;
const uint8_t chipSelect = 10;

const uint32_t SPI_frequency = 8000000;
uint16_t num1;
uint8_t j;

/* Define the Interrupt Service Routine (ISR) */
void __attribute__((interrupt)) myISR() {
  clearIntFlag(_TIMER_3_VECTOR);
  state = SAMPLE;
}


/***    void setup()
 *      Use DHCP to get the IP, mask, and gateway
 *      by default connect to port 44400
 * ------------------------------------------------------------ */
void setup() {
  Serial.begin(9600);
  Serial.println("UDPEchoClient 3.0");
  Serial.println("Digilent, Copyright 2014");
  Serial.println("");
    
  // pinMode(testPin, OUTPUT);
  pinMode(chipSelect, OUTPUT);
  digitalWrite(chipSelect, HIGH);
  spi.begin();
  spi.setTransferSize(DSPI_16BIT);
  spi.setSpeed(SPI_frequency);
  
  sample_counter = 0;
  packet_counter = 0;
  
  setIntVector(_TIMER_3_VECTOR, myISR);
  setIntPriority(_TIMER_3_VECTOR, 6, 0);
  clearIntFlag(_TIMER_3_VECTOR);
  setIntEnable(_TIMER_3_VECTOR);
  start_timer_3();
}

void start_timer_3(void) {
  T3CONbits.TON = 0;         /* Turn the timer off */
  TMR3 = 0;                  /* Clear the counter  */

  // For 200 kHz sampling rate:
  // T3CONbits.TCKPS = 1; 
  // PR3 = 250;               /* Set the frequency = 50 MHz / PR3 */
   
   // For 100 kHz sampling rate:
   T3CONbits.TCKPS = 1; 
   PR3 = 500;               /* Set the frequency = 50 MHz / PR3 */
} 

/***    void loop()
 *      We are using the default timeout values for the DNETck and UdpClient class
 *      which usually is enough time for the Udp functions to complete on their first call.
 *      
 * ------------------------------------------------------------ */
void loop() {

    switch(state)
    {
        case CONNECT:
            if(WiFiConnectMacro())
            {
                Serial.println("WiFi connected");
                deIPcK.begin();
                state = RESOLVEENDPOINT;
            }
            else if(IsIPStatusAnError(status))
            {
                Serial.print("Unable to connect, status: ");
                Serial.println(status, DEC);
                state = CLOSE;
            }
            break;

        case RESOLVEENDPOINT:
            if(deIPcK.resolveEndPoint(szIPServer, portServer, epRemote, &status))
            {
                if(deIPcK.udpSetEndPoint(epRemote, udpClient, portDynamicallyAssign, &status))
                {
                    state = WAIT;
                    Serial.println("Timer 3 started");
                    T3CONbits.TON = 1; // Turn Timer3 on for external ADC interrupts
                }
            }

            // alway check the status and get out on error
            if(IsIPStatusAnError(status))
            {
                Serial.print("Unable to resolve endpoint, error: 0x");
                Serial.println(status, HEX);
                state = CLOSE;
            }
            break;
       case SAMPLE:
              if(buffer_flag == 0) // if using even buffer
              {
                  digitalWrite(chipSelect, LOW);
                  num1 = spi.transfer(0x00);
                  digitalWrite(chipSelect, HIGH);
                  packet_even[sample_counter] = (uint8_t)(num1 >> 5);
                  //packet_even[sample_counter] = sample_counter; // for testing purposes
                  sample_counter++;
                  if(sample_counter == packet_size)
                  {
                      sample_counter = 0;
                      state = WRITE;
                      buffer_flag = 1;
                   }
                   else
                       state = WAIT;                   
              }
              
              else // if using odd buffer
              {
                  digitalWrite(chipSelect, LOW);
                  num1 = spi.transfer(0x00);
                  digitalWrite(chipSelect, HIGH);
                  packet_odd[sample_counter] = (uint8_t)(num1 >> 5);
                  //packet_odd[sample_counter] = 255-sample_counter; // for testing purposes
                  sample_counter++;
                  if(sample_counter == packet_size)
                  {
                      sample_counter = 0;
                      state = WRITE;
                      buffer_flag = 0;
                   }
                   else
                       state = WAIT;
              }
              break;
             
       case WRITE:
            //digitalWrite(testPin, !digitalRead(testPin));
            if(deIPcK.isIPReady(&status))
            {
                if(buffer_flag == 0) // if buffer WAS odd
                {
                    udpClient.writeDatagram(packet_odd, packet_size);
                    packet_counter++;
                    if(packet_counter == numPackets)
                    {
                      T3CONbits.TON = 0;
                      state = CLOSE;
                    }
                    else
                      state = WAIT;     
                }
                
                else // if buffer WAS even
                {
                    udpClient.writeDatagram(packet_even, packet_size);
                    packet_counter++;
                    if(packet_counter == numPackets)
                    {
                      T3CONbits.TON = 0;
                      state = CLOSE;
                    }
                    else
                      state = WAIT;     
                }
            }
            else if(IsIPStatusAnError(status))
            {
                Serial.print("Lost the network, error: 0x");
                Serial.println(status, HEX);
                state = CLOSE;
            }
            break;

        case WAIT:
          // waiting for the next Timer3 interrupt 
          break;
          
        case CLOSE:
            udpClient.close();
            Serial.println("Closing udpClient, Done with sketch.");
            state = DONE;
            break;

        case DONE:
          while(1);
        default:
            break;
    }
    // keep the stack alive each pass through the loop()
    DEIPcK::periodicTasks();
}

lstandage

Tue, 10 Nov 2015 04:57:01 +0000

You asked the question I was going to pose, which was what the speed of the SPI interface with the WiFi compared with the speed of the ADC.

You're sampling at 200 ksps, and the WiFi SPI rate is 20MHz. This means that the WiFi module has 100 clocks to get the data through, including all TCPIP header/trailer data, before the next sample is ready to go. That seems very unlikely to get the data throughput you want.

Bumping up the speed of the WiFi SPI to 25 MHz will get you 25 more clocks per sample. May not be enough, but it's worth a try.


Demian

Tue, 10 Nov 2015 08:37:58 +0000

Thanks for the reply, I'll try that out. However, I should specify that I'm doing double buffering of the sampled data, where I sample 1024 samples (each sample is 8 bit) before sending the array of data to the WiFi module (then I switch to a different buffer for the next 1024 samples). With 1024 bytes of data, this gives a total packet size of 1066 bytes over UDP. To sample 1024 bytes at 200 ksps takes just a bit over 5 ms. To transfer 1066 bytes over SPI between the MCU and WiFi module would take 0.43 ms at 20 MHz (theoretically, I haven't probed the SPI4 channel with a logic analyzer to see if the clock runs continuously or not at 20 MHz). From an unexperienced viewpoint I should think that would be ample time.