Simple line-by-line way to use SSD1306 screens.

SSD1306_simple.cpp

Committer:
austinbrown124
Date:
2020-05-25
Revision:
3:b9855e428053
Parent:
2:574d1b329593

File content as of revision 3:b9855e428053:

#include "SSD1306_simple.h"



    // I2C is stupid. We can only transfer upt to 255 bytes at a time, but we need header shit
    // so we will transfer the data to the screen one row at a time
    // luckily there are only 8 rows
    // this is OK because it keeps the frame buf short anyways
    // and makes less code per interrupt



SSD1306::SSD1306() {   // init screen
    
    RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
    RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
    
    GPIOB->MODER   |= GPIO_MODER_MODER6_1 | GPIO_MODER_MODER7_1;
    GPIOB->AFR[0]    |= 0x44000000;                    // PB6, PB7 to alternate function 6
    
    I2C1->TIMINGR |= 0<<I2C_TIMINGR_PRESC_Pos;  // presacler = 1
    I2C1->TIMINGR |= 5 << I2C_TIMINGR_SCLH_Pos;  //
    I2C1->TIMINGR |= 5 << I2C_TIMINGR_SCLL_Pos;  // 
    I2C1->CR2 |= I2C_CR2_AUTOEND;
    I2C1->CR1 |= I2C_CR1_PE;
    
    I2C1->CR2 &= ~0xFF;  // clear prev address
    I2C1->CR2 |= 0x3C<<1;  // write SSD1306 address
    
    int _vccstate = 0;

    _vccstate = SSD1306_SWITCHCAPVCC;
    //GPIOA->ODR |= GPIO_ODR_8;
    
    // Init sequence
    if (ssd1306_command(SSD1306_DISPLAYOFF) ) {                   // 0xAE
    // if first command doesn't work, give up
    
    ssd1306_command(SSD1306_SETDISPLAYCLOCKDIV);            // 0xD5
    ssd1306_command(0x80);                                  // the suggested ratio 0x80
    
    ssd1306_command(SSD1306_SETMULTIPLEX);                  // 0xA8
    ssd1306_command(SSD1306_LCDHEIGHT - 1);
    
    ssd1306_command(SSD1306_SETDISPLAYOFFSET);              // 0xD3
    ssd1306_command(0x0);                                   // no offset
    ssd1306_command(SSD1306_SETSTARTLINE | 0x0);            // line #0
    ssd1306_command(SSD1306_CHARGEPUMP);                    // 0x8D
    if (_vccstate == SSD1306_EXTERNALVCC)
        { ssd1306_command(0x10); }
    else
        { ssd1306_command(0x14); }
    ssd1306_command(SSD1306_MEMORYMODE);                    // 0x20
    ssd1306_command(0x00);                                  // 0x0 act like ks0108
    ssd1306_command(SSD1306_SEGREMAP | 0x1);
    ssd1306_command(SSD1306_COMSCANDEC);
    
    ssd1306_command(SSD1306_SETCOMPINS);                    // 0xDA
    ssd1306_command(0x12);
    ssd1306_command(SSD1306_SETCONTRAST);                   // 0x81
    if (_vccstate == SSD1306_EXTERNALVCC)
        { ssd1306_command(0x9F); }
    else
        { ssd1306_command(0xCF); }
    
    
    
    ssd1306_command(SSD1306_SETPRECHARGE);                  // 0xd9
    if (_vccstate == SSD1306_EXTERNALVCC)
    { ssd1306_command(0x22); }
    else
    { ssd1306_command(0xF1); }
    ssd1306_command(SSD1306_SETVCOMDETECT);                 // 0xDB
    ssd1306_command(0x40);
    ssd1306_command(SSD1306_DISPLAYALLON_RESUME);           // 0xA4
    ssd1306_command(SSD1306_NORMALDISPLAY);                 // 0xA6
    
    ssd1306_command(SSD1306_DEACTIVATE_SCROLL);
    
    ssd1306_command(SSD1306_DISPLAYON);//--turn on oled panel
    wait_ms(10);
    clearDisplay(); 
    
    
    }
    
    SetupFrameBuf();   // write initial conditions
    
    
    RCC->AHBENR |= RCC_AHBENR_DMA1EN;
    RCC->AHBENR |= RCC_AHBENR_DMA1EN;
    DMA1_Channel6->CPAR = (uint32_t)(&(I2C1->TXDR));        //set peripheral register address- the UART transmit data register 
    DMA1_Channel6->CMAR = (uint32_t)framebuf;          //set memory address- the thing to transfer
    DMA1_Channel6->CCR |= DMA_CCR_PL_1;            //set channel priority to high
    DMA1_Channel6->CCR |= DMA_CCR_MINC;            //memory increment for array transfer
    DMA1_Channel6->CCR |= DMA_CCR_DIR;        //configure data direction: send data to UART
    
    _char_width = FONT8x8_WIDTH;
    _font = FONT_8x8;
    timeout_cnt = TIMEOUT_INIT;
    
    }


// I2C stuff

bool SSD1306::i2cWrite( uint8_t *buf, uint8_t num_bytes) {
    
    I2C1->CR2 &= ~(I2C_CR2_RD_WRN);               // set to write mode
    I2C1->CR2 &=   ~(0xff << I2C_CR2_NBYTES_Pos);
    I2C1->CR2 |= I2C_CR2_START |  (num_bytes << I2C_CR2_NBYTES_Pos);      // transfer X bits and start
    
    int timeout = 10000;
    while(I2C1->CR2 & I2C_CR2_START) {if(--timeout <= 0) { return false; } }
    timeout = 10000;
    for (int i = 0; i < num_bytes; i++) {
        I2C1->TXDR = buf[i];
        while (!(I2C1->ISR & I2C_ISR_TXE)){
            if(--timeout <= 0) { return false; } 
        }
    }
    return true;
}


void SSD1306::DMAi2cWrite( uint8_t *buf, uint8_t num_bytes) {
    
    I2C1->CR1 |= I2C_CR1_TXDMAEN;  
    
    I2C1->CR2 &= ~(I2C_CR2_RD_WRN);               // set to write mode
    I2C1->CR2 &=   ~(0xff << I2C_CR2_NBYTES_Pos); // clear previous number of bytes
    
    I2C1->CR2 |= num_bytes << I2C_CR2_NBYTES_Pos;      // transfer X bits
    
    DMA1_Channel6->CCR &= ~DMA_CCR_EN;
    DMA1_Channel6->CNDTR = num_bytes;
    DMA1_Channel6->CCR |= DMA_CCR_EN; 
    
    I2C1->CR2 |= I2C_CR2_START;      // start
    
}

bool SSD1306::IsReady() {
    
    timeout_cnt--;
    if (timeout_cnt == 0) {ResetI2C(); timeout_cnt = TIMEOUT_INIT; return false;}
    
    if (DMA1_Channel6->CNDTR > 0) {return false;}  // needed because I2C does not start immidiately
    
    if ( (((I2C1->ISR)&I2C_ISR_BUSY) == 0)  && (((I2C1->ISR)&I2C_ISR_TXE) != 0) ) {
        timeout_cnt = TIMEOUT_INIT;
        return true;
    }
    else { return false; }
}

void SSD1306::ResetI2C() {
    
    I2C1->CR1 &= ~I2C_CR1_PE;
    I2C1->CR1 &= ~I2C_CR1_PE;
    I2C1->CR1 &= ~I2C_CR1_PE;
    I2C1->CR1 &= ~I2C_CR1_PE;
    I2C1->CR1 &= ~I2C_CR1_PE;
    I2C1->CR1 &= ~I2C_CR1_PE;
    I2C1->CR1 &= ~I2C_CR1_PE;
    DMA1_Channel6->CCR &= ~DMA_CCR_EN;
    DMA1_Channel6->CNDTR = 0;
    I2C1->CR1 |= I2C_CR1_PE;
    
    I2C1->ISR |= I2C_ISR_TXE;
    
    I2C1->ICR |= I2C_ICR_OVRCF | I2C_ICR_ARLOCF | I2C_ICR_BERRCF;
    I2C1->ICR |= I2C_ICR_STOPCF | I2C_ICR_NACKCF | I2C_ICR_ADDRCF;
}

