如何使用Arduino Nano和OLED显示器构建示波器

2023-03-28  

  示波器是一种电子测试设备,可以使用二维图形监控任何电压的稳定变化,其中一个或多个电压随时间的变化位于垂直 Y 轴上。一般来说,每个电子爱好者或对电子产品感兴趣的人都会在某些时候需要示波器。然而,对于学生和业余爱好者来说,它的价格非常昂贵,这就是为什么在本文中我们将讨论如何使用 Arduino 在家里制作迷你示波器。

  在本文中,我们将构建一个简单、低成本的基于 Arduino 的示波器,该示波器具有 1.3“ OLED 显示屏,可用于准确显示波形。该项目的灵感来自于火柴盒项目中的Peter Balch Oscilloscope。我们更改了很少的代码和硬件可以满足我们的要求。

  构建基于 Arduino 的示波器所需的材料

  使用Arduino Nano制作这款便携式迷你示波器需要以下组件。

pYYBAGLNLDuAUDolAABp7RGaVQY347.png

pYYBAGLNLEWAXvaIAABYSkvmXso706.png

  Arduino示波器的电路图

  构建基于 Arduino 的示波器的原理图非常简单,只需要几个部件,您可以查看下面的完整电路图。

pYYBAGLNLEqAWcs4AAFoLVvDr9k427.png

  原理图的主要部分使用单个运算放大器 IC,即LM358,它在单个芯片内包含两个运算放大器。由于输入信号为交流信号,并且我们没有分轨结构,因此有两个运算放大器(来自单个运算放大器 8 引脚封装)用于使信号交流耦合。两个运算放大器都被馈入用于偏移信号的参考电压,并使用模拟输入将其绘制在示波器图上。可以使用电位器(具有 100K 电阻)更改偏移量。两个运算放大器都设置有相同的负反馈和 x5 增益设置。

  除此之外,OLED 通过 A4 连接,A5 是带有 4.7K 上拉电阻的 I2C SCL 和 SDA 引脚。它可以使用简单的 USB 连接器。这些按钮用于设置示波器的参数。我们在性能板上构建了完整的电路,当我完成设置时,它看起来像这样。

poYBAGLNLFGATKKIAAUq8zykBJs947.png

Arduino示波器——代码说明

编码部分很复杂。要了解编码的工作原理,请查看以下代码片段 -

首先,Oscope 的库是从 Peter Balch 的SimpleSH1106.h库中使用的。对于使用 SH1106 芯片组的 OLED,它是一个非常快速的库。

这些库在以下几行中定义。

#include 

#include 

#include "SimpleSH1106.h"

#include 

 


定义和类型定义在下面的行中定义 -


 


ifndef getBit


#define getBit(sfr, bit) (_SFR_BYTE(sfr) & _BV(bit))


#万一

枚举 Tmode {DC5V, AC500mV, AC100mV, AC20mV,

               逻辑逻辑,

               毫伏表,

               最大模式1

               };

常量 Tmode maxMode = maxMode1 - 1;

 


此外,所需的常量和变量在下面声明 -


 


/---------------------------------------------------- ----------------------------

// 全局常量

//------------------------------------------------ -----------------------------------------

布尔 bHasLogic = true;

布尔 bHasFreq = true;

布尔 bHasVoltmeter = true;

布尔 bHasTestSignal = true;

布尔 bHasSigGen = 假;

常量长波特率 = 115200; // UART 的波特率,单位 bps

常量 int 命令延迟 = 10; // ms 等待串行缓冲区的填充

常量 int COMBUFFERSIZE = 4; // 传入数字的缓冲区大小

常量 int testSignalPin = 3;

常量字符确认 = '@'; // 确认通讯命令

常量字节 SampPerA = 5 + 6; // 6 次

#define LoopNops __asm__("nopn nopn nopn nopn nopn nopn")

常量 int SampPerB = 20;

常量 int BtnHorz = 4; // 按钮

常量 int BtnVert = 7; // 按钮

常量 int FreeRunTimeout = 0x10; // 0.5 秒自由运行

//------------------------------------------------ -----------------------------------------

// 全局变量

//------------------------------------------------ -----------------------------------------

Tmode curMode = DC5V;

uint8_t curVref = 1;

uint8_t curPeriod = 200;

uint8_t curPrescaler = 7;

字符命令缓冲区[COMBUFFERSIZE + 1];

布尔 TrigFalling = true;

uint8_t curSweep = 0;

字节 yGraticulePage0,yGraticuleByte0,yGraticulePage1,yGraticuleByte1,yGraticulePage2,yGraticuleByte2;

字节* pxGratLabel;

字节* pyGratLabel;

字节 xGratLabelLen,yGratLabelLen;

字节 yGraticule0,yGraticule1,yGraticule2,xGraticule1,xGraticule2;

TmenuSel sel = sTime;// 用于主菜单

字节 adj[4] = {0, 0, 0, 0}; // 用于主菜单

bool SendingSerial = false;

int curPwmMode = 0;

常量 int ADCBUFFERSIZE = 128;

uint8_t ADCBuffer[ADCBUFFERSIZE];

int ButtonsTimer1 = 0;

长 Vin = 0; // 用于显示电压表

 


菜单上的图像在这里声明 -


 


/---------------------------------------------------- ----------------------------

// 主菜单的图像

//------------------------------------------------ -----------------------------------------

常量字节 imgMainMenuTop[] PROGMEM = {

  128, // 宽度

  2, // 页面

  1, 224, 147, 32, 130, 0, 3, 248, 252, 6, 130, 2, 3, 6, 252, 248, 130, 0, 2, 96, 240, 130, 144, 2, 176, 32、130、0、2、224、240、130、

  16, 3, 48, 32, 0, 130, 246, 130, 0, 130, 254, 130, 0, 130, 254, 130, 0, 2, 224, 240, 130, 16, 2, 240, 224, 130, 0, 2, 96, 240, 130,

  144, 2, 176, 32, 130, 0, 2, 224, 240, 130, 16, 5, 48, 32, 0, 224, 240, 130, 16, 2, 240, 224, 130, 0, 130, 240、130、16、2、240、224、

  130, 0, 2, 224, 240, 130, 80, 2, 112, 96, 130, 0, 149, 32, 2, 224, 255, 149, 0, 3, 1, 3, 6, 130, 4, 3, 6, 3, 1, 130, 0, 2, 2, 6, 130,

  4, 2, 7, 3, 130, 0, 2, 3, 7, 130, 4, 3, 6, 2, 0, 130, 7, 130, 0, 130, 7, 130, 0, 130, 7, 130, 0, 2, 3, 7, 130, 4, 2, 7, 3, 130, 0, 2, 2, 6,

  130, 4, 2, 7, 3, 130, 0, 2, 3, 7, 130, 4, 5, 6, 2, 0, 3, 7, 130, 4, 2, 7, 3, 130, 0, 130, 63, 130, 4, 2, 7, 3, 130, 0, 2, 3, 7, 130, 4, 2,

  6、2、151、0、1、255

};

常量字节 imgMainMenuMid[] PROGMEM = {

  128, // 宽度

  1, // 页面

  1、255、254、0、1、255

};

常量字节 imgMainMenuBot[] PROGMEM = {

  128, // 宽度

  1, // 页面

  1、255、254、128、1、255

};

常量字节 imgBoxTop[] PROGMEM = {

  128, // 宽度

  1, // 页面

  1、248、254、8、1、248

};

常量字节 imgCaret1[] PROGMEM = {

  4, // 宽度

  1, // 页面

  4、255、126、60、24

};

常量字节 imgCaret2[] PROGMEM = {

  7, // 宽度

  1, // 页面

  7、32、48、56、60、56、48、32

};

常量字节 imgTrian[] PROGMEM = {

  14, // 宽度

  2, // 页面

  28, 3,12,48,192,0,0,0,0,0,0,192,48,12,3,128,128,128,128,131,140,176,176,140,131,128,128,128,128};

常量字节 imgSine[] PROGMEM = {

  14, // 宽度

  2, // 页面

  28, 1,2,28,224,0,0,0,0,0,0,224,28,2,1,128,128,128,129,142,144,160,160,144,142,129,128,128,128};

常量字节 imgSquare[] PROGMEM = {

  14, // 宽度

  2, // 页面

  28, 0,0,0,255,1,1,1,1,1,1,255,0,0,0,160,160,160,191,128,128,128,128,128,128,191,160,160,160};

 


图纸和线条在这里声明 -


 


//------------------------------------------------ -----------------------------------------

// 填充条

// 将屏幕列的位从位 y1 填充到位 y2

// 创建一个必须是 'page' 一部分的栏

// 返回柱

//------------------------------------------------ -----------------------------------------

字节填充条(字节 y1,字节 y2,字节页){

  静态字节 lob[] = {0x00、0x01、0x03、0x07、0x0F、0x1F、0x3F、0x7F、0xFF};

  字节条;

  如果(页面 == y1 / 8){

               如果(页面 == y2 / 8)

               bar = lob[(y2 & 7) + 1];

               别的

               条= 0xFF;

               返回栏 - lob[y1 & 7];

  }

  else if (page == y2 / 8)

               返回吊球[(y2 & 7) + 1];

  else if ((page > y1 / 8) & (page < y2 / 8))

               返回 0xFF;

  别的

               返回0;

}

//------------------------------------------------ -----------------------------------------

// 画框

// 在屏幕周围画一个盒子,左上角写着 s

//------------------------------------------------ -----------------------------------------

无效的drawBox(char * s){

  // 清除SH1106();

  DrawImageSH1106(0, 0, imgBoxTop);

  for (int i = 1; i < 7; i++)

               DrawImageSH1106(0, i, imgMainMenuMid);

  DrawImageSH1106(0, 7, imgMainMenuBot);

  DrawCharSH1106(' ', 6, 0, SmallFont);

  DrawStringSH1106(s, 7, 0, SmallFont);

}

//------------------------------------------------ -----------------------------------------

// 画屏

// 像示波器一样绘制图形

// 大约需要 40 毫秒

//------------------------------------------------ -----------------------------------------

无效画屏(无效){

  字节 i, j, k, y, yPrev, bar, page, lastDrawn;

  字节* pxbz;

  字节* pybz;

  字节 pxlenz,pylenz;

  开关(curMode){

               案例毫伏表:

               drawBox("电压表");

               我 = 20;

               如果(Vin == LONG_MAX)

               DrawStringSH1106("++++", i, 3, LargeDigitsFont);

               否则如果(Vin == -LONG_MAX)

               DrawStringSH1106("----", i, 3, LargeDigitsFont);

               别的 {

               i += DrawIntDP2(Vin / 10, i, 3, LargeDigitsFont);

               DrawStringSH1106("伏特", i, 4, SmallFont);

               }

               返回;

               案例 AC100mV:

               对于 ( i = 0; i < ADCBUFFERSIZE; i++ )

               ADCBuffer[i] = ADCBuffer[i] / 4;

               休息;

               默认:

               对于 ( i = 0; i < ADCBUFFERSIZE; i++ )

               ADCBuffer[i] = 63 - ADCBuffer[i] / 4;

  }

if ((curPeriod == 0) && (curMode <= AC20mV)) {

               yPrev = ADCBuffer[0];

               y = ADC缓冲区[1];

               对于 ( i = 1; i < ADCBUFFERSIZE - 1; i++ ) {

               ADCBuffer[i] = (yPrev + y + ADCBuffer[i + 1]) / 3;

               yPrev = y;

               y = ADC缓冲区[i + 1];

               }

  }

  pxbz = pxGratLabel;

  pxlenz = xGratLabelLen;

  pybz = pyGratLabel;

  pylenz = yGratLabelLen;

  for (page = 0; page <= 7; page++) {

               yPrev = ADCBuffer[0];

               最后绘制 = 255;

               设置页面(页面);

               设置科尔(0);

               Wire.beginTransmission(addr);

               Wire.write(0x40); //后面的字节是数据

               对于 (i = 0; i < ADCBUFFERSIZE; i++) {

               如果 (i % 26 == 0) {

               Wire.endTransmission();

               Wire.beginTransmission(addr);

               Wire.write(0x40); //后面的字节是数据

文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。