Bill Greiman
Well-known member
The first release of SdFat for SDHC on Teensy 3.6 will have lots of SD busy delays. I currently call yield() while the SD is busy or during DMA transfers.
Is yield() good enough or should I use another SD specific weak function?
Here is a program I am using to check SDHC performance and yield efficiency. It checks performance for read/write sizes from 512 bytes to 32 KiB and monitors busy time.
Here is output from a test:
So 99% of the time is spent in yield(). The max single busy period is about 48 ms.
The answer would be simple in an RTOS like ChibiOS. A context switch takes about a half microsecond on a 180 MHz cpu so you would just put the thread to sleep and wake it in sdhc_isr().
Is yield() good enough or should I use another SD specific weak function?
Here is a program I am using to check SDHC performance and yield efficiency. It checks performance for read/write sizes from 512 bytes to 32 KiB and monitors busy time.
Code:
// Simple performance test for Teensy 3.5/3.6 SDHC.
// Demonstrates yield() efficiency.
#include "SdFat.h"
// 32 KiB buffer.
const size_t BUF_DIM = 32768;
// 8 MiB file.
const uint32_t FILE_SIZE = 256UL*BUF_DIM;
SdFatSdio sd;
File file;
uint8_t buf[BUF_DIM];
// buffer as uint32_t
uint32_t* buf32 = (uint32_t*)buf;
// Total usec in read/write calls.
uint32_t totalMicros = 0;
// Time in yield() function.
uint32_t yieldMicros = 0;
// Number of yield calls.
uint32_t yieldCalls = 0;
// Max busy time for single yield call.
uint32_t yieldMaxUsec = 0;
//-----------------------------------------------------------------------------
// Replace "weak" system yield()function.
void yield() {
// Only count cardBusy time.
if (!sd.card()->dmaBusy()) {
return;
}
uint32_t m = micros();
yieldCalls++;
while (sd.card()->dmaBusy()) {
// Do something here.
}
m = micros() - m;
if (m > yieldMaxUsec) {
yieldMaxUsec = m;
}
yieldMicros += m;
}
//-----------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
while (!Serial) {
}
Serial.println("Type any character to begin");
while (!Serial.available()) {
}
if (!sd.begin()) {
sd.initErrorHalt();
}
if (!file.open("TeensyDemo.bin", O_RDWR | O_CREAT)) {
sd.errorHalt("open failed");
}
Serial.println("\nsize,write,read");
Serial.println("bytes,KB/sec,KB/sec");
for (size_t nb = 512; nb <= BUF_DIM; nb *= 2) {
file.truncate(0);
uint32_t nRdWr = FILE_SIZE/nb;
Serial.print(nb);
Serial.print(',');
uint32_t t = micros();
for (uint32_t n = 0; n < nRdWr; n++) {
// Set start and end of buffer.
buf32[0] = n;
buf32[nb/4 - 1] = n;
if (nb != file.write(buf, nb)) {
sd.errorHalt("write failed");
}
}
t = micros() - t;
totalMicros += t;
Serial.print(1000.0*FILE_SIZE/t);
Serial.print(',');
file.rewind();
t = micros();
for (uint32_t n = 0; n < nRdWr; n++) {
if ((int)nb != file.read(buf, nb)) {
sd.errorHalt("read failed");
}
// crude check of data.
if (buf32[0] != n || buf32[nb/4 - 1] != n) {
sd.errorHalt("data check");
}
}
t = micros() - t;
totalMicros += t;
Serial.println(1000.0*FILE_SIZE/t);
}
file.close();
Serial.print("\ntotalMicros ");
Serial.println(totalMicros);
Serial.print("yieldMicros ");
Serial.println(yieldMicros);
Serial.print("yieldCalls ");
Serial.println(yieldCalls);
Serial.print("yieldMaxUsec ");
Serial.println(yieldMaxUsec);
Serial.println("Done");
}
void loop() {
}
Here is output from a test:
size,write,read
bytes,KB/sec,KB/sec
512,597.08,2198.12
1024,1139.13,3191.10
2048,2189.89,5146.60
4096,3729.06,7888.76
8192,4169.73,9330.20
16384,7411.91,13304.00
32768,12544.24,15121.20
totalMicros 42528552
yieldMicros 42183231
yieldCalls 78580
yieldMaxUsec 48156
So 99% of the time is spent in yield(). The max single busy period is about 48 ms.
The answer would be simple in an RTOS like ChibiOS. A context switch takes about a half microsecond on a 180 MHz cpu so you would just put the thread to sleep and wake it in sdhc_isr().