bool SSD1306::ssd1306_command(uint8_t c) {
    
    // I2C
    uint8_t control = 0x00;   // Co = 0, D/C = 0
    uint8_t cmd[2];

    cmd[0] = control;
    cmd[1] = c;
    return i2cWrite(cmd, 2);
}

void SSD1306::_sendCommand(uint8_t command, uint8_t param1, uint8_t param2) {

//  Note continuationbit is set, so COMMAND_MODE must be
//  repeated before each databyte that serves as parameter!

  uint8_t databytes[6];
    
  databytes[0] = COMMAND_MODE;
  databytes[1] = command;    
  databytes[2] = COMMAND_MODE;
  databytes[3] = param1; 
  databytes[4] = COMMAND_MODE;
  databytes[5] = param2; 
  i2cWrite(databytes, 6);    // Write command   
  
}

void SSD1306::_sendData(uint8_t data){

  uint8_t databytes[2];
    
  databytes[0] = DATA_MODE;
  databytes[1] = data;    
  i2cWrite(databytes, 2);  
}

void SSD1306::setPageAddress(uint8_t start, uint8_t end) {

  _sendCommand(SET_PAGE_ADDRESS, start, end);   
}
void SSD1306::setColumnAddress(uint8_t start, uint8_t end) {

  _sendCommand(SET_COLUMN_ADDRESS, start, end);     
}


/*void SSD1306::writeChar(char chr) {

  const uint8_t char_index = chr - 0x20;
  if (font == 8x8) {
      for (uint8_t i = 0; i < _char_width; i++) {
           _sendData( font_8x8[char_index][i] );
      }
  } else if (font == 16x12_0) {
      for (uint8_t i = 0; i < 8; i++) {
           _sendData( font_16x12_0[char_index][i] );
      }
  } else if (font == 16x12_1) {
      for (uint8_t i = 0; i < 8; i++) {
           _sendData( font_16x12_1[char_index][i] );
      }
}*/


void SSD1306::writeString( uint8_t col, const char * text) {
  // screenbuffered version of above function
  // row is 0 thru 7, col is 0 thr 127
  uint16_t index = 0;
  uint16_t len = strlen(text);
  
  while ((col < MAX_COL) && (index < len)) {   
     // write line, starting at given position
     // dont write if its too long
     writeCharToBuf(col, text[index++]);
     col+=_char_width;
  }

} 

void SSD1306::writeInt( uint8_t col, int32_t num) {
    //GPIOA->ODR |= GPIO_ODR_8;
    if(num<0)
    {
        num = -num;
        writeCharToBuf(col, '-');
        col+=_char_width;
    }
    
    char chr = 0;
    static char Representation[]= "0123456789ABCDEF";
    int base = 10;
    int mult = 10000;  // can print maximum of 5 digits
    
    num = num%100000;
    
    while (mult > num) { mult /= base; }
    
    int num2print = 0;
    
    do
    {
        num2print = (num - num%mult)/mult;
        chr = Representation[num2print];
        num -= num2print*mult;
        mult /= base;
        writeCharToBuf(col, chr);
        col+=_char_width;
    }while(mult != 0);

} 

void SSD1306::setFont( uint8_t font ) {
  
  _font = font;
  
  if (font == FONT_8x8) { _char_width = FONT8x8_WIDTH; }
  if (font == FONT_16x12_0) { _char_width = FONT16x12_WIDTH; }
  if (font == FONT_16x12_1) { _char_width = FONT16x12_WIDTH; }
  
}
    


