https://github.com/stevstrong/Arduino_S … 9bad4f52de
Changes:
– some cleanup + optimizations
– added extra flags to allow more flexible usage (e.g. circular mode, interrupt on half transfer, async mode)
– added extra function for dmaSend/dmaTransfer to send single data instead of buffer – this way the code became simpler
– dmaSendAsync() is now obsolete because async mode is now supported by the function dmaSend()
– ISR is always triggered at the end of a DMA transfer, DMA is stopped only if not in circular mode
– backward compatibility is assured.
This made possible to write a simple slave application using:
//-----------------------------------------------------------------------------
void SPI_DMA_Rx_ISR(uint32_t trxComplete)
{
if (trxComplete) {
dma_irq_full_complete = 1;
} else {
dma_irq_half_complete = 1;
}
}
//-----------------------------------------------------------------------------
void SPI_Setup(void)
{
// setup variables
dma_irq_full_complete = 0;
dma_irq_half_complete = 0;
for (uint8_t i = 0; i<DATA_BUFFER_SIZE; i++)
data_tx_buffer[i] = i; // init data to send
// The default SPI class instance is SPI1
SPI.beginTransactionSlave(SPISettings(18000000, MSBFIRST, SPI_MODE0, DATA_SIZE_8BIT));
SPI.onReceive(SPI_DMA_Rx_ISR); // attach our own ISR handler
SPI.dmaTransfer(data_tx_buffer, data_rx_buffer, DATA_BUFFER_SIZE, (DMA_CIRC_MODE|DMA_HALF_TRNS));
}
I made a simple sketch to test SPI1 and SPI2 on the same chip.
Output of SPI2 working as master is connected to SPI1 working as slave.
SPI is per default declared in SPI.cpp as SPI(1).
The sketch does not declare any second instance for SPI2.
This means, that the class instance pointer _spi2_this = 0.
Nevertheless, the SPI2 event callback function
void _spi2EventCallback()
{
reinterpret_cast<class SPIClass*>(_spi2_this)->EventCallback(2);
}
I think this may also solve a problem that was mentioned in a github discussion, about having problems with the callback functions when using setModule(), did you test that?
Furthermore, we could make this a static too rather than have one per object right? Since it takes the spi port as a parameter, I don’t see a point having multiple copies of it.
void SPIClass::EventCallback(uint16_t spi_num)
OK, I will try to make also the event handler static.
I had to declare the SPISettings variables used in the handler as public, but then it works.
In addition, I implemented non-DMA transfer functions for both 8 and 16 bit buffers, including the version to transmit single data instead of buffer.
https://github.com/stevstrong/Arduino_S … fd0d1869ea
Measured: to transfer 50 bytes from an 8 bit buffer takes 5µs less then the DMA equivalent.
This is due to the DMA setting overhead which takes 10µs.
I haven’t had any time this weekend, but I plan to test your changes as soon as I have a moment with a couple of sketches that use 2 spi ports.
I add this comment in the commit:
“Can we rename EventCallback to spiEventCallback?
Since this function is a static, that way we avoid the risk if some other library somewhere or someone’s sketch has a function called EventCallback.”
Added some new functions which make the SPI usage really simple, in both master and slave mode (local version, under testing)
void dmaTransferInit(const void *txBuf, void *rxBuf, uint16 length, uint16 flags)
void dmaTransferInit(const uint16 tx_data, void *rxBuf, uint16 length, uint16 flags)
– called during setup to init the transfer, without starting it. Equivalent of previous dmaTransferSet(…). Will set most of DMA parameters.
– Length and flags (async) will be stored in settings for later use.
flags:
– DMA_HALF_TRNS – will call user callback function (added with onReceive or onTransmit) when the half of the buffer has been transferred. User callback has an input parameter which will tell whether the IRQ is for half or complete transfer.
– DMA_CIRC_MODE – will set the DMA in circular mode. Very useful in slave mode, but also in master mode: combined with the half transfer flag the user can change/process one half of the buffer while the other half being transferred.
– DMA_ASYNC – set if you want to start the transfer without waiting for the end of it. Useful in slave mode combined with the previous flags, but also advantageous in master mode. User has to take care that in master mode the SS pin should be deactivated only at the end of transfer (detectable via user callback or any other utility function below)
void dmaTransfer(void)
– will start the transfer, being equivalent of previous dmaTransferRepeat(). Used together with dmaTransferInit. Takes the previously stored length and async flag. Can be called repetitively after the data has been changed/processed in non-circular mode.
uint16 dmaTransferRemaining()
– returns the remaining number of bytes/words to be transferred. Useful, for example, in slave mode if the number of bytes to receive is not known (sniffing external SPI lines
), or no user callback has been attached.
uint8 dmaTransferReady()
– returns 1 if transfer is completed, otherwise 0. Useful if the user has not attached any callback function.
Identical functions added for dmaSend…, wherein the function dmaSend() has a variant where the user can start the transmission by specify the txBuffer address, the rest of DMA parameters remaining unchanged compared to init:
void dmaSend(const void *txBuf)
void dmaSend(const uint16 tx_data)
Any comments/wishes?
- This DMA functions
- the USB-HID library
I think no other ***duino can provide this such an easy way ![]()
Ok, got a third one: The bootloader(s)
I checked the one in the first post and don’t see these latest changes.
Backward compatibility is maintained for all function currently used in repo.
The description of the new functions can be found here: viewtopic.php?f=9&t=3556#p44922
I attach two test sketches for two blue pills.
One master and one slave, the master sends a buffer over SPI2 to the slave and receives it back in two pieces over SPI1 from the slave.
Used SPI clock is 18MHz for both ports.
With 36MHz on SPI1 I have some issue, have to analyze the situation. I remember Victor tested longer time ago the DMA functions and observed similar issue, so it may be a HW issue.
[stevestrong – Wed May 09, 2018 8:03 pm] –
With 36MHz on SPI1 I have some issue, have to analyze the situation. I remember Victor tested longer time ago the DMA functions and observed similar issue, so it may be a HW issue.
Correct. My test was done with a single port with one TX buffer and one RX buffer.
Used dmaTransfer function, and at 36Mhz RX would lose some bytes (so the RX DMA would not complete some times). At 18Mhz all would be fine.
I loaded the TX buffer with values, then compared with the RX buffer, and the bytes lost would not always be in the same positions, or the same number of them. I suspect at 36Mhz the SPI peripheral has some problem and either the data is someway not loaded to the DR from the internal shift register, or is loaded but the DMA request not generated, or perhaps not serviced in time before the next data overwrites the previous one. I do not remember if I ever check the overflow flag.
Could also be due to only missing some bits here and test, I can’t remember how scrambled was the RX data (if missing whole bytes, or looked like some bits had been lost and thus bits from the next byte completed the previous).
I tested setting the RX DMA priority to highest and TX priority to lowest and still could not prevent the problem. The only way to prevent it during my test was running the port at 18Mhz. I used a short cable between the MOSI and MISO pins (10cm or less)
Since 36Mhz is out of spec, at some point I just decided to stop looking at it and move on.
[Pito – Thu May 10, 2018 11:10 am] –
But our sdcards worked @36MHz in past. Why?
I can’t say for sure, perhaps because the Sdcards do not use TX and RX at the same time?
We use DMA transfer, but it transfers 0xFF repeatedly, perhaps the interference from sending 0s and 1s at high speed causes problems that are not seen when sending a high level consistently? or perhaps has to do with the dma TX memory pointer increasing? (in the sdcard transfer is set to not increase).
It only goes wrong when port 1 with 36MHz is transferring data simultaneously with port 2 (any frequency).
Ports 1 and 2 simultaneously @18MHz is ok.
Port 1 alone @36MHz is ok.
I assume the example sketch you posted works fine? I tried it last night and was getting data errors, but it may be my wiring causing issues as it was only on breadboard. I presume the two transfers are meant to send 0-49 and then 50-99 each time?
When I ran the code I would get some good transfers and then some bad ones for example [0 1 2 3 4 5 6 7 8 9 10 11 12 197 45 86 203 …] or it would become offset by a few characters and end up with [3 4 5 6 7 8 9 … 49 50 51 52]
Dropping the SPI speed to 9MHz seemed to improve the situation but I haven’t investigated further yet.
EDIT:
It was wiring. Removed the breadboard from the setup and now it runs nicely at 18MHz ![]()
[C_D – Thu May 31, 2018 8:30 pm] –
I presume the two transfers are meant to send 0-49 and then 50-99 each time?
Yes, indeed.
A Tx buffer of values 0-99 is transmitted repeatedly, which is same size of the Rx buffer.
So the Rx ISR should detect half transfers and full transfers, and the main loop should print always identical data 0-49 and 50-99.


