一. 前言
前面我们实现了单块的读写,在大批量连续读写时使用多块读写效率更高,所以我们继续来实现多块的读写,有了前面的基础,很快就能实现多块的读写,在原来的基础上主要考虑下多块间的处理。
对应的波形,使用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字节CRC,DA 80。后面是FF,继续等待下一个BLOCK的0xFE开始
等到下一个BLOCK的0xFE,读后面的数据和CRC
CRC是A521
发送CMD12结束读
发送4C 00 00 00 00 61
三. 多块写实现
多块写命令
使用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, 0x00000000, 2))
{
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, 0x00000000, 2))
{
}
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 Token由0xFE变为了0xFC,且需要0xFD Token来结束。
补充一个初始化的问题
ACMD1等待响应0x00时,超时时间要设置足够,按照规格书最大设置为1S。
代码修改下超时时间为1S