在B站上看到有大佬做了個(gè)8位計(jì)算機(jī),非常感興趣,同時(shí)想了解一下計(jì)算機(jī)底層到底是怎么運(yùn)作的,就跟著做了一個(gè)。以下是筆記,寫的比較細(xì)。 先show一下代碼 | 序號 | 指令 | 說明 |
|---|
| 0 | OUT | 顯示 | | 1 | ADD 15 | 加上地址15的值 | | 2 | JC 4 | 進(jìn)位跳轉(zhuǎn)到地址4 | | 3 | JMP 0 | 沒有進(jìn)位跳轉(zhuǎn)到地址0 | | 4 | SUB 15 | 減去地址15的值 | | 5 | OUT | 顯示 | | 6 | JZ 0 | 為0跳轉(zhuǎn)到地址0 | | 7 | JMP 4 | 不為0跳轉(zhuǎn)到地址4 |
15地址設(shè)置成15; 代碼意思是:值自增15,如果到達(dá)進(jìn)位255就變成自減15,如果自薦到達(dá)0就自增。 
基礎(chǔ)知識二極管單項(xiàng)導(dǎo)通器件 1874年,德國科學(xué)家發(fā)現(xiàn)晶體的整流功能 由半導(dǎo)體硅材料制成,硅本身是沒有電極的,在做晶體管的時(shí)候做了雜化處理,在一段加入了硼,一段加入了磷,硼這端會(huì)多出電子空穴,而磷這一端會(huì)多出自由電子,有意思的事情就發(fā)生了。 
因?yàn)镾i是4個(gè)電子,P有3個(gè)電子,N有5個(gè)電子,所以單純的硅會(huì)形成4個(gè)共價(jià)鍵非常穩(wěn)定。 硅: 
磷:N 
硼:P 
當(dāng)雜化之后,P端就會(huì)有很多電子空穴,N端會(huì)多出很多自由電子,在PN交界的地方,N端電子會(huì)自動(dòng)移動(dòng)到P端,形成一個(gè)耗盡區(qū),耗盡區(qū)的電壓為0.7V,所以大多5V的芯片低電壓為0.2V,如果超過0.7V則視為高電壓 

如果加入正電壓會(huì)使耗盡區(qū)擴(kuò)大,造成正向偏壓,如果加入反向電壓,大于耗盡區(qū)0.7V電壓的時(shí)候,電子從N極向P極移動(dòng)沒有任何障礙。 
繪出曲線,橫坐標(biāo)是電源電壓,縱坐標(biāo)是電流,負(fù)向電壓的時(shí)候幾乎沒有電流,負(fù)向電壓特別大的時(shí)候會(huì)擊穿,正向電壓大于0.7V的時(shí)候會(huì)很快獲得很大的電流。 

二極管的這一特性可以做一個(gè)橋式整流電路 



三極管三極管就是二極管的升級,例如NPN型三極管 
這樣在NP的交界處就會(huì)形成兩個(gè)耗盡區(qū), 
可以看成兩個(gè)二極管背靠背相連,不管電源處于哪個(gè)狀態(tài)總有一個(gè)二極管處于反向加壓的狀態(tài),不導(dǎo)通。但是如果中間加一個(gè)電源(第二個(gè)電源),大量電子會(huì)從P端出來,通過電源到達(dá)N端形成通路 
形成通路后,大量電子會(huì)到P端,形成反向偏壓, 如果整體來看,P端非常的窄,并不會(huì)存儲(chǔ)大量電子,大量電子在第一個(gè)電源的驅(qū)動(dòng)下回到電源,形成電流,因?yàn)榈谝粋€(gè)電源的電壓比較大,驅(qū)動(dòng)力比較大,第二個(gè)電源電壓比較小,驅(qū)動(dòng)力比較小 

這種現(xiàn)象簡而言之就是 一個(gè)小電流被放大成一個(gè)大電流, 一個(gè)斷路變成一個(gè)通路
這種晶體管叫雙極結(jié)晶體管, 
晶體管有兩種工作方式: 通過電流,將一個(gè)小電流放大成大電流,

通過電壓,只要基極和發(fā)射機(jī)有電勢差,集電極和發(fā)射極就會(huì)產(chǎn)生大電流,這種又叫場效應(yīng)管



雙極結(jié)型晶體管做的放大電路 


門電路晶體管的基本原理已經(jīng)知道了,門電路就是基于三極管構(gòu)成相關(guān)電路 非門電路: 

與門電路: 
或門電路 
異或門 

鎖存器鎖存器用來做寄存器 將或門改造一下就可以就是SR鎖存器 
SR鎖存器 


再次進(jìn)階D鎖存器,D鎖存器是構(gòu)建寄存器的基礎(chǔ),本計(jì)算機(jī)種所有的寄存器都是由D鎖存器構(gòu)造 

觸發(fā)器觸發(fā)器是為了獲取極短時(shí)間內(nèi)的上升沿 第一種方法: 


從0變成1的時(shí)候,非門需要幾個(gè)納秒的時(shí)候才能將狀態(tài)轉(zhuǎn)過來,所以在非常短的時(shí)候內(nèi)會(huì)出現(xiàn)都為1,這個(gè)時(shí)候與門輸出1,然后非門后的狀態(tài)0輸入,導(dǎo)致輸出變?yōu)?,這樣輸出只有幾個(gè)納秒是1。 第二種方法: 通過電容來實(shí)現(xiàn) 
電容和電阻,當(dāng)信號來的時(shí)候電容充電,獲得輸出1,當(dāng)幾十納秒后,電容充滿電,信號就變成0了 
計(jì)算時(shí)間 
D觸發(fā)器,就是將之前的SR鎖存器的Enable改造一下 

SR觸發(fā)器: 


SR觸發(fā)器,在SR都為1的時(shí)候,處于一種無效的狀態(tài),沒有任何輸出。當(dāng)SR變成0的時(shí)候,誰慢一點(diǎn)誰就會(huì)被觸發(fā)。這是一種隨機(jī)狀態(tài)。 為了解決這個(gè)問題: 
第一種情況 JK都為0,這是一種隨機(jī)狀態(tài),也成為不確定狀態(tài) 

第二種狀態(tài)K=1,J=0的時(shí)候,處于reset狀態(tài),Q=0,反Q=1 
第三種狀態(tài)K=0,J=1的時(shí)候,處于set狀態(tài)Q=1,反Q=0 
最有意思的是第四種狀態(tài)K=1,J=1的時(shí)候,信號會(huì)發(fā)生一次對調(diào) 
這樣會(huì)出現(xiàn)問題 在這個(gè)脈沖內(nèi)做了很多次轉(zhuǎn)換,也就是只要兩個(gè)輸入都是高電平,這個(gè)轉(zhuǎn)換就一直持續(xù)。 這種情況叫做搶先。 所以發(fā)現(xiàn)這個(gè)根本原因出現(xiàn)這個(gè)脈沖電路上,這個(gè)上升沿時(shí)間太多了。如果時(shí)間控制在100ns的時(shí)間內(nèi)就可以只完成1次轉(zhuǎn)換。 把1K電阻換成100電阻,已經(jīng)控制了100ns的時(shí)間,發(fā)現(xiàn)還是不行 

因?yàn)樾盘栍卸秳?dòng),邊緣探測不銳利 用主從JK觸發(fā)器來解決這個(gè)問題 
高電壓的時(shí)候使第一個(gè)鎖存器工作,在低電壓的時(shí)候使第二個(gè)鎖存器工作。 這樣就完全可以避免之前的問題 可以看到這有兩個(gè)鎖存器,這兩個(gè)鎖存器不可能同時(shí)工作,clock高電位第一個(gè)鎖存器工作,clock低電位第二個(gè)鎖存器工作,主從對應(yīng)的RS正好相反 如果高電壓,主鎖存器是SET,到低電壓的時(shí)候從鎖存器就是reset, 如果都是1的時(shí)候,那么主鎖存器執(zhí)行的操作是由從鎖存器的狀態(tài)決定的,而從鎖存器的狀態(tài)正好與主鎖存器狀態(tài)相反 這樣當(dāng)一個(gè)脈沖來的時(shí)候,set和reset會(huì)執(zhí)行一次交換。 基本模塊計(jì)算機(jī)需要的模塊:1.主脈沖,2.計(jì)數(shù)器,3.計(jì)數(shù)器寄存器,4.寄存器A,5.寄存器B,6.ROM,7.指令寄存器,9.顯示模塊,9.控制模塊,10.標(biāo)志位寄存器。 主脈沖模塊
主脈沖主脈沖使用555芯片 


時(shí)許分析 開始的時(shí)候,沒有上電 
開始上電的時(shí)候 

通過電容放電和充電的時(shí)間來控制方波的占空比, 外界的電容和電阻決定了方波的長度 
通過公式來計(jì)算 總的時(shí)間是0.139S 
在5號引腳加入一個(gè)0.01uf的電容接地,可以降噪 


