【Arduino学习】如何编写arduino类库

如何编写arduino类库

前言

我们学习了超声波模块的使用,你已经知道运行相应的程序,就可以让串口输出超声波测距的数值。但你是否思考过,如果程序中需要实现的功能不仅仅是获取超声波传感器读数和串口输出,那程序的可读性会变得怎样呢?或者需要同时控制多个超声波模块,是否需要重复书写多次语句呢?

为了使程序看起来更清晰明了,可以将超声波驱动对端口的配置过程封装成init_SR04函数。该函数仅完成超声波相关初始化,无需返回值,因此可以使用void来声明该函数。而超声波的Trig引脚和Echo引脚,是其初始化必须使用两个变量,我们将其设置为两个参数。

编写函数

init_SR04函数代码如下:

void init_SR04(int TrigPin,int EchoPin)
{
        //初始化超声波
	 pinMode(TrigPin, OUTPUT);
      pinMode(EchoPin, INPUT);
}

将发送触发信号,获取并计算结果的过程封装成GetDistance函数。
函数最后需要返回测出的距离,即一个float类型的变量,因此在该函数我们使用float类型声明函数的返回值,并在函数中添加return语句,返回变量并退出函数。

GetDistance函数代码如下:

float GetDistance (int TrigPin,int EchoPin)
{
//产生一个10μs的高脉冲去触发TrigPin
        digitalWrite(TrigPin, LOW);
        delayMicroseconds(2);
        digitalWrite(TrigPin, HIGH);
        delayMicroseconds(10);
        digitalWrite(TrigPin, LOW);
        float distance = pulseIn(EchoPin, HIGH) / 58.00;
        return distance;
}

现在你只需要在setup和loop中调用这两个函数,就可以完成之前的功能了:

float distance;
void setup()
{
        init_SR04(2,3);
        Serial.begin(9600);
}
void loop()
{
        distance= GetDistance (2,3);
        Serial.print(distance);
        Serial.print("cm");
        Serial.println();
        delay(1000);
}

这样设计程序后,程序的整体可读性增强了不少。这是简单的函数建立与调用,有C语言基础后,应该可以轻松掌握。

/*
通过函数实现SR04超声波模块驱动
*/
float distance;
void init_SR04(int TrigPin,int EchoPin)
{
      pinMode(TrigPin, OUTPUT);
      pinMode(EchoPin, INPUT);
}
float GetDistance (int TrigPin,int EchoPin)
{
      digitalWrite(TrigPin, LOW);
      delayMicroseconds(2);
      digitalWrite(TrigPin, HIGH);
      delayMicroseconds(10);
      digitalWrite(TrigPin, LOW);
      float distance = pulseIn(EchoPin, HIGH) / 58.0;
      return distance;
}
void setup()
{
      init_SR04(2,3);
      Serial.begin(9600);
}
void loop()
{
      distance= GetDistance (2,3);
      Serial.print(distance);
      Serial.print("cm");
      Serial.println();
      delay(1000);
}

掌握了函数的编写方法后,我们即可开始编写类库。

编写头文件和源文件

通常,一个类库中包含两种后缀的文件:.h文件,和.cpp文件。
.h文件称作头文件,其用于声明类库及其成员;.cpp文件称作源文件,其用于定义类库及其成员。

编写头文件

首先我们需要建立一个SR04.h的头文件,在SR04.h这个文件中我们要声明一个SR04超声波类。

类的声明方法如下:

class SR04 {
    public:
    private:
};

通常一个类可以包含两个部分——public和private。public中声明的函数和变量可以被外部程序所访问,而private中声明的函数和变量,只能从这个类的内部访问。

接着,根据我们的实际需求来设计这个类,SR04类的结构如图下图所示:

image-20210926111621326

它包含两个成员函数和三个成员变量。

SR04() 函数是一个与类同名的构造函数,用于初始化对象。我们需要在public中声明这个函数。声明如下:

SR04(int TrigPin,int EchoPin);

这个构造函数用来替代之前我们使用的void init_SR04(int TrigPin,int EchoPin) 函数。需要注意的是构造函数必须与类同名,且不能有返回类型。

我们还需要一个GetDistance函数来获取并处理超声波传感器返回的信息

float GetDistance();

这个函数用来替代我们之前使用的 float GetDistance (int TrigPin,int EchoPin) 函数

还有一些程序运行过程中的函数或变量,用户在使用时并不会接触到他们,我们可以将其放在private部分中声明:

完整的SR04.h代码如下:

#ifndef SR04_H
#define SR04_H
#include "Arduino.h"
class SR04 {
public:
        SR04(int TrigPin,int EchoPin);
        float GetDistance();
private:
        int Trig_pin;
        int Echo_pin;
        float distance;
};
#endif

预处理命令

以“#”开头的语句,为预处理命令。之前我们包含文件使用的**#include,及常量定义时使用的#define**均为预处理命令。
预处理命令并不是C/C++语言的组成部分,编译器不会直接对其进行编译,而是在编译前,系统会预先处理这些命令。

宏定义

