Seeeduino XIAO(Arduinoと互換)の開発環境を作る

組み込みエンジニア

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

Seeeduino XIAOはArduino環境で開発できるARMマイコンを搭載したモジュールです。小サイズなので使用できるピン数はArduino UNOよりも少ないですがArduinoのライブラリを使ってソフト開発ができます。

Seeeduino XIAOの開発環境をArduino IDEに組み込む方法を説明しています。また動作確認用のスケッチとしてタイマ管理でDIのチャタリング防止しながらLEDの点灯消灯のタイミングを切り替えるソフトを作りました。

Seeeduino XIAOで動作確認したことについてリンクをまとめています。

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

Seeeduino XIAOの開発環境を作る

Seeeduino XIAOの開発環境はArduino IDEと同じですが、前提条件としてArduino IDEをインストールしておく必要があります。Arduino IDEがすでにインストールできている場合はボードマネージャーのURLを追加すると使用できるようになります。以降の手順の説明はWindows10を対象としています。

Arduino IDEのインストール

Arduinoの開発環境であるArduino IDEをインストールするまでの手順とスケッチ例を使って開発を行う手順について下記リンクにまとめています。

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

ボードマネージャーのURLの追加とインストールの手順

Seeeduino  XIAO公式Wikiに記載されているボードマネージャーのURL
Seeeduino XIAO公式Wikiに記載されているボードマネージャーのURL

Seeeduino XIAOの開発環境を追加していきます。Seeeduinoのサイト(Seeedウィキ)にボードマネージャに追加するURLが準備されています。

Seeeduino Xiaoをはじめよう – Seeedウィキ(日本語版) (seeedstudio.com)

SeeedウィキではSeeeduino XIAOについてハードウェアやソフトウェアの情報やピン配置などの情報が記載されています。

ボードマネージャの追加までの下記の手順の通りなので難しくはありません。Arduino IDEを起動してファイル欄から「環境設定」を選択すると設定項目が表示されます。

環境設定の追加のボードマネージャーのURLにURLを追加する
環境設定の追加のボードマネージャーのURLにURLを追加する

環境設定内の「追加のボードマネージャのURL」にseeedウィキに記載されているURLを貼り付けて追加します。追加後はOKを押して決定します。

次にArduino IDEのツールからボード:XXXX(XXXXは現在選択しているボード)を選択して現れるタブからボードマネージャをクリックするとボードマネージャを検索できる画面に遷移します。

ボードマネージャでSeeed SAMD Boardsを選択してインストール
ボードマネージャでSeeed SAMD Boardsを選択してインストール

ボードマネージャで検索欄に「Seeeduino」と入力するとSeeed SAMD Boardsが候補として表示されます。(URLを追加していないと表示されません)このボードマネージャをインストールします。

インストールの途中でUSBドライバーのインストールについて障害を起こす可能性があるという警告からUSBドライバーのインストールができない場合があるようです。インストールができなかった場合は手動でインストールすることができます。

USBドライバーを手動でインストールする場合
USBドライバーを手動でインストールする場合

SeeeduinoのUSBドライバはユーザーのフォルダ内のAppDataの階層を下っていくと見つけることができます。

通常のフォルダーの設定では隠しファイルは表示しないようになっているためAppDataフォルダを表示するためにフォルダーの表示タブを選択し隠しファイルにチェックを入れる必要があります。

AppDataフォルダが表示されたら下記のように階層を下ります。

C:\Users\XXXX\AppData\Local\Arduino15\packages\Seeeduino\hardware\samd\1.8.1\drivers

「dpinst-amd64.exe」を起動するとUSBドライバーがインストールされます。dpinst-x86は64bitOS以外対応のドライバーになります。インストールしてUSB接続すると「COM6(Seeeduino XIAO)」のようなCOMポートが表示されると成功です。

ボードの選択とシリアルポートの設定

インストールが完了したらボードの選択を行います。

ボードの選択ーSeeeduino XIAOを選択
ボードの選択ーSeeeduino XIAOを選択

ボードを選択した後はシリアルポートの選択を行います。

Seeeduino XIAO専用のシリアルポートの選択
Seeeduino XIAO専用のシリアルポートの選択

シリアルポートを選択して接続ができるとマイコンボードへのプログラムの書き込みができるようになります。

Seeeduino XIAOの動作を確認

Seeeduino XIAOの動作を確認します。下記リンクでタイマ管理を行ったプログラムをSeeeduino XIAOで使用できるように一部修正して動作を確認します。

Arduino環境でのタイマ管理とDIのチャタリング防止の方法

動作確認の回路図

