一、实例
????? 将任意一个程序下载到FPGA的外部flash芯片中,编写Verilog程序,通过FPGA读取flash中的内容,并将读取的内容通过串口发送出来。本篇是接着上一篇实例,继续完成剩余部分。
????? 下图是整个系统的模块框图:
??? 上一篇文章中已经完成了spi_read模块程序的编写,剩余的按键检测消抖模块和串口发送模块分别在“三大通信协议(2)IIC 续”、“三大通信协议(1)UART”两篇文章中都有说明,这里直接给出程序代码。
`timescale 1ns/1ns
module key_filter
#(
parameter CNT_MAX = 999_999//20ms
)
(
input sys_clk,
input sys_res,
input key,
output reg key_flag
);
reg [19:0]cnt_1000_000;
always@(posedge sys_clk or negedge sys_res)
if(!sys_res)
cnt_1000_000 <= 20'd0;
else if(key == 1'b0)
if(cnt_1000_000 == CNT_MAX)
cnt_1000_000 <= cnt_1000_000;
else
cnt_1000_000 <= cnt_1000_000 + 1'b1;
else
cnt_1000_000 <= 20'd0;
always@(posedge sys_clk or negedge sys_res)
if(!sys_res)
key_flag <= 1'b0;
else if(cnt_1000_000 == CNT_MAX-1)
key_flag <= 1'b1;
else
key_flag <= 1'b0;
endmodule
module uart_tx(
input clk,
input res,
input [7:0] data,
input en,
output reg txd
);
reg tx_d0;
reg tx_d1;
reg tx_flag;
reg [15:0] clk_cnt;
reg [3:0] tx_cnt;
reg [7:0] tdata;
wire start_flag;
parameter clk_fre = 50000000;
parameter uart_bps = 9600;
localparam BPS_CONT = clk_fre/uart_bps;
assign start_flag = (~tx_d1) & tx_d0;
always @(posedge clk or negedge res) begin
if(!res) begin
tx_d0 <= 1'b0;
tx_d1 <= 1'b0;
end
else begin
tx_d0 <= en;
tx_d1 <= tx_d0;
end
end
always @(posedge clk or negedge res) begin
if(!res) begin
tx_flag <= 1'b0;
tdata <= 8'b0;
end
else begin
if(start_flag) begin
tx_flag <= 1'b1;
tdata <= data;
end
else if((tx_cnt == 4'd9) && (clk_cnt == BPS_CONT/2)) begin
tx_flag <= 1'b0;
tdata <= 8'b0;
end
else begin
tx_flag <= tx_flag;
tdata <= tdata;
end
end
end
always @(posedge clk or negedge res) begin
if(!res) begin
tx_cnt <= 4'd0;
clk_cnt <= 16'd0;
end
else if(tx_flag) begin
if(clk_cnt <= BPS_CONT-1) begin
clk_cnt <= clk_cnt + 1'd1;
tx_cnt <= tx_cnt;
end
else begin
clk_cnt <= 16'd0;
tx_cnt <= tx_cnt + 1'd1;
end
end
else begin
tx_cnt <= 4'd0;
clk_cnt <= 16'd0;
end
end
always @(posedge clk or negedge res) begin
if(!res) begin
txd <= 1'b1;
end
else if(tx_flag)begin
case (tx_cnt)
4'd0: txd <= 1'b0;
4'd1: txd <= tdata[0];
4'd2: txd <= tdata[1];
4'd3: txd <= tdata[2];
4'd4: txd <= tdata[3];
4'd5: txd <= tdata[4];
4'd6: txd <= tdata[5];
4'd7: txd <= tdata[6];
4'd8: txd <= tdata[7];
4'd9: txd <= 1'b1;
default:;
endcase
end
else
txd <= 1'b1;
end
endmodule
接下来给出顶层代码
module top_flash_read(
input sys_clk ,
input sys_res ,
input key_in ,
input miso ,
output tx ,
output sclk ,
output cs_n ,
output mosi
);
parameter CNT_MAX = 999_999;//按键消抖检测20ms
wire key_flag;
wire [7:0] tx_data;
wire tx_flag;
key_filter
#(
.CNT_MAX(CNT_MAX)
)u_key_filter
(
. sys_clk (sys_clk),
. sys_res (sys_res),
. key (key_in),
. key_flag (key_flag)
);
spi_read u_spi_read
(
. sys_clk(sys_clk ),
. sys_res(sys_res ),
. key (key_flag),
. miso (miso ),
. sclk (sclk),
. cs_n (cs_n),
. mosi (mosi),
. tx_flag(tx_flag),
. tx_data(tx_data)
);
uart_tx u_uart_tx(
. clk (sys_clk),
. res (sys_res),
. data (tx_data),
. en (tx_flag),
. txd (tx)
);
endmodule
? ? ? 接下来进行上板测试验证。首先将之前测试的数码管显示实验中生成的output_file.jic文件下载到开发板中(该文件会在评论处给出),为了对比最后读出的数据,这里将output_file.jic文件后缀名改为.bin,为的是能够在多功能串口调试助手中能够打开该文件,打开后的output_file.bin如下图所示(都是以十六进制显示):前边的数据是帧头数据,中间蓝色的是Flash中存储的程序的数据,后边的数据是帧尾数据。
? ? ? ?接下来将程序下载到开发板中。(注意:1.将spi_read.v文件中的扇区地址、页地址、字节地址都改为0,为了对比结果是否正确;2.将spi_read.v文件中data <= {data[6:0],miso};改为data <= {miso,data[7:1]};因为miso口输入的数据是先进低位再进高位)
? ? ? 在Signal Tap Logic Analyzer中抓取的信号如下图所示:
? ? ? 对比蓝色框中从Flash中读出的数据与上述output_file.bin文件中的数据,可以发现,数据一致,说明读取正确性。
? ? ? 连接上串口调试助手,将读取到的数据通过串口打印出来,打印结果如下图所示:
?对比上述结果发现,数据完全一致,说明串口发送的数据准确无误。
?总结
? ? ? ? 到此为止,三大数据通信协议告一段落。UART、I2C、SPI三种通信协议目前已经十分成熟,虽然协议内容相对比较简单,但是仍被广泛使用,足以见证其重要性。建议FPGA初学者反复揣摩其中的内涵。本人目前正在做的项目里就有很多涉及到SPI通信的知识,比如通过FPGA配置AD芯片,FPGA向AD芯片发送信息指令时,很多都遵循SPI的通信协议。其实深入研究会发现,很多通信协议都大同小异,实质就是发端与收端按照规定的方式进行通信,当然这些通信的协议完全可以由我们人为规定,只需将发端和收端配置成我们自己拟定的通信协议,然后进行数据的收发。例如,在FPGA与STM32两个主控芯片之间进行数据通信时,我们就可以按照自己拟定的通信方式:STM32作为通信的主机,FPGA作为通信的从机,首先FPGA向STM32发送一个数据传输的起始信号,STM32接收到该信号后进入中断,然后向FPGA发送读取数据的使能信号和时钟信号,FPGA根据STM32发送过来的使能信号和时钟信号,将要发送的数据传给STM32。
? ? ? ? 初次创作,难免文章中存在错误,希望读者能够及时纠正并给予私信,望大家共同进步!
|