element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • About Us
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Summer of FPGA
  • Challenges & Projects
  • Design Challenges
  • Summer of FPGA
  • More
  • Cancel
Summer of FPGA
Blog Security Camera #2: SD Card Interfacing
  • Blog
  • Forum
  • Documents
  • Files
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: angelo76
  • Date Created: 28 Jan 2022 8:20 AM Date Created
  • Views 5721 views
  • Likes 10 likes
  • Comments 0 comments
  • Security Camera
  • fpga
  • summer of fpga
  • cmod s7
Related
Recommended

Security Camera #2: SD Card Interfacing

angelo76
angelo76
28 Jan 2022

Blog 2: SD Card Interfacing

Before we dive into camera interfacing, we must first ready the SD card where we will save the images. The aim of this second blog is to have a working SD Card driver in pure Verilog HDL.

How to Interface with an SD Card

I will not start with what is an SD Card since you already know it (I will leave some references below just in case), we will now  dive into how can you interface with an SD Card.

SD cards have two main operating modes: SD mode and SPI mode. SD mode is used in computers, phones, and any commercialized products that needs an SD card since it maximizes the throughput of writing and reading from it. However, this will require the designer of the SD card driver to sign an NDA with the SD association and pay some royalties. Thus, we hobbyists are restricted to SPI mode. This is not a bad thing since SPI  is a very common communication protocol but the throughput of SD mode is just much higher than SPI mode. SD mode default speed is 100Mbit/s whlle SPI is 25Mbit/sec.

The SPI used in SD card is just the common four-wire type and below is the pin-outs of an SD card. Note the pins needed by the SPI (CS, DI(MOSI) , DO(MISO), and SCLK).

image

So first, we must ready the SPI driver.

SPI Driver

SPI is a very simple protocol. The image below best shows how it works. A byte(8 bits) are transferred in series in reference to the clock. The clock is provided by the Master. For the SD card driver, the slave is the SD card while the FPGA is the Master. There are actually 4 SPI modes to control clock polarity and phase but no need to sweat the details since it just differs on what edge the data is transferred. For the SD card driver, the default mode 0 is used wherein the transferred or received data must be stable at the rising edge of the clock. Thus, data can only switch on falling edges of the clock.

image

Now for the SPI driver code in Verilog HDL:

