ESP32 网络计时器,迭代版本V2,新增电池电量和网络测量

简介

这是一个基于ESP32的计时器项目,具有计时功能、NTP时间同步、电池电量显示、Wi-Fi信号强度显示以及可选择的计时周期。使用了一个OLED显示屏用于显示时间、剩余计时时间、电池电量和Wi-Fi信号强度。

IO连接

OLED屏幕:
    地址:0x3C
    SDA:21
    SCL:22
开始计时按钮:35
停止计时按钮:34
电池测量输入引脚:32
有源蜂鸣器连接引脚:25

使用方法

连接电池/USB和Wi-Fi。
按下开始计时按钮以开始计时。
按下停止计时按钮以暂停计时并切换计时周期。

用途

这个项目可以用作厨房计时器、实验室计时器、日常生活计时器等,具有实用性和便利性。

函数说明

saveIntToEEPROM(int value, int address):将整数值保存到指定的EEPROM地址。
readIntFromEEPROM(int address):从指定的EEPROM地址读取整数值。
buzz(int frequency, long duration):使蜂鸣器以指定频率发出声音,持续指定的时间(毫秒)。
SimpleMovingAverage:简单滑动平均滤波器类,用于对电压值和Wi-Fi信号强度进行滤波处理。
getBatteryPercentage():获取电池电量百分比。
displayBatteryPercent(int percentage):在OLED屏幕上显示电池电量百分比。
checkTimerButtons():检查开始和停止计时按钮的状态,执行相应操作。
readttvalue(int address):从指定的EEPROM地址读取计时周期值。
serialPrintTimeValues():将计时周期值打印到串行监视器。
initializeTimeValues():初始化计时周期值。
setdefaulttime():设置默认计时周期。
displayWifiStatus():在OLED屏幕上显示Wi-Fi信号强度。
updateTimeDisplay():更新OLED屏幕上的时间显示。

基本运行流程

开始
初始化变量
创建计时器实例
设置计时器
启动计时器
计时器回调函数
计时器运行次数是否达到限制
结束计时器
更新计时器

代码流程

1. 初始化

  • a. 连接WiFi网络
  • b. 初始化OLED屏幕
  • c. 设置默认计时时间
  • d. 初始化引脚
  • e. 初始化时间值
  • f. 同步网络时间

2. 主循环

a. 更新时间显示

  - i. 更新网络时间
  - ii. 显示当前时间
  - iii. 显示WiFi信号强度
  - iv. 显示电池电量百分比

b. 检查计时器按钮

  - i. 按钮1:开始/暂停计时器
  - ii. 按钮2:切换计时器时间长度

c. 更新计时器显示

  - i. 如果计时器正在运行
     - 1. 更新计时器时间
     - 2. 显示剩余时间
  - ii. 如果计时器已经暂停
     - 1. 显示暂停图标
  - iii. 如果计时器已经结束
     - 1. 显示计时结束提示
