查看原文
其他

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

wcc149 电子电路开发学习 2021-01-31



标准串口协议的Verilog实现

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

串口发送1个字节实现

  1. /*

  2. 串口协议发送:起始位 + 8位数据位 + 校验位 + 停止位 = 11位 * 16 = 176个时钟周期

  3. clk频率 = 波特率 * 16

  4. */


  5. module uart_tx_8bit(


  6. //input

  7. input clk, //UART时钟=16*波特率

  8. input rst_n,

  9. input [7:0] data_in, //需要发送的数据

  10. input trig, //上升沿发送数据


  11. //output

  12. output busy, //高电平忙:数据正在发送中

  13. output reg tx //发送数据信号


  14. );


  15. reg[7:0] cnt; //计数器

  16. reg trig_buf;

  17. reg trig_posedge_flag;

  18. // reg trig_negedge_flag;

  19. reg send;


  20. reg [10:0] data_in_buf; //trig上升沿读取输入的字节,拼接数据帧


  21. wire odd_bit; //奇校验位 = ~偶校验位

  22. wire even_bit; //偶校验位 = 各位异或

  23. wire POLARITY_BIT = even_bit; //偶校验

  24. // wire POLARITY_BIT = odd_bit; //奇校验


  25. assign even_bit = ^data_in; //一元约简,= data_in[0] ^ data_in[1] ^ .....

  26. assign odd_bit = ~even_bit;

  27. assign busy = send; //输出的忙信号


  28. //起始位+8位数据位+校验位+停止位 = 11位 * 16 = 176个时钟周期

  29. parameter CNT_MAX = 176;


  30. always @(posedge clk)

  31. begin

  32. if(!rst_n)

  33. begin

  34. trig_buf <= 0;

  35. trig_posedge_flag <= 0;

  36. // trig_negedge_flag <= 0;

  37. end

  38. else

  39. begin

  40. trig_buf <= trig;

  41. trig_posedge_flag <= (~trig_buf) & trig; //在trig信号上升沿时产生1个时钟周期的高电平

  42. // trig_negedge_flag <= trig_buf & (~trig); //在trig信号下降沿时产生1个时钟周期的高电平

  43. end

  44. end


  45. always @(posedge clk)

  46. begin

  47. if(!rst_n)

  48. send <= 0;

  49. else if (trig_posedge_flag & (~busy)) //当发送命令有效且线路为空闲时,启动新的数据发送进程

  50. send <= 1;

  51. else if(cnt == CNT_MAX) //一帧资料发送结束

  52. send <= 0;

  53. end


  54. always @ (posedge clk)

  55. begin

  56. if(!rst_n)

  57. data_in_buf <= 11'b0;

  58. else if(trig_posedge_flag & (~busy)) //只读取一次数据,一帧数据发送过程中,改变输入无效

  59. data_in_buf <= {1'b1, POLARITY_BIT, data_in[7:0], 1'b0}; //数据帧拼接

  60. end


  61. always @ (posedge clk)

  62. begin

  63. if(!rst_n)

  64. cnt <= 0;

  65. else if(!send || cnt >= CNT_MAX)

  66. cnt <= 0;

  67. else if(send)

  68. cnt <= cnt + 1;

  69. end


  70. always @(posedge clk)

  71. begin

  72. if(!rst_n)

  73. tx <= 1;

  74. else if(send)

  75. begin

  76. case(cnt) //1位占用16个时钟周期

  77. 0: tx <= data_in_buf[0]; //低位在前,高位在后

  78. 16: tx <= data_in_buf[1]; //bit0,占用第16~31个时钟

  79. 32: tx <= data_in_buf[2]; //bit1,占用第47~32个时钟

  80. 48: tx <= data_in_buf[3]; //bit2,占用第63~48个时钟

  81. 64: tx <= data_in_buf[4]; //bit3,占用第79~64个时钟

  82. 80: tx <= data_in_buf[5]; //bit4,占用第95~80个时钟

  83. 96: tx <= data_in_buf[6]; //bit5,占用第111~96个时钟

  84. 112: tx <= data_in_buf[7]; //bit6,占用第127~112个时钟

  85. 128: tx <= data_in_buf[8]; //bit7,占用第143~128个时钟

  86. 144: tx <= data_in_buf[9]; //发送奇偶校验位,占用第159~144个时钟

  87. 160: tx <= data_in_buf[10]; //发送停止位,占用第160~167个时钟

  88. CNT_MAX: tx <= 1; //无空闲位

  89. default:;

  90. endcase

  91. end

  92. else if(!send)

  93. tx <= 1;

  94. end


  95. endmodule

仿真波形

串口接收1个字节实现

串口接收部分的实现,涉及到串口数据的采样,对于MCU来说,不同单片机集成外设的处理方式有所不同,具体采样原理可以参考内核的Reference Manual。以传统51内核为例,按照所设置的波特率,每个位时间被分为16个时间片。UART接收器会在第7、8、9三个时间片进行采样,按照三取二的逻辑获得该位时间内的采样结果。其它一些类型的单片机则可能会更加严苛,例如有些工业单片机会五取三甚至七取五(设置成抗干扰模式时)。

