int freq[7] = {500, 500, 600, 600, 660, 660, 600};
int upperFreq = 1000;
int TIM2_interval[2] = {500000, 1000000};
/*下面的程序里面没有什么需要修改的参数了*/
#include <Arduino.h>
#include <U8g2lib.h>
#include <arduinoFFT.h>
// OLED 配置
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE);
// FFT 配置
#define SAMPLES 128 // 采样点数
#define SAMPLING_FREQUENCY 10000 // 采样频率 (Hz)
#define FFT_SIZE SAMPLES / 2 // FFT 结果大小
#define SCALE_FACTOR 1000 // 缩放因子
double vReal[SAMPLES];
double vImag[SAMPLES];
volatile int sampleIndex = 0;
volatile bool samplingDone = false;
const int micPin = PA1; // 麦克风引脚
const int buttonPin = PA0;
const int studyPin = PA2;
const int testPin = PA3;
const int speedPin = PA4;
int TIM2_set = 0;
int workingMode = 0; // 启动的时候默认设置为学习模式
HardwareSerial mySerial(PA10, PA9); // 使用 PA9 和 PA10 定义硬件串口
HardwareTimer *MyTim1 = new HardwareTimer(TIM1);
HardwareTimer *MyTim2 = new HardwareTimer(TIM2);
ArduinoFFT<double> FFT = ArduinoFFT<double>(vReal, vImag, SAMPLES, SAMPLING_FREQUENCY);
/*定义乐谱显示环节的变量*/
const int numDigits = 7; // 数字个数
const int screenWidth = 128; // OLED屏幕宽度
const int digitWidth = 18; // 每个数字的预估宽度
int spacing; // 计算得到的间距
// 存储每次绘制的类型(true表示方块,false表示竖线)
bool displayType[7];
int count = 0;
bool detected = false;
int correct_count = 0;
// 绘制第一行乐谱
void displayInitialNumbers()
{
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_logisoso16_tf);
char numStr[] = "1 1 5 5 6 6 5";
int cursorX = 0;
for (int i = 0; i < numDigits; i++)
{
u8g2.setCursor(cursorX, 18);
u8g2.print(numStr[2 * i]); // 2*i因为数字间有空格
cursorX += digitWidth + spacing;
}
u8g2.drawStr(5, 60, workingMode == 1 ? "TRIAL" : "LEARN");
if (workingMode)
{
u8g2.drawStr(60, 60, TIM2_set == 0 ? "S1" : "S2");
}
u8g2.sendBuffer();
}
// 选择性绘制方块或者是横线
void drawSquareOrLine(int position, bool isLow)
{
int x = position * (digitWidth + spacing);
displayType[position] = isLow;
u8g2.drawBox(x, 30, digitWidth - 2, isLow ? 8 : 1); // 绘制方块或竖线
u8g2.sendBuffer();
}
// 电压采样,调用定时器1作为采样频率控制器
void sampleADC()
{
if (sampleIndex < SAMPLES)
{
vReal[sampleIndex] = analogRead(micPin);
vImag[sampleIndex] = 0;
sampleIndex++;
}
else
{
MyTim1->pause(); // 停止定时器
samplingDone = true;
}
}
// 快速傅里叶变换
void performFFT()
{
if (!samplingDone)
return;
// 计算 FFT
FFT.windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD);
FFT.compute(FFT_FORWARD);
FFT.complexToMagnitude();
// 找到幅度最高的三个频率点
double topFrequencies[3] = {0, 0, 0};
double topAmplitudes[3] = {0, 0, 0};
for (int i = 0; i < FFT_SIZE; i++)
{
double amplitude = vReal[i];
double frequency = (i * SAMPLING_FREQUENCY) / SAMPLES;
for (int j = 0; j < 3; j++)
{
if (amplitude > topAmplitudes[j])
{
for (int k = 2; k > j; k--)
{
topAmplitudes[k] = topAmplitudes[k - 1];
topFrequencies[k] = topFrequencies[k - 1];
}
topAmplitudes[j] = amplitude;
topFrequencies[j] = frequency;
break;
}
}
}
if (topFrequencies[0] > freq[count] && topFrequencies[0] < upperFreq)
{
detected = true;
}
if (topFrequencies[1] > freq[count] && topFrequencies[1] < upperFreq)
{
detected = true;
}
if (topFrequencies[2] > freq[count] && topFrequencies[2] < upperFreq)
{
detected = true;
}
// 重置采样状态
sampleIndex = 0;
samplingDone = false;
MyTim1->resume(); // 启动 TIM1 进行下一次采样
}
void timerInterrupt()
{
if (workingMode == 1)
{
if (count < 7)
{
// 在没有完成七个音之前,每次定时器中断去查询在中断期间是否FFT检测到了对应频率的音
drawSquareOrLine(count, detected);
detected == true ? correct_count++ : correct_count = correct_count;
detected = false;
count++;
}
else
{
bool allCorrect = true;
for (int i = 0; i < 7; i++)
{
if (!displayType[i])
{ // 如果有任何一个是竖线
allCorrect = false;
break;
}
}
char buffer[8];
snprintf(buffer, sizeof(buffer), "%dFEN", correct_count);
u8g2.drawStr(90, 60, buffer);
u8g2.sendBuffer();
MyTim2->pause(); // 停止定时器
correct_count = 0;
count = 0; // 重置计数器
}
}
else
{
if (count < 7)
{
if (detected)
{
drawSquareOrLine(count, detected);
count++;
}
detected = false;
}
else
{
MyTim2->pause(); // 停止定时器
count = 0; // 重置计数器
}
}
}
void setup()
{
mySerial.begin(115200); // 初始化硬件串口
pinMode(buttonPin, INPUT_PULLUP);
pinMode(studyPin, INPUT);
pinMode(testPin, INPUT);
pinMode(speedPin, INPUT);
// 初始化 OLED 显示屏
u8g2.begin();
// 配置定时器 TIM1
MyTim1->setOverflow(SAMPLING_FREQUENCY, HERTZ_FORMAT); // 设置定时器频率
MyTim1->attachInterrupt(sampleADC); // 关联中断处理函数
// 配置定时器 TIM2 每500ms触发一次
MyTim2->setOverflow(TIM2_interval[TIM2_set], MICROSEC_FORMAT); // 每500ms触发一次
MyTim2->attachInterrupt(timerInterrupt);
// 显示初始化消息
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_ncenB10_tr);
u8g2.setCursor(32, 32);
u8g2.print("GUITAR");
u8g2.sendBuffer();
mySerial.println("Initialization complete. Starting FFT analysis.");
MyTim1->pause();
}
void loop()
{
if (digitalRead(testPin) == HIGH)
{ // 检测PA3是否被按下
while (digitalRead(testPin) == HIGH)
; // 防抖动处理
MyTim2->pause(); // 暂停定时器
workingMode = 1;
}
if (digitalRead(studyPin) == HIGH)
{ // 检测PA2是否被按下
while (digitalRead(studyPin) == HIGH)
; // 防抖动处理
MyTim2->pause(); // 暂停定时器
mySerial.println("MODE Transit");
workingMode = 0;
}
if (digitalRead(speedPin) == HIGH)
{ // 检测PA4是否被按下
while (digitalRead(speedPin) == HIGH)
; // 防抖动处理
TIM2_set++;
TIM2_set %= 2;
MyTim2->pause(); // 暂停定时器
MyTim2->setOverflow(TIM2_interval[TIM2_set], MICROSEC_FORMAT); // 每500ms触发一次
MyTim2->attachInterrupt(timerInterrupt);
}
if (digitalRead(buttonPin) == LOW)
{ // 检测PA0是否被按下
while (digitalRead(buttonPin) == LOW)
; // 防抖动处理
count = 0;
detected = false;
MyTim2->pause(); // 暂停定时器
displayInitialNumbers(); // 重新显示数字
MyTim2->refresh(); // 重置定时器
MyTim2->resume(); // 重启定时器
}
while (!samplingDone)
{
// 等待
}
MyTim1->pause();
performFFT();
delay(20);
}
Loading
stm32-bluepill
stm32-bluepill
Loading
ssd1306
ssd1306