當(dāng)有信號的時(shí)候,一堆晶體管需要獲取更多的電量,這個(gè)時(shí)候就會(huì)從電源端拉出更多的電流,就會(huì)形成電路中非常常見的過充的現(xiàn)象。 電線也會(huì)產(chǎn)生一些阻抗,也會(huì)阻止電流的變化,所以這個(gè)電壓就會(huì)跳上去, 直接的辦法給電路接一個(gè)非常短的線路 
給正極和負(fù)極加一個(gè)電容,在電路需要電流的時(shí)候給電路提供更多的電流。 
在四號引腳接入一個(gè)5V高電平,防止Reset鎖存器,這樣就不存在誤操作。 
調(diào)整時(shí)鐘的速度,把100K換成可變電阻 
單步脈沖為了更好的測試電路,需要有一個(gè)單步脈沖,類似程序的單步執(zhí)行,按鈕按一下給一個(gè)脈沖 單步脈沖的意思是按1下產(chǎn)生1個(gè)脈沖,用555芯片來消除按鈕的抖動(dòng) 555芯片,消除抖動(dòng)電路,可以控制燈亮的時(shí)間 
電阻是1M,電容是2uf,0.1uf,0.1S時(shí)間間隔,這邊要注意在電路不同的狀態(tài),6,7的電壓應(yīng)該是5V, 

穩(wěn)態(tài)和單穩(wěn)態(tài) 

切換電路
將兩個(gè)狀態(tài)的輸出型號添加到一個(gè)開關(guān)中,切換開關(guān)可以切換2個(gè)狀態(tài)、 但是開關(guān)會(huì)有一個(gè)新的問題,當(dāng)切換的時(shí)候有一個(gè)延遲的問題,這個(gè)時(shí)候需要一個(gè)新的555芯片來解決這個(gè)問題,其實(shí)是用到555芯片內(nèi)的SR鎖存器 
開關(guān)有一個(gè)特性叫做先斷后連, 
這個(gè)電路主要是解決開關(guān)彈跳的問題, 將這三個(gè)電路合并起來 
這樣就可以在自動(dòng)和手動(dòng)切換 
HLT作用是關(guān)閉定時(shí)器,接入低電平, 74LS04有6個(gè)非門 這樣一個(gè)電路需要用到三種芯片效率非常低,可以把電路給改一下 
跟之前的效果一樣,只用到了與非門 最終效果 


總線BUS的工作原理: 
這8條線沒有回路,可以跑1bit的數(shù)據(jù)這非常的靈活 Load:表示數(shù)據(jù)可以放到芯片中 Enable:表示數(shù)據(jù)從芯片放到Bus中 
這里面邊上的藍(lán)色線就是控制線,可以看到這個(gè)控制線就是Clock,所有的部件同步Load 
enable線來控制芯片將數(shù)據(jù)寫到總線中,這需要同時(shí)只有1個(gè)芯片進(jìn)行這樣的操作,不然就會(huì)造成混亂 三態(tài)門在總線中有一個(gè)非常重要的事情,就是同一時(shí)間只有一個(gè)部件向總線中輸出數(shù)據(jù),每個(gè)部件的輸出端其實(shí)就是芯片內(nèi)部門電路的輸出端。 
通常都會(huì)用兩個(gè)這樣輸出, 
三態(tài)門:,0,1,和斷路三種狀態(tài) 

74LS245 8路三態(tài)門芯片 
每個(gè)模塊都接入一個(gè)Enable線,每個(gè)模塊都接入Bus中, 同1時(shí)刻只有一個(gè)模塊Enable線為true,就可以保證只有該數(shù)據(jù)寫入到總線中。 當(dāng)load為高電平的時(shí)候,它會(huì)在下一個(gè)時(shí)鐘周期高電平到來的時(shí)候?qū)⒖偩€中數(shù)據(jù)讀取到模塊中。 所有需要寫入總線的模塊都需要該245模塊 寄存器整個(gè)計(jì)算機(jī)需要8位寄存器A,8位寄存器B,4位計(jì)數(shù)器寄存器,8位指令寄存器 寄存器的構(gòu)造是使用D鎖存器,有高信號就可以保存住高信號 可以通過D觸發(fā)器來構(gòu)建寄存器,同時(shí)加入一個(gè)Load控制,下面這種是Load為0的情況,輸出是什么輸入還是什么 
Load為1的情況,輸入什么輸出還是什么 
74LS74內(nèi)有2個(gè)D觸發(fā)器 


通過搭建上面的電路可以實(shí)現(xiàn) 
數(shù)據(jù)不可以直接輸出到總線中,需要在輸出中加入74LS245 三態(tài)門 74LS173由4個(gè)D觸發(fā)器,包含Load和Enable 

因?yàn)樾枰饨有舨榭醇拇嫫髦械闹?,所?73芯片中的三態(tài)門一直處于打開狀態(tài),外界一個(gè)三態(tài)門來控制輸出。 




本計(jì)算機(jī)種需要用到三個(gè)相同原理的寄存器模塊,寄存器A,寄存器B,指令寄存器。 指令寄存器就是與寄存器A的方向相反 
ALU補(bǔ)碼編碼方式: 用最高位表示符號位,這樣-5和5相加得2是不對的 
另一種編碼方式:得1補(bǔ)碼:用反碼表示負(fù)數(shù) 

-5和5相加得到都是1,這就是得1補(bǔ)碼的原因 
比正確的結(jié)果少1;如果將結(jié)果加1就可以得到正確的結(jié)果 第三種編碼方式:得2補(bǔ)碼,反碼+1表示負(fù)數(shù) 

每一位都有含義 
取反+1; 補(bǔ)碼:取反+1表示負(fù)數(shù),上面為解釋為什么取反+1比較好。 全加器1位加法運(yùn)算,一共就8中情況,前四種不考慮前面的進(jìn)位,后四種情況考慮一下之前的進(jìn)位 結(jié)果有兩位,第一位表示結(jié)算結(jié)果,第二位表示是否有進(jìn)位 
第一位前四種情況可以用異或門來表示 0,0 =》0 0,1=》1 1,0=》1 1,1=》0 
第二位前四種情況可以用與門來表示 0,0=》0 0,1=》0 1,0=》0 1,1=》1 
進(jìn)位4種情況:可以發(fā)現(xiàn)第一位進(jìn)位四種情況正好和之前的相反 那么進(jìn)位的第一位變化的四種情況就可以直接在之前的結(jié)果后面加如一個(gè)異或門。異或門可以控制結(jié)果取反, 


有進(jìn)位的第二位四種情況,不僅要考慮本身有進(jìn)位還要考慮第一位出現(xiàn)進(jìn)位的情況 
將進(jìn)位情況求和 
這個(gè)電路叫做1位全加器 
每個(gè)全加器需要2個(gè)異或門,2個(gè)與門,1一個(gè)或門 1個(gè)異或門需要2個(gè)晶體管 1個(gè)與門需要2個(gè)晶體管 1個(gè)或門需要2個(gè)晶體管 那么可以總結(jié)出1個(gè)全加器需要10個(gè)晶體管,也就是10個(gè)三極管,也就是10個(gè)晶體管可以計(jì)算出1位計(jì)算器。 4個(gè)全加器組合成4位加法器 需要的材料和電路圖 
74LS86內(nèi)有4個(gè)異或門芯片 74LS08內(nèi)有4個(gè)與門芯片 74LS32內(nèi)有4個(gè)或門 2個(gè)四位撥叉開關(guān) 1個(gè)面包板 4個(gè)小燈顯示結(jié)果1個(gè)進(jìn)位 
ALUArithmetic Logic Unit:算術(shù)邏輯單元 該模塊其實(shí)完全由全加器構(gòu)成 用寄存器A和寄存器B,中間加入ALU邏輯電路,這樣該模塊就可以計(jì)算出寄存器A和寄存器B的求和或相減。 對寄存器中的數(shù)據(jù)進(jìn)行操作 
通過之前的全加器來構(gòu)建邏輯單元 , 如何做減法, 現(xiàn)在全加器可以實(shí)現(xiàn)加法,是否可以將被減數(shù)變成負(fù)數(shù)然后執(zhí)行加法運(yùn)算 
通過異或門,當(dāng)A為1的時(shí)候相當(dāng)于取反,當(dāng)A為0的時(shí)候原樣輸出 通過異或門獲取反碼 
4位加法器有一個(gè)進(jìn)位,將這個(gè)1和控制器連接起來,如果如果控制器是減法的話,那正好需要進(jìn)位 這樣就實(shí)現(xiàn)了一個(gè)數(shù)補(bǔ)碼加1的操作。 



中間的就是ALU 


先要進(jìn)行測試,測試是有必要的, 如果出現(xiàn)故障需要先排除故障,先從最簡單的部分入手,然后慢慢縮小范圍。 先設(shè)置A寄存器是0,B寄存器是0 
然后讓B存器器是0,然后讓A每一位依次置1,查看是否有問題,發(fā)現(xiàn)問題然后跟蹤這條線, 然后讓A寄存器是0,然后B依次置1; 出現(xiàn)問題需要刨根問底將其找出來。 不要慌,從第一步開始的第一個(gè)異常,首先分析可能出現(xiàn)這個(gè)現(xiàn)象的原因,大多數(shù)情況下都想不出, 查看接線是否正常,接線正常后查看所有輸出輸入,特定的輸入產(chǎn)生特定的輸出,通過萬用表量輸入和輸出電壓。 
將ALU中產(chǎn)生的數(shù)據(jù)直連到總線中,每當(dāng)有脈沖的時(shí)候,A寄存器從總線中讀取值,ALU從A中讀值,從B中讀值進(jìn)行加操作,并將操作的結(jié)果放到總線中,1個(gè)脈沖實(shí)現(xiàn)加放到總線中讀取總線數(shù)據(jù)的操作。 ROM本計(jì)算機(jī)構(gòu)建了16個(gè)字節(jié)的內(nèi)存; 內(nèi)存的構(gòu)建有兩種方式, 1.直接通過D鎖存器構(gòu)建 2.直接通過一個(gè)電容和一個(gè)晶體管構(gòu)建,然后有一個(gè)電容不停刷新這個(gè)電容的數(shù)據(jù)。 
1word的寄存器,1個(gè)字節(jié)寄存器,輸入輸出,寫和讀 16個(gè)字節(jié) 
哪個(gè)字節(jié)的Enable開,哪個(gè)字節(jié)的數(shù)據(jù)就被讀出來, 這樣需要對16個(gè)字節(jié)進(jìn)行編碼 第一步 需要對16個(gè)字節(jié)進(jìn)行編碼,每個(gè)字節(jié)有8個(gè)D鎖存器,也就是128個(gè)D鎖存器 0-16這16個(gè)數(shù)字表示地址,也就是4個(gè)bit位,這樣一個(gè)數(shù)字代表一個(gè)字節(jié)。 地址譯碼單元直接輸出這個(gè)地址,地址譯碼單元怎么構(gòu)造,首先需要有4個(gè)bit輸入,每個(gè)輸入有高低輸出,然后構(gòu)建一個(gè)有5個(gè)輸入的與門,1位標(biāo)識load,然后四位對應(yīng)地址,那么就有16個(gè)5位輸入與門,代表16個(gè)地址 
這個(gè)地址電路應(yīng)該在內(nèi)存電路的前面,4個(gè)輸入就可以讓內(nèi)存電路輸出該地址的數(shù)據(jù)。 74LS189就是一個(gè)內(nèi)存芯片,是一個(gè)64bit的存儲(chǔ)器,有4個(gè)地址輸入,16個(gè)地址位每個(gè)地址位4個(gè)輸出,其使用的方式就是D寄存器的方式構(gòu)建的內(nèi)存 




因?yàn)檫@邊189的輸出都是低電位有效,所以需要74LS04非門進(jìn)行反轉(zhuǎn),最后接入一個(gè)245三態(tài)門輸出到總線中 地址線需要處理,需求是:實(shí)現(xiàn)從總線中讀取,或者手動(dòng)設(shè)置。 通過4Bit寄存器來獲得輸入,地址寄存器。74LS173正好滿足條件 
地址輸入希望這個(gè)地址寄存器能切換模式手動(dòng)模式和自動(dòng)模式,自動(dòng)模式是從總線中讀取地址,手動(dòng)模式用撥碼開關(guān)來指定地址。 選擇電路
74LS157可以實(shí)現(xiàn)二選一電路 


對撥碼開關(guān)的控制,可以獲得1個(gè)明確0,1信號 

值輸入希望可以手動(dòng)向內(nèi)存中寫入值,同時(shí)也可以選擇從總線中讀入值。 又是一個(gè)選擇電路,但是這邊又8Bit輸入,所以就用了2塊74LS157芯片 


到這可以控制手動(dòng)輸入地址和值的ROM就做好了 計(jì)數(shù)器一個(gè)計(jì)算機(jī)僅僅只有脈沖是不可能正常運(yùn)行的,必須還要有可以指示程序運(yùn)行的計(jì)數(shù)器,指示程序運(yùn)行到 了哪一步。 當(dāng)我們從計(jì)算機(jī)中運(yùn)行程序,這些程序放在內(nèi)存中,它是一條條指令,為了執(zhí)行這些指令需要從內(nèi)存中讀取它,在這個(gè)8位計(jì)算器中需要從地址0開始執(zhí)行。先執(zhí)行地址0的指令,然后執(zhí)行地址1的指令,需要確定當(dāng)前在哪個(gè)地址上執(zhí)行,所以我們需要程序計(jì)數(shù)器。 
在上面我們由JK觸發(fā)器構(gòu)造了一個(gè)計(jì)數(shù)器,這個(gè)程序技術(shù)器也是由4位組成 ,指向下一條需要指向的指令,需要能從總線中讀取數(shù)據(jù) ,這樣可以跳轉(zhuǎn)到別的地址。 程序計(jì)數(shù)器的功能: 第一個(gè)CO就是程序控制器的輸出,把值放到總線中 第二個(gè)J就是jump,從總線中讀取數(shù)據(jù),只獲取4位數(shù)據(jù), 第三個(gè)CE就是控制,控制計(jì)數(shù)器開始計(jì)數(shù)和停止計(jì)數(shù)。不一定每個(gè)脈沖都需要計(jì)數(shù),當(dāng)CE活動(dòng)的時(shí)候,將計(jì)數(shù)器開始計(jì)數(shù) 二分電路怎么把脈沖變成明確的計(jì)數(shù)信號呢? 這就需要之前的基礎(chǔ)知識:主從觸發(fā)器 主從觸發(fā)器的特性,在一次脈沖來的時(shí)候會(huì)進(jìn)行Q和反Q的切換,如果構(gòu)建多個(gè)主從觸發(fā)器,將第一個(gè)主從觸發(fā)器的反Q接到下一個(gè)主從觸發(fā)器的Q,會(huì)發(fā)生什么呢? DM7476就是使用主從觸發(fā)器來構(gòu)造了JK觸發(fā)器 
可以發(fā)現(xiàn)這個(gè)JK觸發(fā)器在下降沿的時(shí)候觸發(fā)。 

接了一個(gè)JK觸發(fā)器可以看的更清楚一些,在每個(gè)脈沖周期,JK觸發(fā)器交換了一次 
當(dāng)去掉一個(gè)顯示的時(shí)候,可以發(fā)現(xiàn)這個(gè)Q亮到不亮再到亮用了2個(gè)脈沖周期 這個(gè)電路稱為二分電路,通過JK觸發(fā)器,將原來的主脈沖的周期擴(kuò)大了一倍。 在原來二分電路的基礎(chǔ)上再加一個(gè)二分JK觸發(fā)器,把第一個(gè)觸發(fā)器的輸出接到下一個(gè)JK觸發(fā)器的輸入 
第二個(gè)JK的轉(zhuǎn)換速度是前一個(gè)的一半,是4倍的主脈沖周期 構(gòu)建4個(gè)JK觸發(fā)器,每一個(gè)都是前一個(gè)的周期的一半 
這樣我們就獲得了一個(gè)2進(jìn)制的計(jì)數(shù)器,可以從0計(jì)數(shù)到15, 計(jì)數(shù)器本計(jì)算機(jī)的計(jì)數(shù)器就是使用了這一原理構(gòu)建,這邊我們使用74LS161作為計(jì)數(shù)器 
其有4個(gè)輸入,4個(gè)輸出,是否寫入控制線,CLock控制線,Enable輸入輸出控制線,清除控制線 這個(gè)芯片非常有用,它的Clock內(nèi)部加了一個(gè)非門,這樣上升沿變成下降沿,我們的JK觸發(fā)器也是下降沿觸發(fā)器 

顯示共陰極和共陽極數(shù)碼管 


構(gòu)建真值表
通過這個(gè)真值表可以獲取a這個(gè)值什么時(shí)候亮 


如果需要顯示真正的數(shù)據(jù),必須要建立一個(gè)真值表,將真值表轉(zhuǎn)化成電路,這樣的電路就是解析器, EEPROM可以替代計(jì)算機(jī)中任何的組合邏輯。 組合邏輯:任何一個(gè)狀態(tài)的輸入對應(yīng)一個(gè)狀態(tài)的輸出 時(shí)序邏輯:寄存器,鎖存器,計(jì)數(shù)器,輸出不進(jìn)取決于當(dāng)前的狀態(tài)也取決于之前的狀態(tài)。 有許多種ROM芯片,這個(gè)芯片是只讀的,還有一種可以變成的只讀芯片的就叫做PROM,提供了一個(gè)空白的芯片,只能寫入一次,寫入之后就不能改變了。EPROM可以重復(fù)寫入,在紫外線的作用下可以擦除內(nèi)部的數(shù)據(jù) 
EEPROM是電可擦寫存儲(chǔ)器,用電就可以擦除。 AT28C16可擦寫只讀存儲(chǔ)器,可以存2K個(gè)字節(jié) 
有兩種封裝形式,直插和貼片, 8條IO引腳,數(shù)據(jù)引腳 11條地址引線,接地線和電源 反CE,反OE和反WE 
需要給WE 一個(gè)100ns-1000ns的時(shí)間, 
用一個(gè)電容和一個(gè)電阻來實(shí)現(xiàn)。RC震蕩電路, 1nf,和680歐姆電阻。 

