//SSD1306/SH1106 OLED implementation for 8-bit PICs
//uses horizontal scanning of fonts and bitmaps to match output order

#include "oled.h"
unsigned char OLEDcol=0; //column variable
unsigned char OLEDpage=0; //page variable
char OLEDbuf[11];//buffer for number display

#ifdef USE_EEPROM_X_OFFSET
__eeprom signed char oledXoffset=2;
const char xAlign[] ={      //font to help align with edges of screen
    9,16,0,2,
    255,129,193,160,144,136,132,130,129,255,129,131,5,9,17,33,65,129,
    129,130,132,136,144,160,193,129,255,129,65,33,17,9,5,131,129,255,
};
#endif

#ifdef OLED_USE_VRAM
unsigned char OLED_VRAM[8][128];
unsigned char oledVramX=0;
unsigned char oledVramY=0;
#endif

//soft I2C
void I2Cinit(void){
#ifdef OLEDPWR    
    anselbits.OLEDPWR=0;
    trisbits.OLEDPWR=0;
    latbits.OLEDPWR=1;  //on    
#endif
    anselbits.I2C_SDA   =0; //digital
    anselbits.I2C_SCL   =0; //digital
    trisbits.I2C_SDA    =1; //input= pulled up by bus
    trisbits.I2C_SCL    =1; //input= pulled up by bus
    latbits.I2C_SDA     =0; //low when driven
    latbits.I2C_SCL     =0; //low when driven
    I2Cstop();      //idle state
}

void I2Cdeinit(void){
#ifdef OLEDPWR    
    latbits.OLEDPWR=0;  //off
#endif
    trisbits.I2C_SDA    =0; //ensure SDA low
    trisbits.I2C_SCL    =0; //ensure SCL low
}

void I2Cstop(void){
    trisbits.I2C_SDA    =0; //ensure SDA low
    trisbits.I2C_SCL    =1; //SCL hi
    trisbits.I2C_SDA    =1; //then SDA hi
}

void I2Cstart(void){        //assume in valid state
    trisbits.I2C_SDA    =0; //SDA low
    trisbits.I2C_SCL    =0; //SCL low
}

void I2Cbyte(unsigned char n){  //send byte, unrolled inline version is slightly faster
    if(n&128){trisbits.I2C_SDA=1;}else{trisbits.I2C_SDA=0;}
    trisbits.I2C_SCL=1;I2Cbitdelay();trisbits.I2C_SCL=0;
    if(n&64){trisbits.I2C_SDA=1;}else{trisbits.I2C_SDA=0;}
    trisbits.I2C_SCL=1;I2Cbitdelay();trisbits.I2C_SCL=0;
    if(n&32){trisbits.I2C_SDA=1;}else{trisbits.I2C_SDA=0;}
    trisbits.I2C_SCL=1;I2Cbitdelay();trisbits.I2C_SCL=0;
    if(n&16){trisbits.I2C_SDA=1;}else{trisbits.I2C_SDA=0;}
    trisbits.I2C_SCL=1;I2Cbitdelay();trisbits.I2C_SCL=0;
    if(n&8){trisbits.I2C_SDA=1;}else{trisbits.I2C_SDA=0;}
    trisbits.I2C_SCL=1;I2Cbitdelay();trisbits.I2C_SCL=0;
    if(n&4){trisbits.I2C_SDA=1;}else{trisbits.I2C_SDA=0;}
    trisbits.I2C_SCL=1;I2Cbitdelay();trisbits.I2C_SCL=0;
    if(n&2){trisbits.I2C_SDA=1;}else{trisbits.I2C_SDA=0;}
    trisbits.I2C_SCL=1;I2Cbitdelay();trisbits.I2C_SCL=0;
    if(n&1){trisbits.I2C_SDA=1;}else{trisbits.I2C_SDA=0;}
    trisbits.I2C_SCL=1;I2Cbitdelay();trisbits.I2C_SCL=0;
    trisbits.I2C_SDA=1;                          //ack
    trisbits.I2C_SCL=1;I2Cbitdelay();trisbits.I2C_SCL=0;
}

#ifdef OLED_USE_VRAM
//if OLED_USE_VRAM is not defined, then #define I2CvramByte I2Cbyte
void I2CvramByte(unsigned char n){  //alias to I2Cbyte, but exfiltrate data to VRAM image
    I2Cbyte(n);
    OLED_VRAM[oledVramY][oledVramX]=n;
    oledVramX=(oledVramX+1)%128;    
}
#endif    

void I2Cbitdelay(void){ //adjust to suit processor speed, bus speed etc
    NOP();
#if _XTAL_FREQ > 16000000UL
    NOP();
    NOP();
    NOP();
    NOP();    
#endif
}

void OLEDsendCommand(unsigned char c){
    I2Cstart();
    I2Cbyte(OLED_ADDRESS); //write
    I2Cbyte(0x80); //write
    I2Cbyte(c); //write
    I2Cstop();
}

void OLEDsendData(unsigned char c){     //not used only does a single byte
    I2Cstart();
    I2Cbyte(OLED_ADDRESS); //write
    I2Cbyte(0x40); //write
    I2CvramByte(c); //write
    I2Cstop();
}

void OLEDinit(void){
    __delay_ms(100);
    OLEDcol=0;
    OLEDpage=0;
    OLEDsendCommand(0x20);  //set addressing mode
    OLEDsendCommand(0x02);  //set OLEDpage addressing mode (SH1106 only supports OLEDpage mode)
    OLEDsendCommand(0x8D);  //Charge pump setting
    OLEDsendCommand(0x14);  //Enable Charge pump
    OLEDsendCommand(0xA1);  //column reverse
    //OLEDsendCommand(0xA0);  //column normal
    OLEDsendCommand(0xC8);  //row reverse
    //OLEDsendCommand(0xC0);  //row normal
    OLEDsendCommand(0x40);  //display start 0
    OLEDsendCommand(0xD3);  //display offset
    OLEDsendCommand(0x00);  //is zero
    OLEDsendCommand(0xAF);  //display on
    OLEDbrightness(0xFF);   //max for now
    //OLEDclear(0);
}

void OLEDbrightness(unsigned char b){
    OLEDsendCommand(0x81);   //set contrast
    OLEDsendCommand(b);      //to b
}

void OLEDclear(unsigned char c){
    unsigned char i,j;
    for(i=0;i<8;i++){
        OLEDsetpage(i);
        OLEDsendCommand(0xB0+i); //cycle through pages
        OLEDsendCommand(0x00);   //column=0 (lower nybble)     
        OLEDsendCommand(0x10);   //column=0 (upper nybble)
        I2Cstart();
        I2Cbyte(OLED_ADDRESS); //write
        I2Cbyte(0x40); //write data
        for(j=0;j<130;j++){ 
            I2CvramByte(c);
        }
        I2Cstop();
    }
}

void OLEDsetpage(unsigned char p){
    //p=7-p;      //flipped
    OLEDsendCommand(0xB0 + (p&7));      //set OLEDpage address    
#ifdef OLED_USE_VRAM
    oledVramY=p;
#endif    
}

void OLEDsetcolumn(unsigned char c){
#ifdef OLED_USE_VRAM
    oledVramX=c;    //use raw value here
#endif
#ifdef USE_EEPROM_X_OFFSET
    c=(unsigned char)(c+oledXoffset);
#endif    
    OLEDsendCommand(0x00 + (c & 0x0F));  //set column lower address
    OLEDsendCommand(0x10 + ((c>>4)&0x0F));   //set column higher address    
}

void OLEDsetpos(unsigned char p,unsigned char c){   //set OLEDpage and column to streamline, needs to occur after address write
#ifdef OLED_USE_VRAM
    oledVramX=c;    //use raw value here
    oledVramY=p;
#endif
#ifdef USE_EEPROM_X_OFFSET
    c=(unsigned char)(c+oledXoffset);
#endif    
    I2Cbyte(0x80); //write command
    I2Cbyte(0xB0|(p));      //set OLEDpage address    
    I2Cbyte(0x80); //write command
    I2Cbyte(0x00 + (c & 0x0F));  //set column lower address
    I2Cbyte(0x80); //write command
    I2Cbyte(0x10 + ((c>>4)&0x0F));   //set column higher address    
}

void OLEDflip(char c){      //to suit flip orientation according
    if(c==TEXT_RH){
        OLEDsendCommand(0xA1);  //column reverse
        OLEDsendCommand(0xC8);  //row reverse
    }else{
        OLEDsendCommand(0xA0);  //column normal
        OLEDsendCommand(0xC0);  //row normal
    }          
}

void OLEDchar(const char c,const char* f){
    unsigned int p;
    unsigned char j,w,h,c0,c1,i;
    w=f[0];
    h=f[1]/8;
    c0=f[2];
    c1=f[3]+c0;
    if(c<c0){return;}   //out of range
    if(c>c1){return;}   //out of range
    p=(c-c0)*((unsigned int)w)*((unsigned int)h)+4;   //4 is data offset
    for(j=0;j<h;j++){
        I2Cstart();
        I2Cbyte(OLED_ADDRESS); //write
        OLEDsetpos(OLEDpage+j,OLEDcol);
        I2Cbyte(0x40); //write data
        for(i=0;i<w;i++){I2CvramByte(f[p++]);}
        I2Cstop();                        
    }
    OLEDcol=OLEDcol+w;
}

void OLEDcharRev(const char c,const char* f){   //reversed
    unsigned int p;
    unsigned char j,w,h,c0,c1,i;
    w=f[0];
    h=f[1]/8;
    c0=f[2];
    c1=f[3]+c0;
    if(c<c0){return;}   //out of range
    if(c>c1){return;}   //out of range
    p=(c-c0)*((unsigned int)w)*((unsigned int)h)+4;   //4 is data offset
    for(j=0;j<h;j++){
        I2Cstart();
        I2Cbyte(OLED_ADDRESS); //write
        OLEDsetpos(OLEDpage+j,OLEDcol);
        I2Cbyte(0x40); //write data
        for(i=0;i<w;i++){I2CvramByte(f[p++]^0xFF);}
        I2Cstop();                        
    }
    OLEDcol=OLEDcol+w;
}

void OLEDchararray(const char* c, const char* f){
    while(*c){
        OLEDchar(*c++,f);
    }
}

void OLEDbitmap(const char* b){ //show bitmap at current column/row
    unsigned char i,j,w,h;
    unsigned int p=2;
    w=b[0];
    h=b[1]/8;
    for(j=0;j<h;j++){
        I2Cstart();
        I2Cbyte(OLED_ADDRESS); //write
        OLEDsetpos(OLEDpage+j,OLEDcol);
        I2Cbyte(0x40); //write data
        for(i=0;i<w;i++){I2CvramByte(b[p++]);}
        I2Cstop();                        
    }
    OLEDcol=OLEDcol+w;    
}

void OLEDscanlong(unsigned long n){ //converts an unsigned long into decimal
    unsigned long r=1000000000;
    int p;
    OLEDbuf[10]=0;
    for(p=0;p<10;p++){OLEDbuf[p]='0';}   //clear
    p=0;
    while(n){
        while(n>=r){
            n=n-r;
            OLEDbuf[p]++;            
        }
        r=r/10;
        p++;
    }
}

void OLEDscanshort(unsigned int n){     //converts an unsigned int into a decimal
    unsigned int r=10000;
    int p;
    OLEDbuf[10]=0;
    for(p=0;p<10;p++){OLEDbuf[p]='0';}   //clear
    p=5;
    while(n){
        while(n>=r){
            n=n-r;
            OLEDbuf[p]++;            
        }
        r=r/10;
        p++;
    }    
}

void OLEDlzb(char n){   //blank OLEDbuf[] up to/including n (ie 8 is all except last)
    char p=0;
    while(OLEDbuf[p]=='0'){
        OLEDbuf[p]=OLED_BLANK_CHAR;
        p=p+1;
        if(p>n){return;}
    }
}

void OLEDblock(char x, char y, unsigned char c){    //draw solid block of pattern c (ie OLEDblock(128,64,n)<> OLEDclear(n)
    char i,j;
    for(j=0;j<y;j++){
        I2Cstart();
        I2Cbyte(OLED_ADDRESS); //write
        OLEDsetpos(OLEDpage+j,OLEDcol);
        I2Cbyte(0x40); //write data
        for(i=0;i<x;i++){I2CvramByte(c);}
        I2Cstop();                                
    }
    OLEDcol=OLEDcol+x;    
}
#ifdef OLED_USE_VRAM
//code to dump VRAM to a stream (eg UART), define sendAbyte as needed
char getDibit(unsigned char d, char b){
    while(b--){d=d>>2;}
    return d&3;
}

void dumpVRAM(void){    //send image of VRAM
    unsigned char i,j,k;
    sendAbyte(CR_CHAR);
    sendAbyte(0xC5);
    sendAbyte(CR_CHAR);
    for(j=0;j<8;j++){
        if(j==8){return;}
        for(k=0;k<4;k++){
            sendAbyte(' ');            
            for(i=0;i<128;i++){
                sendAbyte(dibitChar[getDibit(OLED_VRAM[j][i],k)]);
            }
            sendAbyte(CR_CHAR);
        }
    }
    sendAbyte(0xC5);
    sendAbyte(CR_CHAR);
}
#endif  //OLED_USE_VRAM
