// simple project using Arduino UNO and 128x64 SSD1306 IIC OLED Display, created by upir, 2023
// youtube channel: https://www.youtube.com/upir_upir
// YOUTUBE VIDEO: https://youtu.be/Eyvzw_ujcS0
// WOKWI sketch: https://wokwi.com/projects/372481402319579137
// More videos with Arduino UNO and OLED screens: https://www.youtube.com/playlist?list=PLjQRaMdk7pBZ1UV3IL5ol8Qc7R9k-kwXA
// Links from the video:
// Lopaka editor: https://lopaka.app/
// 128x64 SSD1306 OLED Display 1.54": https://s.click.aliexpress.com/e/_DCYdWXb
// 128x64 SSD1306 OLED Display 0.96": https://s.click.aliexpress.com/e/_DCKdvnh
// 128x64 SSD1306 OLED Display 2.42": https://s.click.aliexpress.com/e/_DFdMoTh
// Arduino UNO: https://s.click.aliexpress.com/e/_AXDw1h
/*------加载库文件------*/
#include <Arduino.h> //加载Arduino库
#include <U8g2lib.h> //加载U8G2库
#include <Wire.h> //加载I2C库
#include "icon.h" //加载图标文件
/*------预定义引脚------*/
#define KeyPrev_Pin 25 //定义上一个按钮引脚
#define KeyNext_Pin 26 //定义下一个按钮引脚
#define SDA_Pin 21 //定义I2C通信引脚
#define SCL_Pin 22 //定义I2C通信引脚
/*------预定义常量------*/
#define SCREEN_WIDTH 128 //定义屏幕的宽度
#define SCREEN_HEIGHT 64 //定义屏幕的高度
#define ICON_WIDTH 48 //定义图标宽度
#define ICON_HEIGHT 48 //定义图标高度
#define DebounceTime 70 //消抖时间(毫秒)
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /*reset=*/U8X8_PIN_NONE, /*clock=*/SCL_Pin, /*data=*/SDA_Pin); // 实例化OLED
/*------定义主菜单项结构体------*/
struct MENU_ITEM {
const unsigned char *icon; //图标
const char *title; //标题
};
/*------功能:创建主菜单项数据------*/
const MENU_ITEM Menu_Items[] = {
{ icon_radio, "收音机" },
{ icon_weather, "天气" },
{ icon_clock, "时钟" },
{ icon_game, "游戏" },
{ icon_about, "关于" },
};
/*------功能:计算菜单条目数量------*/
const uint8_t Menu_LEN = sizeof(Menu_Items) / sizeof(MENU_ITEM);
/*------功能:定义菜单坐标相关变量------*/
short icon_x, icon_x_trg; //主菜单水平坐标icon_x当前数值,icon_x_trg目标数值
short frame, frame_trg; //主菜单方框移动frame当前数值,frame_trg目标数值
/*------功能:定义菜单操作相关变量------*/
uint8_t UI_Select = 0, Last_UI_Select = 0; //主菜单选中索引
/*------初始化OLED显示屏------*/
void OLED_Begin() {
u8g2.setBusClock(800000); //设置时钟频率
u8g2.begin(); //启动U8G2驱动程序
u8g2.setContrast(50); //设置对比度
u8g2.enableUTF8Print(); //允许支持中文字体
//u8g2.setFont(u8g2_font_wqy12_t_gb2312a); //设置中文字体
u8g2.clearBuffer(); //清空显示屏缓存
}
/*------初始化按键------*/
void KEY_Begin() {
pinMode(KeyNext_Pin, INPUT_PULLUP); //初始化引脚
pinMode(KeyPrev_Pin, INPUT_PULLUP);
}
void setup() {
KEY_Begin(); //初始化按键
OLED_Begin(); //初始化屏幕
icon_x_trg = (SCREEN_WIDTH - ICON_WIDTH) / 2;
}
void loop() {
UI_Control();
}
/*------功能:控制UI主界面图标显示------*/
void UI_Control() {
if (Last_UI_Select < UI_Select) {
icon_x = SCREEN_WIDTH - ICON_WIDTH;
} else if (Last_UI_Select > UI_Select) {
icon_x = 0;
}
icon_x_trg = (SCREEN_WIDTH - ICON_WIDTH) / 2;
Last_UI_Select = UI_Select;
MainMenu_Display();
//------绘制菜单界面------//
MainMenu_Display();
//------处理按钮事件------//
Button_Click(KeyNext_Pin, Menu_Next_Click);
Button_Click(KeyPrev_Pin, Menu_Prev_Click);
}
/*------功能:绘制UI主界面菜单内容------*/
void MainMenu_Display() {
u8g2.clearBuffer(); //清空显示屏缓存
u8g2.setFont(u8g2_font_open_iconic_all_2x_t);
if (UI_Select == 0) {
//u8g2.drawXBMP(icon_x - 46, (SCREEN_HEIGHT - ICON_HEIGHT) / 2 - 6, ICON_WIDTH, ICON_HEIGHT, Zero_icon);
u8g2.drawGlyph(110, 40, 118); //绘制本次显示图标
//u8g2.drawXBMP(icon_x + 46, (SCREEN_HEIGHT - ICON_HEIGHT) / 2 - 6, ICON_WIDTH, ICON_HEIGHT, Menu_Items[abs((UI_Select + 1) % Menu_LEN)].icon); //绘制本次显示图标
} else if (UI_Select == Menu_LEN - 1) {
//u8g2.drawXBMP(icon_x - 46, (SCREEN_HEIGHT - ICON_HEIGHT) / 2 - 6, ICON_WIDTH, ICON_HEIGHT, Menu_Items[abs((UI_Select - 1) % Menu_LEN)].icon); //绘制本次显示图标
//u8g2.drawXBMP(icon_x + 46, (SCREEN_HEIGHT - ICON_HEIGHT) / 2 - 6, ICON_WIDTH, ICON_HEIGHT, Zero_icon);
u8g2.drawGlyph(3, 40, 117); //绘制本次显示图标
} else {
//u8g2.drawXBMP(icon_x - 46, (SCREEN_HEIGHT - ICON_HEIGHT) / 2 - 6, ICON_WIDTH, ICON_HEIGHT, Menu_Items[abs((UI_Select - 1) % Menu_LEN)].icon); //绘制本次显示图标
//u8g2.drawXBMP(icon_x + 46, (SCREEN_HEIGHT - ICON_HEIGHT) / 2 - 6, ICON_WIDTH, ICON_HEIGHT, Menu_Items[abs((UI_Select + 1) % Menu_LEN)].icon); //绘制本次显示图标
u8g2.drawGlyph(3, 40, 117);
u8g2.drawGlyph(110, 40, 118);
}
u8g2.drawXBMP(icon_x, (SCREEN_HEIGHT - ICON_HEIGHT) / 2 - 6, ICON_WIDTH, ICON_HEIGHT, Menu_Items[abs(UI_Select % Menu_LEN)].icon); //绘制本次显示图标
u8g2.setFont(u8g2_font_wqy12_t_gb2312a); //设置中文字体
u8g2.drawUTF8((SCREEN_WIDTH - u8g2.getUTF8Width(Menu_Items[UI_Select].title)) / 2, 63, Menu_Items[UI_Select].title);
UI_MoveSet(&icon_x, &icon_x_trg, ICON_WIDTH / 9, ICON_WIDTH / 9 + 1); //调整图标
//Draw_Box(frame); //绘制方框
UI_MoveSet(&frame, &frame_trg, 3, 4); //调整方框
u8g2.sendBuffer(); //发送到缓存显示
}
/*------功能:绘制方框动画------*/
/*void Draw_Box(uint8_t site) {
//------绘制周围方框------//
u8g2.setDrawColor(1);
u8g2.drawBox(36, 3 - site, 10, 2); //左上横线
u8g2.drawBox(36 - site, 3, 2, 10); //左上竖线
u8g2.drawBox(36 - site, 52, 2, 10); //左下竖线
u8g2.drawBox(36, 60 + site, 10, 2); //左下横线
u8g2.drawBox(80, 3 - site, 10, 2); //右上横线
u8g2.drawBox(88 + site, 3, 2, 10); //右上竖线
u8g2.drawBox(88 + site, 52, 2, 10); //右下竖线
u8g2.drawBox(80, 60 + site, 10, 2); //右下横线
}
*/
/*------功能:定义按钮单击函数------*/
void Button_Click(uint8_t pin, void (*callback)()) {
if (digitalRead(pin) == LOW) {
delay(DebounceTime);
if (digitalRead(pin) == LOW) {
callback();
while (!digitalRead(pin))
; // 等待释放
}
}
}
/*------功能:设置下一个点击函数------*/
int Next_Click(int select, int lenth) {
if (select < lenth - 1)
select++;
return select;
}
/*------功能:设置上一个点击函数------*/
int Prev_Click(int select, int lenth) {
if (select > lenth)
select--;
return select;
}
/*------功能:定义按钮执行函数------*/
void Menu_Next_Click() {
UI_Select = Next_Click(UI_Select, Menu_LEN);
frame = 10;
}
/*------功能:定义按钮执行函数------*/
void Menu_Prev_Click() {
UI_Select = Prev_Click(UI_Select, 0);
frame = 10;
}
/*------功能:设置变量数值改变到目标数值------*/
void UI_MoveSet(short *p, short *p_trg, uint8_t step, uint8_t min) {
step = abs(*p_trg - *p) > min ? step : 1; //大于最小值则步幅为step,如果小于最小值步幅为1,快速逼近然后减速
if (*p < *p_trg) { //如果数值小于目标值则逐渐增加
*p += step;
} else if (*p > *p_trg) { //如果数值大于目标值则逐渐减小
*p -= step;
}
}