通過EEPROM來實(shí)現(xiàn)真值表,左邊是地址,右邊的值。 Arduino寫入數(shù)據(jù)

看以下Arduino Nano的引腳數(shù)根本不夠,因?yàn)榈刂肪€11根,數(shù)據(jù)線8根 需要另選一個(gè)方案來向EPROM中寫入數(shù)據(jù)。 通過一個(gè)引腳輸出地址,8根引腳輸出數(shù)據(jù),1根引腳怎么輸出數(shù)據(jù)呢 這邊用到了8個(gè)D觸發(fā)器,思路基本和計(jì)數(shù)器一樣,只不過計(jì)數(shù)的Enable線就是脈沖線,這樣脈沖來一次就+1; 這邊的enable線是通過按鈕輸入,按下為1不按為0 這邊用74LS74來構(gòu)建,其有兩個(gè)D觸發(fā)器 

用4個(gè)74芯片的D觸發(fā)器輸出連接到輸入,構(gòu)建了一個(gè)8位寄存器來獲得8個(gè)連續(xù)的輸入。 當(dāng)脈沖來的時(shí)候按鈕按下為輸入1,不按為輸入0 
Arduino一根數(shù)據(jù)線輸入數(shù)據(jù)問題解決就可以運(yùn)用上面的思路,找到74HC595這個(gè)芯片 

那么現(xiàn)在只需要3根線來控制數(shù)據(jù)輸入,數(shù)據(jù)輸入線DS,時(shí)鐘線SH_CP,和控制輸出線ST_CP 
地址線有11條,所以需要2個(gè)595芯片 

這樣我們的Arduino寫入EEPRom模塊就做好了 現(xiàn)在來寫程序吧; //定義好各個(gè)引腳的標(biāo)志
#define SHIFT_DATA 2
#define SHIFT_CLK 3
#define SHIFT_LATCH 4
#define EEPROM_D0 5
#define EEPROM_D7 12
#define WRITE_EN 13
/*
* 使用移位寄存器將地址數(shù)據(jù)輸出
*/
void setAddress(int address, bool outputEnable) {
shiftOut(SHIFT_DATA, SHIFT_CLK, MSBFIRST, (address >> 8) | (outputEnable ? 0x00 : 0x80));//將地址寫入到595中,高8位
shiftOut(SHIFT_DATA, SHIFT_CLK, MSBFIRST, address);//將地址寫入到595中,低8位
//設(shè)置595輸出地址
digitalWrite(SHIFT_LATCH, LOW);
digitalWrite(SHIFT_LATCH, HIGH);
digitalWrite(SHIFT_LATCH, LOW);
}
/*
* 從指定地址的EEPROM讀取一個(gè)字節(jié)
*/
byte readEEPROM(int address) {
for (int pin = EEPROM_D0; pin <= EEPROM_D7; pin++) {
pinMode(pin, INPUT);
}
setAddress(address, /*outputEnable*/ true);
byte data = 0;
for (int pin = EEPROM_D7; pin >= EEPROM_D0; pin--) {
data = (data << 1) + digitalRead(pin);
}
return data;
}
/*
* 將字節(jié)寫入指定地址的EEPROM。
*/
void writeEEPROM(int address, byte data) {
setAddress(address, /*outputEnable*/ false);//設(shè)置地址到595中并輸出地址
for (int pin = EEPROM_D0; pin <= EEPROM_D7; pin++) {
pinMode(pin, OUTPUT);//設(shè)置引腳
}
for (int pin = EEPROM_D0; pin <= EEPROM_D7; pin++) {
digitalWrite(pin, data & 1);//將數(shù)據(jù)寫到引腳中,只取最后一位
data = data >> 1;
}
digitalWrite(WRITE_EN, LOW);//寫入EMROM
delayMicroseconds(1);
digitalWrite(WRITE_EN, HIGH);
delay(10);
}
/*
* 讀取EEPROM的內(nèi)容并將其打印到串行監(jiān)視器。
*/
void printContents() {
for (int base = 0; base <= 255; base += 16) {
byte data[16];
for (int offset = 0; offset <= 15; offset++) {
data[offset] = readEEPROM(base + offset);
}
char buf[80];
sprintf(buf, "%03x: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
base, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15]);
Serial.println(buf);
}
}
// 用于共陽極7段顯示的4位十六進(jìn)制解碼器
//byte data[] = { 0x81, 0xcf, 0x92, 0x86, 0xcc, 0xa4, 0xa0, 0x8f, 0x80, 0x84, 0x88, 0xe0, 0xb1, 0xc2, 0xb0, 0xb8 };
// 用于共陰極7段顯示的4位十六進(jìn)制解碼器
byte data[] = { 0x7e, 0x30, 0x6d, 0x79, 0x33, 0x5b, 0x5f, 0x70, 0x7f, 0x7b, 0x77, 0x1f, 0x4e, 0x3d, 0x4f, 0x47 };
void setup() {
// put your setup code here, to run once:
pinMode(SHIFT_DATA, OUTPUT);
pinMode(SHIFT_CLK, OUTPUT);
pinMode(SHIFT_LATCH, OUTPUT);
digitalWrite(WRITE_EN, HIGH);//寫低電平有效
pinMode(WRITE_EN, OUTPUT);
Serial.begin(57600);
// Erase entire EEPROM
Serial.print("擦除 EEPROM");
for (int address = 0; address <= 2047; address ++) {
writeEEPROM(address, 0x55);
if (address % 64 == 0) {
writeEEPROM(address, 0x55);
Serial.print(".");
}
}
Serial.println(" done");
// 寫入數(shù)據(jù)
Serial.print("編輯 EEPROM");
for (int address = 0; address < sizeof(data); address ++ ) {//sizeof(data)=16
writeEEPROM(address, data[address]);
if (address % 64 == 0) {//數(shù)據(jù)一共64Bit,
writeEEPROM(address, data[address]);
Serial.print(".");
}
}
Serial.println(" 完成");
// 讀EEPROM中的值
Serial.println("讀.... EEPROM");
printContents();
}
void loop() {
// put your main code here, to run repeatedly:
}


重點(diǎn)看一下 /*
* 使用移位寄存器將地址數(shù)據(jù)輸出
*/
void setAddress(int address, bool outputEnable) {
shiftOut(SHIFT_DATA, SHIFT_CLK, MSBFIRST, (address >> 8) | (outputEnable ? 0x00 : 0x80));
shiftOut(SHIFT_DATA, SHIFT_CLK, MSBFIRST, address);
digitalWrite(SHIFT_LATCH, LOW);
digitalWrite(SHIFT_LATCH, HIGH);
digitalWrite(SHIFT_LATCH, LOW);
}shiftout:一次將數(shù)據(jù)字節(jié)移出一位。從最高(即最左邊)或最低(最右邊)有效位開始。每個(gè)位依次寫入數(shù)據(jù)引腳,然后向時(shí)鐘引腳脈沖(先變高,然后變低),以指示該位可用。 MSBFIRST:最高位有效在先 至此EEPEOM的真值表寫入完畢,我們只使用了16個(gè)地址的數(shù)據(jù),真是極大的浪費(fèi)呢 如何顯示數(shù)據(jù)第一種方案是用三個(gè)EEPROM來表示百,十,個(gè)三個(gè)位的數(shù)據(jù) 
這種方案顯然造成EEPROM的極大浪費(fèi) 第二種方案:復(fù)雜一點(diǎn)點(diǎn),將選擇這種方案,就是順序讓每一個(gè)數(shù)碼管顯示,當(dāng)速度非常塊的時(shí)候,數(shù)碼管看上去就像一直顯示的一樣,怎么才能讓數(shù)碼管順序顯示 這邊我們就用到了上面計(jì)數(shù)器的原理,構(gòu)建一個(gè)單獨(dú)的顯示脈沖,然后通過2個(gè)JK觸發(fā)器就可以獲得4種不同的編碼狀態(tài),00,01,10,11 這邊用74LS76,其正好有兩個(gè)JK觸發(fā)器 
同時(shí)需要將00,01,10,11進(jìn)行解碼,將其變成0001,0010,0100,1000,這樣將這四條線連接到4個(gè)數(shù)碼管,數(shù)碼管就會(huì)順序顯示,這邊我們用到了74LS139 

