示波器是一種電子測試設備,可以使用二維圖形監(jiān)控任何電壓的穩(wěn)定變化,其中一個或多個電壓隨時間的變化位于垂直 Y 軸上。一般來說,每個電子愛好者或對電子產品感興趣的人都會在某些時候需要示波器。然而,對于學生和業(yè)余愛好者來說,它的價格非常昂貴,這就是為什么在本文中我們將討論如何使用 Arduino 在家里制作迷你示波器。
在本文中,我們將構建一個簡單、低成本的基于 Arduino 的示波器,該示波器具有 1.3“ OLED 顯示屏,可用于準確顯示波形。該項目的靈感來自于火柴盒項目中的Peter Balch Oscilloscope。我們更改了很少的代碼和硬件可以滿足我們的要求。
構建基于 Arduino 的示波器所需的材料
使用Arduino Nano制作這款便攜式迷你示波器需要以下組件。
Arduino示波器的電路圖
構建基于 Arduino 的示波器的原理圖非常簡單,只需要幾個部件,您可以查看下面的完整電路圖。
原理圖的主要部分使用單個運算放大器 IC,即LM358,它在單個芯片內包含兩個運算放大器。由于輸入信號為交流信號,并且我們沒有分軌結構,因此有兩個運算放大器(來自單個運算放大器 8 引腳封裝)用于使信號交流耦合。兩個運算放大器都被饋入用于偏移信號的參考電壓,并使用模擬輸入將其繪制在示波器圖上。可以使用電位器(具有 100K 電阻)更改偏移量。兩個運算放大器都設置有相同的負反饋和 x5 增益設置。
除此之外,OLED 通過 A4 連接,A5 是帶有 4.7K 上拉電阻的 I2C SCL 和 SDA 引腳。它可以使用簡單的 USB 連接器。這些按鈕用于設置示波器的參數。我們在性能板上構建了完整的電路,當我完成設置時,它看起來像這樣。
Arduino示波器——代碼說明
編碼部分很復雜。要了解編碼的工作原理,請查看以下代碼片段 -
首先,Oscope 的庫是從 Peter Balch 的SimpleSH1106.h庫中使用的。對于使用 SH1106 芯片組的 OLED,它是一個非??焖俚膸臁?/p>
這些庫在以下幾行中定義。
?
#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 等待串行緩沖區(qū)的填充 常量 int COMBUFFERSIZE = 4; // 傳入數字的緩沖區(qū)大小 常量 int testSignalPin = 3; 常量字符確認 = '@'; // 確認通訊命令 常量字節(jié) SampPerA = 5 + 6; // 6 次 #define LoopNops __asm__("nop\n nop\n nop\n nop\n nop\n nop\n") 常量 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; 字符命令緩沖區(qū)[COMBUFFERSIZE + 1]; 布爾 TrigFalling = true; uint8_t curSweep = 0; 字節(jié) yGraticulePage0,yGraticuleByte0,yGraticulePage1,yGraticuleByte1,yGraticulePage2,yGraticuleByte2; 字節(jié)* pxGratLabel; 字節(jié)* pyGratLabel; 字節(jié) xGratLabelLen,yGratLabelLen; 字節(jié) yGraticule0,yGraticule1,yGraticule2,xGraticule1,xGraticule2; TmenuSel sel = sTime;// 用于主菜單 字節(jié) adj[4] = {0, 0, 0, 0}; // 用于主菜單 bool SendingSerial = false; int curPwmMode = 0; 常量 int ADCBUFFERSIZE = 128; uint8_t ADCBuffer[ADCBUFFERSIZE]; int ButtonsTimer1 = 0; 長 Vin = 0; // 用于顯示電壓表
?
菜單上的圖像在這里聲明 -
?
/---------------------------------------------------- ---------------------------- // 主菜單的圖像 //------------------------------------------------ ----------------------------------------- 常量字節(jié) 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 }; 常量字節(jié) imgMainMenuMid[] PROGMEM = { 128, // 寬度 1, // 頁面 1、255、254、0、1、255 }; 常量字節(jié) imgMainMenuBot[] PROGMEM = { 128, // 寬度 1, // 頁面 1、255、254、128、1、255 }; 常量字節(jié) imgBoxTop[] PROGMEM = { 128, // 寬度 1, // 頁面 1、248、254、8、1、248 }; 常量字節(jié) imgCaret1[] PROGMEM = { 4, // 寬度 1, // 頁面 4、255、126、60、24 }; 常量字節(jié) imgCaret2[] PROGMEM = { 7, // 寬度 1, // 頁面 7、32、48、56、60、56、48、32 }; 常量字節(jié) 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}; 常量字節(jié) 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}; 常量字節(jié) 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 // 創(chuàng)建一個必須是 'page' 一部分的欄 // 返回柱 //------------------------------------------------ ----------------------------------------- 字節(jié)填充條(字節(jié) y1,字節(jié) y2,字節(jié)頁){ 靜態(tài)字節(jié) lob[] = {0x00、0x01、0x03、0x07、0x0F、0x1F、0x3F、0x7F、0xFF}; 字節(jié)條; 如果(頁面 == 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 毫秒 //------------------------------------------------ ----------------------------------------- 無效畫屏(無效){ 字節(jié) i, j, k, y, yPrev, bar, page, lastDrawn; 字節(jié)* pxbz; 字節(jié)* pybz; 字節(jié) 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緩沖區(qū)[1]; 對于 ( i = 1; i < ADCBUFFERSIZE - 1; i++ ) { ADCBuffer[i] = (yPrev + y + ADCBuffer[i + 1]) / 3; yPrev = y; y = ADC緩沖區(qū)[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); //后面的字節(jié)是數據 對于 (i = 0; i < ADCBUFFERSIZE; i++) { 如果 (i % 26 == 0) { Wire.endTransmission(); Wire.beginTransmission(addr); Wire.write(0x40); //后面的字節(jié)是數據 } y = ADC緩沖區(qū)[i]; 如果 (yPrev > y + 1) { if (yPrev == lastDrawn) yPrev--; bar = FillBar(y + 1, yPrev, page); 最后繪制 = yPrev + 1; } 別的 { bar = FillBar(yPrev, yPrev, page); lastDrawn = yPrev; } // } if ((page == 0) && (bar == 0x01) && (i & 1)) 條 = 0; if ((page == 7) && (bar == 0x80) && (i & 1)) 條 = 0; if (page == yGraticulePage0) { 如果 (i & 8) 酒吧 = 酒吧 | y刻度字節(jié)0; } 否則 if (page == yGraticulePage1) { 如果 (i < pylenz) { 酒吧 |= *pybz; pybz++; } 否則如果 (i % 4 == 0) 條形|= yGraticuleByte1; } 否則 if (page == yGraticulePage2) { 如果 (i % 4 == 0) 條 |= yGraticuleByte2; } 如果 ((i == xGraticule1) | (i == xGraticule2)) 酒吧 = 酒吧 | 0x22; if ((page == 7) && (i > xGraticule2 - pxlenz - 2) && (i < xGraticule2 - 1)) { 條形 |= *pxbz; pxbz++; } Wire.write(bar); yPrev = y; } Wire.endTransmission(); } }
?
ADC 在這里聲明 -
?
//------------------------------------------------ ----------------------------------------- // 初始化ADC() //------------------------------------------------ ----------------------------------------- 無效初始化ADC(無效){ 如果(curMode > AC20mV) 返回; ACSR = 0x10; ADCSRA = 0x97; ADCSRB = 0x0 ; //ADC控制和狀態(tài)寄存器B // 0 位 6 – ACME:模擬比較器多路復用器使能 // 000 位 2:0 – ADTSn:ADC 自動觸發(fā)源 [n = 2:0] 自由運行模式 ADMUX = 0x20 + (curVref << 6) + curMode;// ADC 多路復用器選擇寄存器 // rr 位 7:6 – REFSn:參考選擇 = Vcc // 1 位 5 – ADLAR:ADC 左調整結果 // aaaa 位 3:0 – MUXn:模擬通道選擇 DIDR0 = 0x3F;// 數字輸入禁用寄存器 0 // ADC0D=1, ADC1D=1, ADC2D=1, ADC3D=1, ADC4D=1, ADC5D=1, ADC6D=0, ADC7D=0 }
?
屏幕上信號的掃描在下面聲明 -
?
//------------------------------------------------ ----------------------------------------- // 設置掃描 // 設置周期和 ADC 預分頻器 //------------------------------------------------ ----------------------------------------- 無效 setSweep(字節(jié)掃描){ 詮釋 x; 長 t; 如果(掃一掃 == 255){ 如果(curSweep == 0) curSweep = 6; 別的 curSweep——; } 別的 curSweep = 掃描; 開關(curSweep){ 情況 0:curPeriod = 0;curPrescaler = 2; t = 100;pxGratLabel = &ax0_1[0]; xGratLabelLen = sizeof(ax0_1); 休息; 案例1:curPeriod = 4;curPrescaler = 2; t = 400;pxGratLabel = &ax0_4[0]; xGratLabelLen = sizeof(ax0_4); 休息; 案例2:curPeriod = 11;curPrescaler = 3; t = 1000;pxGratLabel = &ax1[0]; xGratLabelLen = sizeof(ax1); 休息; 案例 3:curPeriod = 24;curPrescaler = 3; t = 2000;pxGratLabel = &ax2[0]; xGratLabelLen = sizeof(ax2); 休息; 案例4:curPeriod = 62;curPrescaler = 4; t = 5000;pxGratLabel = &ax5[0]; xGratLabelLen = sizeof(ax5); 休息; 案例5:curPeriod = 125;curPrescaler = 4; t = 10000;pxGratLabel = &ax10[0]; xGratLabelLen = sizeof(ax10); 休息; 案例6:curPeriod = 255;curPrescaler = 5; t = 20000;pxGratLabel = &ax20[0]; xGratLabelLen = sizeof(ax20); 休息; } 如果(curSweep == 0) x = t; 別的 x = 16 * t / (curPeriod * SampPerA + SampPerB); x格線1 = x / 2; x格線2 = x; 發(fā)送確認(); } //------------------------------------------------ ----------------------------------------- // 掃一掃 // 連續(xù)掃描 siggen freq // 整個掃描需要 n mS // SDC regs 被保存和恢復 // 當接收到一個串行字符時停止 //------------------------------------------------ ----------------------------------------- 無效掃描(int n){ 字節(jié)舊ACSR = ACSR; 字節(jié)舊ADCSRA = ADCSRA; 字節(jié)老ADCSRB = ADCSRB; 字節(jié)老ADMUX = ADMUX; 字節(jié)老DIDR0 = DIDR0; 字節(jié)老DIDR1 = DIDR1; 整數 fmin,fmax; fmin = calcFreq(freqSGLo); fmax = calcFreq(freqSGHi); 詮釋我=0; 做 { 長 f = exp((log(fmax) - log(fmin))*i/(n-1) + log(fmin)) +0.5; SG_freqSet(f, waveType); 延遲(1); 我++; 如果 (i >= n) i = 0; } 而 (!Serial.available()); SG_freqSet(calcFreq(freqSGLo), waveType); ACSR = 舊ACSR; ADCSRA = 舊ADCSRA; ADCSRB = 舊ADCSRB; ADMUX = oldADMUX; DIDR0 = 舊DIDR0; DIDR1 = 舊DIDR1; }
?
按鈕增量和模式設置在下面完成 -
?
//------------------------------------------------ ----------------------------------------- // INC模式 // 遞增模式 // 從最大值環(huán)繞 // 跳過不允許的模式 //------------------------------------------------ ----------------------------------------- int incMode(int mode) { 模式++; //if ((mode == mLogic) && (!bHasLogic)) mode++; // if ((mode == mFreqLogic) && ((!bHasFreq) || (!bHasLogic))) mode++; // if ((mode == mFreqAC) && (!bHasFreq)) mode++; if ((mode == mVoltmeter) && (!bHasVoltmeter)) mode++; 如果(模式 > 最大模式) 返回DC5V; 別的 返回模式; } //------------------------------------------------ ----------------------------------------- // 設置模式 // 設置模式和 Vref //------------------------------------------------ ----------------------------------------- 無效 setMode(int 模式){ 詮釋我; 如果(模式 == 255){ curMode = incMode(curMode); } 別的 curMode = 模式; 開關(curMode){ 案例 DC5V: curVref = 1; i = (long)4000 * 64 / readVcc(); 如果(我 <= 63){ y刻度1 = 63 - i; y 刻度 2 = 63 - i / 2; y刻度線0 = 255; pyGratLabel = &ax4V[0]; yGratLabelLen = sizeof(ax4V); } 別的 { y刻度2 = 63 - i; y 刻度 1 = 63 - i / 2; y刻度線0 = 255; pyGratLabel = &ax2V[0]; yGratLabelLen = sizeof(ax2V); } 休息; 案例 AC500mV: curVref = 3; i = (字節(jié))(0.5 / 1.1 * 256 / 4); y刻度1 = 32 - i; y刻度2 = 32 + i; y刻度線0 = 32; pyGratLabel = &ax0_5[0]; yGratLabelLen = sizeof(ax0_5); 休息; 案例 AC100mV: curVref = 3; i = (字節(jié))(0.1 / 1.1 * (R1 + R2) / R2 * 256 / 4); y刻度1 = 32 - i; y刻度2 = 32 + i; y刻度線0 = 32; pyGratLabel = &ax0_1[0]; yGratLabelLen = sizeof(ax0_1); 休息; 案例AC20mV: curVref = 3; i = (字節(jié))(0.02 / 1.1 * (R1 + R2) / R2 * (R1 + R2) / R2 * 256 / 4); y刻度1 = 32 - i; y刻度2 = 32 + i; y刻度線0 = 32; pyGratLabel = &ax20[0]; yGratLabelLen = sizeof(ax20); 休息; 默認: curVref = 1; y 格線 1 = 255; y 格線 2 = 255; y刻度線0 = 255; pyGratLabel = &ax20[0]; yGratLabelLen = sizeof(ax20); 休息; }
?
主菜單的繪制是使用以下代碼片段完成的 -
?
無效drawMainMenu(無效){ int ofs, x, yVcc, pg; 開關(選擇){ 案例 sMode: ofs = -1; 休息; 案例 sTrigger: ofs = -2; 休息; 案例 sTestSig: ofs = -5; 休息; 案例 sSigGen: ofs = bHasTestSignal ? -7:-5;休息; 默認值:ofs = 0; } // DrawImageSH1106(0,ofs,imgMainMenu); DrawImageSH1106(0, ofs + 0, imgMainMenuTop); 對于 (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); 粗體SH1106 = 真; pg = 3 + ofs; DrawStringSH1106("時間:", 12, pg, SmallFont); pg += 2; DrawStringSH1106((adj[1] <= AC20mV ? "Gain:" : "Mode:"), 12, pg, SmallFont); pg += 2; DrawStringSH1106("觸發(fā)器:", 12, pg, SmallFont); pg += 2; 如果(bHasTestSignal){ DrawStringSH1106("測試簽名:", 12, pg, SmallFont); pg += 2; 如果(bHasSigGen){ DrawStringSH1106("信號發(fā)生器", 12, pg, SmallFont); pg += 2; } DrawStringSH1106("Vcc:", 12, pg, SmallFont); yVcc = pg; pg += 2; } 別的 { 如果(bHasSigGen){ DrawStringSH1106("Vcc:", 12, pg, SmallFont); yVcc = pg; pg += 2; DrawStringSH1106("信號發(fā)生器", 12, pg, SmallFont); pg += 2; } 別的 { DrawStringSH1106("Vcc:", 12, pg, SmallFont); yVcc = pg; pg += 2; } } 粗體SH1106 = 假; x = 62; pg = 3 + ofs; 開關(adj[0]){ 案例 0: DrawStringSH1106("1mS", x, pg, SmallFont); 休息; 案例一:DrawStringSH1106("2mS", x, pg, SmallFont); 休息; 案例2:DrawStringSH1106("5mS", x, pg, SmallFont); 休息; 案例 3:DrawStringSH1106("10mS", x, pg, SmallFont); 休息; 案例4:DrawStringSH1106("20mS", x, pg, SmallFont); 休息; 案例5:DrawStringSH1106("50mS", x, pg, SmallFont); 休息; 案例 6:DrawStringSH1106("100mS", x, pg, SmallFont); 休息; } pg += 2; 開關(adj[1]){ 案例 DC5V: DrawStringSH1106("5V DC", x, pg, SmallFont); 休息; 案例 AC500mV: DrawStringSH1106("0.5V?? AC", x, pg, SmallFont); 休息; 案例 AC100mV: DrawStringSH1106("0.1V AC", x, pg, SmallFont); 休息; 案例 AC20mV: DrawStringSH1106("20mV AC", x, pg, SmallFont); 休息; //case mLogic: DrawStringSH1106("Logic", x, pg, SmallFont); 休息; //case mFreqLogic: DrawStringSH1106("頻率邏輯", x, pg, SmallFont); 休息; //case mFreqAC: DrawStringSH1106("Freq AC", x, pg, SmallFont); 休息; case mVoltmeter: DrawStringSH1106("Voltmeter", x, pg, SmallFont); 休息; } pg += 2; 開關(adj[2]){ 案例一:DrawStringSH1106("Fall", x, pg, SmallFont); 休息; 默認值:DrawStringSH1106("Rise", x, pg, SmallFont); } pg += 2; 如果(bHasTestSignal){ 開關(adj[3]){ 案例一:DrawStringSH1106("31250Hz 32uS", x, pg, SmallFont); 休息; 案例2:DrawStringSH1106("3906Hz 256uS", x, pg, SmallFont); 休息; 案例3:DrawStringSH1106("977Hz 1024uS", x, pg, SmallFont); 休息; 案例4:DrawStringSH1106("488Hz 2048uS", x, pg, SmallFont); 休息; 案例5:DrawStringSH1106("244Hz 4096uS", x, pg, SmallFont); 休息; 案例6:DrawStringSH1106("122Hz 8192uS", x, pg, SmallFont); 休息; 案例 7:DrawStringSH1106("31Hz 32768uS", x, pg, SmallFont); 休息; 默認值:DrawStringSH1106("Off", x, pg, SmallFont); } pg += 2; } 如果(bHasSigGen) pg += 2; 如果(yVcc <= 7){ x += DrawIntDP2(readVcc() / 10, x, yVcc, SmallFont); DrawCharSH1106('V', x, yVcc, SmallFont); } }
?
下面使用的按鈕處理程序 -
?
無效檢查按鈕(無效){ 常量字節(jié)超時 = 70;// 1 秒顯示菜單 靜態(tài)int prevHorz = HIGH; 靜態(tài)int prevVert = HIGH; 詮釋我; 如果(數字讀?。˙tnHorz)== 低){ if (prevHorz == HIGH) { 開關(curMode){ //case mFreqLogic: // 案例 mFreqAC: 案例毫伏表: 執(zhí)行菜單(); 返回; } 按鈕定時器1 = 0; 我的延遲(15); 設置掃描(255); prevHorz = 低; } 別的 { if (ButtonsTimer1 > timeout) { 執(zhí)行菜單(); 返回; } } } 別的 { prevHorz = 高; } 如果(數字讀?。˙tnVert)== 低){ if (prevVert == HIGH) { 按鈕定時器1 = 0; 我的延遲(15); 設置模式(255); prevVert = 低; } 別的 { if (ButtonsTimer1 > timeout) { 執(zhí)行菜單(); 返回; } } } 別的 { prevVert = 高; } }
?
頻率測量是使用下面的復雜定時器邏輯完成的 -
?
//================================================= ========================== // Timer1 每 65536 個計數溢出 //================================================= ========================== ISR (TIMER1_OVF_vect) { FC_overflowCount++; } //================================================= ========================== // Timer1 捕捉中斷 // 由比較器調用 //讀取當前timer1捕獲值 // 用于頻率計 //================================================= ========================== ISR (TIMER1_CAPT_vect) { // 在計數器值發(fā)生任何變化之前獲取它 無符號整數 timer1CounterValue = ICR1; // 參見數據表,第 117 頁(訪問 16 位寄存器) 無符號長溢出復制 = FC_overflowCount; 無符號長 t; 靜態(tài)無符號長prevT; // 如果剛剛錯過了溢出 if ((TIFR1 & bit(TOV1)) && timer1CounterValue < 0x7FFF) 溢出復制++; t = (overflowCopy << 16) + timer1CounterValue; if ((!FC_firstAC) && (t-prevT > 100) && (t-prevT > FC_MaxPeriodAC)) FC_MaxPeriodAC = t-prevT; 上一頁T = t; FC_firstAC = 假; } //================================================= ========================== // Timer0 中斷服務由硬件 Timer0 每 1ms = 1000 Hz 調用一次 // 由頻率計數器使用 // 每 1mS 調用一次 //================================================= ========================== ISR(TIMER0_COMPA_vect){ if (FC_Timeout >= FC_LogicPeriod) { // 門時間結束,測量準備就緒 TCCR1B &= ~7; // 門關閉/計數器 T1 停止 位清除(TIMSK0,OCIE0A);// 禁用 Timer0 中斷 FC_OneSec = 真;// 設置結束計數周期的全局標志 // 現在計算頻率值 FC_freq = 0x10000 * FC_overflowCount;// mult #overflows by 65636 FC_freq += TCNT1; // 添加 counter1 的值 } FC_超時++;// 計算中斷事件的數量 if (TIFR1 & 1) { // 如果定時器/計數器 1 溢出標志 FC_overflowCount++; // 計數 Counter1 溢出的次數 位集(TIFR1,TOV1);// 清除定時器/計數器 1 溢出標志 } } //================================================= ========================== // FC_InitLogic // 計算 D5 在 mS 周期內的上升沿數 //================================================= ========================== 無效 FC_InitLogic() { 無中斷(); TIMSK0 = 0x00; 延遲微秒(50);// 等待是否有任何 int 待處理 FC_OneSec = 假;// 重置周期測量標志 FC_Timeout = 0; // 重置中斷計數器 TCCR1A = 0x00;//定時器輸出關閉 TCCR1B = 0x07;// T1 引腳上的外部時鐘源。時鐘在上升沿。 TCNT1 = 0x00;// 計數器 = 0 TCCR0A = 0x02;// 比較輸出關閉;最大計數 = OCRA TCCR0B = 0x03;// 輸入時鐘為 16M/64 TCNT0 = 0x16;// counter = 0 - 為什么這不是 0?設置時間的成本? TIMSK0 = 0x00; OCR0A = 248; // 最大計數值 = CTC 除以 250 = 1mS GTCCR = 0x02;// 重置預分頻器 FC_overflowCount = 0; 位集(TIMSK0,OCIE0A);// 啟用 Timer0 中斷 中斷(); } //================================================= ========================== // FC_InitAC // ACfreqAdcPin = 0..5 - 使用該 ADC 多路復用器并使用 Timer1 測量周期 //================================================= ========================== 無效 FC_InitAC() { 無中斷(); FC_disable(); TCCR1A = 0;// 重置定時器 1 TCCR1B = 位(CS10)| 位(ICES1);// 無預分頻器,輸入捕捉邊沿選擇 TIFR1 = 位 (ICF1) | 位(TOV1);// 清除標志,這樣我們就不會收到虛假中斷 TCNT1 = 0; // Timer1歸零 FC_overflowCount = 0; // 對于 Timer1 溢出 TIMSK1 = 位(TOIE1)| 位(ICIE1);// 定時器 1 溢出和輸入捕捉中斷 ADCSRA = 0; DIDR1 = 1;// D6的數字輸入關閉 ADMUX = ACfreqAdcPin; ACSR = 位(ACI)| 位(ACIC) | (B10 << ACIS0); //“清除”中斷標志;比較器的定時器捕捉;下降沿 ADCSRB = 位(ACME);// 比較器連接到 ADC 多路復用器 FC_firstAC = 真; FC_Timeout = 0; FC_MaxPeriodAC = 0; 中斷(); } //================================================= ========================== // FC_disable //關閉頻率計數器中斷 //================================================= ========================== 無效 FC_disable() { TCCR0A = 0x03;// 沒有比較輸出;高達 0xFF 的快速 PWM TCCR0B = 0x03;// 沒有輸出比較;預分頻器 = 16MHz/64;大約每 1mS 溢出一次 TIMSK0 = 0x00;// 中斷屏蔽寄存器 = 無 GTCCR = 0x00;//控制寄存器=無 OCR0A = 0x00;// 輸出比較寄存器 A = none OCR0B = 0x00;// 輸出比較寄存器 B = none TCCR1A = 0xC0; TCCR1B = 0x05; TCCR1C = 0x00; TIMSK1 = 0x00; } //================================================= ========================== // FC_OneSecPassed // 1 秒過去了嗎? //================================================= ========================== bool FC_OneSecPassed() { 靜態(tài)字節(jié) prevTimer1 = 0; 字節(jié) i; 靜態(tài)無符號長 t = 0; if (bitRead(TIFR0, TOV0)) // 每 1mS 溢出 FC_超時++; 位集(TIFR0,TOV0); 返回 FC_Timeout > 1000; } //================================================= ========================== // FC_CheckLogic //頻率測量器 // 重復調用 // 超時返回true // 結果為 FC_freq //================================================= ========================== 布爾 FC_CheckLogic() { 返回 FC_OneSec; } //================================================= ========================== // FC_CheckAC //頻率測量器 // 重復調用 // 超時返回true // 結果為 FC_freq //================================================= ========================== 布爾 FC_CheckAC() { 無符號長 FC_elapsedTime; 如果(FC_OneSecPassed()){ 如果(FC_MaxPeriodAC > 0) FC_freq = 100 * F_CPU*1.004 / FC_MaxPeriodAC;// 乘以 100 所以可以顯示 2 dp 別的 FC_freq = 0; FC_InitAC(); 返回真; } 返回假; } //------------------------------------------------ ----------------------------------------- // 我的延遲 // 延遲大約 mS 毫秒 // 不使用任何計時器 // 不影響中斷 //------------------------------------------------ ----------------------------------------- 無效我的延遲(int毫秒){ 對于 (int j = 0; j < mS; j++) 延遲微秒(1000); } //------------------------------------------------ ----------------------------------------- // 測量電壓表 // 以 mV 為單位測量 Vin 處的電壓表 //假設電阻器已連接到引腳: // Ra 從引腳到 5V // Rb 從引腳到 0V // Rc 從引腳到 Vin //------------------------------------------------ ----------------------------
?
在設置中,UART、ADC、OLED 啟動,內存緩沖區(qū)設置,I2C 開始。
?
無效設置(無效){ // 打開串口,波特率為 BAUDRATE b/s Serial.begin(波特率); // 清除緩沖區(qū) memset( (void *)commandBuffer, 0, sizeof(commandBuffer) ); // 激活中斷 sei(); 初始化ADC(); Serial.println("ArdOsc " __DATE__); // 編譯日期 Serial.println("OK"); 設置模式(0);// y 增益 5V 設置掃描(5); setPwmFrequency(testSignalPin, 3); // 測試信號 976Hz 1024uS pinMode(BtnHorz,INPUT_PULLUP); pinMode(BtnVert,INPUT_PULLUP); pinMode(LED_BUILTIN,輸出); Wire.begin(); // 加入 i2c 總線作為主機 TWBR = 1; // 頻率=888kHz 周期=1.125uS 初始化SH1106(); }
?
在 void 循環(huán)中,循環(huán)取決于它所在模式的開關狀態(tài),按下按鈕選擇模式。
?
//------------------------------------------------ ----------------------------------------- // 主要例程 // 環(huán)形 //------------------------------------------------ ----------------------------------------- 無效循環(huán)(無效){ 靜態(tài) int ButtonsTimer2 = 0; 開關(curMode){ 案例毫伏表: 如果(檢查電壓表()) 畫屏(); 休息; 默認: if (!SendingSerial) { 發(fā)送ADC(); 開關(掃描類型){ 案例 sw20Frames: 案例 sw100Frames: 案例 sw500Frames: SG_StepSweep(); } } } 檢查按鈕(); }
?
Arduino示波器工作
所有組件都焊接在板上并使用 USB 電纜供電,并針對輸入測試不同的波。
以下是正弦波、方波和三角波的圖像。
這個項目的完整工作可以在視頻中找到,鏈接在這個頁面的底部。此外,該項目還可以修改和改進為基于 BNC 連接器的微型示波器,具有電池供電操作。如果您有更多想法,請將它們放在評論部分,并且有任何問題,您可以使用我們的論壇。
代碼
?
?
//------------------------------------------------ -----------------------------------------
// 版權所有 2018 彼得·巴爾奇
// 遵守 GNU 通用公共許可證
// 在 SH1106 屏幕上顯示樣本作為示波器
//------------------------------------------------ -----------------------------------------
#include
#include
#include "SimpleSH1106.h"
#include
//------------------------------------------------ -----------------------------------------
// 定義和類型定義
//------------------------------------------------ -----------------------------------------
// 獲取寄存器位 - 更快:不會把它變成 0/1
#ifndef 獲取比特
#define getBit(sfr, bit) (_SFR_BYTE(sfr) & _BV(bit))
#萬一
枚舉 Tmode {DC5V, AC500mV, AC100mV, AC20mV,
??????????? 邏輯邏輯,
??????????? 毫伏表,
??????????? 最大模式1
?????????? };
常量 Tmode maxMode = maxMode1 - 1;
枚舉 TmenuSel {sTime, sMode, sTrigger, sTestSig, sSigGen};
//------------------------------------------------ -----------------------------------------
// 全局常量
//------------------------------------------------ -----------------------------------------
布爾 bHasLogic = true;
布爾 bHasFreq = true;
布爾 bHasVoltmeter = true;
布爾 bHasTestSignal = true;
布爾 bHasSigGen = 假;
常量長波特率 = 115200; // UART 的波特率,單位 bps
常量 int 命令延遲 = 10; // ms 等待串行緩沖區(qū)的填充
常量 int COMBUFFERSIZE = 4; // 傳入數字的緩沖區(qū)大小
常量 int testSignalPin = 3;
常量字符確認 = '@'; // 確認通訊命令
常量字節(jié) SampPerA = 5 + 6; // 6 次
#define LoopNops __asm__("nop\n nop\n nop\n nop\n nop\n nop\n")
常量 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;
字符命令緩沖區(qū)[COMBUFFERSIZE + 1];
布爾 TrigFalling = true;
uint8_t curSweep = 0;
字節(jié) yGraticulePage0,yGraticuleByte0,yGraticulePage1,yGraticuleByte1,yGraticulePage2,yGraticuleByte2;
字節(jié)* pxGratLabel;
字節(jié)* pyGratLabel;
字節(jié) xGratLabelLen,yGratLabelLen;
字節(jié) yGraticule0,yGraticule1,yGraticule2,xGraticule1,xGraticule2;
TmenuSel sel = sTime;// 用于主菜單
字節(jié) adj[4] = {0, 0, 0, 0}; // 用于主菜單
bool SendingSerial = false;
int curPwmMode = 0;
常量 int ADCBUFFERSIZE = 128;
uint8_t ADCBuffer[ADCBUFFERSIZE];
int ButtonsTimer1 = 0;
長 Vin = 0; // 用于顯示電壓表
//------------------------------------------------ -----------------------------------------
// SigGen 中使用的全局變量
//------------------------------------------------ -----------------------------------------
常量字節(jié) numberOfDigits = 6; // 頻率的位數
字節(jié)頻率SGLo[numberOfDigits] = {0, 0, 0, 1, 0, 0}; // 1000Hz SelSG = 0..numberOfDigits-1
字節(jié)頻率SGHi[numberOfDigits] = {0, 0, 0, 0, 2, 0}; // 20kHz SelSG = numberOfDigits..2*numberOfDigits-1
字節(jié) SelSG = numberOfDigits-1;
常量字節(jié) SelSGSweep = 2*numberOfDigits;
常量字節(jié) SelSGSine = 2*numberOfDigits+1;
常量 int wSine = 0b0000000000000000;
const int wTriangle = 0b0000000000000010;
常量 int wSquare = 0b0000000000101000;
枚舉 TsweepType {swOff,sw20Frames,sw100Frames,sw500Frames,sw1Sec,sw5Sec,sw20Sec};
int waveType = wSine;
TsweepType sweepType = swOff;
常量 int SG_fsyncPin = 2;
常量 int SG_CLK = 13;
常量 int SG_DATA = 12;
int SG_iSweep,SG_nSweep;
//------------------------------------------------ -----------------------------------------
// 頻率計數器中使用的全局變量
//------------------------------------------------ -----------------------------------------
volatile boolean FC_OneSec;
volatile boolean FC_firstAC;
volatile unsigned long FC_overflowCount;
volatile unsigned long FC_MaxPeriodAC;
無符號長 FC_Timeout = 0;
無符號長 FC_freq;
常量 int ACfreqAdcPin = 3;
常量 int FC_LogicPeriod = 1006; // mS 略長于 1 秒進行校準
//------------------------------------------------ -----------------------------------------
// 刻度標簽
//------------------------------------------------ -----------------------------------------
常量 int R1 = 100;
常量 int R2 = 27;
常量字節(jié) ax2V[] = {98, 81, 73, 70, 0, 3, 28, 96, 28, 3};
常量字節(jié) ax4V[] = {24, 22, 127, 16, 0, 3, 28, 96, 28, 3};
常量字節(jié) ax0_1[] = {62, 65, 65, 62, 0, 64, 0, 2, 127 };
常量字節(jié) ax0_2[] = {62, 65, 65, 62, 0, 64, 0, 98, 81, 73, 70 };
常量字節(jié) ax0_4[] = {62, 65, 65, 62, 0, 64, 0, 24, 22, 127, 16 };
常量字節(jié) ax0_5[] = {62, 65, 65, 62, 0, 64, 0, 47, 69, 69, 57};
常量字節(jié) ax1[] = {2, 127 };
常量字節(jié) ax2[] = {98, 81, 73, 70 };
常量字節(jié) ax4[] = {24, 22, 127, 16 };
常量字節(jié) ax5[] = {47, 69, 69, 57};
常量字節(jié) ax10[] = {2, 127, 0, 62, 65, 65, 62 };
常量字節(jié) ax20[] = {98, 81, 73, 70, 0, 62, 65, 65, 62 };
//------------------------------------------------ -----------------------------------------
//主菜單的圖像
//------------------------------------------------ -----------------------------------------
常量字節(jié) 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
};
常量字節(jié) imgMainMenuMid[] PROGMEM = {
? 128, // 寬度
? 1, // 頁面
? 1、255、254、0、1、255
};
常量字節(jié) imgMainMenuBot[] PROGMEM = {
? 128, // 寬度
? 1, // 頁面
? 1、255、254、128、1、255
};
常量字節(jié) imgBoxTop[] PROGMEM = {
? 128, // 寬度
? 1, // 頁面
? 1、248、254、8、1、248
};
常量字節(jié) imgCaret1[] PROGMEM = {
? 4, // 寬度
? 1, // 頁面
? 4、255、126、60、24
};
常量字節(jié) imgCaret2[] PROGMEM = {
? 7, // 寬度
? 1, // 頁面
? 7、32、48、56、60、56、48、32
};
常量字節(jié) 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};
常量字節(jié) 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};
常量字節(jié) 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
// 創(chuàng)建一個必須是 'page' 一部分的欄
// 返回柱
//------------------------------------------------ -----------------------------------------
字節(jié)填充條(字節(jié) y1,字節(jié) y2,字節(jié)頁){
? 靜態(tài)字節(jié) lob[] = {0x00、0x01、0x03、0x07、0x0F、0x1F、0x3F、0x7F、0xFF};
? 字節(jié)條;
? 如果(頁面 == 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;
}
//------------------------------------------------ -----------------------------------------
// 畫框
// 在屏幕周圍畫一個盒子,左上角寫著 a
//------------------------------------------------ -----------------------------------------
無效的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 毫秒
//------------------------------------------------ -----------------------------------------
無效畫屏(無效){
? 字節(jié) i, j, k, y, yPrev, bar, page, lastDrawn;
? 字節(jié)* pxbz;
? 字節(jié)* pybz;
? 字節(jié) 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緩沖區(qū)[1];
??? 對于 ( i = 1; i < ADCBUFFERSIZE - 1; i++ ) {
????? ADCBuffer[i] = (yPrev + y + ADCBuffer[i + 1]) / 3;
????? yPrev = y;
????? y = ADC緩沖區(qū)[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); //后面的字節(jié)是數據
??? 對于 (i = 0; i < ADCBUFFERSIZE; i++) {
????? 如果 (i % 26 == 0) {
??????? Wire.endTransmission();
??????? Wire.beginTransmission(addr);
??????? Wire.write(0x40); //后面的字節(jié)是數據
????? }
????? y = ADC緩沖區(qū)[i];
??????? 如果 (yPrev > y + 1) {
????????? if (yPrev == lastDrawn)
??????????? yPrev--;
????????? bar = FillBar(y + 1, yPrev, page);
????????? 最后繪制 = yPrev + 1;
??????? } 別的 {
????????? bar = FillBar(yPrev, yPrev, page);
????????? lastDrawn = yPrev;
??????? }
???? // }
????? if ((page == 0) && (bar == 0x01) && (i & 1))
??????? 條 = 0;
????? if ((page == 7) && (bar == 0x80) && (i & 1))
??????? 條 = 0;
????? if (page == yGraticulePage0) {
??????? 如果 (i & 8)
????????? 酒吧 = 酒吧 | y刻度字節(jié)0;
????? }
????? 否則 if (page == yGraticulePage1) {
??????? 如果 (i < pylenz)
??????? {
????????? 酒吧 |= *pybz;
????????? pybz++;
??????? }
??????? 否則如果 (i % 4 == 0)
????????? 條形|= yGraticuleByte1;
????? }
????? 否則 if (page == yGraticulePage2) {
??????? 如果 (i % 4 == 0)
????????? 條 |= yGraticuleByte2;
????? }
????? 如果 ((i == xGraticule1) | (i == xGraticule2))
??????? 酒吧 = 酒吧 | 0x22;
????? if ((page == 7) && (i > xGraticule2 - pxlenz - 2) && (i < xGraticule2 - 1)) {
??????? 條形 |= *pxbz;
??????? pxbz++;
????? }
????? Wire.write(bar);
????? yPrev = y;
??? }
??? Wire.endTransmission();
? }
}
//------------------------------------------------ -----------------------------------------
// 初始化ADC()
//------------------------------------------------ -----------------------------------------
無效初始化ADC(無效){
? 如果(curMode > AC20mV)
??? 返回;
? ACSR = 0x10;
? ADCSRA = 0x97;
? ADCSRB = 0x0 ; //ADC控制和狀態(tài)寄存器B
? // 0 位 6 – ACME:模擬比較器多路復用器使能
? // 000 位 2:0 – ADTSn:ADC 自動觸發(fā)源 [n = 2:0] 自由運行模式
? ADMUX = 0x20 + (curVref << 6) + curMode;// ADC 多路復用器選擇寄存器
? // rr 位 7:6 – REFSn:參考選擇 = Vcc
? // 1 位 5 – ADLAR:ADC 左調整結果
? // aaaa 位 3:0 – MUXn:模擬通道選擇
? DIDR0 = 0x3F;// 數字輸入禁用寄存器 0
? // ADC0D=1, ADC1D=1, ADC2D=1, ADC3D=1, ADC4D=1, ADC5D=1, ADC6D=0, ADC7D=0
}
//------------------------------------------------ -----------------------------------------
// 打印狀態(tài)
//打印各種寄存器值
//------------------------------------------------ -----------------------------------------
//------------------------------------------------ -----------------------------------------
// 設置PwmFrequency
// 定時器模式=1 模式=2 模式=3 模式=4 模式=5 模式=6 模式=7
// 引腳=5 0 f=62500/1 f=62500/8 f=62500/64 f=62500/256 f=62500/1024
// 引腳=6 0 f=62500/1 f=62500/8 f=62500/64 f=62500/256 f=62500/1024
// 引腳=9 1 f=31250/1 f=31250/8 f=31250/64 f=31250/256 f=31250/1024
// 引腳=10 1 f=31250/1 f=31250/8 f=31250/64 f=31250/256 f=31250/1024
// 引腳=3 2 f=31250/1 f=31250/8 f=31250/32 f=31250/64 f=31250/128 f=31250/256 f=31250/1024
// 引腳=11 2 f=31250/1 f=31250/8 f=31250/32 f=31250/64 f=31250/128 f=31250/256 f=31250/1024
//------------------------------------------------ -----------------------------------------
void setPwmFrequency(int pin,字節(jié)模式){
? 發(fā)送確認();
? curPwmMode = 模式;
? 如果(模式 == 0){
??? 類比寫入(引腳,0);
? } 別的 {
??? 類比寫入(引腳,128);
??? if (pin == 5 || pin == 6 || pin == 9 || pin == 10) {
????? 如果(針 == 5 || 針 == 6){
??????? TCCR0B = TCCR0B & 0b11111000 | 模式;
????? } 別的 {
??????? TCCR1B = TCCR1B & 0b11111000 | 模式;
????? }
??? } else if (pin == 3 || pin == 11) {
????? TCCR2B = TCCR2B & 0b11111000 | 模式;
??? }
? }
}
//------------------------------------------------ -----------------------------------------
// 開始定時器1
// TIFR1 在之后變?yōu)榉橇?/p>
// 溢出*1024/16000000 秒
//------------------------------------------------ -----------------------------------------
void StartTimer1(字溢出){
? TCCR1A = 0xC0;// 在比較匹配上設置 OC1A
? TCCR1B = 0x05;// 預分頻器 = 1024
? TCCR1C = 0x00;// 沒有 pwm 輸出
? OCR1AH = 高字節(jié)(溢出);
? OCR1AL = 低字節(jié)(溢出);
? OCR1BH = 0;
? OCR1BL = 0;
? TIMSK1 = 0x00;// 沒有中斷
? TCNT1H = 0; // 必須先寫
? TCNT1L = 0; //清空計數器
? TIFR1 = 0xFF;// 清除所有標志
}
//------------------------------------------------ -----------------------------------------
// 發(fā)送確認
// 如果發(fā)送串行然后發(fā)送@
//------------------------------------------------ -----------------------------------------
無效發(fā)送確認(無效){
? 如果(發(fā)送序列)
??? 序列號.print(ack);
}
//------------------------------------------------ -----------------------------------------
// 讀取Vcc
// 結果為 mV
//------------------------------------------------ -----------------------------------------
長讀Vcc(無效){
? 長結果;
? ACSR = 0x10;
? ADCSRA = 0x97;
? ADCSRB = 0x0;
? // 根據 AVcc 讀取 1.1V 參考
? ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
? 我的延遲(2);
? ADCSRA |= _BV(ADSC); // 兌換
? 而(bit_is_set(ADCSRA,ADSC));
? 結果 = ADCL;
? 結果 |= ADCH << 8;
? 結果 = 1125300L / 結果;// 反算以 mV 為單位的 AVcc
? 初始化ADC();// 設置下一次掃描
? 返回結果;
}
無效GetADCSamples(無效){
? uint8_t d;
? uint8_t* p;
? const int 遲滯 = 2;
? 布爾觸發(fā);
? Trig = TrigFalling ^ (curMode != AC100mV);
? 初始化ADC();
? ADCSRA = 0x80 + (curPrescaler & 7); // ADC 控制和狀態(tài)寄存器 A
? // 1 位 7 – ADEN:ADC 使能
? // 0 位 6 – ADSC:ADC 開始轉換
? // 0 位 5 – ADATE:ADC 自動觸發(fā)啟用
? // 0 位 4 – ADIF:ADC 中斷標志
? // 0 位 3 – ADIE:ADC 中斷使能
? // nnn 位 2:0 – ADPSn:ADC 預分頻器選擇 [n = 2:0]
? 開始定時器1(0);// 沒有超時
? for (d = 0; d < 10; d++) { // 確保 ADC 正在運行
??? 位集(ADCSRA,ADSC);// 開始 ADC 轉換
??? 而(!getBit(ADCSRA,ADIF));// 等待 ADC
??? 位集(ADCSRA,ADIF);// 清除標志
? }
? if (curPeriod == 0) { // 1Msps
??? 而 (Trig ? (ADCH >= 0x80 - 遲滯) : (ADCH < 0x80 + 遲滯)) {
????? d = TCNT1L;// 強制讀取 TCNT1H
????? if (TCNT1H > FreeRunTimeout) 轉到 freeRunFast;
????? 位集(ADCSRA,ADSC);
??? }
??? 而(觸發(fā)?(ADCH < 0x80 + 滯后):(ADCH >= 0x80 - 滯后)){
????? d = TCNT1L;// 強制讀取 TCNT1H
????? if (TCNT1H > FreeRunTimeout) 轉到 freeRunFast;
????? 位集(ADCSRA,ADSC);
??? }
自由奔跑:
??? 對于(p = ADCBuffer;p < ADCBuffer + ADCBUFFERSIZE;p++){
????? *p = ADCH;
????? __asm__("nop"); // 填充到 16 條指令
????? __asm__("nop"); // 填充到 16 條指令
????? 位集(ADCSRA,ADSC);
??? }
? } else { // 慢于 1Msps
??? do { // 等待比較器低電平
????? 位集(ADCSRA,ADSC);// 開始 ADC 轉換
????? 對于 (d = 0; d < curPeriod; d++ )
??????? 循環(huán)數;
????? d = TCNT1L;// 強制讀取 TCNT1H
????? if (TCNT1H > FreeRunTimeout) 轉到 freeRunSlow;
??? } while (Trig ? (ADCH >= 0x80 - 遲滯) : (ADCH < 0x80 + 遲滯));
??? do { // 等待比較器高電平
????? 位集(ADCSRA,ADSC);// 開始 ADC 轉換
????? 對于 (d = 0; d < curPeriod; d++ )
??????? 循環(huán)數;
????? d = TCNT1L;// 強制讀取 TCNT1H
????? if (TCNT1H > FreeRunTimeout) 轉到 freeRunSlow;
??? } while (Trig ? (ADCH < 0x80 + 遲滯) : (ADCH >= 0x80 - 遲滯));
自由運行慢:
??? 位集(ADCSRA,ADSC);// 開始 ADC 轉換
??? 對于 ( p = ADCBuffer; p < ADCBuffer + ADCBUFFERSIZE; p++) {
????? 對于 (d = 0; d < curPeriod; d++ )
??????? 循環(huán)數;
????? *p = ADCH;
????? 位集(ADCSRA,ADSC);// 開始 ADC 轉換
??? }
? }
}
//------------------------------------------------ -----------------------------------------
// 發(fā)送ADC
// 使用 curPrescaler,curPeriod,curMode,curVref
//
// 讀取并發(fā)送一個充滿樣本的緩沖區(qū)
// 預分頻器
// 7 128
// 6 64
// 5 32
// 4 16
// 3 8
// 2 4
// 1 2
// 0 2
// 周期:采樣周期
// 0: 16 個時鐘
// n: n*SampPerA+SampPerB 時鐘
//------------------------------------------------ -----------------------------------------
無效發(fā)送ADC(){
? memset( (void *)ADCBuffer, 0, sizeof(ADCBuffer) );
? 無中斷();
// if (curMode == mLogic)
// GetLogicSamples();
// 別的
GetADCSamples();
? 中斷();
? 數字寫入(LED_BUILTIN,高);
? 如果(發(fā)送序列){
??? 串行寫入((uint8_t)0xAA);
??? Serial.write((uint8_t)0xBB);
??? Serial.write((uint8_t)0xCC);
??? Serial.write((uint8_t *)ADCBuffer, ADCBUFFERSIZE);
? }
? 畫屏();
? 數字寫入(LED_BUILTIN,低);
? 詮釋 t = TCNT1L; // 強制讀取 TCNT1H
? 按鈕Timer1 += TCNT1H;
}
//------------------------------------------------ -----------------------------------------
// 設置掃描
// 設置周期和 ADC 預分頻器
//------------------------------------------------ -----------------------------------------
無效 setSweep(字節(jié)掃描){
? 詮釋 x;
? 長 t;
? 如果(掃一掃 == 255){
??? 如果(curSweep == 0)
????? curSweep = 6;
??? 別的
????? curSweep——;
? } 別的
??? curSweep = 掃描;
? 開關(curSweep){
??? 情況 0:curPeriod = 0;curPrescaler = 2; t = 100;pxGratLabel = &ax0_1[0]; xGratLabelLen = sizeof(ax0_1); 休息;
??? 案例1:curPeriod = 4;curPrescaler = 2; t = 400;pxGratLabel = &ax0_4[0]; xGratLabelLen = sizeof(ax0_4); 休息;
??? 案例2:curPeriod = 11;curPrescaler = 3; t = 1000;pxGratLabel = &ax1[0]; xGratLabelLen = sizeof(ax1); 休息;
??? 案例 3:curPeriod = 24;curPrescaler = 3; t = 2000;pxGratLabel = &ax2[0]; xGratLabelLen = sizeof(ax2); 休息;
??? 案例4:curPeriod = 62;curPrescaler = 4; t = 5000;pxGratLabel = &ax5[0]; xGratLabelLen = sizeof(ax5); 休息;
??? 案例5:curPeriod = 125;curPrescaler = 4; t = 10000;pxGratLabel = &ax10[0]; xGratLabelLen = sizeof(ax10); 休息;
??? 案例6:curPeriod = 255;curPrescaler = 5; t = 20000;pxGratLabel = &ax20[0]; xGratLabelLen = sizeof(ax20); 休息;
? }
? 如果(curSweep == 0)
??? x = t;
? 別的
??? x = 16 * t / (curPeriod * SampPerA + SampPerB);
? x格線1 = x / 2;
? x格線2 = x;
? 發(fā)送確認();
}
//------------------------------------------------ -----------------------------------------
// INC模式
// 遞增模式
// 從最大值環(huán)繞
// 跳過不允許的模式
//------------------------------------------------ -----------------------------------------
int incMode(int mode) {
? 模式++;
//if ((mode == mLogic) && (!bHasLogic)) mode++;
// if ((mode == mFreqLogic) && ((!bHasFreq) || (!bHasLogic))) mode++;
// if ((mode == mFreqAC) && (!bHasFreq)) mode++;
? if ((mode == mVoltmeter) && (!bHasVoltmeter)) mode++;
? 如果(模式 > 最大模式)
??? 返回DC5V;
? 別的
??? 返回模式;
}
//------------------------------------------------ -----------------------------------------
// 設置模式
// 設置模式和 Vref
//------------------------------------------------ -----------------------------------------
無效 setMode(int 模式){
? 詮釋我;
? 如果(模式 == 255){
??? curMode = incMode(curMode);
? } 別的
??? curMode = 模式;
? 開關(curMode){
??? 案例 DC5V:
????? curVref = 1;
????? i = (long)4000 * 64 / readVcc();
????? 如果(我 <= 63){
??????? y刻度1 = 63 - i;
??????? y 刻度 2 = 63 - i / 2;
??????? y刻度線0 = 255;
??????? pyGratLabel = &ax4V[0];
??????? yGratLabelLen = sizeof(ax4V);
????? } 別的 {
??????? y刻度2 = 63 - i;
??????? y 刻度 1 = 63 - i / 2;
??????? y刻度線0 = 255;
??????? pyGratLabel = &ax2V[0];
??????? yGratLabelLen = sizeof(ax2V);
????? }
????? 休息;
??? 案例 AC500mV:
????? curVref = 3;
????? i = (字節(jié))(0.5 / 1.1 * 256 / 4);
????? y刻度1 = 32 - i;
????? y刻度2 = 32 + i;
????? y刻度線0 = 32;
????? pyGratLabel = &ax0_5[0];
????? yGratLabelLen = sizeof(ax0_5);
????? 休息;
??? 案例 AC100mV:
????? curVref = 3;
????? i = (字節(jié))(0.1 / 1.1 * (R1 + R2) / R2 * 256 / 4);
????? y刻度1 = 32 - i;
????? y刻度2 = 32 + i;
????? y刻度線0 = 32;
????? pyGratLabel = &ax0_1[0];
????? yGratLabelLen = sizeof(ax0_1);
????? 休息;
??? 案例AC20mV:
????? curVref = 3;
????? i = (字節(jié))(0.02 / 1.1 * (R1 + R2) / R2 * (R1 + R2) / R2 * 256 / 4);
????? y刻度1 = 32 - i;
????? y刻度2 = 32 + i;
????? y刻度線0 = 32;
????? pyGratLabel = &ax20[0];
????? yGratLabelLen = sizeof(ax20);
????? 休息;
??? 默認:
????? curVref = 1;
????? y 格線 1 = 255;
????? y 格線 2 = 255;
????? y刻度線0 = 255;
????? pyGratLabel = &ax20[0];
????? yGratLabelLen = sizeof(ax20);
????? 休息;
? }
? 開關(curMode){
??? 案例毫伏表:
????? Vin = 0;
????? 畫屏();
????? 休息;
??? 默認值:FC_disable();
? }
? yGraticulePage0 = yGraticule0 / 8;
? yGraticuleByte0 = 1 << (yGraticule0 % 8);
? yGraticulePage1 = yGraticule1 / 8;
? yGraticuleByte1 = 1 << (yGraticule1 % 8);
? yGraticulePage2 = yGraticule2 / 8;
? yGraticuleByte2 = 1 << (yGraticule2 % 8);
? 發(fā)送確認();
}
//------------------------------------------------ -----------------------------------------
// 繪制主菜單
// 為 sel 和 adj 的值繪制主菜單
//------------------------------------------------ -----------------------------------------
無效drawMainMenu(無效){
? int ofs, x, yVcc, pg;
? 開關(選擇){
??? 案例 sMode: ofs = -1; 休息;
??? 案例 sTrigger: ofs = -2; 休息;
??? 案例 sTestSig: ofs = -5; 休息;
??? 案例 sSigGen: ofs = bHasTestSignal ? -7:-5;休息;
??? 默認值:ofs = 0;
? }
? // DrawImageSH1106(0,ofs,imgMainMenu);
? DrawImageSH1106(0, ofs + 0, imgMainMenuTop);
? 對于 (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);
? 粗體SH1106 = 真;
? pg = 3 + ofs;
? DrawStringSH1106("時間:", 12, pg, SmallFont); pg += 2;
? DrawStringSH1106((adj[1] <= AC20mV ? "Gain:" : "Mode:"), 12, pg, SmallFont); pg += 2;
? DrawStringSH1106("觸發(fā)器:", 12, pg, SmallFont); pg += 2;
? 如果(bHasTestSignal){
??? DrawStringSH1106("測試簽名:", 12, pg, SmallFont); pg += 2;
??? 如果(bHasSigGen){
????? DrawStringSH1106("信號發(fā)生器", 12, pg, SmallFont); pg += 2;
??? }
??? DrawStringSH1106("Vcc:", 12, pg, SmallFont); yVcc = pg; pg += 2;
? } 別的 {
??? 如果(bHasSigGen){
????? DrawStringSH1106("Vcc:", 12, pg, SmallFont); yVcc = pg; pg += 2;
????? DrawStringSH1106("信號發(fā)生器", 12, pg, SmallFont); pg += 2;
??? } 別的 {
????? DrawStringSH1106("Vcc:", 12, pg, SmallFont); yVcc = pg; pg += 2;
??? }
? }
? 粗體SH1106 = 假;
? x = 62;
? pg = 3 + ofs;
? 開關(adj[0]){
??? 案例 0: DrawStringSH1106("1mS", x, pg, SmallFont); 休息;
??? 案例一:DrawStringSH1106("2mS", x, pg, SmallFont); 休息;
??? 案例2:DrawStringSH1106("5mS", x, pg, SmallFont); 休息;
??? 案例 3:DrawStringSH1106("10mS", x, pg, SmallFont); 休息;
??? 案例4:DrawStringSH1106("20mS", x, pg, SmallFont); 休息;
??? 案例5:DrawStringSH1106("50mS", x, pg, SmallFont); 休息;
??? 案例 6:DrawStringSH1106("100mS", x, pg, SmallFont); 休息;
? }
? pg += 2;
? 開關(adj[1]){
??? 案例 DC5V: DrawStringSH1106("5V DC", x, pg, SmallFont); 休息;
??? 案例 AC500mV: DrawStringSH1106("0.5V?? AC", x, pg, SmallFont); 休息;
??? 案例 AC100mV: DrawStringSH1106("0.1V AC", x, pg, SmallFont); 休息;
??? 案例 AC20mV: DrawStringSH1106("20mV AC", x, pg, SmallFont); 休息;
??? //case mLogic: DrawStringSH1106("Logic", x, pg, SmallFont); 休息;
??? //case mFreqLogic: DrawStringSH1106("頻率邏輯", x, pg, SmallFont); 休息;
??? //case mFreqAC: DrawStringSH1106("Freq AC", x, pg, SmallFont); 休息;
??? case mVoltmeter: DrawStringSH1106("Voltmeter", x, pg, SmallFont); 休息;
? }
? pg += 2;
? 開關(adj[2]){
??? 案例一:DrawStringSH1106("Fall", x, pg, SmallFont); 休息;
??? 默認值:DrawStringSH1106("Rise", x, pg, SmallFont);
? }
? pg += 2;
? 如果(bHasTestSignal){
??? 開關(adj[3]){
????? 案例一:DrawStringSH1106("31250Hz 32uS", x, pg, SmallFont); 休息;
????? 案例2:DrawStringSH1106("3906Hz 256uS", x, pg, SmallFont); 休息;
????? 案例3:DrawStringSH1106("977Hz 1024uS", x, pg, SmallFont); 休息;
????? 案例4:DrawStringSH1106("488Hz 2048uS", x, pg, SmallFont); 休息;
????? 案例5:DrawStringSH1106("244Hz 4096uS", x, pg, SmallFont); 休息;
????? 案例6:DrawStringSH1106("122Hz 8192uS", x, pg, SmallFont); 休息;
????? 案例 7:DrawStringSH1106("31Hz 32768uS", x, pg, SmallFont); 休息;
????? 默認值:DrawStringSH1106("Off", x, pg, SmallFont);
??? }
??? pg += 2;
? }
? 如果(bHasSigGen)
??? pg += 2;
? 如果(yVcc <= 7){
??? x += DrawIntDP2(readVcc() / 10, x, yVcc, SmallFont);
??? DrawCharSH1106('V', x, yVcc, SmallFont);
? }
}
//------------------------------------------------ -----------------------------------------
// incAdj
// 增加 adj 的值
//------------------------------------------------ -----------------------------------------
無效incAdj(無效)??{
? if (sel == sSigGen)
??? 返回;
? if (sel == sMode) {
??? adj[1] = incMode(adj[1]);
? } 別的 {
??? adj[選擇]++;
??? 如果 (adj[0] > 6) adj[0] = 0;
??? 如果 (adj[2] > 1) adj[2] = 0;
??? 如果 (adj[3] > 7) adj[3] = 0;
? }
? 繪制主菜單;
}
//------------------------------------------------ -----------------------------------------
// incSel
// 增加 sel 的值
//------------------------------------------------ -----------------------------------------
無效incSel(無效){
? 如果(bHasTestSignal){
??? 如果(bHasSigGen){
????? if (sel == sSigGen)
??????? sel = sTime - 1;
??? } 別的 {
????? if (sel == sTestSig)
??????? sel = sTime - 1;
??? }
? } 別的 {
??? 如果(bHasSigGen){
????? if (sel == sSigGen)
??????? sel = sTime - 1;
????? if (sel == sTrigger)
??????? sel = sSigGen - 1;
??? } 別的 {
????? if (sel == sTrigger)
??????? sel = sTime - 1;
??? }
? }
? 選擇 = 選擇 + 1;
? 繪制主菜單;
}
//------------------------------------------------ -----------------------------------------
// DrawIntDP2
// 以 12.34 格式繪制 int 1234
// 在 x, 頁
// 返回繪制的寬度
//------------------------------------------------ -----------------------------------------
int DrawIntDP2(int i, byte x, byte page, word Font) {
? 詮釋開始;
? 開始 = x;
? 如果 (i < 0) {
??? 我 = - 我;
??? x += DrawCharSH1106('-', x, page, 字體);
? }
? x += DrawIntSH1106(i / 100, x, page, 字體);
? x += DrawCharSH1106('.', x, page, 字體);
? x += DrawIntSH1106((i / 10) % 10, x, page, 字體);
? x += DrawIntSH1106(i % 10, x, page, 字體);
? 返回 x - 開始;
}
//------------------------------------------------ -----------------------------------------
// drawSigGenMenu
//------------------------------------------------ -----------------------------------------
無效的drawSigGenMenu(無效){
? 字節(jié) x,y,i;
? drawBox("信號發(fā)生器");
? if (sweepType == swOff) {
??? x = 20;
??? y = 3;
??? for (i = numberOfDigits - 1; i < numberOfDigits; i--) {
????? 如果 (i == SelSG)
??????? DrawImageSH1106(x+2, y+2, imgCaret2);
????? x += DrawIntSH1106(freqSGLo[i], x, y, LargeDigitsFont);
??? }
? } 別的 {
??? x = 60;
??? y = 2;
??? DrawStringSH1106("最大頻率:", 12, y, SmallFont);
??? for (i = numberOfDigits - 1; i < numberOfDigits; i--) {
????? if (i == SelSG-numberOfDigits)
??????? DrawImageSH1106(x-2, y+1, imgCaret2);
????? x += DrawIntSH1106(freqSGHi[i], x, y, SmallFont);
??? }
??? DrawStringSH1106("Hz", x, y, SmallFont);
??? x = 60;
??? y = 4;
??? DrawStringSH1106("最小頻率:", 12, y, SmallFont);
??? for (i = numberOfDigits - 1; i < numberOfDigits; i--) {
????? 如果 (i == SelSG)
??????? DrawImageSH1106(x-2, y+1, imgCaret2);
????? x += DrawIntSH1106(freqSGLo[i], x, y, SmallFont);
??? }
??? DrawStringSH1106("Hz", x, y, SmallFont);
? }
? x = 12;
? y = 6;
? 如果(SelSG == SelSGSine)
??? DrawImageSH1106(x-6, y, imgCaret1);
// 開關 (waveType) {
// case wSine: DrawStringSH1106("Sine", x, y, SmallFont); 休息;
// case wTriangle: DrawStringSH1106("Triangle", x, y, SmallFont); 休息;
// case wSquare: DrawStringSH1106("Square", x, y, SmallFont); 休息;
// }
? 對于 (x=12;x<40;x+=14)
? 開關(波型){
??? 案例 wSine: DrawImageSH1106(x, y, imgSine); 休息;
??? 案例 wTriangle: DrawImageSH1106(x, y, imgTrian); 休息;
??? 案例 wSquare: DrawImageSH1106(x, y, imgSquare); 休息;
? }
? x = 54;
? y = 6;
? 開關(掃描類型){
??? case swOff: DrawStringSH1106("常量", x, y, SmallFont); 休息;
??? case sw1Sec: DrawStringSH1106("Sweep 1 Sec", x, y, SmallFont); 休息;
??? case sw5Sec: DrawStringSH1106("Sweep 5 Sec", x, y, SmallFont); 休息;
??? case sw20Sec: DrawStringSH1106("掃描 20 秒", x, y, SmallFont); 休息;
??? case sw20Frames: DrawStringSH1106("Swp 20 幀", x, y, SmallFont); 休息;
??? case sw100Frames: DrawStringSH1106("Swp 100 幀", x, y, SmallFont); 休息;
??? case sw500Frames: DrawStringSH1106("Swp 500 幀", x, y, SmallFont); 休息;
? }
? 如果(SelSG == SelSGSweep)
??? DrawImageSH1106(x-6, y, imgCaret1);
}
//------------------------------------------------ -----------------------------------------
//返回 10^y
//------------------------------------------------ -----------------------------------------
無符號長冪(int y){
? 無符號長 t = 1;
? 對于(字節(jié) i = 0;i < y;i++)
??? t = t * 10;
? 返回 t;
}
//------------------------------------------------ -----------------------------------------
//根據數組計算頻率。
//------------------------------------------------ -----------------------------------------
無符號長計算頻率(字節(jié)*頻率SG){
? 無符號長 i = 0;
? 對于(字節(jié) x = 0;x < numberOfDigits;x++)
??? i = i + freqSG[x] * 冪(x);
? 返回我;
}
//------------------------------------------------ -----------------------------------------
// SG_WriteRegister
//------------------------------------------------ -----------------------------------------
無效SG_WriteRegister(字數據){
? 數字寫入(SG_CLK,低);
? 數字寫入(SG_CLK,高);
? 數字寫入(SG_fsyncPin,低);
? 對于(字節(jié) i = 0;i < 16;i++){
??? 如果 (dat & 0x8000)
????? 數字寫入(SG_DATA,高);
??? 別的
????? 數字寫入(SG_DATA,低);
??? dat = dat << 1;
??? 數字寫入(SG_CLK,高);
??? 數字寫入(SG_CLK,低);
? }
? 數字寫入(SG_CLK,高);
? 數字寫入(SG_fsyncPin,高);
}
//------------------------------------------------ -----------------------------------------
// SG_Reset
//------------------------------------------------ -----------------------------------------
無效 SG_Reset() {
? 延遲(100);
? SG_WriteRegister(0x100);
? 延遲(100);
}
//------------------------------------------------ -----------------------------------------
// SG_freqReset
// 重置 SG regs 然后設置頻率和波形類型
//------------------------------------------------ -----------------------------------------
void SG_freqReset(長頻率,整波){
? 長 fl = 頻率 * (0x10000000 / 25000000.0);
? SG_WriteRegister(0x2100);
? SG_WriteRegister((int)(fl & 0x3FFF) | 0x4000);
? SG_WriteRegister((int)((fl & 0xFFFC000) >> 14) | 0x4000);
? SG_WriteRegister(0xC000);
? SG_WriteRegister(波);
? 波型 = 波;
}
//------------------------------------------------ -----------------------------------------
// SG_freqSet
// 設置 SG 頻率調節(jié)器
//------------------------------------------------ -----------------------------------------
void SG_freqSet(長頻率,整波){
? 長 fl = 頻率 * (0x10000000 / 25000000.0);
? SG_WriteRegister(0x2000 | wave);
? SG_WriteRegister((int)(fl & 0x3FFF) | 0x4000);
? SG_WriteRegister((int)((fl & 0xFFFC000) >> 14) | 0x4000);
}
//------------------------------------------------ -----------------------------------------
// SG_StepSweep
// 增加 FG 頻率
//------------------------------------------------ -----------------------------------------
無效SG_StepSweep(無效){
? 如果(SG_iSweep > SG_nSweep)SG_iSweep = 0;
? 長 f = exp((log(calcFreq(freqSGHi)) - log(calcFreq(freqSGLo)))*SG_iSweep/SG_nSweep + log(calcFreq(freqSGLo))) +0.5;
? SG_freqSet(f, waveType);
? SG_iSweep++;
}
//------------------------------------------------ -----------------------------------------
// 掃一掃
// 連續(xù)掃描 siggen freq
// 整個掃描需要 n mS
// SDC regs 被保存和恢復
// 當接收到一個串行字符時停止
//------------------------------------------------ -----------------------------------------
無效掃描(int n){
? 字節(jié)舊ACSR = ACSR;
? 字節(jié)舊ADCSRA = ADCSRA;
? 字節(jié)老ADCSRB = ADCSRB;
? 字節(jié)老ADMUX = ADMUX;
? 字節(jié)老DIDR0 = DIDR0;
? 字節(jié)老DIDR1 = DIDR1;
? 整數 fmin,fmax;
? fmin = calcFreq(freqSGLo);
? fmax = calcFreq(freqSGHi);
? 詮釋我=0;
? 做 {
??? 長 f = exp((log(fmax) - log(fmin))*i/(n-1) + log(fmin)) +0.5;
??? SG_freqSet(f, waveType);
??? 延遲(1);
??? 我++;
??? 如果 (i >= n) i = 0;
? } 而 (!Serial.available());
? SG_freqSet(calcFreq(freqSGLo), waveType);
? ACSR = 舊ACSR;
? ADCSRA = 舊ADCSRA;
? ADCSRB = 舊ADCSRB;
? ADMUX = oldADMUX;
? DIDR0 = 舊DIDR0;
? DIDR1 = 舊DIDR1;
}
//------------------------------------------------ -----------------------------------------
// incSelSG
// SigGen 菜單的遞增數字
//------------------------------------------------ -----------------------------------------
無效incSelSG(無效){
? 如果(SelSG == SelSGSine){
??? 開關(波型){
????? 案例 wSine:waveType = wTriangle;休息;
????? 案例 wTriangle:waveType = wSquare;休息;
????? 案例 wSquare:waveType = wSine;休息;
??? }
? } 別的
? 如果(SelSG == SelSGSweep){
??? if (sweepType == sw20Sec)
????? 掃描類型 = swOff;
??? 別的
????? 掃描類型=掃描類型+1;
? } 別的
? 如果(SelSG < numberOfDigits){
??? if (freqSGLo[SelSG] >= 9)
????? 頻率SGLo[SelSG] = 0;
??? 別的
????? 頻率SGLo[SelSG]++;
? } 別的
? 如果(掃描類型!= swOff){
??? if (freqSGHi[SelSG-numberOfDigits] >= 9)
????? freqSGHi[SelSG-numberOfDigits] = 0;
??? 別的
????? freqSGHi[SelSG-numberOfDigits]++;
? }
? drawSigGenMenu();
}
//------------------------------------------------ -----------------------------------------
// incAdjSG
// 增加 SigGen 菜單的插入符號位置
//------------------------------------------------ -----------------------------------------
無效incAdjSG(無效){
? 如果(SelSG == 0)
??? SelSG = SelSGSine;
? 別的
??? 塞爾SG——;
? if ((SelSG >= numberOfDigits) && (SelSG < 2*numberOfDigits) && (sweepType == swOff))
??? SelSG = numberOfDigits-1;
? drawSigGenMenu();
}
//------------------------------------------------ -----------------------------------------
// ExecSigGenMenu
// SigGen 菜單
// 用戶按下 sel 或 Adj 按鈕
// 如果 2 秒內沒有按鈕,則返回
//------------------------------------------------ -----------------------------------------
無效ExecSigGenMenu(無效){
? 靜態(tài)int prevHorz = 0;
? 靜態(tài)int prevVert = 0;
? 詮釋我;
? 常量字節(jié)超時 = 0xC0; // 3 秒退出
? FC_disable();
? drawSigGenMenu();
? 而 ((digitalRead(BtnHorz) == LOW) || (digitalRead(BtnVert) == LOW))
??? ; // 等到按鈕關閉
? 開始定時器1(0);
? SG_iSweep = 0;
? 做 {
??? i = 數字讀?。˙tnVert);
??? 如果(我!= prevVert){
????? prevVert = i;
????? 如果(我 == 低){
??????? incSelSG();
??????? drawSigGenMenu();
??????? SG_freqReset(calcFreq(freqSGLo), waveType);
????? }
????? 我的延遲(100);
????? 開始定時器1(0);
??? }
??? i = 數字讀取(BtnHorz);
??? if (i != prevHorz) {
????? 上一頁Horz = i;
????? 如果(我 == 低){
??????? incAdjSG();
??????? drawSigGenMenu();
????? }
????? 我的延遲(100);
????? 開始定時器1(0);
??? }
??? 如果(掃描類型!= swOff){
????? 開關(掃描類型){
??????? case sw1Sec: SG_nSweep = (long) 1000*65/100; 休息;
??????? case sw5Sec: SG_nSweep = (long) 5000*65/100; 休息;
??????? case sw20Sec: SG_nSweep = (long)20000*65/100; 休息;
??????? case sw20Frames: SG_nSweep = 20; 休息;
??????? case sw100Frames: SG_nSweep = 100; 休息;
??????? case sw500Frames: SG_nSweep = 500; 休息;
????? }
????? SG_StepSweep();
??? }
??? 我 = TCNT1L; // 強制讀取 TCNT1H
? } while ((TCNT1H < timeout) || (sweepType >= sw1Sec));
}
//------------------------------------------------ -----------------------------------------
// 執(zhí)行菜單
// 顯示主菜單
// 用戶按下 sel 或 Adj 按鈕
// 如果 2 秒內沒有按鈕,則返回
//------------------------------------------------ -----------------------------------------
無效執(zhí)行菜單(無效){
? 靜態(tài)int prevHorz = 0;
? 靜態(tài)int prevVert = 0;
? 詮釋我;
? 常量字節(jié)超時 = 0x80; // 2 秒退出
? adj[0] = curSweep;
? adj[1] = curMode;
? adj[2] = TrigFalling;
? adj[3] = curPwmMode;
? FC_disable();
? drawMainMenu();
? 而 ((digitalRead(BtnHorz) == LOW) || (digitalRead(BtnVert) == LOW))
??? ; // 等到按鈕關閉
? 開始定時器1(0);
? 做 {
??? i = 數字讀?。˙tnVert);
??? 如果(我!= prevVert){
????? prevVert = i;
????? 如果(我 == 低){
??????? incSel();
??????? drawMainMenu();
????? }
????? 我的延遲(100);
????? 開始定時器1(0);
??? }
??? i = 數字讀取(BtnHorz);
??? if (i != prevHorz) {
????? 上一頁Horz = i;
????? 如果(我 == 低){
??????? if (sel == sSigGen)
????????? 休息;
??????? incAdj();
??????? if (sel == sTestSig)
????????? setPwmFrequency(testSignalPin, adj[3]);
??????? drawMainMenu();
????? }
????? 我的延遲(100);
????? 開始定時器1(0);
??? }
??? 我 = TCNT1L; // 強制讀取 TCNT1H
? } 而 (TCNT1H < 超時);
? if (sel == sSigGen) {
??? 選擇 = 時間;
??? ExecSigGenMenu();
? }
? setSweep(adj[0]);
? setMode(adj[1]);
? TrigFalling = adj[2] != 0;
? 清除SH1106();
}
//------------------------------------------------ -----------------------------------------
// 檢查按鈕
// 如果 BtnVert 按鈕被按下,改變增益
// 如果 BtnHorz 按鈕被按下,改變掃描
// 如果按鈕按住 2 秒,則顯示菜單
//------------------------------------------------ -----------------------------------------
無效檢查按鈕(無效){
? 常量字節(jié)超時 = 70;// 1 秒顯示菜單
? 靜態(tài)int prevHorz = HIGH;
? 靜態(tài)int prevVert = HIGH;
? 詮釋我;
? 如果(數字讀?。˙tnHorz)== 低){
??? if (prevHorz == HIGH) {
????? 開關(curMode){
??????? //case mFreqLogic:
?????? // 案例 mFreqAC:
??????? 案例毫伏表:
????????? 執(zhí)行菜單();
????????? 返回;
????? }
????? 按鈕定時器1 = 0;
????? 我的延遲(15);
????? 設置掃描(255);
????? prevHorz = 低;
??? } 別的 {
????? if (ButtonsTimer1 > timeout) {
??????? 執(zhí)行菜單();
??????? 返回;
????? }
??? }
? } 別的 {
??? prevHorz = 高;
? }
? 如果(數字讀?。˙tnVert)== 低){
??? if (prevVert == HIGH) {
????? 按鈕定時器1 = 0;
????? 我的延遲(15);
????? 設置模式(255);
????? prevVert = 低;
??? } 別的 {
????? if (ButtonsTimer1 > timeout) {
??????? 執(zhí)行菜單();
??????? 返回;
????? }
??? }
? } 別的 {
??? prevVert = 高;
? }
}
//================================================= ==========================
// Timer1 每 65536 個計數溢出
// 用于頻率計
//================================================= ==========================
ISR (TIMER1_OVF_vect)
{
? FC_overflowCount++;
}
//================================================= ==========================
// Timer1 捕捉中斷
// 由比較器調用
//讀取當前timer1捕獲值
// 用于頻率計
//================================================= ==========================
ISR (TIMER1_CAPT_vect) {
? // 在計數器值發(fā)生任何變化之前獲取它
? 無符號整數 timer1CounterValue = ICR1; // 參見數據表,第 117 頁(訪問 16 位寄存器)
? 無符號長溢出復制 = FC_overflowCount;
? 無符號長 t;
? 靜態(tài)無符號長prevT;
? // 如果剛剛錯過了溢出
? if ((TIFR1 & bit(TOV1)) && timer1CounterValue < 0x7FFF)
??? 溢出復制++;
? t = (overflowCopy << 16) + timer1CounterValue;
? if ((!FC_firstAC) && (t-prevT > 100) && (t-prevT > FC_MaxPeriodAC))
??? FC_MaxPeriodAC = t-prevT;
? 上一頁T = t;
? FC_firstAC = 假;
}
//================================================= ==========================
// Timer0 中斷服務由硬件 Timer0 每 1ms = 1000 Hz 調用一次
// 由頻率計數器使用
// 每 1mS 調用一次
//================================================= ==========================
ISR(TIMER0_COMPA_vect){
? if (FC_Timeout >= FC_LogicPeriod) { // 門時間結束,測量準備就緒
??? TCCR1B &= ~7; // 門關閉/計數器 T1 停止
??? 位清除(TIMSK0,OCIE0A);// 禁用 Timer0 中斷
??? FC_OneSec = 真;// 設置結束計數周期的全局標志
??? // 現在計算頻率值
??? FC_freq = 0x10000 * FC_overflowCount;// mult #overflows by 65636
??? FC_freq += TCNT1; // 添加 counter1 的值
? }
? FC_超時++;// 計算中斷事件的數量
? if (TIFR1 & 1) { // 如果定時器/計數器 1 溢出標志
??? FC_overflowCount++; // 計數 Counter1 溢出的次數
??? 位集(TIFR1,TOV1);// 清除定時器/計數器 1 溢出標志
? }
}
//================================================= ==========================
// FC_InitLogic
// 計算 D5 在 mS 周期內的上升沿數
//================================================= ==========================
無效 FC_InitLogic() {
? 無中斷();
? TIMSK0 = 0x00;
? 延遲微秒(50);// 等待是否有任何 int 待處理
? FC_OneSec = 假;// 重置周期測量標志
? FC_Timeout = 0; // 重置中斷計數器
? TCCR1A = 0x00;//定時器輸出關閉
? TCCR1B = 0x07;// T1 引腳上的外部時鐘源。時鐘在上升沿。
? TCNT1 = 0x00;// 計數器 = 0
? TCCR0A = 0x02;// 比較輸出關閉;最大計數 = OCRA
? TCCR0B = 0x03;// 輸入時鐘為 16M/64
? TCNT0 = 0x16;// counter = 0 - 為什么這不是 0?設置時間的成本?
? TIMSK0 = 0x00;
? OCR0A = 248; // 最大計數值 = CTC 除以 250 = 1mS
? GTCCR = 0x02;// 重置預分頻器
? FC_overflowCount = 0;
? 位集(TIMSK0,OCIE0A);// 啟用 Timer0 中斷
? 中斷();
}
//================================================= ==========================
// FC_InitAC
// ACfreqAdcPin = 0..5 - 使用該 ADC 多路復用器并使用 Timer1 測量周期
//================================================= ==========================
無效 FC_InitAC() {
? 無中斷();
? FC_disable();
? TCCR1A = 0;// 重置定時器 1
? TCCR1B = 位(CS10)| 位(ICES1);// 無預分頻器,輸入捕捉邊沿選擇
? TIFR1 = 位 (ICF1) | 位(TOV1);// 清除標志,這樣我們就不會收到虛假中斷
? TCNT1 = 0; // Timer1歸零
? FC_overflowCount = 0; // 對于 Timer1 溢出
? TIMSK1 = 位(TOIE1)| 位(ICIE1);// 定時器 1 溢出和輸入捕捉中斷
? ADCSRA = 0;
? DIDR1 = 1;// D6的數字輸入關閉
? ADMUX = ACfreqAdcPin;
? ACSR = 位(ACI)| 位(ACIC) | (B10 << ACIS0); //“清除”中斷標志;比較器的定時器捕捉;下降沿
? ADCSRB = 位(ACME);// 比較器連接到 ADC 多路復用器
? FC_firstAC = 真;
? FC_Timeout = 0;
? FC_MaxPeriodAC = 0;
? 中斷();
}
//================================================= ==========================
// FC_disable
//關閉頻率計數器中斷
//================================================= ==========================
無效 FC_disable() {
? TCCR0A = 0x03;// 沒有比較輸出;高達 0xFF 的快速 PWM
? TCCR0B = 0x03;// 沒有輸出比較;預分頻器 = 16MHz/64;大約每 1mS 溢出一次
? TIMSK0 = 0x00;// 中斷屏蔽寄存器 = 無
? GTCCR = 0x00;//控制寄存器=無
? OCR0A = 0x00;// 輸出比較寄存器 A = none
? OCR0B = 0x00;// 輸出比較寄存器 B = none
? TCCR1A = 0xC0;
? TCCR1B = 0x05;
? TCCR1C = 0x00;
? TIMSK1 = 0x00;
}
//================================================= ==========================
// FC_OneSecPassed
// 1 秒過去了嗎?
//================================================= ==========================
bool FC_OneSecPassed() {
? 靜態(tài)字節(jié) prevTimer1 = 0;
? 字節(jié) i;
? 靜態(tài)無符號長 t = 0;
? if (bitRead(TIFR0, TOV0)) // 每 1mS 溢出
??? FC_超時++;
? 位集(TIFR0,TOV0);
? 返回 FC_Timeout > 1000;
}
//================================================= ==========================
// FC_CheckLogic
//頻率測量器
// 重復調用
// 超時返回true
// 結果為 FC_freq
//================================================= ==========================
布爾 FC_CheckLogic() {
? 返回 FC_OneSec;
}
//================================================= ==========================
// FC_CheckAC
//頻率測量器
// 重復調用
// 超時返回true
// 結果為 FC_freq
//================================================= ==========================
布爾 FC_CheckAC() {
? 無符號長 FC_elapsedTime;
? 如果(FC_OneSecPassed()){
??? 如果(FC_MaxPeriodAC > 0)
????? FC_freq = 100 * F_CPU*1.004 / FC_MaxPeriodAC;// 乘以 100 所以可以顯示 2 dp
??? 別的
????? FC_freq = 0;
??? FC_InitAC();
??? 返回真;
? }
? 返回假;
}
//------------------------------------------------ -----------------------------------------
// 我的延遲
// 延遲大約 mS 毫秒
// 不使用任何計時器
// 不影響中斷
//------------------------------------------------ -----------------------------------------
無效我的延遲(int毫秒){
? 對于 (int j = 0; j < mS; j++)
??? 延遲微秒(1000);
}
//------------------------------------------------ -----------------------------------------
// 測量電壓表
// 以 mV 為單位測量 Vin 處的電壓表
//假設電阻器已連接到引腳:
// Ra 從引腳到 5V
// Rb 從引腳到 0V
// Rc 從引腳到 Vin
//------------------------------------------------ -----------------------------------------
長測量電壓表(int pin){
? 常量長 Ra = 120; //從引腳到5V
? 常量長 Rb = 150; //從引腳到0V
? 常量長 Rc = 470; // 從引腳到 Vin
? const int calibrateZero = +20; //校準所以0V =“0V”
? 常量 int calibrateVolts = 1024; // 校準所以 10V = "10V"
? 長Aa,Vb;
? 字節(jié)舊ACSR = ACSR;
? 字節(jié)舊ADCSRA = ADCSRA;
? 字節(jié)老ADCSRB = ADCSRB;
? 字節(jié)老ADMUX = ADMUX;
? 字節(jié)老DIDR0 = DIDR0;
? 字節(jié)老DIDR1 = DIDR1;
? Vb = readVcc();
? Aa = 模擬讀取(引腳);//設置ADC
? 我的延遲(1);// 高輸入阻抗,因此允許穩(wěn)定
? Aa = 模擬讀取(引腳);// 讀取 ADC
? ACSR = 舊ACSR;
? ADCSRA = 舊ADCSRA;
? ADCSRB = 舊ADCSRB;
? ADMUX = oldADMUX;
? DIDR0 = 舊DIDR0;
? DIDR1 = 舊DIDR1;
? 如果 (Aa > 1023 - 10)
??? 返回 LONG_MAX;
? 如果 (Aa < 10)
??? 返回-LONG_MAX;
? 返回 (Aa * Vb - Rc * ((Vb * 1024 - Aa * Vb) / Ra - Aa * Vb / Rb)) / calibrateVolts + calibrateZero;
}
//================================================= ==========================
// 檢查電壓表
// 電壓表測量器
//================================================= ==========================
布爾檢查電壓表(){
? 常量字節(jié) n = 50;
? 靜態(tài)字節(jié) i = 0;
? 靜態(tài)長總和 = 0;
? 長 v;
? 我的延遲(5);
? v = 測量電壓表(6);
? 我++;
? 如果((v |總和)&0x40000000){
??? 總和 = v;
??? Vin = v;
? } 別的 {
??? 總和 += v;
??? Vin =總和/我;
? }
? 如果 (i >= n) {
??? 總和 = 0;
??? 我 = 0;
??? 返回真;
? }
? 返回假;
}
//------------------------------------------------ -----------------------------------------
// InitSigGen
//------------------------------------------------ -----------------------------------------
無效 InitSigGen(無效){
? pinMode(SG_DATA,輸出);
? pinMode(SG_CLK,輸出);
? pinMode(SG_fsyncPin,輸出);
? 數字寫入(SG_fsyncPin,高);
? 數字寫入(SG_CLK,高);
? SG_Reset();
? SG_freqReset(calcFreq(freqSGLo), waveType);
}
//------------------------------------------------ -----------------------------------------
// 主要例程
// 設置函數
//------------------------------------------------ -----------------------------------------
無效設置(無效){
? // 打開串口,波特率為 BAUDRATE b/s
? 串行.開始(波特率);
? // 清除緩沖區(qū)
? memset( (void *)commandBuffer, 0, sizeof(commandBuffer) );
? // 激活中斷
? sei();
? 初始化ADC();
? Serial.println("ArdOsc " __DATE__); // 編譯日期
? Serial.println("OK");
? 設置模式(0);// y 增益 5V
? 設置掃描(5);
? setPwmFrequency(testSignalPin, 3); // 測試信號 976Hz 1024uS
? pinMode(BtnHorz,INPUT_PULLUP);
? pinMode(BtnVert,INPUT_PULLUP);
? pinMode(LED_BUILTIN,輸出);
? Wire.begin(); // 加入 i2c 總線作為主機
??? TWBR = 1; // 頻率=888kHz 周期=1.125uS
? 初始化SH1106();
}
//------------------------------------------------ -----------------------------------------
// 主要例程
// 環(huán)形
//------------------------------------------------ -----------------------------------------
無效循環(huán)(無效){
? 靜態(tài) int ButtonsTimer2 = 0;
? 開關(curMode){
??? 案例毫伏表:
????? 如果(檢查電壓表())
??????? 畫屏();
????? 休息;
??? 默認:
????? if (!SendingSerial) {
??????? 發(fā)送ADC();
??????? 開關(掃描類型){
????????? 案例 sw20Frames: 案例 sw100Frames: 案例 sw500Frames:
??????????? SG_StepSweep();
??????? }
????? }
? }
? 檢查按鈕();
}
評論
查看更多