This commit is contained in:
e2002
2023-01-20 13:51:27 +03:00
parent f90718f2f0
commit f48114e5e5
10 changed files with 2902 additions and 2 deletions

View File

@@ -62,6 +62,9 @@ https://aliexpress.com/item/32965676064.html
- or **ILI9225** 2.0' 220x176 SPI https://aliexpress.com/item/32952021835.html - or **ILI9225** 2.0' 220x176 SPI https://aliexpress.com/item/32952021835.html
- or **Nextion displays** - [more info](https://github.com/e2002/yoradio/tree/main/nextion) - or **Nextion displays** - [more info](https://github.com/e2002/yoradio/tree/main/nextion)
- or **ST7796** 3.5' 480x320 SPI https://aliexpress.com/item/1005004632953455.html?sku_id=12000029911293172 - or **ST7796** 3.5' 480x320 SPI https://aliexpress.com/item/1005004632953455.html?sku_id=12000029911293172
- or **GC9A01A** 1.28' 240x240 https://aliexpress.com/item/1005004069703494.html?sku_id=12000029869654615
- or **ILI9488** 3.5' 480x320 SPI https://aliexpress.com/item/1005001999296476.html?sku_id=12000018365356570
- or **ILI9486** (Testing mode) 3.5' 480x320 SPI https://aliexpress.com/item/1005001999296476.html?sku_id=12000018365356568
##### Controls ##### Controls
- Three tact buttons https://www.aliexpress.com/item/32907144687.html - Three tact buttons https://www.aliexpress.com/item/32907144687.html
@@ -317,7 +320,11 @@ Work is in progress...
--- ---
## Version history ## Version history
#### v0.8.NEW #### v0.8.933
- added support for ILI9488 display
- added support for ILI9486 display in testing mode
#### v0.8.920
**!!! a [full update](#update-over-web-interface) with Sketch data upload is required. After updating please press CTRL+F5 in browser !!!** \ **!!! a [full update](#update-over-web-interface) with Sketch data upload is required. After updating please press CTRL+F5 in browser !!!** \
**Please backup playlist.csv and wifi.csv before updating.** **Please backup playlist.csv and wifi.csv before updating.**
- fixed bug with displaying horizontal scroll in playlist - fixed bug with displaying horizontal scroll in playlist

View File

@@ -0,0 +1,80 @@
#ifndef _GFXcanvas16T_H_
#define _GFXcanvas16T_H_
#include <Adafruit_GFX.h>
template<const uint16_t w, uint16_t h>
class GFXcanvas16T : public Adafruit_GFX
{
public:
GFXcanvas16T() : Adafruit_GFX(w, h) {};
void drawPixel(int16_t x, int16_t y, uint16_t color)
{
if ((x < 0) || (y < 0) || (x >= _width) || (y >= _height)) return;
int16_t t;
switch (rotation) {
case 1:
t = x;
x = WIDTH - 1 - y;
y = t;
break;
case 2:
x = WIDTH - 1 - x;
y = HEIGHT - 1 - y;
break;
case 3:
t = x;
x = y;
y = HEIGHT - 1 - t;
break;
}
buffer[x + y * WIDTH] = color;
}
void fillScreen(uint16_t color)
{
uint32_t i, pixels = WIDTH * HEIGHT;
for (i = 0; i < pixels; i++) buffer[i] = color;
}
uint16_t *getBuffer(void)
{
return buffer;
}
#if 1
template<class T>
void print(T target, const char* name = 0, bool as_progmem = false)
{
static const uint16_t per_line = 10;
uint32_t remain = WIDTH * HEIGHT;
uint16_t* p = buffer;
if (!name) name = "canvas16";
if (as_progmem) target.print(F("const "));
target.print(F("uint16_t ")); target.print(name); target.print(F("[] "));
if (as_progmem) target.print(F("PROGMEM "));
target.println(F("=")); target.println(F("{"));
while (remain > per_line)
{
target.print(F(" "));
for (uint16_t i = 0; i < per_line; i++)
{
target.print(F("0x")); target.print(*p++, HEX); target.print(F(", "));
}
remain -= per_line;
if (remain > 0) target.println();
#if defined (ESP8266)
yield();
#endif
}
if (remain > 0) target.print(F(" "));
while (remain-- > 0)
{
target.print(F("0x")); target.print(*p++, HEX); target.print(F(", "));
}
target.println();
target.println(F("};"));
}
#endif
private:
uint16_t buffer[w * h];
};
#endif

View File

@@ -0,0 +1,719 @@
// created by Jean-Marc Zingg to be a standalone ILI9486_SPI library (instead of the GxCTRL_ILI9486_SPI class for the GxTFT library)
// code extracts taken from https://github.com/Bodmer/TFT_HX8357
// spi kludge handling solution found in https://github.com/Bodmer/TFT_eSPI
// code extracts taken from https://github.com/adafruit/Adafruit-GFX-Library
//
// License: GNU GENERAL PUBLIC LICENSE V3, see LICENSE
//
#include "../core/options.h"
#if DSP_MODEL==DSP_ILI9488 || DSP_MODEL==DSP_ILI9486
#include <SPI.h>
#include "ILI9486_SPI.h"
#define SPI_SPEED 40000000UL
//#define SPI_SPEED 20000000 // max reliable speed is 20Mhz for RPi SPI kludge
//#define SPI_SPEED 4000000
#define ILI9486_SPI_CASET 0x2A
#define ILI9486_SPI_PASET 0x2B
#define ILI9486_SPI_RAMWR 0x2C
#define ILI9486_SPI_RAMRD 0x2E
#define ILI9486_SPI_MADCTL 0x36
#define MADCTL_MY 0x80
#define MADCTL_MX 0x40
#define MADCTL_MV 0x20
#define MADCTL_ML 0x10
#define MADCTL_BGR 0x08
#define MADCTL_MH 0x04
#define ILI9488_INVOFF 0x20
#define ILI9488_INVON 0x21
//ILI9486_SPI::ILI9486_SPI(int8_t cs, int8_t dc, int8_t rst) : Adafruit_GFX(320, 480), _spi_settings(SPI_SPEED, MSBFIRST, SPI_MODE0)
ILI9486_SPI::ILI9486_SPI(int8_t cs, int8_t dc, int8_t rst) : ILI9486_SPI(&SPI, cs, dc, rst) { }
ILI9486_SPI::ILI9486_SPI(SPIClass *spiClass, int8_t cs, int8_t dc, int8_t rst) : Adafruit_GFX(320, 480), _spi_settings(SPI_SPEED, MSBFIRST, SPI_MODE0)
{
_spi = spiClass;
_spi16_mode = true;
_cs = cs;
_dc = dc;
_rst = rst;
_bgr = MADCTL_BGR;
#if defined(ESP8266)
if ((_cs >= 0) && (_dc >= 0))
{
_cs_pinmask = (uint32_t) digitalPinToBitMask(_cs);
_dc_pinmask = (uint32_t) digitalPinToBitMask(_dc);
}
else
{
_cs_pinmask = 0;
_dc_pinmask = 0;
}
#endif
_x_address_set = -1;
_x_address_set = -1;
digitalWrite(_cs, HIGH);
digitalWrite(_dc, HIGH);
pinMode(_cs, OUTPUT);
pinMode(_dc, OUTPUT);
if (rst >= 0)
{
digitalWrite(rst, HIGH);
pinMode(rst, OUTPUT);
}
}
// *** (overridden) virtual methods ***
#if defined(ESP8266) && true // This is for the RPi display that needs 16 bits
#define CMD_BITS (16-1)
void ILI9486_SPI::drawPixel(int16_t x, int16_t y, uint16_t color)
{
// Range checking
if ((x < 0) || (y < 0) || (x >= _width) || (y >= _height)) return;
if (_spi16_mode && _cs_pinmask)
{
_spi->beginTransaction(_spi_settings);
GPOC = _cs_pinmask;
SPI1U1 = (CMD_BITS << SPILMOSI) | (CMD_BITS << SPILMISO);
// No need to send x if it has not changed (speeds things up)
if (_x_address_set != x)
{
GPOC = _dc_pinmask;
SPI1W0 = ILI9486_SPI_CASET << (CMD_BITS + 1 - 8);
SPI1CMD |= SPIBUSY;
while (SPI1CMD & SPIBUSY) {}
GPOS = _dc_pinmask;
SPI1W0 = x >> 0;
SPI1CMD |= SPIBUSY;
while (SPI1CMD & SPIBUSY) {}
SPI1W0 = x << 8;
SPI1CMD |= SPIBUSY;
while (SPI1CMD & SPIBUSY) {}
SPI1W0 = x >> 0;
SPI1CMD |= SPIBUSY;
while (SPI1CMD & SPIBUSY) {}
SPI1W0 = x << 8;
SPI1CMD |= SPIBUSY;
while (SPI1CMD & SPIBUSY) {}
_x_address_set = x;
}
// No need to send y if it has not changed (speeds things up)
if (_y_address_set != y)
{
GPOC = _dc_pinmask;
SPI1W0 = ILI9486_SPI_PASET << (CMD_BITS + 1 - 8);
SPI1CMD |= SPIBUSY;
while (SPI1CMD & SPIBUSY) {}
GPOS = _dc_pinmask;
SPI1W0 = y >> 0;
SPI1CMD |= SPIBUSY;
while (SPI1CMD & SPIBUSY) {}
SPI1W0 = y << 8;
SPI1CMD |= SPIBUSY;
while (SPI1CMD & SPIBUSY) {}
SPI1W0 = y >> 0;
SPI1CMD |= SPIBUSY;
while (SPI1CMD & SPIBUSY) {}
SPI1W0 = y << 8;
SPI1CMD |= SPIBUSY;
while (SPI1CMD & SPIBUSY) {}
_y_address_set = y;
}
GPOC = _dc_pinmask;
SPI1W0 = ILI9486_SPI_RAMWR << (CMD_BITS + 1 - 8);
SPI1CMD |= SPIBUSY;
while (SPI1CMD & SPIBUSY) {}
GPOS = _dc_pinmask;
SPI1W0 = (color >> 8) | (color << 8);
SPI1CMD |= SPIBUSY;
while (SPI1CMD & SPIBUSY) {}
GPOS = _cs_pinmask;
_spi->endTransaction();
}
else
{
_startTransaction();
_setWindow(x, y, 1, 1);
_writeColor16(color, 1);
_endTransaction();
}
}
#else
void ILI9486_SPI::drawPixel(int16_t x, int16_t y, uint16_t color)
{
if ((x < 0) || (y < 0) || (x >= _width) || (y >= _height))
{
return;
}
_startTransaction();
_setWindow(x, y, 1, 1);
_writeColor16(color, 1);
_endTransaction();
}
#endif
void ILI9486_SPI::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color)
{
// if ((x < 0) || (y < 0) || (w < 1) || (h < 1) || (x + w > _width) || (y + h > _height))
// {
// Serial.print("fillRect("); Serial.print(x); Serial.print(", "); Serial.print(y); Serial.print(", "); Serial.print(w); Serial.print(", "); Serial.print(h); Serial.println(") oops? "); delay(1);
// }
// a correct clipping is the goal. try to achieve this
if (x < 0) w += x, x = 0;
if (y < 0) h += y, y = 0;
if (x + w > _width) w = _width - x;
if (y + h > _height) h = _height - y;
if ((w < 1) || (h < 1)) return;
_startTransaction();
_setWindow(x, y, w, h);
_writeColor16(color, uint32_t(w) * uint32_t(h));
_endTransaction();
}
void ILI9486_SPI::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color)
{
fillRect(x, y, 1, h, color);
}
void ILI9486_SPI::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color)
{
fillRect(x, y, w, 1, color);
}
void ILI9486_SPI::fillScreen(uint16_t color)
{
fillRect(0, 0, _width, _height, color);
}
void ILI9486_SPI::setRotation(uint8_t r)
{
Adafruit_GFX::setRotation(r);
_startTransaction();
if (_spi16_mode)
{
_writeCommand16(ILI9486_SPI_MADCTL);
switch (r & 3)
{
case 0:
_writeData16(MADCTL_MX | _bgr);
break;
case 1:
_writeData16(MADCTL_MV | _bgr);
break;
case 2:
_writeData16(MADCTL_MY | _bgr);
break;
case 3:
_writeData16(MADCTL_MX | MADCTL_MY | MADCTL_MV | _bgr);
break;
}
}
else
{
_writeCommand(ILI9486_SPI_MADCTL);
switch (r & 3)
{
case 0:
_writeData(MADCTL_MX | _bgr);
break;
case 1:
_writeData(MADCTL_MV | _bgr);
break;
case 2:
_writeData(MADCTL_MY | _bgr);
break;
case 3:
_writeData(MADCTL_MX | MADCTL_MY | MADCTL_MV | _bgr);
break;
}
}
_endTransaction();
}
void ILI9486_SPI::invertDisplay(bool i)
{
/*_bgr = i ? MADCTL_BGR : 0x00;
setRotation(rotation);*/
sendCommand(i ? ILI9488_INVON : ILI9488_INVOFF);
}
// *** other public methods ***
void ILI9486_SPI::setSpiKludge(bool rpi_spi16_mode)
{
_spi16_mode = rpi_spi16_mode;
}
void ILI9486_SPI::init(void)
{
digitalWrite(_cs, HIGH);
_spi->begin();
_spi->beginTransaction( { SPI_SPEED, MSBFIRST, SPI_MODE0 } );
_spi->endTransaction();
if (_rst >= 0)
{
digitalWrite(_rst, LOW);
delay(20);
digitalWrite(_rst, HIGH);
}
delay(200);
_startTransaction();
if (_spi16_mode)
{
_writeCommand16(0x3A);
_writeData16(0x55); // use 16 bits per pixel color
_writeCommand16(0x36);
_writeData16(0x48); // MX, BGR == rotation 0
// PGAMCTRL(Positive Gamma Control)
_writeCommand16(0xE0);
_writeData16(0x0F);
_writeData16(0x1F);
_writeData16(0x1C);
_writeData16(0x0C);
_writeData16(0x0F);
_writeData16(0x08);
_writeData16(0x48);
_writeData16(0x98);
_writeData16(0x37);
_writeData16(0x0A);
_writeData16(0x13);
_writeData16(0x04);
_writeData16(0x11);
_writeData16(0x0D);
_writeData16(0x00);
// NGAMCTRL(Negative Gamma Control)
_writeCommand16(0xE1);
_writeData16(0x0F);
_writeData16(0x32);
_writeData16(0x2E);
_writeData16(0x0B);
_writeData16(0x0D);
_writeData16(0x05);
_writeData16(0x47);
_writeData16(0x75);
_writeData16(0x37);
_writeData16(0x06);
_writeData16(0x10);
_writeData16(0x03);
_writeData16(0x24);
_writeData16(0x20);
_writeData16(0x00);
// Digital Gamma Control 1
_writeCommand16(0xE2);
_writeData16(0x0F);
_writeData16(0x32);
_writeData16(0x2E);
_writeData16(0x0B);
_writeData16(0x0D);
_writeData16(0x05);
_writeData16(0x47);
_writeData16(0x75);
_writeData16(0x37);
_writeData16(0x06);
_writeData16(0x10);
_writeData16(0x03);
_writeData16(0x24);
_writeData16(0x20);
_writeData16(0x00);
_writeCommand16(0x11); // Sleep OUT
delay(150); // wait some time
_writeCommand16(0x29); // Display ON
}
else
{
_writeCommand(0x3A);
_writeData(0x66); // 18 bit colour for native SPI
_writeCommand(0x36);
_writeData(0x48); // MX, BGR == rotation 0
// PGAMCTRL(Positive Gamma Control)
#if DSP_MODEL==DSP_ILI9486
_writeCommand(0xE0);
_writeData(0x0F);
_writeData(0x1F);
_writeData(0x1C);
_writeData(0x0C);
_writeData(0x0F);
_writeData(0x08);
_writeData(0x48);
_writeData(0x98);
_writeData(0x37);
_writeData(0x0A);
_writeData(0x13);
_writeData(0x04);
_writeData(0x11);
_writeData(0x0D);
_writeData(0x00);
// NGAMCTRL(Negative Gamma Control)
_writeCommand(0xE1);
_writeData(0x0F);
_writeData(0x32);
_writeData(0x2E);
_writeData(0x0B);
_writeData(0x0D);
_writeData(0x05);
_writeData(0x47);
_writeData(0x75);
_writeData(0x37);
_writeData(0x06);
_writeData(0x10);
_writeData(0x03);
_writeData(0x24);
_writeData(0x20);
_writeData(0x00);
// Digital Gamma Control 1
_writeCommand(0xE2);
_writeData(0x0F);
_writeData(0x32);
_writeData(0x2E);
_writeData(0x0B);
_writeData(0x0D);
_writeData(0x05);
_writeData(0x47);
_writeData(0x75);
_writeData(0x37);
_writeData(0x06);
_writeData(0x10);
_writeData(0x03);
_writeData(0x24);
_writeData(0x20);
_writeData(0x00);
#endif
_writeCommand(0x11); // Sleep OUT
delay(150); // wait some time
_writeCommand(0x29); // Display ON
}
_endTransaction();
}
void ILI9486_SPI::setWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
{
_startTransaction();
_setWindow(x, y, w, h);
_endTransaction();
}
void ILI9486_SPI::pushColors(const uint16_t* data, uint16_t n)
{
_startTransaction();
_writeColor16(data, n);
_endTransaction();
}
void ILI9486_SPI::setBackLight(bool lit)
{
_startTransaction();
if (_spi16_mode) _writeCommand16(lit ? 0x29 : 0x28); // Display ON / Display OFF
else _writeCommand(lit ? 0x29 : 0x28); // Display ON / Display OFF
_endTransaction();
}
uint16_t ILI9486_SPI::color565(uint8_t r, uint8_t g, uint8_t b)
{
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}
// code extract taken from Adafruit_SPITFT::drawRGBBitmap https://github.com/adafruit/Adafruit-GFX-Library
void ILI9486_SPI::drawRGBBitmap(int16_t x, int16_t y, uint16_t *pcolors, int16_t w, int16_t h)
{
if (!pcolors) return; // canvas might be empty
int16_t x2, y2; // Lower-right coord
if (( x >= _width ) || // Off-edge right
( y >= _height) || // " top
((x2 = (x + w - 1)) < 0 ) || // " left
((y2 = (y + h - 1)) < 0) ) return; // " bottom
int16_t bx1 = 0, by1 = 0; // Clipped top-left within bitmap
int16_t saveW = w; // Save original bitmap width value
if (x < 0) // Clip left
{
w += x;
bx1 = -x;
x = 0;
}
if (y < 0) // Clip top
{
h += y;
by1 = -y;
y = 0;
}
if (x2 >= _width ) w = _width - x; // Clip right
if (y2 >= _height) h = _height - y; // Clip bottom
pcolors += by1 * saveW + bx1; // Offset bitmap ptr to clipped top-left
_startTransaction();
_setWindow(x, y, w, h); // Clipped area
while (h--) // For each (clipped) scanline...
{
_writeColor16(pcolors, w); // Push one (clipped) row
pcolors += saveW; // Advance pointer by one full (unclipped) line
}
_endTransaction();
}
// code extract taken from Adafruit_SPITFT::drawRGBBitmap https://github.com/adafruit/Adafruit-GFX-Library
void ILI9486_SPI::drawRGBBitmap(int16_t x, int16_t y, const uint16_t bitmap[], int16_t w, int16_t h)
{
const uint16_t* pcolors = bitmap;
int16_t x2, y2; // Lower-right coord
if (( x >= _width ) || // Off-edge right
( y >= _height) || // " top
((x2 = (x + w - 1)) < 0 ) || // " left
((y2 = (y + h - 1)) < 0) ) return; // " bottom
int16_t bx1 = 0, by1 = 0; // Clipped top-left within bitmap
int16_t saveW = w; // Save original bitmap width value
if (x < 0) // Clip left
{
w += x;
bx1 = -x;
x = 0;
}
if (y < 0) // Clip top
{
h += y;
by1 = -y;
y = 0;
}
if (x2 >= _width ) w = _width - x; // Clip right
if (y2 >= _height) h = _height - y; // Clip bottom
pcolors += by1 * saveW + bx1; // Offset bitmap ptr to clipped top-left
_startTransaction();
_setWindow(x, y, w, h); // Clipped area
while (h--) // For each (clipped) scanline...
{
for (int16_t i = 0; i < w; i++) // Push one (clipped) row
{
_writeColor16(pgm_read_word(pcolors + i), 1);
}
pcolors += saveW; // Advance pointer by one full (unclipped) line
}
_endTransaction();
}
// *** private methods
void ILI9486_SPI::_setWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
{
uint16_t xe = x + w - 1;
uint16_t ye = y + h - 1;
if (_spi16_mode)
{
_writeCommand16(ILI9486_SPI_CASET);
uint16_t columns[] = {uint16_t(x >> 8), uint16_t(x & 0xFF), uint16_t(xe >> 8), uint16_t(xe & 0xFF)};
_writeData16(columns, 4);
_writeCommand16(ILI9486_SPI_PASET);
uint16_t rows[] = {uint16_t(y >> 8), uint16_t(y & 0xFF), uint16_t(ye >> 8), uint16_t(ye & 0xFF)};
_writeData16(rows, 4);
_writeCommand16(ILI9486_SPI_RAMWR);
}
else
{
_writeCommand(ILI9486_SPI_CASET);
_writeData16(x);
_writeData16(xe);
_writeCommand(ILI9486_SPI_PASET);
_writeData16(y);
_writeData16(ye);
_writeCommand(ILI9486_SPI_RAMWR);
}
_x_address_set = -1;
_x_address_set = -1;
}
void ILI9486_SPI::_startTransaction()
{
_spi->beginTransaction(_spi_settings);
if (_cs >= 0) digitalWrite(_cs, LOW);
}
void ILI9486_SPI::_endTransaction()
{
if (_cs >= 0) digitalWrite(_cs, HIGH);
_spi->endTransaction();
}
void ILI9486_SPI::_writeCommand(uint8_t cmd)
{
digitalWrite(_dc, LOW);
_spi->transfer(cmd);
digitalWrite(_dc, HIGH);
}
void ILI9486_SPI::sendCommand(uint8_t cmd)
{
_startTransaction();
if (_cs >= 0)
digitalWrite(_cs, LOW);
digitalWrite(_dc, LOW); // Command mode
_spi->transfer(cmd); // Send the command byte
digitalWrite(_dc, HIGH);
if (_cs >= 0)
digitalWrite(_cs, HIGH);
_endTransaction();
/* digitalWrite(_dc, LOW);
_spi->transfer(cmd);
digitalWrite(_dc, HIGH);*/
}
void ILI9486_SPI::_writeCommand16(uint16_t cmd)
{
digitalWrite(_dc, LOW);
_writeData16(cmd);
digitalWrite(_dc, HIGH);
}
void ILI9486_SPI::_writeData(uint8_t data)
{
digitalWrite(_dc, HIGH);
_spi->transfer(data);
}
void ILI9486_SPI::_writeData16(uint16_t data)
{
#if (defined (ESP8266) || defined(ESP32))
_spi->write16(data);
#else
_spi->transfer(data >> 8);
_spi->transfer(data);
#endif
}
void ILI9486_SPI::_writeData16(uint16_t data, uint32_t n)
{
#if (defined (ESP8266) || defined(ESP32)) && true // fastest
uint16_t swapped = ((data << 8) | (data >> 8));
_spi->writePattern((uint8_t*)&swapped, 2, n);
#elif (defined (ESP8266) || defined(ESP32))
while (n-- > 0)
{
_spi->write16(data);
}
#else // wdt on ESP8266
while (n-- > 0)
{
_spi->transfer(data >> 8);
_spi->transfer(data);
}
#endif
}
void ILI9486_SPI::_writeData16(const uint16_t* data, uint32_t n)
{
#if (defined (ESP8266) || defined(ESP32) || (TEENSYDUINO == 147)) && true // fastest
static const uint16_t swap_buffer_size = 64; // optimal for ESP8266 SPI
static const uint32_t max_chunk = swap_buffer_size / 2; // uint16_t's
uint8_t swap_buffer[swap_buffer_size];
const uint8_t* p1 = reinterpret_cast<const uint8_t*> (data);
const uint8_t* p2 = p1 + 1;
while (n > 0)
{
uint32_t chunk = min(max_chunk, n);
n -= chunk;
uint8_t* p3 = swap_buffer;
uint8_t* p4 = p3 + 1;
uint16_t ncopy = chunk;
while (ncopy-- > 0)
{
*p3 = *p2; p3 += 2; p2 += 2;
*p4 = *p1; p4 += 2; p1 += 2;
}
#if (defined (ESP8266) || defined(ESP32))
_spi->transferBytes(swap_buffer, 0, 2 * chunk);
#elif defined(ARDUINO_ARCH_SAM) // same speed
_spi->transfer(SS, swap_buffer, 2 * chunk);
#else
_spi->transfer(swap_buffer, 0, 2 * chunk);
#endif
}
#else
while (n-- > 0)
{
uint16_t color = (*data++);
#if (defined (ESP8266) || defined(ESP32)) && false // faster
_spi->write16(color);
#else
_spi->transfer(color >> 8);
_spi->transfer(color);
#endif
}
#endif
}
void ILI9486_SPI::_writeColor16(uint16_t data, uint32_t n)
{
if (_spi16_mode) return _writeData16(data, n);
#if (defined (ESP8266) || defined(ESP32))
uint8_t rgb888[] = {uint8_t((data & 0xF800) >> 8), uint8_t((data & 0x07E0) >> 3), uint8_t((data & 0x001F) << 3)};
_spi->writePattern(rgb888, 3, n);
#else // wdt on ESP8266
while (n-- > 0)
{
_spi->transfer(uint8_t((data & 0xF800) >> 8));
_spi->transfer(uint8_t((data & 0x07E0) >> 3));
_spi->transfer(uint8_t((data & 0x001F) << 3));
}
#endif
}
#if (defined (ESP8266) || defined(ESP32))
#define SPI_WRITE_BYTES(data, n) _spi->transferBytes(data, 0, n)
#elif defined(ARDUINO_ARCH_SAM)
#define SPI_WRITE_BYTES(data, n) _spi->transfer(SS, data, n)
#elif (TEENSYDUINO == 147)
#define SPI_WRITE_BYTES(data, n) _spi->transfer(data, 0, n)
#elif defined(ARDUINO_ARCH_STM32F1) || defined(ARDUINO_ARCH_STM32F4)
#define SPI_WRITE_BYTES(data, n) _spi->write(data, n)
#else
// valid for all other platforms? else comment out next line
#define SPI_WRITE_BYTES(data, n) _spi->transfer(data, n)
#endif
void ILI9486_SPI::_writeColor16(const uint16_t* data, uint32_t n)
{
if (_spi16_mode) return _writeData16(data, n);
#if defined(SPI_WRITE_BYTES)
static const uint16_t rgb888_buffer_size = 60; // 64 optimal for ESP8266 SPI
static const uint32_t max_chunk = rgb888_buffer_size / 3; // rgb888
uint8_t rgb888_buffer[rgb888_buffer_size];
while (n > 0)
{
uint32_t chunk = min(max_chunk, n);
n -= chunk;
uint8_t* p = rgb888_buffer;
uint16_t ncopy = chunk;
while (ncopy-- > 0)
{
*p++ = uint8_t((*data & 0xF800) >> 8);
*p++ = uint8_t((*data & 0x07E0) >> 3);
*p++ = uint8_t((*data & 0x001F) << 3);
data++;
}
SPI_WRITE_BYTES(rgb888_buffer, 3 * chunk);
}
#else
while (n-- > 0)
{
_spi->transfer(uint8_t((*data & 0xF800) >> 8));
_spi->transfer(uint8_t((*data & 0x07E0) >> 3));
_spi->transfer(uint8_t((*data & 0x001F) << 3));
}
#endif
}
#endif

