diff --git a/HT16K33.cpp b/HT16K33.cpp index 00299b1..331729a 100644 --- a/HT16K33.cpp +++ b/HT16K33.cpp @@ -4,8 +4,6 @@ * IC. It’s not functionally exhaustive by any stretch, but it should * at least work reasonably well. * - * @TODO: build functionality to read key data from the chip. - * **********************************************************************/ #include #include "HT16K33.h" @@ -15,42 +13,133 @@ */ uint16_t _flip_uint16(uint16_t in) { - uint16_t out = 0; - - for (uint8_t i = 0; i < 16; i++) - { - out <<= 1; - out |= in & 1; - in >>= 1; - } + uint16_t out = 0; + + for (uint8_t i = 0; i < 16; i++) { + out <<= 1; + out |= in & 1; + in >>= 1; + } - return out; + return out; } -// Constructor +/** + * Constructor + * Create a new instance of a HT16K33 chipset + * + * @param addr I2C address of the chip. It follows the format (binary) 1110 with the following notation: + * A2-A0 can be controlled using 39k resistors and DIP switches (see datasheet). If not connected, they default to 0. + * + * For 28 PIN package, all three can be controlled + * For 24 PIN package, A2 is always set to 0 + * For 20 PIN package, A2-A0 are set to 0 + * + * The Arduino wire library handles the R/W bit for us. Looking at the datasheet, ignore the R/W bit and shift + * the address one to the right to obtain the 7bit address required for this library. + */ void HT16K33::init(uint8_t addr) { - // orientation flags - resetOrientation(); - - // set the I2C address - _i2c_addr = addr; - - // assign + zero some buffer data - _buffer = (uint16_t*)calloc(8, sizeof(uint16_t)); - - // start everything - Wire.begin(); - Wire.beginTransmission(_i2c_addr); - Wire.write(0x21); // turn it on - Wire.endTransmission(); - - // set blink off + brightness all the way up - setBlink(HT16K33_BLINK_OFF); - setBrightness(15); - - // write the matrix, just in case - write(); + // orientation flags + resetOrientation(); + + // set the I2C address + _i2c_addr = addr; + + // assign + zero some buffer data + _buffer = (uint16_t *) calloc(8, sizeof(uint16_t)); + + // start everything + Wire.begin(); + wakeUp(); + + // set blink off + brightness all the way up + setBlink(HT16K33_BLINK_OFF); + setBrightness(15); + + // write the matrix, just in case + write(); +} + +/** + * Reads the entire key buffer from the chip and returns it in an 3x uint16_t array + * Every array element represents a row (bits 0-12 are used, the rest can be ignored). + * 1 means the key has been pressed. + * + * Note that the buffer is cleared after calling this function and that the interrupt PIN and register is also reset. + * + * @param _key_buffer + */ +void HT16K33::getKeyData(uint16_t _key_buffer[]) +{ + + // Write the register address we want to read + Wire.beginTransmission(_i2c_addr); + Wire.write(HT16K33_CMD_KEYS); + Wire.endTransmission(); + + //Start to read the entire register + Wire.requestFrom(_i2c_addr, (uint8_t)6); + for (uint8_t row = 0; row < 2; row++) { + uint16_t lsb = Wire.read(); + _key_buffer[row] = (Wire.read() << 8 | lsb); + } +} + +/** + * Configure ROW/INT PIN + * + * The ROW/INT PIN can either be used as an additional ROW pin for the display, or can act as interrupt pin for + * the keyscan functionality. By default, it acts as a row pin. + * + * @var row_int: defines whether the pin should act as row (false) or interrupt (true) + * @var act: Defines whether the INT should be active low (0) or active high (1). Ignored if `row_int` is set to false. + */ +void HT16K33::setRowIntPin(bool row_int, bool act) +{ + // send the command + Wire.beginTransmission(_i2c_addr); + Wire.write(HT16K33_CMD_ROWINT | (act & row_int) << 1 | row_int); + Wire.endTransmission(); +} + +/** + * Puts the HT16K33 to sleep. It will wake up if a key is pressed during the scan interval or when calling the + * wakeUp() function again. This can greatly reduce power consumption if mainly used for key scanning. + * The datasheet mentions a strong recommendation to read key data before putting the chip to sleep. + */ +void HT16K33::sleep() +{ + Wire.beginTransmission(_i2c_addr); + Wire.write(HT16K33_CMD_SYSTEM & ~(1UL)); + Wire.endTransmission(); +} + +/** + * Wakes up the HT16K33 from sleep. Wait at least 1ms after waking up for proper initialization before trying to + * use the chip or to read data. + */ +void HT16K33::wakeUp() +{ + Wire.beginTransmission(_i2c_addr); + Wire.write(HT16K33_CMD_SYSTEM | 0x01); + Wire.endTransmission(); +} + +bool HT16K33::getKeyInterrupt() +{ + + //Tell the IC which register to read + Wire.beginTransmission(_i2c_addr); + Wire.write(HT16K33_CMD_INTFLAG); + Wire.endTransmission(); + + uint8_t intflags; + + //Read the INT register + Wire.requestFrom(_i2c_addr, (uint8_t)1); + intflags = Wire.read(); + return (intflags > 0); } /** @@ -58,13 +147,13 @@ void HT16K33::init(uint8_t addr) */ void HT16K33::setBrightness(uint8_t brightness) { - // constrain the brightness to a 4-bit number (0–15) - brightness = brightness & 0x0F; - - // send the command - Wire.beginTransmission(_i2c_addr); - Wire.write(HT16K33_CMD_DIMMING | brightness); - Wire.endTransmission(); + // constrain the brightness to a 4-bit number (0–15) + brightness = brightness & 0x0F; + + // send the command + Wire.beginTransmission(_i2c_addr); + Wire.write(HT16K33_CMD_DIMMING | brightness); + Wire.endTransmission(); } /** @@ -72,9 +161,9 @@ void HT16K33::setBrightness(uint8_t brightness) */ void HT16K33::setBlink(uint8_t blink) { - Wire.beginTransmission(_i2c_addr); - Wire.write(HT16K33_CMD_SETUP | HT16K33_DISPLAY_ON | blink); - Wire.endTransmission(); + Wire.beginTransmission(_i2c_addr); + Wire.write(HT16K33_CMD_SETUP | HT16K33_DISPLAY_ON | blink); + Wire.endTransmission(); } /** @@ -82,9 +171,9 @@ void HT16K33::setBlink(uint8_t blink) */ void HT16K33::resetOrientation(void) { - _reversed = false; - _vFlipped = false; - _hFlipped = false; + _reversed = false; + _vFlipped = false; + _hFlipped = false; } /** @@ -92,7 +181,7 @@ void HT16K33::resetOrientation(void) */ void HT16K33::reverse(void) { - _reversed = !_reversed; + _reversed = !_reversed; } /** @@ -100,7 +189,7 @@ void HT16K33::reverse(void) */ void HT16K33::flipVertical(void) { - _vFlipped = !_vFlipped; + _vFlipped = !_vFlipped; } /** @@ -108,7 +197,7 @@ void HT16K33::flipVertical(void) */ void HT16K33::flipHorizontal(void) { - _hFlipped = !_hFlipped;; + _hFlipped = !_hFlipped;; } @@ -116,11 +205,10 @@ void HT16K33::flipHorizontal(void) * Clears the display buffer. Note that this doesn’t clear the display—you’ll need to call write() to do this. */ void HT16K33::clear(void) -{ - for (uint8_t i = 0; i < 8; i++) - { - _buffer[i] = 0; - } +{ + for (uint8_t i = 0; i < 8; i++) { + _buffer[i] = 0; + } } /** @@ -128,20 +216,17 @@ void HT16K33::clear(void) */ void HT16K33::setPixel(uint8_t col, uint8_t row, uint8_t val) { - // bounds checking - col = col & 0x0F; - row = row & 0x07; - val = val & 0x01; - - // write the buffer - if (val == 1) - { - _buffer[row] |= 1 << col; - } - else - { - _buffer[row] &= ~(1 << col); - } + // bounds checking + col = col & 0x0F; + row = row & 0x07; + val = val & 0x01; + + // write the buffer + if (val == 1) { + _buffer[row] |= 1 << col; + } else { + _buffer[row] &= ~(1 << col); + } } @@ -150,11 +235,11 @@ void HT16K33::setPixel(uint8_t col, uint8_t row, uint8_t val) */ void HT16K33::setRow(uint8_t row, uint16_t value) { - // bound check the row - row = row & 0x07; - - // write it - _buffer[row] = value; + // bound check the row + row = row & 0x07; + + // write it + _buffer[row] = value; } /** @@ -162,11 +247,10 @@ void HT16K33::setRow(uint8_t row, uint16_t value) */ void HT16K33::setColumn(uint8_t col, uint8_t value) { - // just do this via set pixel—waaaay easier! - for (uint8_t row = 0; row < 8; row++) - { - setPixel(col, row, (value & (1 << row)) > 0); - } + // just do this via set pixel—waaaay easier! + for (uint8_t row = 0; row < 8; row++) { + setPixel(col, row, (value & (1 << row)) > 0); + } } /** @@ -174,12 +258,11 @@ void HT16K33::setColumn(uint8_t col, uint8_t value) */ void HT16K33::drawSprite16(Sprite16 sprite, uint8_t colOffset, uint8_t rowOffset) { - // iterate through data and set stuff - for (uint8_t row = 0; row < sprite.height(); row++) - { - _buffer[(row + rowOffset) & 0x07] |= (sprite.readRow(row) << colOffset) & 0xFFFF; - } - + // iterate through data and set stuff + for (uint8_t row = 0; row < sprite.height(); row++) { + _buffer[(row + rowOffset) & 0x07] |= (sprite.readRow(row) << colOffset) & 0xFFFF; + } + } /** @@ -187,23 +270,22 @@ void HT16K33::drawSprite16(Sprite16 sprite, uint8_t colOffset, uint8_t rowOffset */ void HT16K33::drawSprite16(Sprite16 sprite) { - drawSprite16(sprite, 0, 0); -} + drawSprite16(sprite, 0, 0); +} /** * Write the RAM buffer to the matrix. */ void HT16K33::write(void) { - Wire.beginTransmission(_i2c_addr); - Wire.write(HT16K33_CMD_RAM); - - for (uint8_t row = 0; row < 8; row++) - { - writeRow(row); - } - - Wire.endTransmission(); + Wire.beginTransmission(_i2c_addr); + Wire.write(HT16K33_CMD_RAM); + + for (uint8_t row = 0; row < 8; row++) { + writeRow(row); + } + + Wire.endTransmission(); } /** @@ -211,27 +293,22 @@ void HT16K33::write(void) */ void HT16K33::writeRow(uint8_t row) { - // flip vertically - if (_vFlipped) - { - row = 7 - row; - } - - // read out the buffer so we can flip horizontally - uint16_t out = _buffer[row]; - if (_hFlipped) - { - out = _flip_uint16(out); - } - - if (_reversed) - { - Wire.write(out >> 8); // second byte - Wire.write(out & 0xFF); // first byte - } - else - { - Wire.write(out & 0xFF); // first byte - Wire.write(out >> 8); // second byte - } + // flip vertically + if (_vFlipped) { + row = 7 - row; + } + + // read out the buffer so we can flip horizontally + uint16_t out = _buffer[row]; + if (_hFlipped) { + out = _flip_uint16(out); + } + + if (_reversed) { + Wire.write(out >> 8); // second byte + Wire.write(out & 0xFF); // first byte + } else { + Wire.write(out & 0xFF); // first byte + Wire.write(out >> 8); // second byte + } } \ No newline at end of file diff --git a/HT16K33.h b/HT16K33.h index 5e3938d..08cb97c 100644 --- a/HT16K33.h +++ b/HT16K33.h @@ -1,72 +1,95 @@ #ifndef HT16K33_h - #define HT16K33_h - - // include appropriate version of Arduino code - #if (ARDUINO >= 100) - #include "Arduino.h" - #else - #include "WProgram.h" - #endif - - // include Wire for I2C comms - #include - #include "Sprite16.h" - - // different commands - #define HT16K33_CMD_RAM 0x00 - #define HT16K33_CMD_KEYS 0x40 - #define HT16K33_CMD_SETUP 0x80 - #define HT16K33_CMD_ROWINT 0xA0 - #define HT16K33_CMD_DIMMING 0xE0 - - // other options - #define HT16K33_DISPLAY_OFF 0x00 - #define HT16K33_DISPLAY_ON 0x01 - #define HT16K33_BLINK_OFF 0x00 - #define HT16K33_BLINK_1HZ 0x02 - #define HT16K33_BLINK_2HZ 0x04 - #define HT16K33_BLINK_0HZ5 0x06 - - - // actual class - class HT16K33 - { - public: - void init(uint8_t addr); - - // brightness control - void setBrightness(uint8_t brightness); - - // blink controls - void setBlink(uint8_t blink); - - // orientation - void resetOrientation(void); - void reverse(void); - void flipVertical(void); - void flipHorizontal(void); - - // buffer stuff - void clear(void); - void setPixel(uint8_t row, uint8_t col, uint8_t onff); - void setRow(uint8_t row, uint16_t value); - void setColumn(uint8_t col, uint8_t value); - void drawSprite16(Sprite16 data, uint8_t colOffset, uint8_t rowOffset); - void drawSprite16(Sprite16 data); - - // read/write - void write(void); - - private: - uint16_t *_buffer; - uint8_t _i2c_addr; - bool _reversed; - bool _vFlipped; - bool _hFlipped; - - void writeRow(uint8_t row); - - }; - +#define HT16K33_h + +// include appropriate version of Arduino code +#if (ARDUINO >= 100) +#include "Arduino.h" +#else + +#include "WProgram.h" + +#endif + +// include Wire for I2C comms +#include +#include "Sprite16.h" + +// different commands +#define HT16K33_CMD_RAM 0x00 +#define HT16K33_CMD_SYSTEM 0x20 +#define HT16K33_CMD_KEYS 0x40 +#define HT16K33_CMD_INTFLAG 0x60 +#define HT16K33_CMD_SETUP 0x80 +#define HT16K33_CMD_ROWINT 0xA0 +#define HT16K33_CMD_DIMMING 0xE0 + +// other options +#define HT16K33_DISPLAY_OFF 0x00 +#define HT16K33_DISPLAY_ON 0x01 +#define HT16K33_BLINK_OFF 0x00 +#define HT16K33_BLINK_1HZ 0x02 +#define HT16K33_BLINK_2HZ 0x04 +#define HT16K33_BLINK_0HZ5 0x06 + + +// actual class +class HT16K33 { +public: + void init(uint8_t addr); + + // brightness control + void setBrightness(uint8_t brightness); + + // blink controls + void setBlink(uint8_t blink); + + //General Functionality + void setRowIntPin(bool row_int, bool act); + + void sleep(); + + void wakeUp(); + + // orientation + void resetOrientation(void); + + void reverse(void); + + void flipVertical(void); + + void flipHorizontal(void); + + // Keyscan functions + void getKeyData(uint16_t _key_buffer[]); + + bool getKeyInterrupt(void); + + // buffer stuff + void clear(void); + + void setPixel(uint8_t row, uint8_t col, uint8_t onff); + + void setRow(uint8_t row, uint16_t value); + + void setColumn(uint8_t col, uint8_t value); + + void drawSprite16(Sprite16 data, uint8_t colOffset, uint8_t rowOffset); + + void drawSprite16(Sprite16 data); + + // read/write + void write(void); + +private: + uint16_t *_buffer; + uint8_t _i2c_addr; + bool _reversed; + bool _vFlipped; + bool _hFlipped; + + void writeRow(uint8_t row); + +}; + #endif // #HT16K33 \ No newline at end of file diff --git a/README.textile b/README.textile index f9f4366..4debb24 100644 --- a/README.textile +++ b/README.textile @@ -2,6 +2,8 @@ h1. HT16K33 Library for Arduino This is a quick and dirty library providing a relatively easy-to-use interface for driving the "Holtek HT16K33":http://www.holtek.com.tw/documents/10179/11842/HT16K33v120.pdf 16x8 LED driver IC. I wrote it for a project I was working on, and thought it worth chucking up onto Github in case anyone else finds it useful. +In 2019, support for the keyscan funcationality of the HT16K33 has been added. + h2. Quickref This library should be reasonably self-explanatory, and I’ve provided a few example sketches to get you started: @@ -9,6 +11,7 @@ This library should be reasonably self-explanatory, and I’ve provided a few ex * *DemoReel* is your standard ‘smily faces and random animation’ thing that most matrix libraries tend to come with * *OrientationDemo* demonstrates the library’s ability to quickly + easily flip the display vertically + horizontally in software with a minimum of fuss and bother * *ReversalDemo* demonstrates how you can recover from connecting your matrices backward =) +* *KeyscanDemo* demonstrates how you can use the keyscan functionality, either by regular polling, software interrupt or hardware interrupt The API commands are, in no particular order: @@ -26,9 +29,29 @@ The API commands are, in no particular order: - @drawSprite16(Sprite16 data)@ := draws the @Sprite16@ onto the matrix at point (0, 0) (see below)† - @drawSprite16(Sprite16 data, uint8_t x, uint8_t y)@ := as above, but at point (x, y)† - @write()@ := writes the display buffer to the IC (updates the display) +- @sleep()@ := Puts the IC to sleep. It will wake up again when a key is pressed or @wakeUp()@ is called +- @wakeUp()@ := Wake up the IC from sleep mode +- @getKeyInterrupt()@ := returns a boolean value indicating if there's new key data available that can be retrieved +- @getKeyData()@ := returns an array of 3 uint16_t numbers, each one representing a row. The three MSBs are unused + † note: none of these will write anything to the IC—you will still need to call @write()@. +h3. About Keyscan + +Keep in mind that the data register is cleared if you read the data from the IC. + +There are three options to get data from the IC: + +- Periodically poll the IC for new data +- Poll the interrupt register +- Attach a hardware interrupt (see keyscan example) + +Note that you may need to debounce the keys yourself. The IC is doing basic hardware debouncing, however if you query +the data too quickly after receiving the interrupt, the data register (and interrupt) is reset, while the user still +may be pressing the button. If a hardware interrupt is used, less than 20ms are enough to receive the interrupt, read +the data and clear the register again. Refer to the timing diagrams in the datasheet for more details. + h3. About @Sprite16@ @Sprite16@ is a class based on the venerable "Sprite library":http://wiring.org.co/reference/libraries/Sprite/index.html, but which has been built to allow a maximum sprite size of 16x8, compared to 8x8 in the original. @@ -77,10 +100,6 @@ p. Rather than having to rewire everything/rework your PCBs, you can just call @ _(no, this didn’t happen to me. I don’t know why you would think that /s)_ -h2. TODO - -It’d be really nice if this library supported the HT16K33’s keyscan functionality… - h2. Caveats As mentioned at the start, I wrote this library for fun and to scratch a particular itch. I’m offering it to the world Because™, but I make no guarantee that it’s going to work 100% perfectly every time. By the same token, I’m not going to offer any kind of guarantee that I’ll touch this repo once I’ve made my first commit :) diff --git a/examples/KeyscanDemo/KeyscanDemo.ino b/examples/KeyscanDemo/KeyscanDemo.ino new file mode 100644 index 0000000..fa04409 --- /dev/null +++ b/examples/KeyscanDemo/KeyscanDemo.ino @@ -0,0 +1,68 @@ +/** + * This is a quick demo of the keyscan function of the controller, using the ROW/INT pin as interrupt, connected to an + * interrupt pin on Arduino. + */ +#include "HT16K33.h" + +HT16K33 matrix = HT16K33(); + +uint16_t keybuffer[3]; + +volatile bool has_key_data = false; + +const byte interruptPin = 2; + +void setup() +{ + //Serial port for debugging in Arduino IDE + Serial.begin(115200); + + // wait a few ms to give the controller time to initialize + delay(10); + + //Connect to I2C interface (7 bit address, ignore R/W bit in datasheet and shift it one to the right) + matrix.init(0x70); + + //Turn on oscillator + matrix.wakeUp(); + + //Instruct HT16K33 to use interrupt pin, high active + matrix.setRowIntPin(true, true); + + //Configure PIN 2 as interrupt pin - as HT16K33 is instructed to send an active high interrupt, watch for rising edge + attachInterrupt(digitalPinToInterrupt(interruptPin), handle_int, RISING); + + //Zerosize the key buffer, not necessarily needed though + memset(keybuffer, 0, sizeof(keybuffer)); +} + +/* + * Interrupt handler, will be called when the hardware interrupt condition is triggered + */ +void handle_int() +{ + has_key_data = true; +} + +void loop() +{ + // If interrupt has beenm called, fetch key data + if (has_key_data) { + + // Reset flag to false + has_key_data = false; + + // Fetch key data matrix + matrix.getKeyData(keybuffer); + + //Debug output to Arduino IDE serial connection to understand data format + for (int i = 0; i < 2; i++) { + if (keybuffer[i] != 0) { + Serial.print("Key has been pressed in row "); + Serial.print(i); + Serial.print(": "); + Serial.println(keybuffer[i], BIN); + } + } + } +} \ No newline at end of file