Использование без паузы с ультразвуковым датчиком HR-S04

c++ ultrasonics

Как я могу измерить расстояние ультразвукового датчика HR-S04 с помощью миллиметров и микросекунд?

, 👍1

Обсуждение

Я бы использовал возможность захвата ввода Timer1, но это выходит за рамки этого сайта - возможно, вы сможете найти библиотеку, которая сделает это за вас., @Majenko

@Majenko, ты видел ответ на свой вопрос?, @Juraj

@Juraj я так и сделал. По - моему, все в порядке., @Majenko

@Majenko, большинство ультразвуковых кодов, которые я видел, активировали тригонометрию в течение нескольких микросекунд, а затем использовали pulseIn для улавливания эха. но код в ответе срабатывает в течение 10 миллисекунд с использованием состояний, @Juraj

Падение края - вот что имеет значение. Он все еще использует pulseIn., @Majenko


2 ответа


1

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

Вы можете обратиться за любыми разъяснениями по этому поводу!

/*
 *  HC-SR04 sensor measuring
 *
 *  Non-blocking HC-SR04 sensor measuring
 *
 *  The circuit:
 *  - Echo pin connected to pin 2
 *  - Trigger pin connected to pin 3
 *  - VCC pin connected to VCC (5v)
 *  - GND gger pin connected to GND
 *
 *  created 3 Jul 2021
 *  by alessandromrc
 */

#define trigPin 3
#define echoPin 2

unsigned long timerStart = 0;
unsigned long StartTime = micros();
bool timer = false;
const unsigned long HIGH_TRIGGER = 10;
const unsigned long LOW_TRIGGER = 2;

float timeDuration, distance;

enum SensorStatus {
  TRIG_LOW,
  TRIG_HIGH,
  ECHO_HIGH
};

SensorStatus sensorStatus = TRIG_LOW;

bool isTimerReady(const unsigned long Sec) {
  return (micros() - timerStart) < Sec;
}

void setup(void) {
  Serial.begin(9600);
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
}

void loop(void) {
  switch (sensorStatus) {
  case TRIG_LOW: {
    digitalWrite(trigPin, LOW);
    timerStart = micros();
    if (isTimerReady(LOW_TRIGGER)) {
      sensorStatus = TRIG_HIGH;
    }
  }
  break;

  case TRIG_HIGH: {
    digitalWrite(trigPin, HIGH);
    timerStart = micros();
    if (isTimerReady(HIGH_TRIGGER)) {
      sensorStatus = ECHO_HIGH;
    }
  }
  break;

  case ECHO_HIGH: {
    if (!timer) {
      Serial.print("Microseconds Passed in initialization: ");
      Serial.println(micros() - StartTime);
      timer = !timer;
      Serial.println("Starting to Measure");
    }
    digitalWrite(trigPin, LOW);
    timeDuration = pulseIn(echoPin, HIGH);
    Serial.print("Measured: ");
    Serial.print(timeDuration * 0.034 / 2);
    Serial.println(" cm");
    sensorStatus = TRIG_LOW;
  }
  break;
  }
}

Репозиторий Github

,

этого достаточно для срабатывания в течение 5 микросекунд. почему вы используете отдельное состояние для запуска в течение 10 миллисекунд?, @Juraj

@Juraj Я не знал об этом, спасибо, что дал мне знать!, @alessandromrc


3

В ответ на комментарий @Majko к исходному вопросу, вот код, который я использую, который использует функцию захвата входа Timer1 для считывания ультразвукового датчика. Он также использует генератор ШИМ Таймера 1 для создания импульсов запуска. Весь код управляется прерываниями, и ничто не блокирует основную программу.

Она не представляет собой официальную библиотеку стилей со всеми дополнительными частями, но файлы .h и .cpp можно поместить в папку в папке библиотек, как и в процессе установки вручную. На самом деле это просто личная библиотека, которую я использую на некоторых своих роботах, но я рад ею поделиться.

Написанный код поддерживает чипы ATMEGA1284P и ATMEGA328P. Есть пара строк, которые можно прокомментировать и раскомментировать, чтобы переключаться между ними.

Он предоставляет одноэлементный экземпляр, очень похожий на класс Serial, поэтому создавать экземпляры нечего. Все контакты устанавливаются аппаратно, их нельзя изменить.

В зависимости от того, какой общий диапазон вы хотите, вы можете установить для прескалера гораздо меньшее значение. Если вы работаете без прескалера, то разрешение будет примерно в 100 раз больше, чем вы можете получить с микросхемами (разрешение 62,5 нс против 4 мкс). Но это будут более короткие расстояния, например 1,2 м и меньше.

https://github.com/delta-G/PingTimer

#include "PingTimer.h"

int distance;

