From faf6e55bc52a012691ab550c23f2ada4f6d3d436 Mon Sep 17 00:00:00 2001 From: kazu Date: Thu, 11 Feb 2021 20:08:25 +0100 Subject: [PATCH] LCD Driver, some teensy code, SDCard code, MBR/FAT32 --- Debug.h | 13 + Platforms.h | 6 + ext/ctrl/SNESController.h | 3 + ext/lcd/ILI9341.h | 7 +- ext/lcd/ILI9486.h | 301 +++++++++++++++++++++++ ext/lcd/ILI9486p.h | 490 +++++++++++++++++++++++++++++++++++++ ext/sd/MBR.h | 73 ++++++ ext/sd/SDCard.h | 302 +++++++++++++++++++++++ ext/sd/fat32/DirIterator.h | 45 ++++ ext/sd/fat32/FS.h | 100 ++++++++ ext/sd/fat32/FS.h.autosave | 100 ++++++++ ext/sd/fat32/File.h | 64 +++++ ext/sd/fat32/Structs.h | 77 ++++++ ext/sd/main.cpp | 66 +++++ ext/sens/INA219.h | 2 + io/GPIO.h | 14 +- io/HardSPI.h | 2 +- io/SoftI2C.h | 14 +- io/SoftSPI.h | 25 +- 19 files changed, 1679 insertions(+), 25 deletions(-) create mode 100644 ext/lcd/ILI9486.h create mode 100644 ext/lcd/ILI9486p.h create mode 100644 ext/sd/MBR.h create mode 100644 ext/sd/SDCard.h create mode 100644 ext/sd/fat32/DirIterator.h create mode 100644 ext/sd/fat32/FS.h create mode 100644 ext/sd/fat32/FS.h.autosave create mode 100644 ext/sd/fat32/File.h create mode 100644 ext/sd/fat32/Structs.h create mode 100644 ext/sd/main.cpp diff --git a/Debug.h b/Debug.h index 25993a3..8d939dd 100644 --- a/Debug.h +++ b/Debug.h @@ -88,7 +88,20 @@ extern "C" { #define IF_DEBUG(a) a #define debugShow(buf, len) hexdump(buf,len) +#elif TEENSY + + #define debugMod(module, str) {char buf[64]; sprintf(buf, "i[%-10s] ", module); Serial.print(buf); Serial.println(str);} + #define debugMod1(module, str, v1) {char buf[64]; sprintf(buf, "i[%-10s] ", module); Serial.print(buf); sprintf(buf, str, v1); Serial.println(buf);} + #define debugMod2(module, str, v1, v2) {char buf[64]; sprintf(buf, "i[%-10s] ", module); Serial.print(buf); sprintf(buf, str, v1, v2); Serial.println(buf);} + #define debugMod3(module, str, v1, v2, v3) {char buf[64]; sprintf(buf, "i[%-10s] ", module); Serial.print(buf); sprintf(buf, str, v1, v2, v3); Serial.println(buf);} + #define debugMod4(module, str, v1, v2, v3, v4) {char buf[64]; sprintf(buf, "i[%-10s] ", module); Serial.print(buf); sprintf(buf, str, v1, v2, v3, v4); Serial.println(buf);} + #define debugMod5(module, str, v1, v2, v3, v4, v5) {char buf[64]; sprintf(buf, "i[%-10s] ", module); Serial.print(buf); sprintf(buf, str, v1, v2, v3, v4, v5); Serial.println(buf);} + + #define errorMod(module, str) debugMod(module, str); while(true) {} + #else + + #error "unsupported platform" diff --git a/Platforms.h b/Platforms.h index 3951083..60dd982 100644 --- a/Platforms.h +++ b/Platforms.h @@ -34,6 +34,8 @@ #elif (TEENSY) #pragma message "Using Teensy" + #define DELAY_MS(ms) delay(ms) + #else @@ -66,3 +68,7 @@ #include #include #endif + +#if TEENSY + #define IRAM_ATTR +#endif diff --git a/ext/ctrl/SNESController.h b/ext/ctrl/SNESController.h index 65bab03..ea91dbc 100644 --- a/ext/ctrl/SNESController.h +++ b/ext/ctrl/SNESController.h @@ -5,6 +5,9 @@ /** https://fpgalover.com/images/manuals/SNES/time.JPG */ + +// Pinout on the chinese version +// left to right: gnd data latch clk +5v (also works with 3.3) template class SNESController { public: diff --git a/ext/lcd/ILI9341.h b/ext/lcd/ILI9341.h index 443cd80..a955938 100644 --- a/ext/lcd/ILI9341.h +++ b/ext/lcd/ILI9341.h @@ -195,7 +195,7 @@ public: uint32_t res = 0xFFFFFFFF; readBytes(buf, 5); // dummy-byte + 4 data bytes chipDeselect(); - ESP_LOGI(MOD, "%d %d %d %d", buf[1],buf[2],buf[3],buf[4]); + //ESP_LOGI(MOD, "%d %d %d %d", buf[1],buf[2],buf[3],buf[4]); read(); } @@ -216,7 +216,7 @@ public: void draw(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint16_t* data) { chipSelect(); setAddrWindow(x,y,w,h); - const int len = w*h; +// const int len = w*h; // for (int i = 0; i < len; ++i) { // spi::writeWord(data[i]); // } @@ -289,7 +289,8 @@ private: } void wait() { - vTaskDelay(100 / portTICK_PERIOD_MS); + //vTaskDelay(100 / portTICK_PERIOD_MS); + DELAY_MS(100); } diff --git a/ext/lcd/ILI9486.h b/ext/lcd/ILI9486.h new file mode 100644 index 0000000..4ac8778 --- /dev/null +++ b/ext/lcd/ILI9486.h @@ -0,0 +1,301 @@ +#ifndef LCD_ILI9341 +#define LCD_ILI9341 + +#include "../../Debug.h" +#include + +// https://www.displayfuture.com/Display/datasheet/controller/ILI9486L.pdf + +// https://www.waveshare.com/3.5inch-tft-touch-shield.htm +// https://www.waveshare.com/wiki/File:3.5inch_TFT_Touch_Shield_Code.7z + +// https://github.com/ZinggJM/ILI9486_SPI/blob/master/src/ILI9486_SPI.cpp + +static constexpr const uint8_t ili9486_init[] = { + +}; + +//template class ILI9341 { +template class ILI9486 { + +private: + + int w = 320; + int h = 240; + + static constexpr const char* MOD = "ILI9486"; + + static constexpr const uint8_t REG_SWRESET = 0x01; // Software Reset + static constexpr const uint8_t REG_RDDID = 0x04; // Read display identification information + static constexpr const uint8_t REG_RDDST = 0x09; // Read Display Status + static constexpr const uint8_t REG_CASET = 0x2A; // Column Address Set + static constexpr const uint8_t REG_PASET = 0x2B; // Page Address Set + static constexpr const uint8_t REG_RAMWR = 0x2C; // Memory Write + static constexpr const uint8_t REG_RAMRD = 0x2E; // Memory Read + + SPI& spi; + +public: + + ILI9486(SPI& spi) : spi(spi) { + MyGPIO::setOutput(PIN_DATA_COMMAND); + MyGPIO::setOutput(PIN_CS); + } + + + + /** perform display software reset and initialization */ + void init() { + + + // send reset command + wait(); + sendCommand(REG_SWRESET); + wait(); + + + getStatus(); + + //sendCommandAndData(0xb0, {0x00}); // Interface Mode Control + sendCommandAndData(0x11, {}); // disable sleep + + wait(); + + sendCommandAndData(0x3A, {0x55}); // Interface Pixel Format, 16 bits / pixel + sendCommandAndData(0x36, {0x28}); // Memory Access Control + sendCommandAndData(0xC2, {0x44}); // Power Control 3 (For Normal Mode) + sendCommandAndData(0xC5, {0x00, 0x00, 0x00, 0x00}); // VCOM Control + sendCommandAndData(0xE0, {0x0F, 0x1F, 0x1C, 0x0C, 0x0F, 0x08, 0x48, 0x98, 0x37, 0x0A, 0x13, 0x04, 0x11, 0x0D, 0x00}); // PGAMCTRL(Positive Gamma Control) + sendCommandAndData(0xE1, {0x0F, 0x32, 0x2E, 0x0B, 0x0D, 0x05, 0x47, 0x75, 0x37, 0x06, 0x10, 0x03, 0x24, 0x20, 0x00}); // NGAMCTRL (Negative Gamma Correction) + sendCommandAndData(0xE2, {0x0F, 0x32, 0x2E, 0x0B, 0x0D, 0x05, 0x47, 0x75, 0x37, 0x06, 0x10, 0x03, 0x24, 0x20, 0x00}); // Digital Gamma Control 1 + sendCommandAndData(0x36, {0x28}); // Memory Access Control, BGR + sendCommandAndData(0x11, {}), // # Sleep OUT + sendCommandAndData(0x29, {}), // Display ON + + wait(); + + +/* +sendCommand(0x3A); + sendData(0x55); // use 16 bits per pixel color + sendCommand(0x36); + sendData(0x48); // MX, BGR == rotation 0 + // PGAMCTRL(Positive Gamma Control) + sendCommand(0xE0); + sendData(0x0F); + sendData(0x1F); + sendData(0x1C); + sendData(0x0C); + sendData(0x0F); + sendData(0x08); + sendData(0x48); + sendData(0x98); + sendData(0x37); + sendData(0x0A); + sendData(0x13); + sendData(0x04); + sendData(0x11); + sendData(0x0D); + sendData(0x00); + // NGAMCTRL(Negative Gamma Control) + sendCommand(0xE1); + sendData(0x0F); + sendData(0x32); + sendData(0x2E); + sendData(0x0B); + sendData(0x0D); + sendData(0x05); + sendData(0x47); + sendData(0x75); + sendData(0x37); + sendData(0x06); + sendData(0x10); + sendData(0x03); + sendData(0x24); + sendData(0x20); + sendData(0x00); + // Digital Gamma Control 1 + sendCommand(0xE2); + sendData(0x0F); + sendData(0x32); + sendData(0x2E); + sendData(0x0B); + sendData(0x0D); + sendData(0x05); + sendData(0x47); + sendData(0x75); + sendData(0x37); + sendData(0x06); + sendData(0x10); + sendData(0x03); + sendData(0x24); + sendData(0x20); + sendData(0x00); + sendCommand(0x11); // Sleep OUT + wait(); + sendCommand(0x29); // Display ON + wait(); + +*/ + + } + + void sendCommandAndData(uint8_t cmd, std::initializer_list data) { + sendCommand(cmd); + for (uint8_t b : data) {sendData(b);} + } + + uint32_t getID() { + sendCommand(REG_RDDID); + uint32_t res = 0xFFFFFFFF; + spi.read(reinterpret_cast(&res), 4); // dummy-byte + 3 data bytes + return res; + } + + void getStatus() { + uint8_t buf[5]; + sendCommand(REG_RDDST); + uint32_t res = 0xFFFFFFFF; + spi.read(buf, 5); // dummy-byte + 4 data bytes + printf("Status: %02x %02x %02x %02x \n", buf[1],buf[2],buf[3],buf[4]); + } + +/* + void read() { + chipSelect(); + setAddrWindow(0,0,100,100); + sendCommand(REG_RAMRD); + uint8_t buf[32]; + readBytes(buf, 32); + for (int i = 0; i < 32; ++i) { + printf("%d ", buf[i]); + } + printf("\n"); + chipDeselect(); + } +*/ + + void draw(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint16_t* data) { + setAddrWindow(x,y,w,h); + modeDATA(); + chipSelect(); + spi.write((const uint8_t*)data, w*h*2); + chipDeselect(); + } + + void fillRand() { + setAddrWindow(0,0,w,h); + modeDATA(); + chipSelect(); + for (int y = 0; y < h; ++y) { + uint16_t buf[w]; + for (int x = 0; x < w; ++x) {buf[x] = rand();} + spi.write(reinterpret_cast(buf), w*2); + } + chipDeselect(); + } + + void fillColor(uint16_t color) { + setAddrWindow(0,0,w,h); + modeDATA(); + chipSelect(); + for (uint32_t i = 0; i < w*h; ++i) { + spi.writeWord(color); + } + chipDeselect(); + } + +private: + + void wait() { + vTaskDelay(250 / portTICK_PERIOD_MS); + } + + void setAddrWindow(const uint16_t x1, const uint16_t y1, const uint16_t w, const uint16_t h) { + + // end (x,y) + uint16_t x2 = x1 + w - 1; + uint16_t y2 = y1 + h - 1; + + sendCommand(REG_CASET); // Column addr set + sendData(x1 >> 8); //Set the horizontal starting point to the high octet + sendData(x1 & 0xff); //Set the horizontal starting point to the low octet + sendData(x2 >> 8); //Set the horizontal end to the high octet + sendData(x2 & 0xff); //Set the horizontal end to the low octet + + sendCommand(REG_PASET); // Row addr set + sendData(y1 >> 8); + sendData(y1 & 0xff ); + sendData(y2 >> 8); + sendData(y2 & 0xff); + + sendCommand(REG_RAMWR); // write to RAM + // transmit data now + + } + + + + + /** send the given command to the display */ + void sendCommand(const uint8_t cmd) { + modeCMD(); + chipSelect(); + spi.writeByte(0x00); // 16 bit transfer! + spi.writeByte(cmd); + chipDeselect(); + } + + /** send the given data to the display */ + void sendData(const uint8_t data) { + modeDATA(); + chipSelect(); + spi.writeByte(0x00); // 16 bit transfer! + spi.writeByte(data); + chipDeselect(); + } + + + +// /** send the given data to the display */ +// void sendBytes(const uint8_t* data, const size_t len) { +// spi.write(data, len); +// } + +// /** read the given data from the display */ +// void readBytes(uint8_t* data, const size_t len) { +// spi.read(data, len); +// } + + /** select the display (CS=0) */ + inline void chipSelect() { + //asm("nop");asm("nop");asm("nop");asm("nop"); + MyGPIO::clear(PIN_CS); + //asm("nop");asm("nop");asm("nop");asm("nop"); + } + + /** unselect the display (CS=1) */ + inline void chipDeselect() { + //asm("nop");asm("nop");asm("nop");asm("nop"); + MyGPIO::set(PIN_CS); + //asm("nop");asm("nop");asm("nop");asm("nop"); + } + + /** switch to command-mode */ + inline void modeCMD() { + //asm("nop");asm("nop");asm("nop");asm("nop"); + MyGPIO::clear(PIN_DATA_COMMAND); + //asm("nop");asm("nop");asm("nop");asm("nop"); + } + + /** switch to data-mode */ + inline void modeDATA() { + //asm("nop");asm("nop");asm("nop");asm("nop"); + MyGPIO::set(PIN_DATA_COMMAND); + //asm("nop");asm("nop");asm("nop");asm("nop"); + } + + +}; + +#endif diff --git a/ext/lcd/ILI9486p.h b/ext/lcd/ILI9486p.h new file mode 100644 index 0000000..a6b487e --- /dev/null +++ b/ext/lcd/ILI9486p.h @@ -0,0 +1,490 @@ +#ifndef LCD_ILI9486p +#define LCD_ILI9486p + +#include "../../io/GPIO.h" +#include "../../Debug.h" +#include + +// https://www.displayfuture.com/Display/datasheet/controller/ILI9486L.pdf + +// http://www.lcdwiki.com/3.5inch_Arduino_Display-UNO + +// https://github.com/ZinggJM/ILI9486_SPI/blob/master/src/ILI9486_SPI.cpp + + + +// PARALLEL VERSION WITH 8 BIT 8080 BUS + +#pragma GCC push_options +#pragma GCC optimize ("Os") + + +template class ILI9486p { + +private: + + /* + static constexpr const uint8_t PIN_D0 = 15; + static constexpr const uint8_t PIN_D1 = 14; + static constexpr const uint8_t PIN_D2 = 21; + static constexpr const uint8_t PIN_D3 = 20; + static constexpr const uint8_t PIN_D4 = 19; + static constexpr const uint8_t PIN_D5 = 18; + static constexpr const uint8_t PIN_D6 = 17; + static constexpr const uint8_t PIN_D7 = 16; + + static constexpr const uint8_t PIN_B0 = CORE_PIN15_BIT; + static constexpr const uint8_t PIN_B1 = CORE_PIN14_BIT; + static constexpr const uint8_t PIN_B2 = CORE_PIN21_BIT; + static constexpr const uint8_t PIN_B3 = CORE_PIN20_BIT; + static constexpr const uint8_t PIN_B4 = CORE_PIN19_BIT; + static constexpr const uint8_t PIN_B5 = CORE_PIN18_BIT; + static constexpr const uint8_t PIN_B6 = CORE_PIN17_BIT; + static constexpr const uint8_t PIN_B7 = CORE_PIN16_BIT; + */ + static constexpr const uint8_t PIN_D0 = 19; + static constexpr const uint8_t PIN_D1 = 18; + static constexpr const uint8_t PIN_D2 = 14; + static constexpr const uint8_t PIN_D3 = 15; + static constexpr const uint8_t PIN_D4 = 40; + static constexpr const uint8_t PIN_D5 = 41; + static constexpr const uint8_t PIN_D6 = 17; + static constexpr const uint8_t PIN_D7 = 16; + static constexpr const uint8_t PIN_B0 = CORE_PIN19_BIT; + static constexpr const uint8_t PIN_B1 = CORE_PIN18_BIT; + static constexpr const uint8_t PIN_B2 = CORE_PIN14_BIT; + static constexpr const uint8_t PIN_B3 = CORE_PIN15_BIT; + static constexpr const uint8_t PIN_B4 = CORE_PIN40_BIT; + static constexpr const uint8_t PIN_B5 = CORE_PIN41_BIT; + static constexpr const uint8_t PIN_B6 = CORE_PIN17_BIT; + static constexpr const uint8_t PIN_B7 = CORE_PIN16_BIT; + + int w = 320; + int h = 240; + + static constexpr const char* MOD = "ILI9486p"; + + static constexpr const uint8_t REG_SWRESET = 0x01; // Software Reset + static constexpr const uint8_t REG_RDDID = 0x04; // Read display identification information + static constexpr const uint8_t REG_RDDST = 0x09; // Read Display Status + static constexpr const uint8_t REG_CASET = 0x2A; // Column Address Set + static constexpr const uint8_t REG_PASET = 0x2B; // Page Address Set + static constexpr const uint8_t REG_RAMWR = 0x2C; // Memory Write + static constexpr const uint8_t REG_RAMRD = 0x2E; // Memory Read + + char pbuf[64]; + +public: + + ILI9486p() { + + if (PIN_RESET > 0) {MyGPIO::setOutput(PIN_RESET);} + if (PIN_CS > 0) {MyGPIO::setOutput(PIN_CS);} + MyGPIO::setOutput(PIN_RS); + MyGPIO::setOutput(PIN_WR); + MyGPIO::setOutput(PIN_RD); + + if (PIN_CS > 0) {MyGPIO::set(PIN_CS);} + MyGPIO::set(PIN_RD); + MyGPIO::set(PIN_WR); + + outMode(); + chipSelect(); + + /* + // see manual page 666 + uint32_t pus = 0b10; + uint32_t pue = 0b1; + uint32_t pke = 0; + uint32_t ode = 1; + uint32_t speed = 0b00; + uint32_t dse = 0b111; + uint32_t sre = 0b0; + uint32_t s = (pus<<14)|(pue<<13)|(pke<<12)|(ode<<11)|(sre<<0)|(dse<<3)|(speed<<6); + + + CORE_PIN14_PADCONFIG = s; + CORE_PIN15_PADCONFIG = s; + CORE_PIN16_PADCONFIG = s; + CORE_PIN17_PADCONFIG = s; + CORE_PIN18_PADCONFIG = s; + CORE_PIN19_PADCONFIG = s; + CORE_PIN40_PADCONFIG = s; + CORE_PIN41_PADCONFIG = s; + + CORE_PIN20_PADCONFIG = s; + CORE_PIN21_PADCONFIG = s; + CORE_PIN39_PADCONFIG = s; + CORE_PIN38_PADCONFIG = s; + */ + + IOMUXC_GPR_GPR26 = 0xffffffff; + + } + + /** perform display software reset and initialization */ + void init() { + + hardwareReset(); + + // send reset command + wait(); + sendCommand(REG_SWRESET); + wait(); + + //sendCommandAndData(0xb0, {0x00}); // Interface Mode Control + sendCommandAndData(0x11, {}); // disable sleep + + wait(); + + sendCommandAndData(0x3A, {0x55}); // Interface Pixel Format, 16 bits / pixel + sendCommandAndData(0x36, {0x28}); // Memory Access Control + sendCommandAndData(0xC2, {0x44}); // Power Control 3 (For Normal Mode) + sendCommandAndData(0xC5, {0x00, 0x00, 0x00, 0x00}); // VCOM Control + sendCommandAndData(0xE0, {0x0F, 0x1F, 0x1C, 0x0C, 0x0F, 0x08, 0x48, 0x98, 0x37, 0x0A, 0x13, 0x04, 0x11, 0x0D, 0x00}); // PGAMCTRL(Positive Gamma Control) + sendCommandAndData(0xE1, {0x0F, 0x32, 0x2E, 0x0B, 0x0D, 0x05, 0x47, 0x75, 0x37, 0x06, 0x10, 0x03, 0x24, 0x20, 0x00}); // NGAMCTRL (Negative Gamma Correction) + sendCommandAndData(0xE2, {0x0F, 0x32, 0x2E, 0x0B, 0x0D, 0x05, 0x47, 0x75, 0x37, 0x06, 0x10, 0x03, 0x24, 0x20, 0x00}); // Digital Gamma Control 1 + sendCommandAndData(0x36, {0x28}); // Memory Access Control, BGR + sendCommandAndData(0x11, {}), // # Sleep OUT + sendCommandAndData(0x29, {}), // Display ON + + wait(); + + } + + void sendCommandAndData(uint8_t cmd, std::initializer_list data) { + sendCommand(cmd); + for (uint8_t b : data) {sendData(b);} + } + +/* + uint32_t getID() { + outMode(); + sendCommand(REG_RDDID); + inMode(); + uint32_t res = 0xFFFFFFFF; + readBytes(reinterpret_cast(&res), 4); // dummy-byte + 3 data bytes + return res; + } + + void getStatus() { + uint8_t buf[5]; + //chipSelect(); + outMode(); + sendCommand(REG_RDDST); + uint32_t res = 0xFFFFFFFF; + inMode(); + readBytes(buf, 5); // dummy-byte + 4 data bytes + sprintf(pbuf, "Status: %02x %02x %02x %02x \n", buf[1],buf[2],buf[3],buf[4]); + Serial.print(pbuf); + //chipDeselect(); + } +*/ + + void fill(const uint16_t color) { + setAddrWindow(0,0,480,320); + modeDATA(); + for (uint32_t i = 0; i < 480*320; ++i) { + writeByte(color>>8); writeByte(color>>0); + } + } + + /** draw 5-6-5 encoded input data */ + void draw(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint16_t* data) { + setAddrWindow(x,y,w,h); + modeDATA(); + const uint32_t entries = uint32_t(w) * uint32_t(h); + for (uint32_t i = 0; i < entries; ++i) { + writeByte(data[i]>>8); writeByte(data[i]>>0); + } + } + + /** draw 5-6-5 encoded input, stretch X by 2 */ + void draw565x2(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint16_t* data) { + setAddrWindow(x,y,w,h); + modeDATA(); + for (uint32_t i = 0; i < w/2*h; ++i) { + writeByte(data[i]>>8); writeByte(data[i]>>0); + writeByte(data[i]>>8); writeByte(data[i]>>0); + } + } + + void beginDraw(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { + setAddrWindow(x,y,w,h); + modeDATA(); + } + void drawData(uint16_t* data, uint32_t len) { + for (uint32_t i = 0; i < len; ++i) { + writeByte(data[i]>>8); writeByte(data[i]>>0); + } + } + + + /** draw 5-6-5 encoded input, stretch X and Y by 2 */ + void draw565x2y2(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint16_t* data) { + setAddrWindow(x,y,w,h); + modeDATA(); + + uint32_t i = 0; + for (uint16_t y = 0; y < h/2; ++y) { + for (uint16_t x = 0; x < w/2; ++x) { + writeByte(data[i+x]>>8); writeByte(data[i+x]>>0); + writeByte(data[i+x]>>8); writeByte(data[i+x]>>0); + } + for (uint16_t x = 0; x < w/2; ++x) { + writeByte(data[i+x]>>8); writeByte(data[i+x]>>0); + writeByte(data[i+x]>>8); writeByte(data[i+x]>>0); + } + i += w/2; + } + } + + /** draw 3-3-2 encoded input data */ + void draw332(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint8_t* data) { + setAddrWindow(x,y,w,h); + modeDATA(); + for (uint32_t i = 0; i < w*h; ++i) { + const uint8_t t = data[i]; + const uint16_t c565 = (t&0b11100000)<<8 | (t&0b000111000)<<6 | (t&0b00000011)<<3; + writeByte(c565 >> 8); + writeByte(c565 >> 0); + } + } + + + void fillRand() { + setAddrWindow(0,0,w,h); + modeDATA(); + const uint32_t entries = uint32_t(w) * uint32_t(h); + for (uint32_t i = 0; i < entries; ++i) { + const uint16_t rnd = rand(); + writeByte(rnd>>8); writeByte(rnd>>0); + } + } + + +private: + + void wait() { + //vTaskDelay(250 / portTICK_PERIOD_MS); + delay(250); + } + + void setAddrWindow(const uint16_t x1, const uint16_t y1, const uint16_t w, const uint16_t h) { + + // end (x,y) + const uint16_t x2 = x1 + w - 1; + const uint16_t y2 = y1 + h - 1; + + sendCommand(REG_CASET); // Column addr set + sendData(x1 >> 8); //Set the horizontal starting point to the high octet + sendData(x1 & 0xff); //Set the horizontal starting point to the low octet + sendData(x2 >> 8); //Set the horizontal end to the high octet + sendData(x2 & 0xff); //Set the horizontal end to the low octet + + sendCommand(REG_PASET); // Row addr set + sendData(y1 >> 8); + sendData(y1 & 0xff ); + sendData(y2 >> 8); + sendData(y2 & 0xff); + + sendCommand(REG_RAMWR); // write to RAM + + // transmit data now + + } + + + + + /** send the given command to the display */ + void sendCommand(const uint8_t cmd) { + modeCMD(); + writeByte(cmd); + } + + /** send the given data to the display */ + void sendData(const uint8_t data) { + modeDATA(); + writeByte(data); + } + + /** send the given data to the display */ + void writeBytes(const uint8_t* data, const uint32_t len) { + for (uint32_t i = 0; i < len; ++i) { + writeByte(data[i]); + } + } + +// /** read the given data from the display */ +// void readBytes(uint8_t* data, const uint32_t len) { +// for (uint32_t i = 0; i < len; ++i) { +// data[i] = readByte(); +// } +// } + + + + /* + void inMode() { + MyGPIO::setInput(PIN_D0); + MyGPIO::setInput(PIN_D1); + MyGPIO::setInput(PIN_D2); + MyGPIO::setInput(PIN_D3); + MyGPIO::setInput(PIN_D4); + MyGPIO::setInput(PIN_D5); + MyGPIO::setInput(PIN_D6); + MyGPIO::setInput(PIN_D7); + } + + + uint8_t readByte() { + + waitShort(); + modeDATA(); + + chipSelect(); + + MyGPIO::clear(PIN_RD); // commit + waitShort(); + MyGPIO::set(PIN_RD); + waitShort(); + + uint8_t tmp = + (MyGPIO::get(PIN_D0) << 0) | + (MyGPIO::get(PIN_D1) << 1) | + (MyGPIO::get(PIN_D2) << 2) | + (MyGPIO::get(PIN_D3) << 3) | + (MyGPIO::get(PIN_D4) << 4) | + (MyGPIO::get(PIN_D5) << 5) | + (MyGPIO::get(PIN_D6) << 6) | + (MyGPIO::get(PIN_D7) << 7); + + chipDeselect(); + + return tmp; + + } + */ + + void outMode() { + MyGPIO::setOutput(PIN_D0); + MyGPIO::setOutput(PIN_D1); + MyGPIO::setOutput(PIN_D2); + MyGPIO::setOutput(PIN_D3); + MyGPIO::setOutput(PIN_D4); + MyGPIO::setOutput(PIN_D5); + MyGPIO::setOutput(PIN_D6); + MyGPIO::setOutput(PIN_D7); + } + + //static constexpr uint32_t mask = 0b11111111 << 16; + static constexpr uint32_t mask = 0b11111111 << 16; + + uint8_t lastSent = 0; + + inline void writeByte(uint8_t b) { + + // fastest option and does not require reading from the bus?? + GPIO6_DR_TOGGLE = (lastSent ^ b) << 16; + lastSent = b; + + //GPIO6_DR = (GPIO6_DR & ~mask) | (b << 16); + + // commit on rising edge + MyGPIO::clear(PIN_WR); + MyGPIO::set(PIN_WR); + + } + + + + + /* + static constexpr uint32_t mask = (1<>0 & 1)<>1 & 1)<>2 & 1)<>3 & 1)<>4 & 1)<>5 & 1)<>6 & 1)<>7 & 1)< 0) { + MyGPIO::clear(PIN_RESET); + delay(250); + MyGPIO::set(PIN_RESET); + delay(400); + } + } + + /** select the display (CS=0) */ + inline void chipSelect() { + if (PIN_CS > 0) {MyGPIO::clear(PIN_CS);} + } + + /** unselect the display (CS=1) */ + inline void chipDeselect() { + if (PIN_CS > 0) {MyGPIO::set(PIN_CS);} + } + + /** switch to command-mode */ + inline void modeCMD() { + MyGPIO::clear(PIN_RS); + } + + /** switch to data-mode */ + inline void modeDATA() { + MyGPIO::set(PIN_RS); + } + + +}; + +#pragma GCC pop_options + + +#endif diff --git a/ext/sd/MBR.h b/ext/sd/MBR.h new file mode 100644 index 0000000..a9639f4 --- /dev/null +++ b/ext/sd/MBR.h @@ -0,0 +1,73 @@ +#pragma once + +template class MBR { + + BlockDev& dev; + bool present = false; + bool valid = false; + + /** describes one partition */ + class Partition { + + friend class MBR; + + uint8_t type; + uint32_t firstSector; + uint32_t numSectors; + + public: + + bool isPresent() const {return type != 0;} + uint8_t getType() const {return type;} + uint32_t getFirstSector() const {return firstSector;} + uint32_t getNumSectors() const {return numSectors;} + + }; + + Partition partitions[4]; + +public: + + MBR(BlockDev& dev) : dev(dev) { + read(); + } + + bool isPresent() const {return present;} + bool isValid() const {return valid;} + + Partition& getPartition(uint8_t idx) { + return partitions[idx]; + } + +private: + + void read() { + + uint8_t buf[512]; + dev.read(0x00000000, 512, buf); + present = (buf[510] == 0x55) && (buf[511] == 0xAA); + valid = (buf[0x01BC] == 0x00) && (buf[0x01BD] == 0x00); + + if (present && valid) { + + // parse all 4 partitions + for (uint8_t i = 0; i < 4; ++i) { + + const uint8_t* src = &buf[0x1BE + i*16]; + Partition* p = &partitions[i]; + + p->type = src[0x4]; + p->firstSector = getU32(src, 8); + p->numSectors = getU32(src, 12); + + } + + } + + } + + uint32_t getU32(const uint8_t* src, const uint8_t offset) { + return src[offset+0]<<0 | src[offset+1]<<8 | src[offset+2]<<16 | src[offset+3]<<24; + } + +}; diff --git a/ext/sd/SDCard.h b/ext/sd/SDCard.h new file mode 100644 index 0000000..2bbe756 --- /dev/null +++ b/ext/sd/SDCard.h @@ -0,0 +1,302 @@ +#pragma once + +#include "../../io/GPIO.h" +#include "../../Debug.h" + +#define TEENSY_SD_PIN_CS 46 +#define TEENSY_SD_PIN_MOSI 45 +#define TEENSY_SD_PIN_CLK 44 +#define TEENSY_SD_PIN_MISO 43 + + +// http://www.dejazzer.com/ee379/lecture_notes/lec12_sd_card.pdf +// https://www.convict.lu/pdf/ProdManualSDCardv1.9.pdf +// http://rjhcoding.com/avrc-sd-interface-3.php +template class SDCard { + + SPI& spi; + + union R1 { // SanDisk Manual Page 5-13 + uint8_t raw; + struct { + uint8_t inIdleState : 1; + uint8_t eraseReset : 1; + uint8_t illegalCommand : 1; + uint8_t crcError : 1; + uint8_t eraseSeqError : 1; + uint8_t addressError : 1; + uint8_t paramError : 1; + uint8_t zero : 1; + }; + R1() {} + R1(uint8_t val) : raw(val) {} + }; + + union R3 { // response to OCR command, SanDisk Manual Page 5-14 + uint8_t raw[5]; + struct { + R1 state; + uint8_t busy : 1; + uint8_t reserved : 7; + uint32_t allowedVoltages: 20; + uint8_t checkPattern : 4; + }; + }; + + union Rcmd8 { // response to cmd8 + uint8_t raw[5]; + struct { + R1 state; + uint8_t data[4]; + }; + }; + + + + static constexpr const char* NAME = "SDCard"; + +public: + + SDCard(SPI& spi) : spi(spi) { + + } + + bool init() { + + MyGPIO::setOutput(PIN_CS); + debugMod(NAME, "init()"); + + // RESET: MOSI = 1, CS = 1 (deselected!!), at least 74 Clocks + deselect(); + for (uint8_t i = 0; i < 10; ++i) {spi.writeByte(0xFF);} + + // switch to SPI mode + for (uint8_t i = 0; ; ++i) { + const R1 res = cmd0(); + if (res.raw == 1) {break;} + if (i == 4) {debugMod(NAME, "init failed"); return false;} + delay(50); + } + + + // TODO? + // Version 2.0 Card + Rcmd8 r8 = cmd8(); + + if (r8.state.raw == 1) { + debugMod(NAME, "V2/V3 Card"); + if (r8.data[2] == 0x01 && r8.data[3] == 0xAA) { + debugMod(NAME, "Pattern Correct"); + } else { + debugMod(NAME, "Pattern Mismatch"); + return false; + } + } + + // Version 1.0 Card + //R3 res = cmd58(); + + + + uint32_t hcs = 1<<30; + + for (uint8_t i = 0; ; ++i) { + delay(100); + R1 r1 = acmd41(hcs); + if (i == 8) {debugMod(NAME, "init failed"); return false;} + if (r1.raw == 0) {break;} // finished + if (r1.raw == 1) {continue;} // card is still idle + if (r1.raw > 1) {debugMod(NAME, "init failed"); return false;} + } + + debugMod(NAME, "init OK"); + return true; + + } + + uint32_t read(uint32_t addr, uint32_t size, uint8_t* dst) { + + uint32_t read = 0; + uint32_t readAddr = addr & (0xFFFFFFFF - 512); // 512 byte aligned staring address + + if (readAddr != addr) { // non-aligned first read? + uint8_t buf[512]; + uint16_t cOff = (addr-readAddr); // skip the unneeded bytes + uint16_t cSize = 512 - cOff; + readSingleBlock(readAddr, buf); // read a full block + memcpy(dst, buf+cOff, cSize); // copy only the required bytes + size -= cSize; + dst += cSize; + read += cSize; + readAddr += 512; + } + + while(size >= 512) { + readSingleBlock(readAddr, dst); + readAddr += 512; + dst += 512; + read += 512; + size -= 512; + } + + if (size > 0) { + uint8_t buf[512]; + readSingleBlock(readAddr, buf); + memcpy(dst, buf, size); + read += size; + } + + return read; + + } + + /** read a single block of 512 bytes */ + bool readSingleBlock(uint32_t addr, uint8_t* dst) { + + addr = addr / 512; + sendCMD(17, addr>>24, addr>>16, addr>>8, addr>>0, 0xFF); + R1 res; readResponse(&res.raw, 1); + debugMod2(NAME, "readSingleBlock(%08x): %02x", addr, res.raw); + + // read command OK? + if (res.raw != 0) {endCMD(); return false;} + + // wait for data to become available + for (uint16_t i = 0; ; ++i) { + uint8_t res = spi.readWriteByte(0xFF); + if (res == 0xFE) {break;} // available! + if (res != 0xFF) {debugMod(NAME, "invalid"); endCMD(); return false;} // invalid response + if (i > 1024) {debugMod(NAME, "timeout"); endCMD(); return false;} // timeout + } + + // read data + for (uint16_t i = 0; i < 512; ++i) { + dst[i] = spi.readWriteByte(0xFF); + } + + // done + endCMD(); + return true; + + } + +private: + + + /** send a new command */ + void sendCMD(uint8_t cmd, uint8_t arg0, uint8_t arg1, uint8_t arg2, uint8_t arg3, uint8_t crc) { + uint8_t buf[6]; + buf[0] = (0b01 << 6) | (cmd&0b111111); + buf[1] = arg0; + buf[2] = arg1; + buf[3] = arg2; + buf[4] = arg3; + buf[5] = ((crc&0b1111111) << 1) | 1; + select(); + spi.write(buf, 6); + } + + /** end the current command */ + void endCMD() { + deselect(); + //spi.writeByte(0xFF); // SanDisk Manual Page 5-6 (required for card to finish current operation) + } + + + R1 cmd0() { // SanDisk Manual Page 5-1 + sendCMD(0, 0x00, 0x00, 0x00, 0x00, 0x4A); + R1 res; readResponse(&res.raw, 1); + endCMD(); + debugMod1(NAME, "cmd0: %02x", res.raw); + return res; + } + + /** determine whether this is a V2 card?? */ + Rcmd8 cmd8() { + sendCMD(8, 0x00, 0x00, 0x01, 0xAA, 0b1000011); // correct? Needs Checksum!! + //select(); uint8_t tmp[] = {0b01001000, val>>24, val>>16, val>>8, val>>0, 0b00001111}; spi.write(tmp, 6); + //R3 res; readResponse(&res.raw[0], 1); // read the R1 part + //if (res.raw[0] == 1) { // command supported + // readResponse(&res.raw[1], 4); // read the rest of the response + //} + Rcmd8 res; readResponse(res.raw, 5); + endCMD(); + debugMod5(NAME, "cmd8: %02x %02x %02x %02x %02x", res.raw[0], res.raw[1], res.raw[2], res.raw[3], res.raw[4]); + return res; + } + + + + /** read OCR (Operating Conditions Register) */ + R3 cmd58() { + //select(); uint8_t tmp[] = {0b01111010, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b01110101}; spi.write(tmp, 6); + sendCMD(58, 0x00, 0x00, 0x00, 0x00, 0xFF);//0b0111010); + R3 res; readResponse(res.raw, 5); // typical response: 0x01 0x00 0xff 0x80 0x00 + endCMD(); + debugMod5(NAME, "cmd58: %02x %02x %02x %02x %02x", res.raw[0], res.raw[1], res.raw[2], res.raw[3], res.raw[4]); + return res; + } + + + + /** "next command is app specific" */ + R1 cmd55() { + sendCMD(55, 0x00, 0x00, 0x00, 0x00, 0xFF); + R1 res; readResponse(&res.raw, 1); + endCMD(); + debugMod1(NAME, "cmd55: %02x", res.raw); + return res; + } + + R1 acmd41(uint32_t val) { + cmd55(); + sendCMD(41, val>>24, val>>16, val>>8, val>>0, 0xFF); + R1 res; readResponse(&res.raw, 1); + endCMD(); + debugMod1(NAME, "acmd41: %02x", res.raw); + return res; + } + + + + + + + + void readResponse(uint8_t* dst, uint8_t len) { + + // the SD card takes some time to answer, but must receive clock signals meanwhile! + // we detect the actual response as soon as the received byte starts has the highest bit zero. + // [while busy the SD card responds with 0xFF] + // NOTE: it is IMPORTANT to send 0xFF to the card while reading its responses! + + // wait for the first byte to arrive and read it + for (uint8_t i = 0; i < 4; ++i) { + dst[0] = spi.readWriteByte(0xFF); + if ( (dst[0] & 0x80) == 0 ) {break;} + } + + // read all subsequent bytes (if any) + for (uint8_t i = 1; i < len; ++i) { + dst[i] = spi.readWriteByte(0xFF); + } + + } + + + + /** select the card (CS = low) */ + void select() { + spi.writeByte(0xFF); + MyGPIO::clear(PIN_CS); + spi.writeByte(0xFF); + } + + /** deselect the card (CS = high) */ + void deselect() { + spi.writeByte(0xFF); + MyGPIO::set(PIN_CS); + spi.writeByte(0xFF); + } + +}; diff --git a/ext/sd/fat32/DirIterator.h b/ext/sd/fat32/DirIterator.h new file mode 100644 index 0000000..d8459f3 --- /dev/null +++ b/ext/sd/fat32/DirIterator.h @@ -0,0 +1,45 @@ +class DirIterator { + + FS& fs; + ClusterNr nextCluster; + int curEntryInCluster; + uint8_t buf[512]; + +public: + + DirIterator(FS& fs, ClusterNr clusterNr) : fs(fs), nextCluster(clusterNr), curEntryInCluster(255) { + + } + + bool hasNext() { + + while(true) { + + ++curEntryInCluster; + + // reached end of cluster? load the next one + if (curEntryInCluster > fs.tmp.dirEntriesPerSector) { + fs.dev.read(fs.clusterToAbsPos(nextCluster), 512, buf); + nextCluster = fs.getNextCluster(nextCluster); + curEntryInCluster = 0; + } + + DirEntry* desc = reinterpret_cast(buf + (sizeof(DirEntry) * curEntryInCluster)); + + if (desc->isLongFileName()) {continue;} + if (desc->isUnused()) {continue;} + if (desc->isEndOfDirectory()) {return false;} + + return true; + + } + + } + + DirEntry next() { + DirEntry* de = reinterpret_cast(buf + (sizeof(DirEntry) * curEntryInCluster)); + return *de; + } + +}; + diff --git a/ext/sd/fat32/FS.h b/ext/sd/fat32/FS.h new file mode 100644 index 0000000..275aa5e --- /dev/null +++ b/ext/sd/fat32/FS.h @@ -0,0 +1,100 @@ +#pragma once + +#include +#include "Structs.h" + +// https://www.pjrc.com/tech/8051/ide/fat32.html +namespace FAT32 { + + template class FS { + + static constexpr const char* NAME = "FAT32"; + + BlockDev& dev; + AbsOffset offset; + + + FSDesc desc; + Precomputed tmp; + bool valid = false; + + + public: + + #include "File.h" + #include "DirIterator.h" + + /** ctor with the absolute offset addr (in bytes) */ + FS(BlockDev& dev, AbsOffset offset) : dev(dev), offset(offset) { + init(); + } + + /** is the detected FS valid? */ + bool isValid() const { + return valid; + } + + /** get an iterator for the root directory */ + DirIterator getRoot() { + return DirIterator(*this, desc.rootDirFirstCluster); + } + + /** open the given file for reading*/ + File open(const DirEntry& de) { + return File(*this, de.getFirstCluster(), de.size); + } + + private: + + void init() { + + debugMod3(NAME") + uint8_t buf[512]; + dev.read(offset, 512, buf); + + desc.bytesPerSector = getU16(&buf[0x0B]); + desc.sectorsPerCluster = getU8(&buf[0x0D]); + desc.numReservedSectors = getU16(&buf[0x0E]); + desc.numberOfFATs = getU8(&buf[0x10]); + desc.sectorsPerFAT = getU32(&buf[0x24]); + desc.rootDirFirstCluster = getU32(&buf[0x2C]); + + // basic sanity check based on constants + valid = (desc.bytesPerSector == 512) && (desc.numberOfFATs == 2) && (getU16(&buf[0x1FE]) == 0xAA55); + + tmp.bytesPerCluster = desc.sectorsPerCluster * desc.bytesPerSector; + tmp.startOfFAT = offset + (desc.numReservedSectors * desc.bytesPerSector); + tmp.startOfFirstDataCluster = offset + (desc.numReservedSectors * desc.bytesPerSector) + (desc.numberOfFATs * desc.sectorsPerFAT * desc.bytesPerSector); + tmp.startOfFirstRootDirCluster = clusterToAbsPos(desc.rootDirFirstCluster); + tmp.dirEntriesPerSector = desc.bytesPerSector / sizeof(DirEntry); + + /* + std::cout << (int)desc.bytesPerSector << std::endl; + std::cout << (int)desc.sectorsPerCluster << std::endl; + std::cout << (int)desc.numReservedSectors << std::endl; + std::cout << (int)desc.numberOfFATs << std::endl; + std::cout << (int)desc.sectorsPerFAT << std::endl; + std::cout << (int)desc.rootDirFirstCluster << std::endl; + + std::cout << tmp.startOfFAT << std::endl; + std::cout << tmp.startOfFirstDataCluster << std::endl; + std::cout << tmp.startOfFirstRootDirCluster << std::endl; + */ + + } + + /** determine the ClusterNr following the given ClusterNr */ + ClusterNr getNextCluster(ClusterNr clusterNr) { + const AbsPos pos = tmp.startOfFAT + ((clusterNr) * sizeof(uint32_t)); + ClusterNr next; + int read = dev.read(pos, 4, reinterpret_cast(&next)); + return next; + } + + /** convert ClusterNr into an absolute position on disk */ + AbsPos clusterToAbsPos(ClusterNr clusterNr) { + return tmp.startOfFirstDataCluster + ((clusterNr - 2) * desc.sectorsPerCluster * desc.bytesPerSector); + } + + }; +} diff --git a/ext/sd/fat32/FS.h.autosave b/ext/sd/fat32/FS.h.autosave new file mode 100644 index 0000000..543f305 --- /dev/null +++ b/ext/sd/fat32/FS.h.autosave @@ -0,0 +1,100 @@ +#pragma once + +#include +#include "Structs.h" + +// https://www.pjrc.com/tech/8051/ide/fat32.html +namespace FAT32 { + + template class FS { + + static constexpr const char* NAME = "FAT32"; + + BlockDev& dev; + AbsOffset offset; + + + FSDesc desc; + Precomputed tmp; + bool valid = false; + + + public: + + #include "File.h" + #include "DirIterator.h" + + /** ctor with the absolute offset addr (in bytes) */ + FS(BlockDev& dev, AbsOffset offset) : dev(dev), offset(offset) { + init(); + } + + /** is the detected FS valid? */ + bool isValid() const { + return valid; + } + + /** get an iterator for the root directory */ + DirIterator getRoot() { + return DirIterator(*this, desc.rootDirFirstCluster); + } + + /** open the given file for reading*/ + File open(const DirEntry& de) { + return File(*this, de.getFirstCluster(), de.size); + } + + private: + + void init() { + + debugMod3(NAME) + uint8_t buf[512]; + dev.read(offset, 512, buf); + + desc.bytesPerSector = getU16(&buf[0x0B]); + desc.sectorsPerCluster = getU8(&buf[0x0D]); + desc.numReservedSectors = getU16(&buf[0x0E]); + desc.numberOfFATs = getU8(&buf[0x10]); + desc.sectorsPerFAT = getU32(&buf[0x24]); + desc.rootDirFirstCluster = getU32(&buf[0x2C]); + + // basic sanity check based on constants + valid = (desc.bytesPerSector == 512) && (desc.numberOfFATs == 2) && (getU16(&buf[0x1FE]) == 0xAA55); + + tmp.bytesPerCluster = desc.sectorsPerCluster * desc.bytesPerSector; + tmp.startOfFAT = offset + (desc.numReservedSectors * desc.bytesPerSector); + tmp.startOfFirstDataCluster = offset + (desc.numReservedSectors * desc.bytesPerSector) + (desc.numberOfFATs * desc.sectorsPerFAT * desc.bytesPerSector); + tmp.startOfFirstRootDirCluster = clusterToAbsPos(desc.rootDirFirstCluster); + tmp.dirEntriesPerSector = desc.bytesPerSector / sizeof(DirEntry); + + /* + std::cout << (int)desc.bytesPerSector << std::endl; + std::cout << (int)desc.sectorsPerCluster << std::endl; + std::cout << (int)desc.numReservedSectors << std::endl; + std::cout << (int)desc.numberOfFATs << std::endl; + std::cout << (int)desc.sectorsPerFAT << std::endl; + std::cout << (int)desc.rootDirFirstCluster << std::endl; + + std::cout << tmp.startOfFAT << std::endl; + std::cout << tmp.startOfFirstDataCluster << std::endl; + std::cout << tmp.startOfFirstRootDirCluster << std::endl; + */ + + } + + /** determine the ClusterNr following the given ClusterNr */ + ClusterNr getNextCluster(ClusterNr clusterNr) { + const AbsPos pos = tmp.startOfFAT + ((clusterNr) * sizeof(uint32_t)); + ClusterNr next; + int read = dev.read(pos, 4, reinterpret_cast(&next)); + return next; + } + + /** convert ClusterNr into an absolute position on disk */ + AbsPos clusterToAbsPos(ClusterNr clusterNr) { + return tmp.startOfFirstDataCluster + ((clusterNr - 2) * desc.sectorsPerCluster * desc.bytesPerSector); + } + + }; +} diff --git a/ext/sd/fat32/File.h b/ext/sd/fat32/File.h new file mode 100644 index 0000000..f7be642 --- /dev/null +++ b/ext/sd/fat32/File.h @@ -0,0 +1,64 @@ +class File { + + static constexpr const int32_t F_EOF = -1; + + FS& fs; + uint32_t totalSize; + + uint32_t curAbsPos = 0; // position withithin the whole file + uint32_t curCluster = 0; // cluster we are currently reading + uint32_t posInCluster = 0; // position within the current cluster + + +public: + + File(FS& fs, uint32_t firstCluster, uint32_t size) : fs(fs), totalSize(size), curCluster(firstCluster) { + + } + + /** the file's size */ + uint32_t getSize() const {return totalSize;} + + uint32_t read(uint32_t size, uint8_t* dst) { + uint32_t total = 0; + while(true) { + const uint32_t read = _read(size, dst); + if (read == F_EOF) {break;} + size -= read; + dst += read; + total += read; + } + return total; + } + +private: + + int32_t _read(uint32_t readSize, uint8_t* dst) { + + // EOF reached? + if (curAbsPos >= totalSize) { + return F_EOF; + } + + // end of current cluster reached? -> determine the next one + if (posInCluster == fs.tmp.bytesPerCluster) { + curCluster = fs.getNextCluster(curCluster); + posInCluster = 0; + } + + // how many bytes are left in the current cluster? + const uint32_t remainingInCluster = fs.tmp.bytesPerCluster - posInCluster; + + // determine how many bytes to read + const uint32_t toRead = std::min(remainingInCluster, readSize); + + const uint32_t offset = fs.clusterToAbsPos(curCluster) + posInCluster; + const uint32_t read = fs.dev.read(offset, toRead, dst); + posInCluster += read; + curAbsPos += read; + + return read; + + } + +}; diff --git a/ext/sd/fat32/Structs.h b/ext/sd/fat32/Structs.h new file mode 100644 index 0000000..3cd3271 --- /dev/null +++ b/ext/sd/fat32/Structs.h @@ -0,0 +1,77 @@ +#pragma once + +#include +#include + +namespace FAT32 { + + using ClusterNr = uint32_t; + using AbsOffset = uint32_t; + using AbsPos = uint32_t; + + struct FSDesc { + uint16_t bytesPerSector; + uint8_t sectorsPerCluster; + uint16_t numReservedSectors; + uint8_t numberOfFATs; + uint32_t sectorsPerFAT; + ClusterNr rootDirFirstCluster; + }; + + struct Precomputed { + uint32_t bytesPerCluster; + AbsPos startOfFAT; // absolute byte offset where the FAT begins + AbsPos startOfFirstDataCluster; // absolute byte offset where the first cluster is + AbsPos startOfFirstRootDirCluster; // absolute byte offset where the first root dir cluster is + uint32_t dirEntriesPerSector; + }; + + union Attributes { + uint8_t raw; + struct { + uint8_t readOnly : 1; + uint8_t hidden : 1; + uint8_t system : 1; + uint8_t volumeID : 1; + uint8_t directory : 1; + uint8_t archive : 1; + uint8_t unused1 : 1; + uint8_t unused2 : 1; + } bits; + } __attribute__((packed)); + + struct DirEntry { + + unsigned char name[8]; + unsigned char ext[3]; + Attributes attr; + uint8_t dummy1[8]; + uint16_t firstClusterHi; + uint8_t dummy2[4]; + uint16_t firstClusterLo; + uint32_t size; // file size in bytes + + bool isUnused() const {return name[0] == 0xE5;} + bool isDirectory() const {return attr.bits.directory;} + bool isEndOfDirectory() const {return name[0] == 0x00;} + bool isLongFileName() const {return (attr.raw & 0b1111) == 0b1111;} + + uint32_t getFirstCluster() const {return firstClusterHi<<16 | firstClusterLo<<0;} + + std::string getName() const { + char buf[16]; + uint8_t pos = 0; + for (uint8_t i = 0; i < 8; ++i) {if (name[i] != ' ') {buf[pos++] = name[i];}} + buf[pos++] = '.'; + for (uint8_t i = 0; i < 8; ++i) {if ( ext[i] != ' ') {buf[pos++] = ext[i];}} + buf[pos] = 0; + return std::string(buf); + } + + } __attribute__((packed)); + + static inline uint16_t getU8(const uint8_t* src) {return src[0];} + static inline uint16_t getU16(const uint8_t* src) {return src[0]<<0 | src[1]<<8;} + static inline uint32_t getU32(const uint8_t* src) {return src[0]<<0 | src[1]<<8 | src[2]<<16 | src[3]<<24;} + +} diff --git a/ext/sd/main.cpp b/ext/sd/main.cpp new file mode 100644 index 0000000..8358304 --- /dev/null +++ b/ext/sd/main.cpp @@ -0,0 +1,66 @@ +#include +#include + +#include "MBR.h" +#include "fat32/FS.h" + +class Simu { + FILE* f; +public: + + Simu(const char* image) { + f = fopen(image, "rb"); + if (!f) {throw std::runtime_error("failed to open");} + } + + uint32_t read(uint32_t addr, uint32_t size, uint8_t* dst) { + fseek(f, addr, SEEK_SET); + return fread(dst, size, 1, f) * size; + } + +}; + +int main(void) { + + // diff /tmp/ram/TETRIS.GB /apps/workspace/gbemu/tests/tetris.gb + + Simu simu("/tmp/ram/1.dat"); + + MBR mbr(simu); + std::cout << mbr.isPresent() << std::endl; + std::cout << mbr.isValid() << std::endl; + + if (mbr.isPresent() && mbr.isValid()) { + + for (int i = 0; i < 4; ++i) { + std::cout << (int) mbr.getPartition(i).getType() << " - " << mbr.getPartition(i).getFirstSector() << " - " << mbr.getPartition(i).getNumSectors() << std::endl; + } + + using FAT32FS = FAT32::FS; + + FAT32FS fat(simu, mbr.getPartition(0).getFirstSector() * 512); + std::cout << "valid: " << fat.isValid() << std::endl; + + FAT32FS::DirIterator dir = fat.getRoot(); + while(dir.hasNext()) { + FAT32::DirEntry de = dir.next(); + std::cout << de.getName() << std::endl; + FAT32FS::File f = fat.open(de); + + uint8_t* bufff = (uint8_t*) malloc(1024*1024); + uint32_t read = f.read(f.getSize(), bufff); + + std::string name = de.getName(); + std::ofstream out("/tmp/ram/" + name); + out.write((char*)bufff, read); + out.close(); + + } + + // diff /tmp/ram/TETRIS.GB /apps/workspace/gbemu/tests/tetris.gb + // diif /tmp/ram/KIRBY1.GB /apps/workspace/gbemu/tests/Kirby\'s\ Dream\ Land\ \(USA\,\ Europe\).gb + + } + + +} diff --git a/ext/sens/INA219.h b/ext/sens/INA219.h index ae0ceb6..876403c 100644 --- a/ext/sens/INA219.h +++ b/ext/sens/INA219.h @@ -60,6 +60,7 @@ public: return i2c.query(ADDR); } + /* void dumpConfig() { const uint16_t cfg = getConfig(); const uint8_t mode = (cfg & 0b00000000000111) >> 0; @@ -75,6 +76,7 @@ public: if (brng) {printf("- bus voltage range [0:16]\n");} else {printf("- bus voltage range [0:32]\n");} } + */ private: diff --git a/io/GPIO.h b/io/GPIO.h index 78313b1..0fd9a17 100644 --- a/io/GPIO.h +++ b/io/GPIO.h @@ -184,15 +184,19 @@ public: - static void setOutput(uint8_t pin) {pinMode(pin, OUTPUT);} + static inline void setOutput(uint8_t pin) {pinMode(pin, OUTPUT);} - static void setInput(uint8_t pin) {pinMode(pin, INPUT);} + static inline void setInput(uint8_t pin) {pinMode(pin, INPUT);} - static void clear(uint8_t pin) {digitalWriteFast(pin, 0);} + static inline void clear(uint8_t pin) {digitalWriteFast(pin, 0);} + + static inline void set(uint8_t pin) {digitalWriteFast(pin, 1);} + + static inline void setOrClear(uint8_t pin, uint8_t val) {digitalWriteFast(pin, val);} + + static inline uint8_t get(uint8_t pin) {return digitalReadFast(pin);} - static void set(uint8_t pin) {digitalWriteFast(pin, 1);} - static uint8_t get(uint8_t pin) {return digitalReadFast(pin);} }; diff --git a/io/HardSPI.h b/io/HardSPI.h index 3ef0ff4..ecea2cb 100644 --- a/io/HardSPI.h +++ b/io/HardSPI.h @@ -57,7 +57,7 @@ private: // DEVICE specific options memset(&devcfg, 0, sizeof(devcfg)); - devcfg.clock_speed_hz = 20 * 1000 * 1000; // IMPORTANT! + devcfg.clock_speed_hz = 30 * 1000 * 1000; // IMPORTANT! devcfg.mode = 0; devcfg.spics_io_num = -1;//PIN_NUM_CS; // currently not used devcfg.queue_size = 2; diff --git a/io/SoftI2C.h b/io/SoftI2C.h index d6333c8..94b7eb4 100644 --- a/io/SoftI2C.h +++ b/io/SoftI2C.h @@ -24,7 +24,7 @@ template class SoftI2C { inline void sclHi() {MyGPIO::set(PIN_SCL);} inline void sclLo() {MyGPIO::clear(PIN_SCL);} - inline void waitLong() { + void waitLong() { if (fast) { for (uint8_t i = 0; i < 128; ++i) { __asm__ __volatile__("nop"); @@ -36,7 +36,7 @@ template class SoftI2C { } } - inline void waitShort() { + void waitShort() { if (fast) { for (uint8_t i = 0; i < 16; ++i) { __asm__ __volatile__("nop"); @@ -114,13 +114,13 @@ public: // write one byte to the bus and check slave's ACK/NACK - bool IN_FLASH writeByteAndCheck(const uint8_t byte) { + bool writeByteAndCheck(const uint8_t byte) { _writeByte(byte); return _readAck(); } // write several bytes to the bus and check slave's ACK/NACK - bool IN_FLASH writeBytesAndCheck(const uint8_t* src, uint8_t len) { + bool writeBytesAndCheck(const uint8_t* src, uint8_t len) { while(len) { const bool ok = writeByteAndCheck(*src); if (!ok) {return false;} @@ -157,7 +157,7 @@ public: debugMod1(NAME, "found %d", i) } waitLong(); - if (i%16 == 0) {vTaskDelay(100 / portTICK_PERIOD_MS);} + if (i%16 == 0) {delay(10);} } } @@ -242,9 +242,10 @@ private: sclHi(); // clock pulse and read waitShort(); const uint8_t val = sdaRead(); + waitShort(); sclLo(); waitShort(); - return val; + return val ? 1 : 0; } @@ -267,6 +268,7 @@ private: inline bool _readAck() { sdaDirIn(); // switch to input mode + waitShort(); const uint8_t res = _readBit(); //printf("%d\n", res); return res == 0; // ACK when input pulled low diff --git a/io/SoftSPI.h b/io/SoftSPI.h index 9eeb723..564f356 100644 --- a/io/SoftSPI.h +++ b/io/SoftSPI.h @@ -6,13 +6,18 @@ #include "../Debug.h" #pragma GCC push_options -#pragma GCC optimize ("O2") +#pragma GCC optimize ("O3") -template class SoftSPI { +// NOTE: the last template argument has changed from "bool fast" to "int slowdown" where 0 is now fastest! +template class SoftSPI { static constexpr const char* NAME = "softSPI"; + #ifndef BIT + #define BIT(x) (1<