Browse Source

Added HT16K33 module.

Volodymyr Shylov 1 năm trước cách đây
mục cha
commit
8629e4f78c

+ 923 - 0
app/SparkFun_Alphanumeric_Display.cpp

@@ -0,0 +1,923 @@
+/******************************************************************************
+SparkFun_Alphanumeric_Display.cpp
+SparkFun Alphanumeric Display Library Source File
+Priyanka Makin @ SparkFun Electronics
+Original Creation Date: February 25, 2020
+https://github.com/sparkfun/SparkFun_Alphanumeric_Display_Arduino_Library
+
+Updated May 2, 2020 by Gaston Williams to add defineChar function
+
+Pickup a board here: https://sparkle.sparkfun.com/sparkle/storefront_products/16391
+
+This file implements all functions of the HT16K33 class. Functions here range
+from printing to one or more Alphanumeric Displays, changing the display settings, and writing/
+reading the RAM of the HT16K33.
+
+The Holtek HT16K33 seems to be susceptible to address changes intra-sketch. The ADR pins
+are muxed with the ROW and COM drivers so as semgents are turned on/off that affect
+the ADR1/ADR0 pins the address has been seen to change. The best way around this is
+to do a isConnected check before updateRAM() is sent to the driver IC.
+
+Development environment specifics:
+	IDE: Arduino 1.8.9
+	Hardware Platform: Arduino Uno
+	Alphanumeric Display Breakout Version: 1.0.0
+
+This code is beerware; if you see me (or any other SparkFun employee) at the
+local, and you've found our code helpful, please buy us a round!
+
+Distributed as-is; no warranty is given.
+******************************************************************************/
+
+#include <SparkFun_Alphanumeric_Display.h>
+
+/*--------------------------- Character Map ----------------------------------*/
+#define SFE_ALPHANUM_UNKNOWN_CHAR 95
+
+//This is the lookup table of segments for various characters
+//For AVR architecture, use PROGMEM
+#if defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_ARCH_MEGAAVR)
+#include <avr/pgmspace.h>
+static const uint16_t PROGMEM alphanumeric_segs[96]{
+#else
+static const uint16_t alphanumeric_segs[96]{
+#endif
+	// nmlkjihgfedcba
+	0b00000000000000, // ' ' (space)
+	0b00001000001000, // '!'
+	0b00001000000010, // '"'
+ 	0b01001101001110, // '#'
+	0b01001101101101, // '$'
+	0b10010000100100, // '%'
+	0b00110011011001, // '&'
+	0b00001000000000, // '''
+	0b00000000111001, // '('
+	0b00000000001111, // ')'
+	0b11111010000000, // '*'
+	0b01001101000000, // '+'
+	0b10000000000000, // ','
+	0b00000101000000, // '-'
+	0b00000000000000, // '.'
+	0b10010000000000, // '/'
+	0b00000000111111, // '0'
+	0b00010000000110, // '1'
+	0b00000101011011, // '2'
+	0b00000101001111, // '3'
+	0b00000101100110, // '4'
+	0b00000101101101, // '5'
+	0b00000101111101, // '6'
+	0b01010000000001, // '7'
+	0b00000101111111, // '8'
+	0b00000101100111, // '9'
+	0b00000000000000, // ':'
+	0b10001000000000, // ';'
+	0b00110000000000, // '<'
+	0b00000101001000, // '='
+	0b01000010000000, // '>'
+    0b01000100000011, // '?'
+	0b00001100111011, // '@'
+	0b00000101110111, // 'A'
+	0b01001100001111, // 'B'
+	0b00000000111001, // 'C'
+	0b01001000001111, // 'D'
+	0b00000101111001, // 'E'
+	0b00000101110001, // 'F'
+	0b00000100111101, // 'G'
+	0b00000101110110, // 'H'
+	0b01001000001001, // 'I'
+	0b00000000011110, // 'J'
+	0b00110001110000, // 'K'
+	0b00000000111000, // 'L'
+	0b00010010110110, // 'M'
+	0b00100010110110, // 'N'
+	0b00000000111111, // 'O'
+	0b00000101110011, // 'P'
+	0b00100000111111, // 'Q'
+	0b00100101110011, // 'R'
+	0b00000110001101, // 'S'
+	0b01001000000001, // 'T'
+	0b00000000111110, // 'U'
+	0b10010000110000, // 'V'
+	0b10100000110110, // 'W'
+	0b10110010000000, // 'X'
+	0b01010010000000, // 'Y'
+	0b10010000001001, // 'Z'
+	0b00000000111001, // '['
+	0b00100010000000, // '\'
+	0b00000000001111, // ']'
+    0b10100000000000, // '^'
+	0b00000000001000, // '_'
+	0b00000010000000, // '`'
+	0b00000101011111, // 'a'
+	0b00100001111000, // 'b'
+	0b00000101011000, // 'c'
+	0b10000100001110, // 'd'
+	0b00000001111001, // 'e'
+	0b00000001110001, // 'f'
+	0b00000110001111, // 'g'
+	0b00000101110100, // 'h'
+	0b01000000000000, // 'i'
+	0b00000000001110, // 'j'
+	0b01111000000000, // 'k'
+	0b01001000000000, // 'l'
+	0b01000101010100, // 'm'
+	0b00100001010000, // 'n'
+	0b00000101011100, // 'o'
+	0b00010001110001, // 'p'
+	0b00100101100011, // 'q'
+	0b00000001010000, // 'r'
+	0b00000110001101, // 's'
+	0b00000001111000, // 't'
+	0b00000000011100, // 'u'
+	0b10000000010000, // 'v'
+	0b10100000010100, // 'w'
+	0b10110010000000, // 'x'
+	0b00001100001110, // 'y'
+	0b10010000001001, // 'z'
+	0b10000011001001, // '{'
+	0b01001000000000, // '|'
+	0b00110100001001, // '}'
+	0b00000101010010, // '~'
+	0b11111111111111, // Unknown character (DEL or RUBOUT)
+};
+
+/*--------------------------- Device Status----------------------------------*/
+
+bool HT16K33::begin(uint8_t addressDisplayOne, uint8_t addressDisplayTwo, uint8_t addressDisplayThree, uint8_t addressDisplayFour, TwoWire &wirePort)
+{
+	_deviceAddressDisplayOne = addressDisplayOne;				// grab the address of the alphanumeric
+	_deviceAddressDisplayTwo = addressDisplayTwo;   
+	_deviceAddressDisplayThree = addressDisplayThree; 
+	_deviceAddressDisplayFour = addressDisplayFour;				
+
+	if (_deviceAddressDisplayFour != DEFAULT_NOTHING_ATTACHED)
+		numberOfDisplays = 4;
+	else if (_deviceAddressDisplayThree != DEFAULT_NOTHING_ATTACHED)
+		numberOfDisplays = 3;
+	else if (_deviceAddressDisplayTwo != DEFAULT_NOTHING_ATTACHED)
+		numberOfDisplays = 2;
+	else
+		numberOfDisplays = 1;
+
+	//TODO: malloc more displayRAM
+
+	_i2cPort = &wirePort; // Remember the user's setting
+
+	for (uint8_t i = 1; i <= numberOfDisplays; i++)
+	{
+		
+		if (isConnected(i) == false)
+		{
+			return false;
+		}
+		// if (checkDeviceID(i) == false)
+		// {
+		// 	Serial.println(i);
+		// 	Serial.println("Hello, I've failed checkDeviceID()");
+		// 	return false;
+		// }
+		delay(100);
+	}
+
+	if (initialize() == false)
+	{
+		return false;
+	}
+
+	if (clear() == false) // Clear all displays
+	{
+		return false;
+	}
+
+	displayContent[4 * 4] = '\0'; // Terminate the array because we are doing direct prints
+
+	return true;
+}
+
+// Check that the display is responding on the I2C bus
+// The Holtek IC sometimes fails to respond. This attempts multiple times before giving up.
+bool HT16K33::isConnected(uint8_t displayNumber)
+{
+	uint8_t triesBeforeGiveup = 5;
+
+	for (uint8_t x = 0; x < triesBeforeGiveup; x++)
+	{
+	
+		_i2cPort->beginTransmission(lookUpDisplayAddress(displayNumber));
+		if (_i2cPort->endTransmission() == 0)
+		{
+			return true;
+		}
+		delay(100);
+	}
+	return false;
+}
+
+// Run through initialization procedure for each display connected on the bus
+bool HT16K33::initialize()
+{
+	// Turn on system clock of all displays
+	if (enableSystemClock() == false)
+	{
+		return false;
+	}
+
+	// Set brightness of all displays to full brightness
+	if (setBrightness(15) == false)
+	{
+		return false;
+	}
+
+	// Turn blinking off for all displays
+	if (setBlinkRate(ALPHA_BLINK_RATE_NOBLINK) == false)
+	{
+		return false;
+	}
+
+	// Turn on all displays
+	if (displayOn() == false)
+	{
+		return false;
+	}
+
+	return true;
+}
+
+// Turn on the system oscillator for all displays on the I2C bus
+bool HT16K33::enableSystemClock()
+{
+	bool status = true;
+	for (uint8_t i = 1; i <= numberOfDisplays; i++)
+	{
+		if (enableSystemClockSingle(i) == false)
+			status = false;
+	}
+	return status;
+}
+
+// Turn off the system oscillator for all displays on the bus
+bool HT16K33::disableSystemClock()
+{
+	bool status = true;
+	for (uint8_t i = 1; i <= numberOfDisplays; i++)
+	{
+		if (disableSystemClockSingle(i) == false)
+			status = false;
+	}
+	return status;
+}
+
+// Turn on the system oscillator for normal operation mode
+bool HT16K33::enableSystemClockSingle(uint8_t displayNumber)
+{
+	uint8_t dataToWrite = ALPHA_CMD_SYSTEM_SETUP | 1; // Enable system clock
+
+	bool status = writeRAM(lookUpDisplayAddress(displayNumber), dataToWrite);
+	delay(1); // Allow display to start
+	return (status);
+}
+
+// Turn off the system oscillator for standby mode
+bool HT16K33::disableSystemClockSingle(uint8_t displayNumber)
+{
+	uint8_t dataToWrite = ALPHA_CMD_SYSTEM_SETUP | 0; // Standby mode
+
+	return (writeRAM(lookUpDisplayAddress(displayNumber), dataToWrite));
+}
+
+// This function connects the display number to its coressponding address
+uint8_t HT16K33::lookUpDisplayAddress(uint8_t displayNumber)
+{
+	switch (displayNumber)
+	{
+	case 1:
+		return _deviceAddressDisplayOne;
+		break;
+	case 2:
+		return _deviceAddressDisplayTwo;
+		break;
+	case 3:
+		return _deviceAddressDisplayThree;
+		break;
+	case 4:
+		return _deviceAddressDisplayFour;
+		break;
+	}
+
+	return 0; // We shouldn't get here
+}
+
+/*-------------------------- Display configuration functions ---------------------------*/
+
+// Turn off all segments of all displays connected to bus
+bool HT16K33::clear()
+{
+	// Clear the displayRAM array
+	for (uint8_t i = 0; i < 16 * numberOfDisplays; i++)
+		displayRAM[i] = 0;
+
+	digitPosition = 0;
+
+	return (updateDisplay());
+}
+
+// This function sets the brightness of all displays on the bus.
+// Duty cycle valid between 0 (1/16 brightness) and 15 (full brightness)
+bool HT16K33::setBrightness(uint8_t duty)
+{
+	bool status = true;
+	for (uint8_t i = 1; i <= numberOfDisplays; i++)
+	{
+		if (setBrightnessSingle(i, duty) == false)
+			status = false;
+	}
+	return status;
+}
+
+// Set the brightness of a single display
+// Duty cycle valid between 0 (1/16 brightness) and 15 (full brightness)
+bool HT16K33::setBrightnessSingle(uint8_t displayNumber, uint8_t duty)
+{
+	if (duty > 15) // Error check
+		duty = 15; 
+
+	uint8_t dataToWrite = ALPHA_CMD_DIMMING_SETUP | duty;
+	return (writeRAM(lookUpDisplayAddress(displayNumber), dataToWrite));
+}
+
+// Set the blink rate of all displays on the bus
+// Parameter "rate" in Hz
+// Valid options for "rate" are defined by datasheet: 2.0, 1.0, or 0.5 Hz
+// Any other input to this function will result in steady alphanumeric display
+bool HT16K33::setBlinkRate(float rate)
+{
+	bool status = true;
+	for (uint8_t i = 1; i <= numberOfDisplays; i++)
+	{
+		if (setBlinkRateSingle(i, rate) == false)
+			status = false;
+	}
+	return status;
+}
+
+// Set the blink rate of a single display on the bus
+// Parameter "rate" is in Hz
+// Valid options for "rate" are defined by datasheet: 2.0, 1.0, or 0.5 Hz
+// Any other input to this function will result in steady alphanumeric display
+bool HT16K33::setBlinkRateSingle(uint8_t displayNumber, float rate)
+{
+	if (rate == 2.0)
+	{
+		blinkRate = ALPHA_BLINK_RATE_2HZ;
+	}
+	else if (rate == 1.0)
+	{
+		blinkRate = ALPHA_BLINK_RATE_1HZ;
+	}
+	else if (rate == 0.5)
+	{
+		blinkRate = ALPHA_BLINK_RATE_0_5HZ;
+	}
+	//default to no blink
+	else
+	{
+		blinkRate = ALPHA_BLINK_RATE_NOBLINK;
+	}
+
+	uint8_t dataToWrite = ALPHA_CMD_DISPLAY_SETUP | (blinkRate << 1) | displayOnOff;
+	return (writeRAM(lookUpDisplayAddress(displayNumber), dataToWrite));
+}
+
+// Turn a single alphanumeric display on
+bool HT16K33::displayOnSingle(uint8_t displayNumber)
+{
+	return setDisplayOnOff(displayNumber, true);
+}
+
+// Turn a single alphanumeric display off
+bool HT16K33::displayOffSingle(uint8_t displayNumber)
+{
+	return setDisplayOnOff(displayNumber, false);
+}
+
+// Set or clear the display on/off bit of a given display number
+bool HT16K33::setDisplayOnOff(uint8_t displayNumber, bool turnOnDisplay)
+{
+	if (turnOnDisplay) {
+		displayOnOff = ALPHA_DISPLAY_ON;
+	}
+	else {
+		displayOnOff = ALPHA_DISPLAY_OFF;
+	}
+
+	uint8_t dataToWrite = ALPHA_CMD_DISPLAY_SETUP | (blinkRate << 1) | displayOnOff;
+	return (writeRAM(lookUpDisplayAddress(displayNumber), dataToWrite));
+}
+
+// Turn on all displays on the I2C bus
+bool HT16K33::displayOn()
+{
+	bool status = true;
+
+	displayOnOff = ALPHA_DISPLAY_ON;
+
+	for (uint8_t i = 1; i <= numberOfDisplays; i++)
+	{
+		if (displayOnSingle(i) == false)
+			status = false;
+	}
+
+	return status;
+}
+
+// Turn off all displays on the I2C bus
+bool HT16K33::displayOff()
+{
+	bool status = true;
+
+	displayOnOff = ALPHA_DISPLAY_OFF;
+
+	for (uint8_t i = 1; i <= numberOfDisplays; i++)
+	{
+		if (displayOffSingle(i) == false)
+			status = false;
+	}
+
+	return status;
+}
+
+// Turn the decimal point on for a single display
+bool HT16K33::decimalOnSingle(uint8_t displayNumber, bool updateNow)
+{
+	return setDecimalOnOff(displayNumber, true, updateNow);
+}
+
+// Turn the decimal point off for a single display
+bool HT16K33::decimalOffSingle(uint8_t displayNumber, bool updateNow)
+{
+	return setDecimalOnOff(displayNumber, false, updateNow);
+}
+
+// Set or clear the decimal on/off bit
+bool HT16K33::setDecimalOnOff(uint8_t displayNumber, bool turnOnDecimal, bool updateNow)
+{
+	uint8_t adr = 0x03;
+	uint8_t dat;
+
+	if (turnOnDecimal == true)
+	{
+		decimalOnOff = ALPHA_DECIMAL_ON;
+		dat = 0x01;
+	}
+	else
+	{
+		decimalOnOff = ALPHA_DECIMAL_OFF;
+		dat = 0x00;
+	}
+
+	displayRAM[adr + (displayNumber - 1) * 16] = displayRAM[adr + (displayNumber - 1) * 16] | dat;
+
+	if(updateNow)
+	{
+		return updateDisplay();
+	}
+	else
+	{
+		return true;
+	}
+}
+
+// Turn the decimal on for all displays on bus
+bool HT16K33::decimalOn()
+{
+	bool status = true;
+
+	decimalOnOff = ALPHA_DECIMAL_ON;
+
+	for (uint8_t i = 1; i <= numberOfDisplays; i++)
+	{
+		if (decimalOnSingle(i) == false)
+			status = false;
+	}
+
+	return status;
+}
+
+// Turn the decimal point off for all displays on bus
+bool HT16K33::decimalOff()
+{
+	bool status = true;
+
+	decimalOnOff = ALPHA_DECIMAL_OFF;
+
+	for (uint8_t i = 1; i <= numberOfDisplays; i++)
+	{
+		if (decimalOffSingle(i) == false)
+			status = false;
+	}
+	return status;
+}
+
+// Turn the colon on for a single display
+bool HT16K33::colonOnSingle(uint8_t displayNumber, bool updateNow)
+{
+	return setColonOnOff(displayNumber, true, updateNow);
+}
+
+// Turn the colon off for a single display
+bool HT16K33::colonOffSingle(uint8_t displayNumber, bool updateNow)
+{
+	return setColonOnOff(displayNumber, false, updateNow);
+}
+
+// Set or clear the colon on/off bit
+bool HT16K33::setColonOnOff(uint8_t displayNumber, bool turnOnColon, bool updateNow)
+{
+	uint8_t adr = 0x01;
+	uint8_t dat;
+
+	if (turnOnColon == true)
+	{
+		colonOnOff = ALPHA_COLON_ON;
+		dat = 0x01;
+	}
+	else
+	{
+		colonOnOff = ALPHA_COLON_OFF;
+		dat = 0x00;
+	}
+
+	displayRAM[adr + (displayNumber - 1) * 16] = displayRAM[adr + (displayNumber - 1) * 16] | dat;
+
+	if(updateNow)
+	{
+		return updateDisplay();
+	}
+	else
+	{
+		return true;
+	}
+}
+
+// Turn the colon on for all displays on the bus
+bool HT16K33::colonOn()
+{
+	bool status = true;
+
+	colonOnOff = ALPHA_COLON_ON;
+
+	for (uint8_t i = 1; i <= numberOfDisplays; i++)
+	{
+		if (colonOnSingle(i) == false)
+			status = false;
+	}
+	return status;
+}
+
+// Turn the colon off for all displays on the bus
+bool HT16K33::colonOff()
+{
+	bool status = true;
+
+	colonOnOff = ALPHA_COLON_OFF;
+
+	for (uint8_t i = 1; i <= numberOfDisplays; i++)
+	{
+		if (colonOffSingle(i) == false)
+			status = false;
+	}
+	return status;
+}
+
+/*---------------------------- Light up functions ---------------------------------*/
+
+// Given a segment and a digit, set the matching bit within the RAM of the Holtek RAM set
+void HT16K33::illuminateSegment(uint8_t segment, uint8_t digit)
+{
+	uint8_t com;
+	uint8_t row;
+
+	com = segment - 'A'; // Convert the segment letter back to a number
+
+	if (com > 6)
+		com -= 7;
+	if (segment == 'I')
+		com = 0;
+	if (segment == 'H')
+		com = 1;
+
+	row = digit % 4; // Convert digit (1 to 16) back to a relative position on a given digit on display
+	if (segment > 'G')
+		row += 4;
+
+	uint8_t offset = digit / 4 * 16;
+	uint8_t adr = com * 2 + offset;
+
+	// Determine the address
+	if (row > 7)
+		adr++;
+
+	// Determine the data bit
+	if (row > 7)
+		row -= 8;
+	uint8_t dat = 1 << row;
+
+	displayRAM[adr] = displayRAM[adr] | dat;
+}
+
+// Given a binary set of segments and a digit, store this data into the RAM array
+void HT16K33::illuminateChar(uint16_t segmentsToTurnOn, uint8_t digit)
+{
+	for (uint8_t i = 0; i < 14; i++) // There are 14 segments on this display
+	{
+		if ((segmentsToTurnOn >> i) & 0b1)
+			illuminateSegment('A' + i, digit); // Convert the segment number to a letter
+	}
+}
+
+// Print a character, for a given digit, on display
+void HT16K33::printChar(uint8_t displayChar, uint8_t digit)
+{
+	// moved alphanumeric_segs array to PROGMEM
+	uint16_t characterPosition = 65532;
+
+	// space
+	if (displayChar == ' ')
+		characterPosition = 0;
+	// Printable Symbols -- Between first character ! and last character ~
+	else if (displayChar >= '!' && displayChar <= '~')
+	{
+		characterPosition = displayChar - '!' + 1;
+	}
+
+	uint8_t dispNum = digitPosition / 4;
+
+	// Take care of special characters by turning correct segment on
+	if (characterPosition == 14) // '.'
+		decimalOnSingle(dispNum+1, false);
+	if (characterPosition == 26) // ':'
+		colonOnSingle(dispNum+1, false);
+	if (characterPosition == 65532) // unknown character
+		characterPosition = SFE_ALPHANUM_UNKNOWN_CHAR;
+
+	uint16_t segmentsToTurnOn = getSegmentsToTurnOn(characterPosition);
+
+	illuminateChar(segmentsToTurnOn, digit);
+}
+
+// Update the list to define a new segments display for a particular character
+bool HT16K33::defineChar(uint8_t displayChar, uint16_t segmentsToTurnOn)
+{
+	bool result = false;
+
+	// Check to see if character is within range of displayable ASCII characters
+	if (displayChar >= '!' && displayChar <= '~')
+	{
+	  // Get the index of character in table and update its 14-bit segment value
+	  uint16_t characterPosition = displayChar - '!' + 1;
+
+      // Create a new character definition
+      struct CharDef * pNewCharDef = (CharDef *)calloc(1, sizeof(CharDef));
+
+	  // Set the position to the table index
+      pNewCharDef -> position = characterPosition;
+      // Mask the segment value to 14 bits only
+      pNewCharDef -> segments = segmentsToTurnOn & 0x3FFF;
+      // New definition always goes at the end of the list
+      pNewCharDef -> next = NULL;
+
+      // If list is empty set it to the new item
+      if (pCharDefList == NULL)
+      {
+	    pCharDefList = pNewCharDef;
+      }
+      else
+      {
+      // Otherwise go to the end of the list and add it there
+      struct CharDef * pTail = pCharDefList;
+
+      while(pTail->next != NULL)
+      {
+        pTail = pTail->next;
+      }
+
+      pTail->next = pNewCharDef;
+    }
+	// We added the definition so we're all good
+	result = true;
+  }
+  return result;
+}
+
+// Get the character map from the definition list or default table
+uint16_t HT16K33::getSegmentsToTurnOn(uint8_t charPos)
+{
+  uint16_t segments = 0;
+  // pointer to a defined character in list
+  struct CharDef * pDefChar = pCharDefList;
+
+  // Search the chacters list for a match
+  while(pDefChar && (pDefChar->position != charPos))
+  {
+    pDefChar = pDefChar -> next;
+  }
+
+  // If we found a match return that value
+  if (pDefChar != NULL)
+  {
+    segments = pDefChar -> segments;
+  }
+  // Otherwise get the value from the table
+  else
+  {
+    segments = pgm_read_word_near(alphanumeric_segs + charPos);
+  }
+  return segments;
+}
+
+/*
+ * Write a byte to the display.
+ * Required for overloading the Print function.
+ */
+size_t HT16K33::write(uint8_t b)
+{
+	// If user wants to print '.' or ':', don't increment the digitPosition!
+	if (b == '.' || b == ':')
+		printChar(b, 0);
+	else
+	{
+		printChar(b, digitPosition++);
+		digitPosition %= (numberOfDisplays * 4); // Convert displays to number of digits
+	}
+
+	return (updateDisplay()); // Send RAM buffer over I2C bus
+}
+
+/*
+ * Write a character buffer to the display.
+ * Required for overloading the Print function.
+ */
+size_t HT16K33::write(const uint8_t *buffer, size_t size)
+{
+	size_t n = size;
+	uint8_t buff;
+
+	// Clear the displayRAM array
+	for (uint8_t i = 0; i < 16 * numberOfDisplays; i++)
+		displayRAM[i] = 0;
+
+	digitPosition = 0;
+
+	while (size--)
+	{
+		buff = *buffer++;
+		// For special characters like '.' or ':', do not increment the digitPosition
+		if (buff == '.')
+			printChar('.', 0);
+		else if (buff == ':')
+			printChar(':', 0);
+		else
+		{
+			printChar(buff, digitPosition);
+			displayContent[digitPosition] = buff; // Record to internal array
+
+			digitPosition++;
+			digitPosition %= (numberOfDisplays * 4);
+		}
+	}
+
+	updateDisplay(); // Send RAM buffer over I2C bus
+
+	return n;
+}
+
+/*
+ * Write a string to the display.
+ * Required for overloading the Print function.
+ */
+size_t HT16K33::write(const char *str)
+{
+	if (str == NULL)
+		return 0;
+	return write((const uint8_t *)str, strlen(str));
+}
+
+// Push the contents of displayRAM out to the various displays in 16 byte chunks
+bool HT16K33::updateDisplay()
+{
+
+	bool status = true;
+
+	for (uint8_t i = 1; i <= numberOfDisplays; i++)
+	{
+		if (writeRAM(lookUpDisplayAddress(i), 0, (uint8_t *)(displayRAM + ((i-1) * 16)), 16) == false)
+		{
+			//Serial.print("updateDisplay fail at display 0x");
+			//Serial.println(lookUpDisplayAddress(i), HEX);
+			status = false;
+		}
+	}
+
+	return status;
+}
+
+// Shift the display content to the right one digit
+bool HT16K33::shiftRight(uint8_t shiftAmt)
+{
+	for (uint8_t x = (4 * numberOfDisplays) - shiftAmt; x >= shiftAmt; x--)
+	{
+		displayContent[x] = displayContent[x - shiftAmt];
+	}
+
+	// Clear the leading characters
+	for (uint8_t x = 0; x < shiftAmt; x++)
+	{
+		if (x + shiftAmt > (4 * numberOfDisplays))
+			break; // Error check
+
+		displayContent[0 + x] = ' ';
+	}
+
+	return (print(displayContent));
+}
+
+// Shift the display content to the left one digit
+bool HT16K33::shiftLeft(uint8_t shiftAmt)
+{
+	for (int x = 0; x < 4 * numberOfDisplays; x++)
+	{
+		if (x + shiftAmt > (4 * numberOfDisplays))
+			break; // Error check
+		displayContent[x] = displayContent[x + shiftAmt];
+	}
+
+	// Clear the trailing characters
+	for (int x = 0; x < shiftAmt; x++)
+	{
+		if (4 * numberOfDisplays - 1 - x < 0)
+			break; //Error check
+
+		displayContent[4 * numberOfDisplays - 1 - x] = ' ';
+	}
+
+	return (print(displayContent));
+}
+
+/*----------------------- Internal I2C Abstraction -----------------------------*/
+
+bool HT16K33::readRAM(uint8_t address, uint8_t reg, uint8_t *buff, uint8_t buffSize)
+{
+	uint8_t displayNum = 1;
+	if (address == _deviceAddressDisplayTwo)
+		displayNum = 2;
+	else if (address == _deviceAddressDisplayThree)
+		displayNum = 3;
+	else if (address == _deviceAddressDisplayFour)
+		displayNum = 4;
+	isConnected(displayNum); // Wait until display is ready
+
+	_i2cPort->beginTransmission(address);
+	_i2cPort->write(reg);
+	_i2cPort->endTransmission(false);
+
+	if (_i2cPort->requestFrom(address, buffSize) > 0)
+	{
+		for (uint8_t i = 0; i < buffSize; i++)
+			buff[i] = _i2cPort->read();
+		return true;
+	}
+
+	return false;
+}
+
+// Write the contents of the RAM array out to the Holtek IC
+bool HT16K33::writeRAM(uint8_t address, uint8_t reg, uint8_t *buff, uint8_t buffSize)
+{
+	uint8_t displayNum = 1;
+	if (address == _deviceAddressDisplayTwo)
+		displayNum = 2;
+	else if (address == _deviceAddressDisplayThree)
+		displayNum = 3;
+	else if (address == _deviceAddressDisplayFour)
+		displayNum = 4;
+	isConnected(displayNum); //Wait until display is ready
+
+	_i2cPort->beginTransmission(address);
+	_i2cPort->write(reg);
+
+	for (uint8_t i = 0; i < buffSize; i++)
+		_i2cPort->write(buff[i]);
+
+	if (_i2cPort->endTransmission() == 0)
+		return true;
+
+	return false;
+}
+
+// Write a single byte to the display. This is often a command byte.
+// The address of the data to write is contained in the first four bits of dataToWrite
+bool HT16K33::writeRAM(uint8_t address, uint8_t dataToWrite)
+{
+	uint8_t temp = 0;
+	return (writeRAM(address, dataToWrite, (uint8_t *)&temp, 0));
+}

+ 17 - 2
app/application.cpp

@@ -8,8 +8,11 @@
 
 #include "webserver.h"
 #include "tm1650.h"
+#include "SparkFun_Alphanumeric_Display.h"
 #include "AHTxx.h"
 
+HT16K33 display;
+
 /** DHT22 */
 #define DHT22_PIN 2
 DHTesp dht;
@@ -40,6 +43,7 @@ void dotOff(void);
 void showTemperature(void);
 void showHumidity(void);
 void showError(void);
+void shiftStringLeft(void);
 
 // NTP Client
 void onNtpReceive(NtpClient& client, time_t timestamp);
@@ -72,8 +76,15 @@ void init(void) {
 	// BIG digits
 	TM1650_Init();
 
-	// Low LED output
-	
+	// Low 14-Seg LED output
+  if (display.begin(DEFAULT_ADDRESS) == false) {
+    Serial.println("HT16K33 device did not acknowledge!");
+  } else {
+	  Serial.println("HT16K33 displays acknowledged.");
+	}
+  display.print("Heaven");
+	tmpTimer.initializeMs(500, shiftStringLeft).start();
+
 	// refresh big led
 	displayTimer.initializeMs(1000, showWatch).start();
 
@@ -355,3 +366,7 @@ void onTimer_readDHT22()
 
 	Serial.println(')');
 }
+
+void shiftStringLeft(void) {
+	display.shiftLeft();
+}

+ 3 - 2
component.mk

@@ -1,7 +1,8 @@
 ARDUINO_LIBRARIES := ArduinoJson6 DHTesp Timezone SolarCalculator
-HWCONFIG := spiffs3m
+HWCONFIG := spiffs
+#HWCONFIG := spiffs3m
 #HWCONFIG_OPTS := spiffs
 SPIFF_FILES := web
-COM_PORT := COM5
+COM_PORT := COM3
 SMING_RELEASE := 1
 SMING_ARCH := Esp8266

+ 182 - 0
include/SparkFun_Alphanumeric_Display.h

@@ -0,0 +1,182 @@
+/******************************************************************************
+SparkFun_Alphanumeric_Display.h
+SparkFun Alphanumeric Display Library Header File
+Priyanka Makin @ SparkFun Electronics
+Original Creation Date: July 25, 2019
+https://github.com/sparkfun/SparkFun_Alphanumeric_Display_Arduino_Library
+
+Updated April 30, 2020 by Gaston Williams to add defineChar function
+
+Pickup a board here: https://sparkle.sparkfun.com/sparkle/storefront_products/16391
+
+This file prototypes the HT16K33 class, implemented in SparkFun_Alphanumeric_Display.cpp.
+
+Development environment specifics:
+	IDE: Arduino 1.8.9
+	Hardware Platform: Arduino Uno
+	Alphanumeric Display Breakout Version: 1.0.0
+
+This code is beerware; if you see me (or any other SparkFun employee) at the
+local, and you've found our code helpful, please buy us a round!
+
+Distributed as-is; no warranty is given.
+******************************************************************************/
+#ifndef __SparkFun_Alphanumeric_Display_H__
+#define __SparkFun_Alphanumeric_Display_H__
+
+#include <SmingCore.h>
+#include <Wire.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#define DEFAULT_ADDRESS 0x70 // Default I2C address when A0, A1 are floating
+#define DEFAULT_NOTHING_ATTACHED 0xFF
+
+// Define constants for segment bits
+#define SEG_A 0x0001
+#define SEG_B 0x0002
+#define SEG_C 0x0004
+#define SEG_D 0x0008
+#define SEG_E 0x0010
+#define SEG_F 0x0020
+#define SEG_G 0x0040
+#define SEG_H 0x0080
+#define SEG_I 0x0100
+#define SEG_J 0x0200
+#define SEG_K 0x0400
+#define SEG_L 0x0800
+#define SEG_M 0x1000
+#define SEG_N 0x2000
+
+
+typedef enum
+{
+    ALPHA_BLINK_RATE_NOBLINK = 0b00,
+    ALPHA_BLINK_RATE_2HZ = 0b01,
+    ALPHA_BLINK_RATE_1HZ = 0b10,
+    ALPHA_BLINK_RATE_0_5HZ = 0b11,
+} alpha_blink_rate_t;
+
+typedef enum
+{
+    ALPHA_DISPLAY_ON = 0b1,
+    ALPHA_DISPLAY_OFF = 0b0,
+} alpha_display_t;
+
+typedef enum
+{
+    ALPHA_DECIMAL_ON = 0b1,
+    ALPHA_DECIMAL_OFF = 0b0,
+} alpha_decimal_t;
+
+typedef enum
+{
+    ALPHA_COLON_ON = 0b1,
+    ALPHA_COLON_OFF = 0b0,
+} alpha_colon_t;
+
+typedef enum
+{
+    ALPHA_CMD_SYSTEM_SETUP = 0b00100000,
+    ALPHA_CMD_DISPLAY_SETUP = 0b10000000,
+    ALPHA_CMD_DIMMING_SETUP = 0b11100000,
+} alpha_command_t;
+
+// Structure for defining new character displays
+struct CharDef {
+  uint8_t  position;
+  int16_t segments;
+  struct CharDef * next;
+};
+
+// class HT16K33
+class HT16K33 : public Print
+{
+private:
+    TwoWire *_i2cPort;          // The generic connection to user's chosen I2C hardware
+    uint8_t _deviceAddressDisplayOne; // Address of primary alphanumeric display
+    uint8_t _deviceAddressDisplayTwo;
+    uint8_t _deviceAddressDisplayThree;
+    uint8_t _deviceAddressDisplayFour;
+    uint8_t digitPosition = 0;
+    uint8_t numberOfDisplays = 1;
+    bool displayOnOff = 0; // Tracks display on/off bit of display setup register
+    bool decimalOnOff = 0;
+    bool colonOnOff = 0;
+    uint8_t blinkRate = ALPHA_BLINK_RATE_NOBLINK; // Tracks blink bits in display setup register
+
+    // Enough RAM for up to 4 displays on same I2C bus
+    uint8_t displayRAM[16 * 4];
+    char displayContent[4 * 4 + 1] = "";
+
+    // Linked List of character definitions
+    struct CharDef * pCharDefList = NULL;
+
+public:
+    // Device status
+    bool begin(uint8_t addressDisplayOne = DEFAULT_ADDRESS,
+               uint8_t addressDisplayTwo = DEFAULT_NOTHING_ATTACHED,
+               uint8_t addressDisplayThree = DEFAULT_NOTHING_ATTACHED,
+               uint8_t addressDisplayFour = DEFAULT_NOTHING_ATTACHED,
+               TwoWire &wirePort = Wire); // Sets the address of the device and opens the Wire port for communication
+    bool isConnected(uint8_t displayNumber);
+    bool initialize();
+    uint8_t lookUpDisplayAddress(uint8_t displayNumber);
+
+    //Display configuration functions
+    bool clear();
+    bool setBrightness(uint8_t duty);
+    bool setBrightnessSingle(uint8_t displayNumber, uint8_t duty);
+    bool setBlinkRate(float rate);
+    bool setBlinkRateSingle(uint8_t displayNumber, float rate);
+    bool displayOn();
+    bool displayOff();
+    bool displayOnSingle(uint8_t displayNumber);
+    bool displayOffSingle(uint8_t displayNumber);
+    bool setDisplayOnOff(uint8_t displayNumber, bool turnOnDisplay);
+
+    bool enableSystemClock();
+    bool disableSystemClock();
+    bool enableSystemClockSingle(uint8_t displayNumber);
+    bool disableSystemClockSingle(uint8_t displayNumber);
+
+    // Light up functions
+    void illuminateSegment(uint8_t segment, uint8_t digit);
+    void illuminateChar(uint16_t disp, uint8_t digit);
+    void printChar(uint8_t displayChar, uint8_t digit);
+    bool updateDisplay();
+
+    // Define Character Segment Map
+    bool defineChar(uint8_t displayChar, uint16_t segmentsToTurnOn);
+    uint16_t getSegmentsToTurnOn (uint8_t charPos);
+
+    // Decimal functions
+    bool decimalOn();
+    bool decimalOff();
+    bool decimalOnSingle(uint8_t displayNumber, bool updateNow = true);
+    bool decimalOffSingle(uint8_t displayNumber, bool updateNow = true);
+    bool setDecimalOnOff(uint8_t displayNumber, bool turnOnDecimal, bool updateNow = true);
+    
+    // Colon functions
+    bool colonOn();
+    bool colonOff();
+    bool colonOnSingle(uint8_t displayNumber, bool updateNow = true);
+    bool colonOffSingle(uint8_t displayNumber, bool updateNow = true);
+    bool setColonOnOff(uint8_t displayNumber, bool turnOnColon, bool updateNow = true);
+
+    // Shifting
+    bool shiftRight(uint8_t shiftAmt = 1);
+    bool shiftLeft(uint8_t shiftAmt = 1);
+
+    // For overloading the print function
+    virtual size_t write(uint8_t);
+    virtual size_t write(const uint8_t *buffer, size_t size);
+    virtual size_t write(const char *str);
+
+    // I2C abstraction
+    bool readRAM(uint8_t address, uint8_t reg, uint8_t *buff, uint8_t buffSize);
+    bool writeRAM(uint8_t address, uint8_t reg, uint8_t *buff, uint8_t buffSize);
+    bool writeRAM(uint8_t reg, uint8_t data);
+};
+
+#endif