Kinetis MK66, DMA to read GPIO

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

Kinetis MK66, DMA to read GPIO

7,387 Views
luishs
Senior Contributor I

Hi.

I try to use DMA to read several GPIO pins, but in the SDK there is no example of source code. I found the AN4627SW source code in Google (attached), but if I copy it to my current MK66 project, there are many errors when compiling. Most errors are "undeclared", it seems that it is not for Kinetis MK66 and I do not know how to port it.

In the MK66 SDK there are two examples of source code, memory to memory, I'm not sure if I could use them simply by modifying the source address so that it points to the GPIO port address instead of to an address of the RAM (data array) ).


Can someone help me, with some source code for Kinetis MK66, using DMA to read 8 pins of the GPIO port, activated by several external signals? Basically I have an 8-bit data bus, which I need to capture and process, activated by various data from a control bus.

15 Replies

6,810 Views
mjbcswitzerland
Specialist V

Hi Luis

In the uTasker project it is done like this (assuming triggered by the falling edge of PTB16):

#define OUTPUT_LENGTH  100
unsigned long ulOutput[OUTPUT_LENGTH] = { 0 };

static void fnSampleBus(void)
{

    INTERRUPT_SETUP interrupt_setup;                                     // interrupt configuration parameters
    interrupt_setup.int_type = PORT_INTERRUPT;                           // identifier to configure port interrupt
    interrupt_setup.int_port = PORTB;                                    // the port that the interrupt input is on
    interrupt_setup.int_port_bits = PORTB_BIT16;                         // UART input pin on FRDM-K64F
    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 interrupt
    // Configure the DMA trigger
    //
    fnConfigDMA_buffer(9, DMAMUX0_CHCFG_SOURCE_PORTB, sizeof(ulOutput), (void *)&(((GPIO_REGS *)GPIOC_ADD)->PDIR), (void *)&ulOutput, (DMA_DIRECTION_INPUT | DMA_LONG_WORDS), 0, 0); // use DMA channel 9 without any interrupts
    fnDMA_BufferReset(9, DMA_BUFFER_START);
}


It will fill the buffer ulOutput[] with the PORTC input value on each edge.
Bytes (instead of the complete port width) can be copied using DMA_BYTES flag instead (byte buffer)

The code is on GitHub and is compatible with all Kinetis parts with port DMA capability (including K66).

Beware that only one DMA trigger is possible per port. Therefore, for example, maximum 5 different DMA port triggers would be possible on a part with DMA capability on ports A, B, C, D and E.

Regards

Mark

6,810 Views
luishs
Senior Contributor I

I'm thinking that maybe I could do it using only interrupts, instead of the DMA. I tried with interrupts, but I'm not sure if I could read several GPIO pins with a single instruction.

These are the pins that I have assigned to my data bus.

  D0 -> PTA15
  D1 -> PTA14
  D2 -> PTA13
  D3 -> PTA12
  D4 -> PTA11
  D5 -> PTA10
  D6 -> PTA9
  D7 -> PTA8

And to read each pin I use eight instructions, one to read each pin:

#define LEE_D0 GPIO_PinRead (BOARD_INITPINS_D0_GPIO, BOARD_INITPINS_D0_PIN)
#define LEE_D1 GPIO_PinRead (BOARD_INITPINS_D1_GPIO, BOARD_INITPINS_D1_PIN)
#define LEE_D2 GPIO_PinRead (BOARD_INITPINS_D2_GPIO, BOARD_INITPINS_D2_PIN)
#define LEE_D3 GPIO_PinRead (BOARD_INITPINS_D3_GPIO, BOARD_INITPINS_D3_PIN)
#define LEE_D4 GPIO_PinRead (BOARD_INITPINS_D4_GPIO, BOARD_INITPINS_D4_PIN)
#define LEE_D5 GPIO_PinRead (BOARD_INITPINS_D5_GPIO, BOARD_INITPINS_D5_PIN)
#define LEE_D6 GPIO_PinRead (BOARD_INITPINS_D6_GPIO, BOARD_INITPINS_D6_PIN)
#define LEE_D7 GPIO_PinRead (BOARD_INITPINS_D7_GPIO, BOARD_INITPINS_D7_PIN)

Do you know if I could read the 8 pins of the same port, with a single instruction, instead of having to do 8 readings, one for each pin?

The idea is that 6 signals from my control bus, trigger interrupts, and in the interrupt routine, detecting which pin triggered the interrupt, I read the GPIO with the data of my data bus and I keep it for its process (show status of each data in several Led matrix). Now I do 8 readings, to read each one the 8 bits of the data bus assigned each to a pin of the microcontroller.

The signals do not think they are very fast, since it is an arcade machine from the 90s, with an EF68B09EP processor at 8Mhz.

0 Kudos
Reply

6,810 Views
mjbcswitzerland
Specialist V

Luis

The DMA triggers are limited and not the reading.

unsigned char ucDataBus = (unsigned char)(GPIOA_PDIR > 8);

is all that you need to read your 8 bit bus input.

The DMA case simply puts it into a variable for you on each trigger without needing an interrupt each time.

Regards

Mark

6,810 Views
luishs
Senior Contributor I

Hello again.

I have put your instruction unsigned char ucDataBus = (unsigned char)(GPIOA_PDIR > 8);

in my program, but compiler fail, show this error message " error: 'GPIOA_PDIR' undeclared (first use in this function); did you mean 'GPIOA_BASE'? "

I have replace it by this, and compile with success:

unsigned char ucDataBus = (unsigned char)(GPIOA->PDIR > 8);

0 Kudos
Reply

6,808 Views
mjbcswitzerland
Specialist V

Luis

In fact there is just one DMA trigger source for a port but you could set all of your 6 inputs to the same trigger if any of them falling low needs to generate the same event.

In the uTasker code it is done by (assuming the 6 inputs on the same port on PTB16..PTB21)



PORTB_BIT18 | 
 | PORTB_BIT20);

The method used to address peripheral registers depends on the library used. In the uTasker project GPIOA_PDIR  is used .

The major advantage of the uTasker method is that it also allows compete simulation of the operation so the above code can be verified without the need for HW (and in a much more efficient debugging environment).

I have attached a short video showing the code in operation. It shows setting some port input states on port C, triggering DMA by falling edges on the 6 port B inputs and checking that the DMA transfers have been made by looking at the capture buffer in a watch window.

As you see it is possible to do with around 10 lines of intuitive user code and verify operation within a minute or so, making it much more efficient than the traditional library and development methods. It allows professional developers to greatly reduce costs; whereas typical developers spend hours or days working out how it operates internally and experimenting with changes to example code to get it working uTasker developers can quickly complete their development so that they are already selling their product while others are still fighting with the details.

Regards

Mark

6,808 Views
luishs
Senior Contributor I


I think you're working with KDS, and the libraries may be different from the ones used by MCUXpresso.

I have opened MCUXpresso, and in the "Pins" option, it seems that I can assign a DMA interrupt to the input ports, maybe this will configure me the same but using the MCUXpresso libraries. Problem is that need enabled and configured interrupt DMA and the Peripheral Tools seem its not available for Kinetis MK66, so need codify manually.

ScreenHunter_014.jpg

ScreenHunter_011.jpg

0 Kudos
Reply

6,808 Views
mjbcswitzerland
Specialist V

Luis

I am working with the uTasker code, which works in KDS, MCUXpresso, S32, CodeWarrior, Atollic, Crossworks, IAR, Keil, Green Hills, CooCox, GNU and VisualStudio.

It can be used together with the NXP framework but needs the libraries from GitHub (uTasker) to be used too.

In case you can only use NXP libraries you may need to rework and further develop their examples to get something similar since the uTasker hardware layer APIs encapsulate a higher level of functionality and usability on top of its Kinetis chip simulation capabilities.

