三、串口通讯
本章节将会介绍串口通讯内容,串口通讯是常见通讯接口之一,与之还有的是IIC通讯、SPI等低速通讯接口,其分为串口接收和串口发送两部分,本章节将制作一个通用时钟与波特率的串口通讯模块,并制作寄存器手册通过串口进行收发,框架流程如下:
通用时钟与波特率模块主要是通过parameter的方式直接计算出来,通过计数器进行波特率时钟波形的产生,提供两种输出方式,分别是波特率计数最大值和波特率时钟,其代码如下:
module uart_bps #
(
parameter DWID = 32 ,
parameter FREQ = 125000000 ,
parameter UBPS = 115200
)
(
input clk_sys ,
input rst_sys ,
//
output reg bps_clk = 0 ,
output [DWID-1:0] bps_max
);
// ********************************************************
// localparam
// ********************************************************
localparam CLK_PERIOD = 1000_000_000 / FREQ ;
localparam BPS_PERIOD = 1000_000_000 / UBPS ;
localparam BPS_CNT = BPS_PERIOD/CLK_PERIOD ;
// ********************************************************
// signal
// ********************************************************
reg [31:0] cnt_bps = 0 ;
// ********************************************************
// process
// ********************************************************
assign bps_cnt = CLK_PERIOD / BPS_PERIOD ;
always @ ( posedge clk_sys )
begin
if( rst_sys )
cnt_bps <= 0 ;
else if( cnt_bps == BPS_CNT - 1 )
cnt_bps <= 0 ;
else
cnt_bps <= cnt_bps + 1 ;
end
always @ ( posedge clk_sys )
begin
if( rst_sys )
bps_clk <= 0 ;
else if( cnt_bps == BPS_CNT - 1 )
bps_clk <= ~bps_clk ;
else ;
end
assign bps_max = BPS_CNT ;
endmodule
串口通讯主要分为串口接收和串口发送,串口发送波形如下图1,而串口接收波形如下图2,为什么串口接收波形需要移至数据的中心或者尾端?在教材中其实有讲解,这是因为防止串口抖动导致的亚稳态状态,在串口接收中采用16倍采样,而发送时FPGA自己组装发出,所以不必进行16倍采样发出。
对于串口发送与串口接收而言,个人认为串口接收会理解较复杂,且教材采用计数器+case语句方式进行设计,会较为逻辑不连贯,故本章节依据自己理解设计代码,采用中心采样方式已避免绝大多数的亚稳态,串口接收模块如下:
module uart_recv #
(
parameter DWID = 32
)
(
input clk_sys ,
input rst_sys ,
input [DWID-1:0] bps_max ,
input uart_rxd , //串行数据输入
output reg [7:0] recv_data , //数据接收完成
output reg recv_done //数据接收结束
);
// ********************************************************
// signal
// ********************************************************
reg uart_rxd_nedge = 1'b0 ;
reg uart_rxd_reg = 1'b0 ;
reg uart_rxd_ena = 1'b0 ;
reg [7:0] data_reg = 8'b0 ; //接收数据寄存器
reg [3:0] data_bit = 4'd0 ;
reg [DWID-1:0] bps_cnt ; //分频计数
// ********************************************************
// process
// ********************************************************
always @ ( posedge clk_sys )
begin
uart_rxd_reg <= uart_rxd; // 起始条件判断
uart_rxd_nedge <= ( uart_rxd_reg && uart_rxd == 1'b0 ) ? 1'b1 : 1'b0;
end
always @( posedge clk_sys )
begin
if( rst_sys )
uart_rxd_ena <= 1'b0 ;
else if( uart_rxd_nedge )//接收开始
uart_rxd_ena <= 1'b1 ;
else if( data_bit == 4'd0 && bps_cnt == bps_max/2 && uart_rxd == 1'b1 )
uart_rxd_ena <= 1'b0 ;//防止低电平毛刺被当作起始位,采用中心采样
else if( recv_done )
uart_rxd_ena <= 1'b0 ;
end
always @( posedge clk_sys )
begin
if( rst_sys || !uart_rxd_ena)
bps_cnt <= 1;
else if( uart_rxd_ena )
begin
if( bps_cnt == bps_max )
bps_cnt <= 1;
else
bps_cnt <= bps_cnt + 1;
end
end
always @ ( posedge clk_sys ) //位计数器,对应当前接收位数
begin
if( rst_sys )
data_bit <= 4'd0;
else if( bps_cnt == bps_max ) // 表示一个数据计数完成
begin
if(data_bit == 4'd9)
data_bit <= 4'd0;
else
data_bit <= data_bit + 4'd1;
end
end
always @ ( * ) // 串行接收数据(从低位接收,采样中间值)
begin
if( rst_sys || !uart_rxd_ena )
data_reg <= 8'b0 ;
else if( bps_cnt == bps_max/2 ) //接收采样自rxd每位中间值
begin
case(data_bit)
1: data_reg[0] <= uart_rxd ;
2: data_reg[1] <= uart_rxd ;
3: data_reg[2] <= uart_rxd ;
4: data_reg[3] <= uart_rxd ;
5: data_reg[4] <= uart_rxd ;
6: data_reg[5] <= uart_rxd ;
7: data_reg[6] <= uart_rxd ;
8: data_reg[7] <= uart_rxd ;
default: data_reg <= data_reg;
endcase
end
end
always @ ( * )
begin
if( rst_sys )
recv_data <= 8'b0;
else if( recv_done )
recv_data <= data_reg;
end
always @ ( * )
begin
if( rst_sys )
recv_done <= 1'b0;
else if( data_bit == 9 && bps_cnt == bps_max )
recv_done <= 1'b1;
else
recv_done <= 1'b0;
end
endmodule
串口发送模块如下:
module uart_send #
(
parameter DWID = 32
)
(
input clk_sys ,
input rst_sys ,
//
input send_dena , //开始发送标志信号
input [7:0] send_data , //待发送数据
//
input [DWID-1:0] bps_max , //设置波特率
output reg uart_txd , //串行数据输出
output reg send_done //发送结束标志信号
);
// ********************************************************
// signal
// ********************************************************
reg send_star = 1'b0 ; //发送使能信号
reg [7:0] data_reg = 8'b0 ; //待发送数据寄存器
reg [3:0] data_bit = 4'd0 ; //计数当前输出位数
reg [DWID-1:0] bps_cnt = 1 ; //分频计数
// ********************************************************
// process
// ********************************************************
always @ ( posedge clk_sys )
begin
if( rst_sys )
data_reg <= 8'b0;
else if(send_dena)
data_reg <= send_data;
else ;
end
always @ ( posedge clk_sys )
begin
if( rst_sys )
send_star <= 1'b0 ;
else if( send_dena )
send_star <= 1'b1 ;
else if( send_done )
send_star <= 1'b0 ;
else ;
end
always @ ( posedge clk_sys )
begin
if( rst_sys || !send_star ) //复位或未使能
bps_cnt <= 1;
else if( send_star )
begin
if( bps_cnt == bps_max )
bps_cnt <= 1;
else
bps_cnt <= bps_cnt + 1;
end
end
always @ ( posedge clk_sys )
begin
if( rst_sys )
data_bit <= 4'd0; //1-10为有效状态
else if( bps_cnt == bps_max )
begin
if(data_bit == 4'd9)
data_bit <= 4'd0;
else
data_bit <= data_bit + 4'd1;
end
end
always @ ( * )
begin
if( rst_sys || !send_star )
uart_txd <= 1'b1;
else begin
case(data_bit)
0: uart_txd <= 1'b0;
1: uart_txd <= data_reg[0];
2: uart_txd <= data_reg[1];
3: uart_txd <= data_reg[2];
4: uart_txd <= data_reg[3];
5: uart_txd <= data_reg[4];
6: uart_txd <= data_reg[5];
7: uart_txd <= data_reg[6];
8: uart_txd <= data_reg[7];
9: uart_txd <= 1'b1;
default: uart_txd <= 1'b1;
endcase
end
end
always @ ( * )
begin
if( rst_sys )
send_done <= 1'b0;
else if( data_bit == 9 && bps_cnt == bps_max )
send_done <= 1'b1;
else
send_done <= 1'b0;
end
endmodule
两个代码的实现流程均相差不多,先检测下降沿,通过uart_bps模块计算出波特率周期,通过计数器计算达到周期值时或者半周期时将数据发出或者采样,这里的复位后的cnt值为1是因为为0的话,bps_cnt 得等于 bps_mux - 1 ,尽量不在条件语句上进行逻辑运算减小组合逻辑。
验证功能代码如下,可在仿真中产生需要发送的数据送入串口发送模块,串口发送的数据经过串口接收模块提取,从而可以看出发送和接收数值是都一致,一致就说明代码正确:
localparam CLK_PERIOD_BPS = 1_000_000_000/115200/CLK_PERIOD_125M ;
always #(CLK_PERIOD_BPS/2) bps_clk = ~bps_clk ;
// ****************************************************************************
// uart
// ****************************************************************************
always @ ( posedge bps_clk )
begin
if( rst_sys )
cnt_uart <= 0;
else if( reg_uart_txd_dena )
cnt_uart <= 0 ;
else
cnt_uart <= cnt_uart + 1 ;
end
always @ ( posedge bps_clk )
begin
if( rst_sys )
reg_uart_txd_dena <= 0 ;
else if( &cnt_uart )
reg_uart_txd_dena <= 1 ;
else
reg_uart_txd_dena <= 0 ;
end
always @ ( posedge bps_clk )
begin
if( rst_sys )
reg_uart_txd_data <= 8'h0 ;
else if( &cnt_uart )
reg_uart_txd_data <= reg_uart_txd_data + 1 ;
else ;
end
将上诉reg_uart_txd_dena和reg_uart_txd_data 送入uart_send.v中即可,本工程采用工程仿真,故仿真搭建较多,等工程完毕后会上传完整仿真。
本工程的晶振时钟是50M,但一般速率不会取的那么慢,特别是千兆以太网,故在本工程会通过MMCM产生125M时钟,并且通过1s时钟统计,计算出统计值后通过串口发出,MMCM的使用方式以及时钟统计模块均在本章不进行讲解,具体代码待工程完毕后上传github,寄存器代码如下:
module acx720_register #
(
parameter DWID = 32
)
(
input clk_sys ,
input rst_sys ,
// ctrl
input uart_rxd ,
output uart_txd ,
// register
input [31:0] cnt_125m ,
input [31:0] cnt_50m
);
// ********************************************************
// signal
// ********************************************************
wire [7:0] data_byte ;
wire data_dena ;
reg addr_dena = 0 ;
reg [7:0] tx_data ;
wire [31:0] bps_max ;
// ********************************************************
// process
// ********************************************************
always @ ( posedge clk_sys )
begin
if( data_dena )
addr_dena <= 1 ;
else
addr_dena <= 0 ;
end
always @ ( posedge clk_sys )
begin
case ( data_byte )
//read
8'h00 : tx_data <= cnt_125m[7:0] ;
8'h01 : tx_data <= cnt_125m[15:8] ;
8'h02 : tx_data <= cnt_125m[23:16] ;
8'h03 : tx_data <= cnt_125m[31:24] ;
8'h04 : tx_data <= cnt_50m[7:0] ;
8'h05 : tx_data <= cnt_50m[15:8] ;
8'h06 : tx_data <= cnt_50m[23:16] ;
8'h07 : tx_data <= cnt_50m[31:24] ;
default: tx_data <= 8'hff ;
endcase
end
// ********************************************************
// UART
// ********************************************************
uart_bps #
(
.DWID ( DWID ),
.FREQ ( 125000000 ),
.UBPS ( 115200 )
)u_uart_bps
(
.clk_sys ( clk_sys ),
.rst_sys ( rst_sys ),
//
.bps_clk ( bps_clk ),
.bps_max ( bps_max )
);
uart_recv #
(
.DWID ( DWID )
)u_uart_recv
(
.clk_sys ( clk_sys ),
.rst_sys ( rst_sys ),
.bps_max ( bps_max ),
.uart_rxd ( uart_rxd ), //串行数据输入
.recv_data ( data_byte ), //数据接收完成
.recv_done ( data_dena ) //数据接收结束
);
uart_send #
(
.DWID ( DWID )
) u_uart_send
(
.clk_sys ( clk_sys ),
.rst_sys ( rst_sys ),
.bps_max ( bps_max ),
//
.send_dena ( addr_dena ),
.send_data ( tx_data ),
.send_done ( ),
.uart_txd ( uart_txd )
);
endmodule
上板验证结果如下,通过分别输入03、02、01、00可得出数值分别为07、73、59、3F;拼接转换城10进制为124999999,寄存器工作就完成了。