به نام خدا!

در این مطلب میخوام به موضوع نحوه کارکرد و ارتباط با کارت SD بپردازم!

خوب بریم سراغ اصل بحث:

1- مرحله اول: ارسال 80 پالس کلاک

برای ارتباط SPI با کارت SD باید ابتدا اونو init یا مقداردهی اولیه کرد! برای اینکار باید 80 پالس به SD کارت بفرستیم! یعنی:

- پایه CS یا همون فعال ساز کارت حافطه رو 1 میکنیم! (یعنی در حالت فعال قرار میدیم، و اگه Active-Low باشه باید 0 کنیم)

- سپس 80 پالس به کارت SD میفرستیم!

- دوباره پایه CS رو صفر میکنم!(یعنی در حالت غیر فعال قرار میدیم، و اگه Active-Low باشه باید 1 کنیم)


نکته: برای اینکه بتونید یه پالس بفرستید میتونید در هر لبه بالارونده کلاک، مقدار "سیگنال کلاک مربوط به کارت SD" یا همون SCLK رو NOT کنید! فقط باید توجه داشته باشید چون برای ایجاد یک پالس نیاز است دوبار عمل NOT انجام بشه (یه بار صفر بشه و یه بار یک) پس باید اگه میخواید مثلا 80 پالس رو بفرستید باید 160 بار NOT کنید تا 80 پالس بفرستید!


2- ارسال دستور CMD0

این دستور کارت SD رو ریست میکنه و در مد بیکاری قرار میده و باید در ابتدا ارسال بشه!

جوابی که ارسال میشه باید بیت 0امش برابر با 1 باشه! (طبق جدول R1 که در پایین قرار دادم)

بایت CRC این دستور برابر با 0x95 هست! و کل دستور به شکل زیر میشه:

400000000095


3- ارسال دستور CMD8 برای کارت های SDHC (بالای 4 گیگ)

همونطور که گفتم این دستور برای کارت های SDHC هست که معمولا ظرفیت بالای 4 گیگ دارن! و اگه کارت SD شما SDHC باشه روش نوشته HC که نشون دهنده کارت SD با حجم بالاست!

بایت CRC این دستور برابر است با 0x87 و دستور کلی اون به صورت زیر میشه:

48000001aa87


4- ارسال دستور CMD55 به عنوان یک مقدمه برای دستور بعدی

این دستور نیاز به CRC نداره و میتونید یه عدد همینطوری مثل FF ارسال کنید!

شکل کلی اون به صورت زیر هست:

7700000000FF

5- ارسال دستور CMD 41

این دو دستور باعث میشه که کارت SD از حالت بیکار خارج بشه و برای خواندن/نوشتن با SPI آماده بشه!

6940000000FF


خواندن یک بلاک داده:

برای انجام اینکار طبق جدول موجود در این صفحه ما باید از دستور 17 استفاده کنیم تا بتونیم یک بلاک 512 بایتی رو بخونیم! و طبق قالبی که داشتیم کد ما باید به صورت زیر باشه!

0x51 + address + 0xFF

توجه داشته باشید که address یک آدرس 32 بیتی هست! 

یعنی طبق گفته بالا اگه بخوایم بلاک اول حافظه رو بخونیم باید به جای ادرس 0 قرار بدیم و برای کارت SD بفرستیم!

- حالا باید منتظر بمونیم تا کارت SD دیتای خروجی اش رو یعنی DO خودش رو صفر کنه تا بفهمیم که ارسال دیتا شروع شد!

- حالا باید همه دیتا ها رو دریافت کنیم! الان کارت SD باید 512 بایت دیتای 8 بیتی دریافت کنیم! یعنی الان باید 8 بیت ، 8 بیت دیتا رو دریافت کنیم تا 512 بایت بشه!

- حالا باید 8 بیت CRC رو دریافت کنیم (که مقدارش برامون مهم نیست فقط دریافت میکنیم تا فرآیند دریافت کامل بشه)

- بعد از اون میتونیم 512 بایت به آدرس اضافه کنیم تا بتونیم بلاک (سکتور) بعدی رو بخونیم!


نوشتن یک بلاک:

برای اینکار باید از دستور CMD 24 برای نوشتن یک بلاک و از دستور CMD 25 برای نوشتن چند بلاک استفاده کنیم!

برای تک بلاک:

58 + address + FF

و برای چند بلاک از دستور زیر:

59 + address + FF                                                                             

- بایت بعدی باید FC برای نوشتن چند بلاک - و FE برای نوشتن تک بلاک باشه! (یعنی اگه میخواید فقط یک بلاک بنویسید کافیه بعد از انجام مراحل بالا بایت FE رو بفرستید) دلیل این نکته در پایین ذکر شده است! 

- بعد 512 بایت 8 بیتی بفرستید!

- در نهایت دو بایت CRC بفرستید و مقدار آن را FF قرار دهید!


نکته : توجه کنید که باید همیشه پر ارزشترین بایت شما FF باشد تا به کارت SD اعلام کنید که میخواهید با آن کار کنید (از جمله ارسال و دریافت) به این بایت، بایت شروع میگیم! و در چند خط بالا که گفتم باید برای نوشتن تک بلاک FE رو بفرستید منظورم همین بایت هست که بایت شروع هست ولی اینبار FF نیست!

مثلا برای ارسال دستور CMD0 که در بالا گفتیم کدش 400000000095 میشه رو باید به صورت FF400000000095 بفرستید! (و همینطور بقیه..)



توجه: هر دستور ارسالی از قالب زیر پشتیبانی میکند!

Start Bit Transmission Bit Command Bit Pattern Argument CRC7 End Bit
Bit position 47 46 [45:40] [39:8] [7:1] 0
Width (Bits) 1 1 6 32 7 1
Value 0 1 1


جدول بالا از این سایت برداشته شده!


ساختار جواب R1


Bit
0 In idle state The card is in idle state and running the initializing process.
1 Erase reset An erase sequence was cleared before executing because an out of erase sequence command was received.
2 Illegal command An illegal command code was detected.
3 Communication CRC error The CRC check of the last command failed.
4 Erase sequence error An error in the sequence of erase commands occurred.
5 Address error A misaligned address that did not match the block length was used in the command.
6 Parameter error The command’s argument (e.g. address, block length) was outside the allowed range for this card.
7 MSB Always Zero


http://www.chlazza.net/sdcardinfo.html


یه نمونه کد هم براتون قرار میدم! که از اینجا کپی کردم! و تست کردم به خوبی جواب داده (با کارت SD ظرفیت 2 گیگ)

-- VHDL SD card interface
-- by Steven J. Merrifield, June 2008
-- Reads and writes a single block of data, and also writes continuous data
-- Tested on Xilinx Spartan 3 hardware, using Transcend and SanDisk Ultra II cards
-- Read states are derived from the Apple II emulator by Stephen Edwards 
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity sd_controller is
port (
cs : out std_logic;
mosi : out std_logic;
miso : in std_logic;
sclk : out std_logic;
rd : in std_logic;
wr : in std_logic;
dm_in : in std_logic; -- data mode, 0 = write continuously, 1 = write single block
reset : in std_logic;
din : in std_logic_vector(7 downto 0);
dout : out std_logic_vector(7 downto 0);
clk : in std_logic -- twice the SPI clk
);
end sd_controller;
architecture rtl of sd_controller is
type states is (
RST,
INIT,
CMD0,
CMD55,
CMD41,
POLL_CMD,
  
IDLE, -- wait for read or write pulse
READ_BLOCK,
READ_BLOCK_WAIT,
READ_BLOCK_DATA,
READ_BLOCK_CRC,
SEND_CMD,
RECEIVE_BYTE_WAIT,
RECEIVE_BYTE,
WRITE_BLOCK_CMD,
WRITE_BLOCK_INIT, -- initialise write command
WRITE_BLOCK_DATA, -- loop through all data bytes
WRITE_BLOCK_BYTE, -- send one byte
WRITE_BLOCK_WAIT -- wait until not busy
);

-- one start byte, plus 512 bytes of data, plus two FF end bytes (CRC)
constant WRITE_DATA_SIZE : integer := 515;

signal state, return_state : states := RST ;
signal sclk_sig : std_logic := '0';
signal cmd_out : std_logic_vector(55 downto 0);
signal recv_data : std_logic_vector(7 downto 0);
signal address : std_logic_vector(31 downto 0);
signal cmd_mode : std_logic := '1';
signal data_mode : std_logic := '1'; --  0=continuous , 1= single block
signal response_mode : std_logic := '1';
signal data_sig : std_logic_vector(7 downto 0) := x"00";
begin
  
process(clk,reset)
variable byte_counter : integer range 0 to WRITE_DATA_SIZE;
variable bit_counter : integer range 0 to 160;
begin
data_mode <= dm_in;
if rising_edge(clk) then
if (reset='1') then
state <= RST;
sclk_sig <= '0';
else
case state is

when RST =>
sclk_sig <= '0';
cmd_out <= (others => '1');
address <= x"00000000";
byte_counter := 0;
cmd_mode <= '1'; -- 0=data, 1=command
response_mode <= '1'; -- 0=data, 1=command
bit_counter := 160;
cs <= '1';
state <= INIT;

when INIT => -- CS=1, send 80 clocks, CS=0
if (bit_counter = 0) then
cs <= '0';
state <= CMD0;
else
bit_counter := bit_counter - 1;
sclk_sig <= not sclk_sig;
end if;

when CMD0 =>
cmd_out <= x"FF400000000095";
bit_counter := 55;
return_state <= CMD55;
state <= SEND_CMD;
when CMD55 =>
cmd_out <= x"FF770000000001"; -- 55d OR 40h = 77h
bit_counter := 55;
return_state <= CMD41;
state <= SEND_CMD;

when CMD41 =>
cmd_out <= x"FF690000000001"; -- 41d OR 40h = 69h
bit_counter := 55;
return_state <= POLL_CMD;
state <= SEND_CMD;

