實驗1:基於FPGA + adc7928 + FIFO 緩沖8通道數據 + 通過串口打印到PC機(串口連續發送兩個字節)


RTL視圖

 

設計目標: 通過FPGA控制,輪流切換通道進行ADC讀數據,並將數據暫存到FIFO中,同時讀FIFO中的數據,通過串口打印到PC機端。FIFO采用的是16位寬的,深度用的256個字節。

 

1、串口設計要點:串口發送模塊,采用連續不間斷的發送兩個字節,這樣一共需要發出20個bit數據,這樣就避免FIFO讀出16位寬的數據進行分段發 。

(1)、首先需要設計第一個計數器,對位寬進行計數,也就是波特率,這里采用921600的波特率,1bit位寬的時間就是1/921600 s  = 1085ns ,FPGA外接的時鍾是50MHz,一個周期是20ns,顧1085/20 = 54.25,需要計數54個時鍾周期。計數器cnt0

(2)、其次設計第二個計數器,對bit進行計數,就是准備要發送多少個bit數據出去,設計目標是連續發送兩個字節,所以計數20個bit。計數器cnt1

(3)、串口發送模塊還需要產出一個信號,得告訴上游模塊,我已經准備好接收數據了,txd_rdy

    assign txd_rdy = (cnt0_vld  || txd_din_vld )?  1'b0: 1'b1;      //注意兩個條件不能少,尤其是txd_din_vld ,否則相對讀使能信號會延遲兩拍,導致讀使能連續會出兩個數據。詳情請看上篇文章的介紹。

(4)、串口要發送數據,得需要上游給個信號,收到有效信號后,才開始啟動計數器(位寬計數),txd_din_vld.

串口發送模塊完整代碼:

  1 module uart_txd_16(
  2                     clk,
  3                     rst_n,
  4                     txd_din_vld,
  5                     data_din,
  6                     txd_rdy,
  7                     txd_dout
  8                 );
  9                 
 10 parameter DATA_W    = 16;
 11 parameter BAUD_RATE    = 54;
 12 
 13 input clk;
 14 input rst_n;
 15 input txd_din_vld;
 16 input [DATA_W-1:0]data_din;
 17 
 18 output txd_rdy;
 19 output txd_dout;
 20 
 21 wire add_cnt0/* synthesis keep*/;
 22 wire end_cnt0/* synthesis keep*/;
 23 
 24 wire add_cnt1;
 25 wire end_cnt1;
 26 
 27 wire [20-1:0]data_temp;
 28 
 29 reg cnt0_vld;
 30 always @(posedge clk or negedge rst_n)begin
 31     if(!rst_n)begin
 32         cnt0_vld <= 0;
 33     end
 34     else if(txd_din_vld)begin
 35         cnt0_vld <= 1;
 36     end
 37     else if(end_cnt1)begin
 38         cnt0_vld <= 0;
 39     end
 40 end
 41 
 42 reg [8:0] cnt0;
 43 always @(posedge clk or negedge rst_n)begin
 44     if(!rst_n)begin
 45         cnt0 <= 0;
 46     end
 47     else if(add_cnt0)begin
 48         if(end_cnt0)begin
 49             cnt0 <= 0;
 50         end
 51         else begin
 52             cnt0 <= cnt0 + 1;
 53         end
 54     end
 55 end
 56 
 57 assign add_cnt0 = cnt0_vld == 1;
 58 assign end_cnt0 = add_cnt0 && cnt0 == BAUD_RATE - 1;
 59 
 60 reg [5:0] cnt1;
 61 always @(posedge clk or negedge rst_n)begin
 62     if(!rst_n)begin
 63         cnt1 <= 0;
 64     end
 65     else if(add_cnt1)begin
 66         if(end_cnt1)begin
 67             cnt1 <= 0;
 68         end
 69         else begin
 70             cnt1 <= cnt1 + 1;
 71         end
 72     end
 73 end
 74 
 75 assign add_cnt1 = end_cnt0;
 76 assign end_cnt1 = add_cnt1 && cnt1 == 20 - 1;  //數據位寬+起始位+停止位  , 連續發送兩個字節
 77 
 78 reg[DATA_W-1 : 0] data_buf;
 79 always @(posedge clk or negedge rst_n)begin
 80     if(!rst_n)begin
 81         data_buf <= 0;
 82     end
 83     else if(txd_din_vld)begin  //在檢測FIFO輸出的有效信號時,把數據進行鎖存,避免在發送過程中,data_buf 數據發生變化
 84         data_buf <= data_din;
 85     end
 86 end
 87 
 88 assign data_temp = {1'b1, data_buf[7:0], 1'b0, 1'b1, data_buf[15:8], 1'b0};  // 停止位 + 8bit數據 + 起始位, 低位先發
 89 
 90 reg txd_dout;
 91 always @(posedge clk or negedge rst_n)begin
 92     if(!rst_n)begin
 93         txd_dout <= 1;
 94     end
 95     else if(add_cnt0 && cnt0 == 0 && cnt1 >=0 && cnt1 < 20)begin
 96         txd_dout <= data_temp[cnt1];
 97     end
 98 end
 99 
