• <li id="00i08"><input id="00i08"></input></li>
  • <sup id="00i08"><tbody id="00i08"></tbody></sup>
    <abbr id="00i08"></abbr>
  • 新聞中心

    EEPW首頁 > 嵌入式系統 > 設計應用 > FPGA串行接口 1 - RS-232 串行接口的工作原理

    FPGA串行接口 1 - RS-232 串行接口的工作原理

    作者: 時間:2024-01-02 來源:EEPW編譯 收藏

    串行接口RS-232是將FPGA連接到PC的簡單方法。我們只需要一個發射器和接收器模塊。

    本文引用地址:http://www.czjhyjcfj.com/article/202401/454373.htm

    異步發送器

    它通過串行化要發送的數據來創建信號“ TxD”。

    異步接收器

    它從FPGA外部獲取信號“ RxD”,并對其進行“反序列化”,以便在FPGA內部輕松使用。

    RS-232接口具有以下特點:

    • 使用 9 針連接器“DB-9”(較舊的 PC 使用 25 針“DB-25”)。

    • 允許雙向全雙工通信(PC可以同時發送和接收數據)。

    • 可以以大約 10KBytes/s 的最大速度進行通信。

    DB-9 連接器

    您可能已經在 PC 背面看到了此連接器。



    它有 9 個引腳,但 3 個重要的引腳是:

    • 引腳 2:RxD(接收數據)。

    • 引腳 3:TxD(傳輸數據)。

    • 引腳 5:GND(接地)。

    只需使用 3 根電線,您就可以發送和接收數據。

    數據通常由 8 位(我們稱之為字節)的塊發送,并且是“序列化”的:首先發送 LSB(數據位 0),然后發送位 1,...最后是 MSB(第 7 位)。

    異步通信

    此接口使用異步協議。 這意味著沒有時鐘信號沿數據傳輸。 接收器必須有一種方法可以將自身“計時”到輸入的數據位。

    在 RS-232 的情況下,這是這樣完成的:

    1. 電纜的兩端事先就通信參數(速度、格式等)達成一致。這是在通信開始之前手動完成的。

    2. 當線路處于空閑狀態時,發射器會發送“空閑”(=“1”)。

    3. 發送器在發送每個字節之前發送“start”(=“0”),以便接收器可以確定一個字節即將到來。

    4. 發送字節數據的 8 位。

    5. 發送器在每個字節后發送“stop”(=“1”)。

    讓我們看看字節在傳輸時0x55的樣子:

    字節 0x55 以二進制形式01010101。
    但是由于它首先傳輸 LSB(bit-0),因此該行的切換方式如下:1-0-1-0-1-0-1-0。

    下面是另一個示例:

    這里的數據是0xC4,你能看到它嗎?
    這些位更難看到。 這說明了接收方知道數據以何種速度發送是多么重要。

    我們發送數據的速度有多快?

    速度以波特率為單位,即每秒可以發送多少位。 例如,1000 波特意味著每秒 1000 位,或者每個位持續 <> 毫秒。

    RS-232 接口的常見實現(如 PC 中使用的接口)不允許使用任何速度。 如果你想使用123456波特率,你就不走運了。 你必須滿足于一些“標準”速度。常見值包括:

    • 1200波特。

    • 9600波特。

    • 38400波特。

    • 115200 波特(通常是你能做到的最快速度)。

    在 115200 波特時,每個比特持續 (1/115200) = 8.7μs。 如果傳輸 8 位數據,則持續時間為 8 x 8.7μs = 69μs。 但是每個字節都需要一個額外的起始位和停止位,因此實際上需要 10 x 8.7μs = 87μs。 這意味著最大速度為每秒 11.5KB。

    在 115200 波特率下,一些帶有錯誤芯片的 PC 需要一個“長”停止位(1.5 或 2 位長...),這使得最大速度降至每秒 10.5KB 左右。

    物理層

    電線上的信號使用正/負電壓方案。

    • “1”使用 -10V(或介于 -5V 和 -15V 之間)發送。

    • “0”使用+10V(或5V至15V之間)發送。

    因此,空閑線路的電壓約為 -10V。

    串行接口 2 - 波特發生器

    在這里,我們希望以最大速度使用串行鏈路,即 115200 波特(較慢的速度也很容易生成)。 FPGA 通常以 MHz 的速度運行,遠高于 115200Hz(按照今天的標準,RS-232 相當慢)。 我們需要找到一種方法來生成(從FPGA時鐘)盡可能接近每秒115200次的“滴答聲”。

    傳統上,RS-232芯片使用1.8432MHz時鐘,因為這使得生成標準波特頻率變得非常容易。 1.8432MHz 除以 16 得到 115200Hz。

    假設FPGA時鐘信號運行頻率為1.8432MHz

    //我們創建一個4位計數器

    reg [3:0] BaudDivCnt;
    always @(posedge clk) BaudDivCnt <= BaudDivCnt + 1; // count forever from 0 to 15

    / 以及每 16 個時鐘斷言一次的滴答信號(即每秒 115200 次)

    wire BaudTick = (BaudDivCnt==15);

    這很容易。但是,如果你有一個1MHz的時鐘,而不是8432.2MHz,你會怎么做? 要從 115200MHz 時鐘生成 2Hz,我們需要將時鐘除以“17.361111111...” 不完全是一個整數。 解決方案是有時除以 17,有時除以 18,確保比率保持“17.361111111”。 這實際上很容易做到。

    請看下面的“C”代碼:

    while(1) // repeat forever
    {
      acc += 115200;
      if(acc>=2000000) printf("*"); else printf(" ");

      acc %= 2000000;
    }

    它以精確的比例打印“*”,平均每“17.361111111...”循環一次。

    為了在FPGA中有效地獲得相同的結果,我們依賴于這樣一個事實,即串行接口可以容忍波特頻率發生器中幾%的誤差。

    希望 2000000 是 2000000 的冪。 顯然 2000000 不是。 所以我們改變了比例...... 讓我們使用“115200/1024”= 59.17,而不是“356/10”。 這非常接近我們的理想比率,并實現了高效的 FPGA 實現: 我們使用一個 59 位累加器,遞增 <>,每次累加器溢出時都會標記一個刻度。

    // let's assume the FPGA clock signal runs at 2.0000MHz
    // we use a 10-bit accumulator plus an extra bit for the accumulator carry-out
    reg [10:0] acc;   // 11 bits total!
    // add 59 to the accumulator at each clock
    always @(posedge clk)
      acc <= acc[9:0] + 59; // use 10 bits from the previous accumulator result, but save the full 11 bits result
    wire BaudTick = acc[10]; // so that the 11th bit is the accumulator carry-out

    使用我們的 2MHz 時鐘,“BaudTick”每秒置位 115234 次,與理想的 0 相差 03.115200%。

    參數化 FPGA 波特率發生器

    以前的設計使用 10 位累加器,但隨著時鐘頻率的增加,需要更多的位。

    這是一個具有 25MHz 時鐘和 16 位累加器的設計。 設計是參數化的,因此易于定制。

    parameter ClkFrequency = 25000000; // 25MHz
    parameter Baud = 115200;
    parameter BaudGeneratorAccWidth = 16;
    parameter BaudGeneratorInc = (Baud<<BaudGeneratorAccWidth)/ClkFrequency;

    reg [BaudGeneratorAccWidth:0] BaudGeneratorAcc;
    always @(posedge clk)
      BaudGeneratorAcc <= BaudGeneratorAcc[BaudGeneratorAccWidth-1:0] + BaudGeneratorInc;

    wire BaudTick = BaudGeneratorAcc[BaudGeneratorAccWidth];

    最后一個實現問題: “BaudGeneratorInc”計算是錯誤的,因為 Verilog 使用 32 位中間結果,并且計算超出了這個范圍。 更改該行,如下所示以獲得解決方法。

    parameter BaudGeneratorInc = ((Baud<<(BaudGeneratorAccWidth-4))+(ClkFrequency>>5))/(ClkFrequency>>4);

    這條線還有一個額外的優勢,可以對結果進行舍入而不是截斷。

    現在我們有了足夠精確的波特發生器,我們可以繼續使用 RS-232 發射器和接收器模塊。

    串行接口 3 - RS-232 發送器

    我們正在構建一個具有固定參數的“異步發射器”:8 個數據位、2 個停止位、非奇偶校驗。

    它的工作原理是這樣的:

    發送器在 FPGA 內部獲取 8 位數據并將其串行化(從“TxD_start”信號置位時開始)。

    “忙”信號在傳輸發生時被置位(在此期間忽略“TxD_start”信號)。

    序列化數據

    要遍歷起始位、8 個數據位和停止位,狀態機似乎是合適的。

    reg [3:0] state;

    // the state machine starts when "TxD_start" is asserted, but advances when "BaudTick" is asserted (115200 times a second)
    always @(posedge clk)
    case(state)
      4'b0000: if(TxD_start) state <= 4'b0100;
      4'b0100: if(BaudTick) state <= 4'b1000; // start
      4'b1000: if(BaudTick) state <= 4'b1001; // bit 0
      4'b1001: if(BaudTick) state <= 4'b1010; // bit 1
      4'b1010: if(BaudTick) state <= 4'b1011; // bit 2
      4'b1011: if(BaudTick) state <= 4'b1100; // bit 3
      4'b1100: if(BaudTick) state <= 4'b1101; // bit 4
      4'b1101: if(BaudTick) state <= 4'b1110; // bit 5
      4'b1110: if(BaudTick) state <= 4'b1111; // bit 6
      4'b1111: if(BaudTick) state <= 4'b0001; // bit 7
      4'b0001: if(BaudTick) state <= 4'b0010; // stop1
      4'b0010: if(BaudTick) state <= 4'b0000; // stop2
      default: if(BaudTick) state <= 4'b0000;
    endcase

    現在,我們只需要生成“TxD”輸出。

    reg muxbit;

    always @(state[2:0])
    case(state[2:0])
      0: muxbit <= TxD_data[0];
      1: muxbit <= TxD_data[1];
      2: muxbit <= TxD_data[2];
      3: muxbit <= TxD_data[3];
      4: muxbit <= TxD_data[4];
      5: muxbit <= TxD_data[5];
      6: muxbit <= TxD_data[6];
      7: muxbit <= TxD_data[7];
    endcase

    // combine start, data, and stop bits together
    assign TxD = (state<4) | (state[3] & muxbit);

    串行接口 4 - RS-232 接收器

    我們正在構建一個“異步接收器”:

    我們的實現是這樣工作的:

    該模塊在收到 RxD 線時收集數據。

    當一個字節被接收到時,它出現在“數據”總線上。一旦接收到一個完整的字節,就會為一個時鐘置位“data_ready”。

    請注意,“data”僅在斷言“data_ready”時有效。 其余時間,不要使用它,因為新數據可能會洗牌。

    過采樣

    異步接收器必須以某種方式與輸入信號保持同步(它通常無法訪問發射器使用的時鐘)。

    為了確定新的數據字節何時到來,我們通過以波特率頻率的倍數對信號進行過采樣來尋找“開始”位。

    一旦檢測到“起始”位,我們以已知的波特率對線路進行采樣,以獲取數據位。

    接收器通常以波特率的 16 倍對輸入信號進行過采樣。 我們在這里使用了 8 次...... 對于 115200 波特,采樣率為 921600Hz。

    假設我們有一個可用的“Baud8Tick”信號,每秒斷言 921600 次。

    設計

    首先,傳入的“RxD”信號與我們的時鐘沒有關系。
    我們使用兩個D觸發器對其進行過采樣,并將其同步到我們的時鐘域。

    reg [1:0] RxD_sync;
    always @(posedge clk) if(Baud8Tick) RxD_sync <= {RxD_sync[0], RxD}; 

    我們對數據進行過濾,以便 RxD 線上的短尖峰不會與起始位混淆。

    reg [1:0] RxD_cnt;
    reg RxD_bit;

    always @(posedge clk)
    if(Baud8Tick)
    begin
      if(RxD_sync[1] && RxD_cnt!=2'b11) RxD_cnt <= RxD_cnt + 1;
      else
      if(~RxD_sync[1] && RxD_cnt!=2'b00) RxD_cnt <= RxD_cnt - 1;

      if(RxD_cnt==2'b00) RxD_bit <= 0;
      else
      if(RxD_cnt==2'b11) RxD_bit <= 1;
    end

    狀態機允許我們在檢測到“開始”后檢查接收到的每個位。

    reg [3:0] state;

    always @(posedge clk)
    if(Baud8Tick)
    case(state)
      4'b0000: if(~RxD_bit) state <= 4'b1000; // start bit found?
      4'b1000: if(next_bit) state <= 4'b1001; // bit 0
      4'b1001: if(next_bit) state <= 4'b1010; // bit 1
      4'b1010: if(next_bit) state <= 4'b1011; // bit 2
      4'b1011: if(next_bit) state <= 4'b1100; // bit 3
      4'b1100: if(next_bit) state <= 4'b1101; // bit 4
      4'b1101: if(next_bit) state <= 4'b1110; // bit 5
      4'b1110: if(next_bit) state <= 4'b1111; // bit 6
      4'b1111: if(next_bit) state <= 4'b0001; // bit 7
      4'b0001: if(next_bit) state <= 4'b0000; // stop bit
      default: state <= 4'b0000;
    endcase

    請注意,我們使用了“next_bit”信號,從一個位到另一個位。

    reg [2:0] bit_spacing;

    always @(posedge clk)
    if(state==0)
      bit_spacing <= 0;
    else
    if(Baud8Tick)
      bit_spacing <= bit_spacing + 1;

    wire next_bit = (bit_spacing==7);

    最后,移位寄存器收集數據位。

    reg [7:0] RxD_data;
    always @(posedge clk) if(Baud8Tick && next_bit && state[3]) RxD_data <= {RxD_bit, RxD_data[7:1]};    

    串行接口 5 - 如何使用 RS-232 發射器和接收器

    此設計允許從 PC 控制幾個 FPGA 引腳(通過 PC 的串行端口)。

    它在FPGA(名為“GPout”的端口)上創建8個輸出。GPout由FPGA接收到的任何字符進行更新。

    FPGA 上還有 8 個輸入(名為“GPin”的端口)。每次FPGA接收到字符時,都會發送GPin。

    GP 輸出可用于從您的 PC 遠程控制任何東西,可能是 LED 或咖啡機......

    module serialGPIO(

        input clk,

        input RxD,

        output TxD,


        output reg [7:0] GPout,  // general purpose outputs

        input [7:0] GPin  // general purpose inputs

    );


    wire RxD_data_ready;

    wire [7:0] RxD_data;

    async_receiver RX(.clk(clk), .RxD(RxD), .RxD_data_ready(RxD_data_ready), .RxD_data(RxD_data));

    always @(posedge clk) if(RxD_data_ready) GPout <= RxD_data;


    async_transmitter TX(.clk(clk), .TxD(TxD), .TxD_start(RxD_data_ready), .TxD_data(GPin));

    endmodule



    關鍵詞:

    評論


    相關推薦

    技術專區

    關閉
    主站蜘蛛池模板: 芮城县| 扶沟县| 长兴县| 盖州市| 沂南县| 六盘水市| 唐河县| 蒙自县| 洛阳市| 三门县| 青冈县| 板桥市| 枣庄市| 潼关县| 沂源县| 道真| 西平县| 永胜县| 乐东| 文水县| 兴业县| 兴义市| 师宗县| 舟曲县| 许昌市| 连城县| 建湖县| 延川县| 平邑县| 托里县| 卢氏县| 瓮安县| 响水县| 青神县| 宜黄县| 蓬溪县| 恭城| 普陀区| 明溪县| 乌兰察布市| 枝江市|