質問やフィードバックがありましたら、フォームからお願いします
本文は台湾華語で、ChatGPT で翻訳している記事なので、不確かな部分や間違いがあるかもしれません。ご了承ください
本記事はシリーズの第3篇です:
- センサー紹介篇 - DHT11 と MH-Z14A
- データ通信篇 - UART
- Arduino の失敗談
- (未公開)WiFi 編:デバッグの時間を節約するために、ESP32 開発ボードを追加購入しました。これには WiFi と Bluetooth 機能が既に含まれています。
- (未公開)MQTT 編:データを他のデバイスに送信するために、軽量な通信プロトコルである MQTT を使用しました。
- (未公開)Grafana / Web 編:データがデータベースに保存されているのだから、カッコいい方法で表示しなければなりません!ここでは Grafana + Prometheus と Svelte を使用してデータを表示します。
今回は、Arduino やプログラミング言語に不慣れで直面したいくつかの問題についてお話しします。
CPU のデータ読み取り順序の違い(Big Endian と Little Endian)
CPU の命令セットアーキテクチャが異なるため、データを読み取る順序も異なります。これは中国語でバイトオーダー(Endianness)と呼ばれています。最上位バイトから読み取る場合は Big-Endian と呼ばれ、最下位バイトから読み取る場合は Little Endian と呼ばれます。以下の図で説明すると、より明確になるでしょう:
Arduino は読み取り順序として Little Endian を使用しています。詳細はこちらで確認できます。上記のメモリ以外でも、データを読み取る順序が異なる場合は、Big Endian と Little Endian で表現できます。
メモリは管理が容易なため、OS はメモリ空間を固定サイズに分割します。したがって、連続したデータを保存する際に空間が不足すると、データは次の空間に格納され、値を取得する際にそれらを合計する必要があります。
この問題に直面した理由は、最初に ppm
を unsigned short
ポインタとして設定したためです。これにより、自動的に正しい数値が計算されるはずだと考えました。手動でビットシフトを書く必要はありません。例は以下の通りです:
struct Co2Result {
byte startByte;
byte command;
byte high;
byte low;
unsigned short ppm;
};
Co2Result *result = malloc(sizeof(Co2Result));
result->startByte = response[0];
result->command = response[1];
result->high = response[2];
result->low = response[3];
result->ppm = &result->high;
short のサイズは 2byte であるため、ポインタが result->high
を指す場合、2byte 後ろを読み取ることになります。効果は result->high << 8 + result->low
と同等であるべきです。
テストの結果、得られた数字は非常に奇妙でした。これはビット読み取り順序の違いによるものだと思います。ビットシフトを使用すれば正しい数字を取得できます:
result->ppm = (result->high << 8) + result->low;
C のデータ構造はプラットフォームによって異なる
例えば、int
は 2 バイトまたは 4 バイトである可能性があります。一般的な long
は 8 バイトまたは 4 バイトになる可能性があります。Java などの言語では、データ型が他のプラットフォームで異なる結果を持つことはありません。なぜなら、データ型の定義はプログラミング言語の実装から来ているからです。C/C++ では uint8_t
のような型が存在し、プラットフォームに応じて異なるサイズのデータ型を定義できるようになっています。
C に慣れている人にとっては当たり前かもしれませんが、データ構造のサイズを計算する際には sizeof
を使用し、各プラットフォームのデータ型のサイズが固定であると仮定すべきではありません。
byte
データ型の有効活用
Arduino では byte
というデータ型が提供されています。その定義は unsigned char
と同じですが、uint8_t
、char
、int
の違いがよくわからないことがありますので、ここでクリアにしておきます。
byte
とunsigned char
は同じもので、どちらも変数に 8bit のメモリを割り当てます。唯一の違いは、unsigned char
の意味がユーザーにとって広範であるのに対し、byte
はこの数字が 0 ~ 255 の範囲(unsigned)であることをより明確に示しています。Arduino ではbyte の使用を推奨しています。uint8_t
、byte
、unsigned char
は実際にはほぼ同じ概念で、エイリアスのようなものです。C/C++ では、異なるプラットフォームが int を 8bit または 16bit と定義する可能性があるため、通常はuint8_t
を定義してより正確な意味を表現します。
ヘッダーファイルを活用して関数を分割する
Arduino プロジェクトを行う際、コードの構造は比較的小さく、全ての機能を loop
と setup
に詰め込んでもコードが難読化することはありません。しかし、回路が少し複雑になると、全ての実装を同じファイルに詰め込むのは少し煩雑になります。
Arduino IDE の拡張子は .ino
ですが、実質的には C++ であり、C++ の文法をサポートしています。ただし、標準ライブラリを使用することはできません。モジュールを作成し、Arduino の関数を使用するには、Arduino.h
を追加すればよいのです。
#include "Arduino.h"
#include <string.h>
また、vscode
を使用している場合、Arduino の組み込みライブラリを include
することができないことがわかります。直接インクルードすると、コンパイルは通りますが、エディタ上に赤い線が表示されます。この場合、.vscode/c_cpp_properties.json
を追加して、vscode が正しいヘッダーファイルを見つけられるようにします。以下のようになります:(macOS バージョンの例、Linux や Windows は Arduino のインストール位置を別途調査する必要があります)
{
"configurations": [
{
"name": "Mac",
"includePath": [
"${workspaceFolder}/**",
"/Applications/Arduino.app/Contents/Java/hardware/**",
"/Applications/Arduino.app/Contents/Java/hardware/arduino/**"
],
"defines": [],
"macFrameworkPath": [
"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks"
],
"compilerPath": "/usr/bin/clang",
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "clang-x64"
}
],
"version": 4
}
その他
noInterrupet()
とcli()
は同じです。Arduino.h
では次のように定義されています。
#define interrupts() sei()
#define noInterrupts() cli()
- Arduino で直接
std:string
を使用すると問題が発生します。Arduino では std を避けた方が良いでしょう。Arduino では、すべての素晴らしい C++ 機能が使用できません。フォーラムのこちらを参照すると、誰かが std ライブラリを Arduino で動作させることを試みたようですが、進捗については不明です。
この実験を通じて、Arduino を通じてさまざまな低レベルの知識を学ぶのは良い方法だと感じました。内蔵のシリアルポートでデバッグができ、USB を接続して IDE をインストールすればすぐにコードをアップロードできるので、さまざまな手間が省けます。同時に、背後の実装も GitHub でコードを見つけることができ、C/C++ の知識を補充する時間を見つけたいと思いました(大学の授業でしか触れたことがありません)。
この記事は Arduino に不慣れなため、できる限り情報を集めて論述を充実させようとしましたが、不完全な点や誤りがあるかもしれませんので、皆様のご指導をお願いいたします。
この記事が役に立ったと思ったら、下のリンクからコーヒーを奢ってくれると嬉しいです ☕ 私の普通の一日が輝かしいものになります ✨
☕Buy me a coffee