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

اسیلوسکوپ یک دستگاه تست الکترونیکی است که می تواند تغییرات ثابت هر ولتاژ الکتریکی را با استفاده از نمودارهای دو بعدی که در آن تغییر یک یا چند ولتاژ الکتریکی در طول زمان در محور عمودی Y قرار می گیرد، نظارت کند. به طور کلی، هر مهندس الکترونیک یا کسی که علاقه مند به کار با الکترونیک است، در برخی مواقع به یک اسیلوسکوپ نیاز دارد. با این حال، یک اسیلوسکوپ برای دانش آموزان، بسیار گران است و به همین دلیل است که در این مقاله به نحوه ساخت یک مینی اسیلوسکوپ در خانه با استفاده از آردوینو می پردازیم.
ما قبلا پروژه ساخت اسیلوسکوپ با رزبری پای و اسیلوسکوپ با پایتون و آردوینو را طراحی کرده ایم.
در این مقاله، ما یک اسیلوسکوپ ساده و کمهزینه مبتنی بر آردوینو با نمایشگر OLED 1.3 اینچی میسازیم که میتواند برای تجسم دقیق شکلهای موج استفاده شود.
مدار اسیلوسکوپ نمایشگر 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 تغذیه می شوند و امواج مختلف در مقابل ورودی تست می شوند.
در ادامه تصاویری از موج سینوسی، موج مربعی و موج مثلثی وجود دارد.
کار کامل این پروژه را می توانید در ویدیوی لینک شده در پایین همین صفحه مشاهده کنید. علاوه بر این، این پروژه را می توان به یک مینی اسیلوسکوپ مبتنی بر کانکتور BNC با عملکرد باتری تغییر و بهبود داد. اگر ایده های بیشتری دارید آنها را در بخش نظرات مطرح کنید و برای هر گونه سوال، می توانید از انجمن های ما استفاده کنید.