安全数字输入/输出接口SDIO

SDIO框图

需要总体去浏览sd2.0协议的手册,大概了解每个章节写了什么,再看sd卡功能描述--》再看命令--》再看响应,响应对应的数据位---》响应对应的寄存器或者响应的Rx x==1、2、3、4、5、6

APB2对于F407来说等于168M/2=84M;

APB2等于PCLK2:提供给适配器寄存器和FIFO使用

SDIOCLK等于PCLK2:控制单元、命令路径、数据路径使用

SDIO_CK:提供给sd卡的时钟;通过SDIO_CLKCR时钟控制寄存器配置可得;公式如下 

一些观察:状态位等一些和响应都通过SDIO响应寄存器得到;

SD卡2.0协议

只做一些关键性的东西记录;其他细节略;

cmd线:命令和响应都靠这根线;0开始,1结束。所以可以配置上拉

dat线:数据线,空闲为高

使用命令前的SDIO配置(补充)

1、配置SDIO时钟

初始化时钟在2.0协议中配置为400k

配置时钟触发源

配置SD卡时钟源

配置数据线:1位数据线通用,支持卡多;

2、电源控制寄存器开启电源

3、使能sdio卡时钟

sd协议讲的操作(使用命令),在协议手册的第四章sd卡的功能描述:

SD卡识别操作流程 

fod卡识别模式的频率=400k

描述关键的信息,其他细节观察流程图;

  • 上电卡进入idel状态,发送cmd0进行软复位;
  • 操作前先发送cmd8操作条件确认:确认电压,有响应发给卡收到表明主机支持2.0协议
  • 发送CMD55让下个命令为ACMD再发送ACMD41:识别是否符合主机规定的VDD范围的卡             1、通过设置 ACMD41 的参数里面 OCR(工作电压范围)的值为 0,主机可以查询每张         卡,  并且确定通用电压范围,初始化之前不能这样用;

              2、ACMD41响应的ccs位中为1则表示SD卡为高容量卡;否则为普通SD卡

  • 再发送cmd2请求卡CID号:响应发送卡的CID号;进入识别状态
  • 发送cmd3请求卡的新相对地址:响应发送新的相对地址RCA,作为数据传输模式的地址;主机进入到stand by等待状态;

更详细的卡识别和初始化流程

初始化的详细操作:

如下命令按顺序操作:并且有命令且有对应的响应;所有有响应的需要做计数等待溢出,溢出后没有响应都算卡错误;
cmd0

目的:软复位

4.7.2章的详细命令描述

 无响应,复位所有的卡;

cmd8

主要目的:有响应,卡支持sd2.0协议,否则为SD2.0之下或者mac卡;带参数:参数在2.0协议的4.3.13章节发送接口命令条件说明;推荐参数 0x1AA, 其中检查模式文中推荐AA,电压支持0x01;所以参数0x1AA

4.7.2章的详细命令描述

4.9.6章节的,R7响应

 cmd55

目标:在卡识别的流程图看到需要发送ACMD41,在发送之前应该发送CMD55,使得下个命令可以发送AMCD41;原文如下,4.2.2章节;并且在 4.7.2章的详细命令描述也重复提到ACMD使用之前,应该发送CMD55;

 4.7.2章的详细命令描述,如下为简要解析:该命令详细使用在4.3.9章节

 4.9.6章节的,R1响应

acmd41

主要目标:响应就属于sd卡不响应就属于mac卡;区分SD和MAC卡;

 4.7.2章的详细命令描述

 返回OCR寄存器:寄存器如下

 可以知道30、31位可以读取到卡的卡容量和卡上电状态;ACMD41之后响应中可以读取到卡容量和卡上电状态;

CMD2

目标:返回卡的CID;

 4.7.2章的详细命令描述

 4.9.6章节的,R2响应

CMD3

目标:让卡发布重新RCA相对地址;

  4.7.2章的详细命令描述

 4.9.6章节的,R6响应

2.0协议手册的的4.10.1章节的表4-36部分;

 

程序要检查19、22、23位是否发生错误;

                            卡识别程序程序:程序1
                                                              要点

  开始配置、或者改变总线宽度或者时钟一定要尽量用结构体如下;

				SDIO_InitTypeDef SDIO_InitStruct;//改变
				SDIO_InitStruct.SDIO_ClockDiv = 2;//  84m/(2+2)= 
				SDIO_InitStruct.SDIO_ClockEdge =  SDIO_ClockEdge_Rising ;  //时钟信号上升有效
				SDIO_InitStruct.SDIO_ClockBypass = SDIO_ClockBypass_Disable ; //使用APB2
				SDIO_InitStruct.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable ;//禁止时钟节能
				SDIO_InitStruct.SDIO_BusWide = widemode;//总线是1
				SDIO_InitStruct.SDIO_HardwareFlowControl = 
                SDIO_HardwareFlowControl_Disable;//禁止硬件流控制

				SDIO_Init(&SDIO_InitStruct);

 如果使用如下代码:就会出现出错,配置不上,很大原因,我觉得是寄存器性能问题

stm数据手册中有那么一段话:写入数据后,在三个 SDIOCLK (48 MHz) 时钟周期以及两个 PCLK2 时钟周期内无法向该寄 
存器写入数据。在 SD I/O 卡的读取等待间隔期间也可以停止 SDIO_CK:在此情况下, 
SDIO_CLKCR 寄存器不对 SDIO_CK 进行控制。

       
		    SDIO->CLKCR &= ~(3<<11);
			  SDIO->CLKCR |=widemode;
		    SDIO->CLKCR |= 0<<14;
    程序:卡识别

/*** SDIO的配置SDIO控制寄存器、供电,以及命令的 卡的类型 容  电压***/

SD_Error SDIO_PowerON(void)
{
  
 SDIO_InitTypeDef SDIO_InitStruct;
 SDIO_CmdInitTypeDef SDIO_CmdInitStruct;
 SD_Error errorstatus=SD_OK;//状态
 uint32_t response;
 uint8_t validvotage=0,count=0;
 
/* 配置 SDIO 时钟寄存器  */
 SDIO_InitStruct.SDIO_ClockDiv = 208;//  84m/(208+2)=400k 
 SDIO_InitStruct.SDIO_ClockEdge =  SDIO_ClockEdge_Rising ;  //时钟信号上升有效
 SDIO_InitStruct.SDIO_ClockBypass = SDIO_ClockBypass_Disable ; //使用APB2
 SDIO_InitStruct.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable ;//禁止时钟节能
 SDIO_InitStruct.SDIO_BusWide = SDIO_BusWide_1b;//总线是1
 SDIO_InitStruct.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable;//禁止硬件流控制
	
 SDIO_Init(&SDIO_InitStruct);
	
 SDIO_SetPowerState(SDIO_PowerState_ON);//上电
 SDIO_ClockCmd(ENABLE);	//使能时钟;
	
 for(int i =0;i<60;i++)
{
	SDIO_CmdInitStruct.SDIO_Argument = 0;
	SDIO_CmdInitStruct.SDIO_CmdIndex = CMD0_reset ; //发送命令CMD0,软复位
	SDIO_CmdInitStruct.SDIO_Response = SDIO_Response_No ;
	SDIO_CmdInitStruct.SDIO_Wait = SDIO_Wait_No ;
	SDIO_CmdInitStruct.SDIO_CPSM = SDIO_CPSM_Enable;
	SDIO_SendCommand(&SDIO_CmdInitStruct);
	errorstatus = CmdError();
	if(errorstatus == SD_OK ) break;
	
}
 if(errorstatus != SD_OK ) return errorstatus;

  SDIO_CmdInitStruct.SDIO_Argument = CMD8_VHS_AND_CHECk;//主机支持电压,检查模式推荐0xAA ,还有一个参数为0x100
	SDIO_CmdInitStruct.SDIO_CmdIndex = CMD8_CHECK_valot_2_0 ; //CMD8,条件命令,可判断支持电压,支持2.0协议;
	SDIO_CmdInitStruct.SDIO_Response = SDIO_Response_Short ;
	SDIO_CmdInitStruct.SDIO_Wait = SDIO_Wait_No ;
	SDIO_CmdInitStruct.SDIO_CPSM = SDIO_CPSM_Enable;
	SDIO_SendCommand(&SDIO_CmdInitStruct);
   
  errorstatus = CMDResp7Error( );  //等待响应;如果响应成功
  if( errorstatus == DTIMEOUT ) 
	{
		 SD_message1.card_type = MAC_card;  //无响应就是SD1.0的卡或者mac卡
	}
  if( errorstatus == SD_OK )
 {  
    SD_message1.card_type = SD_2_0 ; // 则符合2.0协议
    SD_message1.SD_capacity_type = SD_STAND_SD; 
 } 
	
	SDIO_CmdInitStruct.SDIO_Argument = 0;//没有RCA相对地址
	SDIO_CmdInitStruct.SDIO_CmdIndex = CMD55_APP_CMD ; //CMD55
	SDIO_CmdInitStruct.SDIO_Response = SDIO_Response_Short ;
	SDIO_CmdInitStruct.SDIO_Wait = SDIO_Wait_No ;
	SDIO_CmdInitStruct.SDIO_CPSM = SDIO_CPSM_Enable;
	SDIO_SendCommand(&SDIO_CmdInitStruct);
	errorstatus=CMDResp1Error(CMD55_APP_CMD);//等待响应1
	if( errorstatus == SD_OK)
  {
	
					while( ( !validvotage ) && (count < SDIO_count_max ) )
				 {
							SDIO_CmdInitStruct.SDIO_Argument = 0;
							SDIO_CmdInitStruct.SDIO_CmdIndex = CMD55_APP_CMD ; //CMD55 指定下个指令为特定指令
							SDIO_CmdInitStruct.SDIO_Response = SDIO_Response_Short ;
							SDIO_CmdInitStruct.SDIO_Wait = SDIO_Wait_No ;
							SDIO_CmdInitStruct.SDIO_CPSM = SDIO_CPSM_Enable;
							SDIO_SendCommand(&SDIO_CmdInitStruct);
							
							errorstatus=CMDResp1Error(CMD55_APP_CMD);
							if( errorstatus != SD_OK)
							{  
								return  errorstatus;
							}
							
							SDIO_CmdInitStruct.SDIO_Argument = SDIO_OCR_busy_and_set_vol|SDIO_OCR_CCS; //参考协议,参数电压和容量
							SDIO_CmdInitStruct.SDIO_CmdIndex = CMD41_volta ; //CMD41
							SDIO_CmdInitStruct.SDIO_Response = SDIO_Response_Short ;
							SDIO_CmdInitStruct.SDIO_Wait = SDIO_Wait_No ;
							SDIO_CmdInitStruct.SDIO_CPSM = SDIO_CPSM_Enable;
							SDIO_SendCommand(&SDIO_CmdInitStruct);
							errorstatus  =CMDResp3Error();
							if(errorstatus != SD_OK)
							{
								 return  errorstatus;
							}
							response=SDIO->RESP1;
							
							validvotage=response>>31;//有效电压读取
							count++;
				 }
				 if(count > SDIO_count_max )//次数溢出
				 { 
					 errorstatus=INVALID_VOLTRANGE;
					 
				 }else //ACMD41有响应
				 {   
					   if(SD_message1.card_type == MAC_card && (response & SDIO_OCR_CCS )==0) //ACMD41有响应,并且HCS位 为0 就是SD1.0
						 {
						      SD_message1.card_type = SD_1_0; //不然就继续保持 MAC_card
						 }
						 if(response & SDIO_OCR_CCS ) //HCS为 1 就是高容量
						 {
							 SD_message1.SD_capacity_type= SD_HIGHT_SD ;
						 }
						 else //HCS为 0 就是标准容量
						 {
						   SD_message1.SD_capacity_type=SD_STAND_SD ;
						 
						 }
			   }
					 
	}
/******* 例程,在这个地方,发送CMD55之后 ,发送CMD1,判断MAC卡,CMD1应该是专属mac的,应该在 *************/
	return errorstatus;
}

