آموزش آردوینوپروژه های آردوینو

اسیلوسکوپ با نمایشگر OLED و آردوینو نانو

اسیلوسکوپ یک دستگاه تست الکترونیکی است که می تواند تغییرات ثابت هر ولتاژ الکتریکی را با استفاده از نمودارهای دو بعدی که در آن تغییر یک یا چند ولتاژ الکتریکی در طول زمان در محور عمودی Y قرار می گیرد، نظارت کند. به طور کلی، هر مهندس الکترونیک یا کسی که علاقه مند به کار با الکترونیک است، در برخی مواقع به یک اسیلوسکوپ نیاز دارد. با این حال، یک اسیلوسکوپ برای دانش آموزان، بسیار گران است و به همین دلیل است که در این مقاله به نحوه ساخت یک مینی اسیلوسکوپ در خانه با استفاده از آردوینو می پردازیم.

ما قبلا پروژه ساخت اسیلوسکوپ با رزبری پای و اسیلوسکوپ با پایتون و آردوینو را طراحی کرده ایم.

در این مقاله، ما یک اسیلوسکوپ ساده و کم‌هزینه مبتنی بر آردوینو با نمایشگر OLED 1.3 اینچی می‌سازیم که می‌تواند برای تجسم دقیق شکل‌های موج استفاده شود.

مدار اسیلوسکوپ نمایشگر OLED با آردوینو

شماتیک ساخت اسیلوسکوپ مبتنی بر آردوینو بسیار ساده است و فقط به چند قسمت نیاز دارد، می توانید نمودار مدار کامل را در زیر مشاهده کنید.

مدار اسیلوسکوپ نمایشگر OLED با آردوینو

بخش اصلی شماتیک از یک IC تک آپ امپ استفاده می کند که LM358 است که شامل دو آپ امپ در داخل یک تراشه است. از آنجایی که سیگنال ورودی AC خواهد بود دو آپ امپ برای کوپل شدن سیگنال ac استفاده می شود. آپ امپ با یک ولتاژ مرجع تغذیه می شود که برای جبران سیگنال استفاده می شود و هم با استفاده از ورودی های آنالوگ روی نمودار محدوده رسم می شود. افست را می توان با استفاده از پتانسیومتر (که دارای مقاومت 100K است) تغییر داد. هر دو آپ امپ با بازخورد منفی یکسان با بهره x5 تنظیم می شوند.

به غیر از این، OLED به آردوینو متصل شاده است. از دکمه ها برای تنظیم پارامترهای اسیلوسکوپ استفاده می شود. ما مدار کامل را روی یک برد سوراخدار ساخته‌ایم.

ساخت اسیلوسکوپ با برد آردوینو نانو

قطعات مورد نیاز

اجزای زیر برای ساخت این اسیلوسکوپ کوچک قابل حمل با استفاده از آردوینو نانو مورد نیاز است.

تعداد مقدار دستگاه Package Parts توضیحات
2 دکمه TH S1, S2 Tactile switch / Buttons
1 برد آردوینو نانو ARDUINO_NANO ARDUINO_NANO1 Arduino Nano Board
4 0.1uF / 16V خازن TH C1, C2, C3, C4 0.1uF/16V/Ceramic Disc
1 100K مقاومت TH R2 100K/1/4W/TH
1 10K مقاومت TH R7 10K/1/4W/TH
1 1K مقاومت TH R3 1K/1/4W/TH
2 1M مقاومت TH R6, R8 1M/1/4W/TH
2 270K مقاومت TH R4, R5 270K/1/4W/TH
3 4.7K مقاومت TH R1, R9, R10 4.7K/1/4W/TH
1 LM358 آپ امپ DIL08 IC1 LM358
2 PIN1-2 پین هدر TH Display / Input 4Pin / M/F

توضیح کد اسیلوسکوپ آردوینو

بخش کدگذاری پیچیده است. کد کامل در انتهای صفحه قرار داده شده است. در اینجا قطعه قطعه کد را بررسی میکنیم.

اول از همه، کتابخانه SimpleSH1106.h پیتر بالچ فراخوانی می شود. این یک کتابخانه بسیار سریع برای OLED است که از چیپست SH1106 استفاده می کند.

کتابخانه ها در خطوط زیر تعریف شده اند.

#include <Wire.h>
#include <limits.h>
#include "SimpleSH1106.h"
#include <math.h>

تعریف ها و typedef ها در خطوط زیر انجام شده است.

