//SSD1306/SH1106 OLED implementation for PIC8 including soft I2C

#include "oled.h"

char col=0;
char page=0;
const unsigned int decPlaces[5]={10000,1000,100,10,1};

void I2Cinit(void){
#ifdef OLED_GND
    trisbits.OLED_GND=0;
    anselbits.OLED_GND=0;
    latbits.OLED_GND=0;
#endif
#ifdef OLED_PWR
    trisbits.OLED_PWR=0;
    anselbits.OLED_PWR=0;
    latbits.OLED_PWR=1;
#endif
    trisbits.SDA_PIN=1;
    anselbits.SDA_PIN=0;
    latbits.SDA_PIN=0;  //pulled low when tris cleared
    trisbits.SCL_PIN=1;
    anselbits.SCL_PIN=0;
    latbits.SCL_PIN=0;  //pulled low when tris cleared
    I2Cstop();            //idle and ready
}

void I2Cbit(unsigned char n){      //send one bit
    if(n){
        SDAHI
    }else{
        SDALO
    }
        __delay_us(10);                 
    SCLHI
        __delay_us(10);                 
    SCLLO
}

void I2Cbyte(unsigned char n){     //send byte,return read byte + ACK
    I2Cbit(n&128);
    I2Cbit(n& 64);
    I2Cbit(n& 32);
    I2Cbit(n& 16);
    I2Cbit(n&  8);
    I2Cbit(n&  4);
    I2Cbit(n&  2);
    I2Cbit(n&  1);
    I2Cbit(1);  //ack bit
}

void I2Cstart(void){                 //send start condition
    //SDAHI                            //allow repeated start
    SDALO
        __delay_us(10);                 
    SCLLO
}

void I2Cstop(void){                  //send stop condition
    SDALO
        __delay_us(10);                 
    SCLHI
        __delay_us(10);                 
    SDAHI
}

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
    I2Cbyte(c); //write
    I2Cstop();
}

void OLEDinit(void){
    delay200();             //startup delay for display
    OLEDsendCommand(0x20);  //set addressing mode
    OLEDsendCommand(0x02);  //set page addressing mode (SH1106 only supports page 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(0xAF);  //display on
    OLEDsendCommand(0x81);  //set contrast
    OLEDsendCommand(0xFF);  //to max
    OLEDclear();
}

void OLEDclear(void){
    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<128;j++){
            I2Cbyte(0x00);
        }
        I2Cstop();
    }
}

void OLEDsetpage(unsigned char p){
    //p=7-p;      //flipped
    OLEDsendCommand(0xB0 + (p&7));      //set page address    
}

void OLEDsetcolumn(unsigned char c){
    //c=127-c;     //flipped
    c=c+OLED_X_OFFSET;
    OLEDsendCommand(0x00 + (c & 0x0F));  //set column lower address
    OLEDsendCommand(0x10 + ((c>>4)&0x0F));   //set column higher address    
}

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

void delay2000(void){                   //reused
    __delay_ms(2000); 
}

void delay200(void){                   //reused
    __delay_ms(200); 
}

void getDigits(unsigned int n, char* d){
    unsigned char p=0;
    d[0]='0';
    d[1]='0';
    d[2]='0';
    d[3]='0';
    d[4]='0';
    d[5]=0;
    while(n && (p<5)){
        if(n<decPlaces[p]){
            p=p+1;
        }else{
            d[p]=d[p]+1;
            n=n-decPlaces[p];
        }
    }
}

void zeroBlank(unsigned char* d, char c){   //replace leading zeros with c
    if(d[0]=='0'){
        d[0]=c;
        if(d[1]=='0'){
            d[1]=c;
            if(d[2]=='0'){
                d[2]=c;
                if(d[3]=='0'){
                    d[3]=c;
                }
            }
        }
    }
}

void largeFontChar(char c, const char *f, char mode){ //uses col/page, returns width
    char w,h,c0,c1,x,y;     //use chars here as the fonts are quite small
    char r,m;   //raw, modified data
    unsigned int p0,p;
    w=f[0];
    h=f[1]/8;
    c0=f[2];
    c1=c0+f[3]-1;
    if(c<c0){return;} //out of range
    if(c>c1){return;}
    c=c-c0;
    p0=c*w*h;
    for(y=0;y<h;y++){
        p=4+y+p0;
        OLEDsetcolumn(col);
        OLEDsetpage(page+y);        
        I2Cstart();
        I2Cbyte(OLED_ADDRESS);  //write
        I2Cbyte(0x40);          //write data
        for(x=0;x<w;x++){
            r=f[p];
            switch(mode){
                case 1: m=r^0xFF; break;    //inverted
                case 2:  if((col+x)&1){m=r&0xAA;}else{m=r&0x55;} break; //half tone dark
                case 3:  if((col+x)&1){m=r|0xAA;}else{m=r|0x55;} break; //half tone bright
                default: m=r;break;         //normal
            }
            I2Cbyte(m);
            p=p+h;
        }
        I2Cstop();
    }   
    col=col+w;
}

void largeFontCharArray(char* c, const char *f, char mode){ //uses col/page, returns width
  while(*c){    
    largeFontChar(*c++,f,mode);
    if(col>128){col=0;}      //wrap around (will probably look ugly)
  }      
}
