はじめに
クレアリンクテクノロジー E220-900T22S(JP)
SEMTEC社LoRaチップ「LLCC68」 搭載LoRaモジュール
このLoRaモジュールにはWOR(Wake on Radio)機能があり省電力なIoTに役立つので使ってみようと思います
WORとは
WOR(Wake on Radio)対応で、省電力待機状態のモジュールをワイヤレスプリアンブル信号によって起こして受信させることができ、それによってデバイスへ割り込みを発生させるなどし、機器全体の省電力制御をワイヤレスで実現することが可能です。
E220-900T22S(JP)データシート
https://dragon-torch.tech/wp-content/uploads/2023/12/data_sheet_Rev1.5.pdf
AUXピンがLOWになることでマイコンを起動させます
こちらのライブラリのexampleを参考にSeeed XiaoESP32C3を使ってみました(ピン数は少なめですが1000円以下でコンパクトでセンサー用にばらまくにはおすすめのマイコンボードです)
https://github.com/xreef/LoRa_E32_Series_Library/blob/master/examples/manageWakeUpOfMicrocontroller/esp32_e32_wake_up_from_WOR_deep_sleep/esp32_e32_wake_up_from_WOR_deep_sleep.ino
なおこのLoRa_E32_Series_LibraryはE220-900T22S(JP)に今のところ非対応です
XiaoESP32C3のピン配置
ここでDeepSleepからGPIOWakeupに使えるピンはVDD3P3_RTCによって電力供給されるパッド GPIO0 ~ 5 とあり
このボードではD0~D3の4本のみでこれをLoRaモジュールの AUX ピン用に使います
主に使う関数
-
gpio_hold_en(gpio_num_t gpio_num)
https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/gpio.html?highlight=gpio_hold_en#_CPPv412gpio_hold_en10gpio_num_t -
gpio_hold_dis(gpio_num_t gpio_num)
https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/gpio.html?highlight=gpio_hold_en#_CPPv413gpio_hold_dis10gpio_num_t -
gpio_deep_sleep_hold_en(void)
https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/gpio.html?highlight=gpio_deep_sleep_hold_en#_CPPv423gpio_deep_sleep_hold_env -
esp_deep_sleep_enable_gpio_wakeup(uint64_t gpio_pin_mask, esp_deepsleep_gpio_wake_up_mode_t mode)
https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/system/sleep_modes.html?highlight=esp_deep_sleep_enable_gpio_wakeup#_CPPv433esp_deep_sleep_enable_gpio_wakeup8uint64_t33esp_deepsleep_gpio_wake_up_mode_t -
esp_deep_sleep_start(void)
https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/system/sleep_modes.html?highlight=esp_deep_sleep_start#_CPPv421esp_light_sleep_startv
(注)EPS32やESP32S3とESP32C3とでいくつか関数の名前や仕様が異なる場合があります 今回はESP32C3についての関数です
LoRaモジュールの設定
- 送信‐受信でUART Serial Port Rate、Air Data Rate、SF(拡散率)、BW(帯域幅)、周波数CH、WOR サイクルを同じに設定します
また注意点として
⚫ 周波数チャンネルが 923.5 MHz〜928.0MHz の範囲、すなわち
BW:125kHz で CH:15-37 を選択した場合、
BW:250kHz で CH:14-36 を選択した場合、
および BW:500kHz で CH:13-30 を選択した場合
➢ 送信時間が 400ms 制限の規定があるため、レジスタでの設定値によらずサブパケッ
ト長が 32byte に変更されます。また、プリアンブル送信にかかる時間が制限を超え
てしまうため、WOR モードは利用しないでください。
省電力ゲートウェイ
乾電池駆動のセンサー子機とソーラー電池駆動のLTEゲートウェイを以下のような構成で考えてみました
例えば子機からの送信が数秒/回で送信間隔が1時間のとき親機側は常時受信を待ち受けてる必要がなく大幅に省電力化できます
親機が定期的に子機を起こして終わったらDeepSleep
・NormalMode : 送信:可 受信:可
・WORSendingMode 送信:WORモード 受信:可
・WORReceivingMode 送信:不可 受信:WORモード
・ConfigurationMode 設定変更モード/Deepsleepモード: 送信:不可 受信:不可
#include <Arduino.h>
#define SerialMon Serial
#define SerialLoRa Serial1
// E220-900T22S(JP)へのピンアサイン
#define LoRa_ModeSettingPin_M0 GPIO_NUM_2//D0
#define LoRa_ModeSettingPin_M1 GPIO_NUM_3//D1
#define LoRa_Rx_ESP_TxPin GPIO_NUM_21//D6
#define LoRa_Tx_ESP_RxPin GPIO_NUM_20//D7
#define LoRa_AUXPin GPIO_NUM_4//D2
// E220-900T22S(JP)のbaud rate
#define LoRa_BaudRate 9600
int16_t senserID = 0x0001;
RTC_DATA_ATTR uint16_t bootCount = 0;
esp_sleep_source_t wakeup_reason;
uint8_t conf[] ={0xc0, 0x00, 0x08,
senserID >> 8, //ADDH
senserID & 0xff, //ADDL
0b01110000, // baud_rate 9600 bps SF:9 BW:125
0b11100000, //subpacket_size 32, rssi_ambient_noise_flag on, transmitting_power 13 dBm
0x09, //own_channel
0b11000111, //RSSI on ,fix mode,wor_cycle 4000 ms
0x00, //CRYPT
0x00};
struct msgStruct{
char targetAdressH = 0x00;//GateWay adress 0x0000
char targetAdressL = 0x00;
char targetChannel = 0x09;
uint16_t myadress = senserID;
uint16_t bootcount ;
} msg;
/**
* @brief ノーマルモード(M0=0,M1=0)へ移行する
*/
void SwitchToNormalMode(void){
digitalWrite(LoRa_ModeSettingPin_M0, 0);
digitalWrite(LoRa_ModeSettingPin_M1, 0);
delay(100);
}
/**
* @brief WOR受信モード(M0=0,M1=1)へ移行する
*/
void SwitchToWORReceivingMode(void){
digitalWrite(LoRa_ModeSettingPin_M0, 0);
digitalWrite(LoRa_ModeSettingPin_M1, 1);
delay(100);
}
/**
* @brief コンフィグ/sleepモード(M0=1,M1=1)へ移行する
*/
void SwitchToConfigurationMode(void){
digitalWrite(LoRa_ModeSettingPin_M0, 1);
digitalWrite(LoRa_ModeSettingPin_M1, 1);
delay(100);
}
void IRAM_ATTR deep_sleep(){
SwitchToWORReceivingMode();
delay(100);
Serial.println();
gpio_hold_en(LoRa_ModeSettingPin_M0);
gpio_hold_en(LoRa_ModeSettingPin_M1);
esp_deep_sleep_enable_gpio_wakeup(BIT(LoRa_AUXPin), ESP_GPIO_WAKEUP_GPIO_LOW);
gpio_deep_sleep_hold_en();
//Go to sleep now
Serial.println("Going to sleep now");
delay(1000);
esp_deep_sleep_start();
}
void wakeup_cause_print(esp_sleep_source_t wakeup_reason) {
switch (wakeup_reason) {
case 0: Serial.println("Wakeup caused by ESP_SLEEP_WAKEUP_UNDEFINED"); break;
case 1: Serial.println("Wakeup caused by ESP_SLEEP_WAKEUP_ALL"); break;
case 2: Serial.println("Wakeup caused by ESP_SLEEP_WAKEUP_EXT0"); break;
case 3: Serial.println("Wakeup caused by ESP_SLEEP_WAKEUP_EXT1"); break;
case 4: Serial.println("Wakeup caused by ESP_SLEEP_WAKEUP_TIMER"); break;
case 5: Serial.println("Wakeup caused by ESP_SLEEP_WAKEUP_TOUCHPAD"); break;
case 6: Serial.println("Wakeup caused by ESP_SLEEP_WAKEUP_ULP"); break;
case 7: Serial.println("Wakeup caused by ESP_SLEEP_WAKEUP_GPIO"); break;
case 8: Serial.println("Wakeup caused by ESP_SLEEP_WAKEUP_UART"); break;
case 9: Serial.println("Wakeup caused by ESP_SLEEP_WAKEUP_WIFI"); break;
case 10: Serial.println("Wakeup caused by ESP_SLEEP_WAKEUP_COCPU"); break;
case 11: Serial.println("Wakeup caused by ESP_SLEEP_WAKEUP_COCPU_TRAP_TRIG"); break;
case 12: Serial.println("Wakeup caused by ESP_SLEEP_WAKEUP_BT"); break;
default: Serial.println("Wakeup was not caused by deep sleep"); break;
}
}
void setup() {
pinMode(LoRa_ModeSettingPin_M0, OUTPUT);
pinMode(LoRa_ModeSettingPin_M1, OUTPUT);
SerialMon.begin(9600);
delay(500);
// E220-900T22S(JP)へのLoRa初期設定
SerialLoRa.end();
delay(1000);
SerialLoRa.begin(LoRa_BaudRate, SERIAL_8N1, LoRa_Tx_ESP_RxPin,LoRa_Rx_ESP_TxPin);
wakeup_reason = esp_sleep_get_wakeup_cause();
wakeup_cause_print(wakeup_reason);
if (ESP_SLEEP_WAKEUP_GPIO == wakeup_reason) {
Serial.println("Waked up from external GPIO!");
gpio_hold_dis(LoRa_ModeSettingPin_M0);
gpio_hold_dis(LoRa_ModeSettingPin_M1);
gpio_deep_sleep_hold_dis();
SwitchToNormalMode();
delay(100);
}else{
Serial.println("Waked up from nomal power on!");
SerialMon.printf("\n sensorID: %d\n",senserID);
SwitchToConfigurationMode();
while(!digitalRead(LoRa_AUXPin)){}
SerialMon.printf("I send conf\r\n");
for (size_t i = 0; i < sizeof(conf); i++)
{
SerialMon.printf(" %02x",conf[i]);
}
SerialLoRa.write((uint8_t *)&conf, sizeof(conf));
delay(10000);
deep_sleep();
}
Serial.println("Wake and start ");
SwitchToNormalMode();
msg.bootcount = bootCount;
Serial.printf("boot:%d \n" ,msg.bootcount);
SerialLoRa.flush();
uint8_t payload[]={msg.targetAdressH, msg.targetAdressL, msg.targetChannel ,
msg.myadress & 0xff ,msg.myadress >> 8 ,
msg.bootcount & 0xff, msg.bootcount >> 8,
};
SerialMon.printf("I send data\r\n");
for (size_t i = 0; i < sizeof(payload); i++)
{
SerialMon.printf(" %02x",payload[i]);
}
SerialMon.println();
SerialLoRa.write((uint8_t *)&payload, sizeof(payload));
SerialLoRa.flush();
delay(100);
bootCount++;
deep_sleep();
}
void loop() {}
#include <Arduino.h>
#define SerialMon Serial
#define SerialLoRa Serial1
// E220-900T22S(JP)へのピンアサイン
//(略)
int16_t gatewayAddress = 0x0000;
uint8_t conf[] ={0xc0, 0x00, 0x08,
gatewayAddress >> 8, //ADDH
gatewayAddress & 0xff, //ADDL
0b01110000, // baud_rate 9600 bps SF:9 BW:125
0b11100000, //subpacket_size 32, rssi_ambient_noise_flag on, transmitting_power 13 dBm
0x09, //own_channel
0b11000111, //RSSI on ,fix mode,wor_cycle 4000 ms
0x00, //CRYPT
0x00};
struct msgStruct{
char targetAdressH = 0xFF;//broadcast adress 0xFFFF
char targetAdressL = 0xFF;
char targetChannel = 0x09;
uint16_t sensorAdress ;
uint16_t bootcount ;
} msg;
/**
* @brief ノーマルモード(M0=0,M1=0)へ移行する
*/
void SwitchToNormalMode(void){
digitalWrite(LoRa_ModeSettingPin_M0, 0);
digitalWrite(LoRa_ModeSettingPin_M1, 0);
delay(100);
}
/**
* @brief WOR送信モード(M0=1,M1=0)へ移行する
*/
void SwitchToWORSendingMode(void){
digitalWrite(LoRa_ModeSettingPin_M0, 1);
digitalWrite(LoRa_ModeSettingPin_M1, 0);
delay(100);
}
/**
* @brief コンフィグ/sleepモード(M0=1,M1=1)へ移行する
*/
void SwitchToConfigurationMode(void){
digitalWrite(LoRa_ModeSettingPin_M0, 1);
digitalWrite(LoRa_ModeSettingPin_M1, 1);
delay(100);
}
void setup(){
pinMode(LoRa_ModeSettingPin_M0, OUTPUT);
pinMode(LoRa_ModeSettingPin_M1, OUTPUT);
SerialMon.begin(9600);
delay(500);
SerialLoRa.end();
delay(1000);
SerialLoRa.begin(LoRa_BaudRate, SERIAL_8N1, LoRa_Tx_ESP_RxPin,LoRa_Rx_ESP_TxPin);
// E220-900T22S(JP)へのLoRa初期設定
SwitchToConfigurationMode();
SerialMon.printf("I send conf\r\n");
for (size_t i = 0; i < sizeof(conf); i++)
{
SerialMon.printf(" %02x",conf[i]);
}
SerialLoRa.write((uint8_t *)&conf, sizeof(conf));
SwitchToWORSendingMode();
SerialLoRa.flush();
//この送信は子機を起こすためでメッセージ内容は受信されない
uint8_t payload[]={msg.targetAdressH, msg.targetAdressL, msg.targetChannel ,0x00};
SerialMon.printf("I send data\r\n");
for (size_t i = 0; i < sizeof(payload); i++)
{
SerialMon.printf(" %02x",payload[i]);
}
SerialMon.println();
SerialLoRa.write((uint8_t *)&payload, sizeof(payload));
SerialLoRa.flush();
delay(100);
SwitchToNormalMode(void);
}
void loop(){
//センサーから受信してどこかに送信したりの処理
//子機からの送信が全て終わったらDeepSpeep
}
WORでラズパイを起動させる
ラズベリーパイはGPIO3ピンをLOWにすると起動するようになっています。そこでこのLoRaモジュールを使って遠隔で起動することが出来ます
自動起動のプログラムで終了後シャットダウンさせるようにすれば必要なときだけ動かすような用途に利用できます
ラズパイの遠隔起動
— mnl_t (@mnlt18) February 4, 2024
あらかじめ設定を書き込んでおいたLoRaモジュールを
VCC ‐ 5v
GND ‐ GND
AUX ‐ GPIO3
M0 ‐ GND
M1 ‐ 3.3v
とラズパイに接続
一度起動してシャットダウンさせた状態からWORで起動できた https://t.co/4c3iULFmQF pic.twitter.com/lc545GdXAz