1??項目背景? ?(技術Q交流群:544453837)
? ?? ?RS-485
? ?? ?是從RS-422基礎上發展而來的,所以RS-485許多電氣規定與RS-422相仿。如都采用平衡傳輸方式、都需要在傳輸線上接終接電阻等。RS-485可以采用二線與四線方式,二線制可實現真正的多點雙向通信,而采用四線連接時,與RS-422一樣只能實現點對多的通信,即只能有一個主(Master)設備,其余為從設備,但它比RS-422有改進,無論四線還是二線連接方式總線上可多接到32個設備。
? ?? ?RS-485與RS-422的不同還在于其共模輸出電壓是不同的,RS-485是-7V至+12V之間,而RS-422在-7V至+7V之間,RS-485接收器最小輸入阻抗為12kΩ、RS-422是4kΩ;由于RS-485滿足所有RS-422的規范,所以RS-485的驅動器可以在RS-422網絡中應用。
? ?? ?RS-485與RS-422一樣,其最大傳輸距離約為1219米,最大傳輸速率為10Mb/s。平衡雙絞線的長度與傳輸速率成反比,在100kb/s速率以下,才可能使用規定最長的電纜長度。只有在很短的距離下才能獲得最高速率傳輸。一般100米長雙絞線最大傳輸速率僅為1Mb/s。
? ?? ?CH340
? ?? ?由于串口(COM)不支持熱插拔及傳輸速率較低,目前大部分新主板和便攜電腦已開始取消該接口,只有工控和測量設備以及部分通信設備中還保留有串口。
? ?? ?現在的電腦大部分都有USB接口而沒有串口,為了使用串口,我們需要一個USB轉串口的芯片,它的功能是讓電腦把USB當串口來使用。這種類型的芯片很多,明德揚教學板使用的是CH340芯片。
? ?? ?CH340是一個USB總線的轉接芯片,實現USB轉串口、USB轉IrDA紅外或者USB轉打印口。
? ?? ?在串口方式下,CH340提供常用的MODE聯絡信號,用于為計算機擴展異步串口中,或者將普通的串口設備直接升級到USB總線。
? ?? ?明德揚教學板的串口功能原理如下圖所示。電腦通過USB線,連接到教學板上的USB接口,USB接口連接到CH340芯片,CH340芯片與FPGA相連。在FPGA看來,串口其實就是兩根線:輸入線USB_RXD和輸出線USB_TXD,其他電氣特性、電平轉換的工作,都由CH340搞好了。FPGA通過USB_RXD接收來自電腦過來的串口數據;通過USB_TXD發數據給電腦。

串口時序
? ?? ?USB_RXD和USB_TXD傳輸數據時,是將傳輸數據的每個字符一位接一位地傳輸。下面是USB_RXD和USB_TXD的時序。USB_RXD的時序由CH340芯片產生,FPGA根據時序來接收數據;USB_TXD的時序由FPGA芯片產生,FPGA要按規范來產生時序,使得CH340可以正確地接收。我們可以把產生時序的叫MASTER(主),接收數據叫SLAVE(從)。

串口時序主要包括:空閑、起始位、數據拉、校驗位和停止位。
? ?? ?空閑:空閑狀態下,數據線一直處于高電平狀態。
? ?? ?起始位:當MASTER要發送數據時,首先會將數據線拉低“一段時間”,從而告知SLAVE有數據要傳輸了,要做好準備。
? ?? ?數據位:起始位之后是數據位,數據位的位數由雙方約定,支持4、5、6、7、8位等。雙方約定后才能正確地傳輸。每個數據位傳輸時都會占用“一段時間”,并且是從低位開始傳輸。圖中LSB是低位的意思,MSB是高位的意思。例如要傳輸數據8’b00000001,傳輸時是先送最低位的“1”。
? ?? ?檢驗位:奇偶校驗是一種非常簡單常用的數據校驗方式,分為奇校驗和偶校驗。奇校驗需要保證傳輸的數據總共有奇數個邏輯高電平,若是偶校驗則要保證傳輸的數據有偶數個邏輯高電平。即“奇偶”的意思就是數據中(包括該校驗位)中1的個數。例如:傳輸的數據位是0100_0011。如果是奇校驗,校驗位是0,偶校驗校驗位是1。校驗位不是必須項,雙方可以約定不需要校驗位,或者用奇校驗,或者使用偶校驗。
? ?? ?停止位:最后一個是停止位,MASTER必須保證有停止位,即把數據線變高“一段時間”。由于數據是在傳輸線上定時的,并且每一個設備有其自己的時鐘,很可能在通信中兩臺設備間出現了小小的不同步。因此停止位不僅僅是表示傳輸的結束,并且提供計算機校正時鐘同步的機會。讓SLAVE可以正確地識別下一輪數據的起始位。假如沒有停止位,校驗碼剛好是0,數據連續發送,那么SLAVE就沒法判斷下一輪的起始位。對于SLAVE來說,接收完數據位或校驗位后就表示接收完成,在停止位不需要做什么,只是等待下一輪起始就夠了。
? ?? ?在時序圖中,每個數據都會傳輸“一段時間”,這個一段時間非常重要,傳輸雙方都要做好約定,否則就不能正確地通信。那么這個“一段時間”是多長時間呢?這跟波特率有關。在串口通信中,波特率是一個非常重要的概念。串口通信中常用的波特率是9600、19200、38400、57600、115200。波特率是每個碼元傳輸的速率,在二進制數據傳輸中,和比特率相同,都是每個比特數據傳輸的速率,其倒數為1bit數據的位寬,也就是1bit數據持續的時間。有了這一時間段,就可用FPGA構造計數器實現比特周期的延時,從而實現特定的數據傳輸波特率。
? ?? ?例如,假設波特率為9600,數據位為8位,沒有校驗位,電腦要發數據8’b00110001給FPGA。考慮到波特率為9600,即每位占用時間為1s/9600=104166ns。那么FPGA的USB_RXD(圖中的rx_uart)這根線將如下圖變化。


?中間信號,trigger連到觸發器的信號輸入端D,觸發器的輸出器連的是tri_ff0。將trigger取反,與tri_ff0相與,就得到信號neg_edge,如果neg_edge=1就表示檢測到trigger的下降沿。將tri_ff0取反,與trigger相與,就得到信號pos_edge,如果pos_edge=1,就表示檢測到trigger的上升沿。
? ?? ?我們來講解這個原理,畫出信號的波形圖。

Tri_ff0是觸發器的輸出,因此tri_ff0的信號與trigger信號相似,只是相差一個時鐘周期。我們也可以這樣理解:每個時鐘上升沿看到的tri_ff0的值,其實就是triffer信號上一個時鐘看到的值,也就是tri_ff0是trigger之前的值。
? ?? ?然后我們在看第3時鐘上升沿,此時trigger值為0,而tri_ff0的值為1,即當前trigger的值為0,之前的值為1,這就是下降沿,此時neg_edge為1。當看到neg_edge為1,就表示檢測到trigger的下降沿了。
? ?? ?同樣道理,在第7個時鐘上升沿,看到trigger值為1,而之前值為0,pos_edge為1,表示檢測到trigger的上升沿。
? ?? ?Verilog實現邊沿檢測電路的代碼。
?Tri_ff0是觸發器的輸出,因此tri_ff0的信號與trigger信號相似,只是相差一個時鐘周期。我們也可以這樣理解:每個時鐘上升沿看到的tri_ff0的值,其實就是triffer信號上一個時鐘看到的值,也就是tri_ff0是trigger之前的值。
? ?? ?然后我們在看第3時鐘上升沿,此時trigger值為0,而tri_ff0的值為1,即當前trigger的值為0,之前的值為1,這就是下降沿,此時neg_edge為1。當看到neg_edge為1,就表示檢測到trigger的下降沿了。
? ?? ?同樣道理,在第7個時鐘上升沿,看到trigger值為1,而之前值為0,pos_edge為1,表示檢測到trigger的上升沿。
? ?? ?Verilog實現邊沿檢測電路的代碼。
3.2.2??異步信號同步化
? ?? ?在討論邊沿檢測的波形中,我們把trigger當成理想的同步信號,也就是trigger是滿足D觸發器的建立和保持時間的,這在同步系統中不是問題。但如果trigger不是理想的同步信號,例如外部按鍵信號,例如本工程的rx_uart信號。這些信號什么時候變化,完全是隨機的。很有可能,在時鐘上升沿變化,從而不滿足觸發器的建立時間和保持時間要求,從而出現亞穩態,導致系統崩潰。詳細的原因,可以看D觸發器中,亞穩態一節的內容。根據這一節內容的結論,我們需要對進來的信號打兩拍(用兩個觸發器寄存一下),再來使用。
假設輸入的信號trigger不是同步信號,那么要將該信號用2個觸發器進行寄存,得到tri_ff0和tri_ff1。需要特別注意的是,tri_ff0絕對不要拿來當條件使用,只能使用tri_ff1。我們還需要檢測邊沿,根據前面所說,再用寄存器寄存,得到tri_ff2。根據tri_ff1和tri_ff2,我們就可以得到邊沿檢測。當tri_ff1==1且tri_ff2==0時,上升沿的pos_edge有效;當tri_ff1==0且tri_ff2==1時,下降沿的neg_edge有效。
? ?? ?我們總結一下。如果通過打兩拍的方式,實現了信號的同步化。我們通過打一拍的方式,實現邊沿檢測電路。這兩者不是一定同時出現的。如果進來的信號是異步信號,那就必須先同步化,然后再做檢測。如果進來的信號本身就是同步信號,那就沒有必要做同步化了,直接做邊沿檢測即可。
? ?? ?回到本工程的設計,我們需要檢測rx_uart的下降沿,從而讓flag_add變高。同時,我們注意到rx_uart是異步信號(PC 什么時候發送數據就是隨機的)。所以需要將rx_uart先同步化,再做下降沿檢測。所以先設計如下代碼:
? ?? ?這樣,flag_add變1的條件就變成:rx_uart_ff1==0&& rx_uart_ff2==1。
? ?? ?Flag_add變0的條件,可以完成收完9比特數據就變0,不用再計數了。所以變0條件:end_cnt1。
? ?? ?綜上所述,可以寫出flag_add的代碼。