View File

@@ -0,0 +1,65 @@
// created by Jean-Marc Zingg to be a standalone ILI9486_SPI library (instead of the GxCTRL_ILI9486_SPI class for the GxTFT library)
// code extracts taken from https://github.com/Bodmer/TFT_HX8357
// spi kludge handling solution found in https://github.com/Bodmer/TFT_eSPI
// code extracts taken from https://github.com/adafruit/Adafruit-GFX-Library
//
// License: GNU GENERAL PUBLIC LICENSE V3, see LICENSE
//
#ifndef _ILI9486_SPI_H_
#define _ILI9486_SPI_H_
#include <Arduino.h>
#include <SPI.h>
#include "GFXcanvas16T.h"
#include <Adafruit_GFX.h>
class ILI9486_SPI : public Adafruit_GFX
{
public:
ILI9486_SPI(int8_t cs, int8_t dc, int8_t rst);
ILI9486_SPI(SPIClass *spiClass, int8_t cs, int8_t dc, int8_t rst);
// (overridden) virtual methods
virtual void drawPixel(int16_t x, int16_t y, uint16_t color);
virtual void fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color);
virtual void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color);
virtual void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color);
virtual void fillScreen(uint16_t color);
virtual void setRotation(uint8_t r);
virtual void invertDisplay(bool i);
// other public methods
void setSpiKludge(bool rpi_spi16_mode = true); // call with false before init to disable
void init(void);
void setWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
void pushColors(const uint16_t* data, uint16_t n); // fast one
void setBackLight(bool lit);
uint16_t color565(uint8_t r, uint8_t g, uint8_t b);
void drawRGBBitmap(int16_t x, int16_t y, uint16_t *pcolors, int16_t w, int16_t h);
void drawRGBBitmap(int16_t x, int16_t y, const uint16_t bitmap[], int16_t w, int16_t h);
virtual void _startTransaction();
virtual void _endTransaction();
void _writeCommand(uint8_t cmd);
void sendCommand(uint8_t cmd);
private:
void _setWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
void _writeCommand16(uint16_t cmd);
void _writeData(uint8_t data);
void _writeData16(uint16_t data);
void _writeData16(uint16_t data, uint32_t n);
void _writeData16(const uint16_t* data, uint32_t n);
// note: only use for pixel data, RGB888 on ILI9488, ILI9486 native SPI
void _writeColor16(uint16_t data, uint32_t n);
void _writeColor16(const uint16_t* data, uint32_t n);
private:
bool _spi16_mode;
SPISettings _spi_settings;
SPIClass *_spi;
int8_t _cs, _dc, _rst;
int8_t _bgr;
int32_t _x_address_set, _y_address_set;
uint32_t _cs_pinmask, _dc_pinmask;
};
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
#ifndef options_h #ifndef options_h
#define options_h #define options_h
#define YOVERSION "0.8.920" #define YOVERSION "0.8.933"
/******************************************************* /*******************************************************
DO NOT EDIT THIS FILE. DO NOT EDIT THIS FILE.
@@ -44,6 +44,8 @@ The connection tables are located here https://github.com/e2002/yoradio#connecti
/* !!! DSP_ST7789_240 requires further development when used in conjunction with the VS1053 module !!! See the link https://www.instructables.com/Adding-CS-Pin-to-13-LCD/ */ /* !!! DSP_ST7789_240 requires further development when used in conjunction with the VS1053 module !!! See the link https://www.instructables.com/Adding-CS-Pin-to-13-LCD/ */
#define DSP_ST7796 19 // 480x320 3.5' https://aliexpress.com/item/1005004632953455.html?sku_id=12000029911293172 #define DSP_ST7796 19 // 480x320 3.5' https://aliexpress.com/item/1005004632953455.html?sku_id=12000029911293172
#define DSP_GC9A01A 20 // 240x240 1.28' https://aliexpress.com/item/1005004069703494.html?sku_id=12000029869654615 #define DSP_GC9A01A 20 // 240x240 1.28' https://aliexpress.com/item/1005004069703494.html?sku_id=12000029869654615
#define DSP_ILI9488 21 // 480x320 3.5' https://aliexpress.com/item/1005001999296476.html?sku_id=12000018365356570
#define DSP_ILI9486 22 // (Testing mode) 480x320 3.5' https://aliexpress.com/item/1005001999296476.html?sku_id=12000018365356568
#define DSP_CUSTOM 101 // your display #define DSP_CUSTOM 101 // your display
#ifndef DSP_MODEL #ifndef DSP_MODEL

View File

@@ -0,0 +1,68 @@
/*************************************************************************************
ST7796 480X320 displays configuration file.
Copy this file to yoRadio/src/displays/conf/displayST7789conf_custom.h
and modify it
More info on https://github.com/e2002/yoradio/wiki/Widgets#widgets-description
*************************************************************************************/
#ifndef displayILI9488conf_h
#define displayILI9488conf_h
#define DSP_WIDTH 480
#define DSP_HEIGHT 320
#define TFT_FRAMEWDT 10
#define MAX_WIDTH DSP_WIDTH-TFT_FRAMEWDT*2
#define PLMITEMS 11
#define PLMITEMLENGHT 40
#define PLMITEMHEIGHT 32
#define bootLogoTop 110
/* SROLLS */ /* {{ left, top, fontsize, align }, buffsize, uppercase, width, scrolldelay, scrolldelta, scrolltime } */
const ScrollConfig metaConf PROGMEM = {{ TFT_FRAMEWDT, TFT_FRAMEWDT, 4, WA_LEFT }, 140, true, MAX_WIDTH, 5000, 7, 40 };
const ScrollConfig title1Conf PROGMEM = {{ TFT_FRAMEWDT, 62, 2, WA_LEFT }, 140, true, MAX_WIDTH-6*2*7-6, 5000, 7, 40 };
const ScrollConfig title2Conf PROGMEM = {{ TFT_FRAMEWDT, 86, 2, WA_LEFT }, 140, true, MAX_WIDTH, 5000, 7, 40 };
const ScrollConfig playlistConf PROGMEM = {{ TFT_FRAMEWDT, 146, 3, WA_LEFT }, 140, true, MAX_WIDTH, 1000, 7, 40 };
const ScrollConfig apTitleConf PROGMEM = {{ TFT_FRAMEWDT, TFT_FRAMEWDT, 4, WA_CENTER }, 140, false, MAX_WIDTH, 0, 7, 40 };
const ScrollConfig apSettConf PROGMEM = {{ TFT_FRAMEWDT, 320-TFT_FRAMEWDT-16, 2, WA_LEFT }, 140, false, MAX_WIDTH, 0, 7, 40 };
const ScrollConfig weatherConf PROGMEM = {{ TFT_FRAMEWDT, 116, 2, WA_LEFT }, 140, true, MAX_WIDTH, 0, 7, 40 };
/* BACKGROUNDS */ /* {{ left, top, fontsize, align }, width, height, outlined } */
const FillConfig metaBGConf PROGMEM = {{ 0, 0, 0, WA_LEFT }, DSP_WIDTH, 50, false };
const FillConfig volbarConf PROGMEM = {{ TFT_FRAMEWDT, DSP_HEIGHT-TFT_FRAMEWDT-8, 0, WA_LEFT }, MAX_WIDTH, 8, true };
const FillConfig playlBGConf PROGMEM = {{ 0, 138, 0, WA_LEFT }, DSP_WIDTH, 36, false };
const FillConfig heapbarConf PROGMEM = {{ 0, DSP_HEIGHT-2, 0, WA_LEFT }, DSP_WIDTH, 2, false };
/* WIDGETS */ /* { left, top, fontsize, align } */
const WidgetConfig bootstrConf PROGMEM = { 0, 243, 1, WA_CENTER };
const WidgetConfig bitrateConf PROGMEM = { 6, 62, 2, WA_RIGHT };
const WidgetConfig voltxtConf PROGMEM = { 0, DSP_HEIGHT-38, 2, WA_CENTER };
const WidgetConfig iptxtConf PROGMEM = { TFT_FRAMEWDT, DSP_HEIGHT-38, 2, WA_LEFT };
const WidgetConfig rssiConf PROGMEM = { TFT_FRAMEWDT, DSP_HEIGHT-38, 2, WA_RIGHT };
const WidgetConfig numConf PROGMEM = { 0, 200, 70, WA_CENTER };
const WidgetConfig apNameConf PROGMEM = { TFT_FRAMEWDT, 88, 3, WA_CENTER };
const WidgetConfig apName2Conf PROGMEM = { TFT_FRAMEWDT, 120, 3, WA_CENTER };
const WidgetConfig apPassConf PROGMEM = { TFT_FRAMEWDT, 173, 3, WA_CENTER };
const WidgetConfig apPass2Conf PROGMEM = { TFT_FRAMEWDT, 205, 3, WA_CENTER };
const WidgetConfig clockConf PROGMEM = { 16, 224, 70, WA_RIGHT }; /* 52 is a fixed font size. do not change */
const WidgetConfig vuConf PROGMEM = { TFT_FRAMEWDT, 136, 1, WA_LEFT };
const WidgetConfig bootWdtConf PROGMEM = { 0, 216, 1, WA_CENTER };
const ProgressConfig bootPrgConf PROGMEM = { 90, 14, 4 };
/* BANDS */ /* { onebandwidth, onebandheight, bandsHspace, bandsVspace, numofbands, fadespeed } */
const VUBandsConfig bandsConf PROGMEM = { 32, 130, 4, 2, 10, 10 };
/* STRINGS */
const char numtxtFmt[] PROGMEM = "%d";
const char rssiFmt[] PROGMEM = "WiFi %d";
const char iptxtFmt[] PROGMEM = "%s";
const char voltxtFmt[] PROGMEM = "%d";
const char bitrateFmt[] PROGMEM = "%d kBs";
/* MOVES */ /* { left, top, width } */
const MoveConfig clockMove PROGMEM = { 0, 176, -1 };
const MoveConfig weatherMove PROGMEM = { 8, 120, MAX_WIDTH };
const MoveConfig weatherMoveVU PROGMEM = { 89, 120, 381 };
#endif