100 assign txd_rdy = (cnt0_vld  || txd_din_vld )?  1'b0: 1'b1;
101 
102 endmodule
View Code

 

2、FIFO設計要點:

  (1)、FIFO采用的是show-ahead模式,位寬是16bit,深度256,因為ADC7928采集的數據是16bit的,方便寫入FIFO,所以選擇16bit的位寬,由於串口模塊采用的連續發送兩個字節,所以讀FIFO的數據不用進行分割發。

  (2)、深度是256,所以在寫入之前先判斷寫入的數量是否大於250,大於250的就暫停寫入,同時會通知上游模塊不要再進行ADC采集數據了。fifo_full_flag

  (3)、上游ADC模塊,當收完一個完整數據后,告訴FIFO可以寫入了,din_vld

  (4)、產生讀使能,讀使能采用的是組合邏輯(因為用的是show-ahead模式),同時判斷FIFO是否為空empty ,且下游模塊是否准備好din_rdy 

      assign rdreq = (empty == 0) && (din_rdy == 1);

  (5)、產生讀使能后,同時拿到了FIFO的數據,需要產生一個信號告訴下游模塊,准備接收數據了fifo_dout_vld

  

  記住關鍵點:注意數據對齊,在讀使能有效期間,數據也是在同一拍

 

 

FIFO控制模塊完整代碼:

 1 module control_fifo(
 2                         clk,
 3                         rst_n,
 4                         din_vld,
 5                         fifo_data_din,
 6                         din_rdy,//下游模塊准備好信號
 7                         fifo_dout_vld, //通知下游模塊准備收數據
 8                         fifo_data_dout,
 9                         fifo_full_flag
10 );
11 parameter DATA_WRW    = 16;
12 input clk;
13 input rst_n;
14 input din_vld;
15 input [DATA_WRW-1:0] fifo_data_din;
16 input din_rdy;
17 
18 output fifo_dout_vld;
19 output[DATA_WRW-1:0] fifo_data_dout;
20 output fifo_full_flag;
21 
22 reg  fifo_full_flag;
23 wire rdreq;
24 reg  wrreq;
25 wire [DATA_WRW-1:0] q/* synthesis keep*/;
26 wire [7:0]usedw/* synthesis keep*/;
27 my_fifo    my_fifo_inst (
28                         .clock ( clk ),
29                         .data  ( fifo_data_din ),
30                         .rdreq ( rdreq ),
31                         .wrreq ( wrreq ),
32                         .empty ( empty ),
33                         .full  ( full),
34                         .q     ( q ),
35                         .usedw ( usedw)
36     );
37     
38 //assign wrreq = full? 1'b0 : din_vld;
39 
40 always @(*)begin
41     if(usedw >= 250)begin
42         wrreq = 0;
43         fifo_full_flag = 0;
44     end
45     else begin
46         wrreq = din_vld;
47         fifo_full_flag = 1;
48     end
49         
50 end
51 
52 assign rdreq = (empty == 0) && (din_rdy == 1);
53 
54 reg [DATA_WRW-1:0] fifo_data_dout;
55 always @(posedge clk or negedge rst_n)begin
56     if(!rst_n)begin
57         fifo_data_dout <= 0;
58     end
59     else begin
60         fifo_data_dout <= q;
61     end
62 end
63 
64 reg fifo_dout_vld;
65 always @(posedge clk or negedge rst_n)begin
66     if(!rst_n)begin
67         fifo_dout_vld <= 0;
68     end
69     else begin
70         fifo_dout_vld <= rdreq;
71     end
72 end
73 
74 endmodule
View Code

 