本程序中采用的中间值采样,即取16个时钟周期中的中间位作为当前的采样值。

  1. //Verilog实现串口协议接收,带错误指示,校验错误和停止位错误


  2. /*

  3. 16个时钟周期接收1位,中间采样

  4. */

  5. module my_uart_rx(


  6. input clk, //采样时钟

  7. input rst_n,

  8. input rx, //UART数据输入

  9. output reg [7:0] dataout, //接收数据输出

  10. output reg rx_ok, //接收数据有效,高说明接收到一个字节

  11. output reg err_check, //数据出错指示

  12. output reg err_frame //帧出错指示


  13. );


  14. reg [7:0] cnt;

  15. reg [10:0] dataout_buf;


  16. reg rx_buf;

  17. reg rx_negedge_flag;

  18. reg receive;


  19. wire busy;

  20. wire odd_bit; //奇校验位 = ~偶校验位

  21. wire even_bit; //偶校验位 = 各位异或

  22. wire POLARITY_BIT; //本地计算的奇偶校验

  23. // wire polarity_ok;

  24. // assign polarity_ok = (POLARITY_BIT == dataout_buf[9]) ? 1 : 0; //校验正确=1,否则=0


  25. assign busy = rx_ok;

  26. assign even_bit = ^dataout; //一元约简,= data_in[0] ^ data_in[1] ^ .....

  27. assign odd_bit = ~even_bit;

  28. assign POLARITY_BIT = even_bit; //偶校验

  29. // assign POLARITY_BIT = odd_bit; //奇校验


  30. parameter CNT_MAX = 176;


  31. //rx信号下降沿标志位

  32. always @(posedge clk)

  33. begin

  34. if(!rst_n)

  35. begin

  36. rx_buf <= 0;

  37. rx_negedge_flag <= 0;

  38. end

  39. else

  40. begin

  41. rx_buf <= rx;

  42. rx_negedge_flag <= rx_buf & (~rx);

  43. end

  44. end

  45. //在接收期间,保持高电平

  46. always @(posedge clk)

  47. begin

  48. if(!rst_n)

  49. receive <= 0;

  50. else if (rx_negedge_flag && (~busy)) //检测到线路的下降沿并且原先线路为空闲,启动接收数据进程

  51. receive <= 1; //开始接收数据

  52. else if(cnt == CNT_MAX) //接收数据完成

  53. receive <= 0;

  54. end

  55. //起始位+8位数据位+校验位+停止位 = 11位 * 16 = 176个时钟周期

  56. always @ (posedge clk)

  57. begin

  58. if(!rst_n)

  59. cnt <= 0;

  60. else if(!receive || cnt >= CNT_MAX)

  61. cnt <= 0;

  62. else if(receive)

  63. cnt <= cnt + 1;

  64. end

  65. //校验错误:奇偶校验不一致

  66. always @ (posedge clk)

  67. begin

  68. if(!rst_n)

  69. err_check <= 0;

  70. else if(cnt == 152)

  71. begin

  72. // if(POLARITY_BIT == rx)

  73. if(POLARITY_BIT != dataout_buf[9]) //奇偶校验正确

  74. err_check <= 1; //锁存

  75. // else

  76. // err_check <= 1;

  77. end

  78. end

  79. //帧错误:停止位不为1

  80. always @ (posedge clk)

  81. begin

  82. if(!rst_n)

  83. err_frame <= 0;

  84. else if(cnt == CNT_MAX)

  85. begin

  86. if(dataout_buf[10] != 1) //停止位

  87. err_frame <= 1;

  88. // else

  89. // err_frame <= 1; //如果没有接收到停止位,表示帧出错

  90. end

  91. end


  92. always @ (posedge clk)

  93. begin

  94. if(!rst_n)

  95. dataout <= 11'h00;

  96. else if(receive)

  97. begin

  98. // if(rx_ok)

  99. if(cnt >= 137)

  100. dataout <= dataout_buf[8:1]; //数据位:8-1位

  101. // else if(!rx_ok)

  102. // dataout <= 0;

  103. end

  104. end


  105. always @ (posedge clk)

  106. begin

  107. if(!rst_n)

  108. rx_ok <= 0;

  109. else if(receive)

  110. begin

  111. if(cnt >= 137) //137-169

  112. rx_ok <= 1;

  113. else

  114. rx_ok <= 0;

  115. end

  116. else

  117. rx_ok <= 0;

  118. end



  119. //起始位+8位数据+奇偶校验位+停止位 = 11 * 16 = 176位


  120. always @(posedge clk)

  121. begin

  122. if(!rst_n)

  123. dataout_buf <= 8'h00;

  124. else if(receive)

  125. begin

  126. case (cnt) //中间采样

  127. 8'd8: dataout_buf[0] <= rx; //起始位=0

  128. 8'd24: dataout_buf[1] <= rx; //LSB低位在前

  129. 8'd40: dataout_buf[2] <= rx;

  130. 8'd56: dataout_buf[3] <= rx;

  131. 8'd72: dataout_buf[4] <= rx;

  132. 8'd88: dataout_buf[5] <= rx;

  133. 8'd104: dataout_buf[6] <= rx;

  134. 8'd120: dataout_buf[7] <= rx;

  135. 8'd136: dataout_buf[8] <= rx; //MSB高位在后

  136. 8'd152: dataout_buf[9] <= rx; //奇偶校验位

  137. 8'd168: dataout_buf[10] <= rx; //停止位=1

  138. default:;

  139. endcase

  140. end

  141. end


  142. 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仿真文件。

推荐阅读:



我的博客:www.wangchaochao.top

或微信扫码关注我的公众号:mcu149

由于微信文章不支持超链接,文中出现的软件、程序等文件下载,可以点击"阅读原文",跳转到我的博客文章进行下载。



原创不易,如果觉得我的文章对你有所帮助,可以随手点“好看”分享,你的支持将是我持续更新的动力。

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存