赞
踩
首先我们得直到串口是怎样进行通信的。
我们可以看到,串口通讯的数据格式是由一位起始位、七个数据位(其中最后一位数据位可以作为检验位来使用。)、一位停止位,在空闲时刻为高电平,当我们使用串口发送时,就可以按照这种时序进行发送,接收时也要按照这种时序进行接收。
什么是波特率?
每秒钟发送的数据个数。
概念很简单,我们需要怎样去计算呢?常见的波特率有 9600 、 19200 、 38400 、 57600 、 115200 。
波特率计算:
9600:1_000_000_000 / 9600;
19200:1_000_000_000 / 19200;
38400:1_000_000_000 / 38400;
57600:1_000_000_000 / 57600;
115200:1_000_000_000 / 115200;
这儿的 1_000_000_000 是代表 1s 因为我的开发板是以 ns 为时间单位的,所以我的波特率计算也是以 ns 为时间单位。
发送思路原理:我们可以通过定义一个计数器,来记录每次发送所需要的的时间,然后用一个状态机来记录发送的状态。
module uart_tx_bottom( input Clk, //定义输入时钟信号。 input Reset_n, //定义复位信号。 input [7:0]Data, //定义需要发送的八位数据。 input Send_en, //定义发送使能信号,此信号为1时才可以发送数据。 output reg uart_tx, //定义发送信号,reg型是因为需要在always块中给它赋值。 output reg Tx_done //定义发送完成标志信号,以便下次发送数据。 ); parameter Band_Set = 433; //这儿是利用时钟计数发送一位的时间,1_000_000_000 / 115200 / 20 - 1。 // 其中 / 20 是因为我的开发板时钟周期为20ns, - 1 是因为计数器从0开始计数,原本算出需要计数434次,只需要加到433时就是434次了。 reg [3:0]bps_cnt; //数据发送状态,因为发送有起始位,数据位,结束位,用这个来记录数据发送到哪个位置了。 reg [17:0]div_cnt; //一位数据位发送时间计数器,记录发送数据的时间,用于更新下一段数据。 wire bps_clk; //每一位发送数据前后的标志位,具体作用后面会讲到。 always @(posedge Clk or negedge Reset_n) begin //定义一个计数器的always块。 if (!Reset_n) div_cnt <= 0; //若复位将计数器归0. else if (Send_en) begin //如果有使能才可以进行自加。 if (Band_Set <= div_cnt) //当中间<=写法错误时,这种写法会让编辑器自动报错,可以减少因为没注意而引发的功能错误,而且使用<=可以对未知的情况进行判断,增加程序的健壮性。 div_cnt <= 0; //当计数器记满时,计数器归0. else div_cnt <= div_cnt + 1'b1; //计数器未记满,每个时钟上升沿自加1. end else div_cnt <= 0; //没有使能信号,使计数器归0 end assign bps_clk = (div_cnt == 1); //当每次计数器记到 1 的时候就让数据开始发送,因为如果div_cnt == Band_Set,在每次发送时还需要等待一个数据位的时间数据才会发送,而这样定义后只需要等待1个时钟周期数据就会发送了。因为bps_clk是wire类型的,所以用assign语句赋值,而不是用always语句赋值。 always @(posedge Clk or negedge Reset_n) begin //定义发送状态块,记录发送的状态。 if (!Reset_n) bps_cnt <= 0; // 若是复位,状态为0。 else if (Send_en) begin //发送使能,状态才会改变。 if (bps_clk) begin //当有发送的标志时,状态才自增一次。 if (bps_cnt == 11) bps_cnt <= 0; //当状态计数到第11个状态,状态归0。 else bps_cnt <= bps_cnt + 1'b1; //状态自增1。 end end else bps_cnt <= 0; //发送不使能,状态归0。 end always @(posedge Clk or negedge Reset_n) begin //串口发送块。 if (!Reset_n) uart_tx <= 1; //当复位时,数据线发送高电平,因为空闲状态时数据线为高电平。 else begin case(bps_cnt) //开始发送数据,以数据状态来决定发送的是哪个数据。 1:uart_tx <= 0; //发送起始位,为什么不是0状态时刻发送0,因为在复位或者空闲时状态为0,而此时需要·uart_tx为高电平,所以在0状态时刻不能为0。 2:uart_tx <= Data[0]; //发送数据低位,数据从低位发送到高位,此下依次发送数据。 3:uart_tx <= Data[1]; 4:uart_tx <= Data[2]; 5:uart_tx <= Data[3]; 6:uart_tx <= Data[4]; 7:uart_tx <= Data[5]; 8:uart_tx <= Data[6]; 9:uart_tx <= Data[7]; //一般来说,不用校验位,直接发送数据最后一位。 default:uart_tx <= 1; //数据位发送完成之后,需要发送停止位,我们可以发现,空闲和停止位都是高电平,所以用default来将这些状态综合。 //细心的可以发现,我们状态定义了11个,这儿9个就结束了,为什么需要定义11个呢?因为在最后一个停止位发送为第10个状态,而停止位需要保留一个数据周期,所以在第10个状态发送停止位后,需要再延后一个状态,来保证停止位的时间,所以是11个状态。 endcase end end always @(posedge Clk or negedge Reset_n) begin //定义发送完成标志块。 if (!Reset_n) Tx_done <= 0; //复位时,发送完成标志位为0. else if (10 <= bps_cnt && bps_clk) //当状态为第10或者11个状态时且发送一个数据的标志位置位时才使总发送标志位置1。 Tx_done <= 1; else Tx_done <= 0; //其余时刻都将状态标志位置0。 end endmodule
接收思路原理:因为数据传输是不稳定的,会有波动,所以我们可以将每一个数据接收时间分为 16 份,检测其中中间的 7 份,7 份当中最多的状态就是此时的数据状态。
module uart_rx( //定义串口接收模块。 input Clk, //定义系统时钟信号。 input Reset_n, //定义复位。 input uart_RXD, //定义串口接收信号。 output reg [7:0]uart_Data, //定义接收数据输出。 output reg Rx_done //定义接收完成使能信号。 ); reg [1:0]state_RXD; //定义一个两位寄存器来储存此时刻和上一时刻的接收信号。 wire nedge_RXD; //定义接收信号下降沿寄存器。 reg uart_EN; //定义接收使能。 reg [8:0]cnt_Scan; //定义9位寄存器来计数每一位的16分之1的时间。 parameter Bps_DR = 1_000_000_000 / 115200 / 16 / 20 - 1; //定义每16/1所需时钟周期。 reg [7:0]cnt_state; //定义8位寄存器来记录一共160个状态。 localparam MAX = 159; //定义总共多少状态。localparam代表不能在上层模块中修改。 reg [2:0]state_Start; //定义起始信号寄存器。 reg [2:0]state_Data[7:0]; //定义数据信号寄存器,代表7个3位的寄存器。 reg [2:0]state_Stop; //定义结束信号。 reg state_done; //定义16/1位接收完成使能信号。 always @(posedge Clk or negedge Reset_n) begin //定义数据信号此时刻和上一时刻接收模块。 if (!Reset_n) //默认状态为0. state_RXD <= 2'd0; else //记录状态到寄存器里。 state_RXD <= {state_RXD[0], uart_RXD}; end assign nedge_RXD = (state_RXD == 2'b10); //判断是否为下降沿信号。 always @(posedge Clk or negedge Reset_n) begin //定义接收使能模块。 if (!Reset_n) //复位使能无效。 uart_EN <= 1'b0; else if (nedge_RXD) //只有当下降沿,也就是起始位被检测到使能接收。 uart_EN <= 1'b1; else if (cnt_state == MAX || state_Start[2]) //当160个状态计满或者起始信号为假时,后面会讲到,起始信号为真时,state_Start[2]为0。 uart_EN <= 0; else //其他状态使能信号不变。 uart_EN <= uart_EN; end always @(posedge Clk or negedge Reset_n) begin //定义每16/1为计数器模块。 if (!Reset_n) //复位清零。 cnt_Scan <= 9'd0; else if (uart_EN) begin //使能为有效时。 if (Bps_DR <= cnt_Scan) //计满归零。 cnt_Scan <= 9'd0; else //否则加一。 cnt_Scan <= cnt_Scan + 1'b1; end else //使能失效清零。 cnt_Scan <= 9'd0; end always @(posedge Clk or negedge Reset_n) begin //160为状态计数器模块。 if (!Reset_n) //复位清零。 cnt_state <= 8'd0; else if (uart_EN) begin //使能有效。 if (state_done) begin //当16/1为接收使能有效。 if (MAX <= cnt_state) //计满清零。 cnt_state <= 8'd0; else //否则加一。 cnt_state <= cnt_state + 1'b1; end else //其他状态不变。 cnt_state <= cnt_state; end else //使能失效清零。 cnt_state <= 8'd0; end always @(posedge Clk or negedge Reset_n) begin //数据接收模块。 if (!Reset_n) begin //复位清零。 state_Start <= 3'd0; state_Data[0] <= 3'd0; //定义成[x:0]a[y:0]这样的数据只能一个一个赋值,不能一起赋值。 state_Data[1] <= 3'd0; state_Data[2] <= 3'd0; state_Data[3] <= 3'd0; state_Data[4] <= 3'd0; state_Data[5] <= 3'd0; state_Data[6] <= 3'd0; state_Data[7] <= 3'd0; state_Stop <= 3'd0; end else if (uart_EN) begin //接收使能有效。 case (cnt_state) //更据状态赋值。 5,6,7,8,9,10,11:state_Start <= state_Start + uart_RXD; //因为接收信号只有0或1,所以只需要每次累加,取中间七次,当数据大于4时,便为高电平,否则为低电平,下同。 21,22,23,24,25,26,27:state_Data[0] <= state_Data[0] + uart_RXD; 37,38,39,40,41,42,43:state_Data[1] <= state_Data[1] + uart_RXD; 53,54,55,56,57,58,59:state_Data[2] <= state_Data[2] + uart_RXD; 69,70,71,72,73,74,75:state_Data[3] <= state_Data[3] + uart_RXD; 85,86,87,88,89,90,81:state_Data[4] <= state_Data[4] + uart_RXD; 101,102,103,104,105,106,107:state_Data[5] <= state_Data[5] + uart_RXD; 117,118,119,120,121,122,123:state_Data[6] <= state_Data[6] + uart_RXD; 133,134,135,136,137,138,139:state_Data[7] <= state_Data[7] + uart_RXD; 149,150,151,152,153,154,155:state_Stop <= state_Stop + uart_RXD; endcase end else begin //使能失效清零。 state_Start <= 3'd0; state_Data[0] <= 3'd0; state_Data[1] <= 3'd0; state_Data[2] <= 3'd0; state_Data[3] <= 3'd0; state_Data[4] <= 3'd0; state_Data[5] <= 3'd0; state_Data[6] <= 3'd0; state_Data[7] <= 3'd0; state_Stop <= 3'd0; end end always @(posedge Clk or negedge Reset_n) begin //数据读取转换模块。 if (!Reset_n) //复位清零。 uart_Data <= 8'd0; else if (cnt_state == MAX) begin //将每一位一位数据读取出来,当数据大于4时,数据第三位为1,当数据小于4时,第三位为0,所以我们可以直接用第三位的值来判断读取的值。 uart_Data[0] <= state_Data[0][2]; uart_Data[1] <= state_Data[1][2]; uart_Data[2] <= state_Data[2][2]; uart_Data[3] <= state_Data[3][2]; uart_Data[4] <= state_Data[4][2]; uart_Data[5] <= state_Data[5][2]; uart_Data[6] <= state_Data[6][2]; uart_Data[7] <= state_Data[7][2]; end else //其他状态数据不变。 uart_Data <= uart_Data; end always @(posedge Clk or negedge Reset_n) begin //16/1位完成使能信号。 if (!Reset_n) //复位清零。 state_done <= 1'b0; else if (cnt_Scan == Bps_DR) //当每16/1计数器计满时使能信号。 state_done <= 1'b1; else //否则使能失效。 state_done <= 1'b0; end always @(posedge Clk or negedge Reset_n) begin //接收完成使能信号模块。 if (!Reset_n) //复位清零。 Rx_done <= 1'b0; else if (cnt_state == MAX) //当160个状态计满时使能接收完成信号。 Rx_done <= 1'b1; else //其他时刻使能信号失效。 Rx_done <= 1'b0; end endmodule
以下代码提供为大家检测,它的作用是由电脑发送数据,再由开发板发送回来,博主已经亲测成功,如果测试出来数据失败,可以私信或者评论博主一起解决问题。
module uart( input Clk, input Reset_n, input uart_RXD, output uart_TXD ); reg [7:0]tx_Data; wire [7:0]rx_Data; reg uart_EN; wire Tx_done; wire Rx_done; uart_tx uart_tx( .Clk (Clk), .Reset_n (Reset_n), .uart_Data (tx_Data), .uart_EN (uart_EN), .uart_TXD (uart_TXD), .Tx_done (Tx_done) ); uart_rx uart_rx( .Clk (Clk), .Reset_n (Reset_n), .uart_RXD (uart_RXD), .uart_Data (rx_Data), .Rx_done (Rx_done) ); always @(*) begin tx_Data <= rx_Data; end always @(posedge Clk or negedge Reset_n) begin if (!Reset_n) uart_EN <= 1'b0; else if (Rx_done) uart_EN <= 1'b1; else if (Tx_done) uart_EN <= 1'b0; else uart_EN <= uart_EN; end endmodule
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。