了解STM32F103ZET是高容量多管脚的芯片

了解SD读写线路图

了解SD的基地址

阅读STM32F10xx英文参考 SDIO那章,我们编写代码边看文档解析

建工程,打开包含所有包括外设库函数的样本工程,然后留下如下的外设

  • 官方给的E:\2018-9-3\stm32-奋斗者\STM32 官方库3.5版本\stm32f10x_stdperiph_lib35\STM32F10x_StdPeriph_Lib_V3.5.0\Utilities\STM32_EVAL\Common下的文件只是用于他们的测试版,因此需要修改stm32_eval_sdio_sd.h中的include,由原来的#include "stm32_eval.h"改为#include "stm32f10x.h"



在stm32_eval_sdio_sd.c(我改名为bsp_sdio_sdcard.c)添加新的功能函数

  • 宏定义:sd外设地址(stm32_eval_sdio_sd.h)
/** @defgroup STM32_EVAL_SDIO_SD_Exported_Constants
* @{
*/ /*宏定义*/
#define SDIO_FIFO_ADDRESS ((uint32_t)0x40018080) //SDIO_FIOF地址=SDIO地址+0x80至 sdio地址+0xfc
/**
* @brief SDIO Intialization Frequency (400KHz max)
*/
#define SDIO_INIT_CLK_DIV ((uint8_t)0xB2)
/**
* @brief SDIO Data Transfer Frequency (25MHz max)
*/
/*!< SDIOCLK = HCLK, SDIO_CK = HCLK/(2 + SDIO_TRANSFER_CLK_DIV) */
#define SDIO_TRANSFER_CLK_DIV ((uint8_t)0x01)
  • GPIO 初始化
static void  GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
//使能gpio时钟,判断APB还是AHB,看System architecture图(PDF搜)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOC,ENABLE);
//配置pc8,pc9,pc10,pc11,pc12为D0,D0,D2,D3,D4,CLK,看电路线路图
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 |GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF_PP;
GPIO_Init(GPIOC,&GPIO_InitStructure)
//配置PD2 CMD引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_Init(GPIOD,&GPIO_InitStructure);
//使能SDIO AHB时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_SDIO,ENABLE);
//使能DMA
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2,ENABLE);
}
  • DMA 配置(为啥选择DMA2的channel4,DMA2的选择是看System architecture,channel4看DMA2 request mapping)
/**
配置好dma2,一发现有中断,就自动传输 Rx
**/
void SD_DMA_RxConfig(uint32_t *BufferDST, uint32_t BufferSize)
{
DMA_InitTypeDef DMA_InitStructure;
//清除标志位
DMA_ClearFlag(DMA2_FLAG_TC4 | DMA2_FLAG_TE4 |DMA2_FLAG_HT4 | DMA2_FLAG_GL4);
//禁止DMA
DMA_Cmd(DMA2_Channel4,DISABLE); //传输配置
//外设地址,fifo
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SDIO_FIFO_ADDRESS;
//目标地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)BufferDST;
//传输方向
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
//把字转为字节
DMA_InitStructure.DMA_BufferSize = BufferSize / 4;
//存储地址自增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
//不循环 When circular mode is activated, the number of data to be transferred is automatically reloaded with the initial value programmed during the channel configuration phase, and the DMA requests continue to be served.
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
//外设数据大小为字, 32 位
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
//存储数据大小为字, 32 位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
//通道优先级高
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
//外设地址不自增
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
//非 存储器至存储器模式 The DMA channels can also work without being triggered by a request from a peripheral. This mode is called Memory to Memory mode.
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA4_Channel4,&DMA_InitStructure);
/*!< 使能 DMA 通道 */
DMA_Cmd(DMA2_Channel4, ENABLE);
}
/**
配置好dma2,一发现有中断,就自动传输 Tx
**/
void SD_DMA_TxConfig(uint32_t *BufferSRC, uint32_t BufferSize)
{ DMA_InitTypeDef DMA_InitStructure; DMA_ClearFlag(DMA2_FLAG_TC4 | DMA2_FLAG_TE4 | DMA2_FLAG_HT4 | DMA2_FLAG_GL4); /*!< DMA2 Channel4 disable */
DMA_Cmd(DMA2_Channel4, DISABLE); /*!< DMA2 Channel4 Config */
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SDIO_FIFO_ADDRESS;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)BufferSRC;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//外设为写入目标
DMA_InitStructure.DMA_BufferSize = BufferSize / 4;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址不自增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA2_Channel4, &DMA_InitStructure); /*!< DMA2 Channel4 enable */
DMA_Cmd(DMA2_Channel4, ENABLE);
}
  • 打开stm32_eval_sdio_sd.h文件中发现很多枚举定义,等

    - 枚举:SD_Error、 SDTransferState 和 SDCardState

    - 结构体定义: SD_CSD、 SD_CID、 SD_CardStatus 以及 SD_CardInfo

    - 宏定义:命令号定义、 SDIO 传输方式、 SD 卡插入状态以及 SD 卡类型定义。