View File

@@ -0,0 +1,219 @@
#include "../core/options.h"
#if DSP_MODEL==DSP_ILI9488 || DSP_MODEL==DSP_ILI9486
#include "displayILI9488.h"
//#include <SPI.h>
#include "fonts/bootlogo.h"
#include "../core/player.h"
#include "../core/config.h"
#include "../core/network.h"
#ifndef DEF_SPI_FREQ
#define DEF_SPI_FREQ 40000000UL /* set it to 0 for system default */
#endif
#define TAKE_MUTEX() if(player.mutex_pl) xSemaphoreTake(player.mutex_pl, portMAX_DELAY)
#define GIVE_MUTEX() if(player.mutex_pl) xSemaphoreGive(player.mutex_pl)
#if DSP_HSPI
DspCore::DspCore(): ILI9486_SPI(&SPI2, TFT_CS, TFT_DC, TFT_RST) {}
#else
DspCore::DspCore(): ILI9486_SPI(TFT_CS, TFT_DC, TFT_RST) {}
#endif
#include "tools/utf8RusGFX.h"
void DspCore::initDisplay() {
setSpiKludge(false);
init();
//if(DEF_SPI_FREQ > 0) setSPISpeed(DEF_SPI_FREQ);
cp437(true);
setTextWrap(false);
setTextSize(1);
fillScreen(0x0000);
invert();
flip();
}
void DspCore::drawLogo(uint16_t top) { drawRGBBitmap((width() - 99) / 2, top, bootlogo2, 99, 64); }
void DspCore::drawPlaylist(uint16_t currentItem, char* currentItemText) {
for (byte i = 0; i < PLMITEMS; i++) {
plMenu[i][0] = '\0';
}
config.fillPlMenu(plMenu, currentItem - 5, PLMITEMS);
setTextSize(3);
int yStart = (height() / 2 - PLMITEMHEIGHT / 2) - PLMITEMHEIGHT * (PLMITEMS - 1) / 2 + 3;
for (byte i = 0; i < PLMITEMS; i++) {
if (i == 5) {
strlcpy(currentItemText, plMenu[i], PLMITEMLENGHT - 1);
} else {
setTextColor(config.theme.playlist[abs(i - 5)-1], config.theme.background);
setCursor(TFT_FRAMEWDT, yStart + i * PLMITEMHEIGHT);
fillRect(0, yStart + i * PLMITEMHEIGHT - 1, width(), PLMITEMHEIGHT - 6, config.theme.background);
print(utf8Rus(plMenu[i], true));
}
}
}
void DspCore::clearDsp(bool black) { fillScreen(black?0:config.theme.background); }
GFXglyph *pgm_read_glyph_ptr(const GFXfont *gfxFont, uint8_t c) {
return gfxFont->glyph + c;
}
uint8_t DspCore::_charWidth(unsigned char c){
GFXglyph *glyph = pgm_read_glyph_ptr(&DS_DIGI56pt7b, c - 0x20);
return pgm_read_byte(&glyph->xAdvance);
}
uint16_t DspCore::textWidth(const char *txt){
uint16_t w = 0, l=strlen(txt);
for(uint16_t c=0;c<l;c++) w+=_charWidth(txt[c]);
return w;
}
void DspCore::_getTimeBounds() {
_timewidth = textWidth(_timeBuf);
char buf[4];
strftime(buf, 4, "%H", &network.timeinfo);
_dotsLeft=textWidth(buf);
}
void DspCore::_clockSeconds(){
setTextSize(4);
setTextColor(config.theme.seconds, config.theme.background);
setCursor(width() - 8 - clockRightSpace - CHARWIDTH*4*2, clockTop-clockTimeHeight+1);
sprintf(_bufforseconds, "%02d", network.timeinfo.tm_sec);
print(_bufforseconds); /* print seconds */
setTextSize(1);
setFont(&DS_DIGI56pt7b);
setTextColor((network.timeinfo.tm_sec % 2 == 0) ? config.theme.clock : config.theme.background, config.theme.background);
setCursor(_timeleft+_dotsLeft, clockTop);
print(":"); /* print dots */
setFont();
}
void DspCore::_clockDate(){
if(_olddateleft>0)
dsp.fillRect(_olddateleft, clockTop+10, _olddatewidth, CHARHEIGHT, config.theme.background);
setTextColor(config.theme.date, config.theme.background);
setCursor(_dateleft, clockTop+15);
setTextSize(2);
print(_dateBuf); /* print date */
strlcpy(_oldDateBuf, _dateBuf, sizeof(_dateBuf));
_olddatewidth = _datewidth;
_olddateleft = _dateleft;
setTextSize(4);
setTextColor(config.theme.dow, config.theme.background);
setCursor(width() - 8 - clockRightSpace - CHARWIDTH*4*2, clockTop-CHARHEIGHT*4+4);
print(utf8Rus(dow[network.timeinfo.tm_wday], false)); /* print dow */
}
void DspCore::_clockTime(){
if(_oldtimeleft>0) dsp.fillRect(_oldtimeleft, clockTop-clockTimeHeight+1, _oldtimewidth, clockTimeHeight, config.theme.background);
_timeleft = width()-clockRightSpace-CHARWIDTH*4*2-24-_timewidth;
setTextSize(1);
setFont(&DS_DIGI56pt7b);
setTextColor(config.theme.clock, config.theme.background);
setCursor(_timeleft, clockTop);
print(_timeBuf);
setFont();
strlcpy(_oldTimeBuf, _timeBuf, sizeof(_timeBuf));
_oldtimewidth = _timewidth;
_oldtimeleft = _timeleft;
drawFastVLine(width()-clockRightSpace-CHARWIDTH*4*2-18, clockTop-clockTimeHeight, clockTimeHeight+4, config.theme.div); /*divider vert*/
drawFastHLine(width()-clockRightSpace-CHARWIDTH*4*2-18, clockTop-clockTimeHeight+37, 59, config.theme.div); /*divider hor*/
sprintf(_buffordate, "%2d %s %d", network.timeinfo.tm_mday,mnths[network.timeinfo.tm_mon], network.timeinfo.tm_year+1900);
strlcpy(_dateBuf, utf8Rus(_buffordate, true), sizeof(_dateBuf));
_datewidth = strlen(_dateBuf) * CHARWIDTH*2;
_dateleft = width() - 10 - clockRightSpace - _datewidth;
}
void DspCore::printClock(uint16_t top, uint16_t rightspace, uint16_t timeheight, bool redraw){
clockTop = top;
clockRightSpace = rightspace;
clockTimeHeight = timeheight;
strftime(_timeBuf, sizeof(_timeBuf), "%H:%M", &network.timeinfo);
if(strcmp(_oldTimeBuf, _timeBuf)!=0 || redraw){
_getTimeBounds();
_clockTime();
if(strcmp(_oldDateBuf, _dateBuf)!=0 || redraw) _clockDate();
}
_clockSeconds();
}
void DspCore::clearClock(){
dsp.fillRect(_timeleft, clockTop-clockTimeHeight, _timewidth+CHARWIDTH*3*2+24, clockTimeHeight+12+CHARHEIGHT, config.theme.background);
}
void DspCore::startWrite(void) {
TAKE_MUTEX();
ILI9486_SPI::startWrite();
}
void DspCore::endWrite(void) {
ILI9486_SPI::endWrite();
GIVE_MUTEX();
}
void DspCore::loop(bool force) {
}
void DspCore::charSize(uint8_t textsize, uint8_t& width, uint16_t& height){
width = textsize * CHARWIDTH;
height = textsize * CHARHEIGHT;
}
void DspCore::setTextSize(uint8_t s){
Adafruit_GFX::setTextSize(s);
}
void DspCore::flip(){
setRotation(config.store.flipscreen?3:1);
}
void DspCore::invert(){
TAKE_MUTEX();
invertDisplay(config.store.invertdisplay);
GIVE_MUTEX();
}
void DspCore::sleep(void) {
sendCommand(ILI9488_SLPIN); delay(150); sendCommand(ILI9488_DISPOFF); delay(150);
}
void DspCore::wake(void) {
sendCommand(ILI9488_DISPON); delay(150); sendCommand(ILI9488_SLPOUT); delay(150);
}
void DspCore::writePixel(int16_t x, int16_t y, uint16_t color) {
if(_clipping){
if ((x < _cliparea.left) || (x > _cliparea.left+_cliparea.width) || (y < _cliparea.top) || (y > _cliparea.top + _cliparea.height)) return;
}
ILI9486_SPI::drawPixel(x, y, color);
}
void DspCore::writeFillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) {
if(_clipping){
if ((x < _cliparea.left) || (x >= _cliparea.left+_cliparea.width) || (y < _cliparea.top) || (y > _cliparea.top + _cliparea.height)) return;
}
ILI9486_SPI::writeFillRect(x, y, w, h, color);
}
void DspCore::setClipping(clipArea ca){
_cliparea = ca;
_clipping = true;
}
void DspCore::clearClipping(){
_clipping = false;
}
void DspCore::setNumFont(){
setFont(&DS_DIGI56pt7b);
setTextSize(1);
}
#endif

