Can PIT trigger DMA?

cancel
Showing results for 
Search instead for 
Did you mean: 

Can PIT trigger DMA?

Jump to solution
774 Views
q1220200
Contributor III

Hello,

I'm a little confused about how PIT events and DMA relate, on the FRDM-KL25

What I'm looking to achieve is this ...

Ideally, on the expiration of a PIT timer I want to asynchronously do two things:

  • Transfer a single value from a given memory location to a given register
  • Transfer a fixed array of two byte values through to DAC (play a tiny 12bit audio sample)

However, I'm not completely sure if this is possible.

Firstly, reading the application note "Application of Asynchronous DMA operation" (for the KL25), it lists in section 3.2 the modules that are capable of generating DMA requests, PIT is not among them.

Secondly, referring to pages 87-8 of the "KL25 Sub Family Reference Manual" it specifies that "... PIT generates periodic trigger events to the DMA channel mux ...".

So, can I or can't I use PIT events to initiate a DMA transfer?

I'm working mostly just with CMSIS and occasionally some Freescale libraries on this project.

Any help greatly appreciated!

Paul Swanson#

Tags (4)
1 Solution
441 Views
mjbcswitzerland
Specialist V

Hi Paul

PITs can trigger DMA, with the following restrictions/differences:
1. PIT0 can only trigger DMA channel 0, PIT1 can only trigger DMA channe 1, etc.
2. Its trigger source needs to be set to the "always enabled" DMA MUX channel and its channel trigger enabled

From a programming point of view to use PIT0 you must use DMA channel 0 and set the DMA MUX to (eg. for KL25) to 58 (0x3a) plus 0x40 (enable channel trigger) and enable the DMA mode in the PIT.
To make this more or less transparent, define the PIT0 trigger source as (58 | 0x40) and then standard DMA configuration can be used without needing to consider the special difference.

Regards

Mark

Complete Kinetis solutions for professional needs, training and support: http://www.utasker.com/kinetis.html
Kinetis KL25, KL26, KL27, KL28, KL43, KL46, KL82
- http://http://www.utasker.com/kinetis/FRDM-KL25Z.html
- http://www.utasker.com/kinetis/TWR-KL25Z48M.html
- http://www.utasker.com/kinetis/FRDM-KL26Z.html
- http://www.utasker.com/kinetis/TEENSY_LC.html
- http://www.utasker.com/kinetis/FRDM-KL27Z.html
- http://www.utasker.com/kinetis/Capuccino-KL27.html
- http://www.utasker.com/kinetis/FRDM-KL28Z.html
- http://www.utasker.com/kinetis/FRDM-KL43Z.html
- http://www.utasker.com/kinetis/TWR-KL43Z48M.html
- http://www.utasker.com/kinetis/FRDM-KL46Z.html
- http://www.utasker.com/kinetis/TWR-KL46Z48M.html
- http://www.utasker.com/kinetis/FRDM-KL82Z.html

uTasker: supporting >1'000 registered Kinetis users get products faster and cheaper to market
Request Free emergency remote desk-top consulting at http://www.utasker.com/services.html

Open Source version at https://github.com/uTasker/uTasker-Kinetis

View solution in original post

6 Replies
442 Views
mjbcswitzerland
Specialist V

Hi Paul

PITs can trigger DMA, with the following restrictions/differences:
1. PIT0 can only trigger DMA channel 0, PIT1 can only trigger DMA channe 1, etc.
2. Its trigger source needs to be set to the "always enabled" DMA MUX channel and its channel trigger enabled

From a programming point of view to use PIT0 you must use DMA channel 0 and set the DMA MUX to (eg. for KL25) to 58 (0x3a) plus 0x40 (enable channel trigger) and enable the DMA mode in the PIT.
To make this more or less transparent, define the PIT0 trigger source as (58 | 0x40) and then standard DMA configuration can be used without needing to consider the special difference.

Regards

Mark

Complete Kinetis solutions for professional needs, training and support: http://www.utasker.com/kinetis.html
Kinetis KL25, KL26, KL27, KL28, KL43, KL46, KL82
- http://http://www.utasker.com/kinetis/FRDM-KL25Z.html
- http://www.utasker.com/kinetis/TWR-KL25Z48M.html
- http://www.utasker.com/kinetis/FRDM-KL26Z.html
- http://www.utasker.com/kinetis/TEENSY_LC.html
- http://www.utasker.com/kinetis/FRDM-KL27Z.html
- http://www.utasker.com/kinetis/Capuccino-KL27.html
- http://www.utasker.com/kinetis/FRDM-KL28Z.html
- http://www.utasker.com/kinetis/FRDM-KL43Z.html
- http://www.utasker.com/kinetis/TWR-KL43Z48M.html
- http://www.utasker.com/kinetis/FRDM-KL46Z.html
- http://www.utasker.com/kinetis/TWR-KL46Z48M.html
- http://www.utasker.com/kinetis/FRDM-KL82Z.html

uTasker: supporting >1'000 registered Kinetis users get products faster and cheaper to market
Request Free emergency remote desk-top consulting at http://www.utasker.com/services.html

Open Source version at https://github.com/uTasker/uTasker-Kinetis

View solution in original post

441 Views
q1220200
Contributor III

Hi Mark,

A couple of follow up questions, because I'm still not having any luck getting this working.

Firstly, I should have specified the MCU more precisely, it's the MKL25Z128VLK4.

Secondly, what I'm wanting to achieve right now is this:

  1. PIT Timer triggers a single (32bit) DMA memory to memory transfer
  2. That transfer causes the toggling of a GPIO pin

The goal here is to effectively achieve a periodic toggling of a GPIO pin with interrupting the main loop or requiring an interrupt handler to be called.

 I've no problem getting the PIT timer going. But right now, I can't seem to get the DMA transfer to happen as a result of the PIT timer expiring.

Here's a sample of the source code (just using CMSIS and some boilerplate code from Freescale):

#define LED1 7
const uint32_t LED1_DMA = (1UL << LED1);


// Configure clock for PORTC, DMA Multiplexer, DMA & PIT
SIM->SCGC5 |= SIM_SCGC5_PORTC(1);
SIM->SCGC6 |= SIM_SCGC6_DMAMUX(1) | SIM_SCGC6_PIT(1);
SIM->SCGC7 |= SIM_SCGC7_DMA(1);

// Disable DMAMUX channel and PIT timer
DMAMUX0->CHCFG[0] = 0;
PIT->CHANNEL[0].TCTRL &= ~PIT_TCTRL_TEN(1);

// Configure DMA Source and Destination addresses
DMA0->DMA[0].SAR = (uint32_t)&LED1_DMA;
DMA0->DMA[0].DAR = (uint32_t)&PTC->PTOR;
DMA0->DMA[0].DCR = DMA_DCR_ERQ(1) | DMA_DCR_CS(1);

// Configure PIT
PIT->CHANNEL[0].LDVAL = SystemCoreClock - 1; // 47999999 or one second
PIT->CHANNEL[0].TCTRL |= PIT_TCTRL_TEN(1) | PIT_TCTRL_TIE(1);
PIT->MCR = 0; /* Module enabled, don't freeze in debug */

// Enable DMAMUX0 Channel 0, always on and triggered
DMAMUX0->CHCFG[0] |= 60 | DMAMUX_CHCFG_ENBL(1) | DMAMUX_CHCFG_TRIG(1);

/* Set Port C Pin 7 to GPIO module */
PORTC->PCR[LED1] &= ~PORT_PCR_MUX_MASK; /* Clear MUX bits */
PORTC->PCR[LED1] |= PORT_PCR_MUX(1); /* Enable pin 7 for GPIO */
PTC->PDDR |= (1UL << LED1); /* Set pin 7 to output */
PTC->PCOR |= (1UL << LED1); /* Set pin 7 low */

My understanding is that PIT timer should triggering a DMA transfer of a single 32bit word, as configure, by this stage and that resulting in a GPIO pin toggling (blinking LED) once per second. The PIT is going, but the DMA module is doing nothing.

Any further thoughts would be greatly appreciated.

Regards,

Paul Swanson

0 Kudos
441 Views
q1220200
Contributor III

Not to worry, I solved it!

(With the help of an excellent book, thank you Alexander Dean Textbooks - Efficient Embedded Systems Design and Programming – Arm )

Here's the solution for posterity's sake, how to toggle a GPIO pin using only PIT, DMA & GPIO:

#define LED1 7
const uint32_t LED1_DMA = (1UL << LED1);
uint32_t foo = 0;


void PIT_IRQHandler(void)
{
   PIT->CHANNEL[0].TFLG |= PIT_TFLG_TIF(0);
}
void DMA0_IRQHandler(void)
{
   DMA0->DMA[0].DSR_BCR |= DMA_DSR_BCR_DONE_MASK;
   DMA0->DMA[0].SAR = (uint32_t)&LED1_DMA;
   DMA0->DMA[0].DAR = (uint32_t)&PTC->PTOR;
   DMA0->DMA[0].DSR_BCR = DMA_DSR_BCR_BCR(4);
   DMA0->DMA[0].DSR_BCR &= ~DMA_DSR_BCR_DONE_MASK;
   DMAMUX0->CHCFG[0] |= DMAMUX_CHCFG_ENBL(1);
}

/* The following is inside the main() * and implies board initialisation and loop etc */


// Configure clock for PORTC, DMA Multiplexer, DMA & PIT
SIM->SCGC5 |= SIM_SCGC5_PORTC(1);
SIM->SCGC6 |= SIM_SCGC6_DMAMUX(1) | SIM_SCGC6_PIT(1);
SIM->SCGC7 |= SIM_SCGC7_DMA(1);

/* Set Port C Pin 7 to GPIO module */
PORTC->PCR[LED1] &= ~PORT_PCR_MUX_MASK; /* Clear MUX bits */
PORTC->PCR[LED1] |= PORT_PCR_MUX(1); /* Enable pin 7 for GPIO */
PTC->PDDR |= (1UL << LED1); /* Set pin 7 to output */
PTC->PCOR |= (1UL << LED1); /* Set pin 7 low */

// Disable DMAMUX channel and PIT timer
DMAMUX0->CHCFG[0] = 0;
PIT->CHANNEL[0].TCTRL &= ~PIT_TCTRL_TEN(1);

// Configure DMA Source and Destination addresses
DMA0->DMA[0].DCR = DMA_DCR_ERQ(1) | DMA_DCR_CS(1) | DMA_DCR_EINT(1);

// Configure PIT
PIT->CHANNEL[0].LDVAL = SystemCoreClock - 1; // 47999999 or one second
PIT->CHANNEL[0].TCTRL |= PIT_TCTRL_TEN(1) | PIT_TCTRL_TIE(1);
PIT->MCR = 0; /* Module enabled, don't freeze in debug */

// Enable DMAMUX0 Channel 0, always on and triggered
DMAMUX0->CHCFG[0] |= DMAMUX_CHCFG_TRIG(1);

/* Set up interrupt controller */
NVIC_SetPriority(PIT_IRQn, 2);
NVIC_ClearPendingIRQ(PIT_IRQn);
NVIC_EnableIRQ(PIT_IRQn);

NVIC_SetPriority(DMA0_IRQn, 0);
NVIC_ClearPendingIRQ(DMA0_IRQn);
NVIC_EnableIRQ(DMA0_IRQn);

DMAMUX0->CHCFG[0] |= DMAMUX_CHCFG_SOURCE(60);

DMA0->DMA[0].SAR = (uint32_t)&LED1_DMA;
DMA0->DMA[0].DAR = (uint32_t)&PTC->PTOR;
DMA0->DMA[0].DSR_BCR = DMA_DSR_BCR_BCR(4);
DMA0->DMA[0].DSR_BCR &= ~DMA_DSR_BCR_DONE_MASK;

DMAMUX0->CHCFG[0] |= DMAMUX_CHCFG_ENBL(1);

/* LED toggles off and on once a second from this point on */

0 Kudos
441 Views
mjbcswitzerland
Specialist V

Paul

You don't need to enable a PIT interrupt since DMA does it all.

