verilog串口通信程序
发布时间:2024-11-21
发布时间:2024-11-21
FPGA实现RS-232串口收发的仿真过程(Quartus+Synplify+ModelSim)(2007-09-11
12:17:37)
就结合网上的参考资料和自己的琢磨,做了这个东西。
针对我这个小程序结合FPGA的开发流程,主要走了以下几步: 1. 文本程序输入(Verilog HDL)
2. 功能仿真(ModelSim,查看逻辑功能是否正确,要写一个Test Bench) 3. 综合(Synplify Pro,程序综合成网表)
网上关于RS-232的异步收发介绍得很多,最近没事学着摸索用ModelSim来做时序仿真,
4. 布局布线(Quartus II,根据我选定的FPGA器件型号,将网表布到器件中,并估算出相应的时延)
5. 时序仿真(ModelSim,根据时延做进一步仿真)
这里贴出我的程序和各个详细步骤,能和各位正在学习的新手们一起分享。 0. 原理 略
一、文本程序输入(Verilog HDL) 发送端: module trans(clk, rst, TxD_start, TxD_data, TxD, TxD_busy ); input clk, rst, TxD_start;
input[7:0] TxD_data; // 待发送的数据 output TxD, // 输出端口发送的串口数据 TxD_busy; reg TxD;
reg [7:0] TxD_dataReg; // 寄存器发送模式,因为在串口发送过程中输入端不可能一直保持有效电平 reg [3:0] state;
parameter ClkFrequency = 25000000; // 时钟频率-25 MHz
parameter Baud = 115200; // 串口波特率-115200
// 波特率产生
parameter BaudGeneratorAccWidth = 16;
reg [BaudGeneratorAccWidth:0] BaudGeneratorAcc; wire
[BaudGeneratorAccWidth:0]
BaudGeneratorInc
=
((Baud<<(BaudGeneratorAccWidth-4))+(ClkFrequency>>5))/(ClkFrequency>>4); wire BaudTick = BaudGeneratorAcc[BaudGeneratorAccWidth]; wire TxD_busy;
always @(posedge clk or negedge rst) if(~rst)
BaudGeneratorAcc <= 0; else if(TxD_busy)
BaudGeneratorAcc <= BaudGeneratorAcc[BaudGeneratorAccWidth-1:0] + BaudGeneratorInc; // 发送端状态
wire TxD_ready = (state==0); // 当state = 0时,处于准备空闲状态,TxD_ready = 1 assign TxD_busy = ~TxD_ready; // 空闲状态时TxD_busy = 0 // 把待发送数据放入缓存寄存器 TxD_dataReg always @(posedge clk or negedge rst) if(~rst)
TxD_dataReg <= 8'b00000000; else if(TxD_ready & TxD_start) TxD_dataReg <= TxD_data;
// 发送状态机
always @(posedge clk or negedge rst) if(~rst) begin
state <= 4'b0000; // 复位时,状态为0000,发送端一直发1电平 TxD <= 1'b1; end else case(state)
4'b0000: if(TxD_start) begin
state <= 4'b0100; // 接受到发送信号,进入发送状态 end
4'b0100: if(BaudTick) begin
state <= 4'b1000; // 发送开始位 - 0电平 TxD <= 1'b0; end 4'b1000: if(BaudTick) begin
state <= 4'b1001; // bit 0 TxD <= TxD_dataReg[0]; end 4'b1001: if(BaudTick) begin
state <= 4'b1010; // bit 1 TxD <= TxD_dataReg[1]; end
4'b1010: if(BaudTick) begin
state <= 4'b1011; // bit 2 TxD <= TxD_dataReg[2]; end 4'b1011: if(BaudTick) begin
state <= 4'b1100; // bit 3 TxD <= TxD_dataReg[3]; end 4'b1100: if(BaudTick) begin
state <= 4'b1101; // bit 4 TxD <= TxD_dataReg[4]; end 4'b1101: if(BaudTick) begin
state <= 4'b1110; // bit 5 TxD <= TxD_dataReg[5]; end 4'b1110: if(BaudTick) begin
state <= 4'b1111; // bit 6 TxD <= TxD_dataReg[6]; end 4'b1111: if(BaudTick) begin
state <= 4'b0010; // bit 7 TxD <= TxD_dataReg[7]; end 4'b0010: if(BaudTick) begin
state <= 4'b0011; // stop1
TxD <= 1'b1; end 4'b0011: if(BaudTick) begin
state <= 4'b0000; // stop2 TxD <= 1'b1; end default: if(BaudTick) begin state <= 4'b0000; TxD <= 1'b1; end endcase endmodule 接收端: module rcv(clk, rst, RxD, RxD_data, RxD_data_ready, ); input clk, rst, RxD;
output[7:0] RxD_data; // 接收数据寄存器
output RxD_data_ready; // 接收完8位数据,RxD_data 值有效时,RxD_data_ready 输出读信号
parameter ClkFrequency = 25000000; // 时钟频率-25MHz parameter Baud = 115200; // 波特率-115200 reg[2:0] bit_spacing; reg RxD_delay; reg RxD_start; reg[3:0] state; reg[7:0] RxD_data; reg RxD_data_ready;
// 波特率产生,使用8倍过采样 parameter Baud8 = Baud*8;
parameter Baud8GeneratorAccWidth = 16; wire
[Baud8GeneratorAccWidth:0]
Baud8GeneratorInc
((Baud8<<(Baud8GeneratorAccWidth-7))+(ClkFrequency>>8))/(ClkFrequency>>7); reg [Baud8GeneratorAccWidth:0] Baud8GeneratorAcc; always @(posedge clk or negedge rst) if(~rst)
Baud8GeneratorAcc <= 0; else
Baud8GeneratorAcc
<=
Baud8GeneratorAcc[Baud8GeneratorAccWidth-1:0]
Baud8GeneratorInc;
// Baud8Tick 为波特率的8倍 - 115200*8 = 921600
wire Baud8Tick = Baud8GeneratorAcc[Baud8GeneratorAccWidth]; // next_bit 为波特率 - 115200 always @(posedge clk or negedge rst) if(~rst||(state==0)) bit_spacing <= 0; else if(Baud8Tick)
bit_spacing <= bit_spacing + 1; wire next_bit = (bit_spacing==7);
// 检测到 RxD 有下跳沿时,RxD_start 置1,准备接收数据 always@(posedge clk) if(Baud8Tick) begin
RxD_delay <= RxD;
RxD_start <= (Baud8Tick & RxD_delay & (~RxD)); end
// 状态机接收数据
always@(posedge clk or negedge rst) if(~rst)
state <= 4'b0000; else if(Baud8Tick) case(state)
=
+
4'b0000: if(RxD_start) state <= 4'b1000; // 检测到下跳沿 4'b1000: if(next_bit) state <= 4'b1001; // bit 0 4'b1001: if(next_bit) state <= 4'b1010; // bit 1 4'b1010: if(next_bit) state <= 4'b1011; // bit 2 4'b1011: if(next_bit) state <= 4'b1100; // bit 3 4'b1100: if(next_bit) state <= 4'b1101; // bit 4 4'b1101: if(next_bit) state <= 4'b1110; // bit 5 4'b1110: if(next_bit) state <= 4'b1111; // bit 6 4'b1111: if(next_bit) state <= 4'b0001; // bit 7 4'b0001: if(next_bit) state <= 4'b0000; // 停止位 default: state <= 4'b0000; endcase
// 保存接收数据到 RxD_data 中 always @(posedge clk or negedge rst) if(~rst)
RxD_data <= 8'b00000000;
else if(Baud8Tick && next_bit && state[3]) RxD_data <= {RxD, RxD_data[7:1]}; // RxD_data_ready 置位信号 always @(posedge clk or negedge rst) if(~rst)
RxD_data_ready <= 0; else
RxD_data_ready <= (Baud8Tick && next_bit && state==4'b0001); endmodule
为了测试收发是否正常,写的Test Bench `timescale 1ns / 1ns module rs232_test; reg clk, rst, TxD_start; reg [7:0] TxD_data; wire[7:0] RxD_data;
wire //RxD, TxD, TxD_busy, RxD_data_ready;
trans trans(.clk(clk), .rst(rst),
.TxD_start(TxD_start), .TxD_busy(TxD_busy), .TxD_data(TxD_data), .TxD(TxD) );
rcv rcv(.clk(clk), .rst(rst),
.RxD(TxD), // 收发相接时 RxD = TxD .RxD_data(RxD_data),
.RxD_data_ready(RxD_data_ready) );
initial begin TxD_start = 0; TxD_data = 0; clk = 0; rst = 1; #54 rst = 0; #70 rst = 1;
#40 TxD_start = 1'b1; #10 TxD_data = 8'b11011001; #100 TxD_start = 1'b0; end always begin #30 clk = ~clk; #10 clk = ~clk; end endmodule
二、综合
三、
FPGA与PC串口自收发通信
串口通信其实简单实用,这里我就不多说,只把自己动手写的verilog代码共享下。实
现的功能如题,就是FPGA里实现从PC接收数据,然后把接收到的数据发回去。使用的是串口UART协议进行收发数据。上位机用的是老得掉牙的串口调试助手,如下:
发送数据的波特率可选9600bps,19200bps,38400bps,57600bps,115200bps等,是可调的。
发送格式为:1bit起始位,8bit数据,1bit停止位,无校验位。以下的代码有比较详细的注释,经过下载验证,存在误码率(<5%),仅供学习!
代码如下: (顶层模块):
module my_uart_top(clk,rst_n,rs232_rx,rs232_tx);
input clk;
// 50MHz主时钟
// RS232接收数据信号
RS232发送数据信号
input rst_n; //低电平复位信号 input rs232_rx; output rs232_tx; //
wire bps_start; wire clk_bps;
//接收到数据后,波特率时钟启动信号置位
// clk_bps的高电平为接收或者发送数据位的中间采样点
wire[7:0] rx_data; //接收数据寄存器,保存直至下一个数据来到 wire rx_int; //接收数据中断信号,接收到数据期间始终为高电平 //---------------------------------------------------- speed_select 全双工通信
my_uart_rx
my_uart_tx
endmodule
module speed_select(clk,rst_n,bps_start,clk_bps);
input clk;
// 50MHz主时钟
input rst_n; //低电平复位信号
my_uart_tx(
.clk(clk), //发送数据模块
.rst_n(rst_n), .clk_bps(clk_bps), .rx_data(rx_data), .rx_int(rx_int), .rs232_tx(rs232_tx), .bps_start(bps_start) );
my_uart_rx(
.clk(clk), //接收数据模块
.rst_n(rst_n), .rs232_rx(rs232_rx), .clk_bps(clk_bps), .bps_start(bps_start), .rx_data(rx_data), .rx_int(rx_int) );
.rst_n(rst_n), .bps_start(bps_start), .clk_bps(clk_bps) );
speed_select( .clk(clk), //波特率选择模块,接收和发送模块复用,不支持
input bps_start; output clk_bps;
parameter
parameter
//接收到数据后,波特率时钟启动信号置位
// clk_bps的高电平为接收或者发送数据位的中间采样点
= 5207, //波特率为9600bps
= 2603, //波特率为19200bps = 1301, //波特率为38400bps = 867, //波特率为57600bps = 433; //波特率为115200bps
bps9600
bps19200 bps38400 bps57600 bps115200
bps9600_2 = 2603,
bps19200_2 = 1301, bps38400_2 = 650, bps57600_2 = 433, bps115200_2 = 216;
//分频计数最大值 //分频计数 //波特率时钟寄存器
reg[12:0] bps_para; reg[12:0] cnt; reg clk_bps_r;
reg[12:0] bps_para_2; //分频计数的一半
//---------------------------------------------------------- reg[2:0] uart_ctrl; // uart波特率选择寄存器 //----------------------------------------------------------
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
end
case (uart_ctrl)
3'd0: begin
bps_para <= bps9600; bps_para_2 <= bps9600_2; end
bps_para <= bps19200; bps_para_2 <= bps19200_2; end
//波特率设置
uart_ctrl <= 3'd0; //默认波特率为9600bps
else begin
3'd1: begin
end
end
3'd2: begin
bps_para <= bps38400; bps_para_2 <= bps38400_2; end
bps_para <= bps57600; bps_para_2 <= bps57600_2; end
bps_para <= bps115200; bps_para_2 <= bps115200_2; end
3'd3: begin
3'd4: begin
default: ; endcase
always @ (posedge clk or negedge rst_n)
always @ (posedge clk or negedge rst_n)
assign clk_bps = clk_bps_r;
endmodule
module my_uart_rx(clk,rst_n,rs232_rx,clk_bps,bps_start,rx_data,rx_int);
input clk;
// 50MHz主时钟
// RS232接收数据信号
input rst_n; //低电平复位信号 input rs232_rx;
if(!rst_n) clk_bps_r <= 1'b0;
else if(cnt==bps_para_2 && bps_start) clk_bps_r <= 1'b1; // clk_bps_r高电平为接收else clk_bps_r <= 1'b0; if(!rst_n) cnt <= 13'd0;
else if(cnt<bps_para && bps_start) cnt <= cnt+1'b1; else cnt <= 13'd0;
//波特率时钟计数启动
或者发送数据位的中间采样点
input clk_bps; // clk_bps的高电平为接收或者发送数据位的中间采样点 output bps_start; //接收到数据后,波特率时钟启动信号置位 output[7:0] rx_data;
//---------------------------------------------------------------- reg rs232_rx0,rs232_rx1,rs232_rx2; wire neg_rs232_rx;
always @ (posedge clk or negedge rst_n) begin end
assign neg_rs232_rx = rs232_rx2 & ~rs232_rx1; //接收到下降沿后neg_rs232_rx置高一个时钟周期
//---------------------------------------------------------------- reg bps_start_r; reg[3:0] num; reg rx_int;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
end
bps_start_r <= 1'b1; rx_int <= 1'b1;
//启动接收数据
bps_start_r <= 1'bz; rx_int <= 1'b0;
//移位次数
//接收数据中断信号,接收到数据期间始终为高电平
if(!rst_n) begin
end end
rs232_rx0 <= rs232_rx; rs232_rx1 <= rs232_rx0; rs232_rx2 <= rs232_rx1; rs232_rx0 <= 1'b1; rs232_rx1 <= 1'b1; rs232_rx2 <= 1'b1;
//接收数据寄存器,滤波用
//表示数据线接收到下降沿
//接收数据寄存器,保存直至下一个数据来到
output rx_int; //接收数据中断信号,接收到数据期间始终为高电平
else begin
else if(neg_rs232_rx) begin
//接收数据中断信号使能
end
end
bps_start_r <= 1'bz; rx_int <= 1'b0;
//数据接收完毕 //接收数据中断信号关闭
else if(num==4'd12) begin
end
assign bps_start = bps_start_r;
//----------------------------------------------------------------
reg[7:0] rx_data_r; //接收数据寄存器,保存直至下一个数据来到 //----------------------------------------------------------------
reg[7:0] rx_temp_data; //但前接收数据寄存器 reg rx_data_shift; //数据移位标志
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
end
//接收数据处理
if(clk_bps) begin //读取并保存数据,接收数据为一个起始位,8bit数据,一个结束
end
rx_data_shift <= 1'b0;
if(num<=4'd8) rx_temp_data <= rx_temp_data >> 1'b1; //移位8次,第1else if(num==4'd12) begin
num <= 4'd0; //接收到STOP位后结束,num清零
rx_data_r <= rx_temp_data; //把数据锁存到数据寄存器rx_data
rx_data_shift <= 1'b1; num <= num+1'b1;
if(num<=4'd8) rx_temp_data[7] <= rs232_rx; //锁存9bit(1bit起始位,8rx_data_shift <= 1'b0; rx_temp_data <= 8'd0; num <= 4'd0; rx_data_r <= 8'd0;
else if(rx_int) begin
位
bit数据)
else if(rx_data_shift) begin //数据移位处理
bit起始位移除,剩下8bit正好时接收数据
中 end
assign rx_data = rx_data_r;
endmodule
module my_uart_tx(clk,rst_n,clk_bps,rx_data,rx_int,rs232_tx,bps_start);
input clk;
// 50MHz主时钟
// clk_bps的高电平为接收或者发送数据位的中间采样点
//接收数据中断信号,接收到数据期间始终为高电平,在次利用它的下降沿
input rst_n; //低电平复位信号 input clk_bps; input rx_int; 来启动发送数据
output rs232_tx; // RS232发送数据信号
output bps_start; //接收或者要发送数据,波特率时钟启动信号置位
//---------------------------------------------------------
reg rx_int0,rx_int1,rx_int2; //rx_int信号寄存器,捕捉下降沿滤波用 wire neg_rx_int; // rx_int下降沿标志位
always @ (posedge clk or negedge rst_n) begin end
if(!rst_n) begin
end end
rx_int0 <= rx_int; rx_int1 <= rx_int0; rx_int2 <= rx_int1; rx_int0 <= 1'b0; rx_int1 <= 1'b0; rx_int2 <= 1'b0;
input[7:0] rx_data; //接收数据寄存器
end
end
end
else begin
assign neg_rx_int = ~rx_int1 & rx_int2; //捕捉到下降沿后,neg_rx_int拉地保持一个主时钟周期
//--------------------------------------------------------- reg[7:0] tx_data; //待发送数据的寄存器 //--------------------------------------------------------- reg bps_start_r; reg tx_en;
always @ (posedge clk or negedge rst_n) begin end
assign bps_start = bps_start_r;
//--------------------------------------------------------- reg rs232_tx_r;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
end
num <= 4'd0; rs232_tx_r <= 1'b1;
if(!rst_n) begin
end
//接收数据完毕,准备把接收到的数据发回去
//把接收到的数据存入发送数据寄存器 //进入发送数据状态中
end end
bps_start_r <= 1'bz; tx_en <= 1'b0; bps_start_r <= 1'b1; tx_data <= rx_data; tx_en <= 1'b1;
bps_start_r <= 1'bz; tx_en <= 1'b0; tx_data <= 8'd0;
//发送数据使能信号,高有效
reg[3:0] num;
else if(neg_rx_int) begin
else if(num==4'd11) begin //数据发送完成,复位
end
end
if(clk_bps)
end
//复位
begin num <= num+1'b1; case
(num)
4'd0: rs232_tx_r <= 1'b0; //发送起始位 4'd1: rs232_tx_r <= tx_data[0]; //发送bit0 4'd2: rs232_tx_r <= tx_data[1]; //发送bit1 4'd3: rs232_tx_r <= tx_data[2]; //发送bit2 4'd4: rs232_tx_r <= tx_data[3]; //发送bit3 4'd5: rs232_tx_r <= tx_data[4]; //发送bit4 4'd6: rs232_tx_r <= tx_data[5]; //发送bit5 4'd7: rs232_tx_r <= tx_data[6]; //发送bit6 4'd8: rs232_tx_r <= tx_data[7]; //发送bit7 4'd9: rs232_tx_r <= 1'b0; //发送结束位 default: rs232_tx_r <= 1'b1; endcase
else if(num==4'd11) num <= 4'd0;
assign rs232_tx = rs232_tx_r;
endmodule
FPGA实现串行接口 RS232(1) 2008-12-17 11:38
串行接口(RS-232)
串行接口是连接FPGA和PC机的一种简单方式。这个项目向大家展示了如果使用FPGA来创建RS-232收发器。
下一篇:环境保护应急预案