OLED128x64big

OLED 128x64 SSD1306 or SH1106 os SSD1309 on I2C. PB8=SCL PB9=SDA needs a single foactory/gpio/i2c/config object
Author:
License: LGPL
Github: tiar/HW/OLED128x64big.axo

Inlets

charptr32 line1

charptr32 line2

charptr32 line3

charptr32 line4

charptr32 line5

charptr32 line6

charptr32 line7

charptr32 line8

int32.positive mode

bool32 disable

Outlets

None

Attributes

combo type

combo I2CADDR

objref scope

Declaration
enum SSD1306 {
  LCDWIDTH = 128,
  LCDHEIGHT = 64,
  SETCONTRAST = 0x81, // 2byte cmd,256 contrast steps, reset = 7F
  DISPLAYON = 0xAF,   // entire display on
  DISPLAYOFF = 0xAE,  // entire display off
  DISPLAYALLON_RESUME = 0xA4,
  NORMALDISPLAY = 0xA6,     // invert = 0xA7
  DEACTIVATE_SCROLL = 0x2E, // stop control scroll conf by 26 27 29 2A
  MEMORYMODE =
      0x20, // 2byte cmd, 0 horizontal, 1 vertical, 2 page addressing, 3 invalid
  COLUMNADDR = 0x21,     // 3bytes, start, end (included) valid in horizontal or
                         // vertical mode
  PAGEADDR = 0x22,       // 3bytes, start, end (included) valid in horizontal or
                         // vertical mode
  SETSTARTLINE_0 = 0x40, // set display RAM start line at 0
  SEGREMAP = 0xA0,       // segment remap 0 mapped to SEG0
  SETMULTIPLEX = 0xA8,   // 2 byte cmd, set mux ratio
  COMSCANDEC = 0xC8, // scan from COM[N-1] to COM[0] (0xC0 is COM0 to COM[N-1])
  SETDISPLAYOFFSET = 0xD3,   // 2 byte cmd, vertical shift
  SETCOMPINS = 0xDA,         // 2 byte cmd, seq com pin conf, left right remap
  SETDISPLAYCLOCKDIV = 0xD5, // 2 byte cmd, low nibble A[3:0]+1 = div ratio,
                             // high nibble A[7:4] freq, reset 1000b
  SETPRECHARGE =
      0xD9, // 2 byte cmd, precharge period A[3:0] phase1 A[7:4] phase2, reset:2
  SETVCOMDETECT = 0xDB, // 2 byte Vcomh deselect level A[6:4] 000b 0.65xVcc 010b
                        // 0.77(reset) 011b 0.83
  CHARGEPUMP = 0x8D,    // Enable charge pump seq: 0x8D, 0x14, 0xAF (Charge pump
                        // setting, enable charge pump, display on)
  EXTERNALVCC = 0x1,
  SWITCHCAPVCC = 0x2
};
uint8_t const nibbleToByte[16] = {
    0b00000000, 0b00000011, 0b00001100, 0b00001111, 0b00110000, 0b00110011,
    0b00111100, 0b00111111, 0b11000000, 0b11000011, 0b11001100, 0b11001111,
    0b11110000, 0b11110011, 0b11111100, 0b11111111};

uint8_t *txbuf;
uint8_t *rxbuf;
int32_t mode;

uint8_t text[21 * 8];    // text inputs
uint8_t textBuf[21 * 8]; // text inputs copy (to avoid flicker)
uint8_t tY[128];         // scope input copied and rescaled
bool disable;
// SETUP
// ------------------
void cmd(uint8_t c) {
  txbuf[0] = 0;
  txbuf[1] = c;
  i2cMasterTransmitTimeout(&I2CD1, attr_I2CADDR, txbuf, 2, rxbuf, 0, 30);
}
void cmd(uint8_t c1, uint8_t c2) {
  cmd(c1);
  cmd(c2);
}
void cmd(uint8_t c1, uint8_t c2, uint8_t c3) {
  cmd(c1, c2);
  cmd(c3);
}

