arduino 多个超声波模块HC-SR04 Newping.h库的使用——摆脱万恶的阻塞等待

目录

前言(一点废话)

问题的发现

Newping.h库

        简介

        安装

        例程1:定时器示例TimerExample

         例程2:超声波模块的读取NewPingExample

        例程3:中断读取超声波NewPingEventTimer

        例程4:双超声波模块的读取

尾声(注意事项)


前言(一点废话)

        在研究HC-SR04超声波模块在arduino上的使用的时候,使用网上的代码我发现超声波模块代码的运行会阻塞其他程序的运行,研究程序发现了其中的一些猫腻。

        在stm32上使用的是定时器中断,不会这样子。本来使用arduino是为了简单方便,但是现在却带来了不方便,这我能忍吗。不能忍,于是我开始研究解决方法。最后在某404网站上得知Newping这个库,查了查百度没有相关的内容,于是我补充一下自己的使用心得。

问题的发现

        我在参考网上文章使用超声波模块的时候,发现代码加入其他程序时会使得其他程序阻塞无法流畅运行。代码如下:


 

float      distance;
const int  echo=11;                          //echO接D3脚
const int  trig=12;                          //echO接D2脚
void setup()

{
  Serial.begin(9600);                       //波特率9600
  pinMode(echo,INPUT);                       //设置echo为输入脚
  pinMode(trig,OUTPUT);                      //设置trig为输出脚
  Serial.println("HC-SR04-2019.7.14测距开始:");
}
void loop()
{
 digitalWrite(trig,LOW);
 delayMicroseconds(20); 
 digitalWrite(trig,HIGH);
 delayMicroseconds(20);
 digitalWrite(trig,LOW);                     //发一个20US的高脉冲去触发Trig
 distance  = pulseIn(echo,HIGH);             //计数接收高电平时间
 distance  = distance*340/2/10000;           //计算距离 1:声速:340M/S  2:实际距离1/2声速距离 3:计数时钟为1US
 Serial.print("距离: ");
 Serial.print(distance);
 Serial.println("cm");
 delay(20);                                   //单次测离完成后加20mS的延时再进行下次测量。防止近距离测量时,测量到上次余波,导致测量不准确。
} 


(代码参考自这篇文章【雕爷学编程】Arduino动手做(58)---SR04超声波传感器,感谢作者大大对于超声波模块的讲解,原理详细,图文并茂,推荐大家阅读一下)

读取结果如下:

        在加上时间戳之后我们可以看到,当超声波模块探测到超出范围的距离时,会比上两次之间的延时更长。比如正常探测(倒数第三行与第二行),两次读取之间相差670-637=33ms,但是当突然探测不到超长距离时(倒数第二与倒数第一行),这个间隔为737-670=67ms。如果你的小车同时运行多个超声波模块,这将会是很致命的问题。

        在查找了相关资料之后,我发现这个问题出在函数pulseIn(echo,HIGH)上,这个函数的作用是等待高电平的信号并回传信号变化的时间,用来计算超声波探测的距离。但是很遗憾,他是用阻塞的方法而不是中断的方法,所以拖累了系统整体的运行速度。

        于是我开始寻找解决的方法,我在404网站上搜索到了Newping.h库的相关资料,并且动手尝试了一下。

Newping.h库

        简介

        翻译自Newping库网站原文,网站地址点这里

        

        当我第一次收到超声波传感器时,我对它的性能很不满意。我很快意识到问题不是传感器,而是可用的ping和超声波库造成的。NewPing library完全解决了这些问题,添加了许多新功能,并为这些价格合理的距离传感器注入了新的活力。以下是NewPing的一些功能列表:

        可与多种不同型号的超声波传感器配合使用:HC-SR04、SRF05、SRF06、DYP-ME007、JSN-SR04T和视差)™.

        选择仅使用一个Arduino引脚与除SRF06传感器外的所有传感器连接。

        与所有其他超声波库一样,如果没有收到ping回波,则不会延迟整整一秒钟。

        与整个Arduino系列(和克隆)、Teensy系列(包括19.80美元96Mhz 32位Teensy 3.2)和非AVR微控制器兼容。

        以每秒30次的速度持续可靠地Ping传感器。

        事件驱动草图的计时器中断方法。

        内置数字滤波方法ping_median()便于纠错。

        在访问管脚时使用端口寄存器,以便更快地执行和更小的代码大小。

        允许设置最大距离,超过该距离的ping将被读取为无ping或清除。

        易于使用多个传感器(绘制Ping 3个传感器的示意图-绘制Ping 15个使用定时器的传感器的示意图)。

        更精确的距离计算(厘米、英寸和微秒)。

        不使用pulseIn,它速度慢,在某些超声波传感器型号中给出的结果不正确。

        积极开发,添加功能,解决缺陷/问题。

