基于ESP32开发智能物联网微型设备

介绍
Picoclick 是一种微型设备,可用作物联网按钮。它是一种一键式设备,可让您控制智能家居或物联网中的不同内容。一个基本示例是通过按下按钮来切换灯泡。此外,Picoclick-C3 可以用作迷你开发板,因为它带有 FPC 扩展端口,可以连接其他硬件。
用例
该设备能够连接到 WiFi 网络并执行任务,例如发送消息或写入主题 (MQTT)。与接入点的连接设置需要一些时间(大约 2-3 秒),这对某些应用程序来说太长了——至少对我来说是这样。因此,我总是将我的 Picoclicks 与 ESPNOW 一起使用,ESPNOW 是 Espressif 自己开发的 WiFi 协议。它不需要连接到接入点,因此通信速度超快。然而,当使用 ESPNOW 协议时,需要有一个桥接器(基于 ESP 的设备)将这些消息转换为例如 MQTT 消息。
工作原理
Picoclick 是一款超低功耗设备 - 它可以通过软件关闭自己的电源,并且在不活动时仅消耗约 2µA(这仅归功于嵌入式电池保护)。所谓的电源锁存电路能够管理稳压器的启用。这可以通过两种方式完成:
按钮。当按下按钮时,电压调节器将被启用,因此设备将被激活。
闩锁。GPIO 或处理器的任何其他输出。
对于以前版本的 Picoclick,我选择了处理器的普通 GPIO。按下按钮时,处理器将启动,其首要任务是触发锁存器,该锁存器将保持设备的电源。如果设备应该关闭,锁存器可以接地并且电源将被禁用。这确保了处理器足够快来触发锁存器——否则设备将在没有任何操作的情况下关闭。
这是 Picoclick-C3 的黄金点。它不使用 GPIO 作为锁存器,而是使用嵌入式闪存的电源信号。关于 ESP32-C3 的数据表,在处理器深度睡眠期间电源将被停用以节省一些电量。此外,该电源将在处理器通电后立即出现。因此,要停用 Picoclick 设备,只需进入深度睡眠模式即可。(可以肯定的是:设备不会进入深度睡眠模式,因为它会预先断电)
除此之外,您不必为软件中的闩锁而烦恼,也不必将 ESP32-C3 配置得尽可能快(如 C3T)。因此不再需要使用 ESP-IDF 来刷写 Picoclick。Arduino 框架也将与该设备完美配合。
概述
顾名思义,Picoclick-C3 基于 ESP32-C3,这是一款运行频率高达 160MHz 的 32 位 RISC-V 处理器。
特征
· Picoc尺寸:18x22mm
· 单片机:ESP32-C3FH4
· 单按钮界面
· 两个 APA102-2020 RGB LED
· 嵌入式电池保护
· 带状态 LED 的嵌入式电池充电
· 优化的电池监控
· 带有两个 GPIO 和电源引脚的扩展端口
· 超低功耗设备
· 包括一个板载芯片天线

正面
正面主要只有三样东西:12mm 按钮、两个 APA102-2020 LED 和 6p FPC 连接器。此外还有两个焊接跳线。


背面
背面是主要部件面。它拥有USB Type-C接口、ESP32-C3、电池充电以及电池保护电路、稳压器、天线及其匹配网络和电源锁存电路。



功能 GPIO ESP32-C3 备注
APA102 SDI (LED) 通用输入输出口7 输出
APA102 时钟 (LED) 通用输入输出口6 输出
按钮状态 通用输入输出口5 输入
电池电压 通用输入输出口4 输入,包括一个 1:1 分压器
电池电压触发 通用输入输出口3 输出
电池连接

目前的功耗
下面所有的电流消耗都是测得的。
模式 平均电流 备注
WiFi已激活 74毫安 峰值可达 200mA
WiFi 禁用且 LED 灯变暗 27毫安
Picoclick 已停用 2微安
可以通过以下步骤降低活动模式下的电流消耗:
· 仅在真正需要时才打开 WiFi。
· 降低 CPU 频率。使用嵌入式时钟 (40MHz),您可以将频率设置为 10、20 和 40MHz。10MHz 仅适用于 LED 应用。
· 降低 APA102 LED 亮度。
跳线
有两个具有两种不同功能的焊接跳线。BOOT和USB跳线。

