C#使用CH341 SPI模块读写SD卡
SD卡相关CMD命令
public static byte CMD0 = ;//卡复位
public static byte CMD1 = ;
public static byte CMD9 = ;//命令9 ,读CSD数据
public static byte CMD10 = ;//命令10,读CID数据
public static byte CMD12 = ;//命令12,停止数据传输
public static byte CMD16 = ;//命令16,设置SectorSize 应返回0x00
public static byte CMD17 = ;//命令17,读sector
public static byte CMD18 = ;//命令18,读Multi sector
public static byte ACMD23 = ;//命令23,设置多sector写入前预先擦除N个block
public static byte CMD24 = ;//命令24,写sector
public static byte CMD25 = ;//命令25,写Multi sector
public static byte ACMD41 = ;//命令41,应返回0x00
public static byte CMD55 = ;//命令55,应返回0x01
public static byte CMD58 = ;//命令58,读OCR信息
public static byte CMD59 = ;//命令59,使能/禁止CRC,应返回0x00 public static byte SD_Type = ;
public static byte SD_TYPE_MMC = ;
public static byte SD_TYPE_V1 = ;
public static byte SD_TYPE_V2 = ;
public static byte SD_TYPE_V2HC = ;
//SD传输数据结束后是否释放总线宏定义
public static byte NO_RELEASE = ;
public static byte RELEASE = ;
//SD卡回应标记字
public static byte MSD_RESPONSE_NO_ERROR = 0x00;
public static byte MSD_IN_IDLE_STATE = 0x01;
public static byte MSD_ERASE_RESET = 0x02;
public static byte MSD_ILLEGAL_COMMAND = 0x04;
public static byte MSD_COM_CRC_ERROR = 0x08;
public static byte MSD_ERASE_SEQUENCE_ERROR = 0x10;
public static byte MSD_ADDRESS_ERROR = 0x20;
public static byte MSD_PARAMETER_ERROR = 0x40;
public static byte MSD_RESPONSE_FAILURE = 0xFF;
//数据写入回应字意义
public static byte MSD_DATA_OK = 0x05;
public static byte MSD_DATA_CRC_ERROR = 0x0B;
public static byte MSD_DATA_WRITE_ERROR = 0x0D;
public static byte MSD_DATA_OTHER_ERROR = 0xFF;
//把SD卡设置到挂起模式
//返回值:0,成功设置
// 1,设置失败
public static bool SD_Idle_Sta()
{
byte r1 = 0x0;
for (int i = ; i < 0xf00; i++) ;//纯延时,等待SD卡上电完成 CH341DLL.CH341SetStream(mIndex, m_iChipSelect);
byte[] byInit = new byte[];
pub_Func.memset(byInit, 0xFF, );
CH341DLL.CH341StreamSPI4(mIndex, m_iChipSelect, , byInit); for (int i = ; i < ; i++) //等待响应,或超时退出
{
r1 = SD_SendCommand(CMD0, , 0x95);
if (r1 == 0x01)
break;
}
if (r1 == 0x01)
return false;
else
return true;
}
/// <summary>
/// 向SD卡发送一个命令(结束是不失能片选,还有后续数据传来)
/// </summary>
/// <param name="cmd">命令</param>
/// <param name="arg">命令参数</param>
/// <param name="crc">crc校验值</param>
/// <returns></returns>
public static byte SD_SendCommand(byte cmd,int arg ,byte crc)
{
byte r1 = 0xFF;
CH341DLL.CH341SetStream(mIndex, m_iChipSelect & 0x00);
byte[] byInit = new byte[];
pub_Func.memset(byInit, 0xFF, );
CH341DLL.CH341StreamSPI4(mIndex, m_iChipSelect, , byInit); //高速写命令延时
CH341DLL.CH341SetStream(mIndex, m_iChipSelect & 0x80); //片选端置低,选中SD卡 byte[] data = new byte[];
data[] = (byte)((int)cmd | 0x40); //分别写入命令
data[] = (byte)(arg >> );
data[] = (byte)(arg >> );
data[] = (byte)(arg >> );
data[] = (byte)(arg);
data[] = crc;
CH341DLL.CH341StreamSPI4(mIndex, m_iChipSelect, , data); byte[] byteint = new byte[];
byteint[] = 0xFF;
for(int i=;i<;i++) //等待响应,或超时退出
{
CH341DLL.CH341StreamSPI4(mIndex, m_iChipSelect, , byteint);
if ((r1=byteint[]) != 0xFF)
{
r1 = byteint[];
break;
} } CH341DLL.CH341SetStream(mIndex, m_iChipSelect & 0x00); //关闭片选 byte[] byInit2 = new byte[];
pub_Func.memset(byInit2, 0xFF, );
CH341DLL.CH341StreamSPI4(mIndex, m_iChipSelect, , byInit2); //在总线上额外增加8个时钟,让SD卡完成剩下的工作 return r1;
}
SD卡初始化
public static bool SD_Init()
{
int retry;
byte r1 = 0xFF;
if (SD_Idle_Sta())
{
spiSD.pCurrentWin.showLog.AppendText("初始化失败!\n");
return false;
}
//-----------------SD卡复位到idle结束-----------------
CH341DLL.CH341SetStream(mIndex, m_iChipSelect & 0x80);
r1 = SD_SendCommand(, 0x1aa, 0x87); //获取卡片的SD版本信息
if (r1 == 0x05)
{
//设置卡类型为SDV1.0,如果后面检测到为MMC卡,再修改为MMC
SD_Type = SD_TYPE_V1;
//如果是V1.0卡,CMD8指令后没有后续数据
//片选置高,结束本次命令
CH341DLL.CH341SetStream(mIndex, m_iChipSelect & 0x00);
byte[] byInit = new byte[];
pub_Func.memset(byInit, 0xFF, );
CH341DLL.CH341StreamSPI4(mIndex, m_iChipSelect, , byInit); //在总线上额外增加8个时钟,让SD卡完成剩下的工作
//-----------------SD卡、MMC卡初始化开始-----------------
//发卡初始化指令CMD55+ACMD41
// 如果有应答,说明是SD卡,且初始化完成
// 没有回应,说明是MMC卡,额外进行相应初始化
retry = ;
do
{
//先发CMD55,应返回0x01;否则出错
r1 = SD_SendCommand(CMD55, , );
if (r1 == 0XFF)
{
spiSD.pCurrentWin.showLog.AppendText("初始化失败!\n");
return false;//只要不是0xff,就接着发送
}
//得到正确响应后,发ACMD41,应得到返回值0x00,否则重试200次
r1 = SD_SendCommand(ACMD41, , );
retry++;
} while ((r1 != 0x00) && (retry < ));
// 判断是超时还是得到正确回应
// 若有回应:是SD卡;没有回应:是MMC卡
//----------MMC卡额外初始化操作开始------------
if (retry == )
{
retry = ;
//发送MMC卡初始化命令(没有测试)
do
{
r1 = SD_SendCommand(, , );
retry++;
} while ((r1 != 0x00) && (retry < ));
if (retry == )
{
spiSD.pCurrentWin.showLog.AppendText("初始化失败!\n");
return false; //MMC卡初始化超时
}
//写入卡类型
SD_Type = SD_TYPE_MMC;
}
//----------MMC卡额外初始化操作结束------------ //禁止CRC校验
r1 = SD_SendCommand(CMD59, , 0x95);
if (r1 != 0x00)
{
spiSD.pCurrentWin.showLog.AppendText("初始化失败!\n");
return false; //命令错误,返回r1
}
//设置Sector Size
r1 = SD_SendCommand(CMD16, , 0x95);
if (r1 != 0x00)
{
spiSD.pCurrentWin.showLog.AppendText("初始化失败!\n");
return false;//命令错误,返回r1
}
//-----------------SD卡、MMC卡初始化结束-----------------
}
else if (r1 == 0x01)
{
byte[] buff = new byte[];
byte[] byInit = new byte[];
pub_Func.memset(buff, 0xFF, );
pub_Func.memset(byInit, 0xFF, );
// V2.0的卡,CMD8命令后会传回4字节的数据,要跳过再结束本命令
CH341DLL.CH341StreamSPI4(mIndex, m_iChipSelect, , buff);
CH341DLL.CH341SetStream(mIndex, m_iChipSelect & 0x00);
CH341DLL.CH341StreamSPI4(mIndex, m_iChipSelect, , byInit); //在总线上额外增加8个时钟,让SD卡完成剩下的工作
//V2.0的卡,CMD8命令后会传回4字节的数据,要跳过再结束本命令
//判断该卡是否支持2.7V-3.6V的电压范围
//if(buff[2]==0x01 && buff[3]==0xAA) //不判断,让其支持的卡更多
{
retry = ;
//发卡初始化指令CMD55+ACMD41
do
{
r1 = SD_SendCommand(CMD55, , );
if (r1 != 0x01)
{
spiSD.pCurrentWin.showLog.AppendText("初始化失败!\n");
return false;
}
r1 = SD_SendCommand(ACMD41, 0x40000000, );
if (retry > )
{
spiSD.pCurrentWin.showLog.AppendText("初始化失败!\n");
return false; //超时则返回r1状态
}
} while (r1 != );
//初始化指令发送完成,接下来获取OCR信息
//-----------鉴别SD2.0卡版本开始-----------
r1 = SD_SendCommand(CMD58, , );
if (r1 != 0x00)
{
CH341DLL.CH341SetStream(mIndex, m_iChipSelect & 0x00);//释放SD片选信号
return false; //如果命令没有返回正确应答,直接退出,返回应答
}//读OCR指令发出后,紧接着是4字节的OCR信息
CH341DLL.CH341StreamSPI4(mIndex, m_iChipSelect, , buff);
//OCR接收完成,片选置高
CH341DLL.CH341SetStream(mIndex, m_iChipSelect & 0x00);
CH341DLL.CH341StreamSPI4(mIndex, m_iChipSelect, , byInit);
//检查接收到的OCR中的bit30位(CCS),确定其为SD2.0还是SDHC
//如果CCS=1:SDHC CCS=0:SD2.0
if ((buff[] & 0x40) == 0x40)
{
SD_Type = SD_TYPE_V2HC; //检查CCS
spiSD.pCurrentWin.showLog.AppendText("初始化完成!\n");
spiSD.pCurrentWin.showLog.AppendText("SD卡类型:V2HC\n"); }
else
{
SD_Type = SD_TYPE_V2;
spiSD.pCurrentWin.showLog.AppendText("初始化完成!\n");
spiSD.pCurrentWin.showLog.AppendText("SD卡类型:V2\n");
}
//-----------鉴别SD2.0卡版本结束-----------
}
} return true;
}
向SD卡读取数据
public static bool ReadData(int addr ,ref byte[] data)//读取1个扇区数据
{
byte r1;
//int buff = count * 512;
data = new byte[];
//byte[] byInit = new byte[1];
//pub_Func.memset(byInit, 0xFF, 1);
if (SD_Type != SD_TYPE_V2HC)
{
addr <<= ;//如果不是SDHC卡
}
r1 = SD_SendCommand(CMD17, addr, );//发送读扇区命令
if (r1==0x01) return false; //应答不正确,直接返回
CH341DLL.CH341SetStream(mIndex, m_iChipSelect & 0x80);
if (SD_GetResponse(0xFE)== MSD_RESPONSE_FAILURE)//等待SD卡发回数据起始令牌0xFE
{
CH341DLL.CH341SetStream(mIndex, m_iChipSelect & 0x00); //关闭SD卡
return false;//读取失败
}
pub_Func.memset(data, 0xFF, );
CH341DLL.CH341StreamSPI4(mIndex, m_iChipSelect, , data);
byte[] byInit = new byte[];
pub_Func.memset(byInit, 0xFF, );
CH341DLL.CH341StreamSPI4(mIndex, m_iChipSelect, , byInit);//发送伪CRC码
CH341DLL.CH341SetStream(mIndex, m_iChipSelect & 0x00);//关闭SD卡
return true;
} public static bool ReadDatanum(int addr, ref byte[] data,int counts) //读取多个扇区数据
{
int buff = * counts;
data = new byte[buff];
for(int i=;i<counts;i++)
{
byte[] da1 = new byte[];
if (!ReadData(addr, ref da1))
return false;
pub_Func.memcpy(data, i * , da1, , );
addr++; }
return true;
}
向SD卡写入数据
public static bool SD_WriteMultiBlock( ref int sector,byte[] data,int count)
{
byte r1;
int sectornum = count / ;
if ((count % ) != )
sectornum++;
//byte[] da = new byte[512];
if (SD_Type != SD_TYPE_V2HC)
sector = sector << ;//如果不是SDHC,给定的是sector地址,将其转换成byte地址
if (SD_Type != SD_TYPE_MMC)
r1 = SD_SendCommand(ACMD23, count, 0x00);//如果目标卡不是MMC卡,启用ACMD23指令使能预擦除
r1 = SD_SendCommand(CMD25, sector, 0x00);//发多块写入指令
if (r1 != 0x00) return false; //应答不正确,直接返回
CH341DLL.CH341SetStream(mIndex, m_iChipSelect & 0x80);//开始准备数据传输
byte[] byInit = new byte[];
pub_Func.memset(byInit, 0xFF, );
CH341DLL.CH341StreamSPI4(mIndex, m_iChipSelect, , byInit);//先放3个空数据,等待SD卡准备好
//--------下面是N个sector写入的循环部分
for(int i=;i< sectornum;i++)
{
byInit = new byte[];
pub_Func.memset(byInit, 0xFC, );
CH341DLL.CH341StreamSPI4(mIndex, m_iChipSelect, , byInit);//放起始令牌0xFC 表明是多块写入
byte[] tda = new byte[];
for (int j = ; j < ; j++)
{
if ((j + i * ) < count)
{
tda[j] = data[j + i * ];
}
else
{
tda[j] = 0xFF;
}
}
CH341DLL.CH341StreamSPI4(mIndex, m_iChipSelect, , tda);
byInit = new byte[];
pub_Func.memset(byInit, 0xFF, );//发2个Byte的dummy CRC,第3个byte等待SD卡应答
CH341DLL.CH341StreamSPI4(mIndex, m_iChipSelect, , byInit);
/*
r1 = byInit[2];
if ((r1 & 0x1F) != 0x05)
{
CH341DLL.CH341SetStream(mIndex, m_iChipSelect & 0x00); //如果应答为报错,则带错误代码直接退出
return false;
}
*/
//等待SD卡写入完成
CH341DLL.CH341SetDelaymS(mIndex, );
if (!SD_WaitDataReady())
{
CH341DLL.CH341SetStream(mIndex, m_iChipSelect & 0x00); //等待SD卡写入完成超时,直接退出报错
return false;
} }
//发结束传输令牌0xFD
byInit = new byte[];
pub_Func.memset(byInit, 0xFD, );
CH341DLL.CH341StreamSPI4(mIndex, m_iChipSelect, , byInit);
/*
r1 = byInit[0];
if (r1 == 0x00)
{
return false;
}
if (SD_WaitDataReady()) //等待准备好
{
CH341DLL.CH341SetStream(mIndex, m_iChipSelect & 0x00);
return false;
}
*/
//写入完成,片选置1
CH341DLL.CH341SetStream(mIndex, m_iChipSelect & 0x00);
byInit = new byte[];
pub_Func.memset(byInit, 0xFF, );
CH341DLL.CH341StreamSPI4(mIndex, m_iChipSelect, , byInit); sector += sectornum; return true; }
//等待SD卡写入完成
public static bool SD_WaitDataReady()
{
byte r1 = MSD_DATA_OTHER_ERROR;
int retry = ;
//CH341DLL.CH341SetStream(mIndex, m_iChipSelect & 0x80);
do
{
byte[] byInit = new byte[];
pub_Func.memset(byInit, 0xFF, );
CH341DLL.CH341StreamSPI4(mIndex, m_iChipSelect, , byInit);
r1 = (byte)(byInit[] & 0X1F);//读到回应
if (retry == ) return false;
retry++;
switch (r1)
{
case 0x05://数据接收正确了
r1 = MSD_DATA_OK;
break;
case 0x0B: //CRC校验错误
return false;
case 0x0D://数据写入错误
return false;
default://未知错误
r1 = MSD_DATA_OTHER_ERROR;
break;
}
} while (r1 == MSD_DATA_OTHER_ERROR); //数据错误时一直等待 retry = ;
for(int i=;i<;i++)
{
byte[] byInit = new byte[];
pub_Func.memset(byInit, 0xFF, );
CH341DLL.CH341StreamSPI4(mIndex, m_iChipSelect, , byInit);
if (byInit[] != )//读到数据为0,则数据还未写完成
break;
if (i == )
return false;
}
return true;//成功了
}
GetResponse
public static byte SD_GetResponse(byte Response)
{
int Count = 0xFFF;
byte[] byInit = new byte[];
pub_Func.memset(byInit, 0xFF, );
CH341DLL.CH341StreamSPI4(mIndex, m_iChipSelect, , byInit);
while ((byInit[] != Response) && (Count!=))
{
CH341DLL.CH341StreamSPI4(mIndex, m_iChipSelect, , byInit);
Count--;//等待得到准确的回应
}
if (Count == ) return MSD_RESPONSE_FAILURE;//得到回应失败
else return MSD_RESPONSE_NO_ERROR;//正确回应
}
源码地址:https://download.csdn.net/download/mm3515/11072088
C#使用CH341 SPI模块读写SD卡的更多相关文章
- Android 读写SD卡的文件
今天介绍一下Android 读写SD卡的文件,要读写SD卡上的文件,首先需要判断是否存在SD卡,方法: Environment.getExternalStorageState().equals(Env ...
- android学习笔记47——读写SD卡上的文件
读写SD卡上的文件 通过Context的openFileInput.openFileOutput来打开文件输入流.输出流时,程序打开的都是应用程序的数据文件夹里的文件,其存储的文件大小可能都比较有限- ...
- android 读写sd卡的权限设置
原文:android 读写sd卡的权限设置 在Android中,要模拟SD卡,要首先使用adb的mksdcard命令来建立SD卡的镜像,如何建立,大家上网查一下吧,应该很容易找到,这里不说这个问题. ...
- Android读写SD卡
SD卡的读写是我们在开发Android 应用程序过程中最常见的操作.下面介绍SD卡的读写操作方式: 1. 获取SD卡的根目录 String sdCardRoot = Environment.getEx ...
- android 读写SD卡文件
参考: http://www.oschina.net/code/snippet_176897_7336#11699 写文件: private void SavedToText(Context cont ...
- 以 SPI 方式获取 SD 卡容量(V2.0)
下面是 SD 卡 V2.0 协议的 CSD 寄存器内容,来自官方手册: 单片机如何确定当前的 SD 卡遵循 V2.0 协议 CSD 寄存器为 128 个位,即 16 个字节.通过检测 CSD 寄存器的 ...
- android读写SD卡封装的类
参考了网上的一些资源代码,FileUtils.java: package com.example.test; import java.io.BufferedInputStream; import ja ...
- SPI 方式初始化 SD 卡总流程图(V2.0)
- 今天发现郭的华为手机无法读写sd卡,找到了这个方法
https://bbs.csdn.net/topics/391985867?page=1 华为P9是android 6.0 的==在API23+以上也就是安卓6.0以上的,进行了权限管理不止要在And ...
随机推荐
- vue爬坑:把对象中的数据给了某个变量,改变一个对象的值,另一个对象也变化
今天做项目碰到了 一个坑,一个vue变量赋值给一个新的变量,对这个新的变量里的值做更改,vue的变量也变了.记录一下这个坑坑~~ 然后百度搜到了一个解决方案: 就是把变量先转成字符串,再把字符串转成对 ...
- c++ string 转double
#include <iostream>#include <sstream> //使用stringstream需要引入这个头文件using namespace std; Type ...
- 计算1~100之间,能被3整除但是不能被7整除的数的和(C语言)
#include<stdio.h> int main(agrc *agrv) { int n,i; int sum=0; scanf("%d",&n); for ...
- centos7:ftp上传文件
ftp> lcd /var/www/sss 上传文件的地址还一种上传方式ftp> passivePassive mode off.ftp> passivePassive mode ...
- Angular 2/4/5+ 重复点击菜单刷新界面
记一下,网上没找到方法 自己搞了好久 通过跳转到别的界面在跳回来的方式进行实现 //再次点击刷新界面 if (this.router.url == item.ur ...
- CSS效果:动态图标
HTML: <html lang="en"> <head> <meta charset="UTF-8"> <meta ...
- dos3章
FOR命令中有一些变量,他们的用法许多新手朋友还不太了解,今天给大家讲解他们的用法! 先把FOR的变量全部列出来: ~I - 删除任何引号("),扩展 %I %~f ...
- python基础—sys与os库
python可以用sys库打印环境变量或者查看当前文件的脚本路径,具体代码: import sysprint(sys.path[2])#打印环境变量print(sys.argv)#当前脚本路径 os库 ...
- JavaScript构造函数原理
1.var obj={} plainObject 对象字面量/对象直接量2.构造函数创建 1).系统自带的构造函数 Object() var obj=new Object(); 和 var obj = ...
- String为什么是final类型的
String的源码如下: public final class String implements Serializable, Comparable<String>, CharSequen ...