1
\$\begingroup\$

I'm trying to communicate with SD card. However I'm stuck on CMD8 - when I send CMD0 (400000000095) I got 0x01 after one cycle. When I send CMD8 (48000001aa87) I got nothing (everything I read is 0xFF).

My process of initialization is as follows:

  • Set speed of bus to 100 kHz
  • After SD card is detected wait 2 ms
  • Set CS high
  • Write 0xFF to bus for 100 cycles (800 bits)
  • Set CS low
  • Write 0xFF to bus for 100 cycles (800 bits)
  • Write CMD0 to bus
  • Read until response is not 0xFF and parse it
  • Write CMD8 to bus
  • Read until response is not 0xFF (this does not happens)

What am I doing wrong?

EDIT: Here's code:


bitflags::bitflags! {
    #[derive(Eq, PartialEq, Clone, Copy)]
    struct Status: u8 {
        const IDLE                 = 0b_0000_0001;
        const ERASE_RESET          = 0b_0000_0010;
        const ILLEGAL_COMMAND      = 0b_0000_0100;
        const CRC_ERROR            = 0b_0000_1000;
        const ERASE_SEQUENCE_ERROR = 0b_0001_0000;
        const ADDRESS_ERROR        = 0b_0010_0000;
        const PARAMETER_ERROR      = 0b_0100_0000;
        const BIT_7                = 0b_1000_0000;
    }
}

impl defmt::Format for Status {
    fn format(&self, fmt: defmt::Formatter) {
        defmt::write!(fmt, "STATUS {{ ");
        if self.contains(Status::BIT_7) {
            defmt::write!(fmt, "BIT_7 ");
        }
        if self.contains(Status::PARAMETER_ERROR) {
            defmt::write!(fmt, "PARAMETER_ERROR ");
        }
        if self.contains(Status::ADDRESS_ERROR) {
            defmt::write!(fmt, "ADDRESS_ERROR ");
        }
        if self.contains(Status::ERASE_SEQUENCE_ERROR) {
            defmt::write!(fmt, "ERASE_SEQUENCE_ERROR ");
        }
        if self.contains(Status::CRC_ERROR) {
            defmt::write!(fmt, "CRC_ERROR ");
        }
        if self.contains(Status::ILLEGAL_COMMAND) {
            defmt::write!(fmt, "ILLEGAL_COMMAND ");
        }
        if self.contains(Status::ERASE_RESET) {
            defmt::write!(fmt, "ERASE_RESET ");
        }
        if self.contains(Status::IDLE) {
            defmt::write!(fmt, "IDLE ");
        }
        defmt::write!(fmt, "}}");
    }
}

enum Error {
    Timeout,
    Status(Status),
    SpiError(spi::Error)
}

impl defmt::Format for Error {
    fn format(&self, fmt: defmt::Formatter) {
        match self {
            Error::Timeout => defmt::write!(fmt, "Error::Timeout"),
            Error::Status(status) => defmt::write!(fmt, "Error::ErrorStatus({})", status),
            Error::SpiError(_) => defmt::write!(fmt, "Error::SpiError(Unknown)")
        }
    }
}

impl From<spi::Error> for Error {
    fn from(value: spi::Error) -> Self {
        Error::SpiError(value)
    }
}

trait Command  {
    type Argument: Into<[u8; 4]>;
    type Response: Response;
    const CODE: u8;
}

trait Response: Sized
{
    const LENGTH: usize;
    fn from(raw: [u8; Self::LENGTH], expect_idle: bool) -> Result<Self, Error>;
}

struct ZeroArgument;

impl From<ZeroArgument> for [u8; 4] {
    fn from(_value: ZeroArgument) -> Self {
        [0; 4]
    }
}

struct CMD0;

impl Command for CMD0 {
    type Argument = ZeroArgument;
    type Response = R1;
    const CODE: u8 = 0x40;
}

struct CMD8;

#[derive(defmt::Format)]
enum CMD8Voltage {
    Voltage3V3,
    #[allow(unused)]
    LowVoltage
}

struct CMD8Argument {
    pcie_12_v: bool,
    pcie_availability: bool,
    voltage: CMD8Voltage,
    check_pattern: u8
}

impl Command for CMD8 {
    type Argument = CMD8Argument;
    type Response = R7;
    const CODE: u8 = 0x48;
}

impl Default for CMD8Argument {
    fn default() -> Self {
        Self {
            pcie_12_v: false,
            pcie_availability: false,
            voltage: CMD8Voltage::Voltage3V3,
            check_pattern: 0x00
        }
    }
}

impl From<CMD8Argument> for [u8; 4] {
    #[allow(clippy::identity_op)]
    fn from(value: CMD8Argument) -> Self {
        bitfield::bitfield! {
            pub struct ArgEncode(u32);
            u8;
            pub get_check_pattern, set_check_pattern: 7, 0;
            pub get_voltage_supplied, set_voltage_supplied: 11, 8;
            pub get_pcie_availability, set_pcie_availability: 12;
            pub get_pcie_12v_support, set_pcie_12v_support: 13;
            pub get_reserved, set_reserved: 31, 14;
        }
        let mut encode = ArgEncode(0);
        encode.set_check_pattern(value.check_pattern);
        encode.set_voltage_supplied(match value.voltage {
            CMD8Voltage::Voltage3V3 => 0b0001,
            CMD8Voltage::LowVoltage => 0b0010
        });
        encode.set_pcie_availability(value.pcie_availability);
        encode.set_pcie_12v_support(value.pcie_12_v);
        let mut result = [0; 4];
        result[0] = ((encode.0 >> 24) & 0xFF) as u8;
        result[1] = ((encode.0 >> 16) & 0xFF) as u8;
        result[2] = ((encode.0 >>  8) & 0xFF) as u8;
        result[3] = ((encode.0 >>  0) & 0xFF) as u8;
        result
    }
}

struct R1;

impl Response for R1 {
    const LENGTH: usize = 1;

    fn from(raw: [u8; Self::LENGTH], expect_idle: bool) -> Result<Self, Error> {
        let status = Status::from_bits(raw[0]).unwrap();
        let error_state = !(status - Status::IDLE).is_empty();
        let unexpected_idle = status.contains(Status::IDLE) != expect_idle;
        if !(error_state || unexpected_idle) {
            Ok(Self)
        } else {
            Err(Error::Status(status))
        }
    }
}

#[derive(defmt::Format)]
struct R7 {
    command_version: u8,
    voltage_accepted: Option<CMD8Voltage>,
    pattern: u8
}

impl Response for R7 {
    const LENGTH: usize = 5;

    fn from(raw: [u8; Self::LENGTH], expect_idle: bool) -> Result<Self, Error> {
        let first_byte = [raw[0]];
        let R1 = <R1 as Response>::from(first_byte, expect_idle)?;
        bitfield::bitfield! {
            pub struct RespEncode(u32);
            u8;
            pub get_command_version, set_command_version: 31, 28;
            pub get_reserved, set_reserved: 27, 12;
            pub get_voltage_accepted, set_voltage_accepted: 11, 8;
            pub get_pattern, set_pattern: 7, 0;
        }
        #[allow(clippy::identity_op)]
        let response = RespEncode(
            ((raw[1] as u32) << 24) |
            ((raw[2] as u32) << 16) |
            ((raw[3] as u32) <<  8) |
            ((raw[4] as u32) <<  0)
        );
        Ok(R7 {
            command_version: response.get_command_version(),
            voltage_accepted: match response.get_voltage_accepted() {
                0b0001 => Some(CMD8Voltage::Voltage3V3),
                0b0010 => Some(CMD8Voltage::LowVoltage),
                _ => None
            },
            pattern: response.get_pattern()
        })
    }
}

impl<'d, T: spi::Instance, CS: gpio::Pin, DET: gpio::Pin> SD<'d, T, CS, DET> {
    // ...

    async fn send_command<C: Command>(&mut self, argument: C::Argument, expect_idle: bool, mut timeout: Option<u32>) -> Result<C::Response, Error>
    where
        [u8; C::Response::LENGTH]: Sized
    {
        let mut command = [0; 6];
        command[0] = C::CODE;
        let argument: [u8; 4] = argument.into();
        command[1..5].copy_from_slice(&argument);
        let mut digest = CRC.digest();
        digest.update(&command[0..5]);
        command[5] = (digest.finalize() << 1) | 1;
        defmt::info!("Sending command: {:02x}", command);
        self.spi.write(&command).await?;
        let mut buffer = [0u8; C::Response::LENGTH];
        loop {
            if let Some(ref mut timeout) = timeout {
                if *timeout == 0 {
                    defmt::error!("Timeout on command");
                    return Err(Error::Timeout);
                }
                *timeout -= 1;
            }
            
            self.spi.read(&mut buffer[0..1]).await?;
            if buffer[0] != 0xFF {
                break;
            }
            defmt::info!("Still no response");
        }
        self.spi.read(&mut buffer[1..]).await?;
        defmt::info!("Got response: {:02x}", buffer);
        <C::Response as Response>::from(buffer, expect_idle)
    }

    async fn spi_wait(&mut self, cycles: u32) -> Result<(), Error> {
        for _ in 0..cycles {
            self.spi.write(&[0xFF]).await?;
        }
        Ok(())
    }

    async fn on_insert(&mut self) -> Result<(), Error>{
        defmt::info!("Waiting for voltage to settle");
        Timer::after(Duration::from_millis(2)).await;
        defmt::info!("Putting SD into SPI mode");
        self.cs.set_high();
        self.spi_wait(100).await?;
        self.cs.set_low();
        self.spi_wait(100).await?;

        loop {
            defmt::info!("Attempting to reset");
            match self.send_command::<CMD0>(ZeroArgument, true, Some(8)).await {
                Ok(R1) => {
                    break;
                }
                Err(Error::Timeout) => {},
                Err(e) => Err(e)?
            }
        }
        defmt::info!("Reset");
        defmt::info!("Sending voltage selection");
        let arg = CMD8Argument {
            check_pattern: 0xAA,
            ..Default::default()
        };
        let response = self.send_command::<CMD8>(arg, true, Some(8)).await?;
        defmt::info!("Decoded response as {}", response);
        Ok(())
    }
}
\$\endgroup\$
4
  • \$\begingroup\$ Do you keep the CS pin low after sending the initial clock cycles? \$\endgroup\$
    – nanash1
    Mar 30, 2023 at 8:13
  • \$\begingroup\$ Can you add your section of code that sends the commands and waits for the responses? \$\endgroup\$
    – HandyHowie
    Mar 30, 2023 at 8:42
  • \$\begingroup\$ @nanash1 Yes. It's low \$\endgroup\$ Mar 30, 2023 at 8:57
  • \$\begingroup\$ @HandyHowie Added \$\endgroup\$ Mar 30, 2023 at 9:02

1 Answer 1

1
\$\begingroup\$

I'm not familiar with Rust, so I can't say anything about the code itself and have to go by your description. But I can see two potential problems:

  1. The CS pin shouldn't be kept low in between commands. According to the official SD specification, the CS line should be low for “NCS + Command bytes + NCR + Card Response + NEC”. Otherwise it should be high. I'm not sure if this is the issue here, but that's what standard says.

  2. You have no NCS bytes. Most cards don’t need NCS padding bytes in my experiance, but I’ve personally encountered one card (SanDisk Industrial XI), that won’t work without at least one byte of padding.

\$\endgroup\$
1
  • \$\begingroup\$ I added 8 NCS bytes and flipped CS on and off and it worked. \$\endgroup\$ Mar 31, 2023 at 3:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.