STM32制作SD卡读卡器

 注意,本博客适合像我一样的小白,会的不多,但是想快速做些东西,不适合会写驱动的大佬。

本文使用stm32f103c8t6,裸机开发。

/**

 * 硬件连接:

 *        CS -> PB12 连接SD卡CS

 *        SCK -> PB13 连接SD卡SCK

 *        MISO -> PB14 连接SD卡MISO

 *        MOSI -> PB15 连接SD卡MOSI

 * 注意:

 *        有的SD卡是带有AMS1117的,其VCC要接在5V上,

 *        STM32在只有jinl供电时5V是不会输出的,要用USB给STM32供电!!!

 *        有的SD卡不支持SPI模式,我尝试后发现SNMSUNG的SD卡不支持,但是KINGSTON的可以

 */

【免费】stm32f103c8t6SD卡驱动(Fatfs)_103C8T6的FAFS文件系统资源-CSDN文库

一、SD卡驱动

(一)copy源码,移植

我在CSDN上找到了一位大佬用HAL库和fatfs实现stm32f103c8t6对micro SD卡读写的驱动。于是借(抄)鉴(袭)之。

1.编写SPI初始化函数(CubeIDE编辑生成的初始化代码好像在main.c和xxx_msp.c,比较散乱,于是直接上手写)
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);	//开启GPIOB的时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);	//开启SPI2的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);					//将PB12引脚初始化为推挽输出
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);					//将PB13和PB15引脚初始化为复用推挽输出
	

	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);					//将PB14引脚初始化为上拉输入
	
	
	/*SPI初始化*/
	SPI_InitTypeDef SPI_InitStructure;						//定义结构体变量
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;			//模式,选择为SPI主模式
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;	//方向,选择2线全双工
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;		//数据宽度,选择为8位
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;		//先行位,选择高位先行
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;	//波特率分频,选择128分频
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;				//SPI极性,选择低极性
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;			//SPI相位,选择第一个时钟边沿采样,极性和相位决定选择SPI模式0
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;				//NSS,选择由软件控制
	SPI_InitStructure.SPI_CRCPolynomial = 7;				//CRC多项式,暂时用不到,给默认值7
	SPI_Init(SPI2, &SPI_InitStructure);						//将结构体变量交给SPI_Init,配置SPI1
	
	SPI_Cmd(SPI2, ENABLE);									//使能SPI2,开始运行
	
	/*设置默认电平*/
	SD_CS(1);											//SS默认高电平

从原子哥的讲解来看,我们还需要更改SPI的速度,(也就是SPI_baudratePrescaler)可是 标准库当中并没有给出,我们只好操作寄存器,编写该函数(我比较菜,直接从原子哥的fatfs的SPI源码中copying过来)

 

SPI速度设置函数如下 
/**
 * @brief 设置SPI2的速度为低速
*/
void  sd_spi_speed_low(void)
{
	SPI2->CR1&=0XFFC7;
	SPI2->CR1|=SPI_BaudRatePrescaler_256;	//����SPI2�ٶ� 
	SPI_Cmd(SPI2,ENABLE); 
}

/**
 * @brief 设置SPI2的速度为高速
*/
void  sd_spi_speed_high(void)
{
	SPI2->CR1&=0XFFC7;
	SPI2->CR1|=SPI_BaudRatePrescaler_2;	//����SPI2�ٶ� 
	SPI_Cmd(SPI2,ENABLE); 

}

之后出现了一些灵异事件! 

main.c中有一个读写文件的示例函数:

void WritetoSD(char SD_FileName[], uint8_t write_buff[],uint8_t bufSize)

// {

//  FATFS fs;

//  FIL file;

//  uint8_t res=0;

//  UINT Bw;    

   

//  res = SD_init();        //SD卡初始化



//  if(res == 1)

//  {

//      UsartPrintf(USART_DEBUG,"SD卡初始化失败! \r\n");  

//      OLED_ShowString(1,1,"failed!");

//  }

//  else

//  {

//      UsartPrintf(USART_DEBUG,"SD卡初始化成功! \r\n");      

//      OLED_ShowString(1,1,"init successed!");

//  }

   

//  res=f_mount(&fs,"0:",1);        //挂载

   

// //   if(test_sd == 0)        //用于测试格式化

//  if(res == FR_NO_FILESYSTEM)     //没有文件系统,格式化

//  {

// //       test_sd =1;             //用于测试格式化

//      UsartPrintf(USART_DEBUG,"没有文件系统! \r\n");        

//      OLED_ShowString(2,1,"no file system!");

//      res = f_mkfs("", 0, 0);     //格式化sd卡

//      if(res == FR_OK)

//      {

//          UsartPrintf(USART_DEBUG,"格式化成功! \r\n");

//          OLED_ShowString(1,1,"geshihua sed!");      

//          res = f_mount(NULL,"0:",1);         //格式化后先取消挂载

//          res = f_mount(&fs,"0:",1);          //重新挂载  

//          if(res == FR_OK)

//          {

//              UsartPrintf(USART_DEBUG,"SD卡已经成功挂载,可以进进行文件写入测试!\r\n");

//              OLED_ShowString(3,1,"can test");    

//          }  

//      }

//      else

//      {

//          UsartPrintf(USART_DEBUG,"格式化失败! \r\n");    

//          OLED_ShowString(1,1,"geshihua fad!");      

//      }

//  }

//  else if(res == FR_OK)

//  {

//      UsartPrintf(USART_DEBUG,"挂载成功! \r\n");          

//      OLED_ShowString(1,1,"guazai sed!");

//  }

//  else

//  {

//      UsartPrintf(USART_DEBUG,"挂载失败! \r\n");      

//      OLED_ShowString(1,1,"guazai fad!");

//  }  

   

//  res = f_open(&file,SD_FileName,FA_OPEN_ALWAYS |FA_WRITE);

//  if((res & FR_DENIED) == FR_DENIED)

//  {

//      UsartPrintf(USART_DEBUG,"卡存储已满,写入失败!\r\n");        

//      OLED_ShowString(1,1,"write sed!");      

//  }

   

//  f_lseek(&file, f_size(&file));//确保写词写入不会覆盖之前的数据

//  if(res == FR_OK)

//  {

//      UsartPrintf(USART_DEBUG,"打开成功/创建文件成功! \r\n");      

//      OLED_ShowString(1,1,"create sed!");        

//      res = f_write(&file,write_buff,bufSize,&Bw);        //写数据到SD卡

//      if(res == FR_OK)

//      {

//          UsartPrintf(USART_DEBUG,"文件写入成功! \r\n");            

//          OLED_ShowString(1,1,"write sed!");          

//      }

//      else

//      {

//          UsartPrintf(USART_DEBUG,"文件写入失败! \r\n");        

//          OLED_ShowString(1,1,"write fad!");  

//      }      

//  }

//  else

//  {      

//      OLED_ShowString(1,1,"open fad!");  

//      UsartPrintf(USART_DEBUG,"打开文件失败!\r\n");

//  }  

   

//  f_close(&file);                     //关闭文件      

//  f_mount(NULL,"0:",1);        //取消挂载

   

// }

这个函数在运行时一直卡住,在函数第一行写了USARTprintf也没反应!检查接线也没问题!我删除以上代码,直接在main的开头写USART_Printf电脑却能收到!因此,我认为应该是乱码的问题(因为vscode中所有中文注释都不能正常识别,应该是编码错误),导致了一些编译上的错误 ,于是把所有的printf中的中文全部重写,然后再次编译,结果正常。

但是,我发现SPI一直卡在等待发送完成标志位上,检查连接没有问题。苦苦debug两天,最后,在一位大佬的博客中看到RCC_APB1PeriphClockCmd写错导致SPI没时序,我瞬间明白!!!

注意!!!SPI2是APB1上的外设,开启时钟要用RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);不要写成RCC_APB2PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);或者RCC_AHBPeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);写代码或者移植代码时一定要仔细查验。

之后试验卡在进入IDLe状态,仔细查之,发现SPI的MISO配置成了复用输出!!!改成上拉输入,最终成功!

(二)分析源码

课件图片+源代码(分析都写在注释里了)


/**
 * @brief 向SD卡发送指令,指令共48位
 * @param cmd 命令 8 bit
 * @param arg 命令参数 32 bit
 * @param crc crc校验值 8 bit
*/
int SD_sendcmd(uint8_t cmd,uint32_t arg,uint8_t crc){
	uint8_t r1;
	uint8_t retry;

	SD_CS(0);
		Delay_ms(20);
	SD_CS(1);
		//看看SD卡是否准备好
		//因为向SD卡发送数据时,MISO会拉低,直到完成写入才会为1
		do{
			retry=spi_readwrite(DFF);
		}while(retry!=0xFF);

	spi_readwrite(cmd | 0x40);
	//从高位开始发送
	spi_readwrite(arg >> 24);
	spi_readwrite(arg >> 16);
	spi_readwrite(arg >> 8);
	spi_readwrite(arg);
	spi_readwrite(crc);
	if(cmd==CMD12)spi_readwrite(DFF);//跳过无效字节
	do
		{
			r1=spi_readwrite(0xFF);
		}while(r1&0X80);
		
		return r1;
}

 



/**
 * @brief 读SD卡
 * @param buf 数据缓冲区
 * @param sector 扇区
 * @param CNT 扇区数
 * @retval
*/
uint8_t SD_ReadDisk(uint8_t*buf,uint32_t sector,uint8_t cnt)
{
	uint8_t r1;
	if(SD_TYPE!=V2HC)sector <<= 9;//转换为字节地址 <<9 就是*=512
	if(cnt==1)
	{
		r1=SD_sendcmd(CMD17,sector,0X01);//读命令
		if(r1==0)//指令发送成功
		{
			r1=SD_ReceiveData(buf,512);//接收512个字节   
		}
	}else
	{
		r1=SD_sendcmd(CMD18,sector,0X01);//连续读命令
		do
		{
			r1=SD_ReceiveData(buf,512);//接收512个字节   
			buf+=512;  
		}while(--cnt && r1==0); 	
		SD_sendcmd(CMD12,0,0X01);	//发送停止命令
	}   
	SD_CS(0);//取消片选
	return r1;
}




/**
 * @brief 写入数据
 * @param buf 缓冲区
 * @param sector 开始扇区
 * @param cnt 扇区数
 * @retval 0 成功; 其他 失败
 * 
*/
uint8_t SD_WriteDisk(uint8_t*buf,uint32_t sector,uint8_t cnt)
{
	uint8_t r1;
	if(SD_TYPE!=V2HC)sector *= 512;//转换为字节地址
	if(cnt==1)
	{
		r1=SD_sendcmd(CMD24,sector,0X01);//读命令
		if(r1==0)//发送成功
		{
			r1=SD_SendBlock(buf,0xFE);//写512字节   
		}
	}else
	{
		if(SD_TYPE!=MMC)
		{
			SD_sendcmd(CMD55,0,0X01);	
			SD_sendcmd(CMD23,cnt,0X01);//预先擦除	
		}
 		r1=SD_sendcmd(CMD25,sector,0X01);//发送连续写命令
		if(r1==0)
		{
			do
			{
				r1=SD_SendBlock(buf,0xFC);//接收512个字节
				buf+=512;  
			}while(--cnt && r1==0);
			r1=SD_SendBlock(0,0xFD);//发送连续写结束命令
		}
	}   
	SD_CS(0);//取消片选
	return r1;
}	

二、移植Fatfs

轻量级的网址如下

Petit FAT File System Module (elm-chan.org)

(一)分析源码

pffconf.h

这个是确定要使用的函数和文件系统的


/*---------------------------------------------------------------------------/
/ Function Configurations (0:Disable, 1:Enable)
/---------------------------------------------------------------------------*/

#define	PF_USE_READ		1	/* pf_read() function */
#define	PF_USE_DIR		1	/* pf_opendir() and pf_readdir() function */
#define	PF_USE_LSEEK	1	/* pf_lseek() function */
#define	PF_USE_WRITE	1	/* pf_write() function */

#define PF_FS_FAT12		0	/* FAT12 */
#define PF_FS_FAT16		1	/* FAT16 */
#define PF_FS_FAT32		0	/* FAT32 */

 diskio文件中的接口是要自己实现的,实现之后pff.h中的函数就可以直接拿来用

也可以使用标准FATfsFatFs - Generic FAT Filesystem Module (elm-chan.org)

(二)移植

disk_ioctl

详解SD协议与FatFs文件系统中的Block、Sector的差异,与FatFs中底层驱动disk_ioctl的编写及思路_sd卡一个block多大-CSDN博客

