#include <U8g2lib.h>
#include <Wire.h>
#define buttun_1 2 // 按钮1 的引脚号
#define buttun_2 3 // 按钮2 的引脚号
// U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); //该驱动是1.3寸的OLED屏幕。---------
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE, /* clock=*/SCL, /* data=*/SDA); // 0.9寸的OLED显示器的驱动使用硬件I2C
short x, x_target; // x当前x坐标值, 目标x坐标值
short y = 15, y_target = 15; // y坐标,目标y坐标
int state; // 状态
byte menu_length; // 菜单长度(列表成员数量)
u8g2_uint_t menu_str_h; // 菜单字符串高度 menu_string_height
u8g2_uint_t menu_str_w; // 菜单字符串宽度 menu_string_width
short box_y, box_y_target; // 方框的y坐标,目标y坐标
short box_w, box_w_target; // 方框的宽度,目标宽度
char menu_select = 0; // 菜单选定的成员序号
byte o; // 用于保存长按的按钮号
typedef struct // 定义别名为 MENU_LIST的结构
{
char *str; // 指针变量用于指向菜单列表成员的地址 menu_list
} MENU_LIST;
MENU_LIST menu_list[] = { // 定义结构体数组来存放菜单列表
{"a"},
{"ab"},
{"abc"},
{"abcd"},
{"a"},
{"abcde"},
{"abcdef"}};
typedef struct // 按钮事件
{
byte value; // 当前读取的按钮键值 value
byte lastValue; // 上一次读取的按钮键值 lastValue
byte change; // 改变标记
byte press_mark; // 按下标记
byte longPress_mark; // 长按标记 用来屏蔽松开的时候检测到
} BUTTON_T;
BUTTON_T button[2] = {0}; // 构建按钮事件结构体数组 button并初始化
int button_long_press_count = 0; // 按钮长按计数
typedef struct // 按钮消息
{
byte id; // 按钮号
byte press; // 是否按下
byte update_flag; // 是否最新
byte longPress; // 是否长按
} BUTTON_MSG; // 按钮消息
BUTTON_MSG button_msg = {0}; // 构建结构变量并初始化
/***-------------2.1.1--获取按钮键值------------------***/
byte get_io_val(byte ch) // 获取对应引脚信号并返回
{
if (ch == 0) // 参数为0 : 获取并返回 按钮1 的信号,
{
return digitalRead(buttun_1);
}
else
{
return digitalRead(buttun_2); // 否则 获取并返回 按钮2 的信号
}
}
/***===================================================***/
/***------------按钮初始化函数------***/
void button_init(void) // 按钮初始化
{
for (int i = 0; i < 2; i++)
{
button[i].value = button[i].lastValue = get_io_val(i); // 将按键初始化成上电时候的状态
}
}
/***===================================================***/
/***-------------------------2.1--扫描按钮并标记状态----------------------***/
void button_scan(void) // 按钮状态
{
for (int i = 0; i < 2; i++) // 遍历按钮成员
{
button[i].value = get_io_val(i); // 获取当前按钮键值
if (button[i].value != button[i].lastValue) // 如果键值发生改变
{
button[i].lastValue = button[i].value; // 更新状态
button[i].change = 1; // 改变记录标记
}
}
}
/***===================================================***/
/***--------------------------1-- 按钮执行程序----------------------***/
void button_proc(void)
{
for (int i = 0; i < 2; i++)
{
if (button[i].change == 1) // 如果按键状态改变了 表示有按键 按下 (或松开) 了
{
button[i].change = 0; // 清除状态
if (button[i].value == 0) // 如果是按下状态
{
button_long_press_count = 10; // 开始长按倒计时
}
else // 如果按钮松开
{
if (button[i].longPress_mark) // 判断是否有长按标志,如果有
{
button[i].longPress_mark = 0; // 清除长按标志
button[i].press_mark = 0; // 清除短按计数,避免发送短按消息
Serial.println("longPress_mark");
}
else
{
button[i].press_mark = 1; // 如果没长按标记, 则认为是短按,进行计数
}
Serial.print("1: press_mark1: ");
Serial.print(i);
Serial.println(button[i].press_mark);
}
}
}
}
/***===================================================***/
/***-------------------------2.2.1--s--执行函数----------------------***/
void button_press(void) // 此函数是按键松开后有计数 认为按键s-- button_press_callback
{
for (int i = 0; i < 2; i++)
{
/* if (button[i].press_mark) // 如果有按键计数
{
button[i].press_mark--; // 计数减一
Serial.print("2.2.1: press_mark1: ");
Serial.print(i);
Serial.println(button[i].press_mark);
*/
if (button[i].press_mark) // 如果 press_mark 为 0,发送短按消息
{
button_msg.id = i;
button_msg.press = 1;
button_msg.update_flag = 1;
button_msg.longPress = 0;
button[i].press_mark = 0;
Serial.print("2.2.1: press_mark2: ");
Serial.print(i);
Serial.println(button[i].press_mark);
}
// }
}
}
/***===================================================***/
/***---------------------2.2--长按执行函数----------------------***/
void button_longPress(void) // cb: Callback 按键回调 1ms调用一次
{
if (button_long_press_count != 0) // 如果长按在倒计时
{
button_long_press_count--; //--自减
if (button_long_press_count == 0) // 如果计时时间结束
{
for (int i = 0; i < 2; i++)
{
if (button[i].value == 0) // 再次确认键值, 如果还是按下的 则证明按键为长按,记录长按消息
{
button_msg.id = i; // 按钮号
button_msg.press = 1; // 按下状态
button_msg.update_flag = 1; // 更新标记
button_msg.longPress = 1; // 长按状态
button[i].longPress_mark = 1; // 长按标记
o = i;
button[i].press_mark = 0; // 清除短按计数,避免发送短按消息
Serial.print("2.2: longPress_mark: ");
Serial.println(button[i].longPress_mark);
}
}
}
}
button_press(); // 执行短按函数
}
/***===================================================***/
/***--------------------2---定时器----------------------***/
void system_timer(void) // 系统时钟:1ms运行1次 timer
{
static unsigned long lastTime = 0; // 用于保存系统时钟的静态变量
if (lastTime != millis())
{
lastTime = millis();
button_scan(); // 按钮扫描
button_longPress(); // 按钮执行
}
}
/***===================================================***/
/***--------------------------移动菜单成员位置----------------------***/
void Menu_members_shift(void)
{
if (button_msg.id) // 判断被按下的是哪个按钮,1为 buttun_2:向下移
{
menu_select++; // 列表成员序号 自增
if (menu_select >= menu_length) // 如果列表成员序号大于菜单成员列表
{
menu_select = menu_length - 1; // 列表成员序号定在最后的成员上
}
else if (box_y_target < 40) // 如果方框的目标Y坐标小于40 (在屏幕内)
{
box_y_target += (menu_str_h); // 方框向下移动 1字体的高度
}
else // 若大于等于40(方框超出屏幕范围),
{
y_target -= (menu_str_h); // 则将菜单成员列表向上移动 1字体高度
}
}
else // 0为 buttun_1:向上移,
{
menu_select--;
if (menu_select < 0)
{
menu_select = 0;
}
else if (box_y_target > 10)
{
box_y_target -= (menu_str_h);
}
else
{
y_target += (menu_str_h);
}
}
box_w_target = u8g2.getUTF8Width(menu_list[menu_select].str) + 4; // 获取字符串的像素宽度作为方框的目标宽度
}
/***===================================================***/
/***---------------------------菜单执行函数----------------------***/
void menu_proc(void)
{
if (button_msg.press && button_msg.update_flag) // 如果按钮被按下且已更新键值
{
button_msg.update_flag = 0; // 将更新键值状态置为0 (未更新键值)
if (button_msg.longPress == 0) // 如果没有长按标记
{
Menu_members_shift(); // 更新菜单成员目标Y坐标为下一成员位置
} // else
if (button[o].longPress_mark == 1) // 如果有长按标记
{
// button[o].longPress_mark = 0; // 重置标记
do
{
static unsigned long last_time = 0; //
if (millis() - last_time >= 200) // 每200毫秒更新一次目标Y坐标
{
last_time = millis();
Menu_members_shift();
}
menu_show();
Serial.println("longPress");
} while (!get_io_val(o)); // 直到松开按钮
}
}
menu_show(); // 显示菜单到屏幕上
}
/***------------菜单移动(从当前坐标向目标坐标靠近)------***/
int menu_run(short *a, short *a_trg, byte step) // 两个指针变量分别指向代入坐标的地址,移动坐标的步长
{
if (*a < *a_trg) // 取出两个地址中的数据(坐标值)进行比较,如果当前坐标小于目标坐标
{
step = (*a_trg - *a) > step ? step : 1; // 如果移动距离(目标坐标-当前坐标)小于步长,步长改为 1,避免出现负数
*a += step; // 加大当前坐标值,向目标坐标靠近
}
else if (*a > *a_trg)
{
step = (*a_trg - *a) < step ? step : 1;
*a -= step;
}
else
{
return 0; // 如果两坐标相等,返回0
}
return 1; // 不相等,返回1
}
/***===================================================***/
/***-------------------------菜单显示--------------------------***/
void menu_show(void)
{
u8g2.clearBuffer(); // 清除内部缓冲区
// u8g2.setFont(u8g2_font_helvB14_tn); // 设置数字字体
for (int i = 0; i < menu_length; i++) // 遍历列表成员
{
u8g2.drawStr(x + 10, y + i * menu_str_h, menu_list[i].str); // 输出列表成员
}
u8g2.drawRFrame(x + 7, box_y + 3, box_w + 2, menu_str_h, 3); // 在选定位置绘制方框
menu_run(&box_y, &box_y_target, 3); // 移动方框的Y坐标
menu_run(&box_w, &box_w_target, 4); // 移动方框的W坐标
menu_run(&y, &y_target, 3); // 移动菜单的Y坐标
u8g2.sendBuffer(); // 将内存数据传输到显示器
}
/***===================================================***/
/***-------------------------菜单相关数据初始化--------------------------***/
void menu_init(void)
{
u8g2.begin(); // U8G2初始化
u8g2.setFont(u8g2_font_6x13_mr); // 设置字体
menu_length = sizeof(menu_list) / sizeof(MENU_LIST); // 计算菜单列表的成员数量
menu_str_h = u8g2.getAscent() - u8g2.getDescent() + 4; // 设置菜单成员之间的间距 = 字体高度(基准线以上-基准线以下)+ n
menu_str_w = u8g2.getUTF8Width(menu_list[0].str); // 设置字符串的初始宽度
box_y = box_y_target = y - menu_str_h; // 初始化方框的y坐标
box_w = box_w_target = menu_str_w + 4; // 初始化方框的w宽度
}
/***===================================================***/
void setup(void)
{
pinMode(buttun_1, INPUT_PULLUP); // 设置按钮1为上拉输入模式
pinMode(buttun_2, INPUT_PULLUP); // 设置按钮2为上拉输入模式
button_init(); // 按钮初始化
menu_init(); // 菜单初始化
Serial.begin(115200); // 初始化串口通信
}
void loop(void)
{
button_proc(); // 按键扫描
system_timer(); // 运行定时器
menu_proc(); // 菜单执行
}