基于STM32的简易示波器/频谱仪
基于STM32G031的口袋仪器训练平台,采用128*128 OLED显示,2个通道的模拟输入 + 一个通道的Micphone语音输入,并有一路信号输出。
标签
嵌入式系统
STM32
Arduino
FFT
寒假一起练
奈奎斯特不稳定
更新2022-03-01
哈尔滨理工大学
2977

项目介绍

基于STM32G031的口袋仪器训练平台,采用128*128 OLED显示,2个通道的模拟输入 + 一个通道的Micphone语音输入,并有一路信号输出。

硬件介绍 OLED

OLED(Organic Light-Emitting Diode),又称为有机电激光显示、有机发光半导体(Organic Electroluminescence Display,OLED)。OLED属于一种电流型的有机发光器件,是通过载流子的注入和复合而致发光的现象,发光强度与注入的电流成正比。OLED在电场的作用下,阳极产生的空穴和阴极产生的电子就会发生移动,分别向空穴传输层和电子传输层注入,迁移到发光层。当二者在发光层相遇时,产生能量激子,从而激发发光分子最终产生可见光。

stm32g031

STM32G031x4/x6/x8主流微控制器基于高性能Arm®Cortex®-M0+ 32位RISC核心,工作频率高达64 MHz。它们提供了高水平的集成,适用于消费、工业和电器领域的广泛应用,并可用于物联网(IoT)解决方案。

本次方案使用到了,定时器PWM输出,ADC,外部中断,硬件SPI等功能。

运放

就和模电里讲的运放一样,本项目中的运放主要实现了对输入波形的反转与缩小,并且调整缩小的倍数的功能。

编码器

