#include <LiquidCrystal_I2C.h>
#include <SoftwareSerial.h>
LiquidCrystal_I2C mylcd(0x27, 16, 2);
char contentBuffer[17];
// 设备状态枚举变量
enum AudioCmdCode
{
Unknown = 0, // 未知命令
VolumeUp, // 增大音量
VolumeDown, // 减小音量
VolumeMax, // 最大音量
VolumeMin, // 最小音量
VolumeMid, // 中等音量
StartCommand, // 开始|开始计时|开始锻炼
StopCommand // 结束|结束计时|结束蜂鸣|结束锻炼
};
// 设备状态枚举变量
enum DeviceStatus {
StandBy = 0, // 待命模式
Setting, // 配置模式
Learning, // 学习模式
PreExercise, // 锻炼准备
Exercise // 锻炼模式
};
const int potPin = A0; // 电位器链接引进
const int startButtonPin = 9; // 开始按钮连接引脚,外部下拉电阻
const int stopButtonPin = 10; // 停止按钮链接引脚,外部下拉电阻
const int buzzerPin = 12; // 蜂鸣器连接引脚
const int ledPin = 11; // LED 指示灯连接引脚
const int minStudySeconds = 10; // 最小设定学习时长(秒)
const int maxStudySeconds = 3600; // 最大设定学习时长(秒)
const int deltSeconds = 5; // 时长误差,当变化值超过该值时认为时间变化(秒)
volatile int studySeconds = 10; // 当前设定学习时间(秒)
unsigned long startTime = 0; // 开始时间
volatile int ledLight = 0; // 呼吸灯控制变量:当前PWM值
volatile int lightStep = 15; // 呼吸灯控制变量:PWM变化步长
volatile int musicVolume = 60; // 音乐音量
volatile int deviceStatus = DeviceStatus::StandBy; // 设备状态
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
pinMode(startButtonPin, INPUT);
pinMode(stopButtonPin, INPUT);
pinMode(ledPin, OUTPUT);
pinMode(buzzerPin, OUTPUT);
AudioModuleInit(); // 语音控制模块初始化
LCDModuleInit(); // LCD显示屏 模块初始化
//MP3ModuleInit(); // MP3 播放模块初始化
StandByInit(); // 初始化为待命状态
}
void loop() {
int seconds = GetstudySeconds(); // 获取电位器设定学习时长
AudioCmdCode audioCmdCode = ProcessReceiveData(); // 处理语音模块接收到指令
bool startFlag = GetButtonFlag(startButtonPin) || AudioCmdCode::StartCommand == audioCmdCode; // 开始按钮或语音控制开始
bool stopFlag = GetButtonFlag(stopButtonPin) || (AudioCmdCode::StopCommand == audioCmdCode); // 结束按钮或语音控制结束;
switch (deviceStatus) {
case DeviceStatus::StandBy: // 待机模式
{
if (startFlag) { // 开始Flag有效时进入学习模式
startTime = millis();
deviceStatus = DeviceStatus::Learning;
} else if (abs(seconds - studySeconds) > deltSeconds) { // 电位器变化超过允许误差时进入配置模式
deviceStatus = DeviceStatus::Setting;
}
BreathingLight(); // 呼吸灯控制
LCDShowStandbyInfo(studySeconds, musicVolume); // 显示待机信息
break;
}
case DeviceStatus::Setting: // 配置模式
{
if (abs(seconds - studySeconds) > deltSeconds) { // 电位器发生变化,设置等待时长
startTime = millis(); // 记录设置变化时间
studySeconds = seconds;
} else if (millis() - startTime >= 3000) { // 3秒内电位器未变化,回到待命模式
deviceStatus = DeviceStatus::StandBy;
}
BreathingLight(); // 呼吸灯控制
LCDShowSettingInfo(studySeconds, musicVolume); // 显示设置信息
break;
}
case DeviceStatus::Learning: // 学习模式
{
long elapsedTime = (long)(millis() - startTime);
long remainSeconds = max(studySeconds - elapsedTime / 1000, 0); // 剩余时间(s)
if (remainSeconds > 0) { // 学习过程中
if (stopFlag) { // 如果学习过程中停止Flag有效,回到待命状态
StandByInit();
}
BreathingLight(); // 呼吸灯
} else { // 学习结束
tone(buzzerPin, 200); // 蜂鸣器响
deviceStatus = DeviceStatus::PreExercise; // 进入锻炼准备阶段
}
BreathingLight(); // 呼吸灯控制
LCDShowLearningInfo(studySeconds, remainSeconds, musicVolume); // 显示学习过程信息
break;
}
case DeviceStatus::PreExercise: // 学习结束,锻炼准备
{
if (startFlag) { // 开始Flag有效时
noTone(buzzerPin); // 停止蜂鸣
// while (!IsPlayingMusic()) {
// PreMusic(); // 从头开始播放音乐
// delay(100);
// }
startTime = millis();
deviceStatus = DeviceStatus::Exercise;
} else if (stopFlag) { // 结束锻炼
noTone(buzzerPin); // 停止蜂鸣
StandByInit();
}
digitalWrite(ledPin, !digitalRead(ledPin)); // LED 灯闪烁
LCDShowPreExerciseInfo(musicVolume); // 锻炼准备显示音乐音量
break;
}
case DeviceStatus::Exercise: // 锻炼模式
{
if (/*IsPlayingMusic()*/millis() - startTime <= 15000) { // 正在播放音乐
if (stopFlag) { // 提前结束,退出锻炼模式
//StopMusic();
StandByInit();
} else {
ProcessAudioCtrlVolume(audioCmdCode); // 处理语音控制指令,进行音量调整
}
} else { // 音乐播放结束,回到待命状态
StandByInit();
}
digitalWrite(ledPin, !digitalRead(ledPin)); // LED 灯闪烁
LCDShowExerciseInfo(musicVolume); // 锻炼过程中显示显示音乐音量
break;
}
default:
break;
}
delay(100);
}
// 获取当前电位器的值,并转化为设定时长
int GetstudySeconds() {
int potValue = analogRead(potPin); // 读取电位器的值(0~1023)
return map(potValue, 0, 1023, maxStudySeconds, minStudySeconds); // 将电位器的值转换为设定时长
}
// 设备待命初始化
void StandByInit() {
deviceStatus = DeviceStatus::StandBy;
studySeconds = GetstudySeconds(); // 获取当前电位器的值,其他状态更改了电位器的值,回到待命状态时立即更新等待时间
}
// 判断按键是否按下,并进行按键消抖处理
bool GetButtonFlag(int buttonPin) {
if (1 == digitalRead(buttonPin)) {
delay(20); // 按键消抖
while (digitalRead(buttonPin))
;
return true;
}
return false;
}
// 呼吸灯控制
void BreathingLight() {
ledLight = constrain(ledLight, 0, 255);
analogWrite(ledPin, ledLight);
ledLight += lightStep;
if (ledLight <= 0 || ledLight >= 255) {
lightStep *= -1;
}
}
// 处理语音控制指令设置MP3音量变化
void ProcessAudioCtrlVolume(AudioCmdCode cmdCode) {
switch (cmdCode) {
case AudioCmdCode::VolumeUp: musicVolume = constrain(musicVolume + 10, 10, 100); break;
case AudioCmdCode::VolumeDown: musicVolume = constrain(musicVolume - 10, 10, 100); break;
case AudioCmdCode::VolumeMax: musicVolume = 100; break;
case AudioCmdCode::VolumeMin: musicVolume = 10; break;
case AudioCmdCode::VolumeMid: musicVolume = 50; break;
default: break;
}
// SetMusicVolume(musicVolume);
}
// 模块初始化
void LCDModuleInit()
{
mylcd.init();
mylcd.backlight();
mylcd.clear();
}
// 待机模式显示信息
void LCDShowStandbyInfo(int totalSeconds, int volume)
{
// 第一行显示内容:16个字符
mylcd.setCursor(0, 0);
sprintf(contentBuffer, "--$$$-- Vol:%3d%%", volume);
mylcd.print(contentBuffer);
// 第二行显示内容:
mylcd.setCursor(0, 1);
sprintf(contentBuffer, "Total time,%02d:%02d", totalSeconds / 60, totalSeconds % 60);
mylcd.print(contentBuffer);
}
// 设置模式显示信息
void LCDShowSettingInfo(int totalSeconds, int volume)
{
// 第一行显示内容:16个字符
mylcd.setCursor(0, 0);
sprintf(contentBuffer, "--###-- Vol:%3d%%", volume);
mylcd.print(contentBuffer);
// 第二行显示内容:
mylcd.setCursor(0, 1);
sprintf(contentBuffer, "Total time,%02d:%02d", totalSeconds / 60, totalSeconds % 60);
mylcd.print(contentBuffer);
}
// 学习模式下显示信息
void LCDShowLearningInfo(int totalSeconds, int remainSeconds, int volume)
{
// 第一行显示内容:16个字符
mylcd.setCursor(0, 0);
sprintf(contentBuffer, "-->>>-- Vol:%3d%%", volume);
mylcd.print(contentBuffer);
// 第二行显示内容:以分钟为单位显示总时间、剩余时间,且使用闪烁点表示倒计时进行中
mylcd.setCursor(0, 1);
sprintf(contentBuffer, " %02d:%02d/%02d:%02d", remainSeconds / 60, remainSeconds % 60, totalSeconds / 60, totalSeconds % 60);
mylcd.print(contentBuffer);
}
// 锻炼准备显示信息
void LCDShowPreExerciseInfo(int volume)
{
// 第一行显示内容:16个字符
mylcd.setCursor(0, 0);
sprintf(contentBuffer, "--&&&-- Vol:%3d%%", volume);
mylcd.print(contentBuffer);
// 第二行显示内容:
mylcd.setCursor(0, 1);
sprintf(contentBuffer, " Waiting music");
mylcd.print(contentBuffer);
}
// 锻炼模式显示信息
void LCDShowExerciseInfo(int volume)
{
// 第一行显示内容:16个字符
mylcd.setCursor(0, 0);
sprintf(contentBuffer, "--&&&-- Vol:%3d%%", volume);
mylcd.print(contentBuffer);
// 第二行显示内容:
mylcd.setCursor(0, 1);
sprintf(contentBuffer, " Playing music");
mylcd.print(contentBuffer);
}
const int serialRxPin = 4; // 模块软串口接收引脚
const int serialTxPin = 3; // 模块软件从发送引脚
SoftwareSerial audioSerial(serialRxPin, serialTxPin);
const byte volumeUp[] = { 0xFB, 0x01, 0xFE }; // 增大音量
const byte volumeDown[] = { 0xFB, 0x02, 0xFE }; // 减小音量
const byte volumeMax[] = { 0xFB, 0x03, 0xFE }; // 最大音量
const byte volumeMin[] = { 0xFB, 0x04, 0xFE }; // 最小音量
const byte volumeMid[] = { 0xFB, 0x05, 0xFE }; // 中等音量
const byte startCommand[] = { 0xFB, 0xA0, 0xFE }; // 开始|开始计时|开始锻炼
const byte stopCommand[] = { 0xFB, 0xA1, 0xFE }; // 结束|结束计时|结束蜂鸣|结束锻炼
byte audioBuffer[] = { 0x00, 0x00, 0x00 }; // 接收缓存去大小
// 模块初始化
void AudioModuleInit() {
audioSerial.begin(9600); // 设置串口波特率
}
bool IsBufferEqual(const byte first[], byte second[], int size = 3) {
for (int i = 0; i < size; i++) {
if (first[i] != second[i])
return false;
}
return true;
}
// 接收语音控制指令
AudioCmdCode ProcessReceiveData() {
audioSerial.listen();
int index = 0;
for (int i = 0; i < 3; i++) {
audioBuffer[i] = 0x00;
}
while (audioSerial.available() > 0) {
char inByte = audioSerial.read();
if (index < 3) {
audioBuffer[index++] = inByte;
}
}
if (index == 3) {
Serial.print("Audio Receive: ");
for (int i = 0; i < 3; i++) {
Serial.print("0x");
Serial.print(audioBuffer[i], HEX);
Serial.print(" ");
}
Serial.println("");
if (IsBufferEqual(volumeUp, audioBuffer)) {
Serial.println("volumeUp");
return AudioCmdCode::VolumeUp;
} else if (IsBufferEqual(volumeDown, audioBuffer)) {
Serial.println("volumeDown");
return AudioCmdCode::VolumeDown;
} else if (IsBufferEqual(volumeMax, audioBuffer)) {
Serial.println("volumeMax");
return AudioCmdCode::VolumeMax;
} else if (IsBufferEqual(volumeMin, audioBuffer)) {
Serial.println("volumeMin");
return AudioCmdCode::VolumeMin;
} else if (IsBufferEqual(volumeMid, audioBuffer)) {
Serial.println("volumeMid");
return AudioCmdCode::VolumeMid;
} else if (IsBufferEqual(startCommand, audioBuffer)) {
Serial.println("startCommand");
return AudioCmdCode::StartCommand;
} else if (IsBufferEqual(stopCommand, audioBuffer)) {
Serial.println("stopCommand");
return AudioCmdCode::StopCommand;
}
}
return AudioCmdCode::Unknown;
}