可以看到該編碼器完美滿足我們的需求。 
構(gòu)建公用真值表 
就是用A10,A9,A8,來表示個(gè)位十位百位 這樣真值表就比較復(fù)雜了 舉個(gè)例子321這個(gè)值的真值表: 
改進(jìn)程序 #define SHIFT_DATA 2
#define SHIFT_CLK 3
#define SHIFT_LATCH 4
#define EEPROM_D0 5
#define EEPROM_D7 12
#define WRITE_EN 13
/*
使用移位寄存器輸出地址位和outputEnable信號。
*/
void setAddress(int address, bool outputEnable) {
shiftOut(SHIFT_DATA, SHIFT_CLK, MSBFIRST, (address >> 8) | (outputEnable ? 0x00 : 0x80));
shiftOut(SHIFT_DATA, SHIFT_CLK, MSBFIRST, address);
digitalWrite(SHIFT_LATCH, LOW);
digitalWrite(SHIFT_LATCH, HIGH);
digitalWrite(SHIFT_LATCH, LOW);
}
/*
從指定地址的EEPROM讀取一個(gè)字節(jié)。
*/
byte readEEPROM(int address) {
for (int pin = EEPROM_D0; pin <= EEPROM_D7; pin += 1) {
pinMode(pin, INPUT);
}
setAddress(address, /*outputEnable*/ true);
byte data = 0;
for (int pin = EEPROM_D7; pin >= EEPROM_D0; pin -= 1) {
data = (data << 1) + digitalRead(pin);
}
return data;
}
/*
將字節(jié)寫入指定地址的EEPROM。
*/
void writeEEPROM(int address, byte data) {
setAddress(address, /*outputEnable*/ false);
for (int pin = EEPROM_D0; pin <= EEPROM_D7; pin += 1) {
pinMode(pin, OUTPUT);
}
for (int pin = EEPROM_D0; pin <= EEPROM_D7; pin += 1) {
digitalWrite(pin, data & 1);
data = data >> 1;
}
digitalWrite(WRITE_EN, LOW);
delayMicroseconds(1);
digitalWrite(WRITE_EN, HIGH);
delay(10);
}
/*
讀取EEPROM的內(nèi)容并將其打印到串行監(jiān)視器。
*/
void printContents() {
for (int base = 0; base <= 255; base += 16) {
byte data[16];
for (int offset = 0; offset <= 15; offset += 1) {
data[offset] = readEEPROM(base + offset);
}
char buf[80];
sprintf(buf, "%03x: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
base, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15]);
Serial.println(buf);
}
}
void setup() {
// put your setup code here, to run once:
pinMode(SHIFT_DATA, OUTPUT);
pinMode(SHIFT_CLK, OUTPUT);
pinMode(SHIFT_LATCH, OUTPUT);
digitalWrite(WRITE_EN, HIGH);
pinMode(WRITE_EN, OUTPUT);
Serial.begin(57600);
// Bit patterns for the digits 0..9
byte digits[] = { 0x7e, 0x30, 0x6d, 0x79, 0x33, 0x5b, 0x5f, 0x70, 0x7f, 0x7b };
writeEEPROM(0,0);
Serial.println("寫入個(gè)位 ");
for (int value = 0; value <= 255; value += 1) {
writeEEPROM(value, digits[value % 10]);
}
Serial.println("寫入十位");
for (int value = 0; value <= 255; value += 1) {
writeEEPROM(value + 256, digits[(value / 10) % 10]);
}
Serial.println("寫入百位");
for (int value = 0; value <= 255; value += 1) {
writeEEPROM(value + 512, digits[(value / 100) % 10]);
}
Serial.println("寫入符號位");
for (int value = 0; value <= 255; value += 1) {
writeEEPROM(value + 768, 0);
}
Serial.println("寫入個(gè)位 (后半部)");
for (int value = -128; value <= 127; value += 1) {
writeEEPROM((byte)value + 1024, digits[abs(value) % 10]);
}
Serial.println("寫入十位 (后半部)");
for (int value = -128; value <= 127; value += 1) {
writeEEPROM((byte)value + 1280, digits[abs(value / 10) % 10]);
}
Serial.println("寫入百位 (后半部)");
for (int value = -128; value <= 127; value += 1) {
writeEEPROM((byte)value + 1536, digits[abs(value / 100) % 10]);
}
Serial.println("寫入符號位 (后半部)");
for (int value = -128; value <= 127; value += 1) {
if (value < 0) {
writeEEPROM((byte)value + 1792, 0x01);
} else {
writeEEPROM((byte)value + 1792, 0);
}
}
// Read and print out the contents of the EERPROM
Serial.println("讀..... EEPROM");
printContents();
}
void loop() {
// put your main code here, to run repeatedly:
}控制數(shù)據(jù)顯示現(xiàn)在數(shù)據(jù)顯示的問題已經(jīng)解決了,下面怎么控制其從Bus種讀取數(shù)據(jù)顯示,這邊肯定不能直接顯示總線的數(shù)據(jù),因?yàn)榭偩€的數(shù)據(jù)是不斷變化的,所以需要一個(gè)8bit寄存器控制讀取總線中的數(shù)據(jù),然后控制其顯示, 這邊使用不同的芯片74LS273 


這邊有8個(gè)輸入,8個(gè)輸出,一個(gè)脈沖引腳,一個(gè)重置線 這邊有一個(gè)問題,這個(gè)芯片沒有IEnable線,如果主脈沖接進(jìn)來,每次脈沖變化都會(huì)讀取值,這個(gè)問題可以通過一個(gè)與門來解決,通過與門接入脈沖和控制線,控制線為1的時(shí)候,脈沖變化才有效 
做個(gè)簡單的總結(jié),將已經(jīng)做好的部件連接到總線 

控制器現(xiàn)在這個(gè)部件就缺少一個(gè)控制邏輯就可以正常工作了,來看看有多少個(gè)控制線 
目前有14根控制線,還要做一個(gè)HTL停機(jī)線,在主脈沖中 
如何控制現(xiàn)在我們寫一個(gè)程序,來手動(dòng)運(yùn)行這個(gè)程序 LDA 14 //將內(nèi)存地址14中內(nèi)容讀取到A寄存器
ADD 15 //把內(nèi)存地址15中內(nèi)容與A寄存器中值相加放到寄存器
OUT //把A寄存器中的內(nèi)容放到輸出模塊 這會(huì)很奇怪,這些命令是哪里來的,在之前的計(jì)算機(jī)構(gòu)造中沒有構(gòu)造任何與命令有關(guān)的內(nèi)容,實(shí)際上這些是我們自己定義的,你可以定義任何想做的命令,這是不是非???。 下面我們來定義 LDA:0001 ADD:0010 OUT:1110 那么程序就被翻譯成機(jī)器語言了 LADA 14 // 0001 1110
ADD 15 // 0010 1111
OUT // 1110 xxxx 這個(gè)程序一共三行,我們在加上行號 LADA 14 // 0000 0001 1110
ADD 15 // 0001 0010 1111
OUT // 0010 1110 xxxx 所以想要運(yùn)行這個(gè)程序我們需要將值寫到ROM中,進(jìn)入手動(dòng)模式輸入ROM值 | 地址 | 值 |
|---|
| 0000 | 0001 1110 | | 0001 | 0010 1111 | | 0010 | 1110 0000 |
|
| | 1110 | 0001 1100(28) | | 1111 | 0000 1110(14) |
這個(gè)代碼翻譯成高級語言就是28+14=? 現(xiàn)在我們需要手動(dòng)控制程序的運(yùn)行 首先將指令從內(nèi)存中讀出來放到指令寄存器中,指令寄存器告訴我們數(shù)據(jù)將怎么解析。 取址周期就是將指令從內(nèi)存中取出來放到指令寄存器中。 計(jì)算器中所有的組件都是由程序計(jì)數(shù)器來協(xié)調(diào),計(jì)數(shù)器記錄了當(dāng)前執(zhí)行到哪條指令。計(jì)數(shù)器是從0開始的。 一開始0000 首先將計(jì)數(shù)器的值放到內(nèi)存地址寄存器中, 
可以看到這邊計(jì)數(shù)器和內(nèi)存地址寄存器都是0, 而0地址上ROM的值就是0001 1110 計(jì)數(shù)器輸出+ CO 內(nèi)存地址寄存器輸入+ MI 給一個(gè)脈沖
將內(nèi)存地址中的值放到指令寄存器中 
可以看到ROM中數(shù)據(jù)給了指令寄存器 將內(nèi)存輸出打開+ RO 指令寄存器輸入+ II 給一個(gè)脈沖
這兩步操作取址的操作就完成了,要執(zhí)行下一個(gè)代碼,計(jì)數(shù)器加一 計(jì)數(shù)器加1 CE+ 
計(jì)數(shù)器加一 給一個(gè)脈沖,計(jì)數(shù)器加一變成0001
執(zhí)行任何的代碼都需要上面的三步,上面三步又稱取址周期,其實(shí)就是將計(jì)數(shù)器對應(yīng)的ROM中的值放到指令寄存器中,然后計(jì)數(shù)器加1。下面來解析命令和執(zhí)行命令,這才是與命令相關(guān)的控制邏輯 LDA指令 LDA 14 ,控制器看到指令寄存器的高四位是0001,就知道這是對應(yīng)LDA的操作,就會(huì)執(zhí)行LDA的控制,這是由控制器完成的,我們稍后構(gòu)建它,現(xiàn)在還是手動(dòng)操作,假設(shè)自己的控制器 將指令寄存器后4BIt 輸入到內(nèi)存地址寄存器中 ,以獲得內(nèi)存地址14中的內(nèi)容 
因?yàn)橹噶罴拇嫫髦挥械谒奈唤尤氲娇偩€中,所以地址寄存器獲取第四位的地址數(shù)據(jù),ROM中顯示了該地址中的值,也就是0001 1100其值為28 指令寄存器輸出 + IO 內(nèi)存地址寄存器輸入 + MI 給一個(gè)脈沖
將內(nèi)存地址中的值輸出到寄存器A 
可以看到內(nèi)存中的值給了寄存器A,同時(shí)因?yàn)榧拇嫫鰾位0,ALU就顯示了A+0的值, 至此完成了LDA的命令,將地址14中的值放到寄存器A中。下面執(zhí)行第二個(gè)命令 內(nèi)存輸出+ RO 寄存器A輸入+ AI 給一個(gè)脈沖
ADD指令解析 ADD 15, 要執(zhí)行到該指令現(xiàn)到取到該指令,跟之前的三部取址周期一樣 
指令計(jì)數(shù)器的值給地址寄存器 
內(nèi)存地址中的值給指令寄存器 
計(jì)數(shù)器加1,這個(gè)時(shí)候控制器通過指令寄存器高四位0010分析出執(zhí)行ADD控制 將指令寄存器后4bit輸入到內(nèi)存地址寄存器中 
將指令寄存器中的低四位放到地址寄存器中,這個(gè)時(shí)候ROM顯示該地址中的值 0000 1110 其值位14 指令寄存器輸出+ IO 內(nèi)存地址寄存器輸入+ MI 給一個(gè)脈沖
將內(nèi)存地址15中的值放到B寄存器中,ALU會(huì)自動(dòng)計(jì)算出值 
可以看到ALU自動(dòng)算出求和的值 內(nèi)存輸出+ RO 寄存器B輸入+ BI 給一個(gè)脈沖
將ALU中的值輸出到寄存器A中 
這邊寄存器A獲得ALU的值,同時(shí)ALU更新了,這邊非常酷,鎖操作只發(fā)生在脈沖的上升沿, ALU的輸出 +EO 寄存器A輸入+AI 給一個(gè)脈沖
OUT命令 OUT,前3步是一樣的 


