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).
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.
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.
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.
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).
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:
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.
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/