[MBED] ライブラリの作り方
ライブラリをインポートして使ったことがある人はいると思いますが,自分で作れるのはご存知でしょうか
今回は,自分でライブラリを作る方法をご紹介したいと思います.
[ 注意 ] この記事は,mbed compilerを使った記事になります.mbed compilerとは,2022/12月末くらいまで使われていたオンラインのMBED開発環境です.今はおそらくkeil studioかmbed cliとかを使用しているかと思います.そのせいでところどころ違うかもしれません.
ライブラリとは
ライブラリとは,乱暴な言い方をすると,関数の進化版みたいなやつです.
複数の処理を一つにまとめたものを関数といいますが,複数の関数と変数を一つにまとめたものをクラスといいます.ライブラリとは,このクラスを記述したファイルのことです.
ライブラリの大まかな作り方
ライブラリは,拡張子が.hになっているヘッダファイルと,拡張子が.cppになっているソースファイルの2つのファイルで構成されます.
.h(ヘッダファイル)で書くこと
・関数のプロトタイプ宣言
プロトタイプ宣言とは,関数の宣言です.中身の処理は宣言せずに,戻り値の変数型,引数の変数型と変数名だけを宣言します.
・変数の宣言
・使用するピンの宣言
.cpp(ソースファイル)で書くこと
・実際の関数の中身
実は,ここの部分は.hファイルに書くことも出来ます.そうするとファイルの数を少なくできます.しかし,関数が多いとコードが長くなるので,コードの規模に応じて使い分けましょう.基本はここに関数の中身は書くと思ってもらっていいです.
ライブラリの具体的な作り方
具体的に,.hファイルと.cppファイルの書き方を見ていこうと思います.ここでは,サーボモータを動かすライブラリを例に見ていきたいと思います.サーボモータとは,モータの一種で,pwm信号によって(実は,pwm信号の代わりに通信を使うものもある)モータの「位置」や「速度」を指令し,その指令どおりにモータが動くというものです.ロボコンで使うのは「位置」を指令するものがほとんどです.最初に,全体のコードを下に載せておきます.
#ifndef servo_H
#define servo_H
#include "mbed.h"
class servo
{
private:
//ピン
PwmOut _PIN;
//サーボの現在角度
int theta;
//角度をパルス幅に変換
float ThetatoPulse(int angle);
public:
//ピンセット
servo(PinName PIN);
//サーボの上限角度
int theta_upper;
//制御信号のパルス幅の幅←ややこしっ
float pulse_lower, pulse_upper;
//制御信号の周期を決める
void Period(float period);
//サーボを動かす
void Position(int angle);
//現在の角度を返す
int Get_angle(void);
};
#endif
#include "servo.h"
servo::servo(PinName PIN) : _PIN(PIN)
{
theta = 0;
theta_upper = 180;
pulse_lower = 0.001;
pulse_upper = 0.002;
_PIN.period(0.02);
}
//角度をパルス幅に変換
float servo::ThetatoPulse(int angle)
{
return (pulse_lower + ((pulse_upper - pulse_lower) * ((float)angle / (float)theta_upper)));
}
//サーボを動かす
void servo::Position(int angle)
{
theta = angle;
_PIN.pulsewidth(servo::ThetatoPulse(theta));
}
void servo::Period(float period)
{
_PIN.period(period);
}
//現在の角度を返す
int servo::Get_angle(void)
{
return theta;
}
ファイルを作ろう
まず,プログラムを書き込むファイルを作成します.
新しくプロジェクトを作ります.(プロジェクトの作り方は割愛)
プロジェクトを右クリックして,「新しいライブラリ」をクリック→ライブラリ名を入力して「OK」
ライブラリを右クリックして,「新しいファイル」をクリック→ライブラリ名.hとなるようにファイル名を入力して「OK」
ライブラリを右クリックして,「新しいファイル」をクリック→ライブラリ名.cppとなるようにファイル名を入力して「OK」
これでファイルが完成です.
.hファイルの書き方
最初に,.hファイルの作り方を説明します.
まず,最初に以下の文を書きましょう.おまじないだと思ってもらって.
#ifndef servo_H
#define servo_H
#include "mbed.h"
class servo
{
private:
publilc:
};
#endif
servo_Hとclass servoのところは,ライブラリの名前によって適宜変えてくださいな.
次に,コンストラクタを宣言します.コンストラクタとは,ライブラリを呼び出すときに一番最初に呼び出される関数になります.例えば,DigitalInピンを宣言するときに,
DigitalIn myled(PA_6);
のような感じでプログラムを記述すると思いますが,これは実は,DigitalInというクラスの中の,DigitalIn()という名前のコンストラクタを呼び出しています.
.hファイルにコンストラクタを追加していきましょう.
#ifndef servo_H
#define servo_H
#include "mbed.h"
class servo
{
private:
publilc:
servo(PinName PIN);
};
#endif
コンストラクタとライブラリの名前は,原則同じにしましょう.この時に,()の中身に書いてあるPinName PINというのは,PinNameというデータ型の,PINという変数を引数に取りますよという意味です.なので,()の中には何も書かずに
servo();
という感じで宣言してもOKです.コンストラクタも普通の関数と同じようなものなので,引数を宣言するかしないかは自由に決められます.
注意する点として,コンストラクタには戻り値がありません.つまり,普通の関数なら
void servo(PinName PIN);
という風に書きますが,コンストラクタは戻り値が存在しないので,
servo(PinName PIN);
となります.
次に,クラスの中に変数と関数を宣言します.この時に,privateとpublic,どちらに書くかが大事になります.
ライブラリは,最終的にほかのプログラムに使ってもらうためにあります.ライブラリを使う側のプログラムを親プログラムとしましょう.変数や関数をprivateの方に書くと,親プログラムからはその変数や関数は見えなくなります.見えないというのは,関数なら呼び出せない,変数なら読み書きができないという意味です.逆に,publicの方に書かれた変数や関数は,親プログラムから「見え」ます.
親プログラムから「見える」必要があるのかどうかに応じて変数や関数を宣言する場所を選びます.
例として,次の関数を考えてみましょう.
float ThetatoPulse(int angle);
この関数は,角度を入力すると,サーボをその角度にするためにサーボに入力するべき信号のパルス幅を出力してくれます(詳しい話は省きます).今回示したライブラリは,サーボを簡単に動かすことを目的としているので,今説明した計算などもライブラリの中で完結していて欲しいですね.よって,この関数はprivateの方に追加します.
#ifndef servo_H
#define servo_H
#include "mbed.h"
class servo
{
private:
float ThetatoPulse(int angle);
publilc:
servo(PinName PIN);
};
#endif
次に,以下の変数を見てみます.
float pulse_lower, pulse_upper;
この変数は,サーボモータに入力するpwm信号のパルス幅の上限と下限を決める変数です.
この値は,使用するサーボによって変える必要があります.privateの方にこの変数を書くと,サーボに応じてライブラリのこの値を書き換える必要が出てきます.ライブラリは,一回作った後は,書き換えずにどのプログラムからも使えるようにするのが望ましいです.なので,この変数はpublicに追加します.
#ifndef servo_H
#define servo_H
#include "mbed.h"
class servo
{
private:
float ThetatoPulse(int angle);
publilc:
servo(PinName PIN);
float pulse_lower, pulse_upper;
};
#endif
こんな感じで,どんどん変数や関数を追加していきます.
#ifndef servo_H
#define servo_H
#include "mbed.h"
class servo
{
private:
//ピン
PwmOut _PIN;
//サーボの現在角度
int theta;
//角度をパルス幅に変換
float ThetatoPulse(int angle);
public:
//ピンセット
servo(PinName PIN);
//サーボの上限角度
int theta_upper;
//制御信号のパルス幅の幅←ややこしっ
float pulse_lower, pulse_upper;
//制御信号の周期を決める
void Period(float period);
//サーボを動かす
void Position(int angle);
//現在の角度を返す
int Get_angle(void);
};
#endif
注意して欲しいのは,ピンを宣言する時にはピン名は書かないでください.書いちゃうと,そのピンが存在するマイコンでしかライブラリが動かなくなったり,親プログラムでそのピンが使えなくなったり,いろいろ不便です.ピン名は,親プログラムから指定できるようにしましょう.
.cppファイルの書き方
次に,.cppファイルの書き方を説明します.
最初に,次のコードを書きましょう.これもおまじないだと思って.
#include "servo.h"
servo::servo(PinName PIN) : _PIN(PIN)
{
}
float servo::ThetatoPulse(int angle)
{
}
void servo::Position(int angle)
{
}
void servo::Period(float period)
{
}
int servo::Get_angle(void)
{
}
これは何を書いているのかというと,
戻り値型 ライブラリ名::関数名(引数型 引数名)
{
}
を書いてます.関数がある分だけ書きます.
少し変わっているのが,
servo::servo(PinName PIN) : _PIN(PIN)
{
}
ですね.右側に何か変なもんがついてます.
: _PIN(PIN)
これです.
これは,実は自分もちゃんとは理解してないですが,
.hファイルのほうに
PwmOut _PIN
という記述をしました.この時点では使うピンをまだ決めていないのですが,さっきの箇所で
: _PIN(PIN)
と記述したことにより,この関数の引数に入ってきたPINを,さっき宣言した_PINにしますよ,という処理をしています.
クリリンのおでこみたいなのがありますね.「 : : 」です.これは,「の中の」という意味だと解釈してください.例えば,
servo::servo(PinName PIN) : _PIN(PIN)
は,「servoの中のservo」つまり、「servoライブラリの中のservo関数」という意味になります.
ここまでで,おまじないの説明は終わりです.おまじないって言いながら説明してしまった.
ここからは,関数の中身を書いていきます.基本的に普通の関数と書き方は同じなので,ここはとばしていきます.
#include "servo.h"
servo::servo(PinName PIN) : _PIN(PIN)
{
theta = 0;
theta_upper = 180;
pulse_lower = 0.001;
pulse_upper = 0.002;
_PIN.period(0.02);
}
//角度をパルス幅に変換
float servo::ThetatoPulse(int angle)
{
return (pulse_lower + ((pulse_upper - pulse_lower) * ((float)angle / (float)theta_upper)));
}
//サーボを動かす
void servo::Position(int angle)
{
theta = angle;
_PIN.pulsewidth(servo::ThetatoPulse(theta));
}
void servo::Period(float period)
{
_PIN.period(period);
}
//現在の角度を返す
int servo::Get_angle(void)
{
return theta;
}
これでライブラリのコードは完成です.お疲れ様でした.
ライブラリの使い方
作ったライブラリの使い方を説明していきます.
サンプルプログラムをおいておきます.
#include "mbed.h"
#include "servo.h" //ライブラリを読み込みます
servo servo1(PB_5_ALT0); //コンストラクタを呼び出す (^^)/ オーイ コンストラクタ~
//void Set(int angle, float lower, float upper, float period)
//angle..可動域 lower..パルス幅下限 upper..パルス幅上限 period..駆動周波数 を設定
//void Position(int angle)
//指定した角度にもっていく(0~可動域)
//int Get_angle(void)
//サーボの角度を得る(0~可動域)
Serial PC(USBTX, USBRX);
int main(void)
{
PC.printf("Program Written.");
//ライブラリの中のpublicに書いた変数をいじってます (^^)\\イジイジ
servo1.pulse_lower = 0.0005;
servo1.pulse_upper = 0.0025;
servo1.theta_upper = 180;
//ライブラリの中のpublicに書いた関数を呼び出してます (^^)/ オーイ
servo1.Period(0.02); //これはpwm周期を変える関数
int i;
//ライブラリの中のpublicに書いた関数を呼び出してます (^^)/ オーイ
servo1.Position(0); //これはサーボに位置を指令する関数
//ループ文だよ
while(1){
//一秒ごとにサーボを0度とか45度にしてますよ~
servo1.Position(0);
wait(1.0);
servo1.Position(45);
wait(1.0);
}
}
プログラムの説明は,プログラムのコメントとかを読むと分かるかな,と思います.
最後に
ライブラリの作り方の説明は以上です.
関数などの使い方が分かっていないと,ちょっと分かりにくかったかもしれないですね.
ライブラリを作ると,メインのプログラムを分かりやすく,短くできるのに加えて,複数人での開発をする際にも,分担が楽になります.
ただ,ライブラリを増やしすぎると,全体のプログラムは煩雑になってしますので,それはコードの規模によって,どこまでライブラリ化するかは考える必要があります.
以上です!最後まで読んでくれてありがとうございました.