Hi everyone,
I'm trying to read data with a Bela mini from a IIS3DWB accelerometer and save it in a bin file so that I can analyze the signal on Matlab. The sensor has an output data rate of 26667 Hz and I'd like to exploit the whole bandwidth but I'm unsuccessful. At the moment I'm using hard SPI on pins P2.25-27-29-31 correctly configured through SSH connection and this library:[https://github.com/jeremiahrose/Bela/tree/SPI/libraries/SPI]
I'm using 44100Hz sample rate and calling the auxiliary task for the reading once every 2 audio samples.
Since I'm not getting timestamps for the readings from the sensor I'm calling the file1.log every audio sample, assuming that each reading will be repeated 2 times and that the time distance between one sample and the other is 1/44100 seconds. This is the code (simplified for better reading):

#include <Bela.h>
#include <stdio.h>
#include <stdlib.h>
#include <Spi.h>
#include <libraries/WriteFile/WriteFile.h>

#define IIS3DWB_PIN_CTRL                  0x02
#define IIS3DWB_FIFO_CTRL1                0x07
//............. just a bunch of define, one for each register
#define IIS3DWB_FIFO_DATA_OUT_Z_H         0x7E

// Create a separate thread to poll Spi from
AuxiliaryTask SpiTask;
void readSpi(void*); // Function to be called by the thread
int readInterval = 22050;// How often readSpi() is run (in Hz)

int readCount = 0; // Used for scheduling, do not change
int readIntervalSamples; // How many samples between, do not change

Spi exampleDevice;
WriteFile file1;

int exampleOutput;

// IIS3DBW functions
unsigned char readByte (unsigned char reg){
	exampleDevice.setup({
			.device = "/dev/spidev2.1", // Device to open
			.speed = 10000000, // Clock speed in Hz
			.delay = 5, // Delay after last transfer before deselecting the device
			.numBits = 8, // No. of bits per transaction word
			.mode = Spi::MODE0 // Spi mode
			});
	unsigned char Rx; // Buffer to send
	exampleDevice.single_transfer((reg & 0x7F) | 0x80);
	Rx=exampleDevice.single_transfer(0x00);
	return Rx;
}

void readBytes (unsigned char reg, unsigned char count, unsigned char * dest){
	exampleDevice.setup({
			.device = "/dev/spidev2.1", // Device to open
			.speed = 10000000, // Clock speed in Hz
			.delay = 5, // Delay after last transfer before deselecting the device
			.numBits = 8, // No. of bits per transaction word
			.mode = Spi::MODE0 // Spi mode
			});
	exampleDevice.single_transfer((reg & 0x7F) | 0x80);
	for (unsigned char ii = 0; ii < count; ii++){
		dest[ii] = exampleDevice.single_transfer(0x00);
	}
	
}

void writeByte (unsigned char reg, unsigned char value){
	exampleDevice.setup({
			.device = "/dev/spidev2.1", // Device to open
			.speed = 10000000, // Clock speed in Hz
			.delay = 5, // Delay after last transfer before deselecting the device
			.numBits = 8, // No. of bits per transaction word
			.mode = Spi::MODE0 // Spi mode
			});
	exampleDevice.single_transfer(reg & 0x7F);
	exampleDevice.single_transfer(value);
}


int16_t readAccelDataAxis(char axis)
{
  unsigned char rawData[2];  // x/y/z accel register data stored here
  switch (axis)
  {
    case 'x':
    rawData[0]=readByte(IIS3DWB_OUTX_L_XL);  // Read the 2 raw accel data registers into data array
    rawData[1]=readByte(IIS3DWB_OUTX_H_XL);
    break;
    case 'y':
    rawData[0]=readByte(IIS3DWB_OUTY_L_XL);  // Read the 2 raw accel data registers into data array
    rawData[1]=readByte(IIS3DWB_OUTY_H_XL);
    break;
    case 'z':
    rawData[0]=readByte(IIS3DWB_OUTZ_L_XL);  // Read the 2 raw accel data registers into data array
    rawData[1]=readByte(IIS3DWB_OUTZ_H_XL);
    break;
  }
  return (int16_t)((int16_t)rawData[1] << 8)  | rawData[0] ;  // Turn the MSB and LSB into a signed 16-bit value
}
void init(int Ascale)
{
  writeByte(IIS3DWB_COUNTER_BDR_REG1, 0x00);   // enable latched data ready interrupt  

  //  mask data ready until filter settle complete (bit 3 == 1), disable I2C (bit 2 == 1)
  writeByte(IIS3DWB_CTRL4_C, 0x08 | 0x04);

  writeByte(IIS3DWB_CTRL1_XL, 0xA0 | Ascale << 2); // set accel full scale and enable accel
}

void reset()
{
  writeByte(IIS3DWB_CTRL1_XL, 0x00); // set accel to power down mode
  unsigned char temp = readByte(IIS3DWB_CTRL3_C);
  writeByte(IIS3DWB_CTRL3_C, temp | 0x01); // Set bit 0 to 1 to reset IIS3DWB
  usleep(1000); // Wait for all registers to reset
}

// end of IIS3DBW functions

bool setup(BelaContext *context, void *userData)
{
	reset();
	init(0x02); // configure IIS3DWB 
	// Set up auxiliary task to read Spi outside of the real-time audio thread:
	SpiTask = Bela_createAuxiliaryTask(readSpi, 99, "bela-Spi");
	readIntervalSamples = context->audioSampleRate / readInterval;
	file1.setup("out.bin"); 
	file1.setEchoInterval(10000);
	file1.setFileType(kBinary);
	file1.setFormat("binary: %.0f\n");
	return true;
}

void render(BelaContext *context, void *userData)
{
	for(unsigned int n = 0; n < context->audioFrames; n++) {

		// Schedule auxiliary task for Spi readings
		if(++readCount >= readIntervalSamples) {
			readCount = 0;
			Bela_scheduleAuxiliaryTask(SpiTask);
		}
		// use Spi output for whatever you need here
		file1.log(exampleOutput);
	}
}

// Auxiliary task to read Spi
void readSpi(void*){
	int16_t prova;
	prova=readAccelDataAxis('x');
	exampleOutput = prova;
}

void cleanup(BelaContext *context, void *userData){
}

    and this is what i get:

    alt text
    as you can see there are some points where the value is not updated; taking a closer look:

    alt text
    even when there are no frozen values the value apparently is updated every 16 or more audio frames, so I'm getting an output data rate of approximately 44.1/16= 2.8 kHz which is very far from the desired 20+kHz...
    I'm sure there are many things I can do better, but what I'm doing wrong? CPU usage is at about 60%, I tried various combinations of audio sample rate, block size and where and when to log the readings into the file, but the maximum output data rate that I can get is 5.5 kHz (but with a higher number of frozen values). Logging the readings inside the auxiliary task resolves the first problem but I can't get to know precisely the output data rate.
    Thanks for anyone's opinion.
    PS: sorry for multiple posting but if I try to upload everything in a single post I get an internal server error

      You might be having a problem where you are scheduling the next auxiliary task before the first one is finished. You might be able to look for an error returned from Bela_scheduleAuxiliaryTask(SpiTask), take a look here. Does anyone know how fast you can get data from SPI using ioctl methods? It's possible that you may need to have something running in the real-time loop to get that data rate. Also seems like you are doing setup each time you run readByte, is that necessary?

        If I substitute Bela_scheduleAuxiliaryTask(SpiTask) with if (Bela_scheduleAuxiliaryTask(SpiTask)!=0){printf("Error!!!!");} it doesn't print anything to console, so I don't think that might be the problem...
        I'm doing setup each readByte since it doesn't seem to work otherwise... However if I specify each time the SPI mode with something like exampleDevice.setMode(Spi::MODE0); it works aswell.

          misterbo this library:

          that's fine, but I made some fixes to it here which is what is ultimately going to be merged into master in the near future.

          misterbo once every 2 audio samples.

          That won't run. If you schedule an auxiliary task while it's already running (or at least already scheduled), it won't run twice. Scheduling it means "schedule it to run at the first occasion", and as it's a single core device and the audio thread is running at higher priority, the auxiliary task will run at most once every audio callback Actually I am confused because you are running this at priority 99 which is higher than the audio thread's 95. I guess as the SPI read is a blocking operation, it may stop multiple times in the process of reading. Don't you get any audio underruns at all? The other thing is that when you schedule it "every two samples" you are actually scheduling much faster than that, because going through a for loop of samples in render() doesn't reflect the real duration of samples. In other words, one iteration of the for loop does not last exactly 1/44100 seconds. It is usually much faster than that.
          Even if you were to go down to a smaller block size (e.g.: two samples per block) and try the same, it's unlikely that the auxiliary task would be consistently fast enough at completing execution. This is because it uses the Linux SPI driver, which is not a real-time safe driver and so doesn't have strong timing guarantees.

          Also, you are using writeFile, which also takes up CPU time and also relies on the Linux driver for writing to disk. Ultimately trying to do anything that involves Linux drivers at anywhere close to 26kHz is just not going to happen if you have something else (e.g.: the Bela audio) running on the side. If you don't care about the audio, you could try to run a tighter loop inside the AuxiliaryTask:

          void readSpi(void*){
               while(!Bela_stopRequested()) {
          	int16_t prova;
          	prova=readAccelDataAxis('x');
          	exampleOutput = prova;
                 /// TODO: get a timestamp and log that too
                 file1.log(exampleOutput);
                 usleep(500);
               }
          }

            However, the underlying problem here is the following: you are trying to do many evenly-spaced small reads, which is something that Linux is not good at. What you should do, instead, is try to do larger, more infrequent reads. Apparently your sensor supports FIFOs (section 4.3 of the datasheet) which can contain up to 3k bytes of data. This means you can read up to 3 thousand words at once instead of 2, which drastically lowers the timing requirements on the host (Bela) side. If instead you prefer to go the hard way, you could modify the Bela PRU code - which among other things uses SPI to communicate with the analog ADC at 22.05 or 44.1 or 88.2 kHz - and try to read your accelerometer in there at a constant sampling rate and minimal latency. However, as a quick look at the code would tell you, this is not for the faint of heart

            Rich_Sewell Does anyone know how fast you can get data from SPI using ioctl methods?

            As fast as the SPI clock (which by the way is not always what you require, as there is no PLL and it's just dividing down a 48MHz clock)! The issue is with doing many small transfers, which require a lot of setup time in comparison to the amount of data being transferred. Transfers over 160 bytes use a DMA, which frees up the CPU a lot.

            misterbo If I substitute Bela_scheduleAuxiliaryTask(SpiTask) with if (Bela_scheduleAuxiliaryTask(SpiTask)!=0){printf("Error!!!!");} it doesn't print anything to console,

            I am not confident the return value of Bela_scheduleAuxiliaryTask() actually does what it claims to do in the documentation. You are probably the first person who tries to make use of it.

            misterbo PS: sorry for multiple posting but if I try to upload everything in a single post I get an internal server error

            I've seen that happen recently (even to me right now as you can tell) and I don't know what the source of the issue is.

              6 days later

              giuliomoro thanks a lot fot your suggestions! I tried to read the FIFO but I couldn't make it work, then I found a control register that allowed me to use XYZ output registers to get 3 consecutive samples of a single axis and since I don't care about audio, i just ran a tighter loop inside the auxiliary task:

              void readSpi(void*)
              {
              	while(!Bela_stopRequested())
              	{
              	int16_t prova[3];
              	uint32_t timestamp;
              	int timedif;
              	
              	timestamp=readTimestamp();
              	if (oldtime==0){oldtime=timestamp;}
              	
              	timedif=timestamp-oldtime;
              	
              	if (timedif<9){ //in 1AX_TO_3REGOUT mode we need a reading every 9 timesteps (cons samples are 3 timesteps from each other)
              		usleep(12*(9-timedif)); 
              		timestamp=timestamp+9-timedif;}
              
              	readAccelData(prova); //read all 3x2 output registers to get 3 consecutive samples
              	
              	file2.log(timestamp);
              	for (unsigned char ii = 0; ii < 3; ii++){
              		file1.log(prova[ii]);}
              	
              	oldtime=timestamp;
              }	
              }

              alt text
              This is an histogram for the occurrencies of the timedif vector, ideally I'd like to have all of them in the 9 bin in order to have a stable output data rate (since I have to do some FFT and FRF computations and I need a constant sampling rate), any idea on how I can make it better?
              Another question: can I use 2 SPI devices? I would need another CS pin for the SPI1 bus or use the SPI0 bus, but I don't think I'm allowed to do that.

                misterbo This is an histogram for the occurrencies of the timedif vector, ideally I'd like to have all of them in the 9 bin in order to have a stable output data rate (since I have to do some FFT and FRF computations and I need a constant sampling rate), any idea on how I can make it better?

                This is basically just not going to happen using the Linux driver on a single core device. You really need to get the FIFOs working to be able to use this device with a high and reliable sampling rate from Linux. The Bela infrastructure can do no magic in this. In fact, running this outside of a Bela program would give slightly lower jitter values because the SPI thread wouldn't be interrupted from the audio thread.

                The other solution suggested above is probably the next-best approach if you cannot get the FIFO to work. If you do all the device setup from C++ on Linux, then it shouldn't be too hard to read one 16-bit word from the device instead of reading from the ADC and pass it on to render() through the BelaContext*.

                misterbo Another question: can I use 2 SPI devices? I would need another CS pin for the SPI1 bus or use the SPI0 bus, but I don't think I'm allowed to do that.

                There are two SPI devices available on the pins of BelaMini, only one on Bela. The Spi class uses the peripheral's own chip-select. Each peripheral has more than one channel, each with its own CS chip. To use several devices on the same bus, you can use two channels on the same peripheral (e.g.: /dev/spidev2.0 vs /dev/spidev2.1), but then you'd have to make sure you are only using one of these at any time (as they use the same transmission lines). On BelaMini you could use the two different SPI peripherals /dev/spidev1 and /dev/spidev2 independently (i.e.: even at the same time). If the issue is to use the same transmission lines for several different devices (e.g.: more than the channels available on the SPI peripheral), you could repurpose any GPIO to act as a CS and just manually assert and deassert it before and after the calls to single_transfer().

                By the way, you don't need to call Spi::setup() in readByte() and writeByte(). Just do it once in setup().

                giuliomoro misterbo PS: sorry for multiple posting but if I try to upload everything in a single post I get an internal server error

                this should be now fixed. Let me know if you encounter more of these issues.

                  giuliomoro This is basically just not going to happen using the Linux driver on a single core device. You really need to get the FIFOs working to be able to use this device with a high and reliable sampling rate from Linux.

                  ok got it... So I'll try again...

                  giuliomoro On BelaMini you could use the two different SPI peripherals /dev/spidev1 and /dev/spidev2 independently (i.e.: even at the same time).

                  So what should I do to connect another identical sensor on spidev1? I tried connecting it to pins P1.6,8,10,12 and i got these error messages:

                  PRU interrupt timeout, -1 110 Connection timed out
                  Segmentation fault

                  giuliomoro By the way, you don't need to call Spi::setup() in readByte() and writeByte(). Just do it once in setup().

                  Yeah I know but omitting them will result in the program running forever, and the reset button does nothing. I have to physically disconnect the board. I need at least this line exampleDevice.setMode(Spi::MODE0); in readByte() and writeByte() to make the program work as intended...
                  I don't know, maybe the source of all my problems is in the library? Or maybe I'm using it wrong, I'm not a computer scientist so my coding abilities are really not great...

                  giuliomoro this should be now fixed. Let me know if you encounter more of these issues.

                  yeah now it's fixed

                    misterbo So what should I do to connect another identical sensor on spidev1? I tried connecting it to pins P1.6,8,10,12 and i got these error messages:

                    were those caused by simply connecting it or also adding some code? Please show the code in the latter case. You'd need to at least disable analog channels in the IDE settings to get anything to work there, though that by itself wouldn't explain the Segmentation Fault.

                    misterbo I don't know, maybe the source of all my problems is in the library?

                    It's possible it's the library's fault, I suggested previously that you'd use my updated one https://github.com/BelaPlatform/Bela/tree/SPI/libraries/Spi

                    misterbo Yeah I know but omitting them will result in the program running forever, and the reset button does nothing

                    If you update to the above version of the library and the problem persists, can you send your full code so I can test it ? I recomposed your messages above but some of the IIS3DWB_CTRL3_C and the likes are missing.