// _____________________________________________________________________
void fill(uint8_t v) {
  i2cAcquireBus(&I2CD1);
  cmd(COLUMNADDR, 0, 127); // Column start end
  cmd(PAGEADDR, 0, 7);     // Page start end
  txbuf[0] = 0x40;
  for (int i = 1; i < 129; i++)
    txbuf[i] = v;
  for (int p = 0; p < 8; p++) {
    i2cMasterTransmitTimeout(&I2CD1, attr_I2CADDR, txbuf, 129, rxbuf, 0, 30);
  }
  i2cReleaseBus(&I2CD1);
}

/* returns i
 */
int drawTxt(int i, int NBC, uint8_t *tb, int page, uint8_t *tPage) {
  for (int nc = 0; nc < NBC; nc++) {
    int ascii_32 = tb[nc] - ' ';
    const uint8_t *adChar = tiar_font5x8 + ascii_32 * 5;
    for (int slice = 0; slice < 5; slice++) { // slices are one pixel wide
      tPage[i] = adChar[slice];
      i++;
    }
    tPage[i] = 0;
    i++; // separator space 1 pixel wide => 6 pixels per char
  }
  return i;
}
// _____________________________________________________________________
// text
// returns a page to be sent to the display based on contents of text
void calcTextPage(int page, uint8_t tPage[128]) {
  int i = 0;
  uint8_t *tb = textBuf + 21 * page;
  if (tb[0] >= ' ') { // full text line
    i = drawTxt(i, 21, tb, page, tPage);
    for (; i < 128; i++) {
      tPage[i] = 0;
    }                      // space padding
  } else if (tb[0] == 1) { // strbar

    i = drawTxt(i, 10, tb + 2, page, tPage);

    while (i < 63) {
      tPage[i] = 0;
      i++;
    }                  // space padding
    if (tb[1] < 128) { // positive value => filled bar
      tPage[i] = 0;
      i++;
      for (i = 64; i < 63 + tb[1] && i < 128; i++) {
        tPage[i] = 0b11111000;
      }
    } else { // negative value => hollow bar
      tPage[i] = 0b11111000;
      i++;
      for (i = 64; i < 63 + 256 - tb[1] && i < 127; i++) {
        tPage[i] = 0b10001000;
      }
      tPage[i] = 0b11111000;
      i++;
    }
    for (; i < 128; i++) {
      tPage[i] = 0;
    }                      // space padding
  } else if (tb[0] == 2) { // bar
    if (tb[1] <= 128) {    // positive value => filled bar
      tPage[i] = 0;
      i++;
      for (i = 0; i < tb[1]; i++) {
        tPage[i] = 0b01111100;
      }
    } else { // negative value => hollow bar
      tPage[i] = 0b01111100;
      i++;
      for (; i < 256 - tb[1]; i++) {
        tPage[i] = 0b01000100;
      }
      tPage[i] = 0b01111100;
      i++;
    }
    for (; i < 128; i++) {
      tPage[i] = 0;
    } // space padding

  } else if (tb[0] == 3) { // strbar2
    i = drawTxt(i, 10, tb + 3, page, tPage);

    for (; i < 63; i++) {
      tPage[i] = 0;
    } // space padding

    uint8_t filledBar = 0b00001110;
    uint8_t hollowBar = 0b00001010;
    int v = tb[1];
    if (v < 128) { // positive value => filled bar
      for (i = 63; i < 63 + v && i < 128; i++) {
        tPage[i] = filledBar;
      }
    } else { // negative value => hollow bar
      tPage[i] = filledBar;
      i++;
      for (i = 64; i < 63 + 256 - v && i < 127; i++) {
        tPage[i] = hollowBar;
      }
      tPage[i] = filledBar;
      i++;
    }
    for (; i < 128; i++) {
      tPage[i] = 0;
    } // space padding

    filledBar = 0b11100000;
    hollowBar = 0b10100000;
    v = tb[2];
    if (v < 128) { // positive value => filled bar
      for (i = 63; i < 63 + v && i < 128; i++) {
        tPage[i] |= filledBar;
      }
    } else { // negative value => hollow bar
      i = 63;
      tPage[i] |= filledBar;
      i++;
      for (; i < 63 + 256 - v && i < 127; i++) {
        tPage[i] |= hollowBar;
      }
      tPage[i] |= filledBar;
      i++;
    }

  }
  //_______________________________________________________
  else if (tb[0] == 4) { // bar dble
    uint8_t filledBar = 0b00001110;
    uint8_t hollowBar = 0b00001010;
    int v = tb[1];
    if (v <= 128) { // positive value => filled bar
      for (i = 0; i < v; i++) {
        tPage[i] = filledBar;
      }
    } else { // negative value => hollow bar
      i = 0;
      tPage[i] = filledBar;
      i++;
      for (; i < 256 - v; i++) {
        tPage[i] = hollowBar;
      }
      tPage[i] = filledBar;
      i++;
    }
    for (; i < 128; i++) {
      tPage[i] = 0;
    } // space padding

    filledBar = 0b11100000;
    hollowBar = 0b10100000;
    v = tb[2];
    if (v <= 128) { // positive value => filled bar
      for (i = 0; i < v; i++) {
        tPage[i] |= filledBar;
      }
    } else { // negative value => hollow bar
      i = 0;
      tPage[i] |= filledBar;
      i++;
      for (; i < 256 - v; i++) {
        tPage[i] |= hollowBar;
      }
      tPage[i] |= filledBar;
      i++;
    }
  }
}
// _____________________________________________________________________
// opt function draw

// LSB up
uint8_t const tBar[9] = {0b00000000, 0b10000000, 0b11000000,
                         0b11100000, 0b11110000, 0b11111000,
                         0b11111100, 0b11111110, 0b11111111};

/*
page0
    1
    .
    7
*/

uint8_t vBar(uint8_t val, int page) {
  uint8_t _page = 7 - (val / 8);
  if (page > _page)
    return 0b11111111; // below => light
  else if (page < _page)
    return 0; // above => dark
  else
    return tBar[val & 7];
}

// on the Oled display, a "page" is a 128x8 stripe of pixels
// described by 128 bytes. The 128x64 OLED consists of 8 "pages"
// I calculate and transmit one page at a time.
// It saves memory as small buffers are enough to transmit them.
void calcScopePage(int page, uint8_t tPage[128]) {
  uint16_t y0 = tY[0];
  uint16_t y1 = tY[1];
  uint16_t y2;
  for (int i = 0; i < 128; i++) {
    if (i < 127)
      y2 = tY[i + 1];
    uint16_t yM, ym;
    yM = ym = y1;
    uint16_t y = (y0 + y1) >> 1;
    yM = y > yM ? y : yM;
    ym = y < ym ? y : ym;
    y = (y2 + y1) >> 1;
    yM = y > yM ? y : yM;
    ym = y < ym ? y : ym;
    if (ym == yM)
      if (yM > 0)
        ym--;
      else
        yM++;
    tPage[i] = vBar(yM, page) & ~vBar(ym, page);
    y0 = y1;
    y1 = y2;
  }
}
// _____________________________________________________________________
void sendPage(int page) {
  /*
    Note: I consider that having a little flickering is not a big deal
    (compared to potential audio glitches).
    So, i do not use the chSysLock() chSysUnlock to protect the memcpy.
  */
  i2cAcquireBus(&I2CD1);
  // prepare transmission to the "page"
  cmd(COLUMNADDR, 0, 127);   // Column start end
  cmd(PAGEADDR, page, page); // Page start end
  if (attr_type == 1106) {
    cmd(0xB0 + page);     // set page address
    cmd(2 & 0xf);         // set lower column address
    cmd(0x10 | (2 >> 4)); // set higher column address
  }
  i2cReleaseBus(&I2CD1);

  if (mode == 0) { // 8 text lines
    // on the beginning of drawing (page 0) we update the buffers
    if (page == 0) {
      memcpy(textBuf, text, 21 * 8);
    }
    calcTextPage(page, txbuf + 1);
  } else if (mode == 1) { // Title and scope
    // on the beginning of drawing (page 0) we update the buffers
    if (page == 0) {
      // chSysLock();
      // update the textBuffer
      memcpy(textBuf, text, 21 * 8);
      // update scope buffer
      memcpy(tY, attr_scope.t, 128);
      // chSysUnlock();
      // scale for the display
      int8_t *stY = (int8_t *)tY;
      for (int i = 0; i < 128; i++) {
        tY[i] = ((stY[i] + 64) * 3) >> 3;
      }
    }
    if (page < 2) {
      calcTextPage(page, txbuf + 1);
    } else {
      calcScopePage(page, txbuf + 1);
    }
  } else if (mode == 2) { // full screen scope
    // on the beginning of drawing (page 0) we update the buffers
    if (page == 0) {
      // chSysLock();
      // update scope buffer
      memcpy(tY, attr_scope.t, 128);
      // chSysUnlock();
      // scale for the display
      int8_t *stY = (int8_t *)tY;
      for (int i = 0; i < 128; i++) {
        tY[i] = (stY[i] + 64) >> 1;
      }
    }
    calcScopePage(page, txbuf + 1);
  }
  // transmission of the page

  // transmit the page
  txbuf[0] = 0x40;
  i2cAcquireBus(&I2CD1);
  i2cMasterTransmitTimeout(&I2CD1, attr_I2CADDR, txbuf, 129, rxbuf, 0, 30);
  i2cReleaseBus(&I2CD1);
}
// _____________________________________________________________________
void init() {
  i2cAcquireBus(&I2CD1);
  // Init sequence
  if (attr_type == 1106 || attr_type == 1306) {
    cmd(DISPLAYOFF);
    // 2 byte cmd,
    //  low nibble A[3:0]+1 = div ratio,
    //  high nibble A[7:4] freq  reset 1000b
    cmd(SETDISPLAYCLOCKDIV, 0x80);
    cmd(SETMULTIPLEX, LCDHEIGHT - 1);
    cmd(SETDISPLAYOFFSET, attr_type == 1306 ? 0x00 : 0x01);
    cmd(SETSTARTLINE_0);
    cmd(MEMORYMODE, 0x00); // horizontal
    cmd(SEGREMAP | 0x1);
    cmd(COMSCANDEC);
    // 128 x 64
    cmd(SETCOMPINS, 0x12);
    cmd(SETCONTRAST, 0xCF);
    cmd(SETPRECHARGE, 0xF1);
    cmd(SETVCOMDETECT, 0x40);
    cmd(DISPLAYALLON_RESUME);
    cmd(NORMALDISPLAY);
    cmd(DEACTIVATE_SCROLL);
    cmd(CHARGEPUMP, 0x14);
    cmd(DISPLAYON);
  } else {
    cmd(DISPLAYOFF);
    cmd(SETDISPLAYCLOCKDIV, 0xA0); // vs 80
    cmd(SETMULTIPLEX, 0x3F);
    cmd(SETDISPLAYOFFSET, 0x00);
    cmd(SETSTARTLINE_0);
    cmd(MEMORYMODE, 0x00);
    cmd(SEGREMAP | 0x1);
    cmd(COMSCANDEC);
    cmd(SETCOMPINS, 0x12);
    cmd(SETCONTRAST, 0x6F); // vs 0xCF
    cmd(SETPRECHARGE,
        0xD3); // vs 0xF1 		/* [2] pre-charge period 0x022/f1*/
    cmd(SETVCOMDETECT,
        0x20); // vs 0x40 		/* vcomh deselect level */
               // if vcomh is 0, then this will give the biggest range for
               // contrast control issue #98 restored the old values for the
               // noname constructor, because vcomh=0 will not work for all
               // OLEDs, #116
    cmd(DEACTIVATE_SCROLL);
    cmd(DISPLAYALLON_RESUME);
    cmd(NORMALDISPLAY);
    cmd(CHARGEPUMP, 0x14);
    cmd(DISPLAYON);
  }

  i2cReleaseBus(&I2CD1);
}
// _____________________________________________________________________
void setup() {
  static uint8_t _txbuf[132] __attribute__((section(".sram2")));
  static uint8_t _rxbuf[8] __attribute__((section(".sram2")));
  txbuf = _txbuf;
  rxbuf = _rxbuf;
  init();
}

// _____________________________________________________________________
// THREADS
msg_t ThreadX2() {
  setup();
  while (!chThdShouldTerminate()) {
    if (!disable) {
      for (int i = 0; i < 8; i++) {
        sendPage(i);
      }
    }
    chThdSleepMilliseconds(32);
  }
  chThdExit((msg_t)0);
}

static msg_t ThreadX(void *arg) { ((attr_parent *)arg)->ThreadX2(); }

WORKING_AREA(waThreadX, 192);
Thread *Thd;
Init
for (int i = 0; i < 21 * 8; i++) {
  text[i] = textBuf[i] = ' ';
}
Thd = chThdCreateStatic(waThreadX, sizeof(waThreadX), NORMALPRIO, ThreadX,
                        (void *)this);
Control Rate
disable = inlet_disable; // can be useful when playing with future modules
                         // targetting the same display
mode = inlet_mode;

// two first lines are present whatever the mode
if (inlet_line1 != NULL) {
  int i = 0;
  while (i < 21 & inlet_line1[i] != '\0') {
    text[i] = inlet_line1[i];
    i++;
  }
  while (i < 21) {
    text[i] = ' ';
    i++;
  }
}
if (inlet_line2 != NULL) {
  int i = 0;
  while (i < 21 & inlet_line2[i] != '\0') {
    text[i + 21] = inlet_line2[i];
    i++;
  }
  while (i < 21) {
    text[i + 21] = ' ';
    i++;
  }
}
if (mode == 0) {
  if (inlet_line3 != NULL) {
    int i = 0;
    while (i < 21 & inlet_line3[i] != '\0') {
      text[i + 2 * 21] = inlet_line3[i];
      i++;
    }
    while (i < 21) {
      text[i + 2 * 21] = ' ';
      i++;
    }
  }
  if (inlet_line4 != NULL) {
    int i = 0;
    while (i < 21 & inlet_line4[i] != '\0') {
      text[i + 3 * 21] = inlet_line4[i];
      i++;
    }
    while (i < 21) {
      text[i + 3 * 21] = ' ';
      i++;
    }
  }
  if (inlet_line5 != NULL) {
    int i = 0;
    while (i < 21 & inlet_line5[i] != '\0') {
      text[i + 4 * 21] = inlet_line5[i];
      i++;
    }
    while (i < 21) {
      text[i + 4 * 21] = ' ';
      i++;
    }
  }
  if (inlet_line6 != NULL) {
    int i = 0;
    while (i < 21 & inlet_line6[i] != '\0') {
      text[i + 5 * 21] = inlet_line6[i];
      i++;
    }
    while (i < 21) {
      text[i + 5 * 21] = ' ';
      i++;
    }
  }
  if (inlet_line7 != NULL) {
    int i = 0;
    while (i < 21 & inlet_line7[i] != '\0') {
      text[i + 6 * 21] = inlet_line7[i];
      i++;
    }
    while (i < 21) {
      text[i + 6 * 21] = ' ';
      i++;
    }
  }
  if (inlet_line8 != NULL) {
    int i = 0;
    while (i < 21 & inlet_line8[i] != '\0') {
      text[i + 7 * 21] = inlet_line8[i];
      i++;
    }
    while (i < 21) {
      text[i + 7 * 21] = ' ';
      i++;
    }
  }
}
/*
void copyText(const uint8_t* line, int buff, int nbc){
 if(line != NULL){
    int i = 0;
    while(i < nbc & line[i] != '\0'){
          buff[i] = line[i];
          i++;
    }
    while(i < nbc ){buff[i] = ' ';i++;}
  }
}
ex:
copyText(inlet_line1, text,      21);
copyText(inlet_line2, text+1*21, 21);
copyText(inlet_line3, text+2*21, 21);
copyText(inlet_line4, text+3*21, 21);
copyText(inlet_line5, text+4*21, 21);
copyText(inlet_line6, text+5*21, 21);
copyText(inlet_line7, text+6*21, 21);
copyText(inlet_line8, text+7*21, 21);

*/
Dispose
chThdTerminate(Thd);
chThdWait(Thd);

Privacy

© 2024 Zrna Research