FPGA——串口通信——使用三狀態的狀態機實現任意字節的數據發送


一、設計思路

  • 5字節(40bit)數據准確的發送給8輸入端口,如何發送?
    • 首先要設計一個字節計數器來算數據發送到了哪一個字節
    • 設計三個狀態,分別為:空閑狀態、准備發送狀態、發送數據狀態
    • 准備發送狀態主要完成的工作是:每到一個字節就使能串口模塊以及加載數據
    • 40bit數據的處理方式是:每用完一個字節就右移八位,再將40bit數據的低八位發送給串口模塊
data     <= data_tmp[7:0];
data_tmp <= {data_tmp[7:0],data_tmp[DATAN_W-1:8]};

更正:
在實際應用中,data應該先取高位,所以,以上代碼應該改為

data <= data_tmp[DATAN_W-1:DATAN_W-8];
data_tmp <= {data_tmp[DATAN_W-9:0],data_tmp[DATAN_W-1:DATAN_W-8]};

二、頂層代碼

`timescale 1ns / 1ns

module uart_tx_multibyte(
   clk      ,  //時鍾
   rst_n    ,  //復位
   data_n   ,  //要發送的多字節數據
   trans_go ,  //發送使能
   uart_tx     //串口發送數據
);
parameter   IDLE     =  3'b001;  //空閑狀態
parameter   S1       =  3'b010;  //准備發送數據狀態,使能,裝載數據
parameter   S2       =  3'b100;  //發送數據狀態

//CNTBY_N字節數,CNTBY_W字節數計數器的位寬(更改這兩個參數,即可實現串口任意字節發送)
parameter   CNTBY_N  =  5;    //發送數據字節個數
parameter   DATAN_W  =  8*CNTBY_N;   //發送數據個數
parameter   CNTBY_W  =  3;    //發送數據字節計數器位寬,5=3'b101
parameter   DATA_W   =  8;    //一次串行發送的數據個數
parameter   STATE_W  =  3;    //狀態機位寬


input                   clk;
input                   rst_n;
input    [DATAN_W-1:0]  data_n;
input                   trans_go;
output                  uart_tx;

wire                    uart_tx;
reg      [CNTBY_W-1:0]  cnt_bytes;
wire                    add_cnt_bytes;
wire                    end_cnt_bytes;
reg                     cnt_bytes_flag;
wire                    tx_done;
reg      [DATA_W-1:0]   data;
reg                     send_en;

reg      [STATE_W-1:0]  state_c;
reg      [STATE_W-1:0]  state_n;
wire                    IDLE2S1_start;
wire                    S12S2_start;   
wire                    S22S1_start ;  
wire                    S22IDLE_start ;

reg      [DATAN_W-1:0]  data_tmp;


my_uart_tx my_uart_tx(
   .clk        (clk),      //時鍾
   .rst_n      (rst_n),    //復位
   .data       (data),     //發送數據
   .send_en    (send_en),  //發送使能
   .baud_set   (4'd4),     //波特率設置,默認9600bps
   .uart_tx    (uart_tx),  //串口發送
   .tx_done    (tx_done)   //發送完成標志位
);



always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      cnt_bytes <= 0;
   else if(add_cnt_bytes)begin
      if(end_cnt_bytes)
         cnt_bytes <= 0;
      else
         cnt_bytes <= cnt_bytes + 1'b1;
   end
end
assign add_cnt_bytes = cnt_bytes_flag;
assign end_cnt_bytes = add_cnt_bytes && cnt_bytes == CNTBY_N - 1;

always @(*)begin
   if(tx_done)
      cnt_bytes_flag = 1;
   else
      cnt_bytes_flag = 0;
end

//狀態機
//狀態轉移描述
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      state_c <= IDLE;
   else
      state_c <= state_n;
end
//狀態轉移條件判斷描述
always @(*)begin
   case(state_c)
      IDLE:begin
         if(IDLE2S1_start)
            state_n = S1;
         else
            state_n = state_c;
      end
      S1:begin
         if(S12S2_start)
            state_n = S2;
         else
            state_n = state_c;
      end
      S2:begin
         if(S22S1_start)
            state_n = S1;
         else if(S22IDLE_start)
            state_n = IDLE;
         else
            state_n = state_c;
      end
      default:
         state_n = IDLE;
   endcase
end
//狀態轉移條件描述
assign IDLE2S1_start =  state_c==IDLE  && trans_go;
assign S12S2_start   =  state_c==S1    && send_en;
assign S22S1_start   =  state_c==S2    && tx_done && !end_cnt_bytes;
assign S22IDLE_start =  state_c==S2    && end_cnt_bytes;
//狀態輸出
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      send_en <= 0;
   else if(state_c == S1)
      send_en <= 1;
   else
      send_en <= 0;
end

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)begin
      data <= 0;
      data_tmp <= 0;      
   end
	else if(state_c == IDLE)begin
		data <= 0;
		data_tmp <= data_n;
	end
   else if(state_c == S1 && !send_en)begin
      data <= data_tmp[DATAN_W-1:DATAN_W-8];
      data_tmp <= {data_tmp[DATAN_W-9:0],data_tmp[DATAN_W-1:DATAN_W-8]};
   end
end


endmodule

三、串口發送模塊代碼

module my_uart_tx(
   clk      ,  //時鍾
   rst_n    ,  //復位
   data     ,  //發送數據
   send_en  ,  //發送使能
   baud_set ,  //波特率設置
   uart_tx  ,  //串口發送
   tx_done     //發送完成標志位
);
parameter   DATA_W   =  8;
parameter   SET_W    =  3;
parameter   BYTE_D   =  10;
parameter   BAUT_W   =  17;
parameter   BYTE_W   =  4;


input                   clk;
input                   rst_n;
input    [DATA_W-1:0]   data;
input                   send_en;
input    [SET_W-1:0]    baud_set;
output                  uart_tx;
output                  tx_done;

reg                     uart_tx;
reg                     tx_done;

reg      [BAUT_W-1:0]   cnt_baud;
reg      [BAUT_W-1:0]   baud;
wire                    add_cnt_baud;
wire                    end_cnt_baud;

reg      [BYTE_W-1:0]   cnt_byte;
wire                    add_cnt_byte;
wire                    end_cnt_byte;              

reg      [BYTE_D-1:0]   uart_data;
reg                     cnt_baud_flag;


always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      cnt_baud <= 0;
   else if(add_cnt_baud)begin
      if(end_cnt_baud)
         cnt_baud <= 0;
      else
         cnt_baud <= cnt_baud + 1'b1;
   end
end
assign add_cnt_baud = cnt_baud_flag;
assign end_cnt_baud = add_cnt_baud && cnt_baud == baud - 1;

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      cnt_byte <= 0;
   else if(add_cnt_byte)begin
      if(end_cnt_byte)
         cnt_byte <= 0;
      else
         cnt_byte <= cnt_byte + 1'b1;
   end
end
assign add_cnt_byte = end_cnt_baud;
assign end_cnt_byte = add_cnt_byte && cnt_byte == BYTE_D - 1;

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      uart_tx <= 1;
   else if(cnt_baud == 0 && add_cnt_baud)
      uart_tx <= uart_data[cnt_byte];
end

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      uart_data <= 10'b0;
   else if(!cnt_baud_flag && send_en)
      uart_data <= {1'b1,data,1'b0};
end

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      cnt_baud_flag <= 0;
   else if(send_en)
      cnt_baud_flag <= 1;
   else if(end_cnt_byte)
      cnt_baud_flag <= 0;
end

always @(*)begin
   case(baud_set)
      3'd0:baud = 17'd83333;//600bps
      3'd1:baud = 17'd41666;//1200bps 
      3'd2:baud = 17'd20833;//2400bps 
      3'd3:baud = 17'd10416;//4800bps
      3'd4:baud = 17'd5208 ;//9600bps 
      3'd5:baud = 17'd2604 ;//19200bps 
      3'd6:baud = 17'd1302 ;//38400bps 
      3'd7:baud = 17'd868  ;//57600bps
      default:
         baud = 0;
   endcase
end

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      tx_done <= 0;
   else if(end_cnt_byte)
      tx_done <= 1;
   else
      tx_done <= 0;
end

endmodule

四、板級測試代碼

由於開發板輸入端口不夠,所以專門寫了一個每10毫秒發送一串數據的代碼

module uart_tx_multibyte_test(
   clk      ,
   rst_n    ,
   uart_tx  
);
parameter   DATAN_W  =  40;
parameter   CNT_D    =  500000;
parameter   CNT_W    =  19;

input                   clk;
input                   rst_n;
output                  uart_tx;
reg                     trans_go;


reg      [DATAN_W-1:0]  data_n;
reg      [CNT_W-1:0]    cnt;
wire                    end_cnt;

uart_tx_multibyte uart_tx_multibyte(
   .clk        (clk),      //時鍾
   .rst_n      (rst_n),    //復位
   .data_n     (data_n),   //要發送的多字節數據
   .trans_go   (trans_go), //發送使能
   .uart_tx    (uart_tx)   //串口發送數據
);

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      cnt <= 0;
   else if(end_cnt)
      cnt <= 0;
   else
      cnt <= cnt + 1;
end
assign end_cnt = cnt == CNT_D - 1;

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      data_n <= 40'hA987654321;
   else if(add_cnt_data)begin
      if(end_cnt_data)
         data_n <= 40'hA987654321;
      else
         data_n <= data_n +1'b1;
   end
end
assign add_cnt_data = end_cnt;
assign end_cnt_data = add_cnt_data && data_n == 40'hB987654321 - 1;

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      trans_go <= 0;
   else if(end_cnt)
      trans_go <= 1;
   else
      trans_go <= 0;
end

endmodule

五、頂層代碼仿真文件

`timescale 1ns / 1ps

module uart_tx_multibyte_tb();

parameter   DATAN_W  =  40;   //發送數據個數
parameter   CYCLE    =  20;
parameter   RST_TIME =  3;

reg                  clk;
reg                  rst_n;
reg   [DATAN_W-1:0]  data_n;
reg                  trans_go;
wire                 uart_tx;

uart_tx_multibyte uart_tx_multibyte(
   clk      ,  //時鍾
   rst_n    ,  //復位
   data_n   ,  //要發送的多字節數據
   trans_go ,  //發送使能
   uart_tx     //串口發送數據
);

initial begin
   clk = 1;
   forever begin
      #(CYCLE/2);
      clk = ~clk;
   end
end

initial begin
   rst_n = 1;
   #3;
   rst_n = 0;
   #(RST_TIME*CYCLE);
   rst_n = 1;
end

initial begin
   #1;
   data_n = 0;
   #(10*CYCLE);
   data_n = 40'hA987654321;
   #5400000;
   data_n = 40'h123456789A;
end

initial begin
   #1;
   trans_go = 0;
   #(10*CYCLE);
   trans_go = 1;
   #(CYCLE);
   trans_go = 0;
   #5400000;
   trans_go = 1;
   #(CYCLE);
   trans_go = 0;
end

initial begin
   #12000000;
   $stop;
end
endmodule

六、測試代碼仿真文件

`timescale 1ns / 1ns


module uart_tx_multibyte_test_tb();

reg   clk;
reg   rst_n;
wire  uart_tx;

uart_tx_multibyte_test uart_tx_multibyte_test(
   clk      ,
   rst_n    ,
   uart_tx  
);

parameter CYCLE      =  20;
parameter RST_TIME   =  3;

initial begin
   clk = 1;
   forever begin
      #(CYCLE/2)
      clk = ~clk;
   end
end

initial begin
   rst_n = 1;
   #3;
   rst_n = 0;
   #(RST_TIME * CYCLE);
   rst_n = 1;
end

initial begin
   #15203500
   $stop;
end

endmodule

七、原工程文件

[https://github.com/CNLuzt/FPGA_CODE/tree/master/uart_tx_multibyte]


免責聲明!

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



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