Qespruino_SPI
SPI performance improvement using DMA
Tested on EspruinoWIFI only
-
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)
-
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.
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.
..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
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);
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);
})
// 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(...);
QSPI = require('Qespruino_spi');
const QSPIx= new QSPI( SPIx);
-
SPIx
... one of the native interfaces; tested for EspruinoWIFI's SPI1, SPI2, SPI3
Similar to native SPI.setup()
.
Never call the native's SPI.setup(...) directly!
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(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 withE.toString
,E.newUint8Array
or similar -
repeat_cnt
... optional (default=1); when >1,flat_buf
must be of length 1, 2 or 4
The function
- Waits until any previous transmission finishes,
-
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
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 byE.getAddressOf(mybuf,1)
-
flat_buf_bytes
... number of bytes to transmit starting atflat_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.