接着上一篇,今天我们来建立一个能用于实际工程中的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
对比上一个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
发送字符控制部分由两个信号触发,分别是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
为了验证它是否达到了我们预先的目的,我在modelsim中进行了仿真,得到下面的波形图:
start信号是我们的随机触发信号(在这个DEMO中即是按键信号),我们发现无论这个start信号的脉宽是多少,最后得到的pulse信号的宽度都是一定的,在上面的代码中我们知道它的宽度为pulsewide*clk,是我们可以设定的(仿真中pulsewide为5),说明这个模块达到了我们预先的目的。其实,这个模块在实际的应用中是很有价值的,我们可以理解为它将一种非理想的状况转换成了一种接近理想的状况。
最后,我们的将编译生成的bit数据流文件下载进板子中,能在PC的超级终端(或则串口调试助手)中得到下面的结果:
可见,上面的DEMO与上一篇文章中的相比已经有了很大的改观,这是一个真正有实用价值的DEMO!如果我们像Quartus中一样将它做成一个SOPC嵌入式IP核,将顶层模块中的string和strLen这两个常数改成能用软件设置的寄存器,这样我们就能在软件中编程完成各种字符的发送工作,这是不是很有意义呢!