接下来我们就开始根据 SD 卡识别过程和数据传输过程理解 SD 卡驱动函数代码。这部分代码内容也是非常庞大,不可能全部在文档中全部列出,对于部分函数只介绍其功能。

  • SD卡初始化

  • NVIC初始化
static void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure; /* Configure the NVIC Preemption Priority Bits */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
//SDIO的中断请求 配置好NVIC的中断控制器和中断来,判断谁的优先级高(假设启动多个中断)。先配NVIC,在配外部中断器来屏蔽--硬件或软件(事件或中断)
NVIC_InitStructure.NVIC_IRQChannel = SDIO_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_Init(&NVIC_InitStructure);
//SDIO_IRQ不需要外部中断/事件或软件中断/事件,因此不需要初始化EXIT控制器的全部寄存器,比如中断屏蔽寄存器、事件屏蔽寄存器,看图External interrupt/event controller block diagram
}
  • 初始化SD卡
/**
描述 :初始化SD卡,使卡处于就绪状态(准备传输数据)
*/
- 此函数原stm32_eval_sdio_sd.c有,不需添加,需要修改
SD_Error SD_Init(void)
{
/*重置SD_Error状态*/
SD_Error errorstatus = SD_OK; NVIC_Configuration(); /* SDIO 外设底层引脚初始化 */
GPIO_Configuration(); /*对SDIO的所有寄存器进行复位*/
SDIO_DeInit(); /*上电并进行卡识别流程,确认卡的操作电压 */
errorstatus = SD_PowerON(); /*如果上电,识别不成功,返回“响应超时”错误 */
if (errorstatus != SD_OK)
{
/*!< CMD Response TimeOut (wait for CMDSENT flag) */
return(errorstatus);
} /*卡识别成功,进行卡初始化 */
errorstatus = SD_InitializeCards(); if (errorstatus != SD_OK) //失败返回
{
/*!< CMD Response TimeOut (wait for CMDSENT flag) */
return(errorstatus);
} /*!< Configure the SDIO peripheral */ /*!< on STM32F2xx devices, SDIOCLK is fixed to 48MHz */
/*!< SDIOCLK = HCLK, SDIO_CK = HCLK/(2 + SDIO_TRANSFER_CLK_DIV) */
//重新配置 SDIO 外设,提高时钟频率,由卡识别模式的400khz提升到数据传输模式小于25Mhz
SDIO_InitStructure.SDIO_ClockDiv = SDIO_TRANSFER_CLK_DIV;
//上升沿采集数据
SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Rising;
// 不使能Bypass,使SDIO_CK经过SDIO_ClockDiv分频
SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_Disable;
//开启的话,总线空闲时关闭SD_CLK 时钟
SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable;
//暂时配置成lbit模式
SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_1b;
//硬件流。若开启,在FIFO不能进行发送和接受数据是,数据暂停
SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable; SDIO_Init(&SDIO_InitStructure); if (errorstatus == SD_OK)
{
/*----------------- Read CSD/CID MSD registers ------------------*/
//用来读取csd/cid寄存器
/*调用 SD_GetCardInfo 函数获取 SD 卡信息,它需要一个指向 SD_CardInfo 类型变
量地址的指针形参,这里赋值为 SDCardInfo 变量的地址。 SD 卡信息主要是 CID
和 CSD 寄存器内容,这两个寄存器内容在 SD_InitializeCards 函数中都完成读取过
程并将其分别存放在 CID_Tab 数组和 CSD_Tab 数组中,所以 SD_GetCardInfo 函
数只是简单的把这两个数组内容整合复制到 SDCardInfo 变量对应成员内。正确执
行 SD_GetCardInfo 函数后, SDCardInfo 变量就存放了 SD 卡的很多状态信息,这
在之后应用中使用频率是很高的。
*/
errorstatus = SD_GetCardInfo(&SDCardInfo);
} if (errorstatus == SD_OK)
{
/*----------------- Select Card --------------------------------*/
//通过cm7,rca选择要操作的卡
errorstatus = SD_SelectDeselect((uint32_t) (SDCardInfo.RCA << 16));
} if (errorstatus == SD_OK)
{
//提高读写,开启4bit模式
errorstatus = SD_EnableWideBusOperation(SDIO_BusWide_4b);
} return(errorstatus);
}
  • 调用 SD_PowerON 函数,它用于查询卡的工作电压和时钟控制配置,并返回SD_Error 类型错误,该函数是整个 SD 识别精髓,
  • 此函数原stm32_eval_sdio_sd.c有,不需添加
 //确保 SD 卡的工作电压和配置控制时钟
