I’m trying to design an electrocardiogram recorder. The MCU would read 3 bytes of ECG data from the ADS1291 chip every 8 milliseconds, and log the data onto an SD card. I’ve already wrote a buffer that can store 5 seconds of ECG data in the MCU’s RAM before becoming full, so I won’t have to frequently write small data fragments to the SD card.
The problem is that both the ADS1291 and the SD card uses SPI, and I’m not sure if I can share both devices on the same bus. I know that SD card write operations can take up to 200 milliseconds, which I assume would disrupt the 8 millisecond periodic read operation demanded by ADS1291?
The ADS1291 chip generates an interrupt every 8ms (by pulling a pin low), and I’m using this interrupt to do the 3-byte transfer. What happens if this interrupt occurs during an SD card write operation, if they are both using the same bus?
Is there a way to share the SD card and ADS1291 on the same SPI bus, or do I have to use two separate buses? What would be the best method to handle this situation?
(For the MCU, I’m currently using an STM32L432KC, but will be transferring the code onto a nRF52832 later. Both of these MCUs have more than 1 SPI bus).
Ok, thanks for the suggestion. Ideally I’d like to know a bit more about what would happen in case of an SPI-bus usage “conflict” though. Would the data become corrupt? Or crash Mbed OS?
I would also recommend you to use separate SPI lines for the sake of continuity of operations. That said, you can also share the SPI buses. But, let’s analyse what the code might look like.
SPIs are a leader-(master-)managed peripheral where the leader (typically the MCU) will initiate any transaction with the follower(s). If the bus is shared, then the two peripherals act like followers (slaves).
They will not use the bus until their dedicated and exclusive chip select (CS) pin is signalled by the MCU.
Now, to avoid corruption, you as the leader would essentially operate the followers in a mutually exclusive way by inactivating one device and activating the other. Mbed OS’s peripheral drivers implement a lock mechanism that a separate thread/function issued spi_write() or spi_read() commands will wait to acquire the HAL lock and only then proceed. So there is some protection.
I think this question is mainly about a sound architecture. If a SD card transaction is going on, and you want to switch to the ADS1291, you will have to unselect the SD card by toggling its CS pin and continue with the ADS1291. This would essentially fragment the amount of data you are writing to the SD Card at one go until you are interrupted every 8ms. You would need to implement a state machine to manage how to get interrupted and how to resume writing to SD Card. If you are using a multithreaded approach, you will need to implement a inter-thread communication to play nice with each other (condition_variable or event_flags).
Separate SPIs solve the firmware and hardware complexity, makes your system easier to test, also gives you scalability to read more channels or change rate, without affecting the SD Card writing module. Hope this helps.
Wow, thank you for the detailed analysis! I think I’ll go with using two separate SPI buses. As you said, it indeed greatly simplifies the code & logic involved.
Just one more quick question: If I’m using two separate SPI buses, would an ADS1291 interrupt during an SD card write operation cause any problems? I would assume the CS pin for the SD card just remains low and SCLK (for the SD card) stops, until the ADS1291 interrupt service routine is finished? Does the SD card mandate a continuous stream of data?
I would assume the CS pin for the SD card just remains low and SCLK (for the SD card) stops, until the ADS1291 interrupt service routine is finished?
The short answer is that the SPI peripheral is driven by an independent clock and once started the SPI bus continues the transaction at the set frequency, regardless of what the MCU is doing. Some more explanation and design notes follow:
If your hardware uses the dedicated CS pin from the peripheral, you can allow the Mbed OS’s SPI class handle the CS pin as well with its 4-wire constructor variant. With this, the chip will be unselected after the transaction. Also make use of the multi-byte format (upto 16-bit) for a hassle-free experience of not having to concatenate bytes with application code.
The problem you assumed is typical of bit-banged drivers where the GPIOs are driven by the MCU as SPI pins. The MCUs you mentioned only have to write to the transfer registers of the SPI peripherals and enable the transaction. After this the SPI peripheral notifies the MCU when the transaction is done with a flag in its status register. The MCU is free to run other instructions, which can also be just to wait or poll for the status as is the case with the Mbed OS’s spi_read() API which is a blocking call.
If an interrupt occurs in this stage, the MCU simply serves the interrupt and returns back to either polling the status bit or serving the next SPI-done interrupt (as might be the internal implementation).
Further reading: For non-blocking options, please refer to the API reference, especially the transfer command if you want to do other things in the same thread. In a multi-threaded application, feel free to do what you want in the other threads. In an async context (single user thread), use an EventQueue dispatch along with the transfer command. In bare-metal, the world is your oyster.