NEXYS 3开发板练手--USB UART(三)

  接着上一篇,今天我们来建立一个能用于实际工程中的DEMO。

  首先,为了使我们的发送机不像上一个DEMO一样无节制的循环发送,我们需要修改代码,增加使发送机停止发送的控制部分,修改后的代码如下:

  1 `timescale 1ns / 1ps
  2 //////////////////////////////////////////////////////////////////////////////////
  3 // Company: 
  4 // Engineer:         lwy
  5 // 
  6 // Create Date:    16:00:47 11/11/2013 
  7 // Design Name:    usb-uart tx device for NEXYS3
  8 // Module Name:    UART_CTRL 
  9 // Project Name: 
 10 // Target Devices: 
 11 // Tool versions: 
 12 // Description:This component may be used to transfer data over a UART device. It will
 13 //                    serialize a byte of data and transmit it over a TXD line. The serialized
 14 //                 data has the following characteristics:
 15 //                         *9600 Baud Rate
 16 //                         *8 data bits, LSB first
 17 //                         *1 stop bit
 18 //                         *no parity
 19 //
 20 // Dependencies: 
 21 // Port Descriptions:
 22 //                            clk           -- 外部100M时钟输入;
 23 //                            data          -- 需要传输的数据,8位;
 24 //                            send      -- 发送使能端,低电平开始一个字符传输;
 25 //                            tx        -- 向uart device发送的串行数据;
 26 //                            busy          -- 线路状态标志,高时表示线路忙,低时表示线路空闲
 27 // Revision: 
 28 // Revision 0.01 - File Created
 29 // Additional Comments: 
 30 //
 31 //////////////////////////////////////////////////////////////////////////////////
 32 module UART_CTRL(
 33     clk,
 34     data,
 35     send,
 36     tx,
 37     busy
 38     );
 39 
 40 input clk,send;
 41 input [7:0] data;
 42 output reg tx,busy;
 43 
 44 //状态机状态定义
 45 parameter Idel = 2'b00,//空闲状态
 46              Rdy = 2'b01,//数据准备完成
 47              LoadByte = 2'b10,//数据传入
 48              SendBit = 2'b11;//数据发送
 49 
 50 reg [13:0] BspClkReg;//波特率分频计数
 51 reg BspClk;//波特率时钟
 52 
 53 reg [9:0] tx_data;//发送的数据,加上起始位和停止位
 54 reg [3:0] tx_byte_count;//发送位数计数
 55 
 56 reg [1:0] state;//状态寄存器
 57 
 58 
 59 //波特率分频模块,波特率:9600
 60 always@(posedge clk)
 61 begin
 62     BspClkReg <= BspClkReg + 1;
 63     if(BspClkReg == 5208)
 64     begin
 65         BspClkReg <= 0;
 66         BspClk <= ~BspClk;
 67     end
 68 end
 69 //串口发送模块
 70 always@(posedge BspClk)
 71 begin
 72     case(state)
 73         Idel         : begin
 74                         tx <= 1;
 75                         busy <= 0;
 76                         tx_byte_count <= 0;
 77                         if(send) state <= Rdy;
 78                         end
 79         Rdy          : begin
 80                         if(~send)    state <= Idel;
 81                         else begin
 82                             tx_byte_count <= 0;
 83                             tx <= 1;
 84                             busy <= 1;
 85                             state <= LoadByte; 
 86                             end
 87                         end
 88         LoadByte    : begin
 89                         if(~send)    state <= Idel;
 90                         else begin
 91                             tx_data <= {1'b1,data,1'b0};
 92                             tx <= 1;
 93                             busy <= 1;
 94                             state <= SendBit;
 95                             end
 96                         end
 97         SendBit    : begin
 98                         if(~send)    state <= Idel;
 99                         else begin
100                             tx <= tx_data[0];
101                             busy <= 1;
102                             tx_data <= tx_data >> 1;
103                             tx_byte_count <= tx_byte_count + 1;
104                             if(tx_byte_count == 9)
105                                 state <= Idel;
106                             else
107                                 state <= SendBit;
108                             end
109                         end
110     endcase
111 end
112 
113 endmodule
View Code

   对比上一个DEMO中的状态机部分,我们只是在空闲状态外的每个状态中增加了当send信号无效时使状态机返回空闲状态的控制代码,这样我们就可以通过这个send信号来控制是否使发送机发送信息。

  接下来我们要做的工作就是怎么有效的向发送机输送我们需要发送的信息,并控制发送机正确的运转。要做到这一点,就需要发送机有一个反馈信号,告诉发送控制模块是否已经完成了一次发送工作。其实,我们仔细再看上面的代码,会发现有一个信号我们一直没有管它,那就是busy信号,这个信号就是考虑到这一点而做的准备工作。当发送机处在空闲状态,即不发送信息时,它是低电平,其他时候是高电平,也就是说当我们完成了一次发送工作(一个字符发送完成)的时候,会有一个下降沿。我们的发送控制部分即可捕捉这个下降沿来改变发送机输入端的信息,从而控制发送机发送下一个需要发送的不同字符。这一部分的控制代码如下:

  1 `timescale 1ns / 1ps
  2 //////////////////////////////////////////////////////////////////////////////////
  3 // Company: 
  4 // Engineer:         lwy
  5 // 
  6 // Create Date:    16:00:47 11/11/2013 
  7 // Design Name:    usb-uart tx device for NEXYS3
  8 // Module Name:    usbuart 
  9 // Project Name: 
 10 // Target Devices: 
 11 // Tool versions: 
 12 // Description:    这个模块用来控制向UartCtrl模块的写入字符串数据
 13 //
 14 // Dependencies: 
 15 // Port Descriptions:    CLK    --    系统时钟;
 16 //                                reset -- 系统复位信号
 17 //                                btn    -- 字符串发送命令,高电平有效,这里接入一个button
 18 //                                TxBit    -- 串行字符输出,接UartCtrl模块的tx端,即最终串行数据通过这个端口相device传送
 19 // Revision: 
 20 // Revision 0.01 - File Created
 21 // Additional Comments: 请注意strLen、string这两个常数,如果要改变发送的字符串内容,只需将string赋值为需要发送
 22 //                                的字符串,将strLen赋值为发送字符串长度即可。
 23 //
 24 //////////////////////////////////////////////////////////////////////////////////
 25 module usbuart(
 26     CLK,
 27     reset,
 28     btn,
 29     TxBit
 30     );
 31 
 32 input CLK,reset,btn;
 33 output TxBit;
 34 
 35 //////////////////////////////////////////////////////////////////////////////////
 36 parameter strLen = 15; //字符串长度
 37 parameter string = "Hello USB_UART!";//字符串数据
 38 //////////////////////////////////////////////////////////////////////////////////
 39 
 40 
 41 reg [7:0] TxData;//当前发送的数据
 42 reg send_n;//发送使能,当启动一次数据发送且没有收到UART_CTRL数据发送完成的信号是,因该将它一直保持为高电平
 43 wire lock;//控制数据的改变,当UART_CTRL数据线“忙”(busy为高电平)时,应禁止改变数据,而且send_n应该一直为高
 44 reg [7:0] charCount;//当前发送字符数,一次最多发送255个字符
 45 reg [11:0] Index;//当前字符位索引
 46 
 47 reg [8*strLen:0] str_UART;//存储字符代码
 48 
 49 //状态定义
 50 parameter s0 = 2'b00,s1 = 2'b01,s2 = 2'b10,s3 = 2'b11; 
 51 
 52 reg [1:0] state;//状态变量
 53 wire trigger;//触发信号,由固定脉冲触发模块输出
 54 wire trigger_n;//将高电平触发信号改为低电平
 55 assign trigger_n = ~trigger;
 56 
 57 
 58 //发送字符控制
 59 always@(negedge trigger_n or negedge lock)
 60 begin
 61     if(~trigger_n) //低电平触发发送
 62     begin 
 63         str_UART = string;//初始化字符串
 64         state = s1;
 65         TxData = 10;//\n
 66         send_n = 1;
 67         charCount = strLen;
 68     end
 69     else begin
 70     case(state)
 71         s0 : begin
 72                     TxData = 0;
 73                     send_n = 0;                
 74                 end
 75         s1 : begin
 76                     state = s2;
 77                     TxData = 13;//\r
 78                     send_n =1;
 79                 end
 80         s2 : begin
 81                     Index = charCount * 8 - 1;
 82                     TxData = str_UART[Index-:8];//发送字符串
 83                     send_n = 1;
 84                     charCount = charCount - 1;
 85                     if(charCount == 0)
 86                         state = s0;
 87                     else
 88                         state = s2;
 89                 end
 90         default : state = s0;
 91     endcase
 92     end
 93 end
 94 
 95 //调用UART_CTRL模块
 96 UART_CTRL UartCtrl(
 97                         .clk(CLK),
 98                         .data(TxData[7:0]),
 99                         .send(send_n),
100                         .tx(TxBit),
101                         .busy(lock)
102                         );
103 //调用PulTri模块,产生稳定的触发信号
104 PulTri pulse(
105                 .clk(CLK),
106                 .reset_n(~reset),
107                 .start(btn),
108                 .pulse(trigger)
109                 );
110 
111 endmodule
View Code

   发送字符控制部分由两个信号触发,分别是trigger_n和lock,trigger_n连接按键信号取反后的信号(开发板上的按键按下为高电平),lock连接反馈busy信号。按键按下后触发一次字符串的发送,首先发送换行符(\n\r),然后根据字符串字符个数的设置,循环传送相关字符的ASCII码,这时候状态机的运转靠反馈busy信号触发。当一次字符串发送完成后,将send置为无效,这样发送机停止运转,反馈busy信号也就无效了,开始等待下一个trigger_n信号的触发。通过这三个信号相互配合就能完成一个字符串的发送。

  问题到这仍然还有,因为我们这个trigger_n检测的是按键电平信号,我们知道一次按键按下,速度再快也有几百毫秒的时间,也就是说这个电平信号会持续上百毫秒甚至几秒的时间,而且这个时间通常是不受控制的。这样的触发信号在我们这个模块中是不能接受的。我们需要的这个触发信号每次持续的有效时间不能超过1/9600秒,否则整个时序控制就会错乱,发送状态机接下来的工作会是一种无法预知的状态。因此,我们需要改造这个触发信号,即无论按键按下的时间有多长,最后得到的触发信号的宽度是一定的,或则说我们利用的是按键的边沿信号。为此,我设计了一个能产生稳定触发信号的模块,也上面的代码中例化的PulTri这个模块。这个模块的设计代码如下:

 1 `timescale 1ns / 1ps
 2 //////////////////////////////////////////////////////////////////////////////////
 3 // Company: 
 4 // Engineer:         lwy
 5 // 
 6 // Create Date:    23:02:53 11/12/2013 
 7 // Design Name: 
 8 // Module Name:    PulTri 
 9 // Project Name:      
10 // Target Devices: 
11 // Tool versions: 
12 // Description: 这个模块用来产生pulsewide个时钟宽度的脉冲(高电平)而不关心触发信号脉冲宽度是多少,该模块的输出脉冲宽度固定为pulsewide个时钟周期
13 //
14 // Port Descriptions:    clk     --  系统时钟,他关系到最后的输出脉冲宽度
15 //                                reset_n --    系统复位信号,低电平复位
16 //                                start      --    触发信号端口,高电平触发
17 //                                pulse      --    输出脉冲,该脉冲宽度固定为
18 //                                
19 //
20 // Dependencies: 
21 //
22 // Revision: 
23 // Revision 0.01 - File Created
24 // Additional Comments: 
25 //                                pulsewide这里定义为一常数5,可以根据需要调整
26 //
27 //////////////////////////////////////////////////////////////////////////////////
28 module PulTri(
29               clk,
30               reset_n,
31               start,
32               pulse
33               );
34               
35 input clk,reset_n,start;
36 output reg pulse;
37 
38 parameter pulsewide = 50;//调整触发脉冲的宽度,pulsewide*clk
39 
40 reg counten;
41 reg [7:0] count;
42 
43 initial
44 begin
45   counten <= 0;
46   count <= 0;
47   pulse <= 0;
48 end
49 
50  //计数器启动标记,表示一次延时计数开始
51 always @ ( posedge clk )
52 begin
53 if ( reset_n == 1'b0 )
54   counten <= 1'b0;
55 else
56   begin
57   if ( start == 1'b1 )
58    counten <= 1'b1;
59   else if ( start == 1'b0 && count > pulsewide )
60    counten <= 1'b0;
61   end
62 end
63  
64  //延时计数器,保证延时 pulsewide 个时钟周期
65 always @ ( posedge clk )
66 begin
67 if ( reset_n == 1'b0 )
68   count <= 0;
69 else
70   begin
71   if ( counten == 1'b0 )
72    count <= 0;
73   else if ( counten == 1'b1 && count <= pulsewide )
74    count <= count + 1;
75   else if ( counten == 1'b0 && start == 1'b0 )
76    count <= 0;
77   end
78  end
79  
80  //输出定宽脉冲
81  always @ ( negedge clk )
82  begin
83  if ( reset_n == 'b0 || count >= pulsewide )
84   pulse <= 1'b0;
85  else if ( counten == 1'b1 )
86    pulse <= 1'b1;
87  end
88 
89 endmodule
View Code

  为了验证它是否达到了我们预先的目的,我在modelsim中进行了仿真,得到下面的波形图:

      

  start信号是我们的随机触发信号(在这个DEMO中即是按键信号),我们发现无论这个start信号的脉宽是多少,最后得到的pulse信号的宽度都是一定的,在上面的代码中我们知道它的宽度为pulsewide*clk,是我们可以设定的(仿真中pulsewide为5),说明这个模块达到了我们预先的目的。其实,这个模块在实际的应用中是很有价值的,我们可以理解为它将一种非理想的状况转换成了一种接近理想的状况。

  最后,我们的将编译生成的bit数据流文件下载进板子中,能在PC的超级终端(或则串口调试助手)中得到下面的结果:

                    

  可见,上面的DEMO与上一篇文章中的相比已经有了很大的改观,这是一个真正有实用价值的DEMO!如果我们像Quartus中一样将它做成一个SOPC嵌入式IP核,将顶层模块中的string和strLen这两个常数改成能用软件设置的寄存器,这样我们就能在软件中编程完成各种字符的发送工作,这是不是很有意义呢!

posted @ 2014-01-16 15:11  Mr.想太多  阅读(1107)  评论(12编辑  收藏  举报