BLE通信
- 通信内容
- Pres(気圧):センサの値から1000hPaを引いています
- temp(気温):
- humi(湿度):
- vbat(電圧):リチューム電池のバッテリー
- rssi(無線強度):受信感度です。値はマイナスになるため、ABS関数を使って正の値に変換しています
実測データ
- 時間はX軸に、0.5時間刻みで24時間分を記録しました
- 時間はX軸に、0.5時間刻みで24時間分を記録しました
実装
購入(aliexpressで購入しましたが不具合があると大変です。お勧めしません)
- BMP280ではなくBME280を選んで下さい
Send側
- マイコンはSeeed XIAO BLE nRF52840
wikiを参照
front indication diagram
back indication diagram
Pin List
回路図は下記に示します
Battery Charging current
//Low Charging Current void setup(){ pinMode (P0_13, OUTPUT); } void loop() { digitalWrite(P0_13, HIGH); }
//High Charging Current void setup(){ pinMode (P0_13, OUTPUT); } void loop() { digitalWrite(P0_13, LOW); }
Q1:My Arduino IDE is stuck when uploading code to the board
- A1:You can first try to reset the board by clicking the "Reset Button" once. If that does not work, rapidly click it twice to enter bootloader mode. If that also doesn't work, disconnect the board from the PC, and connect the board again.
Q2:What are the considerations when using XIAO nRF52840 for battery charging?
A2:Currently for this issue, we recommend that users do not turn off the ADC function of P0.14 (D14) or set P0.14 (D14) to high during battery charging.
// Buttery電圧取得 int vbat_raw = analogRead(PIN_VBAT); int vbat_mv = vbat_raw * 2400 / 1023; // VREF = 2.4V, 10bit A/D vbat_mv = vbat_mv * 1510 / 510; // 1M + 510k / 510k
ソース
/* Seed_nRF52840_BLE_Beacon_Send.ino Seeed XIAO BLE nRF52840でバッテリー電圧等のデータを、BLEのアドバタイズデータにのせて送信する */ #define DEBUG 1 #include <Arduino.h> #if DEBUG == 1 #include <Adafruit_TinyUSB.h> // for Serial #endif #include <Wire.h> #define BMP280_address 0x76 #define BME280_address 0x76 #define Ctrl_register 0xf4 #define osrs_t 0b00100000 // Temp 16bit #define osrs_p 0b00000100 // Press 16bit #define mode_normal 0b00000011 #define Config_register 0xf5 #define t_sb 0b00100000 // 62.5 #define filter 0b00000000 #define spi3w_en 0b00000000 #define ID_register 0xd0 #define Temp_register 0xfa #define Tum_register 0xfd #define Press_register 0xf7 // #define CONFIG 0xF5 #define CTRL_MEAS 0xF4 #define CTRL_HUM 0xF2 // BME280気温補正データ uint16_t dig_T1; int16_t dig_T2; int16_t dig_T3; // BME280湿度補正データ uint8_t dig_H1; int16_t dig_H2; uint8_t dig_H3; int16_t dig_H4; int16_t dig_H5; int8_t dig_H6; // BME280気圧補正データ 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; #define BME280_S32_t int32_t #define BME280_U32_t uint32_t #define BME280_S64_t int64_t int32_t t_fine; byte readbuffer[2]; unsigned char dac[26]; unsigned int i; // 設定 const uint16_t DEVICE_ID = 1; // 子機ID (advData.id) const uint8_t DEVICE_TYPE = 10; // 子機種別 (advData.type) // BLE関連 #include "bluefruit.h" // 外部QSPI Flash Memory(省電力化のために使用) #include <Adafruit_SPIFlash.h> Adafruit_FlashTransport_QSPI flashTransport; Adafruit_SPIFlash flash(&flashTransport); // 送信するデータの構造体(nRF52840では2バイト未満はパディングされるので順番に注意) typedef struct { uint8_t maker[4]; // maker_id 子機(nRF52840)の識別用 uint16_t id; // 子機ID uint8_t type; // 子機種別 uint8_t ttl; // TTL (time to live) uint16_t seq; // シーケンス番号 -- ここまで共通フォーマット int16_t pres; // 気圧データ int16_t temp; // 気温データ int16_t humi; // 湿度データ int16_t vbat; // 電圧データ } AdvData; AdvData advData = { .maker = { 0xFF, 0xFF, 0x12, 0x35 }, .id = DEVICE_ID, .type = DEVICE_TYPE, .ttl = 0, .seq = 1, }; // デバッグに便利なマクロ定義 -------- #define sp(x) Serial.println(x) #define spn(x) Serial.print(x) #define spp(k,v) Serial.println(String(k)+"="+String(v)) #define spf(fmt, ...) Serial.printf(fmt, __VA_ARGS__) #define array_length(x) (sizeof(x) / sizeof(x[0])) // deep sleepモードに入る(復帰はリスタートになる) void enterDeepSleep() { nrf_gpio_cfg_sense_set(NRF_GPIO_PIN_MAP(0,2), NRF_GPIO_PIN_SENSE_HIGH); // P0.02 = D0ピンでdeep sleepから復帰 sd_power_system_off(); // NRF_POWER->SYSTEMOFF = 1; } // 測定してデータを送信する uint32_t adc_P, adc_T, adc_H; void measure() { int32_t temp_cal; uint32_t humi_cal, pres_cal; int16_t temp, humi, pres; // Buttery電圧取得 int vbat_raw = analogRead(PIN_VBAT); int vbat_mv = vbat_raw * 2400 / 1023; // VREF = 2.4V, 10bit A/D vbat_mv = vbat_mv * 1510 / 510; // 1M + 510k / 510k advData.vbat = vbat_mv; //測定データ取得 Wire.beginTransmission(BMP280_address);//I2Cスレーブ「Arduino Unoのデータ送信開始 Wire.write(0xF7);//出力データバイトを「気圧データ」のアドレスに指定 Wire.endTransmission();//I2Cスレーブ「Arduino Uno」のデータ送信終了 Wire.requestFrom(BMP280_address, 8);//I2Cデバイス「BME280」に8Byteのデータ要求 for (i=0; i<8; i++){ while (Wire.available() == 0 ){} dac[i] = Wire.read();//dacにI2Cデバイス「BME280」のデータ読み込み if (DEBUG) Serial.printf(" %2x", dac[i]); } adc_P = ((uint32_t)dac[0] << 12) | ((uint32_t)dac[1] << 4) | (( dac[2] >> 4) & 0x0F); adc_T = ((uint32_t)dac[3] << 12) | ((uint32_t)dac[4] << 4) | (( dac[5] >> 4) & 0x0F); adc_H = ((uint32_t)dac[6] << 8) | ((uint32_t)dac[7]); temp_cal = BME280_compensate_T_int32(adc_T);//温度データ補正計算 humi_cal = bme280_compensate_H_int32(adc_H);//湿度データ補正計算 pres_cal = BME280_compensate_P_int64(adc_P);//気圧データ補正計算 advData.pres = (float)pres_cal * 10 / 25600.0;//気圧データを実際の値に計算 advData.temp = (float)temp_cal * 10 / 100.0;//温度データを実際の値に計算 advData.humi = (float)humi_cal * 10 / 1024.0;//湿度データを実際の値に計算 if (DEBUG) Serial.printf("][%d %d %d %d]\n",advData.pres, advData.temp, advData.humi, advData.vbat); // アドバタイズ中なら一旦中断(たぶんしなくていい) if (Bluefruit.Advertising.isRunning()) { Bluefruit.Advertising.stop(); } // データを送信 if (advData.seq > 9999) advData.seq = 0; Bluefruit.Advertising.clearData(); Bluefruit.Advertising.addData(BLE_GAP_AD_TYPE_MANUFACTURER_SPECIFIC_DATA, &advData, sizeof(advData)); Bluefruit.Advertising.start(18); // アドバタイズを開始、引数は終了する時間(s) advData.seq++; } // 初期化 void setup() { Wire.begin(); //BME280動作設定 //BME280動作設定 Wire.beginTransmission(BMP280_address); Wire.write(Config_register); Wire.write(t_sb | filter | spi3w_en); Wire.endTransmission(); delay(100); //BME280測定条件設定 Wire.beginTransmission(BMP280_address); Wire.write(Ctrl_register); Wire.write(osrs_t | osrs_p | mode_normal); Wire.endTransmission(); delay(100); //BME280温度測定条件設定 Wire.beginTransmission(BMP280_address); Wire.write(CTRL_HUM);//湿度測定条件設定 Wire.write(0x01);//「湿度オーバーサンプリングx1」 Wire.endTransmission(); //補正データ取得 readCoefficients(); if (DEBUG) { Serial.begin(115200); pinMode(LED_RED, OUTPUT); pinMode(LED_GREEN, OUTPUT); pinMode(LED_BLUE, OUTPUT); digitalWrite(LED_RED, HIGH); digitalWrite(LED_GREEN, HIGH); digitalWrite(LED_BLUE, HIGH); } // Buttery analogReference(AR_INTERNAL_2_4); // VREF = 2.4V analogReadResolution(10); // 10bit A/D pinMode(14, OUTPUT); digitalWrite(14, LOW); // } // オンボードQSPI Flash MemoryをDeep Power-downモードにして省電力化する flashTransport.begin(); flashTransport.runCommand(0xB9); delayMicroseconds(5); flashTransport.end(); if (DEBUG) { digitalWrite(LED_RED, LOW); delay(500); digitalWrite(LED_RED, HIGH); } //BLEの設定 Bluefruit.begin(); Bluefruit.autoConnLed(false); Bluefruit.setTxPower(4); // 送信強度 最小 -40, 最大 +8 dBm Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); Bluefruit.Advertising.setType(BLE_GAP_ADV_TYPE_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED); Bluefruit.Advertising.setFastTimeout(1); // 高速アドバタイズの終了時間 0=継続(0にするとなぜかstart()が効かない) // WDTの設定 NRF_WDT->CONFIG = 0x01; // Configure WDT to run when CPU is asleep NRF_WDT->CRV = 1+32768*60*30; // CRV = timeout * 32768 + 1 NRF_WDT->RREN = 0x01; // Enable the RR[0] reload register NRF_WDT->TASKS_START = 1; // Start WDT } // メイン void loop() { // int16_t txpowers[4] = { -40, 0, 4, 8 }; // uint16_t intervals[9] = { 32, 64, 128, 256, 384, 512, 1024, 2048, 4096 }; //Battery Hight Charging curren digitalWrite(13, LOW); advData.id = 101; advData.seq = 1; Bluefruit.setTxPower(4); Bluefruit.Advertising.setInterval(256, 256); for (int i=0; i<5; i++) { loop_sub( ); } digitalWrite(LED_GREEN, HIGH); digitalWrite(LED_BLUE, HIGH); while (1) { digitalWrite(LED_RED, LOW); delay(50); digitalWrite(LED_RED, HIGH); delay(2500); // NRF_WDT->RR[0] = WDT_RR_RR_Reload; } } void loop_sub( ) { if (DEBUG) { digitalWrite(LED_GREEN, HIGH); digitalWrite(LED_BLUE, LOW); } uint8_t maker[4]; // maker_id 子機(nRF52840)の識別用 uint16_t id; // 子機ID uint8_t type; // 子機種別 uint8_t ttl; // TTL (time to live) uint16_t seq; // シーケンス番号 -- ここまで共通フォーマット int16_t pres; // 気圧データ int16_t temp; // 気温データ int16_t humi; // 湿度データ measure(); // 測定してデータを送信する if (DEBUG) Serial.printf(" [%x]", advData.vbat); if (DEBUG) Serial.printf("[%2x", advData.maker[0]); if (DEBUG) Serial.printf(" %2x", advData.maker[1]); if (DEBUG) Serial.printf(" %2x", advData.maker[2]); if (DEBUG) Serial.printf(" %2x", advData.maker[3]); if (DEBUG) Serial.printf(" %2x", advData.id >> 8); if (DEBUG) Serial.printf(" %2x", advData.id & 0xff); if (DEBUG) Serial.printf(" %2x", advData.ttl); if (DEBUG) Serial.printf(" %2x", advData.seq >> 8); if (DEBUG) Serial.printf(" %2x", advData.seq & 0xff); delay(500); if (DEBUG) { digitalWrite(LED_BLUE, HIGH); digitalWrite(LED_GREEN, LOW); } delay(1000); // WDT Update NRF_WDT->RR[0] = WDT_RR_RR_Reload; } //温度補正 関数 BME280_S32_t BME280_compensate_T_int32(BME280_S32_t adc_T) { BME280_S32_t var1, var2, T; var1 = ((((adc_T>>3) - ((BME280_S32_t)dig_T1<<1))) * ((BME280_S32_t)dig_T2)) >> 11; var2 = (((((adc_T>>4) - ((BME280_S32_t)dig_T1)) * ((adc_T>>4) - ((BME280_S32_t)dig_T1))) >> 12) * ((BME280_S32_t)dig_T3)) >> 14; t_fine = var1 + var2; T = (t_fine * 5 + 128) >> 8; return (BME280_S32_t)T; } //湿度補正 関数 BME280_U32_t bme280_compensate_H_int32(BME280_S32_t adc_H) { BME280_S32_t v_x1_u32r; v_x1_u32r = (t_fine - ((BME280_S32_t)76800)); v_x1_u32r = (((((adc_H << 14) - (((BME280_S32_t)dig_H4) << 20) - (((BME280_S32_t)dig_H5) * v_x1_u32r)) + ((BME280_S32_t)16384)) >> 15) * (((((((v_x1_u32r * ((BME280_S32_t)dig_H6)) >> 10) * (((v_x1_u32r * ((BME280_S32_t)dig_H3)) >> 11) + ((BME280_S32_t)32768))) >> 10) + ((BME280_S32_t)2097152)) * ((BME280_S32_t)dig_H2) + 8192) >> 14)); v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * ((BME280_S32_t)dig_H1)) >> 4)); v_x1_u32r = (v_x1_u32r < 0 ? 0 : v_x1_u32r); v_x1_u32r = (v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r); return (BME280_U32_t)(v_x1_u32r>>12); } //気圧補正 関数 BME280_U32_t BME280_compensate_P_int64(BME280_S32_t adc_P) { BME280_S64_t var1, var2, p; var1 = ((BME280_S64_t)t_fine) - 128000; var2 = var1 * var1 * (BME280_S64_t)dig_P6; var2 = var2 + ((var1*(BME280_S64_t)dig_P5)<<17); var2 = var2 + (((BME280_S64_t)dig_P4)<<35); var1 = ((var1 * var1 * (BME280_S64_t)dig_P3)>>8) + ((var1 * (BME280_S64_t)dig_P2)<<12); var1 = (((((BME280_S64_t)1)<<47)+var1))*((BME280_S64_t)dig_P1)>>33; if (var1 == 0) { return 0; // avoid exception caused by division by zero } p = 1048576 - adc_P; p = (((p<<31) - var2)*3125) / var1; var1 = (((BME280_S64_t)dig_P9) * (p>>13) * (p>>13)) >> 25; var2 = (((BME280_S64_t)dig_P8) * p) >> 19; p = ((p + var1 + var2) >> 8) + (((BME280_S64_t)dig_P7)<<4); return (BME280_U32_t)p; } void readCoefficients(){ //BME280補正データ取得 Wire.beginTransmission(BMP280_address); Wire.write(0x88);//出力データバイトを「補正データ」のアドレスに指定 Wire.endTransmission(); Wire.requestFrom(BMP280_address, 26); for (i=0; i<26; i++){ while (Wire.available() == 0 ){} dac[i] = Wire.read();//dacにI2Cデバイス「BME280」のデータ読み込み } //気温補正データ dig_T1 = ((uint16_t)((dac[1] << 8) | dac[0])); dig_T2 = ((int16_t)((dac[3] << 8) | dac[2])); dig_T3 = ((int16_t)((dac[5] << 8) | dac[4])); //気圧補正データ dig_P1 = ((uint16_t)((dac[7] << 8) | dac[6])); dig_P2 = ((int16_t)((dac[9] << 8) | dac[8])); dig_P3 = ((int16_t)((dac[11] << 8) | dac[10])); dig_P4 = ((int16_t)((dac[13] << 8) | dac[12])); dig_P5 = ((int16_t)((dac[15] << 8) | dac[14])); dig_P6 = ((int16_t)((dac[17] << 8) | dac[16])); dig_P7 = ((int16_t)((dac[19] << 8) | dac[18])); dig_P8 = ((int16_t)((dac[21] << 8) | dac[20])); dig_P9 = ((int16_t)((dac[23] << 8) | dac[22])); //湿度補正データ dig_H1 = ((uint8_t)(dac[25])); Wire.beginTransmission(BMP280_address); Wire.write(0xE1);//出力データバイトを「補正データ」のアドレスに指定 Wire.endTransmission(); Wire.requestFrom(BMP280_address, 7);//I2Cデバイス「BME280」に7Byteのデータ要求 for (i=0; i<7; i++){ while (Wire.available() == 0 ){} dac[i] = Wire.read();//dacにI2Cデバイス「BME280」のデータ読み込み } dig_H2 = ((int16_t)((dac[1] << 8) | dac[0])); dig_H3 = ((uint8_t)(dac[2])); dig_H4 = ((int16_t)((dac[3] << 4) + (dac[4] & 0x0F))); dig_H5 = ((int16_t)((dac[5] << 4) + ((dac[4] >> 4) & 0x0F))); dig_H6 = ((int8_t)dac[6]); delay(100);//1000msec待機(1秒待機) }
receive側
- マイコンはXIAO EPS32C3
- 0.42インチのOLEDディスプレイ画面、セラミックアンテナ、Type-c USBインターフェイス、リセットボタン、スタートボタンを装備
indication diagram
Pin List
Add the ESP32 development environment
- ESP32 development environment add path, in Arduino IDE, Open the file - > preferences (keyboard shortcuts "CtrI +,") will https://dl.espressif.com/d/package_esp32 index.json development board json address additional development board manager into the site. Click "OK" (new version is "OK"). Click "OK" again (new version is "OK") to return to the Arduino IDE home page.
購入
ソース
/* XIAO_ESP32C3_LED_Receive.ino */ // #include <M5Unified.h> #include <Arduino.h> // U8g2 #include <U8g2lib.h> #ifdef U8X8_HAVE_HW_SPI #include <SPI.h> #endif #ifdef U8X8_HAVE_HW_I2C #include <Wire.h> #endif #define SDA_PIN 5 #define SCL_PIN 6 U8G2_SSD1306_72X40_ER_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); // EastRising 0.42" OLED // LED int ledPin = 8; // LED connected to digital pin 8 // 設定 const size_t RCV_CNT_MAX = 10; // 同時に受信するデバイスの最大数 const int BLE_SCAN_TIME = 5; // BLEのスキャンを行う時間(s) const char XIAO[4] = { 0xFF, 0xFF, 0x12, 0x35 }; // XIAO nRF52840識別値(FFFFは固定) // BLE関連 #include <BLEDevice.h> BLEScan* pBLEScan; // シーケンス番号の記憶 #include <map> std::map<uint16_t, uint16_t> seqHist; // デバッグに便利なマクロ定義 -------- #define sp(x) Serial.println(x) #define spn(x) Serial.print(x) #define spp(k,v) Serial.println(String(k)+"="+String(v)) #define spf(fmt, ...) Serial.printf(fmt, __VA_ARGS__) // その他定義 struct RcvData { uint16_t id; // 子機ID uint8_t type; // 子機種別 uint8_t ttl; // TTL (time to live) uint16_t seq; // シーケンス番号 float pres; // 気圧データ float temp; // 気温データ float humi; // 湿度データ float vbat; // 電圧データ int16_t rssi; // 電波強度 RSSI -50非常に強い -80弱い }; // 初期化 void setup() { // auto cfg = M5.config(); // M5.begin(cfg); Serial.begin(115200); delay(1000); sp("System Start!"); // ディスプレイの設定 Wire.begin(SDA_PIN, SCL_PIN); delay(100); u8g2.begin(); // u8g2.setFont(u8g2_font_helvB10_tr); // choose a suitable font u8g2.setFont(u8g2_font_lubB10_tr); // choose a suitable font // u8g2.setDisplayRotation(U8G2_R2); delay(100); // BLEの設定 sp("init BLE device..."); BLEDevice::init(""); pBLEScan = BLEDevice::getScan(); pBLEScan->setActiveScan(false); // パッシブスキャンにする } // メイン int BleTimer = 0; void loop() { RcvData rcvdatas[RCV_CNT_MAX]; int rcvcnt = 0; float pres,temp,humi; uint16_t rssi; // BLEのスキャンを行う // sp("\nscanning..."); BLEScanResults foundDevices = pBLEScan->start(BLE_SCAN_TIME); int hit = foundDevices.getCount(); // spf("found", hit); // 受信した一覧から対象デバイスを抽出する for (int i=0; i<hit; i++) { BLEAdvertisedDevice dev = foundDevices.getDevice(i); std::string data = dev.getManufacturerData(); // XIAO nRF52840からのデータだったら値を格納する if (data.length() < 18 )continue; if (data[0] == XIAO[0] && data[1] == XIAO[1] && data[2] == XIAO[2] && data[3] == XIAO[3]) { rcvdatas[rcvcnt].id = data[5] << 8 | data[4]; rcvdatas[rcvcnt].type = data[6]; rcvdatas[rcvcnt].ttl = data[7]; rcvdatas[rcvcnt].seq = data[9] << 8 | data[8]; if (seqHist[rcvdatas[rcvcnt].id] == rcvdatas[rcvcnt].seq) continue; // 同じデータは無視 seqHist[rcvdatas[rcvcnt].id] = rcvdatas[rcvcnt].seq; if (rcvdatas[rcvcnt].type == 10) { // 種別10: 実験用 volt(i2) temp(i2) rcvdatas[rcvcnt].pres = (float)(data[11] << 8 | data[10]) / 10.00 ; rcvdatas[rcvcnt].temp = (float)(data[13] << 8 | data[12]) / 10.00; rcvdatas[rcvcnt].humi = (float)(data[15] << 8 | data[14]) / 10.00; rcvdatas[rcvcnt].vbat = (float)(data[17] << 8 | data[16]) / 100; rcvdatas[rcvcnt].rssi = (uint16_t)dev.getRSSI() ; // 電波強度 rcvcnt ++; } if (rcvcnt >= RCV_CNT_MAX) break; } } // ディスプレイに表示 if (rcvcnt > 0) { rcvcnt-- ; if ( BleTimer > 5 ){ Serial.printf("\nPres:%.1f,temp:%.1f,humi:%.1f,vbat:%.1f,rssi:%d", rcvdatas[rcvcnt].pres - 1000, rcvdatas[rcvcnt].temp, rcvdatas[rcvcnt].humi, rcvdatas[rcvcnt].vbat, abs( rcvdatas[rcvcnt].rssi ) ); u8g2.clearBuffer(); // clear the internal memory u8g2.setCursor(0,14); u8g2.printf("pres:%4d", int (rcvdatas[rcvcnt].pres) ); u8g2.setCursor(0,27); u8g2.printf("temp:%4.1f", rcvdatas[rcvcnt].temp); u8g2.setCursor(0,40); u8g2.printf("hum: %4.1f", rcvdatas[rcvcnt].humi); u8g2.display(); u8g2.sendBuffer(); // transfer internal memory to the display } BleTimer = 0; } else { BleTimer++; if ( (BleTimer % 5 ) == 0 ){ Serial.printf(" %d",BleTimer/5); } } delay(100); }