void setup() {
  
  Serial.begin(9600);
  ping.begin();
  ping.sendPing();

}

void loop() {

  // если у датчика пинга есть новые данные, сохраните их на расстоянии и распечатайте.
  if(ping.hasNewData()){
    distance = ping.getDistanceMM();
    Serial.print("Current Distance: ");
    Serial.println(distance);
    ping.sendPing();   
  }   

  // Делаем другие действия, не заблокированные датчиком пинга

}

PingTimer.h

/*

PingTimer — использует захват входа Timer1 для чтения HC-SR04.
Авторские права (C) 2019 Дэвид С.

Эта программа является свободным программным обеспечением: вы можете распространять ее и/или изменять.
это в соответствии с условиями Стандартной общественной лицензии GNU, опубликованной
Фонд свободного программного обеспечения, либо версия 3 Лицензии, либо
(по вашему выбору) любая более поздняя версия.

Данная программа распространяется в надежде, что она будет полезна,
но БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ; даже без подразумеваемой гарантии
ТОВАРНАЯ ПРИГОДНОСТЬ или ПРИГОДНОСТЬ ДЛЯ ОПРЕДЕЛЕННОЙ ЦЕЛИ. См.
Стандартная общественная лицензия GNU для более подробной информации.

Вы должны были получить копию Стандартной общественной лицензии GNU.
вместе с этой программой. Если нет, см. <http://www.gnu.org/licenses/>.

*/

#ifndef PINGTIMER_H_
#define PINGTIMER_H_

#include "Arduino.h"

// Пинг-контакт на OC1A -- PB1 на 328P. (UNO контакт 9)
                    //-- PD5 на 1284P (вывод 13)
#define PING_PIN_MASK (1 << 5)
#define PING_PIN_PORT PORTD

// Вывод эха на ICP1 -- PB0 на 328P (вывод UNO 8)
                    //-- PD6 на 1284P (вывод 14)
#define ECHO_PIN_MASK (1 << 6)
#define ECHO_PIN_PORT PIND

class PingTimer {
private:
    volatile uint16_t timerVal;
    volatile boolean overflowed;
    volatile boolean newData;

    void initTimer();

    void startPulse();


public:
    PingTimer();
    void begin();

    void sendPing();
    void echoHandler();
    void overflowHandler();

    boolean hasNewData();
    boolean hasOverflowed();
    uint16_t getTimerVal();

    int16_t getDistanceMM();

};

extern PingTimer ping;

#endif /* PINGTIMER_H_ */

И PingTimer.cpp

/*

PingTimer — использует захват входа Timer1 для чтения HC-SR04.
Авторские права (C) 2019 Дэвид С.

Эта программа является свободным программным обеспечением: вы можете распространять ее и/или изменять.
это в соответствии с условиями Стандартной общественной лицензии GNU, опубликованной
Фонд свободного программного обеспечения, либо версия 3 Лицензии, либо
(по вашему выбору) любая более поздняя версия.

Данная программа распространяется в надежде, что она будет полезна,
но БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ; даже без подразумеваемой гарантии
ТОВАРНАЯ ПРИГОДНОСТЬ или ПРИГОДНОСТЬ ДЛЯ ОПРЕДЕЛЕННОЙ ЦЕЛИ. См.
Стандартная общественная лицензия GNU для более подробной информации.

Вы должны были получить копию Стандартной общественной лицензии GNU.
вместе с этой программой. Если нет, см. <http://www.gnu.org/licenses/>.

*/

#include "PingTimer.h"

PingTimer ping;


PingTimer::PingTimer(){
    newData = false;
    overflowed = false;
    timerVal = 0;
}

void PingTimer::begin() {
    initTimer();
    // ATMEGA328P
// pinMode(8, INPUT);
// pinMode(9, OUTPUT);
    // ATMEGA1284P
    pinMode(14, INPUT);
    pinMode(13, OUTPUT);
}


void PingTimer::initTimer(){
    cli();
    TCCR1A = 0;  // нет ШИМ
    // Шумоподавление включено, RISING Edge, без прескалера, диапазон 4 мс.
    TCCR1B |= ((1 << ICNC1) | (1 << ICES1) | (1 << CS11) | (0 << CS10));
    // убеждаемся, что аналоговый компаратор не выбран в качестве входа захвата
    ACSR &= ~(1 << ACIC);
    // Отключаем прерывания по таймеру
    TIMSK1 = 0;
    // установка COMPA на 10 мкс для синхронизации импульса запуска
    // и COMPB на 12 мс для сигнала вне диапазона (чуть более 4 метров)
    OCR1AH = 0;
    OCR1AL = 20;  // При прескалере 8 каждый тик составляет 0,5 мкс.
    OCR1BH = 0xB5;
    OCR1BL = 0xB0; // 0xB5B0 — 46512. Это чуть больше 4 метров с прескалером на 8.
    sei();
}

