#今天也是咸鱼的一天~
UART是一个很基础的串口通信协议,有人打趣说只要有单片机的地方,就一定有uart。这话不假,不仅很多模块和单片机的通信有uart,单片机和上位机之间的通信,uart也是最常用的。(寒假的时候,就用C#写了一个上位机,用的就是串口)
Uart 的原理可以参考这个博客:FPGA的串口通讯(UART)
这几天用Robei EDA 给我的一个感想就是,在FPGA的使用设计上,如果是从原有的程序上移植,比较需要注意的就是一份代码,需要由多少个运算模块组成,每个模块的IO分配,变量类型和数据位的设置,还有对于testbech的设置。
而如果是独立的Verilog设计,设计方式是自上而下还是自下而上,每个模块的IO分配,一个模块内部变量的使用,parameter的设置。尤其是parameter,不适合每个模块都对parameter设置为常数,比较好的应该是把局部模块的parameter设置为一个“变量”,然后在顶层模块把这个“变量”设置为parameter。
回到正题……
uart 在robei EDA的库里是有封装好模块,可以直接调用。唯一的坏处就是,这个模块已经封装好了,不能进行任何修改。(当然,也没什么好修改的)
代码可以在Code里面看到。
如果不用这个模块,自己写一个,其实还挺麻烦的……
要分别写一个发送uart_tx
模块和接收uart_rx
模块,激励文件的编写也很麻烦……
分别是发送和接收的模块。代码我是参考正点原子的uart收发测试,上位机像FPGA发送一串数据,FPGA再把数据返回给上位机。
UART_TX
- 延时
//对发送使能信号uart_en延迟两个时钟周期
always@(posedge clk or negedge rst)
begin
if(!rst)
begin
uart_en_d0 <= 1'b0;
uart_en_d1 <= 1'b0;
end
else
begin
uart_en_d0 <= uart_en;
uart_en_d1 <= uart_en_d0;
end
end
- 取上升沿
assign flag = ~d0 & d1;
//捕获uart_en上升沿,得到一个时钟周期的脉冲信号
- 数据寄存
parameter BPS_CNT= CLK_FRE/UART_BPS;
//当脉冲信号en_flag到达时,寄存待发送的数据,并进入发送过程
always@(posedge clk or negedge rst)
begin
if(!rst)
begin
tx_flag <= 1'b0;
tx_data <= 8'b0;
end
else if(flag )
begin
tx_flag <= 1'b1;
tx_data <= uart_din;
end
else if((tx_cnt == 4'd9)&&(clk_cnt == BPS_CNT/2))
begin
tx_flag <= 1'b0;
tx_data <= 8'd0;
end
else
begin
tx_flag <= tx_flag;
tx_data <= tx_data;
end
end
- 启动计数器
//进入发送过程后,启动系统时钟计数器与发送数据计数器
parameter BPS_CNT= CLK_FRE/UART_BPS;
always@(posedge clk or negedge rst)
begin
if(!rst)
begin
clk_cnt <= 16'b0;
tx_cnt <= 4'd0;
end
else if(tx_flag)
begin
if(clk_cnt < BPS_CNT - 1)
begin
clk_cnt <= clk_cnt + 1'b1;
tx_cnt <= tx_cnt;
end
else
begin
clk_cnt <= 16'd0;
tx_cnt <= tx_cnt + 1'b1;
end
end
else
begin
clk_cnt <= 16'b0;
tx_cnt <= 4'd0;
end
end
- 数据配置
//根据发送数据计数器来给uart发送端口赋值
always@(posedge clk or negedge rst)
begin
if(!rst)
uart_txd <= 1'b1;
else if(tx_flag)
case(tx_cnt)
4'd0: uart_txd <= 1'b0;
4'd1: uart_txd <= tx_data[0];
4'd2: uart_txd <= tx_data[1];
4'd3: uart_txd <= tx_data[2];
4'd4: uart_txd <= tx_data[3];
4'd5: uart_txd <= tx_data[4];
4'd6: uart_txd <= tx_data[5];
4'd7: uart_txd <= tx_data[6];
4'd8: uart_txd <= tx_data[7];
4'd9: uart_txd <= 1'b1;
default : ;
endcase
else
uart_txd <= 1'b1;
end
UART_RX
- 数据延时
//对UART接收端口的数据延迟两个时钟周期
always @(posedge clk or negedge rst) begin
if (!rst) begin
uart_rx_d0 <= 1'b0;
uart_rx_d1 <= 1'b0;
end
else begin
uart_rx_d0 <= uart_rx;
uart_rx_d1 <= uart_rx_d0;
end
end
- 获取下降沿
//捕获接收端口下降沿(起始位),得到一个时钟周期的脉冲信号
assign start_flag = uart_rxd_d1 & (~uart_rxd_d0);
- 进入接收过程
parameter BPS_CNT = CLK_FREQ/UART_BPS;
//当脉冲信号start_flag到达时,进入接收过程
always @(posedge clk or negedge rst) begin
if (!rst)
rx_flag <= 1'b0;
else begin
if(flag) //检测到起始位
rx_flag <= 1'b1; //进入接收过程,标志位rx_flag拉高
else if((rx_cnt == 4'd9)&&(clk_cnt == BPS_CNT/2))
rx_flag <= 1'b0; //计数到停止位中间时,停止接收过程
else
rx_flag <= rx_flag;
end
end
- 启动计数器
parameter BPS_CNT = CLK_FREQ/UART_BPS;
always @(posedge clk or negedge rst) begin
if (!rst) begin
clk_cnt <= 16'd0;
rx_cnt <= 4'd0;
end
else if ( rx_flag ) begin //处于接收过程
if (clk_cnt < BPS_CNT - 1) begin
clk_cnt <= clk_cnt + 1'b1;
rx_cnt <= rx_cnt;
end
else begin
clk_cnt <= 16'd0; //对系统时钟计数达一个波特率周期后清零
rx_cnt <= rx_cnt + 1'b1; //此时接收数据计数器加1
end
end
else begin //接收过程结束,计数器清零
clk_cnt <= 16'd0;
rx_cnt <= 4'd0;
end
end
- 数据传输
parameter BPS_CNT = CLK_FREQ/UART_BPS;
always @(posedge clk or negedge rst) begin
if ( !rst)
rxdata <= 8'd0;
else if(rx_flag) //系统处于接收过程
if (clk_cnt == BPS_CNT/2) begin //判断系统时钟计数器计数到数据位中间
case ( rx_cnt )
4'd1 : rxdata[0] <= uart_rx_d1; //寄存数据位最低位
4'd2 : rxdata[1] <= uart_rx_d1;
4'd3 : rxdata[2] <= uart_rx_d1;
4'd4 : rxdata[3] <= uart_rx_d1;
4'd5 : rxdata[4] <= uart_rx_d1;
4'd6 : rxdata[5] <= uart_rx_d1;
4'd7 : rxdata[6] <= uart_rx_d1;
4'd8 : rxdata[7] <= uart_rx_d1; //寄存数据位最高位
default:;
endcase
end
else
rxdata <= rxdata;
else
rxdata <= 8'd0;
end
- 输出数据
always @(posedge clk or negedge rst) begin
if (!rst) begin
uart_data <= 8'd0;
uart_done <= 1'b0;
end
else if(rx_cnt == 4'd9) begin //接收数据计数器计数到停止位时
uart_data <= rxdata; //寄存输出接收到的数据
uart_done <= 1'b1; //并将接收完成标志位拉高
end
else begin
uart_data <= 8'd0;
uart_done <= 1'b0;
end
end
这次没有建立激励文件,因为我仿真了一下,效果并不是很好,我感觉放出来有点奇怪,所以……激励文件就不放了。uart更适合系统的仿真。