diff --git a/esp_idf/s3_ulp_io.inl b/esp_idf/s3_ulp_io.inl index a8df797..5e759d0 100644 --- a/esp_idf/s3_ulp_io.inl +++ b/esp_idf/s3_ulp_io.inl @@ -10,19 +10,14 @@ #define DISABLED 3 #define PROGMEM +#define memcpy_P memcpy +#define pgm_read_byte(a) *(uint8_t *)a + #define HIGH 1 #define LOW 0 #define PIN_SDA 6 #define PIN_SCL 7 -// Pinout for the LilyGo mini-epaper S3 -#define PIN_BUSY 10 -#define PIN_RST 11 -#define PIN_DC 12 -#define PIN_CS 13 -#define PIN_SCK 14 -#define PIN_MOSI 15 - // Pinout for my custom ESP32-S3 circuit //#define PIN_BUSY 12 //#define PIN_RST 7 @@ -34,6 +29,19 @@ // CPU cycles per bit #define I2C_DELAY 40 +void delayMicroseconds(long l) +{ + l *= 40; + while (l) { + __asm__ __volatile__ ("nop"); + l--; + } +} +void delay(long l) +{ + delayMicroseconds(l*1000); +} + void digitalWrite(int iPin, int iState) { ulp_riscv_gpio_output_level(iPin, iState); } @@ -61,39 +69,94 @@ int digitalRead(int iPin) { return (int)ulp_riscv_gpio_get_level(iPin); } /* digitalRead() */ +// +// De-initialize the GPIO pins +// +void bbepDeInitIO(BBEPDISP *pBBEP) +{ + pinMode(pBBEP->iDCPin, DISABLED); + pinMode(pBBEP->iCSPin, DISABLED); + pinMode(pBBEP->iRSTPin, DISABLED); + pinMode(pBBEP->iBUSYPin, DISABLED); + pinMode(pBBEP->iMOSIPin, DISABLED); + pinMode(pBBEP->iCLKPin, DISABLED); +} /* bbepDeInitIO() */ +// +// Initialize the GPIO pins and SPI for use by bb_epaper +// +void bbepInitIO(BBEPDISP *pBBEP, uint8_t u8DC, uint8_t u8RST, uint8_t u8BUSY, uint8_t u8CS, uint8_t u8MOSI, uint8_t u8SCK, uint32_t u32Speed) +{ + (void)u32Speed; // irrelevant for the ULP + pBBEP->iDCPin = u8DC; + pBBEP->iCSPin = u8CS; + pBBEP->iMOSIPin = u8MOSI; + pBBEP->iCLKPin = u8SCK; + pBBEP->iRSTPin = u8RST; + pBBEP->iBUSYPin = u8BUSY; + + pinMode(pBBEP->iDCPin, OUTPUT); + pinMode(pBBEP->iRSTPin, OUTPUT); + digitalWrite(pBBEP->iRSTPin, LOW); + delay(100); + digitalWrite(pBBEP->iRSTPin, HIGH); + delay(100); + if (pBBEP->iBUSYPin != 0xff) { + pinMode(pBBEP->iBUSYPin, INPUT); + } + pinMode(pBBEP->iCSPin, OUTPUT); + pinMode(pBBEP->iMOSIPin, OUTPUT); + pinMode(pBBEP->iCLKPin, OUTPUT); + digitalWrite(pBBEP->iCSPin, HIGH); // we have to manually control the CS pin +} /* bbepInitIO() */ + // // Bit Bang SPI // -void SPIWriteByte(uint8_t uc) +void SPIWriteByte(BBEPDISP *pBBEP, uint8_t uc) { +uint8_t u8MOSI = pBBEP->iMOSIPin; +uint8_t u8SCK = pBBEP->iCLKPin; + if (uc == 0 || uc == 0xff) { // special case - ulp_riscv_gpio_output_level(PIN_MOSI, (uc & 1)); + ulp_riscv_gpio_output_level(u8MOSI, (uc & 1)); for (int i=0; i<8; i++) { - ulp_riscv_gpio_output_level(PIN_SCK, 1); // no delays needed since it runs at a slow clock - ulp_riscv_gpio_output_level(PIN_SCK, 0); + ulp_riscv_gpio_output_level(u8SCK, 1); // no delays needed since it runs at a slow clock + ulp_riscv_gpio_output_level(u8SCK, 0); } } else { for (int i=0; i<8; i++) { - ulp_riscv_gpio_output_level(PIN_MOSI, (uc & 0x80)); // msb first - ulp_riscv_gpio_output_level(PIN_SCK, 1); // no delays needed since it runs at a slow clock + ulp_riscv_gpio_output_level(u8MOSI, (uc & 0x80)); // msb first + ulp_riscv_gpio_output_level(u8SCK, 1); // no delays needed since it runs at a slow clock uc <<= 1; - ulp_riscv_gpio_output_level(PIN_SCK, 0); + ulp_riscv_gpio_output_level(u8SCK, 0); } } } /* SPIWriteByte() */ -void delayMicroseconds(long l) +void bbepWriteCmd(BBEPDISP *pBBEP, uint8_t ucCMD) +{ + digitalWrite(pBBEP->iDCPin, LOW); + digitalWrite(pBBEP->iCSPin, LOW); + SPIWriteByte(pBBEP, ucCMD); + digitalWrite(pBBEP->iDCPin, HIGH); + digitalWrite(pBBEP->iCSPin, HIGH); +} /* bbepWriteCmd() */ + +void bbepWriteData(BBEPDISP *pBBEP, uint8_t *pData, int iLen) { - l *= 40; - while (l) { - __asm__ __volatile__ ("nop"); - l--; + digitalWrite(pBBEP->iCSPin, LOW); + for (int i=0; iiCSPin, HIGH); } -void _delay(long l) -{ - delayMicroseconds(l*1000); -} + +void bbepCMD2(BBEPDISP *pBBEP, uint8_t cmd, uint8_t param) +{ + bbepWriteCmd(pBBEP, cmd); + bbepWriteData(pBBEP, ¶m, 1); +} /* bbepCMD2() */ + void mymemcpy(void *d, void *s, size_t iLen) { uint8_t *d8 = (uint8_t *)d; @@ -168,191 +231,5 @@ int i2str(char *pDest, int iVal) *d++ = 0; // terminator return (int)(d - pDest - 1); // string length } /* i2str() */ -// -// Need to bit-bang I2C -// -uint8_t SDA_READ(void) -{ - return ulp_riscv_gpio_get_level(PIN_SDA); -} -void SDA_HIGH(void) -{ - ulp_riscv_gpio_output_disable(PIN_SDA); - ulp_riscv_gpio_input_enable(PIN_SDA); -} -void SDA_LOW(void) -{ - ulp_riscv_gpio_input_disable(PIN_SDA); - ulp_riscv_gpio_output_enable(PIN_SDA); - ulp_riscv_gpio_output_level(PIN_SDA, 0); -} -void SCL_HIGH(void) -{ - ulp_riscv_gpio_output_disable(PIN_SCL); - ulp_riscv_gpio_input_enable(PIN_SCL); -} -void SCL_LOW(void) -{ - ulp_riscv_gpio_input_disable(PIN_SCL); - ulp_riscv_gpio_output_enable(PIN_SCL); - ulp_riscv_gpio_output_level(PIN_SDA, 0); -} -// Transmit a byte and read the ack bit -// if we get a NACK (negative acknowledge) return 0 -// otherwise return 1 for success -// - -int i2cByteOut(uint8_t b) -{ -uint8_t i, ack; - -for (i=0; i<8; i++) { -// my_sleep_us(iDelay); - if (b & 0x80) - SDA_HIGH(); // set data line to 1 - else - SDA_LOW(); // set data line to 0 - b <<= 1; -// my_sleep_us(iDelay); - SCL_HIGH(); // clock high (slave latches data) - ulp_riscv_delay_cycles(I2C_DELAY); - SCL_LOW(); // clock low - ulp_riscv_delay_cycles(I2C_DELAY); -} // for i -//my_sleep_us(iDelay); -// read ack bit -SDA_HIGH(); // set data line for reading -//my_sleep_us(iDelay); -SCL_HIGH(); // clock line high -ulp_riscv_delay_cycles(I2C_DELAY); // DEBUG - delay/2 -ack = SDA_READ(); -//my_sleep_us(iDelay); -SCL_LOW(); // clock low -ulp_riscv_delay_cycles(I2C_DELAY); // DEBUG - delay/2 -SDA_LOW(); // data low -return (ack == 0); // a low ACK bit means success -} /* i2cByteOut() */ -// -// Receive a byte and read the ack bit -// if we get a NACK (negative acknowledge) return 0 -// otherwise return 1 for success -// -uint8_t i2cByteIn(uint8_t bLast) -{ -uint8_t i; -uint8_t b = 0; - - SDA_HIGH(); // set data line as input - for (i=0; i<8; i++) - { - ulp_riscv_delay_cycles(I2C_DELAY); // wait for data to settle - SCL_HIGH(); // clock high (slave latches data) - ulp_riscv_delay_cycles(I2C_DELAY); - b <<= 1; - if (SDA_READ() != 0) // read the data bit - b |= 1; // set data bit - SCL_LOW(); // clock low - } // for i - if (bLast) - SDA_HIGH(); // last byte sends a NACK - else - SDA_LOW(); -// my_sleep_us(iDelay); - SCL_HIGH(); // clock high - ulp_riscv_delay_cycles(I2C_DELAY); - SCL_LOW(); // clock low to send ack - ulp_riscv_delay_cycles(I2C_DELAY); -// SDA_HIGH(); - SDA_LOW(); // data low - return b; -} /* i2cByteIn() */ -// -// Send I2C STOP condition -// -void i2cEnd(void) -{ - SDA_LOW(); // data line low - ulp_riscv_delay_cycles(I2C_DELAY); - SCL_HIGH(); // clock high - ulp_riscv_delay_cycles(I2C_DELAY); - SDA_HIGH(); // data high - ulp_riscv_delay_cycles(I2C_DELAY); -} /* i2cEnd() */ - -int i2cBegin(uint8_t addr, uint8_t bRead) -{ - int rc; -// SCL_HIGH(); -// my_sleep_us(iDelay); - SDA_LOW(); // data line low first - ulp_riscv_delay_cycles(I2C_DELAY); - SCL_LOW(); // then clock line low is a START signal - addr <<= 1; - if (bRead) - addr++; // set read bit - rc = i2cByteOut(addr); // send the slave address and R/W bit - return rc; -} /* i2cBegin() */ -void I2CWrite(uint8_t addr, uint8_t *pData, int iLen) -{ -uint8_t b; -int rc; - - i2cBegin(addr, 0); - rc = 1; - while (iLen && rc == 1) - { - b = *pData++; - rc = i2cByteOut(b); - if (rc == 1) // success - { - iLen--; - } - } // for each byte - i2cEnd(); -//return (rc == 1) ? (iOldLen - iLen) : 0; // 0 indicates bad ack from sending a byte -} /* I2CWrite() */ -int I2CRead(uint8_t addr, uint8_t *pData, int iLen) -{ - i2cBegin(addr, 1); - while (iLen--) - { - *pData++ = i2cByteIn(iLen == 0); - } // for each byte - i2cEnd(); - return 1; -} /* I2CRead() */ - -int I2CTest(uint8_t addr) -{ -int response = 0; - - if (i2cBegin(addr, 0)) // try to write to the given address - { - response = 1; - } - i2cEnd(); -return response; -} /* I2CTest() */ - -void I2CInit(void) -{ - pinMode(PIN_SDA, INPUT_PULLUP); - pinMode(PIN_SCL, INPUT_PULLUP); -} -void I2CDeInit(void) -{ - pinMode(PIN_SDA, DISABLED); - pinMode(PIN_SCL, DISABLED); -} -// -// Read N bytes starting at a specific I2C internal register -// returns 1 for success, 0 for error -// -void I2CReadRegister(uint8_t iAddr, uint8_t u8Register, uint8_t *pData, int iLen) -{ - I2CWrite(iAddr, &u8Register, 1); - I2CRead(iAddr, pData, iLen); -} /* I2CReadRegister() */ #endif // __ULP_IO__ diff --git a/src/arduino_io.inl b/src/arduino_io.inl index 88553c9..e27c8db 100644 --- a/src/arduino_io.inl +++ b/src/arduino_io.inl @@ -23,8 +23,15 @@ // // Initialize the GPIO pins and SPI for use by bb_eink // -void bbepInitIO(BBEPDISP *pBBEP, uint32_t u32Speed) +void bbepInitIO(BBEPDISP *pBBEP, uint8_t u8DC, uint8_t u8RST, uint8_t u8BUSY, uint8_t u8CS, uint8_t u8MOSI, uint8_t u8SCK, uint32_t u32Speed) { + pBBEP->iDCPin = u8DC; + pBBEP->iCSPin = u8CS; + pBBEP->iMOSIPin = u8MOSI; + pBBEP->iCLKPin = u8SCK; + pBBEP->iRSTPin = u8RST; + pBBEP->iBUSYPin = u8BUSY; + pinMode(pBBEP->iDCPin, OUTPUT); pinMode(pBBEP->iRSTPin, OUTPUT); digitalWrite(pBBEP->iRSTPin, LOW); @@ -46,29 +53,6 @@ void bbepInitIO(BBEPDISP *pBBEP, uint32_t u32Speed) SPI.endTransaction(); // N.B. - if you call beginTransaction() again without a matching endTransaction(), it will hang on ESP32 } /* bbepInitIO() */ // -// Toggle the reset line to wake up the eink from deep sleep -// -void bbepWakeUp(BBEPDISP *pBBEP) -{ - digitalWrite(pBBEP->iRSTPin, LOW); - delay(10); - digitalWrite(pBBEP->iRSTPin, HIGH); - delay(10); -} /* bbepWakeUp() */ -// -// Wait for the busy status line to show idle -// The polarity of the busy signal is reversed on the UC81xx compared -// to the SSD16xx controllers -// -void bbepWaitBusy(BBEPDISP *pBBEP) -{ - uint8_t busy_idle = (pBBEP->chip_type == BBEP_CHIP_UC81xx) ? HIGH : LOW; - delay(1); // some panels need a short delay before testing the BUSY line - while (1) { - if (digitalRead(pBBEP->iBUSYPin) == busy_idle) break; - } -} /* bbepWaitBusy() */ -// // Convenience function to write a command byte along with a data // byte (it's single parameter) // diff --git a/src/bb_ep.inl b/src/bb_ep.inl index 9b62603..c5595ee 100644 --- a/src/bb_ep.inl +++ b/src/bb_ep.inl @@ -971,6 +971,7 @@ const EPD_PANEL panelDefs[] PROGMEM = { {240, 416, epd37_init_sequence_full, NULL, epd37_init_sequence_part, 0, BBEP_CHIP_UC81xx}, // EPD37_240x416 {176, 264, epd27_ws_init_sequence_full, NULL, NULL, BBEP_CS_EVERY_BYTE, BBEP_CHIP_UC81xx}, // EPD27_176x264 Waveshare 2.7" e-paper HAT }; + int bbepSetPanelType(BBEPDISP *pBBEP, int iPanel) { if (pBBEP == NULL || iPanel <= EPD_PANEL_UNDEFINED || iPanel >= EPD_PANEL_COUNT) @@ -988,6 +989,30 @@ int bbepSetPanelType(BBEPDISP *pBBEP, int iPanel) return BBEP_SUCCESS; } /* bbepSetPanelType() */ +// +// Toggle the reset line to wake up the eink from deep sleep +// +void bbepWakeUp(BBEPDISP *pBBEP) +{ + digitalWrite(pBBEP->iRSTPin, LOW); + delay(10); + digitalWrite(pBBEP->iRSTPin, HIGH); + delay(10); +} /* bbepWakeUp() */ +// +// Wait for the busy status line to show idle +// The polarity of the busy signal is reversed on the UC81xx compared +// to the SSD16xx controllers +// +void bbepWaitBusy(BBEPDISP *pBBEP) +{ + uint8_t busy_idle = (pBBEP->chip_type == BBEP_CHIP_UC81xx) ? HIGH : LOW; + delay(1); // some panels need a short delay before testing the BUSY line + while (1) { + if (digitalRead(pBBEP->iBUSYPin) == busy_idle) break; + } +} /* bbepWaitBusy() */ + void bbepSetAddrWindow(BBEPDISP *pBBEP, int x, int y, int cx, int cy) { uint8_t uc[12]; diff --git a/src/bb_ep_gfx.inl b/src/bb_ep_gfx.inl index 093e3af..67697af 100644 --- a/src/bb_ep_gfx.inl +++ b/src/bb_ep_gfx.inl @@ -14,6 +14,9 @@ // // bb_ei_gfx.inl - graphics functions // +// For constrained CPU environments, the NO_RAM macro will be defined +// to reduce codespace used by each of the functions +// #ifndef __BB_EP_GFX__ #define __BB_EP_GFX__ #include "Group5.h" @@ -621,15 +624,17 @@ uint8_t *pBits, u8CMD1, u8CMD2, u8CMD, u8EndMask; c -= pFont->first; // first char of font defined pGlyph = &pFont->glyphs[c]; // glyph info for this character if (pGlyph->width > 1) { // skip this if drawing a space - dx = x + pGlyph->xOffset; // offset from character UL to start drawing - dy = y + pGlyph->yOffset; s = pBits + pGlyph->bitmapOffset; // start of compressed bitmap data if (pFont->rotation == 0 || pFont->rotation == 180) { h = pGlyph->height; w = pGlyph->width; + dx = x + pGlyph->xOffset; // offset from character UL to start drawing + dy = y + pGlyph->yOffset; } else { // rotated w = pGlyph->height; h = pGlyph->width; + dx = x + pGlyph->yOffset; // offset from character UL to start drawing + dy = y + pGlyph->xOffset; } if ((dy + h) > pBBEP->native_height) { // trim it h = pBBEP->native_height - dy; @@ -644,6 +649,7 @@ uint8_t *pBits, u8CMD1, u8CMD2, u8CMD, u8EndMask; rc = g5_decode_init(&g5dec, w, h, s, ty); if (rc != G5_SUCCESS) return; // corrupt data? if (pBBEP->ucScreen) { // backbuffer, draw pixels +#ifndef NO_RAM for (ty=dy; tynative_height; ty++) { uint8_t u8, u8Count; g5_decode_line(&g5dec, u8Cache); @@ -662,6 +668,7 @@ uint8_t *pBits, u8CMD1, u8CMD2, u8CMD, u8EndMask; } } } +#endif // NO_RAM } else { // draw directly into EPD memory // set the memory window for this character iSrcPitch = (w+7)/8; @@ -751,10 +758,6 @@ uint8_t u8Temp[128]; if (iSize == FONT_8x8) // 8x8 font { i = 0; - if (!pBBEP->ucScreen) { // bufferless mode, rotate the coordinate system to fit the situation - // bbepSetAddrWindow(pBBEP, √, pBBEP->iCursorX, 8, pBBEP->native_height-pBBEP->iCursorX); -// bbepWriteCmd(pBBEP, ucCMD1); // write to "new" plane - } while (pBBEP->iCursorX < pBBEP->width && szMsg[i] != 0 && pBBEP->iCursorY < pBBEP->height) { c = (unsigned char)szMsg[i]; @@ -772,7 +775,9 @@ uint8_t u8Temp[128]; bbepWriteCmd(pBBEP, ucCMD); // write to "new" plane bbepWriteData(pBBEP, u8Temp, iLen); } else { // draw in memory +#ifndef NO_RAM // DEBUG +#endif } pBBEP->iCursorX += iLen; if (pBBEP->iCursorX >= pBBEP->width-7 && pBBEP->wrap) { // word wrap enabled? @@ -905,7 +910,9 @@ uint8_t u8Temp[128]; bbepWriteCmd(pBBEP, ucCMD); // write to "new" plane bbepWriteData(pBBEP, &u8Temp[18], iLen); } else { // write to RAM +#ifndef NO_RAM // DEBUG +#endif } pBBEP->iCursorX += iLen; if (pBBEP->iCursorX >= pBBEP->width-11 && pBBEP->wrap) // word wrap enabled? @@ -936,7 +943,9 @@ uint8_t u8Temp[128]; bbepWriteCmd(pBBEP, ucCMD); // write to "new" plane bbepWriteData(pBBEP, u8Temp, iLen); } else { // write to RAM +#ifndef NO_RAM // DEBUG +#endif } pBBEP->iCursorX += iLen; if (pBBEP->iCursorX >= pBBEP->width-5 && pBBEP->wrap) // word wrap enabled? @@ -981,8 +990,10 @@ int miny, maxy; void bbepAllocBuffer(BBEPDISP *pBBEP) { +#ifndef NO_RAM pBBEP->ucScreen = (uint8_t *)malloc(pBBEP->width * ((pBBEP->height+7)>>3)); pBBEP->iScreenOffset = 0; +#endif } /* obdAllocBuffer() */ void bbepDrawLine(BBEPDISP *pBBEP, int x1, int y1, int x2, int y2, uint8_t ucColor) @@ -1334,6 +1345,7 @@ void bbepRectangle(BBEPDISP *pBBEP, int x1, int y1, int x2, int y2, uint8_t ucCo } if (bFilled) { +#ifndef NO_RAM if (pBBEP->ucScreen) { // has a buffer to fill int x, y, iMiddle; iMiddle = (y2 >> 3) - (y1 >> 3); @@ -1367,7 +1379,9 @@ void bbepRectangle(BBEPDISP *pBBEP, int x1, int y1, int x2, int y2, uint8_t ucCo *d++ &= ~ucMask; } } - } else { // no buffer + } else +#endif // NO_RAM + { // no buffer int cx, cy, iPitch; cx = x2-x1+1; iPitch = (cx+7)/8; diff --git a/src/bb_epaper.cpp b/src/bb_epaper.cpp index 1148e5e..1c3af7b 100644 --- a/src/bb_epaper.cpp +++ b/src/bb_epaper.cpp @@ -63,13 +63,7 @@ void BBEPAPER::setAddrWindow(int x, int y, int w, int h) #ifdef ARDUINO void BBEPAPER::initIO(int iDC, int iReset, int iBusy, int iCS, int iMOSI, int iSCLK, uint32_t u32Speed) { - _bbep.iCSPin = iCS; - _bbep.iMOSIPin = iMOSI; - _bbep.iCLKPin = iSCLK; - _bbep.iDCPin = iDC; - _bbep.iRSTPin = iReset; - _bbep.iBUSYPin = iBusy; - bbepInitIO(&_bbep, u32Speed); + bbepInitIO(&_bbep, iDC, iReset, iBusy, iCS, iMOSI, iSCLK, u32Speed); bbepWakeUp(&_bbep); bbepSendCMDSequence(&_bbep, _bbep.pInitFull); } /* initIO() */