將A寄存器中的值顯示出來 
將A寄存器輸出+ AO output寄存器輸入 OI 給一個(gè)脈沖
到這程序執(zhí)行完了 總結(jié)一下 
這些小的指令稱為微指令,這些微指令的前三步都是相同的,之后的操作是不同的, 所以需要控制位對每個(gè)指令構(gòu)造控制邏輯 反正我控制位按照一定的順序排序 每一種微指令對應(yīng)一種控制序列。 真正的微指令會(huì)占用余下的時(shí)間片,實(shí)際上我們需要一個(gè)獨(dú)立的計(jì)數(shù)器,所以需要一個(gè)獨(dú)立的計(jì)數(shù)器 上面通過手動(dòng)的方式設(shè)置控制位,然后手動(dòng)發(fā)送一次主脈沖,在兩個(gè)主脈沖之間改變它的控制位,,所以我們實(shí)際上還需要另一個(gè)脈沖來控制 ,這邊可以用主脈沖的倒轉(zhuǎn),通過非門開獲得另一個(gè)脈沖 這邊還要將各個(gè)指令分步,才能夠讓控制器知道執(zhí)行到了哪一步,可以看到每個(gè)指令最多5步,有些步數(shù)可以合并就合并了。從T0-T4,而有些指令用不到4步,那么多余的步數(shù)計(jì)算機(jī)什么也不做就浪費(fèi)了。這是無法避免的 
現(xiàn)在脈沖有了,步數(shù)分解有了,需要將脈沖變成步數(shù),這和程序計(jì)數(shù)器是一樣的,使用74LS161,這是一個(gè)四位的計(jì)數(shù)器, 


計(jì)數(shù)器有了,現(xiàn)在要將計(jì)數(shù)器解碼,這邊用到了74LS138芯片, 

可以看到其轉(zhuǎn)換成明確信號,這邊和顯示部分用到的139解碼是一樣的邏輯 

這邊我們可以可以清晰的看到程序走到了哪個(gè)時(shí)間片,哪一步 下面我們構(gòu)建非??岬氖虑椋簿褪强刂破鞯恼嬷当?/p> 
第一個(gè)取址,可以看到前兩步, 第二個(gè)LDA用了剩余的三步,最后一步什么也沒做。 第三個(gè)ADD也是三部 
用兩個(gè)28C16就可以完成其組合邏輯,其有11條地址線,8個(gè)輸出線。 
將真值表輸入到28C16中就可以完成控制 Reset
這邊如果程序執(zhí)行完成,需要將所有的寄存器清空,這邊我們構(gòu)建這樣一個(gè)reset電路用來一個(gè)74LS00來構(gòu)建 
將reset和~reset接到所有的寄存器 
到目前為止,計(jì)算機(jī)的主體部分就做好了 Arduino寫入指令Arduino的接線方式和之前的顯示解碼器的方式相同,這邊就不過多說了。 直接上程序 #define SHIFT_DATA 2
#define SHIFT_CLK 3
#define SHIFT_LATCH 4
#define EEPROM_D0 5
#define EEPROM_D7 12
#define WRITE_EN 13
#define HLT 0b1000000000000000 // Halt clock HLT信號
#define MI 0b0100000000000000 // Memory address register in 內(nèi)存地址輸入
#define RI 0b0010000000000000 // RAM data in 內(nèi)存數(shù)據(jù)輸入
#define RO 0b0001000000000000 // RAM data out 內(nèi)存數(shù)據(jù)輸出
#define IO 0b0000100000000000 // Instruction register out 指令寄存器輸出
#define II 0b0000010000000000 // Instruction register in 指令寄存器輸入
#define AI 0b0000001000000000 // A register in A寄存器輸入
#define AO 0b0000000100000000 // A register out A寄存器輸出
#define EO 0b0000000010000000 // ALU out ALU輸出
#define SU 0b0000000001000000 // ALU subtract 減法
#define BI 0b0000000000100000 // B register in B寄存器輸入
#define OI 0b0000000000010000 // Output register in 輸出寄存器輸入
#define CE 0b0000000000001000 // Program counter enable 程序計(jì)數(shù)允許
#define CO 0b0000000000000100 // Program counter out 程序計(jì)數(shù)器輸出
#define J 0b0000000000000010 // Jump (program counter in) 程序計(jì)數(shù)器輸入(JUMP)
uint16_t data[] = { // 列是步數(shù),行是不同的指令
MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0, // 0000 - NOP
MI|CO, RO|II|CE, IO|MI, RO|AI, 0, 0, 0, 0, // 0001 - LDA 加載
MI|CO, RO|II|CE, IO|MI, RO|BI, EO|AI, 0, 0, 0, // 0010 - ADD 加法
MI|CO, RO|II|CE, IO|MI, RO|BI, EO|AI|SU, 0, 0, 0, // 0011 - SUB 減法
MI|CO, RO|II|CE, IO|MI, AO|RI, 0, 0, 0, 0, // 0100 - STA 將寄存器A中值寫入ROM中
MI|CO, RO|II|CE, IO|AI, 0, 0, 0, 0, 0, // 0101 - LDI 將指令寄存器中值寫入寄存器A
MI|CO, RO|II|CE, IO|J, 0, 0, 0, 0, 0, // 0110 - JMP 跳轉(zhuǎn)到指令寄存器第四位的計(jì)數(shù)
MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0, // 0111
MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0, // 1000
MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0, // 1001
MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0, // 1010
MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0, // 1011
MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0, // 1100
MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0, // 1101
MI|CO, RO|II|CE, AO|OI, 0, 0, 0, 0, 0, // 1110 - OUT 輸出
MI|CO, RO|II|CE, HLT, 0, 0, 0, 0, 0, // 1111 - HLT 停機(jī)
};
/*
*使用移位寄存器輸出地址位和outputEnable信號。
*/
void setAddress(int address, bool outputEnable) {
shiftOut(SHIFT_DATA, SHIFT_CLK, MSBFIRST, (address >> 8) | (outputEnable ? 0x00 : 0x80));
shiftOut(SHIFT_DATA, SHIFT_CLK, MSBFIRST, address);
digitalWrite(SHIFT_LATCH, LOW);
digitalWrite(SHIFT_LATCH, HIGH);
digitalWrite(SHIFT_LATCH, LOW);
}
/*
* 從指定地址的EEPROM讀取一個(gè)字節(jié)。
*/
byte readEEPROM(int address) {
for (int pin = EEPROM_D0; pin <= EEPROM_D7; pin += 1) {
pinMode(pin, INPUT);
}
setAddress(address, /*outputEnable*/ true);
byte data = 0;
for (int pin = EEPROM_D7; pin >= EEPROM_D0; pin -= 1) {
data = (data << 1) + digitalRead(pin);
}
return data;
}
/*
* 將字節(jié)寫入指定地址的EEPROM。
*/
void writeEEPROM(int address, byte data) {
setAddress(address, /*outputEnable*/ false);//設(shè)置地址
for (int pin = EEPROM_D0; pin <= EEPROM_D7; pin += 1) {
pinMode(pin, OUTPUT);//設(shè)置數(shù)據(jù)輸出引腳
}
for (int pin = EEPROM_D0; pin <= EEPROM_D7; pin += 1) {
digitalWrite(pin, data & 1);//每個(gè)數(shù)據(jù)引腳賦值
data = data >> 1;
}
digitalWrite(WRITE_EN, LOW);//設(shè)置脈沖
delayMicroseconds(1);
digitalWrite(WRITE_EN, HIGH);
delay(10);
}
/*
* 讀取EEPROM的內(nèi)容并將其打印到串行監(jiān)視器。
*/
void printContents() {
for (int base = 0; base <= 255; base += 16) {
byte data[16];
for (int offset = 0; offset <= 15; offset += 1) {
data[offset] = readEEPROM(base + offset);
}
char buf[80];
sprintf(buf, "%03x: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
base, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15]);
Serial.println(buf);
}
}
void setup() {
// put your setup code here, to run once:
pinMode(SHIFT_DATA, OUTPUT);
pinMode(SHIFT_CLK, OUTPUT);
pinMode(SHIFT_LATCH, OUTPUT);
digitalWrite(WRITE_EN, HIGH);
pinMode(WRITE_EN, OUTPUT);
Serial.begin(57600);
// 寫數(shù)據(jù)
Serial.print("寫 EEPROM");
writeEEPROM(0, 0);
// 將微碼的8個(gè)高位寫到EEPROM的前128個(gè)字節(jié)中
for (int address = 0; address < sizeof(data)/sizeof(data[0]); address += 1) {
writeEEPROM(address, data[address] >> 8);
if (address % 64 == 0) {
writeEEPROM(address, data[address] >> 8);
Serial.print(".");
}
}
// 將微碼的8個(gè)低位寫到EEPROM的前128個(gè)字節(jié)中
for (int address = 0; address < sizeof(data)/sizeof(data[0]); address += 1) {
writeEEPROM(address + 128, data[address]);
if (address % 64 == 0) {
writeEEPROM(address + 128, data[address]);
Serial.print(".");
}
}
Serial.println(" done");
// 讀并打印出EERPROM的內(nèi)容
Serial.println("讀 EEPROM");
printContents();
}
void loop() {
// put your main code here, to run repeatedly:
}添加了更多的指令 ,SUB,STA,LDI,JMP 這時(shí)候計(jì)算機(jī)可以做更多的功能了。 標(biāo)志跳轉(zhuǎn)現(xiàn)在討論一個(gè)問題: 這是不是計(jì)算機(jī)這是不是計(jì)算機(jī),還只是一個(gè)計(jì)算器 這個(gè)計(jì)算機(jī)的頻率只有300HZ左右 是否需要乘法,指數(shù),對數(shù),三角函數(shù)等指令,這些指令肯定是做不出來的,那么問題就回來了我們真正需要什么樣的指令,什么樣的指令才能稱為計(jì)算機(jī),計(jì)算機(jī)是什么? 計(jì)算機(jī): 可以完成任何的指令 可以完成任何的可計(jì)算的問題 什么是可計(jì)算的什么是不可計(jì)算的 這不是計(jì)算機(jī)性能的問題,是通過算法能完成的問題 那么問題就變成了我們需要完成什么樣的算法。 
這個(gè)問題在計(jì)算機(jī)早期圖靈就進(jìn)行研究過 1936年 他寫了關(guān)于這個(gè)問題的一篇論文。這篇論文得出的結(jié)論是,他可以發(fā)明一種機(jī)器,可以完成任何計(jì)算序列 他是這樣描述的: 
有一個(gè)無限長的紙帶,上面有方格,有1和0兩種狀態(tài),有一個(gè)小旗子可以指向這些方格,小旗子有一個(gè)狀態(tài)A,一次只能移動(dòng)一個(gè)。 有一個(gè)小旗子和其狀態(tài)的真值表 現(xiàn)在這個(gè)狀態(tài),A ,瀏覽狀態(tài)是1,就將1寫到袋子上,然后向左移動(dòng)一格,自身的狀態(tài)變成C,就變成了下面的狀態(tài) 
根據(jù)這個(gè)真值表進(jìn)行一直不停的循環(huán)做,一旦停止到Halt,紙帶上就是結(jié)果, 這個(gè)機(jī)器就能完成任何的數(shù)學(xué)算法。只需要設(shè)置好這個(gè)指令表就好了 實(shí)際上圖靈還提高一個(gè)更好的計(jì)算機(jī),稱為通用計(jì)算機(jī),這個(gè)機(jī)器上有一個(gè)指令表,是一個(gè)最基本的狀態(tài),其他計(jì)算機(jī)可以通過編碼的方法將算法映射到這個(gè)指令表上 
到這邊就知道了任何可計(jì)算的問題都可以變成一個(gè)可計(jì)算的序列 在同一個(gè)時(shí)期邱奇也思考了相同的問題 
他寫了一篇論文關(guān)于什么是計(jì)算能力的定義,從完全不同的角度切入這個(gè)問題,他提出新的數(shù)學(xué)系統(tǒng)稱為論的演算。 
這便有一些變量,有一些函數(shù),還有一些函數(shù)的結(jié)果 
在論文的后面,他定義了一些函數(shù),他用這個(gè)方法表達(dá)計(jì)算機(jī),有點(diǎn)像現(xiàn)在的Lambda表達(dá)式 這篇論文的結(jié)論是:不是所有的問題都可以通過計(jì)算解決,有些可以,有些不可以, 在1936年兩個(gè)人從兩種不同的角度思考了這個(gè)問題 當(dāng)圖靈在8月份讀到邱奇的論文,將邱奇的論文放到了附錄中,任何問題可以轉(zhuǎn)換成論的計(jì)算的問題都可以轉(zhuǎn)化成一個(gè)可計(jì)算的問題 
我們計(jì)算機(jī)和圖靈機(jī)比較還缺少什么呢,圖靈機(jī)有一個(gè)操作我們做不到,同一個(gè)指令可以有不同的操作 
如果紙帶是空格向右移動(dòng)如果紙帶為1向左移動(dòng), 有一種指令叫做有條件跳轉(zhuǎn)指令可以做到這一點(diǎn),它和我們的跳轉(zhuǎn)指令有一點(diǎn)像,現(xiàn)在的跳轉(zhuǎn)指令只能跳轉(zhuǎn)到固定的地址 

左右等價(jià) 根據(jù)不同的值來進(jìn)行不同的行為 所以我們可以說如果實(shí)現(xiàn)條件跳轉(zhuǎn)指令我們就可以模擬任何圖靈機(jī) 
條件跳轉(zhuǎn)準(zhǔn)備實(shí)現(xiàn)兩個(gè)條件跳轉(zhuǎn)指令,為0跳轉(zhuǎn)和進(jìn)位跳轉(zhuǎn)0 為0跳轉(zhuǎn),這個(gè)跳轉(zhuǎn)需要計(jì)算ALU中所有的值是否為0 , 

使用這個(gè)電路我們就可以判斷是否為0 74LS08有4個(gè)與門和74LS02有4個(gè)Nor門 

進(jìn)位跳轉(zhuǎn) ALU中高4位芯片有一個(gè)進(jìn)位引腳,我們很容易就可以判斷出是否進(jìn)位了。 

