Skip to content

Instantly share code, notes, and snippets.

@fanoush
Last active September 22, 2021 12:33
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fanoush/3dede6a16cef85fbf55f9d925521e4a0 to your computer and use it in GitHub Desktop.
Save fanoush/3dede6a16cef85fbf55f9d925521e4a0 to your computer and use it in GitHub Desktop.
Inline C P8 LCD DMA demo
E.kickWatchdog();
function KickWd(){
if( (typeof(BTN1)=='undefined')||(!BTN1.read()) ) E.kickWatchdog();
}
var wdint=setInterval(KickWd,2000);
E.enableWatchdog(15, false);
/*
// MIT License (c) 2020 fanoush https://github.com/fanoush
// see full license text at https://choosealicense.com/licenses/mit/
var SPI2 = E.compiledC(`
// int cmd(int,int)
// int cmds(int,int)
// int cmd4(int,int,int,int)
// void setpins(int,int,int,int)
// int enable(int,int)
// void disable()
// void blit_setup(int,int,int,int)
// int blt_pal(int,int,int)
//// int data(int,int)
//// void save()
//// void restore()
//// int fill_color(int,int)
// bpp 16,12 and 8 supported
#define LCD_BPP 12
// with SHARED_SPIFLASH we need to enable SPI only iniside native code and disable it before return
// to allow espruino to execute from SPI flash on shared SPI pins
#define SHARED_SPIFLASH
// also we may need to unselect flash chip CS pin as Espruino is in a middle of read command
#define SPIFLASH_CS (1<<5)
//SPI0 0x40003000
//SPI1 0x40004000
//SPI2 0x40023000
#define SPIBASE 0x40023000
typedef unsigned int uint32_t;
typedef signed int int32_t;
typedef unsigned short uint16_t;
typedef unsigned char uint8_t;
typedef signed char int8_t;
#define NULL ((void*)0)
// if code is in RAM we can put global data into text/code segment
// this allows simpler pc-relative addressing and shorter/faster code
#define __code __attribute__((section(".text")))
//#define __code
// _code volatile uint32_t *SPI =(uint32_t*)SPIBASE;
// direct constant makes smaller/faster code
#define SPI ((volatile uint32_t*)SPIBASE)
//divide register offsets by sizeof(uint32_t)
#define REG(offset) (offset/4)
// SPI master documentation
// https://infocenter.nordicsemi.com/topic/com.nordic.infocenter.nrf52832.ps.v1.1/spi.html?cp=4_2_0_47
// common/nonDMA registers
#define READY REG(0x108)
#define INTENSET REG(0x304)
#define INTENCLR REG(0x308)
#define ENABLE REG(0x500)
#define PSELSCK REG(0x508)
#define PSELMOSI REG(0x50c)
#define PSELMISO REG(0x510)
#define RXD REG(0x518)
#define TXD REG(0x51c)
#define FREQUENCY REG(0x524)
#define CONFIG REG(0x554)
/// EasyDMA registers
#define TASKS_START REG(0x010)
#define TASKS_STOP REG(0x014)
#define EVENTS_STOPPED REG(0x104)
#define EVENTS_ENDRX REG(0x110)
#define EVENTS_END REG(0x118)
#define EVENTS_ENDTX REG(0x120)
#define EVENTS_STARTED REG(0x14C)
#define SHORTS REG(0x200)
#define RXDPTR REG(0x534)
#define RXDMAXCNT REG(0x538)
#define RXDAMOUNT REG(0x53C)
#define RXDLIST REG(0x540)
#define TXDPTR REG(0x544)
#define TXDMAXCNT REG(0x548)
#define TXDAMOUNT REG(0x54C)
#define TXDLIST REG(0x550)
#define ORC REG(0x5c0)
#define GPIO(x) ((volatile uint32_t*)(0x50000000+x))
#define OUT GPIO(0x504)
#define OUTSET GPIO(0x508)
#define OUTCLR GPIO(0x50c)
#define IN GPIO(0x510)
// direction 1=output
#define DIR GPIO(0x514)
#define DIRSET GPIO(0x518)
#define DIRCLR GPIO(0x51c)
__code uint32_t pSCK= -1;
__code uint32_t pMOSI= -1;
__code uint32_t pMISO= -1;
__code uint32_t pCS= 0;
__code uint32_t pCD= 0; //command/data
void setpins(int sck,int mosi,int cs,int cd){
pSCK=sck;pMOSI=mosi;pCS=1<<cs;pCD=1<<cd;
}
__code uint32_t savedintflags=0;
__code uint32_t savedmode=0;
void save(){
savedintflags=SPI[INTENSET];
savedmode=SPI[ENABLE];
}
void restore(){
SPI[ENABLE]=0;
SPI[INTENSET]=savedintflags;
SPI[ENABLE]=savedmode;
}
// pins need to be already preconfigured as gpio input/outputs
int setup(uint32_t speed,uint32_t mode){
if (pSCK>=0 && (pMISO>=0||pMOSI>=0)){
uint32_t flags=SPI[INTENSET];
if (flags) SPI[INTENCLR]=flags; // clear all interrupt flags
SPI[PSELSCK]=pSCK;
SPI[PSELMOSI]=pMOSI;
SPI[PSELMISO]=pMISO;
SPI[FREQUENCY]=speed<<24; // 0x80=8mbits,0x40=4mbits,...
SPI[CONFIG]=mode<<1; //msb first
return 1;
}
return 0;
}
void disable(){
SPI[ENABLE]=0;
SPI[READY]=0;
uint32_t flags=SPI[INTENSET];
if (flags) SPI[INTENCLR]=flags; // clear all interrupt flags
}
int enable(uint32_t speed,uint32_t mode){
if (SPI[ENABLE]) return -1;
if (setup(speed,mode)){
#ifndef SHARED_SPIFLASH
SPI[ENABLE]=7;//SPIM with DMA
SPI[TASKS_STOP]=1;
//isDMA=1;
#endif
return 1;
}
return 0;
}
int write_dma(uint8_t *buffer, uint32_t len,int async);
int data(uint8_t *buffer, int len){
if (pCD==0) return -1;
if(buffer==NULL || len==0) return -1;
#ifdef SHARED_SPIFLASH
#ifdef SPIFLASH_CS
*OUTSET = SPIFLASH_CS;
#endif
SPI[ENABLE]=7;//SPIM with DMA
#endif
if(pCS>0) *OUTCLR = pCS; // CHIP SELECT
*OUTSET = pCD; // data
write_dma(buffer,len,0);
if(pCS>0) *OUTSET = pCS; // CHIP SELECT
#ifdef SHARED_SPIFLASH
SPI[ENABLE]=0;//disable SPI
#endif
return 0;
}
int cmd(uint8_t *buffer, int len){
if (pCD==0) return -1;
#ifdef SHARED_SPIFLASH
#ifdef SPIFLASH_CS
*OUTSET = SPIFLASH_CS;
#endif
SPI[ENABLE]=7;//SPIM with DMA
#endif
*OUTCLR = pCD; // CMD
if(pCS>0) *OUTCLR = pCS; // CHIP SELECT
write_dma(buffer,1,0);
*OUTSET = pCD; // data
if (len>1)
write_dma(buffer+1,len-1,0);
if(pCS>0) *OUTSET = pCS; // CHIP SELECT
#ifdef SHARED_SPIFLASH
SPI[ENABLE]=0;//disable SPI
#endif
return 0;
}
int cmds(uint8_t *ptr,int bufflen){
int cnt=0;
if (!ptr) return cnt;
uint8_t *endptr=ptr+bufflen;
uint8_t len;
while ((len=*ptr++)!=0){
if ((ptr+len)>endptr) return -cnt;// break if we would go past buffer
if(cmd(ptr,len)) break;
ptr+=len;cnt++;
}
return cnt;
}
// send command with up to 3 parameters (espruino allows methods with up to 4 parameters)
int cmd4(int c0,int d1,int d2, int d3){
int cnt=0;
uint8_t buff[4];
if (c0>=0)buff[cnt++]=c0; else return 0;
if (d1>=0)buff[cnt++]=d1;
if (d2>=0)buff[cnt++]=d2;
if (d3>=0)buff[cnt++]=d3;
cmd(buff,cnt);
return cnt;
}
__code uint16_t running=0;
void wait_dma(){
if (running) {
while (SPI[EVENTS_END] == 0); // wait for previous transfer
SPI[EVENTS_END]=0;
running=0;
}
}
int write_dma(uint8_t *ptr, uint32_t len, int async)
{
wait_dma();
int offset = 0;
SPI[RXDPTR]=0;
SPI[RXDMAXCNT]=0;
SPI[EVENTS_END]=0;
do {
SPI[TXDPTR]=(uint32_t)(ptr + offset);
if (len < 0x100) {
SPI[TXDMAXCNT]=len;
len = 0;
} else {
SPI[TXDMAXCNT]=0xff;
offset = offset + 0xff;
len = len - 0xff;
}
SPI[TASKS_START]=1;
if (async && len==0){
running=1; // do not wait for last part
} else {
while (SPI[EVENTS_END] == 0);
SPI[EVENTS_END]=0;
}
} while (len != 0);
return 0;
}
__code uint16_t blit_bpp=0;
__code uint16_t blit_w=0;
__code uint16_t blit_h=0;
__code uint16_t blit_stride=0;
void blit_setup(uint16_t w,uint16_t h,uint16_t bpp, uint16_t stride){
blit_bpp=bpp;blit_w=w;blit_h=h;blit_stride=stride; //*bpp/8;
}
#define LCHUNK 36 // divisible by 3 and 2
#if LCD_BPP==8
//only 8 bit palette entry
typedef uint8_t palbuff_t;
#else
typedef uint16_t palbuff_t;
#endif
int blt_pal(uint8_t *buff,palbuff_t* palbuff,uint8_t xbitoff){
uint8_t *pxbuff=buff;
uint8_t bpp=blit_bpp;
if (pxbuff==NULL || palbuff==NULL || bpp==0 || bpp>8) return -1;
uint8_t mask=(1<<bpp)-1; //pixel bitmask
uint8_t bpos=xbitoff;
uint16_t val=(*pxbuff++)|((*pxbuff++)<<8);val>>=bpos; // we prefetch 8-16 bits
uint16_t w=blit_w;
uint16_t h=blit_h;
uint8_t lbuff_1[LCHUNK];
uint8_t lbuff_2[LCHUNK];
uint8_t *lbuff=lbuff_1;
int lbufpos=0;
int lbuffnum=0;
#ifdef SHARED_SPIFLASH
#ifdef SPIFLASH_CS
*OUTSET = SPIFLASH_CS;
#endif
SPI[ENABLE]=7;//SPIM with DMA
#endif
if(pCS>0) *OUTCLR = pCS; // CHIP SELECT
do {
w=blit_w;
do {
#if LCD_BPP==12
// pixel 1
uint16_t px1=palbuff[val&mask]; // get color
val=val>>bpp;bpos+=bpp;
//pixel 2
uint16_t px2=palbuff[val&mask]; // get color
val=val>>bpp;bpos+=bpp;
if(bpos>=8){bpos-=8;val=val|(*pxbuff++)<<(8-bpos);} //less than 8 bits, prefetch another byte
//put 2x 12bit pixels into buffer
lbuff[lbufpos++]=px1>>4;
lbuff[lbufpos++]=(px1<<4)|(px2>>8);
lbuff[lbufpos++]=px2&255;
w-=2;
#elif LCD_BPP==16
// pixel 1
uint16_t px1=palbuff[val&mask]; // get color
val=val>>bpp;bpos+=bpp;
if(bpos>=8){bpos-=8;val=val|(*pxbuff++)<<(8-bpos);} //less than 8 bits, prefetch another byte
//put 16bit pixel into buffer
lbuff[lbufpos++]=px1>>8;
lbuff[lbufpos++]=px1&255;
w--;
#elif LCD_BPP==8
// pixel 1
// get color from palette or as direct value if no palette
uint8_t px1= (palbuff) ? palbuff[val&mask] : (bpp==6 ? (val&mask)<<2: val&mask);
val=val>>bpp;bpos+=bpp;
if(bpos>=8){bpos-=8;val=val|(*pxbuff++)<<(8-bpos);} //less than 8 bits, prefetch another byte
//put 8bit pixel into buffer
lbuff[lbufpos++]=px1;
w--;
#endif
if (lbufpos >= LCHUNK){
// buffer full, start async draw and switch to other buffer
write_dma(lbuff,lbufpos,1);
lbuffnum=1-lbuffnum;
lbuff = lbuffnum ? lbuff_2 : lbuff_1;
lbufpos=0;
}
} while(w>0);
buff+=blit_stride;
pxbuff=buff;bpos=xbitoff;
val=(*pxbuff++)|(*pxbuff++)<<8;val>>=bpos;//val=(*pxbuff++)>>bpos;
h--;
} while(h>0);
// send the rest
if (lbufpos>0){
write_dma(lbuff,lbufpos,0);
} else {
wait_dma();
}
if(pCS>0) *OUTSET = pCS; // CHIP SELECT
#ifdef SHARED_SPIFLASH
SPI[ENABLE]=0;//disable SPI
#endif
return 0;
}
#if 0
// write same buffer many times repeatedly
int write_many_dma(uint8_t *buffer, int len, int count){
wait_dma();
SPI[RXDPTR]=0;
SPI[RXDMAXCNT]=0;
SPI[EVENTS_END]=0;
SPI[TXDPTR]=(uint32_t)(buffer);
SPI[TXDMAXCNT]=len;
if (count > 1)
SPI[SHORTS]=1<<17;
SPI[TASKS_START]=1;
do {
while (SPI[EVENTS_END] == 0); // wait
SPI[EVENTS_END]=0;
if (count <= 2) SPI[SHORTS]=0; // stop shortcut for next loop
} while (--count);
return 0;
}
#if LCD_BPP==12
int fill_color(uint32_t val,uint32_t len){
uint8_t buff[12]={ // 92ms for 24, 97ms for 12
// val>>4,(val&0xf)<<4|val>>8,val&0xff,
// val>>4,(val&0xf)<<4|val>>8,val&0xff,
// val>>4,(val&0xf)<<4|val>>8,val&0xff,
// val>>4,(val&0xf)<<4|val>>8,val&0xff,
val>>4,(val&0xf)<<4|val>>8,val&0xff,
val>>4,(val&0xf)<<4|val>>8,val&0xff,
val>>4,(val&0xf)<<4|val>>8,val&0xff,
val>>4,(val&0xf)<<4|val>>8,val&0xff
};
return write_many_dma(buff,12,len/8);
}
#elif LCD_BPP==16
int fill_color(uint32_t val,uint32_t len){
uint8_t buff[16]= { // 126ms for 16, 121ms for 32 bytes
// val>>8,val&0xff, val>>8,val&0xff, val>>8,val&0xff, val>>8,val&0xff,
// val>>8,val&0xff, val>>8,val&0xff, val>>8,val&0xff, val>>8,val&0xff,
val>>8,val&0xff, val>>8,val&0xff, val>>8,val&0xff, val>>8,val&0xff,
val>>8,val&0xff, val>>8,val&0xff, val>>8,val&0xff, val>>8,val&0xff
};
return write_many_dma(buff,16,len/8);
}
#elif LCD_BPP==8
int fill_color(uint32_t val,uint32_t len){
uint8_t v=val&0xff;
uint8_t buff[16]= { // 126ms for 16, 121ms for 32 bytes
v, v, v, v,
v, v, v, v,
v, v, v, v,
v, v, v, v
};
return write_many_dma(buff,16,len/16);
}
#endif
#endif
`);
*/
///*
// MIT License (c) 2020 fanoush https://github.com/fanoush
// see full license text at https://choosealicense.com/licenses/mit/
// compiled with options LCD_BPP=12,SHARED_SPIFLASH,SPIFLASH_CS=(1<<5)
var SPI2 = (function(){
var bin=atob("AAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////8QtQNMfEQigGCAoYDjgBC92P///wdLe0QbiUOxBEoTaAAr/NAAIxNgA0p6RBOBcEcYMQJAxv///7L///8t6fBHkEYZTBlO//fl/xlK3/hkwAAjASUTYE/w/w5TYKlGI2AQMh9G/ykA6wMKwvgAoIu//zMxYMb4AOAAIYi//znM+ACQuPEADwbQKbkLS3tEHYEAIL3o8IfU+ACguvEAD/rQJ2AAKd7R8+cYMQJASDUCQDQ1AkAQMAJAUP///y3p8E+dsM3pARJWSnpEBkaS+ACQACgA8JuAACkA8JiACfH/MwcrAPKTgAEjA/oJ8wE727IEeAOTQ3hHSZeIROoDJAKbAPECC0RIHEEgIwNgByMLYNNopLLN6QYBC7FAShNgT+pJA9uyBJM/S3tEE6gFkwqrzekIME/wAAhBRgWbAp2z+AKgA5sBmiNARPoJ9DL4E8ADmyNARPoJ9DL4EyAEmx1E7bIHLULYpLJP6iwTQ1QTEgHxAg5D6gwcQxgDMarxAgojKYP4AcAf+or6APgOIAndASL/91P/2PEBCAu/CZgImEFGACG68QAPy9EfS3tEAT/biB5Ev7JzeDR4ROoDJAKbHEEG8QILpLIAL7bR2bE6Rv/3NP8VS3tE22gLsQaaE2AHmwAgGGAdsL3o8I/eRgg9HvgBO+2yxfEICwP6C/McQ6Sy80aw5//3Bf/j50/w/zDp5wC/ADUCQAgFAFAMBQBQFP///7T+//8w/v//Bv7//xlKekT4tQZGEGkPRhCzE0wTTSAjI2AHIytgEksYYNJoArEaYAAiASEwRv/37/4PS3tEAS8baSNgBN0AInkecBz/9+T+Ckt7RNtoA7EjYAAgKGD4vU/w/zD75wC/CAUAUAA1AkAMBQBQqv3//3z9//9m/f//E7UAKB7bACmmv434BRACJAEkACqkvwKpCRmN+AQApL8BNAH4BCwAK6K/AqoSGQE0IUYBqKi/AvgEPP/3p/8gRgKwEL0AJPrncLUFRoixRhgAJChGEPgBGxmxRRi1QgLZZEIgRnC9//eR/wAo+dEBNO/nBEb15wAADUsbaBC1o7kMSxtoC7EMShNgDksLSntEAAZcaRRgnGlUYNppCEtJABpgWGFZZAEgEL1P8P8w++cANQJABDMCQAgzAkAINQJAEDUCQKr8//8FSgAjE2Ci9X5yE2ADSxtoC7HC+AAycEcANQJABDMCQBC1Bkx8RMTpBQEBIQH6AvIB+gPz4mAjYRC9AL9M/P//");
return {
cmd:E.nativeCall(593, "int(int,int)", bin),
cmds:E.nativeCall(781, "int(int,int)", bin),
cmd4:E.nativeCall(709, "int(int,int,int,int)", bin),
setpins:E.nativeCall(941, "void(int,int,int,int)", bin),
enable:E.nativeCall(829, "int(int,int)", bin),
disable:E.nativeCall(909, "void()", bin),
blit_setup:E.nativeCall(33, "void(int,int,int,int)", bin),
blt_pal:E.nativeCall(221, "int(int,int,int)", bin),
};
})();
// this method would produce code string that can replace bin declaration above with heatshrink compressed variant
// however it seems the gain is very small so is not worth it
// shrink:function(){return `var bin=E.toString(require("heatshrink").decompress(atob("${btoa(require("heatshrink").compress(bin))}")))`;}
//*/
/* slower 16bit mode version
// compiled with options LCD_BPP=16,SHARED_SPIFLASH,SPIFLASH_CS=(1<<5)
var SPI2 = (function(){
var bin=atob("AAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////8QtQNMfEQigGCAoYDjgBC92P///wdLe0QbiUOxBEoTaAAr/NAAIxNgA0p6RBOBcEcYMQJAxv///7L///8t6fBHkEYZTBlO//fl/xlK3/hkwAAjASUTYE/w/w5TYKlGI2AQMh9G/ykA6wMKwvgAoIu//zMxYMb4AOAAIYi//znM+ACQuPEADwbQKbkLS3tEHYEAIL3o8IfU+ACguvEAD/rQJ2AAKd7R8+cYMQJASDUCQDQ1AkAQMAJAUP///y3p8E+bsBNGAJFOSXlEBkaR+ACwACgA8IyAAJoAKgDwiIAL8f8yByoA8oOAASIC+gvyATrSsgR4AZJCeD5NsfgEgETqAiSHHCAiPEgqYAciAmDKaBxBpLLN6QNQCrE4SQpgOUp6RBGoApIIqs3pBSBP8AAJSUYCmrL4AqAdRgGaXUTtsgctAuoEDACaiL8IPTL4HMCBvxf4ASvtssXxCA4C+g7yRPoL9E/qLC6IvxRDAPgB4EocAjEK8f86IymksgD4AsAf+or6C90BIgeT//dX/9nxAQkHmwu/BpgFmElGACG68QAPytEYSnpECPH/ONKIFkQf+oj4cng0eETqAiQcQbccpLK48QAPtNFxsUJG//c2/w5Le0TbaAuxA5oTYASbACAYYBuwvejwj//3FP/w50/w/zD25wgFAFAANQJADAUAUBT///+8/v//Nv7//wr+//8ZSnpE+LUGRhBpD0YQsxNME00gIyNgByMrYBJLGGDSaAKxGmAAIgEhMEb/9//+D0t7RAEvG2kjYATdACJ5HnAc//f0/gpLe0TbaAOxI2AAIChg+L1P8P8w++cAvwgFAFAANQJADAUAUMr9//+c/f//hv3//xO1ACge2wAppr+N+AUQAiQBJAAqpL8CqQkZjfgEAKS/ATQB+AQsACuivwKqEhkBNCFGAaiovwL4BDz/96f/IEYCsBC9ACT653C1BUaIsUYYACQoRhD4ARsZsUUYtUIC2WRCIEZwvf/3kf8AKPnRATTv5wRG9ecAAA1LG2gQtaO5DEsbaAuxDEoTYA5LC0p7RAAGXGkUYJxpVGDaaQhLSQAaYFhhWWQBIBC9T/D/MPvnADUCQAQzAkAIMwJACDUCQBA1AkDK/P//BUoAIxNgovV+chNgA0sbaAuxwvgAMnBHADUCQAQzAkAQtQZMfETE6QUBASEB+gLyAfoD8+JgI2EQvQC/bPz//w==");
return {
cmd:E.nativeCall(561, "int(int,int)", bin),
cmds:E.nativeCall(749, "int(int,int)", bin),
cmd4:E.nativeCall(677, "int(int,int,int,int)", bin),
setpins:E.nativeCall(909, "void(int,int,int,int)", bin),
enable:E.nativeCall(797, "int(int,int)", bin),
disable:E.nativeCall(877, "void()", bin),
blit_setup:E.nativeCall(33, "void(int,int,int,int)", bin),
blt_pal:E.nativeCall(221, "int(int,int,int)", bin),
};
})();
*/
E.kickWatchdog();
//P8 pins
CS=D25;DC=D18;RST=D26;BL=D14;
SCK=D2;MOSI=D3;
RST.reset();
// CLK,MOSI,CS,DC
D2.write(0);D3.write(0);CS.write(1);DC.write(1);
SPI2.setpins(SCK,MOSI,CS,DC);
SPI2.enable(0x80,0); //8MBit, mode 0
function delayms(ms){
digitalPulse(DC,0,ms); // just to wait 10ms
digitalPulse(DC,0,0);
}
function toFlatString(arr){
var b=E.toString(arr);if (b) return b;
print("toFlatString() fail&retry!");E.defrag();b=E.toString(arr);if (b) return b;
print("fail&retry again!");E.defrag();b=E.toString(arr);if (b) return b;
print("failed!"); return b;
}
function toFlatBuffer(a){return E.toArrayBuffer(toFlatString(a));}
function cmd(a){
var l=a.length;
if (!l)return SPI2.cmd4(a,-1,-1,-1);
if (l==2)return SPI2.cmd4(a[0],a[1],-1,-1);
if (l==3)return SPI2.cmd4(a[0],a[1],a[2],-1);
if (l==4)return SPI2.cmd4(a[0],a[1],a[2],a[3]);
if (l==1)return SPI2.cmd4(a[0],-1,-1,-1);
var b=toFlatString(a);
SPI2.cmd(E.getAddressOf(b,true),b.length);
}
function cmds(arr){
var b=toFlatString(arr);
var c=SPI2.cmds(E.getAddressOf(b,true),b.length);
if (c<0)print('lcd_cmds: buffer mismatch, cnt='+c);
return c;
}
RST.set();
function init(){
cmd(0x11); // sleep out
delayms(20);
cmd([0x36, 0]); // MADCTL - This is an unrotated screen
cmd([0x37,0,0]);
// These 2 rotate the screen by 180 degrees
//[0x36,0xC0], // MADCTL
//[0x37,0,80], // VSCSAD (37h): Vertical Scroll Start Address of RAM
cmd([0x3A, 0x03]); // COLMOD - interface pixel format - 03 - 12bpp, 05 - 16bpp
cmd([0xB2, 0xC, 0xC, 0, 0x33, 0x33]); // PORCTRL (B2h): Porch Setting
cmd([0xB7, 0]); // GCTRL (B7h): Gate Control
cmd([0xBB, 0x3E]); // VCOMS (BBh): VCOM Setting
cmd([0xC2, 1]); // VDVVRHEN (C2h): VDV and VRH Command Enable
cmd([0xC3, 0x19]); // VRHS (C3h): VRH Set
cmd([0xC4, 0x20]); // VDVS (C4h): VDV Set
cmd([0xC5, 0xF]); // VCMOFSET (C5h): VCOM Offset Set .
cmd([0xD0, 0xA4, 0xA1]); // PWCTRL1 (D0h): Power Control 1
cmd([0xe0, 0x70, 0x15, 0x20, 0x15, 0x10, 0x09, 0x48, 0x33, 0x53, 0x0B, 0x19, 0x15, 0x2a, 0x2f]); // PVGAMCTRL (E0h): Positive Voltage Gamma Control
cmd([0xe1, 0x70, 0x15, 0x20, 0x15, 0x10, 0x09, 0x48, 0x33, 0x53, 0x0B, 0x19, 0x15, 0x2a, 0x2f]); // NVGAMCTRL (E1h): Negative Voltage Gamma Contro
cmd(0x29); // DISPON (29h): Display On
cmd(0x21); // INVON (21h): Display Inversion On
//cmd([0x2a,0,0,0,239]);
//cmd([0x2b,0,0,0,239]);
//cmd([0x2c]);
}
var bpp=4; // powers of two work, 3=8 colors would be nice
var g=Graphics.createArrayBuffer(240,240,bpp);
var pal;
switch(bpp){
case 2: pal= Uint16Array([0x000,0xf00,0x0f0,0x00f]);break; // white won't fit
// case 1: pal= Uint16Array([0x000,0xfff]);break;
case 1:
pal= Uint16Array( // same as 16color below, use for dynamic colors
[ 0x000,0x00a,0x0a0,0x0aa,0xa00,0xa0a,0xa50,0xaaa,
0x555,0x55f,0x5f5,0x5ff,0xf55,0xf5f,0xff5,0xfff ]);
g.sc=g.setColor;
c1=pal[1]; //save color 1
g.setColor=function(c){ //change color 1 dynamically
c=Math.floor(c);
if (c > 1) {
pal[1]=pal[c]; g.sc(1);
} else if (c==1) {
pal[1]=c1; g.sc(1);
} else g.sc(c);
}; break;
case 4: pal= Uint16Array( // CGA
[
// 12bit RGB444
0x000,0x00a,0x0a0,0x0aa,0xa00,0xa0a,0xa50,0xaaa,
0x555,0x55f,0x5f5,0x5ff,0xf55,0xf5f,0xff5,0xfff
//16bit RGB565
// 0x0000,0x00a8,0x0540,0x0555,0xa800,0xa815,0xaaa0,0xad55,
// 0x52aa,0x52bf,0x57ea,0x57ff,0xfaaa,0xfabf,0xffea,0xffff
]);break;
}
// preallocate setwindow command buffer for flip
g.winCmd=toFlatBuffer([
5, 0x2a, 0,0, 0,0,
5, 0x2b, 0,0, 0,0,
1, 0x2c,
0 ]);
// precompute addresses for flip
g.winA=E.getAddressOf(g.winCmd,true);
g.palA=E.getAddressOf(pal.buffer,true); // pallete address
g.buffA=E.getAddressOf(g.buffer,true); // framebuffer address
g.stride=g.getWidth()*bpp/8;
g.flip=function(force){
var r=g.getModified(true);
if (force)
r={x1:0,y1:0,x2:this.getWidth()-1,y2:this.getHeight()-1};
if (r === undefined) return;
var x1=r.x1&0xfe;var x2=(r.x2+2)&0xfe; // for 12bit mode align to 2 pixels
var xw=(x2-x1);
var yw=(r.y2-r.y1+1);
if (xw<1||yw<1) {print("empty rect ",xw,yw);return;}
/*
cmd([0x2a,0,x1,0,x2-1]);
cmd([0x2b,0,r.y1,0,r.y2]);
cmd([0x2c]);
*/
var c=g.winCmd;
c[3]=x1;c[5]=x2-1; //0x2a params
c[9]=r.y1;c[11]=r.y2; // 0x2b params
SPI2.blit_setup(xw,yw,bpp,g.stride);
var xbits=x1*bpp;
var bitoff=xbits%8;
var addr=g.buffA+(xbits-bitoff)/8+r.y1*g.stride; // address of upper left corner
//VIB.set();//debug
SPI2.cmds(g.winA,c.length);
SPI2.blt_pal(addr,g.palA,bitoff);
//VIB.reset();//debug
};
g.isOn=false;
init();
g.on=function(){
if (this.isOn) return;
cmd(0x11);
g.flip();
//cmd(0x13); //ST7735_NORON: Set Normal display on, no args, w/delay: 10 ms delay
//cmd(0x29); //ST7735_DISPON: Set Main screen turn on, no args w/delay: 100 ms delay
this.isOn=true;
this.setBrightness();
};
g.off=function(){
if (!this.isOn) return;
//cmd(0x28);
cmd(0x10);
BL.set();
this.isOn=false;
};
// does PWM on BL pin, not best for P8 as it has 3 BL pins
g.lev=256;
g.setBrightness=function(lev){
if (lev>=0 && lev<=256)
this.lev=lev;
else
lev=this.lev;
if (this.isOn){
val=lev/256;
if (val==0||val==1)
digitalWrite(BL,1-val);
else
analogWrite(BL,val,{freq:60});
}
};
var VIB=D16;
function vibon(vib){
if(vib.i>=1)VIB.set();else analogWrite(VIB,vib.i);
setTimeout(viboff,vib.on,vib);
}
function viboff(vib){
VIB.reset();
if (vib.c>1){vib.c--;setTimeout(vibon,vib.off,vib);}
}
vibrate=function(intensity,count,onms,offms){
vibon({i:intensity,c:count,on:onms,off:offms});
};
function battVolts(){
return 4.20/0.60*analogRead(D31);
}
function battLevel(v){
var l=3.5,h=4.19;
v=v?v:battVolts();
if(v>=h)return 100;
if(v<=l)return 0;
return 100*(v-l)/(h-l);
}
function battInfo(v){v=v?v:battVolts();return `${battLevel(v)|0}% ${v.toFixed(2)}V`;}
function randomLines(){
g.clear();
var cols=(bpp==1)?14:(1<<bpp)-1,w=g.getWidth(),h=g.getHeight(),r=Math.random;
return setInterval(function(){
g.setColor(1+r()*cols);
g.drawLine(r()*w,r()*h,r()*w,r()*h);
g.flip();
},5);
}
function randomShapes(){
g.clear();
var cols=(bpp==1)?14:(1<<bpp)-1,w=g.getWidth()-10,h=g.getHeight()-10,r=Math.random;
return setInterval(function(){
g.setBgColor(0);
g.setColor(1+r()*cols);
x1=r()*w;x2=10+r()*w;
y1=r()*h;y2=10+r()*h;
if (bpp==1 && ((x1&31)==1)) g.clear(); // for bpp==1 clear sometimes so we can see ellipses again
if (x1&1)
g.fillEllipse(Math.min(x1,x2), Math.min(y1,y2),Math.max(x1,x2), Math.max(y1,y2));
else
g.fillRect(Math.min(x1,x2), Math.min(y1,y2),Math.max(x1,x2), Math.max(y1,y2));
g.flip();
},5);
}
// cube from https://www.espruino.com/Pixl.js+Cube+Badge
var rx = 0, ry = 0, cc = 1;
// Draw the cube at rotation rx and ry
function drawCube(xx,yy,zz) {
// precalculate sin&cos for rotations
var rcx=Math.cos(rx), rsx=Math.sin(rx);
var rcy=Math.cos(ry), rsy=Math.sin(ry);
// Project 3D into 2D
function p(x,y,z) {
var t;
t = x*rcy + z*rsy;
z = z*rcy - x*rsy;
x=t;
t = y*rcx + z*rsx;
z = z*rcx - y*rsx;
y=t;
z += 4;
return [xx + zz*x/z, yy + yy*y/z];
}
var a,b;
// -z
a = p(-1,-1,-1); b = p(1,-1,-1);
g.drawLine(a[0],a[1],b[0],b[1]);
a = p(1,1,-1);
g.drawLine(a[0],a[1],b[0],b[1]);
b = p(-1,1,-1);
g.drawLine(a[0],a[1],b[0],b[1]);
a = p(-1,-1,-1);
g.drawLine(a[0],a[1],b[0],b[1]);
// z
a = p(-1,-1,1); b = p(1,-1,1);
g.drawLine(a[0],a[1],b[0],b[1]);
a = p(1,1,1);
g.drawLine(a[0],a[1],b[0],b[1]);
b = p(-1,1,1);
g.drawLine(a[0],a[1],b[0],b[1]);
a = p(-1,-1,1);
g.drawLine(a[0],a[1],b[0],b[1]);
// edges
a = p(-1,-1,-1); b = p(-1,-1,1);
g.drawLine(a[0],a[1],b[0],b[1]);
a = p(1,-1,-1); b = p(1,-1,1);
g.drawLine(a[0],a[1],b[0],b[1]);
a = p(1,1,-1); b = p(1,1,1);
g.drawLine(a[0],a[1],b[0],b[1]);
a = p(-1,1,-1); b = p(-1,1,1);
g.drawLine(a[0],a[1],b[0],b[1]);
}
function stepCube() {
rx += 0.1;
ry += 0.1;
g.setColor(0);g.fillRect(60,60,180,180);g.setColor(1+cc);cc=(cc+1)%15;
drawCube(120,120,120);
// update the whole display
g.flip();
}
//require("Font6x8").add(Graphics);
//require("Font6x12").add(Graphics);
//require("Font8x12").add(Graphics);
require("Font8x16").add(Graphics);
function info(){
g.clear();
g.setFont("6x8",2);g.setColor(10);
g.drawString("Espruino "+process.version,30,20);
if (bpp==1) g.flip();
g.setFont("6x8",1);g.setColor(14);
g.drawString("ST7789 12 bit mode, 8Mbps SPI with DMA",8,42);
if (bpp==1) g.flip();
for (var c=0;c<8;c++){
g.setColor(c+8);g.fillRect(20+25*c,185,45+25*c,205);
if (bpp==1) g.flip();
}
for ( c=0;c<8;c++) {g.setColor(c);g.fillRect(20+25*c,210,45+25*c,230);
if (bpp==1) g.flip();
}
g.flip();
return setInterval(function(){
stepCube();
},5);
}
var lastmin=-1;
var volts;
function drawClock(){
var d=Date();
volts= volts ? (volts+battVolts())/2:battVolts(); // average until shown
if (d.getMinutes()==lastmin) return;
d=d.toString().split(' ');
var min=d[4].substr(3,2);
var sec=d[4].substr(-2);
var tm=d[4].substring(0,5);
var hr=d[4].substr(0,2);
lastmin=min;
g.clear();
var w=g.getWidth();
g.setColor(15);
g.setFont("8x16");
var batt=battInfo(volts);volts=0;// clear average
g.drawString(batt,w-g.stringWidth(batt)-2,0);
//var tm=hr+" "+min;
/*
g.drawString(hr,40-g.stringWidth(hr)/2,10);
g.drawString(min,40-g.stringWidth(min)/2,80);
*/
g.setFontVector(62);
//g.setFontCopasetic40x58Numeric();
//g.drawString(hr,w/2-g.stringWidth(hr)-5,50);
//g.drawString(min,w/2+5,50);
g.drawString(tm,4+(w-g.stringWidth(tm))/2,50);
//g.setColor(8+4);
//g.setFontVector(26);
//if (sec&1)g.drawString("o o",40-g.stringWidth("o o")/2,60);
//if (sec&1)g.drawString(":",40-g.stringWidth(":")/2,42);
//if (sec&1)g.drawString(". .",40-g.stringWidth(". .")/2,44);
if(bpp==1) g.flip();
g.setFontVector(28);
g.setColor(8+3);
var dt=d[0]+" "+d[1]+" "+d[2];//+" "+d[3];
g.drawString(dt,(w-g.stringWidth(dt))/2,130);
g.flip();
}
function clock(){
lastmin=-1;
drawClock();
return setInterval(function(){
drawClock();
},1000);
}
function sleep(){
g.clear();//g.flip();
g.off();
currscr=-1;
return 0;
}
var screens=[clock,info,randomShapes,randomLines,sleep];
var currscr= -1;
var currint=0;
setWatch(function(){
if (!g.isOn) g.on();
currscr++;if (currscr>=screens.length) currscr=0;
if (currint>0) clearInterval(currint);
currint=screens[currscr]();
},BTN1,{ repeat:true, edge:'rising',debounce:25 }
);
/*
NRF.whitelist=[];
NRF.on('connect',function(addr) {
if (!NRF.whitelist.includes(addr)){
if (BTN1.read()){ // add to whitelist when button is held while connecting
NRF.whitelist.push(addr);
vibrate(1,1,100,0);
} else
NRF.disconnect();
}
NRF.connection = {};
NRF.connection.addr = addr;
NRF.connected=true;
NRF.setRSSIHandler((rssi)=>{NRF.connection.RSSI=rssi;});
});
NRF.on('disconnect',function(reason) {
NRF.connected=false;
NRF.connection = {};
NRF.lastReason=reason;
});
*/
@enteshari
Copy link

What are DC and BL in pins?

@fanoush
Copy link
Author

fanoush commented Jun 28, 2020

DC=data/command, BL=backlight, backlight is on pins 14,22,23 i am using just first one (14) for lowest brightness

@enteshari
Copy link

Thanks Fanoush.
Is DC the same as SWO?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment