Reading SPI accelerometers

What are you developing?
misterbo
Posts: 4
Joined: Tue Mar 30, 2021 1:57 pm

Reading SPI accelerometers

Post by misterbo »

Hi everyone,
I have a Nucleo L476RG board and I'd like to read the outputs from two IIS3DWB accelerometers and send them to a serial communication at 22kHz frequency.
At the moment I'm trying with just a single accelerometer but I'm getting only about 10k values per second, how can I improve the speed?
I've read that the SPI library is not well optimized for speed...
I'm using this library for the accelerometers: https://github.com/kriswiner/IIS3DWB
This is how readBytes and writeByte functions look like:

Code: Select all

void IIS3DWB::readBytes(uint8_t reg, uint8_t count, uint8_t * dest) 
{
  SPI.beginTransaction(SPISettings(10000000, MSBFIRST, SPI_MODE0));
  digitalWrite(_cs, LOW);
  SPI.transfer((reg & 0x7F) | 0x80);
  
  for (uint8_t ii = 0; ii < count; ii++)
  {
    dest[ii] = SPI.transfer(0);
  }
  
  digitalWrite(_cs, HIGH);
  SPI.endTransaction();
}


void IIS3DWB::writeByte(uint8_t reg, uint8_t value) 
{
  SPI.beginTransaction(SPISettings(10000000, MSBFIRST, SPI_MODE0));
  digitalWrite(_cs, LOW); 
  SPI.transfer(reg & 0x7F);
  
  SPI.transfer(value);
  
  digitalWrite(_cs, HIGH);
  SPI.endTransaction();
}
Any help would be really appreciated, thanks!
Fabio
stevestrong
Posts: 502
Joined: Fri Dec 27, 2019 4:53 pm
Answers: 8
Location: Munich, Germany
Contact:

Re: Reading SPI accelerometers

Post by stevestrong »

I assume that you are using the official STM core.
Here are some suggestions:
i) You have to look whether there is another block transfer function to transfer array of bytes instead of doing several single byte transfers.
ii) you can use digitalWriteFast() to set CS.
iii) you can skip SPI.beginTransaction()/endTransaction() for each transfer, it would be enough to set it once in setup if you have no other device on SPI lines which requires different settings.
ag123
Posts: 1653
Joined: Thu Dec 19, 2019 5:30 am
Answers: 24

Re: Reading SPI accelerometers

Post by ag123 »

i think the official core has codes which toggle cs as well during the spi.send(), it seemed it isn't bundled in beginTransaction(0 and endTransaction().
however, if cs is already active, activating it again shouldn't be an issue but just waste some clock cycles.

one approach though is to take spi.h and spi.cpp (which is the main interface) and make a custom one. that would allow you to make customizations that is not normally there. other things are like dma, which similarily takes a customised spi.h and spi.cpp to get there.
misterbo
Posts: 4
Joined: Tue Mar 30, 2021 1:57 pm

Re: Reading SPI accelerometers

Post by misterbo »

Thanks! I eliminated the lines for beginTransaction and endTransaction, and I'm getting slightly better results (12.5kHz) but I didn't get your first point; are you suggesting to use something like

Code: Select all

dest=SPI.transfer(zeros, count)
instead of

Code: Select all

  for (uint8_t ii = 0; ii < count; ii++)
  {
    dest[ii] = SPI.transfer(0);
  }
?
I can't use it, since it's a send-only function.
I've read in another post (viewtopic.php?t=588) someone suggesting to replace the SPI.transfer with a macro like this:

Code: Select all

void  SPI_TRANSFER (uint8_t x)  { // 0.3uS overhead between bytes when optimized // 3.25 uS unoptimized
  // SPI.transfer(x);  // send byte
  SPI1->DR = (x) ;
  while ((SPI1->SR &  SPI_SR_BSY ) | (SPI1->SR & (!SPI_SR_TXE)) ) {}; // wait until - Transmit buffer NOT Empty - Busy flag  SET
}
But this looks like a send-only function, and I need a send-receive one but I don't own the knowledge to properly modify it; maybe something like that could help to improve performances?
Last edited by misterbo on Tue Mar 30, 2021 4:00 pm, edited 2 times in total.
ag123
Posts: 1653
Joined: Thu Dec 19, 2019 5:30 am
Answers: 24

Re: Reading SPI accelerometers

Post by ag123 »

even though spi send and receive concurrently, normally what i observed is that due to the nature of request / response call.
in the request data is send and the received byte (not the response) commonly ignored.
then in the response, i.e. expected return from device, the data sent is normally 0xFF, this keeps the level high, and the received response byte collected and evaluated. some devices actually couldn't do a immediate turn around and may require one or more 'empty' requests, e.g. sending 0xff and ignore the results. perhaps some of them does framing so that you can tell if that is a valid response. i'm not sure if some slow devices could use a protocol like it would keep giving you 0xff until a first byte that isn't oxff. for others, it may be timing centric e.g. send one empty 0xff and ignore results and results comes in the 2nd byte onwards. these would need to consult the timing diagrams while dealing with them.

