标准串口协议的Verilog实现
基于Verilog实现标准串口协议发送8位数据:起始位 + 8位数据位 + 校验位 + 停止位 = 11位,每1位的时间是16个时钟周期,所以输入时钟应该为:波特率*16,带Busy忙信号输出。实现方法比较简单,数据帧的拼接、计数器计时钟周期,每16个时钟周期输出一位数据即可。
串口发送1个字节实现
/* 串口协议发送:起始位 + 8位数据位 + 校验位 + 停止位 = 11位 * 16 = 176个时钟周期 clk频率 = 波特率 * 16 */ module uart_tx_8bit( //input input clk, //UART时钟=16*波特率 input rst_n, input [7:0] data_in, //需要发送的数据 input trig, //上升沿发送数据 //output output busy, //高电平忙:数据正在发送中 output reg tx //发送数据信号 ); reg[7:0] cnt; //计数器 reg trig_buf; reg trig_posedge_flag; // reg trig_negedge_flag; reg send; reg [10:0] data_in_buf; //trig上升沿读取输入的字节,拼接数据帧 wire odd_bit; //奇校验位 = ~偶校验位 wire even_bit; //偶校验位 = 各位异或 wire POLARITY_BIT = even_bit; //偶校验 // wire POLARITY_BIT = odd_bit; //奇校验 assign even_bit = ^data_in; //一元约简,= data_in[0] ^ data_in[1] ^ ..... assign odd_bit = ~even_bit; assign busy = send; //输出的忙信号 //起始位+8位数据位+校验位+停止位 = 11位 * 16 = 176个时钟周期 parameter CNT_MAX = 176; always @(posedge clk) begin if(!rst_n) begin trig_buf <= 0; trig_posedge_flag <= 0; // trig_negedge_flag <= 0; end else begin trig_buf <= trig; trig_posedge_flag <= (~trig_buf) & trig; //在trig信号上升沿时产生1个时钟周期的高电平 // trig_negedge_flag <= trig_buf & (~trig); //在trig信号下降沿时产生1个时钟周期的高电平 end end always @(posedge clk) begin if(!rst_n) send <= 0; else if (trig_posedge_flag & (~busy)) //当发送命令有效且线路为空闲时,启动新的数据发送进程 send <= 1; else if(cnt == CNT_MAX) //一帧资料发送结束 send <= 0; end always @ (posedge clk) begin if(!rst_n) data_in_buf <= 11'b0; else if(trig_posedge_flag & (~busy)) //只读取一次数据,一帧数据发送过程中,改变输入无效 data_in_buf <= {1'b1, POLARITY_BIT, data_in[7:0], 1'b0}; //数据帧拼接 end always @ (posedge clk) begin if(!rst_n) cnt <= 0; else if(!send || cnt >= CNT_MAX) cnt <= 0; else if(send) cnt <= cnt + 1; end always @(posedge clk) begin if(!rst_n) tx <= 1; else if(send) begin case(cnt) //1位占用16个时钟周期 0: tx <= data_in_buf[0]; //低位在前,高位在后 16: tx <= data_in_buf[1]; //bit0,占用第16~31个时钟 32: tx <= data_in_buf[2]; //bit1,占用第47~32个时钟 48: tx <= data_in_buf[3]; //bit2,占用第63~48个时钟 64: tx <= data_in_buf[4]; //bit3,占用第79~64个时钟 80: tx <= data_in_buf[5]; //bit4,占用第95~80个时钟 96: tx <= data_in_buf[6]; //bit5,占用第111~96个时钟 112: tx <= data_in_buf[7]; //bit6,占用第127~112个时钟 128: tx <= data_in_buf[8]; //bit7,占用第143~128个时钟 144: tx <= data_in_buf[9]; //发送奇偶校验位,占用第159~144个时钟 160: tx <= data_in_buf[10]; //发送停止位,占用第160~167个时钟 CNT_MAX: tx <= 1; //无空闲位 default:; endcase end else if(!send) tx <= 1; end endmodule
仿真波形
串口接收1个字节实现
串口接收部分的实现,涉及到串口数据的采样,对于MCU来说,不同单片机集成外设的处理方式有所不同,具体采样原理可以参考内核的Reference Manual。以传统51内核为例,按照所设置的波特率,每个位时间被分为16个时间片。UART接收器会在第7、8、9三个时间片进行采样,按照三取二的逻辑获得该位时间内的采样结果。其它一些类型的单片机则可能会更加严苛,例如有些工业单片机会五取三甚至七取五(设置成抗干扰模式时)。
本程序中采用的中间值采样,即取16个时钟周期中的中间位作为当前的采样值。
//Verilog实现串口协议接收,带错误指示,校验错误和停止位错误 /* 16个时钟周期接收1位,中间采样 */ module my_uart_rx( input clk, //采样时钟 input rst_n, input rx, //UART数据输入 output reg [7:0] dataout, //接收数据输出 output reg rx_ok, //接收数据有效,高说明接收到一个字节 output reg err_check, //数据出错指示 output reg err_frame //帧出错指示 ); reg [7:0] cnt; reg [10:0] dataout_buf; reg rx_buf; reg rx_negedge_flag; reg receive; wire busy; wire odd_bit; //奇校验位 = ~偶校验位 wire even_bit; //偶校验位 = 各位异或 wire POLARITY_BIT; //本地计算的奇偶校验 // wire polarity_ok; // assign polarity_ok = (POLARITY_BIT == dataout_buf[9]) ? 1 : 0; //校验正确=1,否则=0 assign busy = rx_ok; assign even_bit = ^dataout; //一元约简,= data_in[0] ^ data_in[1] ^ ..... assign odd_bit = ~even_bit; assign POLARITY_BIT = even_bit; //偶校验 // assign POLARITY_BIT = odd_bit; //奇校验 parameter CNT_MAX = 176; //rx信号下降沿标志位 always @(posedge clk) begin if(!rst_n) begin rx_buf <= 0; rx_negedge_flag <= 0; end else begin rx_buf <= rx; rx_negedge_flag <= rx_buf & (~rx); end end //在接收期间,保持高电平 always @(posedge clk) begin if(!rst_n) receive <= 0; else if (rx_negedge_flag && (~busy)) //检测到线路的下降沿并且原先线路为空闲,启动接收数据进程 receive <= 1; //开始接收数据 else if(cnt == CNT_MAX) //接收数据完成 receive <= 0; end //起始位+8位数据位+校验位+停止位 = 11位 * 16 = 176个时钟周期 always @ (posedge clk) begin if(!rst_n) cnt <= 0; else if(!receive || cnt >= CNT_MAX) cnt <= 0; else if(receive) cnt <= cnt + 1; end //校验错误:奇偶校验不一致 always @ (posedge clk) begin if(!rst_n) err_check <= 0; else if(cnt == 152) begin // if(POLARITY_BIT == rx) if(POLARITY_BIT != dataout_buf[9]) //奇偶校验正确 err_check <= 1; //锁存 // else // err_check <= 1; end end //帧错误:停止位不为1 always @ (posedge clk) begin if(!rst_n) err_frame <= 0; else if(cnt == CNT_MAX) begin if(dataout_buf[10] != 1) //停止位 err_frame <= 1; // else // err_frame <= 1; //如果没有接收到停止位,表示帧出错 end end always @ (posedge clk) begin if(!rst_n) dataout <= 11'h00; else if(receive) begin // if(rx_ok) if(cnt >= 137) dataout <= dataout_buf[8:1]; //数据位:8-1位 // else if(!rx_ok) // dataout <= 0; end end always @ (posedge clk) begin if(!rst_n) rx_ok <= 0; else if(receive) begin if(cnt >= 137) //137-169 rx_ok <= 1; else rx_ok <= 0; end else rx_ok <= 0; end //起始位+8位数据+奇偶校验位+停止位 = 11 * 16 = 176位 always @(posedge clk) begin if(!rst_n) dataout_buf <= 8'h00; else if(receive) begin case (cnt) //中间采样 8'd8: dataout_buf[0] <= rx; //起始位=0 8'd24: dataout_buf[1] <= rx; //LSB低位在前 8'd40: dataout_buf[2] <= rx; 8'd56: dataout_buf[3] <= rx; 8'd72: dataout_buf[4] <= rx; 8'd88: dataout_buf[5] <= rx; 8'd104: dataout_buf[6] <= rx; 8'd120: dataout_buf[7] <= rx; 8'd136: dataout_buf[8] <= rx; //MSB高位在后 8'd152: dataout_buf[9] <= rx; //奇偶校验位 8'd168: dataout_buf[10] <= rx; //停止位=1 default:; endcase end end endmodule