FPGA——串口通信——使用三状态的状态机实现任意字节的数据发送

一、设计思路

二、顶层代码

`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   DATAN_W  =  40;   //发送数据个数
parameter   CNTBY_N  =  5;    //发送数据字节个数
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[7:0];
      data_tmp <= {data_tmp[7:0],data_tmp[DATAN_W-1: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

七、原工程文件

上一篇:【FPGA开发笔记】—— 数码管动态显示项目详细剖析+个人心得体会


下一篇:FPGA(八)---按键消抖