An archive of community.esquilo.io as of Saturday January 26, 2019.

Need help with a driver for Adafruit 1.2” 7-Segment Display with I2C

JefferyS

Hardware:
Adafruit 1.2" 4-Digit 7-Segment Display w/I2C Backpack - Green
PRODUCT ID: 1268

HT16K33 Datasheet

This page has the Adafruit Arduino code

It does have it's own pull up resisters and I'm using 4-channel I2C-safe Bi-directional Logic Level Converter - BSS138 though they have a way on the display to set the pull ups to 3.3 volt I've used this on another project with success. I also slowed down the speed as they had 400k and was getting errors.

I've tried to port some of the Adafruit library(it contains a lot of code for other displays using the HT16K33) and I have it where it will initialize set the brightness, turn on the oscillator, and attempt to display a colon.

I get no error yet it does nothing. A couple things confuse me, first If I print out any register other than the one for display memory I get 0 so I don't know if I just can read those or if I'm reading them wrong in the code. Second in the datasheet the registers go from D15 to D8, not sure what happened to D7 to D0 but the Display ram does have that.

Here is the code for my class:

class HT16K33Class {
    
    
// HT16K33 registers and HT16K33-specific variables
    // Commands
    static HT16K33_DISPLAY_ADDRESS  = 0x00;
    static HT16K33_SYSTEM_SETUP        = 0x20;
    static HT16K33_DISPLAY_SETUP     = 0x80;
    
    static HT16K33_DISPLAY_ON  = 0x81;
    static HT16K33_DISPLAY_OFF = 0x80;
    static HT16K33_OSCILLATOR_ON   = 0x21;
    static HT16K33_OSCILLATOR_OFF  = 0x20;
    
    static HT16K33_I2C_ADDRESS = 0x77;

    static HT16K33_BLINK_CMD = 0x80;
    static HT16K33_BLINK_DISPLAY_ON = 0x01;
    static HT16K33_BLINK_OFF = 0;
    static HT16K33_BLINK_2HZ  = 1;
    static HT16K33_BLINK_1HZ  = 2;
    static HT16K33_BLINK_HALFHZ  = 3;
    static HT16K33_CMD_BRIGHTNESS = 0xE0;
    static HT16K33_CMD_BRIGHTNESS_LOW = 0xE3;
    static HT16K33_CMD_BRIGHTNESS_MEDIUM = 0xE7;
    static HT16K33_CMD_BRIGHTNESS_HIGH = 0xEF;
    static SEVENSEG_DIGITS = 0x05;

    
    
    static HT16K33_BLANK_CHAR = 16;
    static HT16K33_MINUS_CHAR = 17;
    static HT16K33_CHAR_COUNT = 17;
    
    
    // Class properties; null for those defined in the Constructor
    i2c = null;
    i2cAddress = null;
    debug = false;
    displayBuffer = array(8, 0);
    
    constructor(_i2c, _i2cAddress, _debug) {
        i2c = _i2c;
        i2cAddress = _i2cAddress;
        debug = _debug;
    }
    
    
    function setBrightness(b) {
      if (b > 16) b = 16;
        
      i2c.write8(HT16K33_CMD_BRIGHTNESS, b);
    }

    function blinkRate(b) {

      if (b > 3) b = 0; // turn off if not sure

      i2c.write8(HT16K33_BLINK_CMD, (HT16K33_BLINK_DISPLAY_ON | (b << 1))); 
    }

    function init() {
      i2c.address(i2cAddress);

      i2c.write8(HT16K33_SYSTEM_SETUP, 0x01);  // turn on oscillator

      blinkRate(HT16K33_BLINK_OFF);

      setBrightness(16); // max brightness
    }    
    
    function writeDisplay() {
        for (local i=0; i<8; i++) {
            i2c.write8(HT16K33_DISPLAY_ADDRESS, (displayBuffer[i] & 0xFF));    
            i2c.write8(HT16K33_DISPLAY_ADDRESS, (displayBuffer[i] >> 8));    
        }
    } // function writeDisplay()

    function clear() {
      for (local i=0; i<8; i++) {
        displayBuffer[i] = 0;
      }
    }
     
    function write(c) {

      local r = 0;

      if (c == '\n') position = 0;
      if (c == '\r') position = 0;

      if ((c >= '0') && (c <= '9')) {
        writeDigitNum(position, c-'0');
        r = 1;
      }

      position++;
      if (position == 2) position++;

      return r;
    }

