Hardware triggered DMA data transfer setup problem

Status
Not open for further replies.
Greetiungs fellow programmers.

I encountered a problem, while trying to hook a DMA channel to an SPI DMA request on a Teensy 3.1 board. The program below is supposed to work in the following way:
  • On falling edge on the MISO pin, clock out 4 8-bit frames
  • Each time data is read into the RX FIFO make a DMA request
  • The DMA should move the frame from the top of the FIFO to the next buffer entry (buf)
  • Repeat until the end of the buffer is reached
  • Start overwriting buffer entries from the beginning
Essentially I am trying to create an extended FIFO for my SPI data. The program runs smoothly until I try to connect the SPI DMA request to the DMA channel in this line:
Code:
DMAMUX0_CHCFG0 = DMAMUX_SOURCE_SPI0_RX | DMAMUX_ENABLE;
Probably not the command itself is the problem, but rather some other configuration I missed. The problem is, upon inseting this line into the code, the device stops serial communication, so I can't really start debugging. It's the first time I've dealt with this kind of technology in microcontrollers, so I would not be surprised if I oversaw or misunderstood some aspects.

I would be very grateful for any support or ideas on the issue.
Thanks in advance and have a nice day,
Chrono.

Code:
const int bufSize = 1024;	 // buffer size for receive buffer
char buf[bufSize]; // receive buffer for async transfer
int bufEntrySize = sizeof(*buf); // size of one buf entry in bit
 
void portc_isr()
{
	SPI0_MCR |= SPI_MCR_CLR_RXF; // clear spi0 rx fifo
	for (int i = 0; i < 4; i++)  // clock out 32 bit from slave
	{
		SPI0_SR |= SPI_SR_TCF;  // clear spi0 transfer complete flag
		SPI0_PUSHR = 0xFF; // send "don't care" byte
		while (!(SPI0_SR & SPI_SR_TCF)); // wait for spi transfer to complete
	}

	PORTC_ISFR = (1 << 7);	 // clear portc7 interrupt flag
	NVIC_CLEAR_PENDING(IRQ_PORTC); // clear portc pending interrupts
}

void setup() 
{
	// digital pin config
	PORTC_PCR7 = PORT_PCR_MUX(2) | PORT_PCR_IRQC(10); // configure portc7 as miso and interrupt on falling edge
	NVIC_ENABLE_IRQ(IRQ_PORTC); // enable portc interrupt
	GPIOC_PDDR = (1 << 5); // make portc5 an input
	PORTC_PCR5 = PORT_PCR_DSE | PORT_PCR_MUX(2); // configure portc5 as sin

	// spi config
	SIM_SCGC6 |= SIM_SCGC6_SPI0; // activate spi0 clock
	SPI0_MCR = SPI_MCR_MSTR;	 // enter spi0 master mode
	SPI0_CTAR0 = SPI_CTAR_FMSZ(7) | SPI_CTAR_DBR; // spi0 frame size 8 bit and double baud rate
	SPI0_RSER = SPI_RSER_RFDF_RE | SPI_RSER_RFDF_DIRS; // enable drain rx fifo dma request

	// dma config	
	SIM_SCGC7 |= SIM_SCGC7_DMA;	// enable dma clock
	SIM_SCGC6 |= SIM_SCGC6_DMAMUX; // enable dmamux clock
	DMAMUX0_CHCFG0 = DMAMUX_DISABLE; // disable dmamux
	DMA_CR = 0; // use default settings
	DMA_TCD0_SADDR = &SPI0_POPR; // set dma source to spi pop rx fifo register
	DMA_TCD0_SOFF = 0; // don't increment source pointer
	DMA_TCD0_ATTR = DMA_TCD_ATTR_SSIZE(DMA_TCD_ATTR_SIZE_8BIT) | // source data size is 8 bit
					DMA_TCD_ATTR_DSIZE(DMA_TCD_ATTR_SIZE_8BIT); // destination data size is 8 bit
	DMA_TCD0_NBYTES_MLNO = 1; // transfer one byte per request
	DMA_TCD0_SLAST = 0; // don't change source pointer after major loop
	DMA_TCD0_DADDR = buf; // set destination address to buffer
	DMA_TCD0_DOFF = bufEntrySize; // increment destination pointer by 8 bit each time
	DMA_TCD0_BITER_ELINKNO = DMA_TCD0_CITER_ELINKNO = bufSize; // do buf size major iterations
	DMA_TCD0_DLASTSGA = -sizeof(buf); // return to beginning of buf after major loop finished
	DMA_TCD0_CSR = DMA_TCD_CSR_DREQ; // clear request once served
	DMAMUX0_CHCFG0 = DMAMUX_SOURCE_SPI0_RX | DMAMUX_ENABLE; // set spi0 as source of dma requests and enable mux	
	DMA_SERQ = DMA_SERQ_SERQ(0); // enable requests for channel 0
}

void loop() 
{

}
 
Last edited:
Ok,
I figured out one mistake. I gave the offset in bit instead of byte. That probably crashed the CPU. Still the DMA doesn't seem to resond to the SPIs requests.
 
Last edited:
You enable the DMA intrerrupt, but I cannot see a interrupt handler for the DMA.

The whole is a bit confusing, so the pin ISR writes a block of data to SPI and the DMA should
transfer the data that is shifted in to the buffer, is that what you intend ?

Do you need a DMA interrupt for that ?
 
You are right about the DMA interrupt. I use it for debug purposes and forgot to take out the NVIC command/leave the ISR in.

And yes, when the MISO port detects an edge it write one byte out on the SPI, which has no other effect than to cycle one byte out of my slave device. I already know that this part is working. What I am having trouble with is setting up the DMA/DMAMUX so that the SPI can request to have the incoming bytes pulled from the RX FIFO.
 
Ok, seems I got it working now.
Another mistake of mine was enabling DMA_TCD_CSR_DREQ, this cleared the request flag and thus prevented any work to be done. In case anyone is interested, I provide the working code.

Greets,
Chrono.

Code:
const int bufSize = 1024; // buffer size for receive buffer
char buf[bufSize]; // receive buffer for async transfer
int bufEntrySize = sizeof(*buf); // size of one buf entry in bit

void portc_isr()
{
	SPI0_MCR |= SPI_MCR_CLR_RXF; // clear spi0 rx fifo
	for (int i = 0; i < 4; i++) // clock out 24 bit from adc
	{
		SPI0_SR |= SPI_SR_TCF; // clear spi0 transfer complete flag
		SPI0_PUSHR = 0xFF; // send "don't care" byte
		while (!(SPI0_SR & SPI_SR_TCF)); // wait for spi transfer to complete
	}

	PORTC_ISFR = (1 << 7); // clear portc7 interrupt flag
	NVIC_CLEAR_PENDING(IRQ_PORTC); // clear portc pending interrupts
}

void dma_ch0_isr() // this isr triggers whenever bufLen frames were transfered
{
        // do some work with the data or set some flags
	DMA_CINT = DMA_CINT_CAIR;
}

void setup() 
{
	__disable_irq();

	// digital pin config
	PORTC_PCR7 = PORT_PCR_MUX(2) | PORT_PCR_IRQC(10); // configure portc7 as miso and interrupt on falling edge
	GPIOC_PDDR = (1 << 5); // make portc5 an input
	PORTC_PCR5 = PORT_PCR_DSE | PORT_PCR_MUX(2); // configure portc5 as sin
	NVIC_ENABLE_IRQ(IRQ_PORTC); // enable portc interrupt

	// spi config
	SIM_SCGC6 |= SIM_SCGC6_SPI0; // activate spi0 clock
	SPI0_MCR = SPI_MCR_MSTR; // enter spi0 master mode
	SPI0_CTAR0 = SPI_CTAR_FMSZ(7) | SPI_CTAR_DBR; // spi0 frame size 8 bit and double baud rate
	SPI0_RSER = SPI_RSER_RFDF_RE | SPI_RSER_RFDF_DIRS; // enable drain rx fifo dma request

	// dma config	
	SIM_SCGC7 |= SIM_SCGC7_DMA; // enable dma clock
	SIM_SCGC6 |= SIM_SCGC6_DMAMUX; // enable dmamux clock
	DMAMUX0_CHCFG0 = DMAMUX_DISABLE; // disable dmamux
	DMAMUX0_CHCFG0 = DMAMUX_SOURCE_SPI0_RX | DMAMUX_ENABLE; // set spi0 as source of dma requests and enable mux
	DMA_SERQ = DMA_SERQ_SERQ(0); // enable requests for channel 0
	DMA_TCD0_SADDR = &SPI0_POPR; // set dma source to spi pop rx fifo register
	DMA_TCD0_SOFF = 0; // don't increment source pointer
	DMA_TCD0_ATTR = DMA_TCD_ATTR_SSIZE(DMA_TCD_ATTR_SIZE_8BIT) | // source data size is 8 bit
					DMA_TCD_ATTR_DSIZE(DMA_TCD_ATTR_SIZE_8BIT); // destination data size is 8 bit
	DMA_TCD0_NBYTES_MLNO = 1; // transfer one byte per request
	DMA_TCD0_SLAST = 0; // don't change source pointer after major loop
	DMA_TCD0_DADDR = buf; // set destination address to buffer
	DMA_TCD0_DOFF = bufEntrySize; // increment destination pointer by 1 byte each time
	DMA_TCD0_BITER_ELINKNO = DMA_TCD0_CITER_ELINKNO = bufSize; // do buf size major iterations
	DMA_TCD0_DLASTSGA = -sizeof(buf); // return to beginning of buf after major loop finished
	DMA_TCD0_CSR = DMA_TCD_CSR_INTMAJOR; // clear request once served and activate interrupt on major loop completion
	NVIC_ENABLE_IRQ(IRQ_DMA_CH0); // attach dma channel 0 interrupt		

	__enable_irq();
}

void loop() 
{

}
 
Status
Not open for further replies.
Back
Top