Grove Beginner Kit for Arduinoの使い方

組み込みエンジニア

こんにちは、ENGかぴです。

Grove Beginner kit for Arduinoは初心者のためのスターターキットです。温度・湿度センサーなど複数のセンサーが実装されておりArduino環境でのライブラリの使い方C言語(C++)の学習ができます。

本記事ではGrove Beginner Kit for Arduino(Seeed Studio製)を使用してDOの操作によるLEDの点灯、AD変換値の取得、OLEDに文字を表示、DIでボタンの状態を取得する方法をまとめています。

Grove Beginner kit for Arduino(以下スターターキットとします。)はArduino UNOと同じシリーズのマイコンを使用しているためArduino UNOと同じように開発することができます。Arduinoのライブラリを使用して動作確認したことをまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

開発環境を作る

スターターキットの詳細はSeeed Studioのページにまとめられています。Arduinoライブラリの説明やセンサーの使用例やPWMの考え方などが解説されています。

Grove Beginner Kit for Arduino – Seeed Wiki (seeedstudio.com)

日本語対応のページが作成されていませんが、Google Chromeのブラウザーによる変換機能を使用することで日本語でページを表示することができます。

スターターキットにはソフトがプリインストールされているためUSBを接続すると各センサーの動作を確認することができます。プリインストールされているソフトは公開されているのでいつでも初期状態に戻すことができます。

Arduino IDEのインストール

Arduinoでの開発はArduino IDEを使って行います。インストールの手順などは下記記事にまとめています。

Arduinoの開発環境の作り方とスケッチ例の使い方

VsCodeを使用してもArduino環境でのソフト開発ができます。インストールの手順などは下記記事にまとめています。

VSCodeを使ったArduinoのソフト開発環境作りの手順

Arduino IDEでは書き込みとシリアルモニターを使用し、ソースコードをVsCodeで開発するなど使い分けることもできます。

ライブラリの追加

タイマ管理で使用するMsTimer2ライブラリとOLEDの表示を行うU8g2ライブラリは標準ライブラリとして搭載されていないためライブラリマネージャーで追加します。

ライブラリマネージャーでMstimer2を追加する
ライブラリマネージャーでMstimer2を追加する

Arduino IDEでライブラリの追加を行います。ライブラリマネージャーを開いて検索欄に「mstimer2」のライブラリ名を入力するとライブラリの候補が表示されます。候補の中から「MsTimer2」をインストールします。

OLEDに文字を表示するためにOLED用のライブラリを追加します。Seeed Wikiで紹介されている「U8g2」ライブラリを追加します。

ライブラリマネージャからu8g2ライブラリーを追加
ライブラリマネージャからU8g2ライブラリーを追加

Arduino IDEのツール内のライブラリを管理を選択するとライブラリマネージャ画面が表示されます。検索をフィルタに「u8g2」(大文字小文字はどちらでもよい)を入力すると表示される「U8g2」が候補として表示されます。インストールボタンを押すとライブラリが追加されます。

(即戦力のスキルを身に着ける:DMM WEBCAMP 学習コース(はじめてのプログラミングコース))

ライブラリの準備と初期化

#include <U8g2lib.h>

//U8X8_SSD1306_128X64_NONAME_SW_I2C型の変数を宣言
U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R2, /* reset=*/U8X8_PIN_NONE);

void setup() {
  u8g2.begin(); //初期化
}

U8g2ライブラリを使用するためU8x8lib.hをインクルードします。

クラス宣言する型は対象のOLEDによって異なりますがスターターキットはSSD1306が使用されているのでU8G2_SSD1306_128X64_NONAME_1_HW_I2Cのオブジェクト宣言としてu8g2をインスタンス化(変数を準備する操作できるようにする)します。u8g2に引数を指定して表示方向とリセットピンなどを指定します。

第1引数はU8G2_R0~U8G2_R3で表示方向を指定します。R0はスターターキットのシルク印刷(基板の白い文字)に合わせた向きを基準にすると180度回転した表示になります。R1はR0を90度回転、R2はR1を90度回転、R3はR2を90度回転した表示になります。U8G2_R2を指定するとスターターキットのシルク印刷と同じ向きでのOLEDの表示になります。

第2引数以降はリセットピンを使用する場合やクロックを指定する場合に使用しますが、特に操作しない場合はピンを使用しない設定が反映されるため指定する必要がありません。例ではリセットのピンを使用しない設定を指定しています。

begin()関数を使用するとOLEDの初期化が行われ文字を表示する準備ができます。

#include <MsTimer2.h>

void setup() {

  MsTimer2::set(10, mainTimer); // 10ms period
  MsTimer2::start();
}
/* タイマ管理(timer2がタイムアップするとコールされる) */
void mainTimer(void){
 //処理する内容を入れる
}

MsTimer2はマイコンのタイマで割り込みを任意タイミング(ms)で設定することができ、割り込み発生時にコールバック関数を指定できるライブラリです。MsTimer2ライブラリを使用するためMsTimer2.hをインクルードします。

MsTimer2のネームスペースで定義されたクラスのset()関数でタイマ割り込みが発生するタイミングとコールバックする関数を指定します。

第1引数にms単位で時間を指定します。第2引数にコールする関数名を指定します。例では10ms毎にmainTimer()がコールされます。start()関数でタイマーがスタートして割り込みが発生します。

スポンサーリンク

ライブラリの使用例(U8g2)

void anaShow(void){
  uint16_t bar;

  u8g2.firstPage(); //バッファをクリア
  do{
    u8g2.setFont(u8g2_font_t0_16_mr);
    u8g2.setCursor(20, 32);
    u8g2.print("Analog:");
    u8g2.setCursor(80, 32);
    u8g2.print(diData.ad0);
    u8g2.drawHLine(10, 48, 100);
    bar = diData.ad0 /10 + 10;
    u8g2.drawDisc(bar,48,3,U8G2_DRAW_ALL);
  }while(u8g2.nextPage());
}

OLEDに文字を表示する方法を説明します。firstPage()関数で内部のバッファをクリアします。setCursor()関数でOLEDの表示位置にカーソルを合わせます。

第1引数にX座標を指定します。X座標は画面左から右に向かって進みます。第2引数にY座標を指定します。Y座標は画面上から下に向かって進みます。

print()関数は引数に指定した文字列を表示します。変数を引数に指定した場合は引数の値を文字列に変換して表示します。

点と線をOLEDに表示する方法は使用する関数によって複数通りありますが、私がよく使用する関数を紹介します。

関数説明
drawLine(X1, Y1, X2, Y2)X1, Y1の座標からX2, Y2の座標まで線を引く
drawHLine(X, Y, W)X, Y座標の位置からX方向にWで指定した幅の線を引く
drawVLine(X, Y, H)X, Y座標の位置からY方向にHで指定した高さの線を引く
drawHVLine(X, Y, len, dir)X, Y座標の位置からlenの長さの線をdirで指定した方向に引く
dirは0でX側, 1でY側, 2で-X側, 3で-Y側
drawCircle(X, Y, rad, opt)
drawDisc(X, Y, rad, opt)
X, Y座標の位置からradで指定したサイズ円をoptに従って書くoptは以下の通り
U8G2_DRAW_UPPER_RIGHT(0x01) は右上の1/4円
U8G2_DRAW_UPPER_LEFT(0x02) は左上の1/4円
U8G2_DRAW_LOWER_LEFT(0x04) は左下の1/4円
U8G2_DRAW_LOWER_RIGHT(0x08) は右下の1/4円
U8G2_DRAW_ALL(0x0F) は円全体
Discは円を塗りつぶす場合に使用する
u8g2_DrawEllipse(X, Y, rx, ry ,opt)
u8g2_DrawFilledEllipse(X, Y, rx, ry ,opt)
X, Y座標の位置からrxで指定した幅とryで指定した高さの楕円を書くoptは上記のoptと同じ
DrawFilledEllipseは楕円を塗りつぶす場合に使用する
点と線をOLEDに表示する関数