    function writeDigitRaw(d, bitmask) {
      if (d > 4) return;
      displayBuffer[d] = bitmask;
    }

    function drawColon(state) {
      if (state)
        displayBuffer[2] = 0x2;
      else
        displayBuffer[2] = 0;
    }

    function writeColon() {
       // start at address $02
        i2c.write8(0x04, displayBuffer[2] & 0xFF);
        i2c.write8(0x04, displayBuffer[2] >> 8);
    }

        // dot is true or false
    function writeDigitNum(d, num, dot) {
      if (d > 4) return;

      writeDigitRaw(d, numbertable[num] | (dot << 7));
    }

   
    function printRegisters() {
        printLn("Osc: " + i2c.read8(HT16K33_SYSTEM_SETUP));
        printLn("Display Data: " + i2c.read16(HT16K33_DISPLAY_ADDRESS));
        printLn("Brightness: " + i2c.read8(HT16K33_CMD_BRIGHTNESS));
        printLn("Row/INT: " + i2c.read8(0xA0));
        printLn("Display Setup: " + i2c.read8(HT16K33_DISPLAY_SETUP));

    }
    
} // class HT16K33Class

`
Here is my code to run it

require(["GPIO", "I2C", "system", "string"]);

dofile("sd:/common/utilities.nut");

dofile("sd:/Display7Seg/HT16K33Class.nut");

delay(500);
try {
    // Create an I2C instance
    i2c <- I2C(0);
    
    // Set speed to 400 kHz
    i2c.speed(200000);
    
}
catch(ex) {
    printLn(ex);
}

dsp7Seg <- HT16K33Class(i2c, 0x77, true);

dsp7Seg.init();

dsp7Seg.drawColon(true);

dsp7Seg.writeColon();

dsp7Seg.printRegisters();

// end

The command printLn is in the utilities.nut and it just adds "\r\n" to the end of the print command.

I've tried to keep it simple I even tried without making a class and setting everything in one file using hex numbers for the registers and pretty much got the same thing.

I'm not sure what I'm missing. I did port over another I2C device from Sparkfun SX1509 breakout and was able to get that working fairly well but still working on it.

I'm planning on sharing this with everyone as well as the SX1509 when it's finished.

I'll be happy to answer any questions.

Any help would be most appreciated :smile:

Thanks,

Jeffery

softwarejanitor

I don't have one of those to try it out. It is more expensive from Adafruit than I'm willing to pay on speculation right now. I might order this similar item: https://www.aliexpress.com/item/5pcs-4-Bits-Digital-Tube-LED-Display-Module-With-Clock-Display-for-Arduino/1893590861.html That one uses a different LED driver chipset though, the TM1637. It is also a lot smaller, which is mostly why it is cheaper I'm sure. Anyway, the TM1637 code I found is here: https://github.com/avishorp/TM1637 I don't know if looking at that might help jog you on something for the display you have.

JefferyS

Thanks for the help, unfortunately the TM1637 uses digital pins.

I looked at some of your projects that use I2C and tried some bits of your code to write to the display but it didn't work either though I may have been doing it wrong.

Do you know if any of the I2C devices you wrote code for had a register that was the command and held the data? Kind of seems I saw that during my search for code on this HT16K33 chip.

I think it might help if I kind of knew the underlining actions of the I2C commands for the Esquilo. When you do a write, does it send the address and the write bit like the datasheet shows it needs to be done, does the xfer command send one byte at a time and I should be using that in this case as it doesn't end the I2C transmission I think I read.

Have to go read all the I2C in the docs again.

Thanks,

Jeffery

JefferyS

Shown below is the Write byte and Read byte sequence I guess. Can anyone put any code to these. I've tried several things and sometimes I get errors and other times (different code) it's find but doesn't seem to change a bit in the register.

This seems reasonable for a write byte:
i2c.write8(0x20, 0x01);

Also tried 0x21 in place of the 0x01.
Now I'm guessing the write command sends the Slave Address and since it is a write it sets the write bit too in the first 8 bits. Also the Register address also contains the bits that change. In this case I believe only bit 0 can change.

The read is more involved.

Withe the P in the middle it looks to be two commands to send though I could be wrong.
So I've tried to send a write blob with just the command address in the blob and then a read blob as it would send the Slave address again with the read.

So far I haven't been able to change this register bit.

I think once I understand the pattern I can get the rest of it done but I'm a little confused that the register address and changeable bits are in the same byte.

There is a link to the full datasheet in my first post. I'm sure one of the guru's here could probable look at it and say do this lol

Sure could use some help.

Thanks,

Jeffery

JefferyS

I finally figured it out. The code is the basics just for displaying LED for this 7-Segment display not the whole key press stuff the HT16K33 can do too.
This is the down and dirty code that needs some cleanup but wanted to share before I forgot lol.

I also found that the print command didn't want to work in the class so switched to the error command, not sure why.

Any suggestions for improvement of the way I structure my class and any other code writing improvements would be most welcome :smile:
HT16K33Lib.nut

class HT16K33LED {
    
    
    // HT16K33 registers and HT16K33-specific variables
    // Commands
    static HT16K33_DISPLAY_ADDRESS  = 0x00;
    static HT16K33_SYSTEM_SETUP        = 0x20;
    static HT16K33_DISPLAY_SETUP     = 0x80;
    
    static HT16K33_DISPLAY_ON  = 0x81;
    static HT16K33_DISPLAY_OFF = 0x80;
    static HT16K33_OSCILLATOR_ON   = 0x21;
    static HT16K33_OSCILLATOR_OFF  = 0x20;
    
    static HT16K33_I2C_ADDRESS = 0x70;

    static HT16K33_BLINK_CMD = 0x80;
    static HT16K33_BLINK_DISPLAY_ON = 0x01;
    static HT16K33_BLINK_OFF = 0;
    static HT16K33_BLINK_2HZ  = 1;
    static HT16K33_BLINK_1HZ  = 2;
    static HT16K33_BLINK_HALFHZ  = 3;
    static HT16K33_CMD_BRIGHTNESS = 0xE0;
    static HT16K33_CMD_BRIGHTNESS_LOW = 0xE3;
    static HT16K33_CMD_BRIGHTNESS_MEDIUM = 0xE7;
    static HT16K33_CMD_BRIGHTNESS_HIGH = 0xEF;
    static SEVENSEG_DIGITS = 0x05;

    
    
    static HT16K33_BLANK_CHAR = 16;
    static HT16K33_MINUS_CHAR = 17;
    static HT16K33_CHAR_COUNT = 17;
    
    static numberTable = [
    0x3F, /* 0 */
    0x06, /* 1 */
    0x5B, /* 2 */
    0x4F, /* 3 */
    0x66, /* 4 */
    0x6D, /* 5 */
    0x7D, /* 6 */
    0x07, /* 7 */
    0x7F, /* 8 */
    0x6F, /* 9 */
    0x77, /* a */
    0x7C, /* b */
    0x39, /* C */
    0x5E, /* d */
    0x79, /* E */
    0x71, /* F */
];
    
    static column = [0x00, 0x02, 0x04, 0x06, 0x08]; // COM0, COM1, COM2, COM3, COM4
    
    // Class properties; null for those defined in the Constructor
    i2cBus = null;
    i2cAddress = null;
    debug = false;
    displayBuffer = blob(5);
    writeByte = blob(1);
    
    constructor(_i2cBus, _i2cAddress, _debug) {
        i2cBus = _i2cBus;
        i2cAddress = _i2cAddress;
        debug = _debug;
        
    }


    function blinkRate(_rate) {
        if (_rate > 3) _rate = 0; // turn off if not sure
        writeByte[0] = HT16K33_BLINK_CMD | HT16K33_DISPLAY_ON | (_rate << 1);
          i2cBus.write(writeByte);
    }
    
    function setBrightness(b) {
        if (b > 15) b = 15;
        writeByte[0] = HT16K33_CMD_BRIGHTNESS | b;
        i2cBus.write(writeByte);
    } 
    
    
    function init() {
        i2cBus.address(i2cAddress);
        writeByte[0] = 0x21;
        i2cBus.write(writeByte); // turn on oscillator

        blinkRate(HT16K33_BLINK_OFF);

        setBrightness(4); // max brightness
        
    }
    
    
    function writeDisplay() {
        local data = blob(1);
        data[0] = 0x00;
        i2cBus.write(data);
        
        for (local i=0; i<8; i++) {
            
            data[0] = displayBuffer[i] & 0xFF;
            
error(i + " - " + data[0].tostring() + "\r\n");    
            
            i2cBus.write(data);
            
            data[0] = displayBuffer[i] >> 8;
error(i + " - " + data[0].tostring() + "\r\n");            
            i2cBus.write(data);    
        }
    }
    
    function writeDisplayTest() {
        for (local i=0; i<5; i++) {

            if(debug) error(column[i] + " - " + displayBuffer[i] + "\r\n");
            if(i == 2){
                i2cBus.write8(column[i], displayBuffer[i]);
            }
            else {
                i2cBus.write8(column[i], numberTable[displayBuffer[i]]);
            }
            
        }
    }

    function clear() {
      for (local i=0; i<5; i++) {
        displayBuffer[i] = 0x00;
      }
    }
    
    

    function drawColon(state) {
      if (state)
        displayBuffer[2] = 0x02;
      else
        displayBuffer[2] = 0x00;
    }

    function writeColon() {
        i2cBus.write8(column[2], displayBuffer[2]);
    }
    
    function writeTime(hour, minute) {
        if(hour > 9) {
            local hr = hour.tostring();
            displayBuffer[0] = hr.slice(0,1).tointeger();
            displayBuffer[1] = hr.slice(1).tointeger();
            
        }
        else {
            displayBuffer[0] = 0;
            displayBuffer[1] = hour;
        }
        
        if(minute > 9) {
            local min = minute.tostring();
            displayBuffer[3] = min.slice(0,1).tointeger();
            displayBuffer[4] = min.slice(1).tointeger();
             
        }
        else {
            displayBuffer[3] = 0;
            displayBuffer[4] = minute;
        }
    }
    
    
    function readRegister() {
        local val = 0;
        error("Read Display Register \r\n");
/*
        val = i2cBus.read16(0x00);
        error("0x00 - " + val + "\r\n");
        val = i2cBus.read16(0x02);
        error("0x02 - " + val + "\r\n");
        val = i2cBus.read16(0x04);
        error(val + "\r\n");
        val = i2cBus.read16(0x06);
        error(val + "\r\n");
        val = i2cBus.read16(0x08);
        error(val + "\r\n");
        val = i2cBus.read16(0x0A);
        error(val + "\r\n");
        val = i2cBus.read16(0x0A);
        error(val + "\r\n");
        val = i2cBus.read16(0x0C);
        error(val + "\r\n");
        val = i2cBus.read16(0x0E);
        error(val + "\r\n");
*/
        for(local i = 0; i <16; i+=2) {
            val = i2cBus.read8(i);
            error(i + " - " + val + "\r\n");
            
        }

    }
}// class HT16K33LED

Test.nut

require(["GPIO", "I2C", "system", "string"]);

dofile("sd:/common/utilities.nut");

dofile("sd:/Display7Seg/HT16K33Lib.nut");



delay(500);


try {
    // Create an I2C instance
    i2c <- I2C(0);
    
    // Set speed to 400 kHz
    i2c.speed(100000);
    
}
catch(ex) {
    printLn(ex);
}

dsp7Seg <- HT16K33LED(i2c, 0x77, false);

dsp7Seg.init();

//dsp7Seg.clear();

dsp7Seg.drawColon(true);

//dsp7Seg.writeColon();

//dsp7Seg.writeDigitNum(0, 1);
local t = date();
while(true) {
    t = date();
    dsp7Seg.writeTime(t.hour, t.min);
    dsp7Seg.writeDisplayTest();
    delay(2000);
}

//dsp7Seg.printDisplayBuffer();

//dsp7Seg.readRegister();

utilities.nut

// Returns the Date and time
function DateTimeFormat() {
    local t = date();
    return format("%02d-%02d-%04d %02d:%02d:%02d", (t.month + 1), t.day, t.year, t.hour, t.min, t.sec);
}

// Returns the Date
function DateFormat() {
    local t = date();
    return format("%02d_%02d_%04d", (t.month + 1), t.day, t.year);
}

// Returns Date Time format for log
function LogDateTimeFormat() {
    local t = date();
    return format("[%04d-%02d-%02d %02d:%02d:%02d] ", t.year, (t.month + 1), t.day, t.hour, t.min, t.sec);
}

// Returns the Date format for the log file
function FileDateFormat() {
    local t = date();
    return format("%04d_%02d_%02d", t.year, (t.month + 1), t.day);
}


// Prints a line with Carriage Return
function printLn(str) {
    print(str + "\r\n");   
}