IT技術互動交流平臺

FPGA實現串口與iic控制器總結(1)

來源:IT165收集  發布日期:2016-04-28 21:40:26

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

1、串口

該章節代碼來源于《深入淺出玩轉FPGA》深入淺出的相關章節。
改代碼實現的例子是串口以9600的波特率接受從電腦傳來的一個數據,然后立馬發回電腦。根據頂層框圖可以發現rx的輸出數據接口是接在tx的輸入接口的。所以這并不是一個完整的全功能的串口,是一個閹割板的仿串口協議的收發,并且沒有用狀態機實現。
整體框圖:
頂層
4個文件:

speed_selectspeed_rx(
.clk(clk),//波特率選擇模塊
.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),//波特率選擇模塊
.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模塊中:
一根輸入線受外部限制,在if判斷中作為是否有必要啟動計時器的標志。定義了一個計時器,將在2603個計時周期處輸出改變clk_bps_r變量,作為采樣脈沖。同時已經計算好了在一個數據位的正中心采樣。我們考慮為9600bps,這樣算下來每個數據位的時間是多少,再結合自己板子的時鐘,決定應該算多少個周期。
speed模塊例化了2次,在接受和發送都可以設置選用不同的波特率。已經定義為變量,只需選擇不同的值即可。

my_uart_rx模塊中:

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

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

//----------------------------------------------------------------
reg rs232_rx0,rs232_rx1,rs232_rx2,rs232_rx3;    //接收數據寄存器,濾波用
wire neg_rs232_rx;  //表示數據線接收到下降沿

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;
        end
end
    //因為串口的起始信號是一個下降沿
    //參考按鍵消抖,畫圖發現可以檢測到下降沿,如果正常的一個下降沿可以產生一個時長為20ns的瞬時高脈沖,
    //但是如果是20ns-40ns的毛刺就無法產生這個了
    //下面的下降沿檢測可以濾掉<20ns-40ns的毛刺(包括高脈沖和低脈沖毛刺),
    //這里就是用資源換穩定(前提是我們對時間要求不是那么苛刻,因為輸入信號打了好幾拍) 
    //(當然我們的有效低脈沖信號肯定是遠遠大于40ns的)
assign neg_rs232_rx = rs232_rx3 & rs232_rx2 & ~rs232_rx1 & ~rs232_rx0;    //接收到下降沿后neg_rs232_rx置高一個時鐘周期

可以看到實際上類似與按鍵去抖,可以自己去畫畫隨著clk演變的圖,分析一下對什么樣的噪聲有效。這是一種常見的處理方式。
根據串口的協議,起始位是一個低電平,所以我們要檢測下降沿。

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     //接收到串口接收線rs232_rx的下降沿標志信號
            bps_start_r <= 1'b1;    //啟動串口準備數據接收
            rx_int <= 1'b1;         //接收數據中斷信號使能
        end
    else if(num==4'd12) begin       //接收完有用數據信息
            bps_start_r <= 1'b0;    //數據接收完畢,釋放波特率啟動信號
            rx_int <= 1'b0;         //接收數據中斷信號關閉
        end

assign bps_start = bps_start_r;

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

//----------------------------------------------------------------
reg[7:0] rx_data_r;     //串口接收數據寄存器,保存直至下一個數據來到
//----------------------------------------------------------------
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   //接收數據處理        這個地方體現出對串口波特率的啟動
        if(clk_bps) begin       //這個地方顯然不能把clk_bps放到always的括號里面,因為在speed模塊中分析了其實
        //讀取并保存數據,接收數據為一個起始位,8bit數據,1或2個結束位     
                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     //我們的標準接收模式下只有1+8+1(2)=11bit的有效數據
                num <= 4'd0;            //接收到STOP位后結束,num清零
                rx_data_r <= rx_temp_data;  //把數據鎖存到數據寄存器rx_data中
            end
        end
assign rx_data = rx_data_r; 

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

tx模塊中:

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

rx_int是在rx中定義的,還在接受數據則為1,接受完了即為0 ,這個地方即是通過這種信號的傳遞來實現是先收滿一個數據后再發出來一個數據
還是先是一個濾波,可以分析下他是怎么讓信號繼續保持一個時鐘周期的

//---------------------------------------------------------
reg rx_int0,rx_int1,rx_int2;    //rx_int信號寄存器,捕捉下降沿濾波用
wire neg_rx_int;    // rx_int下降沿標志位
//同樣是一個消抖
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;
        end
end
assign neg_rx_int =  ~rx_int1 & rx_int2;   //捕捉到下降沿后,neg_rx_int拉高保持一個主時鐘周期

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

//---------------------------------------------------------
reg[7:0] tx_data;   //待發送數據的寄存器
//---------------------------------------------------------
reg bps_start_r;
reg tx_en;  //發送數據使能信號,高有效
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   //接收數據完畢,準備把接收到的數據發回去
            bps_start_r <= 1'b1;
            tx_data <= rx_data; //把接收到的數據存入發送數據寄存器(整個寄存器一個周期就全部賦值過去了?)
            tx_en <= 1'b1;      //進入發送數據狀態中
        end
    else if(num==4'd11) begin   //數據發送完成,復位
            bps_start_r <= 1'b0;
            tx_en <= 1'b0;
        end
end
assign bps_start = bps_start_r;

思路與rx一致,同樣也定義了一個tx_en信號來表征是否發完了。注意這個時候num==11。
接下來吧數據發出去,思路與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;   //發送起始位
                        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;   //發送結束位
                        default: rs232_tx_r <= 1'b1;
                        endcase
                end
            else if(num==4'd11) num <= 4'd0;    //復位
        end
end
assign rs232_tx = rs232_tx_r;

其實完整梳理下來思路很清晰,沒有用狀態機依舊實現了串口的功能,后面可以試試如何改為分部的,連續接受后存到一個文件中。
或者把別的部分怎么加進來。
因為不像軟件代碼的順序執行,這個是并發執行的,所以還是有差別的。

下面說說testbench,依舊是參見特權的例子:
關于仿真信號很多,所以需要弄懂整個設計思路,再來分析信號的變化。自己之前犯了個低級錯誤,ml605的板子是200MHZ的,但是自己的`timescale 1ns / 1ps在modelsim中根本產生不了200MHZ的時鐘,后面分析計數器的計數值是對的,但是算總的延時時間不對才定位到這里。所以一定要小心,一點點排查,有耐心聚焦,不要一下子看到這么多信號懵逼了。
testbench的原理我就不講了,信號線應該怎么接,是reg還是wire型。
特權的代碼里面封裝了一些常見的task:

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

調用系統命令,最后會在modelsim串口打出來。注意task的調用方法
這里做了遍歷測試和隨機測試:

        //遍歷測試      
        for(cnt=255;cnt>0;cnt=cnt-1)                //順次發送0-255
            begin                       
                tx_task(cnt);                               //發送數據
                @(negedge rx_flag);             //表示等到這個信號的下降沿再進行下一步。等待接收到的數據
                                                //這個地方是要等串口接收完。185行是一個always塊一直在檢
                                                //測是否收到數據,里面的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)              //順次發送0-255
            begin
                tx_data = {$random};                       
                tx_task(tx_data);                           //發送隨機數據
                @(negedge rx_flag);                     //等待接收到的數據
                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 

這里調用了tx_task。注意@(negedge rx_flag);的用法,因為是仿真語句,并不需要可綜合。這表示在此處一致等等到rx_flag的下降沿,順序結構。

    //串口發送任務,是主動的,所以只需要一個task,我們主動去調用就可以了
    task tx_task;
        input[7:0] txdata;                          //發送數據輸入
        integer i;
        begin
            rs232_rx = 0;                               //起始位
            #tx_bps;
            for(i=0;i<8;i=i+1)                      //8位數據發送
                begin
                    rs232_rx = txdata[7-i];
                    #tx_bps;
                end
                rs232_rx = 1;                           //停止位
                #tx_bps;
        end
    endtask
    integer j;
        //串口接收,是被動的,所以一直要用always塊去檢測是否有數據發上來
    always @(negedge rs232_tx)                      //起始位檢測
        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

其實整體思路還是比較清晰的。
與c語言差別很大,如何實現各種結構,需要積累經驗。然后就是比較verilog要實現的東西一定要想清楚怎么去實現,劃分成那幾個功能模塊,是否用狀態機,內部應定義那些信號來實現各模塊間的控制和數據傳遞。這需要多去聯系來增強感覺。

關于iic留到一下講,更精彩!


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

Tag標簽: 串口   控制器  
  • 專題推薦

About IT165 - 廣告服務 - 隱私聲明 - 版權申明 - 免責條款 - 網站地圖 - 網友投稿 - 聯系方式
本站內容來自于互聯網,僅供用于網絡技術學習,學習中請遵循相關法律法規
湖北快三走势图 ccq| c7k| kym| 5gk| eq6| owo| a6m| osw| 6gi| yg6| gsq| k6e| k6u| sai| 7og| wsi| 5ks| gm5| wia| c5k| uoe| 5cg| oa5| scw| e6o| s6k| iei| 4qm| gc4| umc| q4y| cyw| 4ui| uu5| sqa| w5u| goi| 5qw| gag| wu3| ugk| c3c| cyg| a4q| iou| wuw| oq4| eac| c4o| igi| 2ci| equ| oq3| wuk| u3w| cuy| 3sq| mu3| ais| a3c| sas| 4eo| oi2| gmi| msy| c2q| uyi| 2gq| co2| ekc| c2e| yuy| a3s| cmg| 1ie| yy1| myg| giq| w1y| saw| 2uk| og2| gek| s2a| iqg| 2us| kq0| qwe| y0g| cwe|