verilog 串口, 分支控制 请教 谢谢
这个程序我是在人人网的一个讨论组中下的,作者是郑剑(还是姓吴还是孙的)
据其原帖描述,误码率0.1% 感觉还可以,于是就下来
编译没有问题,功能就是“自发自收”,但是跑到板子里,问题就来了
1 自发自收单个字节(16进制,以下相同),没有问题,但是用示波器观看波形(当时还不会modelsim),看到了现象是,波形中,一个字节数据中的停止位是错的,于是修改了代码
2 自发自收,多个数据时,除了第一个字节,后面数据都是错误的... 看波形,一个字节数据宽应该是(1.04ms),但是实际波形明显多出了 2个Bits的时间宽度...;;修改了原程序中的收、发 数据位个数后,自发自收正常,出错概率很低...基本没有可以说 呵呵
但是新的问题来了,也不知道如何在这个程序上修改了(下面的代码是我修改后的,在我板子上正常的)
功能需求
PC发送数据包格式如下:
bootcode len cmd para checksum
比如发送
40 03 02 01 BB
该命令包的命令码就是:02 ,参数:01
当FPGA 收到命名包,并解析命令包,
当命令码为:02时 , 且参数为:01;调用 音乐播放模块,并播放音乐..
再发送: 40 03 02 02 BA 是
当FPGA 收到命名包,并解析命令包,
当命令码为:02时 , 且参数为:02;停止播放音乐..
请问如何在这个基础上修改呢? 谢谢module my_uart_top(clk,rst_n,rs232_rx,rs232_tx);
input clk; // 主时钟
input rst_n;//低电平复位信号
input rs232_rx; // RS232接收数据信号
output rs232_tx;//RS232发送数据信号
wire bps_start1,bps_start2; //接收到数据后,波特率时钟启动信号置位
wire clk_bps1,clk_bps2; // clk_bps的高电平为接收或者发送数据位的中间采样点
wire rx_data; //接收数据寄存器,保存直至下一个数据来到
wire rx_int;//接收数据中断信号,接收到数据期间始终为高电平
//----------------------------------------------------
speed_select speed_rx( .clk(clk), //波特率选择模块,接收和发送模块复用,不支持全双工通信
.rst_n(rst_n),
.bps_start(bps_start1),
.clk_bps(clk_bps1)
);
my_uart_rx my_uart_rx( .clk(clk), //接收数据模块
.rst_n(rst_n),
.rs232_rx(rs232_rx),
.clk_bps(clk_bps1),
.bps_start(bps_start1),
.rx_data(rx_data),
.rx_int(rx_int)
);
speed_select speed_tx( .clk(clk), //波特率选择模块,接收和发送模块复用,不支持全双工通信
.rst_n(rst_n),
.bps_start(bps_start2),
.clk_bps(clk_bps2)
);
my_uart_tx my_uart_tx( .clk(clk), //发送数据模块
.rst_n(rst_n),
.clk_bps(clk_bps2),
.rx_data(rx_data),
.rx_int(rx_int),
.rs232_tx(rs232_tx),
.bps_start(bps_start2)
);
endmodule
module my_uart_rx(clk,rst_n,rs232_rx,clk_bps,bps_start,rx_data,rx_int);
input clk; // 主时钟
input rst_n;//低电平复位信号
input rs232_rx; // RS232接收数据信号!!
input clk_bps; // clk_bps的高电平为接收或者发送数据位的中间采样点
output bps_start; //接收到数据后,波特率时钟启动信号置位
output rx_data; //接收数据寄存器,保存直至下一个数据来到
output rx_int; //接收数据中断信号,接收到数据期间始终为高电平
//----------------------------------------------------------------
reg rs232_rx0,rs232_rx1,rs232_rx2; //接收数据寄存器,滤波用
wire neg_rs232_rx; //表示数据线接收到下降沿
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
rs232_rx0 <= 1'b1;
rs232_rx1 <= 1'b1;
rs232_rx2 <= 1'b1;
end
else begin
rs232_rx0 <= rs232_rx;
rs232_rx1 <= rs232_rx0;
rs232_rx2 <= rs232_rx1;
end
end
assign neg_rs232_rx = rs232_rx2 & ~rs232_rx1; //接收到下降沿后neg_rs232_rx置高一个时钟周期??
//----------------------------------------------------------------
reg bps_start_r;
reg num; //移位次数
reg rx_int; //接收数据中断信号,接收到数据期间始终为高电平
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
bps_start_r <= 1'b0;
rx_int <= 1'b0;
end
else if(neg_rs232_rx) begin
bps_start_r <= 1'b1; //启动接收数据
rx_int <= 1'b1; //接收数据中断信号使能
end
else if(num==4'd10) begin //??
bps_start_r <= 1'b0; //数据接收完毕
rx_int <= 1'b0; //接收数据中断信号关闭
end
end
assign bps_start = bps_start_r;
//----------------------------------------------------------------
reg rx_data_r;//接收数据寄存器,保存直至下一个数据来到
//----------------------------------------------------------------
reg rx_temp_data; //当前接收数据寄存器
reg rx_data_shift; //数据移位标志
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
rx_data_shift <= 1'b0;
rx_temp_data <= 8'd0;
num <= 4'd0;
rx_data_r <= 8'd0;
end
else if(rx_int) begin //接收数据处理
if(clk_bps) begin //读取并保存数据,接收数据为一个起始位,8bit数据,一个结束位
rx_data_shift <= 1'b1;
num <= num+1'b1;
if(num<=4'd8) rx_temp_data <= rs232_rx; //锁存9bit(1bit起始位,8bit数据) ??如何配置rs232_rx
end
else if(rx_data_shift) begin //数据移位处理
rx_data_shift <= 1'b0;
if(num<=4'd8) rx_temp_data <= rx_temp_data >> 1'b1;//移位8次,第1bit起始位移除,剩下8bit正好是接收数据
else if(num==4'd10) begin
num <= 4'd0;//接收到STOP位后结束,num清零
rx_data_r <= rx_temp_data;//把数据锁存到数据寄存器rx_data中
end
end
end
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; // 主时钟
input rst_n;//低电平复位信号
input clk_bps; // clk_bps的高电平为接收或者发送数据位的中间采样点
input rx_data;//接收数据寄存器
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
if(!rst_n) begin
rx_int0 <= 1'b0;
rx_int1 <= 1'b0;
rx_int2 <= 1'b0;
end
else begin
rx_int0 <= rx_int;
rx_int1 <= rx_int0;
rx_int2 <= rx_int1;
end
end
assign neg_rx_int =~rx_int1 & rx_int2;//捕捉到下降沿后,neg_rx_int拉地保持一个主时钟周期??
//---------------------------------------------------------
reg tx_data; //待发送数据的寄存器
//---------------------------------------------------------
reg bps_start_r;
reg tx_en; //发送数据使能信号,高有效
reg num;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
bps_start_r <= 1'b0;
tx_en <= 1'b0;
tx_data <= 8'd0;
end
else if(neg_rx_int) begin //接收数据完毕,准备把接收到的数据发回去
bps_start_r <= 1'b1;
tx_data <= rx_data;//把接收到的数据存入发送数据寄存器
tx_en <= 1'b1; //进入发送数据状态中
end
else if(num==4'd10) begin //数据发送完成,复位
bps_start_r <= 1'b0;
tx_en <= 1'b0;
end
end
assign bps_start = bps_start_r;
//---------------------------------------------------------
reg rs232_tx_r;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
num <= 4'd0;
rs232_tx_r <= 1'b1;
end
else if(tx_en) begin
if(clk_bps) begin
num <= num+1'b1;
case (num)
4'd0:rs232_tx_r <= 1'b0; //发送起始位
4'd1:rs232_tx_r <= tx_data; //发送bit0
4'd2:rs232_tx_r <= tx_data; //发送bit1
4'd3: rs232_tx_r <= tx_data; //发送bit2
4'd4: rs232_tx_r <= tx_data; //发送bit3
4'd5: rs232_tx_r <= tx_data; //发送bit4
4'd6: rs232_tx_r <= tx_data; //发送bit5
4'd7:rs232_tx_r <= tx_data; //发送bit6
4'd8: rs232_tx_r <= tx_data; //发送bit7
4'd9: rs232_tx_r <= 1'b1; //发送结束位
default: rs232_tx_r <= 1'b1;
endcase
end
else if(num==4'd10) num <= 4'd0; //复位
end
end
assign rs232_tx = rs232_tx_r;//如何配置rs232_tx???
endmodule
module speed_select(clk,rst_n,bps_start,clk_bps);
input clk; // 主时钟
input rst_n;//低电平复位信号
input bps_start;//接收到数据后,波特率时钟启动信号置位
output clk_bps; // clk_bps的高电平为接收或者发送数据位的中间采样点
parameter bps9600 = 5208,//5207, //波特率为9600bps 这里使用FPGA主频计算11.06962Mhz,5207是50MHz主频计算值
bps19200 = 2603, //波特率为19200bps
bps38400 = 1301, //波特率为38400bps
bps57600 = 867, //波特率为57600bps
bps115200= 433; //波特率为115200bps
parameter bps9600_2 = 2604,//2603,
bps19200_2= 1301,
bps38400_2= 650,
bps57600_2= 433,
bps115200_2 = 216;
reg bps_para;//分频计数最大值,以前是13位数据,但是总是有报警,所以我就给改成了32为数据,没有了多余的报警
reg bps_para_2; //分频计数的一半
/*`define bps_para 5207 //因为这个uart_ctrl在always中会产生报警,所以使用伪指令的宏定义
`define bps_para_2 2603*/ //这个宏定义的参数在使用的时候必须给参数前加一个`例如:`bps_para_2
reg cnt; //分频计数
reg clk_bps_r; //波特率时钟寄存器
//----------------------------------------------------------
reg uart_ctrl;// uart波特率选择寄存器,怎样配置uart_ctr??
//----------------------------------------------------------
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
uart_ctrl <= 3'd0; //默认波特率为9600bps
end
else begin
case (uart_ctrl)//波特率设置
3'd0:begin
bps_para <= bps9600;
bps_para_2 <= bps9600_2;
end
3'd1:begin
bps_para <= bps19200;
bps_para_2 <= bps19200_2;
end
3'd2:begin
bps_para <= bps38400;
bps_para_2 <= bps38400_2;
end
3'd3:begin
bps_para <= bps57600;
bps_para_2 <= bps57600_2;
end
3'd4:begin
bps_para <= bps115200;
bps_para_2 <= bps115200_2;
end
default: ;
endcase
end
end
always @ (posedge clk or negedge rst_n)
if(!rst_n) cnt <= 13'd0;
else if(cnt<bps_para && bps_start) cnt <= cnt+1'b1;//波特率时钟计数启动
else cnt <= 13'd0;
always @ (posedge clk or negedge rst_n)
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;
assign clk_bps = clk_bps_r;
endmodule 播放音乐的模块如下所示:module top(sp,
led,
clk,
rst_n);
output sp;
output led;
input clk;
input rst_n; // synthesis attribute clock_buffer of rst_n is ibufg;
reg clk_cnt;
always @ ( posedge clk or negedge rst_n)
if (!rst_n )
clk_cnt <= 24'd0;
else
clk_cnt <= clk_cnt + 1'b1;
wireclk_6mhz = (clk_cnt||(clk_cnt&&clk_cnt)); //(clk_cnt&&clk_cnt);
wireclk_4hz= (clk_cnt&&clk_cnt);
wire high,med,low;
assign led = |{high,med,low};
assign led = |{high,med};
assign led = |high;
song song_inst
(
.clk_6mhz (clk_6mhz),
.clk_4hz (clk_4hz),
.sp ( sp ),
.high ( high),
.med ( med ),
.low ( low )
);
endmodule 我用VHDL写过相似功能的程序,增加以下模块:
1、uart_rx模块之后加一级fifo缓存,防止前后级工作频率不同出现错误(其实可以不加)
2、uart_ctrl模块做分支控制
设计状态机:state_IDLE, state_bootcode, state_len, state_cmd, state_para, state_checksum, state_startmusic, state_stopmusic(state_checksum之后的状态根据功能设置,一个功能写一个状态)
平时一直在state_IDLE状态里,接收到第一个字节进入state_bootcode,然后依次进入state_len, state_cmd, state_para。(如果理解的没错的话,cmd,checksum都应该是一个字节,也就是说接收到的len-2就是para字节数,所以在state_para里面要等接收到len-2个字节再跳转下一个状态)在state_para状态里,判断para是什么:
if para=X"01"
next_state<=state_startmusic;
elsif para=X"02"
next_state<=state_stopmusic;
else
next_state<=state_para;
这样子判断一下跳转到相应功能的状态里面。在state_startmusic, state_stopmusic这些功能状态里面通过信号线置1发出控制指令,在state_IDLE给这些信号线置回0。这样判断控制信号线的脉冲就能知道哪个功能被启动了。
在state_startmusic, state_stopmusic里,一个工作时钟周期之后就进入state_checksum,接收到checksum之后返回state_IDLE。
如果是要判断checksum正确才发出控制指令的话,可以将state_startmusic, state_stopmusic这些状态里面的信号线与checksumistrue这个信号线做与运算再输出就行了。 高手呀
谢谢
这里有个问题 cmd 和para,也就是命令和参数
cmd 不单是 一个
para 参数同样也可能是多个参数
假如多个cmd 的话, 是不是,每个 cmd来了,都要有一个状态? 如02 是播放音乐,,还有03,04...等等的其他命令...
页:
[1]