每转过单位的角度就发出一个脉冲信号(通常为A相、B相输出,A相、B相为相互延迟1/4周期的脉冲输出,根据延迟关系可以区别正反转,而且通过取A相、B相的上升和下降沿可以进行2或4倍频;

思路

这次的主要任务是,使用运放对输入信号进行反向缩小,使用stm32对运放的输出进行读取,并且对数据进行分析,将分析好的数据通过触发的方式显示在屏幕上,需要注意的是,由于运放输出电压与实际电压是一个反向的关系,也就是说在数据分析的时候,或者获取代码的时候,需要将数据调正。本项目中,还需要使用PWM+LPF方案,对输入电压的电平进行调整,经过调试后输入的电压才能正确的显示。按键就是使用的外部中断,关于编码器,我并没有使用stm32自带的编码器解码模块,而是对编码器的一项进行中断,然后读取另外一项的电平,由于编码器的电平变换不会向平时电机上的编码器一样变化的非常快,所以也就不需要计数器的辅助。

示波器的代码时参考了这个网站做的

流程图

FuChOVM_GxXLG7HeNznv8NXgOXXI

代码及其说明

由于最近用arduino框架比较多,并且stm32有支持arduino框架,所以我使用VSCODE+PIO+arduino framework的策略来完成本次项目。接下来将展示代码。

按键中断,编码器读取

创建中断函数指向,在中断函数里处理编码器的数据,其中key3和key5分别是编码器的A相和B相。

 attachInterrupt(Key4, select_IQR, FALLING);
  attachInterrupt(Key3, code_IQR, FALLING);
  attachInterrupt(Key2, change_inf_sta, FALLING);
  attachInterrupt(Key1, change_scop_sta, FALLING);
​
void code_IQR()
{
  // Serial.println("in code IQR");
  if (digitalRead(Key5) == LOW)
  {
    // Serial.println("Key3 HIGH");
    if (scopeP == 0)
    { //
      vRange++;
      if (vRange > 9)
      {
        vRange = 9;
      }
    }
    if (scopeP == 1)
    { //
      // Serial.println("Key3 LOW");
      hRange++;
      if (hRange > 7)
      {
        hRange = 7;
      }
    }
    if (scopeP == 2)
    {            //
      trigD = 0; //
    }
  }
  else
  {
    if (scopeP == 0)
    { //
      vRange--;
      if (vRange < 0)
      {
        vRange = 0;
      }
    }
    if (scopeP == 1)
    { //
      hRange--;
      if (hRange < 0)
      {
        hRange = 0;
      }
    }
    if (scopeP == 2)
    {            //
      trigD = 1; //
    }
  }
}

数据波形读取

数据波形读取的采样率组要是通过delay函数来控制采样周期。

void readWave()
{
 switch (hRange)
{
 case 0: // 50ms
   for (int i = 0; i < REC_LENGTH; i++)
  { // 200
     // waveBuff[i] = (analogRead(analog_pin) - 105) * 3.3 / (601 - 105); // 转3.3V
     long temp_time = micros();
     waveBuff[i] = 1024 - analogRead(analog_pin); // 转3.3V
     data[1] = micros() - temp_time;
     delayMicroseconds(500 * 1000 / REC_LENGTH - 186);
     // delayMicroseconds(100000); //
  }
   break;
 case 1: // 20ms
   for (int i = 0; i < REC_LENGTH; i++)
  { // 200
     // waveBuff[i] = (analogRead(analog_pin) - 105) * 3.3 / (601 - 105); // 转3.3V
     waveBuff[i] = 1024 - analogRead(analog_pin); // 转3.3V
     delayMicroseconds(200 * 1000 / REC_LENGTH - 186);
     // delayMicroseconds(20 * 1000 / REC_LENGTH - (micros() - bais_mic));
     // delayMicroseconds(100000); //
  }
   break;
 case 2: // 10ms
   for (int i = 0; i < REC_LENGTH; i++)
  { // 200
     // waveBuff[i] = (analogRead(analog_pin) - 105) * 3.3 / (601 - 105); // 转3.3V
     waveBuff[i] = 1024 - analogRead(analog_pin); // 转3.3V
     delayMicroseconds(100 * 1000 / REC_LENGTH);
     // delayMicroseconds(10 * 1000 / REC_LENGTH - (micros() - bais_mic));
     // delayMicroseconds(100000); //
  }
   break;
 case 3: // 5ms
   for (int i = 0; i < REC_LENGTH; i++)
  { // 200
     // waveBuff[i] = (analogRead(analog_pin) - 105) * 3.3 / (601 - 105); // 转3.3V
     waveBuff[i] = 1024 - analogRead(analog_pin); // 转3.3V
     delayMicroseconds(50 * 1000 / REC_LENGTH);
     // delayMicroseconds(5 * 1000 / REC_LENGTH - (micros() - bais_mic));
     // delayMicroseconds(100000); //
  }
   break;
 case 4: // 2ms
   for (int i = 0; i < REC_LENGTH; i++)
  { // 200
     // waveBuff[i] = (analogRead(analog_pin) - 105) * 3.3 / (601 - 105); // 转3.3V
     waveBuff[i] = 1024 - analogRead(analog_pin); // 转3.3V
     delayMicroseconds(20 * 1000 / REC_LENGTH);
     // delayMicroseconds(temp_mic > 0 ? temp_mic : 0);
     // delayMicroseconds(100000); //
  }
   break;
 case 5: // 1ms
   for (int i = 0; i < REC_LENGTH; i++)
  { // 200
     // waveBuff[i] = (analogRead(analog_pin) - 105) * 3.3 / (601 - 105); // 转3.3V
     waveBuff[i] = 1024 - analogRead(analog_pin); // 转3.3V
     delayMicroseconds(10000 / REC_LENGTH);
     // delayMicroseconds(temp_mic > 0 ? temp_mic : 0);
     // delayMicroseconds(100000); //
  }
   break;
 case 6: // 500us
   for (int i = 0; i < REC_LENGTH; i++)
  { // 200
     // waveBuff[i] = (analogRead(analog_pin) - 105) * 3.3 / (601 - 105); // 转3.3V
     waveBuff[i] = 1024 - analogRead(analog_pin); // 转3.3V
     delayMicroseconds(5000 / REC_LENGTH);
     // delayMicroseconds(temp_mic > 0 ? temp_mic : 0);
     // delayMicroseconds(100000); //
  }
   break;
 case 7: // 200us
   for (int i = 0; i < REC_LENGTH; i++)
  { // 200
     // waveBuff[i] = (analogRead(analog_pin) - 105) * 3.3 / (601 - 105); // 转3.3V
     waveBuff[i] = 1024 - analogRead(analog_pin); // 转3.3V
     delayMicroseconds(2000 / REC_LENGTH);
     // delayMicroseconds(temp_mic > 0 ? temp_mic : 0);
     // delayMicroseconds(100000); //
  }
   break;
}
}
​

OLED显示

oled显示则是使用Adafruit_SH110X、Adafruit_GFX这两个库实现的

SPIClass m_SPI(PB7, PB6, PB8, NC);
​
Adafruit_SH1107 display = Adafruit_SH1107(SCREEN_WIDTH, SCREEN_HEIGHT, &m_SPI, /*DC=*/OLED_DC, /*RST*/ OLED_RESET, /*CS*/ OLED_CS, 8000000UL);

m_SPI.setMISO(-1);
display.begin();
display.clearDisplay();
display.display();

波形分析

void dataAnalize()
{ //
 int d;
 long sum = 0;
​
 //
 dataMin = 1023; //
 dataMax = 0;    //
 for (int i = 0; i < REC_LENGTH; i++)
{ //
   if (att10x)
  {
     waveBuff[i] = waveBuff[i] - 74; //有74的直流分量无法消除
  }
   d = waveBuff[i];
   sum = sum + d;
   if (d < dataMin)
  { //
     dataMin = d;
  }
   if (d > dataMax)
  { //
     dataMax = d;
  }
}
 P2P = (dataMax - dataMin);
 dataAve = (sum + 10) / 26; // 10
 // max,min
 if (vRange <= 1)
{                                  // Auto1
   rangeMin = dataMin - 20;         // -20
   rangeMin = (rangeMin / 10) * 10; // 10
   if (rangeMin < 0)
  {
     rangeMin = 0; // 0
  }
   rangeMax = dataMax + 20;               // +20
   rangeMax = ((rangeMax / 10) + 1) * 10; // 10
   if (rangeMax > 1020)
  {
     rangeMax = 1023; // 10201023
  }
​
   if (att10x == 1)
  {
     rangeMaxDisp = 100 * (rangeMax * lsb50V); // ADC
     rangeMinDisp = 100 * (rangeMin * lsb50V); //
  }
   else
  { //
     //
     rangeMaxDisp = 100 * (rangeMax * lsb5V);
     rangeMinDisp = 100 * (rangeMin * lsb5V);
  }
}
 else
{ //
   //
}
​
 //
 for (trigP = ((REC_LENGTH / 2) - 51); trigP < ((REC_LENGTH / 2) + 50); trigP++)
{ //
   if (trigD == 0)
  { // 0
     if ((waveBuff[trigP - 1] < (dataMax + dataMin) / 2) && (waveBuff[trigP] >= (dataMax + dataMin) / 2))
    {
       break; //
    }
  }
   else
  { // 0
     if ((waveBuff[trigP - 1] > (dataMax + dataMin) / 2) && (waveBuff[trigP] <= (dataMax + dataMin) / 2))
    {
       break;
    } //
  }
}
 trigSync = true;
 if (trigP >= ((REC_LENGTH / 2) + 50))
{ //
   frq = 0;
   trigP = (REC_LENGTH / 2);
   trigSync = false; // Unsync
}
 else
{
   frq = 0;
   for (int i = trigP + 5; i < REC_LENGTH; (frq++, i++))
  {
     static int last_val = 0;
     char result = 0;
     if (trigD == 0)
    {
       if ((waveBuff[i - 1] < (dataMax + dataMin) / 2) && (waveBuff[i] >= (dataMax + dataMin) / 2))
      {
         break; //
      }
    }
     else
    {
       if ((waveBuff[i - 1] > (dataMax + dataMin) / 2) && (waveBuff[i] <= (dataMax + dataMin) / 2))
      {
         break;
      } //
    }
  }
}
}
​

FFT变化

FFT变化则是使用了Adafruit_ZeroFFT这个库,并且借鉴了这个大佬的使用方法解算出FFT变化。

int ZeroFFT(q15_t *source)
{
 uint16_t twidCoefModifier;
 uint16_t bitRevFactor;
 uint16_t *pBitRevTable;
 uint16_t length = 256;
 q15_t *pSrc = source;
 q15_t *pOut = scratchData;
 q15_t real;
 q15_t img;
 uint16_t i;
​
 /* Initializations of structure parameters for 256 point FFT */
 twidCoefModifier = 1u;
 bitRevFactor = 1u;
 pBitRevTable = (uint16_t *)&armBitRevTable[0];
​
 applyWindow(source, window_hanning_256, 256);
​
 // split the data
​
 for (i = 0; i < length; i++)
{
   *pOut++ = *pSrc++; // real
   *pOut++ = 0;       // imaginary
}
​
 arm_radix2_butterfly_q15(scratchData, length, (q15_t *)twiddleCoefQ15,
                          twidCoefModifier);
 arm_bitreversal_q15(scratchData, length, bitRevFactor, pBitRevTable);
​
 pSrc = source;
 pOut = scratchData;
 for (i = 0; i < length; i++)
{
   real = *pOut++;
   img = *pOut++;
   //*pSrc++ = val > 0 ? val : -val;
   *pSrc++ = sqrt((int32_t)real * real + (int32_t)img * img);
   // pOut++; // discard imaginary phase val
}
 source[0] /= 2;
​
 return 0;
}

遇到的问题

在整个开发过程中,令我最头疼的一个问题就是,aruidno自带的硬件SPI驱动函数,好像没有提供半工工作方式,那会儿我尝试了很多方法:直接配置硬件SPI的寄存器然后使用HAL库初始化,或者使用软件SPI等,最后我在一个外网上找到了一些解决方法,就是初始化硬件SPI后,再使用aruidno自带的库函数,对硬件SPI的MISO引脚赋值为-1,这样做的好处是,以后再使用我需要的SPI引脚硬件SPI在发送数据的时候不会卡住,但是坏处就在于,硬件SPI读取数据的时候会卡死。

成果展示

FikuMTmbV9fUigWiA507ZEF7aZ69FhtyXkdKQ_MzuXrcEh0pzTNp2Con

未来计划

该项目硬件部分还剩MIC部分可以进行结算,但是对于麦克风的使用我还是不太了解,未来要是有机会的话,可以考虑对MIC数据进行分析。

附件下载
main.cpp
firmware.hex
由于我是直接使用openocd下载的,所以无法确定hex文件是否有效
团队介绍
一个即将毕业的大四学生
团队成员
奈奎斯特不稳定
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号