IO模拟SPI操作SD卡系列之二.单块读写

文摘   2024-08-12 08:02   湖南  

.前言

前面我们完成了SD卡的初始化实现,现在继续来实现单块的读写。

二. 单块读

2.1读命令

命令CMD17 - READ_SINGLE_BLOCK

命令格式如下, 参数是32位的地址,需要注意的是对于SDSC(OCRCCS=0)该地址单位是字节,对于SDHCSDXC(OCRCCS=1), 则单位是512字节。

一次传输不能穿越block边界,除非CSDREAD_BLK_MISALIGN设置。

我们一般就按照一次固定读一个Block来进行。

2.2读流程

如下,主机发送命令command,等待设备回r1response,之后最多100msdata blockCRC(16)data block的第一个字节是0xFE(start token下图未体现),后面才是数据,最后是16CRCCRC可不检查。

2.3读出错

读错误,则回data errordata error是一个字节, 4位表示错误状态,高4位为0.

2.4超时判断

对于SDHCSDXC使用100mS的超时时间,对于SDSC则使用100ms(TAAC+NSAC)*100两者的最小值。

2.5代码逻辑

1.发送命令,R1,如果超过8个字节都是读到0xFF则失败,否则继续

2.继续读字节,如果读到0xFE则继续读数据,如果读到0x0x(x不为0)则错误返回,其他值则继续等待直到读到0xFE或者0x0x的错误值,如果超果100mS,则超时返回。

3.如果上面读到了0xFE,则继续读n+2个字节,n为需要读出的字节数,216CRC

2.6实测波形

发送命令读块地址0

51 00 00 00 00 55

等待r1返回非0xFF,即0x00

然后继续等待0xFE,读数据和CRC

这里CRCDA 80

.单块写

3.1写命令

写命令CMD24-WRITE_BLOCK

命令格式如下, 参数是32位的地址,需要注意的是对于SDSC(OCRCCS=0)该地址单位是字节,对于SDHCSDXC(OCRCCS=1), 则单位是512字节。

一次传输不能穿越block边界,除非CSDREAD_BLK_MISALIGN设置。

我们一般就按照一次固定写一个Block来进行。

3.2写流程

如下,主机发送命令command,等待设备回r1response,如果超过8个字节的0xFF则读r1失败返回,之后发data blockdata block的第一个字节是0xFE(下图未体现),后面才是数据。

设备收到数据后回data_respense, 然后回busy

3.3 响应

卡收到数的响应如下,所以回0x05表示接受数据。

3.4 实测波形

发送命令写块0地址

58 00 00 00 00 6F

等响应r1,为0x00

然后发0xFE+数据

无需发CRC

然后等待响应,这里是0x05表示卡接受了数据

然后等待非busy,返回非0x00

.代码实现与测试

Sd.c中实现单块读写,sd.h中申明

int sd_read_sblock(sd_dev_st* dev, uint8_t* buffer, uint32_t block_addr){    uint8_t tmp = 0xff;    uint32_t addr;    uint8_t r1;    uint8_t token;    uint8_t crc[2];    uint8_t cmd_buffer[6];    int res;    int timeout;
if((dev == (sd_dev_st*)0) || (buffer == (uint8_t*)0)) { return -1; } if(dev->en_extclk) { dev->transfer(&tmp,0,1); } dev->setcs(0); if(dev->en_extclk) { dev->transfer(&tmp,0,1); }
if(dev->ccs == 0) { addr = block_addr * 512; /* SDSC使用字节地址 */ } else { addr = block_addr; /* SDHC SDXC使用block地址 */ } sd_set_command(cmd_buffer, 17, addr); dev->transfer(cmd_buffer,0,6); /* 6字节命令 */
r1 = sd_read_r1(dev); if(r1 != 0xFF) { timeout = 100; token = 0xFF; do { dev->transfer(0,&token,1); if(token == 0xFF) { dev->delayms(1); } } while(((timeout--) > 0) && (token == 0xFF));
if(timeout > 0) { if(token == 0xFE) { dev->transfer(0,buffer,512); dev->transfer(0,crc,2); res = 0; } else { res = -4; } } else { res = -3; } } else { res = -2; }
if(dev->en_extclk) { dev->transfer(&tmp,0,1); } dev->setcs(1); if(dev->en_extclk) { dev->transfer(&tmp,0,1); } return res;}

int sd_write_sblock(sd_dev_st* dev, uint8_t* buffer, uint32_t block_addr){ uint8_t tmp = 0xff; uint32_t addr; uint8_t r1; uint8_t token; uint8_t statrt_token = 0xFE; uint8_t cmd_buffer[6]; int res; int timeout; if((dev==(sd_dev_st*)0) || (buffer==(uint8_t*)0)) { return -1; } if(dev->en_extclk) { dev->transfer(&tmp,0,1); } dev->setcs(0); if(dev->en_extclk) { dev->transfer(&tmp,0,1); }
if(dev->ccs == 0) { addr = block_addr * 512; /* SDSC使用字节地址 */ } else { addr = block_addr; /* SDHC SDXC使用block地址 */ } sd_set_command(cmd_buffer, 24, addr); dev->transfer(cmd_buffer,0,6); /* 6字节命令 */
r1 = sd_read_r1(dev); if(r1 != 0xFF) { dev->transfer(&statrt_token, 0, 1); /* 发start token */ dev->transfer(buffer, 0, 512); /* 发数据 */
timeout = 250; token = 0xFF; do { dev->transfer(0,&token,1); if(token == 0xFF) { dev->delayms(1); } } while(((timeout--) > 0) && (token == 0xFF));
if(timeout > 0) { if((token & 0x1F) == 0x05) /* 数据被设备接受 */ { timeout = 250; /* 等待变为非0,即非busy */ do { dev->transfer(0,&token,1); if(token == 0x00) { dev->delayms(1); } } while(((timeout--) > 0) && (token == 0)); res = 0; } else { res = -4; } } else { res = -3; } } else { res = -2; }
if(dev->en_extclk) { dev->transfer(&tmp,0,1); } dev->setcs(1); if(dev->en_extclk) { dev->transfer(&tmp,0,1); } return res;}
/** * @fn sd_read_sblock * 读单Block * @param[in] dev @ref sd_dev_st 指向设备实例 * @param[out] buffer 存储读到的数据 * @param[in] block_addr block地址 * @retval 0:成功 * @retval -1:参数错误 * @retval -2:R1超时 * @retval -3:响应超时 * @retval -4:start token超时 */int sd_read_sblock(sd_dev_st* dev, uint8_t* buffer, uint32_t block_addr);
/** * @fn sd_write_sblock * 写单Block * @param[in] dev @ref sd_dev_st 指向设备实例 * @param[in] buffer 存储待写的数据 * @param[in] block_addr block地址 * @retval 0:成功 * @retval -1:参数错误 * @retval -2:R1超时 * @retval -3:响应超时 * @retval -4:数据未被接受 */int sd_write_sblock(sd_dev_st* dev, uint8_t* buffer, uint32_t block_addr);

测试代码如下

int sd_itf_init(void){    static uint8_t s_read_buffer[2][512];
    if(0 == sd_init(&s_sd_dev))    {        /* 单块写 */        memset(s_read_buffer[0],0x55,512);        if(0 == sd_write_sblock(&s_sd_dev, s_read_buffer[0], 0x00000000))        {
        }        else        {            printf("write sblock err\r\n");        }
        memset(s_read_buffer[1],0xAA,512);        if(0 == sd_write_sblock(&s_sd_dev, s_read_buffer[1], 0x00000001))        {
        }        else        {            printf("write sblock err\r\n");        }
        /* 单块读 */        memset(s_read_buffer[0],0,512);        memset(s_read_buffer[1],0,512);        if(0 == sd_read_sblock(&s_sd_dev, s_read_buffer[0], 0x00000000))        {            printf("[DATA]\r\n");            for (int i = 0; i < 512; ++i)             {                if(i%16==0)                {                    printf("\r\n");                }                printf("0x%02x,",s_read_buffer[0][i]);            }            printf("\r\n");        }        else        {            printf("read sblock err\r\n");        }
        if(0 == sd_read_sblock(&s_sd_dev, s_read_buffer[1], 0x00000001))        {            printf("[DATA]\r\n");            for (int i = 0; i < 512; ++i)             {                if(i%16==0)                {                    printf("\r\n");                }                printf("0x%02x,",s_read_buffer[1][i]);            }            printf("\r\n");        }        else        {            printf("read sblock err\r\n");        }    }
    return 0;}

测试打印如下

.总结

按照手册流程图即可完成单块读写的操作。

注意等待响应,非busy等时的处理,错误响应处理,以及超时处理。
















嵌入式Lee
嵌入式软硬件技术:RTOS,GUI,FS,协议栈,ARM,总线,嵌入式C,开发环境 and blablaba....多年经验分享,非硬货不发,带你扒开每一个技术背后的根本原理。
 最新文章