SPI and DMA - only transfers once.

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

SPI and DMA - only transfers once.

Jump to solution
1,042 Views
JBM
Contributor IV

I have custom hardware that consists of an MK22FN512VMP12 processor connected to a sensor through a GPIO for power and four lines for SPI.  

I am trying to trigger DMA SPI transfers through a PIT interrupt.  The problem I'm seeing is that the DMA transfer happens only on the first PIT interrupt.  For subsequent interrupts, nothing happens.  At this point, I don't care if the sensor is seeing it or not, I just want to get the DMA transfers working.  After I get transmit working, I want to get receive working.

I have the DMA error interrupt handler declared, but it is not currently used.  When I had it enabled, I never got any interrupts.

I've been following this example:

https://community.nxp.com/t5/Kinetis-Microcontrollers/使用DMA降低SPI通信过程中内核负荷-Reduce-core-work-load-with... 

Here's my code.  I've attached the file also.

/*
 * Copyright (c) 2015, Freescale Semiconductor, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * o Redistributions of source code must retain the above copyright notice, this list
 *   of conditions and the following disclaimer.
 *
 * o Redistributions in binary form must reproduce the above copyright notice, this
 *   list of conditions and the following disclaimer in the documentation and/or
 *   other materials provided with the distribution.
 *
 * o Neither the name of Freescale Semiconductor, Inc. nor the names of its
 *   contributors may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdint.h>
#include <stdbool.h>

#include "MK22F51212.h"

/*
 * Macros
 */
#define SPI_PUSHR_CTAS_CTAR0		(0x0)

/*
 *
 */
#define EnableInterrupts __asm(" CPSIE i");
#define DisableInterrupts __asm(" CPSID i");

/*
 * Data array - WHOAMI register from sensor
 */
#define SENSOR_FRAME_READ_WHOAMI							(0x40000091)

#define SENSOR_TRANSMIT_READ_WHOAMI_COUNT	(8)

static uint32_t SENSOR_TRANSMIT_READ_WHOAMI[SENSOR_TRANSMIT_READ_WHOAMI_COUNT] =
{
	SPI_PUSHR_CONT_MASK | SPI_PUSHR_CTAS_CTAR0 | SPI_PUSHR_PCS(1) | SPI_PUSHR_TXDATA((SENSOR_FRAME_READ_WHOAMI >> 24) & 0xFF),
	SPI_PUSHR_CONT_MASK | SPI_PUSHR_CTAS_CTAR0 | SPI_PUSHR_PCS(1) | SPI_PUSHR_TXDATA((SENSOR_FRAME_READ_WHOAMI >> 16) & 0xFF),
	SPI_PUSHR_CONT_MASK | SPI_PUSHR_CTAS_CTAR0 | SPI_PUSHR_PCS(1) | SPI_PUSHR_TXDATA((SENSOR_FRAME_READ_WHOAMI >>   & 0xFF),
	SPI_PUSHR_CONT_MASK | SPI_PUSHR_CTAS_CTAR0 | SPI_PUSHR_PCS(1) | SPI_PUSHR_TXDATA((SENSOR_FRAME_READ_WHOAMI      ) & 0xFF),

	SPI_PUSHR_CONT_MASK | SPI_PUSHR_CTAS_CTAR0 | SPI_PUSHR_PCS(1) | SPI_PUSHR_TXDATA((SENSOR_FRAME_READ_WHOAMI >> 24) & 0xFF),
	SPI_PUSHR_CONT_MASK | SPI_PUSHR_CTAS_CTAR0 | SPI_PUSHR_PCS(1) | SPI_PUSHR_TXDATA((SENSOR_FRAME_READ_WHOAMI >> 16) & 0xFF),
	SPI_PUSHR_CONT_MASK | SPI_PUSHR_CTAS_CTAR0 | SPI_PUSHR_PCS(1) | SPI_PUSHR_TXDATA((SENSOR_FRAME_READ_WHOAMI >>   & 0xFF),
	SPI_PUSHR_EOQ_MASK  | SPI_PUSHR_CTAS_CTAR0 | SPI_PUSHR_PCS(1) | SPI_PUSHR_TXDATA((SENSOR_FRAME_READ_WHOAMI      ) & 0xFF),
};

uint32_t receive_buffer[128];  // Not currently used

/*
 * Transmit
 */
static void spi_transmit_receive(uint32_t* buffer, int count)
{
  SPI0->MCR = SPI_MCR_PCSIS(0x01) | SPI_MCR_MSTR_MASK |
						  SPI_MCR_DIS_RXF_MASK | SPI_MCR_DIS_TXF_MASK |
						  SPI_MCR_CLR_RXF_MASK | SPI_MCR_CLR_TXF_MASK |
						  SPI_MCR_HALT_MASK;

	DMA0->CR = 0x0;

	// Transmit
	DMAMUX->CHCFG[0]  = DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_SOURCE(15);

	/* Set the Source and destination addresses */
	DMA0->TCD[0].SADDR = (uint32_t)buffer;
	DMA0->TCD[0].DADDR = (uint32_t)&SPI0->PUSHR;

	/* Source source and destination offsets */
	DMA0->TCD[0].SOFF = sizeof(uint32_t);
	DMA0->TCD[0].DOFF = 0;

	/* Modulo off and port sizes */
	DMA0->TCD[0].ATTR = DMA_ATTR_SSIZE(2) | DMA_ATTR_DSIZE(2);  //source and destination size 0 = 8 bits, 2 = 32 bits

	/* Transfer size */
	DMA0->TCD[0].NBYTES_MLNO = 4;

	/* No link channel, transactions */
	DMA0->TCD[0].CITER_ELINKNO = DMA_CITER_ELINKNO_CITER(count);
	DMA0->TCD[0].BITER_ELINKNO = DMA_BITER_ELINKNO_BITER(count);

	/* Adjustment to source and destination addresses */
	DMA0->TCD[0].SLAST = 0x0;
	DMA0->TCD[0].DLAST_SGA = 0x0;

	DMA0->TCD[0].CSR = DMA_CSR_DREQ_MASK;

#if 0
	// Receive
	DMAMUX->CHCFG[1]  = DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_SOURCE(14);

	/* Set the Source and destination addresses */
	DMA0->TCD[1].SADDR = (uint32_t)&SPI0->POPR;
	DMA0->TCD[1].DADDR = (uint32_t)receive_buffer;

	/* Source source and destination offsets */
	DMA0->TCD[1].SOFF = 0;
	DMA0->TCD[1].DOFF = sizeof(uint32_t);

	/* Modulo off and port sizes */
	DMA0->TCD[1].ATTR = DMA_ATTR_SSIZE(2) | DMA_ATTR_DSIZE(2);  //source and destination size 0 = 8 bits, 2 = 32 bits

	/* Transfer size */
	DMA0->TCD[1].NBYTES_MLNO = 4;

	/* No link channel, transactions */
	DMA0->TCD[1].CITER_ELINKNO = DMA_CITER_ELINKNO_CITER(count);
	DMA0->TCD[1].BITER_ELINKNO = DMA_BITER_ELINKNO_BITER(count);

	/* Adjustment to source and destination addresses */
	DMA0->TCD[1].SLAST = 0x0;
	DMA0->TCD[1].DLAST_SGA = 0x0;

	DMA0->TCD[1].CSR = DMA_CSR_DREQ_MASK | DMA_CSR_INTMAJOR_MASK;

  DMA0->ERQ = DMA_ERQ_ERQ0_MASK | DMA_ERQ_ERQ1_MASK;
#else
  DMA0->ERQ = DMA_ERQ_ERQ0_MASK | DMA_ERQ_ERQ1_MASK;
#endif

  /*
   * Start the SPI -- now configured for DMA
   */
#if 0
	SPI0->RSER |= SPI_RSER_TFFF_RE_MASK | SPI_RSER_TFFF_DIRS_MASK | SPI_RSER_RFDF_RE_MASK | SPI_RSER_RFDF_DIRS_MASK;
#else
	SPI0->RSER |= SPI_RSER_TFFF_RE_MASK | SPI_RSER_TFFF_DIRS_MASK;
#endif
	SPI0->RSER |= SPI_RSER_EOQF_RE_MASK;
	SPI0->MCR  &= ~SPI_MCR_HALT_MASK;
}

/*
 *
 */
void PIT1_IRQHandler(void)
{
	/* We should get PIT1 interrupts more often -- check them first */
	if (PIT->CHANNEL[1].TFLG & PIT_TFLG_TIF_MASK)
	{
		spi_transmit_receive(SENSOR_TRANSMIT_READ_WHOAMI, SENSOR_TRANSMIT_READ_WHOAMI_COUNT);

		/* Clear interrupt flag */
		PIT->CHANNEL[1].TFLG = PIT_TFLG_TIF_MASK;
	}
}

void DMA0_IRQHandler(void)
{
	 SPI0_SR  |= SPI_SR_EOQF_MASK | SPI_SR_TCF_MASK | SPI_SR_RFDF_MASK | SPI_SR_TFFF_MASK | SPI_SR_RFDF_MASK;
	 SPI0_MCR |= SPI_MCR_HALT_MASK;
	 DMA_CINT |= DMA_CINT_CAIR_MASK;
}

void DMA_Error_IRQHandler(void)
{
	__asm("nop");
}


/*
 * 		None
 */
static void pit_init(void)
{
	/* Start PIT module clock */
	SIM->SCGC6 |= SIM_SCGC6_PIT_MASK;

  // Initialize timer
  PIT->MCR     				 	 = PIT_MCR_FRZ_MASK;
  PIT->CHANNEL[1].LDVAL  = 48000000UL/(2000UL);
  PIT->CHANNEL[1].TFLG   = PIT_TFLG_TIF_MASK;
  PIT->CHANNEL[1].TCTRL  = PIT_TCTRL_TIE_MASK;

  /* Enable IRQ */
  NVIC_EnableIRQ(PIT1_IRQn);

  PIT->CHANNEL[1].TCTRL |= PIT_TCTRL_TEN_MASK;
}

/*
 *
 */
static void ports_init(void)
{
	/* Clocking */
	SIM_SCGC5 |= SIM_SCGC5_PORTC_MASK | SIM_SCGC5_PORTD_MASK;

	/* Power for sensor */
	PORTC->PCR[5]	= PORT_PCR_MUX(1);
	PTC->PDDR = (1 << 5);
	PTC->PSOR = (1 << 5);

	/* Configure SPI ports */
	PORTD->PCR[0] = PORT_PCR_MUX(2);
	PORTD->PCR[1] = PORT_PCR_MUX(2);
	PORTD->PCR[2] = PORT_PCR_MUX(2);
	PORTD->PCR[3] = PORT_PCR_MUX(2);
}

/*
 *
 */
static void spi_dma_init(void)
{
	/* Clocking */
	SIM->SCGC6 |= SIM_SCGC6_SPI0_MASK;
	SIM->SCGC6 |= SIM_SCGC6_DMAMUX_MASK;
	SIM->SCGC7 |= SIM_SCGC7_DMA_MASK;

	/* Configure SPI */
	SPI0->MCR = SPI_MCR_HALT_MASK;
	SPI0->CTAR[0] = SPI_CTAR_FMSZ(7);
	SPI0->CTAR[1] = SPI_CTAR_FMSZ(7);

	NVIC_EnableIRQ(DMA0_IRQn);
}


/*
 *
 */
int main(void)
{
	EnableInterrupts;

	ports_init();
	spi_dma_init();
	pit_init();

	while (true)
	{
	}
	return 0;
}

 

0 Kudos
1 Solution
1,021 Views
mjbcswitzerland
Specialist V

Hi

Below is the code to do this in the uTasker project:

 

#define SPI_DMA_LENGTH    11
#define SPI_DMA_CHANNEL   7

// PIT 0 interrupt callback
//
static __callback_interrupt void _startTx(void)
{
    static unsigned long ulSPI_TxBuffer[SPI_DMA_LENGTH] = {0};           // tx buffer

    unsigned char ucByteValue = (unsigned char)ulSPI_TxBuffer[0];
    WRITE_ONE_TO_CLEAR(SPI1_SR, (SPI_SR_EOQF));                          // clear the end of queue flag
    while (i < (SPI_DMA_LENGTH - 1)) {                                   // prepare a tx data pattern
        ulSPI_TxBuffer[i++] = (SPI_PUSHR_CONT | SPI_PUSHR_CTAS_CTAR0 | SPI_PUSHR_PCS0 | ++ucByteValue);
    }
    ulSPI_TxBuffer[i] = (SPI_PUSHR_EOQ | SPI_PUSHR_CTAS_CTAR0 | SPI_PUSHR_PCS0 | ++ucByteValue); // negate CS line after final bytes is sent
    // Configure Tx DMA (buffer to SPI, single buffer with no interrupt on termination)
    //
    fnConfigDMA_buffer(SPI_DMA_CHANNEL, DMAMUX0_CHCFG_SOURCE_SPI1_TX, sizeof(ulSPI_TxBuffer), &ulSPI_TxBuffer[0], (void *)SPI1_PUSHR_ADDR, (DMA_LONG_WORDS | DMA_DIRECTION_OUTPUT | DMA_SINGLE_CYCLE), 0, PRIORITY_DMA10);
    fnDMA_BufferReset(SPI_DMA_CHANNEL, DMA_BUFFER_START);                // start the transfer
}

// PIT and SPI configuration
//
static void fnConfigurePIT_SPI(void)
{
    PIT_SETUP pit_setup;                                                 // PIT interrupt configuration parameters
    // Configure SPI
    //
    POWER_UP_ATOMIC(6, SPI1);
    _CONFIG_PERIPHERAL(D, 4, (PD_4_SPI1_PCS0 | PORT_SRE_FAST | PORT_DSE_HIGH));
    _CONFIG_PERIPHERAL(D, 5, (PD_5_SPI1_SCK | PORT_SRE_FAST | PORT_DSE_HIGH));
    _CONFIG_PERIPHERAL(D, 7, (PD_7_SPI1_SIN | PORT_SRE_FAST | PORT_DSE_HIGH));
    _CONFIG_PERIPHERAL(D, 6, (PD_6_SPI1_SOUT));
    SPI1_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);
    SPI1_CTAR0 = (SPI_CTAR_DBR | SPI_CTAR_FMSZ_8 | SPI_CTAR_PDT_7 | SPI_CTAR_BR_2);
    SPI1_RSER = (SPI_SRER_TFFF_DIRS | SPI_SRER_TFFF_RE);                 // enable DMA request when tx fifo not full

    pit_setup.int_type = PIT_INTERRUPT;
    pit_setup.mode = PIT_PERIODIC;                                       // PIT periodic interrupt
    pit_setup.ucPIT = 0;                                                 // use PIT0
    pit_setup.int_handler = _startTx;                                    // PIT interrupt callback
    pit_setup.int_priority = PIT0_INTERRUPT_PRIORITY;
    pit_setup.count_delay = PIT_FREERUN_FREQ(64000);                     // 64kHz interrupt rate
    fnConfigureInterrupt((void *)&pit_setup);                            // configure and start the PIT
}

 

 

This is the output showing 11 bytes sent 64000 times a second:

mjbcswitzerland_0-1618283085029.png

Closer look:

mjbcswitzerland_1-1618283153756.png

Some notes:

It is not absolutely necessary to use a DMA completion interrupt since the PIT interrupt can simply configure everything needed each time it starts the transfer.

It IS IMPORTANT to clear the EOQF flag in the status register otherwise it will not transmit a second time.

There is no need to enable and disable the SPI since if the DMA is not active it won't send anything anyway.

Although only a detail it is not recommended to clear flags with

SPI0_SR  |= SPI_SR_EOQF_MASK | SPI_SR_TCF_MASK | SPI_SR_RFDF_MASK | SPI_SR_TFFF_MASK | SPI_SR_RFDF_MASK

but correctly they should be cleared with (not ORing with what is read)

SPI0_SR  = SPI_SR_EOQF_MASK | SPI_SR_TCF_MASK | SPI_SR_RFDF_MASK | SPI_SR_TFFF_MASK | SPI_SR_RFDF_MASK

Although the DMA API you are using needs a lot of careful setup (and error prone due to this) I didn't actually spot anything that was incorrectly configured. Therefore check that the EOQF clear is not somehow being optimised away and try not disabling/enabling the SPI between transfers.

Regards

Mark
[uTasker project developer for Kinetis and i.MX RT]
Contact me by personal message or on the uTasker web site to discuss professional training, solutions to problems or rapid product development requirements

For professionals searching for faster, problem-free Kinetis and i.MX RT 10xx developments the uTasker project holds the key: https://www.utasker.com/iMX/RT1064.html

View solution in original post

0 Kudos
2 Replies
1,005 Views
JBM
Contributor IV

It was the setting of the HALT bit in the SPI MCR register that was causing the problem.  I don't know how they got away with it in the demo...

Thanks!

0 Kudos
1,022 Views
mjbcswitzerland
Specialist V

Hi

Below is the code to do this in the uTasker project:

 

#define SPI_DMA_LENGTH    11
#define SPI_DMA_CHANNEL   7

// PIT 0 interrupt callback
//
static __callback_interrupt void _startTx(void)
{
    static unsigned long ulSPI_TxBuffer[SPI_DMA_LENGTH] = {0};           // tx buffer

    unsigned char ucByteValue = (unsigned char)ulSPI_TxBuffer[0];
    WRITE_ONE_TO_CLEAR(SPI1_SR, (SPI_SR_EOQF));                          // clear the end of queue flag
    while (i < (SPI_DMA_LENGTH - 1)) {                                   // prepare a tx data pattern
        ulSPI_TxBuffer[i++] = (SPI_PUSHR_CONT | SPI_PUSHR_CTAS_CTAR0 | SPI_PUSHR_PCS0 | ++ucByteValue);
    }
    ulSPI_TxBuffer[i] = (SPI_PUSHR_EOQ | SPI_PUSHR_CTAS_CTAR0 | SPI_PUSHR_PCS0 | ++ucByteValue); // negate CS line after final bytes is sent
    // Configure Tx DMA (buffer to SPI, single buffer with no interrupt on termination)
    //
    fnConfigDMA_buffer(SPI_DMA_CHANNEL, DMAMUX0_CHCFG_SOURCE_SPI1_TX, sizeof(ulSPI_TxBuffer), &ulSPI_TxBuffer[0], (void *)SPI1_PUSHR_ADDR, (DMA_LONG_WORDS | DMA_DIRECTION_OUTPUT | DMA_SINGLE_CYCLE), 0, PRIORITY_DMA10);
    fnDMA_BufferReset(SPI_DMA_CHANNEL, DMA_BUFFER_START);                // start the transfer
}

// PIT and SPI configuration
//
static void fnConfigurePIT_SPI(void)
{
    PIT_SETUP pit_setup;                                                 // PIT interrupt configuration parameters
    // Configure SPI
    //
    POWER_UP_ATOMIC(6, SPI1);
    _CONFIG_PERIPHERAL(D, 4, (PD_4_SPI1_PCS0 | PORT_SRE_FAST | PORT_DSE_HIGH));
    _CONFIG_PERIPHERAL(D, 5, (PD_5_SPI1_SCK | PORT_SRE_FAST | PORT_DSE_HIGH));
    _CONFIG_PERIPHERAL(D, 7, (PD_7_SPI1_SIN | PORT_SRE_FAST | PORT_DSE_HIGH));
    _CONFIG_PERIPHERAL(D, 6, (PD_6_SPI1_SOUT));
    SPI1_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);
    SPI1_CTAR0 = (SPI_CTAR_DBR | SPI_CTAR_FMSZ_8 | SPI_CTAR_PDT_7 | SPI_CTAR_BR_2);
    SPI1_RSER = (SPI_SRER_TFFF_DIRS | SPI_SRER_TFFF_RE);                 // enable DMA request when tx fifo not full

    pit_setup.int_type = PIT_INTERRUPT;
    pit_setup.mode = PIT_PERIODIC;                                       // PIT periodic interrupt
    pit_setup.ucPIT = 0;                                                 // use PIT0
    pit_setup.int_handler = _startTx;                                    // PIT interrupt callback
    pit_setup.int_priority = PIT0_INTERRUPT_PRIORITY;
    pit_setup.count_delay = PIT_FREERUN_FREQ(64000);                     // 64kHz interrupt rate
    fnConfigureInterrupt((void *)&pit_setup);                            // configure and start the PIT
}

 

 

This is the output showing 11 bytes sent 64000 times a second:

mjbcswitzerland_0-1618283085029.png

Closer look:

mjbcswitzerland_1-1618283153756.png

Some notes:

It is not absolutely necessary to use a DMA completion interrupt since the PIT interrupt can simply configure everything needed each time it starts the transfer.

It IS IMPORTANT to clear the EOQF flag in the status register otherwise it will not transmit a second time.

There is no need to enable and disable the SPI since if the DMA is not active it won't send anything anyway.

Although only a detail it is not recommended to clear flags with

SPI0_SR  |= SPI_SR_EOQF_MASK | SPI_SR_TCF_MASK | SPI_SR_RFDF_MASK | SPI_SR_TFFF_MASK | SPI_SR_RFDF_MASK

but correctly they should be cleared with (not ORing with what is read)

SPI0_SR  = SPI_SR_EOQF_MASK | SPI_SR_TCF_MASK | SPI_SR_RFDF_MASK | SPI_SR_TFFF_MASK | SPI_SR_RFDF_MASK

Although the DMA API you are using needs a lot of careful setup (and error prone due to this) I didn't actually spot anything that was incorrectly configured. Therefore check that the EOQF clear is not somehow being optimised away and try not disabling/enabling the SPI between transfers.

Regards

Mark
[uTasker project developer for Kinetis and i.MX RT]
Contact me by personal message or on the uTasker web site to discuss professional training, solutions to problems or rapid product development requirements

For professionals searching for faster, problem-free Kinetis and i.MX RT 10xx developments the uTasker project holds the key: https://www.utasker.com/iMX/RT1064.html

0 Kudos