获取卡信息

CMD9

目标:获取卡信息,CSD寄存器

  4.7.2章的详细命令描述

 4.9.6章节的,R2响应

 

 

程序 初始化 ,获取CID获取CSD,rca

/********初始化卡,获取CID和CSD卡信息   RCA      *****/

SD_Error SD_InitializeCards(void)
{
  SDIO_CmdInitTypeDef  SDIO_CmdInitStruct;
  SD_Error errorstate=SD_OK;
	uint16_t rca=0x01;
	
	if( SDIO_GetPowerState() == 0x00 ) //没有上电
	{
	  return  errorstate=SD_NOT_SET_VOLTRANGE;
	}
	
	SDIO_CmdInitStruct.SDIO_Argument = 0;   
	SDIO_CmdInitStruct.SDIO_CmdIndex =  CMD2_SEND_CID; //cmd2 ,让卡发送CID
	SDIO_CmdInitStruct.SDIO_Response =  SDIO_Response_Long ;//2.0协议,指定为长响应
	SDIO_CmdInitStruct.SDIO_Wait = SDIO_Wait_No;
	SDIO_CmdInitStruct.SDIO_CPSM = SDIO_CPSM_Enable ;
	SDIO_SendCommand(&SDIO_CmdInitStruct);
	
  errorstate = CMDResp2Error() ;
	if( errorstate != SD_OK  ){ return errorstate; }
	
	 SD_message1.CID_array[0] =SDIO_GetResponse(SDIO_RESP1); //得到CID
	 SD_message1.CID_array[1] =SDIO_GetResponse(SDIO_RESP2);
	 SD_message1.CID_array[2] =SDIO_GetResponse(SDIO_RESP3);
	 SD_message1.CID_array[3] =SDIO_GetResponse(SDIO_RESP4);
	
	if( SD_message1.card_type == SD_2_0 || SD_message1.card_type == SD_1_0  )
	{
				SDIO_CmdInitStruct.SDIO_Argument = 0;   
				SDIO_CmdInitStruct.SDIO_CmdIndex =  CMD3_SEND_RCA;//让卡发布相对地址
				SDIO_CmdInitStruct.SDIO_Response =  SDIO_Response_Short ;
				SDIO_CmdInitStruct.SDIO_Wait = SDIO_Wait_No;
				SDIO_CmdInitStruct.SDIO_CPSM = SDIO_CPSM_Enable ;
				SDIO_SendCommand(&SDIO_CmdInitStruct);
	      errorstate =CMDResp6Error( CMD3_SEND_RCA,&rca);//响应6 ,响应出RCA地址
		    if( errorstate != SD_OK) return  errorstate;
	      
	}
	if(SD_message1.card_type == MAC_card) 
	{
		  SDIO_CmdInitStruct.SDIO_Argument = (u32)(rca<<16);//这是例程写的是 1<<16,这里是0<<16,mac卡单张
      SDIO_CmdInitStruct.SDIO_CmdIndex = CMD3_SEND_RCA;	//cmd3
      SDIO_CmdInitStruct.SDIO_Response = SDIO_Response_Short; //r6
      SDIO_CmdInitStruct.SDIO_Wait = SDIO_Wait_No;
      SDIO_CmdInitStruct.SDIO_CPSM = SDIO_CPSM_Enable;
      SDIO_SendCommand(&SDIO_CmdInitStruct);	//发送CMD3,短响应 	
			
      errorstate=CMDResp2Error(); 					//等待R2响应   
			
		  if(errorstate!=SD_OK)return errorstate;   	//响应错误	 	
	}
	
	SDIO_CmdInitStruct.SDIO_Argument = rca<<16;//
	SDIO_CmdInitStruct.SDIO_CmdIndex = CMD9_SEND_CSD;	//cmd9  让卡发送CSD寄存器
	SDIO_CmdInitStruct.SDIO_Response = SDIO_Response_Long; //r6
	SDIO_CmdInitStruct.SDIO_Wait = SDIO_Wait_No;
	SDIO_CmdInitStruct.SDIO_CPSM = SDIO_CPSM_Enable;
	SDIO_SendCommand(&SDIO_CmdInitStruct);	//发送CMD9,短响应 	
	errorstate=CMDResp2Error(); 					//等待R2响应   
  if(errorstate!=SD_OK)return errorstate;   	//响应错误	   
	SD_message1.CSD_array[0]= SDIO_GetResponse(SDIO_RESP1);
	SD_message1.CSD_array[1]= SDIO_GetResponse(SDIO_RESP2);
	SD_message1.CSD_array[2]= SDIO_GetResponse(SDIO_RESP3);
	SD_message1.CSD_array[3]= SDIO_GetResponse(SDIO_RESP4);
	SD_message1.RCA=rca;
	return  errorstate;
	

}

 选中卡

 CMD7

目标: 通过相对地址RCA选中卡;

  4.7.2章的详细命令描述

 4.9.6章节的,R1响应

卡状态在2.0协议手册的4.10.1,这里略;

在stm32f4的28.9.6中,如果设置为短响应,那么返回的RESP1寄存器应该是32宽度的卡状态,所以不会是整个R1寄存器48位返回到主机,而是只有8-39的状态位;

