Generate Frequency using GPIO Pin

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

Generate Frequency using GPIO Pin

1,784 Views
mayursavaj
Contributor III

Hello,

I am using MK22F512 series controller. We want to generate frequency using GPIO Pin.

Can anyone help me out?

Thanks,

Mayur

Labels (1)
0 Kudos
Reply
5 Replies

1,345 Views
Kan_Li
NXP TechSupport
NXP TechSupport

Hi Mayur,

If you look into KSDK 2.1, you will find a pit demo for FRDM-K22F, which sets up a one-second interrupt for PIT, you may modify the period for your application and toggle the GPIO in the ISR.

To download KSDK 2.1, please refer to Welcome to MCUXpresso | MCUXpresso Config Tools 


Hope that helps,
Have a great day,
Kan

-----------------------------------------------------------------------------------------------------------------------
Note: If this post answers your question, please click the Correct Answer button. Thank you!
-----------------------------------------------------------------------------------------------------------------------

0 Kudos
Reply

1,345 Views
mjbcswitzerland
Specialist V

Hi

In case you prefer to toggle an output in an ISR (it uses a lot more processor time and is only recommended for lower frequencies - it will also have more jitter) the only change to the above code is to add an IRQ handler call-back.
DMA can also be used at the same time but usually there would be no real advantage in this.

...
pit_setup.int_handler = irq_toggle_output; // rather than pit_setup.int_handler = 0;
...

// IRQ handler to toggle LED rather than allow DMA to do it
//
static void irq_toggle_output(void)
{
    _TOGGLE_PORT(A, PORTA_BIT1)
}

Further notes:
- when using interrupts for generating a square wave it may have some jitter due to other interrupts or due to critical regions globally disabling interrupts
- when generating fast speeds there is a risk that a period may be missed and thus a half-cycle jump occurs (when the processor misses servicing quickly enough)

- when deleting/programming flash it is necessary to disable interrupts in most cases so the frequency will also stop (eg. during about 13ms for a sector erase) - this may be a (serious) problem for some uses

Conclusion:
As long as there is a single DMA channel available in any Kinetis part (K, KL etc.) with DMA it would be unwise to use interrupts to generate a GPIO square wave in the majority of cases. In such instances the solution would represent a poor solution, which can easily be improved greatly with the simple technique shown.

Regards

Mark

0 Kudos
Reply

1,345 Views
mjbcswitzerland
Specialist V

Hi Mayur

If your pin has FlexTimer functionality you can use a FlexTimer to generate a frequency (or PWM).
In case you don't have such a peripheral function and the frequency is not too high you can use any timer interrupt (FlexTimer, PIT, SYSTICK, LPTMR) to generate a periodic interrupt and toggle the GPIO in it.
In the second case a more efficient solution is also to use a periodic timer to generate a DMA trigger that writes to the GPIO toggle register of the GPIO in question (this can also toggle multiple outputs with the same frequency with no CPU intervention and so allows high speeds).

Regards

Mark
Kinetis for Professionals: http://www.utasker.com/kinetis.html
K22: http://www.utasker.com/kinetis/FRDM-K22F.html
HW timers: http://www.utasker.com/docs/uTasker/uTaskerHWTimers.PDF

1,345 Views
mayursavaj
Contributor III

Hi Mark,

Thank you for reply.

My Pin don't have FlexTimer functionality. So, i have to go your 3rd option (periodic Timer).

I have tried and got its working only for 1 to 10 Hz. for higher value its not working.

Can you share any example code?

Regards,

Mayur Savaj

0 Kudos
Reply

1,345 Views
mjbcswitzerland
Specialist V

Hi

In the uTasker project I can generate a 500kHz frequency on one (or more bits on a port) ports when I configure a PIT as follows:

    PIT_SETUP pit_setup;                                                 // interrupt configuration parameters
    pit_setup.int_type = PIT_INTERRUPT;
    pit_setup.int_handler = 0;                                           // no interrupt due to DMA
    pit_setup.int_priority = PIT2_INTERRUPT_PRIORITY;                    // not used since we don't enable the interrupt
    pit_setup.mode = (PIT_PERIODIC | PIT_OUTPUT_DMA_TRIG);               // periodic with DMA trigger to control a port toggle
    pit_setup.ulPortBits = PORTA_BIT1;                                   // toggle PTA1 on each PIT trigger
    pit_setup.ucPortRef = PORTA;                                         // the pin(s) to be toggled is on port A
    pit_setup.ucPIT = 2;                                                 // use PIT2 (this will use DMA channel 2, 0 would use DMA channel 0, etc.)
    pit_setup.count_delay = PIT_US_DELAY(1);                             // 1us period for 500kHz frequency generated
    fnConfigureInterrupt((void *)&pit_setup);                            // configure PIT‍‍‍‍‍‍‍‍‍‍

So you can get an idea of the method, this is what the configuration does when PIT_OUTPUT_DMA_TRIG is set (it also sets up the PIT for periodic operation - not shown):

        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), 0, 0); // source is the port bit and destination is the GPIO toggle register
            fnDMA_BufferReset(PIT_settings->ucPIT, DMA_BUFFER_START);
        }‍‍‍‍‍‍‍‍

Note that in the case of a PIT, the PIT can only trigger the DMA channel with the same number as itself.

The real work to set up the DMA for a K part (KL parts have a different DMA controller - the uTasker project supports both in a compatible manner but I only show the relevant K part code here for clarity) is

// Buffer source to fixed destimation address or fixed source address to buffer
//
static void fnConfigDMA_buffer(unsigned char ucDMA_channel, unsigned char ucDmaTriggerSource, unsigned long ulBufLength, void *ptrBufSource, void *ptrBufDest, unsigned long ulRules, void(*int_handler)(void), int int_priority)
{
    unsigned char ucSize = (unsigned char)(ulRules & 0x07);              // transfer size 1, 2 or 4 bytes

    KINETIS_DMA_TDC *ptrDMA_TCD = (KINETIS_DMA_TDC *)eDMA_DESCRIPTORS;
    ptrDMA_TCD += ucDMA_channel;
    ptrDMA_TCD->DMA_TCD_SOFF = ucSize;                                   // source increment (buffer)
    ptrDMA_TCD->DMA_TCD_DOFF = 0;                                        // destination not incremented
    ptrDMA_TCD->DMA_TCD_DLASTSGA = 0;                                    // no destination displacement on transmit buffer completion
    ptrDMA_TCD->DMA_TCD_SLAST = (-(signed long)(ulBufLength));           // when the buffer has been transmitted set the destination back to the start of it
    switch (ucSize) {
    default:
    case 1:
        ucSize = 1;
        ptrDMA_TCD->DMA_TCD_ATTR = (DMA_TCD_ATTR_DSIZE_8 | DMA_TCD_ATTR_SSIZE_8); // transfer sizes bytes
        break;
    case 2:
        ptrDMA_TCD->DMA_TCD_ATTR = (DMA_TCD_ATTR_DSIZE_16 | DMA_TCD_ATTR_SSIZE_16); // transfer sizes words
        break;
    case 4:
        ptrDMA_TCD->DMA_TCD_ATTR = (DMA_TCD_ATTR_DSIZE_32 | DMA_TCD_ATTR_SSIZE_32); // transfer sizes long words
        break;
    }
    ptrDMA_TCD->DMA_TCD_SADDR = (unsigned long)ptrBufSource;             // source buffer
    ptrDMA_TCD->DMA_TCD_NBYTES_ML = ucSize;                              // each request starts a single transfer
    _DMA_handler[ucDMA_channel] = int_handler;                           // user interrupt callback
    ptrDMA_TCD->DMA_TCD_CSR = 0;                                         // free-running mode without any interrupt
    ptrDMA_TCD->DMA_TCD_DADDR = (unsigned long)ptrBufDest;               // destination
    ptrDMA_TCD->DMA_TCD_BITER_ELINK = ptrDMA_TCD->DMA_TCD_CITER_ELINK = (signed short)(ulBufLength / ucSize); // the number of service requests
    POWER_UP(6, SIM_SCGC6_DMAMUX0);                                      // enable DMA multiplexer 0
    *(unsigned char *)(DMAMUX0_BLOCK + ucDMA_channel) = (ucDmaTriggerSource | DMAMUX_CHCFG_ENBL); // connect trigger source to DMA channel
    DMA_ERQ |= (DMA_ERQ_ERQ0 << ucDMA_channel);                          // enable request source
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

I have also attached a FRDM-K22F binary that will generate a 1MHz signal on the RED led using the technique (using the code above). It also has USB-CDC device on the USB with a command line menu allowing various functions. One needs to be aware that, although the frequency being generated is high precision with low jitter and doesn't use CPU power, the DMA transfers are loading the bus "slightly" - the frequency continues to be generated when the CPU is halted (eg. with debugger) as long as the PIT continues to run. If the toggling speed is increased to a higher rate there can be an impact with USB operation (which also needs to transfer data via the bus) which can cause USB unreliability - this happens when 20MHz is attempted, for example, but 1MHz is not an issue.

Regards

Mark

0 Kudos
Reply