IO模拟SPI操作SD卡系列之三.多块读写

文摘   2024-08-13 08:00   湖南  

一. 前言

前面我们实现了单块的读写,在大批量连续读写时使用多块读写效率更高,所以我们继续来实现多块的读写,有了前面的基础,很快就能实现多块的读写,在原来的基础上主要考虑下多块间的处理。

对应的波形,使用DSView软件打开

通过百度网盘分享的文件:硬件SPI读写TF卡.dsl

链接:https://pan.baidu.com/s/1skq3waazsWMq_cpwY3aPzw?pwd=abqz 

提取码:abqz


二. 多块读实现

多块读命令

使用CMD18

多块读流程,多了个CDM12 Stop命令操作

实现代码如下

int sd_read_mblock(sd_dev_st* dev, uint8_t* buffer, uint32_t block_addr, uint32_t count){    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) || (count == 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, 18, addr); dev->transfer(cmd_buffer,0,6); /* 6字节命令 */
r1 = sd_read_r1(dev); if(r1 != 0xFF) { for(uint32_t bn=0; bn<count; bn++) { timeout = 100000/dev->check_interval; token = 0xFF; do { dev->transfer(0,&token,1); if(token == 0xFF) { dev->delayus(dev->check_interval); } } while(((timeout--) > 0) && (token == 0xFF));
if(timeout > 0) { if(token == 0xFE) { dev->transfer(0,buffer+bn*512,512); dev->transfer(0,crc,2); } else { res = -4; break; } } else { res = -3; break; } }
/* stop */ sd_set_command(cmd_buffer, 12, 0); dev->transfer(cmd_buffer,0,6); /* 6字节命令 */
/* 等1个字节 */ dev->transfer(&tmp,0,1);
r1 = sd_read_r1(dev); if(r1 < 2) { res = 0; } else { res = -5; } } 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_mblock * 读多Block * @param[in] dev @ref sd_dev_st 指向设备实例 * @param[out] buffer 存储读到的数据 * @param[in] block_addr block地址 * @param[in] count 需要读取的block数 * @retval 0:成功 * @retval -1:参数错误 * @retval -2:R1超时 * @retval -3:响应超时 * @retval -4:start token超时 * @retval -5:stop 未收到响应 */int sd_read_mblock(sd_dev_st* dev, uint8_t* buffer, uint32_t block_addr, uint32_t count);

实测波形如下

发送命令52 00 00 00 00 A1从块0开始读,等到r1响应0x00

继续等响应0xFE,是0xFF则继续等

等待0xFE出现,读后面的数据

读到数据和2字节CRCDA 80。后面是FF,继续等待下一个BLOCK0xFE开始

等到下一个BLOCK0xFE,读后面的数据和CRC

CRCA521

发送CMD12结束读

发送4C 00 00 00 00 61

一个字节后响应0x00

注意这里要一个字节后再读响应,上面的0x7F是数据不是响应。

三. 多块写实现

多块写命令

使用CMD25

多块写流程,使用Stop Token0xFD结束写,注意StartToken不再是0xFE而是0xFC

实现代码如下

int sd_write_mblock(sd_dev_st* dev, uint8_t* buffer, uint32_t block_addr, uint32_t count){    uint8_t tmp = 0xff;    uint32_t addr;    uint8_t r1;    uint8_t token;    uint8_t statrt_token = 0xFC;    uint8_t stop_token = 0xFD;    uint8_t cmd_buffer[6];    int res;    int timeout;
if((dev == (sd_dev_st*)0) || (buffer == (uint8_t*)0) || (count == 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, 25, addr); dev->transfer(cmd_buffer,0,6); /* 6字节命令 */
r1 = sd_read_r1(dev); if(r1 != 0xFF) { for(uint32_t bn=0; bn<count; bn++) { dev->transfer(&statrt_token, 0, 1); /* 发start token */ dev->transfer(buffer+bn*512, 0, 512); /* 发数据 */
timeout = 250000/dev->check_interval; token = 0xFF; do { dev->transfer(0,&token,1); if(token == 0xFF) { dev->delayus(dev->check_interval); } } while(((timeout--) > 0) && (token == 0xFF));
if(timeout > 0) { if((token & 0x1F) == 0x05) /* 数据被设备接受 */ { timeout = 250000/dev->check_interval; /* 等待变为非0,即非busy */ do { dev->transfer(0,&token,1); if(token == 0x00) { dev->delayus(dev->check_interval); } } while(((timeout--) > 0) && (token == 0)); res = 0; } else { res = -4; break; } } else { res = -3; break; } }
/* stop */ dev->transfer(&stop_token, 0, 1); /* 发stop token */ /* 等待一个字节 */ dev->transfer(&tmp, 0, 1);
/* 等待非busy */ timeout = 250000/dev->check_interval; token = 0xFF; do { dev->transfer(0,&token,1); if(token == 0x00) { dev->delayus(dev->check_interval); } } while(((timeout--) > 0) && (token == 0x00)); if(token != 0x00) { res = 0; } else { res = -5; } } 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_write_mblock * 写多Block * @param[in] dev @ref sd_dev_st 指向设备实例 * @param[in] buffer 存储待写的数据 * @param[in] block_addr block地址 * @param[in] count 需要写的block数 * @retval -1:参数错误 * @retval -2:R1超时 * @retval -3:响应超时 * @retval -4:数据未被接受 * @retval -5:stop后busy超时 */int sd_write_mblock(sd_dev_st* dev, uint8_t* buffer, uint32_t block_addr, uint32_t count);

实测波形如下

发送59 00 00 00 00 03开始写,等待响应r1=0x00

发送start 0xFC+数据

等待响应,为0xFF,继续等

等到0x05表示被卡接受,继续等待非0x00

等待变为了非00,即非busy,继续下一块写

写完等响应

等到0x05响应表示被接受,继续等待非0x00

发送0xFD,结束


多块写注意发完0xFD Stop Token后不是马上回buys 0x00,而是中间会有0xFF。

注意与单块写的区别,单块写回响应之后,紧跟着busy 0x00.

四. 测试

测试代码如下

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");        }
        /* 多块读 */        memset(s_read_buffer[0],0,512);        memset(s_read_buffer[1],0,512);        if(0 == sd_read_mblock(&s_sd_dev, (uint8_t*)s_read_buffer, 0x000000002))        {            printf("[DATA]\r\n");            for (int i = 0; i < 512*2; ++i)             {                if(i%16==0)                {                    printf("\r\n");                }                printf("0x%02x,",((uint8_t*)s_read_buffer)[i]);            }            printf("\r\n");        }        else        {            printf("read mblock err\r\n");        }
        /* 多块写 */        memset(s_read_buffer[0],0xAA,512);        memset(s_read_buffer[1],0x55,512);        if(0 == sd_write_mblock(&s_sd_dev, (uint8_t*)s_read_buffer, 0x000000002))        {
        }        else        {            printf("write mblock 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;}

打印如下

五. 总结

多块读和单块读区别是多块读需要Stop命令来结束,

多块写和单块写区别是Start Token0xFE变为了0xFC,且需要0xFD Token来结束。


补充一个初始化的问题

ACMD1等待响应0x00时,超时时间要设置足够,按照规格书最大设置为1S。

代码修改下超时时间为1S






















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