/*
* based off bench.ino
* Read data into SD Card from NP2 through FPGA
* Requires I2C interface to probe as well as 32 bit data and clock from FPGA
* and FPGA reset from Teensy
* This program tests high speed port reads and storage to SD Card
*
* NOTE - to use the 32 bit input port:
* - The DEBUG_EN (EMC_01) must not be routed to the MKL02Z32 chip
* - Undo or (comment out) the external memory setups in startup.c (starting at line 180 and line 344
*/
#define VERSION "20260130"
#include "SdFat.h"
#include "sdios.h"
#include <Wire.h>
#define DUALBUFFER // use both RAM1 and RAM2, else just RAM1
// #define USE_PROBE // allow this to set up probe and read from IO port
// SD_FAT_TYPE = 0 for SdFat/File as defined in SdFatConfig.h,
// 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT.
#define SD_FAT_TYPE 2
// Size of read/write.
const size_t BUF_SIZE = 438272; // max from RAM1
const size_t DBUF_SIZE = 505856; // and RAM2
const size_t WR_SIZE = 512;
const size_t WR_WORDS = WR_SIZE/4;
// ==== File size in MB where MB = 1,048576 bytes.
// == 61800 is about an hour
const uint32_t FILE_SIZE_MB = 30900;
const uint64_t FILE_SIZE = 1048576UL * (uint64_t)FILE_SIZE_MB;
const uint32_t blocksWanted = (uint32_t) (FILE_SIZE/WR_SIZE); // number of SD writes
uint32_t readVal;
uint32_t frameCnt = 0;
int8_t bitData[3][1600];
int row = 0;
int col = 0;
int bitCnt = 0;
const int DCLKpin = 1; // data clock in from FPGA
#define IMXRT_GPIO6_DIRECT (*(volatile uint32_t *)0x42000000) // port access to GPIO6 (ADB0,ADB1)
#define IMXRT_GPIO7_DIRECT (*(volatile uint32_t *)0x42004000) // port access to GPIO7 (B0,B1)
#define IMXRT_GPIO8_DIRECT (*(volatile uint32_t *)0x42008000) // port access to GPIO8 (EMC32+)
#define IMXRT_GPIO9_DIRECT (*(volatile uint32_t *)0x4200C000) // port access to GPIO9 (EMC0-31)
volatile uint32_t& portStatusReg = (digitalPinToPortReg(DCLKpin))[6]; // precalc status reg and mask
uint32_t mask = digitalPinToBitMask(DCLKpin);
// SDCARD_SS_PIN is defined for the built-in SD on some boards.
#ifndef SDCARD_SS_PIN
const uint8_t SD_CS_PIN = SS;
#else // SDCARD_SS_PIN
// Assume built-in SD is used.
const uint8_t SD_CS_PIN = SDCARD_SS_PIN;
#endif // SDCARD_SS_PIN
// Try max SPI clock for an SD. Reduce SPI_CLOCK if errors occur.
#define SPI_CLOCK SD_SCK_MHZ(50)
// Try to select the best SD card configuration.
#if HAS_SDIO_CLASS
#define SD_CONFIG SdioConfig(FIFO_SDIO)
#elif ENABLE_DEDICATED_SPI
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK)
#else // HAS_SDIO_CLASS
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SPI_CLOCK)
#endif // HAS_SDIO_CLASS
// Set PRE_ALLOCATE true to pre-allocate file clusters.
const bool PRE_ALLOCATE = true;
// Set SKIP_FIRST_LATENCY true if the first read/write to the SD can
// be avoid by writing a file header or reading the first record.
const bool SKIP_FIRST_LATENCY = true;
//==============================================================================
// End of configuration constants.
//------------------------------------------------------------------------------
// RAM1 buffer
uint32_t buf32[(BUF_SIZE + 3) / 4]; // Insure 4-byte alignment.
uint8_t* buf = (uint8_t*)buf32;
volatile uint32_t* inBufPtr = (uint32_t*)buf32;
uint8_t* bufEndPtr = (uint8_t*) (buf + BUF_SIZE);
volatile uint32_t* inEndPtr = (uint32_t*) (inBufPtr + BUF_SIZE/4);
volatile uint32_t* inPtr = inBufPtr; //+ 4; // !!!!!
uint8_t* outPtr = buf;
// RAM2 buffer
volatile uint32_t DMAMEM Dbuf32[DBUF_SIZE/4] __attribute__((aligned(32)));
uint8_t* Dbuf = (uint8_t*)Dbuf32;
volatile uint32_t* DinBufPtr = (uint32_t*)Dbuf32;
uint8_t* DbufEndPtr = (uint8_t*) (Dbuf + DBUF_SIZE);
volatile uint32_t* DinEndPtr = (uint32_t*) (DinBufPtr + DBUF_SIZE/4);
volatile uint32_t inCnt = 0;
uint64_t wrCnt = 0;
uint32_t blocksWritten = 0;
volatile uint32_t outCnt = 0;
#if SD_FAT_TYPE == 0
SdFat sd;
File file;
#elif SD_FAT_TYPE == 1
SdFat32 sd;
File32 file;
#elif SD_FAT_TYPE == 2
SdExFat sd;
ExFile file;
#elif SD_FAT_TYPE == 3
SdFs sd;
FsFile file;
#else // SD_FAT_TYPE
#error Invalid SD_FAT_TYPE
#endif // SD_FAT_TYPE
// Serial output stream
ArduinoOutStream cout(Serial);
//------------------------------------------------------------------------------
// Store error strings in flash to save RAM.
#define error(s) sd.errorHalt(&Serial, F(s))
//------------------------------------------------------------------------------
void cidDmp() {
cid_t cid;
if (!sd.card()->readCID(&cid)) {
error("readCID failed");
}
cout << F("\nManufacturer ID: ");
cout << uppercase << showbase << hex << int(cid.mid) << dec << endl;
cout << F("OEM ID: ") << cid.oid[0] << cid.oid[1] << endl;
cout << F("Product: ");
for (uint8_t i = 0; i < 5; i++) {
cout << cid.pnm[i];
}
cout << F("\nVersion: ");
cout << int(cid.prv_n) << '.' << int(cid.prv_m) << endl;
cout << F("Serial number: ") << hex << cid.psn << dec << endl;
cout << F("Manufacturing date: ");
cout << int(cid.mdt_month) << '/';
cout << (2000 + cid.mdt_year_low + 10 * cid.mdt_year_high) << endl;
cout << endl;
}
//------------------------------------------------------------------------------
void clearSerialInput() {
uint32_t m = micros();
do {
if (Serial.read() >= 0) {
m = micros();
}
} while (micros() - m < 10000);
}
//------------------------------------------------------------------------------
volatile boolean first = true;
volatile boolean firstVal = 0;
// =====================================
// ====== C L K I N T E R R U P T ====
// =====================================
volatile uint32_t pos = 0;
volatile uint32_t maxBuffUsage = 0;
volatile uint32_t wordCnt = 0;
void ClkInterrupt()
{
portStatusReg = mask; // we worked around the Teensyduino handler, so we need to reset the status flag ourself
#ifdef USE_PROBE
*inPtr = IMXRT_GPIO9_DIRECT; // 32 bits from FPGA
#else
*inPtr = wordCnt++; // inCnt; // = or store seomething usefaul for debug
#endif
#ifdef DUALBUFFER
if( ++inPtr == inEndPtr ) inPtr = DinBufPtr; // reset buffer pointer if needed
if( inPtr == DinEndPtr ) inPtr = inBufPtr;
#else
if( ++inPtr >= inEndPtr ) inPtr = inBufPtr;
#endif
inCnt++; // count up words of data
asm volatile("dsb"); // avoid double calls due to possible bus sync issues
}
//------------------------------------------------------------------------------
void setup()
{
pinMode( DCLKpin, INPUT);
pinMode( 13, OUTPUT);
digitalWriteFast(13, LOW);
// set up NRST3v3 pin as output
GPIO8_GDIR |= (1<<4); // bit 4, GPIO8
IOMUXC_SW_PAD_CTL_PAD_GPIO_SD_B1_04 = IOMUXC_PAD_DSE(7);
IOMUXC_SW_MUX_CTL_PAD_GPIO_SD_B1_04 = 5 | 0x10;
GPIO8_DR_CLEAR = (1<<4); // set NRST3V3 low - hold off run
GPIO9_GDIR = 0; // digital-in bus is all inputs
Wire.begin(); //(I2C_MASTER, 0x00, I2C_PINS_18_19, I2C_PULLUP_EXT, 400000);
Serial.begin(9600);
while (!Serial);
//Serial.println( (unsigned int)inEndPtr, HEX);
//while(1);
if (CrashReport) Serial.print(CrashReport);
Serial.print("ver: ");
Serial.println(VERSION);
Serial.println(__FILE__);
Serial.printf( "%s %s\n", __DATE__, __TIME__ );
Serial.printf( "Teensyduino version %1lu\n", TEENSYDUINO );
Serial.printf( "SdFat version %s\n", SD_FAT_VERSION_STR );
delay(1000);
cout << F("\nUse a freshly formatted SD for best performance.\n");
if (!ENABLE_DEDICATED_SPI)
{
cout << F(
"\nSet ENABLE_DEDICATED_SPI nonzero in\n"
"SdFatConfig.h for best SPI performance.\n");
}
cout << uppercase << showbase << endl; // use uppercase in hex and use 0X base prefix
#ifdef USE_PROBE
// read probe info (this mat be proprietary - so omitted for the forum)
#else // use self generated clock from pin 0
#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;
analogWriteRes(PWMRES); // write PWM resolution
analogWriteFrequency(0,4500000); //4500000);
analogWrite(0, amp);
#endif
Serial.print(" file size = ");
Serial.println( FILE_SIZE );
Serial.print(" file time = ");
Serial.println( FILE_SIZE/18000000);
}
//------------------------------------------------------------------------------
void loop()
{
uint32_t t;
// Discard any input.
clearSerialInput();
// F() stores strings in flash to save RAM
cout << F("Type any character to start\n");
while (!Serial.available()) {
yield();
}
#if HAS_UNUSED_STACK
cout << F("FreeStack: ") << FreeStack() << endl;
#endif // HAS_UNUSED_STACK
if (!sd.begin(SD_CONFIG)) {
sd.initErrorHalt(&Serial);
}
if (sd.fatType() == FAT_TYPE_EXFAT) {
cout << F("Type is exFAT") << endl;
} else {
cout << F("Type is FAT") << int(sd.fatType()) << endl;
}
cout << F("Card size: ") << sd.card()->sectorCount() * 512E-9;
cout << F(" GB (GB = 1E9 bytes)") << endl;
cidDmp();
// open or create file - truncate existing file.
if (!file.open("bench.dat", O_RDWR | O_CREAT | O_TRUNC)) error("open failed");
cout << F("FILE_SIZE = ") << FILE_SIZE << endl;
cout << F("WR_SIZE = ") << WR_SIZE << endl;
cout << F("WR_WORDS = ") << WR_WORDS << endl;
cout << F("WORDS TO WRITE = ") << FILE_SIZE/4 << endl;
#ifdef DUALBUFFER
cout << F("BUF_SIZE = ") << BUF_SIZE+DBUF_SIZE << F(" bytes\n");
Serial.print("run time(secs): ");
Serial.println( FILE_SIZE/18000000 );
Serial.println(BUF_SIZE);
Serial.println(DBUF_SIZE);
Serial.println((unsigned int)bufEndPtr);
Serial.println((unsigned int)DbufEndPtr);
#else
cout << F("BUF_SIZE = ") << BUF_SIZE << F(" bytes\n");
#endif
#ifdef DUALBUFFER
Serial.println("Dual Buffer");
#else
Serial.println("Single Buffer");
#endif
// do write test
// uint32_t n = FILE_SIZE / BUF_SIZE;
#ifdef USE_PROBE
// set up probe
#endif
if (PRE_ALLOCATE)
{
if (!file.preAllocate(FILE_SIZE)) error("preAllocate failed");
}
Serial.println("allocatd");
delay(3000);
// start up data read interrupt after everything is reday 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, 64); // 0;// highest priority, might be good to reduce a bit
Serial.print(inCnt);
Serial.println("go");
GPIO8_DR_SET = (1<<4); // let the FPGA run
#ifdef USE_PROBE
// tell probe start sending data
#endif
t = millis();
uint32_t bytes_out;
// ===== Here is where it all happens ========
//
while( blocksWritten < blocksWanted )
{
while( inCnt < WR_WORDS ); // wait til we have 512 bytes or more //(wrCnt + WR_WORDS) ); // wait until we have > WR_SIZE bytes( WR_SIZE/4 words) of new data
noInterrupts();
inCnt -= WR_WORDS;
if( inCnt > maxBuffUsage ) maxBuffUsage = inCnt;
interrupts();
outCnt++;
if( outCnt > (FILE_SIZE/512) ) GPIO8_DR_CLEAR = (1<<4); // hold FPGA in reset
if ( (bytes_out = file.write(outPtr, WR_SIZE)) != WR_SIZE ) //BUF_SIZE) != BUF_SIZE) {
Serial.print('#'); // error("write failed");
outPtr += WR_SIZE;
#ifdef DUALBUFFER
if( outPtr == bufEndPtr ) outPtr = Dbuf;
if( outPtr == DbufEndPtr ) outPtr = buf;
#else
if( outPtr == bufEndPtr ) outPtr = buf;
#endif
blocksWritten++;
}
// don't need any more data
GPIO8_DR_CLEAR = (1<<4); // hold FPGA in reset
detachInterrupt(DCLKpin);
analogWrite(0, 0);
file.flush(); // sync();
t = millis() - t;
// s = file.fileSize();
Serial.println( outCnt);
Serial.println( (unsigned int)outPtr);
Serial.println( (unsigned int)inPtr);
Serial.println( (unsigned int)buf);
Serial.println( inCnt);
Serial.print("max Buf Usage (bytes): ");
Serial.println(maxBuffUsage*4);
Serial.print("end : ");
Serial.println(blocksWritten);
Serial.print("bytes:");
Serial.println(file.fileSize());
Serial.print("millis:");
Serial.println(t);
Serial.print("bytes/sec:");
Serial.println((file.fileSize()/t ) );
Serial.print("Total frames: ");
frameCnt = file.fileSize()/600;
Serial.println(frameCnt);
file.close();
sd.end();
GPIO8_DR_CLEAR = (1<<4); // hold FPGA in reset
while(1);
}