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
解決済! 解決策の投稿を見る。
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).
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?
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).
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
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:
For GPIO and PORT module:
For Application:
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
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;
Oops ignore the blue = 1; red = 0; lines - that was for debugging!
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
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
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!