導入:アニメの世界を現実に。
アニメやゲームの世界に出てくる道具を作りたい!と皆さん一度は思ったことありませんか?僕はアニメが好きでその中でも特に終末系と分類されるアニメ(少女終末旅行、人類は衰退しました、アポカリプスホテル等)が好きで、その世界観に触れられるようなものを作りたいと思いました。
文明が衰退した後の静かな世界で、もし自分が生き残っていたら……。 「汚染されていない場所はどこか?」「作物を育てられる環境か?」を知るためのデバイスが必要になるはずです。今回は、人類が去った後の地球で黙々と環境ログを収集し続ける、あるいは生き残った人々が生存可能エリアを探すために使う 環境監視端末(通称:終末スキャナー) を自作します。
システム構成
終末世界で動く機械には、「限られた資源で長期間動き続ける省電力性」と、「高度な分析ができる知能」の両立が求められます。そのリアリティを追求し、以下の構成を選定しました。
- 環境データを収集するためのセンサー
- 温湿度気圧計 (BME280)
- 紫外線センサー
- CO2センサー
- CdSセル
- マイコン
- Spresense
- ディスプレイ
- スピーカー、マイク
- スキャナーの筐体
マイコンの選定については、低消費電力ながら、GPSやハイレゾオーディオ、AI処理能力を備えた「オーバースペックな遺産」感を演出するため、Spresenseを選びました。
基本設計
プログラム
このデバイスは、単なる測定器ではなく「自律して動く端末」としての挙動を目指します。
-
常時ログ収集(バックグラウンド): 人が操作していない間も、環境データをSDカード等のストレージに保存し続けます。
-
インタラクティブ表示(フロント): 人がデバイスに触れたり話しかけたりすると、ディスプレイに現在の環境情報や警告、過去の履歴をグラフィカルに表示します。
筐体
終末スキャナーの見た目は某アニメを参考にこんな感じで作りたいと思います。(スキャナーというよりロボットって感じの見た目ですが)
開発工程
システムの中身から外へと開発していこうと思います。今回のシステムでは
- センサーからデータを収集する
- ディスプレイに表示する
- 人とタッチディスプレイやマイクを通じてやり取りする
- 筐体を作る
の順で作成していきます。この記事ではこの最初の「センサーからデータを収集する」部分を実際に作ってみます。
センサーからデータ収集
温湿度気圧センサー (BME280)
Spresenseから温湿度気圧センサー (BME280) からデータを取得するために BME280 と Spresense I2C通信をします。I2C 通信は SDA, と SCL という2本の信号線を使って通信をする規格です。BME280 と Spresense拡張ボード を以下のように接続します。 (Spresense拡張ボードのピン配置図)
| BME280 | Spresense拡張ボード |
|---|---|
| VDD | Vout 5V |
| GND | GND |
| SDI | I2C SDA |
| SDO | GND |
| SCK | I2C SCL |
SpresenseSDK
Spresenseで動かすプログラムを作成方法は2つあり、一つはArduino IDEを使う方法、でもう一つは SpresenseSDK を使う方法です。 Arduino IDE を使う方法は情報が色々あり、プロトタイプをするには簡単ですが、マイコンの性能を100%引き出すことは難しいです。一方 SpresenseSDK を使う方法は情報が少なく、難易度は多少上がりますが、マイコンの性能をほぼ100%引き出すことができたり、複雑な開発をしやすいメリットがあります。今回は筆者の趣味でSpresenseSDKを使ってプログラムを書いてみたいと思います!
SpresenseSDKを使うためのツールのセットアップやexampleプログラムの作成方法は以下の公式ドキュメントを参考にしました。
Spresense SDK スタートガイド (IDE 版)
macOSで開発していたら SpresenseSDK のインストールで少し詰まったのでその時の解決方法を https://hackmd.io/@pond/ry-SvhkzWl に書いておきます。インストールで何か詰まったら参考にしてみてください。
プログラムの実装
Spresense SDKでは、I2Cドライバ(/dev/i2c0 など)に対して ioctl システムコールを使用して通信を行います。以下にセンサーのキャリブレーションを含めたコードを示します。
bme280.c
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <nuttx/i2c/i2c_master.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* I2Cバスのパス (SpresenseメインボードのI2Cコネクタはi2c0) */
#define I2C_DEV_PATH "/dev/i2c0"
/* BME280 I2C Address (SDO=GND: 0x76, SDO=VDD: 0x77) */
#define BME280_ADDR 0x76
/* BME280 Registers */
#define BME280_REG_DIG_T1 0x88
#define BME280_REG_ID 0xD0
#define BME280_REG_RESET 0xE0
#define BME280_REG_CTRL_HUM 0xF2
#define BME280_REG_CTRL_MEAS 0xF4
#define BME280_REG_CONFIG 0xF5
#define BME280_REG_PRESS_MSB 0xF7
/* Calibration data structure */
struct bme280_calib_data {
uint16_t dig_T1;
int16_t dig_T2;
int16_t dig_T3;
uint16_t dig_P1;
int16_t dig_P2;
int16_t dig_P3;
int16_t dig_P4;
int16_t dig_P5;
int16_t dig_P6;
int16_t dig_P7;
int16_t dig_P8;
int16_t dig_P9;
uint8_t dig_H1;
int16_t dig_H2;
uint8_t dig_H3;
int16_t dig_H4;
int16_t dig_H5;
int8_t dig_H6;
};
struct bme280_calib_data calib;
int32_t t_fine; /* Global temperature fine value for pressure/humidity compensation */
/****************************************************************************
* Private Functions
****************************************************************************/
/* I2C Read Wrapper */
static int bme280_read(int fd, uint8_t reg, uint8_t *buffer, int len) {
struct i2c_msg_s msg[2];
struct i2c_transfer_s xfer;
/* Write register address */
msg[0].frequency = 400000;
msg[0].addr = BME280_ADDR;
msg[0].flags = 0;
msg[0].buffer = ®
msg[0].length = 1;
/* Read data */
msg[1].frequency = 400000;
msg[1].addr = BME280_ADDR;
msg[1].flags = I2C_M_READ;
msg[1].buffer = buffer;
msg[1].length = len;
xfer.msgv = msg;
xfer.msgc = 2;
return ioctl(fd, I2CIOC_TRANSFER, (unsigned long)&xfer);
}
/* I2C Write Wrapper */
static int bme280_write(int fd, uint8_t reg, uint8_t value) {
struct i2c_msg_s msg;
struct i2c_transfer_s xfer;
uint8_t buf[2];
buf[0] = reg;
buf[1] = value;
msg.frequency = 400000;
msg.addr = BME280_ADDR;
msg.flags = 0;
msg.buffer = buf;
msg.length = 2;
xfer.msgv = &msg;
xfer.msgc = 1;
return ioctl(fd, I2CIOC_TRANSFER, (unsigned long)&xfer);
}
/* Read Calibration Data */
static void read_calibration_data(int fd) {
uint8_t buf[26];
uint8_t buf_h[7];
// Read Temp/Press calibration
bme280_read(fd, BME280_REG_DIG_T1, buf, 24); // 0x88 to 0x9F
calib.dig_T1 = (buf[1] << 8) | buf[0];
calib.dig_T2 = (int16_t)((buf[3] << 8) | buf[2]);
calib.dig_T3 = (int16_t)((buf[5] << 8) | buf[4]);
calib.dig_P1 = (buf[7] << 8) | buf[6];
calib.dig_P2 = (int16_t)((buf[9] << 8) | buf[8]);
calib.dig_P3 = (int16_t)((buf[11] << 8) | buf[10]);
calib.dig_P4 = (int16_t)((buf[13] << 8) | buf[12]);
calib.dig_P5 = (int16_t)((buf[15] << 8) | buf[14]);
calib.dig_P6 = (int16_t)((buf[17] << 8) | buf[16]);
calib.dig_P7 = (int16_t)((buf[19] << 8) | buf[18]);
calib.dig_P8 = (int16_t)((buf[21] << 8) | buf[20]);
calib.dig_P9 = (int16_t)((buf[23] << 8) | buf[22]);
// Read Humidity calibration
bme280_read(fd, 0xA1, &calib.dig_H1, 1);
bme280_read(fd, 0xE1, buf_h, 7); // 0xE1 to 0xE7
calib.dig_H2 = (int16_t)((buf_h[1] << 8) | buf_h[0]);
calib.dig_H3 = buf_h[2];
calib.dig_H4 = (int16_t)((buf_h[3] << 4) | (buf_h[4] & 0x0F));
calib.dig_H5 = (int16_t)((buf_h[5] << 4) | (buf_h[4] >> 4));
calib.dig_H6 = (int8_t)buf_h[6];
}
/* Temperature Compensation (Returns degrees Celsius) */
static double compensate_temp(int32_t adc_T) {
double var1, var2, T;
var1 = (((double)adc_T) / 16384.0 - ((double)calib.dig_T1) / 1024.0) * ((double)calib.dig_T2);
var2 = ((((double)adc_T) / 131072.0 - ((double)calib.dig_T1) / 8192.0) *
(((double)adc_T) / 131072.0 - ((double)calib.dig_T1) / 8192.0)) * ((double)calib.dig_T3);
t_fine = (int32_t)(var1 + var2);
T = (var1 + var2) / 5120.0;
return T;
}
/* Pressure Compensation (Returns hPa) */
static double compensate_pressure(int32_t adc_P) {
double var1, var2, p;
var1 = ((double)t_fine / 2.0) - 64000.0;
var2 = var1 * var1 * ((double)calib.dig_P6) / 32768.0;
var2 = var2 + var1 * ((double)calib.dig_P5) * 2.0;
var2 = (var2 / 4.0) + (((double)calib.dig_P4) * 65536.0);
var1 = (((double)calib.dig_P3) * var1 * var1 / 524288.0 + ((double)calib.dig_P2) * var1) / 524288.0;
var1 = (1.0 + var1 / 32768.0) * ((double)calib.dig_P1);
if (var1 == 0.0) { return 0; } // Avoid division by zero
p = 1048576.0 - (double)adc_P;
p = (p - (var2 / 4096.0)) * 6250.0 / var1;
var1 = ((double)calib.dig_P9) * p * p / 2147483648.0;
var2 = p * ((double)calib.dig_P8) / 32768.0;
p = p + (var1 + var2 + ((double)calib.dig_P7)) / 16.0;
return p / 100.0; // Pa to hPa
}
/* Humidity Compensation (Returns %RH) */
static double compensate_humidity(int32_t adc_H) {
double var_H;
var_H = (((double)t_fine) - 76800.0);
var_H = (adc_H - (((double)calib.dig_H4) * 64.0 + ((double)calib.dig_H5) / 16384.0 * var_H)) *
(((double)calib.dig_H2) / 65536.0 * (1.0 + ((double)calib.dig_H6) / 67108864.0 * var_H *
(1.0 + ((double)calib.dig_H3) / 67108864.0 * var_H)));
var_H = var_H * (1.0 - ((double)calib.dig_H1) * var_H / 524288.0);
if (var_H > 100.0) var_H = 100.0;
else if (var_H < 0.0) var_H = 0.0;
return var_H;
}
/****************************************************************************
* Public Functions
****************************************************************************/
static int bme280_main() {
int fd;
uint8_t data[8];
uint8_t chip_id;
printf("Starting BME280 example...\n");
/* 1. Open I2C Device */
fd = open(I2C_DEV_PATH, O_RDWR);
if (fd < 0) {
printf("Error: Failed to open %s\n", I2C_DEV_PATH);
return -1;
}
/* 2. Check Chip ID */
bme280_read(fd, BME280_REG_ID, &chip_id, 1);
printf("BME280 Chip ID: 0x%02X (Expected: 0x60)\n", chip_id);
if (chip_id != 0x60) {
printf("Warning: Unknown Chip ID. Please check connection.\n");
}
/* 3. Read Calibration Data */
read_calibration_data(fd);
/* 4. Configure Sensor */
// ctrl_hum must be written before ctrl_meas
// Humidity oversampling x1
bme280_write(fd, BME280_REG_CTRL_HUM, 0x01);
// Config: Standby 1000ms, Filter x16, 3-wire SPI off
bme280_write(fd, BME280_REG_CONFIG, 0xA0);
// Ctrl Meas: Temp x2, Pres x16, Mode Normal
bme280_write(fd, BME280_REG_CTRL_MEAS, 0x57);
/* 5. Measurement Loop */
printf("Entering loop. Press Ctrl+C to exit (if running from nsh).\n");
while (1) {
// Burst read 8 bytes from 0xF7 (Press, Temp, Hum)
if (bme280_read(fd, BME280_REG_PRESS_MSB, data, 8) < 0) {
printf("Error reading data\n");
break;
}
// Parse Raw Data
int32_t adc_P = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4);
int32_t adc_T = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4);
int32_t adc_H = (data[6] << 8) | data[7];
// Compensate (Math)
double temp = compensate_temp(adc_T); // Must calculate Temp first (updates t_fine)
double press = compensate_pressure(adc_P);
double hum = compensate_humidity(adc_H);
// Output
printf("T: %.2f C, P: %.2f hPa, H: %.2f %%\n", temp, press, hum);
sleep(1);
}
close(fd);
return 0;
}
myapps_main.c
#include <nuttx/config.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "sensor/bme280.c"
/****************************************************************************
* main
****************************************************************************/
int main(int argc, char *argv[])
{
bme280_main();
return 0;
}
実行結果
投稿者の人気記事



-
pond6814
さんが
2026/01/14
に
編集
をしました。
(メッセージ: 初版)
-
pond6814
さんが
2026/01/14
に
編集
をしました。
ログインしてコメントを投稿する