Wednesday, March 23, 2011

Measuring boat speed (Part 2)

In the previous post, I described in a general way how the boat speed is measured. I present here the part of the microcontroller code that is used for this task.

volatile uint16_t icp3_start_time;
volatile uint16_t icp3_period;
volatile uint8_t zero_flag;
double boat_speed;
uint16_t period3;
uint8_t sreg;

/*
 * This interrupt routine is called each time
 * a new pulse rising edge is detected
 */
ISR(TIMER3_CAPT_vect)
{
   uint16_t icr3;

   uint16_t test_period;

   /*
    * Make a copy of the timer value saved in the ICR3 register
    * when the rising edge of the pulse occured
    */
    icr3 = ICR3;

   if(zero_flag)  // this is the first pulse of a new series
   {

      /*
       * Save the start timestamp in a global variable
       * for further use
       */
       icp3_start_time = icr3;

      /*
       * Push the timeout window forward by moving the OCR3A timeout
       * value just behind the capture timestamp so that the timer
       * will have to go through nearly a whole cyle before reaching OCR3A
       */
       OCR3A = icr3 - 100;

      /*
       * Next time, go to the 'else' section to calculate the time
       * interval unless a timeout has occured meanwhile, setting back
       * the zero_flag to 1
       */
       zero_flag = 0;
    }
    else
    {
       /*
        * Calculate the number of timer ticks since the last pulse
        */
        test_period = icr3 - icp3_start_time; // length of previous period

       /*
        * This is a debouncing test that is probably not required, but
        * safe to keep. If icp_period is less than 200, this means that
        * the nominal boat speed will be calculated at more than 16.6 knots,
        * an indication of a false or noisy pulse that should be discarded
        */
        if(test_period > 200)   // if we have a good pulse
        {
           icp3_period = test_period;

           icp3_start_time = icr3;    // icr3 becomes the new start time
           OCR3A = icr3 - 100;     // move the timeout window
        }
    }
}
¸
/*
 * This interrupt routine is called when the timeout
 * period is reached, i.e. TCNT3 = OCR3A (boat speed less than 0.05 knot)
 */
ISR(TIMER3_COMPA_vect)
{
   // tell everyone that we have a zero speed
   zero_flag = 1;
}

void init_timer(void)
{
   // Set timer/counter 3 with a prescale of 1024
   // and with input capture on rising edge
   TCCR3B = _BV(ICES3) | _BV(CS32)| _BV(CS30);
   TCNT3 = 0;    // initialize timer/counter 3
   ETIMSK |= _BV(TICIE3);    // enable input capture interrupt

   OCR3A = 0;  // inialize timeout value
   ETIMSK |= _BV(OCIE3A); // enable output compare interrupt
}

int main(void)
{
   // ...

   zero_flag = 1;

   init_timer();

   /* enable interrupts */
   sei();

   boat_speed = 0.0;

   for(;;)
   {
     // begin a new cycle

     // ...

     // calculate the measured boat speed
     if(zero_flag)
        boat_speed = 0.0;
     else
     {
        sreg = SREG;

        cli();  // disable interrupts while we transfer a volatile 16-bit value
        period3 = icp3_period;
        SREG = sreg;  // re-enable interrupts

        boat_speed = 1.0 / (period3 * 0.000064 * 4.7);

        // get the calibration correction factor
        // ...

        // apply the calibration correction factor
        boat_speed *= calbat;
     }

     // put the result in the data structure
     dump_info.mbs1 = boat_speed;

     // ...

     // Wait for timer signal to begin a new cycle

     // ...
   }

5 comments:

  1. I used same accelerometer in my project openmag. Only I got much higher accuracy because I calibrated out the 1% non-linearities as well as misalignments between axes. This can all be done with least squares for optimal results. Code running on atmega2561.

    http://repo.or.cz/w/openmag.git/blob/4d153b85b52a69eaa17d8b22756bddb93a54ca7c:/src/calibration/fit.c

    Also I explain how this works in detail:
    http://repo.or.cz/w/openmag.git/blob_plain/4d153b85b52a69eaa17d8b22756bddb93a54ca7c:/doc/Calibration.texi

    convert to pdf with texi2pdf

    ReplyDelete
  2. Hey Merlin,

    It seems like you're throwing away a lot of data when the boat is moving fast, by only using the most recent pulse every .1s. When the boat's moving slow, samples that are reported more than one interval will be weighted more heavily by the downstream running average.

    I copied your approach, and added a running average of all the raw pulses on the microcrontroller. I’m seeing pretty smooth speed at 5 Hz, without having to do any other filtering of the data. Great response, too.

    You can see what I did and my results here: http://homegrownmarine.com/blog/better-speedo

    ReplyDelete
    Replies
    1. To be precise, I also calculate a running average over the last 4 seconds before displaying the result. The running average is refreshed 10 times per second i.e. it is the average of the last 40 readings. This is illustrated in the following post: http://sailboatinstruments.blogspot.ca/2011/01/damping.html

      Delete
  3. I'm planning to replace the innards of an old Kenyon speedo unit. The sender looks like a standard Airmar unit and seems to be working fine.

    Does the voltage/current from the sender unit pose any threat to the Arduino? Did you utilize an optoisolator? If yes, which one?

    Thanks
    Dave

    ReplyDelete
    Replies
    1. For my 2 Airmar units, it works fine by feeding directly the speed signal to an input pin with the internal pull-up enabled. I guess I may be saved by the internal protection diodes of the pin (see Figure 14-1 of the of the AVR328p datasheet). However, I would strongly recommend to add some external protection. For example, the Raymarine ST40 speed instrument uses an external IMN10 diode before connecting to their 5V microcontroller (along with a capacitor and an inductor). See page 2-4 of the following document:
      http://www.olajedatos.com/documentos/ST40%20Instruments%20Service%20Manual.pdf

      Delete