IT技(ji)術(shu)互動交流平jiao)/h4>

彩神注册官网

來源︰IT165收集(ji)  發布(bu)日期(qi)︰2020-02-20 16:30:26

在剖析了《深(shen)入淺出玩轉FPGA》的串口代碼(ma)和IIC控(kong)制器代碼(ma)、xilinx官方的xilinx的iic控(kong)制器(參見書《FPGACPLD設計(ji)工具──Xilinx ISE使用(yong)詳解》)、《片(pian)上(shang)系(xi)統(tong)設計(ji)思想與源代碼(ma)分析》一書中帶有(you)wishbone接口的iic控(kong)制器後,本文嘗試對以上(shang)做一些總結(jie),並分析不同(tong)的iic控(kong)制器的實現區別。

1、串口

該(gai)章節代碼(ma)來源于(yu)《深(shen)入淺出玩轉FPGA》深(shen)入淺出的相關章節。
改代碼(ma)實現zhi)睦郵譴 諞600的波特(te)率接受從電腦傳來的一個數據,然後立(li)馬(ma)發回電腦。根據頂層框圖可以發現rx的輸出數據接口是接在tx的輸入接口的。所以這(zhe)並不是一個完整的全功能(neng)的串口,是一個閹割板的仿(fang)串口協議的收發,並且沒有(you)用(yong)狀態(tai)機實現zheng)br />整體(ti)框圖︰
頂層
4個文件︰

speed_selectspeed_rx(.clk(clk),//波特(te)率選擇模塊.rst_p(rst_p),.bps_start(bps_start1),.clk_bps(clk_bps1));my_uart_rxmy_uart_rx(.clk(clk),//接收數據模塊.rst_p(rst_p),.rs232_rx(rs232_rx),.rx_data(rx_data),.rx_int(rx_int),.clk_bps(clk_bps1),.bps_start(bps_start1));///////////////////////////////////////////speed_selectspeed_tx(.clk(clk),//波特(te)率選擇模塊.rst_p(rst_p),.bps_start(bps_start2),.clk_bps(clk_bps2));my_uart_txmy_uart_tx(.clk(clk),//發送數據模塊.rst_p(rst_p),.rx_data(rx_data),.rx_int(rx_int),.rs232_tx(rs232_tx),.clk_bps(clk_bps2),.bps_start(bps_start2));

speed_select模塊中︰
一根輸入線受外部限(xian)制,在if判斷(duan)中作為是否有(you)必(bi)要(yao)啟動計(ji)時器的標(biao)志。定義了一個計(ji)時器,將在2603個計(ji)時周期(qi)處輸出改變(bian)clk_bps_r變(bian)量,作為采樣脈沖。同(tong)時已經計(ji)算好(hao)了在一個數據位的正中心采樣。我們(men)考慮(lv)為9600bps,這(zhe)樣算下來每個數據位的時間是多(duo)少,再結(jie)合自己(ji)板子的時鐘,決定應該(gai)算多(duo)少個周期(qi)。
speed模塊例化了2次,在接受和發送都(du)可以設置選用(yong)不同(tong)的波特(te)率。已經定義為變(bian)量,只需選擇不同(tong)的值(zhi)即可。

my_uart_rx模塊中︰

input clk; // 50MHz主時鐘input rst_p; //高電平復位信號(hao)input rs232_rx; // RS232接收數據信號(hao)input clk_bps; // clk_bps的高電平為接收或者發送數據位的中間采樣點,spped模塊就(jiu)是專門產(chan)生這(zhe)麼一個采樣脈沖的output bps_start; //接收到(dao)數據後,波特(te)率時鐘啟動信號(hao)置位output[7:0] rx_data; //接收數據寄存器,保存直至下一個數據來到(dao) output rx_int; //接收數據中斷(duan)信號(hao),接收到(dao)數據期(qi)間始終為高電平

clk_bps即為speed模塊中傳來的采樣脈沖信號(hao),bps_start決定是否有(you)必(bi)要(yao)啟動那個計(ji)時模塊。
進來首先可以看到(dao)是對rs232_rx信號(hao)的一個濾(lv)波︰

//----------------------------------------------------------------reg rs232_rx0,rs232_rx1,rs232_rx2,rs232_rx3; //接收數據寄存器,濾(lv)波用(yong)wire neg_rs232_rx; //表示數據線接收到(dao)下降沿(yan)always @ (posedge clk or posedge rst_p) begin if(rst_p) begin  rs232_rx0 <= 1'b0;  rs232_rx1 <= 1'b0;  rs232_rx2 <= 1'b0;  rs232_rx3 <= 1'b0; end else begin    //打了4拍  rs232_rx0 <= rs232_rx;  rs232_rx1 <= rs232_rx0;  rs232_rx2 <= rs232_rx1;  rs232_rx3 <= rs232_rx2; endend //因為串口的起(qi)始信號(hao)是一個下降沿(yan) //參考按鍵消抖,畫圖發現可以檢(jian)測到(dao)下降沿(yan),如果正常的一個下降沿(yan)可以產(chan)生一個時長(chang)為20ns的瞬時高脈沖, //但是如果是20ns-40ns的毛刺就(jiu)無法產(chan)生這(zhe)個了 //下面(mian)的下降沿(yan)檢(jian)測可以濾(lv)掉<20ns-40ns的毛刺(包括高脈沖和低脈沖毛刺), //這(zhe)里就(jiu)是用(yong)資(zi)源換穩定(前提是我們(men)對時間要(yao)求不是那麼苛刻(ke),因為輸入信號(hao)打了好(hao)幾拍) //(當然我們(men)的有(you)效低脈沖信號(hao)肯定是遠遠大于(yu)40ns的)assign neg_rs232_rx = rs232_rx3 & rs232_rx2 & ~rs232_rx1 & ~rs232_rx0; //接收到(dao)下降沿(yan)後neg_rs232_rx置高一個時鐘周期(qi)

可以看到(dao)實際上(shang)類似與按鍵去抖,可以自己(ji)去畫畫隨著clk演變(bian)的nai)跡 治 幌露允裁囪腦 you)效。這(zhe)是一種(zhong)常見的處理方式。
根據串口的協議,起(qi)始位是一個低電平,所以我們(men)要(yao)檢(jian)測下降沿(yan)。