設計下data信號,該信號的值來自于圖中第2~第9比特的值。第2比特的值賦給data[0],第3比特的值賦給data[1],以此類推,第9比特的值賦給data[7]。

??由于每一個比特都持續5208個時鐘周期,我們必須選定一個時刻,將值賦給data。

首先,不能在end_cnt0的時候賦值,如上圖的點。因為我們這里的5208個時鐘周期是理想、估算的數值,實際上是非常有可能有偏差的。如果我們在end_cnt0的時候取值,就有可能采錯。
? ?? ?最保險的做法是在中間點取值。這樣,即使有比較多的偏差,都不會影響到采樣的正確性。
綜上所述,我們在cnt0數到一半時采到當前rx_uart的值賦給dout,其中第2比特賦給led[0],第3比特賦給led[1],以此類推,第9比特賦給led[7]。
? ?? ?進一步用信號表示,可翻譯成:數到add_cnt0 && cnt0==5208/2 -1時,如果cnt1==1,則將rx_uart_ff1賦給led[0]。如果cnt1==2,則將rx_uart_ff1賦給led[1],以此類推,如果cnt1==8,將rx_uart_ff1賦給led[7]。
? ?? ?那么直接翻譯成代碼。
上面代碼可優化,簡寫成如下:

通常我們設計時,首先是想到實現功能,所以會先寫出前面代碼。在功能實現的前提下,再考慮有沒有優化空間,從而寫出后面代碼。好代碼都是一步步優化出來的。
? ?? ?注意,上面代碼,我們采集的是rx_uart_ff1而不是rx_uart信號。這是因為rx_uart是異步信號,我們只能用同步化后的信號,否則會引起亞穩態。所以只能是rx_uart_ff1。
? ?? ?至此,主體程序已經完成。接下來是將module補充完整。
3.3??信號定義
? ?? ?cnt0是用always產生的信號,因此類型為reg。cnt0計數的最大值為5208,需要用13根線表示,即位寬是13位。
add_cnt0和end_cnt0都是用assign方式設計的,因此類型為wire。并且其值是0或者1,1個線表示即可
? cnt1是用always產生的信號,因此類型為reg。cnt1計數的最大值為9,需要用4根線表示,即位寬是4位。
add_cnt1和end_cnt1都是用assign方式設計的,因此類型為wire。并且其值是0或者1,1根線表示即可。因此代碼如下:

?flag_add是用always方式設計的,因此類型為reg。并且其值是0或者1,1根線表示即可。因此代碼如下:
?rx_uart_ff0、rx_uart_ff1和rx_uart_ff2是用always方式設計的,因此類型為reg。并且其值是0或1,需要1根線表示即可。
4? ???綜合工程和上板4.1??新建工程
? ?? ?1.首先在d盤中創建名為“uart”的工程文件夾,將寫的代碼命名為“uart.v”,頂層模塊名為“uart”。
電子發燒友App
















































評論