/******* 使用相对地址 选择卡     */
SD_Error  SD_Select_card( uint16_t rca )
{
	SDIO_CmdInitTypeDef  SDIO_CmdInitStruct;
	SD_Error state;
  SDIO_CmdInitStruct.SDIO_Argument = rca<<16;//
	SDIO_CmdInitStruct.SDIO_CmdIndex = CMD7_select;	//cmd7
	SDIO_CmdInitStruct.SDIO_Response = SDIO_Response_Short; //r1
	SDIO_CmdInitStruct.SDIO_Wait = SDIO_Wait_No;
	SDIO_CmdInitStruct.SDIO_CPSM = SDIO_CPSM_Enable;
	SDIO_SendCommand(&SDIO_CmdInitStruct);	//发送CMD7,短响应 	
	state=CMDResp1Error(CMD7_select); 					//等待R1响应   
  return state;

}

获取SD卡信息,解析

在使用CMD2之后可以在获取到CID:

卡的CID寄存器如下;

CMD2之后可以在SDIO响应1234寄存器可以读取到CID:

传输数据位高位到低位:每个SDIO响应寄存器32位宽度,所以有:

      SDIO响应寄存器[0]  CID 127--96         
      SDIO响应寄存器[1]  CID 95--64           
      SDIO响应寄存器[2]  CID 63--32                            
      SDIO响应寄存器[3]  CID 31--0           

使用CMD9之后可以获得CSD寄存器;  

CSD寄存器如下:

 

CMD9之后可以在SDIO响应1234寄存器可以读取到CSD:

传输数据位高位到低位:每个SDIO响应寄存器32位宽度,所以有:

      SDIO响应寄存器[0]  CSD127--96         
      SDIO响应寄存器[1]  CSD95--64           
      SDIO响应寄存器[2]  CSD63--32                            
      SDIO响应寄存器[3]  CSD31--0 

解析程序:
/******  解析获取的信息  ********/
SD_Error SD_Get_SD_message1(SD_message_struct *p)
{
	
	SD_message1.SD_CID.CID_manufacturer=    ( SD_message1.CID_array[0]>>24 )&0xff;
  SD_message1.SD_CID.CID_OEM =            ( SD_message1.CID_array[0]>>(32-8-16) )&0xffff ;
  SD_message1.SD_CID.CID_product_name =   (( (long long)  SD_message1.CID_array[0]& 0xff ) << 32 ) + SD_message1.CID_array[1];
  SD_message1.SD_CID.CID_product_versions = (SD_message1.CID_array[2] >>24) &0xff ;
  SD_message1.SD_CID.CID_product_SNnum =    ( SD_message1.CID_array[2] <<8 )  +  (SD_message1.CID_array[3] >>24 );
  SD_message1.SD_CID.Reserved1 =            ( SD_message1.CID_array[3] >> (24-4) ) &0x0f;
  SD_message1.SD_CID.CID_product_date =     ( SD_message1.CID_array[3] >> (24-4-12)  ) &0xffff;
  SD_message1.SD_CID.CID_CRC =              ( SD_message1.CID_array[3] >>1 ) &0x7f;
  SD_message1.SD_CID.Reserved2 =              SD_message1.CID_array[3]&0x01 ;	
	

  SD_message1.SD_CSD. CSD_STRUCT_b2 =  ( SD_message1.CSD_array[0] >> 30 )&0x03   ; //CSD结构体 2位
	SD_message1.SD_CSD. Reserved1_b6 =  ( SD_message1.CSD_array[0] >> 24 )&0x3f      ;  //保留 6位
	SD_message1.SD_CSD. TAAC_b8 =       ( SD_message1.CSD_array[0] >> 16 )&0xff     ;       //数据访问时间
	SD_message1.SD_CSD. NSAC_b8 =       ( SD_message1.CSD_array[0] >> 8 )&0xff  ;      //时钟周期中的数据读访问时间
	SD_message1.SD_CSD. TRAN_SPEED_b8 = ( SD_message1.CSD_array[0] >> 0 )&0xff ;   //最大数据传输速率
	SD_message1.SD_CSD. CCC_b12 =                 ( SD_message1.CSD_array[1] >> 20 )&0xfff    ;      //卡命令类
	SD_message1.SD_CSD. READ_BL_LEN_b4 =          ( SD_message1.CSD_array[1] >> 16 )&0x0f  ; //读数据块最大长度
	SD_message1.SD_CSD. READ_BL_PARTIAL_b1 =      ( SD_message1.CSD_array[1] >> 15 )&0x01; //允许块部分读
	SD_message1.SD_CSD. WRITE_BLK_MISALIGN_b1 =   ( SD_message1.CSD_array[1] >> 14 )&0x01; //写块不对齐
	SD_message1.SD_CSD. READ_BLK_MISALIGN_b1 =    ( SD_message1.CSD_array[1] >> 13 )&0x01;  //读块不对齐
	SD_message1.SD_CSD. DSR_IMP_b1 =              ( SD_message1.CSD_array[1] >> 12 )&0x01;//执行的DSR
	SD_message1.SD_CSD. Reserved2_b6 =            ( SD_message1.CSD_array[1] >> 6 )&0x3f; // 保留2
	/*********    高容量卡  *************/
	if(SD_message1.SD_capacity_type == SD_HIGHT_SD || SD_2_0 )
	{
	SD_message1.SD_CSD. C_SIZE_b22 =             ( (( SD_message1.CSD_array[1] >> 0 )&0x3f)<<22 ) + ( ( SD_message1.CSD_array[2] >> 16 )&0xffff );  //设备尺寸
	SD_message1.SD_CSD. Reserved3_b1 =           ( SD_message1.CSD_array[2] >> 15 )&0x01  ;//保留3
	SD_message1.SD_CSD. ERASE_BLK_EN_b1 =        ( SD_message1.CSD_array[2] >> 14 )&0x01  ; //擦单块使能
	SD_message1.SD_CSD. SECTOR_SIZE_b7 =         ( SD_message1.CSD_array[2] >> 7 )&0x7f ;//擦扇区尺寸
	SD_message1.CardBlockSize = 512 ;
	SD_message1.CardCapacity = (SD_message1.SD_CSD. C_SIZE_b22 +1)*512*1024; 
	}else
	{
	 /***********  非高容量   ************/ //这个应在非2.0协议高容量手册里面 
  SD_message1.SD_CSD.  DeviceSize = ( (( SD_message1.CSD_array[1] >> 8 )&0x3 ) <<10 ) + ( (( SD_message1.CSD_array[1] >> 0 )&0xff )  << 2 )
                                      + 	(  ( SD_message1.CSD_array[2] >> 30 )&0x03  << 0 )	;    //设备尺寸
  SD_message1.SD_CSD.	MaxRdCurrentVDDMin = ( SD_message1.CSD_array[2] >> 27 )&0x07 ;  /*!< Max. read current @ VDD min */
  SD_message1.SD_CSD.	MaxRdCurrentVDDMax = ( SD_message1.CSD_array[2] >> 24 )&0x07 ; /*!< Max. read current @ VDD max */
  SD_message1.SD_CSD. MaxWrCurrentVDDMin = ( SD_message1.CSD_array[2] >> 21 )&0x07   ; /*!< Max. write current @ VDD min */
  SD_message1.SD_CSD. MaxWrCurrentVDDMax = ( SD_message1.CSD_array[2] >> 18 )&0x07 ; /*!< Max. write current @ VDD max */
  SD_message1.SD_CSD.	DeviceSizeMul =      ( SD_message1.CSD_array[2] >> 15 )&0x07 ;   /*!< Device size multiplier */
	SD_message1.CardBlockSize =  1<<SD_message1.SD_CSD. READ_BL_LEN_b4  ;  //块大小为 2的 长的幂
	SD_message1.CardCapacity =  SD_message1.SD_CSD.  DeviceSize  +1  ;  //	
	SD_message1.CardCapacity *=  	1<<( SD_message1.CardCapacity +2);  //设备长的 2的幂次
	SD_message1.CardCapacity *= SD_message1.CardBlockSize ;
	}
	/**********           ****************/
	
	SD_message1.SD_CSD. WP_GRP_SIZE_b7 =         ( SD_message1.CSD_array[2] >> 0 )&0x7f;//写保护组尺寸
	SD_message1.SD_CSD. WP_GRP_ENABLE_b1 =       ( SD_message1.CSD_array[3] >> 31 )&0x01 ;//写保护组使能
	SD_message1.SD_CSD. Reserved4_b2 =           ( SD_message1.CSD_array[3] >> 29 )&0x03 ;//保留4
	SD_message1.SD_CSD. R2W_FACTOR_b3 =          ( SD_message1.CSD_array[3] >> 26 )&0x07;//写速度因素
	SD_message1.SD_CSD. WRITE_BL_LEN_b4 =        ( SD_message1.CSD_array[3] >> 22 )&0x0f; //最大写数据块长度
	SD_message1.SD_CSD. WRITE_BL_PARTIAL_b1 =    ( SD_message1.CSD_array[3] >> 21 )&0x01; //允许块部分写
	SD_message1.SD_CSD. Reserved_b5 =            ( SD_message1.CSD_array[3] >> 16 )&0x1f;//保留5
	SD_message1.SD_CSD. FILE_FORMAT_GRP_b1 =     ( SD_message1.CSD_array[3] >> 15 )&0x01;//文件格式组
	SD_message1.SD_CSD. COPY_b1 =                ( SD_message1.CSD_array[3] >> 14 )&0x01; //拷贝标记
	SD_message1.SD_CSD. PERM_WRITE_PROTECT_b1 =  ( SD_message1.CSD_array[3] >> 13 )&0x01;//永久写保护
	SD_message1.SD_CSD. TMP_WRITE_PROTECT_b1 =   ( SD_message1.CSD_array[3] >> 12 )&0x01;//临时写保护
	SD_message1.SD_CSD. FILE_FORMAT_b2 =         ( SD_message1.CSD_array[3] >> 10 )&0x03;//文件格式
	SD_message1.SD_CSD. Reserved6_b2 =           ( SD_message1.CSD_array[3] >> 8 )&0x03 ; //保留6
	SD_message1.SD_CSD. CRC_b7 =                 ( SD_message1.CSD_array[3] >> 1 )&0x7f ;   //CRC
	SD_message1.SD_CSD. Reserved7_b1 =           ( SD_message1.CSD_array[3] >> 0)&0x01;
	
  
 *p=SD_message1;
 return SD_OK;
}

总线宽度选择

总线宽度选择,需要判断通过cmd9,获取的响应判断位25卡锁定状态,卡没有被锁定,就可以改变总线;

获取当前卡支持的总线数据宽度

在卡寄存器SCR中可以,找到;所以要读取卡的SCR寄存器;如下顺序命令操作;

CMD16

目标:设置块长度,因为要面向块的操作;需要先设置块;

  4.7.2章的详细命令描述

  4.9.6章节的,R1响应

主机设置长度块大小

在设置卡的块大小后,发送ACDM51之前也可以把主机的SDIO数据控制寄存器也进行相应的长度和块设置,应该是SCR比较特殊?只能用块FIFO传输;

cmd55

为了发送ACMD命令

ACMD51

前提:发送ACDM51之前把主机的SDIO数据控制寄存器也进行相应的长度和块设置;

目标:读取卡SCR寄存器;

   4.7.2章的详细命令描述

 

 响应R1略:

短响应,RESP1只有状态位32宽;

读取SCR需要进行FIFO读取:

 /*  因为fifo传输是按字节来传输,不像响应一样按照32位,字传输,所以FIFO:先传的是高字节进来像数组一样组成32位,组成一个字;所以最高字节在字的的前八位;
        然后四个字节组成一个字,再到下一个字;在列程序是这样操作;
 */    

CMD55

此时可以带参数RCA卡相对地址,表明下一条为ACMD外,还使用RCA表明那张卡操作;

ACMD6

读到SCR之后,进行判断宽位支持,支持就进行发送ACMD,带参数宽度,

ACMD6进行修改卡总线宽;

附上SCR寄存器,详细解析在2.0协议中的5.3章节中

主机使能宽度允许

SDIO_CLKCR寄存器中;

位宽识别,配置程序
/*       选择数据线宽                **/
SD_Error support_chek_widebus(SD_message_struct * p ,uint16_t en );

SD_Error SD_Select_widebus_mode( SD_message_struct *p , uint32_t widemode  )
{
	 SD_Error state=SD_OK;
  if( widemode == SDIO_BusWide_8b || p->card_type == MAC_card)
	{
	   return SD_UNpupported; //之前线宽一直是1位;
	}
  if (p->SD_capacity_type== SD_HIGHT_SD || p->card_type == SD_2_0 ||  p->card_type == SD_1_0)
	{
	  
	   state=support_chek_widebus(p ,widemode );//查询是否,卡支持宽度;
	   if( state ==SD_OK )
		 {
				SDIO_InitTypeDef SDIO_InitStruct;//改变位宽应该使用结构体,一次性写入
				SDIO_InitStruct.SDIO_ClockDiv = 2;//  84m/(2+2)= 
				SDIO_InitStruct.SDIO_ClockEdge =  SDIO_ClockEdge_Rising ;  //时钟信号上升有效
				SDIO_InitStruct.SDIO_ClockBypass = SDIO_ClockBypass_Disable ; //使用APB2
				SDIO_InitStruct.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable ;//禁止时钟节能
				SDIO_InitStruct.SDIO_BusWide = widemode;//总线是1
				SDIO_InitStruct.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable;//禁止硬件流控制

				SDIO_Init(&SDIO_InitStruct);
		 }
	
	}
  return state;

}

SD_Error support_chek_widebus(SD_message_struct * p ,uint16_t en )
{
  
	SDIO_CmdInitTypeDef  SDIO_CmdInitStruct;
  SD_Error state=SD_OK;
	uint32_t SCR[2]={0};
	uint8_t wide_bus=0;
	if(SDIO_BusWide_4b ==  en ) 
	{
		wide_bus=2;
	}
	else if(en ==  SDIO_BusWide_1b)  //按照模式设置参数,通过ACMD6 进行设置宽度;
	{
	  wide_bus =0;
	}
	state=find_transmit_SCR(p ,SCR );
	if(  ( ( SCR[1]>>16)&0x0f )  != 0 )
	{
			SDIO_CmdInitStruct.SDIO_Argument = p->RCA<<16;
			SDIO_CmdInitStruct.SDIO_CmdIndex = CMD55_APP_CMD;	//cmd55
			SDIO_CmdInitStruct.SDIO_Response = SDIO_Response_Short; //
			SDIO_CmdInitStruct.SDIO_Wait = SDIO_Wait_No;
			SDIO_CmdInitStruct.SDIO_CPSM = SDIO_CPSM_Enable;
			SDIO_SendCommand(&SDIO_CmdInitStruct);	//发送CMD55,短响应 	 
		  state=CMDResp1Error(  CMD55_APP_CMD  );
		  
			SDIO_CmdInitStruct.SDIO_Argument = wide_bus; //
			SDIO_CmdInitStruct.SDIO_CmdIndex = ACMD6_set_widebus;	//ACMD6给卡设置 总线宽度
			SDIO_CmdInitStruct.SDIO_Response = SDIO_Response_Short; //
			SDIO_CmdInitStruct.SDIO_Wait = SDIO_Wait_No;
			SDIO_CmdInitStruct.SDIO_CPSM = SDIO_CPSM_Enable;
			SDIO_SendCommand(&SDIO_CmdInitStruct);	//	 
		  state=CMDResp1Error(  ACMD6_set_widebus  );	
      if(state != SD_OK)		return state;
		 
	
	}else return SD_UNpupported;
	

 return state;
}
SD_Error find_transmit_SCR(SD_message_struct *p ,uint32_t *p_SCR )
{
 
 SDIO_CmdInitTypeDef  SDIO_CmdInitStruct;
 SDIO_DataInitTypeDef SDIO_DataInitStruct;
 SD_Error state=SD_OK;
 uint32_t temp[2]={0};
 uint8_t i=0;
  SDIO_CmdInitStruct.SDIO_Argument = 8;//8个字节
	SDIO_CmdInitStruct.SDIO_CmdIndex = CMD16_set_blok_len;	//cmd16  设置块命令
	SDIO_CmdInitStruct.SDIO_Response = SDIO_Response_Short; //r1
	SDIO_CmdInitStruct.SDIO_Wait = SDIO_Wait_No;
	SDIO_CmdInitStruct.SDIO_CPSM = SDIO_CPSM_Enable;
	SDIO_SendCommand(&SDIO_CmdInitStruct);	//发送CMD16,短响应 	
	state=CMDResp1Error( CMD16_set_blok_len ); 					//等待R1响应   
	if( state != SD_OK) return state;
 
  SDIO_CmdInitStruct.SDIO_Argument = p->RCA<<16;//RCA 相对地址
	SDIO_CmdInitStruct.SDIO_CmdIndex = CMD55_APP_CMD;	//cmd55
	SDIO_CmdInitStruct.SDIO_Response = SDIO_Response_Short; //r1
	SDIO_CmdInitStruct.SDIO_Wait = SDIO_Wait_No;
	SDIO_CmdInitStruct.SDIO_CPSM = SDIO_CPSM_Enable;
	SDIO_SendCommand(&SDIO_CmdInitStruct);	//发送CMD55,短响应 	
	state=CMDResp1Error(  CMD55_APP_CMD  ); 					//等待R1响应   
	if( state != SD_OK) return state;
 
  SDIO_DataInitStruct.SDIO_DataTimeOut = SDIO_data_timeout ; //配置状态机
  SDIO_DataInitStruct.SDIO_DataLength = 8;//总共长度
  SDIO_DataInitStruct.SDIO_DataBlockSize = SDIO_DataBlockSize_8b;//块大小8字节
	SDIO_DataInitStruct.SDIO_TransferDir = SDIO_TransferDir_ToSDIO;
	SDIO_DataInitStruct.SDIO_TransferMode = SDIO_TransferMode_Block;
	SDIO_DataInitStruct.SDIO_DPSM = SDIO_DPSM_Enable;
  SDIO_DataConfig(& SDIO_DataInitStruct);
	
	SDIO_CmdInitStruct.SDIO_Argument = 0;
	SDIO_CmdInitStruct.SDIO_CmdIndex = CMD51_find_SCR;	//cmd51 让卡发送SCR
	SDIO_CmdInitStruct.SDIO_Response = SDIO_Response_Short; //r1
	SDIO_CmdInitStruct.SDIO_Wait = SDIO_Wait_No;
	SDIO_CmdInitStruct.SDIO_CPSM = SDIO_CPSM_Enable;
	SDIO_SendCommand(&SDIO_CmdInitStruct);	//发送CMD55,短响应 	
	state=CMDResp1Error(  CMD51_find_SCR  ); 					//等待R1响应   
	if( state != SD_OK) return state;
	
	while(! (SDIO_GetFlagStatus(SDIO_FLAG_DCRCFAIL)||SDIO_GetFlagStatus(SDIO_FLAG_RXOVERR)||  // crc  接收溢出
         SDIO_GetFlagStatus(SDIO_FLAG_DTIMEOUT)|| SDIO_GetFlagStatus(SDIO_FLAG_DBCKEND)||  //超时  数据块发送成功
      	 SDIO_GetFlagStatus(SDIO_FLAG_STBITERR) ))//起始位
	{
	    if(SDIO_GetFlagStatus(SDIO_FLAG_RXDAVL))
			{
			  temp[i++]=SDIO_ReadData();
			  if(i==2)break;//读取2个字,8个字节
			}
	
	}
	if( SDIO_GetFlagStatus(SDIO_FLAG_DCRCFAIL)  ) return DCRCFAIL;
	if( SDIO_GetFlagStatus(SDIO_FLAG_RXOVERR)  )  return RXOVERR;
	if( SDIO_GetFlagStatus(SDIO_FLAG_DTIMEOUT)  ) return  DTIMEOUT;
//	if( SDIO_GetFlagStatus(SDIO_FLAG_DBCKEND) ) return DBCKEND;
	if( SDIO_GetFlagStatus(SDIO_FLAG_STBITERR) ) return STBITERR;
	
	SDIO_ClearFlag( SDIO_STATIC_FLAGS );
	//类似一个字节的接收的顺序一样;整理接收顺序;从低字开始,按照低位到高位排上去
	p_SCR[1]= (temp[0]>>24 )&0xff | ( ( (temp[0]>>16 )&0xff )<< 8  )| (( (temp[0]>>8 )&0xff )<< 16 )|( ( (temp[0]>>0 )&0xff )<< 24 );
	p_SCR[0]= (temp[1]>>24 )&0xff | ( ( (temp[1]>>16 )&0xff )<< 8  )| (( (temp[1]>>8 )&0xff )<< 16 )|( ( (temp[1]>>0 )&0xff )<< 24 );
  return state;
}