// Это общедоступная функция для вызова startPulse, поэтому она может оставаться конфиденциальной
// так как он путает регистры
void PingTimer::sendPing() {
    startPulse();
}

void PingTimer::startPulse(){
    // устанавливаем высокий уровень на выводе и настраиваем блок ШИМ на
    // выключаем вывод через 10 мкс
    cli();
    TCNT1H = 0x00;
    TCNT1L = 0x00;
    //Настраиваемся на получение одного импульса ШИМ
    // Очистить OC1A при сравнении. 10-битный быстрый ШИМ
    TCCR1A = ((1 << COM1A1) | (1 << WGM11) | (1 << WGM10));
    // Настраиваем прерывание захвата ввода, чтобы поймать рост
    // вывода echo и запускаем таймер
    // Шумоподавление включено, RISING Edge, прескалер на 8 дает таймер 32 мс = диапазон 5,5 метров
    TCCR1B = ((1 << ICNC1) | (1 << ICES1) | (1 << CS11) | (0 << CS10) | (1 << WGM12));
    // Включаем захват входного сигнала прерывания
    TIFR1 = ((1 << ICF1) | (1 << TOV1));
    TIMSK1 = ((1 << ICIE1));
    overflowed = false;
    newData = false;
    sei();
}



// Вызывается из ISR
void PingTimer::echoHandler() {
    if (ECHO_PIN_PORT & ECHO_PIN_MASK) {  //Восходящий контакт
        // Уничтожаем модуль ШИМ OC1A и возвращаемся в нормальный режим
        TCCR1A = 0;
        // Шумоподавление включено, ПАДАЮЩИЙ фронт, прескалер на 8 дает таймер 32 мс = диапазон 5,5 метров
        TCCR1B = (1 << ICNC1) | (0 << ICES1) | (1 << CS11) | (0 << CS10);
        // Сброс таймера 1
        TCNT1H = 0;
        TCNT1L = 0;
        // Сброс прерываний захвата ввода и переполнения таймера
        TIFR1 = (1 << ICF1) | (1 << TOV1);
        // Включаем захват прерываний и переполнение таймера
        TIMSK1 = (1 << ICIE1) | (1 << TOIE1);
    } else {  // Падающий контакт
        // Читаем регистр ICR1
        uint8_t low = ICR1L;
        uint8_t high = ICR1H;
        timerVal = ((high << 8) | low) - 4; // Шумоподавитель добавляет 4 такта

        // отключаем прерывания по таймеру
        TIMSK1 = 0;
        newData = true;
    }
}

// Вызывается из OVF таймера ISR 1
void PingTimer::overflowHandler(){
    //выключаем прерывания по таймеру
    TIMSK1 = 0;
    newData = true;
    overflowed = true;
}



boolean PingTimer::hasNewData(){
    cli();
    boolean retval = newData;
    newData = false;
    sei();

    return retval;
}

boolean PingTimer::hasOverflowed(){
    cli();
    boolean retval = overflowed;
    overflowed = false;
    sei();

    return retval;
}

uint16_t PingTimer::getTimerVal() {
    uint16_t retval = 0;
    cli();
    if (overflowed) {
        retval = -1;
    } else {
        uint16_t copy = timerVal;
        retval = copy;
    }
    sei();
    return retval;
}

int16_t PingTimer::getDistanceMM() {

    int16_t retval = 0;
    uint16_t copy = getTimerVal();
    if (copy == (uint16_t) -1) {
        retval = -1;
    } else {
        // 16 МГц — 62,5 нс/такт
        // У нас есть прескалер, делящий на 8
        uint32_t nanoSecs = copy * 62.5 * 8;
        //скорость звука в мм/нс
        uint16_t mm = nanoSecs * 0.000344;
        retval = mm / 2;  // половина пути туда и обратно
    }
    return retval;
}

ISR(TIMER1_CAPT_vect){
    ping.echoHandler();
}

ISR(TIMER1_OVF_vect){
    ping.overflowHandler();
}

ISR(TIMER1_COMPB_vect){
    ping.overflowHandler();
}
,

Много лет назад рекомендовалось обращаться к 16-битным регистрам ввода-вывода побайтно, сначала читая младший бит, а затем записывая старший бит. Это древняя история. Поскольку с тех пор gcc научился получать доступ к этим регистрам, теперь вы можете просто написать OCR1B = 46512 и timerVal = ICR1., @Edgar Bonet

Думаю, я это знал, но не был уверен, что правильно запомнил, поэтому просто сделал то, что указано в таблице данных. Если я обновлю это позже, я добавлю это изменение., @Delta_G