#include <WiFi.h>
#include <NTPClient.h>
#include <Wire.h>
#include <Adafruit_SSD1306.h>
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include <EEPROM.h>
#include <WebServer.h>
#define OLED_ADDR 0x3C // OLED屏幕地址
#define OLED_SDA 21    // ESP32开发板上的SDA引脚
#define OLED_SCL 22    // ESP32开发板上的SCL引脚
#define BUTTON1 35     // 开始计时按钮
#define BUTTON2 34     // 停止计时按钮
#define BAT_PIN 32     // 电池测量输入引脚
#define BUZZER_PIN 25  // 有源蜂鸣器连接的引脚
#define MAXMENU 5
#define ADDR_1 1
#define ADDR_TT1 2
#define ADDR_TT2 3
#define ADDR_TT3 4
#define ADDR_TT4 5
#define EEPROMSIZE 128
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "ntp.ntsc.ac.cn"); // NTP客户端
Adafruit_SSD1306 display(128, 64, &Wire, -1);   // OLED屏幕
int menuvalue = 1;
int wifiStrength = 0;
// 计时器相关变量
unsigned long startTime = 0;     // 计时器开始时间
unsigned long stopTime = 0;      // 计时器停止时间
bool timerRunning = false;       // 是否正在计时
unsigned long duration = 0;      // 计时器持续时间
unsigned long remainingTime = 0; // 剩余时间
int tt1 = 5 * 60 * 1000;         // 默认值为5分钟
int tt2 = 10 * 60 * 1000;        // 默认值为10分钟
int tt3 = 30 * 60 * 1000;        // 默认值为30分钟
int tt4 = 60 * 60 * 1000;        // 默认值为60分钟
// 时间显示相关变量
String lastFormattedTime = "";    // 上次更新的时间
unsigned long lastUpdateTime = 0; // 上次更新时间的时间戳
// 默认倒计时时间
int defaulttime = 5 * 60 * 1000;
// 设置默认倒计时时间
void setdefaulttime();
WebServer server(80);
bool saveIntToEEPROM(int value, int address)
{
  EEPROM.begin(EEPROMSIZE);
  EEPROM.put(address, value);
  bool success = EEPROM.commit();
  EEPROM.end();
  display.setCursor(0, 50);
  display.setTextSize(1);
  if (success)
  {
    display.println("Save Success");
  }
  else
  {
    display.println("Failed");
  }

  Serial.print("save menuvalue:");
  Serial.println(value);
  return success;
}

int readIntFromEEPROM(int address)
{
  if (EEPROM.begin(EEPROMSIZE))
  {
    int value;
    if (EEPROM.get(address, value))
    {
      EEPROM.end();
      return value;
    }
    else
    {
      Serial.println("Failed to read from EEPROM");
    }
  }
  else
  {
    Serial.println("EEPROM initialization failed");
  }
  EEPROM.end();
  return 0;
}

unsigned long timerDuration = 5 * 60 * 1000; // 初始计时器长度为5分钟

// frequency参数是需要发出的声音频率,单位为Hz;duration参数是发出声音的持续时间,单位为毫秒。
void buzz(int frequency, long duration)
{
  int period = 1000000 / frequency; // 计算周期
  int pulse = period / 2;           // 计算脉冲时间

  for (long i = 0; i < duration * 1000L; i += period)
  {
    digitalWrite(BUZZER_PIN, HIGH); // 发送高电平
    delayMicroseconds(pulse);       // 持续脉冲时间的一半
    digitalWrite(BUZZER_PIN, LOW);  // 发送低电平
    delayMicroseconds(pulse);       // 持续脉冲时间的一半
  }
}
// 简单滑动平均滤波器
class SimpleMovingAverage {
  public:
    SimpleMovingAverage(int windowSize) {
      _windowSize = windowSize;
      _values = new float[windowSize];
      reset();
    }
    
    ~SimpleMovingAverage() {
      delete[] _values;
    }
    
    void reset() {
      _index = 0;
      _sum = 0;
      for (int i = 0; i < _windowSize; i++) {
        _values[i] = 0;
      }
    }
    
    float add(float value) {
      _sum -= _values[_index];
      _values[_index] = value;
      _sum += value;
      _index = (_index + 1) % _windowSize;
      return _sum / _windowSize;
    }

