#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_trg; //x当前x坐标值,x_trg 目标x坐标值
short y = 15, y_trg = 15; //y坐标,目标y坐标
int state; //状态
byte CD_ChangDu;
u8g2_uint_t str_h; //菜单字符串高度
u8g2_uint_t str_w; //菜单字符串宽度
short frame_y, frame_y_trg; //方框的y坐标
short frame_len, frame_len_trg; //方框的宽度
char ui_select = 0;
typedef struct //定义别名为CAIDAN_LIEBIAO的结构
{
char *str; //指针变量用于指向菜单列表成员的地址
} CD_LIEBIAO;
CD_LIEBIAO LieBiao[] = { //定义结构体数组来存放菜单列表
{"a"}, {"ab"}, {"abc"}, {"abcd"}, {"a"}, {"abcde"}, {"abcdef"}, {"ab"}, {"abc"}, {"abcd"}, {"a"}, {"abcde"}, {"abcdef"}
};
typedef struct
{
byte val; //当前读取的按钮键值
byte last_val; //上一次读取的按钮键值
byte change; //改变标记
byte press_tick; //按下时间
byte long_press_flag; //长按标志 用来屏蔽松开的时候检测到
} KEY_T;
KEY_T key[2] = {0}; //构建按钮事件结构体数组key并初始化
int key_long_press_tick = 0;//按键长按计数
typedef struct
{
byte id; //按键号
byte press; //是否按下
byte update_flag; //是否最新
byte long_press; //是否长按
} KEY_MSG; //按钮消息
KEY_MSG key_msg = {0}; //构建变量并初始化
byte j = 0;
/***-------------2.1.1--获取按钮键值------------------***/
byte get_io_val (byte ch) //获取对应引脚信号并返回
{
if (ch == 0) //参数为0返回 按钮1 的信号,
{
return digitalRead(buttun_1);
j = 0;
}
else
{
return digitalRead(buttun_2); //否则返回 按钮2 的信号
j = 1;
}
}
/***===================================================***/
/***------------按钮初始化函数------***/
void key_init(void) //按钮初始化
{
for (int i = 0; i < 2; i++)
{
key[i].val = key[i].last_val = get_io_val(i);//将按键初始化成上电时候的状态
}
}
/***===================================================***/
/***-------------------------2.1--扫描按钮并标记状态----------------------***/
void key_scan (void) //按钮状态
{
for (int i = 0; i < 2; i++) //遍历按钮成员
{
key[i].val = get_io_val(i); //获取当前按钮键值
if (key[i].val != key[i].last_val) //如果键值发生改变
{
key[i].last_val = key[i].val; //更新状态
key[i].change = 1; //改变记录标记
}
}
}
/***===================================================***/
byte o;
/***--------------------------1-- 按钮执行程序----------------------***/
void key_proc(void)
{
for (int i = 0; i < 2; i++)
{
if (key[i].change == 1) //如果按键状态改变了 表示有按键按下/松开了
{
key[i].change = 0; //清除状态
if (key[i].val == 0) //如果是按下
{
key_long_press_tick = 10; //开始长按倒计时
}
else //如果按钮松开
{
if (key[i].long_press_flag) //如果有长按标志
{
key[i].long_press_flag = 0; //清除长按标志
key[i].press_tick = 0; //清除短按计数,避免发送短按消息
Serial.print("1: long_press_flag: ");
Serial.print(i);
Serial.println(key[i].long_press_flag);
}
else
{
key[i].press_tick = 1;//如果没长按标记, 则认为是短按 计数
Serial.print("1: press_tick: ");
Serial.print(i);
Serial.println(key[i].press_tick);
}
}
}
}
}
/***===================================================***/
/***-------------------------2.2.1--短按执行函数----------------------***/
void key_down_cb(void) //此函数是按键松开后有计数 认为按键短按
{
for (int i = 0; i < 2; i++)
{
if (key[i].press_tick) //如果有按键计数
{
key[i].press_tick--; //计数减一
Serial.print("2.2.1: press_tick: ");
Serial.print(i);
Serial.println(key[i].press_tick);
if (!key[i].press_tick) //如果为0
{
key_msg.id = i; //发送短按msg
key_msg.press = 1;
key_msg.update_flag = 1;
key_msg.long_press = 0;
}
}
}
}
/***===================================================***/
/***---------------------2.2--长按执行函数----------------------***/
void key_press_cb(void) //cb: Callback 按键回调 1ms调用一次
{
if (key_long_press_tick != 0) //如果长按在倒计时
{
key_long_press_tick--; //--自减
//Serial.print("2.2: key_long_press_tick: ");
//Serial.println(key_long_press_tick);
if (key_long_press_tick == 0) //如果计时时间结束
{
for (int i = 0; i < 2; i++)
{
if (key[i].val == 0)//再次确认键值, 如果还是按下的 则证明按键为长按
{
key_msg.id = i;
key_msg.press = 1;
key_msg.update_flag = 1;
key_msg.long_press = 1;
key[i].long_press_flag = 1; //发送长按msg
o = i;
Serial.print("2.2: long_press_flag: ");
Serial.println(key[i].long_press_flag);
}
}
}
}
key_down_cb();
}
/***===================================================***/
/***--------------------------2-系统时钟----------------------***/
void system_tick()//系统1ms时钟
{
static unsigned long tick = 0;
if (tick != millis())
{
tick = millis();
key_scan();
key_press_cb();
}
}
/***===================================================***/
/***--------------------------更新菜单成员的目标坐标----------------------***/
void update_y_trg (void)
{
if (key_msg.id) //判断被按下的是哪个按钮,buttun_1向上移,buttun_2向下移
{
ui_select ++;
if (ui_select >= CD_ChangDu ) //如果方框的位置大于菜单成员列表
{
ui_select = CD_ChangDu - 1; //方框定在最后的成员上
}
else if (frame_y_trg < 40) //如果方框的目标Y坐标小于40 (在屏幕内)
{
frame_y_trg += (str_h ); //向下移动方框
}
else
{
y_trg -= (str_h ); //否则向上移动菜单成员列表
}
Serial.print("y_trg: ");
Serial.println(y_trg);
}
else
{
ui_select --;
if (ui_select < 0)
{
ui_select = 0;
}
else if (frame_y_trg > 10)
{
frame_y_trg -= (str_h );
}
else
{
y_trg += (str_h );
}
Serial.print("y_trg: ");
Serial.println(y_trg);
}
frame_len_trg = u8g2.getUTF8Width(LieBiao[ui_select].str) + 4; // 获取字符串的像素宽度
}
/***===================================================***/
/***---------------------------菜单执行函数----------------------***/
void ui_proc (void)
{
if (key_msg.press && key_msg.update_flag) //如果按钮被按下且已更新键值
{
key_msg.update_flag = 0; //将更新键值状态置为0 (未更新键值)
if (key_msg.long_press == 0) //如果没有长按标记
{
update_y_trg (); //更新菜单成员目标Y坐标为下一成员位置
} //else
if (key[o].long_press_flag == 1) //如果有长按标记
{
key[o].long_press_flag = 0; //重置标记
do
{
static unsigned long last_time = 0; //
if (millis() - last_time >= 200) //每200毫秒更新一次目标Y坐标
{
last_time = millis();
update_y_trg ();
}
cd_show();
} while (!get_io_val(o) ); //直到松开按钮
}
}
cd_show();
}
/***------------菜单移动(从当前坐标向目标坐标靠近)------***/
int cd_run(short *a, short *a_trg, byte temp) //两个指针变量分别指向代入坐标的地址,加减坐标的步长
{
if (*a < *a_trg) //取出两个地址中的数据(坐标值)进行比较,如果当前坐标小于目标坐标
{
temp = (*a_trg - *a) > temp ? temp : 1; //如果目标坐标-当前坐标小于步长,步长赋值为1,避免出现负数
*a += temp; //加大当前坐标值,向目标坐标靠近
}
else if ( *a > *a_trg)
{
temp = (*a_trg - *a) < temp ? temp : 1;
*a -= temp;
}
else
{
return 0; //如果两坐标相等,返回0
}
return 1; //不相等,返回1
}
/***===================================================***/
/***-------------------------菜单显示--------------------------***/
void cd_show(void)
{
u8g2.clearBuffer(); // 清除内部缓冲区
// u8g2.setFont(u8g2_font_helvB14_tn); // 设置数字字体
for (int i = 0; i < CD_ChangDu ; i++) //遍历列表成员
{
u8g2.drawStr(x + 10, y + i * str_h, LieBiao[i].str); // 输出列表成员
}
u8g2.drawRFrame(x + 7, frame_y + 3, frame_len + 2, str_h, 3);
cd_run(&frame_y, &frame_y_trg, 4);
cd_run(&frame_len, &frame_len_trg, 5);
cd_run(&y, &y_trg, 4);
u8g2.sendBuffer(); // 将内存数据传输到显示器
}
/***===================================================***/
/***-------------------------菜单相关数据初始化--------------------------***/
void cd_init (void)
{
u8g2.begin(); //U8G2初始化
u8g2.setFont(u8g2_font_6x13_mr); // 设置字体
CD_ChangDu = sizeof(LieBiao) / sizeof(CD_LIEBIAO); //计算菜单列表的成员数量
str_h = u8g2.getAscent() - u8g2.getDescent() + 4; // 设置菜单成员x间距 = 字体高度(基准线以上-基准线以下)+ n
str_w = u8g2.getUTF8Width(LieBiao[0].str); // 设置字符串的初始宽度
frame_y = frame_y_trg = y - str_h; //初始化方框的y坐标
frame_len = frame_len_trg = str_w + 4; //初始化方框的宽度
}
/***===================================================***/
void setup(void)
{
pinMode(buttun_1, INPUT_PULLUP);
pinMode(buttun_2, INPUT_PULLUP);
key_init(); //按钮初始化
cd_init(); //菜单初始化
Serial.begin(115200);
//Serial.println(str_h);
// Serial.println(str_w);
}
void loop(void)
{
key_proc(); //按键扫描
system_tick();//系统tick
ui_proc();
}