#include <LiquidCrystal.h> // 引入液晶显示库
LiquidCrystal lcd(6, 5, 7, 8, 9, 10); // 初始化液晶显示对象,设置引脚
//6:rs--5:e--7:d4--8:d5--9:d6--10:d7
// 定义编码器引脚
const int encoderPinA = A0; // 编码器 A 引脚
const int encoderPinB = A1; // 编码器 B 引脚
const int buttonPin = A2; // 编码器按键引脚
// 状态机定义
enum MenuState {
SELECTING, // 选择状态
ADJUSTING // 调节状态
};
MenuState currentState = SELECTING; // 初始化当前状态为选择状态
volatile int encoderValue = 0; // 编码器值
int lastEncoderValue = 0; // 上一个编码器值
int selectedIndex = 0; // 当前选中的菜单索引
unsigned long lastButtonPress = 0; // 上次按下按钮的时间
const unsigned long debounceDelay = 200; // 防抖延迟时间
// 定义菜单和参数
const char* menus[] = {"ci shu", "shi jian", "ma da", "sou suo", "tui chu"}; // 菜单数组
const int menuCount = 5; // 菜单数量
int parameters[] = {0, 0, 0, 0, 0}; // 每个菜单对应的参数数组
// 函数声明
void display(); // 显示菜单和参数的函数
void displayMenus(); // 显示菜单的函数
void displayParameters(); // 显示参数的函数
void toggleState(); // 切换状态的函数
void updateEncoder(); // 更新编码器值的函数
void setup() {
lcd.begin(16, 2); // 初始化液晶显示,设置为16列2行
pinMode(buttonPin, INPUT_PULLUP); // 设置按钮引脚为输入并启用内部上拉电阻
pinMode(encoderPinA, INPUT); // 设置编码器A引脚为输入
pinMode(encoderPinB, INPUT); // 设置编码器B引脚为输入
display(); // 显示初始化菜单
}
void loop() {
if (digitalRead(buttonPin) == LOW) { // 检查按钮是否被按下
unsigned long currentTime = millis(); // 获取当前时间
if (currentTime - lastButtonPress > debounceDelay) { // 检查防抖时间
lastButtonPress = currentTime; // 记录按下时间
toggleState(); // 切换状态
if (currentState == ADJUSTING) { // 如果当前状态为调节
lcd.setCursor(12, selectedIndex - (selectedIndex > 1 ? 1 : 0)); // 确定调节参数值的光标的行数
lcd.print("<"); // 打印小于号,表示正在调节参数值
} else {
display(); // 如果不是调节状态,刷新整个屏幕
}
}
}
updateEncoder(); // 更新编码器值
if (encoderValue != lastEncoderValue) { // 如果编码器值发生变化
if (currentState == ADJUSTING) { // 如果当前状态为调节
int delta = encoderValue - lastEncoderValue; // 计算变化量
parameters[selectedIndex] = constrain(parameters[selectedIndex] + delta, 0, 10000); // 限制参数值在0到10000之间
lastEncoderValue = encoderValue; // 更新上一个编码器值
lcd.setCursor(13, selectedIndex - (selectedIndex > 1 ? 1 : 0)); // 确定调节参数值的光标的行数
char buffer[4]; // 字符缓冲区
sprintf(buffer, "%3d", parameters[selectedIndex]); // 格式化参数值
lcd.print(buffer); // 打印参数值
}
if (currentState == SELECTING) { // 如果当前状态为选择
selectedIndex = (selectedIndex + (encoderValue - lastEncoderValue) + menuCount) % menuCount; // 更新选中的菜单索引
lastEncoderValue = encoderValue; // 更新上一个编码器值
display(); // 刷新显示
}
}
}
// 显示菜单和参数
void display() {
lcd.clear(); // 清除液晶显示
int start = 0; // 确定显示的起始菜单索引
if (selectedIndex > 1) start = selectedIndex - 1; // 如果选中的索引大于1,起始索引减1
for (int i = start; i < menuCount && i < start + 2; i++) { // 遍历菜单项
lcd.setCursor(0, i - start); // 设置光标位置
if (i == selectedIndex) { // 如果是选中的菜单项
lcd.print(">"); // 在选中的菜单名前添加箭头
lcd.print(menus[i]); // 打印菜单名
}
else {
lcd.print(" "); // 在非选中菜单名前添加空格
lcd.print(menus[i]); // 打印菜单名
}
// 移动光标到行末,准备打印参数值
lcd.setCursor(12, i - start); // 设置光标位置到参数值位置
char buffer[4]; // 字符缓冲区
sprintf(buffer, "%3d", parameters[i]); // 格式化参数值并存储在缓冲区
lcd.print(buffer); // 打印参数值
}
}
// 更新编码器的值
void updateEncoder() {
static int CLKPrevious = digitalRead(encoderPinA); // 存储上一个编码器A引脚的状态
int CLKNow = digitalRead(encoderPinA); // 读取当前编码器A引脚的状态
if (CLKNow != CLKPrevious && CLKNow == 1) { // 仅在状态变化且当前状态为高电平时处理
int pinB = digitalRead(encoderPinB); // 读取编码器B引脚的状态
if (pinB != CLKNow) {
encoderValue++; // 顺时针旋转,增加编码器值
} else {
encoderValue--; // 逆时针旋转,减少编码器值
}
}
CLKPrevious = CLKNow; // 更新上一个状态为当前状态
}
void displayMenus() {
lcd.setCursor(0, 0); // 移动光标到第一行
for (int i = 0; i < menuCount; i++) { // 遍历所有菜单项
lcd.print(menus[i]); // 打印菜单名
if (i < menuCount - 1) lcd.print(" "); // 菜单之间添加空格
}
}
void displayParameters() {
lcd.setCursor(0, 1); // 移动光标到第二行
for (int i = 0; i < menuCount; i++) { // 遍历所有菜单项
if (i == selectedIndex) { // 如果是选中的菜单项
char buffer[8]; // 足够容纳 6 位数字和结束符
sprintf(buffer, "%6d", parameters[i]); // 格式化为6位,右对齐
lcd.print(buffer); // 显示格式化后的参数
} else {
lcd.print(" "); // 空白以对齐
}
if (i < menuCount - 1) lcd.print(" "); // 参数之间空格
}
lcd.print(" "); // 清除行末的字符
}
void toggleState() {
currentState = (currentState == SELECTING) ? ADJUSTING : SELECTING; // 切换状态
}