Seeeduino XIAOの動作確認の回路図
Seeeduino XIAOの動作確認の回路図

Seeeduinoのピン配置などの情報はseeedウィキを参照してください。タイマ管理でLEDを点灯消灯させ、SW1を押すと点灯タイミングが500ms毎に点灯消灯が切り替わりSW2を押すと500ms点灯後1000ms消灯するように点灯消灯のタイミングが変化するスケッチを作りました。

シリアルモニタにはSW1が押された時「di1–ok」を表示しSW2が押された時は「di2-ok」を表示するようにしています。

タイマの管理

Arduino UNOではタイマ割り込みを管理するようなライブラリが標準では搭載されていない(私が知らないだけかもしれません)ためMsTimer2のライブラリをインストールして使用していましたが、Seeeduino XIAOでは標準ライブラリとしてTimerTC3.hが搭載されています。このライブラリを使用してタイマ管理を行います。

#include <TimerTC3.h>

void setup() {

    TimerTc3.initialize(1000); //1000us
    TimerTc3.attachInterrupt(TimerCnt); //タイマ経過後に発生する割り込み関数を指定
}
/* callback function add */
void TimerCnt(){
    ++cnt10ms;
}

タイマのライブラリ「TimerTC3.h」をインクルードしてsetup()関数内のinitialize()で割り込みの周期をattachInterrupt()で割り込み発生時にコールバックする関数を指定しています。この設定によりTimerCnt()関数が1000us(1ms)毎にコールされます。

#define BASE_CNT 10 //10msがベースタイマとなる

/* Timer Management function add */
void mainTimer(){

    if( cnt10ms >= BASE_CNT ){
        cnt10ms -=BASE_CNT; //10msごとにここに遷移する
        if( timled > TIME_UP ){
            timled--; //timledを10msに一度カウント更新
        }
    }
}

TimerCnt()関数内でカウントしたcnt10msが規定回数以上(BASE_CNT)になると内部の処理を行うため10ms毎にタイマの変数を更新する処理となります。

例えばtimledに50をセットしていた場合timledが0になるまでは10ms毎にカウント更新を50回行うため500ms経過したことになります。

細かく時間管理したい場合は1msの割り込みで管理した方が良いとも言えますが、割り込みが多くなるため他の要因でも割り込みを使用していた場合割り込みの渋滞の要因にもつながるため注意が必要です。今回はcnt10msを更新しているだけなので問題になる可能性は低いと思います。

チャタリング防止

ボタンやリレー接点をON/OFFするとデジタル信号が安定せず不安定な状態となる区間があります。この区間ではデジタル信号の1と0が頻繁に切り替わってしまうチャタリングが発生します。

チャタリングによる誤動作を防止するためにはデジタル信号が安定したときにDI情報を取得するようなフィルタリング処理が必要になります。

チャタリングによる誤動作防止のため、DI信号が安定したとみなせる区間になった場合デジタル値を取得するようにします。

/* DiFilter function add */
void DiFilter(){

    if( timdifilt == TIME_UP ){
        difilt.buf[DI1][difilt.wp] = digitalRead(PIN_DI1);
   
        if( difilt.buf[DI1][0] == difilt.buf[DI1][1] &&
            difilt.buf[DI1][1] == difilt.buf[DI1][2] &&
            difilt.buf[DI1][2] == difilt.buf[DI1][3] ){ //4回一致を確認
                difilt.di1 = difilt.buf[DI1][0];
        }
 
        if( ++difilt.wp >= DI_FILT_MAX ){ //次回の保存先を更新 
            difilt.wp = 0;
        }
        timdifilt = FILT_MIN;
    }
}

例ではタイマ管理しながら10ms毎にDiFilter()内の処理を行い4回値が一致した場合に値を採用するようにしています。一般的にチャタリングは数十ms程度なので10ms毎に4回一致のフィルタでも対策になることが多いです。

例では10ms毎に状態確認を行い4回一致で安定したと判断するフィルタとしているため応答速度が40ms以上必要になります。

応答速度を高速にしたい場合は1ms毎に8回一致にするなど誤動作にならない程度に調整しながら任意のフィルタを構成してもよいでしょう。結局のところ不安定な区間が判定することが目的なので要求されている応答速度に合わせてフィルタを構成するとよいでしょう。

動作確認

動作結果
動作結果

シリアルモニタを開きSW1を押すと「di1–ok」が表示され、LEDの点灯/消灯のタイミングが500ms程度であることが確認できました。SW2を押すと「di2–ok」が表示されLEDの消灯の時間の方が長くなったことが確認できました。

