How to implement a frequency counter (tachometer) with K22F

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

How to implement a frequency counter (tachometer) with K22F

Jump to solution
3,773 Views
timmmm
Contributor II

Hi,

I'm trying to accurately count the frequency of a square wave input. I.e. over exactly 1 second, how many pulses were there? I'm using a FRDM-K22F board.

To do that I looked at using the FlexTimer module which seems to support an external clock. Unfortunately it also seems like that really was intended to be an external crystal rather than an arbitrary signal. You can only connect it to two pins and neither of those are exposed.

Can anyone suggest anything else? Ideally I'd like to do everything in hardware rather than interrupts, for accuracy.

I've previously used Nordic's nRF51822 and that has counters that can be driven from external inputs, and also a "Programmable Peripheral Interface" (or something) which allowed you to connect events together (e.g. so when the 1 second timer runs out it automatically stops the counter). Is there anything like that for Kenetis?

Cheers,

Tim

1 Solution
2,068 Views
alex_yang
NXP Employee
NXP Employee

Hi Tim,

Maybe you can use DMA to count the frequency of a square wave input,


DMA request is produced when the peripheral request and trigger are effective simultaneously, When DMA finish a minor loop transfer, DMA channel counter count down from the reload value until it reach zero, If we set DMA major loop counter to be the a proper value and set DMA trigger source to be external edge sensitive capture, by reading DMA channel’s countdown register, we can use any of single GPIO for pulse input capture.

Kinetis K series includes a DMA request MUX that allows up to 64 DMA request signals to be mapped to any of the DMA channels. Thus, we can arrange PORTA-PORTE’s rising/falling trigger source to DMA channel 0-5.

Here is my test result:

Below picture show the result of using DMA to capture three different square waves with different frequency (10 KHz, 20 KHz and 1 KHz).

1.jpg

2.jpg

View solution in original post

9 Replies
2,068 Views
williamcarlisle
Contributor II

My setup is very similar to the example above and I have a callback timer setup with a frequency of 1 hz.

As soon as my pulse input hits 33 Khz, the counting halts and will not come back until power is cycled.

Does anyone know if there are any limitations to this implementation?

0 Kudos
2,069 Views
alex_yang
NXP Employee
NXP Employee

Hi Tim,

Maybe you can use DMA to count the frequency of a square wave input,


DMA request is produced when the peripheral request and trigger are effective simultaneously, When DMA finish a minor loop transfer, DMA channel counter count down from the reload value until it reach zero, If we set DMA major loop counter to be the a proper value and set DMA trigger source to be external edge sensitive capture, by reading DMA channel’s countdown register, we can use any of single GPIO for pulse input capture.

Kinetis K series includes a DMA request MUX that allows up to 64 DMA request signals to be mapped to any of the DMA channels. Thus, we can arrange PORTA-PORTE’s rising/falling trigger source to DMA channel 0-5.

Here is my test result:

Below picture show the result of using DMA to capture three different square waves with different frequency (10 KHz, 20 KHz and 1 KHz).

1.jpg

2.jpg

2,068 Views
timmmm
Contributor II

PS: This is all quite new to me. I don't suppose you could post your code as you have already written it? (Much easier to learn from working examples than a reference manual!)

Cheers,

Tim

0 Kudos
2,068 Views
alex_yang
NXP Employee
NXP Employee

sure, but I did it long time ago, Here is the codes for reference:

Initialization and application

To initialize DMA multiple pulse counting capability. We need to perform following step:

For DMA module:

  1. Enable DMAMUX and DMA clock and configure DMAMUX request source.
  2. Set DMA source and destination address to a dummy variable address.
  3. Set DMA source and destination minor and major address adjustment and offset to zero.
  4. Set each DMA channel’s countdown counter(CITER, BITER) to proper value;
  5. Enable DMA channel request to wait peripheral trigger signal.

For GPIO and PORT module:

  1. Set PORT pin mux to GPIO
  2. Enable DMA rising/falling request on PORT_MUX register.

For Application:

  1. Initializing DMA and GPIO module.
  2. Read DMA channel counters value in application to get pulse count value.

Reference code:

Uint32_t dummy;

/* clock gate initialization */

SIM->SCGC5 |= SIM_SCGC5_PORTA_MASK,

SIM->SCGC5 |= SIM_SCGC5_PORTB_MASK,

SIM->SCGC5 |= SIM_SCGC5_PORTC_MASK,

SIM->SCGC5 |= SIM_SCGC5_PORTD_MASK,

SIM->SCGC5 |= SIM_SCGC5_PORTE_MASK,

/* set pin to GPIO function and enable DMA request source */

PORTA->PCR[0] |= PORT_PCR_MUX(1)| PORT_PCR_IRQC(1);

PORTA->PCR[0] |= PORT_PCR_MUX(1)| PORT_PCR_IRQC(1);

PORTA->PCR[0] |= PORT_PCR_MUX(1)| PORT_PCR_IRQC(1);

PORTA->PCR[0] |= PORT_PCR_MUX(1)| PORT_PCR_IRQC(1);

PORTA->PCR[0] |= PORT_PCR_MUX(1)| PORT_PCR_IRQC(1);

/* enable DMA and DMAMUX clock */

SIM->SCGC6 |= SIM_SCGC6_DMAMUX_MASK;   

SIM->SCGC7 |= SIM_SCGC7_DMA_MASK;

Before configuring DMA MUX trigger and source number, the DMAMUX->CFG[ENBL]

bit should be disabled.

/* clears register for changing source and trigger */

DMAMUX->CHCFG[1] = 0;

DMAMUX->CHCFG[2] = 0;

DMAMUX->CHCFG[3] = 0;

DMAMUX->CHCFG[4] = 0;

/* set DMA channel request source */

DMAMUX->CHCFG[0]= DMA MUX_CHCFG_ENBL_MASK| DMA MUX_CHCFG_SOURCE(49);

DMAMUX->CHCFG[1]= DMA MUX_CHCFG_ENBL_MASK| DMA MUX_CHCFG_SOURCE(50);

DMAMUX->CHCFG[2]= DMA MUX_CHCFG_ENBL_MASK| DMA MUX_CHCFG_SOURCE(51);

DMAMUX->CHCFG[3]= DMA MUX_CHCFG_ENBL_MASK| DMA MUX_CHCFG_SOURCE(52);

DMAMUX->CHCFG[4]= DMA MUX_CHCFG_ENBL_MASK| DMA MUX_CHCFG_SOURCE(53);

Before initializing DMA register, define the source data and the address of source and destination.

DMA module initialization (only take DMA channel 0 for example)

/* source configuration */

DMA0->TCD[0].SADDR = &dummy;

DMA0->TCD[0].ATTR = DMA_ATTR_SSIZE(0);

DMA0->TCD[0].SOFF= 0; /* no address shift after each transfer */

DMA0->TCD[0].SLAST = 0;

/* destination configuration */

DMA0->TCD[0].DADDR = &dummy;

DMA0->TCD[0].ATTR = DMA_ATTR_DSIZE(0);

DMA0->TCD[0].DOFF= 0;

DMA0->TCD[0].DLAST_SGA= 0;

/* set CITER and BITER to maximum value */

DMA0->TCD[0].CITER_ELINKNO = DMA_CITER_ELINKNO_CITER_MASK;

DMA0->TCD[0].BITER_ELINKNO = DMA_CITER_ELINKNO_BITER_MASK;

DMA0->TCD[0].NBYTES_MLNO = 1; /* transfer one byte on each trigger arrived */

/* enable auto close requirement */

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

/* start transfer */

DMA0->SERQ = DMA_SERQ_SERQ(0);

Read DMA channel counter in your application, pulse counting value is BITER minus CITER:

Uint32_t value;

value = DMA0->TCD[0]. BITER_ELINKNO - DMA0->TCD[0].CITER_ELINKN

0 Kudos
2,068 Views
timmmm
Contributor II