(纯机翻)

        这个库优化了超声波模块的使用代码,使用了计时器中断的方法去读取数据,他的效率比传统的代码高了很多。但是使用的时候有一些注意事项,我们之后再说。

        安装

        我们可以在官方的ide中下载到这个库:工具->管理库,搜索Newping,就可以下载

 装完之后就可以在:文件->示例 中发现他的例程。下面看一下他的例程:

        例程1:定时器示例TimerExample

        定时器中断的使用示例,他用的是定时器2,可能会和一些pwm引脚冲突。这个例程的功能是:led按照500ms的频率闪烁,但是不阻塞程序。这里我稍微改了一下,输出了以下串口数据,用来表示程序并没有被阻塞。

// ---------------------------------------------------------------------------
// While the NewPing library's primary goal is to interface with ultrasonic sensors, interfacing with
// the Timer2 interrupt was a result of creating an interrupt-based ping method. Since these Timer2
// interrupt methods were built, the library may as well provide the functionality to use these methods
// in your sketches.  This shows how simple it is (no ultrasonic sensor required).  Keep in mind that
// these methods use Timer2, as does NewPing's ping_timer method for using ultrasonic sensors.  You
// can't use ping_timer at the same time you're using timer_ms or timer_us as all use the same timer.
// ---------------------------------------------------------------------------

#include <NewPing.h>

#define LED_PIN 13 // Pin with LED attached.

void setup() {
  pinMode(LED_PIN, OUTPUT);
  Serial.begin(9600);
  NewPing::timer_ms(500, toggleLED); // Create a Timer2 interrupt that calls toggleLED in your sketch once every 500 milliseconds.
}

void loop() {
  // Do anything here, the Timer2 interrupt will take care of the flashing LED without your intervention.
  Serial.println("hello");
}

void toggleLED() {
  digitalWrite(LED_PIN, !digitalRead(LED_PIN)); // Toggle the LED.
}

上传程序之后我们可以看到led在按一定频率闪烁,看到串口也在按正常的速的输出数据,没有阻塞:

         例程2:超声波模块的读取NewPingExample

        超声波模块按照图示接线:

代码如下:

// ---------------------------------------------------------------------------
// Example NewPing library sketch that does a ping about 20 times per second.
// ---------------------------------------------------------------------------

#include <NewPing.h>

#define TRIGGER_PIN  12  // Arduino pin tied to trigger pin on the ultrasonic sensor.
#define ECHO_PIN     11  // Arduino pin tied to echo pin on the ultrasonic sensor.
#define MAX_DISTANCE 200 // Maximum distance we want to ping for (in centimeters). Maximum sensor distance is rated at 400-500cm.这里设置最大读取距离,当超过最大读取距离的时候数据会置0,如果你检测的物体不是很平整,就会出现很多个0的数据,这时候你需要进行滤波操作

NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE); // NewPing setup of pins and maximum distance.

void setup() {
  Serial.begin(115200); // Open serial monitor at 115200 baud to see ping results.
}

void loop() {
  delay(50);                     // Wait 50ms between pings (about 20 pings/sec). 29ms should be the shortest delay between pings.
//  Serial.print("Ping: ");//注释了不必要的输出,方便串口绘图
  Serial.println(sonar.ping_cm()); // Send ping, get distance in cm and print result (0 = outside set distance range)
//  Serial.println("cm");//注释了不必要的输出,方便串口绘图
}

 串口读取结果如下:

 我们打开串口绘图器(工具->串口绘图器)可以看到以下的图像:

 我们可以修改这句代码来限制使用距离:

#define MAX_DISTANCE 200 

这里设置最大读取距离,当超过最大读取距离的时候数据会置0,如果你检测的物体不是很平整,就会出现很多个0的数据,这时候你需要进行滤波操作(这里略)。

