FPGA之SD卡读写操作

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.youkuaiyun.com/weixin_41892263/article/details/83039174
(呕心沥血,写了接近三个小时。我觉得如果耐心看的话,真的会有所帮助哦哈哈哈。看在博主这么kindhearted的份上,点个赞吧!!!!)

学到后面发现例程文件越来越多,代码越来越恶心了。SD卡,I2C_EEPROM,SPI_Flash这种芯片的各种时序看得头晕,数据手册冗长啰嗦,搞不好还是100多页的英文版。。。不是人看的。。所以我觉得更加要把自己的心得写出来,让更多爱学习的人提高自己的效率。虽说看完博客不会给你特别大的帮助,但起码会给你入个门,有个感性的了解。

OK,讲了那么多,下面开始我们今天的小任务:解剖FPGA黑金开发板AX301SD卡的例程:

在研究代码之前,应该要事先对SD卡的操作指令和时序了解:

可以去看看这篇博客:https://blog.youkuaiyun.com/c602273091/article/details/41593955 

(我浏览了下这个作者的主页,真的是太大佬了,任重而道远啊,要学的东西还很多,希望我将来也可以成为这样的大佬。)

请牢记刚刚那篇博客所讲的东西,然后我开始结合代码讲解。PS:代码要剖开讲,完整的代码就是我剖开的顺序。

首先分析sd_card_cmd.v这个文件

sd_card_cmd.v
module sd_card_cmd(
    input                       sys_clk,
    input                       rst,
    input[15:0]                 spi_clk_div,                  //SPI module clock division parameter
    input                       cmd_req,                      //SD card command request
    output                      cmd_req_ack,                  //SD card command request response
    output reg                  cmd_req_error,                //SD card command request error
    input[47:0]                 cmd,                          //SD card command
    input[7:0]                  cmd_r1,                       //SD card expect response
    input[15:0]                 cmd_data_len,                 //SD card command read data length
    input                       block_read_req,               //SD card sector data read request
    output reg                  block_read_valid,             //SD card sector data read data valid
    output reg[7:0]             block_read_data,              //SD card sector data read data
    output                      block_read_req_ack,           //SD card sector data read response
    input                       block_write_req,              //SD card sector data write request
    input[7:0]                  block_write_data,             //SD card sector data write data next clock is valid
    output                      block_write_data_rd,          //SD card sector data write data
    output                      block_write_req_ack,          //SD card sector data write response
    output                      nCS_ctrl,                     //SPI module chip select control
    output reg[15:0]            clk_div,
    output reg                  spi_wr_req,                   //SPI module data sending request
    input                       spi_wr_ack,                   //SPI module data request response
    output[7:0]                 spi_data_in,                  //SPI module send data
    input[7:0]                  spi_data_out                  //SPI module data returned
);
parameter S_IDLE         = 0;
parameter S_WAIT         = 1;
parameter S_INIT         = 2;
parameter S_CMD_PRE      = 3;
parameter S_CMD          = 4;
parameter S_CMD_DATA     = 5;
parameter S_READ_WAIT    = 6;
parameter S_READ         = 7;
parameter S_READ_ACK     = 8;
parameter S_WRITE_TOKEN  = 9;
parameter S_WRITE_DATA_0 = 10;
parameter S_WRITE_DATA_1 = 11;
parameter S_WRITE_CRC    = 12;
parameter S_WRITE_SUC    = 13;
parameter S_WRITE_BUSY   = 14;
parameter S_WRITE_ACK    = 15;
parameter S_ERR          = 16;
parameter S_END          = 17;
 
reg[4:0]                      state;
reg                           CS_reg;
reg[15:0]                     byte_cnt;
reg[7:0]                      send_data;
wire[7:0]                     data_recv;
reg[9:0]                      wr_data_cnt;
 
