26482|83

5979

帖子

8

TA的资源

版主

楼主
 

EE_FPGA基础教程系列 --NO.5-- 串口调试 [复制链接]

 

 

Table of Contents

 

1. 串口通信 ....................................................4
1.1 串口的常识 ..............................................4
1.2 串口通信原理 ..........................................4
2. 代码 ..........................................................5
 调试前准备 ...................................................15
4. 上电调试 ....................................................16
5. 总结 ...........................................................16
 

1. 串口通信
1.1 串口的常识
  
  串口,即UART(Universal Asynchronous Receiver/Transmitter),就是一种通用串行数据总线。这里,我们学习的是基于RS-232的串口,RS-232的电气特性是:逻辑1 = -3V~-15V;逻辑0 = +3~+15V。老式的个人计算机上一般有两组RS-232 接口,分别称为 COM1 和 COM2。现在的电脑,尤其是笔记本电脑,基本是没有串口了的。但,串口作为常用的通信总线,总在很多地方用到,很有必要学习下。“哪里有需求,哪里就有商机!”现在到处都有串口转USB的转换线卖,售价呢也就两三块钱。

  回到我们的EE_FPGA上,这块板子,我们不需要转换线了,因为设计的时候已经在板子上加入了串口转USB芯片PL2303HX。调出原理图如下:

 
  其实,人家卖的转换线呢,就是里面就是做了这么一个电路!

  这里,PL2303HX的芯片手册,以及更多关于串口和RS-232的知识,请大家自行Google、百度之。重点到我们最关心的串口的代码编写上来。

  1.2 串口通信原理

  大家观察原理图可以看到,串口通信其实只用到了FPGA的两个I/O口,分别是I/O_39、I/O_37。通常,我们把这两根线命名为tx、rx,其实就是数据接收线和发送线。

  我们再看下串口数据的传输协议:


 
  典型的一帧数据格式是1个起始位,8个数据位,1个奇偶校验位,1-2个停止位。其中,数据线上没有数据传输的时候是保持高电平,而第一个低电平的出现就是起始位。当发送数据和接收数据的时候,按以上格式进行就可以了。

  不知道大家会不会有这样一个问题,经常听说串口传输的速度是多少多少,看了这个数据帧格式,没发现速度是怎么控制的啊。对的,这儿就引出了波特率的问题。波特率时钟并没有表现在传输线上,它其实是用来指示我们每采样或发送一个数据位的速度的。比如说,我们定义一个波特率时钟为96k,那么我们用这个时钟把数据串行一个一个打出去,接收端只要匹配一个相同的时钟,一个一个数据位接受进来,那数据传输就可以了!

  2. 代码

  这部分的代码相对于前面的就稍稍有点多了,我们来一点一点分析,由于这次用到了模块调用,这次上代码,我们把每个文件名都写上。首先我们来看下这个工程的树状代码:

 


 
  通过这个树状图,大家应该就对这个工程有一定了解了,主模块是uart,它包含四个子模块,分别是接收模块rx、接收模块的波特率时钟rx_clk、发送模块tx、发送模块的波特率时钟tx_clk。
1、主模块:uart.v
  1. module uart(
    clk,
    rst_n,
    rxd,
    txd
    );

  2. input clk;  //输入时钟,50M
    input rst_n;
    input rxd;  //串口输入rx

  3. output txd;  //串口输出tx

  4. wire bps_start1,bps_start2; //rx、tx的波特率启动信号
    wire bps_clk1,bps_clk2; //rx、tx的波特率时钟
    wire rx_done; //数据接收完毕信号,有rx输出到tx,rx接收数据由tx发送回去
    wire[7:0] data; //数据寄存器

  5.  bps_generate rx_clk(
          .clk(clk),   //  50M
          .rst_n(rst_n),
          .bps_start(bps_start1), //拉高,产生波特率时钟
          .bps_clk(bps_clk1)
         );


  6.  rx  rx          (
          .clk(clk),
          .rst_n(rst_n),
    .rxd(rxd),
          .bps_clk(bps_clk1),
          .bps_start(bps_start1),
          .rx_done(rx_done),
          .data(data)
         );

  7.  bps_generate tx_clk(
          .clk(clk),   //  50M
          .rst_n(rst_n),
          .bps_start(bps_start2), //拉高,产生波特率时钟
          .bps_clk(bps_clk2)
         );


  8.  tx tx           (
          .clk(clk),
          .rst_n(rst_n),
          .bps_clk(bps_clk2),
          .bps_start(bps_start2),
    .data(data),
          .rx_done(rx_done),
          .txd(txd)
         );
    endmodule

