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 » Pulse width measurement(Please Help!)

May 12, 2012
by reddog30jen
reddog30jen's Avatar

I have been trying to write what I would think to be a fairly simple prog for Wind speed sensor.

http://www.inspeed.com/anemometers/Vortex_Wind_Sensor.asp

I have scoured nerdkit forums and the internet for hours and hours and can't find anything very helpful. This is the final part to my Weather Station project. I made separate prog from main to simplify for now. I know this code is prob not close to what I really need because I may need to average over several samples and maybe debounce, but I can't even get it to compile so I can't get anywhere. Please Help! Errors are realtimeclock1.c:77: warning: 'main' is normally a non-static function realtimeclock1.c:102: error: expected declaration or statement at end of input

I have never got errors like this before and don't know how to fix. Think it has to do with time variable but no clue how to fix. Here's the code

// realtimeclock1.c
// for NerdKits with ATmega168
// mrobbins@mit.edu

#define F_CPU 14745600

#include <stdio.h>
#include <stdlib.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"

// PIN DEFINITIONS:

void realtimeclock_setup() {
  // setup Timer0:
  // CTC (Clear Timer on Compare Match mode)
  // TOP set by OCR0A register
  TCCR0A |= (1<<WGM01);
  // clocked from CLK/1024
  // which is 14745600/1024, or 14400 increments per second
  TCCR0B |= (1<<CS02) | (1<<CS00);
  // set TOP to 143
  // because it counts 0, 1, 2, ... 142, 143, 0, 1, 2 ...
  // so 0 through 143 equals 144 events
  OCR0A = 143;
  // enable interrupt on compare event
  // (14400 / 144 = 100 per second)
  TIMSK0 |= (1<<OCIE0A);
}

// the_time will store the elapsed time
// in hundredths of a second.
// (100 = 1 second)
// 
// note that this will overflow in approximately 248 days!

volatile int32_t the_time;
volatile int32_t t1;
volatile int32_t t2;

void init_accelerometer(){

  //make PC4 input pin
  DDRC &= ~(1<<PC4);
  PORTC |= (1<<PC4);
  //Enabled pullup because speed sensor is connected to ground
  //Enable PIN Change Interrupt 1 - This enables interrupts on pins
  //PCINT14...8 see p70 of datasheet
  PCICR |= (1<<PCIE1);

  //Set the mask on Pin change interrupt 1 so that only PCINT12 (PC4) triggers
  //the interrupt. see p71 of datasheet
  PCMSK1 |= (1<<PCINT12);
}
SIGNAL(SIG_OUTPUT_COMPARE0A) {
  // when Timer0 gets to its Output Compare value,
  // one one-hundredth of a second has elapsed (0.01 seconds).
  the_time++;
}

ISR(PCINT1_vect){

if(PINC & (1<<PC4)) {t1 = the_time;}// if change in PC4 plus high ---  ie start of pulse then mark start time t1

else {t2 = the_time; //if change in PC4 plus low -- IE end of pulse,  t2 - t1 = pulse width

}

  double pulse_width = 0.0;

int main() {
  realtimeclock_setup();
  init_accelerometer();
  // init lcd
  lcd_init();
  FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
  lcd_home();

  // init serial port
  uart_init();
  FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
  stdin = stdout = &uart_stream;

  // turn on interrupt handler
  sei();

  while(1) {
    pulse_width = (t2-t1);
    lcd_home();
    fprintf_P(&lcd_stream, PSTR("%16.2f sec"), (double) pulse_width / 100.0);
  }

  return 0;
}

Also, Am I going in the right direction here or should I read timer value and reset every time pulses come or something else? Thanks

May 12, 2012
by esoderberg
esoderberg's Avatar

Reddog,

You are missing a closing bracket on line 72

also

Try "ISR(TIMER0_COMPA_vect)" in place of "SIGNAL(SIG_OUTPUT_COMPARE0A)"

It should compile with those changes; however, there's a problem with you pulse width calculation "pulse_width = (t2-t1);" It is only valid after t2 is found and prior to the next t1 being marked. If you make this calculation just after t1 is found you will be calculating the difference between the new t1 and the old t2(a negative number), which is probably not what you want. A simple solution would be to make pulse_width volatile and put that line of code inside the interrupt vector after your "else" statement. This way pulse_width will only be calculated when it has the correct values to do so.

May 12, 2012
by pcbolt
pcbolt's Avatar

Reddog -

Two things - if you take esoderberg's sound suggestion and make "pulse_width" volatile you may need to declare it up higher in the code where the other volatile variables are (I'm not sure the compiler will like it where it is now).

Second, if your pulse widths are expected to be less than 10 ms (1/10 of a second), you'll just get 0 for the answer. So your code may be working correctly and it'll seem like it isn't. You may need to change your timer settings for better resolution.

May 13, 2012
by pcbolt
pcbolt's Avatar

Correction to last post --> that should read 1/100 of a second. Ooops.

May 13, 2012
by reddog30jen
reddog30jen's Avatar

Yes that sure was the problem, a silly bracket. Always is, lol. It actually makes sense to me now too because compiler thought my static main was part of my ISR. I had been staring at my code for so long that I was blinded to look for such a simple mistake. Thank You so much esoderberg and pcbolt for your responses. They were extremely helpful. I believe this timer has accurate enough resolution for this sensor because the smallest pulse width I am getting is about .08 sec and that is with it really humming. I tried averaging over several samples but it didn't seem to make much of a difference. Since I didn't find anything else like this in the forums here's the fixed parts of code. Program works good to measure mph from 3-Cup wind sensor with one magnet.

volatile uint32_t the_time;
volatile uint32_t t1;
volatile uint32_t t2;
volatile double new_pulse_width;
volatile double old_pulse_width;
volatile double mph = 0.0;

ISR(PCINT1_vect){
old_pulse_width = new_pulse_width;

if(PINC & (1<<PC4)) {t1 = the_time;}// if change in PC4 plus high
//  ie start of pulse then mark start time t1

else {t2 = the_time;} //if change in PC4 plus low -- IE end of pulse,  
//t2 - t1 = pulse width
new_pulse_width = (t2-t1);

//Pulse width should equal old value if negative
//Only measure from t1-t2 not vice versa
if(t2 < t1){new_pulse_width = old_pulse_width;} 
mph = 2.5 * (1 / (new_pulse_width / 100.0));
 //Formula from company states mph = 2.5 * Hz

}

while(1) {

    // Was starting with -1 for some reason?
    if(the_time <= 10){mph = 0.0;}
    //If nothing happens for 275 us reset to zero
    if((the_time - t1 >= 275)){
    mph = 0.0; new_pulse_width =0.0;}

    lcd_home();
    //Displays Pulse Width in seconds and Mph
    fprintf_P(&lcd_stream, PSTR("%3.2f sec"), new_pulse_width / 100.0);
    lcd_line_four();
    fprintf_P(&lcd_stream, PSTR("%3.1f mph"), mph);
    delay_ms(250);
  }

Let me know if this code doesn't look right. Just one quick question if someone doesn't mind answering. Anyone know why when I stream variable like this one to the lcd and the size or how many significant digits changes the lcd gives character lead ons or copies such as mphh or for pressure kpaa. It seems to happen randomly. Is there a fix??

May 14, 2012
by Rick_S
Rick_S's Avatar

Print a blank space or two after the "mph" or "kpa" when you drop from double digit speeds to single, the string shifts to the left one position leaving the last character from the previous on the display. Printing the blank space with your string will "erase" a trailing leftover.

Rick

May 14, 2012
by reddog30jen
reddog30jen's Avatar

One thing to add to thread. There was a small bug in the program where after anemometer was not spinning for a bit and then started spinning mph would report "inf" for a second before operating as normal. I looked it up to be an infinite error, overflow or "bad math" lol. Maybe cause of big pulse number?? Not sure exactly why but taking mph formula out of ISR and moving into while(1) section fixed bug. Changed variable to regular double too.

Hey Rick S.,Thanks, your trick worked for the lcd. Can you or anyone else explain why that happens. Is it a bug in the lcd file or something you need to work out with individual progs. Thanks again everyone

May 14, 2012
by pcbolt
pcbolt's Avatar

Reddog -

It looks like you may be dividing by zero if the variable "new_pulse_width" becomes 0. This could happen if the interrupt triggers twice within the 1/100th of a second clock "tick", or at the beginning of the program when the variable is first used. You could just add a simple "if" clause to test for zero before dividing to get mph.

As for the LCD, if you write "1234.56" at the first line and first position, then write "123.45" in the same spot, the "6" from the first write will never get "written over" and will persist until it is either written over or cleared. You could clear the display before writting to it each time but this sometimes produces a flicker. Rick's idea works because it overwrites the previous data with blank spaces. It's not really a bug, the LCD is just doing what its told.

Post a Reply

Please log in to post a reply.

Did you know that a motor's no-load current at a given voltage is much less than it's resistance would suggest? Learn more...