// 4-Bit LED Digital Module with 2x 74HC595D-chip, there are 5 pins from top to bottom:
// VCC : pin 5V;
// SCLK : pin A0;
// SRLK : pin A1;
// DIO : pin A3
// GND : pin GND
#define PIN_SCLK A0 // serial clock, set LOW & HIGH after set PIN_DIO
#define PIN_RCLK A1 // register clock, set LOW & HIGH after write digit
#define PIN_DIO A2 // set PIN_DIO set index bit from 0,1,2,3,4,5,6,7 = A,B,C,D,E,F,G,DP
const byte TOTAL_DIGITS = 4; // default is 4, you may need to change this value depends on amounts of digits on your module, i'm not sure will it work if changed, good luck ;))
const byte BYTES_0_TO_9[] = { // list of bytes to display from top to bottom, 1 byte = 8 bits, bit starts from right to left, from left to right are DotPoint,G,F,E,D,C,B,A
B00111111, // 0
B00000110, // 1
B01011011, // 2
B01001111, // 3
B01100110, // 4
B01101101, // 5
B01111101, // 6
B00000111, // 7
B01111111, // 8
B01101111, // 9
};
const unsigned int POW_10[] = { // cache value for saving performance, https://www.arduino.cc/reference/en/language/functions/math/pow/
1, // pow(10, 0);
10, // pow(10, 1);
100, // pow(10, 2);
1000, // pow(10, 3);
10000, // pow(10, 4);
100000, // pow(10, 5);
1000000, // pow(10, 6);
10000000, // pow(10, 7);
};
const byte BYTE_NEGATIVE = B01000000;
const byte BYTE_CELSIUS = B00111001;
const byte BYTE_FAHRENHEIT = B01110001;
const byte BYTE_DOT_POINT = B10000000;
const byte BYTE_VOLTAGE = B00111110;
const byte BYTE_AMPERE = B01110111;
const byte MIN_PERCENT = 0;
const byte MAX_PERCENT = 100;
void setup() {
pinMode(PIN_SCLK, OUTPUT);
pinMode(PIN_RCLK, OUTPUT);
pinMode(PIN_DIO, OUTPUT);
digitalWrite(PIN_SCLK, HIGH);
digitalWrite(PIN_RCLK, HIGH);
digitalWrite(PIN_DIO, HIGH);
}
void write(unsigned short duration, byte dataPGFEDCBA[]) {
do {
for(byte indexDigit = 0; indexDigit < TOTAL_DIGITS; indexDigit++) { // write each digits from most right to most left
digitalWrite(PIN_RCLK, LOW);
shiftOut(PIN_DIO, PIN_SCLK, MSBFIRST, ~dataPGFEDCBA[indexDigit]); // shift out byte display 8 bit display PGFEDCBA, because of we are using 4-digit common anode, need inverse bit by using "~"
shiftOut(PIN_DIO, PIN_SCLK, MSBFIRST, 1 << indexDigit); // shift out byte display 8 bit display digits
// there is another way to set 16 bits, instead of shiftOut
// is make 3 line of code for 16 times, just change isOn variable for each bit of bytePGFEDCBA & byteDigit from most left to most right ;))
// digitalWrite(PIN_DIO, isOn); digitalWrite(PIN_DIO, isOn); digitalWrite(PIN_SCLK, HIGH);
digitalWrite(PIN_RCLK, HIGH);
}
} while(duration-- > 0); // avoid case duration = 0--
}
void writeInt(unsigned short duration, int number) { // 4 digit display from -999 to 9999, short is enough for 4 digit, but i wanted to support up to 8 digits, then i use int
byte dataPGFEDCBA[TOTAL_DIGITS];
byte totalDigit;
if(number >= 0) { // zero and positive numbers
totalDigit = TOTAL_DIGITS; // show full digits
} else { // negative numbers
totalDigit = TOTAL_DIGITS - 1; // can not show full digits because of most left digit display negative symbol
dataPGFEDCBA[totalDigit] = BYTE_NEGATIVE; // show negative symbol "-";
number *= -1; // convert to positive number
}
for(byte indexDigit = 0; indexDigit < totalDigit; indexDigit++) {
dataPGFEDCBA[indexDigit] = BYTES_0_TO_9[(unsigned short)(number / POW_10[indexDigit]) % 10];
}
write(duration, dataPGFEDCBA);
}
void writeFloat(unsigned short duration, float number) { // 4 digit display from -99.9 to 999.9
byte dataPGFEDCBA[TOTAL_DIGITS];
byte totalDigit;
if(number >= 0) { // zero and positive numbers
totalDigit = TOTAL_DIGITS; // show full digits
} else { // negative numbers
totalDigit = TOTAL_DIGITS - 1; // can not show full digits because of most left digit display negative symbol
dataPGFEDCBA[totalDigit] = BYTE_NEGATIVE; // show negative symbol "-";
number *= -1; // convert to positive number
}
dataPGFEDCBA[0] = BYTES_0_TO_9[(unsigned short)(number * 10) % 10]; // number after dot
dataPGFEDCBA[1] = BYTES_0_TO_9[(unsigned short)(number) % 10] | BYTE_DOT_POINT; // number before dot
for(byte indexDigit = 2; indexDigit < totalDigit; indexDigit++) {
dataPGFEDCBA[indexDigit] = BYTES_0_TO_9[(unsigned short)(number / POW_10[indexDigit - 1]) % 10]; // remain numbers
}
write(duration, dataPGFEDCBA);
}
void writeIntUnit(unsigned short duration, int number, byte byteUnit) { // 4 digit display from -99 to 999, short is enough for 4 digit, but i wanted to support up to 8 digits, then i use int
byte dataPGFEDCBA[TOTAL_DIGITS];
dataPGFEDCBA[0] = byteUnit; // write unit
byte totalDigit;
if(number >= 0) { // zero and positive numbers
totalDigit = TOTAL_DIGITS; // show full digits
} else { // negative numbers
totalDigit = TOTAL_DIGITS - 1; // can not show full digits because of most left digit display negative symbol
dataPGFEDCBA[totalDigit] = BYTE_NEGATIVE; // show negative symbol "-";
number *= -1; // convert to positive number
}
for(byte indexDigit = 1; indexDigit < totalDigit; indexDigit++) {
dataPGFEDCBA[indexDigit] = BYTES_0_TO_9[(unsigned short)(number / POW_10[indexDigit - 1]) % 10];
}
write(duration, dataPGFEDCBA);
}
void writeFloatUnit(unsigned short duration, float number, byte byteUnit) { // 4 digit display from -9.9 to 99.9
byte dataPGFEDCBA[TOTAL_DIGITS];
if(number >= 0) { // zero and positive numbers
dataPGFEDCBA[3] = BYTES_0_TO_9[(unsigned short)(number / 10) % 10];
} else { // negative numbers
dataPGFEDCBA[3] = BYTE_NEGATIVE; // show negative symbol "-";
number *= -1; // convert to positive number
}
dataPGFEDCBA[0] = byteUnit; // write symbol
dataPGFEDCBA[1] = BYTES_0_TO_9[(unsigned short)(number * 10) % 10]; // number after dot
dataPGFEDCBA[2] = BYTES_0_TO_9[(unsigned short)(number) % 10] | BYTE_DOT_POINT; // number before dot
write(duration, dataPGFEDCBA);
}
void writeTime(unsigned short duration, byte hours, byte minutes) {
byte dataPGFEDCBA[TOTAL_DIGITS];
dataPGFEDCBA[0] = BYTES_0_TO_9[minutes % 10];
dataPGFEDCBA[1] = BYTES_0_TO_9[minutes / 10 % 10];
dataPGFEDCBA[2] = BYTES_0_TO_9[hours % 10] | BYTE_DOT_POINT;
dataPGFEDCBA[3] = BYTES_0_TO_9[hours / 10 % 10];
write(duration, dataPGFEDCBA);
}
void writeText(unsigned short duration, byte data[]) { // length of data == TOTAL_DIGITS
byte dataPGFEDCBA[TOTAL_DIGITS];
for(byte indexDigit = 0; indexDigit < TOTAL_DIGITS; indexDigit++) {
dataPGFEDCBA[indexDigit] = data[TOTAL_DIGITS - indexDigit - 1];
}
write(duration, dataPGFEDCBA);
}
void writePercent(unsigned short duration, byte data[], byte animationLength, byte percent) { // support percent from 0 to 100
byte dataPGFEDCBA[TOTAL_DIGITS];
for(byte indexDigit = 0; indexDigit < TOTAL_DIGITS; indexDigit++) { // check all digits from most right to most left
dataPGFEDCBA[indexDigit] = 0; // initialize value = 0, change later
for(byte indexAnimation = 0; indexAnimation < animationLength; indexAnimation++) {
byte percentCurrent = MAX_PERCENT * ((TOTAL_DIGITS - indexDigit) * animationLength - indexAnimation) / (TOTAL_DIGITS * animationLength);
if(percentCurrent <= percent) { // is percent can be display
dataPGFEDCBA[indexDigit] = data[indexAnimation];
break;
}
}
}
write(duration, dataPGFEDCBA);
}
void writeTextRunLeft(unsigned short delayMove, byte data[], unsigned short length) {
for(unsigned short offset = 0; offset < length; offset++) {
// slice
byte dataPGFEDCBA[TOTAL_DIGITS];
for(byte indexDigit = 0; indexDigit < TOTAL_DIGITS && indexDigit < length; indexDigit++) {
dataPGFEDCBA[indexDigit] = data[(offset + indexDigit) % length];
}
writeText(delayMove, dataPGFEDCBA);
}
}
void testInt() {
for(int value = -999; value <= 9999; value++){
writeInt(0, value);
}
}
void testFloat() {
for(float value = -99.9; value <= 999.9; value+=0.5){
writeFloat(10, value);
}
}
void testIntUnit() {
for(int value = -99; value <= 999; value++){
writeIntUnit(10, value, BYTE_CELSIUS);
}
for(int value = -99; value <= 999; value++){
writeIntUnit(10, value, BYTE_FAHRENHEIT);
}
for(int value = -99; value <= 999; value++){
writeIntUnit(10, value, BYTE_VOLTAGE);
}
for(int value = -99; value <= 999; value++){
writeIntUnit(10, value, BYTE_AMPERE);
}
}
void testFloatUnit() {
for(float value = -9.9; value <= 99.9; value += 0.5){
writeFloatUnit(20, value, BYTE_CELSIUS);
}
for(float value = -9.9; value <= 99.9; value += 0.5){
writeFloatUnit(20, value, BYTE_FAHRENHEIT);
}
for(float value = -9.9; value <= 99.9; value += 0.5){
writeFloatUnit(20, value, BYTE_VOLTAGE);
}
for(float value = -9.9; value <= 99.9; value += 0.5){
writeFloatUnit(20, value, BYTE_AMPERE);
}
}
void testTime() {
for(byte hour = 0; hour < 24; hour++) {
for(byte minute = 0; minute < 60; minute++){
writeTime(10, hour, minute);
}
}
}
void testProgress() {
byte durationPercent = 20;
byte percent = 0;
byte dataPGFEDCBA1[] = {B00110110, B00110000};
do { writePercent(durationPercent, dataPGFEDCBA1, sizeof(dataPGFEDCBA1), percent); } while(++percent <= MAX_PERCENT); // fade in
do { writePercent(durationPercent, dataPGFEDCBA1, sizeof(dataPGFEDCBA1), percent); } while(percent-- > 0); // fade out
percent = 0;
byte dataPGFEDCBA2[] = {B00110110, B00110010, B00110000, B00100000};
do { writePercent(durationPercent, dataPGFEDCBA2, sizeof(dataPGFEDCBA2), percent); } while(++percent <= MAX_PERCENT); // fade in
do { writePercent(durationPercent, dataPGFEDCBA2, sizeof(dataPGFEDCBA2), percent); } while(percent-- > 0); // fade out
percent = 0;
byte dataPGFEDCBA3[] = {B01001001,B01000001,B00000001};
do { writePercent(durationPercent, dataPGFEDCBA3, sizeof(dataPGFEDCBA3), percent); } while(++percent <= MAX_PERCENT); // fade in
do { writePercent(durationPercent, dataPGFEDCBA3, sizeof(dataPGFEDCBA3), percent); } while(percent-- > 0); // fade out
}
void testText() {
byte dataPGFEDCBA[] = {
B01110011, // P
B00110000, // l, I (left)
B11011100, // a
B01101110, // Y, y
};
writeText(1000, dataPGFEDCBA);
}
void testRunningText() {
byte dataPGFEDCBA[] = {
B01110110, // H
B01111001, // E
B00110110, // ll
B01011100, // o
B00000000, // (space)
B00111110, // U,V
B00111110, // U,V
B01011100, // o
B01010000, // r
B00110000, // l, I (left)
B01011110, // d
B00000000, // (space)
};
writeTextRunLeft(1000, dataPGFEDCBA, sizeof(dataPGFEDCBA));
}
void loop()
{
testInt();
testFloat();
testIntUnit();
testFloatUnit();
testTime();
testProgress();
testText();
testRunningText();
}