NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Microcontroller Programming » Using two timers

January 22, 2016
by lsoltmann
lsoltmann's Avatar

I am having some trouble setting up two timers to measure two pulse widths and I was hoping some one could shed some light on the problem. Some background information: I am trying the read the RPM of two motors (specifically, two of these https://www.pololu.com/product/2823) that put out a square wave proportional to the RPM. Each motor puts out two square waves to determine speed and direction but I’m only interested in speed so the plan is to have one encoder from each motor going into pins PD2 and PD3. For bench testing I have it setup so that both PD2 and PD3 read from the same encoder. Using a single timer (timer0) I was able to get the pulse width measurement from PD2 no problem. From what I read on various forums, I would need two timers to read two independent pulse widths so I set up timer2 with the same settings as timer0. Now timer2 is reading correctly from PD3 while timer0 is not. Since the same pulse width is being fed into both right now, I expect them to return the same value but timer0 seems to be returning a value approximately 4 times larger than that of timer2. It is responding to RPM changes, it’s just always about 4 times greater than the correct RPM reading provided by timer2. If I remove all references to timer2, timer0 then functions correctly again but only one RPM value is read.

Is there anything special that needs to be done when using two timers instead of just one? Can two independent pulse widths be measured using only a single timer?

//
// Pololu Motor Encoder Reader (SPI)
//
// Author: Lars Soltmann
// Code contributions from: Noter
//
// This version utilizes two timers.

#define F_CPU 14745600

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <util/delay.h>
#include <stdbool.h>
#include <stdlib.h>
#include <ctype.h>

// preprocessor macros for port/pin manulipulation
//
#define INPUT2(port,pin) DDR ## port &= ~_BV(pin)
#define OUTPUT2(port,pin) DDR ## port |= _BV(pin)
#define CLEAR2(port,pin) PORT ## port &= ~_BV(pin)
#define SET2(port,pin) PORT ## port |= _BV(pin)
#define TOGGLE2(port,pin) PORT ## port ^= _BV(pin)
#define READ2(port,pin) ((PIN ## port & _BV(pin))?1:0)
//
#define INPUT(x) INPUT2(x)
#define OUTPUT(x) OUTPUT2(x)
#define CLEAR(x) CLEAR2(x)
#define SET(x) SET2(x)
#define TOGGLE(x) TOGGLE2(x)
#define READ(x) READ2(x)
#define PULLUP_ON(x) INPUT2(x); SET2(x)
#define PULLUP_OFF(x) INPUT2(x); CLEAR2(x)

// local functions
void spi_slave_init(void);
uint8_t spi_recv_char(uint8_t send);

// define ports, pins
//
#define DD_MISO         B,4
#define DD_MOSI         B,3
#define DD_SCK          B,5
#define DD_SS           B,2

// Timer0 Variables
volatile uint32_t the_time_0;
volatile uint32_t pulse_width_0;
volatile uint32_t dataline_0;
uint32_t pulse_length_0;

// Timer2 Variables
volatile uint32_t the_time_2;
volatile uint32_t pulse_width_2;
volatile uint32_t dataline_2;
uint32_t pulse_length_2;

uint8_t byte_received;

// Interrupt on INT0
ISR(INT0_vect){
    pulse_width_0 = the_time_0;
    the_time_0 = 0;

    // 50000 is 0.271267sec which should cover entire RPM range
    // (with and without CD4017 counter (for averaging))
    // so as not to give back an infinite pulse length.  Pulse
    // length is essentially infinite when RPM is zero so this
    // forces the measurement to zero.

    if (pulse_width_0<50000) {
        dataline_0=pulse_width_0;
    }
    else {
        dataline_0 = 0;
    }
    sei();

}

// Interrupt on INT1
ISR(INT1_vect){
    pulse_width_2 = the_time_2;
    the_time_2 = 0;

    // 50000 is 0.271267sec which should cover entire RPM range
    // (with and without CD4017 counter (for averaging))
    // so as not to give back an infinite pulse length.  Pulse
    // length is essentially infinite when RPM is zero so this
    // forces the measurement to zero.

    if (pulse_width_2<50000) {
        dataline_2=pulse_width_2;
    }
    else {
        dataline_2 = 0;
    }
    sei();

}

void PulseTimer_Setup() {
    // SETUP TIMER0:
    TCCR0A |= (1<<WGM01); // CTC mode
    // clocked from CLK/8
    // which is 14745600/8, or 1843200 increments per second
    TCCR0B |= (1<<CS01);
    // so 0 through 9 equals 10 events
    OCR0A = 9;
    // enable interrupt on compare event
    // (1843200 / 10 = 1.8432e5 per second)
    // 5.425347222 usec
    TIMSK0 |= (1<<OCIE0A); // Timer A

    // SETUP TIMER2: SAME SETTINGS AT TIMER0
    TCCR2A |= (1<<WGM21);
    TCCR2B |= (1<<CS21);
    OCR2A = 9;
    TIMSK2 |= (1<<OCIE2A); // Timer A
}

ISR(TIMER0_COMPA_vect) {
    the_time_0++;
}

ISR(TIMER2_COMPA_vect) {
    the_time_2++;
}

void init_pw_pin(){

    //make PD2 PD3 input pin
    PORTD |= (1<<PD2);
    PORTD |= (1<<PD3);

    //Enable External interupt INT0 and INT1. This enables interrupts on pin PD2 and PD3
    EIMSK |= (1<<INT0);
    EIMSK |= (1<<INT1);

    //Set the interupts to fire on rising edge
    EICRA |= (1<<ISC00);
    EICRA |= (1<<ISC01);
    EICRA |= (1<<ISC10);
    EICRA |= (1<<ISC11);
}

// --------------------------------------------------------------------------------------------------------
int main() {
    // initialize SPI
    spi_slave_init();
    PulseTimer_Setup();
    init_pw_pin();
    sei();
    // LED as output
    DDRC |= (1<<PC4);

    /*
    Since the SPI waits until a transmission is recieved, this loop hangs in the spi_rec_char() function.
    To ensure that a fresh RPM is always sent, the program will wait at the first SPI read, which contains
    no valuable information and acts as an initiator for the program to prepare a fresh RPM value for sending.
    The new RPM values is then split up and sent in three bytes.  LED is only used for debug to visually
    see when data is being sent.

    On the master side, 7 bytes should be read at once, the first one is ignored and the next six contain
    the information of interest (1-3 for motor 1 and 4-6 for motor 2)
    */

    while(true){
        byte_received = spi_recv_char(0);
        pulse_length_0=dataline_0*5.425347222;
        pulse_length_2=dataline_2*5.425347222;
        PORTC |= (1<<PC4); // turn on LED -- only for debug
        byte_received = spi_recv_char(((pulse_length_0 >> 16) & 0xFF));
        byte_received = spi_recv_char(((pulse_length_0 >> 8) & 0xFF));
        byte_received = spi_recv_char(((pulse_length_0) & 0xFF));
        byte_received = spi_recv_char(((pulse_length_2 >> 16) & 0xFF));
        byte_received = spi_recv_char(((pulse_length_2 >> 8) & 0xFF));
        byte_received = spi_recv_char(((pulse_length_2) & 0xFF));
        PORTC &= ~(1<<PC4); // turn off LED -- only for debug
    }
}
// --------------------------------------------------------------------------------------------------------
//
void spi_slave_init(void){
    // setup slave SPI pins
    OUTPUT(DD_MISO);
    INPUT(DD_MOSI);
    INPUT(DD_SCK);
    INPUT(DD_SS);
    // enable SPI
    SPCR = _BV(SPE);
}

uint8_t spi_recv_char(uint8_t send){
    // character to send
    SPDR = send;
    // wait for received character
    while(!(SPSR & _BV(SPIF)));
    // return recieved character
    return(SPDR);
}
January 23, 2016
by esoderberg
esoderberg's Avatar

If you have two simultaneous events triggering two different interrupts they're going to interfere with each other - the code can't execute two interrupts at the same time.

January 23, 2016
by lsoltmann
lsoltmann's Avatar

That definitely makes sense, I just figured that it would not happen all the time and that it wouldn't create such a noticeable difference in the RPM. On the bench, the motor is rotating at 60RPM which corresponds to a pulse width of 0.002sec. Timer2 is reading this correctly while Timer0 is reading the same pulse width as 0.0005sec which corresponds to 240RPM.

January 23, 2016
by esoderberg
esoderberg's Avatar

Try using channel A from encoder for timer on PD2 and channel B for timer on PD3. Because the quadrature encoder channels are out of phase with each other, this should prevent interference.

January 24, 2016
by lsoltmann
lsoltmann's Avatar

Results are the same. There appears to be no noticeable difference between both pins reading ChA, or PD2 reading ChA and PD3 reading ChB. I used an o-scope to verify that the encoder outputs are correct. Based on this, it makes me think there is something wrong with the code but I can't figure out what.

January 26, 2016
by BobaMosfet
BobaMosfet's Avatar

Isoltmann,

Why do you have sei() in two of your 4 interrupts? Normally, that is a chip-wide setting and needs called once after you have all interrupts set up (registers, flagbits, etc), like at the start of main after some setup code (which I see, also).

BM

January 26, 2016
by lsoltmann
lsoltmann's Avatar

I'm still new to using interrupts and I remember seeing it someone else's code so I assumed it was needed. I removed the two instances inside the ISR functions but the results are still the same.

January 26, 2016
by BobaMosfet
BobaMosfet's Avatar

Isoltmann,

I didn't think removing sei would probably change the larger problems you describe. First, which motor did you get, the 30:1 ratio one? If so, at max RPM of 350, you only have 2.6ms between pulses. That may not seem like much, but if your MCU is fast enough, and you set it up properly, it's an ocean of time. Which MCU are you using, and at what clock (FPU) rate?

There are 2 methods to deal with this. Each uses a different interrupt style.

  1. Clock, whereby you set up an interrupt and it can poll your pins and determine state of A & B signals at any resolution of the clock.

  2. Pin-Change, whereby you do NOT set up a clock, but rather configure each pin to fire an interrupt when pin state changes (either leading, trailing, or perhaps both depending on chip).

Each has pros and cons, and some of what you have to choose is determined by which motor, and which chip you have, so please confirm both :)

