# 尝试 ESP32+Arduino 创建一个带屏幕的ws2812 灯带控制器

灯带物料表:

https://docs.geeksman.com/esp32/

ws 2812 灯带 一条。 Nano V3 Atmega328P 一块

Pasted image 20240328174354.png 4按键板一块 供电模组: 18650 软包锂电池 usb 5V供电(暂定)

一块 Oled 单色屏(用于显示UI)SSD1306 驱动

代码:

参考地址:

https://blog.csdn.net/qq_55493007/article/details/125374751 `https://doc.itprojects.cn/0006.zhishi.esp32/02.doc/index.html

参考代码:

import neopixel
from machine import Pin
import time
import urandom
GBIO_IN = Pin(15) # 控制信号输入引脚
LED_NUM = 30 # LED灯的数量
# class NeoPixel(pin, n, bpp=3, timing=0)
# pin :输出引脚,可使用引脚见下文
# n :LED灯的个数
# bpp:
# 3:默认为3元组RGB
# 4:对于具有3种以上颜色的LED,例如RGBW像素或RGBY像素,采用4元组RGBY或RGBY像素
# timing:默认等于0,为400KHz速率;等于1,为800KHz速率
#
LED = neopixel.NeoPixel(pin=GBIO_IN, n=LED_NUM, timing=1) # 创建控制对象
LED.fill((255, 0, 0)) # GRB填充数据(RGB顺序, 0为不亮,255为全亮)
LED.write() # 写入数据
time.sleep(1)
while True:
r = urandom.randint(0, 255)
g = urandom.randint(0, 255)
b = urandom.randint(0, 255)
for i in range(30):
LED[i] = (r, g, b) # 依次设置LED灯珠的颜色
LED.write() # 写入数据
time.sleep_ms(50)
r = urandom.randint(0, 255)
g = urandom.randint(0, 255)
b = urandom.randint(0, 255)
for i in range(29, -1, -1):
LED[i] = (r, g, b) # 依次设置LED灯珠的颜色
LED.write() # 写入数据
time.sleep_ms(50)

项目需求可变量:

灯带长度(代码中配置) 循环长度(代码中配置) 速度 : float[] 可调节 // ms 亮度: float[] 可调节 // 0 ~ 1 模式:text[] 可轮流切换

根据 current_mode 来进行队列生成方法的选择

处理时考虑 灯带长度,循环长度,亮度 生成色表 通过色表来进行硬件渲染 速度决定更新时长间隔

ui如何处理?

按键功能如何分配?

暂定4 按键?

按键需要实现功能:

有UI: 开机(确定), +, -, 返回 // 改为三个按钮: 取消返回键,确定和返回共用按钮 开机 主界面: 模式 常亮(单色) 呼吸灯(单色 / 渐变) 跑马灯(没有颜色选项) 渐变(没有颜色选项) 闪烁(受到颜色影响,可随机,可渐变) 随机颜色(不受颜色影响) 缓亮(固定颜色) 亮度(灯光亮度)[0到255] 32,127,255 速度(灯光速度) 单位毫秒,更新一次的时间 uint speedList[] = { 20, 50, 100 }; 切换随机 切换渐变

// 返回 HSV 颜色 (待定)
int rgb_to_hsv(int r,int g,int b){
  double min, max, delta;
  min = std::min(std::min(r, g), b);
  max = std::max(std::max(r, g), b);
  v = max / 255.0;
  delta = max - min;
    if (max != 0) {
        s = delta / max;
    } else {
        s = 0;
        h = -1;
        return;
    }
    // max 为红色
        if (r == max) {
        h = (g - b) / delta;
    } else if (g == max) {
        h = 2 + (b - r) / delta;
    } else {
        h = 4 + (r - g) / delta;
    }
    return h,s,v
}

屏幕操作参考: https://www.bilibili.com/video/BV1RM4y1a7J5?p=18 Pasted image 20240327163930.png

按键板:

K1A4
K2A5
K3A6
K4A7
GNDGND

新版按键:

K1D55
K2D44
K3D33
K4D22
GNDGND

OLED 屏幕 # 0.91寸OLED模块12832液晶屏ssd1306显示屏 IIC模块 4针oled屏

GNDGND
VCCVIN/3V3
SCKD1317// 链接到 SCL(A5) 上 成功点亮
SDAA427

大号屏幕链接图:

Pasted image 20240328184358.png

测试项目: pong

