在运行ESP32SUPERMINI的SD卡例程时遇到报错,显示初始化不成功,解决方法,使用软件SPI,测试有效,参考代码如下:(后有软件SPISD卡读取文件)
以下是我的解决方案,如果不正确可能是我的资料提供有误,但总体思路是使用软件SPI替代硬件SPI,可以自己去尝试。
示例代码
//调用库及宏定义方面
//OLED相关
#include <U8g2lib.h>
#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif
U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ 3, /* data=*/ 2, /* reset=*/ U8X8_PIN_NONE); //Low spped I2C
//定义OLED屏接口
//micro SD相关
#include <mySD.h>
ext::File root;
//自设定函数
void printDirectory(ext::File dir, int numTabs) {
while(true) {
ext::File entry = dir.openNextFile();
if (! entry) {
break;
}
for (uint8_t i=0; i<numTabs; i++) {
Serial.print('\t'); // we'll have a nice indentation
}
// Print the name
Serial.print(entry.name());
/* 递归目录,否则打印文件大小 */
if (entry.isDirectory()) {
Serial.println("/");
printDirectory(entry, numTabs+1);
} else {
/* files have sizes, directories do not */
Serial.print("\t\t");
Serial.println(entry.size());
}
entry.close();
}
}
//全局变量设置
//设定每行字数变量
int Ln=0;//列数
int col=0;//行数
int page=0;//页数
int num=0;//字数
int by=32;
int i=0;
int j=0;
//设定汉字字符数组尝试
char ch[32]={};
char sram[9600]={};
//设置标志位以确定向后翻页还是向前翻页
int latest=0;//确定最新记录位
//初始化设定
void setup() {
Serial.begin(115200);//启动串口
//oled初始化
u8g2.begin();//u8g2库初始化
u8g2.setFont(u8g2_font_wqy12_t_gb2312b);//设置中文字符集
u8g2.enableUTF8Print(); //启用打印功能
//sd卡初始化
Serial.print("Initializing SD card...");//初始化中
if (!SD.begin(7, 6, 5, 4)) {Serial.println("initialization failed!");return;}
//SD卡接口初始化
Serial.println("initialization done.");//初始化完成
root = SD.open("/");
if (root) {printDirectory(root, 0);root.close();} else {Serial.println("error opening file");}
//验证能否打开文件
root = SD.open("TEXT.txt");//打开TEXT文件
//下一页按钮初始化 0 21
pinMode(0,OUTPUT);
pinMode(21,INPUT);
digitalWrite(0,HIGH);
//上一页按钮初始化 1 20
pinMode(1,OUTPUT);
pinMode(20,INPUT);
digitalWrite(1,HIGH);
}
void loop() {
if(digitalRead(21)==1){nextpage();}
if(digitalRead(20)==1){lastpage();}
if(num==1500){while(sram[i]){sram[i]=0;i++;}i=0;latest=0;num=0;}Serial.println(num);
//if(digitalRead(21==1)&&digitalRead(20)==1){delay(50);if(digitalRead(21==1)&&digitalRead(20)==1)j=100;}
//if(digitalRead(21==1)&&digitalRead(20)==1){delay(50);if(digitalRead(21==1)&&digitalRead(20)==1)while(j<100){nextpage();j++;if(num==1500){while(sram[i]){sram[i]=0;i++;}i=0;latest=0;num=0;}}j=0;}
u8g2.sendBuffer();
}
//下一页函数
void nextpage()
{
page++;
u8g2.clearBuffer();
//先判断是否到达最新进度
if(num>=latest)
{ //第一行
u8g2.setCursor(0, 12);
if(root){while(Ln<by){ sram[num]=root.read(); ch[Ln] =sram[num] ;u8g2.print(ch[Ln]);Ln++;num++;}}
Ln=0;
//第二行
u8g2.setCursor(0, 24);
if(root){while(Ln<by){ sram[num]=root.read(); ch[Ln] =sram[num] ;u8g2.print(ch[Ln]);Ln++;num++;}}
Ln=0;
//第三行
u8g2.setCursor(0, 36);
if(root){while(Ln<by){ sram[num]=root.read(); ch[Ln] =sram[num] ;u8g2.print(ch[Ln]);Ln++;num++;}}
Ln=0;
//第四行
u8g2.setCursor(0, 48);
if(root){while(Ln<by){ sram[num]=root.read(); ch[Ln] =sram[num] ;u8g2.print(ch[Ln]);Ln++;num++;}}
Ln=0;
//第五行
u8g2.setCursor(0, 60);
if(root){while(Ln<by-10){ sram[num]=root.read(); ch[Ln] =sram[num] ;u8g2.print(ch[Ln]);Ln++;num++;}}
Ln=0;
u8g2.setCursor(110, 60);
u8g2.print(page);
}
//如果进度不是最新的
if(num<latest)
{u8g2.setCursor(0, 12);
while(Ln<by){ ch[Ln] =sram[num] ;u8g2.print(ch[Ln]);Ln++;num++;}
Ln=0;
//第二行
u8g2.setCursor(0, 24);
while(Ln<by){ ch[Ln] =sram[num] ;u8g2.print(ch[Ln]);Ln++;num++;}
Ln=0;
//第三行
u8g2.setCursor(0, 36);
while(Ln<by){ ch[Ln] =sram[num] ;u8g2.print(ch[Ln]);Ln++;num++;}
Ln=0;
//第四行
u8g2.setCursor(0, 48);
while(Ln<by){ ch[Ln] =sram[num] ;u8g2.print(ch[Ln]);Ln++;num++;}
Ln=0;
//第五行
u8g2.setCursor(0, 60);
while(Ln<by-10){ ch[Ln] =sram[num] ;u8g2.print(ch[Ln]);Ln++;num++;}
}
u8g2.setCursor(110, 60);
u8g2.print(page);
Ln=0;if(num>=latest){latest=num;} u8g2.sendBuffer();
}
//上一页函数
void lastpage()
{
page--;
u8g2.clearBuffer();
if( num>=316 )
{
num=num-316;
}
//第一行
u8g2.setCursor(0, 12);
while(Ln<32){ ch[Ln] =sram[num] ;u8g2.print(ch[Ln]);Ln++;num++;}
Ln=0;
//第二行
u8g2.setCursor(0, 24);
while(Ln<32){ ch[Ln] =sram[num] ;u8g2.print(ch[Ln]);Serial.print(ch[Ln]);Ln++;num++;}
Ln=0;
//第三行
u8g2.setCursor(0, 36);
while(Ln<32){ ch[Ln] =sram[num] ;u8g2.print(ch[Ln]);Serial.print(ch[Ln]);Ln++;num++;}
Ln=0;
//第四行
u8g2.setCursor(0, 48);
while(Ln<32){ ch[Ln] =sram[num] ;u8g2.print(ch[Ln]);Serial.print(ch[Ln]);Ln++;num++;}
Ln=0;
//第五行
u8g2.setCursor(0, 60);
while(Ln<22){ ch[Ln] =sram[num] ;u8g2.print(ch[Ln]);Serial.print(ch[Ln]);Ln++;num++;}
Ln=0;
u8g2.setCursor(110, 60);
u8g2.print(page);
u8g2.sendBuffer();
}
mySD.cpp
/*
SD - a slightly more friendly wrapper for sdfatlib
This library aims to expose a subset of SD card functionality
in the form of a higher level "wrapper" object.
License: GNU General Public License V3
(Because sdfatlib is licensed with this.)
(C) Copyright 2010 SparkFun Electronics
This library provides four key benefits:
* Including `SD.h` automatically creates a global
`SD` object which can be interacted with in a similar
manner to other standard global objects like `Serial` and `Ethernet`.
* Boilerplate initialisation code is contained in one method named
`begin` and no further objects need to be created in order to access
the SD card.
* Calls to `open` can supply a full path name including parent
directories which simplifies interacting with files in subdirectories.
* Utility methods are provided to determine whether a file exists
and to create a directory heirarchy.
Note however that not all functionality provided by the underlying
sdfatlib library is exposed.
*/
/*
Implementation Notes
In order to handle multi-directory path traversal, functionality that
requires this ability is implemented as callback functions.
Individual methods call the `walkPath` function which performs the actual
directory traversal (swapping between two different directory/file handles
along the way) and at each level calls the supplied callback function.
Some types of functionality will take an action at each level (e.g. exists
or make directory) which others will only take an action at the bottom
level (e.g. open).
*/
#include "mySD.h"
// Used by `getNextPathComponent`
#define MAX_COMPONENT_LEN 12 // What is max length?
#define PATH_COMPONENT_BUFFER_LEN MAX_COMPONENT_LEN+1
bool getNextPathComponent(char *path, unsigned int *p_offset,
char *buffer) {
/*
Parse individual path components from a path.
e.g. after repeated calls '/foo/bar/baz' will be split
into 'foo', 'bar', 'baz'.
This is similar to `strtok()` but copies the component into the
supplied buffer rather than modifying the original string.
`buffer` needs to be PATH_COMPONENT_BUFFER_LEN in size.
`p_offset` needs to point to an integer of the offset at
which the previous path component finished.
Returns `true` if more components remain.
Returns `false` if this is the last component.
(This means path ended with 'foo' or 'foo/'.)
*/
// TODO: Have buffer local to this function, so we know it's the
// correct length?
int bufferOffset = 0;
int offset = *p_offset;
// Skip root or other separator
if (path[offset] == '/') {
offset++;
}
// Copy the next next path segment
while (bufferOffset < MAX_COMPONENT_LEN
&& (path[offset] != '/')
&& (path[offset] != '\0')) {
buffer[bufferOffset++] = path[offset++];
}
buffer[bufferOffset] = '\0';
// Skip trailing separator so we can determine if this
// is the last component in the path or not.
if (path[offset] == '/') {
offset++;
}
*p_offset = offset;
return (path[offset] != '\0');
}
boolean walkPath(char *filepath, SdFile& parentDir,
boolean (*callback)(SdFile& parentDir,
char *filePathComponent,
boolean isLastComponent,
void *object),
void *object = NULL) {
/*
When given a file path (and parent directory--normally root),
this function traverses the directories in the path and at each
level calls the supplied callback function while also providing
the supplied object for context if required.
e.g. given the path '/foo/bar/baz'
the callback would be called at the equivalent of
'/foo', '/foo/bar' and '/foo/bar/baz'.
The implementation swaps between two different directory/file
handles as it traverses the directories and does not use recursion
in an attempt to use memory efficiently.
If a callback wishes to stop the directory traversal it should
return false--in this case the function will stop the traversal,
tidy up and return false.
If a directory path doesn't exist at some point this function will
also return false and not subsequently call the callback.
If a directory path specified is complete, valid and the callback
did not indicate the traversal should be interrupted then this
function will return true.
*/
SdFile subfile1;
SdFile subfile2;
char buffer[PATH_COMPONENT_BUFFER_LEN];
unsigned int offset = 0;
SdFile *p_parent;
SdFile *p_child;
SdFile *p_tmp_sdfile;
p_child = &subfile1;
p_parent = &parentDir;
while (true) {
boolean moreComponents = getNextPathComponent(filepath, &offset, buffer);
boolean shouldContinue = callback((*p_parent), buffer, !moreComponents, object);
if (!shouldContinue) {
// TODO: Don't repeat this code?
// If it's one we've created then we
// don't need the parent handle anymore.
if (p_parent != &parentDir) {
(*p_parent).close();
}
return false;
}
if (!moreComponents) {
break;
}
boolean exists = (*p_child).open(*p_parent, buffer, F_RDONLY);
// If it's one we've created then we
// don't need the parent handle anymore.
if (p_parent != &parentDir) {
(*p_parent).close();
}
// Handle case when it doesn't exist and we can't continue...
if (exists) {
// We alternate between two file handles as we go down
// the path.
if (p_parent == &parentDir) {
p_parent = &subfile2;
}
p_tmp_sdfile = p_parent;
p_parent = p_child;
p_child = p_tmp_sdfile;
} else {
return false;
}
}
if (p_parent != &parentDir) {
(*p_parent).close(); // TODO: Return/ handle different?
}
return true;
}
/*
The callbacks used to implement various functionality follow.
Each callback is supplied with a parent directory handle,
character string with the name of the current file path component,
a flag indicating if this component is the last in the path and
a pointer to an arbitrary object used for context.
*/
boolean callback_pathExists(SdFile& parentDir, char *filePathComponent,
boolean isLastComponent, void *object) {
/*
Callback used to determine if a file/directory exists in parent
directory.
Returns true if file path exists.
*/
SdFile child;
boolean exists = child.open(parentDir, filePathComponent, F_RDONLY);
if (exists) {
child.close();
}
return exists;
}
boolean callback_makeDirPath(SdFile& parentDir, char *filePathComponent,
boolean isLastComponent, void *object) {
/*
Callback used to create a directory in the parent directory if
it does not already exist.
Returns true if a directory was created or it already existed.
*/
boolean result = false;
SdFile child;
result = callback_pathExists(parentDir, filePathComponent, isLastComponent, object);
if (!result) {
result = child.makeDir(parentDir, filePathComponent);
}
return result;
}
/*
boolean callback_openPath(SdFile& parentDir, char *filePathComponent,
boolean isLastComponent, void *object) {
Callback used to open a file specified by a filepath that may
specify one or more directories above it.
Expects the context object to be an instance of `SDClass` and
will use the `file` property of the instance to open the requested
file/directory with the associated file open mode property.
Always returns true if the directory traversal hasn't reached the
bottom of the directory heirarchy.
Returns false once the file has been opened--to prevent the traversal
from descending further. (This may be unnecessary.)
if (isLastComponent) {
SDClass *p_SD = static_cast<SDClass*>(object);
p_SD->file.open(parentDir, filePathComponent, p_SD->fileOpenMode);
if (p_SD->fileOpenMode == FILE_WRITE) {
p_SD->file.seekSet(p_SD->file.fileSize());
}
// TODO: Return file open result?
return false;
}
return true;
}
*/
boolean callback_remove(SdFile& parentDir, char *filePathComponent,
boolean isLastComponent, void *object) {
if (isLastComponent) {
return SdFile::remove(parentDir, filePathComponent);
}
return true;
}
boolean callback_rmdir(SdFile& parentDir, char *filePathComponent,
boolean isLastComponent, void *object) {
if (isLastComponent) {
SdFile f;
if (!f.open(parentDir, filePathComponent, F_READ)) return false;
return f.rmDir();
}
return true;
}
/* Implementation of class used to create `SDCard` object. */
boolean SDClass::begin(uint8_t csPin, int8_t mosi, int8_t miso, int8_t sck) {
/*
Performs the initialisation required by the sdfatlib library.
Return true if initialization succeeds, false otherwise.
*/
return card.init(SPI_HALF_SPEED, csPin, mosi, miso, sck) &&
volume.init(card) &&
root.openRoot(volume);
}
//call this when a card is removed. It will allow you to inster and initialise a new card.
void SDClass::end()
{
root.close();
}
// this little helper is used to traverse paths
SdFile SDClass::getParentDir(const char *filepath, int *index) {
// get parent directory
SdFile d1 = root; // start with the mostparent, root!
SdFile d2;
// we'll use the pointers to swap between the two objects
SdFile *parent = &d1;
SdFile *subdir = &d2;
const char *origpath = filepath;
while (strchr(filepath, '/')) {
// get rid of leading /'s
if (filepath[0] == '/') {
filepath++;
continue;
}
if (! strchr(filepath, '/')) {
// it was in the root directory, so leave now
break;
}
// extract just the name of the next subdirectory
uint8_t idx = strchr(filepath, '/') - filepath;
if (idx > 12)
idx = 12; // dont let them specify long names
char subdirname[13];
strncpy(subdirname, filepath, idx);
subdirname[idx] = 0;
// close the subdir (we reuse them) if open
subdir->close();
if (! subdir->open(parent, subdirname, F_READ)) {
// failed to open one of the subdirectories
return SdFile();
}
// move forward to the next subdirectory
filepath += idx;
// we reuse the objects, close it.
parent->close();
// swap the pointers
SdFile *t = parent;
parent = subdir;
subdir = t;
}
*index = (int)(filepath - origpath);
// parent is now the parent diretory of the file!
return *parent;
}
ext::File SDClass::open(const char *filepath, uint8_t mode) {
/*
Open the supplied file path for reading or writing.
The file content can be accessed via the `file` property of
the `SDClass` object--this property is currently
a standard `SdFile` object from `sdfatlib`.
Defaults to read only.
If `write` is true, default action (when `append` is true) is to
append data to the end of the file.
If `append` is false then the file will be truncated first.
If the file does not exist and it is opened for writing the file
will be created.
An attempt to open a file for reading that does not exist is an
error.
*/
int pathidx;
// do the interative search
SdFile parentdir = getParentDir(filepath, &pathidx);
// no more subdirs!
filepath += pathidx;
if (! filepath[0]) {
// it was the directory itself!
return ext::File(parentdir, "/");
}
// Open the file itself
SdFile file;
// failed to open a subdir!
if (!parentdir.isOpen())
return ext::File();
// there is a special case for the Root directory since its a static dir
if (parentdir.isRoot()) {
if ( ! file.open(root, filepath, mode)) {
// failed to open the file :(
return ext::File();
}
// dont close the root!
} else {
if ( ! file.open(parentdir, filepath, mode)) {
return ext::File();
}
// close the parent
parentdir.close();
}
if (mode & (F_APPEND | F_WRITE))
file.seekSet(file.fileSize());
return ext::File(file, filepath);
}
/*
File SDClass::open(char *filepath, uint8_t mode) {
//
Open the supplied file path for reading or writing.
The file content can be accessed via the `file` property of
the `SDClass` object--this property is currently
a standard `SdFile` object from `sdfatlib`.
Defaults to read only.
If `write` is true, default action (when `append` is true) is to
append data to the end of the file.
If `append` is false then the file will be truncated first.
If the file does not exist and it is opened for writing the file
will be created.
An attempt to open a file for reading that does not exist is an
error.
//
// TODO: Allow for read&write? (Possibly not, as it requires seek.)
fileOpenMode = mode;
walkPath(filepath, root, callback_openPath, this);
return ext::File();
}
*/
//boolean SDClass::close() {
// /*
//
// Closes the file opened by the `open` method.
//
// */
// file.close();
//}
boolean SDClass::exists(char *filepath) {
/*
Returns true if the supplied file path exists.
*/
return walkPath(filepath, root, callback_pathExists);
}
//boolean SDClass::exists(char *filepath, SdFile& parentDir) {
// /*
//
// Returns true if the supplied file path rooted at `parentDir`
// exists.
//
// */
// return walkPath(filepath, parentDir, callback_pathExists);
//}
boolean SDClass::mkdir(char *filepath) {
/*
Makes a single directory or a heirarchy of directories.
A rough equivalent to `mkdir -p`.
*/
return walkPath(filepath, root, callback_makeDirPath);
}
boolean SDClass::rmdir(char *filepath) {
/*
Makes a single directory or a heirarchy of directories.
A rough equivalent to `mkdir -p`.
*/
return walkPath(filepath, root, callback_rmdir);
}
boolean SDClass::remove(char *filepath) {
return walkPath(filepath, root, callback_remove);
}
void SDClass::enableCRC(boolean mode) {
card.enableCRC(mode);
}
// allows you to recurse into a directory
ext::File ext::File::openNextFile(uint8_t mode) {
dir_t p;
//Serial.print("\t\treading dir...");
while (_file->readDir(&p) > 0) {
// done if past last used entry
if (p.name[0] == DIR_NAME_FREE) {
//Serial.println("end");
return ext::File();
}
// skip deleted entry and entries for . and ..
if (p.name[0] == DIR_NAME_DELETED || p.name[0] == '.') {
//Serial.println("dots");
continue;
}
// only list subdirectories and files
if (!DIR_IS_FILE_OR_SUBDIR(&p)) {
//Serial.println("notafile");
continue;
}
// print file name with possible blank fill
SdFile f;
char name[13];
_file->dirName(p, name);
//Serial.print("try to open file ");
//Serial.println(name);
if (f.open(_file, name, mode)) {
//Serial.println("OK!");
return ext::File(f, name);
} else {
//Serial.println("ugh");
return ext::File();
}
}
//Serial.println("nothing");
return ext::File();
}
void ext::File::rewindDirectory(void) {
if (isDirectory())
_file->rewind();
}
SDClass SD;
mySD.h
/*
SD - a slightly more friendly wrapper for sdfatlib
This library aims to expose a subset of SD card functionality
in the form of a higher level "wrapper" object.
License: GNU General Public License V3
(Because sdfatlib is licensed with this.)
(C) Copyright 2010 SparkFun Electronics
*/
#ifndef __SD_H__
#define __SD_H__
#if ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
#include <SPI.h>
#include <utility/SdFat.h>
#include <utility/SdFatUtil.h>
#include <utility/Sd2Card.h>
#define FILE_READ F_READ
#define FILE_WRITE (F_READ | F_WRITE | F_CREAT)
namespace ext{
class File : public Stream {
private:
char _name[13]; // our name
SdFile *_file; // underlying file pointer
public:
File(SdFile f, const char *name); // wraps an underlying SdFile
File(void); // 'empty' constructor
~File(void); // destructor
#if ARDUINO >= 100
virtual size_t write(uint8_t);
virtual size_t write(const uint8_t *buf, size_t size);
#else
virtual void write(uint8_t);
virtual void write(const uint8_t *buf, size_t size);
#endif
virtual int read();
virtual int peek();
virtual int available();
virtual void flush();
int read(void *buf, uint16_t nbyte);
boolean seek(uint32_t pos);
uint32_t position();
uint32_t size();
void close();
operator bool();
char * name();
boolean isDirectory(void);
File openNextFile(uint8_t mode = F_RDONLY);
void rewindDirectory(void);
using Print::write;
};
}
class SDClass {
private:
// These are required for initialisation and use of sdfatlib
Sd2Card card;
SdVolume volume;
SdFile root;
// my quick&dirty iterator, should be replaced
SdFile getParentDir(const char *filepath, int *indx);
public:
// This needs to be called to set up the connection to the SD card
// before other methods are used.
boolean begin(uint8_t csPin = SD_CHIP_SELECT_PIN, int8_t mosi = -1, int8_t miso = -1, int8_t sck = -1);
//call this when a card is removed. It will allow you to inster and initialise a new card.
void end();
// Open the specified file/directory with the supplied mode (e.g. read or
// write, etc). Returns a File object for interacting with the file.
// Note that currently only one file can be open at a time.
ext::File open(const char *filename, uint8_t mode = FILE_READ);
// Methods to determine if the requested file path exists.
boolean exists(char *filepath);
// Create the requested directory heirarchy--if intermediate directories
// do not exist they will be created.
boolean mkdir(char *filepath);
// Delete the file.
boolean remove(char *filepath);
boolean rmdir(char *filepath);
void enableCRC(boolean mode);
private:
// This is used to determine the mode used to open a file
// it's here because it's the easiest place to pass the
// information through the directory walking function. But
// it's probably not the best place for it.
// It shouldn't be set directly--it is set via the parameters to `open`.
int fileOpenMode;
friend class File;
friend boolean callback_openPath(SdFile&, char *, boolean, void *);
};
extern SDClass SD;
#endif