when POLL_CMD =>
if (recv_data(0) = '0') then
state <= IDLE;
else
state <= CMD55;
end if;
       
when IDLE => 
if (rd = '1') then
state <= READ_BLOCK;
elsif (wr='1') then
state <= WRITE_BLOCK_CMD;
else
state <= IDLE;
end if;

when READ_BLOCK =>
cmd_out <= x"FF" & x"51" & address & x"FF";
bit_counter := 55;
return_state <= READ_BLOCK_WAIT;
state <= SEND_CMD;

when READ_BLOCK_WAIT =>
if (sclk_sig='1' and miso='0') then
state <= READ_BLOCK_DATA;
byte_counter := 511;
bit_counter := 7;
return_state <= READ_BLOCK_DATA;
state <= RECEIVE_BYTE;
end if;
sclk_sig <= not sclk_sig;
when READ_BLOCK_DATA =>
if (byte_counter = 0) then
bit_counter := 7;
return_state <= READ_BLOCK_CRC;
state <= RECEIVE_BYTE;
else
byte_counter := byte_counter - 1;
return_state <= READ_BLOCK_DATA;
bit_counter := 7;
state <= RECEIVE_BYTE;
end if;

when READ_BLOCK_CRC =>
bit_counter := 7;
return_state <= IDLE;
address <= std_logic_vector(unsigned(address) + x"200");
state <= RECEIVE_BYTE;
       
when SEND_CMD =>
if (sclk_sig = '1') then
if (bit_counter = 0) then
state <= RECEIVE_BYTE_WAIT; -- % in the end work this line will run
else
bit_counter := bit_counter - 1;
cmd_out <= cmd_out(54 downto 0) & '1';
end if;
end if;
sclk_sig <= not sclk_sig;

when RECEIVE_BYTE_WAIT =>
if (sclk_sig = '1') then
if (miso = '0') then
recv_data <= (others => '0');
if (response_mode='0') then
bit_counter := 3; -- already read bits 7..4
else
bit_counter := 6; -- already read bit 7
end if;
state <= RECEIVE_BYTE;
end if;
end if;
sclk_sig <= not sclk_sig;
when RECEIVE_BYTE =>
if (sclk_sig = '1') then
recv_data <= recv_data(6 downto 0) & miso;
if (bit_counter = 0) then
state <= return_state;
dout <= recv_data(6 downto 0) & miso;
else
bit_counter := bit_counter - 1;
end if;
end if;
sclk_sig <= not sclk_sig;
when WRITE_BLOCK_CMD =>
cmd_mode <= '1';
if (data_mode = '0') then
cmd_out <= x"FF" & x"59" & address & x"FF"; -- continuous
else
cmd_out <= x"FF" & x"58" & address & x"FF"; -- single block
end if;
bit_counter := 55;
return_state <= WRITE_BLOCK_INIT;
state <= SEND_CMD;

when WRITE_BLOCK_INIT => 
cmd_mode <= '0';
byte_counter := WRITE_DATA_SIZE; 
state <= WRITE_BLOCK_DATA;

when WRITE_BLOCK_DATA => 
if byte_counter = 0 then
state <= RECEIVE_BYTE_WAIT;
return_state <= WRITE_BLOCK_WAIT;
response_mode <= '0';
else
if ((byte_counter = 2) or (byte_counter = 1)) then
data_sig <= x"FF"; -- two CRC bytes
elsif byte_counter = WRITE_DATA_SIZE then
if (data_mode='0') then
data_sig <= x"FC"; -- start byte, multiple blocks
else
data_sig <= x"FE"; -- start byte, single block
end if;
else
-- just a counter, get real data here
data_sig <= std_logic_vector(to_unsigned(byte_counter,8));
end if;
bit_counter := 7;
state <= WRITE_BLOCK_BYTE;
byte_counter := byte_counter - 1;
end if;

when WRITE_BLOCK_BYTE => 
if (sclk_sig = '1') then
if bit_counter=0 then
state <= WRITE_BLOCK_DATA;
else
data_sig <= data_sig(6 downto 0) & '1';
bit_counter := bit_counter - 1;
end if;
end if;
sclk_sig <= not sclk_sig;

when WRITE_BLOCK_WAIT =>
response_mode <= '1';
if sclk_sig='1' then
if MISO='1' then
if (data_mode='0') then
state <= WRITE_BLOCK_INIT;
else
address <= std_logic_vector(unsigned(address) + x"200");
state <= IDLE;
end if;
end if;
end if;
sclk_sig <= not sclk_sig;
when others => state <= IDLE;
        end case;
      end if;
    end if;
  end process;
  sclk <= sclk_sig;
  mosi <= cmd_out(55) when cmd_mode='1' else data_sig(7);
  
end rtl;

خوب امیدوارم مفید بوده باشه!

میدونم خیلی کامل نبود ولی خوب همینم 3 ساعت وقتم رو گرفت :)

موفق باشید

یا علی مدد...!