PR

Arduino UNO R4 WiFiでアクセスポイントを実装する

組み込みエンジニア
本記事はプロモーションが含まれています。

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

Arduino UNO R4 WiFiは無線通信モジュールのESP32-S3-MINIが実装されており、WiFiS3ライブラリでアクセスポイントとして動作させることができます。地磁気センサーのデータをスマホで表示して動作確認しました。

地磁気センサーからデータを取得する方法は下記記事にまとめています。

Arduino UNO R4 WiFiのWireで地磁気センサーからデータを取得する

地磁気センサーの情報を簡易的なHTMLデータを生成してブラウザーに表示します。地磁気センサーモジュールにAE-BM1422AGMV(秋月電子)を使用しています。

以下ではArduino UNO R4 WiFiをUNOR4-WiFiと表記します。Arduinoのライブラリを使用して動作確認したことをまとめています。

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

WiFiのアクセスポイントを実装

UNOR4-WiFiはWiFiS3ライブラリを使用することでアクセスポイントやサーバーとして動作させることでクライアント(スマホ等)からの接続に対して応答することができます。

アクセスポイントの実装に必要な項目を説明します。以下ではWiFiクラス、WiFiServerクラス、WiFiClientクラスのメンバー関数を赤文字で表記します。

PR:わからないを放置せず、あなたにあった最低限のスキルを身に着けるコツを教える テックジム 「書けるが先で、理解が後」を体験しよう!

初期化の準備

#include "WiFiS3.h"

const char *ssid = "EngKapi1"; //SSID
const char *pass = "22223333"; //password
WiFiServer server(80);

UNOR4-WiFiでWiFi通信を実装するためにWiFiS3ライブラリをインクルードします。

アクセスポイントとして動作させるためにSSIDとパスワードを設定する必要があります。初期化時に設定する必要があるため定数のポインタとして宣言しています。

SSIDはWiFiのアクセスポイントで表示される名称で任意の名称にすることができます。パスワードはアクセスポイントに接続するためのパスワードを指定します。

WiFiServerクラスの変数としてserver()を引数でインスタンス化します。引数はブラウザーがアクセスする際のポート番号です。HTMLデータの転送番号のデフォルトである80番を指定しています。

予約されている番号以外であれば任意の番号でも問題ありませんが、ブラウザーでアクセスする場合にIPアドレスとポート番号を指定する必要があるのでデフォルトの80をお勧めします。

アクセスポイントの設定

void setup() {

  WiFi.config(IPAddress(192,168,11,1));
  WiFi.softAP(ssid, pass); //WiFiのアクセスポイントの設定
  server.begin(); //サーバーの開始
}

config()関数の第1引数にIPアドレスを指定します。第2引数はゲートウェイを使用する場合はゲートウェイのIPアドレスを指定します。第3引数はサブネットマスクを指定します。

第2引数と第3引数を指定しない場合は第1引数のIPアドレスを基準にデフォルトの設定になります。

softAP()関数で第1引数と第2引数にアクセスポイント名とパスワードを設定します。

第3引数はチャンネル番号を1~14chで指定します。デフォルトで1chを使用するようになっているため、変更しない場合は指定する必要はありません。

上記でインスタンス化したWiFiServerクラスのserverのbegin()関数でサーバーをスタートしてクライアントからの接続を待機します。

PR:RUNTEQ(ランテック )- マイベスト4年連続1位を獲得した実績を持つWebエンジニア養成プログラミングスクール

クライアント接続の処理

void mainWiFi(void){

  WiFiClient client = server.available();

  if (client) {
    String currentLine = "";

    while (client.connected()){
      delayMicroseconds(10);  
      if (client.available()){
        char c = client.read();
    
        if (c == '\n') {
          if (currentLine.length() == 0) {
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println();
            HtmlSet();//HTMLデータの生成
            client.print(html);
            client.println();
            break;
          }
          else{
            currentLine = "";
          }
        }
        else if (c != '\r'){
          currentLine += c;
        }
      }
    }
    client.stop();
  }
}

クライアント接続をavailable()関数で待機します。接続があるとクライアントの情報をWiFiClientクラスの変数に格納します。

クライアントが存在すると5行目の処理によって内部の処理に移ります。

クライアントからIPアドレスや接続情報などのHTTPリクエスト(ヘッダー)が送信されるのでwhile文(8行目)のループでヘッダーを取得します。

何らかの要因でクライアントの接続が切れた場合にループから抜けるためconnected()関数で接続中であるかの確認を行いながらヘッダーを取得します。

10行目のavailable()関数でクライアントから送信されたヘッダーがあるかを確認します。ヘッダーのデータが存在する場合はread()関数で読み込みます。

ヘッダーは改行コードのCR(\r)LF(\n)で終わるため\nを確認(13行目)してヘッダーの終端を判定します。その後はクライアントに応答のHTMLデータをprint()関数及びprintln()関数を使って文字列で応答します。

最初にヘッダーに対する応答としてステータス(15行目)を送信します。HTTP/11はHTTPのバージョン情報です。200はステータスコードを示すものでOKは成功したことを通知するものです。つまりクライアントが送信したヘッダーを正常に処理したことを通知しています。

16行目はクライアントにHTMLデータを送信するという通知です。ここまでが応答のヘッダーになります。ヘッダーの終了を示すため改行コードを挿入(17行目)しています。

18行目のHtmlSet()関数はHTMLデータを生成する自作の関数です。詳細は次の項目を参照してください。

19行目のprint()関数に生成したHTMLデータを指定することでHTMLデータを送信します。

HTMLデータの送信後はstop()関数でクライアントの接続を停止(32行目)します。

PR:スキマ時間で自己啓発!スマホで学べる人気のオンライン資格講座【スタディング】まずは気になる講座を無料で体験しよう!

HTMLデータの生成

String html;

void HtmlSet(void){
  html = "";

  html += "<html lang=\"ja\">";
  html += "<head>";
  html += "<meta http-equiv=\"refresh\" content=\"2\">";
  html += "<meta charset=\"UTF-8\">";
  html += "<title>Sensor BM1422</title>";
  html += "</head>";
  html += "<body>";
  html += "<h1>BM1422 地磁気センサ</h1>";
  html += "<h2>X: ";
  html += mag[0];
  html += "[uT]";
  html += "</h2>";
  //省略
  html += "</body>";
  html += "</html>";
}

HTMLデータの作成方法は様々ですがString型を使って文字列を生成して送信する方法を紹介します。htmlをString型で宣言してHTMLデータになるように文字列を付け加えていきHTMLの文字列を生成します。

6行目はブラウザーに主言語が日本語であることを宣言しています。

8行目はブラウザーに画面の更新(リフレッシュ)を2秒間隔で行ってくださいと指示するHTMLのメタタグです。

9行目は文字コードがUTF-8であることを通知する宣言です。文字コードを指定することで文字コードの解釈の違いによる文字化けが起こらないようにしています。

14行目~17行目は地磁気センサーから読み出したX軸のデータを表示します。

HTMLデータの生成がうまくいかないとブラウザーの表示が真っ白になってしまうなど動作不良の原因になります。テキストファイルでHTMLデータを作成してChromeで開く(拡張子を.htmlにする)などして動作確認しておくとよいと思います。

String型はRAMの容量を圧迫する原因になるため文字の増やし過ぎには注意が必要です。

スポンサーリンク

動作確認

動作確認用の回路図
動作確認用の回路図

BM1422で使用するWireライブラリはA4にIIC_SDA1(SDA)、A5にIIC_SCL1(SCL)が割り振られています。I2C通信はそれぞれのピンをプルアップする必要がありますが、AE-BM1422AGMVに付属しているプルアップ抵抗を使用しています。

電源を投入すると磁気のデータを1秒ごとに取得しながらクライアントからの接続要求を待機します。

スマホでアクセスポイントを検索すると「EngKapi1」のアクセスポイントが表示されるので選択してパスワード「22223333」を入力すると接続ができます。

ChromeやSafariなどのブラウザーで「192.168.11.1」を入力すると地磁気センサーから取得したデータを含めたHTMLのページが表示されます。

スマホの表示と地磁気センサーの様子(N極が上の場合)
スマホの表示と地磁気センサーの様子

BM1422モジュールに磁石のN極(回路図側を上)を設置したときのスマホの表示を確認しました。2秒ごとに測定データが更新されていました。

この状態でN極とS極の向きを変えたときの測定データを確認しました。

スマホの表示と地磁気センサーの様子(S極が上の場合)
スマホの表示と地磁気センサーの様子(S極が上の場合)

スマホの表示が更新されX軸とY軸の磁気の向きが反転している様子がわかりました。

ソースコード全体