但是这个例程仍然没有使用中断,我们可以看到还有一个万恶的delay。我们看第三个例程:

        例程3:中断读取超声波NewPingEventTimer

代码如下:

// ---------------------------------------------------------------------------
//翻译:这个例子展示了如何使用NewPing的ping_timer方法,该方法使用Timer2中断来获取

//平时间。与标准ping方法相比,使用此方法的优点是,它允许更高的精度

//事件驱动的草图,让你看起来同时做两件事。一个例子就是ping

//一个超声波传感器,用于在导航的同时防止可能发生的碰撞。这使得

//正确绘制草图以完成多任务。请注意,由于ping_timer方法使用Timer2,

//其他也使用Timer2的功能或库也会受到影响。例如,PWM功能开启

//Arduino Uno上的插脚3和11(Arduino Mega上的插脚9和10)以及音调库。注意,只有PWM

//引脚功能丢失(因为它们使用Timer2进行PWM),引脚仍可使用。

//注:对于Teensy/Leonardo(ATmega32U4),该库使用Timer4而不是Timer2。
// ---------------------------------------------------------------------------
#include <NewPing.h>

#define TRIGGER_PIN   12 // Arduino pin tied to trigger pin on ping sensor.
#define ECHO_PIN      11 // Arduino pin tied to echo pin on ping sensor.
#define MAX_DISTANCE 200 // Maximum distance we want to ping for (in centimeters). Maximum sensor distance is rated at 400-500cm.

NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE); // NewPing setup of pins and maximum distance.

unsigned int pingSpeed = 100; // How frequently are we going to send out a ping (in milliseconds). 50ms would be 20 times a second.
unsigned long pingTimer;     // Holds the next ping time.

void setup() {
  Serial.begin(115200); // Open serial monitor at 115200 baud to see ping results.
  pingTimer = millis(); // Start now.
}

void loop() {
  // Notice how there's no delays in this sketch to allow you to do other processing in-line while doing distance pings.
  if (millis() >= pingTimer) {   // pingSpeed milliseconds since last ping, do another ping.
    pingTimer += pingSpeed;      // Set the next ping time.
    sonar.ping_timer(echoCheck); // Send out the ping, calls "echoCheck" function every 24uS where you can check the ping status.
  }
//  Serial.println("hello");
  // Do other stuff here, really. Think of it as multi-tasking.
}

void echoCheck() { // Timer2 interrupt calls this function every 24uS where you can check the ping status.
  // Don't do anything here!
  if (sonar.check_timer()) { // This is how you check to see if the ping was received.
    // Here's where you can add code.
//    Serial.print("Ping: ");
    Serial.println(sonar.ping_result / US_ROUNDTRIP_CM); // Ping returned, uS result in ping_result, convert to cm with US_ROUNDTRIP_CM.
//为了方便串口绘图,此处同样进行了修改
//    Serial.println("cm");
  }
  // Don't do anything here!
}

结果如下:

这个例程相较于上一个例程的优点在于使用了中断读取数据,精度更高,没有delay延时。作者在例子中也有如下的注解:

//这个例子展示了如何使用NewPing的ping_timer方法,该方法使用Timer2中断来获取

//平时间。与标准ping方法相比,使用此方法的优点是,它允许更高的精度

//事件驱动的草图,让你看起来同时做两件事。一个例子就是ping

//一个超声波传感器,用于在导航的同时防止可能发生的碰撞。这使得

//正确绘制草图以完成多任务。请注意,由于ping_timer方法使用Timer2,

//其他也使用Timer2的功能或库也会受到影响。例如,PWM功能开启

//Arduino Uno上的插脚3和11(Arduino Mega上的插脚9和10)以及音调库。注意,只有PWM

//引脚功能丢失(因为它们使用Timer2进行PWM),引脚仍可使用。

//注:对于Teensy/Leonardo(ATmega32U4),该库使用Timer4而不是Timer2。

 然而使用定时器中断会影响一些引脚的pwm,所以我们在制作小车时尽量要避开这些引脚。

        例程4:双超声波模块的读取

        当你需要同时使用多个超声波模块时,原库中提供了NewPing3Sensor这个例程,然而这个例程并没有使用中断读取,会存在约50ms的延时,如果你对这个延时很介意的话,你可以使用下面这段代码来使用多个超声波模块,他使用了定时器中断的方法读取数据(代码并没有出现在官方的库示例中,而是源自作者的一个问答:戳这里跳转):

