reg[BaudGeneratorAccWidth:
0]BaudGeneratorAcc;
always@(posedgeclk)
BaudGeneratorAcc<=BaudGeneratorAcc[BaudGeneratorAccWidth-1:
0]+BaudGeneratorInc;
wireBaudTick=BaudGeneratorAcc[BaudGeneratorAccWidth];
当要注意的是,上面程序中BaudGeneratorInc的计算公式出错,因为在Verilog语言中中间结果只能32位,而这个公式计算的结果超过了32位。
所以要把这行改为
需注意计算结果不能超过32位这一要求,应适当调整移位的位数使得中间的结果不会超过32位
parameterBaudGeneratorInc=((Baud<<(BaudGeneratorAccWidth-4))+(ClkFrequency>>5))/(ClkFrequency>>4);
程序改变,得到的波特率不变。
.0555********
RS232发送接收模块
RS-232发送模块
下面是我们所想要实现的:
它应该能像这样工作:
∙发送器接收8位的数据,并将其串行输出。
("TxD_start"置位后开始传输).
∙当有数传输的时候,使"busy"信号有效,此时“TxD_start”信号被忽略.
RS-232模块的参数是固定的:
8位数据,2个停止位,无奇偶校验.
数据串行化
假设我们已经有了一个115200波特的"BaudTick"信号.
我们需要产生开始位、8位数据以及停止位。
用状态机来实现看起来比较合适。
reg[3:
0]state;
always@(posedgeclk)
case(state)
4'b0000:
if(TxD_start)state<=4'b0100;
4'b0100:
if(BaudTick)state<=4'b1000;//开始位
4'b1000:
if(BaudTick)state<=4'b1001;//bit0
4'b1001:
if(BaudTick)state<=4'b1010;//bit1
4'b1010:
if(BaudTick)state<=4'b1011;//bit2
4'b1011:
if(BaudTick)state<=4'b1100;//bit3
4'b1100:
if(BaudTick)state<=4'b1101;//bit4
4'b1101:
if(BaudTick)state<=4'b1110;//bit5
4'b1110:
if(BaudTick)state<=4'b1111;//bit6
4'b1111:
if(BaudTick)state<=4'b0001;//bit7
4'b0001:
if(BaudTick)state<=4'b0010;//停止位1
4'b0010:
if(BaudTick)state<=4'b0000;//停止位2
default:
if(BaudTick)state<=4'b0000;
endcase
注意看这个状态机是怎样实现当"TxD_start"有效就开始,但只在"BaudTick"有效的时候才转换状态的。
.
现在,我们只需要产生"TxD"输出即可.
regmuxbit;
always@(state[2:
0])
case(state[2:
0])
0:
muxbit<=TxD_data[0];
1:
muxbit<=TxD_data[1];
2:
muxbit<=TxD_data[2];
3:
muxbit<=TxD_data[3];
4:
muxbit<=TxD_data[4];
5:
muxbit<=TxD_data[5];
6:
muxbit<=TxD_data[6];
7:
muxbit<=TxD_data[7];
endcase
//将开始位、数据以及停止位结合起来
assignTD=(state<4)|(state[3]&muxbit);
完整的代码在这里可以得到。
RS232接收模块
下面是我们想要实现的模块:
我们的设计目的是这样的:
1.当RxD线上有数据时,接收模块负责识别RxD线上的数据
2.当收到一个字节的数据时,锁存接收到的数据到"data"总线,并使"data_ready"有效一个周期。
注意:
只有当"data_ready"有效时,"data"总线的数据才有效,其他的时间里不要使用"data"总线上的数据,因为新的数据可能已经改变了其中的部分数据。
过采样
异步接收机必须通过一定的机制与接收到的输入信号同步(接收端没有办法得到发送断的时钟)。
这里采用如下办法。
1.为了确定新数据的到来,即检测开始位,我们使用几倍于波特率的采样时钟对接收到的信号进行采样。
2.一旦检测到"开始位",再将采样时钟频率降为已知的发送端的波特率。
典型的过采样时钟频率为接收到的信号的波特率的16倍,这里我们使用8倍的采样时钟。
当波特率为115200时,采样时钟为921600Hz。
假设我们已经有了一个8倍于波特率的时钟信号"Baud8Tick",其频率为921600Hz。
具体设计
首先,接受到的"RxD"信号与我们的时钟没有任何关系,所以采用两个D触发器对其进行过采样,并且使之我我们的时钟同步。
reg[1:
0]RxD_sync;
always@(posedgeclk)if(Baud8Tick)RxD_sync<={RxD_sync[0],RxD};
首先我们对接收到的数据进行滤波,这样可以防止毛刺信号被误认为是开始信号。
reg[1:
0]RxD_cnt;
regRxD_bit;
always@(posedgeclk)
if(Baud8Tick)
begin
if(RxD_sync[1]&&RxD_cnt!
=2'b11)RxD_cnt<=RxD_cnt+1;
else
if(~RxD_sync[1]&&RxD_cnt!
=2'b00)RxD_cnt<=RxD_cnt-1;
if(RxD_cnt==2'b00)RxD_bit<=0;
else
if(RxD_cnt==2'b11)RxD_bit<=1;
end
一旦检测到"开始位",使用如下的状态机可以检测出接收到每一位数据。
reg[3:
0]state;
always@(posedgeclk)
if(Baud8Tick)
case(state)
4'b0000:
if(~RxD_bit)state<=4'b1000;//startbitfound?
4'b1000:
if(next_bit)state<=4'b1001;//bit0
4'b1001:
if(next_bit)state<=4'b1010;//bit1
4'b1010:
if(next_bit)state<=4'b1011;//bit2
4'b1011:
if(next_bit)state<=4'b1100;//bit3
4'b1100:
if(next_bit)state<=4'b1101;//bit4
4'b1101:
if(next_bit)state<=4'b1110;//bit5
4'b1110:
if(next_bit)state<=4'b1111;//bit6
4'b1111:
if(next_bit)state<=4'b0001;//bit7
4'b0001:
if(next_bit)state<=4'b0000;//stopbit
default:
state<=4'b0000;
endcase
注意,我们使用了"next_bit"来遍历所有数据位。
reg[2:
0]bit_spacing;
always@(posedgeclk)
if(state==0)
bit_spacing<=0;
else
if(Baud8Tick)
bit_spacing<=bit_spacing+1;
wirenext_bit=(bit_spacing==7);
最后我们使用一个移位寄存器来存储接受到的数据。
reg[7:
0]RxD_data;
always@(posedgeclk)if(Baud8Tick&&next_bit&&state[3])RxD_data<={RxD_bit,RxD_data[7:
1]};
RS232接收模块(Verilog)
以上程序均标注了J
调用串口发送接收模块
`timescale1ns/1ps
moduleserialfun(clk,RxD,TxD);
inputclk; //系统时钟
inputRxD;
outputTxD;
//////////////////////////////////////////////////
wireRxD_data_ready;
wire[7:
0]RxD_data;
async_receiverdeserializer( //RS-232接收模块
.clk(clk),
.RxD(RxD),
.RxD_data_ready(RxD_data_ready),
//当接收到一个字节的数据时,"RxD_data_ready"有效一个周期
.RxD_data(RxD_data) //接收一个字节数据
);
///////////////////////////////////////////////////
async_transmitterserializer( //RS-232发送模块
.clk(clk),
.TxD(TxD),
.TxD_start(RxD_data_ready),
//"TxD_start"置位后开始传输
.TxD_data(RxD_data) //发送一个字节数据
);
endmodule
这个程序的结果是在从计算机发送八个字节到FPGA,FPGA再把这八个字节转发回计算机。
要注意是如果以十六进制发送的话,就要以十六进制显示,8个字节可以发送2个字符(0~F)。
如果没选以十六进制发送的话,会以ASCII码发送,只能发送一个字符(一个字符的ASCII有8个字节)。
在Spartan3EStarterKit开发板上有两个串口,所以设置管脚时要注意选择哪个串口,选择母头的话(DCE)与计算机相连的串口线选择交叉的公母线;选择公头的话(DTE),与计算机相连的串口线选择交叉的母母线。
我选择了公头,UCF文件如下(约束管脚)
NET"clk" LOC="C9"|IOSTANDARD=LVCMOS33;
NET"RxD" LOC="U8"|IOSTANDARD=LVTTL;
NET"TxD" LOC="M13"|IOSTANDARD=LVTTL |DRIVE=8 |SLEW=SLOW;
本文XilinxISE工程文件(在Spartan3EStarterKit开发板上实现)
参考资料:
1) 什么是波特率,比特率,调制速率?
2)Serialinterface(RS-232)