Tachometer
Up to Project Discussion
Hello,
I'm doing a simple engine monitoring, and need to implement a tachometer. I have a falling 5V signal that stays low for no less then .0003 secs at my upper rpm limit. Are there any examples of a tachometer implementation that will meet these timing requirements? Or examples interrupt driven counters that might be close?
I think I want to fire an interrupt incrementing a counter whenever this input line goes low in one task then reset that counter every half second (or some time) relaying the count data to a Count Per time value. I'm not sure what line I can use as an external interrupt line, or if this is even a possible implementation. Any ideas or help would be greatly appreciated.
Thanks in advance,
Ryan
you should be able to punch this out using fasttimer. it works on the order of microseconds, which should be good enough for your app.
I've posted some firmwares in my profile that you could use as a template. The quadrature encoder firmware uses a fasttimer to detect and compare two input lines. It also implements a hard-coded autosend
http://www.makingthings.com/Members/tunell/firmwares-of-all-colors
A few questions about the ISRs
Are the PA30 and PA14 through hole solder pads on the app board in between the controller connectors tied to IRQ0 and IRQ1?
I read a posting "Bad interrupt behavior using : XSemaphoreGiveFromISR", where PA30 or IRQ0 was used. Are IRQ0 and IRQ1 line hooked to anything else, am I going to break some other possible functionality by hooking up to one of these two external IRQ's? Is IRQ0 used for the Jtag debugging port?
What do you have to do to make the interrupt fire on a falling edge, is it just disabling the pullup resistor. Is that what is done in the following line of code, or does this line do more then that. the comment said /* Store the Source Mode Register */ but it looks like it sets the source type of IRQ0, am I reading it wrong?
AT91C_BASE_AIC-AIC_SMR[ AT91C_ID_IRQ0 ] = AT91C_AIC_SRCTYPE_EXT_NEGATIVE_EDGE | 4 ;
Are there any ISRs that are in the current code base that use one of the two external interrupts?
Thanks in advance for the help.
Is there a way to calculate the stack size needed for a given task that uses a fasttimer?
Previously Ryan Chard wrote:
I think so.
A few questions about the ISRs
Are the PA30 and PA14 through hole solder pads on the app board in between the controller connectors tied to IRQ0 and IRQ1?
I read a posting "Bad interrupt behavior using : XSemaphoreGiveFromISR", where PA30 or IRQ0 was used.
They resolved that problem in the thread. It was a good catch that I probably wouldn't have made. But, then, any such routines I write, myself, are already naked.
Are IRQ0 and IRQ1 line hooked to anything else,
Not that I can tell from here: http://www.makingthings.com/resources/schematics/controller-port-table
am I going to break some other possible functionality by hooking up to one of these two external IRQ's?
I don't think so.
Is IRQ0 used for the Jtag debugging port?
I 'm pretty sure it isn't. All that's on that port is the dedicated TAP pins and NRST.
AIC_SMR encodes both the type and the priority of the interrupt. Hence, the 4. Yes, this is how you set that it is negative edge triggered. Disabling the pullup is purely an electronic thing, based more or less on what you're feeding to the interrupt pin. If it's a switch closure or an open-collector or open-drain transistor pulling down to ground, you still want the pullup to keep the pin from floating.What do you have to do to make the interrupt fire on a falling edge, is it just disabling the pullup resistor. Is that what is done in the following line of code, or does this line do more then that. the comment said /* Store the Source Mode Register */ but it looks like it sets the source type of IRQ0, am I reading it wrong?
AT91C_BASE_AIC-AIC_SMR[ AT91C_ID_IRQ0 ] = AT91C_AIC_SRCTYPE_EXT_NEGATIVE_EDGE | 4 ;
You also might disable output on that pin (just in case it's been set previously), and, though I don't know if it's strictly necessary for inputs (some chips have the peripheral inputs always connected to the pins, and I don't remember whether this is one of them or not), set the pin for peripheral, rather than PIO use. The pin claiming logic might already take care of that for you (or disrupt it), which would be easy enough to find out by reviewing the source code.
Edge triggered interrupts set the pending bits for those interrupts. You need to clear those (by writing the mask for that bit to AIC_ICR) after servicing or you'll spend all of your time reentering the interrupt service routine, because the hardware doesn't know you're done servicing the interrupt for that transition until you do that. Actually, you might want to clear that bit as soon as possible just to avoid missing the next edge, should it occur while you're servicing interrupts. For comparison, level-sensitive interrupts get their pending bits cleared when the condition goes away, which usually happens as a result of something you do in the service routine.
Also (and this is really important), you have to make sure your ISR (or its wrapper if you go that route) is compiled in ARM mode as opposed to thumb. I cannot find the AIC setup in the code right now to verify, but usually the AIC is used such that it autovectors to the service routine, which has to be in ARM mode. In that case, compiling it in thumb mode would produce an undefined instruction exception. That is true of any interrupt, not just EXT0 and EXT1. See analogin_isr.c for an example. If you use the wrapper approach that they use there, your actual meat and potatoes routine (called AnalogInIsr_Handler in that example) could be in either ARM or thumb mode, since it's called through the interworking code by the wrapper if that's the case. The wrapper must be in ARM mode, though, for the aforementioned reason.
Programming interrupt service routines requires extra care. You have to save registers yourself (that's what the wrapper (or, more specifically, the portSAVE_CONTEXT and portRESTORE_CONTEXT macros in it) does in analogin_isr.c), because the hardware doesn't do it. This is why they are usually given the "naked" attribute. There is also an "IRQ" attribute, but I haven't used it, and, judging solely from analogin_isr.c, neither does the MC firmware. Supposedly, if it works, that would take care of the register saving and returning for you, but wouldn't get you the possible advantage of coding your ISR in thumb mode, which the wrapper approach gives you (at the expense of some overhead which is surely made up for by not running in the slower ARM mode which suffers from FLASH wait states).
If using analogin_isr.c for inspiration, make sure you notice the naked attribute on the prototype for AnalogInIsr_Wrapper. It is important. It's easy to read right over it if you're in a hurry.
Previously Ryan Chard wrote:
Actually, this sort of thing (assuming you're only after the speed reading, rather than trying to time things (such as ignition pulses) relative to TDC) is a perfect application for timer capture hardware. It would still require an interrupt service routine, which would read the captured value (the number of clocks since the previous relevant edge) and store it where your app can access it. You might need to choose the clock source carefully to have decent resolution at both low and high ends. If the engine is stopped, it could take an infinite amount of time to get the next reading, and your first one once the engine turns over again will be bogus, as the timer will have overflowed. You could set the second compare register to interrupt you when an overflow occurs so you can handle those situations.I have a falling 5V signal that stays low for no less then .0003 secs at my upper rpm limit. Are there any examples of a tachometer implementation that will meet these timing requirements?
Unfortunately, the pins for that are used by things on the App Board. You'd have to sacrifice an analog/digital in, digital out, or servo 0. If the op-amps are fast enough, that could even be an advantage: using an analog/digital in at the screw terminal, rather than having to solder.
And, of course, you'd have to study the Timer Counter section of the datasheet. It's section 32 in my copy.
I'm not aware of any of the 3 TC channels being used for anything on the MC.
Lou,
Thank you for the advice. I got the tach working and now I'm having fun getting the servo timing married with the tach timing. Things go much better when one has a scope.
In Servo_IRQCallback() the line
s->pIoBase->PIO_CODR = s->pin;
gets called four times before the pulse turns on.
Does anyone know why you have to hit the Clear Output Data Register four times before it turns the pulse on. Is there a way to just flip it on with one write? like an Io_SetTrue();
Thanks again for your input.
Does Anyone know what to read to get system ticks or a microsecond incrementing timer. I sure there is probably a processor tick counter, I'm just not sure how to get to it. I would like one that is aready running and doesn't involve another fasttimer.
Thank you
Ryan
Previously Ryan Chard wrote:
I suggested the alternate approach just in case that doesn't work out. I know where you're at with this. I've been there. I know that in certain situations, I would have loved, in hindsight, to have had an alternate plan, were my preferred plan not to work out after all. You don't have to consider it seriously. You don't have to consider it at all. But it's there if you want it later.
Lou,
Thank you for the advice. I got the tach working and now I'm having fun getting the servo timing married with the tach timing. Things go much better when one has a scope.
I hear you on the scope.
Can you give me a URL and line number? I cannot find what you're talking about at http://www.makingthings.com/ref/firmware/html/servo_8c-source.htmlIn Servo_IRQCallback() the line
s->pIoBase->PIO_CODR = s->pin;
gets called four times before the pulse turns on.
in line 00427 or otherwise.
If your version is older than the one I'm looking at above, that might be an editing error that's been removed. If it's newer, maybe it's a simple means to guarantee a minimum pulse length. Though I don't actually see how that would help anything in this state machine, since the overhead of returning from and reentering the ISR would be much longer than a few extra writes to PIO_CODR.
You can just set or clear it with one write to PIO_SODR or PIO_CODR.Does anyone know why you have to hit the Clear Output Data Register four times before it turns the pulse on. Is there a way to just flip it on with one write? like an Io_SetTrue();
The PIT is used to generate the system tick. The actual tick variable is incremented in vTaskIncrementTick in tasks.c of FreeRTOS. There, you can see that the variable is xTickCount and also that it is apparently possible to define a hook function to be called for each tick. The tick counter will only increment once per millisecond or so, though. PIT rollover is, after all, where the regular Timers and task switching come from.
You could read the CPIV (current count) in the PIT to get finer resolution, but you'd have to check it at least as often as the system tick occurs (or take xTickCount into account, which brings up a thorny synchronization issue), as that's how often it rolls over after 3 million counts of roughly 333ns each. If you go that route, make sure to read PIT_PIIR and not PIT_PIVR, as the latter would mess up system timing by resetting the counter.
Previously Lou Deluxe wrote:
it rolls over after 3 million counts of roughly 333ns each.3 thousand counts. I thought that seemed high.
Lou,
You found the right line number in the servo.c 00427. I hacked a version of servo.c to determine the timing. I thought the if statement above this line was what was making the determination as to when the line was to be turned on. So I wrote variables there to try and capture the state change. I found that the call on 00427 happened on every other pass there the isr, just like the switch suggests.
I did this by adding a Io_SetTrue(SERVOHEARTBEATIO); at the front of the Servo_IRQ_Callback and a Io_SetFalse(SERVOHEARTBEATIO); just before the two exit points.
SERVOHEARTBEATIO = IO_PA26;//DigiOut6
Io_Start(SERVOHEARTBEATIO,true);
in Servo_Init(); to turn on the IO line.
Hook the scope up and you can watch your timing.
I also set a few globals that then report the period values sent to the fasttimer, and
the timing goes like this. The middle times that are repeated and don't seem to change are.
1512,2418,1512,2418,1512,2418 then the down time and up time are split to determine position.
Note 1512+2418=3930 if you add the isr time to that you get really close to 4ms. 4ms time 5 is our period.
The first thing I noticed about this implementation is that is consumes more time then nessasary, and causes timing interference with any other fasttimer.
Are you sure you can just set it with one write to PIO_CODR. Because that isn't what I found. I haven't run down what AT91C_PIO_PB24 is exactly, and my have something to do with the four write thing.
on a side note: where would you put a global struct that needed to be accessed in both a task and in Servo.c. Filling it i servo.c and reading from it in a task. Most of my experience has been post design hacking, or designed in a higher level language.
Previously Ryan Chard wrote:
So the four times thing spans ISR invocations?
Lou,
You found the right line number in the servo.c 00427. I hacked a version of servo.c to determine the timing. I thought the if statement above this line was what was making the determination as to when the line was to be turned on. So I wrote variables there to try and capture the state change. I found that the call on 00427 happened on every other pass there the isr, just like the switch suggests.
the timing goes like this. The middle times that are repeated and don't seem to change are.
1512,2418,1512,2418,1512,2418 then the down time and up time are split to determine position.
I think what you're seeing here is each of the 4 servo channels being driven in turn. If we go back to where you're seeing CODR being written 4 times, I suspect you'll find it being written with the bitmasks for the 4 servo pins. Actually, looking at the top of case 0, where s is set in terms of Servo->index, which has just been incremented, I'm pretty sure of it.
It appears that even without a servo set active, its PIO line is still twiddled in the output register. It just won't be reflected at the pin if the driver isn't enabled.If what you're concerned about is the time spent servicing the remaining 3 servos, you could redefine SERVO_COUNT to be 1 (in line 00029 of servo_internal.h). You might also want to increase Servo->gap (which is set in line 00373), lest you end up with that one servo channel just getting run 4 times as fast. I think the maximum frame length (to keep the servo from going limp due to lack of pulses) is 20ms. The math on line 00373 seems to have developed some bit rot, as the comments don't agree with the constants.
My rough calculation, based on how I think it works and their stated goal of a 15.6ms frame, would make it 15624 - SERVO_CYCLE == 15624 - 2048 == 13576. You could go further and make it a 20ms frame with Servo->gap = 17952. If those timings are off, you, having a scope, have the capability of measuring the actual frame length and adjusting Servo->gap accordingly.
Merely reducing the number of servos to service isn't going to make all of your problems go away, though. It'll just make them show up less often.
Note 1512+2418=3930 if you add the isr time to that you get really close to 4ms.
The reason that the total (3930us) is close to 4ms is that Servo->gap was chosen to yield 4 (separate) servo pulses per 15.6ms frame.
4ms time 5 is our period.
Our period? What happens every 19.65ms? While it is certainly possible that I have missed a divide by 5 somewhere, I'm just not seeing it in the code. Maybe you're seeing it on the scope, in which case, I cannot explain it.
If you come up with a more efficient implementation which doesn't lose any functionality, I imagine the maintainer(s) will be interested in seeing it.The first thing I noticed about this implementation is that is consumes more time then nessasary, and causes timing interference with any other fasttimer.
As for interference between things using fasttimer, it is, of course possible. Heck, you've even observed it. Maybe there is a flaw in it. Or maybe it is just as you suggest and the servo stuff uses too much time for even a flawless implementation of fasttimer to cope with. After all, while servicing a FIQ, as fasttimer (thus, the servo interrupt handler) does, nothing can interrupt, including another fasttimer.
I don't know how I'd go about making the servo code run faster, but you seem to have an idea (hence "consumes more time then nessasary"). I suppose I could come up with something faster, but I'm not running servos, so the impetus to put that much work into it isn't there for me. If I were going to redo it, I'd probably be using one of the (I think) available TC channels. Actually, if it were me, I'd probably do servos in an external AVR or PWM chip hung off of SPI (instead of bit-banging them in the MC firmware), but that isn't the way they designed the hardware.
Are you sure you can just set it with one write to PIO_CODR. Because that isn't what I found. I haven't run down what AT91C_PIO_PB24 is exactly, and my have something to do with the four write thing.Pretty sure, yeah.
AT91C_PIO_PB24 is a bitmask which indicates the line that is cleared when you write that bit to PIO_CODR. That line is connected to a FET, which is connected to the pulse line on the servo connector.
When you write AT91C_PIO_PB24 to AT91C_BASE_PIOB->PIO_CODR, it clears the line for servo0. When you write AT91C_PIO_PA23, AT91C_PIO_PA21, or AT91C_PIO_PA22 to AT91C_BASE_PIOA->PIO_CODR, it clears the line for servo1, 2, or 3.
What you might be missing is that s->pIoBase and s->pin have different values for handling the 4 servos. So, yeah, the statement runs 4 times to handle all 4 servos, but each time actually refers to a different servo channel's pin.
on a side note: where would you put a global struct that needed to be accessed in both a task and in Servo.c. Filling it i servo.c and reading from it in a task. Most of my experience has been post design hacking, or designed in a higher level language.Speaking only for myself, I'd probably put it in another header file which is included by both servo.c and your task definition file(s). If the definition of the struct includes an instantiation of the struct, It's up to you where you put that. I'd lean slightly toward putting it in servo.c, but only slightly. If you do that, it might be wise to have that #ifdefed based on something in your new header file, just like the new code you're writing to fill the struct.
Lou,
I think your correct about each time it being run it is for each servo. The loop is run through five time. I'm fairly sure of this due to the scope and debug data I have. The servo pulse is 1-2ms every 20ms. Each cycle, an up and a down is 4ms there are five of them, 4 times five is 20. I'm not real sure of the incrementing method, but that is what I see on the scope. My current path is to add my tach read handing to the servo isr and double up the frequency of the reads, using an every other switch. Once I get the function working like I want I will adjust the Servo->gap to fix the added function time. This should be fairly straight forward.
I like the idea of using a pwm chip attached to the spi bus. Got any recommendations of a relatively inexpensive demo board/kits available? I need to get this method working, but may need make it faster later. Like if they go down under 1ms time, and have to be run from an interrupt.
Thanks again for your help
Previously Ryan Chard wrote:
Lou,
I think your correct about each time it being run it is for each servo. The loop is run through five time. I'm fairly sure of this due to the scope and debug data I have. The servo pulse is 1-2ms every 20ms. Each cycle, an up and a down is 4ms there are five of them, 4 times five is 20. I'm not real sure of the incrementing method, but that is what I see on the scope.
Not having a scope set up and not running a servo, I can only guess, but my guess is that you're expecting to see a 20ms frame, so that's what you're seeing; when it is actually closer to 16ms. There is not a longer gap at the end of the frame to distinguish it visually. If you're triggering your scope on the servo0 line, the fifth pulse you see is probably the first of the following frame.
The code that makes me think that to be the case is this:
if ( ++Servo->index >= SERVO_COUNT || Servo->index < 0 )
Servo->index = 0;
Well, that plus the part where they're setting Servo->gap.
Now, if that said ++Servo->index > SERVO_COUNT, I could see it going through 5 times, even though that wouldn't be correct operation as far as I can tell. The fifth iteration would be indexing a fifth servo, for which the data structure isn't present. The pulse would be random (but probably fixed) at best and you'd be writing random (but probably fixed) data to a random (you get the idea) memory location at worst.
I'm not sure how I'd go about doing that, but you seem to be, and that's what's important.My current path is to add my tach read handing to the servo isr and double up the frequency of the reads, using an every other switch. Once I get the function working like I want I will adjust the Servo->gap to fix the added function time. This should be fairly straight forward.
Does alternating help you? In the interest of my own enlightenment, what does that get you over, say, not doubling the interrupt overhead and just doing your tach stuff before the servo stuff every time? The best I can come up with is that it would allow you to specify your own period (FastTimer_SetTime) to use for the tach without worrying about the variability of the servo pulse lengths.
The only thing that I know of that would qualify as a relatively inexpensive demo board/kit would probably be something in the *duino family. Anything else is probably going to be a custom build or an overpriced vendor eval board. That seems to be the way the industry goes.I like the idea of using a pwm chip attached to the spi bus. Got any recommendations of a relatively inexpensive demo board/kits available? I need to get this method working, but may need make it faster later. Like if they go down under 1ms time, and have to be run from an interrupt.
Fortunately, AVRs (or PIC, if that's your favorite) still come in DIP packages and often contain SPI hardware, timers, PWMs, etc., thus making it easier to go the stripboard route if need be.
Also available in DIP is the TLC5940 (from TI), which, while designed as an LED driver, ought to be able to drive servos with the right clock source and some logic to kick off each PWM cycle (for all 16 channels at once). There are other PWM LED driver chips (from Allegro, for instance, but those get their clock internally (can't change it to be appropriate for servos) or from the SPI clock (maybe workable, but too complex for this kind of duty)). There are probably others that I don't even know about. I looked for multichannel PWM chips other than those for LED driving, and have so far found nothing other than the following...
This chip http://www.rabbit.com/products/rio/ looks great for this sort of thing and more (steppers, etc.) (heck, it could even do tachometer duty), but it only comes in SMT. It's also fairly expensive for what it is at $5 for just the chip in singles. At $299, the "prototyping board" for that chip doesn't qualify in my book as inexpensive. Yeah, that price includes a Rabbit processor board, but that doesn't contribute value for your needs, so, basically, the prototyping board costs $299.
And then, there are various AVR/PIC-based servo controller boards out there, already designed and made. They seem to tend to be connected via async serial, which may or may not be just fine for a given application. An example of that might be this: http://www.pololu.com/catalog/product/207
Thanks again for your help
Sure. I do what I can.
Previously Lou Deluxe wrote:
Also available in DIP is the TLC5940 (from TI), which, while designed as an LED driver, ought to be able to drive servos with the right clock source and some logic to kick off each PWM cycle (for all 16 channels at once).I am apparently not the only person to have thought of this. It appears that they're already working on it in the Arduino camp. http://www.arduino.cc/playground/Learning/TLC5940