时钟供给配置

(PS:最好使用库函数的结构体重新配置;一次性写入,手册有时间要求)

在SDIO_CLKCR寄存器中

SDIO_CK 频率 = SDIOCLK / [CLKDIV + 2]。

在stmf4手册看到框图SDIOCLK =APB2:

APB2是多少,可以从工程文件中的启动文件中找到,上电默认84M

stm32的一些技巧+小总结_qq_36658033的博客-CSDN博客

参考 APB2默认分频查找

SD卡传输模式流程

fpp数据传输模式时钟频率25M

 cmd3命令后进入传输模式,cmd0和cmd15都可以返回到卡识别模式;

  • 发送cmd9获取卡的具体数据:块长度、存储容量、传输模式的状态;
  • 发送cmd4:根据总线上卡的数量,数据传输频率,配置它们的DSR寄存器;同时频率从400k识别,到数据传输的25M;
  • cmd7选中一张卡进入传输模式,带参数相对地址RCA,其他卡进入等待stand by状态;

各种数据传输模式的关系总结如下:2.0协议的4.3章节,略

读数据

读取数据分为单块和多块读;

单块读取

cmd17

目标:读取一个块;

 响应是R1,短响应返回32位宽状态,略;

实际操作

1、数据控制寄存器清零

2、判断上个命令的响应返回的卡状态,卡锁不可以读;

3、校验块大小数据,需要是2的幂

4、cmd16给卡设置块大小,并响应正确;

6、设置主机块大小

7、cmd17发送读取块命令,附带单元地址,512个字节一个单元

8.1轮询接收

  •   8.1.0关中断,为了其他中断不影响
  •   8.1.1 查询SIDO_STA寄存器的:上溢/CRC/超时/完成(标志)/起始位错误,进行循环查询,直到以上错误或者完成传输;
  •   8.1.2 轮询接收半满状态,就接收8个字
  •  知识点:SDIO的fifo宽为32,深度为32,也就是32个字FIFO,其中包括收发的。所以接收满是16个字;
  •   8.1.3计数超时,接收设置超时,不接收时候计时减减;
  •   8.1.4退出轮询后,错误状态解析,查询是否有fifo还有小于8字节的数据,有就接收;
  • 开中断

8.2DMA接收

  • 配置需要的中断  数据CRC失败 、数据超时 、数据块结束、结束上益、起始位错误
  •  SDIO DMA使能 ,配置DMA,数据方向,数据块大小(接收的字数)等,
  • 直到DMA数据块传输结束;或者超时;

多块读取

cmd18

