Spi read to blobs - can this code be made faster?

summary; I think the limiting factor in speed of this code is the entering of bytes into the blob.

I am using the ADE7953 energy measuring chip from Analog Devices. This ic includes a feature that you can read the data of the instantaneous waveform (plot the sine curve of voltage, waveform of the current). the fastest I have been able to read this is to get 60 readings in 37 milliseconds which is about 1600 Hz.

The read is done by sending a 2-byte address and 3rd byte indicating I want to read rather than write to the register. The ADE chip responds by sending 3 bytes, a 24 bit number. I tried getting the communication to go full duplex but the chip does not seem to work this way.

I have run the spi clock as high as 3750 kHz and it does not get appreciably faster than much slower speeds, though of course there is a speed gain due to the shorter communication time.

3750 kHz - time to build data points is 37.2ms
486 kHz - time to build data points is 48.1 ms

have I done all I can?

btw, I am sending a table with blobs in it from device to agent. The agent is decoding the blobs and making arrays, then converting to JSON for the browser. this is an optimization I added recently and I feel it made the software more responsive.

I only ask for this waveform data infrequently. The primary function of the device is to do the mundane ; log watts.

`
cswriter <- CSpin.write.bindenv(CSpin);

myspi <- hardware.spi189;
myspireader <- myspi.writeread.bindenv(myspi);

function getawavespi(){

//pre-compute the register address string

local Vaddr = format("%c%c", (V>>8), V&0xff);
local IAaddr = format("%c%c", (IA>>8), IA&0xff);

//add the read byte and blank bytes to keep the clock going
//strings are 6 bytes long
local readVolts= Vaddr + format("%c",128) + format("%c%c%c",0xaa,0xaa,0xaa);

local readCurrent= IAaddr + format("%c",128)+ format("%c%c%c",0xaa,0xaa,0xaa);

//blobs to hold the data, 3 bytes each, 60 data points

local IAblob = blob(180); //current
local Vblob = blob(180); //volts
local tblob = blob(240); //time

//results of spi read
local resultV = “”;
local resultA = “”;

local start = hardware.micros();

//60 data points, taking roughly 37 to 50 milliseconds

for(local a=0;a<60;a+=1){
 
 
  cswriter(0);
 
    resultV = myspireader(readVolts);
 
  cswriter(1);
 

  tblob.writen(hardware.micros(), 'i');

  try {
    Vblob.writen(resultV[5],'b');
    Vblob.writen(resultV[4],'b');
    Vblob.writen(resultV[3],'b');
    
  }
  catch (e)
  {
    Vblob.writen(0,'b');
    Vblob.writen(0,'b');
    Vblob.writen(0,'b');
  }
  
  cswriter(0);

  resultA = myspireader(readCurrent);

  cswriter(1);
 
  try {
    IAblob.writen(resultA[5],'b');
    IAblob.writen(resultA[4],'b');
    IAblob.writen(resultA[3],'b');
    
  }
  catch (e)
  {
    IAblob.writen(0,'b');
    IAblob.writen(0,'b');
    IAblob.writen(0,'b');
  }

}
server.log(hardware.micros()-start);//37.26 milliseconds

cswriter(1);

//built a table up with tables and values to send to the agent
local sendtable = {};
sendtable[“IAblob”] <- IAblob;
sendtable[“Vblob”] <- Vblob;
sendtable[“tblob”] <- tblob;
sendtable[“start”] <- start;

//Get RMS values so we can normalize the vertical scale
local ICOUNTA = readbus(IRMSA)
local VCOUNT = readbus( VRMS);

//add RMS values to the table so that the agent will get them
sendtable[“ICOUNTA”] <- ICOUNTA;
sendtable[“VCOUNT”] <- VCOUNT;

//Send the raw blobs to the agent where they can be decoded and
//turned into waveforms

agent.send(“wave”,sendtable);

}`

sorry, had a look and this post was really long. I’ll paraphrase the question.

I have a loop that does a spi read. It gets 6 bytes, but the first 3 are junk. I need to record the 3 bytes and then read spi again, quickly as possible, and continue looping to get 60 datapoints.

The way I am doing it is by writing the bytes to a blob one at a time.

The only other thing I could think of would be to reference positions in an array of characters by [n]. Maybe I will try that next.

based on the speed of the loop I figured it must be spending its time writing to the blob.

11.47ms, about three times faster:

`CSpin <- hardware.pin9;

const V = 100;
const IA = 30;

hardware.spi257.configure(0,3750);

function getawavespi(){
local cswriter = CSpin.write.bindenv(CSpin);
local myspi = hardware.spi257;
local myspireader = myspi.writeread.bindenv(myspi);

//pre-compute the register address string

local Vaddr = format("%c%c", (V>>8), V&0xff);
local IAaddr = format("%c%c", (IA>>8), IA&0xff);

//add the read byte and blank bytes to keep the clock going
//strings are 6 bytes long
local readVolts= Vaddr + format("%c",128) + format("%c%c%c",0xaa,0xaa,0xaa);

local readCurrent= IAaddr + format("%c",128)+ format("%c%c%c",0xaa,0xaa,0xaa);

//blobs to hold the data, 3 bytes each, 60 data points

local IAblob = blob(360); //current
local Vblob = blob(360); //volts
local tblob = blob(240); //time

local twriter = tblob.writen.bindenv(tblob);
local vwriter = Vblob.writestring.bindenv(Vblob);
local iwriter = IAblob.writestring.bindenv(IAblob);
local us = hardware.micros.bindenv(hardware);

//results of spi read
local resultV = "";
local resultA = "";

local start = hardware.micros();

for(local a=0;a<60;a+=1){
    cswriter(0);
    resultV = myspireader(readVolts);
    cswriter(1);
    
    twriter(us(), 'i');
    vwriter(resultV);
    
    cswriter(0);
    resultA = myspireader(readCurrent);
    cswriter(1);
    
    iwriter(resultA);
}
server.log(hardware.micros()-start);//37.26 milliseconds

}

getawavespi();`

Here’s what I did:

  • Initial time: 36.36ms
  • Make cswriter, myspi, and myspireader locals of getawavespi: 32.66ms
  • Remove try/catch (those operations never fail unless SPI is deconfigured): 32.64ms
  • Write the whole of resultV/resultA to the blobs (leaving the agent to pick out the three useful bytes, but using 1/3 as many Squirrel instructions): 23.35ms
  • Bind a local twriter to tblob.writen: 21.62ms
  • Bind a local vwriter to Vblob.writestring: 19.51ms
  • Bind a local iwriter to IAblob.writestring: 17.71ms
  • Bind a local us to hardware.micros (!): 11.47ms

The usual messages apply: locals are fast, and bound methods are faster than global lookups.

Peter

wow, Peter this is awesome! thanks so much.

I will definitely try this.

I knew about locals being faster but I had nowhere near enough skill to apply it as you did.

I might be able to eek out a little more speed because I have just realized I really don’t need to call hardware.micros(). I took that out of my code and dropped from 37ms to 29ms. I get sufficient information just looking at the starting time and ending time and dividing by how many times I went through the loop.

I might also do some post-processing on the device to get the useful information from the blob and discard the waste. This prevents useless bytes having to be sent over wifi. This is not always noticed but I have the concern that in noisy environments with weak wifi bandwidth or signal strength that I am always better off with the lowest amount of data to send.

I used to do this with i2c and I think there must have been some errors and it is why the try/catch was in there.

works!

The reading speed is 194 microseconds so this is getting near the maximum acquisition speed in the chip. The reproduction of the current waveform of this CFL bulb is close to true now.

final code; I added code to remove the garbage bytes before sending the message from device to agent. I also took out the reading of hardware.micros() but I think I will experiment with adding that back in. The voltage waveform has a bit of jitter now - maybe due to not stamping the time.

`

function getawavespi(){

//credit:
//peter, electricimp.com
//for helping with all the awesome speed improvements

local chipSelect = CSpin.write.bindenv(CSpin);
local myspi = hardware.spi189;
local myspireader = myspi.writeread.bindenv(myspi);

//pre-compute the register address string

local Vaddr = format("%c%c", (V>>8), V&0xff);
local IAaddr = format("%c%c", (IA>>8), IA&0xff);

//add the read byte and blank bytes to keep the clock going
//strings are 6 bytes long
local readVolts= Vaddr + format("%c",128) + format("%c%c%c",0xaa,0xaa,0xaa);

local readCurrent= IAaddr + format("%c",128)+ format("%c%c%c",0xaa,0xaa,0xaa);

//blobs to hold the data, 6 bytes each X number of data points


local _IAblob = blob(waveN*6); //current
local _Vblob = blob(waveN*6); //volts

local vwriter = _Vblob.writestring.bindenv(_Vblob);
local iwriter = _IAblob.writestring.bindenv(_IAblob);

local _result = "";

local start = hardware.micros();
local endT = 0;

for(local a=0;a<waveN;a+=1){
  
    chipSelect(0);
    _result = myspireader(readVolts);
    chipSelect(1);
    
   
    vwriter(_result);
     
     
    chipSelect(0);
    _result = myspireader(readCurrent);
    chipSelect(1);
    
    iwriter(_result);
    
   
};//194 microseconds per loop

endT = hardware.micros();
mylog(endT-start);

//built a table up with tables and values to send to the agent

local IAblob = blob(waveN*3); //current
local Vblob = blob(waveN*3); //volts

//pack the blob down, getting rid of waste bytes
//The agent could also do this, but this way the wifi
//does not have to send bytes that are not used

for(local a=3;a<(waveN*6);a+=6){
    
    Vblob.writen(_Vblob[a+2],'b');
    Vblob.writen(_Vblob[a+1],'b');
    Vblob.writen(_Vblob[a],'b');
    
    IAblob.writen(_IAblob[a+2],'b');
    IAblob.writen(_IAblob[a+1],'b');
    IAblob.writen(_IAblob[a],'b');
    
};

//built a table up with tables and values to send to the agent

local sendtable = {};
sendtable["IAblob"] <- IAblob;
sendtable["Vblob"] <- Vblob;
sendtable["span"] <- (endT-start);

//Get RMS V and I values so we can normalize the vertical scale
local ICOUNTA = readbus(IRMSA)
local VCOUNT = readbus( VRMS);

//add RMS values to the table so that the agent will get them
sendtable["ICOUNTA"] <- ICOUNTA;
sendtable["VCOUNT"] <- VCOUNT;

//Send the raw blobs to the agent where they can be decoded and
//turned into waveforms, then JSON for Highcharts

agent.send("wave",sendtable);

}
`

Oh, that is really sweet! Now I want to use your code on my becky plugtops :slight_smile: