1. 概念
2.协议
串口的协议如上图所示,分为如下几个状态:空闲,其实,发送数据,发送校验位(可选),发送停止位;在空闲状态下信号为高;起始位标志着数据开始发送,FPGA检测到(发送)一个低电平;数据位为协议传输的内容;奇偶校验位是可选的;奇校验的功能是判断数据为中1的个数,保证连上校验位中1的个数是奇数个;偶校验就是保证1的个数为偶数个;停止位就是将信号拉至高电平。
串口是一种慢速协议,常用的波特率有:9600,19200,38400,57600,115200等。波特率的概念就是每秒发送的字节数(bit/s);这里举一个例子,波特率为9600,也就是9600bit/s,也就是一秒发送1200byte(1200个字节);
3.模块划分
在常见的开发板教程中,会存在同时发送多个数据出现乱码的情况,这里会作为一个小问题点解决这个问题。模块整体划分如下:晶振的50mhz时钟先进入pll产生一个50mhz的更优质的时钟。随经过复位产生模块与uart时钟匹配的复位信号,将该时钟与复位信号一起传入uart_rx和uart_tx模块。
uart_rx模块解析传入的数据后将其交给用户,同时产生一个标志该数据可用的valid信号;用户也可产生一个数据经由tx模块发送到串口线上;这里采用类似于AXI写法的风格来编写代码。
4.部分代码解析
驱动模块顶层,上述模块划分部分的接口可以从下面的代码段中看清楚;为了输出给用户使用,这里把串口的时钟和复位一起输出出去;此外,顶层驱动代码就是按照上述框图例化;
(
input i_clk ,
input i_rst ,
input i_uart_rx ,
output o_uart_tx ,
input [P_UART_DATA_WIDTH - 1 : 0] i_user_tx_data ,
input i_user_tx_valid ,
output o_user_tx_ready ,
output [P_UART_DATA_WIDTH - 1 : 0] o_user_rx_data ,
output o_user_rx_valid ,
output o_user_clk ,
output o_user_rst
);
时钟分频模块,在上一篇博客中已经提过,这里不再提及;
复位产生模块,由于不同模块之间产生的时钟不同。例如50mhz的时钟持续一个周期的复位信号在9600波特率下极有可能检测不到,这里做了一个复位产生模块;也就是直接在所在时钟域按照输入位数打拍;uart_tx模块,该模块是串口的发送模块,用户产生一个数据,在tx模块准备好了之后发送出去。ready信号表示tx模块当前是否准备完毕,在数据开始发送时拉低,在计数器计数到数据发送或者校验位结束时拉高;比较麻烦的点是在于校验位的计算;如果事先知道发送的数据位数,这里可以直接按位异或即可得到结果,然而这里数据位数时使用参数化的,因此需要时序逻辑解决;具体解析在注释后边;
always@(posedge i_clk,posedge i_rst)
begin
if(i_rst)
ro_user_tx_ready <= 'd1;
else if(r_cnt == 3 + P_UART_DATA_WIDTH - 3 && P_UART_CHECK == 0)
ro_user_tx_ready <= 'd1;
else if(r_cnt == 3 + P_UART_DATA_WIDTH - 2 && P_UART_CHECK > 0)
ro_user_tx_ready <= 'd1;
else if(w_tx_active)
ro_user_tx_ready <= 'd0;
else
ro_user_tx_ready <= ro_user_tx_ready;
end
always@(posedge i_clk,posedge i_rst)
begin
if(i_rst)
r_cnt <= 'd0;
else if(r_cnt == 3 + P_UART_DATA_WIDTH - 3 && P_UART_CHECK == 0)//起始使 + 数据使 + 校验使 + 停止使 - 停止使 - 校验使 - 1
r_cnt <= 'd0;
else if(r_cnt == 3 + P_UART_DATA_WIDTH - 2 && P_UART_CHECK > 0)
r_cnt <= 'd0;
else if(!ro_user_tx_ready)
r_cnt <= r_cnt + 1;
else
r_cnt <= r_cnt;
end
always@(posedge i_clk,posedge i_rst)
begin
if(i_rst)
r_tx_data <= 'd0;
else if(w_tx_active)
r_tx_data <= i_user_tx_data;
else if(!ro_user_tx_ready)
r_tx_data <= r_tx_data >> 1;//0101 0101 发鿁最后一位即叿
else
r_tx_data <= r_tx_data;
end
always@(posedge i_clk,posedge i_rst)
begin
if(i_rst)
ro_uart_tx <= 'd1;
else if(w_tx_active)//起始使
ro_uart_tx <= 'd0;
else if(r_cnt == 3 + P_UART_DATA_WIDTH - 3 && P_UART_CHECK > 0)
ro_uart_tx <= P_UART_CHECK == 1 ? ~r_tx_check : r_tx_check ;
else if(r_cnt >= 3 + P_UART_DATA_WIDTH - 3 && P_UART_CHECK == 0)//没有校验 ,直接停止位
ro_uart_tx <= 'd1;
else if(r_cnt >= 3 + P_UART_DATA_WIDTH - 2 && P_UART_CHECK > 0)//有校骿 ,晚丿拍直接停止位
ro_uart_tx <= 'd1;
else if(!ro_user_tx_ready)//数据使
ro_uart_tx <= r_tx_data[0];
else
ro_uart_tx <= 'd1;
end
always@(posedge i_clk,posedge i_rst)
begin
if(i_rst)
r_tx_check <= 'd0;
else if(r_cnt == 1 + P_UART_DATA_WIDTH - 1)//计数到数据位发鿁结板
r_tx_check <= 'd0;
else //uart_tx 在奇校验旿 = _ r_tx_check 偶校验时 = r_tx_check;
r_tx_check <= r_tx_check ^ r_tx_data[0];// 奇校骿 0000 0001 r_tx_check = 1 , uart_tx = 0 0000 0011 r_tx_check = 0 uart_tx=1
end
uart_rx模块,由于串口是从低位先发送,然后再发送高位的,这里使用移位的操作,也就是舍弃掉之前数据的最低位,然后将rx数据接到最高位,如下图所示;
always@(posedge i_clk,posedge i_rst)
begin
if(i_rst)
ro_user_rx_data <= 'd0;
else if(r_cnt >= 1 && r_cnt <= P_UART_DATA_WIDTH)
ro_user_rx_data <= {i_uart_rx,ro_user_rx_data[P_UART_DATA_WIDTH - 1 : 1]};
else
ro_user_rx_data <= ro_user_rx_data;
end
其他模块,在做数据回环实验的时候,连续发送数据时tx发送数据较慢,通常使用FIFO将rx的数据缓存,如下图所示;rx数据接收完成后以valid作为写使能写入fifo;当tx模块空闲且fifo不为空时,用户从fifo里面读出数据并通过tx发送;
uart_fifo uart_fifo_u0 (
.clk (wo_user_clk ) ,
.srst (wo_user_rst ) ,
.din (w_user_rx_data ) ,
.wr_en (w_user_rx_valid) ,
.rd_en (r_fifo_rden) ,
.dout (w_user_tx_data ) ,
.full () ,
.empty (w_user_empty)
);
5.仿真
时钟分频
产生复位
无校验位 tx
无校验rx
有校验位tx奇校验;
有校验位tx偶校验;
有校验rx;
6.计划
下周更新SPI协议。