read/write直接调用sd的read/write 即可 

三、移植USB驱动

我使用Cube Mx 生成代码,打开CubeMx,选择stm32f103c8t6芯片,"connectivity"中选择USB,middleWare选择USB_DEVICE->Mass Storage xxx,生成源码

(一)找驱动

(一)用Cube生成(失败)

1.生成源码的父文件夹中有MiddleWare子文件夹,找ST-》\STM32_USB_Device_Library,把里面的所有文件取出备用,还有根目录下\USB_DEVICE文件夹里面的所有文件也取出备用(总之就是所有usbd开头的)。

2.尝试编译,发现各种报错,最终找到这几个文件引用的hal库是“stm32f1xxx_pcd.h”“stm32f1xxx_pcd.c”“stm32f1xxx_pcd_ex.h”“stm32f1xxx_pcd_ex.c”“stm32f1xxx_ll_usb.h”“stm32f1xxx_ll_usb.c”,又发现还引用了USB_BASE(在stm32f1xx_HAL.h里面),除此之外还有许多杂散的内容,报错未定义标识符后查找该标识符的位置,然后全部扔进一个stm32f1xx_hal_def.h文件中,再把上述几个文件中引用的“stm32f1xx_hal.h”换成"stm32f10x.h"和"stm32f1xx_hal_def.h",这样就确保这些文件引用的HAL库的东西都在。

还需注意的是许多文件中有宏判断HAL_PCD_MODULE_ENABLED,找到这些宏,直接去掉判断,不然会导致有些函数没编译,链接时会报错。之后Error_Handler我直接替换成usart_Printf,方便调试。

调试发现在USBD_LL_START报错,设备管理器这边显示:

由于该设备有问题,Windows 已将其停止。 (代码 43)

请求 USB 设备描述符失败。

放置printf调试,发现串口这边只接受到了USBD_LL,后面的没收到,估计是进中断了

/**
  * @brief  Starts the low level portion of the device driver.
  * @param  pdev: Device handle
  * @retval USBD status
  */
USBD_StatusTypeDef USBD_LL_Start(USBD_HandleTypeDef *pdev)
{
  HAL_StatusTypeDef hal_status = HAL_OK;
  USBD_StatusTypeDef usb_status = USBD_OK;

    UsartPrintf(USART_DEBUG, "USBD_LL_Start start\n");
  hal_status = HAL_PCD_Start(pdev->pData);

  usb_status =  USBD_Get_USB_Status(hal_status);

    UsartPrintf(USART_DEBUG, "USBD_LL_Start end\n");
  return usb_status;
}

 因为HAl库和标准库混用,有没有找到中断,最后尝试了改大堆栈、PA12接上拉电阻等等,都没有成功

2.去官网找

um0424 - 工具与软件 - Search STMicrocontrollers

解压之后Device_Lib_V4.1.0\Libraries\STM32_USB-FS-Device_Driver里面的文件都要拷贝到自己的工程里面,之后根据编译报错提示,把STM32_USB-FS-Device_Lib_V4.1.0\Projects\Mass_Storage例程里面的mass_mal.h/.c, hw_config.h/.c, platform_config.h, usb_desc.c/.h, usb_pwr.c/.h, usb_lib.c/.h,"usb_istr.h""usb_istr.c", “usb_conf.h”, “memory.c/.h”, “usb_scsi.h/.c”, “usb_bot.c/.h”都加入进来

(二)、移植

1.hw_config.h

1.加入中断处理函数(很重要,估计之前那个就是没有中断导致Windows卡在设备标识符识别上了)

//USB唤醒中断
void USBWakeUp_IRQHandler(void)
{
  EXTI_ClearITPendingBit(EXTI_Line18);
}

void USB_LP_CAN1_RX0_IRQHandler(void)
{
  USB_Istr();//处理各种USB中断事件
}


/*******************************************************************************
* Function Name  : Leave_LowPowerMode
* Description    : Restores system clocks and power while exiting suspend mode
* Input          : None.
* Return         : None.
*******************************************************************************/
void Enter_LowPowerMode(void)
{
  bDeviceState = SUSPENDED;
}

/*******************************************************************************
* Function Name  : Leave_LowPowerMode
* Description    : Restores system clocks and power while exiting suspend mode
* Input          : None.
* Return         : None.
*******************************************************************************/
void Leave_LowPowerMode(void)
{
  DEVICE_INFO *pInfo = &Device_Info;

  /* Set the device state to the correct state */
  if (pInfo->Current_Configuration != 0)
  {
    /* Device configured */
    bDeviceState = CONFIGURED;
  }
  else
  {
    bDeviceState = ATTACHED;
  }
}

2.修改函数


/*******************************************************************************
* Function Name  : Set_USBClock
* Description    : Configures USB Clock input (48MHz)
* Input          : None.
* Return         : None.
*******************************************************************************/
void Set_USBClock(void)
{
  /* Select USBCLK source */
  RCC_USBCLKConfig(RCC_USBCLKSource_PLLCLK_1Div5);
  /* Enable the USB clock */
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_USB, ENABLE);

}

/*******************************************************************************
* Function Name  : USB_Interrupts_Config
* Description    : Configures the USB interrupts
* Input          : None.
* Return         : None.
*******************************************************************************/
void USB_Interrupts_Config(void)
{
  NVIC_InitTypeDef NVIC_InitStructure; 
  EXTI_InitTypeDef EXTI_InitStructure;

  /*配置USB的EXTI中断*/
  EXTI_ClearITPendingBit(EXTI_Line18); 
  EXTI_InitStructure.EXTI_Line = EXTI_Line18;
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
  EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  EXTI_Init(&EXTI_InitStructure);

  /*配置USB的EXTI中断*/
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  
  NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
  
    /* Enable the USB Wake-up interrupt */
  NVIC_InitStructure.NVIC_IRQChannel = USBWakeUp_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_Init(&NVIC_InitStructure);
}

3.USB_Cable_Config函数实际上实在PA12没有上拉电阻的时候配置的。DISABLE:不上拉电阻,ENABLE:上拉电阻

4.删除IntToUnicode的static修饰

5.删除MAL_Config,USB_Disconnect_Config函数,加一个函数控制USB通断。


/*******************************************************************************
* Function Name  : MAL_Config
* Description    : MAL_layer configuration
* Input          : None.
* Return         : None.
*******************************************************************************/
void USB_PortSet(FunctionalState newState)
{
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
  if (newState == ENABLE)
  {
    _SetCNTR(_GetCNTR() & (~2));//连接
  }
  else
  {
    _SetCNTR(_GetCNTR() | (2));//断开
    GPIOA->CRH &= 0xFFF00FFF;
    GPIOA->CRH |= 0x00033000;
  }
}

6.把LED控制相关的函数都留空,或放置自己的代码,之后相应的更该头文件,并加入inttounicode的函数声明;

2.mass_mal.c

1.includeSD驱动代码

2.修改数组定义(MAX_LUN即支持磁盘的数量,这里只支持SD卡,MAX_LUN为一)

long long Mass_Memory_Size[MAX_LUN+1];//超过4G的卡无法存储

uint32_t Mass_Block_Size[MAX_LUN+1];

uint32_t Mass_Block_Count[MAX_LUN+1];

3.修改Init

/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
/*******************************************************************************
* Function Name  : MAL_Init
* Description    : Initializes the Media on the STM32
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
uint16_t MAL_Init(uint8_t lun)
{
  uint16_t status = MAL_OK;
  if (sd_init() == SD_ERROR)
  {
    status = MAL_FAIL;
  }
  return status;
}

4.修改read,write(对于4G以上的SD卡,uint32_t改为uint64_t, SD驱动也要改)

/*******************************************************************************
* Function Name  : MAL_Write
* Description    : Write sectors
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
uint16_t MAL_Write(uint8_t lun, uint32_t Memory_Offset, uint32_t *Writebuff, uint16_t Transfer_Length)
{
  //SD卡一个扇区的数据是512byte,所以除以2**9
  sd_write_disk((uint8_t *)Writebuff, Memory_Offset>>9, Transfer_Length>>9);
  return MAL_OK;
}

/*******************************************************************************
* Function Name  : MAL_Read
* Description    : Read sectors
* Input          : None
* Output         : None
* Return         : Buffer pointer
*******************************************************************************/
uint16_t MAL_Read(uint8_t lun, uint32_t Memory_Offset, uint32_t *Readbuff, uint16_t Transfer_Length)
{
  //SD卡一个扇区的数据是512byte,所以除以2**9
  sd_read_disk((uint8_t *)Readbuff, Memory_Offset>>9, Transfer_Length>>9);
  return MAL_OK;
}

