Hello,
I never used DMA before but i'm struggling with one thing. I got simple code which should copy one array to another with DMA, trigerred by falling edge on GPIO. It works only for the first time, any other falling edges never copy arrays again.
I noticed that clearing DMA_CSR_DONE_MASK solves the problem but it's not good idea to clear it all the time in main loop. Shoudn't it reset everytime there is a falling edge on the GPIO? or should i reset it in interrupt generated by DMA?
Thanks
#include <stdio.h>
#include "board.h"
#include "peripherals.h"
#include "pin_mux.h"
#include "clock_config.h"
#include "MK80F25615.h"
#include "fsl_debug_console.h"volatile uint8_t bufornadawczy[3]={0x0D,0x0C,0x0B};
volatile uint8_t buforodbiorczy[3]={0,0,0};
void dma_init(void)
{
DMAMUX -> CHCFG[0] = (DMAMUX_CHCFG_ENBL_MASK |DMAMUX_CHCFG_SOURCE(51));
DMA0->ERQ |= DMA_ERQ_ERQ0_MASK;DMA0->TCD[0].SADDR = (uint32_t) &bufornadawczy[0];
DMA0->TCD[0].DADDR = (uint32_t) &buforodbiorczy[0];DMA0->TCD[0].SOFF = 1;
DMA0->TCD[0].DOFF = 1;DMA0->TCD[0].ATTR = DMA_ATTR_SSIZE(0) | DMA_ATTR_DSIZE(0);
DMA0->TCD[0].NBYTES_MLNO = 1;
DMA0->TCD[0].CITER_ELINKNO = DMA_CITER_ELINKNO_CITER(3);
DMA0->TCD[0].BITER_ELINKNO = DMA_BITER_ELINKNO_BITER(3);DMA0->TCD[0].SLAST = -3;
DMA0->TCD[0].DLAST_SGA = -3;
DMA0->TCD[0].CSR |= DMA_CSR_DREQ_MASK;
}
int main(void) {
BOARD_InitBootPins();
BOARD_InitBootClocks();
BOARD_InitBootPeripherals();
BOARD_InitDebugConsole();SIM->SCGC5 = (SIM_SCGC5_PORTC_MASK|SIM_SCGC5_PORTE_MASK);
SIM->SCGC6 = (SIM_SCGC6_DMAMUX_MASK|SIM_SCGC6_SPI0_MASK);
SIM->SCGC7 = SIM_SCGC7_DMA_MASK;
//led
PORTE-> PCR[7] = PORT_PCR_MUX(0x01);
GPIOE -> PDDR |= GPIO_PDDR_PDD(1<<7);
GPIOE -> PSOR |= GPIO_PSOR_PTSO(1<<7);PORTC -> PCR[14] |= (PORT_PCR_MUX(0x01)|PORT_PCR_IRQC(2)|PORT_PCR_PE_MASK|PORT_PCR_PS_MASK);//INT2_A, DMA Request, ISR flag
dma_init();
__NVIC_EnableIRQ(61);
while(1)
{
}
return 0 ;
}
Hi Grzegorz
DMA0->TCD[0].CSR |= DMA_CSR_DREQ_MASK;
If you want a burst on each trigger you shouldn't set DREG.
To do this in the uTasker project I do:
#define DMA_CHANNEL_FOR_PORT_EDGE 0 // use DMA channel 0
INTERRUPT_SETUP interrupt_setup; // interrupt configuration parameters
fnConfigDMA_buffer(DMA_CHANNEL_FOR_PORT_EDGE, DMAMUX0_CHCFG_SOURCE_PORTE, sizeof(bufornadawczy), (void *)bufornadawczy, (void *)buforodbiorczy, (DMA_DIRECTION_BUFFER_BUFFER | DMA_BUFFER_BURST_MODE | DMA_BYTES), 0, 0); // configure the DMA channel without any interrupts (free-runnning)
fnDMA_BufferReset(DMA_CHANNEL_FOR_PORT_EDGE, DMA_BUFFER_START); // enable the DMA operation (trigger will be enabled later)
// Configure the pin DMA trigger
//
interrupt_setup.int_type = PORT_INTERRUPT; // identifier to configure port interrupt
interrupt_setup.int_port = PORTE; // the port that the interrupt/dma input is on
interrupt_setup.int_port_bits = PORTE_BIT14; // PTE14
interrupt_setup.int_port_sense = (IRQ_FALLING_EDGE | PULLUP_ON | PORT_DMA_MODE); // DMA on falling edge
interrupt_setup.int_handler = 0; // no interrupt handler when using DMA
fnConfigureInterrupt((void *)&interrupt_setup); // configure DMA trigger on falling edge
The uTasker simulator allows it to be tested in Visual Studio. See the video that I have attached.
Regards
Mark
So 1 DMA request = 1 minor loop but i want to transfer let's say 8 bytes to SPI per 1 request, can't do it with DMA major int. So the only way to do this is to reconfig line for interrupt and start transfer in interrupt service using "DMA0->SSRT |= DMA_SSRT_SSRT(0)" 8 times or it can be done any other way?
Grzegorz
It is possible to do a channel burst transfer to an SPI but it means that the writes are very fast and if the SPI doesn't have adequate FIFO depth (SPI0 has 4 but others have only 1) it will overrun the transmitter and only the first 4 or 1 will be sent.
When you sent to SPI via DMA you therefore need to use the SPIs DMA trigger to control transmission and not the trigger from a pin. You can use the trigger for the pin to set up the transfer (via interrupt) or via DMA.
Below I can show you how a pin DMA trigger can be used to control the start of an SPI DMA transmission burst (as included in the uTasker project). In fact it is used to automate both transmission and reception, transmitting a predefined sequence of bytes, controlling the CS line, reading returned data and finally clearing SPI flags via DMA when the transmission has completed (otherwise it won't work a second time!).
It is useful for external high speed ADCs that indicated data is ready via sync line (triggering the operation to write and read a number of bytes purely using DMA).
// Initialise SPI interface
//
POWER_UP_ATOMIC(6, SPI0);
_CONFIG_PERIPHERAL(D, 0, (PD_0_SPI0_PCS0 | PORT_SRE_FAST | PORT_DSE_HIGH));
_CONFIG_PERIPHERAL(D, 1, (PD_1_SPI0_SCK | PORT_SRE_FAST | PORT_DSE_HIGH));
_CONFIG_PERIPHERAL(D, 2, (PD_2_SPI0_SOUT | PORT_SRE_FAST | PORT_DSE_HIGH));
_CONFIG_PERIPHERAL(D, 3, (PD_3_SPI0_SIN));
SPI0_MCR = (SPI_MCR_MSTR | SPI_MCR_DCONF_SPI | SPI_MCR_CLR_RXF | SPI_MCR_CLR_TXF | SPI_MCR_PCSIS_CS0 | SPI_MCR_PCSIS_CS1 | SPI_MCR_PCSIS_CS2 | SPI_MCR_PCSIS_CS3 | SPI_MCR_PCSIS_CS4 | SPI_MCR_PCSIS_CS5);
SPI0_RSER = (SPI_SRER_TFFF_DIRS | SPI_SRER_TFFF_RE | SPI_SRER_RFDF_DIRS | SPI_SRER_RFDF_RE); // enable rx and tx DMA requests
SPI0_CTAR0 = (SPI_CTAR_DBR | SPI_CTAR_FMSZ_8 | SPI_CTAR_PDT_7 | SPI_CTAR_BR_4 | SPI_CTAR_CPHA | SPI_CTAR_CPOL); // for 50MHz bus, 25MHz speed and 140ns min de-select time
interrupt_setup.int_port = PORTE; // the port that the interrupt/dma input is on
interrupt_setup.int_port_bits = PORTE_BIT4;
interrupt_setup.int_port_sense = (IRQ_FALLING_EDGE | PULLUP_ON | PORT_DMA_MODE); // DMA on falling edge
interrupt_setup.int_handler = 0; // no interrupt handler when using DMA
{
// Configure the DMA trigger from an input pin edge to start an SPI transfer
//
#define DMA_CHANNEL_FOR_CS_END 6 // use DMA channel 6 for CS end trigger
#define DMA_CHANNEL_FOR_PORT_EDGE 7 // use DMA channel 7 for SPI sequence start trigger
#define DMA_CHANNEL_FOR_SPI_TX 8 // use DMA channel 8 for SPI Tx
#define DMA_CHANNEL_FOR_SPI_RX 9 // use DMA channel 9 for SPI Rx
static const unsigned long ulSPI_TX[8] = { // fixed SPI transmission (0x01, 0x02,.. 0x08) with CS asserted throughout the frame
(0x01 | SPI_PUSHR_CONT | SPI_PUSHR_PCS0 | SPI_PUSHR_CTAS_CTAR0),
(0x02 | SPI_PUSHR_CONT | SPI_PUSHR_PCS0 | SPI_PUSHR_CTAS_CTAR0),
(0x03 | SPI_PUSHR_CONT | SPI_PUSHR_PCS0 | SPI_PUSHR_CTAS_CTAR0),
(0x04 | SPI_PUSHR_CONT | SPI_PUSHR_PCS0 | SPI_PUSHR_CTAS_CTAR0),
(0x05 | SPI_PUSHR_CONT | SPI_PUSHR_PCS0 | SPI_PUSHR_CTAS_CTAR0),
(0x06 | SPI_PUSHR_CONT | SPI_PUSHR_PCS0 | SPI_PUSHR_CTAS_CTAR0),
(0x07 | SPI_PUSHR_CONT | SPI_PUSHR_PCS0 | SPI_PUSHR_CTAS_CTAR0),
(0x08 | SPI_PUSHR_EOQ | SPI_PUSHR_PCS0 | SPI_PUSHR_CTAS_CTAR0), // final byte negates CS after transmission
};
static volatile unsigned char ucRxData[128] = {0}; // circular reception buffer
static const unsigned char ucDMA_start = ((DMA_ERQ_ERQ0 << (DMA_CHANNEL_FOR_SPI_TX - 8)) | (DMA_ERQ_ERQ0 << (DMA_CHANNEL_FOR_SPI_RX - 8))); // the value to be written to start the SPI DMA transfer
static const unsigned long ulSPI_clear = (SPI_SR_RFDF | SPI_SR_RFOF | SPI_SR_TFUF | SPI_SR_EOQF | SPI_SR_TCF); // this is written to the SPI status register after the CS negates in order to clear flags and allow subsequent transfers
fnConfigDMA_buffer(DMA_CHANNEL_FOR_SPI_TX, DMAMUX0_CHCFG_SOURCE_SPI0_TX, sizeof(ulSPI_TX), (void *)ulSPI_TX, (void *)SPI0_PUSHR_ADDR, (DMA_DIRECTION_OUTPUT | DMA_LONG_WORDS | DMA_SINGLE_CYCLE), 0, 0); // source is the tx buffer and destination is the SPI transmit register without interrupts (free-running)
fnConfigDMA_buffer(DMA_CHANNEL_FOR_SPI_RX, DMAMUX0_CHCFG_SOURCE_SPI0_RX, sizeof(ucRxData), (void *)SPI0_POPR_ADDR, (void *)ucRxData, (DMA_DIRECTION_INPUT | DMA_BYTES | DMA_HALF_BUFFER_INTERRUPT), spi_half_buffer, PRIORITY_DMA9); // source is the SPI reception register and destination is the input buffer with interrupt at half- and full buffer
fnConfigDMA_buffer(DMA_CHANNEL_FOR_PORT_EDGE, DMAMUX0_CHCFG_SOURCE_PORTE, sizeof(ucDMA_start), (void *)&ucDMA_start, (void *)(((unsigned char *)DMA_ERQ_ADDR) + 1), (DMA_FIXED_ADDRESSES | DMA_BYTES), 0, 0); // use DMA channel without any interrupts (free-runnning)
fnDMA_BufferReset(DMA_CHANNEL_FOR_PORT_EDGE, DMA_BUFFER_START); // enable the DMA operation - a falling edge on the port will now trigger SPI Tx and Rx DMA operation
fnConfigureInterrupt((void *)&interrupt_setup); // configure cs port DMA (falling edge)
interrupt_setup.int_port = PORTD; // the port that the interrupt input is on
interrupt_setup.int_port_bits = PORTD_BIT0;
interrupt_setup.int_port_sense = (IRQ_RISING_EDGE | PULLUP_ON | PORT_DMA_MODE | PORT_KEEP_PERIPHERAL); // DMA on rising edge (keep CS peripheral to trigger on the end of a transfer
fnConfigDMA_buffer(DMA_CHANNEL_FOR_CS_END, DMAMUX0_CHCFG_SOURCE_PORTD, sizeof(ulSPI_clear), (void *)&ulSPI_clear, (void *)SPI0_SR_ADDR, (DMA_FIXED_ADDRESSES | DMA_LONG_WORDS), 0, 0); // use DMA channel without any interrupts (free-runnning)
fnDMA_BufferReset(DMA_CHANNEL_FOR_CS_END, DMA_BUFFER_START); // enable the DMA operation - a rising edge on the port will now clear the the SPI status register
fnConfigureInterrupt((void *)&interrupt_setup); // configure cs port DMA (rising edge)
}
This uses 4 DMA channels and represents just one of probably numerous possibilities to do more or less the same thing. There may be more efficient ones but this was simple to set up using the uTasker DMA and port interrupt/DMA APIs.
Regards
Mark
I made some progress in my code, using some tips You gave me. It's working perfectly fine when there is just one senor connected to SPI. It get worse when i got 2 sensors, 2 interrupts and i need to reconfigurate DMA every time there is new interrupt (both sensors requires different data to be sent) and stored in different arrays.
So here is my new question: is it a good idea to reconfigurate DMA so often?
Is there any easier way to send 8 bytes when INT1 is active and 7 bytes when INT2 is active and then repeat it also with receiving data to one array when INT1 is active and when INT2 is to another or just DMA is better when sending same packs of data all the time?