The application note mentioned in PteroDAQ frequency channels is Freescale’s AN5083, Using DMA for pulse counting on Kinetis, which I finally got around to looking up and reading today, after implementing frequency channels on the KL26, KL25, and K20 processors (Teensy LC, FRDM-KL25Z, and Teensy 3.1 boards, respectively). The reason I looked it up finally was to look for a workaround for the biggest problem—namely that the CITER counter on the K20 has a maximum of 32767. If I’m counting at the highest frequency (supposedly 8MHz or 6.26MHz, depending which documentation I believe, but I’ve not been able to get measurements much above 4MHz), that requires sampling at 125–245Hz or faster. But I often want to sample at factors of 60Hz, to alias out any frequency modulation from line interference. The cruder DMA systems of the KL25 and KL26 use a 20-bit counter, and should be able to count to 4-5MHz (though they are likely to top out at 3MHz), so I could sample as slowly as once every 3.8s, even at the highest measurable input frequency.
I had no problem getting the counting to work with the 15-bit DMA_TCD_CITER counter. I can live with the 32767 count limit (at 60Hz sampling, that would be a 1.966MHz limit on the counting), but I wanted to see if I could use the optional interrupt that can be set up when the CITER counter ends to increment a software counter and reset the DMA counting. That should give me an arbitrarily wide counter, at the cost of perhaps missing a count at the wraparound (an error of 30.5ppm, about the same as the clock error).
Unfortunately, AN5083 merely explains what I had already figured out for myself—without even the essential warning that you need a NOP after disabling the DMAMUX before looking at the counter to avoid a race condition on the KL25 and KL26:
(&DMAMUX0_CHCFG0)[0] &= ~(DMAMUX_ENABLE); // turn off at DMAMUX __asm__ volatile( "nop" ); uint32_t count = 0xfffff - (DMA_DSR_BCR0 & 0xfffff);
They only say
The major loop counter of the DMA register DMA_TCDn_CITER_ELINKNO is limited to 15 bits, in the case where the channel linking feature of the DMA is not in use (refer to DMA chapter in the Reference Manual). Therefore, there is a limitation of 32K major loops count. So, user must check CITER register and make sure it will not reach to zero. If, CITER reaches zero, BITER will reload the value from BITER register.
I tried for a good chunk of yesterday to get the code working with the interrupt. I can get the interrupt to occur (detected by turning off an LED in the interrupt return), I can read the longer frequency counter (detected by turning off an LED if the count to return is >32767), and the count is returned to be pushed on the queue, but something gets wedged and that longer count is never output to the host.
The processor needs to be power-cycled, the same as if the processor had gone into an infinite loop (which is what the Teensyduino unimplemented_isr routine does). I did disable the DMA error interrupt routine (with DMA_CEEI), so that isn’t the problem. I even confirmed this by enabling the error interrupt and providing a dma_error_isr routine that turns off the LED, but the LED did not turn off. I also checked the DMA_ES register at the beginning and end of the dma_ch0_isr routine and in the routine that reads the counter, and DMA_ES was always 0. So if there is an error, it is not being reported by the hardware.
I suppose that an SWD debugging interface (not available on the Teensy 3.1 board) might be useful here, as I could at least check my conjecture that the processor is looping in the fault_isr routine. It may be wedged somewhere else. And there is always the possibility that there is an undocumented hardware bug (like the race condition between DMAMUX disabling and reading DMA_DSR_BCR on the KL26) that requires some workaround that I haven’t thought of yet.
I’ve given up on the higher resolution counters for Teensy 3.1 for now and just pushed the 15-bit implementation (with the interrupt code mostly commented out) to the BitBucket PteroDAQ repository. If anyone reading this blog is familiar with the DMA interrupts on the K20 processor, please look at the code in kinetis_frequency.c and tell me what I’m doing wrong!