https://wokwi.com/projects/348849468083274322

已知问题:屏幕字库不支持中文

切换菜单的 代码

<SPI.h>
<Wire.h>
<Adafruit_GFX.h>
<Adafruit_SSD1306.h>
UP_BUTTON 2
DOWN_BUTTON 3
String MENU_LIST[4] = { "Color", "Speed", "Light", "Power off" };
int CURRENT_MENU = 0;
int MENU_ITEMS = 4;
Adafruit_SSD1306 display = Adafruit_SSD1306(128, 64, &Wire);
uint8_t count;
void setup()
{
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  // Display the splash screen (we're legally required to do so)
  display.display();
  // unsigned long start = millis();
  pinMode(UP_BUTTON, INPUT_PULLUP);
  pinMode(DOWN_BUTTON, INPUT_PULLUP);
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(10, 18);
  display.display();
}
void loop()
{
  static bool up_state = false;
  static bool down_state = false;
  up_state |= (digitalRead(UP_BUTTON) == LOW);
  down_state |= (digitalRead(DOWN_BUTTON) == LOW);
  if (up_state)
  {
    delay(300);
    CURRENT_MENU = (CURRENT_MENU+1)%MENU_ITEMS;
  }
  if (down_state)
  {
    delay(300);
    if (CURRENT_MENU > 0) {
      CURRENT_MENU -= 1;
    } else {
      CURRENT_MENU = 0;
    }
  }
  up_state = down_state = false;
  display.clearDisplay();
  display.setCursor(5, 18);
  display.print(CURRENT_MENU);
  display.setCursor(25, 18);
  display.print(MENU_LIST[CURRENT_MENU]);
  display.display();
}

// 调试问题:https://www.jianshu.com/p/8e14f233d0e7

文字显示 在线取模: https://www.zhetao.com/fontarray.html https://javl.github.io/image2cpp/ 默认配置即可

Pasted image 20240329171501.png

忍杀取模 64 * 32 Pasted image 20240329171435.png

// 'sp20240329_170854_974', 58x32px
const unsigned char epd_bitmap_sp20240329_170854_974 [] PROGMEM = {
0x00, 0x00, 0x01, 0xc0, 0x10, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x0f, 0x80, 0x1c, 0x00, 0x03, 0x00,
0x3c, 0x00, 0x7f, 0x80, 0x1e, 0x00, 0x0e, 0x00, 0x0f, 0xc3, 0xff, 0x80, 0x1f, 0x90, 0x7e, 0x00,
0x03, 0xff, 0xff, 0x80, 0x9f, 0x99, 0xf4, 0x00, 0x01, 0xff, 0xc7, 0x00, 0x7f, 0x1f, 0xcc, 0x80,
0x01, 0xfc, 0x0f, 0x00, 0x3e, 0x0f, 0x18, 0x80, 0x03, 0xf8, 0x0f, 0x00, 0x3f, 0x0e, 0x19, 0x80,
0x03, 0xf0, 0x0f, 0x00, 0x3f, 0xce, 0x31, 0x80, 0x07, 0xe0, 0x0f, 0x00, 0x73, 0x8f, 0x33, 0xc0,
0x1f, 0xc0, 0x07, 0x00, 0x61, 0x1f, 0x73, 0xc0, 0x7f, 0x80, 0x87, 0x80, 0x80, 0x3f, 0x63, 0x80,
0x3f, 0x80, 0x67, 0x80, 0x38, 0x7e, 0x67, 0x80, 0x0f, 0x80, 0x3f, 0xc0, 0xf0, 0x1c, 0xff, 0x00,
0x03, 0xc8, 0x1f, 0xc0, 0x70, 0x08, 0xfe, 0x00, 0x00, 0x64, 0x07, 0x80, 0x31, 0xc1, 0xfc, 0x00,
0x23, 0x16, 0x03, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x11, 0xc6, 0x00, 0x00, 0x78, 0x3f, 0x80, 0x00,
0x19, 0xe7, 0x40, 0x81, 0xf0, 0x07, 0xfe, 0x00, 0x39, 0xcf, 0x41, 0x00, 0x78, 0x40, 0xfc, 0x00,
0x79, 0x9f, 0x63, 0x00, 0x7e, 0x30, 0x78, 0x00, 0x79, 0x9e, 0x66, 0x00, 0xff, 0x98, 0xf0, 0x00,
0x33, 0x8c, 0xf6, 0x00, 0xbb, 0x8f, 0xe0, 0x00, 0x03, 0x81, 0xf7, 0x01, 0x33, 0x07, 0xc0, 0x00,
0x03, 0xc1, 0xf7, 0x80, 0x31, 0x0f, 0xf8, 0x00, 0x01, 0xe1, 0xe7, 0x80, 0x39, 0x1f, 0xf0, 0x00,
0x00, 0x70, 0xc7, 0x00, 0x78, 0x79, 0xe0, 0x00, 0x00, 0x38, 0x0f, 0x00, 0xfc, 0xe1, 0xc0, 0x00,
0x00, 0x1c, 0x1e, 0x00, 0x7d, 0x81, 0x00, 0x00, 0x00, 0x06, 0x38, 0x00, 0x1c, 0x00, 0x00, 0x00,
0x00, 0x03, 0xe0, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00
};
// Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 272)
const int epd_bitmap_allArray_LEN = 1;
const unsigned char* epd_bitmap_allArray[1] = {
epd_bitmap_sp20240329_170854_974
};

