Arduino M0 Pro でマルチタスクプログラミング : NCES IoT Base Shield : GROVE・RTOS機能

#はじめに

この記事では，TOPPERS/R2CAによる NCES IoT Base Shield のGrove機能の+RTOS機能使い方について説明します．

NCES IoT Base Shiledについては[この記事](http://qiita.com/exshonda/items/48b30a165e4c6845e900)を参照して下さい．

NCES IoT Base ShiledでGroveモジュールを使用する方法については[この記事](http://qiita.com/exshonda/items/4b8b9525d7bfaca8a6ad)を参照して下さい．


# 必要なハードウェア

- [Arduino M0 Pro](http://www.arduino.org/products/boards/4-arduino-boards/arduino-m0-pro) 
- NCES IoT Base Shield

- [Grove Digital Light Sensor](http://www.seeedstudio.com/wiki/Grove_-_Digital_Light_Sensor)
- [Grove OLED Display 0.96](http://www.seeedstudio.com/wiki/Grove_-_OLED_Display_0.96%22)
- [Grove Touch Sensor](http://www.seeedstudio.com/wiki/Grove_-_Touch_Sensor)
- [Grove Chainable RGB LED](http://www.seeedstudio.com/wiki/Grove_-_Chainable_RGB_LED)
- [Grove LED](http://www.seeedstudio.com/wiki/Grove_-_Red_LED)
- Windows PC
 - Windows7とWindows10で動作を確認しています．
- USB(microB)ケーブル
 - ボードには付属していないので用意して下さい．

# アプリケーション仕様

作成するアプリケーションの仕様は次の通り．
- 機能1:LEDを1秒周期で点滅する
- 機能2:周期的にDigital Light センサーの値を読み込んで，値をOLED Display に表示する．
- 機能3:Touch Sensorを押している間は，Light センサーの値の表示の更新を停止して，OLED Displayの表示を反転させる．
- 機能4:Chainable LEDを周期的に色を変更する．

# ハードウェアセットアップ

各種センサーをIoTシールドに接続します．Grove Digital LightとGrove OLED Displayは，I2Cポートに接続します．Touch SensorはD4以外のデジタルのコネクタに接続しますここではD3に接続します．LEDはD4に，Chainable RGB LEDはD8に接続します

<img width="400" alt="aFullSizeRender.jpg.jpg" src=https://qiita-image-store.s3.amazonaws.com/0/106870/08d531e1-d7cf-d4d5-c7f5-835711517cbd.jpeg>

# プロジェクトフォルダ
プロジェクトフォルダは，NCESIoT_RTOS にあります．必要なライブラリ等は入っていますので，ハードウェアの
セットアップ後に動作させることは可能です．

# 割込み無し版

まず割込みを使用しないバージョンを作成します．

各モジュールのライブラリはGroveのWikiからダウンロードしてプロジェクトフォルダ以下に置きます．今回はDigital Light，OLED Display，ChainableLEDのライブラリを使用します．

まず機能1-4を別々のタスクで実現するためにタスクを追加で3個(合計4個)作成します．

```example/NCESIoT_RTOS/r2ca_app.h
#define R2CA_NUM_TASK 3
```

割込みを使わないのでUSE_INTERRUPTをコメントアウトします．ifdefが入っていると読みにくいのでifdefをとった状態のコードを掲載します．

```example/NCESIoT_RTOS/r2ca_app.cpp
#include "r2ca.h"
#include <Wire.h>
#include <Digital_Light_TSL2561.h>
#include <SeeedOLED.h>
#include <ChainableLED.h>

#define LED_PIN  4

extern void task1_setup();
extern void task2_setup();
extern void task3_setup();

void setup()
{
    Serial.begin(115200);
    pinMode(LED_PIN, OUTPUT);

    task1_setup();
    task2_setup();
    task3_setup();
}

void loop() 
{
    digitalWrite(LED_PIN, HIGH);   // turn the LED on (HIGH is the voltage level)
    delay(1000);                   // wait for a second
    digitalWrite(LED_PIN, LOW);    // turn the LED off by making the voltage LOW
    delay(1000);                   // wait for a second    
}

#define TOUCH_PIN 3

int is_update_oled;

void task1_setup() 
{
    Wire.begin();
    TSL2561.init();

    SeeedOled.init();
    SeeedOled.deactivateScroll();

    is_update_oled = 1;
} 
 
void task1_loop() 
{    
    Serial.print("The Light value is: ");
    Serial.println(TSL2561.readVisibleLux());

    if (is_update_oled == 1) {
        wai_sem(OLED_SEM);
        SeeedOled.clearDisplay();
        SeeedOled.putNumber(TSL2561.readVisibleLux());
        sig_sem(OLED_SEM);
    }
    
    delay(1000);
}

void task2_setup()
{
    pinMode(TOUCH_PIN, INPUT_PULLUP);
}

void task2_loop() 
{
    delay(1);    

    int TouchSensorValue = digitalRead(TOUCH_PIN);

    if(TouchSensorValue==1) {
        is_update_oled = 0;
        wai_sem(OLED_SEM);
        SeeedOled.setInverseDisplay();
        sig_sem(OLED_SEM);
    }else{        
        is_update_oled = 1;
        wai_sem(OLED_SEM);
        SeeedOled.setNormalDisplay();
        sig_sem(OLED_SEM);
    }
}

#define NUM_LEDS  1

ChainableLED leds(8, 9, NUM_LEDS);

void task3_setup()
{
    leds.init();
}

float hue = 0.0;
boolean up = true;
int count = 0;

void task3_loop()
{
    for (byte i=0; i<NUM_LEDS; i++)
      leds.setColorHSB(i, hue, 1.0, 0.5);
    
    delay(50);
    
    if (up)
      hue+= 0.025;
    else
      hue-= 0.025;
    
    if (hue>=1.0 && up)
      up = false;
    else if (hue<=0.0 && !up)
      up = true;
}
```

メインタスクセットアップ(setup())はシリアルポートとLEDのための初期化を行っています．
メインタスクループ(loop())では，LEDを一秒間隔で点滅させます．

タスク1セットアップ(task1_setup())では， Digital Light と OLED Display を初期化しています．
タスク1ループ(task1_loop())では，1秒ごとにDigital Light の値を読み込んでその結果をOLEDに表示します．

タスク2セットアップ(task2_setup())では，Touch Sensorのためにポートを初期化します．Touch Sensorを接続しているポートはマクロTOUCH_PINで定義しています．

タスク2ループ(task2_loop())では，Touch Sensorを読み込んで，押されていればOLEDを反転させます．ここで重要なのは，delay(1)を入れることです．これを入れないとメインタスクに処理が移りません．

タスク間の通信はグローバル変数で実現しています．具体的には，is_update_oledを使用しています．Touch Sensorが押されていれば is_update_oled を0に設定します．is_update_oled が0だとメインタスクはOLEDの更新を停止します．

OLEDは両方のタスクから操作するため，セマフォにより排他制御します．操作前にwai_sem()でセマフォを取得して，使用後にsig_sem()でセマフォを返却します．

セマフォはr2ca_app.cfgに宣言します．

```example/NCESIoT_RTOS/r2ca_app.cfg
INCLUDE("r2ca_lib.cfg");

CRE_SEM(OLED_SEM, {TA_NULL, 1, 1});
```

タスク3セットアップ(task3_setup())では，ChainableLEDのための初期化を行います．
タスク2ループ(task3_loop())では，ChainableLEDの色を周期的に変更します．


# 割込みあり版

次に割込みを使用するバージョンを作成します．

USE_INTERRUPTの定義を有効にします．ifdefが入っていると読みにくいのでifdefをとった状態のコードを掲載します．

```example/NCESIoT_RTOS/r2ca_app.cpp
#include "r2ca.h"
#include <Wire.h>
#include <Digital_Light_TSL2561.h>
#include <SeeedOLED.h>
#include <ChainableLED.h>

#define LED_PIN  4

void task1_setup();
void task2_setup();
void task3_setup();

void setup()
{
    Serial.begin(115200);
    pinMode(LED_PIN, OUTPUT);

    task1_setup();
    task2_setup();
    task3_setup();
}

void loop() 
{
    digitalWrite(LED_PIN, HIGH);   // turn the LED on (HIGH is the voltage level)
    delay(1000);                   // wait for a second
    digitalWrite(LED_PIN, LOW);    // turn the LED off by making the voltage LOW
    delay(1000);                   // wait for a second    
}

#define TOUCH_PIN 3

int is_update_oled;

void task1_setup() 
{
    Wire.begin();
    TSL2561.init();

    SeeedOled.init();
    SeeedOled.deactivateScroll();

    is_update_oled = 1;
} 
 
void task1_loop() 
{    
    Serial.print("The Light value is: ");
    Serial.println(TSL2561.readVisibleLux());

    if (is_update_oled == 1) {
        wai_sem(OLED_SEM);
        SeeedOled.clearDisplay();
        SeeedOled.putNumber(TSL2561.readVisibleLux());
        sig_sem(OLED_SEM);
    }
    
    delay(1000);
}

void onTouch(void) {
    iwup_tsk(R2CA_TASK1);
}

void task2_setup()
{
    pinMode(TOUCH_PIN, INPUT_PULLUP);
    attachInterrupt(TOUCH_PIN, onTouch, CHANGE);
}

void task2_loop() 
{
    slp_tsk();
    
    int TouchSensorValue = digitalRead(TOUCH_PIN);

    if(TouchSensorValue==1) {
        is_update_oled = 0;
        wai_sem(OLED_SEM);
        SeeedOled.setInverseDisplay();
        sig_sem(OLED_SEM);
    }else{        
        is_update_oled = 1;
        wai_sem(OLED_SEM);
        SeeedOled.setNormalDisplay();
        sig_sem(OLED_SEM);
    }
}

#define NUM_LEDS  1

ChainableLED leds(8, 9, NUM_LEDS);

void task3_setup()
{
    leds.init();
}

float hue = 0.0;
boolean up = true;
int count = 0;

void task3_loop()
{
    for (byte i=0; i<NUM_LEDS; i++)
      leds.setColorHSB(i, hue, 1.0, 0.5);
    
    delay(50);
    
    if (up)
      hue+= 0.025;
    else
      hue-= 0.025;
    
    if (hue>=1.0 && up)
      up = false;
    else if (hue<=0.0 && !up)
      up = true;
}

```


割込み無し版との差分について説明します．

割込みあり版では，Touch Sensor の状態が変化すると割込みハンドラを起動するようにします．

まずタスク2セットアップ(task2_setup())で，attachInterrupt()を呼び出して割込みを設定します．Touch Sensor を押した際にも話した際にも割込みを発生させたいため，引数にCHANGEを指定します．

割込みハンドラは onTouch()として宣言します．onTouch()は，iwup_tsk()でタスク1を起動します．

タスク2メインループ(task2_loop())ではonTouch()からiwup_tsk()により起動されるのを待ち，起動されたらTouch Sensor の値を読み，割込みなし版と同じ処理を実施します．

# おわりに

この記事では，RTOSのAPIの実例を紹介しました．