SD Card no data after power loss, Teensy 4.1

slash2

Well-known member
I have designed a system that is essentially a data logger. It is a custom board based on the T4.1 chipset, but I have verified the problem with a standard T4.1.

When I go to start logging, I create the file and pre-allocate the desired file length in order to get the fastest write speed. The SD Card is also fully formatted before each logging session. So if do the allocate, stop, pull the card, and read the file, it is all 0's, as expected. However, If I start logging, and lose power during data collection, the file is also all 0's. I could see losing some portion of the data, but not all of it. It can occur after gigabytes have been written. The file length shows as the allocated amount. I don't 'flush' until right before 'close' (the flush is probably redundant there). Does this mean a flush (or close) has to happen to have anything stored? I mean, the data has to be stored, but perhaps the file system hides that?

Of course, I should guard against this, but sometimes stuff happens, and I'd hate to see the data lost.

Here is a really pared down version of the code:

Pins 0 and 1 must be connected when run on a T4.1. On my board the data clock is generated elsewhere.

Code:
#define T4CLOCK
#define USBCOMMS

#define DATA_FILE_NAME "test.bin"

const uint32_t FILE_SIZE_MB = 256;

econst size_t WR_SIZE = 512;
const size_t WR_WORDS = WR_SIZE / 4;

uint64_t FILE_SIZE = (uint64_t)1048576UL * (uint64_t)FILE_SIZE_MB;
uint32_t blocksWanted = (uint32_t)(FILE_SIZE / WR_SIZE);

#define PWMRES 4     // PWM resolution 8 bits = 256 steps
#define PWMSTEPS 16  // to match PWMRES: there are 256 steps
const int amp = PWMSTEPS / 2 - 1;

Stream* serialCmd;

const int CLKpin = 0;
const int DCLKpin = 1;

void ClkInterrupt()
{
  portStatusReg = mask;         // we worked around the Teensyduino handler, so we need to reset the status flag ourself
  *inPtr = IMXRT_GPIO9_DIRECT;  //  inCnt; // maxBuffUsage; //pos++; //outCnt; //
  if (++inPtr >= inEndPtr) inPtr = inBufPtr;
  inCnt++;              // count up words of data
  asm volatile("dsb");  // avoid double calls due to possible bus sync issues
}

// =====================================
// ====== S E T U P  ====
// =====================================
void setup()
{
  Serial6.begin(19200);
 
  Serial.begin(19200); // open USB serial, need it for real time mode
#ifdef DUALCONTROL
  while (!Serial) ;
#endif

  pinMode(DCLKpin, INPUT);

  #ifdef USBCOMMS
  serialCmd = &Serial;
#else
  serialCmd = &Serial6;
#endif

  int sdTry;
  for (sdTry = 0; sdTry < 4; sdTry++)
    if (SD.begin(BUILTIN_SDCARD)) break;
  if (sdTry == 4)
  {
    SD.sdfs.end();
    serialCmd->println("!!! ===== SD FAIL ==== !!!");  //sd.initErrorHalt(&Serial6);
  }
}

loop()
{
   takeData();
   while(1);
}

//==================================
// === T A K E  D A T A  ====
// ===============================

void takeData(void)
{
  digitalWriteFast(13, LOW);  // LED off

  if (!file.open(DATA_FILE_NAME, O_RDWR | O_CREAT | O_TRUNC)) serialCmd->println("open failed");


  if (!file.preAllocate(FILE_SIZE))
  {
    serialCmd->println("preAllocate failed");
    return;
  }

  uint32_t bytes_out;
  blocksWritten = 0;  // reset block count
  inCnt = 0;
  maxBuffUsage = 0;
  outCnt = 0;
  inPtr = inBufPtr;
  outPtr = buf;

  #ifdef T4CLOCK
  analogWriteRes(PWMRES);                 // write PWM resolution
  analogWriteFrequency(CLKpin, 4500000); //42500000);  //500000); //500000);
  analogWrite(CLKpin, amp);
  #endif

  serialCmd->println("running");

  // start up data read interrupt after everything is ready to go
  attachInterrupt(DCLKpin, nullptr, RISING);          //CHANGE); // let Teensyduino do the setup work
  attachInterruptVector(IRQ_GPIO6789, ClkInterrupt);  // override Teensyduino handler and invoke the callback directly
  NVIC_ENABLE_IRQ(IRQ_GPIO6789);
  NVIC_SET_PRIORITY(IRQ_GPIO6789, 0);  // 0 is highest priority, might be good to reduce a bit

  #ifdef T4CLOCK
  analogWriteRes(PWMRES);                 // write PWM resolution
  analogWriteFrequency(CLKpin, 4500000); //42500000);  //500000); //500000);
  analogWrite(CLKpin, amp);
  #endif

  //  ===== Here is where it all happens ========

  while (blocksWritten < blocksWanted)
  {
    while (inCnt < WR_WORDS);  // wait til we have 512 bytes or more
    digitalWriteFast(13, HIGH);
    noInterrupts();
    inCnt -= WR_WORDS;
    if (inCnt > maxBuffUsage) maxBuffUsage = inCnt;
    interrupts();

    outCnt++;

    if ((bytes_out = file.write(outPtr, WR_SIZE)) != WR_SIZE)  //BUF_SIZE) != BUF_SIZE) {
      serialCmd->print('#');                                       // error("write failed");

    outPtr += WR_SIZE;
    if (outPtr == bufEndPtr) outPtr = buf;
    blocksWritten++;
  }

#ifdef T4CLOCK
  analogWrite(0, 0);  // turn off freq gen
#endif

  file.flush();

  file.close();

}
 
Preallocate is good for speed as the file system already knows where all the data is going.

Without a close then all the data that streamed out is not accounted for, accounting which takes time

Close calls sync() to have the file system update directory entry so the data written is documented in the dir entry

sync() called alone updates the DIR entry for length, and leaves the file open,

this thread has some details: https://forum.pjrc.com/index.php?threads/sd-datalogging-best-practice-in-2019.55114/post-197915
 
Thanks!

So if I understand correctly; the data is being saved, but if the directory isn't updated it doesn't know that. So when I try to look at the data, it thinks the file is empty (though length is allocated)? Weird.

If I add the sync() it takes about 2msec. I think I can work with that as long as I'm not close to my max buffer, but I only need to do this every second or so.

I received an email with another reply, but do not see it here. But let me answer that:
1. Sorry the program does not compile. I did not try this pared down version; it was mostly to show my overall logic.
2. The line:
attachInterrupt(DCLKpin, nullptr, RISING);
was questioned, presumably due to the nullptr. The next line :
attachInterruptVector(IRQ_GPIO6789, ClkInterrupt);
fills in the vector. As I recall, this minimizes the overhead on the interrupt. The first line does most of the work.
 
As for Interrupts here: I am not sure what the attachInterrupt(DCLKpin, nullptr, RISING);
Is supposed to do? Especially if you actually never set it to some other function pointer.

The only thing of use it might do is the call:
Code:
NVIC_ENABLE_IRQ(IRQ_GPIO6789);

Another option you have, is to map this pin back to Normal mode, and then you can use some more
specific interrupt vector without mucking with the 6789, which potentially could impact other things...
That is:
Code:
const int DCLKpin = 1;
Which I belived is GPIO 1 Pin2

From the interrupt table:
1790954630654.png

Code:
You can for example set that pin back to normal, by:
    // Use fast GPIO6, GPIO7, GPIO8, GPIO9
    IOMUXC_GPR_GPR26 = 0xFFFFFFFF;
    IOMUXC_GPR_GPR27 = 0xFFFFFFFF;
    IOMUXC_GPR_GPR28 = 0xFFFFFFFF;
    IOMUXC_GPR_GPR29 = 0xFFFFFFFF;
setting the appropriate bit in IOMUXC_GPR_GPR26 probably: 0xFFFFFFFB
in your sketch and then for example you could use IRQ 80: IRQ_GPIO1_0_15

potentially 74: IRQ_GPIO1_INT2 - Although when I was playing with these a long time ago I am not sure if I got the
one pin versions working ok...
 
OP doesn't need to do all that because they're only using one single pin interrupt, that's why they've replaced the generic GPIO6789 handler. The first attachInterrupt() call is for configuring which state of the pin will trigger the interrupt.
 
Yes, and any pin interruption during the time it takes to forward the signal to GPIO6789 causes a crash.

This faint hope that “it won’t happen” is, unfortunately, widespread—even in the libraries. Unfortunately, that’s also sloppy coding.
And it will definitely crash if an interrupt occurs at the wrong time.
I’d really like to know what’s so bad about programming it correctly.
3 lines more to hack into the keyboard? Whow....

...and for the internals - why no check for nullptr? it could by replaced by the dummy_isr.
Maybe too much work, too?

And that's in Arduino... for beginners...
 
Last edited:
Code:
attachInterrupt(DCLKpin, ClkInterrupt, RISING);
attachInterruptVector(IRQ_GPIO6789, ClkInterrupt);
This does't even need a line more ;)
Stil sloppy style, but does not risk a crash.

Why not copy the code from attachInterrrupt to your sourcecode and modify it?
 
Last edited:
Before I mention a few other minor points.
Don't assume that your interrupts are triggered “as quickly as possible.” At least not without first having thoroughly checked everything.
The Teensy code constantly and frequently disables interrupts globally in the most unexpected places.
Even in the “eventresponder.” (The biggest mess of all.)
Note: This is called from `yield`. So basically everywhere.
 
Last edited:
"if (!file.open(DATA_FILE_NAME, O_RDWR | O_CREAT | O_TRUNC))serialCmd->println("open failed");" - you are doing a println, but you don't handle it any further if the open failed.
The same thing right below it.if (!file.preAllocate(FILE_SIZE)){serialCmd->println("preAllocate failed");return;If ("preAllocate failed"), the file must also be closed.

Writing: If your SD card can't handle writing at this high speed, you have a problem. You can't rely on it either. It even depends on the age of the card. Formatting doesn't help. The controller on the card is in control. If it decides to squeeze in another task briefly, that can cause issues. It's out of your control.
if (++inPtr >= inEndPtr)
inPtr = inBufPtr;

You are preventing a buffer overflow here, but you'll lose data when that happens. I'd increment a "overflow counter" or something like this to see it.
And - inCnt should be volatile (not visible in the code)

Use asm volatile ("dsb":::"memory");

if ((bytes_out = file.write(outPtr, WR_SIZE)) != WR_SIZE)
serialCmd->print('#');

outPtr += WR_SIZE;
blocksWritten++;

- You print '#' but outPtr and blockwriten are incremented anyway...

I'd check these issues first. There are more.
 
Last edited:
Back
Top