always @ (posedge clk or posedge rst_p) if(rst_p) begin  bps_start_r <= 1'bz;  //?  rx_int <= 1'b0; end else if(neg_rs232_rx) begin //接收到(dao)串口接收線rs232_rx的下降沿(yan)標(biao)志信號(hao)  bps_start_r <= 1'b1; //啟動串口準備數據接收  rx_int <= 1'b1;  //接收數據中斷(duan)信號(hao)使能(neng) end else if(num==4'd12) begin //接收完有(you)用(yong)數據信息  bps_start_r <= 1'b0; //數據接收完畢,釋(shi)放波特(te)率啟動信號(hao)  rx_int <= 1'b0;  //接收數據中斷(duan)信號(hao)關閉 endassign bps_start = bps_start_r;

檢(jian)測到(dao)rx上(shang)有(you)效的低電平,置位這(zhe)2個信號(hao),啟動計(ji)數器模塊開始準備技(ji)術(shu)采樣數據,同(tong)時表明正在接收數據。當一幀(zheng)數據完了(12位),則(ze)釋(shi)放。

//----------------------------------------------------------------reg[7:0] rx_data_r; //串口接收數據寄存器,保存直至下一個數據來到(dao)//----------------------------------------------------------------reg[7:0] rx_temp_data; //當前接收數據寄存器always @ (posedge clk or posedge rst_p) if(rst_p) begin  rx_temp_data <= 8'd0;  num <= 4'd0;  rx_data_r <= 8'd0; end else if(rx_int) begin //接收數據處理 這(zhe)個地方jiao)ti)現出對串口波特(te)率的啟動 if(clk_bps) begin //這(zhe)個地方顯(xian)然不能(neng)把clk_bps放到(dao)always的括號(hao)里面(mian),因為在speed模塊中分析了其實 //讀取(qu)並保存數據,接收數據為一個起(qi)始位,8bit數據,1或2個結(jie)束位   num <= num+1'b1;  case (num)   4'd1: rx_temp_data[0] <= rs232_rx; //鎖存第0bit   4'd2: rx_temp_data[1] <= rs232_rx; //鎖存第1bit   4'd3: rx_temp_data[2] <= rs232_rx; //鎖存第2bit   4'd4: rx_temp_data[3] <= rs232_rx; //鎖存第3bit   4'd5: rx_temp_data[4] <= rs232_rx; //鎖存第4bit   4'd6: rx_temp_data[5] <= rs232_rx; //鎖存第5bit   4'd7: rx_temp_data[6] <= rs232_rx; //鎖存第6bit   4'd8: rx_temp_data[7] <= rs232_rx; //鎖存第7bit   default: ;   endcase  end else if(num == 4'd12) begin //我們(men)的標(biao)準接收模式下只有(you)1+8+1(2)=11bit的有(you)效數據  num <= 4'd0;  //接收到(dao)STOP位後結(jie)束,num清零(ling)  rx_data_r <= rx_temp_data; //把數據鎖存到(dao)數據寄存器rx_data中  end endassign rx_data = rx_data_r; 

rx_int作為這(zhe)個always中的if的啟動信號(hao),按照時鐘脈沖鎖存數據,當收滿一幀(zheng)(包括奇(qi)偶校驗(yan)和停止(zhi)位),num清0,傳出rx的值(zhi)。
記住整個過(guo)程都(du)是多(duo)個並行的always塊,是如何實現流程控(kong)制pin)模/em>
即是通(tong)過(guo)if判斷(duan)中的是否滿足條(tiao)件來實現zhi)/em>,如︰
else if(neg_rs232_rx) begin //等到(dao)有(you)效的下降沿(yan)啟動信號(hao)
if(num==4’d12) begin //相當與for循環
if(clk_bps) begin //等待speed模塊中的clk計(ji)數器到(dao)那一刻(ke)

tx模塊中︰

input clk;  // 50MHz主時鐘input rst_p; //高電平復位信號(hao)input clk_bps; // clk_bps_r高電平為接收數據位的中間采樣點,同(tong)時也作為發送數據的數據改變(bian)點input[7:0] rx_data; //接收數據寄存器input rx_int; //接收數據中斷(duan)信號(hao),接收到(dao)數據期(qi)間始終為高電平,在該(gai)模塊中利(li)用(yong)它的下降沿(yan)來啟動串口發送數據(下降沿(yan)表示數據接受完了)output rs232_tx; // RS232發送數據信號(hao)output bps_start; //接收或者要(yao)發送數據,波特(te)率時鐘啟動信號(hao)置位

rx_int是在rx中定義的,還在接受數據則(ze)為1,接受完了即為0 ,這(zhe)個地方即是通(tong)過(guo)這(zhe)種(zhong)信號(hao)的傳遞來實現是先收滿一個數據後再發出來一個數據
還是先是一個濾(lv)波,可以分析下lv)竊趺慈眯藕hao)繼續保持一個時鐘周期(qi)的

//---------------------------------------------------------reg rx_int0,rx_int1,rx_int2; //rx_int信號(hao)寄存器,捕捉下降沿(yan)濾(lv)波用(yong)wire neg_rx_int; // rx_int下降沿(yan)標(biao)志位//同(tong)樣是一個消抖always @ (posedge clk or posedge rst_p) begin if(rst_p) begin  rx_int0 <= 1'b0;  rx_int1 <= 1'b0;  rx_int2 <= 1'b0; end else begin  rx_int0 <= rx_int;  rx_int1 <= rx_int0;  rx_int2 <= rx_int1; endendassign neg_rx_int = ~rx_int1 & rx_int2; //捕捉到(dao)下降沿(yan)後,neg_rx_int拉高保持一個主時鐘周期(qi)

接下來是把數據從rx中傳到(dao)tx中去