這邊就搭建好了2個(gè)標(biāo)識,但是有一個(gè)問題, 
在獲得這個(gè)標(biāo)識后,加命令還有一步就是將ALU中的值放到寄存器A中,這樣在進(jìn)行跳轉(zhuǎn)指令的時(shí)候標(biāo)識就沒有了, 所以這邊需要將進(jìn)位標(biāo)識存起來,這邊我們需要一個(gè)173芯片 其實(shí)Internal x86也有進(jìn)位標(biāo)識計(jì)數(shù)器 
一共32位 
這樣就多了一個(gè)控制線,F(xiàn)I:標(biāo)識Flag的輸入, 
這是新的真值表,用了10個(gè)地址位,非常棒 直接用Arduino寫入真值表 #define SHIFT_DATA 2
#define SHIFT_CLK 3
#define SHIFT_LATCH 4
#define EEPROM_D0 5
#define EEPROM_D7 12
#define WRITE_EN 13
#define HLT 0b1000000000000000 // Halt clock HLT信號
#define MI 0b0100000000000000 // Memory address register in 內(nèi)存地址輸入
#define RI 0b0010000000000000 // RAM data in 內(nèi)存數(shù)據(jù)輸入
#define RO 0b0001000000000000 // RAM data out 內(nèi)存數(shù)據(jù)輸出
#define IO 0b0000100000000000 // Instruction register out 指令寄存器輸出
#define II 0b0000010000000000 // Instruction register in 指令寄存器輸入
#define AI 0b0000001000000000 // A register in A寄存器輸入
#define AO 0b0000000100000000 // A register out A寄存器輸出
#define EO 0b0000000010000000 // ALU out ALU輸出
#define SU 0b0000000001000000 // ALU subtract 減法
#define BI 0b0000000000100000 // B register in B寄存器輸入
#define OI 0b0000000000010000 // Output register in 輸出寄存器輸入
#define CE 0b0000000000001000 // Program counter enable 程序計(jì)數(shù)允許
#define CO 0b0000000000000100 // Program counter out 程序計(jì)數(shù)器輸出
#define J 0b0000000000000010 // Jump (program counter in) 程序計(jì)數(shù)器輸入(JUMP)
#define FI 0b0000000000000001 // Flags in Flags 標(biāo)志位輸入
#define FLAGS_Z0C0 0
#define FLAGS_Z0C1 1
#define FLAGS_Z1C0 2
#define FLAGS_Z1C1 3
#define JC 0b0111
#define JZ 0b1000
uint16_t UCODE_TEMPLATE[16][8] = {
{ MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0 }, // 0000 - NOP
{ MI|CO, RO|II|CE, IO|MI, RO|AI, 0, 0, 0, 0 }, // 0001 - LDA
{ MI|CO, RO|II|CE, IO|MI, RO|BI, EO|AI|FI, 0, 0, 0 }, // 0010 - ADD
{ MI|CO, RO|II|CE, IO|MI, RO|BI, EO|AI|SU|FI, 0, 0, 0 }, // 0011 - SUB
{ MI|CO, RO|II|CE, IO|MI, AO|RI, 0, 0, 0, 0 }, // 0100 - STA
{ MI|CO, RO|II|CE, IO|AI, 0, 0, 0, 0, 0 }, // 0101 - LDI
{ MI|CO, RO|II|CE, IO|J, 0, 0, 0, 0, 0 }, // 0110 - JMP
{ MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0 }, // 0111 - JC
{ MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0 }, // 1000 - JZ
{ MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0 }, // 1001
{ MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0 }, // 1010
{ MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0 }, // 1011
{ MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0 }, // 1100
{ MI|CO, RO|II|CE, 0, 0, 0, 0, 0, 0 }, // 1101
{ MI|CO, RO|II|CE, AO|OI, 0, 0, 0, 0, 0 }, // 1110 - OUT
{ MI|CO, RO|II|CE, HLT, 0, 0, 0, 0, 0 }, // 1111 - HLT
};
uint16_t ucode[4][16][8];//主要把指令根據(jù)進(jìn)位劃分一下
void initUCode() {
// ZF = 0, CF = 0
memcpy(ucode[FLAGS_Z0C0], UCODE_TEMPLATE, sizeof(UCODE_TEMPLATE));
// ZF = 0, CF = 1
memcpy(ucode[FLAGS_Z0C1], UCODE_TEMPLATE, sizeof(UCODE_TEMPLATE));
ucode[FLAGS_Z0C1][JC][2] = IO|J;
// ZF = 1, CF = 0
memcpy(ucode[FLAGS_Z1C0], UCODE_TEMPLATE, sizeof(UCODE_TEMPLATE));
ucode[FLAGS_Z1C0][JZ][2] = IO|J;
// ZF = 1, CF = 1
memcpy(ucode[FLAGS_Z1C1], UCODE_TEMPLATE, sizeof(UCODE_TEMPLATE));
ucode[FLAGS_Z1C1][JC][2] = IO|J;
ucode[FLAGS_Z1C1][JZ][2] = IO|J;
}
/*
* 使用移位寄存器輸出地址位和outputEnable信號。
*/
void setAddress(int address, bool outputEnable) {
shiftOut(SHIFT_DATA, SHIFT_CLK, MSBFIRST, (address >> 8) | (outputEnable ? 0x00 : 0x80));
shiftOut(SHIFT_DATA, SHIFT_CLK, MSBFIRST, address);
digitalWrite(SHIFT_LATCH, LOW);
digitalWrite(SHIFT_LATCH, HIGH);
digitalWrite(SHIFT_LATCH, LOW);
}
/*
* 從指定地址的EEPROM讀取一個(gè)字節(jié)。
*/
byte readEEPROM(int address) {
for (int pin = EEPROM_D0; pin <= EEPROM_D7; pin += 1) {
pinMode(pin, INPUT);
}
setAddress(address, /*outputEnable*/ true);
byte data = 0;
for (int pin = EEPROM_D7; pin >= EEPROM_D0; pin -= 1) {
data = (data << 1) + digitalRead(pin);
}
return data;
}
/*
* 將字節(jié)寫入指定地址的EEPROM。
*/
void writeEEPROM(int address, byte data) {
setAddress(address, /*outputEnable*/ false);
for (int pin = EEPROM_D0; pin <= EEPROM_D7; pin += 1) {
pinMode(pin, OUTPUT);
}
for (int pin = EEPROM_D0; pin <= EEPROM_D7; pin += 1) {
digitalWrite(pin, data & 1);
data = data >> 1;
}
digitalWrite(WRITE_EN, LOW);
delayMicroseconds(1);
digitalWrite(WRITE_EN, HIGH);
delay(10);
}
/*
*讀取EEPROM的內(nèi)容并將其打印到串行監(jiān)視器。
*/
void printContents(int start, int length) {
for (int base = start; base < length; base += 16) {
byte data[16];
for (int offset = 0; offset <= 15; offset += 1) {
data[offset] = readEEPROM(base + offset);
}
char buf[80];
sprintf(buf, "%03x: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
base, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15]);
Serial.println(buf);
}
}
void setup() {
// put your setup code here, to run once:
initUCode();
pinMode(SHIFT_DATA, OUTPUT);
pinMode(SHIFT_CLK, OUTPUT);
pinMode(SHIFT_LATCH, OUTPUT);
digitalWrite(WRITE_EN, HIGH);
pinMode(WRITE_EN, OUTPUT);
Serial.begin(57600);
// Program data bytes
Serial.print("寫 EEPROM");
// 將微碼的8個(gè)高位寫到EEPROM的前128個(gè)字節(jié)中
writeEEPROM(0,0);
for (int address = 0; address < 1024; address += 1) {
int flags = (address & 0b1100000000) >> 8;//flag標(biāo)識
int byte_sel = (address & 0b0010000000) >> 7;//高低位標(biāo)識
int instruction = (address & 0b0001111000) >> 3;//指令
int step = (address & 0b0000000111);//步數(shù)
if (byte_sel) {//高低位
writeEEPROM(address, ucode[flags][instruction][step]);
} else {
writeEEPROM(address, ucode[flags][instruction][step] >> 8);
}
if (address % 64 == 0) {
if (byte_sel) {
writeEEPROM(address, ucode[flags][instruction][step]);
} else {
writeEEPROM(address, ucode[flags][instruction][step] >> 8);
}
Serial.print(".");
}
}
Serial.println(" done");
// Read and print out the contents of the EERPROM
Serial.println("讀 EEPROM");
printContents(0, 1024);
}
void loop() {
// put your main code here, to run repeatedly:
}到這就做好了。 總結(jié)我收獲了什么: 計(jì)算機(jī)底層是怎么運(yùn)行,控制器是怎么控制 調(diào)試的時(shí)候也遇到一些坑 寄存器沒有正常工作指令計(jì)數(shù)器工作正常,寄存器A和寄存器B工作不正常,這三個(gè)模塊是同一個(gè)脈沖線接過來的,先接入指令計(jì)數(shù)器,再接入寄存器A和寄存器B, 一開始并沒有懷疑脈沖線的問題,因?yàn)橹噶钣?jì)數(shù)器正常工作,寄存器沒有正常工作,檢查了寄存器的接線發(fā)現(xiàn)沒有問題,量了電壓發(fā)現(xiàn)脈沖電壓非常小0.02V波動(dòng),這也太不正常了,量了下指令計(jì)數(shù)器的電壓是正常的,這就很奇怪了,后來發(fā)現(xiàn)最后寄存器脈沖線短路接地了,導(dǎo)致一直沒有脈沖, 控制器沒有正常工作發(fā)現(xiàn)控制器是輸出不正常,做了個(gè)簡單的測試電路,手動(dòng)檢查控制器的eprom內(nèi)存的值,發(fā)現(xiàn)確實(shí)沒有輸出正確的值,檢查Arduino nano的寫入接線和視頻中接線不同,導(dǎo)致寫入數(shù)據(jù)地址也不相同,調(diào)整Arduino nano和控制線,輸出正常, 經(jīng)驗(yàn)每個(gè)模塊先用跳線接一下再進(jìn)行測試,如果發(fā)現(xiàn)測試沒有問題再用標(biāo)準(zhǔn)接線將其接通, 正常調(diào)試需要一步步執(zhí)行,當(dāng)出現(xiàn)異常了先解決出現(xiàn)的第一個(gè)異常,然后再解決剩余的異常,遇到異常不要慌,一步步解決,不要跳過問題進(jìn)行下一個(gè)問題。
引用大佬的視頻教程,截圖基本都源自于該大佬,并稍加改動(dòng)
|