Seeeduino XIAOにおいてもArduino UNOのようにライブラリを使用しながら簡単に動作確認できます。また500円ほどと安価にもかかわらず各種センサーの動作確認ができることも良い点だと思います。

Seeeduino XIAOは標準の電源がDC3.3Vであり最近のセンサーはDC3.3V仕様のものが多いこともあるので組み合わせやすいことも利点だと思います。

ソースコード全体

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

#include <TimerTC3.h>

#define TIME_UP 0
#define TIME_OFF -1
#define BASE_CNT 10 //10msがベースタイマとなる
#define LED_ONOFF 50
#define LED_ONOFF2 100
#define FILT_MIN 1

#define PIN_DO1 0
#define PIN_DI1 2
#define PIN_DI2 3
#define DI_FILT_MAX 4

enum DI_NO{
    DI1 = 0,
    DI2,
    DI_MAX
};

struct DIFILT_TYP{
    uint8_t wp;
    uint8_t buf[DI_MAX][DI_FILT_MAX];
    uint8_t di1;
    uint8_t di2;
};

// application use
int8_t timled = LED_ONOFF;
int8_t timled2 = TIME_OFF;
int8_t timdifilt = TIME_OFF;
uint8_t timmax=LED_ONOFF;
int8_t cnt10ms;
bool btnflg1;
bool btnflg2;
DIFILT_TYP difilt;

/*** Local function prototypes */
void TimerCnt();
void mainTimer();
void DiFilter();

void setup() {
    // put your setup code here, to run once:
    pinMode( PIN_DO1, OUTPUT);
    pinMode( PIN_DI1, INPUT_PULLUP );
    pinMode( PIN_DI2, INPUT_PULLUP );
  
    TimerTc3.initialize(1000);
    TimerTc3.attachInterrupt(TimerCnt);
    Serial.begin(115200);
    timdifilt = FILT_MIN;

    for( uint8_t i=0; i < 10; i++ ){
        mainTimer();
        DiFilter();
        delay(10);
    }
}

void loop() {
 
    mainTimer();
    DiFilter();

    if( timled == TIME_UP ){ //timled2と交互に使用してLEDをONOFFしている
        timled = TIME_OFF;
        timled2 = LED_ONOFF; //LED_ONOFF2を長くするとON期間が長くなる
        digitalWrite(PIN_DO1, HIGH);
    }
    if( timled2 == TIME_UP ){ 
        timled2 = TIME_OFF;
        timled = timmax; //LED_ONOFFを長くするとOFF期間が長くなる
        digitalWrite(PIN_DO1, LOW);
    }

    if(difilt.di1 == 0){
        if(btnflg1){
            btnflg1 = false;
            Serial.println("di1--ok");
            timmax = LED_ONOFF;
       }
    }else{
        btnflg1 = true;
    }
  
    if(difilt.di2 == 0){
        if(btnflg2){
            btnflg2 = false;
            Serial.println("di2--ok");
            timmax = LED_ONOFF2;
       }
    }else{
        btnflg2 = true;
    }
}
/* callback function add */
void TimerCnt(){
    ++cnt10ms;
}
/* Timer Management function add */
void mainTimer(){

    if( cnt10ms >= BASE_CNT ){
        cnt10ms -=BASE_CNT; //10msごとにここに遷移する
        if( timled > TIME_UP ){
            timled--;
        }
        if( timled2 > TIME_UP ){
            timled2--;
        }
        if( timdifilt > TIME_UP ){
            timdifilt--;
        }
    }
}
/* DiFilter function add */
void DiFilter(){

    if( timdifilt == TIME_UP ){
        difilt.buf[DI1][difilt.wp] = digitalRead(PIN_DI1);
        difilt.buf[DI2][difilt.wp] = digitalRead(PIN_DI2);
    
        if( difilt.buf[DI1][0] == difilt.buf[DI1][1] &&
            difilt.buf[DI1][1] == difilt.buf[DI1][2] &&
            difilt.buf[DI1][2] == difilt.buf[DI1][3] ){ //4回一致を確認
            difilt.di1 = difilt.buf[DI1][0];
        }
    
        if( difilt.buf[DI2][0] == difilt.buf[DI2][1] &&
            difilt.buf[DI2][1] == difilt.buf[DI2][2] &&
            difilt.buf[DI2][2] == difilt.buf[DI2][3] ){ //4回一致を確認
            difilt.di2 = difilt.buf[DI2][0];
        }
        
        if( ++difilt.wp >= DI_FILT_MAX ){
            difilt.wp = 0;
        }
        timdifilt = FILT_MIN;
    }
}

関連リンク

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

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

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

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

TECH::CAMPプログラミング教養【無料体験会】

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

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