ソースコードは記事作成時点において動作確認できていますが、使用しているライブラリの更新により動作が保証できなくなる可能性があります。また、ソースコードを使用したことによって生じた不利益などの一切の責任を負いかねます。参考資料としてお使いください。

#include <Wire.h>
//#include <Adafruit_SSD1331.h>
#include "WiFiS3.h"

#define TIME_OFF -1 //タイマを使用しない場合
#define TIME_UP 0 //タイムアップ
#define BASE_CNT 10 //ベースタイマカウント値
#define TIME_BM1422 100 //BM1422の計測タイマ  
#define TIME_OUT 20  //BM1422の通信タイムアウト
#define TIME_LED 50
#define BM1422_ADRS 0x0E
#define CTRL1_ADRS 0x1B
#define CTRL2_ADRS 0x1C
#define CTRL3_ADRS 0x1D
#define CTRL4_ADRS 0x5C
#define CTRL4_ADRS2 0x5D
#define STA1_ADRS 0x18
#define DATA_ADRS 0x10

#define CTRL1_SET 0xC2
#define CTRL2_SET 0x0C //未使用
#define CTRL3_SET 0x40
#define CTRL4_SET 0x00

typedef enum{
  BM1422_STEP1 = 0,
  BM1422_STEP2,
  BM1422_STEP3,
  BM1422_STEP4,
  BM1422_STEP5,
  BM1422_IDLE,     
  BM1422_MAX  
}BM1422_MODE;

uint32_t beforetimCnt = millis();
int16_t timbm1422;
int16_t timled;
int16_t timMeas;
BM1422_MODE md;
uint8_t cmadbuf[3]; //送信するデータを格納
uint8_t mdata[6]; //測定データ換算前
float mag[3]; //換算後データ
int status = WL_IDLE_STATUS;
WiFiServer server(80);
const char *ssid = "EngKapi1"; //SSID
const char *pass = "22223333"; //password
String html;

void mainApp(void);
void mainTimer(void);
bool BM1422Cmd(uint8_t* cmd, uint8_t len);
bool BM1422GetData(uint8_t adr, uint8_t* reg_data, uint16_t len);
void MagSet(void);
void mainWiFi(void);
void HtmlSet(void);

void setup() {

  Serial.begin(115200);
  Wire.begin();

  WiFi.config(IPAddress(192,168,11,1));

  status = WiFi.beginAP(ssid, pass);
  if (status != WL_AP_LISTENING) {
    Serial.println("Creating access point failed");
    // don't continue
    while (true);
  }
  // start the web server on port 80
  server.begin();
}

void loop() {
  mainTimer();
  mainApp();
  mainWiFi();
}

/* タイマ管理 */
void mainTimer(void){

  if ( millis() - beforetimCnt > BASE_CNT ){
    beforetimCnt = millis();

    if( timbm1422 > TIME_UP ){
      --timbm1422;
    }
    if( timled > TIME_UP ){
      --timled;
    }

    if( timMeas > TIME_UP ){
      --timMeas;
    }
  }
}
/* メイン処理 */
void mainApp(void){

  switch(md){
    case BM1422_STEP1:
      cmadbuf[0] = CTRL1_ADRS;
      cmadbuf[1] = CTRL1_SET;

      if( BM1422Cmd(cmadbuf, 2)){
        timbm1422 = TIME_OUT;
        md = BM1422_STEP2;
      }
      else{
        Serial.println("STEP1 NG");
      }
      break;
    case BM1422_STEP2:
      if( timbm1422 == TIME_UP ){
        cmadbuf[0] = CTRL4_ADRS;
        cmadbuf[1] = CTRL4_SET;
        cmadbuf[2] = CTRL4_SET;

        if( BM1422Cmd(cmadbuf, 3) ){
          timbm1422 = TIME_OUT;
          md = BM1422_STEP3;
        }
        else{
          Serial.println("STEP2 NG");
        }
      }
      break;
    case BM1422_STEP3: //データ測定開始
      if( timbm1422 == TIME_UP ){
        cmadbuf[0] = CTRL3_ADRS;
        cmadbuf[1] = CTRL3_SET;

        if( BM1422Cmd(cmadbuf, 2 )){
          timbm1422 = TIME_OUT;
          md = BM1422_STEP4;
        }
        else{
          Serial.println("STEP3 NG");
        }
      }
      break;
    case BM1422_STEP4:
      if( timbm1422 == TIME_UP ){
        timbm1422 = TIME_OUT;

        uint8_t sta;
        BM1422GetData(STA1_ADRS, &sta, 1);

        if( sta &= 0x40 ){
          md = BM1422_STEP5;
          Serial.println("Measure OK");
        }
      }
      break;
    case BM1422_STEP5:
      if( timbm1422 == TIME_UP ){
        timbm1422 = TIME_OUT;

        BM1422GetData(DATA_ADRS, mdata, sizeof(mdata));
        MagSet();
        md = BM1422_IDLE;
        timMeas = TIME_BM1422;
      }
      break;
    case BM1422_IDLE:
      if( timMeas == TIME_UP ){
        timMeas = TIME_OFF;
        md = BM1422_STEP3;
      } 
      break;
  }

  if( timled == TIME_UP ){
    timled = TIME_LED;
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
  }

}
/* 磁気センサーの値を換算 */
void MagSet(void){
  int16_t x;
  int16_t y;
  int16_t z;

  x = (mdata[1] << 8) + mdata[0];
  y = (mdata[3] << 8) + mdata[2];
  z = (mdata[5] << 8) + mdata[4];

  mag[0] = (float)x * 0.042;
  mag[1] = (float)y * 0.042;
  mag[2] = (float)z * 0.042;

  Serial.print("X: "); Serial.print(mag[0]);
  Serial.print(" Y: "); Serial.print(mag[1]);
  Serial.print(" Z: "); Serial.println(mag[2]);
}

