小言_互联网的博客

fpga实操训练(uart串口)

391人阅读  评论(0)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】

        如果说led灯、按键、数码管这些都只能算是基础的话,那么学习fpga遇到的第一个门槛就是uart。要做好uart,首先需要了解串口的一些特性。

1、uart串口的基本特性

        1)波特率。所谓波特率,就是多长时间接收一个数据。这个速度可以快,可以慢。关键是双方要匹配。

        2)数据的构成。一般uart的数据,由起始位、数据位、校验位、停止位构成。校验位一般不用。所以串口一般是有10个数据构成。

2、开发方法

        1)状态机。前面编写的程序都比较简单,印象中除了按键消抖那一章用到了状态机,其他章节都没有用到。事实上,在fpga开发上,状态机用到的地方非常多,不管是底层模块,还是高层模块。

        2)开发顺序,可以按照先发送、后接收的方法一步一步来完成。为什么怎么做?主要是因为发送比较纯粹一些,等发送功能开发结束后,可以马上看到效果,提高自己的信心。而且,后续等接收功能开发完毕后,可以把数据回显到发送功能,马上可以进行调试。相反,如果先开发了接收功能,容易看不到效果,打击信心。

3、uart发送


  
  1. module uart_send(clk, rst, finish_flag, recv_data,out);
  2. input clk;
  3. input rst;
  4. input finish_flag;
  5. input recv_data;
  6. output out;
  7. wire clk;
  8. wire rst;
  9. wire finish_flag;
  10. wire[ 7: 0] recv_data;
  11. reg out;
  12. `define SEND_INTERVAL 32 'd49_9999
  13. `define UART_INTERVAL ( 50* 1000000/ 115200)
  14. reg[ 31: 0] internal_count;
  15. reg[ 2: 0] state;
  16. reg[ 2: 0] next_state;
  17. reg[ 31: 0] count;
  18. reg[ 2: 0] bitnum;
  19. reg[ 7: 0] send_data;
  20. always@(posedge clk or negedge rst)
  21. if(!rst)
  22. send_data <= 8 'h30;
  23. else if(finish_flag)
  24. send_data <= recv_data;
  25. always@(posedge clk or negedge rst)
  26. if(!rst)
  27. internal_count <= 32 'b0;
  28. else if(state == 3 'b000) begin
  29. if(internal_count == `SEND_INTERVAL)
  30. internal_count <= 32 'b0;
  31. else
  32. internal_count <= internal_count + 1 'b1;
  33. end else
  34. internal_count <= 32 'b0;
  35. always@(posedge clk or negedge rst)
  36. if(!rst)
  37. state <= 3 'b000;
  38. else
  39. state <= next_state;
  40. always@(*)
  41. if(!rst) begin
  42. next_state <= 3 'b000;
  43. end else
  44. case (state)
  45. 3 'b000:
  46. begin
  47. if(internal_count == `SEND_INTERVAL)
  48. next_state <= 3 'b001;
  49. else
  50. next_state <= 3 'b000;
  51. end
  52. 3 'b001:
  53. begin
  54. if(count == `UART_INTERVAL)
  55. next_state <= 3 'b010;
  56. else
  57. next_state <= 3 'b001;
  58. end
  59. 3 'b010:
  60. begin
  61. if(count == `UART_INTERVAL && bitnum == 3 'b111)
  62. next_state <= 3 'b011;
  63. else
  64. next_state <= 3 'b010;
  65. end
  66. 3 'b011:
  67. begin
  68. if(count == `UART_INTERVAL)
  69. next_state <= 3 'b000;
  70. else
  71. next_state <= 3 'b011;
  72. end
  73. default:
  74. next_state <= 3 'b000;
  75. endcase
  76. always @(posedge clk or negedge rst)
  77. if(!rst)
  78. count <= 32 'b0;
  79. else if(state != 3 'b000) begin
  80. if(count == `UART_INTERVAL)
  81. count <= 32 'b0;
  82. else
  83. count <= count + 1 'b1;
  84. end else
  85. count <= 32 'b0;
  86. always @(posedge clk or negedge rst)
  87. if(!rst)
  88. bitnum <= 3 'b000;
  89. else if(state == 3 'b010 && count == `UART_INTERVAL)
  90. bitnum <= bitnum + 1;
  91. always @(posedge clk or negedge rst)
  92. if(!rst)
  93. out <= 1 'b1;
  94. else begin
  95. case(state)
  96. 3 'b000:
  97. out <= 1 'b1;
  98. 3 'b001:
  99. out <= 1 'b0;
  100. 3 'b010:
  101. out <= send_data[bitnum];
  102. 3 'b011:
  103. out <= 1 'b1;
  104. default:
  105. out <= 1 'b1;
  106. endcase
  107. end
  108. endmodule

        整个数据的发送过程基本是按照状态机走的,每10ms发送一次。空闲状态是3'b000,准备发送数据后,状态跳转到3'b001,空闲状态下输出都是1。在这个状态下,首先发送一个bit的起始位,起始位是0。什么时候发送结束呢,就看这个count什么时候达到UART_INTERVAL了,而这个数值正是根据波特率算出来的。起始位发送结束后,状态跳转到3'b010。这个状态是数据位,总共有8位数据需要发送。等全部数据发送结束后,状态继续跳转到3'b011,即停止位数据的发送。这就是大概的步骤。

        整个代码中,使用到了一个小技巧,那就是bitnum。bitnum巧妙地利用了数据溢出效应,在所有的8位数据发送之后,重新reset为0。

4、uart接收


  
  1. module uart_recv(clk, rst, in, finish_flag, recv_data, led);
  2. input clk;
  3. input rst;
  4. input in;
  5. output finish_flag;
  6. output recv_data;
  7. output led; // only for debug use
  8. // baudrate setting
  9. localparam RECV_INTERVAL= 50* 1000000/ 115200;
  10. localparam LED_INTERVAL = 32 'd4999_9999;
  11. wire clk;
  12. wire rst;
  13. wire in;
  14. reg led;
  15. reg[ 31: 0] led_count;
  16. reg tmp0;
  17. reg tmp1;
  18. wire diff;
  19. // value for state machine
  20. reg[ 2: 0] state;
  21. reg[ 2: 0] next_state;
  22. assign diff = tmp1 && ~tmp0;
  23. reg[ 31: 0] interval_cnt;
  24. reg[ 2: 0] bit_cnt;
  25. reg[ 7: 0] recv_latch;
  26. reg[ 7: 0] recv_data;
  27. // recv_data to store input data
  28. always @(posedge clk or negedge rst)
  29. if(!rst)
  30. recv_data <= 8 'b0;
  31. else if(state == 3 'b100)
  32. recv_data <= recv_latch;
  33. // finish flag
  34. reg finish_flag;
  35. always@(posedge clk or negedge rst)
  36. if(!rst)
  37. finish_flag <= 1 'b0;
  38. else if(finish_flag)
  39. finish_flag <= 1 'b0;
  40. else if(state == 3 'b100)
  41. finish_flag <= 1 'b1;
  42. // count for led debug
  43. always @(posedge clk or negedge rst)
  44. if(!rst)
  45. led_count <= 32 'b0;
  46. else if(led) begin
  47. if(led_count != LED_INTERVAL)
  48. led_count <= led_count + 1;
  49. else
  50. led_count <= 32 'b0;
  51. end else
  52. led_count <= 32 'b0;
  53. // led for debug signal
  54. always @(posedge clk or negedge rst)
  55. if(!rst)
  56. led <= 1 'b0;
  57. else if(led && led_count == LED_INTERVAL)
  58. led <= 1 'b0;
  59. else if(finish_flag)
  60. led <= 1 'b1;
  61. // state machine
  62. always @(posedge clk or negedge rst)
  63. if(!rst)
  64. state <= 3 'b000;
  65. else
  66. state <= next_state;
  67. always @(*)
  68. if(!rst)
  69. next_state <= 3 'b000;
  70. else
  71. case (state)
  72. 3 'b000:
  73. if(diff)
  74. next_state <= 3 'b001;
  75. else
  76. next_state <= 3 'b000;
  77. 3 'b001:
  78. if(interval_cnt == RECV_INTERVAL)
  79. next_state <= 3 'b010;
  80. else
  81. next_state <= 3 'b001;
  82. 3 'b010:
  83. if(interval_cnt == RECV_INTERVAL && bit_cnt == 3 'b111)
  84. next_state <= 3 'b011;
  85. else
  86. next_state <= 3 'b010;
  87. 3 'b011:
  88. if(interval_cnt == RECV_INTERVAL/ 2 - 1)
  89. next_state <= 3 'b100;
  90. else
  91. next_state <= 3 'b011;
  92. 3 'b100: // make sure data has been received
  93. next_state <= 3 'b000;
  94. default:
  95. next_state <= 3 'b000;
  96. endcase
  97. // interval count
  98. always @(posedge clk or negedge rst)
  99. if(!rst)
  100. interval_cnt <= 32 'b0;
  101. else if(state != 3 'b000) begin
  102. if(interval_cnt == RECV_INTERVAL)
  103. interval_cnt <= 32 'b0;
  104. else
  105. interval_cnt <= interval_cnt + 1 'b1;
  106. end else
  107. interval_cnt <= 32 'b0;
  108. always @(posedge clk or negedge rst)
  109. if(!rst)
  110. bit_cnt <= 3 'b000;
  111. else if(state == 3 'b010) begin
  112. if(interval_cnt == RECV_INTERVAL)
  113. bit_cnt <= bit_cnt + 1 'b1;
  114. end
  115. // get data
  116. always @(posedge clk or negedge rst)
  117. if(!rst)
  118. tmp0 <= 1 'b0;
  119. else
  120. tmp0 <= in;
  121. always @(posedge clk or negedge rst)
  122. if(!rst)
  123. tmp1 <= 1 'b0;
  124. else
  125. tmp1 <= tmp0;
  126. // recv data bit by bit
  127. always @(posedge clk or negedge rst)
  128. if(!rst)
  129. recv_latch <= 8 'b0;
  130. else
  131. case(state)
  132. 3 'b010:
  133. if(interval_cnt == RECV_INTERVAL/ 2 - 1)
  134. recv_latch[bit_cnt] <= tmp1;
  135. default:
  136. recv_latch <= recv_latch;
  137. endcase
  138. endmodule

        uart的接收部分也使用到了状态机。其中3'b000代表空闲状态。什么时候开始接收数据呢,就看diff什么时候变更为1。接下来的步骤就和uart发送有点类似了,先是接收起始位,然后是有效数据位,最后是停止位。对于我们来说,真正有效的部分是数据位。状态机有两个地方需要注意下,第一是停止位在RECV_INTREVAL/2-1就可以停下来了,这主要为了防止后面新的数据丢失;第二添加了一个3'b100状态,这是为了可以让finish_flag和recv_data同时有效。

        另外还有一个小插曲。由于不怎么会用signal tap,就用led进行了调试。即,如果真的收到了数据,led亮起,延时1s。


  
  1. // count for led debug
  2. always @(posedge clk or negedge rst)
  3. if(!rst)
  4. led_count <= 32 'b0;
  5. else if(led) begin
  6. if(led_count != LED_INTERVAL)
  7. led_count <= led_count + 1;
  8. else
  9. led_count <= 32 'b0;
  10. end else
  11. led_count <= 32 'b0;
  12. // led for debug signal
  13. always @(posedge clk or negedge rst)
  14. if(!rst)
  15. led <= 1 'b0;
  16. else if(led && led_count == LED_INTERVAL)
  17. led <= 1 'b0;
  18. else if(finish_flag)
  19. led <= 1 'b1;

        关于uart接收部分,还有一个想说的,就是真正数据接收的部分都是在RECV_INTERVAL/2-1来完成的。大家可以想想为什么。

5、测试总入口    


  
  1. module uart_test(clk, rst, in, out, led);
  2. input clk;
  3. input rst;
  4. input in;
  5. output out;
  6. output led;
  7. wire clk;
  8. wire rst;
  9. wire in;
  10. wire out;
  11. wire led;
  12. wire finish_flag;
  13. wire [7:0] recv_data;
  14. uart_recv uart_recv0(
  15. .clk(clk),
  16. .rst(rst),
  17. .in(in),
  18. .finish_flag(finish_flag),
  19. .recv_data(recv_data),
  20. .led(led)
  21. );
  22. uart_send uart_send0(
  23. .clk(clk),
  24. .rst(rst),
  25. .finish_flag(finish_flag),
  26. .recv_data(recv_data),
  27. .out(out)
  28. );
  29. endmodule

        这个测试总入口非常简单,就是分别把uart_send和uart_recv实例化,用finish_flag和recv_data把两者之间联系在一起。

6、pin绑定

         所有这些都准备好之后,就可以把信号和pin脚bind在一起了。生成sof文件,烧入开发板。

7、开始实验

        实验主要分成两部分。一部分是上电的时候;一部分是按下按键的时候。选择一个串口软件,这里用的putty。ax301开发板选择了usb转串口的芯片,所以本身相当于自带串口的。首先,需要以管理员身份打开putty。建议先打开putty,后烧入sof。

         接着,你会看到这样的窗口,

         设置好串口和波特率之后,直接单击Open,就可以看到相关的打印,

         这个时候如果按下数字1,就会发现打印的内容发生了变化,

         具体操作,还是希望大家多实践实践。


转载:https://blog.csdn.net/feixiaoxing/article/details/128362958
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场