Multiple busses behaving as one

Hello guys,

First, sorry for the long message. Hope one of you will have enough will to read it entirely!

I am working on an ESP32s3 and am currently planning on using two I2Cs and 2 SPIs.

I implemented my busses on Arduino, but when I look at the output with a logic analyser, it seems that no matter how I manage my code, new data is sent on a bus only when previous message was fully transmitted, thus behaving as if all my busses were only one.

For example, let's say I want to transmit 0b0101 on one SPI bus, and transmit 0b1100 on one I2C bus. Arduino libraries (SPI.h & Wire.h) will first send the full SPI message (here 4 bits, but will be marginally higher in my application). Only then, when the SPI bus is available, will it send data on the I2C bus.

I am quite sure it comes from the fact that the whole transmition is handled as one function, so the controller waits until it is finished to start the next transmit function. So technically I do have multiple busses, but I can't send data on multiple busses at the same time. So basically, it becomes pretty much pointless to have multiple SPIs...

Here are a few ideas I got :

  • Increasing bus speed to get rid of one SPI and one I2C bus. It is doable, but feels quite wrong as I need to use different SPI modes depending on the slave. And I am not quite sure what is the correct margin of safety needed to have a sufficient speed to have no data conflict, but avoiding to have a very high speed bus as I will do my own PCB after that.

  • Doing my own SPI and I2C library to directly write on transmit registers. Wich makes programing on Arduino pretty much pointless. Not sure to have enough time to do that reliably.

  • I may have done something wrong, or simply misunderstood Arduino's libraries or even the busses themselves.

  • I need to use different libraries that handle the job correctly. But is there any?

Do you guys have any suggestions on that?

Many thanks,
Me

It's technically possible to write more or less at the same time to multiple buses, but you need to use DMA and I don't think the ESP32 Arduino core is programmed that way. You'd have to figure that out though. It would boil down to option 2 and digging down into the Espressif datasheet/reference manual pretty deeply.

If you want to explore alternative options, it would help if you could give some context on the application, peripheral hardware and where the need for truly simultaneous data transfer comes from. There's a good chance you can do what you want with Arduino by taking a somewhat different approach - or maybe the timings aren't as strict as they currently seem if we dig a little deeper into the system you're working on.

Okay here's a bit of context on what I am planning to do:

I have one esp32s3 working at 80MHz, two cores available: one is transmitting data via BLE/Wifi or is in light sleep mode. Second core is either transferring sensor data or in iddle state.

Here's a list of the sensors:

  • 2 PPG sensors. Total of 6 Leds and 2 Photodiodes (3 * 2 * 16 bits sampled at 128Hz for each PPG. Sent via SPI mode 0/3)
  • One single lead ECG (same data rate, but sent via I2C)
  • Accelerometer (8 bits sampled at 100Hz, sent via SPI mode 2)
  • PMIC controlling battery registers, SoC, interrupt ... via I2C
  • One temperature sensor (16 bit sampled at about 10Hz, sent via I2C)
  • All of above data is stored in a 256 Mbits external Flash using single SPI
  • All of above data is sent through BLE/WiFi periodically (every 15 minutes or so)

I think that data rates are very low. But I am not exactly sure how big the bus frequency needs to be to totally avoid any conflict. It appears that standards bus speeds would be more than enough to handle everything easily. But I am trying to have an "industrially correct" approach, even though it is nothing but a prototype. Is there some sort of globally accepted "time not sending data vs time sending data" ratio, to avoid conflict?

EDIT: Oh and I need to add one important detail. I am trying to make power consumption as low as possible as the entire system is battery powered. Hence why I would like to have bus frequency as loooow as possible (well, at least reasonably low. I am not trying to get any low frequency noise lol)

OK, so the challenge you're having is not so much that you need several transfers happening at the same time, but that you need to have a sufficiently high sampling rate across all sensor systems, correct? That would probably make things a little easier.

How large is the timing problem you're actually running into? I.e. what is the actual sampling rate across the different sensors and which one(s) is/are below the intended rate?

I assume you've made a timing chart to figure out which sensors you would have to poll at each moment in time so that the samples don't interfere with each other. I also assume you've done back-of-the-envelope calculations of the amount of data you need to pull in and the actual anticipated bandwidth on your I2C and SPI buses, and have used this to determine overall feasibility. Correct?

Yes, you are right! Having data transfers happening in parallel is not entirely necessary as long as I can have a sufficient sampling rate.

And yes I made a timing chart to have a ballpark of the amount of data that needs to be sent, and the time necessary to do so. But my struggle is not exactly "Is it possible to send data fast enough with these busses" but more "How low can my frequency go without running into any problem?".

To simplify, if SPI and I2C runs at about 400kHz (just to give an example), it would basically mean that I am capable of sending one bit of data every 14 clock cycle in order to be able to send all my data in time. (Well, in theory... In practice I may have a lot less time avaible). So my system would be in some sort of idle state during 14 clock cycles between two transmit command, not counting wake up times yet, possible software time loss etc.
Does that sound like a reasonable rate to you?
I may be too anxious about a not so problematic situation, but as I say I am really trying to have a good approach, especially concerning power consumption.

Hmmm, ok, so the 'problem' actually sounds like a different one than what I go from your initial post. I'd formulate it as "how energy efficient will the transfers be if they're being done sequentially and the core is basically busy waiting a lot of the time?"

My approach to this would be to try and keep things simple. Just keep using the Arduino way, even if it doesn't use DMA, and see if you can throttle back the core speed so that it wastes less clock cycles (=less mAh) while waiting for a bunch of transfers to finish. Then speed it back up once you process the data, then let it go back to sleep until the next bunch of transfers. You can measure how this performs in terms of power use. Then compare it to an even simpler scenario where you don't even throttle back the CPU and see if the power savings are actually worth the trouble.

Keep in mind that even if you were to use DMA in order to free the core of the transfer tasks, most of the peripherals and their clocks would still need to be running while the transfers are occurring. You might end up realizing that you're doing a lot of work (if you implement DMA transfers) for very marginal gains.

1 Like

Okay that sounds like a good plan, I'll try the simple aproach first and see how it goes.
But I have to admit that it feels weird for me to make transfers sequentially.
What is the point of having multiple SPI then? Especially considering that " the arduino way" of using spi, is to initialize it every time you want to make a new transfer... sounds pretty stupid if you were to ask me.

Oh and thank you for your time answering me! :wink:

You can write you own code to use both SPI busses at the same time.

Yup and I already mentioned it. But as I said: " the Arduino way" is having only ONE bus at a time. And I do not necessarely want to redo entire libraries when the whole point of coding on Arduino is having most of the tough work already done.
Not that I can't do it. But I may not have enough time

Understood.