設(shè)計二進(jìn)制時鐘: 理解多路復(fù)用約束
設(shè)計一個二進(jìn)制時鐘是一個有吸引力的項目,它能讓您展示數(shù)字電子學(xué)、微控制器編程以及物理組裝和中等復(fù)雜電路的能力。關(guān)于這個主題,已經(jīng)有許多文章進(jìn)行了探討。然而,深入探討設(shè)計決策和必要的妥協(xié)的文章卻不多。這是一個遺憾,因為這個主題提供了一個極好的機(jī)會來探索微控制器的物理限制、歐姆定律的應(yīng)用、多路復(fù)用的遞減收益以及易于故障排除的組件布局。
本文引用地址:http://cafeforensic.com/article/202403/456771.htm本文主要討論以二進(jìn)制時鐘形式呈現(xiàn)的六列四行 LED 矩陣,如圖1所示。為了最大的可訪問性,我們將把對話限制在 Arduino Nano Every 1上的I/O引腳和電源的物理功能上。請注意,本文中提出的想法直接適用于七段 LED 顯示器,或者更一般地說,適用于所有多路LED陣列。
現(xiàn)在是回顧二進(jìn)制時鐘操作的好時機(jī),因為我們將重點關(guān)注底層硬件和多路復(fù)用方面。
圖 1 : Arduino Nano Every 在面包板上的圖片,背景是 LED 陣列和 Digilent Analog Discovery。
多路復(fù)用定義
多路復(fù)用是有限資源的一種時間共享形式。在我們的例子中,微控制器只有有限的I/O引腳。通過使用時分多路復(fù)用(TDM),我們能夠用循環(huán)序列連續(xù)激活LED或LED組。通過快速切換 LED 的激活狀態(tài),所有LED看起來都是一樣亮的。這種效果依賴于“閃爍融合”(flicker fusion),即人眼無法檢測到多路復(fù)用陣列中LED的快速切換。
LED通常以公共陽極或公共陰極的形式排列成組。圖2展示了一個具有6列4行 LED 矩陣和公共陽極配置的示例。
技術(shù)提示: 術(shù)語“時分多路復(fù)用”(TDM)通常與以太網(wǎng)等通信系統(tǒng)相關(guān)聯(lián)。在以太網(wǎng)中,許多電腦共享一個通信通道。由于信道是有限的資源,每臺計算機(jī)根據(jù)已建立的通信協(xié)議輪流使用。雖然我們將TDM這一術(shù)語應(yīng)用于LED多路復(fù)用是一種簡化,但它確實為我們理解復(fù)雜的通信系統(tǒng)提供了一個很好的起點。
圖 2 :采用Arduino Nano Every的6列4行復(fù)用LED矩陣示意圖。
公共陽極和公共陰極這兩個術(shù)語會引起混淆。例如,對圖2中的LED矩陣的檢查顯示,許多二極管按列共享陽極連接,按行共享陰極連接。只有當(dāng)我們檢查激活序列時,才會發(fā)現(xiàn)共同的陽極配置。
圖3給出了多路復(fù)用器時序的邏輯分析儀視圖。關(guān)于公共二極管連接,我們注意到在任何給定時間有一個且只有一個列被激活。因此,共同連接是陽極。在本例中,關(guān)聯(lián)列晶體管將所有關(guān)聯(lián)led的陽極拉到5.0 VDC導(dǎo)軌上。單獨的行驅(qū)動器然后應(yīng)用一個地來激活所需的LED(s)。
關(guān)于圖3,我們觀察到:
列驅(qū)動信號呈現(xiàn)為Arduino D2到D7引腳對應(yīng)的前6個信號。為方便起見,已將信號名稱添加為紅色高亮文本。我們看到一個,而且只有一個,信號在任何時候都是活躍的。請注意,PNP晶體管被用作列驅(qū)動器。另外,回想一下,這個晶體管將通過將其底座拉向地面來激活。
最低的4行是Arduino輸出引腳D19到D21對應(yīng)的行驅(qū)動器。這些也是如圖2所示的低電平激活,其中二極管通過將其拉向地面打開。
Digilent Analog Discovery已配置為顯示與4行驅(qū)動器相關(guān)的二進(jìn)制數(shù)。這是一些邏輯分析儀的一個功能,允許操作員將幾行“加入”成一個方便的ASCII, HEX或二進(jìn)制顯示。為了方便起見,我們用藍(lán)色突出顯示了這一點。
技術(shù)提示 :配置LED多路復(fù)用有兩種常用方法,包括公共陽極和公共陰極。本文的特點是PNP列驅(qū)動器與共同陽極LED。在這里,PNP晶體管將公共陽極連接到正軌,然后將接地應(yīng)用到各個陰極。當(dāng)組件旋轉(zhuǎn)90度,將公共驅(qū)動器應(yīng)用于行而不是列時,需要仔細(xì)檢查。
圖 3 :邏輯分析儀屏幕截圖顯示了帶有測試和高光的LED定時。
技術(shù)提示 :關(guān)于多路復(fù)用和共陽極/陰極的討論適用于七段LED顯示器和LED陣列。
當(dāng)前的限制
本文受到Arduino Nano Every功能的限制。這既包括可用的電源,也包括Nano Every的板載電源、單個引腳和引腳組。
具有ATMega4809微控制器規(guī)格的相關(guān)Arduino Nano Every是:
ABX00028-datasheet.pdf (arduino.cc)
5.0直流電源電流限制= 950毫安
單個引腳(源或匯)= 40毫安
電源軌引腳(在高溫下)= 100毫安
對于選用的SSL-LX2573GDLED燈:
室溫穩(wěn)定電流= 25毫安
10 us脈沖的峰值電流= 150毫安
這些數(shù)字中的大多數(shù)是絕對設(shè)計最大額定值。為了長壽命,我們必須保持較寬的安全裕度。作為起點,我們把每一個電流值減半。我們將看到,這個決定對復(fù)用選項有嚴(yán)重的影響。它在圖2原理圖上投下陰影,對晶體管列驅(qū)動器和多路復(fù)用led的數(shù)量產(chǎn)生影響。
技術(shù)提示 :微控制器的內(nèi)部芯片使用小鍵合線連接到I/O引腳。這些線與硅走線一起具有有限的載流能力。這反映在器件的設(shè)計最大規(guī)格上。在前面的例子中,我們看到單個I/O引腳具有40 mA的設(shè)計最大值。然而,三個這樣的引腳在最大電流下工作將壓倒模對電源或模對地的鍵合線或硅走線。這種累積電流是一個重要但經(jīng)常被忽視的設(shè)計考慮因素。這是一個會破壞微控制器的錯誤,也許不會立即破壞,但會導(dǎo)致產(chǎn)品不可靠。
占空比vs亮度
在解釋晶體管列驅(qū)動器和電阻選擇之前,我們需要理解閃爍與LED亮度之間的關(guān)系。請記住,LED矩陣是采用時分多路復(fù)用(TDM)的。每個LED都會“閃爍”,因為它會依次打開和關(guān)閉,如圖3中的時序圖所示。您可能已經(jīng)進(jìn)行過相關(guān)的實驗,探索了LED的閃爍現(xiàn)象。
在您學(xué)習(xí) Arduino 的早期階段,您可能已經(jīng)使用 PWM 命令來調(diào)整 LED 的亮度,例如:
void loop() { static uint8_t val; // Maintain contents across loop iterations analogWrite(LED_PIN, val++); // About 5 seconds to reach 100% duty cycle delay(20); }
這個PWM操作與TDM操作直接相關(guān),當(dāng)我們考慮對單個LED的影響時。在兩種情況下,LED都會以一定的時間開啟和關(guān)閉,并且這種開啟和關(guān)閉的時間比例決定了LED的亮度。
結(jié)果如圖4所示,LED分別以100%、50%、25%、12.5%和6.25%的占空比工作,這分別對應(yīng)于TDM槽的1、2、4、8和16個時間段。對于低占空比的LED,結(jié)果并不理想。事實上,很難看到占空比為1/16的LED。
圖 4 :不同占空比下led的相對亮度。
多路復(fù)用的方法
我們的多路復(fù)用目標(biāo)是獲得最大的LED亮度,同時保持在當(dāng)前的硬件限制內(nèi)?,F(xiàn)在我們對硬件的物理限制有了更好的了解,我們可以探索復(fù)用方法:
一次一個LED:我們可以消除列驅(qū)動器,并允許微控制器直接控制列和行。雖然這對于電路成本和簡單性來說是非常理想的,但我們很快就遇到了當(dāng)前的限制。如果沒有列驅(qū)動器,相關(guān)的微控制器引腳必須提供必要的電流。這限制了我們一次只能使用一個或兩個LED,因為累積電流將超過微控制器設(shè)計的最大值??紤]到6 x 4陣列,這需要4%或8%的占空比。LED會很暗。
每次一列,由微控制器控制列驅(qū)動器和行:使用PNP晶體管向列提供電流,我們可以自由地打開所有行。給定6 × 4陣列,這導(dǎo)致每個LED的占空比為17%。本文中給出的結(jié)果對于光線昏暗的房間來說是最低限度可接受的。
用列、行驅(qū)動晶體管一次一列。這種方法允許LED以高于微控制器容量的電流驅(qū)動。由于LED具有低占空比,因此可以通過增加電流來增加亮度。在LED數(shù)據(jù)表中有一定的模糊性,然而,電流肯定可以增加到設(shè)計最大值,也許是2倍以上,而不會損壞LED。回想一下,我們選擇的LED指定為連續(xù)25 mA和150 mA, 10 us脈沖。這種方法需要仔細(xì)考慮LED溫度,并可能導(dǎo)致LED壽命顯著縮短。使用風(fēng)險自負(fù)。
并行控制:對于這篇文章,我們假設(shè)微控制器引腳是有限的資源。有任何數(shù)量的端口擴(kuò)展選項,將允許直接控制LED。一個例子是8位TLC6C598移位寄存器。它具有50mA開漏驅(qū)動器,最大VDS為40 VDC。74HC595是另一種適用于面包板原型設(shè)計的常見選擇。
電阻的選擇
需要選擇合適的電阻來確定 LED 的工作電流。為柱驅(qū)動晶體管的基極選擇合適的電阻也很重要。當(dāng)激活LED 的數(shù)量隨著顯示的數(shù)量變化時,這個基極電阻對于保持一致的 LED 亮度很重要。
LED 的計算相對簡單。第一步是確定當(dāng)前的局限性。使用所選的多路復(fù)用方案,在任何給定時間最多可激活4個LED。我們遇到的第一個限制是微控制器的累積電流。對于保守的設(shè)計,這是50mA。因此,每個LED被限制在約13 mA。電阻器計算為:
這里我們假設(shè)PNP列驅(qū)動器VCE非常接近于零。
對于晶體管基極電阻的計算,我們將實現(xiàn)一個強(qiáng)制的beta條件。這個晶體管工作點確保晶體管深度飽和,這將確保所有LED具有相同的亮度。為了設(shè)置這個條件,我們選擇電阻器,使基極電流為集電極電流的1/10。給定4個有源LED,集電極電流約為50 mA。通過強(qiáng)制beta操作,我們將強(qiáng)制基極電流為5mA。
改進(jìn)空間
這篇文章的重點是多路LED的各個方面。很少甚至沒有考慮將設(shè)備作為可靠的計時器來操作。本說明所附的代碼使用了 Arduino millis() 函數(shù),該函數(shù)不被稱為可靠的實時計時器。所有這些設(shè)備都依賴于微控制器的高速振蕩器,它不像正確實現(xiàn)的32.768 kHz時鐘晶體那樣精確。
為了提高性能,你可能想要集成一個實時時鐘模塊。對于一個額外的挑戰(zhàn),可以嘗試一個基于 GPS 或 WWVB (原子鐘接收器)的帶天線的計時器。
#define C0_PIN 2
#define C1_PIN 3
#define C2_PIN 4
#define C3_PIN 5
#define C4_PIN 6
#define C5_PIN 7
#define R0_PIN 21
#define R1_PIN 20
#define R2_PIN 19
#define R3_PIN 18
void set_column(uint8_t c) {
digitalWrite(C0_PIN, HIGH);
digitalWrite(C1_PIN, HIGH);
digitalWrite(C2_PIN, HIGH);
digitalWrite(C3_PIN, HIGH);
digitalWrite(C4_PIN, HIGH);
digitalWrite(C5_PIN, HIGH);
switch (c) {
case 0: digitalWrite(C0_PIN, LOW); break;
case 1: digitalWrite(C1_PIN, LOW); break;
case 2: digitalWrite(C2_PIN, LOW); break;
case 3: digitalWrite(C3_PIN, LOW); break;
case 4: digitalWrite(C4_PIN, LOW); break;
case 5: digitalWrite(C5_PIN, LOW); break;
default: break;
}
}
void set_row(uint8_t n) {
bool D0 = ((n & 0x01) == 0);
bool D1 = ((n & 0x02) == 0);
bool D2 = ((n & 0x04) == 0);
bool D3 = ((n & 0x08) == 0);
digitalWrite(R0_PIN, D0);
digitalWrite(R1_PIN, D1);
digitalWrite(R2_PIN, D2);
digitalWrite(R3_PIN, D3);
}
void set_LED(uint8_t c, uint8_t n) {
set_column(c);
set_row(n);
}
void setup() {
pinMode(C5_PIN, OUTPUT);
pinMode(C4_PIN, OUTPUT);
pinMode(C3_PIN, OUTPUT);
pinMode(C2_PIN, OUTPUT);
pinMode(C1_PIN, OUTPUT);
pinMode(C0_PIN, OUTPUT);
pinMode(R3_PIN, OUTPUT);
pinMode(R2_PIN, OUTPUT);
pinMode(R1_PIN, OUTPUT);
pinMode(R0_PIN, OUTPUT);
}
void loop() {
uint8_t i;
uint32_t now = millis() /1000;
uint8_t seconds = now % 60;
uint8_t minutes = (now / 60) % 60;
uint8_t hours = (now / 3600) % 24;
uint8_t hoursHigh = hours / 10;
uint8_t hoursLow = hours % 10;
uint8_t minutesHigh = minutes / 10;
uint8_t minutesLow = minutes % 10;
uint8_t secondsHigh = seconds / 10;
uint8_t secondsLow = seconds % 10;
for (i = 0; i < 6; i++) {
switch (i) {
case 0: set_LED(0, secondsLow); break;
case 1: set_LED(1, secondsHigh); break;
case 2: set_LED(2, minutesLow); break;
case 3: set_LED(3, minutesHigh);break;
case 4: set_LED(4, hoursLow); break;
case 5: set_LED(5, hoursHigh); break;
default: break;
}
delay(2);
}
}
評論