如你的程序中用**#define COL 1112** 定义了一个名为COL的常量,实际在编译前,系统会将你代码中所有的COL替换为1112,再对替换后的代码进行编译。
这种定义方式称之为宏定义。即使用一个特定的标识符来代表一个字符串。其一般形式为:

#define 标识符字符串

在Arduino中,我们常用到HIGH、LOW、INPUT、OUTPUT等参数、及圆周率PI等常量都是通过宏的方式定义的。

文件包含

若程序中使用**#include包含了一个文件,例如#include <EEPROM.h>,在预处理时系统会将该命令替换成EEPROM.h**文件中的实际内容,再对替换后的代码进行编译。

文件包含命令的一般形式为:

#include<文件名>

或者

#include“文件名”

两种形式的实际效果是一样的,只是使用**<文件名>形式时,系统会优先在Arduino库文件中寻找目标文件,若没有找到,系统会再到当前Arduino项目的项目文件夹中查找;而使用“文件名”**形式时,系统会优先在你的Arduino项目文件中查找目标文件,若没有找到,再查找Arduino库文件。

条件编译

回到SR04.h,在其中,你会看到以下代码:

#ifndef SR04_H
#define SR04_H
.
.
.
#endif
#ifndef 标识符
程序端
#endif

为条件编译命令。**#ifndef SR04_H会查找标识符SR04_H是否在程序的其他位置被#define**定义过。若没有被定义过,则定义该标识符。这个写法主要为了防止重复的包含某文件,避免程序编译出错。

编写源文件

接着,我们还要建立一个SR04.cpp源文件。在SR04.cpp文件中,我们需要写出了头文件中声明的成员函数的具体实现代码。

#include "Arduino.h"
#include "SR04.h"
SR04::SR04(int TP, int EP)
{
   pinMode(TP,OUTPUT);
   pinMode(EP,INPUT);
   Trig_pin=TP;
   Echo_pin=EP;
}
float SR04::GetDistance()
{
        digitalWrite(Trig_pin, LOW);
        delayMicroseconds(2);
        digitalWrite(Trig_pin, HIGH);
        delayMicroseconds(10);
        digitalWrite(Trig_pin, LOW);
        float distance = pulseIn(Echo_pin, HIGH) / 58.00;
        return distance;
}

域操作符

我们在SR04.h文件中声明了SR04类及其成员,在SR04.cpp中定义该函数的实现方法。在类声明以外定义成员函数时,需要使用域操作符::,说明该函数作用于SR04类。

关键字高亮

一个SR04超声波类库编写完成了,但它还不是一个完美的Arduino类库,因为他没有一个可以让Arduino IDE识别并高亮关键字的keywords.txt文件,我们再建立一个keywords.txt文件,并键入以下代码:

SR04   KEYWORD1
GetDistance   KEYWORD2

需要注意的是“SR04 KEYWORD1”及“GetDistance KEYWORD2”之间的空格,应由键盘“Tab”键输入。
在Arduino IDE的关键字高亮中,会识别KEYWORD1为数据类型高亮方式,KEYWORD2为函数高亮方式。
有了keywords.txt,在Arduino IDE里使用该类库,你就能看到代码高亮效果了。
一个完整的Arduino类库就建立好了。

使用该类库,你需要在Arduino IDE安装目录下的libraries文件夹中新建一个名为SR04的文件夹,并将SR04.h、SR04.cpp、keywords.txt三个文件放入该文件夹中

img

建立示例程序

为了方便其他用户学习和使用你编写的类库,你还可以在SR04文件夹中新建一个examples文件夹,并放入你提供的示例程序,方便其他使用者学习和使用这个类库。这里,我在examples文件夹中新建了一个SR04_Example文件夹,并放入了一个SR04_Example.ino文件

SR04_Example.ino完整程序代码如下:

#include <SR04.h>

SR04 ultrasonic = SR04(2,3);
void setup()
{
  Serial.begin(9600);
}
void loop()
{
  float distance = ultrasonic.GetDistance();
  Serial.print(distance);
  Serial.print("cm");
  Serial.println();
}

至此,一个完整的Arduino类库就建立完成了,重启Arduino IDE后,在Arduino IDE菜单>文件>示例 中可以找到该示例程序。编译并下载示例程序到你的Arduino控制器,验证你的类库是否还需要修改。

类的优化

为了方便理解和学习Arduino类库的编写方法,笔者在教学中将库进行了一定简化。你可能会在使用过程中遇到一些检测出错的情况,例如检测到的距离过大或为0等。你可以对这个库进行更多的优化,使之达到更好的检测效果。

这里给出三种优化思路,大家可以自己尝试优化这个类库:
1.当检测到的距离超出了SR04超声波模块可检测的范围时(3cm-450cm),输出错误信息或者重新检测;
2.每次检测时,检测两次或者多次,将得到的值做比较,如果偏差较大,则认为是检测出错,并放弃检测结果,重新检测距离;
3.使用pulseIn(pin, value, timeout) 取代pulseIn(pin, value) 检测脉冲宽度,通过限定检测脉冲超时时间来限定超声波传感器的检测距离。

本文转自:奈何col arduino教程—-编写arduino类库