  private:
    int _windowSize;
    float* _values;
    int _index;
    float _sum;
};
SimpleMovingAverage filter(30);
SimpleMovingAverage filter2(30);
float getBatteryPercentage()
{
  float percentage;
  int reading = analogRead(32);// 读取IO32引脚电压
  float voltage = reading * 0.001 * 5; // 计算电压值
   // 将电压值映射到0-100的百分比范围
   float filteredVoltage = filter.add(voltage); // 使用滤波器平滑电压值
   
  if (filteredVoltage < 3.3) {
      percentage=0;
    } else if (filteredVoltage > 4.2) {
       percentage= 100;
    } else {
       percentage= (filteredVoltage - 3.3) / (4.2 - 3.3) * 100;
    }
  display.setCursor(100, 0);                                    // 将光标移动到屏幕右上角
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.print((int)percentage);
  display.print("%");
  Serial.println("filteredVoltage:"+(String)percentage);
  return percentage; // 返回电池电量百分比
}
void displayBatteryPercent(int percentage)
{
  display.setCursor(100, 0);
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.println(String(percentage) + "%");
}
void checkTimerButtons()
{
  if (digitalRead(BUTTON1) == LOW && !timerRunning)
  {
    // 开始计时器
    buzz(2000, 100);
    startTime = millis();
    timerRunning = true;
  }
  if (digitalRead(BUTTON2) == LOW)
  {
    buzz(2000, 100);

    if (timerRunning)
    {
      // 停止计时器
      stopTime = millis();
      timerRunning = false;
      remainingTime = timerDuration - (stopTime - startTime);
      while (digitalRead(BUTTON2) == LOW)
        ;
      delay(100);
    }
    else
    {
      // 切换计时器时间长度
      if (menuvalue == 1)
      {
        timerDuration = tt1;
        // saveIntToEEPROM(tt1,ADDR_TT1);
        menuvalue = 2;
      }
      else if (menuvalue == 2)
      {
        timerDuration = tt2;

        // saveIntToEEPROM(tt2,ADDR_TT2);
        menuvalue = 3;
      }
      else if (menuvalue == 3)
      {
        timerDuration = tt3;

        // saveIntToEEPROM(tt3,ADDR_TT3);
        menuvalue = 4;
      }
      else if (menuvalue == 4)
      {
        timerDuration = tt4;

        // saveIntToEEPROM(tt4,ADDR_TT4);
        menuvalue = 1;
      }
      remainingTime = timerDuration;
      defaulttime = timerDuration;
      saveIntToEEPROM(menuvalue, ADDR_1);
      while (digitalRead(BUTTON2) == LOW)
        ;
    }
  }
}

int readttvalue(int address)
{
  int valtemp = 0;

  valtemp = readIntFromEEPROM(address);
  Serial.print("read tt val:");
  Serial.println(valtemp);
  if (valtemp > 2000 && valtemp < 120 * 60 * 1000)
  {
    return valtemp;
  }
  else
  {
    return 4 * 60 * 1000;
  }
}

void serialPrintTimeValues()
{
  Serial.println("tt1 value: " + String(tt1));
  Serial.println("tt2 value: " + String(tt2));
  Serial.println("tt3 value: " + String(tt3));
  Serial.println("tt4 value: " + String(tt4));
}
void initializeTimeValues()
{
  // 可以在此处读取EEPROM中保存的时间选择状态,并根据状态设置默认时间
  tt1 = 5 * 60 * 1000; // readttvalue(ADDR_TT1);
  tt2 = 10 * 60 * 1000;
  tt3 = 30 * 60 * 1000;
  tt4 = 60 * 60 * 1000;
  serialPrintTimeValues();
}
void setdefaulttime()
{
  menuvalue = readIntFromEEPROM(ADDR_1);
  Serial.print("read menuvalue:");
  Serial.println(menuvalue);
  if (menuvalue > 0 && menuvalue <= MAXMENU)
  {
    switch (menuvalue)
    {
    case 1:
      defaulttime = tt1;
      break;
    case 2:
      defaulttime = tt2;
      break;
    case 3:
      defaulttime = tt3;
      break;
    case 4:
      defaulttime = tt4;
      break;
    default:
      defaulttime = tt1;
      break;
    }
  }
  else
  {
    menuvalue = 1;
    saveIntToEEPROM(menuvalue, ADDR_1);
    defaulttime = 5 * 60 * 1000;
  }
  remainingTime = defaulttime;
}
void displayWifiStatus()
{
  int dBm = WiFi.RSSI();
  int quality;
  if (dBm <= -100)
  {
    quality = 0;
  }
  else if (dBm >= -50)
  {
    quality = 100;
  }
  else
  {
    quality = 2 * (dBm + 100);
  }
  int filteredDBM = int(filter2.add(quality));
  display.setCursor(0, 25);
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.print("WiFi: ");
  display.print(filteredDBM);
  display.print("%");
  // display.display();
}
void updateTimeDisplay()
{
  timeClient.update();
  String formattedTime = timeClient.getFormattedTime();
  display.clearDisplay();
  display.setCursor(0, 0);
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.println(formattedTime);
  displayWifiStatus();
  getBatteryPercentage();
  if (timerRunning)
  {
    duration = millis() - startTime;
    remainingTime = timerDuration - duration;

    display.setCursor(0, 36);
    display.setTextSize(4);
    int minutes = remainingTime / 1000 / 60;
    int seconds = (remainingTime / 1000) % 60;
    if (minutes < 10)
    {
      display.print("0");
    }
    display.print(minutes);
    display.print(":");
    if (seconds < 10)
    {
      display.print("0");
    }
    display.println(seconds);
  }
  else
  {
    display.setCursor(0, 36);
    display.setTextSize(4);
    int _minutes = remainingTime / 1000 / 60;
    int _seconds = (remainingTime / 1000) % 60;
    if (_minutes < 10)
    {
      display.print("0");
    }
    display.print(_minutes);
    display.print(":");
    if (_seconds < 10)
    {
      display.print("0");
    }
    // drawWiFiSignalStrength();
    display.println(_seconds);
  }

  display.display();
}

