前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >详解串行通信协议及其FPGA实现(二)

详解串行通信协议及其FPGA实现(二)

作者头像
单片机点灯小能手
发布2020-07-16 19:54:09
5740
发布2020-07-16 19:54:09
举报

标准串口协议的Verilog实现

基于Verilog实现标准串口协议发送8位数据:起始位 + 8位数据位 + 校验位 + 停止位 = 11位,每1位的时间是16个时钟周期,所以输入时钟应该为:波特率*16,带Busy忙信号输出。实现方法比较简单,数据帧的拼接、计数器计时钟周期,每16个时钟周期输出一位数据即可。

串口发送1个字节实现

代码语言:javascript
复制
/*串口协议发送:起始位 + 8位数据位 + 校验位 + 停止位 = 11位 * 16 = 176个时钟周期clk频率 = 波特率 * 16*/
module uart_tx_8bit(
//inputinput clk,                //UART时钟=16*波特率input rst_n,input [7:0] data_in,       //需要发送的数据input trig,                //上升沿发送数据
//outputoutput 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个时钟周期的高电平    endend
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个时钟周期中的中间位作为当前的采样值。

代码语言:javascript
复制
//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);    endend//在接收期间,保持高电平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;    endend//帧错误:停止位不为1always @ (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;      //如果没有接收到停止位,表示帧出错    endend
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;    endend
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    endend
endmodule

代码工程下载

后台回复【串口FPGA】关键字,我会把下载链接发送给你。

或者使用:git clone命令下载代码

  • Github工程地址: https://github.com/whik/UARTDemoVerilog
  • Gitee工程地址: https://gitee.com/whik/UARTDemoVerilog

工程包含:

  • myuartrx:串口接收1个字节示例程序
  • uarttx8bit:串口发送1个字节示例程序
  • uarttxdemo:串口每隔500ms循环发送0-9字符

及以上文件对应的Testbench仿真文件。

推荐阅读:

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-08-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 电子电路开发学习 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 串口发送1个字节实现
  • 串口接收1个字节实现
  • 代码工程下载
  • 推荐阅读:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档