#include <NewPing.h>

#define SONAR_NUM     2//这里修改传感器个数
#define MAX_DISTANCE 200
#define PING_INTERVAL 35 // Milliseconds between each sensor ping (35ms is about the minimum to avoid cross-sensor echos.  You could try making this as low as 29ms and see what happens).

const int pingCycle = PING_INTERVAL * (SONAR_NUM + 1); // This used to be PING_SPEED, pings as fast as possible.
unsigned long pingTimer[SONAR_NUM + 1]; // +1 for timer that displays results.
unsigned int cm[SONAR_NUM]; // Where the ping distances are stored.
uint8_t currentSensor = 0;  // Keeps track of which sensor is active.

NewPing sonar[SONAR_NUM] = {
  NewPing(12, 11, MAX_DISTANCE),
  NewPing(10, 9, MAX_DISTANCE),
//这里添加传感器
};

void setup() {
  Serial.begin(115200);
  pingTimer[0] = millis() + 75; // First ping starts at 75ms, gives time for the Arduino to chill before starting.
  for (uint8_t i = 0; i < SONAR_NUM; i++) {
    pingTimer[i+1] = pingTimer[i] + PING_INTERVAL;
  }
}

void loop() {
  for (uint8_t i = 0; i <= SONAR_NUM; i++) {
    if (millis() >= pingTimer[i]) {
      pingTimer[i] += pingCycle; // Set next time this sensor will be pinged.
      if (i == SONAR_NUM) oneSensorCycle(); // Sensor ping cycle complete, do something with the results.
      else {
        sonar[currentSensor].timer_stop(); // Make sure previous timer is canceled before starting a new ping.
        currentSensor = i; // Sensor being accessed.
        cm[currentSensor] = 0; // Make distance zero in case there's no ping echo for this sensor.
        sonar[currentSensor].ping_timer(echoCheck); // Do the ping and wait for the echo interrupt.
      }
    }
  }
  // The rest of your code would go here.
}

void echoCheck() {
  if (sonar[currentSensor].check_timer()) { // Check to see if the ping was received.
    cm[currentSensor] = sonar[currentSensor].convert_cm(sonar[currentSensor].ping_result); // Set the sensor distance to the array.
  }
}

void oneSensorCycle() { // Sensor ping cycle complete, do something with the results.
  for (uint8_t i = 0; i < SONAR_NUM; i++) {
    Serial.print("ping");//我在原来的代码上进行了更改,方便串口绘图
    Serial.print(i);
    Serial.print(" ");
    Serial.print(cm[i]);
    Serial.print("\t");
  }
  Serial.println();
}

得到如下的结果 

 值得一提的是,如果你使用多个超声波模块,你需要确保他们相互之间不会干扰

我们在这里看到两个模块工作良好

我准备将这个代码放在我的ros小车上运行,这部分的代码之后再更新

尾声(注意事项)

        值得一提的是,代码读出来的数据仍然是原始数据,存在噪声,如果你需要数据尽可能的能用,你可能需要进行滤波操作。库中的NewpingTimerMedian提供了一个中值滤波的代码,但是他在我的设备上工作不理想。

        这个库使用了定时器2作为中断,在这个引脚上使用的一些库可能会被影响,如:

在Arduino工作中,tone( ) 493函数使用timer2。

(源自这篇文章:arduino 计时器和中断

并且一些pwm引脚也是使用的这个定时器:

Arduino Uno上的插脚3和11(Arduino Mega上的插脚9和10)以及音调库。注意,只有PWM引脚功能丢失(因为它们使用Timer2进行PWM),引脚仍可使用

由于我使用的是mega2560,所以我这里贴上一些pwm引脚使用的定时器(源自这篇文章):

timer 0 (controls pin 13, 4);
timer 1 (controls pin 12, 11);
timer 2 (controls pin 10, 9);
timer 3 (controls pin 5, 3, 2);
timer 4 (controls pin 8, 7, 6); 

先写先到这里,如果有错误欢迎指正 。感谢各位大佬的文章和代码

  • 16
    点赞
  • 75
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值