assign cmd_req_ack = (state == S_END);
assign block_read_req_ack = (state == S_READ_ACK);
assign block_write_req_ack= (state == S_WRITE_ACK);
assign block_write_data_rd = (state == S_WRITE_DATA_0);
assign spi_data_in = send_data;
assign data_recv = spi_data_out;
assign nCS_ctrl = CS_reg;
这些参数的设定比较多,大家一定要有个初步的印象。我自己学习的时候,特地用一张白纸记下一些寄存器的状态,例如spi_wr_ack什么时候为1,只有这样在if条件判断的时候才会理解得更加清楚。而且大家要记住,只要是input的寄存器,就一定是另一个文件的output,一一对应好,把条件理清楚,再开始看接下来的模块化代码。

always@(posedge sys_clk or posedge rst)
begin
    if(rst == 1'b1)
    begin
        CS_reg <= 1'b1;
        spi_wr_req <= 1'b0;
        byte_cnt <= 16'd0;
        clk_div <= 16'd0;
        send_data <= 8'hff;
        state <= S_IDLE;
        cmd_req_error <= 1'b0;
        wr_data_cnt <= 10'd0;
    end
    else
        case(state)
            S_IDLE:
            begin
                state <= S_INIT;
                clk_div <= spi_clk_div;
                CS_reg <= 1'b1;
            end
            S_INIT:
            begin
                //send 11 bytes on power(at least 74 SPI clocks)
                if(spi_wr_ack == 1'b1)
                begin
                    if(byte_cnt >= 16'd10)
                    begin
                        byte_cnt <= 16'd0;
                        spi_wr_req <= 1'b0;
                        state <= S_WAIT;
                    end
                    begin
                        byte_cnt <= byte_cnt + 16'd1;
                    end
                end
                else
                begin
                    spi_wr_req <= 1'b1;
                    send_data <= 8'hff;
                end
            end
这段代码对应的原理是:SD卡的初始化,需要先给予至少74个CLK后。那么为什么要74个CLK呢?因为在上电初期,电压的上升过程据SD卡组织的计算约合64个CLK周期才能到达SD卡的正常工作电压他们管这个叫做Supply ramp up time,其后的10个CLK是为了与SD卡同步,之后开始CMD0的操作,严格按照此项操作,一定没有问题。因此,在S_INIT状态下,要发送10个字节,才进入等待状态。我一开始也很懵逼,为什么是10,然后看到初始化这个状态应该就是等待的那个原理,转念一想10*8=80(一个字节8位2333),刚好大于74个CLK。

S_WAIT:
            begin
                cmd_req_error <= 1'b0;
                wr_data_cnt <= 10'd0;
                //wait for  instruction
                if(cmd_req == 1'b1)
                    state <= S_CMD_PRE;
                else if(block_read_req == 1'b1)
                    state <= S_READ_WAIT;
                else if(block_write_req == 1'b1)
                    state <= S_WRITE_TOKEN;
                clk_div <= spi_clk_div;
            end
然后就是等待状态了。假想一下SD卡是一个工人,这个时候它有三个任务,发送命令,读操作,写操作,这个时候就是在等待有没有人让它去做那样的事情。

S_CMD_PRE:
            begin
                //before sending a command, send an byte 'ff',provide some clocks
                if(spi_wr_ack == 1'b1)
                begin
                    state <= S_CMD;
                    spi_wr_req <= 1'b0;
                    byte_cnt <= 16'd0;
                end
                else
                begin
                    spi_wr_req <= 1'b1;
                    CS_reg <= 1'b1;
                    send_data <= 8'hff;
                end
            end
S_CMD:
            begin
                if(spi_wr_ack == 1'b1)
                begin
                    if((byte_cnt == 16'hffff) || (data_recv != cmd_r1 && data_recv[7] == 1'b0))
                    begin
                        state <= S_ERR;
                        spi_wr_req <= 1'b0;
                        byte_cnt <= 16'd0;
                    end
                    else if(data_recv == cmd_r1)
                    begin
                        spi_wr_req <= 1'b0;
                        if(cmd_data_len != 16'd0)
                        begin
                            state <= S_CMD_DATA;
                            byte_cnt <= 16'd0;
                        end
                        else
                        begin
                            state <= S_END;
                            byte_cnt <= 16'd0;
                        end
                    end
                    else
                        byte_cnt <=  byte_cnt + 16'd1;
                end
                else
                begin
                    spi_wr_req <= 1'b1;
                    CS_reg <= 1'b0;
                    if(byte_cnt == 16'd0)
                        send_data <= (cmd[47:40] | 8'h40);
                    else if(byte_cnt == 16'd1)
                        send_data <= cmd[39:32];
                    else if(byte_cnt == 16'd2)
                        send_data <= cmd[31:24];
                    else if(byte_cnt == 16'd3)
                        send_data <= cmd[23:16];
                    else if(byte_cnt == 16'd4)
                        send_data <= cmd[15:8];
                    else if(byte_cnt == 16'd5)
                        send_data <= cmd[7:0];
                    else
                        send_data <= 8'hff;
                end
            end
我把命令的两段代码都放在一起写。我一开始也混淆了,为什么这个spi_wr_ack老是出现,把我完全弄糊涂了。后来一想,发送命令也是write操作啊,,只不过我是在写 命 令!而已啊。所以,这段代码的理解是:当写命令应答完成,就进入下一个状态S_CMD,写命令清0,字节计数器清零,否则(应答没完成),写请求,CS保持,然后发送校验码0xff。

对应的原理,那篇博客也有讲到:写入一个CMD命令时,首先将CS拉低,在SD卡的DIN引脚输入命令及相应的参数和校验码。

接着就是S_CMD指令:如果应答完成,需要校验。发送一个写命令和地址参数,后跟校验码,不采用校验码,发送0xff即可,SD卡会以R1回应。这里就是校验到底与R1返回的数据对不对。如果不对,则进入错误,如果正确,当命令的数据长度不为0时,进入S_CMD_DATA状态,否则结束。否则计数器开始计时等待响应。如果应答没有完成,这才是S_CMD真正要做的事情,把CS拉低,然后开始发送命令。然后,我相信你会对这段代码感到懵逼,没关系,我来给你解释下:每一个命令的长度都是固定的6个字节,前1个字节的值=命令号+0x40;中间4个字节为参数,不同的命令参数格式都不相同,但参数最多为4个字节;最后1个字节是CRC校验码和1位固定结束位‘1’。所以你会看到cmd的命令在sd_card_sec_read_write.v中,每一个cmd都是由6个字节组成,如:8'd0,8'h00,8'h00,8'h00,8'h00,8'h95。

然后进入了一个叫S_CMD_DATA的状态。

S_CMD_DATA:
            begin
                if(spi_wr_ack == 1'b1)
                begin
                    if(byte_cnt == cmd_data_len - 16'd1)
                    begin
                        state <= S_END;
                        spi_wr_req <= 1'b0;
                        byte_cnt <= 16'd0;
                    end
                    else
                    begin
                        byte_cnt <= byte_cnt + 16'd1;
                    end
                end
                else
                begin
                    spi_wr_req <= 1'b1;
                    send_data <= 8'hff;
                end
            end
这个又是什么东西。真的恶心。后来顺着思路,我去找cmd_data_len到底是个什么东西,发现在sd_card_sec_read_write.v中,只有CMD8有4个字节的长度,其他均为0!这其中一定有什么骚规则,没错,后来百度了资料,果然:原来CMD8会返回5个字节的数据,首先接收到第一个字节格式为R1的数据,这个数据只要判断是否为0X01即可,如果为0X01,表示SD卡响应了CMD8命令,如果不为0X01(一般会是0X09或0X05,不用关心),则表示SD卡不支持CMD8命令。在接收到0X01之后,随后需要接收4字节数据,其中31-28位为command version,即命令的类型,此处为CMD8;然后27-12位是保留的数据位,通常为0;然后11-8位是SD卡支持的电压范围,此处得到的应该是‘0001’;最后一个字节是我们在CMD8的命令中发送给SD卡的数据,SD卡又原模原样的返回来了,在命令中我们发送的是0XAA,此处得到的也应该是0XAA。

命令就此讲完了,然后就开始了读和写的等待状态:

S_READ_WAIT:
            begin
                if(spi_wr_ack == 1'b1 && data_recv == 8'hfe)
                begin
                    spi_wr_req <= 1'b0;
                    state <= S_READ;
                    byte_cnt <= 16'd0;
                end
                else
                begin
                    spi_wr_req <= 1'b1;
                    send_data <= 8'hff;
                end
            end
            S_READ:
            begin
                if(spi_wr_ack == 1'b1)
                begin
                    if(byte_cnt == 16'd513)
                    begin
                        state <= S_READ_ACK;
                        spi_wr_req <= 1'b0;
                        byte_cnt <= 16'd0;
                    end
                    else
                    begin
                        byte_cnt <= byte_cnt + 16'd1;
                    end
                end
                else
                begin
                    spi_wr_req <= 1'b1;
                    send_data <= 8'hff;
                end
            end
首先是读:SD卡读一个块:  发送一个读命令,后面的参数是读取的块的首地址和0xff,SD会以R1回应,如果地址正确,回应应该为0。接着是SD卡发送数据,以令牌0xfe为起始,后面是要读取的数据,最后是2个字节的校验码

首先,在读的等待状态时,要等待写应答,为什么呢?我自己的理解是,读也需要先写地址,才能够读到地址所对应的数据。然后就是0xfe,如上所说。如果写没有应答,则发送写请求。

当进入读状体时,这个时候就要读正式数据512Bytes了,读完时,,则进入了S_READ_ACK的读应答状态。否则,字节计数器+1,等待读完。如果没有写应答信号,则发送写请求。

然后进入写状态:

S_WRITE_TOKEN:
                if(spi_wr_ack == 1'b1)
                begin
                    state <= S_WRITE_DATA_0;
                    spi_wr_req <= 1'b0;
                    byte_cnt <= 16'd0;  
                end
                else
                begin
                    spi_wr_req <= 1'b1;
                    send_data <= 8'hfe;
                end
            S_WRITE_DATA_0:
            begin
                state <= S_WRITE_DATA_1;
                wr_data_cnt <= wr_data_cnt + 10'd1;
            end
            S_WRITE_DATA_1:
            begin
                if(spi_wr_ack == 1'b1 && wr_data_cnt == 10'd512)
                begin
                    state <= S_WRITE_CRC;
                    spi_wr_req <= 1'b0;
                end
                else if(spi_wr_ack == 1'b1)
                begin
                    state <= S_WRITE_DATA_0;
                    spi_wr_req <= 1'b0;
                end
                else
                begin
                    spi_wr_req <= 1'b1;
                    send_data <= block_write_data;
                end
            end
            S_WRITE_CRC:
            begin
                if(spi_wr_ack == 1'b1)
                begin
                    if(byte_cnt == 16'd1)
                    begin
                        state <= S_WRITE_SUC;
                        spi_wr_req <= 1'b0;
                        byte_cnt <= 16'd0;
                    end
                    else
                    begin
                        byte_cnt <= byte_cnt + 16'd1;
                    end
                end
                else
                begin
                    spi_wr_req <= 1'b1;
                    send_data <= 8'hff;
                end
            end
            S_WRITE_SUC :
            begin
                if(spi_wr_ack == 1'b1)
                begin
                    if(data_recv[4:0] == 5'b00101)
                    begin
                        state <= S_WRITE_BUSY;
                        spi_wr_req <= 1'b0;
                    end
                end
                else
                begin
                    spi_wr_req <= 1'b1;
                    send_data <= 8'hff;
                end
            end
            S_WRITE_BUSY :
            begin
                if(spi_wr_ack == 1'b1)
                begin
                    if(data_recv == 8'hff)
                    begin
                        state <= S_WRITE_ACK;
                        spi_wr_req <= 1'b0;
                    end
                end
                else
                begin
                    spi_wr_req <= 1'b1;
                    send_data <= 8'hff;
                end
            end
写的状态要求如下:

在S_WRITE_TOKEN状态下,发送0xfe,发送写请求(对,应该是从else if开始理解),当写应答时,进入S_WRITE_DATA_0状态,此时计数器加1,进入S_WRITE_DATA_1状态,对SD卡进行写操作。这段代码我怎么感觉有点多余,后面还没应答的时候一直返回S_WRITE_DATA_0,其实就是计数计到513,把开始的0xfe也算上,到达513之后,开始进入CRC检验。然后就是上面的第三条,读到00101才说明写成功。然后进入第四条。当接收到0xff时则说明写操作完成,发送一个写成功的应答信号,否则继续写。。。

然后就是错误警告和应答状态又回到空闲状态:

    S_ERR:
            begin
                state <= S_END;
                cmd_req_error <= 1'b1;
            end
            S_READ_ACK,S_WRITE_ACK,S_END:
            begin
                state <= S_WAIT;
            end
            default:
                state <= S_IDLE;
最后还有两段代码,就是对读操作的补充,因为读操作其实就是读SD卡的内容,当读有效时,把spi通信接收到的data_recv发送到主机。

吐血。。。把sd_card_cmd的思路讲清楚了,细节上的理解需要你们自己去写代码,然后才能够透彻地理解清楚。

接下来我们来看看另一个文件:sd_card_sec_read_write.v

sd_card_sec_read_write.v
这里无非就是一些命令,我们来看看。

鉴于篇幅太长,输入输出以及寄存器的代码还有RESET的代码我就不发上来了。直接进入状态机:

case(state)
            S_IDLE:
            begin
                state <= S_CMD0;
                sd_init_done <= 1'b0;
                spi_clk_div <= SPI_LOW_SPEED_DIV[15:0];
            end
空闲状态下,发送CMD0,

S_CMD0:
            begin
                if(cmd_req_ack & ~cmd_req_error)
                begin
                    state <= S_CMD8;
                    cmd_req <= 1'b0;
                end
                else
                begin
                    cmd_req <= 1'b1;
                    cmd_data_len <= 16'd0;
                    cmd_r1 <= 8'h01;
                    cmd <= {8'd0,8'h00,8'h00,8'h00,8'h00,8'h95};
                end
大家不要意外,8'd0,8'h00,8'h00,8'h00,8'h00,8'h95是CMD0的序列,CMD0时,发送命令请求,且没有返回长度(这在前面的CMD8时详细讲过,r1的返回值应该是0x01。没有错误时,进入CMD8.

后面的CMDx大同小异,我就忽略带过,接着随着CMD8进入CMD55,CMD41,CMD16。具体为什么请看上面的图片,初始化时的步骤。这些步骤就是为了确定SD卡的型号。发送指令看它返回什么。

接着

S_WAIT_READ_WRITE:
            begin
                if(sd_sec_write ==  1'b1)
                begin
                    state <= S_CMD24;
                    sec_addr <= sd_sec_write_addr;
                end
                else if(sd_sec_read == 1'b1)
                begin
                    state <= S_CMD17;
                    sec_addr <= sd_sec_read_addr;
                end
 
                spi_clk_div <= 16'd0;
            end
然后就是写和读的等待命令。当写时,发送地址,同时进入CMD24,读时,发送地址,进入CMD17

S_CMD24:
            begin
                if(cmd_req_ack & ~cmd_req_error)
                begin
                    state <= S_WRITE;
                    cmd_req <= 1'b0;
                end
                else
                begin
                    cmd_req <= 1'b1;
                    cmd_data_len <= 16'd0;
                    cmd_r1 <= 8'h00;
                    cmd <= {8'd24,sec_addr,8'hff};
当进入CMD24时,发送cmd:命令加地址加0xff,然后进入S_WRITE,真正开始写了。

S_WRITE:
            begin
                if(block_write_req_ack == 1'b1)
                begin
                    block_write_req <= 1'b0;
                    state <= S_WRITE_END;
                end
                else
                    block_write_req <= 1'b1;
            end
这里只是给了给寄存器赋了1,让它执行命令,具体的写前面已经讲过了。

然后就是读了,一样,CMD17和CMD24有异曲同工之处,然后同样读也是发送一个读指令。

S_CMD17:
            begin
                if(cmd_req_ack & ~cmd_req_error)
                begin
                    state <= S_READ;
                    cmd_req <= 1'b0;
                end
                else
                begin
                    cmd_req <= 1'b1;
                    cmd_data_len <= 16'd0;
                    cmd_r1 <= 8'h00;
                    cmd <= {8'd17,sec_addr,8'hff};
                end
            end
            S_READ:
            begin
                if(block_read_req_ack)
                begin
                    state <= S_READ_END;
                    block_read_req <= 1'b0;
                end
                else
                begin
                    block_read_req <= 1'b1;
                end
            end
然后就结束了,进入等待读写状态。

S_WRITE_END:
            begin
                state <= S_WAIT_READ_WRITE;
            end
            S_READ_END:
            begin
                state <= S_WAIT_READ_WRITE;
            end
            default:
                state <= S_IDLE;
天呐。终于讲完了。博主本来也是半懂的状态开始写,写着写着思路越来越清晰,现在自己也完全理清楚了。希望能帮到大家。也期待自己的博文一篇篇变多,然后成为大佬哈哈哈哈。
 

FPGA 读写SD音乐播放例程Verilog逻辑源码Quartus工程文件+文档说明,,FPGA型号Cyclone4E系列中的EP4CE6F17C8,Quartus版本17.1。 实验简介 在其他实验中我们已经完成了 SD 读写和音频模块的录音播放,本实验通过搜索 SD 中 WAV 音乐文件,然后送到音频模块播放,完成一个简单音乐播放器的功能。 2 实验原理 本实验一个关键是在没有文件系统的情况下,搜索 SD 每个扇区的内容,匹配出 WAV 文件, 这里有一个假设:假设每一个文件都是从一个扇区第一个字节开始而且文件是连续存储的,经过 大量实验,发现 FAT32 文件格式中的文件确实如此。 2.1 WAV 文件格式 大部分的文件都有文件头,WAV 也丌例外,我们通过分析 SD 一个扇区的前几个字节,判 断这个文件是否为 WAV 文件。 WAV 文件作为多媒体中使用的声波文件格式之一,它是以 RIFF 格式为标准的。RIFF 是英文 Resource Interchange File Format 的缩写,每个 WAV 文件的头四个字节便是“RIFF”,所以本实验 就简单的以每个扇区的前 4 个字节是否为“RIFF”判断该文件是否为 WAV 文件,紧接着 4 个字节 表示文件的大小,这样我们就可以确定要读取的数据量。WAV 文件头大小是 88 字节,在播放时 要把前 88 个字节的文件头去掉。 module top( input clk, input rst_n, input key1, input wm8731_bclk, //audio bit clock input wm8731_daclrc, //DAC sample rate left right clock output wm8731_dacdat, //DAC audio data output input wm8731_adclrc, //ADC sample rate left right clock input wm8731_adcdat, //ADC audio data input inout wm8731_scl, //I2C clock inout wm8731_sda, //I2C data output sd_ncs, //SD card chip select (SPI mode) output sd_dclk, //SD card clock output sd_mosi, //SD card controller data output input sd_miso, //SD card controller data input output [5:0] seg_sel, output [7:0] seg_data ); wire[9:0] lut_index; wire[31:0] lut_data; wire[3:0] state_code; wire[6:0] seg_data_0; //I2C master controller i2c_config i2c_config_m0( .rst (~rst_n ), .clk (clk ),
FPGA读写SDVerilog设计逻辑Quartus工程源码文件,FPGA型号Cyclone4E系列中的EP4CE10F17C8,Quartus版本18.0。 module top_sd_rw( input sys_clk , //系统时钟 input sys_rst_n , //系统复位,低电平有效 //SD接口 input sd_miso , //SDSPI串行输入数据信号 output sd_clk , //SDSPI时钟信号 output sd_cs , //SDSPI片选信号 output sd_mosi , //SDSPI串行输出数据信号 //LED output [3:0] led //LED灯 ); //wire define wire clk_ref ; wire clk_ref_180deg ; wire rst_n ; wire locked ; wire wr_start_en ; //开始写SD数据信号 wire [31:0] wr_sec_addr ; //写数据扇区地址 wire [15:0] wr_data ; //写数据 wire rd_start_en ; //开始写SD数据信号 wire [31:0] rd_sec_addr ; //读数据扇区地址 wire error_flag ; //SD读写错误的标志 wire wr_busy ; //写数据忙信号 wire wr_req ; //写数据请求信号 wire rd_busy ; //读忙信号 wire rd_val_en ; //数据读取有效使能信号 wire [15:0] rd_val_data ; //读数据 wire sd_init_done ; //SD初始化完成信号 //***************************************************** //** main code //***************************************************** assign rst_n = sys_rst_n & locked; //锁相环 pll_clk u_pll_clk( .areset (1'b0 ), .inclk0 (sys_clk ), .c0 (clk_ref ), .c1 (clk_ref_180deg), .locked (locked ) ); //产生SD测试数据 data_gen u_data_gen( .clk (clk_ref), .rst_n (rst_n), .sd_init_done (sd_init_done), .wr_busy (wr_busy), .wr_req (wr_req), .wr_start_en (wr_start_en), .wr_sec_addr (wr_sec_addr), .wr_data (wr_data), .rd_val_en (rd_val_en), .rd_val_da
FPGA读写SD读取BMP图片通过LCD显示例程实验 Verilog逻辑源码Quartus工程文件+文档说明,FPGA型号Cyclone4E系列中的EP4CE6F17C8,Quartus版本17.1。 1 实验简介 在前面的实验中我们练习了 SD 读写,VGA 视频显示等例程,本实验将 SD 里的 BMP 图 片读出,写入到外部存储器,再通过 VGA、LCD 等显示。 本实验如果通过液晶屏显示,需要有液晶屏模块。 2 实验原理 在前面的实验中我们在 VGA、LCD 上显示的是彩条,是 FPGA 内部产生的数据,本实验将彩 条替换为 SD 内的 BMP 图片数据,但是 SD 读取速度远远不能满足显示速度的要求,只能先写 入外部高速 RAM,再读出后给视频时序模块显示 module top( input clk, input rst_n, input key1, output [5:0] seg_sel, output [7:0] seg_data, output vga_out_hs, //vga horizontal synchronization output vga_out_vs, //vga vertical synchronization output[4:0] vga_out_r, //vga red output[5:0] vga_out_g, //vga green output[4:0] vga_out_b, //vga blue output sd_ncs, //SD card chip select (SPI mode) output sd_dclk, //SD card clock output sd_mosi, //SD card controller data output input sd_miso, //SD card controller data input output sdram_clk, //sdram clock output sdram_cke, //sdram clock enable output sdram_cs_n, //sdram chip select output sdram_we_n, //sdram write enable output sdram_cas_n, //sdram column address strobe output sdram_ras_n, //sdram row address strobe output[1:0] sdram_dqm, //sdram data enable output[1:0] sdram_ba, //sdram bank address output[12:0] sdram_addr, //sdram address inout[15:0] sdram_dq //sdram data ); parameter MEM_DATA_BITS = 16 ; //external memory user interface data width parameter ADDR_BITS = 24
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值