This is how it is done in the uTasker project (generating a 500kHz complimentary square wave on PTA0 and PTA1 using PIT1) :

void fnGeneratePIT_square_wave(void)
{
    PIT_SETUP pit_setup;                                                 // PIT interrupt configuration parameters
    pit_setup.int_type = PIT_INTERRUPT;
    _CONFIG_DRIVE_PORT_OUTPUT_VALUE(A, (PORTA_BIT0 | PORTA_BIT1), (PORTA_BIT1), (PORT_SRE_SLOW | PORT_DSE_HIGH)); // set initial port states
    pit_setup.ucPortRef = PORTA;
    pit_setup.ulPortBits = (PORTA_BIT0 | PORTA_BIT1);                    // toggle PTA0/1 on each PIT trigger
    pit_setup.ucPIT = 1;                                                 // use PIT1 (this will use DMA channel 1, 0 would use DMA channel 0, etc.)
    pit_setup.count_delay = PIT_US_DELAY(1);                             // 1us period (for 500kHz square wave)
    pit_setup.mode = (PIT_PERIODIC | PIT_OUTPUT_DMA_TRIG);               // periodic with DMA trigger to control a port toggle
    fnConfigureInterrupt((void *)&pit_setup);                            // configure PIT
}
‍‍‍‍‍‍‍‍‍‍‍

The PIT method has a DMA port output capability built into it. In the lower layers it will look similar to what you have done for the KL25 but will also work on Kinetis parts with eDMA or LPIT so the user doesn't need to worry about the details. You will need to redevelop the same if you move to a part with eDMA.

Regards

Mark

441 Views
q1220200
Contributor III

Hi Mark,

That's interesting. Perhaps you can help me better understand your terminology.

On this MCU, I can see that PIT is able to (or does) trigger the first two DMA channels. However, I can not see any documentation in the Sub Family manual or other the suggests the PIT module itself contains any configurable options directly related to DMA; rather the DMAMUX / DMA modules can accept a trigger from the PIT if so configured.

When you say "the PIT method has a DMA port output capability built into it", is this ultimately what you're referring to (in the context of uTasker)?

Also, back to your first statement, is it not necessary to clear the PIT interrupt flag with an ISR for proper operation of the PIT?

Thanks as always,

Paul Swanson

0 Kudos
441 Views
mjbcswitzerland
Specialist V

Paul

The PIT triggers DMA each time it overflows - there is nothing to configure in the PIT to do this apart from setting it to a periodic operating mode.

You don't need to enable PIT interrupt and you don't need to handle a PIT interrupt (this would be counter productive if it were the case).

In the uTasker PIT driver the DMA port case is simply a set up for periodic mode with out interrupt, plus the following lines of code (which add the DMA setup and starts it):

        if ((PIT_settings->mode & PIT_OUTPUT_DMA_TRIG) != 0) {           // if the PIT is to trigger a port toggle by DMA
            // Note that PIT channel number must match with the DMA channel (hardware requirement) and the DMA channel is chosen here to match the PIT
            //
            static unsigned long ulPortBits[_PITS_AVAILABLE] = {0};
            ulPortBits[PIT_settings->ucPIT] = PIT_settings->ulPortBits;  // the port bit(s) to be toggled
            fnConfigDMA_buffer(PIT_settings->ucPIT, (DMAMUX0_DMA0_CHCFG_SOURCE_PIT0 + PIT_settings->ucPIT), sizeof(ulPortBits[PIT_settings->ucPIT]), &ulPortBits[PIT_settings->ucPIT], (unsigned long *)(GPIO_BLOCK + (0x040 * PIT_settings->ucPortRef) + 0x00c), (DMA_DIRECTION_OUTPUT | DMA_LONG_WORDS | DMA_FIXED_ADDRESSES | DMA_NO_MODULO), 0, 0); // source is the port bit and destination is the GPIO toggle register (DMA_FIXED_ADDRESSES and DMA_NO_MODULO are used only by KL parts)
            fnDMA_BufferReset(PIT_settings->ucPIT, DMA_BUFFER_START);
        }
‍‍‍‍‍‍‍‍

Regards

Mark