`timescale 1ns/1ps
		       //HI_FREQ_DIV=clk division for the high freq,LO_FREQ_DIV=clk division for low freq [can be any even number >=2]
module spi #(parameter HI_FREQ_DIV=12,LO_FREQ_DIV=30,SPI_MODE=0) (  //SPI_MODE can be either 0,1,2,3
	input wire clk,rst,                            
	//SPI control
	input clk_div, //1: high frequency , 0=low frequency
	input wire rd,wr,hold,  //pins to start read or write operation, hold is for holding the clock for multibyte read/write 
	input wire[7:0] wr_data, //data to be sent to the slave
	output wire[7:0] rd_data, //data received from the slave
	output reg done_tick, //ticks if either write or read operation is finished
	output ready, //can perform read/write operation only if ready is "1" (except for multibyte read/write where state will never go to IDLE)
	//SPI pinouts
	input wire miso,
	output wire mosi,
	output wire sclk,cs_n
	);
	
	/* template
	    spi #(.HI_FREQ_DIV(12),.LO_FREQ_DIV(30),.SPI_MODE(0)) m0 //High freq: 12MHz/12=1MHz , Low freq: 12MHz/30=400KHz
    ( 
        .clk(clk),
        .rst(rst),                                
        //SPI control
        .rd(rd),
        .wr(wr),
        .hold(hold),  //pins to start read or write operation, hold is for holding the clock for multibyte read/write 
        .wr_data(wr_data), //data to be sent to the slave
        .rd_data(rd_data), //data received from the slave
        .done_tick(done_tick), //ticks if either write or read operation is finished
        .ready(ready), //can perform read/write operation only if ready is "1" (except for multibyte read/write where state will never go to IDLE)
        //SPI pinouts
        .miso(miso),
        .mosi(mosi),
        .sclk(sclk),
        .cs_n(cs_n_1)
	);
	*/
	
	localparam IDLE=0,
               WRITE=1,
               WRITE_DELAY=2,
               READ=3,
               HOLD=4;
		   
		   
	localparam CPOL=SPI_MODE[1], //clock polarity
		       CPHA=SPI_MODE[0]; //clock phase


	reg[2:0] state_q=0,state_d; 
	reg[3+!CPHA:0] count_q=0,count_d; //counter for shifting 8 times
	reg[7:0] wr_data_q=0,wr_data_d; //contains data to be sent to the slave
	reg[7:0] rd_data_q=0,rd_data_d;  //contains data received from the slave
	reg[$clog2(LO_FREQ_DIV+1)-1:0] clk_ctr_q=0,clk_ctr_d; //counter for clock division
    reg cs_n_q=0,cs_n_d;
    reg sclk_q=0,sclk_d;
    reg hold_q=0,hold_d;
    wire[$clog2(LO_FREQ_DIV+1)-1:0] CLK_DIV=clk_div? HI_FREQ_DIV:LO_FREQ_DIV;    
    
	//register operations
	always @(posedge clk,posedge rst) begin
		if(rst) begin
			state_q<=0;
			count_q<=0;
			wr_data_q<=0;
			rd_data_q<=0;
			clk_ctr_q<=0;
			cs_n_q<=0;
			sclk_q<=0;
			hold_q<=0;
		end
		else begin
			state_q<=state_d;
			count_q<=count_d;
			wr_data_q<=wr_data_d;
			rd_data_q<=rd_data_d;
			clk_ctr_q<=clk_ctr_d;
			cs_n_q<=cs_n_d;
			sclk_q<=sclk_d;
			hold_q<=hold_d;
		end
		
	end
	
	//FSM logic
	always @* begin
		state_d=state_q;
		count_d=count_q;
		wr_data_d=wr_data_q;
		rd_data_d=rd_data_q;
		clk_ctr_d=clk_ctr_q;
		cs_n_d=cs_n_q;
		sclk_d=sclk_q;
		hold_d=hold_q;
        done_tick=0;
        
		//logic for clk division and sclk 
		clk_ctr_d=(state_q==IDLE || state_q==HOLD || clk_ctr_q==CLK_DIV-1)? 0:clk_ctr_q+1'b1;
		sclk_d=(clk_ctr_q==CLK_DIV-1) || (clk_ctr_q==((CLK_DIV>>1)-1))? !sclk_q:sclk_q; 

		case(state_q) 
			IDLE: begin
                    sclk_d=CPOL; //CPOL (clock polarity) is the initial value of clock 
                    cs_n_d=1; //cs_n is active low
                    count_d=0;
                    if(wr) begin
                        cs_n_d=0; 
                        wr_data_d=wr_data;			
                        state_d=CPHA? WRITE_DELAY:WRITE; //CPHA=1: MOSI is delayed by half-clk cycle
                    end
                    if(rd) begin
                        cs_n_d=0;
                        state_d=READ;
                     end
			      end
			      
			WRITE: if(clk_ctr_q==(CLK_DIV>>CPHA)-1) begin //CPHA=1: MOSI is updated at half-clk cycle , CPHA=0: MOSI is updated at end-of-clk cycle
					wr_data_d=wr_data_q<<1;//shift data 8 times (MSB first)
					count_d=(count_q==7)? 0:count_q+1'b1; 
                    if(count_q==7) begin
                        done_tick=1;
                        if(wr) begin //write again (multibyte write)
                        count_d=0;
                        wr_data_d=wr_data;			
                        state_d=WRITE;
                        end
                        else if(rd) begin //read after writing
                            count_d=0;
                            state_d=READ; 
                        end
                        else if(hold) begin
                            sclk_d=CPOL;
                            state_d=HOLD;
                        end
                        else begin //go back to IDLE
                            state_d=IDLE;
                            cs_n_d=1;
                            sclk_d=CPOL;
                        end
                        
                     end
				    end
				    
	 WRITE_DELAY: if(clk_ctr_q==(CLK_DIV>>1)-1) state_d=WRITE; //for CPHA=1 when MOSI is delayed by half-clk cycle
	 
			READ:if(clk_ctr_q==(CLK_DIV>>!CPHA)-1)begin //CPHA=1: MISO is read at end-of-clk cycle , CPHA=0: MISO is read at half-clk cycle
					rd_data_d={rd_data_q[6:0],miso}; //shift read data (MSB first)
					count_d=(count_q==7+!CPHA)? 0:count_q+1'b1; //CPHA=0 needs one more clk cycle(thus 0-to-8) BEFORE GOING IDLE (not needed if not going to idle after reading)
					if(count_q==7) begin 
                        done_tick=1;
                        hold_d=hold;
                        if(wr) begin //write after reading
                            count_d=0;
                            wr_data_d=wr_data;			
                            state_d=WRITE;
                        end
                        else if(rd) begin
                            count_d=0;
                            state_d=READ; //multibyte read
                        end    
                        else if(CPHA) begin //for CPHA=1 only,goes IDLE
                            state_d=IDLE;
                            sclk_d=CPOL;
                            cs_n_d=1;
                            if(hold) begin
                                state_d=HOLD;
                                cs_n_d=0;
                            end
                        end
                        
                     end  
                    if(count_q==8) begin //for CPHA=0 only, goes IDLE
                            rd_data_d=rd_data_q; //we added one more clk cycle for CPHA=0 before going IDLE but rd_data_q must not be updated again for that last clk cycle
                            state_d=IDLE;
                            sclk_d=CPOL;
                            cs_n_d=1;
                            if(hold_q) begin
                                state_d=HOLD;
                                cs_n_d=0;
                            end
                     end
				    end
				    
		        HOLD: begin
		                sclk_d=CPOL; //CPOL (clock polarity) is the initial value of clock 
                        count_d=0;
                        hold_d=0;
                        if(wr) begin
                            wr_data_d=wr_data;			
                            state_d=CPHA? WRITE_DELAY:WRITE;
                        end
                        if(rd) state_d=READ;
                      end
		     default: state_d=IDLE;
		endcase
	end
	assign mosi=(state_q==WRITE)? wr_data_q[7]:1'b1; //asserted to "1" when not sending/receiving data
	assign rd_data=rd_data_d; 
	assign sclk=sclk_q;
	assign cs_n=cs_n_q;
    assign ready=state_q==IDLE || state_q==HOLD;
endmodule	

Just like what I had stated on my first blog, I love code re-use so I also always parameterize my codes as much as possible. In this case, we can choose the SPI_MODE (0-to-3) and we have two frequency options to run the SPI: HI_FREQ_DIV and LO_FREQ_DIV. This would allow the SPI to switch between low and high frequencies which will be useful for the SD card driver since initializing the SD card is limited to 400KHz max but since we want to maximize the throughput, we must switch to high speed(25MHz) when starting to write data to it.  All other pins are intuitive, we can start reading using rd pin and write using wr pin. Below is the overall schematic of the SPI driver.

image

Now that the SPI driver is ready, we can start making the SD card driver.

SD Card Driver

To interface with an SD card, we will need a MicroSD Card adapter just like the one below. This module is very common to be used by Arduino hobbyists since it has level shifters to switch the 5V inputs to 3.3V which is what an SD card can only handle.

image

On the perspective of the FPGA, the SD card is just a simple addressable sector on which it can read and write. It will use the SPI to send the commands for writing and also the data to be saved to the SD card. The command frame from FPGA to SD card is a fixed length packet (6 bytes). First byte is the index of the command, second-to-fourth bytes are the arguments, and the last byte is the 8-bit CRC. After sending this data packet, clock must continue for another 8 clock cycles before the SD card will start sending the response. Response can either be R1,R2,R3, and R7. List of all SD card commands are available from this pdf (page 219 to page 222).

image

There are a total of 58 usable commands but for my SD card driver, I only used these seven basic commands:

CMD Index Name Description
CMD0 GO_IDLE_STATE Reset then start SPI mode
CMD8 SEND_IF_COND Check voltage range
CMD59 CRC_ON_OFF Turn on/off the CRC validation
ACMD41 SD_SEND_OP_COND Start initialization
CMD58 READ_OCR Check CCS bit
CMD24 WRITE_BLOCK Write in a block(512 bytes fixed)
CMD13 SEND_STATUS Check if write operation succeeded

Now for the tricky part,  before doing a read or write operation, the SD card must be first initialized properly. The sequence of commands for initialization are shown below:

image

For writing to SD card, CMD14 must be used. SD card has a fixed block length of 512 bytes. This means that a write command will not stop until it receive 512 bytes from the host. After writing,the SD card will be busy for some definite time and will leave the DO(or MISO) pin low. When this pin becomes high again, we can then send the next command.

And that is all the things we must know to make a basic SD card driver, here is my driver implementation in Verilog HDL:

`timescale 1ns / 1ps

module sdcard_interface(
    input wire clk,rst,
    output wire led0_r,led0_g,led0_b, //{red,green,blue} red if SDCARD initialization is stuck at CMD0, blue if stuck somewhere else, green if initialization complete
    output idle, //sdcard not busy
    //HOST interface
    input write, //start writing to SD card
    output reg rd_fifo, //read next data to be written
    input[7:0] data, //data to be written to SD catd
    //SPI pinouts
	input wire SD_MISO,
	output wire SD_MOSI,
	output wire SD_DCLK,SD_nCS,
	//UART for debugging
	output wire uart_rx,uart_tx
    );
	 //FSM states
    localparam POWER_ON=0, 
               COMMANDS=1,
               SEND_COMMAND=2,
               RECEIVE_RESPONSE=3,
               END_CMD=4,
			   IDLE=5,
			   DELAY=6,
			   WRITE_1=7,
			   WRITE_2=8,
			   BUSY=9;

					 
    reg[3:0] state_q=0,state_d;
    reg[9:0] counter_q=0,counter_d; //counter for the 74 clk cycles needed for power-on
	reg[3:0] cmd_counter_q=0,cmd_counter_d; //index for cmd_list
	reg[3:0] response_counter_q=0,response_counter_d; //number of bytes needed for a response (R1=1 byte , R7=R3=5 bytes)
    reg[55:0] wr_data_q=0,wr_data_d;
	reg[39:0] rd_data_q=0,rd_data_d;
    reg[2:0] led_q=0,led_d;
	reg stuck_q=0,stuck_d;
	reg[9:0] stuck_counter_q=0,stuck_counter_d;
	reg[15:0] addr_counter_q,addr_counter_d;
	
    //SPI pinouts
    reg rd,wr,hold;
    reg[7:0] wr_data;
    reg clk_div_q=0,clk_div_d;
    wire[7:0] rd_data;
    wire done_tick,ready;
    wire clk_div,cs_n_1;
    
    //uart PINOUTS
	 reg wr_uart,rd_uart;
	 reg[7:0] wr_data_uart;
	 wire[7:0] rd_data_uart;
	 wire rx_empty;
	 
	 //list of commands for SDCARD initialization
	 localparam INIT_LAST_INDEX=6;
	 reg[55:0] cmd_list[10:0];
	 initial begin
		cmd_list[0]=48'h40_00_00_00_00_95;  //CMD0: GO_IDLE_STATE (R1) //resets SDCARD for SPI mode
		cmd_list[1]=48'h48_00_00_01_AA_87;  //CMD8: SEND_IF_COND (R7)  //chceck host voltage supply and if ver2.00 above
		cmd_list[2]=48'h7B_00_00_00_00_83;  //CMD59 CRC_ON_OFF (R1) //turn off CRC checking
		cmd_list[3]=48'h77_00_00_00_00_00;  //CMD55: prefix for every ACMD (application commad) (R1)
		cmd_list[4]=48'h69_40_00_00_00_00;  //ACMD41:SD_SEND_OP_COND ((R1) //iniitialize SDCARD
		cmd_list[5]=48'h7A_00_00_00_00_00;  //CMD58:READ_OCR (R3) //check if SDSC(standard) or SDHC/SDXC
		cmd_list[6]=48'h50_00_00_02_00_00; //CMD16: set block length to 512
		cmd_list[7]=48'h58_00_00_00_00_00;  //CMD24: Single WRITE TO ADDRESS 0
		cmd_list[8]=48'h4D_00_00_00_00_00;  //Status Reg
	 end
	 				 
	 
    //register operations
    always @(posedge clk,posedge rst) begin
        if(rst) begin
            state_q<=0;
            counter_q<=0;
            wr_data_q<=0;
			rd_data_q<=0;
            led_q<=0;
			cmd_counter_q<=0;
			response_counter_q<=0;
			stuck_q<=0;
			stuck_counter_q<=0;
			clk_div_q<=0;
			addr_counter_q<=0;
        end
        else begin
            state_q<=state_d;
            counter_q<=counter_d;
            wr_data_q<=wr_data_d;
			rd_data_q<=rd_data_d;
            led_q<=led_d;
		    cmd_counter_q<=cmd_counter_d;
		    response_counter_q<=response_counter_d;
		    stuck_q<=stuck_d;
		    stuck_counter_q<=stuck_counter_d;
		    clk_div_q<=clk_div_d;
		    addr_counter_q<=addr_counter_d;
        end
    end
    
    //FSM logic 
    always @* begin
        state_d=state_q;
        counter_d=counter_q;
        wr_data_d=wr_data_q;
		rd_data_d=rd_data_q;
        led_d=led_q;
		cmd_counter_d=cmd_counter_q;
	    response_counter_d=response_counter_q;	  
	    stuck_d=stuck_q;
	    stuck_counter_d=stuck_counter_q;
	    clk_div_d=clk_div_q;
	    addr_counter_d=addr_counter_q;
        rd=0;
        wr=0;
        hold=0;
		wr_data=8'hff;
        rd_fifo=0;
		 
        case(state_q)
      ///////////////////////////////////START SDCARD INITIALIZATION////////////////////////////////////////////
            POWER_ON: begin //send at least 74 clk cycles with cs_n and d_out line high
                        rd=1;
                        led_d=3'b100;
					    cmd_counter_d=0;
					    clk_div_d=0;
					    addr_counter_d=0;
                        if(done_tick) begin
                            counter_d=counter_q+1'b1;
                            if(counter_q==15) begin//8*10=80 clk cycles had passed
                                rd=0;
                                state_d=COMMANDS;
                            end
                        end
                      end
                      
            COMMANDS: if(ready) begin //commands to be sent to SDCARD
                        wr_data_d=cmd_list[cmd_counter_q]; 
                        if(cmd_counter_q==7) wr_data_d[39:8]=2049+addr_counter_q; //start address(2050): Real start: 2049-2048+1=2
                        state_d=SEND_COMMAND;
						response_counter_d=0;
						counter_d=0;
		                stuck_counter_d=0;
		                stuck_d=0;
                      end
                      
        SEND_COMMAND: if(ready || done_tick) begin 
                        wr_data_d={wr_data_q[47:0],8'hff}; //shift by 1 byte 
                        wr_data=wr_data_d[55:48];
                        wr=1;
                        counter_d=counter_q+1'b1;
                        if(counter_q==7) begin //6 bytes had been sent to SPI, another 1 byte for the 8 clk cycles needed by sdcard before responding
							wr=0;
                            rd=1; //response always starts at logic 0 so hold the clock until then
                            counter_d=0;
                            state_d=RECEIVE_RESPONSE;
                        end
                      end 
                 
     RECEIVE_RESPONSE: if(done_tick) begin
                         response_counter_d=response_counter_q+1; //counter for some responses that has multiple bytes (R3 and R7)
                         //rules for types of response for every command
                         case(cmd_counter_q) 
                            0: begin
                                    cmd_counter_d=(rd_data==8'h01)? cmd_counter_q+1:cmd_counter_q; //CMD0: resets SDCARD for SPI mode
                                    state_d=END_CMD;
                                end
                           1: begin
                                    rd_data_d={rd_data_q[31:0],rd_data};
                                    rd=1;
                                    led_d=3'b001;
                                    if(response_counter_d==5) begin//5 bytes had been received
                                        cmd_counter_d=(rd_data_d==40'h01_00_00_01_AA)? cmd_counter_q+1:cmd_counter_q; //CMD8: Host Voltage Supply is correct and SDCARD is ver2.00 or later
                                        rd=0;
                                        state_d=END_CMD;
                                    end
                                end
                            2: begin
                                    cmd_counter_d=(rd_data==8'h01)? cmd_counter_q+1:cmd_counter_q; //CMD59: turns off CRC checking
                                    state_d=END_CMD;
                                end
                            3: begin
                                    cmd_counter_d=(rd_data==8'h01)? cmd_counter_q+1:cmd_counter_q; //CMD55: Now ready for next command which is an Application Commmand(ACMD)
                                    state_d=END_CMD;
                                end
                            4: begin
                                    stuck_counter_d=stuck_counter_q+1;
                                    if(stuck_counter_q==1000) stuck_d=1; //if ACMD41 stuck 1000x , go back to power-on
                                    cmd_counter_d=(rd_data==8'h00)? cmd_counter_q+1:cmd_counter_q-1; //ACMD41: Initialization of SDCARD is complete 
                                    state_d=END_CMD;
                                end
                            5: begin
                                    rd_data_d={rd_data_q[31:0],rd_data};
                                    rd=1;
                                    if(response_counter_d==5) begin//5 bytes had been received
                                            rd=0;
                                            cmd_counter_d=(rd_data_d==40'h00_C0_FF_80_00)? cmd_counter_q+1:cmd_counter_q; //CMD58: CCS bit is 1 and SDCARD is classified as SDHC(High Capacity) or SDXC(eXtended Capacity)
                                            state_d=END_CMD;
                                    end
                                end  
                             6: begin
                                    cmd_counter_d=(rd_data==8'h00)? cmd_counter_q+1:cmd_counter_q; //CMD55: Now ready for next command which is an Application Commmand(ACMD)
                                    state_d=END_CMD;
                                end
                             7: begin //acknowledge for write 
                                    state_d=WRITE_1;
                                end
                             8: begin
                                    rd_data_d={rd_data_q[31:0],rd_data};
                                    rd=1;
                                    if(response_counter_d==2) begin//5 bytes had been received
                                        cmd_counter_d=(rd_data_d==16'h0000)? cmd_counter_q+1:cmd_counter_q; //CMD8: Host Voltage Supply is correct and SDCARD is ver2.00 or later
                                        rd=1;
                                        state_d=IDLE;
                                        led_d=3'b010;
                                    end
                                  end
                          endcase			
                         end

				END_CMD:if(ready) begin //must provide 8 clks before shutting down sclk or starting new command
                                wr=1;
                                state_d=stuck_q? POWER_ON:COMMANDS;
                                if(cmd_counter_q==INIT_LAST_INDEX+1 || clk_div_q) state_d=DELAY;
                          end
                   DELAY: if(ready) begin  //delay before switching to high frequency for writing in SDCARD 
                            stuck_counter_d=stuck_counter_d+1;
                            clk_div_d=1;
                            if(stuck_counter_d==1000) begin 
                            led_d=3'b010;
                            state_d=IDLE;
                            end
                          end
   ///////////////////////////////////////INITIALIZATION COMPLETE///////////////////////////////////////////////
   
   
   /////////////////////////////SDCARD READ/WRITE OPERATION//////////////////////////////////////////////////
					IDLE: if(write) begin
					           cmd_counter_d=7; //WRITE
					           state_d=COMMANDS;
					           addr_counter_d=addr_counter_q+1;
					           led_d=100; 
					      end
				 WRITE_1: if(ready || done_tick) begin 
                            if(counter_q==0 || counter_q==513 || counter_q==514) wr_data=8'b1111_1110; //start_token
                            else begin
                                wr_data=data;
                                rd_fifo=1;
                            end
                            wr=1;
                            counter_d=counter_q+1'b1;
                            if(counter_q==515) begin //515 bytes had been sent to SPI,
                                wr=0;
                                rd_fifo=0;
                                rd=1; //Data response immediately
                                counter_d=0;
                                state_d=WRITE_2;
                            end
                          end 
                 WRITE_2: if(done_tick) begin
                                if(rd_data[4:0] == 5'b0_010_1) begin //data accepted
                                    state_d=BUSY;
                                end
                           end
                    BUSY: begin
                            rd=1;
                            if(SD_MISO && done_tick) begin //sd card finishes the writing operation
                                cmd_counter_d=8;
                                state_d=COMMANDS;
                            end
                          end
             default: state_d=POWER_ON;
        endcase
    end
    
    assign SD_nCS=(state_q==POWER_ON || state_q==COMMANDS || (state_q==END_CMD && ready) || state_q==IDLE)? 1'b1:cs_n_1; //at power_on, toggle clk 74 times WHILE cs_n is high

	assign led0_r=led_q[2]? clk:1'b1, 
	       led0_g=led_q[1]? clk:1'b1,
	       led0_b=led_q[0]? clk:1'b1; //PWM used is the clk itself 
    assign idle=state_q==IDLE;
	 
    //module instantiations
    spi #(.HI_FREQ_DIV(20), .LO_FREQ_DIV(250), .SPI_MODE(0)) m0 //High freq: 100MHz/6=16.7MHz , Low freq: 100MHz/250=400KHz
    ( 
        .clk(clk),
        .rst(rst),                                
        //SPI control
        .clk_div(clk_div_q),
        .rd(rd),
        .wr(wr),
        .hold(hold),  //pins to start read or write operation, hold is for holding the clock for multibyte read/write 
        .wr_data(wr_data), //data to be sent to the slave
        .rd_data(rd_data), //data received from the slave
        .done_tick(done_tick), //ticks if either write or read operation is finished
        .ready(ready), //can perform read/write operation only if ready is "1" (except for multibyte read/write where state will never go to IDLE)
        //SPI pinouts
        .miso(SD_MISO),
        .mosi(SD_MOSI),
        .sclk(SD_DCLK),
        .cs_n(cs_n_1)
	);
	
	 uart #(.DBIT(8),.SB_TICK(16),.DVSR(326),.DVSR_WIDTH(9),.FIFO_W(10)) m1 //9600 Baud
	(
		.clk(clk),
		.rst_n({!rst}),
		.rd_uart(rd_uart),
		.wr_uart(wr_uart),
		.wr_data(wr_data_uart),
		.rx(uart_rx),
		.tx(uart_tx),
		.rd_data(rd_data_uart),
		.rx_empty(rx_empty),
		.tx_full()
    );
    
    //UART for debugging SDCARD responses
    always @* begin
    wr_uart=0;
    rd_uart=0;
	wr_data_uart=0;
	    
 	wr_uart= wr || (state_q==RECEIVE_RESPONSE && done_tick) || (state_q==WRITE_2 && done_tick); //write all commands passing thrugh MOSI and MISO
	wr_data_uart=wr? wr_data:rd_data;
    end
		
	
endmodule

For easier debugging of the driver, I added a UART and some RGB leds. At power-up, the SD card will automatically start the initialization process.After initialization,, idle pin will go high, this notifies the host that it can start the write operation by triggering the write pin and send the data to be sent to the data pin. Since 512 subsequent data must be sent in one go, the driver will make the rd_fifo high to notify the host that it needs the next data to write.The first address will depend on parameter FIRST_ADDRESS which on this case is set to address 2050. Below is the overall schematic of the SD card driver.

image

Preview for the Next Blog:

Now that I have implemented the driver for the SD card, will it really work? Next stop: testing the SD card driver.

6-Part Blog Series

  • Security Camera #1: Project Proposal
  • Security Camera #2: SD Card Interfacing
  • Security Camera #3: Testing the SD Card Driver
  • Security Camera #4: Interfacing with OV7670 Camera
  • Security Camera #5: Adding Peripheral Sensors
  • Security Camera #6: Project Demonstration and Final Words

To see more of my FPGA projects, visit my GitHub account: https://github.com/AngeloJacobo

References:

http://elm-chan.org/docs/mmc/mmce.html

http://nerdclub-uk.blogspot.com/2012/11/how-spi-works-with-sd-card.html

http://nerdclub-uk.blogspot.com/2012/11/how-spi-works-with-sd-card.html

https://components101.com/modules/micro-sd-card-module-pinout-features-datasheet-alternatives

https://openlabpro.com/guide/interfacing-microcontrollers-with-sd-card/  

  • Sign in to reply
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube