Makato-kanのアイコン画像
Makato-kan 2026年01月29日作成
製作品 製作品 閲覧数 22
Makato-kan 2026年01月29日作成 製作品 製作品 閲覧数 22

試作 屋外専用 カメラ付き温湿度計

試作 屋外専用 カメラ付き温湿度計
初めに

任意の場所で測定した 温湿度の記録を後で見返しても、どこで記録したかわからなくなっている場合があり、今回スプレッセンスのカメラをつけてモニターとして使えるチャンスがあったので、画像データも記録する温湿度ロガーを作ってみました。

動作

3分毎に撮影したJPEGファイルと緯度経度と温湿度を測定したCSVファイルを作り、SDカードに記録します。

本体の感じ

キャプションを入力できます

材料
番号 品名 個数
1 スプレッセンスメイン基板 1
2 拡張基板 1
3 カメラ基板 1
4 BME280 1
5 DS18B20 1
カメラ取付部品

ユニバーサル基板に"屋外用強力両面テープ"を使って固定しています。
データはFreeCADで作成し、githubにアップしました。
カメラ取付部品 https://github.com/makatokan/spresense-camera-adapter/tree/main
キャプションを入力できます

開発環境のセットアップ
番号 検索名 バージョン
1 ArduinoIDE 2.3.7
2 Spresense 2.4.5 ボードマネージャー
3 カメラ基板
4 BME280 by Tyler Glenn 3.0.0 ライブラリマネージャー
5 DallasTemperature(DS18B20) 4.0.5 ライブラリマネージャー
DS18B20について

arduinoide付属のOneWireではスプレッセンス側の対応が一部無いらしく正常動作しません、 URL:https://note.com/regnant_saya/n/n2560adf75690 に記述ございました。
この記事にあったライブラリを使わせていただいております。
尚このライブラリはオリジナルのOneire.hと入れ替えて使うようになっていますのでおりますのでオリジナルも必要な場合はあらかじめコピーとるとか別な名前で使えるようにするとかの工夫必要です

回路図

キャプションを入力できます

プログラム
#include <RTC.h> #include <GNSS.h> #include <Wire.h> //BME280 #include <BME280I2C.h> #include <OneWire.h> //DS18B20 #include <SDHCI.h> #include <Camera.h> #define MY_TIMEZONE_IN_SECONDS (9 * 60 * 60) // JST #define SERIAL_BAUD 9600 #define SD_CS_PIN 4 // ← SDカード CSピン float latitude1(NAN),longitude1(NAN); int test_lati(0),check_data(0),year_check(0); String time1(""),time2(""); String filename = ""; String filename1 = ""; SDClass SD; SpGnss Gnss; SpNavData NavData; BME280I2C bme; File dataFile; float temp(NAN), hum(NAN), pres(NAN);//温度・湿度・気圧 String temp2(""); int DS18B20_Pin(13); //ピンNo変えるときはOneWire.cppのライブラリも書き換える OneWire ds(DS18B20_Pin); // on digital pin 13 //Camera /** * Print error message */ void printError(enum CamErr err) { Serial.print("Error: "); switch (err) { case CAM_ERR_NO_DEVICE: Serial.println("No Device"); break; case CAM_ERR_ILLEGAL_DEVERR: Serial.println("Illegal device error"); break; case CAM_ERR_ALREADY_INITIALIZED: Serial.println("Already initialized"); break; case CAM_ERR_NOT_INITIALIZED: Serial.println("Not initialized"); break; case CAM_ERR_NOT_STILL_INITIALIZED: Serial.println("Still picture not initialized"); break; case CAM_ERR_CANT_CREATE_THREAD: Serial.println("Failed to create thread"); break; case CAM_ERR_INVALID_PARAM: Serial.println("Invalid parameter"); break; case CAM_ERR_NO_MEMORY: Serial.println("No memory"); break; case CAM_ERR_USR_INUSED: Serial.println("Buffer already in use"); break; case CAM_ERR_NOT_PERMITTED: Serial.println("Operation not permitted"); break; default: break; } } /** * Callback from Camera library when video frame is captured. */ void CamCB(CamImage img) { /* Check the img instance is available or not. */ if (img.isAvailable()) { /* If you want RGB565 data, convert image data format to RGB565 */ img.convertPixFormat(CAM_IMAGE_PIX_FMT_RGB565); /* You can use image data directly by using getImgSize() and getImgBuff(). * for displaying image to a display, etc. */ // Serial.print("Image data size = "); // Serial.print(img.getImgSize(), DEC); // Serial.print(" , "); // Serial.print("buff addr = "); // Serial.print((unsigned long)img.getImgBuff(), HEX); // Serial.println(""); } else { Serial.println("Failed to get video stream image"); } } //Camera //GNSS void printClock(RtcTime &rtc){ time1 = String(rtc.year())+","+String(rtc.month())+","+String(rtc.day()); time2 = String(rtc.hour())+":"+String(rtc.minute()); year_check = rtc.year(); } void updateClock(){ static RtcTime old; RtcTime now = RTC.getTime(); if (now != old){ printClock(now); old = now; } } void printBME280Data(Stream* client){ BME280::TempUnit tempUnit(BME280::TempUnit_Celsius); BME280::PresUnit presUnit(BME280::PresUnit_Pa); bme.read(pres, temp, hum, tempUnit, presUnit); temp2 = String(temp,1)+","+String(hum,0)+","+String(pres/100,1); delay(10); } float getTemp(){ //DS18B20温度測定 delay(50); //returns the temperature from one DS18S20 in DEG Celsius byte data[12]; byte addr[8]; if ( !ds.search(addr)){ //no more sensors on chain, reset search ds.reset_search(); return -1000; } if ( OneWire::crc8( addr, 7) != addr[7]){ Serial.println("CRC is not valid!"); return -1000; } if ( addr[0] != 0x10 && addr[0] != 0x28){ Serial.print("Device is not recognized"); return -1000; } delay(50); ds.reset(); ds.select(addr); ds.write(0x44,1); // start conversion, with parasite power on at the end byte present = ds.reset(); ds.select(addr); ds.write(0xBE); // Read Scratchpad for (int i = 0; i < 9; i++) { // we need 9 bytes data[i] = ds.read(); } ds.reset_search(); byte MSB = data[1]; byte LSB = data[0]; float tempRead = ((MSB << 8) | LSB); //using two's compliment float TemperatureSum = tempRead / 16; return TemperatureSum; } String zero2(int v) { if (v < 10) return "0" + String(v); return String(v); } void setup(){ pinMode(LED0, OUTPUT); pinMode(LED1, OUTPUT); pinMode(LED2, OUTPUT); pinMode(LED3, OUTPUT); Serial.begin(SERIAL_BAUD); while (!Serial); Wire.begin(); //拡張基板I2C用 Serial.println("Start Wire!"); RTC.begin(); int ret; ret = Gnss.begin(); assert(ret == 0); ret = Gnss.start(); assert(ret == 0); Serial.println("Start GNSS!"); while(!bme.begin()) { Serial.println("Could not find BME280 sensor!"); delay(1000); } Serial.println("Start BME280!"); if (Gnss.waitUpdate()){ // Get the UTC time Gnss.getNavData(&NavData); SpGnssTime *time = &NavData.time; // Check if the acquired UTC time is accurate if (time->year >= 2000) { RtcTime now = RTC.getTime(); // Convert SpGnssTime to RtcTime RtcTime gps(time->year, time->month, time->day, time->hour, time->minute, time->sec, time->usec * 1000); #ifdef MY_TIMEZONE_IN_SECONDS // Set the time difference gps += MY_TIMEZONE_IN_SECONDS; #endif int diff = now - gps; if (abs(diff) >= 1) { RTC.setTime(gps); } } } while(check_data == 0){ if (Gnss.waitUpdate()) { Gnss.getNavData(&NavData); SpGnssTime *time = &NavData.time; if (time->year >= 2000) { RtcTime now = RTC.getTime(); RtcTime gps(time->year, time->month, time->day, time->hour, time->minute, time->sec, time->usec * 1000); #ifdef MY_TIMEZONE_IN_SECONDS gps += MY_TIMEZONE_IN_SECONDS; #endif int diff = now - gps; if (abs(diff) >= 1) { RTC.setTime(gps); } } } updateClock(); Gnss.waitUpdate(); delay(10); Gnss.getNavData(&NavData); digitalWrite(LED0,HIGH); delay(10); latitude1 = (NavData.latitude); longitude1 = (NavData.longitude); delay(10); test_lati = (NavData.latitude); if(test_lati >0){ check_data = 1; } } // updateClock(); // --- SDカード初期化 --- Serial.print("Initializing SD card... "); if (!SD.begin(SD_CS_PIN)) { Serial.println("SD init failed!"); } else { Serial.println("SD init OK."); } digitalWrite(LED0,LOW); getTemp(); //Camera /* begin() without parameters means that * number of buffers = 1, 30FPS, QVGA, YUV 4:2:2 format */ CamErr err; Serial.println("Prepare camera"); err = theCamera.begin(); if (err != CAM_ERR_SUCCESS) { printError(err); } /* Start video stream. * If received video stream data from camera device, * camera library call CamCB. */ Serial.println("Start streaming"); err = theCamera.startStreaming(true, CamCB); if (err != CAM_ERR_SUCCESS) { printError(err); } /* Auto white balance configuration */ Serial.println("Set Auto white balance parameter"); err = theCamera.setAutoWhiteBalanceMode(CAM_WHITE_BALANCE_DAYLIGHT); if (err != CAM_ERR_SUCCESS) { printError(err); } /* Set parameters about still picture. * In the following case, QUADVGA and JPEG. */ Serial.println("Set still picture format"); err = theCamera.setStillPictureImageFormat( CAM_IMGSIZE_QUADVGA_H, CAM_IMGSIZE_QUADVGA_V, CAM_IMAGE_PIX_FMT_JPG); if (err != CAM_ERR_SUCCESS) { printError(err); } /** * @brief Take picture with format JPEG per second */ //Camera } void loop(){ float temperature = getTemp(); printBME280Data(&Serial); delay(10); Serial.println("BME280 temp = "); updateClock(); Gnss.waitUpdate(); delay(10); Gnss.getNavData(&NavData); delay(10); latitude1 = (NavData.latitude); longitude1 = (NavData.longitude); int yy = year_check % 100; // 下2桁 int mm = RTC.getTime().month(); int dd = RTC.getTime().day(); int hh = RTC.getTime().hour(); int mi = RTC.getTime().minute(); filename = zero2(yy) + zero2(mm) + zero2(dd) + zero2(hh) + zero2(mi) + ".csv"; Serial.println("CSV filename = " + filename); String data1 = String(time1)+","+String(latitude1)+","+String(longitude1)+","+String(temp2)+","+String(temperature,1); dataFile = SD.open("/" + filename, FILE_WRITE); if (dataFile) { dataFile.println(data1); dataFile.close(); Serial.println("Write OK: " + data1); } else { Serial.println("Write ERROR: " + filename); } // ----------------------------- delay(10); //Camera Serial.println("Call takePicture()"); CamImage img = theCamera.takePicture(); /* Check availability of the img instance. */ /* If any errors occur, the img is not available. */ if (img.isAvailable()) { /* Create file name */ filename1 = zero2(yy) + zero2(mm) + zero2(dd) + zero2(hh) + zero2(mi) + ".jpg"; Serial.println("JPEG filename = " + filename1); Serial.print("Save taken picture as "); Serial.print(filename1); Serial.println(""); /* Remove the old file with the same file name as new created file, * and create new file. */ SD.remove(filename1); File myFile = SD.open(filename1, FILE_WRITE); myFile.write(img.getImgBuff(), img.getImgSize()); myFile.close(); delay(100); } else { /* The size of a picture may exceed the allocated memory size. * Then, allocate the larger memory size and/or decrease the size of a picture. * [How to allocate the larger memory] * - Decrease jpgbufsize_divisor specified by setStillPictureImageFormat() * - Increase the Memory size from Arduino IDE tools Menu * [How to decrease the size of a picture] * - Decrease the JPEG quality by setJPEGQuality() */ Serial.println("Failed to take picture"); } //Camera delay(10); digitalWrite(LED3,HIGH); delay(30000); digitalWrite(LED3,LOW); delay(150000); }
出力ファイル例

ファイル名で2020年1月26日4時22分を表しています
2601020422.csv
データの並び 年 月 日 緯度 経度 温度 湿度 気圧 DS18B20の温度
2026,1,26,35.36,138.77,9.0,75,980.4,8.1
ほかにカメラ画像を日付付き名前のjpeg保存しております。

最後に

カメラを固定する方法に手間取り、1月に3Dプリンタを購入して部材を作りました
 外装ケースまで作る事が出来なかったので、試作といたしました。

Makato-kanのアイコン画像
屋外で温度湿度を測定し記録するので、壊れやすいと思い大量にDHT11とESP-WROOM-02を購入したのですが思いのほか壊れなかったので、何にでもDHT11をくっつけています。
ログインしてコメントを投稿する