понедельник, 13 мая 2013 г.

Arduino хронограф (часть 2)

В этой статье будет говориться о программе для ардуино позволяющей получить большую точность. Описание аппаратной части смотрите в первой части.


Для начала неплохо бы выяснить как работает функция micros(). На самом деле считает время таймер/счетчик 0 микроконтроллера. Он запущен с предделителем 64. При тактовой частоте микроконтроллера в 16 МГц значение счетного регистра таймера/счетчика 0 изменится на 1 за:

64.0 * (1.0 / 16000000.0) * 1000000.0 = 4.0 мкс

Функция micros() всего лишь читает счетный регистр таймера/счетчика 0 и прибавив полученное число к системному времени хранящемуся в глобальной переменной возвращает результат. Это означает, что большей точности от этой функции не добиться.

Поэтому я предлагаю непосредственную работу с таймером/счетчиком микроконтроллера. Без предделителя и при тактовой частоте микроконтроллера в 16 МГц значение счетного регистра таймера/счетчика изменится на 1 за:

(1.0 / 16000000.0) * 1000000.0 = 0.0625 мкс

Разница весьма существенна. Но сделать этот код подходящим для всех платок непросто. Да и не смогу я протестировать эти программы. Нет у меня всех Arduino. И мне немного лень так серьезно заморачиваться. Я напишу программу для самых распространенных в мире Arduino микроконтроллеров - ATmega168 и ATmega328.

Только прежде чем написать скетч упомяну еще кое какие моменты влияющие на точность. Ненужные в данной программе прерывания способны вызвать задержки в вызове функций обрабатывающих сигналы с фототранзисторов сенсора. Прежде всего это прерывания таймеров. Поэтому их стоит отключить. В связи с этим не будут работать некоторые стандартные функции. Но так как их использование в этой программе не предполагается, то ничего страшного в этом нет.

Новый скетч для ATmega168 и ATmega328:

////////////////////////
//
// Arduino Uno
//
////////////////////////
//
// Sketch: Chronograph
//

#include <util/delay.h>

double data;

volatile unsigned char s1 = 0;
volatile unsigned char s2 = 0;

// Timer/Counter 0 initialization
void Timer0_Init( void )
{
    TCNT0 = 0;

    // Bits: COM0A1 COM0A0 COM0B1 COM0B0 - - WGM01 WGM00
    TCCR0A = 0;

    // Bits: FOC0A FOC0B - - WGM02 CS02 CS01 CS00
    TCCR0B = 0;

    // Bits: - - - - - OCIE0B OCIE0A TOIE0
    TIMSK0 = 0;

    // Bits: - - - - - OCF0B OCF0A TOV0
    TIFR0 = 0;
}

// Timer/Counter 1 initialization
void Timer1_Init( void )
{
    TCNT1 = 0;

    // Bits: COM1A1 COM1A0 COM1B1 COM1B0 - - WGM11 WGM10
    TCCR1A = 0;

    // Bits: ICNC1 ICES1 - WGM13 WGM12 CS12 CS11 CS10
    TCCR1B = 0;

    // Bits: FOC1A FOC1B - - - - - -
    TCCR1C = 0;

    // Bits: - - ICIE1 - - OCIE1B OCIE1A TOIE1
    TIMSK1 = 0;

    // Bits: – – ICF1 – - OCF1B OCF1A TOV1
    TIFR1 = 0;
}

void setup()
{
    // Global disable interrupts
    cli();

    // Timer/Counter 0 initialization
    Timer0_Init();

    // Timer/Counter 1 initialization
    Timer1_Init();

    // Global enable interrupts
    sei();

    Serial.begin(9600);

    attachInterrupt(0, sensor_1, FALLING);
    attachInterrupt(1, sensor_2, FALLING);
}

void loop()
{
    while ( s1 == 0 && s2 == 0 ) ;
    _delay_ms(800); // wait 800 ms

    if ( s1 != 0 && s2 != 0 )
    {
        data = 0.06 / (TCNT1 * (1.0 / 16000000.0)); // v = s / t
    }
    else
    {
        data = 0;
    }

    Serial.println(data);

    TCCR1B = 0;
    TCNT1 = 0;

    s1 = 0;
    s2 = 0;
}

void sensor_1()
{
    if ( s1 == 0 )
    {
        TCCR1B = (1<<CS10); // Timer/Counter 1 running (no prescaling)
        s1 = 1;
    }
}

void sensor_2()
{
    if ( s2 == 0 )
    {
        TCCR1B = 0; // Timer/Counter 1 stopped (no clock source)
        s2 = 1;
    }
}

//
// End
//
////////////////////////

Для подсчета времени я решил использовать шестнадцатиразрядный таймер/счетчик 1. При работе микроконтроллера на частоте в 16 MHz таймер/счетчик 1 работая без предделителя переполнится за:

(1.0 / 16000000.0) * 65536.0 = 0.004096 с

При расстоянии между фототранзисторами равном 6 сантиметрам получим скорость:

0.06 / 0.004096 = 14.6484 м/с

Это минимальная скорость которая может быть измерена в данном случае.

При скорости пули в 100 м/с и при расстоянии между фототранзисторами равном 6 сантиметрам таймер/счетчик 1 успеет сосчитать до:

(0.06 / 100.0) / (1.0 / 16000000.0) = 9600.0

Точность хронографа с этой программой очень высока. Ошибка вносимая в измерения составляет всего лишь несколько тактов МК. Причина в работе компилятора. Можно дизассемблировать прошивку и посмотреть код обработки внешних прерываний INT0 и INT1 (они обрабатывают сигналы с фототранзисторов). Есть способы позволяющие компенсировать эту ошибку. Но я пойду другим путем. Каким именно, напишу в следующей статье.