硬件连接与配置
STM32与TF卡的硬件连接需确保电平匹配和信号完整性:
SCK(时钟)接STM32的SPI_SCK引脚
MOSI(主出从入)接SPI_MOSI
MISO(主入从出)接SPI_MISO
CS(片选)接任意GPIO或SPI_NSS引脚
为提高稳定性,建议在MOSI、SCK等信号线上加10kΩ上拉电阻,并保证走线短而直,远离干扰源。
软件实现流程
SPI初始化 配置SPI为主模式,时钟极性(CPOL)为低,相位(CPHA)为第一边沿采样(即模式0),波特率分频适中(如64分频),确保与TF卡兼容。
TF卡初始化
上电后发送至少74个时钟脉冲,然后发送CMD0(复位命令),使TF卡进入SPI模式;接着发送CMD8、ACMD41等命令完成初始化,直到卡返回就绪状态。
读写操作
使用CMD17/CMD18进行单/多扇区读取 使用CMD24/CMD25进行单/多扇区写入 每个扇区大小通常为512字节,必须对齐操作
错误处理与优化 增加超时重试机制,轮询响应状态(如0x00表示成功),并在写入后等待非忙状态。可结合DMA提升传输效率,减少CPU占用。
文件系统支持
#include "spi_tfcard.h"
static SPI_HandleTypeDef *tf_spi_handle;
static uint8_t tf_card_type;
/**
* @brief 向TF卡发送一个字节
* @param data: 要发送的数据
* @retval 接收到的数据
*/
static uint8_t TF_SPI_ReadWrite_Byte(uint8_t data)
{
uint8_t rx_data;
HAL_SPI_TransmitReceive(tf_spi_handle, &data, &rx_data, 1, 100);
return rx_data;
}
/**
* @brief 向TF卡发送多个字节
* @param data: 要发送的数据指针
* @param size: 数据大小
* @retval 无
*/
static void TF_SPI_ReadWrite_Buffer(uint8_t *tx_data, uint8_t *rx_data, uint16_t size)
{
HAL_SPI_TransmitReceive(tf_spi_handle, tx_data, rx_data, size, 1000);
}
/**
* @brief TF卡片选使能
* @retval 无
*/
static void TF_CS_Enable(void)
{
TF_CS_LOW();
// 延时等待
TF_SPI_ReadWrite_Byte(0xFF);
}
/**
* @brief TF卡片选失能
* @retval 无
*/
static void TF_CS_Disable(void)
{
TF_CS_HIGH();
// 延时等待
TF_SPI_ReadWrite_Byte(0xFF);
}
/**
* @brief 向TF卡发送命令
* @param cmd: 命令
* @param arg: 参数
* @param crc: CRC校验值
* @retval 响应值
*/
static uint8_t TF_Send_Cmd(uint8_t cmd, uint32_t arg, uint8_t crc)
{
uint8_t response;
uint8_t cmd_buffer[6];
uint8_t retry = 0;
// 构造命令帧
cmd_buffer[0] = (cmd | 0x40);
cmd_buffer[1] = (uint8_t)(arg >> 24);
cmd_buffer[2] = (uint8_t)(arg >> 16);
cmd_buffer[3] = (uint8_t)(arg >> 8);
cmd_buffer[4] = (uint8_t)(arg);
cmd_buffer[5] = crc;
// 发送命令
TF_CS_Enable();
TF_SPI_ReadWrite_Buffer(cmd_buffer, &response, 6);
// 等待响应
while ((response = TF_SPI_ReadWrite_Byte(0xFF)) == 0xFF) {
retry++;
if (retry > 100) break;
}
return response;
}
/**
* @brief 等待TF卡准备就绪
* @retval 状态值
*/
uint8_t TF_Wait_Ready(void)
{
uint8_t response;
uint16_t retry = 0;
do {
response = TF_SPI_ReadWrite_Byte(0xFF);
retry++;
} while ((response != 0xFF) && (retry < 0xFFF));
return response;
}
/**
* @brief 接收数据包
* @param buffer: 数据缓冲区
* @param length: 数据长度
* @retval 无
*/
static void TF_Receive_Data_Packet(uint8_t *buffer, uint16_t length)
{
uint8_t token;
uint16_t i;
// 等待数据起始令牌
while ((token = TF_SPI_ReadWrite_Byte(0xFF)) == 0xFF);
if (token == 0xFE) {
// 接收数据
for (i = 0; i < length; i++) {
buffer[i] = TF_SPI_ReadWrite_Byte(0xFF);
}
// 读取CRC(忽略)
TF_SPI_ReadWrite_Byte(0xFF);
TF_SPI_ReadWrite_Byte(0xFF);
}
}
/**
* @brief 发送数据包
* @param buffer: 数据缓冲区
* @param length: 数据长度
* @param token: 起始令牌
* @retval 响应值
*/
static uint8_t TF_Send_Data_Packet(uint8_t *buffer, uint16_t length, uint8_t token)
{
uint8_t response;
uint16_t i;
// 发送起始令牌
TF_SPI_ReadWrite_Byte(token);
if (token != 0xFD) {
// 发送数据
for (i = 0; i < length; i++) {
TF_SPI_ReadWrite_Byte(buffer[i]);
}
// 发送CRC(忽略)
TF_SPI_ReadWrite_Byte(0xFF);
TF_SPI_ReadWrite_Byte(0xFF);
// 读取数据响应
response = TF_SPI_ReadWrite_Byte(0xFF);
response &= 0x1F;
if (response != 0x05) {
return TF_CARD_ERROR;
}
}
return TF_CARD_READY;
}
/**
* @brief 初始化TF卡
* @param hspi: SPI句柄
* @retval 卡类型
*/
uint8_t TF_Card_Init(SPI_HandleTypeDef *hspi)
{
uint8_t response;
uint8_t retry;
uint16_t i;
tf_spi_handle = hspi;
// 片选初始化为高电平
TF_CS_Disable();
// 发送至少74个时钟脉冲
for (i = 0; i < 10; i++) {
TF_SPI_ReadWrite_Byte(0xFF);
}
// 发送CMD0进入SPI模式
retry = 0;
do {
response = TF_Send_Cmd(CMD0, 0, 0x95);
retry++;
} while ((response != 0x01) && (retry < 100));
if (response != 0x01) {
return TF_CARD_ERROR;
}
// 检查是否支持V2.0
response = TF_Send_Cmd(CMD8, 0x1AA, 0x87);
if (response == 0x01) {
// 支持V2.0
// 读取剩余4字节响应
TF_SPI_ReadWrite_Byte(0xFF);
TF_SPI_ReadWrite_Byte(0xFF);
TF_SPI_ReadWrite_Byte(0xFF);
TF_SPI_ReadWrite_Byte(0xFF);
// 发送ACMD41激活卡
retry = 0;
do {
TF_Send_Cmd(CMD55, 0, 0);
response = TF_Send_Cmd(ACMD41, 0x40000000, 0);
retry++;
} while ((response != 0x00) && (retry < 2000));
if (retry >= 2000) {
return TF_CARD_ERROR;
}
// 检查是否为SDHC卡
response = TF_Send_Cmd(CMD58, 0, 0);
if (response == 0x00) {
// 读取OCR寄存器
if ((TF_SPI_ReadWrite_Byte(0xFF) & 0x40) == 0x40) {
tf_card_type = TF_CARD_TYPE_SDHC;
} else {
tf_card_type = TF_CARD_TYPE_SD_V2;
}
// 读取剩余3字节
TF_SPI_ReadWrite_Byte(0xFF);
TF_SPI_ReadWrite_Byte(0xFF);
TF_SPI_ReadWrite_Byte(0xFF);
}
} else {
// V1.0卡或MMC卡
TF_Send_Cmd(CMD55, 0, 0);
response = TF_Send_Cmd(ACMD41, 0, 0);
if (response <= 1) {
tf_card_type = TF_CARD_TYPE_SD_V1;
retry = 0;
do {
TF_Send_Cmd(CMD55, 0, 0);
response = TF_Send_Cmd(ACMD41, 0, 0);
retry++;
} while ((response != 0x00) && (retry < 2000));
} else {
tf_card_type = TF_CARD_TYPE_MMC;
retry = 0;
do {
response = TF_Send_Cmd(CMD1, 0, 0);
retry++;
} while ((response != 0x00) && (retry < 2000));
}
if (retry >= 2000) {
return TF_CARD_ERROR;
}
// 设置扇区大小为512字节
if (TF_Send_Cmd(CMD16, 512, 0) != 0x00) {
return TF_CARD_ERROR;
}
}
TF_CS_Disable();
TF_SPI_ReadWrite_Byte(0xFF);
return tf_card_type;
}
/**
* @brief 获取TF卡类型
* @retval 卡类型
*/
uint8_t TF_Get_Card_Type(void)
{
return tf_card_type;
}
/**
* @brief 读取扇区数据
* @param sector: 扇区号
* @param buffer: 数据缓冲区
* @retval 状态值
*/
uint8_t TF_Read_Sector(uint32_t sector, uint8_t *buffer)
{
uint8_t response;
// SDHC卡使用扇区地址,其他卡使用字节地址
if (tf_card_type != TF_CARD_TYPE_SDHC) {
sector *= 512;
}
response = TF_Send_Cmd(CMD17, sector, 0);
if (response != 0x00) {
TF_CS_Disable();
return TF_CARD_ERROR;
}
TF_Receive_Data_Packet(buffer, 512);
TF_CS_Disable();
TF_SPI_ReadWrite_Byte(0xFF);
return TF_CARD_READY;
}
/**
* @brief 写入扇区数据
* @param sector: 扇区号
* @param buffer: 数据缓冲区
* @retval 状态值
*/
uint8_t TF_Write_Sector(uint32_t sector, uint8_t *buffer)
{
uint8_t response;
// SDHC卡使用扇区地址,其他卡使用字节地址
if (tf_card_type != TF_CARD_TYPE_SDHC) {
sector *= 512;
}
response = TF_Send_Cmd(CMD24, sector, 0);
if (response != 0x00) {
TF_CS_Disable();
return TF_CARD_ERROR;
}
response = TF_Send_Data_Packet(buffer, 512, 0xFE);
if (response != TF_CARD_READY) {
TF_CS_Disable();
return TF_CARD_ERROR;
}
// 等待写入完成
TF_Wait_Ready();
TF_CS_Disable();
TF_SPI_ReadWrite_Byte(0xFF);
return TF_CARD_READY;
}
我要赚赏金
