很多东西看懂了和自己来做那是两回事。串口通讯的协议比较简单。接收端RX一旦检测到一个下降沿就开始接收数据,数据的位数,有无奇偶校验,都是可以自己设定的(帧格式),最后是停止位。TX负责发送数据。
这个程序主要包括三部分:发送模块,接受模块和波特率产生模块。
在接受模块:
rs232_rx是数据接受端直接与要通讯的机器的TX端相连(中间有时当然需要做电平转换)。
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
rs232_rx0 <= 1'b0;
rs232_rx1 <= 1'b0;
rs232_rx2 <= 1'b0;
rs232_rx3 <= 1'b0;
end
else begin
rs232_rx0 <= rs232_rx;
rs232_rx1 <= rs232_rx0;
rs232_rx2 <= rs232_rx1;
rs232_rx3 <= rs232_rx2;
end
endassign neg_rs232_rx = rs232_rx3 & rs232_rx2 & (~rs232_rx1) & (~rs232_rx0);
这是在检测起始信号,其实就是一个下降沿检测电路,定义这么多寄存器的目的在于避免误操作,在某一时刻rs232_rx上的电平变成0时,rs232_rx0中的数据会立即变成0,rs232_rx1中的数据会过一个时钟周期之后才变成0,rs232_rx2中的数据会过两个时钟周期之后才变成0,rs232_rx3中的数据会过三个时钟周期之后才变成0。也就是说如果是一个干扰,那它的低电平时间应该很短,在rs232_rx2还没有变成低电平时rs232_rx0又变成了高电平,最后运算的时候取反则下降沿标志位neg_rs232_rx还是不会置1。通俗的讲以下降沿的那时刻为基准则rs232_rx3和rs232_rx2是下降沿前2个系统时钟周期内rs232_rx上的电平信号,rs232_rx1和rs232_rx0是下降沿后2个系统时钟周期内rs232_rx上的电平信号,如果系统时钟是50MHz那么两个系统时钟周期则是40ns,只有下降沿后低电平维持至少40ns才会被认为是起始信号。
当检测到下降沿后,rx_int信号和bps_start信号置一,rx_int一边输出到发送数据模块,一边作为内部信号用作开始发送数据的信号。bps_start 是输出给波特率产生模块告诉它现在开始启动计数器开始计数。先说一下波特率产生器模块,这个模块其实比较简单,类似一个采样时钟产生器。
它只有一个输入信号bps_start一个输出信号clk_bps。bps_start 用于启动里面的计数器,里面需要注意的是两个参数的设置假设我们现在用的是9600的波特率即一秒钟传输9600bit数据,传输一位的时间是1/9600s=104.16us,假设50MHz(20ns)的系统时钟,那么计104.16us需要104.16/0.02=5208个数。这个模块完成两个逻辑当bps_start启动信号来了以后马上开始计数,当计数计到一半时发出一个clk_bps信号即采样信号,告诉接受模块或者是发送模块(接受模块在这一时刻读取RX上的数据,发送模块在这一时刻把数据发送到TX),当启动信号有的时候它计数到了以后就会清零。
else if(rx_int) begin //准备接受数据
if(clk_bps) begin //当检测脉冲来的时候,来一次收一次数据
num <= num +1'b1;
case(num)
4'd1:rx_temp_data[0] <= rs232_rx; //锁存第一位数据
4'd2:rx_temp_data[1] <= rs232_rx;
4'd3:rx_temp_data[2] <= rs232_rx;
4'd4:rx_temp_data[3] <= rs232_rx;
4'd5:rx_temp_data[4] <= rs232_rx;
4'd6:rx_temp_data[5] <= rs232_rx;
4'd7:rx_temp_data[6] <= rs232_rx;
4'd8:rx_temp_data[7] <= rs232_rx;
default: ;
endcase
end
else if(num == 4'd12) begin //一帧数据节后完成
num <= 4'd0; //计数器清零
rx_data_r <= rx_temp_data; //把接受的数据给数据接受寄存器保存这是接受模块中数据接受部分的代码,当rx_int 有效时就开始检测波特率发生器的采样信号来一个clk_bps 就把num加一。注意这里的num值是从1开始的,也就是说第一位起始位不读,从数据位开始读。在num == 4'd12的时候,num清零,并且在还会把rx_int 赋0,bps_start赋0。rx_int赋0表示一帧数据已经接收完成两个作用一是告诉发射模块你现在可以把我接受的数据发射出去,2是自己内部判断条件不成立不用接受数据,等待下一个起始位。bps_start赋0则是告诉波特率发生器现在不用工作。在此当中有两点不是很懂:1, num <= num +1'b1 ; case(num) 我觉得跟c不一样,它是并行的,来一个时钟条件满足num加一,但case(num)里面的值应该是没有加以前的值,同样的道理滞后一拍(这点未验证)。2, num的值为什么要到12后才停止,1个起始位,8个数据位,无奇偶校验,1个停止位都算上也才11位数据?等会再去查查。
最后一个模块是发送模块,
rx_int 是接受模块发出的信号,整个工程实现的功能就是上位机发一个数据这边接受然后把接受的数据再发回给上位机。当rx_int由1变为0时(也是下降沿检测),检测到了以后,就给中间的控制信号,(在这部分定义一个寄存器tx_en) neg_tx_int 下降沿标志位有效则tx_en 置1, bps_start置1,并且把接受模块的数据给发送模块。波特率发生器开始计数,下面的发送模块开始发送数据,大体过程和接受一样。当num==4'd12时,tx_en赋0,停止发送,bps_start赋0,波特率模块停止计数。
具体的发送数据部分判断tx_en为高时,来一个采样信号clk_bps,num加1,发相应的数据,还要注意他的起始位和结束位。
在整个工程中,波特率发生器其实只有一个,但是只是在顶层例化的时候把它例化了两遍,于是就有了连个,一个跟发送模块相连,一个跟接受模块,它们是单独的,只是在例化的时候把引脚重新命一个名字,相连的模块名字一样就好。
把别人的程序抄一遍,搞懂是最基础的,下一步就是在此基础上发展完善。准备把他的功能完善,写成一个通用模块,就那这个工程开刀,把仿真,时序分析,时序约束整个设计流程完成。Go on!
用户380721 2011-5-13 10:24