九州・福岡・東京ときどきIoT

21年間のはてなダイアリー&アメブロからの避難所

MultiPurposShieldとかHY-M302とかいうArduino学習ボードスケッチ

Arduinoプログラム解説:多機能センサー制御システム

 

このArduinoスケッチは、複数の入出力デバイスを統合的に制御するシステムです。主要な機能は、各種センサーからのデータ読み取り、そのデータに基づくアクチュエーター(LEDやブザー)の制御、そしてシリアル通信によるデバッグ情報の出力です。


 

プログラムの構成要素

 

 

1. プリプロセッサディレクティブと変数定義

 

 

C++
 
#include <DHT.h>
#include <Adafruit_Sensor.h>

// ピン定義 (Pin Definitions)
#define DHTPIN 4
...
#define KEY_LED_B_PIN 13

// オブジェクトの作成 (Object Instantiation)
DHT dht(DHTPIN, DHTTYPE);

// グローバル変数 (Global Variables)
int prevPotValue = 0;
int prevButton1State = HIGH;
int prevButton2State = HIGH;

 

  • #include: 外部ライブラリ(DHT.hAdafruit_Sensor.h)をインクルードしています。これにより、DHTセンサーの複雑なデータ取得処理を簡単に扱えます。

  • #define: 各コンポーネントを接続するピン番号に、わかりやすい名前を割り当てています。これにより、プログラムの可読性が向上し、ピンの変更も容易になります。

  • DHT dht(DHTPIN, DHTTYPE);: DHTセンサーライブラリのインスタンスを生成し、オブジェクト指向プログラミングの概念を適用しています。

  • グローバル変数: prevPotValueprevButton1Stateは、loop()関数が繰り返し実行されるたびに、前回の状態を記憶しておくために使用されます。これによって、イベントの「瞬間」を検出するロジック(例: ボタンが押された瞬間)を実装できます。


 

2. ブザーを鳴らす関数 (playTone)

 

 

C++
 
void playTone(int pin, int frequency, int duration) {
  long period = 1000000 / frequency;
  long delayTime = period / 2;
  for (long i = 0; i < (long)duration * 1000L / period; i++) {
    digitalWrite(pin, HIGH);
    delayMicroseconds(delayTime);
    digitalWrite(pin, LOW);
    delayMicroseconds(delayTime);
  }
}

 

  • この関数は、ハードウェアタイマーを使用する標準のtone()関数との競合を避けるために、ソフトウェアPWMの原理でブザーを制御します。

  • frequency(周波数)から周期(period)を計算し、digitalWrite()delayMicroseconds()を繰り返すことで、手動で矩形波を生成しています。これにより、11番ピンのPWM出力との干渉を防ぎます。


 

3. 初期設定 (setup)

 

 

C++
 
void setup() {
  Serial.begin(9600);
  dht.begin();
  
  pinMode(BUTTON1_PIN, INPUT_PULLUP);
  ...
  Serial.println("DHT_Temp(C)\t...");
}

 

  • Serial.begin(9600);: PCとのシリアル通信を開始し、デバッグやデータロギングのために使用します。ボーレート(通信速度)は9600bpsに設定されています。

  • pinMode(..., INPUT_PULLUP);: ボタンのピンをINPUT_PULLUPモードで設定しています。これにより、外部にプルアップ抵抗を接続しなくても、ボタンが押されていないときにHIGHの状態を保てます。

  • pinMode(..., OUTPUT);: LEDやブザーなどのアクチュエーターをOUTPUTモードに設定しています。

  • ヘッダー行の出力: シリアルモニターでのデータ表示を整理するために、各データの列名を出力しています。


 

4. メインループ (loop)

 

 

C++
 