复制代码

 

  通常,我们在顶层模块里不会写具体操作的代码,主模块的作用是将各个模块连接在一起。这样有利于代码的维护!

  我们看到主模块对外的输入输出引脚只有时钟clk,复位rst_n,以及数据输入线rx和输出线tx。往下看,我们看到一堆wire的定义,这个很重要。由于input和output的端口Quartus软件会自动配置成wire型,所以这些端口不用再wire定义。而其他剩下的端口,如果不“wire”一下,那么下面的各模块例化相互的端口就不会连接在一起。

  接下来终于要说到模块例化了,所谓“例化”,是从英文的instantiate翻译过来的。这里我说个不太严谨的说法:就是把另一个模块添加在这个模块当中,更不严谨的,也可以说是调用吧。

  1. bps_generate rx_clk(
          .clk(clk),   //  50M
          .rst_n(rst_n),
          .bps_start(bps_start1), //拉高,产生波特率时钟
          .bps_clk(bps_clk1)
         );
复制代码

 

  我们引用波特率时钟模块来说明例化的格式吧。首先,bps_generate是原来模块的模块名,后面加一个或几个空格,紧跟着rx_clk是在这个模块下(也就是uart)的模块名。通常,我们会把两个命名一样,或者后面的命名为:i1、i2,意思是模块1,2…这个可以随意。接下来在();里添加这个模块的输入输出引脚,其中.clk()是原来模块的名称,而括号里则是在当前模块下的名称。在当前模块里,两个名称一样,再“wire”一下,那这个信号就连接在一起了。这儿,你可以直接给这个接口赋一个数,例如.data(16’h11),这样,也是可以的。

   2、波特率产生模块:bps_generate.v

  1. module bps_generate(
    clk,
    rst_n,
    bps_start,
    bps_clk
    );
    input clk;   //  50M
    input rst_n;
    input bps_start; //高电平,产生波特率时钟

  2. output bps_clk;

  3. parameter CNT_NUM = 434;  //波特率为50M / 434 = 115200
    parameter CNT_NUM_2 = 216; //计数值的一半,产生一个高电平

  4. reg[15:0] cnt;
    always @(posedge clk or negedge rst_n)
    begin
     if(!rst_n)
      cnt <= 16'b0;
     else if(cnt == CNT_NUM)
      cnt <= 16'b0;
     else if(bps_start)
      cnt <= cnt + 1'b1;
     else cnt <= 16'b0;  
    end

  5. reg bps_clk_r;
    always @(posedge clk or negedge rst_n)
    begin
     if(!rst_n)
      bps_clk_r <= 1'b0;
     else if(cnt == CNT_NUM_2)
      bps_clk_r <= 1'b1;
     else bps_clk_r <= 1'b0;
    end

  6. assign bps_clk = bps_clk_r;

  7. endmodule

复制代码

 

  这里,我们首先用到了parameter,因为我们常通过修改计数值来改波特率时钟,所以,用parameter是很方便的!

  接下来就是计数的操作,我想不用介绍太多。最后产生的波特率时钟差不多是这样的,我们利用每个高电平来采集、发送数据。

 
  这里,还要讲到一个模块重复调用的问题。由于接收和发送的波特率时钟是一样的,我们在顶层模块uart调用的时候都是调用了这个代码文件,但分别命名为rx_clk 和tx_clk,Quartus软件在综合的时候就会综合成两个电路。不同于软件程序的调用,大家一定要引起重视!


  3、接收模块:rx.v

  1. module rx(
    clk,
    rst_n,
    bps_clk,
    rxd,
    bps_start,
    rx_done,
    data
    );

  2. input clk;
    input rst_n;
    input bps_clk;
    input rxd;

  3. output bps_start;
    output rx_done;
    output[7:0] data;

  4.  

  5. reg reg_rxd0;
    always @(posedge clk or negedge rst_n)
    begin
     if(!rst_n)
      reg_rxd0 <= 1'b1;
     else
      reg_rxd0 <= rxd;
    end 

  6. reg reg_rxd1,reg_rxd2;
    always @(posedge clk or negedge rst_n)
    begin
     if(!rst_n) begin
      reg_rxd1 <= 1'b1;
      reg_rxd2 <= 1'b1;
     end
     else begin
      reg_rxd1 <= reg_rxd0;
      reg_rxd2 <= reg_rxd1;
     end
    end 

  7. wire reg_negedge = reg_rxd2 & (~reg_rxd1);  //下降沿检测

  8. reg bps_start_r;
    reg rx_done_r;   
    always @(posedge clk or negedge rst_n)
    begin
     if(!rst_n) begin
      bps_start_r <= 1'b0;
        rx_done_r <= 1'b0;
       end
     else if(reg_negedge)
      bps_start_r <= 1'b1; //检测到起始位,打开波特率时钟
        else if(state == 4'd9)
      rx_done_r <= 1'b1; //数据接收完成,启动一次数据传输
     else if(state == 4'd10) begin
      bps_start_r <= 1'b0; //一帧数据传输完毕,关闭波特率时钟
            rx_done_r <= 1'b0; //标志位关闭,避免重复传输
        end
    end

  9. assign bps_start = bps_start_r;
    assign rx_done = rx_done_r; //数据传输标志位,拉高,表明rx接收一帧数据完成,tx发送一次该组数据

  10.  

  11. reg[3:0] state;
    always @(posedge clk or negedge rst_n)
    begin
     if(!rst_n)
      state <= 4'b0;
     else if(state == 4'd10)
      state <= 4'b0;   //一帧数据传输完毕,回到初始状态
     else if(bps_clk)     //波特率每个高电平进行状态转移
     begin
      case(state)
       4'd0: state <= 4'd1;
       4'd1: state <= 4'd2;
       4'd2: state <= 4'd3;
       4'd3: state <= 4'd4;
       4'd4: state <= 4'd5;
       4'd5: state <= 4'd6;
       4'd6: state <= 4'd7;
       4'd7: state <= 4'd8;
       4'd8: state <= 4'd9;
       4'd9: state <= 4'd10;
       4'd10: state <= 4'b0;
       default: state <= 4'b0;
      endcase
     end
    end

  12. reg[7:0] data_temp;
    always @(posedge clk or negedge rst_n)
    begin
     if(!rst_n)
     begin
      data_temp <= 8'b0;
     end
     else if(bps_clk)
     begin
      case(state)
       4'd1: data_temp[0] <= rxd;
       4'd2: data_temp[1] <= rxd;
       4'd3: data_temp[2] <= rxd;
       4'd4: data_temp[3] <= rxd;
       4'd5: data_temp[4] <= rxd;
       4'd6: data_temp[5] <= rxd;
       4'd7: data_temp[6] <= rxd;
       4'd8: data_temp[7] <= rxd; //逐位存入数据
      endcase
     end
    end

  13. assign data = data_temp;

  14. endmodule

复制代码

 

  在这个代码文件中,蓝色部分是用来下降沿检测的,前面已经介绍过。红色部分,是一个简单的状态机。FPGA内部相当于硬件电路,都是并行执行,但是,有些逻辑却是有一定顺序的,这时候,我们就需要使用状态机来完成顺序执行。大家发现,红色部分第一个always模块的case结构,状态state是随着波特率的高电平else if(bps_clk)一个一个转移的。而在第二个always模块里,利用每一个状态执行一次数据读入的操作!


  4、数据发送模块:tx.v

  1. module tx(
    input clk,
    input rst_n,
    input bps_clk,
    input rx_done,
    input[7:0] data,

  2. output bps_start,
    output txd
    );

  3. reg bps_start_r;
    always @(posedge clk or negedge rst_n)
    begin
     if(!rst_n)
      bps_start_r <= 1'b0;
     else if(rx_done)
      bps_start_r <= 1'b1;
     else if(state == 4'd11)
      bps_start_r <= 1'b0;
    end

  4. assign bps_start = bps_start_r;

  5. reg[7:0] tx_data;
    always @(posedge clk or negedge rst_n)
    begin
     if(!rst_n)
      tx_data <= 8'b0;
     else if(rx_done)
      tx_data <= data;
    end

  6. reg[3:0] state;
    always @(posedge clk or negedge rst_n)
    begin
     if(!rst_n)
      state <= 4'b0;
     else if(bps_clk)
     begin
      case(state)
       4'd0: state <= 4'd1;
       4'd1: state <= 4'd2;
       4'd2: state <= 4'd3;
       4'd3: state <= 4'd4;
       4'd4: state <= 4'd5;
       4'd5: state <= 4'd6;
       4'd6: state <= 4'd7;
       4'd7: state <= 4'd8;
       4'd8: state <= 4'd9;
       4'd9: state <= 4'd11;
      // 4'd10: state <= 4'd11;
       4'd11: state <= 4'b0;
       default: state <= 4'b0;
      endcase
     end
    end

  7. reg txd_r;
    always @(posedge clk or negedge rst_n)
    begin
     if(!rst_n)
      txd_r <= 1'b1;
     else if(bps_clk)
     begin
      case(state)
       4'd1: txd_r <= 1'b0;
       4'd2: txd_r <= tx_data[0];
       4'd3: txd_r <= tx_data[1];
       4'd4: txd_r <= tx_data[2];
       4'd5: txd_r <= tx_data[3];
       4'd6: txd_r <= tx_data[4];
       4'd7: txd_r <= tx_data[5];
       4'd8: txd_r <= tx_data[6];
       4'd9: txd_r <= tx_data[7];
      // 4'd10: txd_r <= 1'b1; //crc
       4'd11: txd_r <= 1'b1; //stop
      endcase
     end
    end

  8. assign txd = txd_r;

  9. endmodule

复制代码

 

  发送模块和接收模块是非常相似的,这里就不重复介绍了。


  3. 调试前准备

  对于我们这个串口调试:
  1、我们需要准备一个USB数据线,是标准的一边小头,一边大头的那种,我想,很多MP3,手机都是采用这种数据线的,很好找。
  2、安装串口转USB的驱动程序。
  3、安装一个串口调试助手。
这些,我们都将在帖子里附件打包。
  4. 上电调试
 分配好管脚,下载代码,打开串口大师如下图:当上面的窗口能显示出下面窗口我们发送的内容,就算是调试成功了O(∩_∩)O~!
 


  5. 总结

  这次实验,我们学习了简单的串口RS232的通信时序和逻辑设计。也是我们第一次用到了模块调用,第一次设计了一个相对长一些的代码。

[ 本帖最后由 xieqiang 于 2011-5-12 11:37 编辑 ]

最新回复

感谢楼主分享  详情 回复 发表于 2017-12-29 20:02
点赞 关注(4)
个人签名生活就是油盐酱醋再加一点糖,快活就是一天到晚乐呵呵的忙
===================================
做一个简单的人,踏实而务实,不沉溺幻想,不庸人自扰
 

回复
举报

5979

帖子

8

TA的资源

版主

沙发
 

EE_FPGA基础教程系列 -- NO.5--串口调试

封面及版本信息

 

 

 

相关资料下载

 

串口调试.rar (2.49 MB, 下载次数: 2280)

[ 本帖最后由 xieqiang 于 2011-5-12 11:36 编辑 ]
 
 

回复

6066

帖子

93

TA的资源

裸片初长成(初级)

板凳
 
顶一个,很详细呀!
 
 
 

回复

7172

帖子

195

TA的资源

五彩晶圆(高级)

4
 
顶顶 很详细 很清晰 不过还没开始学FPGA  可惜啊
 
 
 

回复

602

帖子

0

TA的资源

一粒金砂(中级)

5
 
很好,很详细,可是我的套件还没有给我邮过来呢!
个人签名白天图生存,晚上谋发展!!!
 
 
 

回复

5979

帖子

8

TA的资源

版主

6
 
呵呵 你是哪位啊
非常抱歉!
个人签名生活就是油盐酱醋再加一点糖,快活就是一天到晚乐呵呵的忙
===================================
做一个简单的人,踏实而务实,不沉溺幻想,不庸人自扰
 
 
 

回复

12

帖子

0

TA的资源

一粒金砂(中级)

7
 
谢谢楼主分享,学习了,受益匪浅啊,
 
 
 

回复

996

帖子

0

TA的资源

一粒金砂(高级)

8
 
能不能增加VHDL的啊!~
 
 
 

回复

5979

帖子

8

TA的资源

版主

9
 
能 会陆续出来的 呵呵
个人签名生活就是油盐酱醋再加一点糖,快活就是一天到晚乐呵呵的忙
===================================
做一个简单的人,踏实而务实,不沉溺幻想,不庸人自扰
 
 
 

回复

2

帖子

0

TA的资源

一粒金砂(初级)

10
 

顶 呵呵

太棒了
 
 
 

回复

67

帖子

0

TA的资源

一粒金砂(中级)

11
 
我也来顶一个
 
 
 

回复

31

帖子

0

TA的资源

一粒金砂(中级)

12
 
谢谢楼主分享,学习了,受益匪浅啊,
 
 
 

回复

1

帖子

0

TA的资源

一粒金砂(初级)

13
 
谢谢!!!!!!附件呢????????
 
 
 

回复

2万

帖子

74

TA的资源

管理员

14
 

回复 13楼 wangalong 的帖子

仔细看1楼 2楼
加EE小助手好友,
入技术交流群
EE服务号
精彩活动e手掌握
EE订阅号
热门资讯e网打尽
聚焦汽车电子软硬件开发
认真关注技术本身
个人签名

加油!在电子行业默默贡献自己的力量!:)

 
 
 

回复

108

帖子

0

TA的资源

一粒金砂(中级)

15
 
谢了
 
 
 

回复

80

帖子

0

TA的资源

一粒金砂(中级)

16
 
留名慢慢看
 
 
 

回复

80

帖子

0

TA的资源

一粒金砂(中级)

17
 
求证一下,这个波特率应该是50/435吧
 
 
 

回复

34

帖子

0

TA的资源

一粒金砂(中级)

18
 

回复 17楼 stepan 的帖子

对的,很细心,原文写错了
 
 
 

回复

83

帖子

0

TA的资源

一粒金砂(中级)

19
 
为什么我的串口输出全是乱码····
 
 
 

回复

83

帖子

0

TA的资源

一粒金砂(中级)

20
 
而且奇怪的是我用signaltap采数竟然啥也没有··用示波器看是有的·····
 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/10 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表