/* コマンド送出 */
bool BM1422Cmd(uint8_t* cmd, uint8_t len){
  bool ret = false;

  Wire.beginTransmission(BM1422_ADRS); //スレーブが存在するか確認
  byte error = Wire.endTransmission();
  if( error == 0){ //スレーブが存在する場合下の処理
    Wire.beginTransmission(BM1422_ADRS);

    for(uint8_t i= 0; i < len; i++ ){
      Wire.write(*cmd);
      ++cmd;
    }
  
    Wire.endTransmission(); //ストップ・コンディションの発行
    ret = true;
  }

  return ret;
}
/* データを取得 */
bool BM1422GetData(uint8_t adr, uint8_t* reg_data, uint16_t len){
  bool  ret = false;

  Wire.beginTransmission(BM1422_ADRS); //スタート・コンディションの発行
  Wire.write(adr); //書き込む対象のアドレスをセット(ライトで指定)
  byte error = Wire.endTransmission();
  if( error == 0){ //スレーブが存在する場合下の処理
    if( Wire.requestFrom(BM1422_ADRS, len) == len ){
      for( uint16_t i=0; i < len; i++ ){
        *reg_data = Wire.read(); //len分だけデータをリードする
        ++reg_data;
      }
      ret = true;
    }
  }
  return ret;
}
/* WiFiメイン */
void mainWiFi(void){

  WiFiClient client = server.available();

  if (client) {
    String currentLine = "";
    Serial.println("New Client.");

    while (client.connected()){
      delayMicroseconds(10);  
      if (client.available()){
        char c = client.read();
        //Serial.write(c);
        if (c == '\n') {
          if (currentLine.length() == 0) {
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println();
            HtmlSet();
            client.print(html);
            client.println();
            break;
          }
          else{
            currentLine = "";
          }
        }
        else if (c != '\r'){
          currentLine += c;
        }
      }
    }
    client.stop();
    Serial.println("Client Disconnected.");
  }
}
/* クライアントに返信するhtmlデータを生成 */
void HtmlSet(void){
  html = "";

  html += "<html lang=\"ja\">";
  html += "<head>";
  html += "<meta http-equiv=\"refresh\" content=\"2\">";
  html += "<meta charset=\"UTF-8\">";
  html += "<title>Sensor BM1422</title>";
  html += "</head>";
  html += "<body>";
  html += "<h1>BM1422 地磁気センサ</h1>";
  html += "<h2>X: ";
  html += mag[0];
  html += "[uT]";
  html += "</h2>";
  html += "<h2>Y: ";
  html += mag[1];
  html += "[uT]";
  html += "</h2>";
  html += "<h2>Z: ";
  html += mag[2];
  html += "[uT]";
  html += "</h2>";
  html += "</body>";
  html += "</html>";
}

SSIDとパスワードはユーザーに合わせて任意に変更しても問題ありません。

関連リンク

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

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

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

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

Raspberry Pi Picoで学べるソフト開発

PR:RUNTEQ(ランテック )- マイベスト4年連続1位を獲得した実績を持つWebエンジニア養成プログラミングスクール

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

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