Note that the uTasker methods make the pin configuration tools redundant (these also don't support all chips and older ones like PE are legacy and not supported any more anyway). As you can see, the uTasker API just needs to be told the pins you want to use and it does all the work for you (and will work on any other chip too without needing to port code or go through the same generation steps again each time something is changed).

Regards

Mark

6,808 Views
luishs
Senior Contributor I

I have compile your source code but there are a lot of errors. I do not know what is the problem, probably you work with a different library, or I need add some library to my project. I work with MCUXpresso and SDK source examples to start this project.

These are the errors when I try to compile your source code:

../source/pit.c:423:5: error: unknown type name 'INTERRUPT_SETUP'
INTERRUPT_SETUP interrupt_setup; // interrupt configuration parameters
^~~~~~~~~~~~~~~
../source/pit.c:424:20: error: request for member 'int_type' in something not a structure or union
interrupt_setup.int_type = PORT_INTERRUPT; // identifier to configure port interrupt
^
../source/pit.c:424:32: error: 'PORT_INTERRUPT' undeclared (first use in this function); did you mean 'PORT_DFER_DFE'?
interrupt_setup.int_type = PORT_INTERRUPT; // identifier to configure port interrupt
^~~~~~~~~~~~~~
PORT_DFER_DFE
../source/pit.c:424:32: note: each undeclared identifier is reported only once for each function it appears in
../source/pit.c:425:20: error: request for member 'int_port' in something not a structure or union
interrupt_setup.int_port = PORTB; // the port that the interrupt input is on
^
../source/pit.c:427:20: error: request for member 'int_port_bits' in something not a structure or union
interrupt_setup.int_port_bits = PORTB_BIT16; // UART input pin on FRDM-K64F
^
../source/pit.c:427:37: error: 'PORTB_BIT16' undeclared (first use in this function); did you mean 'PORTB_IRQn'?
interrupt_setup.int_port_bits = PORTB_BIT16; // UART input pin on FRDM-K64F
^~~~~~~~~~~
PORTB_IRQn
../source/pit.c:429:20: error: request for member 'int_port_bits' in something not a structure or union
interrupt_setup.int_port_bits = (PORTB_BIT16 | PORTB_BIT17 | PORTB_BIT18 | PORTB_BIT19 | PORTB_BIT20);
^
../source/pit.c:429:52: error: 'PORTB_BIT17' undeclared (first use in this function); did you mean 'PORTB_BIT16'?
interrupt_setup.int_port_bits = (PORTB_BIT16 | PORTB_BIT17 | PORTB_BIT18 | PORTB_BIT19 | PORTB_BIT20);
^~~~~~~~~~~
PORTB_BIT16
../source/pit.c:429:66: error: 'PORTB_BIT18' undeclared (first use in this function); did you mean 'PORTB_BIT17'?
interrupt_setup.int_port_bits = (PORTB_BIT16 | PORTB_BIT17 | PORTB_BIT18 | PORTB_BIT19 | PORTB_BIT20);
^~~~~~~~~~~
PORTB_BIT17
../source/pit.c:429:80: error: 'PORTB_BIT19' undeclared (first use in this function); did you mean 'PORTB_BIT18'?
interrupt_setup.int_port_bits = (PORTB_BIT16 | PORTB_BIT17 | PORTB_BIT18 | PORTB_BIT19 | PORTB_BIT20);
^~~~~~~~~~~
PORTB_BIT18
../source/pit.c:429:94: error: 'PORTB_BIT20' undeclared (first use in this function); did you mean 'PORTB_BIT19'?
interrupt_setup.int_port_bits = (PORTB_BIT16 | PORTB_BIT17 | PORTB_BIT18 | PORTB_BIT19 | PORTB_BIT20);
^~~~~~~~~~~
PORTB_BIT19
../source/pit.c:431:20: error: request for member 'int_port_sense' in something not a structure or union
interrupt_setup.int_port_sense = (IRQ_FALLING_EDGE | PULLUP_ON | PORT_DMA_MODE); // DMA on falling edge
^
../source/pit.c:431:39: error: 'IRQ_FALLING_EDGE' undeclared (first use in this function)
interrupt_setup.int_port_sense = (IRQ_FALLING_EDGE | PULLUP_ON | PORT_DMA_MODE); // DMA on falling edge
^~~~~~~~~~~~~~~~
../source/pit.c:431:58: error: 'PULLUP_ON' undeclared (first use in this function)
interrupt_setup.int_port_sense = (IRQ_FALLING_EDGE | PULLUP_ON | PORT_DMA_MODE); // DMA on falling edge
^~~~~~~~~
../source/pit.c:431:70: error: 'PORT_DMA_MODE' undeclared (first use in this function); did you mean 'PORT_PCR_ODE'?
interrupt_setup.int_port_sense = (IRQ_FALLING_EDGE | PULLUP_ON | PORT_DMA_MODE); // DMA on falling edge
^~~~~~~~~~~~~
PORT_PCR_ODE
../source/pit.c:432:20: error: request for member 'int_handler' in something not a structure or union
interrupt_setup.int_handler = 0; // no interrupt handler when using DMA
^
../source/pit.c:433:5: warning: implicit declaration of function 'fnConfigureInterrupt' [-Wimplicit-function-declaration]
fnConfigureInterrupt((void *)&interrupt_setup); // configure interrupt
^~~~~~~~~~~~~~~~~~~~
../source/pit.c:436:5: warning: implicit declaration of function 'fnConfigDMA_buffer' [-Wimplicit-function-declaration]
fnConfigDMA_buffer(9, DMAMUX0_CHCFG_SOURCE_PORTB, sizeof(ulOutput), (void *)&(((GPIO_REGS *)GPIOC_ADD)->PDIR), (void *)&ulOutput, (DMA_DIRECTION_INPUT | DMA_LONG_WORDS), 0, 0); // use DMA channel 9 without any interrupts
^~~~~~~~~~~~~~~~~~
../source/pit.c:436:27: error: 'DMAMUX0_CHCFG_SOURCE_PORTB' undeclared (first use in this function); did you mean 'DMAMUX_CHCFG_SOURCE_SHIFT'?
fnConfigDMA_buffer(9, DMAMUX0_CHCFG_SOURCE_PORTB, sizeof(ulOutput), (void *)&(((GPIO_REGS *)GPIOC_ADD)->PDIR), (void *)&ulOutput, (DMA_DIRECTION_INPUT | DMA_LONG_WORDS), 0, 0); // use DMA channel 9 without any interrupts
^~~~~~~~~~~~~~~~~~~~~~~~~~
DMAMUX_CHCFG_SOURCE_SHIFT
../source/pit.c:436:85: error: 'GPIO_REGS' undeclared (first use in this function); did you mean 'GPIO_Type'?
fnConfigDMA_buffer(9, DMAMUX0_CHCFG_SOURCE_PORTB, sizeof(ulOutput), (void *)&(((GPIO_REGS *)GPIOC_ADD)->PDIR), (void *)&ulOutput, (DMA_DIRECTION_INPUT | DMA_LONG_WORDS), 0, 0); // use DMA channel 9 without any interrupts
^~~~~~~~~
GPIO_Type
../source/pit.c:436:96: error: expected expression before ')' token
fnConfigDMA_buffer(9, DMAMUX0_CHCFG_SOURCE_PORTB, sizeof(ulOutput), (void *)&(((GPIO_REGS *)GPIOC_ADD)->PDIR), (void *)&ulOutput, (DMA_DIRECTION_INPUT | DMA_LONG_WORDS), 0, 0); // use DMA channel 9 without any interrupts
^
../source/pit.c:436:136: error: 'DMA_DIRECTION_INPUT' undeclared (first use in this function)
fnConfigDMA_buffer(9, DMAMUX0_CHCFG_SOURCE_PORTB, sizeof(ulOutput), (void *)&(((GPIO_REGS *)GPIOC_ADD)->PDIR), (void *)&ulOutput, (DMA_DIRECTION_INPUT | DMA_LONG_WORDS), 0, 0); // use DMA channel 9 without any interrupts
^~~~~~~~~~~~~~~~~~~
../source/pit.c:436:158: error: 'DMA_LONG_WORDS' undeclared (first use in this function); did you mean 'DMA_CHN_IRQS'?
fnConfigDMA_buffer(9, DMAMUX0_CHCFG_SOURCE_PORTB, sizeof(ulOutput), (void *)&(((GPIO_REGS *)GPIOC_ADD)->PDIR), (void *)&ulOutput, (DMA_DIRECTION_INPUT | DMA_LONG_WORDS), 0, 0); // use DMA channel 9 without any interrupts
^~~~~~~~~~~~~~
DMA_CHN_IRQS
../source/pit.c:437:5: warning: implicit declaration of function 'fnDMA_BufferReset' [-Wimplicit-function-declaration]
fnDMA_BufferReset(9, DMA_BUFFER_START);
^~~~~~~~~~~~~~~~~
../source/pit.c:437:26: error: 'DMA_BUFFER_START' undeclared (first use in this function); did you mean 'DMA_CSR_START'?
fnDMA_BufferReset(9, DMA_BUFFER_START);
^~~~~~~~~~~~~~~~
DMA_CSR_START
../source/pit.c: In function 'main':
../source/pit.c:860:18: warning: unused variable 'pitConfig' [-Wunused-variable]
pit_config_t pitConfig;
^~~~~~~~~
At top level:
../source/pit.c:418:13: warning: 'fnSampleBus' defined but not used [-Wunused-function]
static void fnSampleBus(void)

0 Kudos
Reply

6,807 Views
mjbcswitzerland
Specialist V

Luis

1. The DMA controller supports an interrupt on either half or full buffer completion. If it were to interrupt on each DMA transfer it would not really have any advantage over interrupts.

2. In DMA mode the sources trigger the DMA transfer but there is no way to know which one it was (also, without interrupts, one doesn't necessarily know that it has taken place)

3. I set up for continuous operation but

fnDMA_BufferReset(9, DMA_BUFFER_STOP);

can be used to stop it.

From your latest information I think that interrupt driven operation is the most suitable for you. This is the code that does it in the uTasker project. I also attached a further video to show how it is verified.

static void __callback_interrupt irq_control_change(int iInputRef)
{
    unsigned char ucData = (unsigned char)(_READ_PORT(C) >> 16);         // read the data bus (PTC23..PTC16)
    switch (iInputRef) {
    case 16:                                                             // PTB16
        ucData = ucData;
        break;
    case 17:                                                             // PTB17
        ucData = ucData;
        break;
    case 18:                                                             // PTB18
        ucData = ucData;
        break;
    case 19:                                                             // PTB19
        ucData = ucData;
        break;
    case 20:                                                             // PTB20
        ucData = ucData;
        break;
    case 21:                                                             // PTB21
        ucData = ucData;
        break;
    }
}


static void fnConfigureBus(void)
{
    INTERRUPT_SETUP interrupt_setup;                                     // interrupt configuration parameters
    interrupt_setup.int_type = PORT_INTERRUPT;                           // identifier to configure port interrupt
    interrupt_setup.int_port = PORTB;                                    // the port that the interrupt input is on
    interrupt_setup.int_port_bits = (PORTB_BIT16 | PORTB_BIT17 | PORTB_BIT18 | PORTB_BIT19 | PORTB_BIT20 | PORTB_BIT21); // UART input pin on FRDM-K64F
    interrupt_setup.int_port_sense = (IRQ_FALLING_EDGE | PULLUP_ON);     // interrupt on falling edge
    interrupt_setup.int_handler = irq_control_change;                    // interrupt handler on any edge
    interrupt_setup.int_priority = 2;                                    // use interrupt priority 2 for port interrupts
    fnConfigureInterrupt((void *)&interrupt_setup);                      // configure interrupt
}

Regards

Mark

0 Kudos
Reply

6,807 Views
luishs
Senior Contributor I

Thanks Mark.


Yes, as far as I understand, in this case using the DMA would not bring advantages, I would have to use common interrupts assigned to each port that should trigger the capture of the 8bit data bus in the GPIO.


I can configure the interrupts in MCUXpresso from the "Pins" tool, which generates all the source code automatically. I just needed to know how to read the state of the 8 pins, with a single instruction, since before I did it reading one by one each pin, and also reading one by one each of the control bus pins. It was slow and did not work well, probably because the data entered faster than the program was able to read.


Now in the interrupt is already identified the signal that caused the interrupt, and with the way you have indicated me to read the 8 pins with a single instruction, I think that must work all right.

In any case, I appreciate the information about the configuration of the DMA, I'm sure it will be very useful for other programs.

PS: seem there are may differences and compatibility problems, between KDS and MCUXpresso.

Greetings.

0 Kudos
Reply

6,807 Views
luishs
Senior Contributor I

Some questions, about your code, please.

1.- When some of the six GPIO signals trigger the DMA, how do I detect it in my program, to copy and use the data stored by the DMA in the output variable ?, is a Flag activated or the program must jump to any routine with a special name, such as when a pin interrupt is configured?

2.- Is there a way to know which of the six signals was the one that triggered the DMA?, I will need it to be able to identify and process that data, according to which signal of the control bus triggered the capture of data from the data bus.


3.- The configuration that you put of the DMA, need some additional instruction to start or stop the DMA?.

Regards

0 Kudos
Reply

6,807 Views
luishs
Senior Contributor I

Thank you very much for the clarification Mark, now I understand.

So this is going to be very useful for what I need to program.

Thanks for your help, I'm going to try it.

0 Kudos
Reply

6,807 Views
luishs
Senior Contributor I


About your suggestion using  unsigned char ucDataBus = (unsigned char)(GPIOA_PDIR > 8);

Is it possible to read the status of each pin, with only one GPIO reading, to load them into an array of 8 elements?

0 Kudos
Reply

6,807 Views
luishs
Senior Contributor I

Thanks, I will use PDIR.

Sorry, but I do not understand how trigger the DMA with six signals (GPIO pins), if possible, to capture my data bus.

I understand that the limit of DMA trigger is one signal, then I can not use six different signals (GPIO input pins) to trigger same DMA to capture 8 GPIO pins. Or I have misunderstood what is the DMA trigger limit.

0 Kudos
Reply

6,807 Views
luishs
Senior Contributor I

Thanks mark.

Then it is not possible to trigger the GPIO data capture, using several ports as triggers to capture the same GPIO data (8 pins)?

The problem is that I have an 8 bit data bus, and a 6 signal control bus. Each time one of these 6 signals is set to a low logic level, it tells me that I have data on the data bus, and that I must capture it.

I thought I could do it with the DMA, but for that I must be able to trigger the capture of the 8 pins of the data bus, with 6 different signals from my control bus.


Is it not possible to do this with the DMA, is only a hardware signal possible to trigger the DMA?

0 Kudos
Reply