Simple example to demonstrate a Master SPI configuration where the data movement is done with DMA channels (One for Master Transmit and the other Master Receive).
This project is using a standard ADS configuration and iLLD's. However the configuration of the QSP1, DMA channels 3 and 4 along with the ports are implemented in code as bare metal.
In this code example, QSPI1 is configured as a master SPI. DMA channel 3 is used to transmit the data packet whereas DMA channel 4 is used to receive the data packet (SPI is use in full duplex mode. In this example the MTSR pin it externally connected to the MRST pin as there is not a real SPI slave device). The code directly accesses the registers for the QSPI and DMA. It doesn't use the iLLD's for these peripherals.
* The system clock (PLL) is configured to run at 300MHz.
* The system peripheral bus is configured to run at 100MHz.
* The QSPI clock is fSOURCE2 configured to run at 200MHz.
Details to the Demo
The QSPI is operated in "Short Data Continuous Mode" When using this mode the transfer requires that you write two BACON entries. The first write is to begin the data exchange and ensure the LAST=0 at the start of the data transfer (it can also be used to remove any delay between the data on the bus). The other BACON write is used to close the data transfer before the last data value is written (i.e LAST=1). In this example the DMA transmit interrupt is used to to write the last BACON and data value to complete the data exchange. Therefore the DMA transfer count for the TX is always one less than the receive DMA transfer count.
The SPI data exchange is initialed be writing the BACON word to the BACONENTRY register. This will cause the Tx DMA to send its data until the transmit count is zero. The receive DMA is also triggered by data being received from the QSPI. When both DMA counts go to zero interrupts are raised for the CPU.
The demo assumes you have data buffers of "8-bits" in size that are aligned on a word boundaries. Where you have the choice to transfer the data using the DMA with either 8-bit, 16-bit or 32-bit transfers. The QSPI and DMA will handle the choice from the below define "QQSPI_DL" define. additionally the QSPI will handle the Endianess of the bytes in both directions.
#define DMA_CH_SIZE_INT8 0 #define DMA_CH_SIZE_INT16 1 #define DMA_CH_SIZE_INT32 2 /*change this for the size of the DMA transfer, the endianess is handled by the QSPI */ #define QSPI_DL DMA_CH_SIZE_INT8
When connected to a debugger you can set the variable "test=1" to kick off the SPI data exchange.
Example of one data exchange on the Master QSPI
* (channel 1, white) is P11.6 QSPI1 Master Clock Output
* (channel 2, tan) is P11.9 QSPI1 Master Transmit Output
* (channel 3, red) is P11.3 QSPI1 Master Receive Input B
* (channel 4, brown) is P10.5 QSPI1 Slave Select Output 9 (ECON1)
#define QSPI_DL DMA_CH_SIZE_INT8
#define QSPI_DL DMA_CH_SIZE_INT32
@cwunder Thank you very much for the detailed info and the attached project.
I am thinking that there might be a typo inside the while loop where the variable "test" value is compared.. I am thinking test value to be compared to "0" (instead of 1) and then its value to be set to "1" inside the while loop before calling "QSPI1_StoreBACON"... Assuming my this assumption is true, can you please confirm my understanding of this project below?
- As QPSI1 is configured as 'SingleMoveMode' (TXFM), calling QSPI1_StoreBACON(baconEntrySpi1Start) triggers DMA service request as BACONENTRY update causes FIFO to be updated. And since QSPI1 Tx interrupt is enabled with TOS as DMA, DMA_CH3 service request will be triggered which will then copy the actual 1st byte of Tx data. And this cycle of DMA transfer will repeat for the next 63 bytes as DMA.TRL is set to 63.
- And once all 63 bytes DMA transfers complete, DMA_CH3 interrupt (serviced by CPU) will be triggered as TCNT (transfer count) reaches 0 (all 63bytes are transferred)
- Inside the ISR (DMA_Ch3ISR),
- BACONENTRY is updated with LAST value = 1; (indicating this is the last byte)
- DATAENTRY is updated with last byte value of actual Tx data to be transmitted..
- After above two actions in DMA_Ch3ISR, QSPI will trigger DMA_CH3 service request again to repeat the above steps 1 ~ 3.
Is my understanding correct? If so, I have couple of questions, please
1. Inside the ISR (DMA_Ch3ISR), at what point the QPSI will trigger DMA service request? Will it be triggered after the write to BACONENTRY (line #542) or will it be triggered after the write to DATAENTRY0 (line #544)? I am asking this question, because QSPI in SingleMoveMode (TXFM) will trigger DMA service request as soon as TXFIFO is updated.. correct? Does this mean.. DMA channel3 service will trigger as soon as BACONENTRY is updated (line #542)? If so, doesn't the last data value be lost? And DMA will start the transfer of byte 0~byte62?
2. Instead of using CPU interrupt, is there an other way (like polling) for updating BANCONENTRY(baconEntrySpi1Stop) and DATAENTRY (for last dataByte) ?
The SPI transfer does not occur until you set the variable "test=1" from a debugger. Steps 1 through 3 are as you describe however step 4 will not occur as the DMA channels are now disabled and you must re-initialize them in order to have another transfer occur.
Inside the ISR (DMA_Ch3ISR), at what point the QPSI will trigger DMA service request? It will never occur as the DMA channel was disabled once its count equaled zero. Tx events from the QSPI are not going anywhere as the destination is the DMA channel however it is not longer enabled.
Instead of using CPU interrupt, is there an other way (like polling) for updating BANCONENTRY(baconEntrySpi1Stop) and DATAENTRY (for last dataByte) ? Yes I suppose you could not enable the interrupt for the DMA_CH3. Your software could poll the TCOUNT field to become zero.
@cwunder Thank you for the feedback again.
I am re-initializing DMA again in my periodic function if "u8TriggerQspiExchng == 2". I am setting this variable to "2" at the end of the ISR as can be seen in the belowT-32 screen shot. With this, I am able to see the data exchange.
How ever after few cycles (1199 exchanges as indicated by SpiTxCnt), I am getting bus error exception as shown in the picture below. It seems to point to DCache area.
Is the method of re-initializing DMA that I used above the correct or proper way? Is there a better approach that you can suggest? Problem seems to be more frequent if I re-enable DMA channel for QSPI Rx ..
Any suggestions would be a great help to me.
I am not sure of the issue. After each transfer I would think you would check the status flags of the QSPI and DMA to ensure there wasn't any issue from the last transfer.
In my test I re-initialize the source and destination pointers, TREL values and then enable the channels (ECH bits)
@cwunder Appreciate your feedback.
If I re-initialize DMA again as by calling DMA_QSPI1_Init() after the transaction complete as shown below, I don't get the problem any more.
But as you suggested, I will place some checks/counters to look for any errors in QSPI/DMA.
@cwunder Thank you for your continued support. If it is possible, would you please share an bare metal example for Slave Node to respond this example project of 64bytes? As far as Slave software is concerned, I believe I shall configure QSPI SLAVE node Rx interrupt with DMA to move data from RXEXIT to local buffer. So as soon as Slave RXFIFO is updated, dma moves data from RXEXIT to local buffer.
Now the point where I am struggling is how to (or at which point) initiate the Slave Tx to trigger dma move from local buffer to QSPI SLAVE’s DATAENTRY register. I think it is critical that Slave’s TX is placed in the right position..