3、ad7928設計要點:

首先查看手冊時序圖,第一次看這種時序圖,第一感覺,時鍾從哪里開始數比較好,就讓人拿不定注意,是從前頭的虛線那開始,結尾虛線結束嗎? 就會有各種疑問,網上搜資料教程也沒具體詳細將這一點。所以干脆不要想那么多,按自己的思路來

我是這樣數時鍾的,如下圖紅色線,意味着要產生17個這樣的完整時鍾,接下就設計數器了:

 

(1)、第一個計數器,產生sclk時鍾,也就是位寬,這里采用4分頻,sclk也就是50/4 = 12.5MHz,查看手冊,該時鍾最大可以到20MHz,計數cnt0

(2)、第二個計數器,對bit數量進行計數,發數據和收數據都需要借助這個計數器cnt1

(3)、我們可以知道在哪個階段發數據,何時收數據,所以需結合兩個計數器進行操作。

(4)、產生CS信號,可以在第一個時鍾的高電平中間拉低CS信號,在第17個時鍾的高電平中間位置拉高就行。

(5)、產生din 信號,也就是發出數據給AD7928,看時序圖,又有點蒙了,到底是在sclk的上升沿發,還是高電平期間發數據呢,因為第一個數據是在sclk高電平期間就發出了,所以后面都采用在高電平期間將數據送到din上,

    也就是CS拉低的同時,發出數據。

(6)、接收dout上的數據,看時序圖還是蒙了,到底是高電平期間采數據,還是下降沿采數據,可以查看t4和 t7說明,t4是sclk下降沿到dout的訪問時間,數據保持時間是t7,說明是在下降沿時采數據(其實我有點納悶,為啥不是在高電平期間采樣,在高電平期間,我看dout時序應該是最穩的)

    OK 我們還是按要求來,在sclk時鍾下降沿采數據。

 

采數據 、發數據、 CS拉低拉高位置就都已經確認下來了,如下圖所示,綠色虛線位置發  送數據  , 橘黃色虛線位置是 采數據 ,CS 是在第一個時鍾周期的高電平期間(綠色虛線)拉低,在第17個時鍾周期的高電平期間拉高。知道了關鍵節點,就可以寫代碼了。

 

 

 ad7928的完整代碼:

  1 module ad7928(
  2                 clk,
  3                 rst_n,
  4                 adc_dout,
  5                 adc_cs,
  6                 adc_sclk,
  7                 adc_din,
  8                 din_vld,
  9                 adc_dout_vld,
 10                 adc_data_out
 11 );
 12 
 13 parameter WRITE     = 1'b1    ;
 14 parameter SEQ       = 1'b0    ;
 15 parameter PM1        = 1'b1    ;
 16 parameter PM0        = 1'b1    ;
 17 parameter SHADOW    = 1'b0    ;
 18 parameter RANGE        = 1'b0    ;
 19 parameter CODING    = 1'b1    ;
 20 parameter ADDRES    = 3'b010;
 21 
 22 input                 clk        ;
 23 input                 rst_n    ;
 24 input                 adc_dout;
 25 input                 din_vld ;
 26 
 27 output                 adc_cs    ;
 28 output                 adc_sclk;
 29 output                 adc_din    ;
 30 output                 adc_dout_vld;
 31 output [16-1:0]     adc_data_out;
 32 
 33 wire add_cnt0;
 34 wire end_cnt0;
 35 
 36 wire add_cnt1;
 37 wire end_cnt1;
 38 
 39 wire add_cnt2;
 40 wire end_cnt2;
 41 
 42 wire [15:0]    data;
 43 
 44 reg [2:0] cnt0;
 45 always @(posedge clk or negedge rst_n)begin
 46     if(!rst_n)begin
 47         cnt0 <= 0; 
 48     end
 49     else if(add_cnt0)begin
 50         if(end_cnt0)begin
 51             cnt0 <= 0;
 52         end
 53         else begin
 54             cnt0 <= cnt0 + 1;
 55         end
 56     end
 57 end
 58 
 59 assign add_cnt0 = din_vld;
 60 assign end_cnt0 = add_cnt0 && cnt0 == 4-1;
 61 
 62 
 63 reg [4:0] cnt1;
 64 always @(posedge clk or negedge rst_n)begin
 65     if(!rst_n)begin
 66         cnt1 <= 0; 
 67     end
 68     else if(add_cnt1)begin
 69         if(end_cnt1)begin
 70             cnt1 <= 0;
 71         end
 72         else begin
 73             cnt1 <= cnt1 + 1;
 74         end
 75     end
 76 end
 77 
 78 assign add_cnt1 = end_cnt0;
 79 assign end_cnt1 = add_cnt1 && cnt1 == 17-1;
 80 
 81 reg [3:0] cnt2;
 82 always @(posedge clk or negedge rst_n)begin
 83     if(!rst_n)begin
 84         cnt2 <= 0; 
 85     end
 86     else if(add_cnt2)begin
 87         if(end_cnt2)begin
 88             cnt2 <= 0;
 89         end
 90         else begin
 91             cnt2 <= cnt2 + 1;
 92         end
 93     end
 94 end
 95 
 96 assign add_cnt2 = end_cnt1;
 97 assign end_cnt2 = add_cnt2 && cnt2 == 8-1;
 98 
 99 