ifndef getBit

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

#endif
enum Tmode {DC5V, AC500mV, AC100mV, AC20mV,
               mLogic,
               mVoltmeter,
               maxMode1
               };
const Tmode maxMode = maxMode1 - 1;

علاوه بر این، ثابت های مورد نیاز و متغیرها در زیر اعلام می شوند:

همچنین اگر در مورد این مطلب سوالی داشتید در انتهای صفحه در قسمت نظرات بپرسید
/-----------------------------------------------------------------------------
// Global Constants
//-----------------------------------------------------------------------------
bool bHasLogic = true;
bool bHasFreq = true;
bool bHasVoltmeter = true;
bool bHasTestSignal = true;
bool bHasSigGen = false;
const long BAUDRATE  = 115200;  // Baud rate of UART in bps
const int COMMANDDELAY  = 10;  // ms to wait for the filling of Serial buffer
const int COMBUFFERSIZE = 4; // Size of buffer for incoming numbers
const int testSignalPin = 3;
const char ack = '@'; // acknowledge for comms command
const byte SampPerA = 5 + 6; // 6 nops
#define LoopNops __asm__("nop\n nop\n nop\n nop\n nop\n nop\n")
const int SampPerB = 20;
const int BtnHorz = 4; // pushbutton
const int BtnVert = 7; // pushbutton
const int FreeRunTimeout = 0x10; // 0.5 sec for free run
//-----------------------------------------------------------------------------
// Global Variables
//-----------------------------------------------------------------------------
Tmode curMode = DC5V;
uint8_t curVref = 1;
uint8_t curPeriod = 200;
uint8_t curPrescaler = 7;
char commandBuffer[COMBUFFERSIZE + 1];
bool TrigFalling = true;
uint8_t curSweep = 0;
byte yGraticulePage0, yGraticuleByte0, yGraticulePage1, yGraticuleByte1, yGraticulePage2, yGraticuleByte2;
byte* pxGratLabel;
byte* pyGratLabel;
byte xGratLabelLen, yGratLabelLen;
byte yGraticule0, yGraticule1, yGraticule2, xGraticule1, xGraticule2;
TmenuSel sel = sTime; // for main menu
byte adj[4] = {0, 0, 0, 0}; // for main menu
bool SendingSerial = false;
int curPwmMode = 0;
const int ADCBUFFERSIZE = 128;
uint8_t ADCBuffer[ADCBUFFERSIZE];
int ButtonsTimer1 = 0;
long Vin = 0; // used to display Voltmeter

تصاویر موجود در منو در اینجا اعلام شده است:

/-----------------------------------------------------------------------------
// images for the main menu
//-----------------------------------------------------------------------------
const byte imgMainMenuTop[] PROGMEM = {
  128, // width
  2, // pages
  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
};
const byte imgMainMenuMid[] PROGMEM = {
  128, // width
  1, // pages
  1, 255, 254, 0, 1, 255
};
const byte imgMainMenuBot[] PROGMEM = {
  128, // width
  1, // pages
  1, 255, 254, 128, 1, 255
};
const byte imgBoxTop[] PROGMEM = {
  128, // width
  1, // pages
  1, 248, 254, 8, 1, 248
};
const byte imgCaret1[] PROGMEM = {
  4, // width
  1, // pages
  4, 255, 126, 60, 24
};
const byte imgCaret2[] PROGMEM = {
  7, // width
  1, // pages
  7, 32, 48, 56, 60, 56, 48, 32
};
const byte imgTrian[] PROGMEM = {
  14, // width
  2, // pages
  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};
const byte imgSine[] PROGMEM = {
  14, // width
  2, // pages
  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};
const byte imgSquare[] PROGMEM = {
  14, // width
  2, // pages
  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};

نقشه ها و خطوط در اینجا اعلام شده است –

