【小梅哥FPGA進階教程】第九章 基於串口獵人軟件的串口示波器


九、基於串口獵人軟件的串口示波器

 

1、實驗介紹

本實驗,為芯航線開發板的綜合實驗,該實驗利用芯航線開發板上的ADC、獨立按鍵、UART等外設,搭建了一個具備豐富功能的數據采集卡,芯航線開發板負責進行數據的采集並將數據通過串口發送到PC機上,PC端,利用強大的串口調試工具——串口獵人,來實現數據的接收分析,並將數據分別以波形、碼表、柱狀圖的形式動態顯示出來,以讓使用者能夠直觀的看到ADC采集到的信號細節。同時,用戶也可以使用串口獵人通過串口給下位機(FPGA)發送指令,下位機將對接收到的指令進行解碼,然后依據解碼結果來配置FPGA中各個子模塊的控制寄存器,以實現通過串口控制FPGA中子模塊工作狀態的功能。

本實驗中,涉及到的應用模塊和知識點如下所示:

串口收發模塊的設計和使用;

串口收發模塊仿真模型的設計;

串口簡單數據幀的解碼;

串口幀轉Memory Mapped總線的設計;

Memory Mapped Slave模塊的設計;

線性序列機設計思想的應用(ADC驅動);

獨立按鍵消抖的分析與實現;

直接數字頻率合成(DDS)的設計與實現;

使能時鍾對系統間模塊協調工作的重要性;

串口獵人的詳細使用;

完整系統的仿真驗證設計;

頭文件在設計中的運用;

Quartus II軟件中可定制化存儲器ROM的使用;

本實驗不僅注重可綜合的代碼編寫,同時更注重代碼的仿真驗證。通過仿真,我們能夠尋找設計中可能存在的問題並修正。最終,在整個系統仿真無誤的基礎上,下載到開發板上一次性成功。

2、系統結構

下圖為本設計的框架結構圖:

圖片1

系統采用模塊化設計,在模塊划分的過程中,重點考慮了系統的可擴展性,下表為對系統中各模塊功能的簡單介紹。

圖片2

系統中各端口和信號的功能介紹如下:

圖片3-1圖片3-2圖片3-3

 

本實驗為綜合性實驗,代碼量較大,因此這里只針對部分代碼進行講解。如果文檔中沒有講到的內容,大家可以參看代碼注釋。

模塊詳解

3.1 Tx_Bps_Gen

Tx_Bps_Gen為發送波特率生成模塊,每當有Byte_En信號到來時,即開始產生發送一個完整字節的數據需要的完整波特率時鍾信號。

本設計,波特率支持9600bps到921600bps。例如,需要產生的波特率時鍾為9600bps,即波特率時鍾頻率為9600Hz,周期為104.17us。生成9600Hz波特率時鍾的核心思想就是對系統時鍾進行計數,這里設定系統時鍾為50MHz,則一個時鍾的周期為20ns,我們只需要對系統時鍾計數5208次,每計數5208次產生一個時鍾周期的高電平脈沖,即可實現生成9600Hz波特率時鍾的功能。相應代碼如下所示:

