stm32中的SDIO

SDIO-SD卡

文章目录

  • SDIO-SD卡
  • SD卡结构
    • 物理结构
      • SD卡寄存器列表
  • SDIO总线
    • SDIO总线拓扑
    • SDIO总线
    • SDIO总线协议
    • 常规数据传输
    • 宽位数据包
  • 命令
    • 命令格式
    • 命令的类型
    • 命令集
  • SD卡的操作模式
    • 数据传输模式
  • STM32 的 SDIO 功能框图
    • 控制单元
    • 命令路径
    • CPSM 状态机描述图
    • 数据路径
    • 数据 FIFO
  • SDIO结构体
    • SDIO初始化结构体
    • SDIO 命令初始化结构体
    • SDIO 数据初始化结构体
  • SD卡的读写实验
    • 准备工作
    • void SD_EraseTest(void)
    • void SD_MultiBlockTest(void)
    • 比较函数和填充函数补充
    • 测试程序
    • main.c

SD卡结构

物理结构

在这里插入图片描述

  • 存储单元是存储数据部件,存储单元通过存储单元接口与卡控制单元进行数据传输;
  • 电源检测单元保证SD卡工作在合适的电压下,如出现掉电或上状态时,它会使控制单元和存储单元接口复位;
  • 卡及接口控制单元控制SD卡的运行状态,它包括有8个寄存器;
  • 接口驱动器控制SD卡引脚的输入输出
  • SD卡总共有8个寄存器,用于设定或表示SD卡信息。

SD卡寄存器列表

在这里插入图片描述

SDIO总线

SDIO总线拓扑

SD卡与SDIO接口示意图

在这里插入图片描述

推荐一个单独SD总线应该连接一个单独的SD卡

SD卡使用9-pin接口通信,其中3根电源线、1根时钟线、1根命令线和4根数据线,具体如下:

• CLK:时钟线,由SDIO主机产生,即由STM32控制器输出;

• CMD:命令控制线,SDIO主机通过该线发送命令控制SD卡,如果命令要求SD卡提供应答,SD卡也是通过该线传输应答信息;

• D0-3:数据线,传输读写数据;SD卡可将D0拉低表示忙状态;

• VDD、VSS1、VSS2:电源和地信号;

SDIO总线

SDIO不管是从主机控制器向SD卡传输,还是SD卡向主机控制器传输都只以CLK时钟线的上升沿为有效。

在这里插入图片描述

开始通讯的时候要发送一个起始位"0",最后由一个停止位"1"停止。

SD通讯一般是主机发送一个命令,从设备接收到命令后做出响应(有些命令没有响应可能)如果有数据传输,DATA线也需要用

SDIO总线协议

在这里插入图片描述

补充:

  • SD数据是以块(Black)形式传输的,SDHC卡数据块长度一般为512字节
  • 数据块需要CRC位来保证数据传输成功。
  • CRC位由SD卡系统硬件生成。
  • STM32控制器可以控制使用单线或4线传输,本开发板(野火指南者)设计使用4线传输

协议:

SD数据传输支持单块和多块读写,它们分别对应不同的操作命令,多块写入还需要使用命令来停止整个写入操作。

数据写入前需要检测SD卡忙状态,因为SD卡在接收到数据后编程到存储区过程需要一定操作时间。SD卡忙状态通过把D0线拉低表示。

常规数据传输

在这里插入图片描述

使用4数据线传输时,每次传输4bit数据,每根数据线都必须有起始位、终止位以及CRC位,CRC位每根数据线都要分别检查,并把检查结果汇总然后在数据传输完后通过D0线反馈给主机

宽位数据包

在这里插入图片描述

对SD卡而言宽位数据包发送方式是针对SD卡SSR(SD状态)寄存器内容发送的,SSR寄存器总共有512bit,在主机发出ACMD13命令后SD卡将SSR寄存器内容通过DAT线发送给主机。

命令

命令格式

在这里插入图片描述

命令的类型

• 无响应广播命令 (bc),发送到所有卡,不返回任务响应;

• 带响应广播命令 (bcr),发送到所有卡,同时接收来自所有卡响应;

• 寻址命令 (ac),发送到选定卡,DAT 线无数据传输;

• 寻址数据传输命令 (adtc),发送到选定卡,DAT 线有数据传输。

在标准中定义了两种类型的通用命令:特定应用命令 (ACMD) 和常规命令 (GEN_CMD)。要使用 SD 卡制造商特定的 ACMD 命令如 ACMD6,需要在发送该命令之前发送 CMD55 命令,告知 SD 卡接下来的命令为特定应用命令。CMD55 命令只对紧接的第一个命令有效,SD 卡如果检测到 CMD55 之后的第一条命令为 ACMD 则执行其特定应用功能,如果检测发现不是 ACMD 命令,则执行标准命令。

命令集

在这里插入图片描述

七个响应类型:

  1. R1 Response (Normal Response): R1响应是最基本的响应,包含一个字节的状态位,用于指示命令是否成功执行。

  2. R1b Response (Normal with Busy): 类似于R1,但在命令执行完成后,卡会持续将忙位(busy)发送给主机,直到卡准备好执行下一个命令。

  3. R2 Response (CID or CSD Register Response): 包含两个字节的数据,用于读取CID(Card ID)或CSD(Card Specific Data)寄存器内容。

  4. R3 Response (OCR Register Response): 包含四个字节的数据,用于读取OCR(Operating Conditions Register)寄存器内容。

  5. R6 Response (Published RCA Response): 包含一个字节的状态位和一个字节的相对卡地址(RCA),用于获取卡的相对地址。

  6. R7 Response (Card Interface Condition Response): 包含一个字节的状态位和一个字节的回应信息,用于卡初始化阶段。

  7. Data Response (for Data Transfer Commands): 在读/写数据时,卡会响应数据响应,用于指示数据是否成功接收。

注意:

  • 短响应是 48bit长度,只有 R2 类型是长响应,其长度为 136bit。
  • 除了 R3 类型之外,其他响应都使用 CRC7 校验来校验,对于 R2 类型是使用 CID 和 CSD 寄存器内部 CRC7。

SD卡的操作模式

在这里插入图片描述

  1. 主 机 上 电 后, 所 有 卡 处 于 空 闲 状 态, 包 括 当 前 处 于 无 效 状 态 的 卡。
  2. 主 机 也 可 以 发 送GO_IDLE_STATE(CMD0) 让所有卡软复位从而进入空闲状态,但当前处于无效状态的卡并不会复位。
  3. 主机在开始与卡通信前,需要先确定双方在互相支持的电压范围内。SD 卡有一个电压支持范围,主机当前电压必须在该范围可能才能与卡正常通信。SEND_IF_COND(CMD8) 命令就是用于验证卡接口操作条件的 (主要是电压支持)。卡会根据命令的参数来检测操作条件匹配性,如果卡支持主机电压就产生响应,否则不响应。而主机则根据响应内容确定卡的电压匹配性。CMD8 是 SD卡标准 V2.0 版本才有的新命令,所以如果主机有接收到响应,可以判断卡为 V2.0 或更高版本SD 卡。
  4. SD_SEND_OP_COND(ACMD41) 命令可以识别或拒绝不匹配它的电压范围的卡。ACMD41 命令的 VDD 电压参数用于设置主机支持电压范围,卡响应会返回卡支持的电压范围。对于对 CMD8有响应的卡,把 ACMD41 命令的 HCS 位设置为 1,可以测试卡的容量类型,如果卡响应的 CCS 位为 1 说明为高容量 SD 卡,否则为标准卡。卡在响应 ACMD41 之后进入准备状态,不响应 ACMD41的卡为不可用卡,进入无效状态。ACMD41 是应用特定命令,发送该命令之前必须先发 CMD55。
  5. ALL_SEND_CID(CMD2) 用来控制所有卡返回它们的卡识别号 (CID),处于准备状态的卡在发送CID 之后就进入识别状态。
  6. 之后主机就发送 SEND_RELATIVE_ADDR(CMD3) 命令,让卡自己推荐一个相对地址 (RCA) 并响应命令。这个 RCA 是 16bit 地址,而 CID 是 128bit 地址,使用 RCA简化通信。卡在接收到 CMD3 并发出响应后就进入数据传输模式,并处于待机状态,主机在获取所有卡 RCA 之后也进入数据传输模式

数据传输模式

在这里插入图片描述

CMD7 用来选定和取消指定的卡,卡在待机状态下还不能进行数据通信,因为总线上可能有多个卡都是出于待机状态,必须选择一个 RCA 地址目标卡使其进入传输状态才可以进行数据通信。同时通过 CMD7 命令(选择卡命令)也可以让已经被选择的目标卡返回到待机状态。数据传输模式下的数据通信都是主机和目标卡之间通过寻址命令点对点进行的。卡处于传输状态下可以使用表 SD 部分命令描述 中面向块的读写以及擦除命令对卡进行数据读写、擦除。CMD12可以中断正在进行的数据通信,让卡返回到传输状态。CMD0 和 CMD15 会中止任何数据编程操作,返回卡识别模式,这可能导致卡数据被损坏。

STM32 的 SDIO 功能框图

在这里插入图片描述

时钟的配置:

SDIO 使用两个时钟信号,一个是 SDIO 适配器时钟 (SDIOCLK=HCLK=72MHz),另外一个是 AHB总线时钟的二分频 (HCLK/2,一般为 36MHz)。适配器寄存器和 FIFO 使用 AHB 总线一侧的时钟(HCLK/2),控制单元、命令通道和数据通道使用 SDIO 适配器一侧的时钟 (SDIOCLK)。

SDIO_CK 是 SDIO 接口与 SD 卡用于同步的时钟信号。它使用 SDIOCLK 作为 SDIO_CK 的时钟来源,可以通过设置 BYPASS 模式直接得到,这时 SDIO_CK = SDIOCLK=HCLK。若禁止 BYPASS 模式,可以通过配置时钟寄存器的 CLKDIV 位控制分频因子,即 SDIO_CK=SDIOCLK/(2+CLKDIV)= HCLK/(2+CLKDIV)。

配置时钟时要注意,SD 卡普遍要求 SDIO_CK 时钟频率不能超过 25MHz。STM32 控制器的 SDIO 是针对 MMC 卡和 SD 卡的主设备,所以预留有 8 根数据线,对于 SD 卡最多用四根数据线。SDIO 适配器是 SD 卡系统主机部分,是 STM32 控制器与 SD 卡数据通信中间设备。SDIO 适配器由五个单元组成,分别是控制单元、命令路径单元、数据路径单元、寄存器单元以及 FIFO。

内部适配器

在这里插入图片描述

控制单元

在这里插入图片描述

电源管理部件会在系统断电和上电阶段禁止 SD 卡总线输出信号。时钟管理部件控制 CLK 线时钟信号生成。一般使用 SDIOCLK 分频得到。

命令路径

命令路径控制命令发送,并接收卡的响应。

在这里插入图片描述

CPSM 状态机描述图

STM32 控制器以命令路径状态机 (CPSM) 来描述 SDIO适配器的状态变化,并加入了等待超时检测功能,以便退出永久等待的情况。

由stm32自己控制无需十分了解

在这里插入图片描述

数据路径

数据路径部件负责与 SD 卡相互数据传输

在这里插入图片描述

SD 卡系统数据传输状态转换参考图数据传输模式卡状态转换 ,SDIO 适配器以数据路径状态机(DPSM) 来描述 SDIO 适配器状态变化情况。并加入了等待超时检测功能,以便退出永久等待情况。发送数据时,DPSM 处于等待发送 (Wait_S) 状态,如果数据 FIFO 不为空,DPSM 变成发送状态并且数据路径部件启动向卡发送数据。接收数据时,DPSM 处于等待接收状态,当 DPSM 收到起始位时变成接收状态,并且数据路径部件开始从卡接收数据。

DPSM 状态机

在这里插入图片描述

数据 FIFO

数据 FIFO(先进先出) 部件是一个数据缓冲器,带发送和接收单元。控制器的 FIFO 包含宽度为32bit、深度为 32 字的数据缓冲器和发送/接收逻辑。其中 SDIO 状态寄存器 (SDIO_STA) 的 TXACT位用于指示当前正在发送数据,RXACT 位指示当前正在接收数据,这两个位不可能同时为 1。• 当 TXACT 为 1 时,可以通过 AHB 接口将数据写入到传输 FIFO。• 当 RXACT 为 1 时,接收 FIFO 存放从数据路径部件接收到的数据。根据 FIFO 空或满状态会把 SDIO_STA 寄存器位值 1,并可以产生中断和 DMA 请求。

SDIO结构体

SDIO初始化结构体

 typedef struct {uint32_t SDIO_ClockEdge; // 时钟沿uint32_t SDIO_ClockBypass; // 旁路时钟uint32_t SDIO_ClockPowerSave; // 节能模式uint32_t SDIO_BusWide; // 数据宽度uint32_t SDIO_HardwareFlowControl; // 硬件流控制uint8_t SDIO_ClockDiv; // 时钟分频} SDIO_InitTypeDef;

SDIO 命令初始化结构体

typedef struct {uint32_t SDIO_Argument; // 命令参数uint32_t SDIO_CmdIndex; // 命令号uint32_t SDIO_Response; // 响应类型uint32_t SDIO_Wait; // 等待使能uint32_t SDIO_CPSM; // 命令路径状态机} SDIO_CmdInitTypeDef;

SDIO 数据初始化结构体

typedef struct {uint32_t SDIO_DataTimeOut; // 数据传输超时uint32_t SDIO_DataLength; // 数据长度uint32_t SDIO_DataBlockSize; // 数据块大小uint32_t SDIO_TransferDir; // 数据传输方向uint32_t SDIO_TransferMode; // 数据传输模式uint32_t SDIO_DPSM; // 数据路径状态机} SDIO_DataInitTypeDef;

SD卡的读写实验

准备工作

#include "sdio/sdio_test.h"
#include "./led/bsp_led.h"
#include "./sdio/bsp_sdio_sdcard.h"
#include "./usart/bsp_usart.h"/* Private typedef -----------------------------------------------------------*/
typedef enum {FAILED = 0, PASSED = !FAILED} TestStatus;/* Private define ------------------------------------------------------------*/
#define BLOCK_SIZE            512 /* Block Size in Bytes */#define NUMBER_OF_BLOCKS      10  /* For Multi Blocks operation (Read/Write) */
#define MULTI_BUFFER_SIZE    (BLOCK_SIZE * NUMBER_OF_BLOCKS)/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
uint8_t Buffer_Block_Tx[BLOCK_SIZE], Buffer_Block_Rx[BLOCK_SIZE];
uint8_t Buffer_MultiBlock_Tx[MULTI_BUFFER_SIZE], Buffer_MultiBlock_Rx[MULTI_BUFFER_SIZE];
volatile TestStatus EraseStatus = FAILED, TransferStatus1 = FAILED, TransferStatus2 = FAILED;
SD_Error Status = SD_OK;/* Private function prototypes -----------------------------------------------*/
static void SD_EraseTest(void);
static void SD_SingleBlockTest(void);
void SD_MultiBlockTest(void);
static void Fill_Buffer(uint8_t *pBuffer, uint32_t BufferLength, uint32_t Offset);
static TestStatus Buffercmp(uint8_t* pBuffer1, uint8_t* pBuffer2, uint32_t BufferLength);
static TestStatus eBuffercmp(uint8_t* pBuffer, uint32_t BufferLength);/* Private functions---------------------------------------------------------*/

void SD_EraseTest(void)

void SD_EraseTest(void)
{  /*------------------- Block Erase ------------------------------------------*/if (Status == SD_OK){/* Erase NumberOfBlocks Blocks of WRITE_BL_LEN(512 Bytes) */Status = SD_Erase(0x00, (BLOCK_SIZE * NUMBER_OF_BLOCKS));}if (Status == SD_OK){Status = SD_ReadMultiBlocks(Buffer_MultiBlock_Rx, 0x00, BLOCK_SIZE, NUMBER_OF_BLOCKS);/* Check if the Transfer is finished */Status = SD_WaitReadOperation();/* Wait until end of DMA transfer */while(SD_GetStatus() != SD_TRANSFER_OK);}/* Check the correctness of erased blocks */if (Status == SD_OK){EraseStatus = eBuffercmp(Buffer_MultiBlock_Rx, MULTI_BUFFER_SIZE);}if(EraseStatus == PASSED){    LED_GREEN;printf("SD卡擦除测试成功!\n");}else{LED_BLUE;printf("SD卡擦除测试失败!\n");printf("温馨提示:部分SD卡不支持擦除测试,若SD卡能通过下面的single读写测试,即表示SD卡能够正常使用。\n");}
}
  1. 擦除操作:

    Status = SD_Erase(0x00, (BLOCK_SIZE * NUMBER_OF_BLOCKS));
    

    这里调用了SD_Erase函数,擦除了从地址0x00开始,总共 BLOCK_SIZE * NUMBER_OF_BLOCKS 大小的数据块。Status 用于存储函数执行的状态。

  2. 读取操作:

    Status = SD_ReadMultiBlocks(Buffer_MultiBlock_Rx, 0x00, BLOCK_SIZE, NUMBER_OF_BLOCKS);
    

    如果擦除操作成功,接下来进行读取操作。这里调用了SD_ReadMultiBlocks函数,从地址0x00开始读取 BLOCK_SIZE 大小的数据块,总共读取 NUMBER_OF_BLOCKS 个块。

  3. 等待读取操作完成:

    Status = SD_WaitReadOperation();
    

    等待读取操作完成,确保数据已被成功读取。

  4. 检查擦除的块的正确性:

    EraseStatus = eBuffercmp(Buffer_MultiBlock_Rx, MULTI_BUFFER_SIZE);
    

    通过比较读取的数据块与期望的擦除状态,判断擦除是否成功。

  5. 根据检查结果输出信息:

    if (EraseStatus == PASSED)
    {LED_GREEN;printf("SD卡擦除测试成功!\n");
    }
    else
    {LED_BLUE;printf("SD卡擦除测试失败!\n");printf("温馨提示:部分SD卡不支持擦除测试,若SD卡能通过下面的single读写测试,即表示SD卡能够正常使用。\n");
    }
    

    根据擦除测试的结果,点亮相应的LED并输出信息。

void SD_MultiBlockTest(void)


{  /*--------------- Multiple Block Read/Write ---------------------*//* Fill the buffer to send */Fill_Buffer(Buffer_MultiBlock_Tx, MULTI_BUFFER_SIZE, 0x0);if (Status == SD_OK){/* Write multiple block of many bytes on address 0 */Status = SD_WriteMultiBlocks(Buffer_MultiBlock_Tx, 0x00, BLOCK_SIZE, NUMBER_OF_BLOCKS);/* Check if the Transfer is finished */Status = SD_WaitWriteOperation();while(SD_GetStatus() != SD_TRANSFER_OK);}if (Status == SD_OK){/* Read block of many bytes from address 0 */Status = SD_ReadMultiBlocks(Buffer_MultiBlock_Rx, 0x00, BLOCK_SIZE, NUMBER_OF_BLOCKS);/* Check if the Transfer is finished */Status = SD_WaitReadOperation();while(SD_GetStatus() != SD_TRANSFER_OK);}/* Check the correctness of written data */if (Status == SD_OK){TransferStatus2 = Buffercmp(Buffer_MultiBlock_Tx, Buffer_MultiBlock_Rx, MULTI_BUFFER_SIZE);}if(TransferStatus2 == PASSED){LED_GREEN;printf("Multi block 测试成功!");}else{LED_RED;printf("Multi block 测试失败,请确保SD卡正确接入开发板,或换一张SD卡测试!");}
}

这是一个多块数据读写测试的C语言函数。让我解释一下主要的步骤:

  1. 填充发送缓冲区:

    Fill_Buffer(Buffer_MultiBlock_Tx, MULTI_BUFFER_SIZE, 0x0);
    

    这里调用了 Fill_Buffer 函数,用0x0填充了 Buffer_MultiBlock_Tx 缓冲区,该缓冲区用于发送数据。

  2. 多块数据写操作:

    Status = SD_WriteMultiBlocks(Buffer_MultiBlock_Tx, 0x00, BLOCK_SIZE, NUMBER_OF_BLOCKS);
    

    如果填充缓冲区成功,接下来进行多块数据写入操作。调用了 SD_WriteMultiBlocks 函数,将填充的数据写入从地址0x00开始的多个数据块。

  3. 等待写操作完成:

    Status = SD_WaitWriteOperation();
    while(SD_GetStatus() != SD_TRANSFER_OK);
    

    等待写入操作完成,确保数据已成功写入SD卡。

  4. 多块数据读操作:

    Status = SD_ReadMultiBlocks(Buffer_MultiBlock_Rx, 0x00, BLOCK_SIZE, NUMBER_OF_BLOCKS);
    

    如果写入操作成功,接下来进行多块数据读取操作。调用了 SD_ReadMultiBlocks 函数,从地址0x00开始读取多个数据块。

  5. 等待读操作完成:

    Status = SD_WaitReadOperation();
    while(SD_GetStatus() != SD_TRANSFER_OK);
    

    等待读取操作完成,确保数据已成功读取。

  6. 检查写入的数据的正确性:

    TransferStatus2 = Buffercmp(Buffer_MultiBlock_Tx, Buffer_MultiBlock_Rx, MULTI_BUFFER_SIZE);
    

    通过比较写入的数据和读取的数据,判断写入是否成功。

  7. 根据检查结果输出信息:

    if(TransferStatus2 == PASSED)
    {LED_GREEN;printf("Multi block 测试成功!");
    }
    else
    {LED_RED;printf("Multi block 测试失败,请确保SD卡正确接入开发板,或换一张SD卡测试!");
    }
    

    根据多块数据读写测试的结果,点亮相应的LED并输出信息。

这段代码主要用于测试SD卡的多块数据读写功能,并通过比较写入和读取的数据来验证操作的正确性。如果测试成功,LED变绿并输出成功信息,否则LED变红并输出失败信息。

比较函数和填充函数补充

/*** @brief  Compares two buffers.* @param  pBuffer1, pBuffer2: buffers to be compared.* @param  BufferLength: buffer's length* @retval PASSED: pBuffer1 identical to pBuffer2*         FAILED: pBuffer1 differs from pBuffer2*/
TestStatus Buffercmp(uint8_t* pBuffer1, uint8_t* pBuffer2, uint32_t BufferLength)
{while (BufferLength--){if (*pBuffer1 != *pBuffer2){return FAILED;}pBuffer1++;pBuffer2++;}return PASSED;
}/*** @brief  Fills buffer with user predefined data.* @param  pBuffer: pointer on the Buffer to fill* @param  BufferLength: size of the buffer to fill* @param  Offset: first value to fill on the Buffer* @retval None*/
void Fill_Buffer(uint8_t *pBuffer, uint32_t BufferLength, uint32_t Offset)
{uint16_t index = 0;/* Put in global buffer same values */for (index = 0; index < BufferLength; index++){pBuffer[index] = index + Offset;}
}/*** @brief  Checks if a buffer has all its values are equal to zero.* @param  pBuffer: buffer to be compared.* @param  BufferLength: buffer's length* @retval PASSED: pBuffer values are zero*         FAILED: At least one value from pBuffer buffer is different from zero.*/
TestStatus eBuffercmp(uint8_t* pBuffer, uint32_t BufferLength)
{while (BufferLength--){/* In some SD Cards the erased state is 0xFF, in others it's 0x00 */if ((*pBuffer != 0xFF) && (*pBuffer != 0x00)){return FAILED;}pBuffer++;}return PASSED;
}/*********************************************END OF FILE**********************/

测试程序

void SD_Test(void)
{LED_BLUE;/*------------------------------ SD Init ---------------------------------- *//* SD卡使用SDIO中断及DMA中断接收数据,中断服务程序位于bsp_sdio_sd.c文件尾*/if((Status = SD_Init()) != SD_OK){    LED_RED;printf("SD卡初始化失败,请确保SD卡已正确接入开发板,或换一张SD卡测试!\n");}else{printf("SD卡初始化成功!\n");		 }if(Status == SD_OK) {LED_BLUE;/*擦除测试*/SD_EraseTest();LED_BLUE;/*single block 读写测试*/SD_SingleBlockTest();//暂不支持直接多块读写,多块读写可用多个单块读写流程代替LED_BLUE;/*muti block 读写测试*/SD_MultiBlockTest();}
}

main.c

int main(void)
{									   /* 初始化LED灯 */LED_GPIO_Config();LED_BLUE;	/* 初始化独立按键 */Key_GPIO_Config();/*初始化USART1*/USART_Config();printf("\r\n欢迎使用野火  STM32 开发板。\r\n");	printf("在开始进行SD卡基本测试前,请给开发板插入32G以内的SD卡\r\n");			printf("本程序会对SD卡进行 非文件系统 方式读写,会删除SD卡的文件系统\r\n");		printf("实验后可通过电脑格式化或使用SD卡文件系统的例程恢复SD卡文件系统\r\n");		printf("\r\n 但sd卡内的原文件不可恢复,实验前务必备份SD卡内的原文件!!!\r\n");		printf("\r\n 若已确认,请按开发板的KEY1按键,开始SD卡测试实验....\r\n");	/* Infinite loop */while (1){	/*按下按键开始进行SD卡读写实验,会损坏SD卡原文件*/if(	Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON){printf("\r\n开始进行SD卡读写实验\r\n");	SD_Test();			}} 

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

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

相关文章

SQL注入实战:二阶注入

一、二阶注入的原理 1、二阶注入也称为SOL二次注入。 2、二次注入漏洞是一种在Web应用程序中广泛存在的安全漏洞形式:相对于一次注入漏洞而言&#xff0c;二次注入漏洞更难以被发现&#xff0c;但是它却具有与一次注入攻击漏洞相同的攻击威力。 3、简单的说&#xff0c;二次…

3D应用开发工具HOOPS引领数字化工厂浪潮:制造业转型的关键角色!

随着科技的迅猛发展&#xff0c;制造业正经历着数字化转型的浪潮。在这一变革的前沿&#xff0c;Tech Soft 3D 的 HOOPS技术正扮演着关键的角色。 本文将深入研究HOOPS技术如何在数字化工作流程中发挥作用&#xff0c;以及它是如何引领制造业朝着更高效、智能的未来迈进的。 …

NeRF:神经辐射场复杂场景的新视图合成技术

NeRF&#xff1a;神经辐射场复杂场景的新视图合成技术 NeRF&#xff1a;神经辐射场复杂场景的新视图合成技术项目背景与意义如何运行&#xff1f;快速开始更多数据集 预训练模型方法与实现结语服务 NeRF&#xff1a;神经辐射场复杂场景的新视图合成技术 在计算机视觉领域&…

vue项目如何打包,java项目如何打包

目录 vue项目如何打包 java项目如何打jar包 使用Maven打包为JAR&#xff08;方式一&#xff09;视图&#xff1a; 先双击clean再双击package即可打包 使用Maven打包为JAR&#xff08;方式二&#xff09;命令&#xff1a; 1、确保你已经安装了Maven&#xff0c;并且配置了相应…

美赛提交流程与注意事项详细介绍

美赛提交流程 01 美赛选题步骤选题第一步&#xff1a;选题第二步&#xff1a;选题第三步: 02 论文提交邮箱登录提交论文发送邮箱查询进度 03 美赛提交注意事项04 题型分布/获奖技巧资料获取 内含获奖技巧、提交步骤等超多干货&#xff01; 01 美赛选题步骤 选题第一步&#xff…

kafka summary

最近整体梳理之前用到的一些东西&#xff0c;回顾Kafka的时候好多东西都忘记了&#xff0c;把一些自己记的比较模糊并且感觉有用的东西整理一遍并且记忆一遍&#xff0c;仅用于记录以备后续回顾 Kafka的哪些场景中使用了零拷贝 生产者发送消息&#xff1a;在 Kafka 生产者发送…

仅使用 Python 创建的 Web 应用程序(前端版本)第09章_购物车

在本章中,我们将实现购物车页面。 完成后的图像如下。 创建过程与之前相同,如下。 No分类内容1Model创建继承BaseDataModel的数据类Cart、CartItem2Service创建一个 CartAPIClient3Page定义PageId并创建继承自BasePage的页面类4Application将页面 ID 和页面类对添加到 Multi…

Spring Boot 中的外部化配置

Spring Boot 中的外部化配置 一、配置文件基础1.配置文件格式&#xff08;1&#xff09;YAML 基本语法规则&#xff08;2&#xff09;YAML 支持三种数据结构 2.application 文件3.application.properties 配置文件4.application.yml 配置文件5.Environment6.组织多文件7.多环境…

Soul CEO张璐积极履行反诈责任,倡导共建安全网络

近期,备受期待的反诈电影《鹦鹉杀》热映,深入剖析杀猪盘这一网络诈骗行为。为协助更多人增强反诈意识,备受欢迎的社交应用Soul App积极响应,在Soul CEO张璐的带领下,邀请电影中的演员和平台的反诈中心共同参与反诈宣传。此外,一旦用户在平台搜索“诈骗”、“杀猪盘”、“鹦鹉杀…

《WebKit 技术内幕》学习之十五(4):Web前端的未来

4 Cordova项目 Cordova是一个开源项目&#xff0c;能够提供将Web网页打包成本地应用格式的可运行文件。读者可能对Cordova项目陌生&#xff0c;但是大家可能对它的前身非常熟悉&#xff0c;那就是PhoneGap项目&#xff0c;它后来被Adobe公司收购。 图15-4描述了Cordova的主要工…

protobuf消息定义和使用注意事项

如果涉及到多端通讯&#xff0c;定义的protobuf消息格式可能不一样&#xff0c;会导致出现各种问题&#xff0c;比如名称或者消息的先后顺序&#xff0c;或者每个消息的id顺序不一致&#xff0c;都有可能导致解析不出来&#xff0c;我这里总结一下。 message消息名称可以不一致…

Ubuntu20.4 Mono C# gtk 编程习练笔记(四)

连续实时绘图 图看上去不是很清晰&#xff0c;KAZAM录屏AVI尺寸80MB&#xff0c; 转换成gif后10MB, 按CSDN对GIF要求&#xff0c;把它剪裁缩小压缩成了上面的GIF&#xff0c;图像质量大不如原屏AVI&#xff0c;但应该能说明原意&#xff1a;随机数据随时间绘制在 gtk 的 drawin…

Windows Server 安装 Docker

一、简介 Docker 不是一个通用容器工具&#xff0c;它依赖运行的 Linux 内核环境。Docker 实质上是在运行的 Linux 服务器上制造了一个隔离的文件环境&#xff0c;所以它执行的效率几乎等同于所部署的 Linux 主机服务器性能。因此&#xff0c;Docker 必须部署在 Linux 内核系统…

Leetcode 1268 搜索推荐系统

题目信息 LeetoCode地址: 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 题目理解 这道题的题意不难理解&#xff0c;在我们使用搜索引擎的每一天都会遇到&#xff0c;不需要输入完整的关键词&#xff0c;哪怕仅仅只输入一个字&#xff0c;搜索引…

【ArcGIS微课1000例】0095:横向图例制作案例教程

文章目录 一、加载数据二、高程分级显示三、横向图例四、注意事项一、加载数据 为了便于直观演示,本实验加载一个栅格数据(配套实验数据包中的0095.rar)并进行分级显示,效果如下: 二、高程分级显示 双击dem数据图层,打开栅格数据的【图层属性】对话框,切换到【符号系统…

最长公共子串的问题(正常方法和矩阵法,动态规划)

题目&#xff1a; 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 &#xff0c;返回 0 。 一个字符串的 子序列 是指这样一个新的字符串&#xff1a;它是由原字符串在不改变字符的相对顺序的情况下删除某些字符…

Java通过模板替换实现excel的传参填写

以模板为例子 将上面$转义的内容替换即可 package com.gxuwz.zjh.util;import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import java.io.*; import java.util.HashMap; import java.util.Map; import java.io.IOException; impor…

这些SQL你练习过吗?(网友提供的SQL)

行转列SQL练习 题目 把图1转换成图2结果展示 图1 CREATE TABLE TEST_TB_GRADE (ID int(10) NOT NULL AUTO_INCREMENT,USER_NAME varchar(20) DEFAULT NULL,COURSE varchar(20) DEFAULT NULL,SCORE float DEFAULT 0,PRIMARY KEY (ID) )insert into TEST_TB_GRADE(USER_NAME, CO…

STM32单片机项目之多功能智能小车硬件设计

基于STM32单片机多功能智能小车功能说明&#xff1a; TFTLCD显示按键LVGL&#xff08;菜单、小车工作模式选择、设置&#xff09;手机蓝牙遥控模式射频手柄遥控模式5路红外寻迹模式超声波避障模式语音播报低功耗控制 硬件原理图设计 单片机最小系统&#xff1a; 由于要使用…

滴滴开源小程序框架 Mpx 新特性:局部运行时能力增强

Mpx 是滴滴开源的一款增强型跨端小程序框架&#xff0c;自 2018 年立项开源以来如今已经进入第六个年头&#xff0c;在这六年间&#xff0c;Mpx 根植于业务&#xff0c;与业务共同成长&#xff0c;针对小程序业务开发中遇到的各类痛点问题提出了解决方案&#xff0c;并在滴滴内…