BM

January 27, 2016
by lsoltmann
lsoltmann's Avatar

I am using the 30:1 motor with an ATmega168 running at 14.7456MHz. Just out of curiosity, what equation did you use to relate the RPM to pulse width? The equation I derived for output RPM is RPM = 60/(16 * 30 * t) where 't' is in seconds/count. Based on this equation, 2.6ms corresponds to 48RPM. I am only using one encoder from each motor and only the rising edge so according to the website, that is 16 counts/rev of the motor shaft and 480 counts/rev of the output shaft.

January 27, 2016
by BobaMosfet
BobaMosfet's Avatar

Isoltman

Formula derived using values from the website/datasheets you referenced.

1/((No_Load_RPM/60)*Pulse_Frequency) = Time_Between_Edges_per_second

Just FYI, I chose 64 pulses, because if it were me, I'd want the finest grain control of the motor possible. But you can replace 64 for 16 in the math I describe:

350RPM is max (no load) RPM for the 30:1 motor. 350 revolutions-per-minute/60 seconds-per-minute is Max RPS = 5.8333RPS. A & B pulses = 64 pulses per revolution. So, 64 * 5.33333~ = 373.333 pulses per second, max. 1/373.333 = 0.0026 or 2.6ms between pulses at MAX speed. This is not pulse-width. This is the time between each rise & change in leading/trailing edge between A & B wave-forms. So, if anything, it's exactly half of your pulse width which would therefore be 5.2ms. This is 'worst case', because anything that slows the motor down will mean more time between pulses.

Again, I'm using both rising and falling edges from each A & B encoder-- because I would want the smallest step that the encoder can alert me to, for fine-grain control. What my equation above does, is it determines the number of revolutions that can occur per second at a given speed, and multiplies that times the number of pulses per second that the encoder configuration is set to generate so I know how many pulses total get generated per second at that speed. Then I divide 1 second by that number of pulses to determine time between each pulse edge.

If you replace my 64 with your 16, you get: 10.71ms between pulses at max (no load) speed.

The proof:

Now, to take this further, I looked again at the oscilloscope image they showed, which they state has the A encoder running at 2.519kHz. They further state that using just it, which is a factor of 16, if we divide that frequency by that factor of 16, we can determine the actual RPS of the motor:

2519/16 = 157.4375 RPS

since this is per second, we multiply by 60 to get RPM

157.4375 * 60 = 9446.25 RPM.

Which tells us, the oscilloscope is monitoring a 1:1 motor without a gearbox, because it's the only motor they are showing that will operate at up to 11,000 RPM no-load. Going back to my equation and putting these values in:

1/((No_Load_RPM/60)*Pulse_Frequency) = Time_Between_Edges_per_second

1 / ((9446.25 / 60) * 16) = x 1 / (157.4375 * 16) = x 1 / 2519 = x 396uS

If you look at the scale on the oscilloscope shot, it's at the 200uS scale. The width of the oscilloscope screen is 2 seconds. Thus you can see my math exactly matches what is shown in the oscilloscope shot. It's 396uS between leading edges of the A-signal.

BM

January 27, 2016
by BobaMosfet
BobaMosfet's Avatar

In the above, it didn't wrap the 'math' steps write-- here it is again.

1 / ((9446.25 / 60) * 16) = x
1 / (157.4375 * 16) = x
1 / 2519 = x 396uS

Hope that makes more sense.

BM

P.S. Since the ATMEGA168 is running at 14.7456MHz, each clock cycle is at 1/14.7456MHz or 0.00000006781 seconds per clock cycle. So, going back to my original 2.6mS between pulse edges-- you could execute 35,000-40,000 instructions during that period, depending on instruction pipeline paralleling/overlap, on the ATMEGA168. You have oceans of time to do whatever is needed, even at the 64-pulse encoder scale.

January 28, 2016
by lsoltmann
lsoltmann's Avatar

Thank you for the very informative write up! After some manipulation I was able to rearrange my equation to match yours (including gear ratio) so at least I know that the RPM values are being calculated correctly. Based on the math, it appears there is ample time between pulses to complete the interrupt tasks. In your last paragraph when you list the sec/clock cycle you do not have a clock divider, would it be recommended in this case not to use one (currently I have a divide by 8)?

Also, in a previous post you list two ways of measuring the pulse width. It looks like I am using method 2, although it mentions not setting up a clock. In this case would you advise using one method over the other?

January 28, 2016
by BobaMosfet
BobaMosfet's Avatar

Isoltmann--

Wasn't ready to discuss the prescaler, was just determining limits, so we could see if the hardware would do what you needed. It will. :)

Now-- about prescaler:

As for a clock and the prescaler/divider, the easiest way to handle this is determine your minimum pulse (edge change occurrence), and then set a clock up that is about 5 times faster than that. All this clock does, is increment a 32-bit number.

Once that is in place, you can either poll your input, or use a pin-change interrupt on leading edge (since you're looking at 1/16th second pulses) and whenever that interrupt fires, you capture your clock value, and subtract the last clock value from the current one. In this way, you can determine pretty accurately, what your time is between pulses, and hence speed.

I recommend, since you're just watching 1 pin for the A-signal, to use a pin-change interrupt. As for the prescaler/divider-- it's a question of how much of the processor's heartbeat is needed to service your clock. Dividing by 8 means you are commiting 1/8th of the processor's clock cycles to your clock, your purpose. Is that enough? An 8th of the processor's clock is 1,843,200 cycles per second. Seems wasteful, when all you really need is a clock that beats at about 2kHz. Since the prescaler won't go that far, I would take it as far as it can go-- set the prescaler to divide by 1024. That way you are only devoting 1/1024th of the MCU's heartbeat to your clock. And still, that's way faster than you actually need, but will yield excellent accuracy.

14,745,600 / 1024 = 14,400 clock 'ticks' per second. You only actually need not ever more than 2,000 (even for 64 pulses/second), so having a clock 7 times faster, yields greater accuracy. And by that, I mean, 1/14,400th of a second is: 0.000069444 seconds between ticks (or 69.4uS).

Now, I realize this may seem like overkill, but it's because you are trying to make two different time-bases work together-- spinning motor is one time base, and the MCU frequency is another-- and they are not necessarily in sync.

What I've describe above, will do for you what you need with amazing accuracy, and without bogging the MCU down. It's incredibly simple, reliable, and should work. All you need to handle outside of this is the rolling of the 32-bit number for the clock and the offset difference that might create for that one-time occurance (each time it rolls). And the other is managing the comparison startup for your delta between 2 clock values, if you don't have a 'before' already saved from a previous edge detection.

BM

January 28, 2016
by lsoltmann
lsoltmann's Avatar

Perhaps I'm missing some fundamental concept but wouldn't the RPM accuracy be fairly low using the 69.4uS resolution? At full no load RPM, the output shaft is going ~350RPM, using the equation to back out the pulse width taking into account the gear ratio, the pulse width is 357uS (350=60/(16 * 30 * t)). Since the resolution is 69.4uS, that means 5.1 ticks have gone by so the reading will most likely jump between 4 and 5 ticks or 5 and 6 ticks, this means the RPM measurement accuracy is 90 or 60RPM respectively. The accuracy is better at lower RPM's, for 60RPM on the output shaft, the accuracy is ~2 RPM. Using the current settings, the accuracy is ~5RPM at 350 output and 0.15RPM at an output of 60.

I changed the prescaler and OCR0A to obtain the 69.4uS/tick and was able to confirm the numbers given above. I also noticed that instead of Timer2 giving the correct RPM value, Timer0 was now showing correctly and Timer2 was now reading values one order of magnitude less than Timer0. This is essentially reversed from what was happening with the previous settings. In addition to that, when using the /8 prescaler, one timer was off by a factor of 3-4 which is close to 2^3=8. Using the /1024 prescaler, the timer is off by approximately a factor of 10 which is 2^10=1024. Maybe this is coincidence, but I'm a little suspicious.

January 29, 2016
by BobaMosfet
BobaMosfet's Avatar

Isoltmann--

I'm describing a hybrid of the two methods to maximize resolution and resource usage to satisfy the engineering goal hopefully as best as possible. In the method I'm suggesting, you only use one timer. And one pin-change interrupt for the single pin (for the 1/16 pulses). You don't need 2.

The 69.4uS (or 69.4 millionths of a second) is how frequently you increment your clock. So it's very current. When a pin-change interrupt fires for a pulse on a pin, and the interrupt routine grabs a clock value, it's going to be very precise because the clock isn't incrementing in some slower scale (like 1ms). So the delta between clock update and pin-change interrupt is going to be very small. With the 69.4uS clock, every milisecond is 14.4 ticks. Every second 14,400 ticks.

Simple, fast, graceful-- and uses very little processor resource to manage quite well.

The amount of time between pulses is determined by the delta of the two clock counts you sample at each pin-change interrupt. At max no-load speed, for just 16-pulses per RPS, you're looking at a minimum of 10.71ms between pin-change interrupts. It won't be less time than that.

So, if the clock I suggest at 69.4uS, counts 14.4 ticks per ms, then 10.71*14.4 = 154.224 ticks. In other words:

clock:     ...354...355...356...357... ~ ...508...509...510...511...
pc-int:                ^                              ^
delta:                 1<------------- x ------------>2  = (509-355) = 154

Thus, delta-time: 154 / 14.4 = 10.69ms. It's only off by 20uS. That's pretty accurate considering the non-critical nature of the endeavor.

((1/Pulse_Frequency)/16)*60 = RPM
((1/0.01069)/16)*60 = RPM
(93.54536950420954/16) * 60 = RPM
5.846585594013096 * 60 = RPM
350.7951356407858 = RPM

Just take the integer value for RPM.

BM

January 29, 2016
by lsoltmann
lsoltmann's Avatar

I think we might be on two different pages here. Let me see if I got all this right. So you are saying that I can measure the pulse width of two motors using only one timer? When you mention RPM, are you referring to the output shaft RPM or the actual motor RPM? At full speed no load the output shaft is doing 350 while the motor is doing 10500. Since the encoder measure the motor speed not output shaft speed, at full speed the pulse width is 357us using the 16 counts/rev. You mentioned 10.71ms which would be if the motor speed was 350 RPM, meaning the output speed would be 12RPM. This is why I was questioning the accuracy of the 69.4us counter since a pulse width of 357us can't really be measured accurately with multiples of 69.4.

Also, when you say grab a clock value, what exactly does this mean? I'm assuming it is different from what I am doing in the above code where I am incrementing a counter based on the clock.

January 30, 2016
by BobaMosfet
BobaMosfet's Avatar

Isoltmann-

Good questions all-- sorry, I couldn't answer sooner. Let me address in the order you referenced.

  1. 2 motors-- You only need one clock, no matter how many motors you have. You need only one pin-change interrupt per each motor.
  2. RPM-- in all cases, I am referring to the motor RPM, because in this case the gearbox gears up, not down. Motor shaft turns slower than output shaft from gearbox (as stated on the webpage you referenced: "...and an integrated quadrature encoder that provides a resolution of 64 counts per revolution of the motor shaft, which corresponds to 1920 counts per revolution of the gearbox’s output shaft."). So, 64 counts of motor shaft = 1920 counts of output shaft (what you connect to whatever is being driven).
  3. 10.71ms RPM-- time between leading edges of 16 CPR, see #2.
  4. 'Grab' a clock value-- Sorry for the wording. That's just jargon for copying the existing clock value into a variable when a pin-change interrupt fires, so that you have a value to compare with the future clock value when the next pin-change interrupt fires.

BM

January 30, 2016
by JKITSON
JKITSON's Avatar

lsoltmann & BM

Very good thread. I have learned a lot just reading back & forth many times. Like the good old days of Nerdkits. Keep it up as I have a long ways to go...

Thanks Jim

January 31, 2016
by lsoltmann
lsoltmann's Avatar

It looks like it is now working correctly. I went with your suggestion of using one timer, which after some thought and a face palm, ended up being a much easier implementation than I thought. Using the code in the first post, I removed all instances of Timer2 in the setup and then changed the ISR on Timer0 to be

ISR(TIMER0_COMPA_vect) {
    the_time_0++;
    the_time_2++;
}

The pin change interrupt then resets the_time_x back to zero as needed. As far as the motor RPM goes, the gearbox gears down the motor shaft. I pulled the cover off of the encoder and put a sharpie mark on the shaft and rotated the output shaft about a quarter of a turn. This resulted in the motor shaft making about 8 revolutions. I also ran the motor at 12V with an oscilloscope connected to measure the pulse frequency of a single encoder. At full speed the time between rising edges is 346uS, not 10.71mS.

I very much appreciate all of the help and feedback, especially in regards to timer function. I now have a much better understanding of how the timer works and how to setup and modify it.

Just out of curiosity, in the case described here, what would be the difference (and any advantages or disadvantages) between these two settings

Case 1: prescaler = 8 OCR0A = 39

Case 2: prescaler = 64 OCR0A = 4

Both of these cases end up having a clock resolution of 21.7uS. I understand the use of OCRxA in the case of PWM to change pulse width but in this case, OCR0A is constant. On the surface, both work and provide the same RPM output. Is there any difference to using one case over the other (speaking strictly for use in this application)? Based on BobaMosfet's comment on processor use, it would seem that case 1 would require more processor power since the clock is being serviced more often than case 2, leaving less time for other computations.

February 01, 2016
by BobaMosfet
BobaMosfet's Avatar

Isoltman-

Glad you got it working! WOOHOOOO!!

Good job on the motor .v. shaft speed-- you did the only way to tell for sure, since the Pololu information was vague up front (at least to me). To me, at first, their table makes it look like they are publishing motor encoder speed (which you'd expect because they have a column for with and without gearbox, but only one set of numbers, and the encoder attaches to the motor, not the gearbox). With your proof, I reviewed it again and realized the 1:1 ratio published was the tell-tail. So, apparently, the max speed in any case is 11,000 RPM for the motor, in which case minimum time between pulses is: 0.0000909 (90uS). Since this is the minimum window (if you wanted your code robust for use with any of these motors), you might look at speed the clock up again so it can get at least 5-10 ticks counted in between 90uS pulses.

Regarding prescaler-- here is a good way to look at it. The more times the MCU has to interrupt itself to service your code, the more times it's possible for that interrupt to be interrupted-- or for it to interrupt any other interrupt you have going. And the less accurate interrupts can become timewise, overall. The less you interrupt the main clock, the more time the chip has to do its clean up and activity stuff-- the smoother the whole thing will run.

If you look in the datasheet and read the paragraphs around the clock/prescaler section, you'll find that they are describing an equation for calculating your OCRXX register value.

OCRxx = ((Clock/Prescaler)/ticks)-1

Clock:      Master Clock (14.7xxx MHz or whatever MCU is crystal'd at)
Prescaler:  Master clock divider (like 8) - a Reciprocal (clock/8 = clock * 1/8)
Ticks:      Desired Per Second resolution of my clock (like 1000)

Now, to answer your question between which prescaler to use-- it's simple. Use the value to that yields the fewest amount of interrupts of the main clock and yet meets your need with sufficient accuracy UNLESS, the OCRxx value ends up being too low (like 0). In which case, up to the next prescaler value (move from 64 -> 8, for example), and rerun the equation.

Or, clock the MCU with a faster external crystal. I believe the ATMEGA168 will run fine with a 20MHz crystal (I know the PU/automative versions do). Adding almost 6M more cycles to play with!!! :)

Also-- I like what you did with setting counter back to zero-- you don't need to worry about roll.

BM

February 01, 2016
by BobaMosfet
BobaMosfet's Avatar

Isoltman--

One other thing you can do, for interrupt-learning purposes-- choose another non-used pin on the 168, and when you enter your interrupt turn it on. When you leave your interrupt, turn it off.

Put a trace from your 'scope on that pin, and see how much time your MCU actually spends in your interrupt (this doesn't show stack-time, because you're in the interrupt, but it's still pretty cool).

This is useful for anyone wanting to get a better feel for how fast the MCU actually is, and how much time it's spending inside any given function one might want to test.

BM

February 01, 2016
by lsoltmann
lsoltmann's Avatar

Went with your suggestion and setup two output pins, one in the pin change interrupt and one in the timer interrupt.

ISR(INT0_vect){
    PORTC |= (1<<PC0);
    pulse_width_0 = the_time_0;
    the_time_0 = 0;
    PORTC &= ~(1<<PC0);
}

ISR(TIMER0_COMPA_vect) {
    PORTC |= (1<<PC1);
    the_time_0++;
    the_time_2++;
    PORTC &= ~(1<<PC1);
}

(I left out the 'if else' statement in the pin change interrupt from the original post because I ended up removing it). The test was setup as follows

  • ATmega168 running at 14.7456MHz
  • prescaler = 8
  • OCR0A = 9
  • 1 motor with encoder A connected to PD2 (16 count per motor rev)

giving the clock a resolution of 5.43uS. Based on the oscilloscope measurement, its takes 1.8uS to execute the pin change interrupt code. The output from the clock interrupt is pretty close the clock resolution (5.8 vs 5.43uS). Running the motor at 57RPM confirms the time between pin change interrupts. Given the measurement of 2.28mS this should theoretically be an RPM of 55. Not too far off. Increasing the RPM to max, 371 at 12.1V, gives a time between interrupts of 344uS. Theoretically this should be 363 RPM. Not surprised by the increased error. All RPM values referenced here are output shaft RPM. This has definitely been a fun learning project. Based on the results, the interrupt takes almost no time at all. The clock interrupt on the other hand, seems to take up quite a bit of CPU time, which I guess makes sense given the high resolution.

Post a Reply

Please log in to post a reply.

Did you know that you can adjust most wire strippers to make it easier to strip insulation faster? Learn more...