void SSD1306::writeCharToBuf( uint8_t col, char chr ) {

    //GPIOA->ODR |= GPIO_ODR_8;

    uint8_t char_index = chr - 0x20;
    
    uint16_t k = FRAME_BUF_OFFSET + col;
    
    if (col > MAX_COL) {col = MAX_COL;} //will overwrite but whatever
    
    /*for (uint8_t j = 0; j < 8; j++) {
        framebuf[k] = font_8x8[char_index][j];
        k++;
    }*/
    
    if (_font == FONT_8x8) {
        for (uint8_t i = 0; i < FONT8x8_WIDTH; i++) {
            if (k >= (COLUMNS+FRAME_BUF_OFFSET)) { break; }
            framebuf[k] = font_8x8[char_index][i];
            k++;
        }
    } else if (_font == FONT_16x12_0) {
        for (uint8_t i = 0; i < FONT16x12_WIDTH; i++) {
            if (k >= (COLUMNS+FRAME_BUF_OFFSET)) { break; }
            framebuf[k] = font_16x12_0[char_index][i];
            k++;
        }
    } else if (_font == FONT_16x12_1) {
        for (uint8_t i = 0; i < FONT16x12_WIDTH; i++) {
            if (k >= (COLUMNS+FRAME_BUF_OFFSET)) { break; }
            framebuf[k] = font_16x12_1[char_index][i];
            k++;
        }
    }
    
    
    
    // lets try above but with DMA. It is 11us with no DMA
    //DMA1_Channel2->CNDTR = 8;        // send 8 bytes
    //DMA1_Channel2->CMAR = (uint32_t)&font_8x8[char_index];          //set memory address- charachters. Will need to be moved on the fly
    //DMA1_Channel2->CPAR = (uint32_t)&framebuf[k];        //set peripheral register address- frame buffer
    //DMA1_Channel2->CCR |= DMA_CCR_EN;
    //while(
    //while (!(DMA1->ISR & DMA_ISR_TCIF2));
    //DMA1->IFCR |= DMA_IFCR_CTCIF2;
    //
    //for (uint8_t j = 0; j < 8; j++) {
    //    framebuf[k] = font_8x8[char_index][j];
    //    k++;
    //}
    
    

    //GPIOA->ODR &= ~GPIO_ODR_8;
}

void SSD1306::writeFrameBufRow( uint8_t page ) {
    
    framebuf[9] = page; 
    
    //i2cWrite(framebuf, COLUMNS+FRAME_BUF_OFFSET);   // takes about 3.2ms to write whole thing
    DMAi2cWrite(framebuf, COLUMNS+FRAME_BUF_OFFSET);  // takes 18 MICROSECONDS!!! sheeeeeet

}

void SSD1306::WriteRow( uint8_t page ) {
    writeFrameBufRow( page );
    }

void SSD1306::ClearBuf() {
    
    for (uint8_t j = FRAME_BUF_OFFSET; j < COLUMNS+FRAME_BUF_OFFSET; j++) {
        framebuf[j] = 0;
    }

}
// Standard version
void SSD1306::clearDisplay() {
 
  //setDisplayOff();
  setPageAddress(0, MAX_PAGE);  // all pages
  setColumnAddress(0, MAX_COL); // all columns

  for (uint8_t page = 0; page < PAGES; page++) {
    for (uint8_t col = 0; col < COLUMNS; col++) {
      _sendData(0x00);
    }
  }

}


void SSD1306::SetupFrameBuf() {
    
    for (uint8_t i = 0; i < PAGES; i++) {
    
        framebuf[0] = COMMAND_MODE;
        framebuf[1] = SET_COLUMN_ADDRESS;    
        framebuf[2] = COMMAND_MODE;
        framebuf[3] = 0; 
        framebuf[4] = COMMAND_MODE;
        framebuf[5] = MAX_COL; 
        framebuf[6] = COMMAND_MODE;
        framebuf[7] = SET_PAGE_ADDRESS;    
        framebuf[8] = COMMAND_MODE;
        framebuf[9] = 0; 
        framebuf[10] = COMMAND_MODE;
        framebuf[11] = MAX_PAGE; 
        framebuf[12] = DATA_MODE;
    }
}