SD_Error SD_PowerON(void)
{
SD_Error errorstatus = SD_OK;
uint32_t response = 0, count = 0, validvoltage = 0;
uint32_t SDType = SD_STD_CAPACITY; /*!< Power ON Sequence -----------------------------------------------------*/
/*!< Configure the SDIO peripheral */
/*!< SDIOCLK = HCLK, SDIO_CK = HCLK/(2 + SDIO_INIT_CLK_DIV) */
/*!< on STM32F2xx devices, SDIOCLK is fixed to 48MHz */
/*!< SDIO_CK for initialization should not exceed 400 KHz */
SDIO_InitStructure.SDIO_ClockDiv = SDIO_INIT_CLK_DIV;
SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Rising;
SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_Disable;
SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable;
// 初始化的时候暂时把数据线配置成一根
SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_1b;
//禁止硬件流控制
SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable;
SDIO_Init(&SDIO_InitStructure); /*!< Set Power State to ON */
//开启外设电源
SDIO_SetPowerState(SDIO_PowerState_ON); /*!< Enable SDIO Clock */
//使能SDIO时钟
SDIO_ClockCmd(ENABLE); /*!< CMD0: GO_IDLE_STATE ---------------------------------------------------*/
/*!< No CMD response required */
//发送一系列命令。开始卡的识别流程
SDIO_CmdInitStructure.SDIO_Argument = 0x0;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_GO_IDLE_STATE;
//设置具体的返回类型,
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_No;
//SDIO是否开启或关闭等待中断
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
/* CPSM 在开始发送命令之前等待数据传输结束 */
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
//检测是否正确接收CM0 , CmdError 函数用于无需响应的命令发送
errorstatus = CmdError(); if (errorstatus != SD_OK)
{
/*!< CMD Response TimeOut (wait for CMDSENT flag) */
//响应超时
return(errorstatus);
} /*!< CMD8: SEND_IF_COND ----------------------------------------------------*/
/*!< Send CMD8 to verify SD card interface operating condition */
/*!< Argument: - [31:12]: Reserved (shall be set to '0')
- [11:8]: Supply Voltage (VHS) 0x1 (Range: 2.7-3.6 V)
- [7:0]: Check Pattern (recommended 0xAA) */
/*!< CMD Response: R7 */
//发送CMD8检查SD卡电压操作
//发送 CMD8 命令,检测 SD 卡支持的操作条件,主要就是电压匹配, CMD8 的响
//应类型是 R7,使用 CmdResp7Error 函数可获取得到 R7 响应结果,它是通过检测
//SDIO_STA 寄 存 器 相 关 位 完 成 的 , 并 具 有 等 待 超 时 检 测 功 能 。
SDIO_CmdInitStructure.SDIO_Argument = SD_CHECK_PATTERN;
SDIO_CmdInitStructure.SDIO_CmdIndex = SDIO_SEND_IF_COND;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
//检测是否正确接收 ,Checks for error conditions for R7 response. 搜索R7 (Card interface condition)
errorstatus = CmdResp7Error(); if (errorstatus == SD_OK)
{
CardType = SDIO_STD_CAPACITY_SD_CARD_V2_0; /*!< SD Card 2.0 */
SDType = SD_HIGH_CAPACITY;
}
else //无响应,说明1.x
{
/*!< CMD55 */
SDIO_CmdInitStructure.SDIO_Argument = 0x00;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_APP_CMD);
}
/*!< CMD55 */
//发送CMD55,用于检测sd卡还是mmc卡,或者不支持的卡
SDIO_CmdInitStructure.SDIO_Argument = 0x00;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure); //是否响应,没响应就是mmc或不支持的卡
errorstatus = CmdResp1Error(SD_CMD_APP_CMD); /*!< If errorstatus is Command TimeOut, it is a MMC card */
/*!< If errorstatus is SD_OK it is a SD card: SD card 2.0 (voltage range mismatch)
or SD card 1.x */
if (errorstatus == SD_OK) //响应cmd44.是sd卡,可能为1.x也可能2.x
{ //下面,循环发送sdio支持的电压范围,
/*!< SD CARD */
/*!< Send ACMD41 SD_APP_OP_COND with Argument 0x80100000 */
while ((!validvoltage) && (count < SD_MAX_VOLT_TRIAL))
{
// 在发送 ACMD 命令前都要先向卡发送 CMD55 ,CMD55用于指示下一条指令是应用指令
/*!< SEND CMD55 APP_CMD with RCA as 0 */
SDIO_CmdInitStructure.SDIO_Argument = 0x00;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure); errorstatus = CmdResp1Error(SD_CMD_APP_CMD); if (errorstatus != SD_OK)
{
return(errorstatus);
}
/* ACMD41 ,确定卡是不是 SDSC 还是 SDHC,返回R3(就是OCR寄存器),需要用CmdResp3Error返回状态,主要从OCR寄存器31位0或1来判断那种类型
* 命令参数由支持的电压范围及 HCS 位组成, HCS 位置一来区分卡是 SDSC 还是 SDHC
* 0:SDSC
* 1:SDHC
* 响应: R3,对应的是 OCR 寄存器
*/
/*使用 ACMD41 命令判断卡的具体类型。因为是 A 类命令,所以在发送 ACMD41
之前必须先发送 CMD55, CMD55 命令的响应类型的 R1。如果 CMD55 命令都没
有响应说明是 MMC 卡或不可用卡。在正确发送 CMD55 之后就可以发送
ACMD41,并根据响应判断卡类型, ACMD41 的响应号为 R3, CmdResp3Error 函
数用于检测命令正确发送并带有超时检测功能,但并不具备响应内容接收功能,
需要在判定命令正确发送之后调用 SDIO_GetResponse 函数才能获取响应的内容。
*/
SDIO_CmdInitStructure.SDIO_Argument = SD_VOLTAGE_WINDOW_SD | SDType;//0x80100000
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_APP_OP_COND;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure); errorstatus = CmdResp3Error();
if (errorstatus != SD_OK)
{
return(errorstatus);
}
/* 若卡需求电压在 SDIO 的供电电压范围内,会自动上电并标志 pwr_up 位
* 读取卡寄存器,卡状态
*/
response = SDIO_GetResponse(SDIO_RESP1);
/* 读取卡的 ocr 寄存器的 pwr_up 位,看是否已工作在正常电压 */
validvoltage = (((response >> 31) == 1) ? 1 : 0);
count++; //计算循环
}
//结束循环 // 循环检测超过一定次数还没上电
if (count >= SD_MAX_VOLT_TRIAL)
{
// SDIO 不支持 card 的供电电压
errorstatus = SD_INVALID_VOLTRANGE;
return(errorstatus);
} /*检查卡返回信息中的 HCS 位*/
/* 判断 ocr 中的 ccs 位 ,如果是 sdsc 卡则不执行下面的语句 */
if (response &= SD_HIGH_CAPACITY) //判断30位是否为1
{
CardType = SDIO_HIGH_CAPACITY_SD_CARD;
} }/*!< else MMC Card */ return(errorstatus);
}

到此,程序执行SD卡的SD模式流程图执行到如下图、

判断执行 SD_PowerON 函数无错误后,执行下面的 SD_InitializeCards 函数进行与 SD 卡相关的初始化,使得卡进入数据传输模式下的待机模式。

  • 此函数原stm32_eval_sdio_sd.c有,不需添加
 //描述 :初始化所有的卡或者单个卡进入就绪状态
SD_Error SD_InitializeCards(void)
{
SD_Error errorstatus = SD_OK;
uint16_t rca = 0x01; if (SDIO_GetPowerState() == SDIO_PowerState_OFF)
{
errorstatus = SD_REQUEST_NOT_APPLICABLE;
return(errorstatus);
}
//判断卡的类型
if (SDIO_SECURE_DIGITAL_IO_CARD != CardType)
{
/*!< Send CMD2 ALL_SEND_CID 响应: R2,对应 CID 寄存器*/
SDIO_CmdInitStructure.SDIO_Argument = 0x0;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_ALL_SEND_CID;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Long;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure); errorstatus = CmdResp2Error(); if (SD_OK != errorstatus)
{
return(errorstatus);
}
/* 将返回的 CID 信息存储起来 CID_Tab已经定义好了,不用我们自己,直接用 */
CID_Tab[0] = SDIO_GetRespon se(SDIO_RESP1);
CID_Tab[1] = SDIO_GetResponse(SDIO_RESP2);
CID_Tab[2] = SDIO_GetResponse(SDIO_RESP3);
CID_Tab[3] = SDIO_GetResponse(SDIO_RESP4);
}
if ((SDIO_STD_CAPACITY_SD_CARD_V1_1 == CardType) || (SDIO_STD_CAPACITY_SD_CARD_V2_0 == CardType) || (SDIO_SECURE_DIGITAL_IO_COMBO_CARD == CardType)
|| (SDIO_HIGH_CAPACITY_SD_CARD == CardType))
{
/*!< Send CMD3 SET_REL_ADDR with argument 0* 要求各个 SD 卡返回自身的 RCA 地址. */
/*!< SD Card publishes its RCA. */
SDIO_CmdInitStructure.SDIO_Argument = 0x00;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_REL_ADDR;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
/* 把接收到的卡相对地址存起来 */
errorstatus = CmdResp6Error(SD_CMD_SET_REL_ADDR, &rca); if (SD_OK != errorstatus)
{
return(errorstatus);
}
}
/*******************************************************************/
if (SDIO_SECURE_DIGITAL_IO_CARD != CardType)
{
RCA = rca; /*!< Send CMD9 SEND_CSD with argument as card's RCA
响应:R2 对应寄存器 CSD(Card-Specific Data)*/
SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)(rca << 16);
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SEND_CSD;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Long;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure); errorstatus = CmdResp2Error(); if (SD_OK != errorstatus)
{
return(errorstatus);
} CSD_Tab[0] = SDIO_GetResponse(SDIO_RESP1);
CSD_Tab[1] = SDIO_GetResponse(SDIO_RESP2);
CSD_Tab[2] = SDIO_GetResponse(SDIO_RESP3);
CSD_Tab[3] = SDIO_GetResponse(SDIO_RESP4);
}
/*全部卡初始化成功 */
errorstatus = SD_OK; /*!< All cards get intialized */ return(errorstatus);
}

;执行 SD_InitializeCards 函数无错误后 SD 卡就已经处于数据传输模式下的待机状态,退出 SD_InitializeCards 后会返回前面的 SD_Init 函数,执行接下来代码,以下是 SD_Init 函数的后续执行过程。执行之后,卡就从待机状态转变为传输模式,可以说数据传输已经是万事俱备了。

SD 卡数据操作:包括数据读取、数据写入以及存储区擦除。数据读取和写入都可以分为单块操作和多块操作。

  • 擦除函数
  • 此函数原stm32_eval_sdio_sd.c有,不需添加
SD_Error SD_Erase(uint32_t startaddr, uint32_t endaddr)
{
SD_Error errorstatus = SD_OK;
uint32_t delay = 0;
__IO uint32_t maxdelay = 0;
uint8_t cardstate = 0; /*!< Check if the card coomnd class supports erase command */
if (((CSD_Tab[1] >> 20) & SD_CCCC_ERASE) == 0)
{
errorstatus = SD_REQUEST_NOT_APPLICABLE;
return(errorstatus);
} maxdelay = 120000 / ((SDIO->CLKCR & 0xFF) + 2); if (SDIO_GetResponse(SDIO_RESP1) & SD_CARD_LOCKED) //卡已上锁
{
errorstatus = SD_LOCK_UNLOCK_FAILED;
return(errorstatus);
} if (CardType == SDIO_HIGH_CAPACITY_SD_CARD)
{
//在 sdhc 卡中,地址参数为块地址,每块 512 字节,而 sdsc 卡地址为字节地址
//所以若是 sdhc 卡要对地址/512 进行转换
startaddr /= 512;
endaddr /= 512;
} /*!< According to sd-card spec 1.0 ERASE_GROUP_START (CMD32) and erase_group_end(CMD33) */
if ((SDIO_STD_CAPACITY_SD_CARD_V1_1 == CardType) || (SDIO_STD_CAPACITY_SD_CARD_V2_0 == CardType) || (SDIO_HIGH_CAPACITY_SD_CARD == CardType))
{
/*!< Send CMD32 SD_ERASE_GRP_START with argument as addr */
SDIO_CmdInitStructure.SDIO_Argument = startaddr;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_ERASE_GRP_START;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; //R1
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure); errorstatus = CmdResp1Error(SD_CMD_SD_ERASE_GRP_START);
if (errorstatus != SD_OK)
{
return(errorstatus);
} /*!< Send CMD33 SD_ERASE_GRP_END with argument as addr */
SDIO_CmdInitStructure.SDIO_Argument = endaddr;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_ERASE_GRP_END;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure); errorstatus = CmdResp1Error(SD_CMD_SD_ERASE_GRP_END);
if (errorstatus != SD_OK)
{
return(errorstatus);
}
} /*!< Send CMD38 ERASE */
SDIO_CmdInitStructure.SDIO_Argument = 0;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_ERASE;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure); errorstatus = CmdResp1Error(SD_CMD_ERASE); if (errorstatus != SD_OK)
{
return(errorstatus);
} for (delay = 0; delay < maxdelay; delay++)
{} /*!< Wait till the card is in programming state */
errorstatus = IsCardProgramming(&cardstate); while ((errorstatus == SD_OK) && ((SD_CARD_PROGRAMMING == cardstate) || (SD_CARD_RECEIVING == cardstate)))
{
errorstatus = IsCardProgramming(&cardstate);
} return(errorstatus);
}

SD_WriteBlock 函数用于向指定的目标地址写入一个块的数据,它有三个形参,分别为指向待写入数据的首地址的指针变量、目标写入地址和块大小。块大小一般都设置为512 字节。 (函数不用自己添加,但需要修改)

SD_Error SD_WriteBlock(uint8_t *writebuff, uint64_t WriteAddr, uint16_t BlockSize)
{
SD_Error errorstatus = SD_OK; #if defined (SD_POLLING_MODE)
uint32_t bytestransferred = 0, count = 0, restwords = 0;
uint32_t *tempbuff = (uint32_t *)writebuff;
#endif TransferError = SD_OK;
TransferEnd = 0;
StopCondition = 0; SDIO->DCTRL = 0x0; if (CardType == SDIO_HIGH_CAPACITY_SD_CARD)
{
BlockSize = 512;
WriteAddr /= 512;
} /*-------------- add , 没有这一段容易卡死在DMA检测中 -------------------*/
/* Set Block Size for Card,cmd16,
* 若是sdsc卡,可以用来设置块大小,
* 若是sdhc卡,块大小为512字节,不受cmd16影响
*/
SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) BlockSize;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCKLEN;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure); errorstatus = CmdResp1Error(SD_CMD_SET_BLOCKLEN); if (SD_OK != errorstatus)
{
return(errorstatus);
}
/*********************************************************************************/ /*!< Send CMD24 WRITE_SINGLE_BLOCK */
SDIO_CmdInitStructure.SDIO_Argument = WriteAddr; //写入地址
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_WRITE_SINGLE_BLOCK;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; //r1
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure); errorstatus = CmdResp1Error(SD_CMD_WRITE_SINGLE_BLOCK); if (errorstatus != SD_OK)
{
return(errorstatus);
} //配置sdio的写数据寄存器
SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT;
SDIO_DataInitStructure.SDIO_DataLength = BlockSize;
SDIO_DataInitStructure.SDIO_DataBlockSize = (uint32_t) 9 << 4; //可用此参数代替SDIO_DataBlockSize_512b
SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToCard;//写数据,
SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_Block;
SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable; //开启数据通道状态机
SDIO_DataConfig(&SDIO_DataInitStructure); /*!< In case of single data block transfer no need of stop command at all */
#if defined (SD_POLLING_MODE) //普通模式
while (!(SDIO->STA & (SDIO_FLAG_DBCKEND | SDIO_FLAG_TXUNDERR | SDIO_FLAG_DCRCFAIL | SDIO_FLAG_DTIMEOUT | SDIO_FLAG_STBITERR)))
{
if (SDIO_GetFlagStatus(SDIO_FLAG_TXFIFOHE) != RESET)
{
if ((512 - bytestransferred) < 32)
{
restwords = ((512 - bytestransferred) % 4 == 0) ? ((512 - bytestransferred) / 4) : (( 512 - bytestransferred) / 4 + 1);
for (count = 0; count < restwords; count++, tempbuff++, bytestransferred += 4)
{
SDIO_WriteData(*tempbuff);
}
}
else
{
for (count = 0; count < 8; count++)
{
SDIO_WriteData(*(tempbuff + count));
}
tempbuff += 8;
bytestransferred += 32;
}
}
}
if (SDIO_GetFlagStatus(SDIO_FLAG_DTIMEOUT) != RESET)
{
SDIO_ClearFlag(SDIO_FLAG_DTIMEOUT);
errorstatus = SD_DATA_TIMEOUT;
return(errorstatus);
}
else if (SDIO_GetFlagStatus(SDIO_FLAG_DCRCFAIL) != RESET)
{
SDIO_ClearFlag(SDIO_FLAG_DCRCFAIL);
errorstatus = SD_DATA_CRC_FAIL;
return(errorstatus);
}
else if (SDIO_GetFlagStatus(SDIO_FLAG_TXUNDERR) != RESET)
{
SDIO_ClearFlag(SDIO_FLAG_TXUNDERR);
errorstatus = SD_TX_UNDERRUN;
return(errorstatus);
}
else if (SDIO_GetFlagStatus(SDIO_FLAG_STBITERR) != RESET)
{
SDIO_ClearFlag(SDIO_FLAG_STBITERR);
errorstatus = SD_START_BIT_ERR;
return(errorstatus);
}
#elif defined (SD_DMA_MODE) //dma模式
SDIO_ITConfig(SDIO_IT_DATAEND, ENABLE); //数据传输结束中断
SD_DMA_TxConfig((uint32_t *)writebuff, BlockSize); //配置dma,跟rx类似
SDIO_DMACmd(ENABLE); // 使能sdio的dma请求
#endif return(errorstatus);
}

SD_WaitWriteOperation 函数用于检测和等待数据写入完成,在调用数据写入函数之后一般都需要调用, SD_WaitWriteOperation 函数适用于单块及多块写入函数。


/**
* 上述代码调用库函数 SD_DMAEndOfTransferStatus 一直检测 DMA 的传输完成标志,
当 DMA 传输结束时,该函数会返回 SET 值。另外, while 循环中的判断条件使用的
TransferEnd 和 TransferError 是全局变量,它们会在 SDIO 的中断服务函数根据传输情况被
设置, 传输结束后,根据 TransferError 的值来确认是否正确传输,若不正确则直接返回错
误代码。 SD_WaitWriteOperation 函数最后是清除相关标志位并返回错误。由于这个函数里
的 while 循环的存在, 它会确保 DMA 的传输结束。
*/
SD_Error SD_WaitWriteOperation(void)
{
SD_Error errorstatus = SD_OK;
//等待dma是否传输
while ((SD_DMAEndOfTransferStatus() == RESET) && (TransferEnd == 0) && (TransferError == SD_OK))
{} if (TransferError != SD_OK)
{
return(TransferError);
} /*!< Clear all the static flags */
SDIO_ClearFlag(SDIO_STATIC_FLAGS); return(errorstatus);
}

SD_ReadBlock函数:

SD_Error SD_ReadBlock(uint8_t *readbuff, uint64_t ReadAddr, uint16_t BlockSize)
{
SD_Error errorstatus = SD_OK;
#if defined (SD_POLLING_MODE)
uint32_t count = 0, *tempbuff = (uint32_t *)readbuff;
#endif TransferError = SD_OK;
TransferEnd = 0; //传输结束标置位,在中断服务置1
StopCondition = 0; SDIO->DCTRL = 0x0; if (CardType == SDIO_HIGH_CAPACITY_SD_CARD)
{
BlockSize = 512;
ReadAddr /= 512;
}
/*******************add,没有这一段容易卡死在DMA检测中*************************************/
/* Set Block Size for Card,cmd16,
* 若是sdsc卡,可以用来设置块大小,
* 若是sdhc卡,块大小为512字节,不受cmd16影响
*/
SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) BlockSize;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCKLEN;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; //r1
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure); errorstatus = CmdResp1Error(SD_CMD_SET_BLOCKLEN); if (SD_OK != errorstatus)
{
return(errorstatus);
}
/*********************************************************************************/
SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT;
SDIO_DataInitStructure.SDIO_DataLength = BlockSize;
SDIO_DataInitStructure.SDIO_DataBlockSize = (uint32_t) 9 << 4;
SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToSDIO;
SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_Block;
SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable;
SDIO_DataConfig(&SDIO_DataInitStructure); /*!< Send CMD17 READ_SINGLE_BLOCK */
SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)ReadAddr;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_READ_SINGLE_BLOCK;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure); errorstatus = CmdResp1Error(SD_CMD_READ_SINGLE_BLOCK); if (errorstatus != SD_OK)
{
return(errorstatus);
} #if defined (SD_POLLING_MODE)
/*!< In case of single block transfer, no need of stop transfer at all.*/
/*!< Polling mode */
while (!(SDIO->STA &(SDIO_FLAG_RXOVERR | SDIO_FLAG_DCRCFAIL | SDIO_FLAG_DTIMEOUT | SDIO_FLAG_DBCKEND | SDIO_FLAG_STBITERR)))
{
if (SDIO_GetFlagStatus(SDIO_FLAG_RXFIFOHF) != RESET)
{
for (count = 0; count < 8; count++)
{
*(tempbuff + count) = SDIO_ReadData();
}
tempbuff += 8;
}
} if (SDIO_GetFlagStatus(SDIO_FLAG_DTIMEOUT) != RESET)
{
SDIO_ClearFlag(SDIO_FLAG_DTIMEOUT);
errorstatus = SD_DATA_TIMEOUT;
return(errorstatus);
}
else if (SDIO_GetFlagStatus(SDIO_FLAG_DCRCFAIL) != RESET)
{
SDIO_ClearFlag(SDIO_FLAG_DCRCFAIL);
errorstatus = SD_DATA_CRC_FAIL;
return(errorstatus);
}
else if (SDIO_GetFlagStatus(SDIO_FLAG_RXOVERR) != RESET)
{
SDIO_ClearFlag(SDIO_FLAG_RXOVERR);
errorstatus = SD_RX_OVERRUN;
return(errorstatus);
}
else if (SDIO_GetFlagStatus(SDIO_FLAG_STBITERR) != RESET)
{
SDIO_ClearFlag(SDIO_FLAG_STBITERR);
errorstatus = SD_START_BIT_ERR;
return(errorstatus);
}
while (SDIO_GetFlagStatus(SDIO_FLAG_RXDAVL) != RESET)
{
*tempbuff = SDIO_ReadData();
tempbuff++;
} /*!< Clear all the static flags */
SDIO_ClearFlag(SDIO_STATIC_FLAGS); #elif defined (SD_DMA_MODE)
SDIO_ITConfig(SDIO_IT_DATAEND, ENABLE);
SDIO_DMACmd(ENABLE);
SD_DMA_RxConfig((uint32_t *)readbuff, BlockSize);
#endif return(errorstatus);
}

其他读写操作函数雷同,不用修改。但需要把最后面4句话替换成:

到此,SD卡初始化完成了,以上只是介绍重要那部分的函数,其他有些细微的修改,先引用,请点击:SD读写测试

设置SDIO 中断服务函数(stm32f10x_it.c)

  // 在 SDIO_ITConfig()这个函数开启了 sdio 中断 ,
void SDIO_IRQHandler(void)
{
//SDIO中断相关处理
SD_ProcessIRQSrc(); //定义在bsp_sdio_sdcard.c }

/*
* 函数名:SD_ProcessIRQSrc
* 描述 :数据传输结束中断
* 输入 :无
* 输出 :SD错误类型
*/
SD_Error SD_ProcessIRQSrc(void)
{
if (StopCondition == 1) //发送读取、多块读写命令时置1
{
SDIO->ARG = 0x0; //命令参数寄存器
SDIO->CMD = 0x44C; // 命令寄存器: 0100 01 001100
// [7:6] [5:0]
// CPSMEN WAITRESP CMDINDEX
// 开启命令状态机 短响应 cmd12 STOP_ TRANSMISSION
TransferError = CmdResp1Error(SD_CMD_STOP_TRANSMISSION);
}
else
{
TransferError = SD_OK;
}
SDIO_ClearITPendingBit(SDIO_IT_DATAEND); //清中断
SDIO_ITConfig(SDIO_IT_DATAEND, DISABLE); //关闭sdio中断使能
TransferEnd = 1;
return(TransferError);
}

至此,我们已经介绍了 SD 卡初始化、 SD 卡数据操作的基础功能函数以及 SDIO 相关中断服务函数内容,利用这个 SDIO 驱动,可以编写一些简单的 SD 卡读写测试程序。

测试 SD 卡部分的函数是我们自己编写的,存放在 sdio_test.c 文件等。

最后附上我编写的程序代码,主要实现串口输入内容,然后保存到sd卡中。再读取出来,输出到串口那里。

点击下载:【sd卡读取

使用STM32F103ZET霸道主板实现SD卡的读写(非文件系统)的更多相关文章

  1. 【译】如何在 Android 5.0 上获取 SD卡 的读写权限

    因为最近项目需要,涉及到 SD卡 的读写操作,然而申请 <!-- 读写权限 --> <uses-permission android:name="android.permi ...

  2. SD卡spi读写流程

    SD卡spi读写流程 1.SD卡的命令格式: SD卡的指令由6字节(Byte)组成,如下: Byte1:0 1 x x x x x x(命令号,由指令标志定义,如CMD39为100111即16进制0x ...

  3. 快速解决设置Android 23.0以上版本对SD卡的读写权限无效的问题

    快速解决设置Android 23.0以上版本对SD卡的读写权限无效的问题 转 https://www.jb51.net/article/144939.htm 今天小编就为大家分享一篇快速解决设置And ...

  4. 基础学习总结(三)--文本、SD卡数据读写

    简单的文本数据写入文件不需要权限,读写SD卡在4.0版本前需要写权限.在4.0后需要读写权限 布局: <LinearLayout xmlns:android="http://schem ...

  5. 使用FreeRTOS在SD卡驱动使用非系统延时导致上电重启不工作的情况

    一.问题描述在一个使用FreeRTOS的工程中,只做了SD卡的驱动,由于RTOS使用了Systick,故非系统延时函数使用的是 DWT中的时钟周期(CYCCNT)计数功能,但是在SD卡驱动中使用了这个 ...

  6. 使用STM32F103ZET霸道主板实现LCD显示屏显示

    简单了解液晶显示屏 液晶显示屏LCD是靠背光LED发光,然后经过横竖透光,每个点电压可以改变光线的方向,总之能改变透光度0-100%,最后就是每个像素点对应红绿蓝RGB,RGB各自的亮度不同,组成的颜 ...

  7. 解决SD卡频繁读写问题 Anything-sync-daemon 映射linux目录到tmpfs并定时同步

    Anything-sync-daemon (asd) is a is a diminutive pseudo-daemon designed to manage target directories ...

  8. [笔记]SD卡相关资料

    ESD静电放电模块 我知道的flash分为两种NOR flash和NAND flash,NOR falsh容量一般为1~16M用于单片机代码存储,NAND flash最小的是8M最大的现在听说有90G ...

  9. Android中使用SQLiteOpenHelper管理SD卡中的数据库

    使用Android中自带的SQLiteOpenHelper可以完成数据库的创建与管理,但有两点局限: (1)数据库创建在内存卡中,大小受限,创建位置位于/data/data/应用程序名/databas ...

随机推荐

  1. ndk学习之c++语言基础复习----C++容器、类型转换、异常与文件流操作

    继续来复习C++,比较枯燥,但是这是扎实掌握NDK开发的必经之路,不容小觑. 容器: 容器,就是用来存放东西的盒子. 常用的数据结构包括:数组array, 链表list, 树tree, 栈stack, ...

  2. Java常用数学类和BigDecimal

    笔记: Math类 * java.lang.Math提供了一系列静态方法用于科学计算:其方法的参数和返回值类型一般为double型. * abs 绝对值 * acos,asin,atan,cos,si ...

  3. JAVA遇见HTML——JSP篇(JSP内置对象下)

    request.getSession() 网上资料解释: request只能存在于一次访问里 session对象的作用域为一次会话 session长驻在服务器内存里,session有id标识,一个se ...

  4. JAVA遇见HTML——JSP篇(2、JSP基础语法)

    <%@ page language="java" import="java.util.*" contentType="text/html; ch ...

  5. SQL的左连接与右链接

    1.准备工作 Oracle  外连接(OUTER JOIN)包括以下: 左外连接(左边的表不加限制) 右外连接(右边的表不加限制) 全外连接(左右两表都不加限制) 对应SQL:LEFT/RIGHT/F ...

  6. VueCropper 图片裁剪

    基于vue的图片裁剪vue-cropper 简小咖 关注  0.2 2018.12.12 15:42 字数 164 阅读 3900评论 1喜欢 3 vue-cropper官网http://xyxiao ...

  7. the schema version of 'microsoft.aspnet.mvc' is incompatible with version of nuget

    Nuget versioning issue with package restore http://stackoverflow.com/questions/12035976/nuget-versio ...

  8. bzoj 3551

    按照困难度升序排序Kruskal重构树这样一来一个点的子树中的所有困难值都小于改点的困难值对于每次询问倍增找出困难值最大且小于x的点该点的子树中的第k大就是询问的答案主席书维护区间k大 #includ ...

  9. (WA)BZOJ 4821: [Sdoi2017]相关分析

    二次联通门 : BZOJ 4821: [Sdoi2017]相关分析 2017.8.23 Updata 妈妈!!这道题卡我!!!就是不然我过!!!!! #include <cstdio> # ...

  10. 验证码的实现类ValidateCode

    package com.yujie.util; import javax.imageio.ImageIO;import java.awt.*;import java.awt.image.Buffere ...