//---------------------------------------------------------reg[7:0] tx_data; //待發送數據的寄存器//---------------------------------------------------------reg bps_start_r;reg tx_en; //發送數據使能(neng)信號(hao),高有(you)效reg[3:0] num;always @ (posedge clk or posedge rst_p) begin if(rst_p) begin  bps_start_r <= 1'bz;  tx_en <= 1'b0;  tx_data <= 8'd0; end else if(neg_rx_int) begin //接收數據完畢,準備把接收到(dao)的數據發回去  bps_start_r <= 1'b1;  tx_data <= rx_data; //把接收到(dao)的數據存入發送數據寄存器(整個寄存器一個周期(qi)就(jiu)全部賦值(zhi)過(guo)去了?)  tx_en <= 1'b1; //進入發送數據狀態(tai)中 end else if(num==4'd11) begin //數據發送完成,復位  bps_start_r <= 1'b0;  tx_en <= 1'b0; endendassign bps_start = bps_start_r;

思路(lu)與rx一致,同(tong)樣也定義了一個tx_en信號(hao)來表征是否發完了。注(zhu)意這(zhe)個時候num==11。
接下來吧數據發出去,思路(lu)與rx一致︰

//---------------------------------------------------------reg rs232_tx_r;always @ (posedge clk or posedge rst_p) begin if(rst_p) begin  num <= 4'd0;  rs232_tx_r <= 1'b1; end else if(tx_en) begin  if(clk_bps) begin   num <= num+1'b1;   case (num)   4'd0: rs232_tx_r <= 1'b0; //發送起(qi)始位   4'd1: rs232_tx_r <= tx_data[0]; //發送bit0   4'd2: rs232_tx_r <= tx_data[1]; //發送bit1   4'd3: rs232_tx_r <= tx_data[2]; //發送bit2   4'd4: rs232_tx_r <= tx_data[3]; //發送bit3   4'd5: rs232_tx_r <= tx_data[4]; //發送bit4   4'd6: rs232_tx_r <= tx_data[5]; //發送bit5   4'd7: rs232_tx_r <= tx_data[6]; //發送bit6   4'd8: rs232_tx_r <= tx_data[7]; //發送bit7   4'd9: rs232_tx_r <= 1'b1; //發送結(jie)束位   default: rs232_tx_r <= 1'b1;   endcase  end  else if(num==4'd11) num <= 4'd0; //復位 endendassign rs232_tx = rs232_tx_r;

其實完整梳理下來思路(lu)很(hen)清晰,沒有(you)用(yong)狀態(tai)機依(yi)舊實現了串口的功能(neng),後面(mian)可以試試如何改為分部的,連續接受後存到(dao)一個文件中。
或者把別的部分怎麼加(jia)進來chu)br />因為不像軟件代碼(ma)的順(shun)序執行,這(zhe)個是並發執行的,所以還是有(you)差別的。

