一.前言
前面我们完成了SD卡的初始化实现,现在继续来实现单块的读写。
二. 单块读
2.1读命令
命令CMD17 - READ_SINGLE_BLOCK
命令格式如下, 参数是32位的地址,需要注意的是对于SDSC卡(OCR的CCS=0)该地址单位是字节,对于SDHC和SDXC卡(OCR的CCS=1), 则单位是512字节。
一次传输不能穿越block边界,除非CSD的READ_BLK_MISALIGN设置。
我们一般就按照一次固定读一个Block来进行。
2.2读流程
如下,主机发送命令command,等待设备回r1的response,之后最多100ms回data block和CRC(16位),data block的第一个字节是0xFE(start token下图未体现),后面才是数据,最后是16位 CRC,CRC可不检查。
2.3读出错
读错误,则回data error,data error是一个字节, 低4位表示错误状态,高4位为0.
2.4超时判断
对于SDHC和SDXC使用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为需要读出的字节数,2为16位CRC。
2.6实测波形
发送命令读块地址0
51 00 00 00 00 55
等待r1返回非0xFF,即0x00,
然后继续等待0xFE,读数据和CRC
这里CRC是DA 80
三.单块写
3.1写命令
写命令CMD24-WRITE_BLOCK
命令格式如下, 参数是32位的地址,需要注意的是对于SDSC卡(OCR的CCS=0)该地址单位是字节,对于SDHC和SDXC卡(OCR的CCS=1), 则单位是512字节。
一次传输不能穿越block边界,除非CSD的READ_BLK_MISALIGN设置。
我们一般就按照一次固定写一个Block来进行。
3.2写流程
如下,主机发送命令command,等待设备回r1的response,如果超过8个字节的0xFF则读r1失败返回,之后发data block,data 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等时的处理,错误响应处理,以及超时处理。