串口发送与接收模块设计代码分析
1.1tx_bps_gen
tx_bps_gen为发送波特率生成模块,每当有byte_en信号到来时,即开始产生发送一个完整字节的数据需要的完整波特率时钟信号。
本设计,波特率支持9600bps到921600bps。例如,需要产生的波特率时钟为9600bps,即波特率时钟频率为9600hz,周期为104.17us。生成9600hz波特率时钟的核心思想就是对系统时钟进行计数,这里设定系统时钟为50mhz,则一个时钟的周期为20ns,我们只需要对系统时钟计数5208次,每计数5208次产生一个时钟周期的高电平脉冲,即可实现生成9600hz波特率时钟的功能。相应代码如下所示:
018 parameter system_clk = 50_000_000; /*输入时钟频率设定,默认50m*/
019
020 /*根据输入时钟频率计算生成各波特率时分频计数器的计数最大值*/
021 localparam bps9600 = system_clk/9600 - 1;
022 localparam bps19200 = system_clk/19200 - 1;
023 localparam bps38400 = system_clk/38400 - 1;
024 localparam bps57600 = system_clk/57600 - 1;
025 localparam bps115200 = system_clk/115200 - 1;
026 localparam bps230400 = system_clk/230400 - 1;
027 localparam bps460800 = system_clk/460800 - 1;
028 localparam bps921600 = system_clk/921600 - 1;
029
030 reg [31:0]bps_para;/*波特率分频计数器的计数最大值*/
031
032 always@(posedge clk or negedge rst_n)
033 if(!rst_n)begin
034 bps_para 《= bps9600;/*复位时波特率默认为9600bps*/
035 end
036 else begin
037 case(baud_set)/*根据波特率控制信号选择不同的波特率计数器计数最大值*/
038 3‘d0: bps_para 《= bps9600;
039 3’d1: bps_para 《= bps19200;
040 3‘d2: bps_para 《= bps38400;
041 3’d3: bps_para 《= bps57600;
042 3‘d4: bps_para 《= bps115200;
043 3’d5: bps_para 《= bps230400;
044 3‘d6: bps_para 《= bps460800;
045 3’d7: bps_para 《= bps921600;
046 default: bps_para 《= bps9600;
047 endcase
048 end
049
050 //=========================================================
051 reg[12:0]count;
052
053 reg n_state;
054 localparam idel_1 = 1‘b0,
055 send = 1’b1;
056
057 reg bps_en;
058
059 /*-------波特率时钟生成控制逻辑--------------*/
060 always@(posedge clk or negedge rst_n)
061 if(!rst_n)begin
062 bps_en 《= 1‘b0;
063 n_state 《= idel_1;
064 end
065 else begin
066 case(n_state)
067 idel_1:
068 if(byte_en)begin/*检测到字节发送使能信号,则启动波特率生成进程,同时进入发送状态*/
069 bps_en 《= 1’b1;
070 n_state 《= send;
071 end
072 else begin
073 n_state 《= idel_1;
074 bps_en 《= 1‘b0;
075 end
076 send:
077 if(tx_done == 1)begin/*发送完成,关闭波特率生成进程,回到空闲状态*/
078 bps_en 《= 1’b0;
079 n_state 《= idel_1;
080 end
081 else begin
082 n_state 《= send;
083 bps_en 《= 1‘b1;
084 end
085 default:n_state 《= idel_1;
086 endcase
087 end
088
089 /*-------波特率时钟生成定时器--------------*/
090 always@(posedge clk or negedge rst_n)
091 if(!rst_n)
092 count 《= 13’d0;
093 else if(bps_en == 1‘b0)
094 count 《= 13’d0;
095 else begin
096 if(count == bps_para)
097 count 《= 13‘d0;
098 else
099 count 《= count + 1’b1;
100 end
101
102 /*输出数据接收采样时钟*/
103 //-----------------------------------------------
104 always @(posedge clk or negedge rst_n)
105 if(!rst_n)
106 bps_clk 《= 1‘b0;
107 else if(count== 1)
108 bps_clk 《= 1’b1;
109 else
110 bps_clk 《= 1‘b0;
第18行“parameter system_clk = 50_000_000;”,这里用一个全局参数定义了系统时钟,暂时设定为50m,可根据实际使用的板卡上的工作时钟进行修改。
所谓波特率生成,就是用一个定时器来定时,产生频率与对应波特率时钟频率相同的时钟信号。例如,我们使用波特率为115200bps,则我们需要产生一个频率为115200hz的时钟信号。那么如何产生这样一个115200hz的时钟信号呢?这里,我们首先将115200hz时钟信号的周期计算出来,1秒钟为1000_000_000ns,因此波特率时钟的周期tb= 1000000000/115200 =8680.6ns,即115200信号的一个周期为8680.6ns,那么,我们只需要设定我们的定时器定时时间为8680.6ns,每当定时时间到,产生一个系统时钟周期长度的高脉冲信号即可。系统时钟频率为50mhz,即周期为20ns,那么,我们只需要计数8680/20个系统时钟,就可获得8680ns的定时,即bps115200=tb/tclk - 1=tb*fclk - 1=fclk/115200-1。相应的,其它波特率定时值的计算与此类似,这里小梅哥就不再一一分析。20行至28行为波特率定时器定时值的计算部分。
为了能够通过外部控制波特率,设计中使用了一个3位的波特率选择端口:baud_set。通过给此端口不同的值,就能选择不同的波特率,此端口控制不同波特率的原理很简单,就是一个多路选择器,第32行至第48行即为此多路选择器的控制代码, baud_set的值与各波特率的对应关系如下:
000 :9600bps;
001 :19200bps;
010 :38400bps;
011 :57600bps;
100 :115200bps;
101 :230400bps;
110 :460800bps;
111 :921600bps;
1.2uart_byte_tx
uart_byte_tx为字节发送模块,该模块在波特率时钟的节拍下,依照uart通信协议发送一个完整的字节的数据。当一个字节发送完毕后,tx_done产生一个高脉冲信号,以告知其它模块或逻辑一个字节的数据已经传输完成,可以开始下一个字节的发送了。其发送一个字节数据的实现代码如下:
33 /*计数波特率时钟,11个波特率时钟为一次完整的数据发送过程*/
34 always@(posedge clk or negedge rst_n)
35 if(!rst_n)
36 bps_clk_cnt 《= 4’b0;
37 else if(bps_clk_cnt == 4‘d11)
38 bps_clk_cnt 《= 4’b0;
39 else if(bps_clk)
40 bps_clk_cnt 《= bps_clk_cnt + 1‘b1;
41 else
42 bps_clk_cnt 《= bps_clk_cnt;
43
44 /*生成数据发送完成标志信号*/
45 always@(posedge clk or negedge rst_n)
46 if(!rst_n)
47 tx_done 《= 1’b0;
48 else if(bps_clk_cnt == 4‘d11)
49 tx_done 《= 1’b1;
50 else
51 tx_done 《= 1‘b0;
52
53 /*在开始发送起始位的时候就读取并寄存data_byte,以免data_byte变化导致数据的丢失*/
54 always@(posedge clk or negedge rst_n)
55 if(!rst_n)
56 data = 8’d0;
57 else if(bps_clk & bps_clk_cnt == 4‘d1)
58 data 《= data_byte;
59 else
60 data 《= data;
61
62 /*发送数据序列机*/
63 always@(posedge clk or negedge rst_n)
64 if(!rst_n)
65 rs232_tx 《= 1’b1;
66 else begin
67 case(bps_clk_cnt)
68 4‘d1: rs232_tx 《= 1’b0;
69 4‘d2: rs232_tx 《= data[0];
70 4’d3: rs232_tx 《= data[1];
71 4‘d4: rs232_tx 《= data[2];
72 4’d5: rs232_tx 《= data[3];
73 4‘d6: rs232_tx 《= data[4];
74 4’d7: rs232_tx 《= data[5];
75 4‘d8: rs232_tx 《= data[6];
76 4’d9: rs232_tx 《= data[7];
77 4‘d10: rs232_tx 《= 1’b1;
78 default:rs232_tx 《= 1‘b1;
79 endcase
80 end
在uart协议中,一个完整的字节包括一位起始位、8位数据位、一位停止位即总共十位数据,那么,要想完整的实现这十位数据的发送,就需要11个波特率时钟脉冲,如下所示:
bps_clk信号的第一个上升沿到来时,字节发送模块开始发送起始位,接下来的2到9个上升沿,发送8个数据位,第10个上升沿到第11个上升沿为停止位的发送。
单个串口接收模块中实现串口数据接收的主要代码如下所示:
025 always @ (posedge clk or negedge rst_n)
026 if(!rst_n) begin
027 rs232_rx0 《= 1’b0;
028 rs232_rx1 《= 1‘b0;
029 rs232_rx2 《= 1’b0;
030 rs232_rx3 《= 1‘b0;
031 end
032 else begin
033 rs232_rx0 《= rs232_rx;
034 rs232_rx1 《= rs232_rx0;
035 rs232_rx2 《= rs232_rx1;
036 rs232_rx3 《= rs232_rx2;
037 end
038
039 wire neg_rs232_rx= rs232_rx3 & rs232_rx2 & ~rs232_rx1 & ~rs232_rx0;
040
041 assign byte_en = neg_rs232_rx;
042
043 /*----------计数采样时钟--------------*/
044 /*9倍波特率采样时钟,故一个完整的接收过程有90个波特率时钟*/
045 reg[6:0]sample_clk_cnt;
046 always @ (posedge clk or negedge rst_n)
047 if(!rst_n)
048 sample_clk_cnt 《= 7’d0;
049 else if(sample_clk)begin
050 if(sample_clk_cnt == 7‘d89)
051 sample_clk_cnt 《= 7’d0;
052 else
053 sample_clk_cnt 《= sample_clk_cnt + 1‘b1;
054 end
055 else
056 sample_clk_cnt 《= sample_clk_cnt;
057
058 reg [1:0]start_bit; /*起始位,这里虽然定义,但并未使用该位来判断接收数据的正确性,即默认接收都是成功的*/
059 reg [1:0]stop_bit; /*停止位,这里虽然定义,但并未使用该位来判断接收数据的正确性,即默认接收都是成功的*/
060 reg [1:0] data_tmp[7:0];/*此部分较为复杂,请参看说明文档中相关解释*/
061
062 always @ (posedge clk or negedge rst_n)
063 if(!rst_n)begin
064 data_tmp[0] 《= 2’d0;
065 data_tmp[1] 《= 2‘d0;
066 data_tmp[2] 《= 2’d0;
067 data_tmp[3] 《= 2‘d0;
068 data_tmp[4] 《= 2’d0;
069 data_tmp[5] 《= 2‘d0;
070 data_tmp[6] 《= 2’d0;
071 data_tmp[7] 《= 2‘d0;
072 start_bit 《= 2’d0;
073 stop_bit 《= 2‘d0;
074 end
075 else if(sample_clk)begin
076 case(sample_clk_cnt)
077 7’d0:
078 begin
079 data_tmp[0] 《= 2‘d0;
080 data_tmp[1] 《= 2’d0;
081 data_tmp[2] 《= 2‘d0;
082 data_tmp[3] 《= 2’d0;
083 data_tmp[4] 《= 2‘d0;
084 data_tmp[5] 《= 2’d0;
085 data_tmp[6] 《= 2‘d0;
086 data_tmp[7] 《= 2’d0;
087 start_bit 《= 2‘d0;
088 stop_bit 《= 2’d0;
089 end
090 7‘d3,7’d4,7‘d5: start_bit 《= start_bit + rs232_rx;
091 7’d12,7‘d13,7’d14:data_tmp[0] 《= data_tmp[0] + rs232_rx;
092 7‘d21,7’d22,7‘d23:data_tmp[1] 《= data_tmp[1] + rs232_rx;
093 7’d30,7‘d31,7’d32:data_tmp[2] 《= data_tmp[2] + rs232_rx;
094 7‘d39,7’d40,7‘d41:data_tmp[3] 《= data_tmp[3] + rs232_rx;
095 7’d48,7‘d49,7’d50:data_tmp[4] 《= data_tmp[4] + rs232_rx;
096 7‘d57,7’d58,7‘d59:data_tmp[5] 《= data_tmp[5] + rs232_rx;
097 7’d66,7‘d67,7’d68:data_tmp[6] 《= data_tmp[6] + rs232_rx;
098 7‘d75,7’d76,7‘d77:data_tmp[7] 《= data_tmp[7] + rs232_rx;
099 7’d84,7‘d85,7’d86:stop_bit 《= stop_bit + rs232_rx;
100 default:;
101 endcase
102 end
103 else ;
根据串口发送协议,一个字节的数据传输是以一个波特率周期的低电平作为起始位的,因此,成功接收uart串口数据的核心就是准确检测起始位。由于外部串口发送过来的数据与接收系统不在同一个时钟域,因此不能直接使用该信号的下降沿来作为检测标志,我们需要在fpga中,采用专用的边沿检测电路来实现,第25行至37行通过四个移位寄存器,存储连续四个时钟上升沿时外部发送数据线的状态,第39行通过比较前两个时钟时数据线的状态与后两个时钟时数据线的状态,来得到该数据线的准确下降沿,以此保证起始位的准确检测。
在简单的串口接收中,我们通常选取一位数据的中间时刻进行采样,因为此时数据最稳定,但是在工业环境中,存在着各种干扰,在干扰存在的情况下,如果采用传统的中间时刻采样一次的方式,采样结果就有可能受到干扰而出错。为了滤除这种干扰,这里采用多次采样求概率的方式。如下图,将一位数据平均分成9个时间段,对位于中间的三个时间段进行采样。然后对三个采样结果进行统计判断,如果某种电平状态在三次采样结果中占到了两次及以上,则可以判定此电平状态即为正确的数据电平。例如4、5、6时刻采样结果分别为1、1、0,那么就取此位解码结果为1,否则,若三次采样结果为0、1、0,则解码结果就为0。
因为采样一位需要9个时钟上升沿,因此,采样一个完整的数据需要10*9,即90个时钟上升沿,这里,采样时钟为波特率时钟的9倍。产生采样时钟的部分代码如下所示:
089 /*-------波特率时钟生成定时器--------------*/
090 always@(posedge clk or negedge rst_n)
091 if(!rst_n)
092 count 《= 10‘d0;
093 else if(bps_en == 1’b0)
094 count 《= 10‘d0;
095 else begin
096 if(count == bps_para)
097 count 《= 10’d0;
098 else
099 count 《= count + 1‘b1;
100 end
101
102 //=====================================================
103 /*输出数据接收采样时钟*/
104 always @(posedge clk or negedge rst_n)
105 if(!rst_n)
106 sample_clk 《= 1’b0;
107 else if(count== 1)
108 sample_clk 《= 1‘b1;
109 else
110 sample_clk 《= 1’b0;
这里,bps_para的计算原理和前面tx_bps_gen模块中的bps_para的计算原理一致,不过这里,因为采样时钟为波特率时钟的9倍,所以,bps_para为tx_bps_gen模块中的bps_para的1/9。计算bps_para的相关代码如下:
018 parameter system_clk = 50_000_000; /*输入时钟频率设定,默认50m*/
019
020 /*根据输入时钟频率计算生成各波特率时分频计数器的计数最大值*/
021 localparam bps9600 = system_clk/9600/9 - 1;
022 localparam bps19200 = system_clk/19200/9 - 1;
023 localparam bps38400 = system_clk/38400/9 - 1;
024 localparam bps57600 = system_clk/57600/9 - 1;
025 localparam bps115200 = system_clk/115200/9 - 1;
026 localparam bps230400 = system_clk/230400/9 - 1;
027 localparam bps460800 = system_clk/460800/9 - 1;
028 localparam bps921600 = system_clk/921600/9 - 1;
029
030 reg [31:0]bps_para;/*波特率分频计数器的计数最大值*/
031
032 always@(posedge clk or negedge rst_n)
033 if(!rst_n)begin
034 bps_para 《= bps9600; /*复位时波特率默认为9600bps*/
035 end
036 else begin
037 case(baud_set) /*根据波特率控制信号选择不同的波特率计数器计数最大值*/
038 3‘d0: bps_para 《= bps9600;
039 3’d1: bps_para 《= bps19200;
040 3‘d2: bps_para 《= bps38400;
041 3’d3: bps_para 《= bps57600;
042 3‘d4: bps_para 《= bps115200;
043 3’d5: bps_para 《= bps230400;
044 3‘d6: bps_para 《= bps460800;
045 3’d7: bps_para 《= bps921600;
046 default: bps_para 《= bps9600;/*异常情况,恢复到9600的波特率*/
047 endcase
048 end
工业互联网的脉搏怎样才可以摸得准
数字绝缘电阻表的使用方法
单片机与MMC卡的接口
苹果发布iOS12.1.4更新 修复FaceTime群聊漏洞
PyTorch开源深度学习框架简介
分析一下串口发送与接收模块的设计代码
台积电与三星下重金,ASML 2019年EUV产量订单有望全满
【有奖直播】Molex智能手表连接器产品方案及应用介绍
陪伴机器人未来三年内会实现直播视频技术
4G路由器和普通路由器的区别
显同电子分享舞台LED租赁屏需注意的问题
泰克示波器常见故障及原因分析
乘用车电线束设计验证方法
在Virtuoso中认识PMOS管和NMOS管
半导体前端工艺:金属布线—为半导体注入生命的连接
通过5张图了解K8S的Service网络
氮化镓技术的壁垒是什么 氮化镓晶体管工作原理
小米MAX评测:大屏也有颜值 视频阅读的最佳利器
SGS授予开阳电子AEC-Q100认证证书,助力车规器件可靠性再升级
去黑头仪怎么样?要选用具有医疗标准的品牌