Skip to content

Qespruino_SPI

andiy edited this page Feb 8, 2018 · 14 revisions

SPI performance improvement using DMA
Tested on EspruinoWIFI only

Using DMA improves SPI performance in two ways:

  1. Eliminates inter-byte gaps caused by software reload of SPI TX register as described here

    • Native SPI: max. 635 kByte/sec (e.g. 5 MBaud)
    • QSPI: max. 1562 kByte/sec (e.g. 12.5 MBaud, which is the hardware limit from the CPU's clock system)
  2. Transmission commands return immediately after transmission has started
    This allows your application to prepare the next data (or do something else) in parallel to transmission.

why not write"Async"?

The chosen procedure is still synchronous, but none-blocking. This is closer to hardware behaviour and allows a more practicable coding avaoiding the async's hell of callbacks as well as the demand for unpredictable buffer queues.

why wrapper class

..instead of overriding original SPI class?

  • override of native prototypes is possible, but there is no option for an inherited call to super (see here)
  • override+inherit is at least necessary for the .setup() method to fetch current baudrate

SAMPLES

Basic

const QSPI= require('Qespruino_spi');
const QSPI1= new QSPI(SPI1);	// SPI1,SPI2,SPI3
QSPI1.setup(...);		// similar to SPI1.setup(...)

// allocate some *flat* transmission buffer
let buf1= E.newUint8Array(byte_cnt);  	// new Uint8Array( E.toArrayBuffer( E.toString({data:0,count:10000})));		

// send something w/o blocking	
buf1[..]=...;
QSPI1.writeInterlaced(buf1);

// we need a 2nd buffer to prepare the next transmission in parallel to transmitting the first one
let buf2= E.newUint8Array(byte_cnt);

// any followup-transmission waits automatically (synchronous) until the previous one finished its job
buf2[..]=...;				
QSPI1.writeInterlaced(buf2);

// now buf2 is on its way to the peripheral.. and buf1 is granted to be available again for new data
buf1[..]=...;
QSPI1.writeInterlaced(buf1);

// ..we go on and on and on... with toggling buffers 
buf2[..]=...;				
QSPI1.writeInterlaced(buf2);

Dealing with CS pins

At the end of a transmission we may need to toggle a CS pin (to deselect an SPI slave).

We can do this either synchronous..

QSPI1.awaitInterlaced()
CS_pin.write( UNSELECT_SLAVE);

..or async using a callback.

QSPI1.awaitInterlaced(function(){
  CS_pin.write( UNSELECT_SLAVE);
})							

Mixing interlaced with native SPI

// just do a synchronous wait for the end of the recent interlaced transmission
QSPI1.awaitInterlaced();
// and now use any transmission method of the native SPI 
// note: **do never use the native .setup method**
SPI1.write(...);
SPI1.write4bit(...);
SPI1.write8bit(...);
SPI1.send(...);

REFERENCE

require

QSPI = require('Qespruino_spi');

QSPI constructor

const QSPIx= new QSPI( SPIx);
  • SPIx ... one of the native interfaces; tested for EspruinoWIFI's SPI1, SPI2, SPI3

QSPI.setup

Similar to native SPI.setup().

Never call the native's SPI.setup(...) directly!

QSPI.awaitInterlaced

If you need to know when transmission ends, e.g. to toggle a CS pin. See examples Dealing with CS pins.

Note that the async callback may fire synchronously if the DMA has already finished when the function is called.

QSPI.writeInterlaced

QSPI.writeInterlaced(flat_buf)

To send a buffer content of 1, 2 or 4 bytes multiple times, add repeat_cnt:

QSPI.writeInterlaced(flat_buf124,repeat_cnt)
  • flat_buf ... flat buffer created with E.toString, E.newUint8Array or similar
  • repeat_cnt ... optional (default=1); when >1, flat_buf must be of length 1, 2 or 4

The function

  1. Waits until any previous transmission finishes,
  2. starts the new transmission and returns then immediately

Therefore it is important that flat_buf remains unchanged until transmission completes. This is granted when

  • the next writeInterlaced(...) returns, or
  • awaitInterlaced() returns, or
  • awaitInterlaced(callback) fires the callback

QSPI.writeInterlaced$

Limited hi-performace call of writeInterlaced().

QSPI.writeInterlaced$(flat_buf_ptr,flat_buf_bytes)
  • flat_buf_ptr ... address of a flat buffer variable as returned by E.getAddressOf(mybuf,1)
  • flat_buf_bytes ... number of bytes to transmit starting at flat_buf_ptr

This function is intended to provide the fastest possible way to start (repetitive) transmissions, at the expense of calling parameter validation.

Use this only if you need a bit more speed and you really know what you do.

EXTERNAL DOCS