【STM32+HAL】利用SDIO将大量数据存入SD卡,并根据需要读出

一、所用材料

  1. STM32F407ZGT6
  2. SD卡

二、实现功能

        当我们需要存储的数据量非常庞大时,STM32F407ZGT6自带的192K字节的SRAM或是一些板子上配备的外部SRAM内存不够用时,可以存入SD卡中。本代码实现的功能是利用代码中的一个小数组去接收数据,当数据个数达到数组的最大个数时,会按顺序将数据存放在SD卡相应的块上,并准备进行下一次的存储。

三、实现过程

 3.1 CubeMX

  1、基础配置

  2、配置RCC

        与以往不同的是SDIO适配器的时钟是单独配置的,需要专用的SDIOCLK,标准工作在48MHz。

  3、配置SDIO

3.2 KEIL 

  1、更改代码

        在生成的SDIO初始化函数MX_SDIO_SD_Init()中需要将参数配置中的SD卡数据总线宽度从默认的4位手动修改为1位,在SD卡初始化时应该以不超过400KHz的速率,1位总线宽度进行初始化,如果不修改这里SD卡将无法成功初始化。我第一次尝试的时候程序一直跑不通都找不到原因,最后网上找了很久才知道是这个原因。

  2、添加代码

        在main.c中添加获取SD信息并打印的代码,测试单片机与SD卡是否已经连接好。

HAL_SD_GetCardInfo(&hsd, &pCardInfo);
printf("pCardInfo.CardType = %u\r\n",pCardInfo.CardType);       //SD卡类型
printf("pCardInfo.CardVersion = %u\r\n",pCardInfo.CardVersion); //版本
printf("pCardInfo.BlockNbr = %u\r\n",pCardInfo.BlockNbr);       //SD卡块数
printf("pCardInfo.BlockSize = %u\r\n",pCardInfo.BlockSize);     //每一块大小
printf("\r\n");

        倘若可以正常输出信息,则可以进行下一步的SD卡读写的试验。这边的定义简单说明一下,因为SD卡的一个块中有512个字节,所以为了方便起见,我定义了一个八位的数组,即一个数据占一个字节的内存,可存储0-255之间的数据。而我又将数组中的数据个数定义为512,那么当一个数组的数据存满时,一整个数组的内存大小正好是512个字节,与SD卡中一个块的大小相同。

#define BLOCK_SIZE   512                            //一个块的字节数
#define NUMBERS_PER_CHUNK 512  						//多少个数据存一次
#define INT_SIZE 1             						//一个数值占几个字节
#define BUFFER_SIZE (NUMBERS_PER_CHUNK * INT_SIZE)  //存一次sd卡的数组的大小(其实就是512)

uint8_t buffer_TX[NUMBERS_PER_CHUNK];  // 用于暂时存储需要发送到SD卡的数据
uint8_t buffer_RX[NUMBERS_PER_CHUNK];  // 用于接收从SD卡中读取来的数据	 

        在测试开始前这边需要先介绍一下SD卡的写入函数,读取函数同理。               

HAL_StatusTypeDef HAL_SD_WriteBlocks(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks, uint32_t Timeout)
1、*hsd: 这是一个指向SD卡句柄(SD_HandleTypeDef)的指针,该句柄包含了SD卡操作所需的配置和状态信息。通常在程序初始化阶段会对这个句柄进行配置,以设置SD卡的通信参数等。

2、*pData: 这是一个指向数据缓冲区的指针,该缓冲区存储着要写入SD卡的数据。通常这是一个字节数组。

3、BlockAdd: 这个参数指定了写入操作的起始块地址。在SD卡中,数据存储是按块进行的,每个块通常有512字节。这里的数值表示从SD卡中的第几个块开始写入。

3、NumberOfBlocks:这个参数指定了要写入的块的数量。

4、Timeout:这是超时值,单位是毫秒。这个参数定义了操作的最长等待时间。如果在1000毫秒内操作没有完成,则函数返回一个超时错误。

        接下来是SD卡的写入测试 ,以下代码实现的是先在buffer_TX[ ]数组中存满0-255的数字,存满512个字节后放到SD的第一个块上,然后chunk+1,下一次就将存储到SD卡的下一个块中。

uint16_t blockNum = 0;               // 起始块号
	 
/*--------------------------SD卡写测试----------------------------------*/
for (int chunk = 0; chunk < 2; chunk++) 
{
    // 填充缓冲区
    for (int i = 0; i < NUMBERS_PER_CHUNK; i++)
    {
        buffer_TX[i] = i/2;
		printf("%d ",buffer_TX[i]); 
    }

    // 计算写入的块数量
    uint32_t blocksToWrite = BUFFER_SIZE / BLOCK_SIZE; //(512*1=512)
			 
			
    // 将数据写入SD卡
    if (HAL_SD_WriteBlocks(&hsd, (uint8_t *)buffer_TX, blockNum, 1, 10000) == HAL_OK) 
	{
	    while(HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER);  //处于传输状态退出
        printf("\r\nWriteBlocks Successfully\r\n");
    }
	else
    {
        printf("WriteBlocks Failed\n");
    }
        blockNum += 1;  // 更新块号以写入下一个段
}

        如果运行顺利的话,你的串口调试助手中将会收到以下的内容。

        接下来就是就是从SD卡中读出数据,原理与SD卡的写入相同。

blockNum = 0;  // 起始块号

for (int chunk = 0; chunk < 2; chunk++)
{
    // 计算读取的块数量
    uint32_t blocksToRead = BUFFER_SIZE / BLOCK_SIZE;

    // 从SD卡读取数据
    if (HAL_SD_ReadBlocks(&hsd, (uint8_t *)buffer_RX, blockNum, 1, 1000) == HAL_OK) 
    {
        while(HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER);//返回到传输状态退出
        printf("ReadBlocks Successfully\r\n");
	    for (int i = 0; i < NUMBERS_PER_CHUNK; i++) 
	    {
            printf("%d ", buffer_RX[i]);
	    }
    }
    else
    {
        printf("WriteBlocks Failed\n");
    }
    blockNum += 1;  // 更新块号以读取下一个段
}

        如果运行顺利的话 ,你的串口调试助手中将会收到以下的内容。说明已经从SD卡的相应块中读出了刚才存入的数据。

  3、代码拓展

        场景为智能小车在循迹时,需要将运动时产生的数据记录下来,并在需要时复刻出循迹时的数据,源代码的实现是有一个定时器中断,即每过一段相应的时间,摄像头就向单片机传输一次数据。以下是需要用到的存取数据的代码。

/*-----------------写入函数-----------------*/
void write_sd (int put) 
{
    if(i<NUMBERS_PER_CHUNK){
    buffer_TX[i] = put;
//	printf("%d,", buffer_TX[i]);
	i++;
    }
    else
    {
		HAL_SD_GetCardInfo(&hsd, &pCardInfo); 
		if (HAL_SD_WriteBlocks(&hsd, (uint8_t *)buffer_TX, blockNum, 1, 10000) == HAL_OK)             
        {
            while(HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER);  //处于传输状态退出
            printf("WriteBlocks Successfully\r\n");
		}
		i=0;
		buffer_TX[i] =  put;
		blockNum += 1;
	}	

}


/*-----------------读取函数-----------------*/
void read_sd(void)
{
// 从SD卡读取数据
    if (HAL_SD_ReadBlocks(&hsd, (uint8_t *)buffer_RX, blockNum, 1, 1000) == HAL_OK) 
	{
        while(HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER);//返回到传输状态退出
		printf("ReadBlocks Successfully\r\n");
	}
    blockNum+= 1;  // 更新块号以读取下一个段
 }

        整个代码需要实现的效果是,每一次定时器中断都往buffer_TX中存入一个数据,当buffer_TX数组被填满时,就存放入SD卡中。当小车停下时就将未存满的buffer_TX直接放入相应的块中,并更新块号,便于存储多套数据。而读取数据要做的是,每当buffer_TX数组内的数据被使用完时,就重新从SD卡的下一块中读取新的一组数据。

HAL_SD_CardInfoTypeDef pCardInfo;//定义结构体用来接收卡信息

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == htim6.Instance)
    {
        if(flag=1)    //存入模式,speed_dif 为任意你获取的并想存入SD卡的数据
        {           
            write_sd(speed_dif);
	        else if(stop == 1)		//当数据采集停止时				    																																								
		    {																																																										
		        if(first_stop == 1)			//达到终点时要将没有集满SD一个块的数据仍存放进SD卡中
			    {
			        if (HAL_SD_WriteBlocks(&hsd, (uint8_t *)buffer_TX, blockNum, 1, 10000) == HAL_OK) 															
				    {
				        while(HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER);  //处于传输状态退出																
					    printf("WriteBlocks Successfully\r\n");
				    }																																																							
			        first_stop = 0;
			        blockNum += 1;				//从下一块重新开始记录																			
			        blockdata_first = blockNum;				//记下当前记录到的块数
			        i = 0;								//从0开始																									
			    }
	        }
        }
        if(flag=2)    //读出模式
        {    
            if(flag2_stop == 0)							
			{
                if(first_flag2)							//如果刚到读出模式,则读取一次数据
				{
				    read_sd();	
                    first_flag2 = 0;
				}
			    printf("%d ", buffer_RX[j]);      //把读出来的数据给电机
				j++;
				if(buffer_RX[j-1]==0&&buffer_RX[j]==0&&buffer_RX[j+1]==0)  //判断SD卡中保存的几组数据是否读完,当读到一连串的0时,说明数据没有保存到这个为止,即数据读取完毕,读出模式可以结束
                    flag2_stop = 1;
			}
        }
    }
}

                这部分代码因为应用的场景比较特殊,大家需要根据自己的需求更改其中的变量以及内容,这里只能算是提供一种思路上的参照,具体的配置和逻辑还是需要大家自行判断。

四、结语

        这篇博客主要是趁自己还有印象的时候记录一下前几天配置SD卡的过程,因为前几天做的一题需要将循迹小车的运动轨迹复刻下来,所以临时学习了SD卡的存储,配置和写代码的过程中花费了很长的时间,所以感觉有必要在博客中记录一笔,也便于日后自己翻阅。文章没有深入探讨SD卡存取的底层逻辑,只是一个使用的方案,大家如果有什么问题,或者这篇博客有什么不足,欢迎讨论哦。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值