100 reg adc_sclk;
101 always @(posedge clk or negedge rst_n)begin
102     if(!rst_n)begin
103         adc_sclk <= 1;
104     end
105     else if(add_cnt0 && cnt0 >= 2 && cnt0 < 4)begin
106         adc_sclk <= 0;
107     end
108     else if(add_cnt0 && cnt0 >= 0 && cnt0 < 2)begin
109         adc_sclk <= 1;
110     end
111 end
112 
113 reg adc_cs;
114 always @(posedge clk or negedge rst_n)begin
115     if(!rst_n)begin
116         adc_cs <= 1;
117     end
118     else if(add_cnt0 && cnt0 == 2-1 && cnt1 == 1-1)begin
119         adc_cs <= 0;
120     end
121     else if(add_cnt0 && cnt0 == 2-1 && cnt1 == 17-1)begin
122         adc_cs <= 1;
123     end
124 end
125 
126 reg [2:0] channel_sel;
127 always @(posedge clk or negedge rst_n)begin
128     if(!rst_n)begin
129         channel_sel <= 3'b000;
130     end
131     else begin
132         case(cnt2)
133             0        :    channel_sel <= 3'b000;
134             1        :    channel_sel <= 3'b001;
135             2        :    channel_sel <= 3'b010;
136             3        :    channel_sel <= 3'b011;
137             4        :    channel_sel <= 3'b100;
138             5        :    channel_sel <= 3'b101;
139             6        :    channel_sel <= 3'b110;
140             7        :    channel_sel <= 3'b111;
141             default :     channel_sel <= 3'b000;
142         endcase
143     end
144 end 
145 
146 reg adc_din;  //給ADC送數據,進行通道切換
147 always @(posedge clk or negedge rst_n)begin
148     if(!rst_n)begin
149         adc_din <= 1;
150     end
151     else if(add_cnt0 && cnt0 == 2-1 && cnt1 >=0 && cnt1 < 16)begin
152         adc_din = data[15-cnt1];
153     end
154 end
155 
156 assign data = {WRITE, SEQ, 1'b0, channel_sel, PM1, PM0, SHADOW, 1'b0, RANGE, CODING, 4'b0000};
157 
158 reg [16-1:0] data_temp; //從ADC上讀數據
159 always @(posedge clk or negedge rst_n)begin
160     if(!rst_n)begin
161         data_temp <= 15'b000_0000_0000_0000;
162     end
163     else if(add_cnt0 && cnt0 == 3-1 && cnt1 >= 0 && cnt1 < 16)begin
164         data_temp[15-cnt1] <= adc_dout;
165     end
166 end
167 
168 reg [16-1:0] adc_data_out;  //將收到的完整數據進行鎖存
169 always @(posedge clk or negedge rst_n)begin
170     if(!rst_n)begin
171         adc_data_out <= 0;
172     end
173     else if(end_cnt1)begin
174         adc_data_out <= data_temp;
175     end
176 end
177 
178 reg adc_dout_vld;  //數據有效時,同時產生一個有效標志
179 always @(posedge clk or negedge rst_n)begin
180     if(!rst_n)begin
181         adc_dout_vld <= 0;
182     end
183     else if(end_cnt1)begin
184         adc_dout_vld <= 1;
185     end
186     else begin
187         adc_dout_vld <= 0;
188     end
189 end
190 
191 endmodule
View Code

 

頂層設計代碼:

 1 module ad7928_fifo_top(
 2                         clk,
 3                         rst_n,
 4                         adc_dout,
 5                         
 6                         adc_cs,
 7                         adc_sclk,
 8                         adc_din,
 9                         txd_dout
10 );
11 
12 input                 clk;
13 input                 rst_n;
14 input                 adc_dout;
15 
16 output                 adc_cs;
17 output                 adc_sclk;
18 output              adc_din;
19 output                 txd_dout;
20 
21 wire  [16-1 : 0]    fifo_data_dout;
22 wire  [16-1 : 0]    adc_data_out;
23 wire                 adc_dout_vld;
24 wire                txd_rdy;
25 wire                 fifo_dout_vld;
26 wire                 fifo_full_flag;
27 
28 ad7928                u1_adc(
29                                     .clk            (clk),
30                                     .rst_n            (rst_n),
31                                     .adc_dout        (adc_dout),
32                                     .adc_cs            (adc_cs),
33                                     .adc_sclk        (adc_sclk),
34                                     .adc_din        (adc_din),
35                                     .din_vld        (fifo_full_flag),
36                                     .adc_dout_vld    (adc_dout_vld),
37                                     .adc_data_out    (adc_data_out)
38                     );
39                     
40 control_fifo        u2_fifo(
41                                     .clk(clk),
42                                     .rst_n(rst_n),
43                                     .din_vld(adc_dout_vld),
44                                     .fifo_data_din(adc_data_out),
45                                     .din_rdy(txd_rdy),//下游模塊准備好信號
46                                     .fifo_dout_vld(fifo_dout_vld), //通知下游模塊准備收數據
47                                     .fifo_data_dout(fifo_data_dout),
48                                     .fifo_full_flag(fifo_full_flag)
49 );
50 
51 uart_txd_16            u3_uart_txd(
52                                     .clk(clk),
53                                     .rst_n(rst_n),
54                                     .txd_din_vld(fifo_dout_vld),
55                                     .data_din(fifo_data_dout),
56                                     .txd_rdy(txd_rdy),
57                                     .txd_dout(txd_dout)
58                     );
59                     
60 endmodule 
View Code

signal TAB波形:

 

 串口打印的數據:測試時,先按住復位按鍵,然后點開始運行仿真觸發,釋放復位按鍵,然后迅速關閉串口,拉到最開始的位置對比數據,因為串口會一直在打印數據,且速率還是滿塊的。

可以對比下,第一個數據是6392 ,0E4A ,1C7F,  2AB7,  38EC, 4727, 5564 , 6396 , 71D5, ...和仿真上的數據是完全對的,第一個數據6392 其實是第6個通道的數據。由於第一次發數據時,ADC同時也會送出數據,而此時送出的數據我們並沒有配置通道,所以ADC估計會根據上一回的配置進行送出數據。

0E4A ,1C7F,  2AB7,  38EC, 4727, 5564 , 6396 , 71D5 從這數據上來看, 可以明顯看出是哪個通道的,0E4A 是0通道,1C7F是1通道的,2AB7是2通道的,具體可以看手冊上的dout數據說明。

0通道電壓:0E4A  ,  E4A  轉十進制 3658,

 3658 * 5 /4096 =  4.46533V  = 4.5V ,

從原路圖上計算:V0 = (R2+R4+R8+R9+R10+R11+R13+R14)  / (R1 +R2+R4+R8+R9+R10+R11+R13+R14)  * 5 = 8 / 9 * 5 =   4.444V, 這是理論計算電壓,用萬用實測的是4.5V,說明ADC測試的電壓還是蠻准的。

 

其他通道可以以此類推進行計算。

 

 


免責聲明!

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



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