Dose anyone have any code for doing frequency measurement?
Maple frequency measurement
(17 posts) (8 voices)-
Posted 4 years ago #
-
Would you give a bit more information? It might be easier to help.
For example:
Is the signal you are trying to measure digital or analogue?
Do you know approximtaly what order of magnitude the signal frequency is?
How accurate does the measurement need to be, and what sort of duration does it last?
If it is analogue, is the signal a single continous tone, or is it a complex signal which needs to be analysed to extract the constituent frequencies?Posted 4 years ago # -
Its digital at 100hz to 8mhz, ideally it would be as accurate as possible.
Posted 4 years ago # -
Any code ideas?
Posted 4 years ago # -
house91320 - most of the timers have 'input capture' pins.
The timer can be configured to count, then when an input capture pin is triggered by a signal edge, the timer count is copied into a 'capture register', and an interrupt triggered. The interrupt service routine can pick up the value stored in the capture register, and that is a measure of duration.The resolution of a single timer is 16 bits, which is far from the range needed for 100Hz to 8MHz. If you can have several attempts at the same signal, you could try to adjust the frequency the timer counts (the prescaler) so that it can be captured in 16 bits. That might not be an option.
It is possible to 'chain' timers together, if you want to think of one timer's counter reaching the maximum value, and the 'carry' from that timerdriving a second timer counter, effectively giving you a 32-bit counter.The timer can only count at the 72MHz clock frequency, and the sampling of the input signal will effectively be rounded to the nearest clock edge. So an 8MHz signal is only going to register as, roughly a count of 12 (depending on exactly when the edge happens), with an error of almost 10%.
If the signal is fast, but repetitive, the signal from the input capture trigger can be divided, say by 16, so the counter value captured in the time for 16 cycles of the input signal, averaging the duration, and hence giving a bit more resolution (though the same absolute error).
Have a look at the RM0008 manual, for example section 15.3.5 "Input capture mode" and see if it makes sense.
Some of the existing library can be used to set the timer up for counting, but I think you will have to get down to the hardware level, to set up some of the modes, and to get at the capture register.I haven't written code to do this, but it looks quite doable.
(full disclosure: I am not a member of LeafLabs staff)
Posted 4 years ago # -
I wasn't able to find anything besides this post on the forum regarding input capture. I was planning on doing an input capture to measure a digital signal of 5us to 20ms in length. If have time to get it working this weekend I will post my code.
Posted 4 years ago # -
Okay, 5us to 20ms is a dynamic range of 4,000, and the timer is only 16 bits, so only about 4000/65,536= 1/16th, or 4 bits precision
If that is enough precision it should be straightforward
If it isn't, it is possible to 'chain' timers to construct a 32bit count, in which case, you might get to 72MHz clock frequency, or about 1/280th, better than 1% :-)I've done this on Arduino and the fundamental mechanism looks the same, though the registers are different.
you could work up to this in a few steps:
1. Poll the pin with digitalRead() and use micros() to measure time
2. set a timer to the right frequency (about 4MHz, a "prescaler" of 8) to get to the right count frequency (for a 16 bit count), use that count as the time base, and read it at edges. See http://leaflabs.com/docs/libmaple/api/timer.html#count-and-prescaler
3. reset the counter to zero when you poll the start edgeand read the count when you see the finish edge
4. poll the pin directly & read the timer
- use the pin map to convert from the pin number to the actual 16-bit port and pin within port,
read the input data register (RM0008 section 9.2.3 "Port input data register (GPIOx_IDR) (x=A..G)") to poll the pin
e.g.while (GPIOB_IDR&(BIT(6))) {} // wait for port B pin 6 to clear
This is only more precise in that there is a smaller window for an interrupt to hit (as digitalRead should be deterministic)
5. Set the timer to capture signal change itself see http://leaflabs.com/docs/libmaple/api/timer.html#capture-compare
initially poll the timer to see if it has 'captured' i,e, timed a change
6. Get the captured count it with interrupts!-)HTH
Posted 4 years ago # -
Here is the solution I came up with last night, it has a function to set things up and then the interrupt routine. I am not sure what all is implemented in libmaple, I noticed that there were some "TODO" in the comments of timer.h so I set some registers myself and used a few timer convenience functions, the comments should explain what is going on:
void testSetup() { pinMode(5, INPUT_PULLDOWN); TIMER4_BASE->CCMR1 = 0x00F1; //channel 1 configure as input, IC1 mapped on TI1 TIMER4_BASE->CCMR2 = 0x0000; //channels 3&4 disabled TIMER4_BASE->CCER = 0x0001; //configure channel 1 as an input TIMER4_BASE->SMCR = 0x0000; //all slave modes disabled, CC transitions on clock TIMER4_BASE->CR1 = 0x0001; //enable the counter TIMER4_BASE->CR2 = 0x0000; //connect TIM4_CH1 pin to TI1 input TIMER4_BASE->PSC = 0x00FF; //set a random large prescalar just for testing timer_attach_interrupt(TIMER4, TIMER_CC1_INTERRUPT, __measurePulse_irq); timer_resume(TIMER4); } void __measurePulse_irq() { //get the bit to see if this is an up or down pulse polarity = TIMER4_BASE->CCER & 0x0002; if(polarity == 0) //this is a rising edge capture { timer_set_count(TIMER4, 0x000); //zero the counter temp = TIMER4_BASE->CCR1; //clear the interrupt by reading CCR1 value TIMER4_BASE->CCER |= 0x0002; //change polarity to capture falling edge } else //this is a falling edge capture { value = TIMER4_BASE->CCR1; //read the captured value TIMER4_BASE->CCER &= ~0x0002; //change polarity to capture rising edge timer_set_count(TIMER4, 0x0000); //zero the counter } }
To test this code I created this test loop in main, then wired pin7 to pin5:
int main() { pinMode(7, OUTPUT); newTest(); while(1) { delay(500); SerialUSB.println(value); count++; digitalWrite(7, HIGH); delay(count); digitalWrite(7, LOW); } }
Hopefully someone may find this code useful, I will post a second version when I get it cleaned up and adjusted later today. On another note does anyone know if libmaple supports using I2C with DMA?
I also have a question with interrupts, how are interrupts "attached"? Does it use one of the External Interrupts?
I noticed there seems to be interrupt vectors defined in nvic.c/h, timer.c/h and exti.c/h, I would like to take advantage of some of these. Would someone be willing to type up a quick explanation of what I need to do to use some of these?
Posted 4 years ago # -
On another note does anyone know if libmaple supports using I2C with DMA?
I see no reason why dma.h won't support I2C with DMA. The relevant channels are DMA1 channels 6 and 7. See this thread for a nice discussion on using I2C with SPI, including example code:
http://forums.leaflabs.com/topic.php?id=1075
I also have a question with interrupts, how are interrupts "attached"? Does it use one of the External Interrupts?
Sometimes, but not always. The enum
nvic_irq_num
in nvic.h gives all of the possible interrupt vectors on the MCU:http://leaflabs.com/docs/libmaple/api/nvic.html#project0nvic_8h_1a89e3d57340a69fbdef83f12661543d83
Some of these have to do with external interrupts; these begin with NVIC_EXTI (so
NVIC_EXTI0
,NVIC_EXTI_15_10
).Interrupt "attaching" is jargon derived from the Wiring project. If you want to attach to a timer interrupt, use timer_attach_interrupt() (or the more restrictive but more user-friendly HardwareTimer::attachInterrupt()).
To attach to an external interrupt (EXTI), just use attachInterrupt().
--------------------------------------------------
I've been meaning to write more about this for the documentation, so here's a sketch of how this all works (note: this omits certain details, but hopefully gets the important points across). Definitely optional reading, but you may find it interesting.
On the Cortex M3, there's a table of pointers to the functions you want to use as interrupt handlers. This is called the vector table; its address in memory is the value of the register SCB_VTOR (accessible in libmaple as
SCB_BASE->VTOR
).When an interrupt occurs, the MCU looks up the corresponding handler function from the vector table. It then proceeds to save the current context by stacking registers and doing some other stuff, then jumps to the handler. When the interrupt is done, this context saving gets undone and you go back to user code. (The truth is a little bit more complicated because interrupts can nest, but that's the basic idea).
Part of what the libmaple startup sequence does is set up the vector table so that the addresses of our handler functions (whose names all begin with "__irq"; see libmaple's support/ld/names.inc for a list of all the names) get stored in the appropriate places in the vector table.
When you call one of the various
attachInterrupt()
orfoo_attach_interrupt()
functions, it doesn't replace the vector table entry. Instead, what happens is that a pointer to your function is stored in a secondary table of functions (this secondary table being part of libmaple), and when the interrupt occurs, the corresponding__irq_foo()
calls your function after looking it up in libmaple's table.
An example of such a secondary table is the array
exti_channels
in exti.c, libmaple v0.0.12:https://github.com/leaflabs/libmaple/blob/v0.0.12/libmaple/exti.c#L49
The
handler
fields in the elements ofexti_channels
are what hold the functions you pass toattachInterrupt()
. To see this in action, you can connect the dots between the source code for __irq_exti0() and attachInterrupt().This may seem complicated, and it does add some overhead, but the result is more user friendly.
For instance, a lot of interrupts get multiplexed into a single interrupt vector. E.g., there's a single
__irq_tim1_cc()
that has to handle all 4 channel capture/compare interrupts for timer 1.timer_attach_interrupt()
hides this complexity from you, so that you can pretend like there's a single interrupt for each of those channels.Further, serving an interrupt is usually more complicated than just providing a function that serves your application's purpose when the interrupt occurs. Often, there are peripheral registers which you have to massage each time the interrupt happens in order to keep things working. External interrupt handlers need to clear pending masks in EXTI_PR. DMA interrupt handlers need to clear DMA_ISR bits. Timer interrupts need to clear TIMx_SR bits, etc. etc. In each case, the libmaple
__irq_foo()
function handles this for you behind the scenes.We think this increase in usability is worth the performance penalty. You may disagree. If so, you can use nvic_set_vector_table() to set up your own vector table. If you do, you're on your own ;).
HTH!
edits: examples, clarifications
Posted 4 years ago # -
mbolivar, thanks for the quick reply! I should have enough stuff to keep me busy for awhile now :D
Posted 4 years ago # -
Hey all,
I was able to mash the code in this thread together to get my own frequency measurement going on my Maple (Rev 5). Here is a pastebin of the code for those who would like to use it:
Just use a wire to short pins 5 and 7 together. Pin 7 generates the square wave, pin 5 measures it.
-----
I need a little help refining this example. The code uses Timer4, channel 1 in Capture mode to measure the time between pulses. However, if I'd like the variable 'value' to get set to zero if Timer4 overflows without measuring a pulse. Without this feature, the old value continues getting displayed when you disconnect the wire because the capture interrupt never fires.
I (tried to) set up Timer4, channel 2 in Compare mode with the compare value set to the overflow value. The intention is that I want a second interrupt to fire when Timer4 overflows which sets the variable 'overflow_flag'.
I'd really appreciated it if people could review my code and let me know why I can't get the overflow interrupt to fire. In particular, check out this section in setup_timer() where I set up the overflow channel and interrupt:
//New code to set overflow timer
timer4.setMode(2, TIMER_OUTPUT_COMPARE); //Set this channel to compare
timer4.setCompare(2, 65534); //Set the compare for the channel to the overflow vale
timer4.attachInterrupt(2, handle_overflow); //Assign the interrupt handlerThanks!
Chris Troutner
thesolarpowerexpert.comPosted 4 years ago # -
I did some timer capture stuff over in the thread at http://forums.leaflabs.com/topic.php?id=1170 where I had DMA moving the data across to an array, where I could process it outside of an interrupt handler..
Posted 4 years ago # -
I know you want to run the input capture and output compare on the same timer, but I wonder if that isnt the issue. I am savvy enough with the timers to say one way or the other or if you need to be clever to do it.
I noticed in your code that you reset the timer value on the input capture, perhaps you are never overflowing?
I also noticed that you configured the input capture after the output compare via bit-banding. Make sure you arnt smashing the output compare configuration accidentally when writing to the timer registers to setup input compare. Since you are bit-banding you probably dont need to do read-modify writes, but its possible you got one of the vals wrong.
Posted 4 years ago # -
Good points! And thank you for reviewing my code!
I only reset the time value on an input capture. If the input is never captured, the timer should over-flow, which is when I want to generate the overflow/compare interrupt.
You may be right that the code is messing up the overflow/compare since I configure it before the capture configuration. This was intentional, and I was wondering the same thing.
That bit bang code is from airthimble's code. I didn't want to mess it up, so I didn't change it. I think I just need to go through each bit in the reference manual and figure out if it's messing up my overflow/compare. I was hoping someone might be able to see the problem. Oh well.
Otherwise, I think you're right that I'll just have to use another timer. I was hoping to use the same timer, and in theory it should work. Hmm...
Cheers!
Chris Troutner
thesolarpowerexpert.comPosted 4 years ago # -
chris.troutner - you might move forward faster by trying to get the pieces working in the easiest configuration first. Specifically using two timers, and then trying to merge both functions onto one timer. Then you could have two easier problems to solve, and when everything works, a working reference case to compare to when you try the merge of timer functionality.
Think of it like a program optimisation problem, and take Michael Jacksons advice:
"The First Rule of Program Optimization: Don't do it. The Second Rule of Program Optimization (for experts only!): Don't do it yet."
(http://en.wikipedia.org/wiki/Program_optimization#Quotes)Having said that, my reading of RM0008
(http://www.st.com/internet/com/TECHNICAL_RESOURCES/TECHNICAL_LITERATURE/REFERENCE_MANUAL/CD00171190.pdf)
e.g "15.3.2 Counter modes" is "In upcounting mode, the counter counts from 0 to the auto-reload value (content of the
TIMx_ARR register), then restarts from 0 and generates a counter overflow event."
So there is no need for an extra channel, there is an event generated when the timer counter reaches the value in the reload register. Set the reload value to 65535 (or less if you need it), and the overflow event will signal that there was no input.Posted 4 years ago #
Reply »
You must log in to post.