例ではdrawDisc()関数を使用して円を書いています。AD変換値をX座標に指定することで円が左右に動くように表示することができます。円は全体を表示するためU8G2_DRAW_ALLで指定しています。

nextPage()関数で行データごとのデータを更新してOLEDの表示を作りますが、ウェイトが必要となるためdo while()で表示が終えるまでループさせています。本記事では約120ms必要でした。OLEDの表示は遅延が発生するため頻繁な更新はしないように調整が必要です。

ライブラリの使用例(analogRead)

diData.adbuf[diData.adwp] = analogRead(PIN_AD0);

if( ++diData.adwp >= ADBUF_MAX){ //データの格納先を更新
  diData.adwp = 0;
}

sum=0;
for( i=0; i < ADBUF_MAX; i++){ //データの合計を計算
  sum += diData.adbuf[i];
}
diData.ad0 = sum /ADBUF_MAX; //平均を計算

AD変換値を取得するライブラリは標準ライブラリなのでインクルードの必要はありません。AD変換値の取得はanalogRead()関数を使用します。引数にデータを取得するアナログピンの番号を指定します。戻り値がAD変換値になるので変数に格納して使用します。

例では取得したデータを保管する場所を更新しながら貯めていき平均値を計算した結果をAD変換値として採用しています。

アナログデータはノイズの影響を受けることがあるため複数回取得したデータの平均値をとる(ローパスフィルタの効果)ことでノイズによるデータのふらつきを防止する効果があります。

スポンサーリンク

動作確認

動作確認の様子
動作確認の様子

電源を入れるとOLEDにBlink: にBlinkのパターン及びAnalog: にAD変換値を表示します。ボタンを押すとBlinkモードに応じたパターンでLEDをBlink(点灯/消灯)を開始します。LEDがBlinkしている状態でボタンを押すとBlinkモードを0にしてLEDの点灯を停止します。

可変抵抗を反時計回りに操作するとAD変換値の値が大きくなりOLEDの●表示が画面右に進みます。Analog値が250進む毎にBlinkのパターンを+1します。

可変抵抗をAnalog: が大きくなる方向に操作すると●表示が右側に進みLEDのBlinkタイミングが遅くなることが確認できました。次に可変抵抗をAnalog: が小さくなる方向に操作すると●表示が左側に進みLEDのBlinkタイミングが速くなることが確認できました。

Grove Beginner Kit for ArduinoはLEDやその他センサーなどの周辺回路を検討しなくてもUSBに接続するだけでArduino環境に慣れることができ、C言語(C++)などの練習に使えるため良いスターターキットだと感じています。

ソースコード全体

以下のソースコードはコンパイルして動作確認をしております。コメントなど細かな部分で間違っていたりやライブラリの更新などにより動作しなくなったりする可能性があります。参考としてお使いいただければと思います。

#include <U8g2lib.h>
#include <MsTimer2.h>

#define DIFILT_MAX 4
#define ADBUF_MAX 8
#define TIME_UP 0
#define TIME_OFF -1
#define TIM_DIFILT 1
#define TIM_LEDFL_BASE 20
#define TIM_OLED 20
#define PIN_DI1 DD6
#define PIN_DO1 DD4
#define PIN_AD0 PIN_A0
#define BLINK_DIV 250

struct DIFILT{
  uint8_t wp;
  uint8_t buf[DIFILT_MAX];
  uint8_t di4;
  bool  di4flg;
  uint8_t adwp;
  uint16_t adbuf[ADBUF_MAX];
  uint16_t ad0;
};

U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2( U8G2_R2);
int16_t timDifilter = TIME_OFF;
int16_t timLedFl = TIME_OFF;
int16_t timoled;
DIFILT diData;
uint8_t btncnt;
uint8_t blink;

/* Local function prototypes */
void mainTimer(void);
void DiFilter(void);
void mainApp(void);
void anaShow(void);

void setup() {
  uint8_t i;

  Serial.begin(115200);

  pinMode(PIN_DI1, INPUT);
  pinMode(PIN_DO1, OUTPUT);
  digitalWrite(PIN_DO1,LOW);

  i=0;
  while( i < 10){
    DiFilter();
    delay(10);
    i++;
    timDifilter = TIME_UP;
  }

  u8g2.begin();
  MsTimer2::set(10, mainTimer); // 10ms period
  MsTimer2::start();
}

void loop() {

  DiFilter();
  mainApp();
}

/*メイン処理*/
void mainApp(void){

  if( diData.di4 == 1 ){
    if( diData.di4flg == false ){
      diData.di4flg = true;
      
      if( ++btncnt % 2 ){
        timLedFl = TIME_UP;
      }
      else{
        timLedFl = TIME_OFF;
        digitalWrite(PIN_DO1,LOW);
        blink = 0;
      }
      Serial.print("Button cnt:");
      Serial.println(btncnt);
    }
  }
  else{
    diData.di4flg = false;
  }

  if( timLedFl == TIME_UP){
    blink = diData.ad0 / BLINK_DIV + 1;
    timLedFl = TIM_LEDFL_BASE * blink;
    digitalWrite(PIN_DO1,!digitalRead(PIN_DO1));
  }

  if( timoled == TIME_UP){
    timoled = TIM_OLED;
    anaShow();
  }
}
/* OLED表示*/
void anaShow(void){
  uint16_t bar;

  u8g2.firstPage(); //バッファをクリア
  do{
    u8g2.setFont(u8g2_font_t0_16_mr);
    u8g2.setCursor(20, 16);
    u8g2.print("Blink:");
    u8g2.setCursor(80, 16);
    u8g2.print(blink);
    u8g2.setCursor(20, 32);
    u8g2.print("Analog:");
    u8g2.setCursor(80, 32);
    u8g2.print(diData.ad0);
    u8g2.drawHLine(10, 48, 100);
    bar = diData.ad0 /10 + 10;
    u8g2.drawDisc(bar,48,3,U8G2_DRAW_ALL);
  }while(u8g2.nextPage());
}

/* タイマ管理(timer2がタイムアップするとコールされる) */
void mainTimer(void){

  if( timDifilter > TIME_UP ){
    --timDifilter;
  }

  if( timLedFl > TIME_UP ){
    --timLedFl;
  }

  if( timoled > TIME_UP ){
    --timoled;
  }
}

/* DIフィルタ */
void DiFilter(void){
  bool boo = true;
  uint8_t i;
  uint16_t sum;

  if( timDifilter == TIME_UP ){
    timDifilter = TIM_DIFILT;

    diData.buf[diData.wp] = digitalRead(PIN_DI1);

    for( i=1; i < sizeof(diData.buf);i++){
      if( diData.buf[i - 1] != diData.buf[i]){
        boo = false;
      }
    }

    if(boo){ //データがすべて一致なので採用する
      diData.di4 = diData.buf[0];
    }
    if( ++diData.wp >= sizeof(diData.buf)){
      diData.wp = 0;
    }

    diData.adbuf[diData.adwp] = analogRead(PIN_AD0);

    if( ++diData.adwp >= ADBUF_MAX){
      diData.adwp = 0;
    }

    sum=0;
    for( i=0; i < ADBUF_MAX; i++){
      sum += diData.adbuf[i];
    }
    diData.ad0 = sum /ADBUF_MAX;
  }
}

関連リンク

Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方

ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方

エンジニア転職なら100%自社内開発求人に強い【クラウドリンク】

最後まで、読んでいただきありがとうございました。

タイトルとURLをコピーしました