018     parameter system_clk = 50_000_000; /*輸入時鍾頻率設定,默認50M*/
019
020 /*根據輸入時鍾頻率計算生成各波特率時分頻計數器的計數最大值*/        
021     localparam bps9600 = system_clk/9600 - 1;
022     localparam bps19200 = system_clk/19200 - 1;
023     localparam bps38400 = system_clk/38400 - 1;
024     localparam bps57600 = system_clk/57600 - 1;
025     localparam bps115200 = system_clk/115200 - 1;
026     localparam bps230400 = system_clk/230400 - 1;
027     localparam bps460800 = system_clk/460800 - 1;
028     localparam bps921600 = system_clk/921600 - 1;       
029     
030     reg [31:0]BPS_PARA;/*波特率分頻計數器的計數最大值*/
031
032     always@(posedge Clk or negedge Rst_n)
033     if(!Rst_n)begin
034         BPS_PARA <= bps9600;/*復位時波特率默認為9600bps*/
035     end
036     else begin
037         case(Baud_Set)/*根據波特率控制信號選擇不同的波特率計數器計數最大值*/
038             3'd0: BPS_PARA <= bps9600;
039             3'd1: BPS_PARA <= bps19200;
040             3'd2: BPS_PARA <= bps38400;
041             3'd3: BPS_PARA <= bps57600;
042             3'd4: BPS_PARA <= bps115200;
043             3'd5: BPS_PARA <= bps230400;
044             3'd6: BPS_PARA <= bps460800;
045             3'd7: BPS_PARA <= bps921600;            
046             default: BPS_PARA <= bps9600;
047         endcase
048     end
049     
050 //=========================================================
051     reg[12:0]Count;
052     
053     reg n_state;
054     localparam IDEL_1 = 1'b0,
055                   SEND   = 1'b1;
056                   
057     reg BPS_EN;
058     
059 /*-------波特率時鍾生成控制邏輯--------------*/  
060     always@(posedge Clk or negedge Rst_n)
061     if(!Rst_n)begin
062         BPS_EN <= 1'b0;
063         n_state <= IDEL_1;
064     end
065     else begin
066         case(n_state)
067             IDEL_1:
068                 if(Byte_En)begin/*檢測到字節發送使能信號,則啟動波特率生成進程,同時進入發送狀態*/
069                     BPS_EN <= 1'b1;
070                     n_state <= SEND;
071                 end
072                 else begin
073                     n_state <= IDEL_1;
074                     BPS_EN <= 1'b0;
075                 end
076             SEND:
077                 if(Tx_Done == 1)begin/*發送完成,關閉波特率生成進程,回到空閑狀態*/
078                     BPS_EN <= 1'b0;
079                     n_state <= IDEL_1;
080                 end
081                 else begin
082                     n_state <= SEND;
083                     BPS_EN <= 1'b1;
084                 end
085             default:n_state <= IDEL_1;
086         endcase
087     end
088
089 /*-------波特率時鍾生成定時器--------------*/ 
090     always@(posedge Clk or negedge Rst_n)
091     if(!Rst_n)
092         Count <= 13'd0; 
093     else if(BPS_EN == 1'b0)
094         Count <= 13'd0;
095     else begin
096         if(Count == BPS_PARA)
097             Count <= 13'd0;
098         else 
099             Count <= Count + 1'b1;
100     end
101     
102 /*輸出數據接收采樣時鍾*/  
103 //-----------------------------------------------
104     always @(posedge Clk or negedge Rst_n)
105     if(!Rst_n)
106         Bps_Clk <= 1'b0;
107     else if(Count== 1)
108         Bps_Clk <= 1'b1;
109     else 
110         Bps_Clk <= 1'b0;

第18行“parameter system_clk = 50_000_000;”,這里用一個全局參數定義了系統時鍾,暫時設定為50M,可根據實際使用的板卡上的工作時鍾進行修改。

所謂波特率生成,就是用一個定時器來定時,產生頻率與對應波特率時鍾頻率相同的時鍾信號。例如,我們使用波特率為115200bps,則我們需要產生一個頻率為115200Hz的時鍾信號。那么如何產生這樣一個115200Hz的時鍾信號呢?這里,我們首先將115200Hz時鍾信號的周期計算出來,1秒鍾為1000_000_000ns,因此波特率時鍾的周期Tb= 1000000000/115200 =8680.6ns,即115200信號的一個周期為8680.6ns,那么,我們只需要設定我們的定時器定時時間為8680.6ns,每當定時時間到,產生一個系統時鍾周期長度的高脈沖信號即可。系統時鍾頻率為50MHz,即周期為20ns,那么,我們只需要計數8680/20個系統時鍾,就可獲得8680ns的定時,即bps115200=Tb/Tclk - 1=Tb*fclk - 1=fclk/115200-1。相應的,其它波特率定時值的計算與此類似,這里小梅哥就不再一一分析。20行至28行為波特率定時器定時值的計算部分。

為了能夠通過外部控制波特率,設計中使用了一個3位的波特率選擇端口:Baud_Set。通過給此端口不同的值,就能選擇不同的波特率,此端口控制不同波特率的原理很簡單,就是一個多路選擇器,第32行至第48行即為此多路選擇器的控制代碼, Baud_Set的值與各波特率的對應關系如下:

000 : 9600bps;

001 : 19200bps;

010 :38400bps;

011 :57600bps;

100 :115200bps;

101 :230400bps;

110 :460800bps;

111 :921600bps;

3.2 Uart_Byte_Tx

Uart_Byte_Tx為字節發送模塊,該模塊在波特率時鍾的節拍下,依照UART通信協議發送一個完整的字節的數據。當一個字節發送完畢后,Tx_Done產生一個高脈沖信號,以告知其它模塊或邏輯一個字節的數據已經傳輸完成,可以開始下一個字節的發送了。其發送一個字節數據的實現代碼如下:

33  /*計數波特率時鍾,11個波特率時鍾為一次完整的數據發送過程*/    
34      always@(posedge Clk or negedge Rst_n)
35      if(!Rst_n)
36          Bps_Clk_Cnt <= 4'b0;
37      else if(Bps_Clk_Cnt == 4'd11)
38          Bps_Clk_Cnt <= 4'b0;
39      else if(Bps_Clk)
40          Bps_Clk_Cnt <= Bps_Clk_Cnt + 1'b1;
41      else
42          Bps_Clk_Cnt <= Bps_Clk_Cnt;
43
44  /*生成數據發送完成標志信號*/        
45      always@(posedge Clk or negedge Rst_n)
46      if(!Rst_n)
47          Tx_Done <= 1'b0;
48      else if(Bps_Clk_Cnt == 4'd11)
49          Tx_Done <= 1'b1;
50      else
51          Tx_Done <= 1'b0;
52
53  /*在開始發送起始位的時候就讀取並寄存Data_Byte,以免Data_Byte變化導致數據的丟失*/       
54      always@(posedge Clk or negedge Rst_n)
55      if(!Rst_n)
56          Data = 8'd0;
57      else if(Bps_Clk & Bps_Clk_Cnt == 4'd1)
58          Data <= Data_Byte;
59      else
60          Data <= Data;
61
62  /*發送數據序列機*/       
63      always@(posedge Clk or negedge Rst_n)
64      if(!Rst_n)  
65          Rs232_Tx <= 1'b1;
66      else begin
67          case(Bps_Clk_Cnt)
68              4'd1: Rs232_Tx <= 1'b0;
69              4'd2: Rs232_Tx <= Data[0];
70              4'd3: Rs232_Tx <= Data[1];
71              4'd4: Rs232_Tx <= Data[2];  
72              4'd5: Rs232_Tx <= Data[3];
73              4'd6: Rs232_Tx <= Data[4];
74              4'd7: Rs232_Tx <= Data[5];
75              4'd8: Rs232_Tx <= Data[6];
76              4'd9: Rs232_Tx <= Data[7];
77              4'd10: Rs232_Tx <= 1'b1;
78              default:Rs232_Tx <= 1'b1;
79          endcase
80      end

在UART協議中,一個完整的字節包括一位起始位、8位數據位、一位停止位即總共十位數據,那么,要想完整的實現這十位數據的發送,就需要11個波特率時鍾脈沖,如下所示:

 

圖片6

BPS_CLK信號的第一個上升沿到來時,字節發送模塊開始發送起始位,接下來的2到9個上升沿,發送8個數據位,第10個上升沿到第11個上升沿為停止位的發送。

3.3 Uart_Byte_Rx

單個串口接收模塊中實現串口數據接收的主要代碼如下所示:

025     always @ (posedge Clk or negedge Rst_n) 
026     if(!Rst_n) begin
027         Rs232_Rx0 <= 1'b0;
028         Rs232_Rx1 <= 1'b0;
029         Rs232_Rx2 <= 1'b0;
030         Rs232_Rx3 <= 1'b0;
031     end
032     else begin 
033         Rs232_Rx0 <= Rs232_Rx;
034         Rs232_Rx1 <= Rs232_Rx0;
035         Rs232_Rx2 <= Rs232_Rx1;
036         Rs232_Rx3 <= Rs232_Rx2;
037     end
038     
039     wire neg_Rs232_Rx= Rs232_Rx3 & Rs232_Rx2 & ~Rs232_Rx1 & ~Rs232_Rx0;
040     
041     assign Byte_En = neg_Rs232_Rx;
042
043 /*----------計數采樣時鍾--------------*/
044 /*9倍波特率采樣時鍾,故一個完整的接收過程有90個波特率時鍾*/
045     reg[6:0]Sample_Clk_Cnt;
046     always @ (posedge Clk or negedge Rst_n) 
047     if(!Rst_n)
048         Sample_Clk_Cnt <= 7'd0;
049     else if(Sample_Clk)begin
050         if(Sample_Clk_Cnt == 7'd89)
051             Sample_Clk_Cnt <= 7'd0;
052         else
053             Sample_Clk_Cnt <= Sample_Clk_Cnt + 1'b1;
054     end
055     else
056         Sample_Clk_Cnt <= Sample_Clk_Cnt;
057
058     reg [1:0]Start_Bit; /*起始位,這里雖然定義,但並未使用該位來判斷接收數據的正確性,即默認接收都是成功的*/
059     reg [1:0]Stop_Bit;  /*停止位,這里雖然定義,但並未使用該位來判斷接收數據的正確性,即默認接收都是成功的*/
060     reg [1:0] Data_Tmp[7:0];/*此部分較為復雜,請參看說明文檔中相關解釋*/
061     
062     always @ (posedge Clk or negedge Rst_n)
063     if(!Rst_n)begin
064         Data_Tmp[0] <= 2'd0;
065         Data_Tmp[1] <= 2'd0;
066         Data_Tmp[2] <= 2'd0;
067         Data_Tmp[3] <= 2'd0;
068         Data_Tmp[4] <= 2'd0;
069         Data_Tmp[5] <= 2'd0;
070         Data_Tmp[6] <= 2'd0;
071         Data_Tmp[7] <= 2'd0;
072         Start_Bit <= 2'd0;
073         Stop_Bit <= 2'd0;       
074     end
075     else if(Sample_Clk)begin
076         case(Sample_Clk_Cnt)
077             7'd0:
078                 begin
079                     Data_Tmp[0] <= 2'd0;
080                     Data_Tmp[1] <= 2'd0;
081                     Data_Tmp[2] <= 2'd0;
082                     Data_Tmp[3] <= 2'd0;
083                     Data_Tmp[4] <= 2'd0;
084                     Data_Tmp[5] <= 2'd0;
085                     Data_Tmp[6] <= 2'd0;
086                     Data_Tmp[7] <= 2'd0;
087                     Start_Bit <= 2'd0;
088                     Stop_Bit <= 2'd0;   
089                 end
090             7'd3,7'd4,7'd5: Start_Bit <= Start_Bit + Rs232_Rx;
091             7'd12,7'd13,7'd14:Data_Tmp[0] <= Data_Tmp[0] + Rs232_Rx;
092             7'd21,7'd22,7'd23:Data_Tmp[1] <= Data_Tmp[1] + Rs232_Rx;
093             7'd30,7'd31,7'd32:Data_Tmp[2] <= Data_Tmp[2] + Rs232_Rx;
094             7'd39,7'd40,7'd41:Data_Tmp[3] <= Data_Tmp[3] + Rs232_Rx;
095             7'd48,7'd49,7'd50:Data_Tmp[4] <= Data_Tmp[4] + Rs232_Rx;
096             7'd57,7'd58,7'd59:Data_Tmp[5] <= Data_Tmp[5] + Rs232_Rx;    
097             7'd66,7'd67,7'd68:Data_Tmp[6] <= Data_Tmp[6] + Rs232_Rx;
098             7'd75,7'd76,7'd77:Data_Tmp[7] <= Data_Tmp[7] + Rs232_Rx;    
099             7'd84,7'd85,7'd86:Stop_Bit <= Stop_Bit + Rs232_Rx;
100             default:;
101         endcase
102     end
103     else ;

根據串口發送協議,一個字節的數據傳輸是以一個波特率周期的低電平作為起始位的,因此,成功接收UART串口數據的核心就是准確檢測起始位。由於外部串口發送過來的數據與接收系統不在同一個時鍾域,因此不能直接使用該信號的下降沿來作為檢測標志,我們需要在fpga中,采用專用的邊沿檢測電路來實現,第25行至37行通過四個移位寄存器,存儲連續四個時鍾上升沿時外部發送數據線的狀態,第39行通過比較前兩個時鍾時數據線的狀態與后兩個時鍾時數據線的狀態,來得到該數據線的准確下降沿,以此保證起始位的准確檢測。

在簡單的串口接收中,我們通常選取一位數據的中間時刻進行采樣,因為此時數據最穩定,但是在工業環境中,存在着各種干擾,在干擾存在的情況下,如果采用傳統的中間時刻采樣一次的方式,采樣結果就有可能受到干擾而出錯。為了濾除這種干擾,這里采用多次采樣求概率的方式。如下圖,將一位數據平均分成9個時間段,對位於中間的三個時間段進行采樣。然后對三個采樣結果進行統計判斷,如果某種電平狀態在三次采樣結果中占到了兩次及以上,則可以判定此電平狀態即為正確的數據電平。例如4、5、6時刻采樣結果分別為1、1、0,那么就取此位解碼結果為1,否則,若三次采樣結果為0、1、0,則解碼結果就為0。

 

圖片8

因為采樣一位需要9個時鍾上升沿,因此,采樣一個完整的數據需要10*9,即90個時鍾上升沿,這里,采樣時鍾為波特率時鍾的9倍。產生采樣時鍾的部分代碼如下所示:

089 /*-------波特率時鍾生成定時器--------------*/ 
090     always@(posedge Clk or negedge Rst_n)
091     if(!Rst_n)
092         Count <= 10'd0; 
093     else if(BPS_EN == 1'b0)
094         Count <= 10'd0;
095     else begin
096         if(Count == BPS_PARA)
097             Count <= 10'd0;
098         else 
099             Count <= Count + 1'b1;
100     end
101     
102 //=====================================================
103 /*輸出數據接收采樣時鍾*/
104     always @(posedge Clk or negedge Rst_n)
105     if(!Rst_n)
106         Sample_Clk <= 1'b0;
107     else if(Count== 1)
108         Sample_Clk <= 1'b1;
109     else 
110         Sample_Clk <= 1'b0;

這里,BPS_PARA的計算原理和前面Tx_Bps_Gen模塊中的BPS_PARA的計算原理一致,不過這里,因為采樣時鍾為波特率時鍾的9倍,所以,BPS_PARA為Tx_Bps_Gen模塊中的BPS_PARA的1/9。計算BPS_PARA的相關代碼如下:

018     parameter system_clk = 50_000_000;  /*輸入時鍾頻率設定,默認50M*/
019
020 /*根據輸入時鍾頻率計算生成各波特率時分頻計數器的計數最大值*/    
021     localparam bps9600 = system_clk/9600/9 - 1;
022     localparam bps19200 = system_clk/19200/9 - 1;
023     localparam bps38400 = system_clk/38400/9 - 1;
024     localparam bps57600 = system_clk/57600/9 - 1;
025     localparam bps115200 = system_clk/115200/9 - 1;
026     localparam bps230400 = system_clk/230400/9 - 1;
027     localparam bps460800 = system_clk/460800/9 - 1;
028     localparam bps921600 = system_clk/921600/9 - 1;     
029     
030     reg [31:0]BPS_PARA;/*波特率分頻計數器的計數最大值*/
031
032     always@(posedge Clk or negedge Rst_n)
033     if(!Rst_n)begin
034         BPS_PARA <= bps9600;    /*復位時波特率默認為9600bps*/
035     end
036     else begin
037         case(Baud_Set)  /*根據波特率控制信號選擇不同的波特率計數器計數最大值*/
038             3'd0: BPS_PARA <= bps9600;
039             3'd1: BPS_PARA <= bps19200;
040             3'd2: BPS_PARA <= bps38400;
041             3'd3: BPS_PARA <= bps57600;
042             3'd4: BPS_PARA <= bps115200;
043             3'd5: BPS_PARA <= bps230400;
044             3'd6: BPS_PARA <= bps460800;
045             3'd7: BPS_PARA <= bps921600;            
046             default: BPS_PARA <= bps9600;/*異常情況,恢復到9600的波特率*/
047         endcase
048     end

3.4 CMD

CMD模塊為串口數據幀接收與解析模塊,該模塊負責對串口接收到的每一幀的數據進行解碼判斷,並從數據幀中提取出地址字節和數據字節。最后將地址字節和數據字節轉換為類似於Avalon-MM形式的總線,以實現對其它模塊的控制寄存器的讀寫,從而實現通過串口控制FPGA中各個模塊工作的目的。

在工業應用中,串口指令大多以數據幀的格式出現,包含幀頭、幀長、幀命令、幀內容、校驗和以及幀尾,不會只是單純的傳輸數據。在這個實驗中,小梅哥也使用了數據幀的形式來通過上位機向FPGA發送命令,不過這里我使用的幀格式非常簡單,幀格式以幀頭、幀長、幀內容以及幀尾組成,忽略了校驗部分內容,幀頭、幀長以及幀尾內容都是固定的,不固定的只是幀內容,以下為小梅哥的設計中一幀數據的格式:

圖片11

由於數據幀本身結構簡單,因此數據幀的解析過程也相對簡潔,以下為小梅哥的數據幀解析狀態機設計,該狀態機分為幀頭解析、幀長解析、數據接收以及幀尾解析。默認時,狀態機處於幀頭解析狀態,一旦出現幀頭數據,則跳轉到幀長接收狀態,若下一個字節為幀長數據(這里嚴格意義上並不能算作幀長,因為長度固定,充其量只能算作幀頭,讀者不須過分糾結),則開始連續接收三個字節的數據,若非指定的幀長內容,則表明這是一次無關傳輸,狀態機將返回到幀頭解析狀態繼續等待新的數據幀到來。在幀尾解析狀態,若解析到的數據並非指定的幀尾數據,則表明此次數據幀非有效幀,則將此幀已解析到的數據舍棄。若為幀尾數據,則解析成功,產生命令有效標志信號(CMD_Valid),Memory Mapped 總線進程在檢測到此命令有效信號后,即產生寫外設寄存器操作。

 

圖片12

命令解析的狀態機實現代碼如下所示:

017     localparam 
018         Header = 8'hAA, /*幀頭*/
019         Length = 8'd3,      /*幀長*/
020         Tail   = 8'h88; /*幀尾*/
021
022 /*----------狀態定義-----------------*/     
023     localparam
024         CMD_HEADER = 6'b00_0001,
025         CMD_LENGTH = 6'b00_0010,
026         CMD_DATAA  = 6'b00_0100,
027         CMD_DATAB  = 6'b00_1000,
028         CMD_DATAC  = 6'b01_0000,
029         CMD_TAIL   = 6'b10_0000;
030     
031     
032     always@(posedge Clk or negedge Rst_n)
033     if(!Rst_n)begin
034         reg_CMD_DATA <= 24'd0;
035         CMD_Valid <= 1'b0;
036         state <= CMD_HEADER;
037     end
038     else if(Rx_Int)begin
039         case(state)
040             CMD_HEADER: /*解碼幀頭數據*/
041                 if(Rx_Byte == Header)
042                     state <= CMD_LENGTH;
043                 else
044                     state <= CMD_HEADER;
045             
046             CMD_LENGTH: /*解碼幀長數據*/
047                 if(Rx_Byte == Length)
048                     state <= CMD_DATAA;
049                 else
050                     state <= CMD_HEADER;
051             
052             CMD_DATAA:  /*解碼數據A*/
053                 begin
054                     reg_CMD_DATA[23:16] <= Rx_Byte;
055                     state <= CMD_DATAB;
056                 end
057                 
058             CMD_DATAB:  /*解碼數據B*/
059                 begin
060                     reg_CMD_DATA[15:8] <= Rx_Byte;
061                     state <= CMD_DATAC;             
062                 end
063                 
064             CMD_DATAC:  /*解碼數據C*/
065                 begin
066                     reg_CMD_DATA[7:0] <= Rx_Byte;
067                     state <= CMD_TAIL;              
068                 end
069
070             CMD_TAIL:   /*解碼幀尾數據*/
071                 if(Rx_Byte == Tail)begin
072                     CMD_Valid <= 1'b1;  /*解碼成功,發送解碼數據有效標志*/
073                     state <= CMD_HEADER;
074                 end
075                 else begin
076                     CMD_Valid <= 1'b0;
077                     state <= CMD_HEADER;
078                 end
079             default:;
080         endcase 
081     end
082     else begin
083         CMD_Valid <= 1'b0;
084         reg_CMD_DATA <= reg_CMD_DATA;
085     end

第23行到第29行為狀態機編碼,這里采用獨熱碼的編碼方式。狀態機的編碼方式有很多種,包括二進制編碼、獨熱碼、格雷碼等,二進制編碼最接近我們的常規思維,但是在FPGA內部,其譯碼電路較為復雜,且容易出現競爭冒險,導致使用二進制編碼的狀態機最高運行速度相對較低。獨熱碼的譯碼電路最簡單,因此采用獨熱碼方式編碼的狀態機運行速度較二進制編碼方式高很多,但是編碼會占用較多的數據位寬。格雷碼以其獨特的編碼特性,能夠非常完美的解決競爭冒險的問題,使狀態機綜合出來的電路能夠運行在很高的時鍾頻率,但是格雷碼編碼較為復雜,尤其對於位寬超過4位的格雷碼,編碼實現較二進制編碼和獨熱碼編碼要復雜的多。這里,詳細的關於狀態機的編碼問題,小梅哥不做過多的討論,更加細致的內容,請大家參看夏宇聞老師經典書籍《Verilog數字系統設計教程》中第12章相關內容。

Memory Mapped 總線進程根據命令有效標志信號產生寫外設寄存器操作的相關代碼如下所示:

087 /*------驅動總線寫外設寄存器--------*/    
088     always@(posedge Clk or negedge Rst_n)
089     if(!Rst_n)begin
090         m_wr <= 1'b0;
091         m_addr <= 8'd0;
092         m_wrdata <= 16'd0;
093     end
094     else if(CMD_Valid)begin
095         m_wr <= 1'b1;
096         m_addr <= reg_CMD_DATA[23:16];
097         m_wrdata <= reg_CMD_DATA[15:0]; 
098     end
099     else begin
100         m_wr <= 1'b0;
101         m_addr <= m_addr;
102         m_wrdata <= m_wrdata;   
103     end

在本系統中,需要通過該Memory Mapped 總線配置的寄存器總共有12個,分別位於ADC采樣速率控制模塊(Sample_Ctrl)、串口發送控制模塊(UART_Tx_Ctrl)、直接數字頻率合成信號發生器模塊(DDS)中,各寄存器地址分配及物理意義如下所示:

圖片15

指令使用說明:

圖片16-1圖片16-2

例如,系統在上電后,各個模塊默認是沒有工作的,要想在上位機上看到數據,就必須先通過上位機發送控制命令。因為系統上電后默認選擇的數據通道為DDS生成的數據,為了以最快的方式在串口獵人上看到波形,一種可行的控制順序如下所示:

使能DDS生成數據(AA 03 06 00 01 88) —> 使能采樣DDS數據(AA 03 0C 00 01 88) —>使能串口發送數據(AA 03 04 00 01 88),

這里,為了演示方便,因此在系統中對數據采樣速率和DDS生成的信號的頻率初始值都做了設置,因此不設置采樣率和輸出頻率控制字這幾個寄存器也能在串口獵人上接收到數據。

經過此操作后,串口獵人的接收窗口中就會不斷的接收到數據了。當然,這離我們最終顯示波形還有一段距離,這部分內容我將放到文檔最后,以一次具體的使用為例,來step by step的介紹給大家。

關於Memory Mapped 總線如何實現各模塊寄存器的配置,這里小梅哥以ADC采樣控制模塊Sample_Ctrl中三個寄存器的配置來進行介紹。Sample_Ctrl中三個寄存器的定義及配置代碼如下所示:

14      reg [15:0]ADC_Sample_Cnt_Max_L;/*采樣分頻計數器計數最大值的低16位,ADDR = 8'd1*/
15      reg [15:0]ADC_Sample_Cnt_Max_H;/*采樣分頻計數器計數最大值的高16位,ADDR = 8'd2*/
16      reg ADC_Sample_En;/*采樣使能寄存器,ADDR = 8'd3*/
17
18  /*-------設置采樣分頻計數器計數最大值---------*/  
19      always@(posedge Clk or negedge Rst_n)
20      if(!Rst_n)begin
21          ADC_Sample_Cnt_Max_H <= 16'd0;
22          ADC_Sample_Cnt_Max_L <= 16'd49999;/*默認設置采樣率為1K*/
23      end
24      else if(m_wr && (m_addr == `ADC_S_Cnt_Max_L))//寫采樣分頻計數器計數最大值的低16位
25          ADC_Sample_Cnt_Max_L <= m_wrdata;
26      else if(m_wr && (m_addr == `ADC_S_Cnt_Max_H))//寫采樣分頻計數器計數最大值的高16位
27          ADC_Sample_Cnt_Max_H <= m_wrdata;
28      else begin
29          ADC_Sample_Cnt_Max_H <= ADC_Sample_Cnt_Max_H;
30          ADC_Sample_Cnt_Max_L <= ADC_Sample_Cnt_Max_L;
31      end
32      
33  /*---------寫采樣使能寄存器-------------*/
34      always@(posedge Clk or negedge Rst_n)
35      if(!Rst_n)
36          ADC_Sample_En <= 1'b0;
37      else if(m_wr && (m_addr == `ADC_Sample_En))
38          ADC_Sample_En <= m_wrdata[0];
39      else
40          ADC_Sample_En <= ADC_Sample_En;

采樣率的控制采用定時器的方式實現。使用一個計數器持續對系統時鍾進行計數,一旦計數滿設定時間,則產生一個時鍾周期的高脈沖信號,作為ADC采樣使能信號。這里,系統時鍾周期為20ns,因此,如果要實現采樣1K的采樣率(采樣周期為1ms),則需對系統時鍾計數50000次;若實現20K的采樣率(采樣周期為50us),則需要對系統時鍾計數2500次。以此類推,可知改變采樣率的實質就是改變計數器的計數最大值,因此,我們要想改變采樣速率,也只需要改變采樣率控制計數器的計數最大值即可。所以這里,我們設計了兩個16位的寄存器,分別存儲采樣率控制計數器的計數最大值的低16位和高16位,如第14、15行所示。當我們需要修改ADC的采樣率時,直接通過串口發送指令,修改這兩個寄存器中的內容即可。

這里,小梅哥使用自己設計的一個山寨版Memory Mapped 總線來配置各個寄存器,該總線包含三組信號,分別為:

寫使能信號:m_wr;

寫地址信號:m_addr;

寫數據信號:m_wrdata;

那么,這三組信號是如何配合工作的呢?我們以配置ADC_Sample_Cnt_Max_H和ADC_Sample_Cnt_Max_L這兩個寄存器來進行介紹,這里再貼上這部分代碼:

18  /*-------設置采樣分頻計數器計數最大值---------*/  
19      always@(posedge Clk or negedge Rst_n)
20      if(!Rst_n)begin
21          ADC_Sample_Cnt_Max_H <= 16'd0;
22          ADC_Sample_Cnt_Max_L <= 16'd49999;/*默認設置采樣率為1K*/
23      end
24      else if(m_wr && (m_addr == `ADC_S_Cnt_Max_L))//寫采樣分頻計數器計數最大值的低16位
25          ADC_Sample_Cnt_Max_L <= m_wrdata;
26      else if(m_wr && (m_addr == `ADC_S_Cnt_Max_H))//寫采樣分頻計數器計數最大值的高16位
27          ADC_Sample_Cnt_Max_H <= m_wrdata;
28      else begin
29          ADC_Sample_Cnt_Max_H <= ADC_Sample_Cnt_Max_H;
30          ADC_Sample_Cnt_Max_L <= ADC_Sample_Cnt_Max_L;
31      end

復位時,讓{ ADC_Sample_Cnt_Max_H,ADC_Sample_Cnt_Max_L }為49999,即設置默認采樣率為1K,每當m_wr為高且m_addr等於ADC_Sample_Cnt_Max_H寄存器的地址時,就將m_wrdata的數據更新到ADC_Sample_Cnt_Max_H寄存器中,同理,若當m_wr為高且m_addr等於ADC_Sample_Cnt_Max_L寄存器的地址時,就將m_wrdata的數據更新到ADC_Sample_Cnt_Max_L寄存器中。其他寄存器的配置原理與此相同,因此不再做闡述,相信大家舉一反三,便可理解了。

4、DDS基本原理

注:本文內容摘抄自周立功編寫的教材《EDA實驗與實踐》196~197頁。

DDS(Direct Digital Synthesizer)即數字合成器,是一種新型的頻率合成技術,具有相對帶寬大,頻率轉換時間短、分辨率高和相位連續性好等優點,很容易實現頻率,相位,和幅度的數控調制,廣泛應用於通信領域。

DDS的基本結構圖如圖1所示:

 

圖片19

圖1 DDS的基本結構圖

主要由相位累加器,相位調制器,正弦數據表,和D/A轉換器構成,相位累加器由N位加法器與N位寄存器構成。每來一個時鍾,加法器就將頻率控制字,與累加寄存器輸出的相位數據相加,相加的結果又反饋至累加寄存器的數據輸入端,以使加法器在下一個時鍾脈沖的作用下繼續與頻率控制字相加,這樣,相位累加器在時鍾作用下,不斷對頻率控制字進行線性相位累加。由此可以看出,在每一個時鍾脈沖輸入時,相位累加器便把頻率控制字累加一次。相位累加器輸出的數據就是合成信號的相位,相位累加器的溢出頻率,就是DDS輸出的信號頻率,用相位累加器輸出的數據,作為波形存儲器的相位采樣地址,這樣就可以把存儲在波形存儲器里的波形采樣值經查表找出,完成相位到幅度的轉換,波形存儲器的付出送到D/A轉換器,由D/A轉換器將數字信號轉換成模擬信號輸出,DDS信號流程示意圖如圖4.51所示。

圖片20

圖2 DDS信號流程示意圖

由於相位累加器為N位,相當於把正弦信號在相位上的精度定義為N位,(N的取值范圍一般為24~32),所以其分辨率為1/2N,若系統時鍾頻率為Fclk,頻率控制字fword為1,則輸出頻率為Fout=Fclk/2N,這個頻率相當於“基頻”,若fword為B,則輸出頻率為

 

圖片21

當系統輸入時鍾頻率,Fclk不變時,輸出信號頻率由頻率控制字M所決定,由上式可得:

 

圖片22

其中B為頻率字,注意B要取整,有時會有誤差,在本設計中,N取32位,系統時鍾頻率Fclk為120兆,

選取ROM的地址(即相位累加器的輸出數據)時,可以間隔選通,相位寄存器輸出的位數一般取10~16位,這種截取方法稱為截斷式用法,以減少ROM的容量,M太大會導致ROM容量的成倍上升,而輸出精度受D/A位數的限制未有很大改善,在本設計中M取12位。

以上為周立功《EDA實驗與實踐》一書中對DDS原理的介紹

DDS原理再解釋。

上面的對DDS原理的解釋,還是有部分同學反映不夠直觀,讀完之后還是不明白DDS究竟是怎么控制頻率和相位的,那么,這里小梅哥再用更加通俗的方式給大家講解一下。

如圖3,為一個完整周期的正弦信號的波形,總共有33個采樣點,其中第1點和第33點的

值相同,第33點為下一個周期的起始點,因此,實際一個周期為32個采樣點(1~32)。因為是在matlab中生成的,因此起始點為1,而不是我們常見的0,這里對我們理解DDS的原理沒有任何影響,因此不必過多糾結。

 

圖片23

圖3 32個采樣點的正弦信號波形

 

圖片24

圖4 16個采樣點的正弦信號波形

我們要使用FPGA控制DAC來輸出這樣一個周期的正弦信號,每1ms輸出一個數值。如果每個點都輸出,則總共輸出這一個完整的周期信號需要輸出32個點,因此輸出一個完整的信號需要32ms,則輸出信號的頻率為1000/32Hz。

假如,我們現在用這一組數據來輸出一個2*(1000/32)Hz的正弦信號,因為輸出信號頻率為2*(1000/32)Hz,那么輸出一個完整的周期的正弦波所需要的時間為32/2,即16ms,為了保證輸出信號的周期為16ms,那么,我們就需要對我們的輸出策略進行更改,上面輸出周期為32ms的信號時,我們采用的為逐點輸出的方式,以32個點來輸出一個完整的正弦信號,而我們FPGA控制DAC輸出信號的頻率固定為1ms,因此,我們要輸出周期為16ms的信號,只能輸出16個點來表示一個完整的周期。我們這里選擇以每隔一個點輸出一個數據的方式,例如,我們可以選擇輸出(1、3、5、7……29、31)這些點,因為采用這些點,我們還是能夠組成一個完整的周期的正弦信號,而輸出時間縮短為一半,則頻率提高了一倍。最終結果如上圖4所示。

如果我們需要輸出頻率為(1/2)*(1000/32)Hz,即周期為64ms,則只需要以此組數據為基礎,每2ms輸出一個數據即可,例如第1ms和第2ms輸出第一個點,第3ms和第4ms輸出第二個點,以此類推,第63ms和第64ms輸出第32個點,即可實現周期加倍,即頻率減半的效果。

對於相位的調整,則更加簡單,我們只需要在每個取樣點的序號上加上一個偏移量,便可實現相位的控制。例如,上面默認的是第1ms時輸出第一個點的數據,假如我們現在在第1ms時從第9個點開始輸出,則將相位左移了90度,這就是控制相位的原理。

實現DDS輸出時,將橫坐標上的數據作為ROM的地址,縱坐標上的數據作為ROM的輸出,那么指定不同的地址就可實現對應值的輸出。而我們DDS輸出控制頻率和相位,歸結到底就是控制ROM的地址。

了解了以上原理之后,再來設計DDS系統就很容易了,以下為DDS信號發生器的代碼:

4.1 DDS_Module

01  module DDS_Module(
02          Clk,
03          Rst_n,
04          EN,
05          Fword,
06          Pword,
07          DA_Clk,
08          DA_Data
09      );
10
11      input Clk;/*系統時鍾*/
12      input Rst_n;/*系統復位*/
13      input EN;/*DDS模塊使能*/
14      input [31:0]Fword;/*頻率控制字*/
15      input [11:0]Pword;/*相位控制字*/
16      
17      output DA_Clk;/*DA數據輸出時鍾*/
18      output [9:0]DA_Data;/*D輸出輸出A*/
19      
20      reg [31:0]Fre_acc;  
21      reg [11:0]Rom_Addr;
22
23  /*---------------相位累加器------------------*/    
24      always @(posedge Clk or negedge Rst_n)
25      if(!Rst_n)
26          Fre_acc <= 32'd0;
27      else if(!EN)
28          Fre_acc <= 32'd0;   
29      else 
30          Fre_acc <= Fre_acc + Fword;
31
32  /*----------生成查找表地址---------------------*/        
33      always @(posedge Clk or negedge Rst_n)
34      if(!Rst_n)
35          Rom_Addr <= 12'd0;
36      else if(!EN)
37          Rom_Addr <= 12'd0;
38      else
39          Rom_Addr <= Fre_acc[31:20] + Pword; 
40
41  /*----------例化查找表ROM-------*/     
42      ddsrom ddsrom(
43          .address(Rom_Addr),
44          .clock(Clk),
45          .q(DA_Data)
46      );
47
48  /*----------輸出DA時鍾----------*/  
49      assign DA_Clk = (EN)?Clk:1'b1;
50
51  endmodule

5、仿真驗證:

以上分部分介紹了系統的各個關鍵模塊的設計。接下來,我們來對該設計進行仿真驗證。因為該實驗是基於串口的,為了實現仿真驗證,這里小梅哥分別編寫了一個串口發送的仿真模型(Uart_Tx_Model)和一個串口接收的仿真模型(Uart_Rx_Model),兩個仿真模型的設計都較為簡單,但是我們卻可以通過該模型模擬對我們的設計進行串口數據的發送和接收,並實時打印仿真模型發送的數據與接收到的數據。關於仿真模型的代碼,這里只貼上代碼,不做具體解釋。(此貼回復超過100條我就專門開文講解testbench的編寫技巧)

以下為串口接收仿真模型的代碼

001    `timescale 1ns/1ps
002
003    module Uart_RX_Model(Baud_Set,uart_rx);
004        
005        input [2:0]Baud_Set;/*波特率選擇信號*/
006        input uart_rx;/*仿真模型串口接收引腳*/
007        
008        reg Clk;/*仿真模型內部時鍾,50M*/
009        reg Rst_n;/*仿真模型內部復位信號*/
010        
011        wire Mid_Flag_Receive;/*數據中點(采樣點)標志信號*/
012        
013        reg Receive_Baud_Start;/*接收波特率生成使能信號*/
014        reg [7:0]rx_data;/*接收數據移位寄存器*/
015        
016        reg [7:0]Rx_Byte;/*最終接收結果*/
017            
018        initial Clk = 1;
019        always#10 Clk = ~Clk;
020        
021    /*例化波特率設置模塊*/    
022        baud_select baud_select_Receive(
023            .Clk(Clk),
024            .Rst_n(Rst_n),
025            .Baud_Set(Baud_Set),
026            .Baud_Start(Receive_Baud_Start),
027            .Mid_Flag(Mid_Flag_Receive)
028        );
029        
030        initial begin
031            Rst_n = 0;
032            Rx_Byte = 0;
033            rx_data = 0;
034            #100 Rst_n = 1;
035        end
036
037    /*接收一個字節的數據*/
038        initial begin
039        forever begin
040            @(negedge uart_rx)
041                begin
042                    Receive_Baud_Start = 1;
043                    @(posedge Mid_Flag_Receive);
044                    @(posedge Mid_Flag_Receive)rx_data[0] = uart_rx;
045                    @(posedge Mid_Flag_Receive)rx_data[1] = uart_rx;    
046                    @(posedge Mid_Flag_Receive)rx_data[2] = uart_rx;    
047                    @(posedge Mid_Flag_Receive)rx_data[3] = uart_rx;
048                    @(posedge Mid_Flag_Receive)rx_data[4] = uart_rx;    
049                    @(posedge Mid_Flag_Receive)rx_data[5] = uart_rx;
050                    @(posedge Mid_Flag_Receive)rx_data[6] = uart_rx;
051                    @(posedge Mid_Flag_Receive)rx_data[7] = uart_rx;
052                    @(posedge Mid_Flag_Receive)begin Receive_Baud_Start = 0;Rx_Byte = rx_data;end
053                    $display("Master_receive Data = %0h",Rx_Byte); 
054                end
055            end
056        end
057
058    endmodule

以下為串口發送仿真模型的設計代碼

001    `timescale 1ns/1ps
002
003    module Uart_Tx_Model(Baud_Set,Tx_Data,Tx_En,uart_tx,Tx_Done);
004        
005        input [2:0]Baud_Set;    /*波特率選擇信號*/
006        input [7:0]Tx_Data;    /*待發送數據字節*/
007        input Tx_En;            /*數據字節發送使能信號*/
008        output reg uart_tx;    /*仿真串口發送模型發送信號*/
009        output reg Tx_Done;    /*發送完成信號*/
010        
011        reg Clk;    /*仿真模型內部工作時鍾*/
012        reg Rst_n;    /*仿真模型內部復位信號*/
013        
014        wire Bps_Clk;    /*發送波特率時鍾波特率*/
015        reg Bps_En;    /*發送波特率使能信號*/
016            
017        initial Clk = 1;
018        always#10 Clk = ~Clk;
019
020    /*----例化發送波特率時鍾生成模塊-----*/    
021        TxModel_Bps_Gen TxModel_Bps_Gen_send(
022            .Clk(Clk),
023            .Rst_n(Rst_n),
024            .Baud_Set(Baud_Set),
025            .Tx_Done(Tx_Done),
026            .Bps_Clk(Bps_Clk),
027            .Byte_En(Bps_En)
028        );
029            
030        initial begin
031            Tx_Done = 0;
032            uart_tx = 1;
033            Rst_n = 0;
034            Bps_En = 0;
035            #100;
036            Rst_n = 1;
037            forever@(posedge Tx_En)/*每來一個發送使能信號即執行一次發送過程*/
038                Uart_Send(Tx_Data);    
039        end
040
041    /*執行一次字節數據的發送*/    
042        task Uart_Send;
043            input [7:0]Data;
044            begin
045                Bps_En = 1;
046                Tx_Done = 0;
047                $display("Uart_Send Data = %0h",Data);/*打印發送的數據*/
048                @(posedge Bps_Clk) #0.1 uart_tx = 0;
049                @(posedge Bps_Clk) #0.1 uart_tx = Data[0];
050                @(posedge Bps_Clk) #0.1 uart_tx = Data[1];
051                @(posedge Bps_Clk) #0.1 uart_tx = Data[2];
052                @(posedge Bps_Clk) #0.1 uart_tx = Data[3];
053                @(posedge Bps_Clk) #0.1 uart_tx = Data[4];
054                @(posedge Bps_Clk) #0.1 uart_tx = Data[5];
055                @(posedge Bps_Clk) #0.1 uart_tx = Data[6];
056                @(posedge Bps_Clk) #0.1 uart_tx = Data[7];
057                @(posedge Bps_Clk) #0.1 uart_tx = 1;
058                @(posedge Bps_Clk) #0.1 ;
059                Tx_Done = 1;
060                Bps_En = 0;
061                #20 Tx_Done = 0;
062            end
063        endtask
064        
065    endmodule

以下為仿真頂層模塊的設計

001    `timescale 1ns/1ns
002    `include "../rtl/header.v"
003    module uart_scope_tb;
004        localparam     KEY_WIDTH = 3;
005        
006        reg Clk;
007        reg Rst_n;
008        reg [KEY_WIDTH - 1:0]Key_in;
009        
010        reg ADC_Din;
011        wire ADC_Clk;
012        wire ADC_Cs_n;
013        
014    /*波特率設置總線,此處默認為9600bps,仿真不做波特率修改測試*/    
015        wire [2:0]Baud_Set;
016        reg [7:0]Tx_Data;/*串口發送仿真模型待發送數據字節*/
017        reg Tx_En;    /*串口發送仿真模型發送使能信號*/
018        wire Rs232_MTSR;    /*串口“主機(PC)發送-從機(FPGA)接收”信號*/
019        wire Rs232_MRST;    /*串口“主機(PC)接收-從機(FPGA)發送”信號*/
020        wire Tx_Done;    /*串口字節發送完成信號*/
021        
022        assign Baud_Set = 3'd0;/*設置波特率為固定的9600bps*/
023        
024        localparam 
025            Header = 8'hAA,    /*幀頭*/
026            Length = 8'd3,        /*幀長*/
027            Tail   = 8'h88;    /*幀尾*/
028
029    /*------例化串口示波器頂層模塊------*/
030        uart_scope uart_scope(
031            .Clk(Clk),
032            .Rst_n(Rst_n),
033            .Rs232_Rx(Rs232_MTSR),
034            .Rs232_Tx(Rs232_MRST),
035            .Key_in(Key_in),
036            .ADC_Din(ADC_Din),
037            .ADC_Clk(ADC_Clk),
038            .ADC_Cs_n(ADC_Cs_n)
039        );
040        
041    /*------例化串口發送仿真模型------*/
042        Uart_Tx_Model Uart_Tx_Model(
043            .Baud_Set(Baud_Set),
044            .Tx_Data(Tx_Data),
045            .Tx_En(Tx_En),
046            .uart_tx(Rs232_MTSR),
047            .Tx_Done(Tx_Done)
048        );
049        
050    /*------例化串口接收仿真模型------*/
051    //該模型接收FPGA發送出來的數據並打印在modelsim的transcript窗口中    
052        Uart_RX_Model Uart_RX_Model(
053            .Baud_Set(Baud_Set),
054            .uart_rx(Rs232_MRST)
055        );
056
057    /*-------生成50M時鍾信號--------*/    
058        initial Clk = 0;
059        always #10 Clk = ~Clk;
060
061    /*-------生成ADC_Din數據-------*/    
062    /*此處不對ADC的采樣結果多做計較,只要求保
063      證ADC_Din上有數據即可,有興趣者可自己編寫仿真模型*/
064        initial ADC_Din = 1;
065        always #1315 ADC_Din = ~ADC_Din;
066        
067        initial begin
068            Rst_n = 1'b0;
069            Tx_En = 1'b0;
070            Tx_Data = 8'd0;
071            Key_in = 4'b1111;
072            #200;
073            Rst_n = 1'b1;    /*釋放復位信號,系統即進入正常工作狀態*/
074            #1000;
075            En_DDS_Run;    /*使能DDS信號發生器生成信號數據*/
076            #10000;
077            En_S_DDS;    /*使能采樣ADC數據*/
078            En_S_ADC;    /*使能采樣DDS數據*/
079            #10000;
080            En_UART_Send;/*使能串口發送,此時串口獵人軟件上將會開始持續接收到數據*/    
081        end
082        
083        initial begin
084        #200_000_000;press_key(0);
085        #200_000_000;press_key(1);
086        #200_000_000;
087        $stop;
088        end
089        
090        
091
092    /*---發送命令幀數據任務-----*/    
093        task Send_CMD;
094            input [7:0]DATAA,DATAB,DATAC;/*用戶數據(地址、數據高字節,數據低字節)*/
095            begin
096                Tx_Data = Header;/*需發送數據為幀頭*/
097                Tx_En = 1;    /*啟動發送*/
098                #20 Tx_En = 0;    /*一個時鍾周期后,清零發送啟動信號*/
099                @(posedge Tx_Done)/*等待發送完成信號*/
100                #1000;
101                
102                Tx_Data = Length;/*需發送數據為幀長,此處幀長只是數據內容的長度*/
103                Tx_En = 1;    /*啟動發送*/
104                #20 Tx_En = 0;    /*一個時鍾周期后,清零發送啟動信號*/
105                @(posedge Tx_Done)/*等待發送完成信號*/
106                #1000;
107                
108                Tx_Data = DATAA;/*需發送數據第一個字節,此數據代表外設寄存器的地址*/
109                Tx_En = 1;    /*啟動發送*/
110                #20 Tx_En = 0;    /*一個時鍾周期后,清零發送啟動信號*/
111                @(posedge Tx_Done)/*等待發送完成信號*/
112                #1000;
113                
114                Tx_Data = DATAB;/*需發送數據第二個字節,此數據代表寫入外設寄存器的內容高8位*/
115                Tx_En = 1;    /*啟動發送*/
116                #20 Tx_En = 0;    /*一個時鍾周期后,清零發送啟動信號*/
117                @(posedge Tx_Done)/*等待發送完成信號*/
118                #1000;
119                
120                Tx_Data = DATAC;/*需發送數據第三個字節,此數據代表寫入外設寄存器的內容低8位*/
121                Tx_En = 1;    /*啟動發送*/
122                #20 Tx_En = 0;    /*一個時鍾周期后,清零發送啟動信號*/
123                @(posedge Tx_Done)/*等待發送完成信號*/
124                #1000;
125                
126                Tx_Data = Tail;/*需發送數據為幀尾*/
127                Tx_En = 1;    /*啟動發送*/
128                #20 Tx_En = 0;    /*一個時鍾周期后,清零發送啟動信號*/
129                @(posedge Tx_Done)/*等待發送完成信號*/
130                #1000;
131                #10000;    
132            end
133        endtask    
134        
135        task En_DDS_Run;/*使能DDS生成數據*/
136            begin
137                Send_CMD(`DDS_En, 8'h00, 8'h01);
138                $display("En DDS Run");
139            end
140        endtask
141        
142        task Stop_DDS_Run;/*停止DDS生成數據*/
143            begin
144                Send_CMD(`DDS_En, 8'h00, 8'h00);
145                $display("Stop DDS Run");
146            end
147        endtask
148        
149        task En_S_DDS;/*使能采樣DDS數據*/
150            begin
151                Send_CMD(`DDS_Sample_En, 8'h00, 8'h01);
152                $display("En Sample DDS data");
153            end
154        endtask
155        
156        task Stop_S_DDS;/*停止采樣DDS數據*/
157            begin
158                Send_CMD(`DDS_Sample_En, 8'h00, 8'h00);
159                $display("Stop Sample DDS data");
160            end
161        endtask
162        
163        task En_UART_Send;/*使能串口發送*/
164            begin
165                Send_CMD(`UART_En_Tx, 8'h00, 8'h01);
166                $display("En UART Send");
167            end
168        endtask
169        
170        task Stop_UART_Send;/*停止串口發送*/
171            begin
172                Send_CMD(`UART_En_Tx, 8'h00, 8'h00);
173                $display("Stop UART Send");
174            end
175        endtask
176        
177            task En_S_ADC;/*使能采集ADC數據*/
178            begin
179                Send_CMD(`ADC_Sample_En, 8'h00, 8'h01);
180                $display("En Sample ADC data");
181            end
182        endtask
183        
184        task Stop_S_ADC;/*停止采集ADC數據*/
185            begin
186                Send_CMD(`ADC_Sample_En, 8'h00, 8'h00);
187                $display("Stop Sample ADC data");
188            end
189        endtask
190
191        task Set_ADC_Sample_Speed;/*設置ADC采樣率*/
192            input[25:0] Fs;/*采樣率實際頻率*/
193            reg [31:0] S_cnt_top;/*分頻計數器計數最大值*/
194            begin
195            /*由采樣實際頻率值換算出采樣分頻計數器計數最大值*/
196                S_cnt_top = 50000000/Fs - 1;
197            /*寫采樣分頻計數器計數最大值低16位*/
198                Send_CMD(`ADC_S_Cnt_Max_L,S_cnt_top[15:8],S_cnt_top[7:0]);
199            /*寫采樣分頻計數器計數最大值高16位*/
200                Send_CMD(`ADC_S_Cnt_Max_H,S_cnt_top[31:24],S_cnt_top[23:16]);
201                $display("Set ADC Sample Speed as  = %0d" ,Fs);
202            end
203        endtask
204        
205        task Set_DDS_Sample_Speed;/*設置DDS數據的采樣率*/
206            input[25:0] Fs;/*采樣率實際頻率*/
207            reg [31:0] S_cnt_top;/*分頻計數器計數最大值*/
208            begin
209            /*由采樣實際頻率值換算出采樣分頻計數器計數最大值*/
210                S_cnt_top = 50000000/Fs - 1;
211            /*寫采樣分頻計數器計數最大值低16位*/    
212                Send_CMD(`DDS_S_Cnt_Max_L,S_cnt_top[15:8],S_cnt_top[7:0]);
213            /*寫采樣分頻計數器計數最大值高16位*/
214                Send_CMD(`DDS_S_Cnt_Max_H,S_cnt_top[31:24],S_cnt_top[23:16]);
215                $display("Set DDS Sample Speed as  = %0d" ,Fs);
216            end
217        endtask
218        
219        task Set_DDS_Fout_Speed;/*設置DDS輸出信號頻率*/
220            input[25:0] Fs;/*輸出信號實際頻率*/
221            reg [31:0] r_fword;/*DDS頻率控制字*/
222            begin
223            /*由實際要求輸出頻率數據換算出頻率控制字*/
224                r_fword = Fs*65536*65536/50000000;
225                Send_CMD(`DDS_Fword_L,r_fword[15:8],r_fword[7:0]);
226                Send_CMD(`DDS_Fword_H,r_fword[31:24],r_fword[23:16]);
227                $display("Set DDS Fout as = %0d" ,Fs);
228            end
229        endtask
230        
231        
232        task press_key;
233            input [KEY_WIDTH/2:0]Key;
234            reg [15:0]myrand;
235            begin
236                Key_in = {KEY_WIDTH{1'b1}};
237                /*按下抖動*/
238                repeat(20)begin
239                    myrand = {$random} % 65536;
240                    #myrand Key_in[Key] = ~Key_in[Key];
241                end
242                Key_in[Key] = 1'b0;
243                            
244                #22000000;/*穩定期*/
245                
246                /*釋放抖動*/
247                repeat(20)begin
248                    myrand = {$random} % 65536;
249                    #myrand Key_in[Key] = ~Key_in[Key];
250                end
251                Key_in[Key] = 1'b1;
252                #22000000;/*穩定期*/
253            end
254        endtask
255            
256    endmodule

下圖為系統仿真架構圖:

 

圖片29

這里,在我們提供的工程中,已經設置好了Nativelink,用戶只需要在Quartus II中點擊tools—run rtl simulation tool—rtl simulation即可自動調用modelsim-altera並執行仿真,因為這里完全模擬真實時序進行仿真,因此運行完整個仿真大約需要5—10分鍾。

仿真完成后,結果如圖所示:

 

圖片30

其中,Rx_Byte為串口接收仿真模型接收到的數據,這里以波形的方式展示。ADC_Data為ADC采樣結果,DDS_Data為DDS輸出的數據最下方為按鍵標志和按鍵結果,當按下按鍵1時,數據通道切換為ADC的采樣結果,當按下按鍵2時,數據通道切換為DDS的輸出數據。

(如果用戶在進行仿真的過程中發現仿真無法運行,在modelsim中提示錯誤的話,請刪除simulation—>modelsim文件夾下除wave.do和mydo.do文件外的其他所有文件,然后在quartus 中重新啟動仿真)

6、基於串口獵人的板級驗證

這里,我們使用一款功能非常強大的串口調試軟件——串口獵人來調試我們的設計。串口獵人的安裝這里不做過多的講述。首先,我們將FPGA系統的sof文件配置到fpga中,然后運行串口獵人軟件,串口獵人打開后界面如下所示:

 

圖片31

我們點擊圖中的動畫即可讓該動畫消失。

接下來我們載入預先設置好的配置文件,如下圖所示:

 

圖片32

我們點擊右下角的“載入”按鈕,在彈出的界面中,定位到我們本實驗的根目錄,選擇“serialhunter.ini”文件,

 

圖片33

點擊打開。

切換到高級發碼選項卡,顯示如下所示:

 

圖片34

點擊啟動自動發碼。

回到基本功能選項卡,可以看到,窗口中開始連續不斷的接收到數據,如下圖所示:

 

圖片35

此時,我們切換到波形顯示選項卡,可看到如下所示的效果:

 

圖片36

表明我們已經正確的接收到了波形數據。

切換到碼表選項卡,效果如下圖所示:

 

圖片37

然后,我們切換到柱狀顯示選項卡,效果如下所示:

 

圖片38

然后,我們回到高級發碼選項卡,將0~3組發碼列表前的勾選取消,勾選上第4組,然后點擊啟動自動發碼。此時,我們就已經將fpga系統的接收和發送波特率速率切換到了115200,如下圖所示:

 

圖片39

因為波特率不對,所以接下來接收到的數據就全部是錯誤的了。我們回到基本功能選項卡,將波特率切換為115200bps,如下圖所示:

 

圖片40

然后我們再回到波形顯示選項卡,結果如下所示:

 

圖片41

這時,我們再回到高級發碼選項卡,取消第4組發碼的勾選,勾選上第5組發碼,然后點擊自動發碼,再回到波形顯示選項卡,結果如下所示:

 

圖片42

此時,我們的DDS輸出信號頻率便更改為50Hz了。其他更多指令內容,這里就不一一介紹了,歡迎各位積極探索。

7、總結

當然,這個系統的最終目標是教會大家在fpga中使用串口進行簡單的數據收發,離真正的虛擬示波器還相差甚遠。此串口獵人顯示的波形頻率並不能嚴格的和實際信號的頻率對應上,這一點望各位悉知。也歡迎有上位機開發基礎的同學來根據本系統開發獨立的上位機軟件。另外,在使用中,我們只需要按下按鍵2,就能將數據通道切換到ADC的采樣結果上來,此時,給ADC的輸入上給出不同的電壓,在碼表選項卡上就能明顯的看到數值的變化,可作為電壓表之用。按下按鍵1則切換到內部DDS通道。

另外,文檔中使用的ADC型號為TCL549,我們開發套件后續配備的ADC模塊已經更改為TLV1544,因此,我們在工程中使用了條件編譯的方式,用戶可以根據手頭使用的ADC具體型號,設置編譯條件即可切換ADC型號,如下圖所示:

 

圖片43

如果使用TLV1544作為實際采樣器件,只需要在uart_scope.v文件的開頭,將 “`define USE_TLV1544 1”這句話使能,將“`define USE_TLC549 1”這句話注釋掉即可。反之亦然。

 

圖片44

由於本系統涉及到的功能模塊和代碼較多,無法一一為各位講解,希望各位能夠仔細閱讀代碼,代碼中小梅哥都做了詳細的注釋,希望大家通過代碼,能進一步學習verilog語法,增強對系統級仿真的意識。

如有更多問題,歡迎加入芯航線 FPGA 技術支持群交流學習:472607506

小梅哥

芯航線電子工作室

關於學習資料,小梅哥系列所有能夠開放的資料和更新(包括視頻教程,程序代碼,教程文檔,工具軟件,開發板資料)都會發布在我的雲分享。(記得訂閱)鏈接:http://yun.baidu.com/share/home?uk=402885837&view=share#category/type=0

贈送芯航線AC6102型開發板配套資料預覽版下載鏈接:鏈接:http://pan.baidu.com/s/1slW2Ojj 密碼:9fn3

贈送SOPC公開課鏈接和FPGA進階視頻教程。鏈接:http://pan.baidu.com/s/1bEzaFW 密碼:rsyh


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM