Помогите вычислить ширину импульса!

Тема в разделе "Arduino & Shields", создана пользователем mib, 17 фев 2017.

  1. mib

    mib Нуб

    Здравствуйте!
    Не могу точно вычислить ширину импульсов.
    Имеется Nano, с нее подается шим сигнал на Uno в радиусе 1000-2000 мкс с шагом 1 мкс.
    Требуется точное считывание вплоть до 1 шага для продвижения действий дальше.

    1. Пробовал через pulsein,
    плюсы: изменение по 1 шагу (1 мкс);
    минусы: даже при голом коде ширина ~ (995-1985), при наличии Serial порта и его загруженности ширина резко падает, в чем причина и откуда такая зависимость я пока не понимаю, так же само значение держится нестабильно и постоянно дергается.

    Код с pulsein:
    Код (C++):
    int pin = 2;
    unsigned long duration;
    void setup()
    {
      pinMode(pin, INPUT);
    }
    void loop()
    {
    duration = pulseIn(pin, HIGH);
    }
    2. Через прерывания:
    плюсы: измеряется точно, почти нет помех, Serial порт не влияет, не мешает программе;
    минусы: собственно главный, ради чего все и замышлялось, минимальный шаг = 4 мкс.

    Код с прерыванием:
    Код (C++):
    #define pin 2
    volatile unsigned long ChannelIn;

    void setup() {
      pinMode(pin, INPUT);
      attachInterrupt(digitalPinToInterrupt(pin), calcChannel, CHANGE);
    }

    void calcChannel()
    {
      static unsigned long Start;
      if(digitalRead(pin))
      {
        Start = micros();  
      }
      else
      {
        ChannelIn = (unsigned long)(micros() - Start);
      }
    }

    void loop() {
      // put your main code here, to run repeatedly:
    }
    3. Так же нашел нечто непонятное:
    Код (C++):
    int pin = 2;
    unsigned long duration;
    unsigned long v1 = 0;
    unsigned long v2 = 0;
    unsigned long itog = 0;
    void setup()
    {
      pinMode(pin, INPUT);

    }
    void loop()
    {
    duration = pulseIn(pin, HIGH);
    while (digitalRead(pin) == LOW);
    v1 = micros();
    while (digitalRead(pin) == HIGH);
    v2 = micros();
    itog = v2-v1;
    }
    Собственно "itog" показывает значения как и во 2 случае с прерываниями, вроде бы просто и отлично, но опять же минимальный шаг 4 мкс все перекрывает.
    Самое непонятное, что работает только с "pulsein", без него выдает ахинею.

    Сам новичок и буду рад любой помощи.
     
  2. mcureenab

    mcureenab Гуру

    Мелкое время берите из регистра таймера.
    Трудность может быть с согласованием чтения счетчика микросекунд и регистра вблизи точки переполнения таймера. При неверном подходе время из регистра может оказаться в другой эпохе счетчика микросекунд.

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

    Короче, чтение счетчика микросекунд и таймера должно происходить в критической св кода. Как минимум надо запретить прерывания и проверить не переполнился ли таймер.


    Алгоритм согласованного чтения регистра таймера t и счетчика микросекунд m.
    Читаем m1 = m;
    Читаем t;
    Читаем m2 = m;
    Если m1+1<m2 ошибка. Время потеряно.
    Если m1=m2 результат (m1, t).
    Если m1<m2 смотрим t. Если t ближе к началу, результат (m2, t), иначе результат (m1, t).
    Если m2<m1 счетчик m перешел в новую эру.
     
    Последнее редактирование: 17 фев 2017
  3. rkit

    rkit Гуру

    С прошивкой ардуино не получится точно попасть в 1 мкс.
    Влияет, он обрабатывается на прерываниях.
     
    Последнее редактирование: 17 фев 2017
  4. serg_admin

    serg_admin Гик

    Что бы ни чего не влияло надо заблокировать прерывания. Команда cli(). Порт отрубится. После pulseIn включаем sei(). Тогда pulseIn будет работать максимально точно. Если период между cli() и sei() меньше чем время передача байта через RS232 порт - то порт RS232 будет продолжать работать.
    Код (C++):
    cli();
    duration = pulseIn(pin, HIGH);
    sei();
     
    Последнее редактирование: 17 фев 2017
    mib и arkadyf нравится это.
  5. serg_admin

    serg_admin Гик

    Если хочешь через прерывания, то лучше организуй свой счетчик на 16-ти битном таймере.
    Запускаешь на нарастающем фронте, отключаешь на спадающем. Точность при делителе 8 до 0,5 мкСек

    Кроме того: когда ты цепляешь прерывание командой
    Код (C++):
    attachInterrupt(digitalPinToInterrupt(pin), calcChannel, CHANGE);
    Неявно это означает, что при старте прерывания перед твоим кодом выполняется код ядра Arduino - стало быть ты будешь иметь некоторую (возможно статичную) погрешность измерения в плюс. Более точно повесить свой обработчик через макрос ISR, но в этом случае команда attachInterrupt для всех выводов конкретного порта работать не будет.
     
    Последнее редактирование: 17 фев 2017
    mib и arkadyf нравится это.
  6. mcureenab

    mcureenab Гуру

    Для захвата времени служит Input Capture pin. По срезу сигнала ICP значение регистров TCNTn копируется в регистры ICRn и поднимается прерывание. Теперь не важно сколько тактов процессора пройдет с момента захвата. Значение можно прочитать из регистров ICRn.
     
    mib нравится это.
  7. AlexU

    AlexU Гуру

    Код (C++):
    void calcChannel()
    {
      static unsigned long Start;
      // первым делом засекаем время,
      // если это делать позже, то измерения будут менее точными
      unsigned long current = micros();

      // вместо digitalRead(), которая отнимает драгоценное время,
      // используем прямое чтение из порта
      // для второго пина -- реистр PIND, третий бит
      if(PIND & 4)
      {
        Start = current;
      }
      else
      {
        ChannelIn = (unsigned long)(current - Start);
      }
    }
    И да, желательно воспользоваться советом:
     
  8. mcureenab

    mcureenab Гуру

    Как этим можно работать с импульсами 1 мкс?

    Замените micros на регистр ICRn. И прерывание на ICP.
     
  9. AlexU

    AlexU Гуру

    От куда информация?
    Согласен, действительно 4 мкс.
     
    Последнее редактирование: 17 фев 2017
  10. mcureenab

    mcureenab Гуру

    Засада micros() в том, что таймер работает в масштабе 1/64. Если изменить масштаб многие функции могут работать некорректно. Но аналогичную функциональность можно реализовать на timer1.

    https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/cores/arduino/wiring.c

    Код (C++):
    unsigned long micros() {
        unsigned long m;
        uint8_t oldSREG = SREG, t;
     
        cli();
        m = timer0_overflow_count;
    #if defined(TCNT0)
        t = TCNT0;
    #elif defined(TCNT0L)
        t = TCNT0L;
    #else
        #error TIMER 0 not defined
    #endif

    #ifdef TIFR0
        if ((TIFR0 & _BV(TOV0)) && (t < 255))
            m++;
    #else
        if ((TIFR & _BV(TOV0)) && (t < 255))
            m++;
    #endif

        SREG = oldSREG;
     
        return ((m << 8) + t) * (64 / clockCyclesPerMicrosecond());
    }
     
    Эта строчка интересная. Разве t может быть 255?

    Код (C++):
        if ((TIFR0 & _BV(TOV0)) && (t < 255))
    Чтобы получать прерывания с частотой ровно 1кГц, нужно 16МГц / 64 / 250. Или в Ардуино миллисекунды не те за кого себя выдают? Фактически частота равна 16МГц / 64 / 256?

    Тут скорее должно быть
    Код (C++):
        if ((TIFR0 & _BV(TOV0)) && (t < 127))
    Т.е. если переполнение произошло ( TOV0 ) и значение TIFR0 маленькое, значит переполнение произошло до его захвата в t. Если значение TIFR0 большое, значит переполнение произошло после его захвата в t.
     
    Последнее редактирование: 18 фев 2017
  11. mib

    mib Нуб

    стало заметно лучше, но не идеально: диапазон 999-1998.
    Простите, совершенно не понимаю как это сделать, не могли бы вы написать код для моего примера? Заранее спасибо.
     
  12. AlexU

    AlexU Гуру

    Да.
    Да, частота равна 16МГц / 64 / 256, т.е. миллисекунды считаются чуть медленней, чем нужно. В приведённой Вами ссылке есть код обработчика прерывания TIMER0_OVF_vect, в коде присутствует компенсация подсчёта миллисекунд.
     
  13. serg_admin

    serg_admin Гик

    Вообще, что бы надежно мерить с точностью 1 мкСек, у тебя должно быть разрешение не менее 0,25мкСек, следовательно:
    - Либо функция на Assemblere
    - Либо свой таймер - 16-ти битный таймер дает разрешение 1/16 мкСек при этом длительность до 4096 мкСек.

    Думаю других вариантов у тебя в принципе нет.
     
  14. mcureenab

    mcureenab Гуру

    Спасибо! Так бы сразу не понял к чему там хитрые расчеты. Смотрю миллисекунды и микросекунды в разных переменных подсчитываются и по разному. В расчетах времени их нельзя смешивать, хотя таймер у них общий.

    В общем тот-же алгоритм (для микросекунд) надо на Таймер 1 сделать. И можно будет десятые мкс ловить.

    ТС, у вас таймер 1 не занят? Если есть сомнения, напишите, какие библиотеки вы используете. Некоторые библиотеки использую таймеры.

    Наверно библиотека для тонких расчетов где то уже есть. Стоит поискать.
     
  15. mib

    mib Нуб

    с Ассемблером не знаком и некогда изучать.
    Как сделать то свой таймер? Я просто не имею ни малейшего понятия. Есть ли примеры в ардуино, которые можно переделать под себя?
    скорее всего не занят, пока что доп библиотеки не использую, в будущем могут быть
    <Bounce.h>
    <PWM.h>
    <LiquidCrystal.h>
    Как все-таки это сделать?
     
  16. serg_admin

    serg_admin Гик

    Вот здесь у человека реализовано прерывание таймера для attiny13 (там смотри спойлер)
    http://forum.amperka.ru/threads/Оптимизация-кода-Что-еще-можно.11084/#post-107045

    Книга заклинаний для atmega328p (Arduino UNO/ pro mini/ nano)
    http://www.atmel.com/images/Atmel-8...PA-168A-168PA-328-328P_datasheet_Complete.pdf

    стр. 111-130 - Описание работы таймера (16-bit timer). 131 - 137 справочник

    По ICP там же стр.133 "16.11.2 TCCR1B – Timer/Counter1 Control Register B", но это уже когда с таймером разберешься.

    Когда будут конкретные вопросы пиши.
     
    mib нравится это.
  17. serg_admin

    serg_admin Гик

    Вот запуск таймера для Atmega328.
    Код (C++):
      // Делитель счетчика 256 (CS10=0, CS11=0, CS12=1).
       // 256 * 65536 = 16 777 216 (тактов) - примерно раз в секунду
       TCCR1B = _BV(CS12); // Для тебя делитель 1 (TCCR1B = _BV(CS10))
       TIMSK1 = _BV(TOIE1); // Задействовать прерывания
     
    И обработка прерывания
    Код (C++):
    ISR (TIMER1_OVF_vect) {
    ...
    }
     
    mib нравится это.
  18. mcureenab

    mcureenab Гуру

    Код (C++):
    // mcureen 1

    class Timer1 {
    public:
      enum CS : uint8_t { // Clock Select
        CS_STOP = 0,                              // No clock source. (Timer/Counter stopped), Initial
        CS_1 =                        _BV(CS10),  // clkI/O/1 (No prescaling)
        CS_8 =              _BV(CS11),            // clkI/O/8 (From prescaler)
        CS_64 =             _BV(CS11)|_BV(CS10),  // clkI/O/64 (From prescaler)
        CS_256 =  _BV(CS12),                      // clkI/O/256 (From prescaler)
        CS_1024 = _BV(CS12)          |_BV(CS10),  // clkI/O/1024 (From prescaler)
        CS_FALL = _BV(CS12)|_BV(CS11),            // External clock source on T1 pin. Clock on falling edge (PD6 (T1/OC4D/ADC9))
        CS_RISE = _BV(CS12)|_BV(CS11)|_BV(CS10)   // External clock source on T1 pin. Clock on rising edge  (PD6 (T1/OC4D/ADC9))
      };
      enum _WGM : uint8_t { // WGM internal bit mask
        _WGM__ = 0b0000,
        _WGM13 = 0b1000,
        _WGM12 = 0b0100,
        _WGM11 = 0b0010,
        _WGM10 = 0b0001,
      };
      enum WGM : uint8_t { // Waveform Generation Mode
        WGM_0  = _WGM__,                            // Normal, Initial
        WGM_1  =                            _WGM10, // PWM, Phase Correct, 8-bit
        WGM_2  =                   _WGM11,          // PWM, Phase Correct, 9-bit
        WGM_3  =                   _WGM11 | _WGM10, // PWM, Phase Correct, 10-bit
        WGM_4  =          _WGM12,                   // CTC
        WGM_5  =          _WGM12          | _WGM10, // Fast PWM, 8-bit
        WGM_6  =          _WGM12 | _WGM11,          // Fast PWM, 9-bit
        WGM_7  =          _WGM12 | _WGM11 | _WGM10, // Fast PWM, 10-bit
        WGM_8  = _WGM13,                            // PWM, Phase and Frequency Correct
        WGM_9  = _WGM13                   | _WGM10, // PWM, Phase and Frequency Correct
        WGM_10 = _WGM13          | _WGM11,          // PWM, Phase Correct
        WGM_11 = _WGM13          | _WGM11 | _WGM10, // PWM, Phase Correct
        WGM_12 = _WGM13 | _WGM12,                   // CTC
        WGM_14 = _WGM13 | _WGM12 | _WGM11,          // Fast PWMFast PWM
        WGM_15 = _WGM13 | _WGM12 | _WGM11 | _WGM10, // Fast PWMFast PWM
      };
      static Timer1& instance(){
        static Timer1 i;
        return i;
      }
      unsigned long timer_tiks(unsigned int *hi) const;
      void start_timer() { PRR0 &= ~( _BV(PRTIM1) ); } // Initial
      void stop_timer()  { PRR0 |= _BV(PRTIM1); }
      void set_CS(CS mode);    // Clock Select
      void set_WGM(WGM mode);  // Waveform Generation Mode
      void int_overflow();     // TIMER1_OVF_vect handler
    protected:
    private:
      Timer1();
      ~Timer1();
      Timer1(Timer1 const&) = delete;
      Timer1& operator= (Timer1 const&) = delete;
      unsigned long volatile overflow_count;
    };

    ISR(TIMER1_OVF_vect){
      Timer1::instance().int_overflow();
    }

    void Timer1::int_overflow(){
      overflow_count++;
    }

    Timer1::Timer1()
    :overflow_count(0){
        TIMSK1 |= _BV(TOIE1); // разрешаем прерывание по переполнению таймера
    };

    Timer1::~Timer1(){
        TIMSK1 &= ~(_BV(TOIE1)); // запрещаем прерывание по переполнению таймера
    };

    void Timer1::set_CS(
      CS mode
    ){
      TCCR1B = ( TCCR1B & ~( _BV(CS12)|_BV(CS11)|_BV(CS10) ) ) | mode;  
    }

    void Timer1::set_WGM(
      WGM mode
    ){
        TCCR1A = TCCR1A & ~( _BV(WGM11)|_BV(WGM10) ) | (_WGM11 & mode ? _BV(WGM11) : 0) | (_WGM10 & mode ? _BV(WGM10) : 0) ;
        TCCR1B = TCCR1B & ~( _BV(WGM13)|_BV(WGM12) ) | (_WGM13 & mode ? _BV(WGM13) : 0) | (_WGM12 & mode ? _BV(WGM12) : 0) ;
    }


    unsigned long Timer1::timer_tiks(unsigned int *hi) const {
        uint8_t oldSREG(SREG);
        cli();
        unsigned long m(overflow_count);
        uint16_t t(TCNT1);
        if( (TIFR1 & _BV(TOV1)) && ( t < 2048 ) ) m++;
        SREG = oldSREG;
        if( hi ) *hi = m >> 16;
        return (m << 16) + t;
    }

    // Конец класса

    // Создаем ссылку на таймер
    Timer1& t1(Timer1::instance());

    void setup() {
      // put your setup code here, to run once:
      t1.set_WGM(Timer1::WGM_0);
      t1.set_CS(Timer1::CS_1);
      t1.start_timer();
      Serial.begin(9600);
    }

    void loop() {
      // put your main code here, to run repeatedly:
      static unsigned long tm0 = 0;
      unsigned int tm1_hi;
      unsigned long tm1 = t1.timer_tiks(&tm1_hi);
      Serial.print(tm1_hi); Serial.print(":"); Serial.println((tm1  - tm0)/16.0);
      tm0 = tm1;
      delay(100);
    }

     
    Код (Text):
    0:100568.57
    0:100569.07
    0:100557.75
    0:100576.07
    0:100568.25
    0:100573.88
    0:100567.19
    0:100564.00
    0:100566.94
    0:100573.88
    0:100557.32
    0:100570.50
    0:100562.25
    0:100577.94
    0:100571.75
     
     
  19. mcureenab

    mcureenab Гуру

    Сделал подряд два замера:
    Код (Text):
    0:3.00
    0:3.00
    0:3.00
    0:3.00
    0:3.00
    0:3.00
    0:3.00
    0:9.19
    0:3.00
    0:3.00
    0:3.00
    0:3.00
    0:3.00
    0:3.00
     
    вот такая вот фигня иногда проскакивает. Наверняка какое-то прерывание вклинивается.
     
  20. mcureenab

    mcureenab Гуру

    А такой вариант тикает стабильно
    Код (C++):
    void loop() {
      // put your main code here, to run repeatedly:
      uint8_t oldSREG(SREG);
      cli();
      unsigned long tm1 = t1.timer_tiks(0);
      //delay(1);
      unsigned long tm2 = t1.timer_tiks(0);
      SREG = oldSREG;
      Serial.print(tm1_hi); Serial.print(":"); Serial.println((tm2  - tm1)/16.0);
      delay(100);
    }
     
    Код (Text):
    0:2.81
    0:2.81
    0:2.81
    0:2.81
    0:2.81
    0:2.81
    0:2.81
    0:2.81
    0:2.81
    0:2.81
    0:2.81
    0:2.81
    0:2.81
     
    но, понятно, длительные интервалы так измерять нельзя.

    Остается вариант ловить время по прерыванию.