Thanks for the code! I fixed a few typos etc. and it works as follows:

 uint32_t dummy;
 // This does some initialisation stuff that we need.
 DigitalIn inputPin(PTC1);
 // Set selected pin to GPIO function and enable DMA request source.
 // PORT_PCR_IRQC(1) means DMA on rising edge.
 // E.g. PORTC->PCR[1] means pin 1 on port C (which is SW2).
 PORTC->PCR[1] |= PORT_PCR_MUX(1) | PORT_PCR_IRQC(1);
 blue = 1;
 red = 0;
 // Enable DMA and DMAMUX clock.
 SIM->SCGC6 |= SIM_SCGC6_DMAMUX_MASK;
 SIM->SCGC7 |= SIM_SCGC7_DMA_MASK;
 // Before configuring DMA MUX trigger and source number, the DMAMUX->CHCFG[ENBL]
 // bit should be zeroed. The 0 index refers to DMA channel 0.
 DMAMUX->CHCFG[0] = 0;
 // Set DMA channel request source.
 // 49 is the source for port A, 50 for port B, etc.
 DMAMUX->CHCFG[0] = DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_SOURCE(51);
 // Before initializing DMA register, define the source data and the address of source and destination.
 // DMA module initialization.
 // Source configuration
 DMA0->TCD[0].SADDR = reinterpret_cast<uint32_t>(&dummy);
 DMA0->TCD[0].SOFF = 0; // No address shift after each transfer
 DMA0->TCD[0].SLAST = 0;
 // Destination configuration
 DMA0->TCD[0].DADDR = reinterpret_cast<uint32_t>(&dummy);
 DMA0->TCD[0].DOFF = 0;
 DMA0->TCD[0].DLAST_SGA = 0;
 DMA0->TCD[0].ATTR = DMA_ATTR_SSIZE(0) | DMA_ATTR_DSIZE(0);
 // Set CITER and BITER to maximum value (0x7FFF).
 DMA0->TCD[0].CITER_ELINKNO = DMA_CITER_ELINKNO_CITER_MASK;
 DMA0->TCD[0].BITER_ELINKNO = DMA_CITER_ELINKNO_CITER_MASK;
 DMA0->TCD[0].NBYTES_MLNO = 1; // Transfer one byte on each trigger arrived
 // The channel's ERQ (enable request) bit is cleared when the major loop is complete.
 DMA0->TCD[0].CSR |= DMA_CSR_DREQ_MASK;
 // Start transfer (set enable request).
 DMA0->SERQ = DMA_SERQ_SERQ(0);
 // Since I can only count 65000 pulses, to get good accuracy at low speed we need
 // to record the time at an edge, rather than record the counts at a time.
 // To do that we just interrupt when CITER reaches zero.
 DMA0->TCD[0].CSR |= DMA_CSR_INTMAJOR_MASK;
 // Enable interrupt in NVIC.
// NVIC_SetVector(DMA0_IRQn, (uint32_t)DMA0_TransferCompleteIRQ);
// NVIC_EnableIRQ(DMA0_IRQn);
 // Read DMA channel counter in your application, pulse counting value is BITER minus CITER:
 for (;;)
 {
 uint32_t value = DMA0->TCD[0].BITER_ELINKNO - DMA0->TCD[0].CITER_ELINKNO;
0 Kudos
2,068 Views
timmmm
Contributor II

Oops ignore the blue = 1; red = 0; lines - that was for debugging!

0 Kudos
2,068 Views
timmmm
Contributor II

Ah I thought there might be something I could do with DMA. I've not really used it before though so I am a little unsure what it actually does. Anyway, very cool that this can be done just with DMA though. And thank you so much for proving that it works - I will go and read the chapter on DMA thoroughly now. :-)

Cheers,

Tim

0 Kudos
2,068 Views
chris_brown
NXP Employee
NXP Employee

Hi Tim,

So there are a few things I can suggest. 

1) You probably want to look at using the LPTMR instead of the FTM since the LPTMR has features designed for your use case.  It has a pulse counter option. 

2) You could continue down the track that you are on if you are set on using the FTM.  The external inputs are designed to take in a clock (not connected to a crystal).  These are more for connection to a device like a canned oscillator. So I think it would still perform the function that you want.  You would just want to check the CNT value whenever you interrupt every second to see how many pulses came through. 

If neither of these still work I do have another option up my sleeve.  Please let me know if you have any other questions. 

Thanks,

Chris

2,068 Views
timmmm
Contributor II

Ah yes, LPTMR makes much more sense and is much simpler than FTM. I was going to ask if it was possible to do it all in hardware as well, but Xi Yang has answered that already below!

Thanks very much!

0 Kudos