in my previous dealing with SPI which is a ili9341 spi lcd library
https://github.com/ag88/Adafruit_ILI9341_SPI_stm32duino
optimizing cs pin toggling can make a difference if you have a lot of calls that toggles the cs pin. this is true for many graphics primitives.
hence i used bit banding - that is digitalWriteFast()
and it did show an improvement at least compared to the Adafruit original library which i did not use big bbanding (digitalWriteFast())
https://github.com/ag88/Adafruit_ILI934 ... nd-results

the greatest improvement i've achieved is using dma (but only libmaple core), you can see the speed differences in the benchmarks.
using dma can be troublesome if you may be concerned about errors on the line. the dma speedups is primary due to that i'm pushing color pixels (of same color) and i simply resort to pushing the whole buffer out as fast as is possible without bothering if any errors occurred. this may not be your case and dma may have limited use but may still improve things if it is relevant. i think stm core currently doesn't have dma specific functions and if you want to implement that, it'd be necessary to use hal and in addition a customized spi.h, spi.cpp.
Last edited by ag123 on Tue Mar 30, 2021 4:10 pm, edited 1 time in total.
misterbo
Posts: 4
Joined: Tue Mar 30, 2021 1:57 pm

Re: Reading SPI accelerometers

Post by misterbo »

Thanks for your answer! I'm trying to use digitalWriteFast but I'm getting an "invalid conversion from 'uint8_t' {aka 'unsigned char'} to 'PinName' [-fpermissive]" error... And I can't use dma since I need reliable results (I need to do some FRF computation on those signals).
However I'm using only one sensor and getting half the necessary output data rate from it, I'd need huge improvements in order to read both sensors at 22kHz and I'm starting to think that it's not feasible with this setup.
ag123
Posts: 1653
Joined: Thu Dec 19, 2019 5:30 am
Answers: 24

Re: Reading SPI accelerometers

Post by ag123 »

literally i did not use digitalWriteFast() in my ili9341 spi lcd library
rather i used bit banding directly
https://github.com/ag88/Adafruit_ILI934 ... TM.cpp#L71
https://github.com/ag88/Adafruit_ILI934 ... M.cpp#L692
https://github.com/ag88/Adafruit_ILI934 ... STM.h#L250

doing it this way will make your code 'unportable' and that you'd need to assure yourself that it does bit banding in the same way that it is 'hard coded', addresses etc

found some interesting articles about bit banding
http://www.micromouseonline.com/2010/07 ... the-stm32/
http://www.martinhubacek.cz/arm/advance ... nipulation

and you'd need to dig into the reference manuals, including programming ref as well to see that those bit banding address mappings are after all the same on your mcu.
https://github.com/ag88/Adafruit_ILI934 ... M.cpp#L690
ag123
Posts: 1653
Joined: Thu Dec 19, 2019 5:30 am
Answers: 24

Re: Reading SPI accelerometers

Post by ag123 »

another thing though is that you may need to check the response turn around time on the device (accelerometer?), some devices simply couldn't catch up with a fast spi and like mentioned prior, it'd perhaps as an example keep giving you 0xff until it is ready to return the data?
misterbo
Posts: 4
Joined: Tue Mar 30, 2021 1:57 pm

Re: Reading SPI accelerometers

Post by misterbo »

Ok, before entering the world of bit banding I tried keeping the cs line low without modifying it in writebyte and readbyte functions (even if the numbers I'm getting do not have any meaning, just to see what kind of improvement I could get with bit banding), and I'm getting an output data rate of 16k words per second, which is still far from 22k (considering the fact that I have to read 2 sensors). So it's surely an improvement, but I still have to cut edges somewhere else...
The accelerometers are rated for 10MHz SPI speed which is what I'm using.
ag123
Posts: 1653
Joined: Thu Dec 19, 2019 5:30 am
Answers: 24

Re: Reading SPI accelerometers

Post by ag123 »

one of those ways you may like to try is perhaps to copy the spi.h, spi.cpp and associated codes, less hal. maybe try to 'optimize' it a little making a leaner function/method etc. another approach for the most time sensitive part is to read the spi registers directly. you could review the ref manual and core (or even libmaple, etc to get a feel of it) codes. that i'd guess is closest to 'bare metal' as you can get. i think this is rather doable just that the codes would be deemed 'unportable', but if it is a dedicated use case (e.g. for one particular mcu) i'd after all do that to get the speeds needed.

that macro you have quoted looks like it, just cross reference the ref manual to see which status registers to monitor, when reading / writing directly to registers
https://www.st.com/resource/en/referenc ... ronics.pdf
Rx and Tx buffers
In reception, data are received and then stored into an internal Rx buffer while In
transmission, data are first stored into an internal Tx buffer before being transmitted.
A read access of the SPI_DR register returns the Rx buffered value whereas a write access
to the SPI_DR stores the written data into the Tx buffer.
so

Code: Select all

SPI1->DR = send_byte;
after writing to it, sends that byte out, and if you read

Code: Select all

recv_byte = SPI1->DR;
, that's the byte in the buffer while you write the byte out. customary, to send data, just write data in SPI1->DR and ignore the byte in there (it may be necessary to read it and discard it though). to receive data write 0xff in SPI1->DR then read that data register again. it would also be necessary to check the various status registers to check completion, etc.

the quoted manual is for the stm32f4xx series, u'd need to find one that is more relevant to your soc
Post Reply

Return to “Projects”