void loop() {
  // 1. センサーデータの読み取り
  float h = dht.readHumidity();
  float t_dht = dht.readTemperature();
  ...

  // 2. VR(ポテンショメーター)の変更検出とブザー制御
  if (abs(potValue - prevPotValue) > 5) {
    playTone(BUZZER_PIN, 500, 50);
    prevPotValue = potValue;
  }
  ...
  // 5. キー(ボタン)に連動してブザーとLEDを制御する
  if (button1_state == LOW && prevButton1State == HIGH) {
    playTone(BUZZER_PIN, 1000, 50);
  }
  ...
}

 

  • loop()関数は、Arduinoの電源が入っている限り、無限に繰り返されます。

  • センサーデータ読み取り: DHTセンサー、LM35温度センサー、LDRCdSセル)、ポテンショメーター、ボタンの状態を読み取ります。analogRead()は0から1023の値を返します。

  • VRのブザー制御: ポテンショメーターの値が前回の値から5以上変化した場合に、playTone()関数を呼び出してブザーを鳴らします。

  • LED輝度制御:

    • brightness = map(ldrValue, 50, 800, 255, 0);: LDR(光センサー)の読み取り値(50〜800)を、LEDの輝度(0〜255)に線形変換しています。これにより、周囲が暗くなるとLEDが明るくなるロジックを実装しています。

    • potValueの値に応じて、RGB LEDの赤、緑、青の値を計算し、analogWrite()でPWM出力を制御して色を調整します。

  • キーによるブザーとLED制御:

    • if (button1_state == LOW && prevButton1State == HIGH): これは、ボタンが**押された瞬間(立ち下がりエッジ)**を検出する標準的な方法です。この条件が満たされたときだけブザーを鳴らすことで、ボタンを押し続けたときに音が鳴り続けるのを防いでいます。

  • シリアル出力: 各センサーの読み取り値とボタンの状態をシリアルポートに出力し、PCでデータの監視ができるようにしています。\t(タブ)で区切ることで、表形式での表示を可能にしています。

このプログラムは、複数のセンサーからのアナログ・デジタル入力を受け付け、それに応じて複数のアクチュエーターを制御する、基本的な組込みシステムの良い例となっています。特に、ハードウェアの制約をソフトウェアで回避するplayTone()関数は、問題解決の一つのアプローチとして注目に値します。

 

つづき さらに深堀します。

このプログラムについて、さらに詳しく見ていきましょう。前回は主要な構成とplayTone関数について解説しましたので、今回はloop関数内の各処理について深掘りします。


 

1. センサーデータの読み取りと処理

 

loop()関数の冒頭では、さまざまなセンサーから環境データを取得しています。

 

C++
 
// 1. センサーデータの読み取り
float h = dht.readHumidity();
float t_dht = dht.readTemperature();
int lm35_adc_value = analogRead(LM35_PIN);
float t_lm35 = lm35_adc_value * (500.0 / 1024.0);
int ldrValue = analogRead(LDR_PIN);
int potValue = analogRead(POT_PIN);
int button1_state = digitalRead(BUTTON1_PIN);
int button2_state = digitalRead(BUTTON2_PIN);

 

  • dht.readHumidity()dht.readTemperature():DHT11センサーからデジタルデータとして湿度と温度を直接読み取っています。DHT11ライブラリが、この複雑なデータ通信を裏側で処理してくれています。

  • analogRead():LM35、LDRポテンショメーター(VR)といったアナログセンサーから、0から1023までの電圧値(ADC値)を読み取っています。

  • t_lm35の計算:LM35はアナログセンサーなので、読み取ったADC値を実際の温度に変換する式が必要です。lm35_adc_value * (500.0 / 1024.0)という式は、5Vの電源で動作するLM35の特性(10mV/℃)に基づいています。ArduinoanalogReadは5Vを1024段階で表現するため、500.0 / 1024.0は「1段階あたり約4.88mV」という換算係数になり、これにlm35_adc_valueをかけることで、摂氏温度に変換しています。


 

2. ポテンショメーターとRGB LEDの連動ロジック

 

この部分では、ポテンショメーター(VR)の回転とLDRの明るさに応じて、3色LEDの色と明るさを動的に制御しています。

 

C++
 
// 3. CDS(LDR)の明るさに応じてLEDの輝度を調整
int brightness = map(ldrValue, 50, 800, 255, 0); 
// 4. VRの値に応じてRGB LEDの色を調整
...
analogWrite(LED_R_PIN, map(redValue, 0, 255, 0, brightness));

 

  • map(ldrValue, 50, 800, 255, 0)map()関数は、ある範囲の値を別の範囲に線形変換する便利な機能です。ここでは、LDRが読み取る明るさの値(ldrValue)が50から800の範囲で、LEDの輝度(brightness)を255から0に変化させています。明るい場所(ldrValueが大きい)ではLEDを暗く、暗い場所(ldrValueが小さい)ではLEDを明るくするロジックです。

  • if-else if-elseポテンショメーターの値(potValue)を3つの範囲に分割し、それぞれの範囲でRGBの各色の値をmap()関数を使って調整しています。これにより、ポテンショメーターを回すと、赤→緑→青へと滑らかに色が変化するグラデーションを実装しています。

  • analogWrite():最終的なRGBの各色の値に、LDRから得たbrightness値をさらにmap()関数で乗算し、analogWrite()によってPWM(パルス幅変調)出力を行っています。これにより、光の色と明るさの両方を同時に制御できるのです。


 

3. ボタンのイベント検出と制御

 

キー連動のブザーとLEDの制御は、単にボタンの状態を読み取るだけでなく、「ボタンが押された瞬間」を正確に捉える工夫がされています。

 

C++
 
// 5. キー(ボタン)に連動してブザーとLEDを制御する
if (button1_state == LOW && prevButton1State == HIGH) {
  playTone(BUZZER_PIN, 1000, 50);
}
// ...
// ボタンの状態を更新
prevButton1State = button1_state;
prevButton2State = button2_state;

 

  • button1_state == LOW && prevButton1State == HIGH:この条件式は、ボタンの状態がHIGH(押されていない)からLOW(押された)に変化した瞬間を検出しています。

  • prevButton1Stateという前回の状態を記憶する変数があるため、ボタンが押されっぱなしになっている間はブザーが連続して鳴るのを防ぎ、イベントを一度だけ発生させることができます。これは、チャタリング(物理的なボタンを押したときの微細なON/OFFの繰り返し)対策にも有効です。


 

4. シリアル通信によるデータの出力

 

プログラムの最後では、すべてのセンサーデータをPCのシリアルモニターに出力しています。

 

C++
 
// 6. すべてのデータを1行で出力
if (isnan(h) || isnan(t_dht)) {
  Serial.print("NaN\tNaN\t");
} else {
  ...
}
Serial.print(t_lm35);
Serial.print("\t");
...
Serial.println(button2_state == LOW ? "Pressed" : "Not Pressed");
delay(100);

 

  • isnan():DHTセンサーが正常に動作しない場合に返されるNaN(Not a Number)という特殊な値をチェックし、エラー処理を行っています。

  • Serial.print()Serial.println():それぞれのデータの後に\t(タブ)を挿入することで、シリアルモニター上でデータがきれいに表形式で表示されるように整形しています。

  • delay(100)loop()関数が次の繰り返しに進む前に100ミリ秒待機しています。これにより、センサーのデータが過度に高速で更新されるのを防ぎ、シリアルモニターの表示を安定させています。

このように、このプログラムはセンサーからの入力、ロジックによる処理、アクチュエーターへの出力、そして外部への情報提供(シリアル通信)という、組込みシステムの基本要素をすべて網羅した、バランスの取れた構成になっています。

コピペで動く全リスト

#include <DHT.h>
#include <Adafruit_Sensor.h>

// ピン定義
#define DHTPIN 4
#define DHTTYPE DHT11
#define BUTTON1_PIN 3
#define BUTTON2_PIN 2
#define BUZZER_PIN 5
#define LDR_PIN A1
#define LM35_PIN A2
#define POT_PIN A0
#define LED_R_PIN 9
#define LED_G_PIN 10
#define LED_B_PIN 11                      

// キー連動用の新しいLEDピンを定義
#define KEY_LED_R_PIN 12
#define KEY_LED_B_PIN 13

// オブジェクトの作成
DHT dht(DHTPIN, DHTTYPE);

int prevPotValue = 0;
int prevButton1State = HIGH;
int prevButton2State = HIGH;

// ブザーを鳴らす関数
void playTone(int pin, int frequency, int duration) {
  long period = 1000000 / frequency;
  long delayTime = period / 2;
  for (long i = 0; i < (long)duration * 1000L / period; i++) {
    digitalWrite(pin, HIGH);
    delayMicroseconds(delayTime);
    digitalWrite(pin, LOW);
    delayMicroseconds(delayTime);
  }
}

