功能演示+讲解视频地址:【esp8266+arduino】WiFi+蓝牙实现无线开关功能演示(宿舍神器)_哔哩哔哩_bilibili
源码地址:基于esp8266的开关外设装置: 存放关于此装置的所有工程文件和教程资料,供大家一起学习
篇幅太长不好看,新开一篇接着记录
目录
9、按键 测试
10、EEPROM 测试
11、整合全部模块
功能:项目中作为舵机角度初始化设置的输入:+ 键 角度加,- 键 角度减;长按 +键 确认角度,长按 -键 重新进行角度设置。
TestKey:(中断方式,无法消抖)
#define PLUS 5 //D1 +
#define SUB 4 //D2 -
//声明中断服务函数 外部中断配置
void ICACHE_RAM_ATTR highInterrupt_plus();
void ICACHE_RAM_ATTR highInterrupt_sub();
//中断服务函数
void highInterrupt_plus() {
Serial.println(F("plus"));
digitalWrite(D5,!digitalRead(D5));// 改变LED的点亮或者熄灭状态
}
void highInterrupt_sub() {
Serial.println(F("sub"));
digitalWrite(D5,!digitalRead(D5));// 改变LED的点亮或者熄灭状态
}
void setup() {
// Start Serial
Serial.begin(115200);
//prepare interupt 设置外置按钮管脚为上拉输入模式
pinMode(PLUS,INPUT_PULLUP);
pinMode(SUB,INPUT_PULLUP);
pinMode(D5, OUTPUT); //设置D5引脚为输出模式以便控制LED
// 设置外置按钮管脚中断为上升沿触发模式
attachInterrupt(PLUS, highInterrupt_plus, RISING);
attachInterrupt(SUB, highInterrupt_sub, RISING);
}
void loop() {
}
一般按键的消抖则是通过延时一定的时间来实现。但对于一个定时的系统来说,延时消抖的做法破坏了程序的同步性,是系统中的一个不确定因素。我找到下面的链接,里面提供了一个优化对按键消抖的设计,同时实现了短按、长按按键的功能。
长按,短按参考:《ESP8266学习笔记》之 采用定时器内的按键扫描方法,摒弃传统的延时按键消抖_慕容流年的博客-CSDN博客_esp8266sdk按键消抖程序
TestKeyTick:
本按键检测程序的优化是通过定时器来实现的。首先开启一个1毫秒的定时器,然后在1ms的定时周期内检测按键中断。当按键触发外部中断的时候,系统将按键的变量值加1。按键弹起后将检测变量值乘以1毫秒的定时周期,就能得到按键被按下的总时间,同时如果按键松开就清零按键的变量值。根据按的时间设置长按、短按的阈值,便可实现短按、长按的功能。同时可以设置一个抖动的时间阈值来通过程序消除按键的抖动。按键检测的整体实现流程图如下:
#include
#include
#define PLUS D1 // +
#define SUB D2 // -
//读按键值
#define KEYP digitalRead(PLUS)
#define KEYS digitalRead(SUB)
#define short_press_time 10 //短按时间
#define long_press_time 1000 //长按时间
#define NO_KEY_PRES 0 //无按键按下
#define PLUS_KEY_PRES 1
#define SUB_KEY_PRES 2
#define PLUS_KEY_LONG_PRES 11
#define SUB_KEY_LONG_PRES 22
bool KEYP_UP = 0;//按键状态,0为弹起,1为按下
bool KEYS_UP = 0;//按键状态,0为弹起,1为按下
uint16_t KEYP_PRESS_COUNT = 0;//按KEYU时间计数
uint16_t KEYS_PRESS_COUNT = 0;//按KEYM时间计数
uint8_t KEYP_KEY_READ = NO_KEY_PRES;//KEYU按键状态
uint8_t KEYS_KEY_READ = NO_KEY_PRES;//KEYM按键状态
bool PLUS_INIT = 0; //舵机角度调整,0为不工作,1为工作
bool SUB_INIT = 0; //舵机角度调整,0为不工作,1为工作
bool ACK = 1; //舵机角度确定,0为确定,1为不确定
Ticker key_tick;
Servo myServo; // 定义Servo对象来控制
int pos = 90; // 角度存储变量
void flip() {
//PLUS_KEY
if(KEYP==0){
KEYP_PRESS_COUNT++;
KEYP_UP = 1;
if(KEYP_PRESS_COUNT<=short_press_time)
KEYP_KEY_READ = NO_KEY_PRES;
if(KEYP_PRESS_COUNT>=short_press_time&&KEYP_PRESS_COUNT<=long_press_time)
KEYP_KEY_READ = PLUS_KEY_PRES;
if(KEYP_PRESS_COUNT>=long_press_time)
KEYP_KEY_READ = PLUS_KEY_LONG_PRES;
}
if(KEYP){
KEYP_PRESS_COUNT = 0;
KEYP_UP = 0;//按键状态,0为弹起,1为按下
}
//SUB_KEY
if(KEYS==0){
KEYS_PRESS_COUNT++;
KEYS_UP = 1;
if(KEYS_PRESS_COUNT<=short_press_time)
KEYS_KEY_READ = NO_KEY_PRES;
if(KEYS_PRESS_COUNT>=short_press_time&&KEYS_PRESS_COUNT<=long_press_time)
KEYS_KEY_READ = SUB_KEY_PRES;
if(KEYS_PRESS_COUNT>=long_press_time)
KEYS_KEY_READ = SUB_KEY_LONG_PRES;
}
if(KEYS){
KEYS_PRESS_COUNT = 0;
KEYS_UP = 0;//按键状态,0为弹起,1为按下
}
if(KEYP_KEY_READ == PLUS_KEY_LONG_PRES && KEYP_UP == 0){
Serial.println("--KEY_PLUS长按");
ACK = 0; //确认舵机角度
KEYP_KEY_READ = NO_KEY_PRES;
}
if(KEYP_KEY_READ == PLUS_KEY_PRES && KEYP_UP == 0){
Serial.println("--KEY_PLUS短按");
PLUS_INIT = 1; //舵机角度调整工作
KEYP_KEY_READ = NO_KEY_PRES;
}
if(KEYS_KEY_READ == SUB_KEY_LONG_PRES && KEYS_UP == 0){
Serial.println("--KEY_SUB长按");
KEYS_KEY_READ = NO_KEY_PRES;
ACK = 1; //不确认舵机角度
pos = 90; //角度归中
initServo();
}
if(KEYS_KEY_READ == SUB_KEY_PRES && KEYS_UP == 0){
Serial.println("--KEY_SUB短按");
SUB_INIT = 1; //舵机角度调整工作
KEYS_KEY_READ = NO_KEY_PRES;
}
}
void initServo(){
Serial.println("舵机角度初始化");
while(ACK){
if(PLUS_INIT){
pos += 10;
PLUS_INIT = 0;
}
if(SUB_INIT){
pos -= 10;
SUB_INIT = 0;
}
myServo.write(pos); // 舵机角度写入
delay(100);
}
Serial.println("舵机角度调整结束");
Serial.println(pos);
myServo.write(90); // 舵机角度写入
delay(100);
}
void setup() {
// Start Serial
Serial.begin(115200);
delay(100);
myServo.attach(12); //D6
myServo.write(pos); // 舵机角度写入
delay(50);
//prepare interupt 设置外置按钮管脚为上拉输入模式
pinMode(PLUS,INPUT_PULLUP);
pinMode(SUB,INPUT_PULLUP);
pinMode(D5, OUTPUT); //设置D5引脚为输出模式以便控制LED
key_tick.attach_ms(1, flip);//按键检测定时器(1ms)
initServo();
}
void loop() {
}
功能:实现舵机角度写入,断电不丢失数据
参考:(基于Ardunio)ESP8266之使用EEPROM_许沐白的博客-CSDN博客_esp8266 eeprom
writeEEPROM:(用来写0)
nclude
byte byte1=0;
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
Serial.println("");
EEPROM.begin(1024); //开启EEPROM,开辟1024个位空间
//读取与保存byte类型
EEPROM.write(0,byte1); //给EEPROM 第0位,写入byte_1的值
EEPROM.commit();
byte byte2=EEPROM.read(0);
Serial.print("byte2的值:");
Serial.println(byte2);
}
void loop() {
// put your main code here, to run repeatedly:
}
整合起来的程序实现的就是演示视频里的功能,下图是整个开关外设装置的系统框图:
下图是整个装置的系统流程图:
除了软件还有硬件,下面是硬件电路图:
关键是要找到对应的端口,根据需求,下表是本装置的引脚对应图,图上的线色是我连接使用的杜邦线的颜色(实际要看各位的需求!!!这个只是我的装置的线色)
这里也反应出一个问题就是之前说的引脚坑的问题,大家看着可能会比较乱
testAll:
#include
#include
#include
#include
#include
#include
#include
//-------------------------------- 关于按键的一些定义-----------------------------
#define PLUS D1 // +
#define SUB D2 // -
//读按键值
#define KEYP digitalRead(PLUS)
#define KEYS digitalRead(SUB)
#define short_press_time 10 //短按时间
#define long_press_time 1000 //长按时间
#define NO_KEY_PRES 0 //无按键按下
#define PLUS_KEY_PRES 1
#define SUB_KEY_PRES 2
#define PLUS_KEY_LONG_PRES 11
#define SUB_KEY_LONG_PRES 22
//------------------------------- 页面代码 ----------------------------------------
// 首页
const char* page_html = "\
\r\n\
\r\n\
\r\n\
\r\n\
\r\n\
Switch \r\n\
\r\n\
\r\n\
\r\n\
\r\n\
\r\n\
\r\n\
";
// 操作成功页
const char* page_success_html = "\
\r\n\
\r\n\
\r\n\
\r\n\
\r\n\
Switch \r\n\
\r\n\
\r\n\
\r\n\
\r\n\
\r\n\
\r\n\
";
//------------------------------- 环境配置 ----------------------------------------
const char* AP_SSID = "Switch"; // 这里为AP名字--AP账号
const char* AP_PSW = "12345678"; // 这里为AP密码 8位以上
const byte DNS_PORT = 53; // DNS端口号
bool ledBlinkFlag = 0; // 低电压LED闪烁标志位
bool switchStatus = 0; // 当前开关转态,默认为0
bool KEYP_UP = 0; //按键状态,0为弹起,1为按下
bool KEYS_UP = 0; //按键状态,0为弹起,1为按下
uint16_t KEYP_PRESS_COUNT = 0; //按KEYU时间计数
uint16_t KEYS_PRESS_COUNT = 0; //按KEYM时间计数
uint8_t KEYP_KEY_READ = NO_KEY_PRES;//KEYU按键状态
uint8_t KEYS_KEY_READ = NO_KEY_PRES;//KEYM按键状态
bool PLUS_INIT = 0; //舵机角度调整,0为不工作,1为工作
bool SUB_INIT = 0; //舵机角度调整,0为不工作,1为工作
bool ACK = 1; //舵机角度确定,0为确定,1为不确定
IPAddress apIP(192, 168, 4, 1); // esp8266-AP-IP地址
DNSServer dnsServer; // 创建dnsServer实例
ESP8266WebServer server(80); // 创建WebServer
SoftwareSerial btSerial(D7, D8); // 串口2: Rx,Tx
Servo myServo; // 定义Servo对象
Ticker adcTick; // 电压检测定时器
Ticker ledTick; // LED闪烁定时器
Ticker keyTick; // 按键检测定时器
int pos = 90; // 角度存储变量
int posChange = 0; // 改变的角度值
//------------------------------- 网页请求处理 ----------------------------------------
void handleRoot() {//访问主页回调函数
server.send(200, "text/html", page_html);
}
//Post回调函数:处理LED控制请求
void handleRootPost() {
//digitalWrite(D5,!digitalRead(D5));// 改变LED的点亮或者熄灭状态
servoService();
//server.sendHeader("Location",page_success_html);// 跳转回页面根目录
server.send(200, "text/html", page_success_html);
server.send(303); // 发送Http相应代码303 跳转
Serial.println("/SWITCH 请求");
}
void handleRootSuccess() {//访问主页回调函数
server.send(200, "text/html", page_html);
Serial.println("/SUCCESS请求");
}
//------------------------------- 配置函数 ----------------------------------------
//基础初始化
void initBasic(void){
delay(1000);
Serial.begin(115200); // 串口波特率
btSerial.begin(9600); // 蓝牙波特率
EEPROM.begin(1024); // 开启EEPROM,开辟1024个位空间
pinMode(D5, OUTPUT); // 绿灯
pinMode(D3, OUTPUT); // 黄灯
Serial.println("Started...");
btSerial.write("start");
myServo.attach(12); // D6
myServo.write(pos); // 舵机角度写入
delay(50);
//prepare interupt 设置外置按钮管脚为上拉输入模式
pinMode(PLUS,INPUT_PULLUP);
pinMode(SUB,INPUT_PULLUP);
digitalWrite(D5,LOW); // 绿灯熄灭
ledBlinkFlag = 1; // 黄灯闪烁
adcTick.attach(5, adcService); //电压检测定时器(每5秒检测一次)
ledTick.attach_ms(250, ledBlink);//led闪烁定时器(每250毫秒闪烁一次)
keyTick.attach_ms(1, flip); //按键检测定时器(每1毫秒检测一次)
int eeprom_pos=EEPROM.read(0);
Serial.print("eeprom_pos=");
Serial.println(eeprom_pos);
pos=eeprom_pos;
posChange = abs(pos-90);
// 舵机初始化,rom中有值就无需进行舵机初始化
if(eeprom_pos<60||eeprom_pos>130){
pos=90;
initServo();
}
}
//初始化AP模式
void initSoftAP(void){
WiFi.mode(WIFI_AP);
WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
if(WiFi.softAP(AP_SSID,AP_PSW)){
Serial.println("ESP8266 SoftAP is right");
}
}
//初始化WebServer
void initWebServer(void){
/*必须在第二个参数上添加上HTTP_GET才能不影响强制门户,防止有些设备无法弹出强制门户,要用域名访问,
如果不加第二个参数,就只能实现域名访问而无法强制门户
在无法响应的http请求响应回调设置为主页的回调函数,才可以强制门户*/
server.on("/", HTTP_GET, handleRoot);//设置主页回调函数
server.onNotFound(handleRoot);//设置无法响应的http请求的回调函数
server.on("/SWITCH", HTTP_POST, handleRootPost);//设置Post请求回调函数
server.on("/SUCCESS", HTTP_POST, handleRootSuccess);//设置Post请求回调函数
server.begin();//启动WebServer
Serial.println("WebServer started!");
}
//初始化DNS
void initDNS(void){//初始化DNS服务器
if(dnsServer.start(DNS_PORT, "*", apIP)){//判断将所有地址映射到esp8266的ip上是否成功
Serial.println("start dnsserver success.");
}
else Serial.println("start dnsserver failed.");
}
//舵机角度初始化
void initServo(){
Serial.println("舵机角度初始化");
while(ACK){
if(PLUS_INIT){
pos += 10;
PLUS_INIT = 0;
}
if(SUB_INIT){
pos -= 10;
SUB_INIT = 0;
}
myServo.write(pos); // 舵机角度写入
delay(100);
}
//读取与保存byte类型
EEPROM.write(0,pos); //给EEPROM 第0位,写入byte_1的值
EEPROM.commit();
int eeprom_pos=EEPROM.read(0);
digitalWrite(D5,HIGH); // 绿灯亮
Serial.println("舵机角度调整结束");
Serial.println(pos);
Serial.print("new_eeprom_pos=");
Serial.println(eeprom_pos);
posChange = abs(pos-90);
myServo.write(90); // 舵机角度写入
delay(100);
}
//WiFi环境初始化
void connectNewWifi(void){
WiFi.setAutoConnect(true); //设置自动连接(经过测试,强制门户必须要加上这一句)
//WiFi.begin(); //(经过测试,强制门户必须要加上这一句,我并没有找到原因)
int count = 0;
while (WiFi.status() != WL_CONNECTED) {
delay(500);
count++;
if(count > 10){//如果5秒内没有连上,就开启Web配网 可适当调整这个时间
initSoftAP();
initWebServer();
initDNS();
break;//跳出 防止无限初始化
}
Serial.print(".");
}
ledBlinkFlag = 0; // 初始化结束,黄灯停止闪烁
digitalWrite(D3,LOW); // 黄灯灭
digitalWrite(D5,HIGH); // 绿灯亮
}
//------------------------------- 服务函数 ----------------------------------------
//蓝牙服务
void bluetoothService(){
if (btSerial.available() > 0) { // check if bluetooth module sends some data to esp8266
char data = btSerial.read(); // read the data from bluetooth
switch (data)
{
case 'C': // if receive data is 'C'(change)
//digitalWrite(D5,!digitalRead(D5)); // 改变LED的点亮或者熄灭状态
servoService();
Serial.println("bluetooth chances the status");
break;
default:
break;
}
}
}
//LED闪烁服务
void ledBlink(){
if(ledBlinkFlag){
digitalWrite(D3,!digitalRead(D3));// 黄灯闪烁
}
}
//ADC服务
void adcService(){
//输出0-1023 对应 外部输入电压 0-1.0v
int adcValues = analogRead(A0);
Serial.print("ADC Value: ");
Serial.println(adcValues);
if (adcValues < 300){
ledBlinkFlag = 1;
digitalWrite(D5,LOW); // 绿灯熄灭
}
}
//按键服务
void flip() {
//PLUS_KEY
if(KEYP==0){
KEYP_PRESS_COUNT++;
KEYP_UP = 1;
if(KEYP_PRESS_COUNT<=short_press_time)
KEYP_KEY_READ = NO_KEY_PRES;
if(KEYP_PRESS_COUNT>=short_press_time&&KEYP_PRESS_COUNT<=long_press_time)
KEYP_KEY_READ = PLUS_KEY_PRES;
if(KEYP_PRESS_COUNT>=long_press_time)
KEYP_KEY_READ = PLUS_KEY_LONG_PRES;
}
if(KEYP){
KEYP_PRESS_COUNT = 0;
KEYP_UP = 0;//按键状态,0为弹起,1为按下
}
//SUB_KEY
if(KEYS==0){
KEYS_PRESS_COUNT++;
KEYS_UP = 1;
if(KEYS_PRESS_COUNT<=short_press_time)
KEYS_KEY_READ = NO_KEY_PRES;
if(KEYS_PRESS_COUNT>=short_press_time&&KEYS_PRESS_COUNT<=long_press_time)
KEYS_KEY_READ = SUB_KEY_PRES;
if(KEYS_PRESS_COUNT>=long_press_time)
KEYS_KEY_READ = SUB_KEY_LONG_PRES;
}
if(KEYS){
KEYS_PRESS_COUNT = 0;
KEYS_UP = 0;//按键状态,0为弹起,1为按下
}
if(KEYP_KEY_READ == PLUS_KEY_LONG_PRES && KEYP_UP == 0){
Serial.println("--KEY_PLUS长按");
ACK = 0; //确认舵机角度
KEYP_KEY_READ = NO_KEY_PRES;
}
if(KEYP_KEY_READ == PLUS_KEY_PRES && KEYP_UP == 0){
Serial.println("--KEY_PLUS短按");
PLUS_INIT = 1; //舵机角度调整工作
KEYP_KEY_READ = NO_KEY_PRES;
}
if(KEYS_KEY_READ == SUB_KEY_LONG_PRES && KEYS_UP == 0){
Serial.println("--KEY_SUB长按");
KEYS_KEY_READ = NO_KEY_PRES;
ACK = 1; //不确认舵机角度
pos = 90; //角度归中
initServo();
}
if(KEYS_KEY_READ == SUB_KEY_PRES && KEYS_UP == 0){
Serial.println("--KEY_SUB短按");
SUB_INIT = 1; //舵机角度调整工作
KEYS_KEY_READ = NO_KEY_PRES;
}
}
//舵机服务
void servoService(){
if(switchStatus){
switchStatus = 0;
myServo.write(90+posChange); // 舵机角度写入
delay(100);
}
else{
switchStatus = 1;
myServo.write(90-posChange); // 舵机角度写入
delay(100);
}
Serial.println("开关操作完成");
myServo.write(90); // 舵机角度写入
delay(100);
}
//------------------------------- setup(配置函数) 只执行一次 ----------------------------------------
void setup() {
initBasic();
connectNewWifi();
}
//------------------------------- loop(主函数) 一直执行-------------------------------------------
void loop() {
server.handleClient(); //wifi服务
dnsServer.processNextRequest(); //DNS服务
bluetoothService(); //蓝牙服务
}