引导跳线
左侧跳线将 MCU 的引导引脚连接到 GND,从而强制进入 ESP32 的引导模式。
USB 跳线
右边的跳线将 USB 电压连接到电池电压,这样 Picoclick 就可以直接通过 USB 端口供电,而无需连接另一个电源。那时(当然)不使用电池充电器。
备注!:如果此跳线闭合,请勿将电池连接到 Picoclick,否则会毁坏 Picoclick、您的电池或两者。
软 件
重要的提示
Picoclick 的典型过程如下所示:
1. 1. 按下按钮将激活 Picoclick。
2. 2.将发送一条消息。
3. 3.一些 LED 的东西会发生。
4. 4.Picoclick 将自行停用。
根据使用情况和/或所使用的协议,可以或多或少地快速处理这四个步骤。例如,如果使用 ESP-NOW,可以在 500 毫秒内到达第 4 点。
如果您想将新代码上传到 Picoclick,那么激活设备很重要。这听起来很简单,但如果设备只激活了几百毫秒,那么您可能不会在正确的时间点开始上传过程。即使按住按钮的时间更长,一旦 MCU 进入深度睡眠模式,嵌入式闪存的电源也会被停用。
为了有足够的时间上传新代码,建议在进入深度睡眠模式之前使用循环。这个循环看起来像这样:
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++;
}
// 添加一个循环,只要按下按钮就会等待进入深度睡眠。
// 一旦进入深度睡眠,USB 控制台就不再可用。
esp_deep_sleep_start();
}
因此只要按下按钮,设备就会交替闪烁两个 LED。此后将进入深度睡眠模式。
如果您没有遵循这些说明并且无法将代码闪存到您的 Picoclick,那么您必须按照下一章中的步骤进行操作。
引导跳线
Boot soldering jumper将ESP32的boot strapping pin连接到GND,再次上电即进入boot模式。
需要引导焊接跳线的原因:
· 您的代码进入深度睡眠模式的速度太快(如上所述)
· 您不小心上传了错误的代码(例如)切换 GPIO9
· 您的刷机过程在上传过程中崩溃
如果发生上述情况,那么您必须像这样进行:
1. 切断电源。如果您通过 USB 或扩展端口为 Picoclick 供电,则只需拔下它即可;如果你用电池供电,你必须拆焊它(至少一个连接)
2. 焊接或短接 PCB 按钮侧的引导跳线。
3. 连接电源(建议使用 USB 或扩展端口,否则必须再次拆焊电池)。
4. 如果在最后一步中尚未完成,请将 Picoclick 连接到您的计算机。
5. 按下 Picoclick 的按钮并点击 PlatformIO 中的上传按钮。
6. 如果完成,断开所有电源。
7. 拆焊引导跳线。
8. 连接你的最终电源(这里是可以再次连接电池的地方)。
获取电池电压
Picoclick-C3 具有优化的电池监控功能,在不使用时不会消耗任何电量。作为比较:即使 Picoclick 未处于活动状态,C3T 也需要大约 3µA 的电流。
要读取电池电压,您必须将状态ADC_ENABLE_PIN
从高变为低,然后可以读取几毫秒的电压。读取电池电压后,返回该引脚的高电平状态以便之后再次读取电压是有用的。
下面的函数读取 ADC 连接到的模拟引脚,并返回过滤后的(100 除以 100 的总和)电池电压(以伏特为单位)。在最后一行代码中,原始模拟值将使用乘法器转换为电压值。如果需要,也可以添加恒定的线性偏移。
#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("...")
会将数据输出到未连接到 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 中打开串行监视器,您必须按下左下角带有电源适配器的按钮。
ESP-NOW
ESP -NOW是低延迟应用程序的绝佳协议,因为无接入点通信可实现超快速消息传输。按下按钮和接收发送消息之间的延迟低至 200 毫秒。这非常适合智能家居解决方案,您可以立即看到结果(灯/插座亮起)。
将 ESP-NOW 与 Picoclicks 一起使用时,您必须使用基于 ESP 的桥将这些消息转换为另一种协议的消息。在大多数情况下,ESP-NOW 到 MQTT 桥是最好的选择。在讨论网桥之前,让我们先看看发送者代码。
ESP-NOW发送器
在这里,发送者是物联网按钮本身。一旦按下 Picoclick,一条消息将充满信息并发送到其目的地。之后 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() {
}
上面的代码还做了一件事,确保消息传递成功。在它发出包含所有信息的初始消息后,它等待接收设备的应答 300 毫秒:
while(!espnow_answer_received && millis() <= t_wait_answer_start + 300){
delayMicroseconds(1);
}
如果执行接收回调,则该espnow_answer_received
标志将设置为true
,因此一旦发送方收到消息。如果不是这种情况,则 while 循环将在 300 毫秒后退出。为了向用户提供反馈,LED 将根据接收状态呈绿色或红色亮起。
CRGB col = espnow_answer_received ? CRGB::Green : CRGB::Red;
ESP-NOW 接收器
接收器可以是任何基于 ESP 的设备,但在这种情况下,我也使用 Picoclick。按下按钮后,接收器将开机并等待来自发送器的消息。在等待期间,LED 将循环显示所有颜色。
收到消息后,send_answer()
将调用该函数。它使用接收到的发送方设备的 MAC 地址添加 ESP-NOW 对等点,发送应答消息,然后删除对等点。
当接收器设备像发送器一样将其 LED 变为绿色时,两个设备将几乎同时亮起绿色。
#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。有关该频道或 Home Assistant 集成的更多信息,您应该查看视频。
要运行此代码,您需要在顶部部分添加您的 WiFi 凭据,并在重新连接函数中添加您的 MQTT 凭据。
该代码将所有 ESP-NOW 消息转发为 MQTT 消息,以集成到 Home Assistant 中。主题由标识符“节点”和四位数字“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)。它有一个超低功率稳压器,可用于通过触发中断来激活 Picoclick。中断可以通过软件配置。
硬件
概述
技术制图
测量单位为毫米,网格为 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);
}