From c979798fcc1b4b230958f12297539b2a4351636a Mon Sep 17 00:00:00 2001 From: Oleg Mazurov Date: Tue, 11 Jun 2013 21:12:45 -0600 Subject: [PATCH] commit message --- .gitmodules | 9 + examples/testusbhostFAT/Makefile | 42 ++ examples/testusbhostFAT/README.md | 30 + examples/testusbhostFAT/avr-gdb.conf | 6 + examples/testusbhostFAT/testusbhostFAT.ino | 629 +++++++++++++++++++++ 5 files changed, 716 insertions(+) create mode 100644 .gitmodules create mode 100644 examples/testusbhostFAT/Makefile create mode 100644 examples/testusbhostFAT/README.md create mode 100644 examples/testusbhostFAT/avr-gdb.conf create mode 100644 examples/testusbhostFAT/testusbhostFAT.ino diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..8c705909 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "examples/testusbhostFAT/generic_storage"] + path = examples/testusbhostFAT/generic_storage + url = https://github.com/xxxajk/generic_storage +[submodule "examples/testusbhostFAT/xmem2"] + path = examples/testusbhostFAT/xmem2 + url = https://github.com/xxxajk/xmem2 +[submodule "examples/testusbhostFAT/Arduino_Makefile_master"] + path = examples/testusbhostFAT/Arduino_Makefile_master + url = https://github.com/xxxajk/Arduino_Makefile_master diff --git a/examples/testusbhostFAT/Makefile b/examples/testusbhostFAT/Makefile new file mode 100644 index 00000000..47cc738b --- /dev/null +++ b/examples/testusbhostFAT/Makefile @@ -0,0 +1,42 @@ +# +# These are set for a mega 1280 + quadram plus my serial patch. +# If you lack quadram, or want to disable LFN, just change _FS_TINY=1 _USE_LFN=0 +# +# If your board is a mega 2560 uncomment the following two lines +# BOARD = mega2560 +# PROGRAMMER = wiring +# ...and then comment out the following two lines +BOARD = mega +PROGRAMMER = arduino + +# set your Arduino tty port here +PORT = /dev/ttyUSB0 + +EXTRA_FLAGS = -D _FS_TINY=0 +EXTRA_FLAGS += -D _USE_LFN=1 + +# Don't worry if you don't have external RAM, xmem2 detects this situation. +# You *WILL* be wanting to get some kind of external ram on your mega in order to +# do anything that is intense. +EXTRA_FLAGS += -D HAVEXMEM=1 +EXTRA_FLAGS += -D EXT_RAM_STACK=1 +EXTRA_FLAGS += -D EXT_RAM_HEAP=1 + + + +EXTRA_FLAGS += -D DISABLE_SERIAL1 +EXTRA_FLAGS += -D DISABLE_SERIAL2 +EXTRA_FLAGS += -D DISABLE_SERIAL3 + +# You should not need to change this, but I place it here if you want to play. +# These are the defaults for the optimization of the flash and ram +#OPT_FLAGS = -Os -fno-exceptions -ffunction-sections -fdata-sections -MMD + +# The following are the libraries used. +LIB_DIRS = +LIB_DIRS += ../../ +LIB_DIRS += ../testusbhostFAT/xmem2 +LIB_DIRS += ../testusbhostFAT/generic_storage + +# And finally, the part that brings everything together for you. +include Arduino_Makefile_master/_Makefile.master diff --git a/examples/testusbhostFAT/README.md b/examples/testusbhostFAT/README.md new file mode 100644 index 00000000..6f05ef32 --- /dev/null +++ b/examples/testusbhostFAT/README.md @@ -0,0 +1,30 @@ +This small sketch tests the USB host shield mass storage library. + +__Note:__ This will not run a Arduino Uno due to the limited ram available in the ATmega328p. + +The Arduino Mega (ATmega1280) and the Arduino Mega 2560 (ATmega2560) are confirmed to work with this test code. + +To compile this example you will need the following libraries as well: + +* [xmem2](https://github.com/xxxajk/xmem2) +* [generic_storage FATfs](https://github.com/xxxajk/generic_storage) + +It is recommended to get a external RAM shield or apply the following patch: . + +The following shield is recommended: . + +You must use the bundled [Makefile](Makefile) to compile the code instead of the Arduino IDE if you do not use external RAM. The master makefile is bundled as a submodule, but can also be downloaded manually at the following link: . + +To download the USB Host library and all the needed libraries for this test. + +Run the following command in a terminal application: + +``` +git clone --recursive https://github.com/felis/USB_Host_Shield_2.0 +``` + +If you want to update all the submodules run: + +``` +git submodule foreach --recursive git pull origin master +``` \ No newline at end of file diff --git a/examples/testusbhostFAT/avr-gdb.conf b/examples/testusbhostFAT/avr-gdb.conf new file mode 100644 index 00000000..7ab686f2 --- /dev/null +++ b/examples/testusbhostFAT/avr-gdb.conf @@ -0,0 +1,6 @@ +file build/testusbhostFAT.elf +target remote localhost:4242 +set {int}0x802200 = 0xffff +set {int}0x802220 = 0x0000 +#graph display `x /3xh 0x802200` + diff --git a/examples/testusbhostFAT/testusbhostFAT.ino b/examples/testusbhostFAT/testusbhostFAT.ino new file mode 100644 index 00000000..1bcc1a7b --- /dev/null +++ b/examples/testusbhostFAT/testusbhostFAT.ino @@ -0,0 +1,629 @@ + + +/* + * Mega + USB storage + optional expansion RAM + funky status LED, + * Includes interactive debug level setting, and supports emulated hot-plug. + * + * IMPORTANT! PLEASE USE Arduino 1.0.5 or better! + * Older versions HAVE MAJOR BUGS AND WILL NOT WORK AT ALL! + * Use of gcc-avr and lib-c that is newer than the Arduino version is even better. + * + */ + +// Keep this at zero until enumeration is fixed. +// Set to 1 if you are fixing enumeration. +#define WANT_HUB_TEST 0 +#ifndef HAVE_XMEM +// Set this to zero to disable xmem +#define HAVE_XMEM 1 +#endif + + +#include +#if HAVE_XMEM +#include +#endif +#include +#include +#include +#include +#include +#include +#if WANT_HUB_TEST +#include +#endif +#include +#include +#include +#include +#include +#include + +// Warning! Do not use this unless you are aware of what it does! +#if HAVE_XMEM +#define GOD_MODE 0 +#endif +static FILE mystdout; + +int led = 13; // the pin that the LED is attached to +volatile int brightness = 0; // how bright the LED is +volatile int fadeAmount = 80; // how many points to fade the LED by +volatile uint8_t current_state = 1; +volatile uint32_t LEDnext_time; // fade timeout +volatile uint8_t last_state = 0; +volatile boolean fatready = false; +volatile boolean partsready = false; +volatile boolean notified = false; +volatile uint32_t HEAPnext_time; // when to print out next heap report +volatile boolean runtest = false; +volatile boolean usbon = false; +volatile uint32_t usbon_time; +volatile boolean change = false; +volatile boolean reportlvl = false; +int cpart = 0; +PCPartition *PT; + +#if WANT_HUB_TEST +#define MAX_HUBS 2 +static USBHub *Hubs[MAX_HUBS]; +#endif + +static PFAT *Fats[MAX_PARTS]; +static part_t parts[MAX_PARTS]; +static storage_t sto[MAX_PARTS]; + +#define prescale1 ((1 << WGM12) | (1 << CS10)) +#define prescale8 ((1 << WGM12) | (1 << CS11)) +#define prescale64 ((1 << WGM12) | (1 << CS10) | (1 << CS11)) +#define prescale256 ((1 << WGM12) | (1 << CS12)) +#define prescale1024 ((1 << WGM12) | (1 << CS12) | (1 << CS10)) + +extern "C" unsigned int freeHeap(); + +/* +unsigned int getHeapend(){ + extern unsigned int __heap_start; + + if ((unsigned int)__brkval == 0) { + return (unsigned int)&__heap_start; + } else { + return (unsigned int)__brkval; + } +} + +unsigned int freeHeap() { + if (SP < (unsigned int)__malloc_heap_start) { + return ((unsigned int)__malloc_heap_end - getHeapend()); + } else { + return (SP - getHeapend()); + } +} + */ +static int my_putc(char c, FILE *t) { + Serial.write(c); +} + +void setup() { + for (int i = 0; i < MAX_PARTS; i++) { + Fats[i] = NULL; + } + // Set this to higher values to enable more debug information + // minimum 0x00, maximum 0xff + UsbDEBUGlvl = 0x51; + // declare pin 9 to be an output: + pinMode(led, OUTPUT); + pinMode(2, OUTPUT); + // Initialize 'debug' serial port + Serial.begin(115200); + + //fdevopen(&my_putc, 0); + // too bad we can't tinker with iob directly, oh well. + mystdout.put = my_putc; + mystdout.get = NULL; + mystdout.flags = _FDEV_SETUP_WRITE; + mystdout.udata = 0; + stdout = &mystdout; + + // Blink pin 9: + delay(500); + analogWrite(led, 255); + delay(500); + analogWrite(led, 0); + delay(500); + analogWrite(led, 255); + delay(500); + analogWrite(led, 0); + delay(500); + analogWrite(led, 255); + delay(500); + analogWrite(led, 0); + printf_P(PSTR("\r\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nStart\r\n")); + printf_P(PSTR("Current UsbDEBUGlvl %02x\r\n"), UsbDEBUGlvl); + printf_P(PSTR("'+' and '-' increase/decrease by 0x01\r\n")); + printf_P(PSTR("'.' and ',' increase/decrease by 0x10\r\n")); + printf_P(PSTR("'t' will run a 10MB write/read test and print out the time it took.\r\n")); + printf_P(PSTR("'e' will toggle vbus off for a few moments.\r\n")); + printf_P(PSTR("\r\n\r\nLong filename support: " +#if _USE_LFN + "Enabled" +#else + "Disabled" +#endif + "\r\n")); + analogWrite(led, 255); + delay(500); + analogWrite(led, 0); + delay(500); + + delay(100); + analogWrite(led, 255); + delay(100); + analogWrite(led, 0); + LEDnext_time = millis() + 1; +#ifdef EXT_RAM + printf_P(PSTR("Total EXT RAM banks %i\r\n"), xmem::getTotalBanks()); +#endif + printf_P(PSTR("Available heap: %u Bytes\r\n"), freeHeap()); + printf_P(PSTR("SP %x\r\n"), (uint8_t *)(SP)); + + // Even though I'm not going to actually be deleting, + // I want to be able to have slightly more control. + // Besides, it is easier to initialize stuff... +#if WANT_HUB_TEST + for (int i = 0; i < MAX_HUBS; i++) { + Hubs[i] = new USBHub(&Usb); + printf_P(PSTR("Available heap: %u Bytes\r\n"), freeHeap()); + } +#endif + while (Usb.Init() == -1) { + printf_P(PSTR("No\r\n")); + Notify(PSTR("OSC did not start."), 0x40); + } + // usb VBUS _OFF_ + Usb.gpioWr(0x00); + digitalWrite(2, 0); + usbon_time = millis() + 2000; + cli(); + TCCR3A = 0; + TCCR3B = 0; + // (0.01/(1/((16 *(10^6)) / 8))) - 1 = 19999 +#if GOD_MODE + OCR3A = 10; +#else + OCR3A = 19999; +#endif + TCCR3B |= prescale8; + TIMSK3 |= (1 << OCIE1A); + sei(); + + HEAPnext_time = millis() + 10000; +} + +#if GOD_MODE +volatile uint16_t *foof = reinterpret_cast(0x2200); +#endif + +void serialEvent() { + // Adjust UsbDEBUGlvl level on-the-fly. + // + to increase, - to decrease, * to display current level. + // . to increase by 16, , to decrease by 16 + // e to flick VBUS + // * to report debug level + if (Serial.available()) { + int inByte = Serial.read(); + switch (inByte) { + case '+': + if (UsbDEBUGlvl < 0xff) UsbDEBUGlvl++; + reportlvl = true; + break; + case '-': + if (UsbDEBUGlvl > 0x00) UsbDEBUGlvl--; + reportlvl = true; + break; + case '.': + if (UsbDEBUGlvl < 0xf0) UsbDEBUGlvl += 16; + reportlvl = true; + break; + case ',': + if (UsbDEBUGlvl > 0x0f) UsbDEBUGlvl -= 16; + reportlvl = true; + break; + case '*': + reportlvl = true; + break; + case 't': + runtest = true; + break; + case 'e': + change = true; + usbon = false; + break; +#if GOD_MODE + case 'z': + cli(); + *foof = 0xffff; + *(foof + 2) = 0x0000; + sei(); + break; +#endif + } + } +} + +ISR(TIMER3_COMPA_vect) { +#if GOD_MODE +#if !EXT_RAM_HEAP && !EXT_RAM_STACK + // Super cool debug feature to detect stack and heap collisions. + // We can use this in conjuction with the AVR dragon + // to check max depth of the stack and height of heap. + if (*foof >= (uint16_t)(SP)) { + *foof = (uint16_t)(SP); + *(foof + 2) = (uint16_t)__brkval; + } +#endif +#endif + if (millis() >= LEDnext_time) { + LEDnext_time = millis() + 30; + + // set the brightness of pin 9: + analogWrite(led, brightness); + + // change the brightness for next time through the loop: + brightness = brightness + fadeAmount; + + // reverse the direction of the fading at the ends of the fade: + if (brightness <= 0) { + brightness = 0; + fadeAmount = -fadeAmount; + } + if (brightness >= 255) { + brightness = 255; + fadeAmount = -fadeAmount; + } + } +} + + +bool isfat(uint8_t t) { + return (t == 0x01 || t == 0x04 || t == 0x06 || t == 0x0b || t == 0x0c || t == 0x0e || t == 0x1); +} + +void die(FRESULT rc) { + printf_P(PSTR("Failed with rc=%u.\r\n"), rc); + //for (;;); + +} + +/*make sure this is a power of two. */ +#define mbxs 128 + +void loop() { + uint8_t My_Buff_x[mbxs]; /* File read buffer */ + FIL My_File_Object_x; /* File object */ + DIR My_Dir_Object_x; /* Directory object */ + FILINFO My_File_Info_Object_x; /* File information object */ + + // Print a heap status report about every 10 seconds. + if (millis() >= HEAPnext_time) { + if (UsbDEBUGlvl > 0x50) { + printf_P(PSTR("Available heap: %u Bytes\r\n"), freeHeap()); +#if GOD_MODE + cli(); + uint16_t p = *foof; + sei(); + printf_P(PSTR("MAXSP %4.4x, current %4.4x\r\n"), p, SP); +#endif + } + HEAPnext_time = millis() + 10000; + } + + // Horrid! This sort of thing really belongs in an ISR, not here! + // We also will be needing to test each hub port, we don't do this yet! + if (!change && !usbon && millis() >= usbon_time) { + change = true; + usbon = true; + } + + if (change) { + change = false; + if (usbon) { + printf_P(PSTR("VBUS on\r\n")); + Usb.gpioWr(0xFF); + digitalWrite(2, 1); + } else { + Usb.gpioWr(0x00); + digitalWrite(2, 0); + usbon = false; + usbon_time = millis() + 2000; + } + } + Usb.Task(); + current_state = Usb.getUsbTaskState(); + if (current_state != last_state) { + if (UsbDEBUGlvl > 0x50) + printf_P(PSTR("USB state = %x\r\n"), current_state); + if (current_state == USB_STATE_RUNNING) { + fadeAmount = 30; + /* + partsready = false; + for (int i = 0; i < cpart; i++) { + if (Fats[i] != NULL) + delete Fats[i]; + } + fatready = false; + notified = false; + cpart = 0; + */ + } + if (current_state == USB_DETACHED_SUBSTATE_WAIT_FOR_DEVICE) { + fadeAmount = 80; + partsready = false; + for (int i = 0; i < cpart; i++) { + if (Fats[i] != NULL) + delete Fats[i]; + Fats[i] = NULL; + } + fatready = false; + notified = false; + cpart = 0; + } +#if 0 + if (current_state == 0xa0) { + printf_P(PSTR("VBUS off\r\n")); + // safe to do here + Usb.gpioWr(0x00); + digitalWrite(2, 0); + usbon = false; + usbon_time = millis() + 2000; + change = false; + } +#endif + last_state = current_state; + } + + // only do any of this if usb is on + if (usbon) { + if (partsready && !fatready) { + if (cpart > 0) fatready = true; + } + + // This is horrible, and needs to be moved elsewhere! + for (int B = 0; B < MAX_DRIVERS; B++) { + if (!partsready && Bulk[B]->GetAddress() != NULL) { + // Build a list. + int ML = Bulk[B]->GetbMaxLUN(); + //printf("MAXLUN = %i\r\n", ML); + ML++; + for (int i = 0; i < ML; i++) { + if (Bulk[B]->LUNIsGood(i)) { + partsready = true; + sto[i].private_data = &info[i]; + info[i].lun = i; + info[i].B = B; + info[i].volmap = 0; // TO-DO: keep track of > 1 + sto[i].Read = *PRead; + sto[i].Write = *PWrite; + sto[i].Reads = *PReads; + sto[i].Writes = *PWrites; + sto[i].Status = *PStatus; + sto[i].TotalSectors = Bulk[B]->GetCapacity(i); + sto[i].SectorSize = Bulk[B]->GetSectorSize(i); + printf_P(PSTR("LUN:\t\t%u\r\n"), i); + printf_P(PSTR("Total Sectors:\t%08lx\t%lu\r\n"), sto[i].TotalSectors, sto[i].TotalSectors); + printf_P(PSTR("Sector Size:\t%04x\t\t%u\r\n"), sto[i].SectorSize, sto[i].SectorSize); + // get the partition data... + PT = new PCPartition; + + if (!PT->Init(&sto[i])) { + part_t *apart; + for (int j = 0; j < 4; j++) { + apart = PT->GetPart(j); + if (apart != NULL && apart->type != 0x00) { + memcpy(&(parts[cpart]), apart, sizeof (part_t)); + printf_P(PSTR("Partition %u type %#02x\r\n"), j, parts[cpart].type); + // for now + if (isfat(parts[cpart].type)) { + Fats[cpart] = new PFAT; + int r = Fats[cpart]->Init(&sto[i], cpart, parts[cpart].firstSector); + if (r) { + delete Fats[cpart]; + Fats[cpart] = NULL; + } else cpart++; + } + } + } + } else { + // try superblock + Fats[cpart] = new PFAT; + int r = Fats[cpart]->Init(&sto[i], cpart, 0); + if (r) { + printf_P(PSTR("Superblock error %x\r\n"), r); + delete Fats[cpart]; + Fats[cpart] = NULL; + } else cpart++; + + } + delete PT; + } else { + sto[i].Read = NULL; + sto[i].Write = NULL; + sto[i].Writes = NULL; + sto[i].Reads = NULL; + sto[i].TotalSectors = 0UL; + sto[i].SectorSize = 0; + } + } + + } + } + + if (fatready) { + if (Fats[0] != NULL) { + struct Pvt * p; + p = ((struct Pvt *)(Fats[0]->storage->private_data)); + if (!Bulk[p->B]->LUNIsGood(p->lun)) { + // media change + fadeAmount = 80; + partsready = false; + for (int i = 0; i < cpart; i++) { + if (Fats[i] != NULL) + delete Fats[i]; + Fats[cpart] = NULL; + } + fatready = false; + notified = false; + cpart = 0; + } + + } + } + if (fatready) { + FRESULT rc; /* Result code */ + UINT bw, br, i; + ULONG ii, wt, rt, start, end; + + if (!notified) { + fadeAmount = 5; + notified = true; + printf_P(PSTR("\r\nOpen an existing file (message.txt).\r\n")); + rc = f_open(&My_File_Object_x, "0:/MESSAGE.TXT", FA_READ); + if (rc) printf_P(PSTR("Error %i, message.txt not found.\r\n")); + else { + printf_P(PSTR("\r\nType the file content.\r\n")); + for (;;) { + rc = f_read(&My_File_Object_x, &(My_Buff_x[0]), mbxs, &br); /* Read a chunk of file */ + if (rc || !br) break; /* Error or end of file */ + for (i = 0; i < br; i++) { + /* Type the data */ + if (My_Buff_x[i] == '\n') + Serial.write('\r'); + if (My_Buff_x[i] != '\r') + Serial.write(My_Buff_x[i]); + Serial.flush(); + } + } + if (rc) { + f_close(&My_File_Object_x); + goto out; + } + + printf_P(PSTR("\r\nClose the file.\r\n")); + rc = f_close(&My_File_Object_x); + if (rc) goto out; + } + printf_P(PSTR("\r\nCreate a new file (hello.txt).\r\n")); + rc = f_open(&My_File_Object_x, "0:/Hello.TxT", FA_WRITE | FA_CREATE_ALWAYS); + if (rc) { + die(rc); + goto outdir; + } + printf_P(PSTR("\r\nWrite a text data. (Hello world!)\r\n")); + rc = f_write(&My_File_Object_x, "Hello world!\r\n", 14, &bw); + if (rc) { + goto out; + } + printf_P(PSTR("%u bytes written.\r\n"), bw); + + printf_P(PSTR("\r\nClose the file.\r\n")); + rc = f_close(&My_File_Object_x); + if (rc) { + die(rc); + goto out; + } +outdir: + printf_P(PSTR("\r\nOpen root directory.\r\n")); + rc = f_opendir(&My_Dir_Object_x, "0:/"); + if (rc) { + die(rc); + goto out; + } + + printf_P(PSTR("\r\nDirectory listing...\r\n")); + printf_P(PSTR("Available heap: %u Bytes\r\n"), freeHeap()); + for (;;) { + rc = f_readdir(&My_Dir_Object_x, &My_File_Info_Object_x); /* Read a directory item */ + if (rc || !My_File_Info_Object_x.fname[0]) break; /* Error or end of dir */ + + if (My_File_Info_Object_x.fattrib & AM_DIR) { + Serial.write('d'); + } else { + Serial.write('-'); + } + Serial.write('r'); + + if (My_File_Info_Object_x.fattrib & AM_RDO) { + Serial.write('-'); + } else { + Serial.write('w'); + } + if (My_File_Info_Object_x.fattrib & AM_HID) { + Serial.write('h'); + } else { + Serial.write('-'); + } + + if (My_File_Info_Object_x.fattrib & AM_SYS) { + Serial.write('s'); + } else { + Serial.write('-'); + } + + if (My_File_Info_Object_x.fattrib & AM_ARC) { + Serial.write('a'); + } else { + Serial.write('-'); + } + +#if _USE_LFN + if (*My_File_Info_Object_x.lfname) + printf_P(PSTR(" %8lu %s (%s)\r\n"), My_File_Info_Object_x.fsize, My_File_Info_Object_x.fname, My_File_Info_Object_x.lfname); + else +#endif + printf_P(PSTR(" %8lu %s\r\n"), My_File_Info_Object_x.fsize, &(My_File_Info_Object_x.fname[0])); + } +out: + if (rc) die(rc); + printf_P(PSTR("\r\nTest completed.\r\n")); + + } + + + + + if (runtest) { + runtest = false; + printf_P(PSTR("\r\nCreate a new 10MB test file (10MB.bin).\r\n")); + for (bw = 0; bw < mbxs; bw++) My_Buff_x[bw] = bw & 0xff; + rc = f_open(&My_File_Object_x, "0:/10MB.bin", FA_WRITE | FA_CREATE_ALWAYS); + if (rc) goto failed; + start = millis(); + for (ii = 10485760 / mbxs; ii > 0; ii--) { + rc = f_write(&My_File_Object_x, &My_Buff_x[0], mbxs, &bw); + if (rc || !bw) goto failed; + } + rc = f_close(&My_File_Object_x); + if (rc) goto failed; + end = millis(); + wt = end - start; + printf_P(PSTR("Time to write 10485760 bytes: %lu ms (%lu sec) \r\n"), wt, (500 + wt) / 1000UL); + rc = f_open(&My_File_Object_x, "0:/10MB.bin", FA_READ); + start = millis(); + if (rc) goto failed; + for (;;) { + rc = f_read(&My_File_Object_x, &My_Buff_x[0], mbxs, &bw); /* Read a chunk of file */ + if (rc || !bw) break; /* Error or end of file */ + } + end = millis(); + if (rc) goto failed; + rc = f_close(&My_File_Object_x); + if (rc) goto failed; + rt = end - start; + printf_P(PSTR("Time to read 10485760 bytes: %lu ms (%lu sec)\r\nDelete test file\r\n"), rt, (500 + rt) / 1000UL); +failed: + rc = f_unlink("0:/10MB.bin"); + if (rc) die(rc); + printf_P(PSTR("10MB timing test finished.\r\n")); + } + } + } +}