下面(mian)說說testbench,依(yi)舊是參見特(te)權的例子︰
關于(yu)仿(fang)真信號(hao)很(hen)多(duo),所以需要(yao)弄懂整個設計(ji)思路(lu),再來分析信號(hao)的變(bian)化。自己(ji)之(zhi)前犯了個低級錯誤,ml605的板子是200MHZ的,但是自己(ji)的`timescale 1ns / 1ps在modelsim中根本產(chan)生不了200MHZ的時鐘,後面(mian)分析計(ji)數器的計(ji)數值(zhi)是對的,但是算總的延時時間不對才(cai)定位到(dao)這(zhe)里。所以一定要(yao)小心,一點點排查,有(you)耐(nai)心聚焦,不要(yao)一下子看到(dao)這(zhe)麼多(duo)信號(hao)懵逼了。
testbench的原理我就(jiu)不講(jiang)了,信號(hao)線應該(gai)怎麼接,是reg還是wire型。
特(te)權的代碼(ma)里面(mian)封(feng)裝了一些常見的task︰

 //----------------------------------------- //常用(yong)信息打印任務(wu)封(feng)裝 //----------------------------------------- //警告(gao)信息打印任務(wu) task warning; input[2*8:1] msg; begin  $write('WARNING at %t : %s ',$time,msg); end endtask //錯誤信息打印任務(wu) task error; input[20*8:1] msg; begin  $write('ERROR at %t:%s ',$time,msg); end endtask  //致命錯誤打印並停止(zhi)仿(fang)真任務(wu) task fatal; input[20*8:1] msg; begin  $write('FATAL at %t : %s',$time,msg);  $write('Simulation false');  $stop; end endtask //完成仿(fang)真任務(wu) task terminate; begin  $write('Simulation Successful');  $stop; end endtask

調(diao)用(yong)系(xi)統(tong)命令(ling),最後會在modelsim串口打出來chu)Wzhu)意task的調(diao)用(yong)方法
這(zhe)里做了遍歷測試和隨機測試︰

 //遍歷測試  for(cnt=255;cnt>0;cnt=cnt-1)  //順(shun)次發送0-255  begin     tx_task(cnt);    //發送數據  @(negedge rx_flag);  //表示等到(dao)這(zhe)個信號(hao)的下降沿(yan)再進行下一步。等待接收到(dao)的數據      //這(zhe)個地方是要(yao)等串口接收完。185行是一個always塊一直在檢(jian)      //測是否收到(dao)數據,里面(mian)的rx_flag表示是否收完  if(data_temp ==cnt)   $write('transmit:%d, receive:%d; ture',cnt,data_temp); //自收發數據正確  else begin   $write('transmit:%d, receive:%d; error',cnt,data_temp); //自收發數據錯誤   error('false');   end  end  #10_000;    //10us延時 //隨機測試 for(cnt=1; cnt<255; cnt=cnt+1)  //順(shun)次發送0-255  begin  tx_data = {$random};     tx_task(tx_data);    //發送隨機數據  @(negedge rx_flag);   //等待接收到(dao)的數據  if(data_temp ==tx_data)   $write('transmit:%d, receive:%d; ture',cnt,data_temp);  //自收發數據正確  else begin   $write('transmit:%d, receive:%d; error',cnt,data_temp); //自收發數據錯誤   error('false');   end  end  terminate; end 

這(zhe)里調(diao)用(yong)了tx_task。注(zhu)意@(negedge rx_flag);的用(yong)法,因為是仿(fang)真語句,並不需要(yao)可綜(zong)合。這(zhe)表示在此處一致等等到(dao)rx_flag的下降沿(yan),順(shun)序結(jie)構(gou)。

 //串口發送任務(wu),是主動的,所以只需要(yao)一個task,我們(men)主動去調(diao)用(yong)就(jiu)可以了 task tx_task; input[7:0] txdata;    //發送數據輸入 integer i; begin  rs232_rx = 0;    //起(qi)始位  #tx_bps;  for(i=0;i<8;i=i+1)   //8位數據發送  begin   rs232_rx = txdata[7-i];   #tx_bps;  end  rs232_rx = 1;    //停止(zhi)位  #tx_bps; end endtask integer j; //串口接收,是被(bei)動的,所以一直要(yao)用(yong)always塊去檢(jian)測是否有(you)數據發上(shang)來 always @(negedge rs232_tx)   //起(qi)始位檢(jian)測 begin  #(tx_bps/2);  if(rs232_tx == 0)  begin   rx_flag = 1;   #tx_bps;   for(j=0;j<8;j=j+1)   begin   data_temp[7-j] = rs232_tx;   #tx_bps;   end   rx_flag = 0;  end end

其實整體(ti)思路(lu)還是比較清晰的。
與c語言差別很(hen)大,如何實現各種(zhong)結(jie)構(gou),需要(yao)積(ji)累經驗(yan)。然後就(jiu)是比較verilog要(yao)實現zhi)畝 饕歡ㄒyao)想清楚怎麼去實現,劃分成那幾個功能(neng)模塊,是否用(yong)狀態(tai)機,內部應定義那些信號(hao)來實現各模塊間的控(kong)制和數據傳遞。這(zhe)需要(yao)多(duo)去聯系(xi)來增(zeng)強感覺。

關于(yu)iic留(liu)到(dao)一下講(jiang),更精彩!


lijiuyang
4-28夜于(yu)武昌藍巢逸品

Tag標(biao)簽(qian)︰串口  控(kong)制器  
  • 彩神注册官网

About IT165 - 廣告(gao)服務(wu) - 隱私聲明 - 版權申(shen)明 - 免(mian)責條(tiao)款(kuan) - 網站(zhan)地圖 - 網友(you)投稿(gao) - 聯系(xi)方式
本站(zhan)內容來自于(yu)互聯網,僅供用(yong)于(yu)網絡技(ji)術(shu)學習,學習中請遵循相關法律法規
彩神注册官网 | 下一页