Tasmota/lib/Xlatb_RA8876-gemu-1.0/RA8876.cpp

1408 lines
38 KiB
C++
Raw Normal View History

2019-08-19 12:21:54 +01:00
/*MIT License
Copyright (c) 2017 xlatb
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <SPI.h>
#include "RA8876.h"
#include <limits.h>
/* TODO
font 0 x and y size with line,col cmd
support for rotation
fast picture write
*/
//void SHOW_STAGE(uint8_t stage) {
// Serial.printf(">%d,\n", stage);
//
const uint16_t RA8876_colors[]={RA8876_BLACK,RA8876_WHITE,RA8876_RED,RA8876_GREEN,RA8876_BLUE,RA8876_CYAN,RA8876_MAGENTA,\
RA8876_YELLOW,RA8876_NAVY,RA8876_DARKGREEN,RA8876_DARKCYAN,RA8876_MAROON,RA8876_PURPLE,RA8876_OLIVE,\
RA8876_LIGHTGREY,RA8876_DARKGREY,RA8876_ORANGE,RA8876_GREENYELLOW,RA8876_PINK};
uint16_t RA8876::GetColorFromIndex(uint8_t index) {
if (index>=sizeof(RA8876_colors)/2) index=0;
return RA8876_colors[index];
}
// Constructor when using software SPI. All output pins are configurable.
RA8876::RA8876(int8_t cs,int8_t mosi,int8_t miso,int8_t sclk,int8_t bp) : Renderer(RA8876_TFTWIDTH, RA8876_TFTHEIGHT) {
m_csPin = cs;
_mosi = mosi;
_miso = miso;
_sclk = sclk;
/*
_bp = bp;
if (_bp<99) {
pinMode(_bp, OUTPUT);
digitalWrite(_bp,HIGH);
}*/
_hwspi = 1;
}
//#define RA8876_CS_LOW digitalWrite(m_csPin, LOW)
//#define RA8876_CS_HIGH digitalWrite(m_csPin, HIGH)
2020-04-12 12:22:23 +01:00
#ifdef ESP8266
2019-08-19 12:21:54 +01:00
#define RA8876_CS_LOW GPOC=(1<<m_csPin);
#define RA8876_CS_HIGH GPOS=(1<<m_csPin);
2020-04-12 12:22:23 +01:00
#else
#define RA8876_CS_LOW digitalWrite(1<<m_csPin,0);
#define RA8876_CS_HIGH digitalWrite(1<<m_csPin,1);
#endif
2019-08-19 12:21:54 +01:00
/*
extern void ICACHE_RAM_ATTR RA8876_digitalWrite(uint8_t pin, uint8_t val) {
//stopWaveform(pin);
if(pin < 16){
if(val) GPOS = (1 << pin);
else GPOC = (1 << pin);
} else if(pin == 16){
if(val) GP16O |= 1;
else GP16O &= ~1;
}
}
*/
void RA8876::writeCmd(uint8_t x) {
RA8876_CS_LOW
SPI.transfer(RA8876_CMD_WRITE);
SPI.transfer(x);
RA8876_CS_HIGH
}
void RA8876::writeData(uint8_t x) {
RA8876_CS_LOW
SPI.transfer(RA8876_DATA_WRITE);
SPI.transfer(x);
RA8876_CS_HIGH
}
uint8_t RA8876::readData(void) {
RA8876_CS_LOW
SPI.transfer(RA8876_DATA_READ);
uint8_t x = SPI.transfer(0);
RA8876_CS_HIGH
return x;
}
// Reads the special status register.
// This register uses a special cycle type instead of having an address like other registers.
// See data sheet section 19.1.
uint8_t RA8876::readStatus(void) {
RA8876_CS_LOW
SPI.transfer(RA8876_STATUS_READ);
uint8_t x = SPI.transfer(0);
RA8876_CS_HIGH
return x;
}
void RA8876::writeReg(uint8_t reg, uint8_t x) {
writeCmd(reg);
writeData(x);
}
// Like writeReg(), but does two successive register writes of a 16-bit value, low byte first.
void RA8876::writeReg16(uint8_t reg, uint16_t x) {
writeCmd(reg);
writeData(x & 0xFF);
writeCmd(reg + 1);
writeData(x >> 8);
}
uint8_t RA8876::readReg(uint8_t reg) {
writeCmd(reg);
return readData();
}
// Like readReg(), but does two successive register reads of a 16-bit value, low byte first.
uint16_t RA8876::readReg16(uint8_t reg) {
uint16_t v;
writeCmd(reg);
v = readData();
writeCmd(reg + 1);
v |= readData() << 8;
return v;
}
// Trigger a soft reset. Note that the data sheet section 19.2 says that this only resets the
// "internal state machine", not any configuration registers.
void RA8876::softReset(void) {
SPI.beginTransaction(m_spiSettings);
// Trigger soft reset
writeReg(RA8876_REG_SRR, 0x01);
delay(5);
// Wait for status register to show "normal operation".
uint8_t status;
for (int i = 0; i < 250; i++) {
delay(1);
if (((status = readStatus()) & 0x02) == 0)
break;
}
SPI.endTransaction();
return;
}
void RA8876::DisplayOnff(int8_t on) {
uint8_t dpcr;
SPI.beginTransaction(m_spiSettings);
dpcr = readReg(RA8876_REG_DPCR);
if (on) {
dpcr |= 0x40; // Display on
dim(dimmer);
} else {
dpcr &= 0x40^0xff; // Display off
// backlight off
writeReg(RA8876_REG_TCMPB0L,0);
writeReg(RA8876_REG_TCMPB0H,0);
}
writeReg(RA8876_REG_DPCR, dpcr);
SPI.endTransaction();
}
// 0-15
void RA8876::dim(uint8_t contrast) {
SPI.beginTransaction(m_spiSettings);
dimmer=contrast;
// pwm0 duty
uint32_t duty=(contrast*1024)/15;
writeReg(RA8876_REG_TCMPB0L,duty);
writeReg(RA8876_REG_TCMPB0H,duty>>8);
SPI.endTransaction();
}
// pwm needed for backlight dimmer
void RA8876::PWM_init(void) {
SPI.beginTransaction(m_spiSettings);
writeReg(RA8876_REG_PSCLR,3);
uint8_t val=RA8876_PWM_TIMER_DIV4<<6|RA8876_PWM_TIMER_DIV4<<4|RA8876_XPWM1_OUTPUT_PWM_TIMER1<<2|RA8876_XPWM0_OUTPUT_PWM_TIMER0;
writeReg(RA8876_REG_PMUXR,val);
val=RA8876_PWM_TIMER1_INVERTER_ON<<6|RA8876_PWM_TIMER1_AUTO_RELOAD<<5|RA8876_PWM_TIMER1_START<<4|RA8876_PWM_TIMER0_DEAD_ZONE_DISABLE<<3|
RA8876_PWM_TIMER0_INVERTER_OFF<<2|RA8876_PWM_TIMER0_AUTO_RELOAD<<1|RA8876_PWM_TIMER0_START;
writeReg(RA8876_REG_PCFGR,val);
uint16_t duty=0x00ff;
// pwm0 duty for backlight
writeReg(RA8876_REG_TCMPB0L,duty);
writeReg(RA8876_REG_TCMPB0H,duty>>8);
// pwm1 duty not needed
duty=0x00ff;
writeReg(RA8876_REG_TCMPB1L,duty);
writeReg(RA8876_REG_TCMPB1H,duty>>8);
// clocksperiod
uint16_t clocks_per_period=1024;
writeReg(RA8876_REG_TCNTB0L,clocks_per_period);
writeReg(RA8876_REG_TCNTB0H,clocks_per_period>>8);
writeReg(RA8876_REG_TCNTB1L,clocks_per_period);
writeReg(RA8876_REG_TCNTB1H,clocks_per_period>>8);
SPI.endTransaction();
}
// Given a target frequency in kHz, finds PLL parameters k and n to reach as
// close as possible to the target frequency without exceeding it.
// The k parameter will be constrained to the range 1..kMax.
// Returns true iff PLL params were found, even if not an exact match.
bool RA8876::calcPllParams(uint32_t targetFreq, int kMax, PllParams *pll) {
bool found = false;
int foundk, foundn;
uint32_t foundFreq;
uint32_t foundError; // Amount lower than requested frequency
// k of 0 (i.e. 2 ** 0 = 1) is possible, but not sure if it's a good idea.
for (int testk = 1; testk <= kMax; testk++)
{
if (m_oscClock % (1 << testk))
continue; // Step size with this k would be fractional
int testn = (targetFreq / (m_oscClock / (1 << testk))) - 1;
if ((testn < 1) || (testn > 63))
continue; // Param n out of range for this k
// Fvco constraint found in data sheet section 6.1.2
uint32_t fvco = m_oscClock * (testn + 1);
if ((fvco < 100000) && (fvco > 600000))
continue; // Fvco out of range
// Found some usable params, but perhaps at a lower frequency than requested.
uint32_t freq = (m_oscClock * (testn + 1)) / (1 << testk);
uint32_t error = targetFreq - freq;
if ((!found) || (found && (foundError > error)))
{
found = true;
foundk = testk;
foundn = testn;
foundFreq = freq;
foundError = error;
// No need to keep searching if the frequency match was exact
if (foundError == 0)
break;
}
}
if (found)
{
pll->freq = foundFreq;
pll->k = foundk;
pll->n = foundn;
}
return found;
}
// Calculates the clock frequencies and their PLL parameters.
bool RA8876::calcClocks(void) {
// Data sheet section 5.2 gives max clocks:
// memClock : 166 MHz
// coreClock: 120 MHz (133MHz if not using internal font)
// scanClock: 100 MHz
// Mem clock target is the same as SDRAM speed, but capped at 166 MHz
uint32_t memClock = m_sdramInfo->speed * 1000;
if (memClock > 166000)
memClock = 166000;
if (!calcPllParams(memClock, 3, &m_memPll))
return false;
// Core clock target will be the same as the mem clock, but capped to
// 120 MHz, because that is the max frequency if we want to use the
// internal font.
uint32_t coreClock = m_memPll.freq;
if (coreClock > 120000)
coreClock = 120000;
if (!calcPllParams(coreClock, 3, &m_corePll))
return false;
// Scan clock target will be the display's dot clock, but capped at 100 MHz
uint32_t scanClock = m_displayInfo->dotClock;
if (scanClock > 100000)
scanClock = 100000;
if (!calcPllParams(scanClock, 7, &m_scanPll))
return false;
//dumpClocks();
// Data sheet section 6.1.1 rules:
// 1. Core clock must be less than or equal to mem clock
if (m_corePll.freq > m_memPll.freq)
return false;
// 2. Core clock must be greater than half mem clock
if ((m_corePll.freq * 2) <= m_memPll.freq)
return false;
// 3. Core clock must be greater than (scan clock * 1.5)
if (m_corePll.freq <= (m_scanPll.freq + (m_scanPll.freq >> 1)))
return false;
return true;
}
// Dump clock info to serial monitor.
/*
void RA8876::dumpClocks(void)
{
Serial.println("\nMem\n---");
Serial.print("Requested kHz: "); Serial.println(m_sdramInfo->speed * 1000);
Serial.print("Actual kHz : "); Serial.println(m_memPll.freq);
Serial.print("PLL k : "); Serial.println(m_memPll.k);
Serial.print("PLL n : "); Serial.println(m_memPll.n);
Serial.println("\nCore\n----");
Serial.print("kHz : "); Serial.println(m_corePll.freq);
Serial.print("PLL k : "); Serial.println(m_corePll.k);
Serial.print("PLL n : "); Serial.println(m_corePll.n);
Serial.println("\nScan\n----");
Serial.print("Requested kHz: "); Serial.println(m_displayInfo->dotClock);
Serial.print("Actual kHz : "); Serial.println(m_scanPll.freq);
Serial.print("PLL k : "); Serial.println(m_scanPll.k);
Serial.print("PLL n : "); Serial.println(m_scanPll.n);
// TODO: Frame rate?
return;
}
*/
bool RA8876::initPLL(void) {
//Serial.println("init PLL");
SPI.beginTransaction(m_spiSettings);
//Serial.print("DRAM_FREQ "); Serial.println(m_memPll.freq);
//Serial.print("7: "); Serial.println(m_memPll.k << 1);
//Serial.print("8: "); Serial.println(m_memPll.n);
writeReg(RA8876_REG_MPLLC1, m_memPll.k << 1);
writeReg(RA8876_REG_MPLLC2, m_memPll.n);
//Serial.print("CORE_FREQ "); Serial.println(m_corePll.freq);
//Serial.print("9: "); Serial.println(m_corePll.k << 1);
//Serial.print("A: "); Serial.println(m_corePll.n);
writeReg(RA8876_REG_SPLLC1, m_corePll.k << 1);
writeReg(RA8876_REG_SPLLC2, m_corePll.n);
// Per the data sheet, there are two divider fields for the scan clock, but the math seems
// to work out if we treat k as a single 3-bit number in bits 3..1.
//Serial.print("SCAN_FREQ "); Serial.println(m_scanPll.freq);
//Serial.print("5: "); Serial.println(m_scanPll.k << 1);
//Serial.print("6: "); Serial.println(m_scanPll.n);
writeReg(RA8876_REG_PPLLC1, m_scanPll.k << 1);
writeReg(RA8876_REG_PPLLC2, m_scanPll.n);
// Toggle bit 7 of the CCR register to trigger a reconfiguration of the PLLs
writeReg(RA8876_REG_CCR, 0x00);
delay(2);
writeReg(RA8876_REG_CCR, 0x80);
delay(2);
uint8_t ccr = readReg(RA8876_REG_CCR);
SPI.endTransaction();
return (ccr & 0x80) ? true : false;
}
// Initialize SDRAM interface.
bool RA8876::initMemory(SdramInfo *info) {
//Serial.println("init memory");
uint32_t sdramRefreshRate;
uint8_t sdrar = 0x00;
uint8_t sdrmd = 0x00;
// Refresh rate
sdramRefreshRate = ((uint32_t) info->refresh * info->speed * 1000) >> info->rowBits;
// Number of banks
if (info->banks == 2)
; // NOP
else if (info->banks == 4)
sdrar |= 0x20;
else
return false; // Unsupported number of banks
// Number of row bits
if ((info->rowBits < 11) || (info->rowBits > 13))
return false; // Unsupported number of row bits
else
sdrar |= ((info->rowBits - 11) & 0x03) << 3;
// Number of column bits
if ((info->colBits < 8) || (info->colBits > 12))
return false; // Unsupported number of column bits
else
sdrar |= info->colBits & 0x03;
// CAS latency
if ((info->casLatency < 2) || (info->casLatency > 3))
return false; // Unsupported CAS latency
else
sdrmd |= info->casLatency & 0x03;
SPI.beginTransaction(m_spiSettings);
//Serial.print("SDRAR: "); Serial.println(sdrar); // Expected: 0x29 (41 decimal)
writeReg(RA8876_REG_SDRAR, sdrar);
//Serial.print("SDRMD: "); Serial.println(sdrmd);
writeReg(RA8876_REG_SDRMD, sdrmd);
//Serial.print("sdramRefreshRate: "); Serial.println(sdramRefreshRate);
writeReg(RA8876_REG_SDR_REF_ITVL0, sdramRefreshRate & 0xFF);
writeReg(RA8876_REG_SDR_REF_ITVL1, sdramRefreshRate >> 8);
// Trigger SDRAM initialization
writeReg(RA8876_REG_SDRCR, 0x01);
// Wait for SDRAM to be ready
uint8_t status;
for (int i = 0; i < 250; i++) {
delay(1);
if ((status = readStatus()) & 0x40)
break;
}
SPI.endTransaction();
//Serial.println(status);
return (status & 0x40) ? true : false;
}
void RA8876::DisplayInit(int8_t p,int8_t size,int8_t rot,int8_t font) {
setRotation(rot);
setTextWrap(false); // Allow text to run off edges
cp437(true);
setTextFont(font&3);
setTextSize(size&7);
setTextColor(RA8876_WHITE,RA8876_BLACK);
setCursor(0,0);
fillScreen(RA8876_BLACK);
setDrawMode(drawmode);
PWM_init();
}
bool RA8876::initDisplay() {
SPI.beginTransaction(m_spiSettings);
// Set chip config register
uint8_t ccr = readReg(RA8876_REG_CCR);
ccr &= 0xE7; // 24-bit LCD output
ccr &= 0xFE; // 8-bit host data bus
writeReg(RA8876_REG_CCR, ccr);
writeReg(RA8876_REG_MACR, 0x00); // Direct write, left-to-right-top-to-bottom memory
writeReg(RA8876_REG_ICR, 0x00); // Graphics mode, memory is SDRAM
uint8_t dpcr = readReg(RA8876_REG_DPCR);
dpcr &= 0xFB; // Vertical scan top to bottom
dpcr &= 0xF8; // Colour order RGB
dpcr |= 0x80; // Panel fetches PDAT at PCLK falling edge
writeReg(RA8876_REG_DPCR, dpcr);
uint8_t pcsr = readReg(RA8876_REG_PCSR);
pcsr |= 0x80; // XHSYNC polarity high
pcsr |= 0x40; // XVSYNC polarity high
pcsr &= 0xDF; // XDE polarity high
writeReg(RA8876_REG_PCSR, pcsr);
// Set display width
writeReg(RA8876_REG_HDWR, (m_displayInfo->width / 8) - 1);
writeReg(RA8876_REG_HDWFTR, (m_displayInfo->width % 8));
// Set display height
writeReg(RA8876_REG_VDHR0, (m_displayInfo->height - 1) & 0xFF);
writeReg(RA8876_REG_VDHR1, (m_displayInfo->height - 1) >> 8);
// Set horizontal non-display (back porch)
writeReg(RA8876_REG_HNDR, (m_displayInfo->hBackPorch / 8) - 1);
writeReg(RA8876_REG_HNDFTR, (m_displayInfo->hBackPorch % 8));
// Set horizontal start position (front porch)
writeReg(RA8876_REG_HSTR, ((m_displayInfo->hFrontPorch + 4) / 8) - 1);
// Set HSYNC pulse width
writeReg(RA8876_REG_HPWR, ((m_displayInfo->hPulseWidth + 4) / 8) - 1);
// Set vertical non-display (back porch)
writeReg(RA8876_REG_VNDR0, (m_displayInfo->vBackPorch - 1) & 0xFF);
writeReg(RA8876_REG_VNDR1, (m_displayInfo->vBackPorch - 1) >> 8);
// Set vertical start position (front porch)
writeReg(RA8876_REG_VSTR, m_displayInfo->vFrontPorch - 1);
// Set VSYNC pulse width
writeReg(RA8876_REG_VPWR, m_displayInfo->vPulseWidth - 1);
// Set main window to 16 bits per pixel
writeReg(RA8876_REG_MPWCTR, 0x04); // PIP windows disabled, 16-bpp, enable sync signals
// Set main window start address to 0
writeReg(RA8876_REG_MISA0, 0);
writeReg(RA8876_REG_MISA1, 0);
writeReg(RA8876_REG_MISA2, 0);
writeReg(RA8876_REG_MISA3, 0);
// Set main window image width
writeReg(RA8876_REG_MIW0, m_width & 0xFF);
writeReg(RA8876_REG_MIW1, m_width >> 8);
// Set main window start coordinates
writeReg(RA8876_REG_MWULX0, 0);
writeReg(RA8876_REG_MWULX1, 0);
writeReg(RA8876_REG_MWULY0, 0);
writeReg(RA8876_REG_MWULY1, 0);
// Set canvas start address
writeReg(RA8876_REG_CVSSA0, 0);
writeReg(RA8876_REG_CVSSA1, 0);
writeReg(RA8876_REG_CVSSA2, 0);
writeReg(RA8876_REG_CVSSA3, 0);
// Set canvas width
writeReg(RA8876_REG_CVS_IMWTH0, m_width & 0xFF);
writeReg(RA8876_REG_CVS_IMWTH1, m_width >> 8);
// Set active window start coordinates
writeReg(RA8876_REG_AWUL_X0, 0);
writeReg(RA8876_REG_AWUL_X1, 0);
writeReg(RA8876_REG_AWUL_Y0, 0);
writeReg(RA8876_REG_AWUL_Y1, 0);
// Set active window dimensions
writeReg(RA8876_REG_AW_WTH0, m_width & 0xFF);
writeReg(RA8876_REG_AW_WTH1, m_width >> 8);
writeReg(RA8876_REG_AW_HT0, m_height & 0xFF);
writeReg(RA8876_REG_AW_HT1, m_height >> 8);
// Set canvas addressing mode/colour depth
uint8_t aw_color = 0x00; // 2d addressing mode
if (m_depth == 16)
aw_color |= 0x01;
else if (m_depth == 24)
aw_color |= 0x02;
writeReg(RA8876_REG_AW_COLOR, aw_color);
// Turn on display
dpcr = readReg(RA8876_REG_DPCR);
dpcr |= 0x40; // Display on
writeReg(RA8876_REG_DPCR, dpcr);
// TODO: Track backlight pin and turn on backlight
SPI.endTransaction();
return true;
}
void RA8876::setRotation(uint8_t m) {
return;
/*
SPI.beginTransaction(m_spiSettings);
rotation = m % 4; // can't be higher than 3
switch (rotation) {
case 0:
writeReg(RA8876_REG_MACR, 0x00);
_width = RA8876_TFTWIDTH;
_height = RA8876_TFTHEIGHT;
break;
case 1:
writeReg(RA8876_REG_MACR, 0x02);
_width = RA8876_TFTHEIGHT;
_height = RA8876_TFTWIDTH;
break;
case 2:
writeReg(RA8876_REG_MACR, 0x01);
_width = RA8876_TFTWIDTH;
_height = RA8876_TFTHEIGHT;
break;
case 3:
writeReg(RA8876_REG_MACR, 0x03);
_width = RA8876_TFTHEIGHT;
_height = RA8876_TFTWIDTH;
break;
}
m_width = _width;
m_height = _height;
SPI.endTransaction();*/
}
SdramInfo defaultSdramInfo =
{
120, // 120 MHz
3, // CAS latency 3
4, // 4 banks
12, // 12-bit row addresses
9, // 9-bit column addresses
64 // 64 millisecond refresh time
};
DisplayInfo defaultDisplayInfo =
{
1024, // Display width
600, // Display height
50000, // Pixel clock in kHz
160, // Horizontal front porch
160, // Horizontal back porch
70, // HSYNC pulse width
12, // Vertical front porch
23, // Vertical back porch
10 // VSYNC pulse width
};
bool RA8876::begin(void) {
m_sdramInfo = &defaultSdramInfo;
m_displayInfo = &defaultDisplayInfo;
m_width = m_displayInfo->width;
m_height = m_displayInfo->height;
m_depth = 16;
m_oscClock = 10000; // 10000kHz or 10MHz
textcolor = 0xFFFF; // White
textbgcolor = 0; // black
m_fontRomInfo.present = false; // No external font ROM chip
// Set up chip select pin
pinMode(m_csPin, OUTPUT);
digitalWrite(m_csPin, HIGH);
if (!calcClocks()) {
//Serial.println("calcClocks failed");
return false;
}
SPI.begin();
m_spiSettings = SPISettings(RA8876_SPI_SPEED, MSBFIRST, SPI_MODE3);
softReset();
if (!initPLL()) {
//Serial.println("initPLL failed");
return false;
}
if (!initMemory(m_sdramInfo)) {
//Serial.println("initMemory failed");
return false;
}
if (!initDisplay()) {
//Serial.println("initDisplay failed");
return false;
}
// Set default font
selectInternalFont(RA8876_FONT_SIZE_16);
setTextScale(1);
setRotation(0);
clearScreen(0);
return true;
}
void RA8876::fillScreen(uint16_t color) {
clearScreen(color);
}
// Show colour bars of 8 colours in repeating horizontal bars.
// This does not alter video memory, but rather instructs the video controller to display
// the pattern rather than the contents of memory.
void RA8876::colorBarTest(bool enabled) {
SPI.beginTransaction(m_spiSettings);
uint8_t dpcr = readReg(RA8876_REG_DPCR);
if (enabled)
dpcr = dpcr | 0x20;
else
dpcr = dpcr & ~0x20;
writeReg(RA8876_REG_DPCR, dpcr);
SPI.endTransaction();
}
void RA8876::drawLine(int16_t x1, int16_t y1, int16_t x2, int16_t y2, uint16_t color) {
drawTwoPointShape(x1, y1, x2, y2, color, RA8876_REG_DCR0, 0x80);
};
void RA8876::drawRect(int16_t x1, int16_t y1, int16_t x2, int16_t y2, uint16_t color) {
drawTwoPointShape(x1, y1, x1+x2, y1+y2, color, RA8876_REG_DCR1, 0xA0);
};
void RA8876::fillRect(int16_t x1, int16_t y1, int16_t x2, int16_t y2, uint16_t color) {
drawTwoPointShape(x1, y1, x1+x2, y1+y2, color, RA8876_REG_DCR1, 0xE0);
};
void RA8876::drawTriangle(int16_t x1, int16_t y1, int16_t x2, int16_t y2, int16_t x3, int16_t y3, uint16_t color) {
drawThreePointShape(x1, y1, x2, y2, x3, y3, color, RA8876_REG_DCR0, 0xA2);
};
void RA8876::fillTriangle(int16_t x1, int16_t y1, int16_t x2, int16_t y2, int16_t x3, int16_t y3, uint16_t color) {
drawThreePointShape(x1, y1, x2, y2, x3, y3, color, RA8876_REG_DCR0, 0xE2);
};
void RA8876::drawCircle(int16_t x, int16_t y, int16_t radius, uint16_t color) {
drawEllipseShape(x, y, radius, radius, color, 0x80);
};
void RA8876::fillCircle(int16_t x, int16_t y, int16_t radius, uint16_t color) {
drawEllipseShape(x, y, radius, radius, color, 0xC0);
};
void RA8876::drawRoundRect(int16_t x0, int16_t y0, int16_t w, int16_t h, int16_t radius, uint16_t color) {
drawThreePointShape1(x0, y0, x0+w, y0+h, radius, radius, color, RA8876_REG_DCR1, 0xB0);
}
void RA8876::fillRoundRect(int16_t x0, int16_t y0, int16_t w, int16_t h, int16_t radius, uint16_t color) {
drawThreePointShape1(x0, y0, x0+w, y0+h, radius, radius, color, RA8876_REG_DCR1, 0xF0);
}
void RA8876::clearScreen(uint16_t color) {
setCursor(0, 0); fillRect(0, 0, m_width, m_height, color);
};
void RA8876::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) {
drawTwoPointShape(x, y, x, y+h, color, RA8876_REG_DCR1, 0xE0);
}
void RA8876::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) {
drawTwoPointShape(x, y, x+w, y, color, RA8876_REG_DCR1, 0xE0);
}
void RA8876::setTextSize(uint8_t s) {
setTextScale(s, s);
textsize_x=s;
textsize_y=s;
//Serial.printf("scale %d\n",s);
}
// Picture16bppBteMpuWriteColorExpansionWithChromaKey
// PAGE1_START_ADDR,SCREEN_WIDTH, 50, 50+128+50+10, 128, 128,COLOR65K_WHITE,COLOR65K_BLACK,"sun.bin");
// ra8876lite.bteMpuWriteWithROP(s1_addr, s1_image_width, s1_x, s1_y, des_addr, des_image_width, des_x, des_y, width, height, rop_code);
//dCardShowPicture16bppBteMpuWriteWithROP(PAGE1_START_ADDR, SCREEN_WIDTH, 50+128, 50, PAGE1_START_ADDR, SCREEN_WIDTH, 50+128, 50,
// 128, 128,RA8876_BTE_ROP_CODE_6,"appli.bin");
/*bte_Source1_MemoryStartAddr(s1_addr);
bte_Source1_ImageWidth(s1_image_width);
bte_Source1_WindowStartXY(s1_x,s1_y);
bte_DestinationMemoryStartAddr(des_addr);
bte_DestinationImageWidth(des_image_width);
bte_DestinationWindowStartXY(des_x,des_y);
bte_WindowSize(width,height);
lcdRegDataWrite(RA8876_BTE_CTRL1,rop_code<<4|RA8876_BTE_MPU_WRITE_WITH_ROP);//91h
lcdRegDataWrite(RA8876_BTE_COLR,RA8876_S0_COLOR_DEPTH_16BPP<<5|RA8876_S1_COLOR_DEPTH_16BPP<<2|RA8876_DESTINATION_COLOR_DEPTH_16BPP);//92h
lcdRegDataWrite(RA8876_BTE_CTRL0,RA8876_BTE_ENABLE<<4);//90h
ramAccessPrepare();
// put picturess
activeWindowXY(x,y);
activeWindowWH(width,height);
setPixelCursor(x,y);
*/
void RA8876::setAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
uint16_t xs=x1-x0;
uint16_t ys=y1-y0;
cursor_x=x0;
cursor_y=y0;
addrw_x1=x0;
addrw_x2=x1;
uint8_t flag=0;
if (!x0 && !y0 && !x1 && !y1) {
x0=0;
y0=0;
x1=_width;
y1=_height;
flag=1;
}
if (x1>_width) x1=_width;
if (y1>_height) y1=_height;
SPI.beginTransaction(m_spiSettings);
#if 1
// activeWindowXY(x,y);
writeReg(RA8876_REG_AWUL_X0,x0);//56h
writeReg(RA8876_REG_AWUL_X1,x0>>8);//57h
writeReg(RA8876_REG_AWUL_Y0,y0);//58h
writeReg(RA8876_REG_AWUL_Y1,y0>>8);//59h
//activeWindowWH(width,height);
writeReg(RA8876_REG_AW_WTH0,xs);//5ah
writeReg(RA8876_REG_AW_WTH1,xs>>8);//5bh
writeReg(RA8876_REG_AW_HT0,ys);//5ch
writeReg(RA8876_REG_AW_HT1,ys>>8);//5dh
//setPixelCursor(x,y);
writeReg(RA8876_REG_CURH0,x0); //5fh
writeReg(RA8876_REG_CURH1,x0>>8);//60h
writeReg(RA8876_REG_CURV0,y0);//61h
writeReg(RA8876_REG_CURV1,y0>>8);//62h
//ramAccessPrepare();
#else
// source address, page1=0
// bte_Source1_MemoryStartAddr
uint32_t addr=0;
writeReg(RA8876_REG_S1_STR0,addr);//9dh
writeReg(RA8876_REG_S1_STR1,addr>>8);//9eh
writeReg(RA8876_REG_S1_STR2,addr>>16);//9fh
writeReg(RA8876_REG_S1_STR3,addr>>24);//a0h
// screen width
// bte_Source1_ImageWidth
uint16_t width=_width;
writeReg(RA8876_REG_S1_WTH0,width);//abh
writeReg(RA8876_REG_S1_WTH1,width>>8);//ach
// source window
// bte_Source1_WindowStartXY
writeReg(RA8876_REG_S1_X0,x0);//adh
writeReg(RA8876_REG_S1_X1,x0>>8);//aeh
writeReg(RA8876_REG_S1_Y0,y0);//afh
writeReg(RA8876_REG_S1_Y1,y0>>8);//b0h
// dest mem
// bte_DestinationMemoryStartAddr
writeReg(RA8876_REG_DT_STR0,addr);//a7h
writeReg(RA8876_REG_DT_STR1,addr>>8);//a8h
writeReg(RA8876_REG_DT_STR2,addr>>16);//a9h
writeReg(RA8876_REG_DT_STR3,addr>>24);//aah
// bte_DestinationImageWidth
writeReg(RA8876_REG_DT_WTH0,width);//abh
writeReg(RA8876_REG_DT_WTH1,width>>8);//ach
//bte_DestinationWindowStartXY(des_x,des_y);
writeReg(RA8876_REG_DT_X0,x0);//adh
writeReg(RA8876_REG_DT_X1,x0>>8);//aeh
writeReg(RA8876_REG_DT_Y0,y0);//afh
writeReg(RA8876_REG_DT_Y1,y0>>8);//b0h
// bte_WindowSize
writeReg(RA8876_REG_BTE_WTH0,xs);//b1h
writeReg(RA8876_REG_BTE_WTH1,xs>>8);//b2h
writeReg(RA8876_REG_BTE_HIG0,ys);//b3h
writeReg(RA8876_REG_BTE_HIG1,ys>>8);//b4h
writeReg(RA8876_BTE_CTRL1,RA8876_BTE_ROP_CODE_12<<4|RA8876_BTE_MPU_WRITE_WITH_ROP);//91h
writeReg(RA8876_BTE_COLR,RA8876_S0_COLOR_DEPTH_16BPP<<5|RA8876_S1_COLOR_DEPTH_16BPP<<2|RA8876_DESTINATION_COLOR_DEPTH_16BPP);//92h
writeReg(RA8876_BTE_CTRL0,RA8876_BTE_ENABLE<<4);//90h
#endif
writeCmd(RA8876_REG_MRWDP); //04h();
if (flag) SPI.endTransaction();
}
void RA8876::pushColors(uint16_t *data, uint8_t len, boolean first) {
SPI.beginTransaction(m_spiSettings);
while (len--) {
//uint16_t color=RA8876_WHITE;
uint16_t color=*data++;
//waitWriteFifo();
writeData(color&0xff);
//waitWriteFifo();
writeData(color>>8);
}
SPI.endTransaction();
}
void RA8876::drawPixel(int16_t x, int16_t y, uint16_t color) {
//Serial.println("drawPixel");
//Serial.println(readStatus());
SPI.beginTransaction(m_spiSettings);
writeReg(RA8876_REG_CURH0, x & 0xFF);
writeReg(RA8876_REG_CURH1, x >> 8);
writeReg(RA8876_REG_CURV0, y & 0xFF);
writeReg(RA8876_REG_CURV1, y >> 8);
writeReg(RA8876_REG_MRWDP, color & 0xFF);
writeReg(RA8876_REG_MRWDP, color >> 8);
SPI.endTransaction();
}
void RA8876::drawTwoPointShape(int x1, int y1, int x2, int y2, uint16_t color, uint8_t reg, uint8_t cmd) {
//Serial.println("drawTwoPointShape");
SPI.beginTransaction(m_spiSettings);
// First point
writeReg(RA8876_REG_DLHSR0, x1 & 0xFF);
writeReg(RA8876_REG_DLHSR1, x1 >> 8);
writeReg(RA8876_REG_DLVSR0, y1 & 0xFF);
writeReg(RA8876_REG_DLVSR1, y1 >> 8);
// Second point
writeReg(RA8876_REG_DLHER0, x2 & 0xFF);
writeReg(RA8876_REG_DLHER1, x2 >> 8);
writeReg(RA8876_REG_DLVER0, y2 & 0xFF);
writeReg(RA8876_REG_DLVER1, y2 >> 8);
// Colour
writeReg(RA8876_REG_FGCR, color >> 11 << 3);
writeReg(RA8876_REG_FGCG, ((color >> 5) & 0x3F) << 2);
writeReg(RA8876_REG_FGCB, (color & 0x1F) << 3);
// Draw
writeReg(reg, cmd); // Start drawing
// Wait for completion
wait_ready();
SPI.endTransaction();
}
// 1 us => 10 ms
#define RA8876_TIMEOUT 10000
void RA8876::waitWriteFifo(void) {
uint8_t status = readStatus();
uint32_t iter = 0;
while (status & 0x80) {
status = readStatus();
iter++;
if (iter>RA8876_TIMEOUT) {
// timeout, soft reset
softReset();
SPI.beginTransaction(m_spiSettings);
Serial.printf("iter timeout fifo\n");
return;
}
}
};
void RA8876::waitTaskBusy(void) {
//while (readStatus() & 0x08);
wait_ready();
};
void RA8876::wait_ready(void) {
uint8_t status = readStatus();
uint32_t iter = 0;
while (status & 0x08) {
status = readStatus();
iter++;
if (iter>RA8876_TIMEOUT) {
// timeout, soft reset
softReset();
SPI.beginTransaction(m_spiSettings);
Serial.printf("iter timeout cmd\n");
return;
}
}
// got at max 1800
// Serial.printf("iter: %d\n",iter);
}
void RA8876::drawThreePointShape(int x1, int y1, int x2, int y2, int x3, int y3, uint16_t color, uint8_t reg, uint8_t cmd) {
//Serial.println("drawThreePointShape");
SPI.beginTransaction(m_spiSettings);
// First point
writeReg(RA8876_REG_DLHSR0, x1 & 0xFF);
writeReg(RA8876_REG_DLHSR1, x1 >> 8);
writeReg(RA8876_REG_DLVSR0, y1 & 0xFF);
writeReg(RA8876_REG_DLVSR1, y1 >> 8);
// Second point
writeReg(RA8876_REG_DLHER0, x2 & 0xFF);
writeReg(RA8876_REG_DLHER1, x2 >> 8);
writeReg(RA8876_REG_DLVER0, y2 & 0xFF);
writeReg(RA8876_REG_DLVER1, y2 >> 8);
// Third point
writeReg(RA8876_REG_DTPH0, x3 & 0xFF);
writeReg(RA8876_REG_DTPH1, x3 >> 8);
writeReg(RA8876_REG_DTPV0, y3 & 0xFF);
writeReg(RA8876_REG_DTPV1, y3 >> 8);
// Colour
writeReg(RA8876_REG_FGCR, color >> 11 << 3);
writeReg(RA8876_REG_FGCG, ((color >> 5) & 0x3F) << 2);
writeReg(RA8876_REG_FGCB, (color & 0x1F) << 3);
// Draw
writeReg(reg, cmd); // Start drawing
// Wait for completion
wait_ready();
SPI.endTransaction();
}
void RA8876::drawThreePointShape1(int x1, int y1, int x2, int y2, int x3, int y3, uint16_t color, uint8_t reg, uint8_t cmd) {
//Serial.println("drawThreePointShape");
SPI.beginTransaction(m_spiSettings);
// First point
writeReg(RA8876_REG_DLHSR0, x1 & 0xFF);
writeReg(RA8876_REG_DLHSR1, x1 >> 8);
writeReg(RA8876_REG_DLVSR0, y1 & 0xFF);
writeReg(RA8876_REG_DLVSR1, y1 >> 8);
// Second point
writeReg(RA8876_REG_DLHER0, x2 & 0xFF);
writeReg(RA8876_REG_DLHER1, x2 >> 8);
writeReg(RA8876_REG_DLVER0, y2 & 0xFF);
writeReg(RA8876_REG_DLVER1, y2 >> 8);
// corner radius
writeReg(RA8876_REG_ELL_A0, x3 & 0xFF);
writeReg(RA8876_REG_ELL_A1, x3 >> 8);
writeReg(RA8876_REG_ELL_B0, y3 & 0xFF);
writeReg(RA8876_REG_ELL_B1, y3 >> 8);
#define RA8876_REG_ELL_A0 0x77 // Draw ellipse major radius 0
#define RA8876_REG_ELL_A1 0x78 // Draw ellipse major radius 1
#define RA8876_REG_ELL_B0 0x79 // Draw ellipse minor radius 0
#define RA8876_REG_ELL_B1 0x7A // Draw ellipse minor radius 1
// Colour
writeReg(RA8876_REG_FGCR, color >> 11 << 3);
writeReg(RA8876_REG_FGCG, ((color >> 5) & 0x3F) << 2);
writeReg(RA8876_REG_FGCB, (color & 0x1F) << 3);
// Draw
writeReg(reg, cmd); // Start drawing
// Wait for completion
wait_ready();
SPI.endTransaction();
}
void RA8876::drawEllipseShape(int x, int y, int xrad, int yrad, uint16_t color, uint8_t cmd) {
//Serial.println("drawEllipseShape");
SPI.beginTransaction(m_spiSettings);
// First point
writeReg16(RA8876_REG_DEHR0, x);
writeReg16(RA8876_REG_DEVR0, y);
// Radii
writeReg16(RA8876_REG_ELL_A0, xrad);
writeReg16(RA8876_REG_ELL_B0, yrad);
// Colour
writeReg(RA8876_REG_FGCR, color >> 11 << 3);
writeReg(RA8876_REG_FGCG, ((color >> 5) & 0x3F) << 2);
writeReg(RA8876_REG_FGCB, (color & 0x1F) << 3);
// Draw
writeReg(RA8876_REG_DCR1, cmd); // Start drawing
// Wait for completion
wait_ready();
SPI.endTransaction();
}
void RA8876::setCursor(int16_t x, int16_t y) {
SPI.beginTransaction(m_spiSettings);
writeReg16(RA8876_REG_F_CURX0, x);
writeReg16(RA8876_REG_F_CURY0, y);
SPI.endTransaction();
//Serial.printf("curs %d:%d\n",x,y);
cursor_x = x;
cursor_y = y;
}
int RA8876::getCursorX(void) {
SPI.beginTransaction(m_spiSettings);
int x = readReg16(RA8876_REG_F_CURX0);
SPI.endTransaction();
return x;
}
int RA8876::getCursorY(void) {
SPI.beginTransaction(m_spiSettings);
int y = readReg16(RA8876_REG_F_CURY0);
SPI.endTransaction();
return y;
}
// Given a font encoding value, returns the corresponding bit pattern for
// use by internal fonts.
uint8_t RA8876::internalFontEncoding(enum FontEncoding enc) {
uint8_t e;
switch (enc)
{
case RA8876_FONT_ENCODING_8859_2:
e = 0x01;
break;
case RA8876_FONT_ENCODING_8859_4:
e = 0x02;
break;
case RA8876_FONT_ENCODING_8859_5:
e = 0x03;
break;
default:
e = 0x00; // ISO-8859-1
break;
}
return e;
}
void RA8876::setTextMode(void) {
// Restore text colour
textcolor=textcolor;
writeReg(RA8876_REG_FGCR, textcolor >> 11 << 3);
writeReg(RA8876_REG_FGCG, ((textcolor >> 5) & 0x3F) << 2);
writeReg(RA8876_REG_FGCB, (textcolor & 0x1F) << 3);
writeReg(RA8876_REG_BGCR, textbgcolor >> 11 << 3);
writeReg(RA8876_REG_BGCG, ((textbgcolor >> 5) & 0x3F) << 2);
writeReg(RA8876_REG_BGCB, (textbgcolor & 0x1F) << 3);
waitTaskBusy();
// Enable text mode
uint8_t icr = readReg(RA8876_REG_ICR);
writeReg(RA8876_REG_ICR, icr | 0x04);
if (textcolor==textbgcolor) {
setDrawMode_reg(1);
} else {
setDrawMode_reg(0);
}
}
void RA8876::setGraphicsMode(void) {
waitTaskBusy();
// Disable text mode
uint8_t icr = readReg(RA8876_REG_ICR);
writeReg(RA8876_REG_ICR, icr & ~0x04);
}
void RA8876::selectInternalFont(enum FontSize size, enum FontEncoding enc) {
m_fontSource = RA8876_FONT_SOURCE_INTERNAL;
m_fontSize = size;
m_fontFlags = 0;
SPI.beginTransaction(m_spiSettings);
writeReg(RA8876_REG_CCR0, 0x00 | ((size & 0x03) << 4) | internalFontEncoding(enc));
uint8_t ccr1 = readReg(RA8876_REG_CCR1);
//ccr1 |= 0x40; // Transparent background
ccr1 &=0x40^0xff;
writeReg(RA8876_REG_CCR1, ccr1);
SPI.endTransaction();
}
void RA8876::setDrawMode(uint8_t mode) {
drawmode=mode;
setDrawMode_reg(mode);
}
void RA8876::setDrawMode_reg(uint8_t mode) {
SPI.beginTransaction(m_spiSettings);
uint8_t ccr1 = readReg(RA8876_REG_CCR1);
if (mode) {
ccr1 |= 0x40; // Transparent background
} else {
ccr1 &=0x40^0xff; // opaque background
}
writeReg(RA8876_REG_CCR1, ccr1);
SPI.endTransaction();
}
void RA8876::selectExternalFont(enum ExternalFontFamily family, enum FontSize size, enum FontEncoding enc, FontFlags flags) {
m_fontSource = RA8876_FONT_SOURCE_EXT_ROM;
m_fontSize = size;
m_fontFlags = flags;
SPI.beginTransaction(m_spiSettings);
//Serial.print("CCR0: "); Serial.println(0x40 | ((size & 0x03) << 4), HEX);
writeReg(RA8876_REG_CCR0, 0x40 | ((size & 0x03) << 4)); // Select external font ROM and size
uint8_t ccr1 = readReg(RA8876_REG_CCR1);
ccr1 |= 0x40; // Transparent background
//Serial.print("CCR1: "); Serial.println(ccr1, HEX);
writeReg(RA8876_REG_CCR1, ccr1);
//Serial.print("GTFNT_CR: "); Serial.println((enc << 3) | (family & 0x03), HEX);
writeReg(RA8876_REG_GTFNT_CR, (enc << 3) | (family & 0x03)); // Character encoding and family
SPI.endTransaction();
}
/*
void RA8876::setTextColor(uint16_t color) {
textcolor = color;
textbgcolor = color;
};
void RA8876::setTextColor(uint16_t c, uint16_t bg) {
textcolor = c;
textbgcolor=bg;
}
*/
void RA8876::setTextScale(int scale) {
setTextScale(scale, scale);
};
int RA8876::getTextSizeY(void) {
return ((m_fontSize + 2) * 8) * m_textScaleY;
}
void RA8876::setTextScale(int xScale, int yScale) {
xScale = constrain(xScale, 1, 4);
yScale = constrain(yScale, 1, 4);
m_textScaleX = xScale;
m_textScaleY = yScale;
SPI.beginTransaction(m_spiSettings);
uint8_t ccr1 = readReg(RA8876_REG_CCR1);
ccr1 = (ccr1 & 0xF0) | ((xScale - 1) << 2) | (yScale - 1);
//Serial.println(ccr1, HEX);
writeReg(RA8876_REG_CCR1, ccr1);
SPI.endTransaction();
}
// Similar to write(), but does no special handling of control characters.
void RA8876::putChars(const char *buffer, size_t size) {
SPI.beginTransaction(m_spiSettings);
setTextMode();
// Write characters
writeCmd(RA8876_REG_MRWDP);
for (unsigned int i = 0; i < size; i++)
{
waitWriteFifo();
writeData(buffer[i]);
}
setGraphicsMode();
SPI.endTransaction();
}
void RA8876::putChars16(const uint16_t *buffer, unsigned int count) {
SPI.beginTransaction(m_spiSettings);
setTextMode();
// Write characters
writeCmd(RA8876_REG_MRWDP);
for (unsigned int i = 0; i < count; i++)
{
waitWriteFifo();
writeData(buffer[i] >> 8);
waitWriteFifo();
writeData(buffer[i] & 0xFF);
}
setGraphicsMode();
SPI.endTransaction();
}
extern uint8_t wr_redir;
size_t RA8876::xwrite(uint8_t c) {
return xwrite(&c, 1);
};
//#define RA8876_DEBUG
size_t RA8876::xwrite(const uint8_t *buffer, size_t size) {
#ifdef RA8876_DEBUG
char buff[128];
memcpy(buff,buffer,size);
buff[size]=0;
Serial.printf("write start: %s\n",buff);
#endif
SPI.beginTransaction(m_spiSettings);
setTextMode();
writeCmd(RA8876_REG_MRWDP); // Set current register for writing to memory
for (unsigned int i = 0; i < size; i++)
{
char c = buffer[i];
if (!c) continue;
if (c == '\r')
; // Ignored
else if (c == '\n')
{
setCursor(0, getCursorY() + getTextSizeY());
writeCmd(RA8876_REG_MRWDP); // Reset current register for writing to memory
}
else if ((m_fontFlags & RA8876_FONT_FLAG_XLAT_FULLWIDTH) && ((c >= 0x21) || (c <= 0x7F)))
{
// Translate ASCII to Unicode fullwidth form (for Chinese fonts that lack ASCII)
uint16_t fwc = c - 0x21 + 0xFF01;
waitWriteFifo();
writeData(fwc >> 8);
waitWriteFifo();
writeData(fwc & 0xFF);
}
else
{
waitWriteFifo();
writeData(c);
}
}
setGraphicsMode();
SPI.endTransaction();
#ifdef RA8876_DEBUG
Serial.printf("write end:\n");
#endif
return size;
}
void RA8876::FastString(uint16_t x,uint16_t y,uint16_t tcolor, const char* str) {
setCursor(x,y);
setTextColor(tcolor,textbgcolor);
xwrite((uint8_t*)str,strlen(str));
}