void setup() {
  Serial.begin(9600);
  dht.begin();
 
  pinMode(BUTTON1_PIN, INPUT_PULLUP);
  pinMode(BUTTON2_PIN, INPUT_PULLUP);
  pinMode(BUZZER_PIN, OUTPUT);
  pinMode(LED_R_PIN, OUTPUT);
  pinMode(LED_G_PIN, OUTPUT);
  pinMode(LED_B_PIN, OUTPUT);
  pinMode(KEY_LED_R_PIN, OUTPUT);
  pinMode(KEY_LED_B_PIN, OUTPUT);
  pinMode(LDR_PIN, INPUT);
  pinMode(POT_PIN, INPUT);
 
  // ヘッダー行を出力
  Serial.println("DHT_Temp(C)\tDHT_Humi(%)\tLM35_Temp(C)\tLDR(0-1023)\tPot(0-1023)\tButton1\tButton2");
}

void loop() {
  // 1. センサーデータの読み取り
  float h = dht.readHumidity();
  float t_dht = dht.readTemperature();
 
  int lm35_adc_value = analogRead(LM35_PIN);
  float t_lm35 = lm35_adc_value * (500.0 / 1024.0);

  int ldrValue = analogRead(LDR_PIN);
  int potValue = analogRead(POT_PIN);
  int button1_state = digitalRead(BUTTON1_PIN);
  int button2_state = digitalRead(BUTTON2_PIN);
 
  // 2. VRポテンショメーター)の変更検出とブザー制御
  if (abs(potValue - prevPotValue) > 5) {
    playTone(BUZZER_PIN, 500, 50);
    prevPotValue = potValue;
  }

  // 3. CDSLDR)の明るさに応じてLEDの輝度を調整
  int brightness = map(ldrValue, 50, 800, 255, 0);

  // 4. VRの値に応じてRGB LEDの色を調整
  int redValue, greenValue, blueValue;

  if (potValue < 341) {
    redValue = map(potValue, 0, 340, 255, 0);
    greenValue = map(potValue, 0, 340, 0, 255);
    blueValue = 0;
  } else if (potValue < 683) {
    redValue = 0;
    greenValue = map(potValue, 341, 682, 255, 0);
    blueValue = map(potValue, 341, 682, 0, 255);
  } else {
    redValue = map(potValue, 683, 1023, 0, 255);
    greenValue = 0;
    blueValue = map(potValue, 683, 1023, 255, 0);
  }

  // 輝度を色に適用
  analogWrite(LED_R_PIN, map(redValue, 0, 255, 0, brightness));
  analogWrite(LED_G_PIN, map(greenValue, 0, 255, 0, brightness));
  analogWrite(LED_B_PIN, map(blueValue, 0, 255, 0, brightness));
 
  // 5. キー(ボタン)に連動してブザーとLEDを制御する
  if (button1_state == LOW && prevButton1State == HIGH) {
    playTone(BUZZER_PIN, 1000, 50);
  }
  if (button1_state == LOW) { // ボタン1が押されたら赤色LEDを点灯
    digitalWrite(KEY_LED_R_PIN, HIGH);
  } else {
    digitalWrite(KEY_LED_R_PIN, LOW);
  }

  if (button2_state == LOW && prevButton2State == HIGH) {
    playTone(BUZZER_PIN, 1500, 50);
  }
  if (button2_state == LOW) { // ボタン2が押されたら青色LEDを点灯
    digitalWrite(KEY_LED_B_PIN, HIGH);
  } else {
    digitalWrite(KEY_LED_B_PIN, LOW);
  }

  // ボタンの状態を更新
  prevButton1State = button1_state;
  prevButton2State = button2_state;
 
  // 6. すべてのデータを1行で出力
  if (isnan(h) || isnan(t_dht)) {
    Serial.print("NaN\tNaN\t");
  } else {
    Serial.print(t_dht);
    Serial.print("\t");
    Serial.print(h);
    Serial.print("\t");
  }

  Serial.print(t_lm35);
  Serial.print("\t");
  Serial.print(ldrValue);
  Serial.print("\t");
  Serial.print(potValue);
  Serial.print("\t");
  Serial.print(button1_state == LOW ? "Pressed" : "Not Pressed");
  Serial.print("\t");
  Serial.println(button2_state == LOW ? "Pressed" : "Not Pressed");
 
  delay(100);
}