最简显示BMP代码

<SPI.h>
<Wire.h>
<Adafruit_GFX.h>
<Adafruit_SSD1306.h>
Adafruit_SSD1306 display = Adafruit_SSD1306(128, 32, &Wire);
// 'sp20240329_170854_974', 58x32px
const unsigned char epd_bitmap_sp20240329_170854_974 [] PROGMEM = {
0x00, 0x00, 0x01, 0xc0, 0x10, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x0f, 0x80, 0x1c, 0x00, 0x03, 0x00,
0x3c, 0x00, 0x7f, 0x80, 0x1e, 0x00, 0x0e, 0x00, 0x0f, 0xc3, 0xff, 0x80, 0x1f, 0x90, 0x7e, 0x00,
0x03, 0xff, 0xff, 0x80, 0x9f, 0x99, 0xf4, 0x00, 0x01, 0xff, 0xc7, 0x00, 0x7f, 0x1f, 0xcc, 0x80,
0x01, 0xfc, 0x0f, 0x00, 0x3e, 0x0f, 0x18, 0x80, 0x03, 0xf8, 0x0f, 0x00, 0x3f, 0x0e, 0x19, 0x80,
0x03, 0xf0, 0x0f, 0x00, 0x3f, 0xce, 0x31, 0x80, 0x07, 0xe0, 0x0f, 0x00, 0x73, 0x8f, 0x33, 0xc0,
0x1f, 0xc0, 0x07, 0x00, 0x61, 0x1f, 0x73, 0xc0, 0x7f, 0x80, 0x87, 0x80, 0x80, 0x3f, 0x63, 0x80,
0x3f, 0x80, 0x67, 0x80, 0x38, 0x7e, 0x67, 0x80, 0x0f, 0x80, 0x3f, 0xc0, 0xf0, 0x1c, 0xff, 0x00,
0x03, 0xc8, 0x1f, 0xc0, 0x70, 0x08, 0xfe, 0x00, 0x00, 0x64, 0x07, 0x80, 0x31, 0xc1, 0xfc, 0x00,
0x23, 0x16, 0x03, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x11, 0xc6, 0x00, 0x00, 0x78, 0x3f, 0x80, 0x00,
0x19, 0xe7, 0x40, 0x81, 0xf0, 0x07, 0xfe, 0x00, 0x39, 0xcf, 0x41, 0x00, 0x78, 0x40, 0xfc, 0x00,
0x79, 0x9f, 0x63, 0x00, 0x7e, 0x30, 0x78, 0x00, 0x79, 0x9e, 0x66, 0x00, 0xff, 0x98, 0xf0, 0x00,
0x33, 0x8c, 0xf6, 0x00, 0xbb, 0x8f, 0xe0, 0x00, 0x03, 0x81, 0xf7, 0x01, 0x33, 0x07, 0xc0, 0x00,
0x03, 0xc1, 0xf7, 0x80, 0x31, 0x0f, 0xf8, 0x00, 0x01, 0xe1, 0xe7, 0x80, 0x39, 0x1f, 0xf0, 0x00,
0x00, 0x70, 0xc7, 0x00, 0x78, 0x79, 0xe0, 0x00, 0x00, 0x38, 0x0f, 0x00, 0xfc, 0xe1, 0xc0, 0x00,
0x00, 0x1c, 0x1e, 0x00, 0x7d, 0x81, 0x00, 0x00, 0x00, 0x06, 0x38, 0x00, 0x1c, 0x00, 0x00, 0x00,
0x00, 0x03, 0xe0, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00
};
// 动画用帧数控制
// Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 272)
const int epd_bitmap_allArray_LEN = 1;
const unsigned char* epd_bitmap_allArray[1] = {
epd_bitmap_sp20240329_170854_974
};
void setup()
{
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.drawBitmap(35, 0, epd_bitmap_sp20240329_170854_974, 58, 32, SSD1306_WHITE);
display.display();
}
void loop()
{
}

最终控制代码

<Arduino.h>
<String.h>
<Adafruit_NeoPixel.h>
<Adafruit_GFX.h>
<Adafruit_SSD1306.h>
// 按钮参数
UP_BTN 2 // D2 (可省略)
DOWN_BTN 3 // D3
CONFIRM_BTN 4 // D4
// 灯带参数
LED_PIN 6 //D6
LED_NUM 30
DelayTime 50
Adafruit_NeoPixel strip(LED_NUM, LED_PIN, NEO_GRB + NEO_KHZ800);
/*
屏幕链接:
SCK A5
SDA A4
*/
Adafruit_SSD1306 display = Adafruit_SSD1306(128, 32, &Wire);
// 先测试通过上下按键切换不同的菜单选项
// 菜单
String menuList[] = {
"1:Mode",
"2:Bright",
"3:Color",
"4:Delay",
};
// 当前菜单项
uint8_t currentMenuIndex = 0;
// 菜单总长度(用于处理
uint8_t menuLength = 4;
// 标记是否在子菜单中,负责渲染菜单/选项 和 处理按键操作
bool isSubMenu = false;
// 模式相关
String modeList[] = {
"Rainbow Flow", // 0 彩虹跑马灯
"Rainbow Pause", // 1 彩虹暂停
"Rainbow Breath", // 2 彩虹呼吸灯
"Breath", // 3 单色呼吸灯
"Blink", // 4 闪烁
"Pulse", // 5 脉动
"Sigle Color", // 6 单色常亮(从头开始依次点亮)
};
uint8_t currentModeIndex = 0;
uint8_t modeLength = 7;
// 亮度相关 默认中等亮度
float brightList[] = { 15, 31, 127, 255 };
uint8_t currentBrightIndex = 1;
uint8_t brightLength = 4;
// 速度配置项 // 单位毫秒,更新一次的时间
unsigned long delayList[] = { 20, 50, 100, 500 };
uint8_t currentDelayIndex = 2;
uint8_t delayLength = 4;
// 色彩 // 可能不需要,考虑通过Hue 实现渐变
uint32_t colorList[] = {
strip.Color(255, 0, 0), // 红色
strip.Color(255, 165, 0), // 橙色
strip.Color(255, 255, 0), // 黄色
strip.Color(0, 128, 0), // 绿色
strip.Color(0, 255, 255), // 青色
strip.Color(0, 0, 255), // 蓝色
strip.Color(128, 0, 128), // 紫色
strip.Color(255, 255, 255) // 白色
};
String colorNameList[] = {
"Red",
"Orange",
"Yellow",
"Green",
"Cyan",
"Blue",
"Purple",
"White",
};
uint8_t currentColorIndex = 1;
uint8_t colorLength = 8;
// 处理按钮截流和灯效延迟
bool btn_flag = false;
unsigned long previousMillisBtn = 0;
unsigned long previousMillis = 0;
// seed用于生成特效时记录当前的渲染指数
long rainbowSeed = 0;
int breathSeed = 0;
int pulseSeed = 0;
// 灯效相关代码
// 控制呼吸灯强度
void setBreathBrightness(int breathSteps) {
int step = floor(brightList[currentBrightIndex] / breathSteps);
/**
* 当前的 seed 提供初始值
* 共计循环 steps *2 次,每次单独计算亮度,
* 随后计算本次亮度(如何处理默认亮度?通过判断 currentModeIndex 跳过、
* seed小于steps: 直接设定亮度为 seed * step
* seed大于steps: 设定为 currentBright - (seed-steps)*step
* */
int bright = 0;
if (breathSeed <= breathSteps) {
bright = breathSeed * step;
} else {
bright = brightList[currentBrightIndex] - ((breathSeed - breathSteps) * step);
}
breathSeed = (breathSeed + 1) % (2 * breathSteps);
strip.setBrightness(bright);
}
// 彩虹跑马灯 参数控制是否静态
void rainbowFlow(bool isStatic) {
// 向上取整
uint16_t step = ceil(65535 / strip.numPixels());
uint16_t firstHue = 65535 - rainbowSeed * step; // 根据seed算出当前第一个led的Hue seed
// 直接算出修正值,用于修正
for (uint16_t i = 0; i < strip.numPixels(); i++)
// 遍历灯条
{
/**
* 计算颜色
* seed 需要跟 灯珠数量对比
* 第一个偏移为0, 每个灯珠的 Hue 步进为多少?(65535/30?)
* 指定第i灯的颜色
* 例如:长度10, 0灯初始Hue为0, 1灯初始Hue 为 6553
* 第二帧时: 0灯Hue 为 65535-6553 1灯为0
* 3帧: 0 = 65535-6553-6553(65535*(10-2)/10) 1 为 65535*(10-1)/10
*/
uint16_t pixelHue = firstHue + step * i;
strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue)));
}
if (!isStatic) {
rainbowSeed = (rainbowSeed + 1) % strip.numPixels();
}
strip.show();
}
void rainbowBreath(int steps) {
setBreathBrightness(steps);
rainbowFlow(false);
}
void singleColor(int len = strip.numPixels()) {
// 单色,直接点亮
if (len > 0) {
strip.fill(colorList[currentColorIndex], 0, len);
} else {
strip.clear();
}
strip.show();
}
// 此处 steps 不能小于最小亮度
void singleBreath(int breathSteps)
// 通过 breathSeed 来控制当前的呼吸灯状态。 breathSteps 控制段数
{
setBreathBrightness(breathSteps);
singleColor();
}
/**
* 处理灯光闪烁
*/
void blink() {
// 跟呼吸同理,但是只有亮灭,step 为1 即可实现
singleBreath(1);
}
void pulse() {
/**
* 通过 pulseSeed 实现。上限为 length*2
* 根据 pulse获得当前长度,然后用 fill 填充长度即可
*/
int total = strip.numPixels();
strip.clear();
if (pulseSeed <= total) {
singleColor(pulseSeed);
} else {
singleColor(total - (pulseSeed - total));
}
pulseSeed = (pulseSeed + 1) % (total * 2);
}
// 功能性方法,处理屏幕和按键
/**处理按钮按下的后续操作
* 总共三个按钮需要处理
* 按键类型用对应针脚代替
* 上 D2 2
* 下 D3 3
* 确认 D4 4
* (也可以简化掉 上键
*
* 判断处理顺序:
* 先判断是否为 confirm、
* 判断当前是否 sub
*/
void handlePressBtn(uint8_t type) {
if (type == CONFIRM_BTN) {
isSubMenu = !isSubMenu;
} else if (!isSubMenu)
// 主菜单
{
switch (type) {
case DOWN_BTN: // 下
currentMenuIndex = (currentMenuIndex + 1) % menuLength;
break;
case UP_BTN: // 上
currentMenuIndex = (currentMenuIndex == 0 ? menuLength : currentMenuIndex) - 1;
break;
}
} else
// 子菜单
{
switch (type) {
case DOWN_BTN: // 下
switch (currentMenuIndex) {
case 0: // MODE
currentModeIndex = (currentModeIndex + 1) % modeLength;
break;
case 1: // Bright
currentBrightIndex = (currentBrightIndex + 1) % brightLength;
break;
case 2: // Color
currentColorIndex = (currentColorIndex + 1) % colorLength;
break;
case 3: // Delay
currentDelayIndex = (currentDelayIndex + 1) % delayLength;
break;
}
break;
case UP_BTN: // 上
switch (currentMenuIndex) {
case 0: // MODE
currentModeIndex = (currentModeIndex == 0 ? modeLength : currentModeIndex) - 1;
break;
case 1: // Bright
currentBrightIndex = (currentBrightIndex == 0 ? brightLength : currentBrightIndex) - 1;
break;
case 2: // Color
currentColorIndex = (currentColorIndex == 0 ? colorLength : currentColorIndex) - 1;
break;
case 3: // Delay
currentDelayIndex = (currentDelayIndex == 0 ? delayLength : currentDelayIndex) - 1;
break;
}
break;
}
}
}
/**
* 该方法用于渲染当前屏幕内容
*/
void renderScreen() {
display.clearDisplay();
// 根据当前是否为子菜单判断
if (!isSubMenu) {
// 字号2
display.setCursor(0, 6);
display.setTextSize(2);
// 渲染主菜单内容
display.print(menuList[currentMenuIndex]);
} else {
// 字号1
display.setTextSize(1);
// 先渲染Menu项:
display.setCursor(0, 0);
display.print(menuList[currentMenuIndex] + ":");
// 再获取 subMenu 选项
String text;
// 当前为子菜单时,根据case 渲染对应的 item
switch (currentMenuIndex) {
case 0: // MODE
text = modeList[currentModeIndex];
break;
case 1: // Bright
text = String(currentBrightIndex + 1);
break;
case 2: // Color
text = colorNameList[currentColorIndex];
break;
case 3: // Delay
text = "Delay: " + String(delayList[currentDelayIndex]) + " ms";
break;
}
display.setCursor(12, 12);
display.print(text);
}
display.display();
}
/**
* 用于点亮灯条,根据当前的参数选择不同的灯条渲染方法
String modeList[] = {
"Rainbow Flow", // 彩虹跑马灯
"Rainbow Gradient", // 彩虹暂停
"Rainbow Breath", // 彩虹呼吸灯
"Breath", // 单色呼吸灯
"Blink", // 闪烁
"Pulse", // 脉动
"Sigle Color", // 单色常亮(从头开始依次点亮)
};
* *灯效处理内部不加延时
*/
void renderStrip() {
// 设置亮度
// 如果 mode 不为呼吸灯,呼吸灯则跳过设置亮度
if (currentModeIndex != 2 && currentModeIndex != 3) {
strip.setBrightness(brightList[currentBrightIndex]);
}
switch (currentModeIndex) {
case 0: // 彩虹跑马灯
rainbowFlow(false);
break;
case 1: // 彩虹暂停
// rainbowStatic();
rainbowFlow(true);
break;
case 2: // 彩虹呼吸灯
rainbowBreath(15);
break;
case 3: // 单色呼吸灯
singleBreath(15);
break;
case 4: // 闪烁
// 呼吸 step 为 1 即可实现
singleBreath(1);
break;
case 5: // 脉动
pulse();
break;
case 6: // 单色常亮
singleColor();
break;
default:
rainbowFlow(false);
}
// 测试用
}
// 最后执行阶段
void setup() {
// 初始化灯条
strip.begin();
strip.setBrightness(brightList[currentBrightIndex]);
strip.show();
// 初始化按键
pinMode(UP_BTN, INPUT_PULLUP);
pinMode(DOWN_BTN, INPUT_PULLUP);
pinMode(CONFIRM_BTN, INPUT_PULLUP);
// 初始化屏幕
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(WHITE);
}
void loop() {
/**
* 此处结构为:
* 初始化
* 监听按键
* 按下则按照情况处理
* 渲染界面
* 根据当前的 isSubmenu 和 currentMenu 以及 index 等处理显示内容
* 点亮灯条
* 根据当前的所有参数(Bright, Mode, Delay, Random) 进行渲染
*/
unsigned long currentMillis = millis(); // 获取当前时间
// 监听按键
static bool up_state = false;
static bool down_state = false;
static bool confirm_state = false;
// 读取按键
up_state |= (digitalRead(UP_BTN) == LOW);
down_state |= (digitalRead(DOWN_BTN) == LOW);
confirm_state |= (digitalRead(CONFIRM_BTN) == LOW);
if (currentMillis - previousMillisBtn >= 300) {
// 按钮flag 复位
btn_flag = false;
}
if (!btn_flag && (up_state || down_state || confirm_state)) {
delay(20);
// 储存当前按钮时间戳并且设定 flag, 跳过200ms内的处理
previousMillisBtn = currentMillis;
btn_flag = true;
if (up_state) {
handlePressBtn(UP_BTN);
} else if (down_state) {
handlePressBtn(DOWN_BTN);
} else if (confirm_state) {
handlePressBtn(CONFIRM_BTN);
}
}
up_state = down_state = confirm_state = false;
// 比较按钮时间戳并且在200ms后复位 btn_flag
// 展示屏幕内容
renderScreen();
// 根据指定的延迟渲染灯条
if (currentMillis - previousMillis >= delayList[currentDelayIndex]) {
renderStrip();
previousMillis = currentMillis;
}
}
My avatar

感谢您阅读我的博客文章!


More Posts