基于ESP32開發(fā)智能物聯(lián)網(wǎng)微型設備

介紹
Picoclick 是一種微型設備,可用作物聯(lián)網(wǎng)按鈕。它是一種一鍵式設備,可讓您控制智能家居或物聯(lián)網(wǎng)中的不同內(nèi)容。一個基本示例是通過按下按鈕來切換燈泡。此外,Picoclick-C3 可以用作迷你開發(fā)板,因為它帶有 FPC 擴展端口,可以連接其他硬件。
用例
該設備能夠連接到 WiFi 網(wǎng)絡并執(zhí)行任務,例如發(fā)送消息或?qū)懭胫黝} (MQTT)。與接入點的連接設置需要一些時間(大約 2-3 秒),這對某些應用程序來說太長了——至少對我來說是這樣。因此,我總是將我的 Picoclicks 與 ESPNOW 一起使用,ESPNOW 是 Espressif 自己開發(fā)的 WiFi 協(xié)議。它不需要連接到接入點,因此通信速度超快。然而,當使用 ESPNOW 協(xié)議時,需要有一個橋接器(基于 ESP 的設備)將這些消息轉(zhuǎn)換為例如 MQTT 消息。
工作原理
Picoclick 是一款超低功耗設備 - 它可以通過軟件關(guān)閉自己的電源,并且在不活動時僅消耗約 2μA(這僅歸功于嵌入式電池保護)。所謂的電源鎖存電路能夠管理穩(wěn)壓器的啟用。這可以通過兩種方式完成:
按鈕。當按下按鈕時,電壓調(diào)節(jié)器將被啟用,因此設備將被激活。
閂鎖。GPIO 或處理器的任何其他輸出。
對于以前版本的 Picoclick,我選擇了處理器的普通 GPIO。按下按鈕時,處理器將啟動,其首要任務是觸發(fā)鎖存器,該鎖存器將保持設備的電源。如果設備應該關(guān)閉,鎖存器可以接地并且電源將被禁用。這確保了處理器足夠快來觸發(fā)鎖存器——否則設備將在沒有任何操作的情況下關(guān)閉。
這是 Picoclick-C3 的黃金點。它不使用 GPIO 作為鎖存器,而是使用嵌入式閃存的電源信號。關(guān)于 ESP32-C3 的數(shù)據(jù)表,在處理器深度睡眠期間電源將被停用以節(jié)省一些電量。此外,該電源將在處理器通電后立即出現(xiàn)。因此,要停用 Picoclick 設備,只需進入深度睡眠模式即可。(可以肯定的是:設備不會進入深度睡眠模式,因為它會預先斷電)
除此之外,您不必為軟件中的閂鎖而煩惱,也不必將 ESP32-C3 配置得盡可能快(如 C3T)。因此不再需要使用 ESP-IDF 來刷寫 Picoclick。Arduino 框架也將與該設備完美配合。
?
?
?
?
?
概述
顧名思義,Picoclick-C3 基于 ESP32-C3,這是一款運行頻率高達 160MHz 的 32 位 RISC-V 處理器。
特征
·??????? Picoc尺寸:18x22mm
·??????? 單片機:ESP32-C3FH4
·??????? 單按鈕界面
·??????? 兩個 APA102-2020 RGB LED
·??????? 嵌入式電池保護
·??????? 帶狀態(tài) LED 的嵌入式電池充電
·??????? 優(yōu)化的電池監(jiān)控
·??????? 帶有兩個 GPIO 和電源引腳的擴展端口
·??????? 超低功耗設備
·??????? 包括一個板載芯片天線

正面
正面主要只有三樣東西:12mm 按鈕、兩個 APA102-2020 LED 和 6p FPC 連接器。此外還有兩個焊接跳線。


?
背面
背面是主要部件面。它擁有USB Type-C接口、ESP32-C3、電池充電以及電池保護電路、穩(wěn)壓器、天線及其匹配網(wǎng)絡和電源鎖存電路。



功能? ? ? ? ? ? ? ? ? ? ? ? ? ??GPIO ESP32-C3? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?備注
APA102 ? SDI (LED)? ? ? ? ? ? ? ? ? ? ? ?通用輸入輸出口7? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?輸出
APA102 時鐘 (LED)? ? ? ? ? ? ? ? ? ? ? ??通用輸入輸出口6? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ? ? ? 輸出
按鈕狀態(tài)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?通用輸入輸出口5? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?輸入
電池電壓? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?通用輸入輸出口4? ? ? ? ? ? ? ? ? ? 輸入,包括一個 1:1 分壓器
電池電壓觸發(fā)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?通用輸入輸出口3? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?輸出
電池連接

目前的功耗
下面所有的電流消耗都是測得的。
模式? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??平均電流? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?備注
WiFi已激活? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 74毫安? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?峰值可達 200mA
WiFi 禁用且 LED 燈變暗? ? ? ? ? ? ? ? ? ? ? ? ?27毫安
Picoclick 已停用? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?2微安
可以通過以下步驟降低活動模式下的電流消耗:
·??????? 僅在真正需要時才打開 WiFi。
·??????? 降低 CPU 頻率。使用嵌入式時鐘 (40MHz),您可以將頻率設置為 10、20 和 40MHz。10MHz 僅適用于 LED 應用。
·??????? 降低 APA102 LED 亮度。
跳線
有兩個具有兩種不同功能的焊接跳線。BOOT和USB跳線。

引導跳線
左側(cè)跳線將 MCU 的引導引腳連接到 GND,從而強制進入 ESP32 的引導模式。
USB 跳線
右邊的跳線將 USB 電壓連接到電池電壓,這樣 Picoclick 就可以直接通過 USB 端口供電,而無需連接另一個電源。那時(當然)不使用電池充電器。
備注?。?/strong>如果此跳線閉合,請勿將電池連接到 Picoclick,否則會毀壞 Picoclick、您的電池或兩者。
?
?
?
軟 ?件
重要的提示
Picoclick 的典型過程如下所示:
1.?? 1. 按下按鈕將激活 Picoclick。
2.?? 2.將發(fā)送一條消息。
3.?? 3.一些 LED 的東西會發(fā)生。
4.?? 4.Picoclick 將自行停用。
根據(jù)使用情況和/或所使用的協(xié)議,可以或多或少地快速處理這四個步驟。例如,如果使用 ESP-NOW,可以在 500 毫秒內(nèi)到達第 4 點。
如果您想將新代碼上傳到 Picoclick,那么激活設備很重要。這聽起來很簡單,但如果設備只激活了幾百毫秒,那么您可能不會在正確的時間點開始上傳過程。即使按住按鈕的時間更長,一旦 MCU 進入深度睡眠模式,嵌入式閃存的電源也會被停用。
為了有足夠的時間上傳新代碼,建議在進入深度睡眠模式之前使用循環(huán)。這個循環(huán)看起來像這樣:
int counter = 0;
while(digitalRead(BUTTON_PIN) == 1){
??? leds[0] = counter % 2 == 0 ? CRGB::Blue : CRGB::Black;
??? leds[1] = (counter+1) % 2 == 0 ? CRGB::Blue : CRGB::Black;
??? FastLED.show();
??? delay(50);
??? counter++;
}
?
// 添加一個循環(huán),只要按下按鈕就會等待進入深度睡眠。
// 一旦進入深度睡眠,USB 控制臺就不再可用。
esp_deep_sleep_start();
}
?
因此只要按下按鈕,設備就會交替閃爍兩個 LED。此后將進入深度睡眠模式。
如果您沒有遵循這些說明并且無法將代碼閃存到您的 Picoclick,那么您必須按照下一章中的步驟進行操作。
引導跳線
Boot soldering jumper將ESP32的boot strapping pin連接到GND,再次上電即進入boot模式。
需要引導焊接跳線的原因:
·??????? 您的代碼進入深度睡眠模式的速度太快(如上所述)
·??????? 您不小心上傳了錯誤的代碼(例如)切換 GPIO9
·??????? 您的刷機過程在上傳過程中崩潰
如果發(fā)生上述情況,那么您必須像這樣進行:
1.?? 切斷電源。如果您通過 USB 或擴展端口為 Picoclick 供電,則只需拔下它即可;如果你用電池供電,你必須拆焊它(至少一個連接)
2.?? 焊接或短接 PCB 按鈕側(cè)的引導跳線。
3.?? 連接電源(建議使用 USB 或擴展端口,否則必須再次拆焊電池)。
4.?? 如果在最后一步中尚未完成,請將 Picoclick 連接到您的計算機。
5.?? 按下 Picoclick 的按鈕并點擊 PlatformIO 中的上傳按鈕。
6.?? 如果完成,斷開所有電源。
7.?? 拆焊引導跳線。
8.?? 連接你的最終電源(這里是可以再次連接電池的地方)。
?
?
獲取電池電壓
Picoclick-C3 具有優(yōu)化的電池監(jiān)控功能,在不使用時不會消耗任何電量。作為比較:即使 Picoclick 未處于活動狀態(tài),C3T 也需要大約 3μA 的電流。
要讀取電池電壓,您必須將狀態(tài)ADC_ENABLE_PIN
從高變?yōu)榈停缓罂梢宰x取幾毫秒的電壓。讀取電池電壓后,返回該引腳的高電平狀態(tài)以便之后再次讀取電壓是有用的。
下面的函數(shù)讀取 ADC 連接到的模擬引腳,并返回過濾后的(100 除以 100 的總和)電池電壓(以伏特為單位)。在最后一行代碼中,原始模擬值將使用乘法器轉(zhuǎn)換為電壓值。如果需要,也可以添加恒定的線性偏移。
#define BAT_VOLT_MULTIPLIER?? 1.43
#define BAT_VOLT_OFFSET?????? 0
?
float get_battery_voltage(){
? digitalWrite(ADC_ENABLE_PIN, LOW);
? delayMicroseconds(10);
? int sum = 0;
? for(int i=0; i<100; i++){
??? sum = sum + analogRead(ADC_PIN);
? }
? float result = sum/100.0;
? digitalWrite(ADC_ENABLE_PIN, HIGH);
? return float(result) * BAT_VOLT_MULTIPLIER + BAT_VOLT_OFFSET;
}
串行輸出
Picoclick 沒有標準串行控制臺,因為它使用 USB-CDC 接口與處理器通信。使用 寫入標準控制臺Serial.print("...")
會將數(shù)據(jù)輸出到未連接到 Picoclick 的 UART 接口。
盡管如此,仍然有一種方法可以使用串行控制臺。這就像使用printf("...")
instead of 一樣簡單Serial.print("...")
。此外,串行控制臺不需要使用Serial.begin(X)
.
將 Picoclick 的電池電壓寫入串行控制臺的基本示例:
#include <Arduino.h>
#include <WiFi.h>
#include "config.h"
?
void setup(){
? pinMode(BUTTON_PIN, INPUT);
? pinMode(ADC_ENABLE_PIN, OUTPUT);
? pinMode(ADC_PIN, INPUT);
? analogReadResolution(12);
? digitalWrite(ADC_ENABLE_PIN, HIGH);
?
? btStop();
? WiFi.mode(WIFI_OFF);
?
? FastLED.addLeds<APA102, APA102_SDI_PIN, APA102_CLK_PIN, BGR>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);
? FastLED.setBrightness(160);
? delay(50);
?
? set_fastled(CRGB::Blue);
?
? printf("Setup done!\r\n");
}
?
void loop() {
? printf("Battery voltage: %i mV\r\n", int(get_battery_voltage()));
? delay(500);
}
請確保不要忘記將回車符 ( \r
) 和換行符 ( \n
) 添加到字符串的末尾。
要在 PlatformIO 中打開串行監(jiān)視器,您必須按下左下角帶有電源適配器的按鈕。
?
ESP-NOW
ESP -NOW是低延遲應用程序的絕佳協(xié)議,因為無接入點通信可實現(xiàn)超快速消息傳輸。按下按鈕和接收發(fā)送消息之間的延遲低至 200 毫秒。這非常適合智能家居解決方案,您可以立即看到結(jié)果(燈/插座亮起)。
將 ESP-NOW 與 Picoclicks 一起使用時,您必須使用基于 ESP 的橋?qū)⑦@些消息轉(zhuǎn)換為另一種協(xié)議的消息。在大多數(shù)情況下,ESP-NOW 到 MQTT 橋是最好的選擇。在討論網(wǎng)橋之前,讓我們先看看發(fā)送者代碼。
ESP-NOW發(fā)送器
在這里,發(fā)送者是物聯(lián)網(wǎng)按鈕本身。一旦按下 Picoclick,一條消息將充滿信息并發(fā)送到其目的地。之后 Picoclick 將再次停用。功能代碼可能如下所示:
#include <Arduino.h>
#include <WiFi.h>
#include <esp_now.h>
#include <FastLED.h>
#include "config.h"
?
// ESPNOW packet structure.
// Can be modified but should be the same on the receivers side.
typedef struct struct_message {
? int id;
? int value;
? int battery_level;
? int single_tap_duration;
} struct_message;
?
typedef struct struct_message_recv {
??? bool answer;
} struct_message_recv;
?
struct_message data;
struct_message_recv data_recv;
?
#define ESPNOW_ID 8888 // Random 4 digit number
uint8_t receiver_address[] = {0x10, 0x91, 0xA8, 0x32, 0x7B, 0x70}; // Mac address of the receiver.
?
bool espnow_answer_received = false;
?
void on_data_recv(const uint8_t * mac, const uint8_t *incomingData, int len) {
? memcpy(&data_recv, incomingData, sizeof(data_recv));
? espnow_answer_received = true;
}
?
void setup(){
? pinMode(BUTTON_PIN, INPUT);
? pinMode(ADC_ENABLE_PIN, OUTPUT);
? pinMode(ADC_PIN, INPUT);
? analogReadResolution(12);
? digitalWrite(ADC_ENABLE_PIN, HIGH);
?
? btStop();
? WiFi.mode(WIFI_STA);
?
? FastLED.addLeds<APA102, APA102_SDI_PIN, APA102_CLK_PIN, BGR>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);
? FastLED.setBrightness(160);
? delay(50);
?
? if(esp_now_init() != ESP_OK) {
??? printf("Error initializing ESP-NOW\r\n");
??? return;
? }
?
? set_fastled(CRGB::Blue);
?
? esp_now_peer_info_t peerInfo;
?
? memcpy(peerInfo.peer_addr, receiver_address, 6);
? peerInfo.channel = 0;?
? peerInfo.encrypt = false;
?????
? if(esp_now_add_peer(&peerInfo) != ESP_OK){
??? printf("Failed to add peer\r\n");
??? return;
? }
?
? esp_now_register_recv_cb(on_data_recv);
?
? // Fill ESPNOW struct with values.
? data.id = ESPNOW_ID;
? data.value = 1;
? data.battery_level = int(get_battery_voltage());
? data.single_tap_duration = 1000;
?
? esp_now_send(receiver_address, (uint8_t *) &data, sizeof(data));
?
? // wait on espnow answer
? unsigned long t_wait_answer_start = millis();
? while(!espnow_answer_received && millis() <= t_wait_answer_start + 300){
??? delayMicroseconds(1);
? }
?
? // This will reduce power consumption.
? WiFi.mode(WIFI_OFF);
? setCpuFrequencyMhz(10);
?
? CRGB col = espnow_answer_received ? CRGB::Green : CRGB::Red;
? set_fastled(col);
? delay(500);
?
? int counter = 0;
? while(digitalRead(BUTTON_PIN) == 1){
??? set_fastled(counter % 2 == 0 ? CRGB::Blue : CRGB::Black, (counter+1) % 2 == 0 ? CRGB::Blue : CRGB::Black);
??? delay(50);
??? counter++;
? }
?
? set_fastled(CRGB::Blue);
? delay(500);
?
? // Add a loop which will wait as long as the button is pressed before entering deepsleep.
? // Once in deepsleep the USB console is not available anymore.
? esp_deep_sleep_start();
}
?
void loop() {
?
}
上面的代碼還做了一件事,確保消息傳遞成功。在它發(fā)出包含所有信息的初始消息后,它等待接收設備的應答 300 毫秒:
while(!espnow_answer_received && millis() <= t_wait_answer_start + 300){
??? delayMicroseconds(1);
}
如果執(zhí)行接收回調(diào),則該espnow_answer_received
標志將設置為true
,因此一旦發(fā)送方收到消息。如果不是這種情況,則 while 循環(huán)將在 300 毫秒后退出。為了向用戶提供反饋,LED 將根據(jù)接收狀態(tài)呈綠色或紅色亮起。
CRGB col = espnow_answer_received ? CRGB::Green : CRGB::Red;
ESP-NOW 接收器
接收器可以是任何基于 ESP 的設備,但在這種情況下,我也使用 Picoclick。按下按鈕后,接收器將開機并等待來自發(fā)送器的消息。在等待期間,LED 將循環(huán)顯示所有顏色。
收到消息后,send_answer()
將調(diào)用該函數(shù)。它使用接收到的發(fā)送方設備的 MAC 地址添加 ESP-NOW 對等點,發(fā)送應答消息,然后刪除對等點。
當接收器設備像發(fā)送器一樣將其 LED 變?yōu)榫G色時,兩個設備將幾乎同時亮起綠色。
#include <Arduino.h>
#include <WiFi.h>
#include <esp_now.h>
#include <FastLED.h>
#include "config.h"
?
// ESPNOW packet structure.
// Can be modified but should be the same on the receivers side.
typedef struct struct_message {
? int id;
? int value;
? int battery_level;
? int single_tap_duration;
} struct_message;
?
typedef struct struct_message_recv {
??? bool answer;
} struct_message_recv;
?
struct_message data;
struct_message_recv data_answer;
?
#define ESPNOW_ID 8888 // Random 4 digit number
uint8_t receiver_address[] = {0x10, 0x91, 0xA8, 0x32, 0x7B, 0x70}; // Mac address of the receiver. 10:91:A8:32:7B:70
?
uint8_t temp_address[6];
uint8_t last_recv_address[6];
?
String mac;
?
bool need_answer = false;
?
void on_data_recv(const uint8_t * mac, const uint8_t *incomingData, int len) {
? memcpy(&data, incomingData, sizeof(data));
? memcpy(temp_address, mac, 6);
? need_answer = true;
}
?
esp_now_peer_info_t peerInfo;
?
void send_answer(){
? memcpy(peerInfo.peer_addr, temp_address, 6);
? peerInfo.channel = 0;?
? peerInfo.encrypt = false;
?????
? if(esp_now_add_peer(&peerInfo) != ESP_OK){
??? printf("Failed to add peer\r\n");
??? return;
? }
?
? data_answer.answer = true;
?
? esp_now_send(temp_address, (uint8_t *) &data_answer, sizeof(data_answer));
? if(esp_now_del_peer(temp_address) != ESP_OK){
??? printf("Failed to delete peer\r\n");
??? return;
? }
? memcpy(last_recv_address, temp_address, 6);
? memset(temp_address, 0, 6);
?
? set_fastled(CRGB::Green);
}
?
String mac_to_string(uint8_t *addr){
? String mac_str = String(addr[0], HEX) + ":" + String(addr[1], HEX) + ":" + String(addr[2], HEX) + ":"
??? + String(addr[3], HEX) + ":" + String(addr[4], HEX) + ":" + String(addr[5], HEX);
? mac_str.toUpperCase();
? return mac_str;
}
?
void setup(){
? pinMode(BUTTON_PIN, INPUT);
? pinMode(ADC_ENABLE_PIN, OUTPUT);
? pinMode(ADC_PIN, INPUT);
? analogReadResolution(12);
? digitalWrite(ADC_ENABLE_PIN, HIGH);
?
? WiFi.mode(WIFI_STA);
?
? FastLED.addLeds<APA102, APA102_SDI_PIN, APA102_CLK_PIN, BGR>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);
? FastLED.setBrightness(160);
? delay(50);
?
? if(esp_now_init() != ESP_OK) {
??? printf("Error initializing ESP-NOW\r\n");
??? return;
? }
?
? set_fastled(CRGB::Blue);
?
? esp_now_register_recv_cb(on_data_recv);
?
? mac = WiFi.macAddress();
? // printf("MAC %s\r\n", mac.c_str());
?
? delay(500);
}
?
unsigned long led_timer = millis();
int hue1 = 0, hue2 = 0;
int brightness = 255;
?
void loop() {
? if(digitalRead(BUTTON_PIN) == 1){
??? set_fastled(CRGB::Red);
??? delay(1000);
??? esp_deep_sleep_start();
? }
?
? if(need_answer){
??? need_answer = false;
??? send_answer();
??? led_timer += 1000;
? }
?
? if(millis() >= led_timer + 15){
??? led_timer = millis();
??? set_fastled(CHSV(hue1, 255, brightness), CHSV(hue2, 255, brightness));
??? hue1 = (hue1 + 1)%255;
??? hue2 = (hue1 + 127)%255;
? }
}
ESP-NOW 到 MQTT 橋
以下代碼是單個設備上 ESP-NOW 到 MQTT 的橋接。要使一切正常,您需要將路由器的頻道更改為固定頻道 1。有關(guān)該頻道或 Home Assistant 集成的更多信息,您應該查看視頻。
要運行此代碼,您需要在頂部部分添加您的 WiFi 憑據(jù),并在重新連接函數(shù)中添加您的 MQTT 憑據(jù)。
該代碼將所有 ESP-NOW 消息轉(zhuǎn)發(fā)為 MQTT 消息,以集成到 Home Assistant 中。主題由標識符“節(jié)點”和四位數(shù)字“ESPNOW_ID”生成。典型的主題可能如下所示:home/node1234/value
#include <Arduino.h>
#include <WiFi.h>
#include <esp_now.h>
#include <esp_wifi.h>
#include <PubSubClient.h>
#include <time.h>
?
const char* ssid = "YOUR_SSID";
const char* password = "YOUR_PWD";
const char* mqtt_server = "YOUR_MQTT_IP"; // Example: 192.168.1.4
?
WiFiClient espClient;
PubSubClient client(espClient);
?
String current_time_str = "";
?
const char* ntpServer = "pool.ntp.org";
const long? gmtOffset_sec = 3600;
const int?? daylightOffset_sec = 3600;
struct tm timeinfo;
int current_seconds = 0;
?
int32_t channel;
?
#define LED_PIN???? 15 // Wemos S2: 15, ThingPlusS2: 13
?
typedef struct struct_message {
??? int id;
??? int value;
??? int battery_level;
??? int single_tap_duration;
} struct_message;
?
typedef struct struct_message_recv {
??? bool answer;
} struct_message_recv;
?
uint8_t temp_address[6];
?
bool need_answer = false;
?
struct_message data;
struct_message_recv data_answer;
bool new_data_received = false;
bool new_data_to_mqtt = false;
unsigned long t_new_data_received = 0;
?
#define N_SINGLETAP_SLOTS?? 10
bool reset_after_single_tap = false;
unsigned long t_reset_single_tap[N_SINGLETAP_SLOTS];
int reset_after_single_tap_id[N_SINGLETAP_SLOTS];
int single_tap_duration[N_SINGLETAP_SLOTS];
?
?
int battery_percentage[] = {4200, 4150, 4110, 4080, 4020, 3980, 3950, 3910, 3870, 3850, 3840, 3820, 3800, 3790, 3770, 3750, 3730, 3710, 3690, 3610, 3270};
int get_battery_percentage(float mv){
? int battery_mv = int(mv);
? int perc = 0;
? for(int i=0; i<=20; i++) if(battery_mv > battery_percentage[20-i]) perc+=5;
? return constrain(perc, 0, 100);
}
?
String get_formatted_time(){
? String t = "";
? if(timeinfo.tm_hour < 10) t += "0";
? t += timeinfo.tm_hour;
? t += ":";
? if(timeinfo.tm_min < 10) t += "0";
? t += timeinfo.tm_min;
? t += ":";
? if(timeinfo.tm_sec < 10) t += "0";
? t += timeinfo.tm_sec;
? return t;
}
?
?
void on_data_recv(const uint8_t * mac, const uint8_t *incomingData, int len) {
? digitalWrite(LED_PIN, 1);
? memcpy(&data, incomingData, sizeof(data));
? memcpy(temp_address, mac, 6);
? need_answer = true;
?
? printf("\r\n");
? printf("Receivid packet from %X:%X:%X:%X:%X:%X\r\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
? printf("Data - ID: %i, Value: %i, Battery: %i, TapDuration: %i\r\n", data.id, data.value, data.battery_level, data.single_tap_duration);
? printf("\r\n");
?
? if(data.single_tap_duration > 0){
??? reset_after_single_tap = true;
??? bool slot_found = false;
??? for(int i=0; i<N_SINGLETAP_SLOTS; i++){
????? if(single_tap_duration[i] == 0 && !slot_found){
??????? single_tap_duration[i] = data.single_tap_duration;
??????? reset_after_single_tap_id[i] = data.id;
??????? t_reset_single_tap[i] = millis();
??????? slot_found = true;
????? }
??? }
? }
?
? new_data_received = true;
? new_data_to_mqtt = true;
? t_new_data_received = millis();
}
?
esp_now_peer_info_t peerInfo;
?
void send_answer(){
? memcpy(peerInfo.peer_addr, temp_address, 6);
? peerInfo.channel = 0;?
? peerInfo.encrypt = false;
?????
? if(esp_now_add_peer(&peerInfo) != ESP_OK){
??? printf("Failed to add peer\r\n");
??? return;
? }
?
? data_answer.answer = true;
? esp_now_send(temp_address, (uint8_t *) &data_answer, sizeof(data_answer));
? delay(10);
? if(esp_now_del_peer(temp_address) != ESP_OK){
??? printf("Failed to delete peer\r\n");
??? return;
? }
? memset(temp_address, 0, 6);
}
?
?
void callback(char* topic, byte* message, unsigned int length) {
? // digitalWrite(LED_PIN, 1);
? String messageTemp;
?
? for (int i = 0; i < length; i++) {
??? messageTemp += (char)message[i];
? }
? // digitalWrite(LED_PIN, 0);
}
?
void reconnect() {
? while (!client.connected()) {
??? digitalWrite(LED_PIN, 1);
??? if (client.connect("MQTTClient_new", "YOUR_MQTT_USER", "YOUR_MQTT_PWD")) {
????? client.subscribe("home/#");
????? digitalWrite(LED_PIN, 0);
??? } else {
????? printf("Cannot connect to MQTT Server - Restarting in 5s!\r\n");
????? delay(5000);
??? }
? }
}
?
int32_t getWiFiChannel(const char *ssid) {
? if (int32_t n = WiFi.scanNetworks()) {
????? for (uint8_t i=0; i<n; i++) {
????????? if (!strcmp(ssid, WiFi.SSID(i).c_str())) {
????????????? return WiFi.channel(i);
????????? }
????? }
? }
? return 0;
}
?
void setup() {
? pinMode(LED_PIN, OUTPUT);
?
? WiFi.mode(WIFI_AP_STA);
?
? channel = getWiFiChannel(ssid);
? esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
? printf("WiFi channel: %i\r\n", channel);
?
? printf("Connecting to: %s\r\n", ssid);
? WiFi.begin(ssid, password);
?
? while (WiFi.status() != WL_CONNECTED) {
??? digitalWrite(LED_PIN, 1);
??? delay(150);
??? printf(".\r\n");
??? digitalWrite(LED_PIN, 0);
??? delay(150);
? }
?
? printf("WiFi connected with IP: %s\r\n", WiFi.localIP().toString().c_str());
?
? configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
?
? client.setServer(mqtt_server, 1883);
? // client.setCallback(callback);
?
? if (esp_now_init() != ESP_OK) {
??? printf("Error initializing ESP-NOW\r\n");
??? return;
? }
?
? esp_now_register_recv_cb(on_data_recv);
?
? reconnect();
?
? String payload_mac = String(WiFi.macAddress());
? client.publish("home/s2macaddr", payload_mac.c_str());
}
?
unsigned long t_send_time_running = 0;
?
void loop() {
? if(need_answer){
??? need_answer = false;
??? send_answer();
? }
?
? if(new_data_received){
??? if(new_data_to_mqtt){
????? new_data_to_mqtt = false;
?
????? String topic_value = "home/node" + String(data.id) + "/value";
????? String topic_battery = "home/node" + String(data.id) + "/battery";
????? String topic_batterylevel = "home/node" + String(data.id) + "/batterylevel";
?
????? client.publish(topic_value.c_str(), String(data.value).c_str());
????? client.publish(topic_battery.c_str(), String(data.battery_level).c_str());
????? client.publish(topic_batterylevel.c_str(), String(get_battery_percentage(data.battery_level)).c_str());
??? }
?
??? if(millis() >= t_new_data_received + 1000){
????? digitalWrite(LED_PIN, 0);
????? new_data_received = false;
??? }
? }
?
? if(reset_after_single_tap){
??? bool any_reset_open = false;
??? for(int i=0; i<N_SINGLETAP_SLOTS; i++){
????? if(single_tap_duration[i] > 0){
??????? any_reset_open = true;
??????? if(millis() >= t_reset_single_tap[i] + single_tap_duration[i]){
????????? printf("Send zero\r\n");
????????? String topic_value = "home/node" + String(reset_after_single_tap_id[i]) + "/value";
????????? client.publish(topic_value.c_str(), "0");
?
????????? single_tap_duration[i] = 0;
????????? reset_after_single_tap_id[i] = 0;
??????? }
????? }
??? }
?? ?if(!any_reset_open){
????? reset_after_single_tap = false;
??? }
? }
?
? unsigned int start = millis();
? if(!getLocalTime(&timeinfo)){
??? printf("Failed to obtain time\r\n");
? }
? unsigned int stop = millis();
?
? if(current_seconds != timeinfo.tm_sec){
??? current_seconds = timeinfo.tm_sec;
??? printf("Time: %s\r\n", get_formatted_time());
??? if(timeinfo.tm_sec == 0){
????? client.publish("home/current_time", get_formatted_time().c_str());
??? }
? }
?
? if (!client.connected()) {
??? reconnect();
? }
?
? client.loop();
}
運動傳感器
運動傳感器擴展板基于STMicroelectronics 的LIS3DHTR 。傳感器使用 I2C 與 Picoclick 通信(SDA = GPIO2,SCL = GPIO8)。它有一個超低功率穩(wěn)壓器,可用于通過觸發(fā)中斷來激活 Picoclick。中斷可以通過軟件配置。
硬件
概述
技術(shù)制圖
測量單位為毫米,網(wǎng)格為 0.5 毫米
·??????? 印刷電路板:18 毫米 x 10 毫米
·??????? 厚度:1mm
·??????? 安裝孔:3.2mm
·??????? 圓角半徑:2mm
原理圖
軟件
?
#include <Arduino.h>
#include <WiFi.h>
#include <FastLED.h>
#include <SparkFunLIS3DH.h>
#include <Wire.h>
#include "config.h"
?
LIS3DH lis(I2C_MODE, 0x19); //Default constructor is I2C, addr 0x19.
?
void configIntterupts();
?
void setup(){
? pinMode(BUTTON_PIN, INPUT);
? pinMode(ADC_ENABLE_PIN, OUTPUT);
? pinMode(ADC_PIN, INPUT);
? analogReadResolution(12);
? digitalWrite(ADC_ENABLE_PIN, HIGH);
?
? btStop();
? WiFi.mode(WIFI_OFF);
? setCpuFrequencyMhz(10);
?
? FastLED.addLeds<APA102, APA102_SDI_PIN, APA102_CLK_PIN, BGR>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);
? FastLED.setBrightness(160);
? delay(50);
?
? set_fastled(CRGB::Blue);
?
? Wire.begin(SDA_PIN, SCL_PIN);
? delay(100);
?
? lis.settings.accelSampleRate = 50;? //Hz.? Can be: 0,1,10,25,50,100,200,400,1600,5000 Hz
? lis.settings.accelRange = 2;????? //Max G force readable.? Can be: 2, 4, 8, 16
?
? lis.settings.adcEnabled = 0;
? lis.settings.tempEnabled = 0;
? lis.settings.xAccelEnabled = 1;
? lis.settings.yAccelEnabled = 1;
? lis.settings.zAccelEnabled = 1;
?
? lis.begin();
?
? // int dataToWrite = B01001111;
? // lis.writeRegister(LIS3DH_CTRL_REG1, dataToWrite);
?
? // configIntterupts();
}
?
unsigned long t_sensor = millis();
?
void loop() {
? if(digitalRead(BUTTON_PIN) == 1){
??? set_fastled(CRGB::Red);
??? delay(500);
??? esp_deep_sleep_start();
? }
?
? if(millis() >= t_sensor + 500){
??? t_sensor = millis();
??? float x = lis.readFloatAccelX();
??? float y = lis.readFloatAccelY();
??? float z = lis.readFloatAccelZ();
??? printf("X: %f, Y: %f, Z: %f\r\n", x, y, z);
? }
}
?
void configIntterupts(){
? uint8_t dataToWrite = 0;
?
? // //LIS3DH_INT1_CFG??
? // //dataToWrite |= 0x80;//AOI, 0 = OR 1 = AND
? // //dataToWrite |= 0x40;//6D, 0 = interrupt source, 1 = 6 direction source
? // //Set these to enable individual axes of generation source (or direction)
? // // -- high and low are used generically
? // //dataToWrite |= 0x20;//Z high
? // //dataToWrite |= 0x10;//Z low
??// dataToWrite |= 0x08;//Y high
? // //dataToWrite |= 0x04;//Y low
? // //dataToWrite |= 0x02;//X high
? // //dataToWrite |= 0x01;//X low
? // lis.writeRegister(LIS3DH_INT1_CFG, dataToWrite);
?
? // //LIS3DH_INT1_THS??
? // dataToWrite = 0;
? // //Provide 7 bit value, 0x7F always equals max range by accelRange setting
? // dataToWrite |= 0x10; // 1/8 range
? // lis.writeRegister(LIS3DH_INT1_THS, dataToWrite);
?
? // //LIS3DH_INT1_DURATION?
? // dataToWrite = 0;
? // //minimum duration of the interrupt
? // //LSB equals 1/(sample rate)
? // dataToWrite |= 0x01; // 1 * 1/50 s = 20ms
? // lis.writeRegister(LIS3DH_INT1_DURATION, dataToWrite);
?
? //LIS3DH_CLICK_CFG??
? dataToWrite = 0;
? //Set these to enable individual axes of generation source (or direction)
? // -- set = 1 to enable
? //dataToWrite |= 0x20;//Z double-click
? dataToWrite |= 0x10;//Z click
? //dataToWrite |= 0x08;//Y double-click
? dataToWrite |= 0x04;//Y click
? //dataToWrite |= 0x02;//X double-click
? dataToWrite |= 0x01;//X click
? lis.writeRegister(LIS3DH_CLICK_CFG, dataToWrite);
?
? //LIS3DH_CLICK_SRC
? dataToWrite = 0;
? //Set these to enable click behaviors (also read to check status)
? // -- set = 1 to enable
? //dataToWrite |= 0x20;//Enable double clicks
??dataToWrite |= 0x04;//Enable single clicks
? //dataToWrite |= 0x08;//sine (0 is positive, 1 is negative)
? dataToWrite |= 0x04;//Z click detect enabled
? dataToWrite |= 0x02;//Y click detect enabled
? dataToWrite |= 0x01;//X click detect enabled
? lis.writeRegister(LIS3DH_CLICK_SRC, dataToWrite);
?
? //LIS3DH_CLICK_THS??
? dataToWrite = 0;
? //This sets the threshold where the click detection process is activated.
? //Provide 7 bit value, 0x7F always equals max range by accelRange setting
? dataToWrite |= 0x0A; // ~1/16 range
? lis.writeRegister(LIS3DH_CLICK_THS, dataToWrite);
?
? //LIS3DH_TIME_LIMIT?
? dataToWrite = 0;
? //Time acceleration has to fall below threshold for a valid click.
? //LSB equals 1/(sample rate)
? dataToWrite |= 0x08; // 0x08: 8 * 1/50 s = 160ms
? lis.writeRegister(LIS3DH_TIME_LIMIT, dataToWrite);
?
? //LIS3DH_TIME_LATENCY
? dataToWrite = 0;
? //hold-off time before allowing detection after click event
? //LSB equals 1/(sample rate)
? dataToWrite |= 0x0F; // 4 * 1/50 s = 160ms,
? lis.writeRegister(LIS3DH_TIME_LATENCY, dataToWrite);
?
? //LIS3DH_TIME_WINDOW
? dataToWrite = 0;
? //hold-off time before allowing detection after click event
? //LSB equals 1/(sample rate)
? dataToWrite |= 0x8F; // 16 * 1/50 s = 320ms
? lis.writeRegister(LIS3DH_TIME_WINDOW, dataToWrite);
?
? //LIS3DH_CTRL_REG5
? //Int1 latch interrupt and 4D on? int1 (preserve fifo en)
? lis.readRegister(&dataToWrite, LIS3DH_CTRL_REG5);
? dataToWrite &= 0xF3; //Clear bits of interest
? dataToWrite |= 0x08; //Latch interrupt (Cleared by reading int1_src)
? //dataToWrite |= 0x04; //Pipe 4D detection from 6D recognition to int1?
? lis.writeRegister(LIS3DH_CTRL_REG5, dataToWrite);
?
? //LIS3DH_CTRL_REG3
? //Choose source for pin 1
? dataToWrite = 0;
? dataToWrite |= 0x80; //Click detect on pin 1
? // dataToWrite |= 0x40; //AOI1 event (Generator 1 interrupt on pin 1)
? // dataToWrite |= 0x20; //AOI2 event ()
? //dataToWrite |= 0x10; //Data ready
? //dataToWrite |= 0x04; //FIFO watermark
? //dataToWrite |= 0x02; //FIFO overrun
? lis.writeRegister(LIS3DH_CTRL_REG3, dataToWrite);
?
? // //LIS3DH_CTRL_REG6
? // //Choose source for pin 2 and both pin output inversion state
? // dataToWrite = 0;
? // // dataToWrite |= 0x80; //Click int on pin 2
? // // dataToWrite |= 0x40; //Generator 1 interrupt on pin 2
? // //dataToWrite |= 0x10; //boot status on pin 2
? // //dataToWrite |= 0x02; //invert both outputs
? // lis.writeRegister(LIS3DH_CTRL_REG6, dataToWrite);
}