Использование без паузы с ультразвуковым датчиком HR-S04
Как я могу измерить расстояние ультразвукового датчика HR-S04 с помощью миллиметров и микросекунд?
@alessandromrc, 👍1
Обсуждение2 ответа
Я заметил, что у многих людей в сети возникают проблемы с использованием 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
В ответ на комментарий @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
- C++ против языка Arduino?
- Как использовать SPI на Arduino?
- Какие накладные расходы и другие соображения существуют при использовании структуры по сравнению с классом?
- Ошибка: expected unqualified-id before 'if'
- Что лучше использовать: #define или const int для констант?
- Функции со строковыми параметрами
- Библиотека DHT.h не импортируется
- ошибка: ожидаемое первичное выражение перед токеном ','
Я бы использовал возможность захвата ввода Timer1, но это выходит за рамки этого сайта - возможно, вы сможете найти библиотеку, которая сделает это за вас., @Majenko
@Majenko, ты видел ответ на свой вопрос?, @Juraj
@Juraj я так и сделал. По - моему, все в порядке., @Majenko
@Majenko, большинство ультразвуковых кодов, которые я видел, активировали тригонометрию в течение нескольких микросекунд, а затем использовали pulseIn для улавливания эха. но код в ответе срабатывает в течение 10 миллисекунд с использованием состояний, @Juraj
Падение края - вот что имеет значение. Он все еще использует pulseIn., @Majenko