简介
这是一个基于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();
}
}
}