void setup()
{
  // 连接WiFi网络
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
  Serial.begin(115200);
  Wire.begin(OLED_SDA, OLED_SCL);
  display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR);
  display.clearDisplay();
  display.setCursor(0, 0);
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.println("Connecting to ");
  display.println("360WiFi-016C34");
  display.println("Password: ");
  display.println("wangjinxuan");
  display.display();
  WiFi.begin("360WiFi-016C34", "wangjinxuan");
  int i = 0;
  while (WiFi.status() != WL_CONNECTED && i < 10)
  {
    delay(500);
    display.clearDisplay();
    display.setCursor(0, 0);
    display.print("Connecting");
    for (int j = 0; j < i; j++)
    {
      display.print(".");
    }
    display.display();
    i++;
  }
  if (WiFi.status() != WL_CONNECTED)
  {
    display.clearDisplay();
    display.setCursor(0, 0);
    display.println("Failed to connect.");
    display.display();
    while (true)
    {
      // 连接失败,停止程序
    }
  }
  wifiStrength = WiFi.RSSI();
  // 初始化OLED屏幕
  Wire.begin(OLED_SDA, OLED_SCL);
  display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR);
  display.clearDisplay();

  // 初始化数值
  setdefaulttime();

  // 初始化引脚
  pinMode(BUTTON1, INPUT_PULLUP);
  pinMode(BUTTON2, INPUT_PULLUP);
  pinMode(BAT_PIN, INPUT);
  pinMode(BUZZER_PIN, OUTPUT);
  // 初始化时间数值
  initializeTimeValues();
  // 同步时间
  timeClient.begin();
  timeClient.setTimeOffset(28800); // 设置时区(这里为东八区)
}

const unsigned long SLEEP_DELAY = 60 * 60 * 1000; // 休眠时间为1小时
unsigned long lastActiveTime = millis();          // 上次操作时间为当前时间
void sleepMode()
{
  display.clearDisplay();
  display.display();
  esp_deep_sleep_start();
}
void loop()
{
  checkTimerButtons();
  updateTimeDisplay();
  if (timerRunning)
  {
    duration = millis() - startTime;
    remainingTime = timerDuration - duration;
    if (remainingTime <= 0 || remainingTime > timerDuration)
    {
      // 计时器已完成
      timerRunning = false;
      buzz(2000, 1000);
      delay(800);
      buzz(2000, 1000);
      delay(800);
      buzz(2000, 1000);
      delay(800);
      remainingTime = timerDuration;
      // TODO: 执行计时器完成操作
    }
  }
  else
  {
    // 计时器未启动,检查是否需要进入休眠模式
    unsigned long currentTime = millis();
    if (currentTime - lastActiveTime >= SLEEP_DELAY)
    {
      // 如果距离上次操作已经超过休眠延时,则进入休眠模式
      sleepMode();
    }
  }
}

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值