5.修改函数


/*******************************************************************************
* Function Name  : MAL_GetStatus
* Description    : Get status
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
uint16_t MAL_GetStatus (uint8_t lun)
{

  return MAL_OK;
}

3.修改memory.h

1.Read_Memory,Write。。。类型改为uint64_t

4.修改scsi_data.c

给磁盘改名称

uint8_t Standard_Inquiry_Data[] =
  {
    0x00,          /* Direct Access Device */
    0x80,          /* RMB = 1: Removable Medium */
    0x02,          /* Version: No conformance claim to standard */
    0x02,

    36 - 4,          /* Additional Length */
    0x00,          /* SCCS = 1: Storage Controller Component */
    0x00,
    0x00,
    /* Vendor Identification */
    'S', 'T', 'M', ' ', ' ', ' ', ' ', ' ',
    /* Product Identification */
    'S', 'D', ' ', 'F', 'l', 'a', 's', 'h', ' ',
    'D', 'i', 's', 'k', ' ', ' ', ' ',
    /* Product Revision Level */
    '1', '.', '0', ' '
  };
uint8_t Standard_Inquiry_Data2[] =
  {
    0x00,          /* Direct Access Device */
    0x80,          /* RMB = 1: Removable Medium */
    0x02,          /* Version: No conformance claim to standard */
    0x02,

    36 - 4,          /* Additional Length */
    0x00,          /* SCCS = 1: Storage Controller Component */
    0x00,
    0x00,
    /* Vendor Identification */
    'S', 'T', 'M', ' ', ' ', ' ', ' ', ' ',
    /* Product Identification */
    'N', 'A', 'N', 'D', ' ', 'F', 'l', 'a', 's', 'h', ' ',
    'D', 'i', 's', 'k', ' ',
    /* Product Revision Level */
    '1', '.', '0', ' '
  };
5.修改usb_prop.c

uint32_t Max_Lun = MAX_LUN;

这样方便设置磁盘数量

6.修改usb_pwr.c
/**
  * Function Name  : Suspend
  * Description    : sets suspend mode operating conditions
  * Input          : None.
  * Output         : None.
  * Return         : USB_SUCCESS.
  */
void Suspend(void)
{
  uint32_t i = 0;
  uint16_t wCNTR;
  __IO uint32_t savePWR_CR = 0;
  //Store CNTR value
  wCNTR = _GetCNTR();
  for (i=0; i<8; i++)
  {
    EP[i] = _GetENDPOINT(i);
  }
  //unmark RESET flag
  wCNTR |= CNTR_RESETM;
  _SetCNTR(wCNTR);
  //apply FRES
  wCNTR |= CNTR_FRES;
  _SetCNTR(wCNTR);
  //claer FRES
  wCNTR &= ~CNTR_FRES;
  _SetCNTR(wCNTR);
  //poll for RESET flag in ISTR
  while ((_GetISTR()&ISTR_RESET) == 0);
  //claer RESET flag in ISTR
  _SetISTR((uint16_t)CLR_RESET);
  //restore Endpoints
  for (i=0; i<8; i++)
  {
    _SetENDPOINT(i, EP[i]);
  }
  wCNTR |= CNTR_FSUSP;
  _SetCNTR(wCNTR);
  //force low-power mode in the macrocell
  wCNTR = _GetCNTR();
  wCNTR |= CNTR_LPMODE;
  _SetCNTR(wCNTR);
  Enter_LowPowerMode();
}
7.优化数组定义

mass_mal.h添加


extern long long Mass_Memory_Size[MAX_LUN+1];
extern uint32_t Mass_Block_Size[MAX_LUN+1];
extern uint32_t Mass_Block_Count[MAX_LUN+1];

memory.c数组改指针(用malloc申请),防止内存不够

extern uint32_t *Data_Buffer; /* 512 bytes*/
uint8_t TransferState = TXFR_IDLE;
/* Extern variables ----------------------------------------------------------*/
extern uint8_t *Bulk_Data_Buff;  /* data buffer*/

 memory.h 声明一下这两个数组

usb_scsi,usb_bot去掉这两个数组,然后我们在main.c申请内存即可

7.修改main.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "..\Storage\SDdriver.h"

#include "..\Storage\USB\mass_mal.h"
#include "..\Storage\USB\usb_lib.h"
#include "..\Storage\USB\usb_bot.h"
#include "..\Storage\USB\usb_pwr.h"
#include "..\Storage\USB\usb_init.h"
#include "..\Storage\USB\hw_config.h"
#include "..\Storage\USB\memory.h"

#include "..\Interface\usart.h"
//#include "../Games/dino.h"

//STM32F103C8


int main(void)
{
	Usart1_Init(115200);
	UsartPrintf(USART1, "Start to init sd card\n");
	if (sd_init() == SD_OK)
	{
		UsartPrintf(USART1, "Succeed to init sd card\n");
	}
	else
	{
		UsartPrintf(USART1, "Failed to initialize sd card\n");
	}
	
	UsartPrintf(USART1, "Start to init usb device\n");

	uint32_t *Data_Buffer = (uint32_t *)malloc(BULK_MAX_PACKET_SIZE *2*sizeof(uint32_t)); /* 512 bytes*/
	uint8_t *Bulk_Data_Buff = (uint8_t *)malloc(BULK_MAX_PACKET_SIZE*sizeof(uint8_t));  /* data buffer*/
	
	Delay_ms(1000);
	USB_PortSet(DISABLE);
	Delay_ms(500);
	USB_PortSet(ENABLE);
	USB_Interrupts_Config();
	Set_USBClock();
	USB_Init();

	while (1)
	{

	}
}

之后编译正常,并且电脑能识别到stm32为大容量设备

(三)无法访问

之后想到程序设置中MAX_LUN为1,然而我的U盘有2个分区,导致读写错误,于是用Windows自带的分区工具分区

win+X,点磁盘管理 ,先删除两个分区,之后右键可分配的内存,格式化即可,但是再次尝试还是失败。。。估计是mass_mal的read\write那有问题。。。

后来测试发现无法正常读取容量,在这篇文章中找到答案,降低分频,虽能读到容量,但Windows仍然提示函数错误,估计还是硬件连接导致的,下次画PCB再测试STM32单片机使用SPI模式驱动micro sd卡模块,无法读取CSD寄存器,无法读取扇区,初始化能够成功_micro sd卡模块与stm32-CSDN博客

  • 29
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
STM32F103是一款32位的ARM Cortex-M3系列微控制器,具有较高的性能和丰富的外设资源。要实现基于SPI的SD卡读卡器,可以按照以下步骤进行: 1. 硬件连接:将SD卡读卡器的MISO(Master-In-Slave-Out),MOSI(Master-Out-Slave-In),SCK(SPI时钟线)和CS(片选)分别连接到STM32F103的相应引脚。 2. 配置SPI外设:在STM32CubeIDE中,通过HAL库函数配置SPI外设,设置通信速率、数据位宽、数据传输模式等参数。 3. 初始化SD卡:通过SPI发送初始化命令给SD卡读卡器,进行SD卡的初始化,例如设置工作电压、选择SPI模式等。 4. 发送指令:通过SPI向SD卡发送指令进行读写操作。例如,可以发送CMD0命令来复位SD卡,CMD8命令来获取SD卡的特性等。 5. 数据传输:通过SPI进行数据传输。可以通过CMD17命令选择块号并读取SD卡上的数据块,或者使用CMD24命令选择块号并向SD卡写入数据块。 6. 错误处理:在进行SD卡读写过程中,需要根据返回的响应码进行错误处理。比如,如果接收到的响应码表明命令执行失败,可以进行相应的错误处理。 7. 关闭SD卡:在程序结束或切换到其他操作之前,使用CMD12命令关闭SD卡。 总之,通过配置SPI外设和通过SPI发送命令和数据来实现基于SPI的SD卡读卡器。需要注意的是,具体的代码实现和配置可能会因不同的开发环境、工具链和SD卡读卡器而有所不同。因此,以上提供的步骤仅为一般性指导,具体实施时还需参考相关文档和资料。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值