//-----------------------------------------------------------------------------
// FillBar
//   fills the bits of a screen column from bit y1 to bit y2
//   makes a bar that must be part of 'page'
//   returns the bar
//-----------------------------------------------------------------------------
byte FillBar(byte y1, byte y2, byte page) {
  static byte lob[] = {0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF};
  byte bar;
  if (page == y1 / 8) {
               if (page == y2 / 8)
               bar = lob[(y2 & 7) + 1];
               else
               bar = 0xFF;
               return bar - lob[y1 & 7];
  }
  else if (page == y2 / 8)
               return lob[(y2 & 7) + 1];
  else if ((page > y1 / 8) & (page < y2 / 8))
               return 0xFF;
  else
               return 0;
}
//-----------------------------------------------------------------------------
// draw box
//   draws a box around the screen with s written at top-left
//-----------------------------------------------------------------------------
void drawBox(char* s) {
  //           clearSH1106();
  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);
}
//-----------------------------------------------------------------------------
// drawScreen
//   draws a graph like an oscilloscope
//   takes about 40mS
//-----------------------------------------------------------------------------
void drawScreen(void) {
  byte i, j, k, y, yPrev, bar, page, lastDrawn;
  byte* pxbz;
  byte* pybz;
  byte pxlenz, pylenz;
  switch (curMode) {
               case mVoltmeter:
               drawBox("Voltmeter");
               i = 20;
               if (Vin == LONG_MAX)
               DrawStringSH1106("++++",  i, 3, LargeDigitsFont);
               else if (Vin == -LONG_MAX)
               DrawStringSH1106("----",  i, 3, LargeDigitsFont);
               else {
               i += DrawIntDP2(Vin / 10,  i, 3, LargeDigitsFont);
               DrawStringSH1106("Volts",  i, 4, SmallFont);
               }
               return;
               case AC100mV:
               for ( i = 0; i < ADCBUFFERSIZE; i++ )
               ADCBuffer[i] = ADCBuffer[i] / 4;
               break;
               default:
               for ( i = 0; i < ADCBUFFERSIZE; i++ )
               ADCBuffer[i] = 63 - ADCBuffer[i] / 4;
  }
if ((curPeriod == 0) && (curMode <= AC20mV)) {
               yPrev = ADCBuffer[0];
               y = ADCBuffer[1];
               for ( i = 1; i < ADCBUFFERSIZE - 1; i++ ) {
               ADCBuffer[i] = (yPrev + y + ADCBuffer[i + 1]) / 3;
               yPrev = y;
               y = ADCBuffer[i + 1];
               }
  }
  pxbz = pxGratLabel;
  pxlenz = xGratLabelLen;
  pybz = pyGratLabel;
  pylenz = yGratLabelLen;
  for (page = 0; page <= 7; page++) {
               yPrev = ADCBuffer[0];
               lastDrawn = 255;
               setupPage(page);
               setupCol(0);
               Wire.beginTransmission(addr);
               Wire.write(0x40); // the following bytes are data
               for (i = 0; i < ADCBUFFERSIZE; i++) {
               if (i % 26 == 0) {
               Wire.endTransmission();
               Wire.beginTransmission(addr);
               Wire.write(0x40); // the following bytes are data
               }
               y = ADCBuffer[i];
               if (yPrev > y + 1) {
               if (yPrev == lastDrawn)
               yPrev--;
               bar = FillBar(y + 1, yPrev, page);
               lastDrawn = yPrev + 1;
               } else {
               bar = FillBar(yPrev, yPrev, page);
               lastDrawn = yPrev;
               }
               // }
               if ((page == 0) && (bar == 0x01) && (i & 1))
               bar = 0;
               if ((page == 7) && (bar == 0x80) && (i & 1))
               bar = 0;
               if (page == yGraticulePage0) {
               if (i & 8)
               bar = bar | yGraticuleByte0;
               }
               else if (page == yGraticulePage1) {
               if (i < pylenz)
               {
               bar |= *pybz;
               pybz++;
               }
               else if (i % 4 == 0)
               bar |= yGraticuleByte1;
               }
               else if (page == yGraticulePage2) {
               if (i % 4 == 0)
               bar |= yGraticuleByte2;
               }
               if ((i == xGraticule1) | (i == xGraticule2))
               bar = bar | 0x22;
               if ((page == 7) && (i > xGraticule2 - pxlenz - 2) && (i < xGraticule2 - 1)) {
               bar |= *pxbz;
               pxbz++;
               }
               Wire.write(bar);
               yPrev = y;
               }
               Wire.endTransmission();
  }
}

ADC در اینجا اعلام شده است:

//-----------------------------------------------------------------------------
// initADC()
//-----------------------------------------------------------------------------
void initADC(void) {
  if (curMode > AC20mV)
               return;
  ACSR = 0x10;
  ADCSRA = 0x97;
  ADCSRB = 0x0 ; //ADC Control and Status Register B
  // 0 Bit 6 – ACME: Analog Comparator Multiplexer Enable
  // 000 Bits 2:0 – ADTSn: ADC Auto Trigger Source [n = 2:0] Free Running mode
  ADMUX = 0x20 + (curVref << 6) + curMode; // ADC Multiplexer Selection Register
  // rr       Bits 7:6 – REFSn: Reference Selection = Vcc
  // 1        Bit 5 – ADLAR: ADC Left Adjust Result
  // aaaa  Bits 3:0 – MUXn: Analog Channel Selection
  DIDR0 = 0x3F; // Digital Input Disable Register 0
  // ADC0D=1, ADC1D=1, ADC2D=1, ADC3D=1, ADC4D=1, ADC5D=1, ADC6D=0, ADC7D=0
}

جریان سیگنال های روی صفحه در زیر اعلام شده است:

//-----------------------------------------------------------------------------
// setSweep
// set period and ADC prescaler
//-----------------------------------------------------------------------------
void setSweep(byte Sweep) {
  int x;
  long t;
  if (Sweep == 255) {
               if (curSweep == 0)
               curSweep = 6;
               else
               curSweep--;
  } else
               curSweep = Sweep;
  switch (curSweep) {
               case 0: curPeriod = 0;   curPrescaler = 2; t = 100;   pxGratLabel = &ax0_1[0]; xGratLabelLen = sizeof(ax0_1); break;
               case 1: curPeriod = 4;   curPrescaler = 2; t = 400;   pxGratLabel = &ax0_4[0]; xGratLabelLen = sizeof(ax0_4); break;
               case 2: curPeriod = 11;  curPrescaler = 3; t = 1000;  pxGratLabel = &ax1[0];   xGratLabelLen = sizeof(ax1);   break;
               case 3: curPeriod = 24;  curPrescaler = 3; t = 2000;  pxGratLabel = &ax2[0];   xGratLabelLen = sizeof(ax2);   break;
               case 4: curPeriod = 62;  curPrescaler = 4; t = 5000;  pxGratLabel = &ax5[0];   xGratLabelLen = sizeof(ax5);   break;
               case 5: curPeriod = 125; curPrescaler = 4; t = 10000; pxGratLabel = &ax10[0];  xGratLabelLen = sizeof(ax10);  break;
               case 6: curPeriod = 255; curPrescaler = 5; t = 20000; pxGratLabel = &ax20[0];  xGratLabelLen = sizeof(ax20);  break;

  }
  if (curSweep == 0)
               x = t;
  else
               x = 16 * t / (curPeriod * SampPerA + SampPerB);
  xGraticule1 = x / 2;
  xGraticule2 = x;
  SendAck();
}
//-----------------------------------------------------------------------------
// Sweep
//   sweeps siggen freq continuously
//   takes n mS for whole sweep
//   SDC regs are saved and restored
//   stops when receives a serial char
//-----------------------------------------------------------------------------
void Sweep(int n) {
  byte oldACSR = ACSR;
  byte oldADCSRA = ADCSRA;
  byte oldADCSRB = ADCSRB;
  byte oldADMUX = ADMUX;
  byte oldDIDR0 = DIDR0;
  byte oldDIDR1 = DIDR1;
  int fmin,fmax;
  fmin = calcFreq(freqSGLo);
  fmax = calcFreq(freqSGHi);
  int i=0;
  do {
               long f = exp((log(fmax) - log(fmin))*i/(n-1) + log(fmin)) +0.5;
               SG_freqSet(f, waveType);
               delay(1);
               i++;
               if (i >= n) i = 0;
  } while (!Serial.available());
  SG_freqSet(calcFreq(freqSGLo), waveType);
  ACSR = oldACSR;
  ADCSRA = oldADCSRA;
  ADCSRB = oldADCSRB;
  ADMUX = oldADMUX;
  DIDR0 = oldDIDR0;
  DIDR1 = oldDIDR1;
}

تنظیم دکمه های افزایش و تنظیم حالت در زیر انجام می شود:

//-----------------------------------------------------------------------------
// incMode
//   increment Mode
//   wrap around from max
//   skip over modes that are not allowed
//-----------------------------------------------------------------------------
int incMode(int mode) {
  mode++;
//if ((mode == mLogic)        && (!bHasLogic)) mode++;
//  if ((mode == mFreqLogic) && ((!bHasFreq) || (!bHasLogic))) mode++;
//  if ((mode == mFreqAC)   && (!bHasFreq)) mode++;
  if ((mode == mVoltmeter) && (!bHasVoltmeter)) mode++;
  if (mode > maxMode)
               return DC5V;
  else
               return mode;
}
//-----------------------------------------------------------------------------
// setMode
// set mode and Vref
//-----------------------------------------------------------------------------
void setMode(int mode) {
  int i;
  if (mode == 255) {
               curMode = incMode(curMode);
  } else
               curMode = mode;
  switch (curMode) {
               case DC5V:
               curVref = 1;
               i = (long)4000 * 64 / readVcc();
               if (i <= 63) {
               yGraticule1 = 63 - i;
               yGraticule2 = 63 - i / 2;
               yGraticule0 = 255;
               pyGratLabel = &ax4V[0];
               yGratLabelLen = sizeof(ax4V);
               } else {
               yGraticule2 = 63 - i;
               yGraticule1 = 63 - i / 2;
               yGraticule0 = 255;
               pyGratLabel = &ax2V[0];
               yGratLabelLen = sizeof(ax2V);
               }
               break;
               case AC500mV:
               curVref = 3;
               i = (byte)(0.5 / 1.1 * 256 / 4);
               yGraticule1 = 32 - i;
               yGraticule2 = 32 + i;
               yGraticule0 = 32;
               pyGratLabel = &ax0_5[0];
               yGratLabelLen = sizeof(ax0_5);
               break;
               case AC100mV:
               curVref = 3;
               i = (byte)(0.1 / 1.1 * (R1 + R2) / R2 * 256 / 4);
               yGraticule1 = 32 - i;
               yGraticule2 = 32 + i;
               yGraticule0 = 32;
               pyGratLabel = &ax0_1[0];
               yGratLabelLen = sizeof(ax0_1);
               break;
               case AC20mV:
               curVref = 3;
               i = (byte)(0.02 / 1.1 * (R1 + R2) / R2 * (R1 + R2) / R2 * 256 / 4);
               yGraticule1 = 32 - i;
               yGraticule2 = 32 + i;
               yGraticule0 = 32;
               pyGratLabel = &ax20[0];
               yGratLabelLen = sizeof(ax20);
               break;
               default:
               curVref = 1;
               yGraticule1 = 255;
               yGraticule2 = 255;
               yGraticule0 = 255;
               pyGratLabel = &ax20[0];
               yGratLabelLen = sizeof(ax20);
               break;
  }

ترسیم منوی اصلی با استفاده از قطعه کد زیر انجام می شود:

void drawMainMenu(void) {
  int ofs, x, yVcc, pg;
  switch (sel) {
               case sMode:         ofs = -1; break;
               case sTrigger: ofs = -2; break;
               case sTestSig: ofs = -5; break;
               case sSigGen:  ofs = bHasTestSignal ? -7 : -5; break;
               default:                 ofs = 0;
  }
  //  DrawImageSH1106(0,ofs,imgMainMenu);
  DrawImageSH1106(0, ofs + 0, imgMainMenuTop);
  for (x = 2; x < 14; x++)
               DrawImageSH1106(0, ofs + x, imgMainMenuMid);
  DrawImageSH1106(0, ofs + 10 + bHasTestSignal * 2 + bHasSigGen * 2, imgMainMenuBot);
  DrawImageSH1106(6, 3 + sel * 2 + ofs, imgCaret1);
  BoldSH1106 = true;
  pg = 3 + ofs;
  DrawStringSH1106("Time:",                                        12, pg, SmallFont); pg += 2;
  DrawStringSH1106((adj[1] <= AC20mV ? "Gain:" : "Mode:"), 12, pg, SmallFont); pg += 2;
  DrawStringSH1106("Trigger:",                                     12, pg, SmallFont); pg += 2;
  if (bHasTestSignal) {
               DrawStringSH1106("Test sig:",                      12, pg, SmallFont); pg += 2;
               if (bHasSigGen) {
               DrawStringSH1106("Signal Generator",                        12, pg, SmallFont); pg += 2;
               }
               DrawStringSH1106("Vcc:",                              12, pg, SmallFont); yVcc = pg; pg += 2;
  } else {
               if (bHasSigGen) {
               DrawStringSH1106("Vcc:",                              12, pg, SmallFont); yVcc = pg; pg += 2;
               DrawStringSH1106("Signal Generator",                        12, pg, SmallFont); pg += 2;
               } else {
               DrawStringSH1106("Vcc:",                              12, pg, SmallFont); yVcc = pg; pg += 2;
               }
  }
  BoldSH1106 = false;
  x = 62;
  pg = 3 + ofs;
  switch (adj[0]) {
               case 0:  DrawStringSH1106("1mS",   x, pg, SmallFont); break;
               case 1:  DrawStringSH1106("2mS",   x, pg, SmallFont); break;
               case 2:  DrawStringSH1106("5mS",   x, pg, SmallFont); break;
               case 3:  DrawStringSH1106("10mS",  x, pg, SmallFont); break;
               case 4:  DrawStringSH1106("20mS",  x, pg, SmallFont); break;
               case 5:  DrawStringSH1106("50mS",  x, pg, SmallFont); break;
               case 6:  DrawStringSH1106("100mS", x, pg, SmallFont); break;
  }
  pg += 2;
  switch (adj[1]) {
               case DC5V:          DrawStringSH1106("5V DC",            x, pg, SmallFont); break;
               case AC500mV:   DrawStringSH1106("0.5V AC",         x, pg, SmallFont); break;
               case AC100mV:   DrawStringSH1106("0.1V AC",         x, pg, SmallFont); break;
               case AC20mV:     DrawStringSH1106("20mV AC",       x, pg, SmallFont); break;
               //case mLogic:      DrawStringSH1106("Logic",              x, pg, SmallFont); break;
               //case mFreqLogic: DrawStringSH1106("Freq Logic", x, pg, SmallFont); break;
               //case mFreqAC:  DrawStringSH1106("Freq AC",         x, pg, SmallFont); break;
               case mVoltmeter: DrawStringSH1106("Voltmeter",  x, pg, SmallFont); break;
  }
  pg += 2;
 switch (adj[2]) {
               case 1:  DrawStringSH1106("Fall", x, pg, SmallFont); break;
               default: DrawStringSH1106("Rise", x, pg, SmallFont);
  }
  pg += 2;
  if (bHasTestSignal) {
               switch (adj[3]) {
               case 1:  DrawStringSH1106("31250Hz 32uS", x, pg, SmallFont); break;
               case 2:  DrawStringSH1106("3906Hz 256uS", x, pg, SmallFont); break;
               case 3:  DrawStringSH1106("977Hz 1024uS", x, pg, SmallFont); break;
               case 4:  DrawStringSH1106("488Hz 2048uS", x, pg, SmallFont); break;
               case 5:  DrawStringSH1106("244Hz 4096uS", x, pg, SmallFont); break;
               case 6:  DrawStringSH1106("122Hz 8192uS", x, pg, SmallFont); break;
               case 7:  DrawStringSH1106("31Hz 32768uS", x, pg, SmallFont); break;
               default: DrawStringSH1106("Off",                   x, pg, SmallFont);
               }
               pg += 2;
  }
  if (bHasSigGen)
               pg += 2;
  if (yVcc <= 7) {
               x += DrawIntDP2(readVcc() / 10, x, yVcc, SmallFont);
               DrawCharSH1106('V', x, yVcc, SmallFont);
  }
}

اندازه گیری فرکانس با استفاده از منطق تایمر پیچیده در زیر انجام می شود:

//=========================================================================
// Timer1 overflows every 65536 counts
//=========================================================================
ISR (TIMER1_OVF_vect)
{
  FC_overflowCount++;
}
//=========================================================================
// Timer1 Capture interrupt
//             invoked by comparator
//             read the current timer1 capture value
//             used in freq meter
//=========================================================================
ISR (TIMER1_CAPT_vect) {
  // grab counter value before it changes any more
  unsigned int timer1CounterValue = ICR1;  // see datasheet, page 117 (accessing 16-bit registers)
  unsigned long overflowCopy = FC_overflowCount;
  unsigned long t;
  static unsigned long prevT;
  // if just missed an overflow
  if ((TIFR1 & bit(TOV1)) && timer1CounterValue < 0x7FFF)
               overflowCopy++;
  t = (overflowCopy << 16) + timer1CounterValue;
  if ((!FC_firstAC) && (t-prevT > 100) && (t-prevT > FC_MaxPeriodAC))
               FC_MaxPeriodAC = t-prevT;
  prevT = t;
  FC_firstAC = false;
}
//=========================================================================
//  Timer0 Interrupt Service is invoked by hardware Timer0 every 1ms = 1000 Hz
//  used by frequancy counter
//  called every 1mS
//=========================================================================
ISR(TIMER0_COMPA_vect) {
  if (FC_Timeout >= FC_LogicPeriod) {           // end of gate time, measurement ready
               TCCR1B &= ~7;                  // Gate Off  / Counter T1 stopped
               bitClear(TIMSK0, OCIE0A);              // disable Timer0 Interrupt
               FC_OneSec = true;            // set global flag for end count period
               // calculate now frequeny value
               FC_freq = 0x10000 * FC_overflowCount; // mult #overflows by 65636
               FC_freq += TCNT1;            // add counter1 value
  }
  FC_Timeout++;                 // count number of interrupt events
  if (TIFR1 & 1) {                  // if Timer/Counter 1 overflow flag
               FC_overflowCount++;                       // count number of Counter1 overflows
               bitSet(TIFR1, TOV1);        // clear Timer/Counter 1 overflow flag
  }
}
//=========================================================================
// FC_InitLogic
//   count number of rising edges at D5 over mS period
//=========================================================================
void FC_InitLogic() {
  noInterrupts ();
  TIMSK0 = 0x00;
  delayMicroseconds(50);  // wait if any ints are pending
  FC_OneSec = false;                        // reset period measure flag
  FC_Timeout = 0;             // reset interrupt counter
  TCCR1A = 0x00; // timer output off
  TCCR1B = 0x07; // External clock source on T1 pin. Clock on rising edge.
  TCNT1 = 0x00; // counter = 0
  TCCR0A = 0x02; // compare output off; max count = OCRA
  TCCR0B = 0x03; // input clk is 16M/64
  TCNT0 = 0x16; // counter = 0 - why is this not 0? cos of set-up time?
  TIMSK0 = 0x00;
  OCR0A = 248; // max count value = CTC divider by 250 = 1mS
  GTCCR = 0x02; // reset prescaler
  FC_overflowCount = 0;
  bitSet(TIMSK0, OCIE0A);               // enable Timer0 Interrupt
  interrupts ();
}
//=========================================================================
// FC_InitAC
//   ACfreqAdcPin = 0..5 - use that ADC mux and measure period with Timer1
//=========================================================================
void FC_InitAC() {
  noInterrupts ();
  FC_disable();
  TCCR1A = 0; // reset Timer 1
  TCCR1B =  bit(CS10) | bit(ICES1);  // no prescaler, Input Capture Edge Select
  TIFR1 = bit(ICF1) | bit(TOV1);  // clear flags so we don't get a bogus interrupt
  TCNT1 = 0;        // Timer1 to zero
  FC_overflowCount = 0;  // for Timer1 overflows
  TIMSK1 = bit(TOIE1) | bit(ICIE1);   // interrupt on Timer 1 overflow and input capture
  ADCSRA = 0;
  DIDR1 = 1; // digital input of D6 is off
  ADMUX = ACfreqAdcPin;
  ACSR = bit(ACI)  | bit(ACIC) | (B10 << ACIS0); // "clear" interrupt flag; timer capture from comparator; falling edge
  ADCSRB = bit(ACME); // Comparator connected to ADC mux
  FC_firstAC = true;
  FC_Timeout = 0;
  FC_MaxPeriodAC = 0;
  interrupts ();
}
//=========================================================================
// FC_disable
//   turn off freq counter interrupts
//=========================================================================
void FC_disable() {
  TCCR0A = 0x03; // no compare output; Fast PWM up to 0xFF
  TCCR0B = 0x03; // no Output Compare; prescaler = 16MHz/64; overflow approx every 1mS
  TIMSK0 = 0x00; // Interrupt Mask Register = none
  GTCCR = 0x00; // Control Register = none
  OCR0A = 0x00; // Output Compare Register A = none
  OCR0B = 0x00; // Output Compare Register B = none
  TCCR1A = 0xC0;
  TCCR1B = 0x05;
  TCCR1C = 0x00;
  TIMSK1 = 0x00;
}
//=========================================================================
// FC_OneSecPassed
//   has 1 second passed?
//=========================================================================
bool FC_OneSecPassed() {
  static byte prevTimer1 = 0;
  byte i;
  static unsigned long t = 0;
  if (bitRead(TIFR0, TOV0)) // overflow every 1mS
               FC_Timeout++;
  bitSet(TIFR0, TOV0);
  return FC_Timeout > 1000;
}
//=========================================================================
// FC_CheckLogic
//   frequency measurer
//   call repeatedly
//   returns true when has timed out
//   result in FC_freq
//=========================================================================
bool FC_CheckLogic() {
  return FC_OneSec;
}
//=========================================================================
// FC_CheckAC
//   frequency measurer
//   call repeatedly
//   returns true when has timed out
//   result in FC_freq
//=========================================================================
bool FC_CheckAC() {
  unsigned long FC_elapsedTime;
  if (FC_OneSecPassed()) {
               if (FC_MaxPeriodAC > 0)
               FC_freq = 100 * F_CPU*1.004 / FC_MaxPeriodAC; // mult by 100 so can display 2 d.p.
               else
               FC_freq = 0;
               FC_InitAC();
               return true;
  }
  return false;
}
//-----------------------------------------------------------------------------
// myDelay
//   delays for approx mS milliSeconds
//   doesn't use any timers
//   doesn't affect interrupts
//-----------------------------------------------------------------------------
void myDelay(int mS) {
  for (int j = 0; j < mS; j++)
               delayMicroseconds(1000);
}
//-----------------------------------------------------------------------------
// MeasureVoltmeter
//   measures Voltmeter at Vin in mV
//   assumes resistors have been connected to pin:
//             Ra from pin to 5V
//             Rb from pin to 0V
//             Rc from pin to Vin
//----------------------------------------------------------------------------

در setup واحد های UART، ADC، OLED راه اندازی می شود، بافر حافظه تنظیم می شود و ارتباط I2C شروع می شود.

void setup (void) {
  // Open serial port with a baud rate of BAUDRATE b/s
  Serial.begin(baud rate);
  // Clear buffers
  memset( (void *)commandBuffer, 0, sizeof(commandBuffer) );
  // Activate interrupts
  sei();
  initADC();
  Serial.println("ArdOsc " __DATE__); // compilation date
  Serial.println("OK");
  setMode(0); // y-gain 5V
  setSweep(5);
  setPwmFrequency(testSignalPin, 3); // test signal 976Hz 1024uS
  pinMode(BtnHorz, INPUT_PULLUP);
  pinMode(BtnVert, INPUT_PULLUP);
  pinMode(LED_BUILTIN, OUTPUT);
  Wire.begin(); // join i2c bus as master
               TWBR = 1; // freq=888kHz period=1.125uS
  initSH1106();
}

در void loop، برنامه به حالت سوئیچ بستگی دارد که در چه حالتی قرار دارد.

void setup (void) {
  // Open serial port with a baud rate of BAUDRATE b/s
  Serial.begin(baud rate);
  // Clear buffers
  memset( (void *)commandBuffer, 0, sizeof(commandBuffer) );
  // Activate interrupts
  sei();
  initADC();
  Serial.println("ArdOsc " __DATE__); // compilation date
  Serial.println("OK");
  setMode(0); // y-gain 5V
  setSweep(5);
  setPwmFrequency(testSignalPin, 3); // test signal 976Hz 1024uS
  pinMode(BtnHorz, INPUT_PULLUP);
  pinMode(BtnVert, INPUT_PULLUP);
  pinMode(LED_BUILTIN, OUTPUT);
  Wire.begin(); // join i2c bus as master
               TWBR = 1; // freq=888kHz period=1.125uS
  initSH1106();
}

نحوه کار اسیلوسکوپ OLED آردوینو

تمام قطعات روی برد لحیم شده و با استفاده از کابل USB تغذیه می شوند و امواج مختلف در مقابل ورودی تست می شوند.

در ادامه تصاویری از موج سینوسی، موج مربعی و موج مثلثی وجود دارد.

نحوه کار اسیلوسکوپ OLED آردوینو

کار کامل این پروژه را می توانید در ویدیوی لینک شده در پایین همین صفحه مشاهده کنید. علاوه بر این، این پروژه را می توان به یک مینی اسیلوسکوپ مبتنی بر کانکتور BNC با عملکرد باتری تغییر و بهبود داد. اگر ایده های بیشتری دارید آنها را در بخش نظرات مطرح کنید و برای هر گونه سوال، می توانید از انجمن های ما استفاده کنید.

موارد موجود در فایل : سورس کامل
5/5 - (1 امتیاز)

برای دریافت مطالب جدید کانال تلگرام یا پیج اینستاگرام آیرنکس را دنبال کنید.
تصویر از محمد رحیمی

محمد رحیمی

محمد رحیمی هستم. سعی میکنم در آیرنکس مطالب مفید قرار بدهم. سوالات مربوط به این مطلب را در قسمت نظرات همین مطلب اعلام کنید. سعی میکنم در اسرع وقت به نظرات شما پاسخ بدهم.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد.