HAL库 CubeMX STM32采用SDIO实现对SD卡和NAND Flash的读写

news2024/9/21 0:40:05

目录

一、选择合适的存储芯片。 

可以去雷龙官网白嫖,白嫖链接:免费样品

二、SD卡/SD NAND底层原理

三、 CubeMX配置STM32具体步骤

1、时钟和系统配置

2、配置SDIO

3、配置DMA (可选)

4、设置串口

四、代码编写

1、公共代码

2、常规方式读写

3、DMA方式读写

五、结果分析

1、输入的函数参数是扇区编号,而不是实际偏移地址。

2、测试结果


完整项目源代码下载地址:HAL库CubeMXSTM32采用SDIO实现对SD卡和NANDFlash的读写资源-CSDN文库

一、选择合适的存储芯片。 

        最近在做一个项目,需要实现大量存储读取数据,但是stm32上自带的存储器容量太小了,比如我用的这款STM32F103ZET6本身的flash容量为512K,不够用。

        相关单片机芯片型号资源如下:

最终项目采用的方案是:雷龙公司的CSNP4GCR01这款存储卡。当然也可以用TF卡,使用方法都是采用SDIO总线驱动,程序都是一模一样的,但是这款相较于普通的TF卡有更多的优势,具体体现在以下几点:

        在一些贴片芯片的PCB设计中,无论是在面积有着严格要求中还是在实际恶劣环境中,并且胜在价格、封装以及稳定性上有优势,综合来说性价比更高,雷龙公司的NAND FLASH方案占有明显优势,这也正是我在项目中选择使用它的原因。

       可以去雷龙官网白嫖,白嫖链接:免费样品

二、SD卡/SD NAND底层原理

        根据SD卡的容量,可划分为SDSC、SDHC、SDXC三种标准。现今,市场的主流SD产品是SDHC和SDXC这两种较大容量的存储卡,而SDSC卡因容量过小,已逐渐被市场淘汰。SD卡(三种卡的统称)的存储空间是由一个一个扇区组成的,SD卡的扇区大小是512byte,若干个扇区又可以组成一个分配单元(也被成为簇),分配单元常见的大小为4K、8K、16K、32K、64K。

        具体原理这里我就不具体写了,网上有很多,可以参考以下链接:

SD NAND 的 SDIO在STM32上的应用详解(上篇)_sdio接两个芯片_深圳市雷龙发展有限公司的博客-CSDN博客

三、 CubeMX配置STM32具体步骤

1、时钟和系统配置

2、配置SDIO

(1)Clock transition on which the bit capture is made: Rising transition。主时钟 SDIOCLK 产生 CLK 引脚时钟有效沿选择,可选上升沿或下降沿,它设定 SDIO 时钟控制寄存器(SDIO_CLKCR)的 NEGEDGE 位的值,一般选择设置为上升沿。(参考链接)

(2)SDIO Clock divider bypass:Disbale。时钟分频旁路使用,可选使能或禁用,它设定 SDIO_CLKCR 寄存器的 BYPASS 位。如果使能旁路,SDIOCLK 直接驱动 CLK 线输出时钟;如果禁用,使用 SDIO_CLKCR 寄存器的 CLKDIV 位值分频 SDIOCLK,然后输出到 CLK 线。一般选择禁用时钟分频旁路。

(3)SDIO Clock output enable when the bus is idle: Disable the power save for the clock。节能模式选择,可选使能或禁用,它设定 SDIO_CLKCR 寄存器的 PWRSAV 位的值。如果使能节能模式,CLK 线只有在总线激活时才有时钟输出;如果禁用节能模式,始终使能 CLK 线输出时钟。

(4)SDIO hardware flow control: The hardware control flow is disabled。硬件流控制选择,可选使能或禁用,它设定 SDIO_CLKCR 寄存器的 HWFC_EN 位的值。硬件流控制功能可以避免 FIFO 发送上溢和下溢错误。

SDIOCLK clock divide factor: 6。时钟分频系数,它设定 SDIO_CLKCR 寄存器的 CLKDIV 位的值,设置 SDIOCLK 与 CLK 线输出时钟分频系数: 

CLK 线时钟频率=SDIOCLK/([CLKDIV+2])。

识别卡阶段:时钟频率 FOD,最高为 400kHz
数据传输模式:时钟频率FPP,默认最高为 25MHz
如果通过相关寄存器配置使 SDIO 工作在高速模式,此时数据传输模式最高频率为 50MHz

### 注意:刚开始我做的过程中,参考了下面的SDIOCLK,然后就设置了时钟分频系数为0,导致CLK 线时钟频率=72/([0+2])=36M大于最高数据传输速率25M,生成代码后,程序一直卡死在SDIO初始化函数中,导致SD卡初始化失败。

仔细查看上面的第二点(2),由于在我们之前配置中禁用了时钟分频旁路,所以我们不能参考下面这个SDIOCLK,所以实际使用SDIOCLK=72M,应该设置时钟分频系数为2以上。

3、配置DMA (可选)

        在一些实际使用场合中,比如需要把摄像头帧数据存储到SD卡,需要高效的存储和取用大量数据时,我们往往采用DMA,减轻CPU的负担。

        SDIO 外设支持生成 DMA 请求,使用 DMA 传输可以提高数据传输效率,因此在 SDIO 的控制代码中,可以把它设置为 DMA 传输模式。

4、设置串口

       打开串口,方便通过串口实时打印出SD卡的信息以及查看调试信息。

四、代码编写

1、公共代码

以下两种模式下添加的公共代码部分

#include <stdio.h>
#include <string.h>

#define BLOCK_START_ADDR         0     /* Block start address      */
#define NUM_OF_BLOCKS            1     /* 扇区编号  */
#define BUFFER_WORDS_SIZE        ((BLOCKSIZE * NUM_OF_BLOCKS) >> 2) /* Total data size in bytes */

//这里定义大小为512byte,正好是SD卡一个扇区,偏移地址0x00000200
uint8_t Buffer_Tx[512],Buffer_Rx[512] = {0};
uint8_t Buffer_Tx_DMA[1024],Buffer_Rx_DMA[1024] = {0};
uint32_t i;

extern DMA_HandleTypeDef hdma_sdio;

int fputc(int c, FILE *stream)    //重写fputc函数
{
 /*
    huart1是工具生成代码定义的UART1结构体,
    如果以后要使用其他串口打印,只需要把这个结构体改成其他UART结构体。
*/
    HAL_UART_Transmit(&huart1, (unsigned char *)&c, 1, 1000);   
    return 1;
}


// 打印SD卡基本信息
void show_sdcard_info(void)
{
		printf("Micro SD Card Test...\r\n");
		/* 检测SD卡是否正常(处于数据传输模式的传输状态) */
		if(HAL_SD_GetCardState(&hsd) == HAL_SD_CARD_TRANSFER)
		{      
				printf("Initialize SD card successfully!\r\n");
				// 打印SD卡基本信息
				printf(" SD card information! \r\n");
				printf(" CardCapacity  				: %llu \r\n", (unsigned long long)hsd.SdCard.BlockSize * hsd.SdCard.BlockNbr);// 显示容量
				printf(" CardBlockSize 				: %d \r\n", hsd.SdCard.BlockSize);   // 块大小
				printf(" LogBlockNbr   				: %d \r\n", hsd.SdCard.LogBlockNbr);	// 逻辑块数量
				printf(" LogBlockSize  				: %d \r\n", hsd.SdCard.LogBlockSize);// 逻辑块大小
				printf(" RCA                 	: %d \r\n", hsd.SdCard.RelCardAdd);  // 卡相对地址
				printf(" CardType            	: %d \r\n", hsd.SdCard.CardType);    // 卡类型
				// 读取并打印SD卡的CID信息
				HAL_SD_CardCIDTypeDef sdcard_cid;
				HAL_SD_GetCardCID(&hsd,&sdcard_cid);
				printf(" ManufacturerID: %d \r\n",sdcard_cid.ManufacturerID);
		}
		else
		{
				printf("SD card init fail!\r\n" );
		}
}

/* 擦除SD卡块 */
void erase_sdcard(SD_HandleTypeDef *hsd, uint32_t BlockStartAdd, uint32_t BlockEndAdd)
{
  printf("------------------- Block Erase -------------------------------\r\n");
  if(HAL_SD_Erase(hsd, BlockStartAdd, BlockEndAdd) == HAL_OK)
  {
    /* Wait until SD cards are ready to use for new operation */
    while(HAL_SD_GetCardState(hsd) != HAL_SD_CARD_TRANSFER)
    {
    }
    printf("\r\nErase Block Success!\r\n");
  }
  else
  {
      printf("\r\nErase Block Failed!\r\n");					
  }
}

2、常规方式读写

/* 填充缓冲区数据 */
void write_sdcard(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks, uint32_t Timeout)
{
  /* 向SD卡块写入数据 */
  printf("------------------- Start Write SD card block data ------------------\r\n");
  if(HAL_SD_WriteBlocks(hsd, pData, BlockAdd, NumberOfBlocks, Timeout) == HAL_OK)
  {
    while(HAL_SD_GetCardState(hsd) != HAL_SD_CARD_TRANSFER)
    {
    }
    printf("\r\nWrite Block Success!\r\n");
  }
  else
  {
    printf("\r\nWrite Block Failed!\r\n");
  }
}

/* 读取操作之后的数据 */
void read_sdcard(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks, uint32_t Timeout)
{
  printf("------------------- Start Read SD card block data ------------------\r\n");
  if(HAL_SD_ReadBlocks(hsd, pData, BlockAdd, NumberOfBlocks, Timeout) == HAL_OK)
  {
    while(HAL_SD_GetCardState(hsd) != HAL_SD_CARD_TRANSFER)
    {
    }
    printf("\r\nRead Block Success!\r\n");
  }
  else
  {
    printf("\r\nRead Block Failed!\r\n");				
  }
}

主函数里代码如下:

  /* USER CODE BEGIN 2 */
show_sdcard_info();
//擦除5个扇区
erase_sdcard(&hsd, 0, 5);
memset(Buffer_Tx, 0x55, sizeof(Buffer_Tx));
write_sdcard(&hsd, Buffer_Tx, 1, 2, 10);
read_sdcard(&hsd, Buffer_Rx, 1, 2, 10);
//查看读取到的数据
for(i = 0; i < sizeof(Buffer_Rx)/sizeof(Buffer_Rx[0]); i++)
{
   printf("0x%02x:%02x ", i, Buffer_Rx[i]);
}
  /* USER CODE END 2 */

3、DMA方式读写

这里需要注意:SDIO DMA每次由读数据变为写数据或者由写数据变为读数据时,都需要重新初始化DMA,是为了更改数据传输的方向。

这里特别坑,参考别的博主的代码发现不能用,经过自己亲自修改后,代码更改了很多次才发现这个问题,以下代码验证通过可直接使用

//非阻塞式DMA读
HAL_StatusTypeDef SDIO_ReadBlocks_DMA(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks)
{
	HAL_StatusTypeDef Return_Status;
	HAL_SD_CardStateTypeDef SD_Card_Status;
	
	do{ SD_Card_Status = HAL_SD_GetCardState(hsd);}while(SD_Card_Status != HAL_SD_CARD_TRANSFER );

	HAL_DMA_DeInit(&hdma_sdio);
	hdma_sdio.Instance = DMA2_Channel4;
	hdma_sdio.Init.Direction = DMA_PERIPH_TO_MEMORY;
	hdma_sdio.Init.PeriphInc = DMA_PINC_DISABLE;
	hdma_sdio.Init.MemInc = DMA_MINC_ENABLE;
	hdma_sdio.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
	hdma_sdio.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
	hdma_sdio.Init.Mode = DMA_NORMAL;
	hdma_sdio.Init.Priority = DMA_PRIORITY_LOW;
	if (HAL_DMA_Init(&hdma_sdio) != HAL_OK)
	{
		Error_Handler();
	}
	__HAL_LINKDMA(hsd,hdmarx,hdma_sdio);	
 
	MX_SDIO_SD_Init();
	
	Return_Status = HAL_SD_ReadBlocks_DMA(hsd, pData, BlockAdd, NumberOfBlocks);
	
	return Return_Status;
}

//非阻塞式DMA写
HAL_StatusTypeDef SDIO_WriteBlocks_DMA(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks)
{
	HAL_StatusTypeDef Return_Status;
	HAL_SD_CardStateTypeDef SD_Card_Status;
	do{ SD_Card_Status = HAL_SD_GetCardState(hsd);}while(SD_Card_Status != HAL_SD_CARD_TRANSFER );

	HAL_DMA_DeInit(&hdma_sdio);
	hdma_sdio.Instance = DMA2_Channel4;
	hdma_sdio.Init.Direction = DMA_PERIPH_TO_MEMORY;
	hdma_sdio.Init.PeriphInc = DMA_PINC_DISABLE;
	hdma_sdio.Init.MemInc = DMA_MINC_ENABLE;
	hdma_sdio.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
	hdma_sdio.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
	hdma_sdio.Init.Mode = DMA_NORMAL;
	hdma_sdio.Init.Priority = DMA_PRIORITY_LOW;
	if (HAL_DMA_Init(&hdma_sdio) != HAL_OK)
	{
		Error_Handler();
	}
	__HAL_LINKDMA(hsd,hdmarx,hdma_sdio);	
 
	MX_SDIO_SD_Init();

	Return_Status = HAL_SD_WriteBlocks_DMA(hsd,pData, BlockAdd, NumberOfBlocks);
	
	return Return_Status;
}

主函数里代码如下:

/* USER CODE BEGIN 2 */

HAL_StatusTypeDef Return_Status;	
memset(Buffer_Tx_DMA, 0x55, sizeof(Buffer_Tx_DMA));
Return_Status = SDIO_WriteBlocks_DMA(&hsd,Buffer_Tx_DMA, 0, 1);
printf("write status :%d\r\n",Return_Status);
Return_Status = SDIO_ReadBlocks_DMA(&hsd,Buffer_Rx_DMA, 0, 2);
printf("read status :%d\r\n",Return_Status);
//查看读取到的数据
for(i = 0; i < sizeof(Buffer_Rx_DMA)/sizeof(Buffer_Rx_DMA[0]); i++)
{
  printf("0x%02x:%02x ", i, Buffer_Rx_DMA[i]);
}

/* USER CODE END 2 */

五、结果分析

1、输入的函数参数是扇区编号,而不是实际偏移地址。

SD卡的扇区大小是512byte ,所以每个扇区的偏移地址是0x200

这里的参数要传入SD卡扇区的编号,而不是地址,进入原函数我们可以看到官方内部已经帮我们做好了地址偏移。

2、测试结果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1132700.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue 数据劫持代理原理

function lianxi(){// vue 数据劫持代理let data {username:curry,age:33}//模拟组件的实例let _this {}//利用Object.defineProperty()for( let item in data){//console.log(item,data[item])Object.defineProperty(_this,item,{//get:用来获取扩展属性值的,当获取该属性值…

基本指令(2):通配符,重定向,命令行管道

一、通配符 rm -rf ./* # * —— 通配符&#xff0c;指定路径下的所有文件&#xff08;不包括隐藏文件&#xff09;二、重定向 在理解重定向前&#xff0c;先要有一个概念&#xff1a;Linux下一切皆文件&#xff0c;大部分硬件设备都可以看做有读写方法&#xff0c;只不过有些方…

Web前端免费接入Microsoft Azure AI文本翻译,享每月2百万个字符的翻译

Azure 文本翻译是 Azure AI 翻译服务的一项基于云的 REST API 功能。 文本翻译 API 支持实时快速准确地进行源到目标文本翻译。 文本翻译软件开发工具包 (SDK) 是一组库和工具&#xff0c;可用于轻松地将文本翻译 REST API 功能集成到应用程序中。 文本翻译 SDK 可跨 C#/.NET、…

国腾GM8775C完全替代CS5518 MIPIDSI转2 PORT LVDS

集睿致远CS5518描述&#xff1a; CS5518是一款MIPI DSI输入、LVDS输出转换芯片。MIPI DSI 支持多达4个局域网&#xff0c;每条通道以最 大 1Gbps 的速度运行。LVDS支持18位或24位像素&#xff0c;25Mhz至154Mhz&#xff0c;采用VESA或JEIDA格 式。它只能使用单个1.8v电源&am…

人脸活体检测:Domain-Generalized Face Anti-Spoofing with Unknown Attacks

论文作者&#xff1a;Zong-Wei Hong,Yu-Chen Lin,Hsuan-Tung Liu,Yi-Ren Yeh,Chu-Song Chen 作者单位&#xff1a;National Taiwan University; E.SUN Financial Holding Co., Ltd.; National Kaohsiung Normal University 论文链接&#xff1a;http://arxiv.org/abs/2310.11…

Echarts多曲线数值与Y周刻度不符合

发现问题&#xff1a; 在展示多曲线图表的时候&#xff0c;发现图表曲线数值相差不大&#xff0c;但是图表展示的曲线相差很大&#xff0c;仔细观察之后发现是展示有问题(其实这并不能算是错误&#xff0c;只是由于忽略&#xff0c;导致的配置与预期不符合)。 问题复现&#x…

【低代码平台】JeecgBoot代码生成器如何使用?Online代码生成

Online代码生成 目前Vue3已经支持两种模式&#xff1a;Online在线模式 和 GUI模式代码生成。 JeecgBoot版本要求&#xff1a; 3.2.0 ( 提供了vue3、vue3Native模板目录 ) 第一步&#xff1a;通过online表单在线建表 jeecg提供了在线建表的功能&#xff0c;找到菜单&#xff1a;…

关于城市综合管廊分析与应用

安科瑞 华楠 摘要&#xff1a;文章介绍了城市综合管廊的概念和我国综合管廊建设的背景&#xff0c;并总结归纳了综合管廊的设计要点以及注意事项&#xff0c;为今后的综合管廊设计提供参考。 关键词&#xff1a;城市&#xff1b;综合管廊&#xff1b;应用 1 规划背景及技术路…

GEE案例——一个完整的火灾监测案例dNBR差异化归一化烧毁指数

差异化归一化烧毁指数 dNBR是"差异化归一化烧毁指数"的缩写。它是一种用于评估卫星图像中烧毁区域严重程度的遥感指数。dNBR值通过将火灾前的归一化烧毁指数(NBR)减去火灾后的NBR来计算得出。该指数常用于野火监测和评估。 dNBR(差异化归一化烧毁指数)是一种用…

报错:Could not resolve host: mirrorlist.centos.org;Unknown error

报错&#xff1a;Could not resolve host: mirrorlist.centos.org;Unknown error 一般是因为网络配置错误导致无法连接外网&#xff0c;我们先尝试ping一下www.baidu.com发现无法ping通。 果然&#xff0c;接下来我们就开始排查吧&#xff01;&#xff01; 1.网络配置查看 打开…

LeetCode88——合并两个有序数组

LeetCode88——合并两个有序数组 1.题目描述&#xff1a; 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中&#xff0c;使合并后的数组同样按 非递减…

SylixOS BSP开发(八)

初始化FPU、MMU和Cache组件 本来想在不初始化这些部件的情况下把SylixOS先启动起来感受下&#xff0c;结果测试发现如果MMU不使能的话&#xff0c;系统启动过程中线程无法进行调度emm。。。所以只好把这一章节提前来讲了。这三个组件的初始化都是在bspInit.c中进行的。 1. FPU初…

从零开始做一个SDWAN

VPN和SD-WAN的区别 VPN&#xff08;Virtual Private Network&#xff09;和SD-WAN&#xff08;Software-Defined Wide Area Network&#xff09;是两种不同的网络技术&#xff0c;它们在目的、功能和实施方式上有一些重要的区别。 目的和应用场景: VPN&#xff1a;VPN主要用于…

做图标设计一些常用的设计规范分享

应用程序图标设计是将某个概念转换为清晰易读的图形&#xff0c;从而降低用户的理解成本&#xff0c;提高界面的美感。在我们的企业级应用设计范围内&#xff0c;应用图标在界面设计的许多元素中往往只占很小的比例&#xff0c;调用时会缩小到比设计稿小很多倍的尺寸。此外&…

kafka3.X集群安装(不使用zookeeper)

参考: 【kafka专栏】不用zookeeper怎么安装kafka集群-最新kafka3.0版本 一、kafka集群实例角色规划 在本专栏的之前的一篇文章《kafka3种zk的替代方案》已经为大家介绍过在kafka3.0种已经可以将zookeeper去掉。 上图中黑色代表broker&#xff08;消息代理服务&#xff09;&…

DLT645转modbus协议网关采集电表的数据方法

DLT645有两个版本分别是DLT645-97和DLT645-07&#xff0c;该协议主要用于电表抄表&#xff0c;采用为主-从结构的半双工通讯模式&#xff0c;硬件接口使用RS-485今天我们来看下&#xff0c;用远创智控YC-645-TCP网关如何采集电表的数据 1&#xff0c;首先&#xff0c;我们需要…

淘宝京东抖音1688苏宁等关键词搜索商品API接口(关键词搜索商品API接口,关键词搜索商品列表接口)

淘宝京东抖音1688苏宁等关键词搜索商品API接口&#xff08;关键词搜索商品API接口&#xff0c;关键词搜索商品列表接口&#xff09;代码对接如下&#xff1a; item_search-按关键字搜索淘宝商品 taobao.item_search 1.公共参数 名称类型必须描述keyString是调用key&#xf…

UG\NX二次开发 获取面的面面积、周长

文章作者:里海 来源网站:王牌飞行员_里海_里海NX二次开发3000例,里海BlockUI专栏,C\C++-CSDN博客 感谢粉丝订阅 感谢 weixin_38891498 订阅本专栏,非常感谢。 简介 UG\NX二次开发 获取面的面面积、周长 效果 代码 #include "me.hpp" #include <NXOpen/Session…

docker部署prometheus+grafana服务器监控(二) - 安装数据收集器 node-exporter

在目标服务器安装数据收集器 node-exporter 1. 安装数据收集器 node-exporter wget https://github.com/prometheus/node_exporter/releases/download/v1.6.1/node_exporter-1.6.1.linux-amd64.tar.gztar xvf node_exporter-1.6.1.linux-amd64.tar.gzmv node_exporter-1.6.1…

【蓝桥】铺地板

1、题目 问题描述 小蓝家要装修了&#xff0c;小蓝爸爸买了很多块&#xff08;可理解为数量无限&#xff09; 2 3 2 \times 3 23 规格的地砖&#xff0c;小蓝家的地板是 n m n \times m nm 规格的&#xff0c;小蓝想问你&#xff0c;能否用这些 2 3 2 \times 3 23 的地砖…