目标:读取多个块,直到cmd12停止;

 实际操作

1、这与单块读取的实际操作的1-5都相同;校验长度不得超过SDIO数据长度寄存器的值;且长度为块大小的倍数

2、主机设置的块大小,以及块大小*块数的长度;

3、cmd18开始多块传输,带参数扇区地址(大容量卡一个单元512字节),直到cmd12结束,

4.1轮询接收

  •   4.1.0关中断,为了其他中断不影响
  •   4.1.1 查询SIDO_STA寄存器的:上溢/CRC/超时/完成(标志)/起始位错误,进行循环查询,直到以上错误或者完成传输;
  •   4.1.2 轮询接收半满状态,就接收8个字。
  •  知识点:SDIO的fifo宽为32,深度为32,也就是32个字FIFO,其中包括收发的。所以接收满是16个字;
  •   4.1.3计数超时,接收设置超时,不接收时候计时减减;
  •   4.1.4退出轮询后,错误状态解析,查询是否有fifo还有小于8字节的数据,有就接收;
  • 4.1.5数据完成标记位,和主机设置的长度参数有关;但是仍然在读数据,这时候需要cmd12命令停止读取;发送cmd12
  • 开中断,清除sdio所有标志位;

5.1DMA传输

  • 与单款读取操作一样,就是配置的dma长度参数==块*块数(即长度)
  • SDIO中断函数中,加上如果SDIO传输完成,发送cmd12结束传输;以及需要正确响应;这里考虑,有没有不占用查询状态时间的CDM12响应;(命令响应接收中断
  • 附加:也可以考虑DMA完成中断之类的;

写数据

没有数据传输dat总线拉高;

块长度和RCA错误时读命令不能执行;

cmd13

目标:让卡进入发送状态

R1响应略

cmd24一个块传输

cmd25 多个块传输,直到cmd12命令后才停止

 R1响应略,R1中有返回的卡状态,详情在SD2.0协议中文手册的4.9章节

cmd12 次命令后多块传输才会停止

命令详情略;

单块写

实际操作

1、清空数据控制寄存器

2、清除状态机配置

3、判断锁卡状态,锁卡无法操作

4、校验块大小,应该为2的幂;设置发送cmd16,带参数块的大小,设置卡的块大小

5、发送cmd13使得卡进入发送状态,查卡状态,R1响应,取得卡的状态,直到非空闲状态,进入发送态

6、主机设置块大小,长度等;

7、轮询发送

   7.1 进入轮询   //数据块发送成功/下溢/CRC/超时/起始位错误  这些条件真则退出轮询体

   7.2查询传输FIFO没半空,至少可以写入8个字

   7.3 如果不够8个字,计算剩余的字长度,如果多出一个字节,字节,不是字,那么就增加一个发送字的长度;把数据送给发送fifo

     如果够8个字,那么就把8个字直接放到发送fifo中

8、DMA发送

  • 配置需要的中断  数据CRC失败 、数据超时 、数据块结束、结束上益、起始位错误
  •  SDIO DMA使能 ,配置DMA,数据方向,数据块大小(接收的字数)等,
  • 直到DMA数据块传输结束;或者超时;

多块写

CMD23

带参数,块数量

目标:设置块数量;

CMD25

目标:开始多块传输

CMD12

中断有有cmd12指令停止传输

实际操作:

1、清空数据控制寄存器

2、清除状态机配置

3、判断锁卡状态,锁卡无法操作

4、校验块大小,应该为2的幂;设置发送cmd16,带参数块的大小,设置卡的块大小

5、CMD23、CMD25

6、主机设置块大小,长度等

7、轮询发送

  • //下溢/CRC/数据结束/超时/起始位错误,退出轮询体
  • 发送fifo不够8字发送。并且按照4字节送进一个FIFO,不够4字节的按照一个字,一个字送进fifo;
  • 够8字,直接把8个放进fifo中;
  • 超时计数

8、DMA发送

  • 配置需要的中断  数据CRC失败 、数据超时 、数据块结束、结束上益、起始位错误
  •  SDIO DMA使能 ,配置DMA,数据方向,数据块大小(接收的字数)等,
  • 直到DMA数据块传输结束;传输完成中断,发送CMD12结束传输;
  • 超时计数;

 读写程序

太长了,下面直接放程序地址

响应补充:

响应在实验过程中,都是轮询查询;对CRC,超时,命令接收,数据接收等状态查询;

遇到的错误

1、响应的函数如R3的响应中,始终会出现命令CRC错误;应该屏蔽不获取这个错误状态;

2、RCA的相对地址没有位移到16位;这是命令要求的填充参数;

3、数据传输结束查询,作为返回转态。应该不返回数据传输结束状态;

4、使用DMA的时候要注意读写的DMA方向不同;

5、数据传输完成中断和DMA的数据传输中断,在一定数据长范围内等效;超过65535就不等效果,DMA最多只可以传65535个数据,而SDIO数据可以传输2的24次方;

重点1:写有时候卡死,特别是从上电先读取,再到写入,就产生了卡死,不产生任何写数据相关的标记位,导致了卡死(轮询方式)

解决方法:降低频率,和状态机的配置超时参数,不能是32位全满;

DMA无法读取或者写入等待到标志位:

使用中断功能,不使用轮询;并且可以使用DMA标记位,也可以用SDIO的数据结束标记位;

DMA的配置参数需要全部正确,不然在使用DMA时出现未知错误,并且数据只会传输几次且数据不完整,并且有时不会产生任何中断,包括DMA中断和SDIO块的中断;

还有一点是,使用CMD13查询卡编程状态的时候,也会出现影响指令响应,之后的指令有可能会出现一些响应错误;

DMA无法读取、写入

配置中断的时候,需要提前清除中断标记位。因为可能存在标志位;而且全局标记符号,用完了需要立马清除,给下一次使用;

附件

工程

SDIO2.0程序,DMA中断资源-CSDN文库

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值