View File

@@ -0,0 +1,39 @@
#ifndef displayILI9488_h
#define displayILI9488_h
#include "../core/options.h"
#include "Arduino.h"
#include <Adafruit_GFX.h>
#include "../ILI9488/ILI9486_SPI.h"
#include "fonts/DS_DIGI56pt7b.h" // https://tchapi.github.io/Adafruit-GFX-Font-Customiser/
#include "tools/l10n.h"
#define CHARWIDTH 6
#define CHARHEIGHT 8
typedef GFXcanvas16 Canvas;
#include "widgets/widgets.h"
#include "widgets/pages.h"
#if __has_include("conf/displayST7796conf_custom.h")
#include "conf/displayILI9488conf_custom.h"
#else
#include "conf/displayILI9488conf.h"
#endif
#define BOOT_PRG_COLOR 0xE68B
#define BOOT_TXT_COLOR 0xFFFF
#define PINK 0xF97F
#define ILI9488_SLPIN 0x10
#define ILI9488_SLPOUT 0x11
#define ILI9488_DISPOFF 0x28
#define ILI9488_DISPON 0x29
class DspCore: public ILI9486_SPI {
#include "tools/commongfx.h"
};
extern DspCore dsp;
#endif

View File

@@ -38,6 +38,8 @@
#include "displayST7796.h" #include "displayST7796.h"
#elif DSP_MODEL==DSP_GC9A01A #elif DSP_MODEL==DSP_GC9A01A
#include "displayGC9A01A.h" #include "displayGC9A01A.h"
#elif DSP_MODEL==DSP_ILI9488 || DSP_MODEL==DSP_ILI9486
#include "displayILI9488.h"
#endif #endif
//extern DspCore dsp; //extern DspCore dsp;