// Arduino Mega 2560 + HV5812P VFD driver // // Tube wiring: // - HVOut1..HVOut7 -> digit segments A..G // - HVOut8 -> decimal point segment on the indicator grid // - HVOut9 -> alarm bell segment on the indicator grid // - HVOut10..HVOut13 -> digits 1..4 // - HVOut14 -> indicator grid between digits 2 and 3 // // Send an integer over the USB serial port and it will be shown on the VFD. // Examples: // 42 // -17 // 1234. // enables the decimal point // 1234! // enables the alarm bell // 1234.! // enables both #include namespace { constexpr uint8_t kHvDataPin = 51; // MOSI on Mega 2560 constexpr uint8_t kHvClockPin = 52; // SCK on Mega 2560 constexpr uint8_t kHvLatchPin = 53; // User-configurable latch/strobe pin constexpr int8_t kHvBlankPin = 49; // Set to -1 if BL/OE is not connected constexpr bool kBlankActiveHigh = true; constexpr unsigned long kSerialBaud = 115200; constexpr unsigned long kDigitHoldMicros = 2000; constexpr uint8_t kDigitCount = 4; constexpr uint8_t kSegmentCount = 7; constexpr uint8_t kDriverBits = 20; constexpr uint8_t kSegmentStartBit = 0; // HVOut1 -> bit 0 constexpr uint8_t kPointSegmentBit = 7; // HVOut8 -> bit 7 constexpr uint8_t kBellSegmentBit = 8; // HVOut9 -> bit 8 constexpr uint8_t kGridStartBit = 9; // HVOut10 -> bit 9 constexpr uint8_t kIndicatorGridBit = 13; // HVOut14 -> bit 13 char g_displayBuffer[kDigitCount] = {' ', ' ', ' ', ' '}; char g_inputBuffer[16]; uint8_t g_inputLength = 0; bool g_pointEnabled = false; bool g_bellEnabled = false; uint8_t g_rawOutput = 0; // Seven-segment encoding order is A, B, C, D, E, F, G. uint8_t encodeCharacter(char c) { switch (c) { case '0': return 0b0111111; case '1': return 0b0000110; case '2': return 0b1011011; case '3': return 0b1001111; case '4': return 0b1100110; case '5': return 0b1101101; case '6': return 0b1111101; case '7': return 0b0000111; case '8': return 0b1111111; case '9': return 0b1101111; case 'A': case 'a': return 0b1110111; case 'B': case 'b': return 0b1111100; case 'C': case 'c': return 0b0111001; case 'D': case 'd': return 0b1011110; case 'E': case 'e': return 0b1111001; case 'F': case 'f': return 0b1110001; case '-': return 0b1000000; default: return 0; } } void shiftDriverWord(uint32_t word) { digitalWrite(kHvLatchPin, HIGH); digitalWrite(kHvClockPin, HIGH); for (int8_t bit = kDriverBits - 1; bit >= 0; --bit) { digitalWrite(kHvDataPin, (word >> bit) & 0x1U ? HIGH : LOW); digitalWrite(kHvClockPin, LOW); digitalWrite(kHvClockPin, HIGH); } digitalWrite(kHvLatchPin, LOW); digitalWrite(kHvLatchPin, HIGH); } void setDisplayBlanked(bool blanked) { if (kHvBlankPin < 0) { return; } const bool level = kBlankActiveHigh ? blanked : !blanked; digitalWrite(kHvBlankPin, level ? HIGH : LOW); } void blankDisplay() { shiftDriverWord(0); } uint32_t maskForHvOutput(uint8_t hvOutput) { if (hvOutput == 0 || hvOutput > kDriverBits) { return 0; } return 1UL << (hvOutput - 1); } void renderDigit(uint8_t digitIndex) { uint32_t word = 0; const uint8_t segments = encodeCharacter(g_displayBuffer[digitIndex]); for (uint8_t segment = 0; segment < kSegmentCount; ++segment) { if ((segments >> segment) & 0x1U) { word |= (1UL << (kSegmentStartBit + segment)); } } word |= (1UL << (kGridStartBit + digitIndex)); shiftDriverWord(word); } void renderIndicator() { uint32_t word = 1UL << kIndicatorGridBit; if (g_pointEnabled) { word |= 1UL << kPointSegmentBit; } if (g_bellEnabled) { word |= 1UL << kBellSegmentBit; } shiftDriverWord(word); } void writeTextToDisplay(const char* text) { for (uint8_t i = 0; i < kDigitCount; ++i) { g_displayBuffer[i] = ' '; } size_t len = strlen(text); if (len > kDigitCount) { text += len - kDigitCount; len = kDigitCount; } const uint8_t start = kDigitCount - len; for (uint8_t i = 0; i < len; ++i) { g_displayBuffer[start + i] = text[i]; } } void setDisplayFromNumber(long value) { char buffer[16]; ltoa(value, buffer, 10); writeTextToDisplay(buffer); } bool parseDisplayCommand(const char* input, char* displayText, size_t displayTextSize, bool& pointEnabled, bool& bellEnabled) { size_t inputIndex = 0; size_t displayIndex = 0; if (input[inputIndex] == '-') { if (displayIndex + 1 >= displayTextSize) { return false; } displayText[displayIndex++] = input[inputIndex++]; } const size_t digitStart = inputIndex; while (isxdigit(static_cast(input[inputIndex]))) { if (displayIndex + 1 >= displayTextSize) { return false; } displayText[displayIndex] = toupper(static_cast(input[inputIndex])); ++displayIndex; ++inputIndex; } if (inputIndex == digitStart) { return false; } pointEnabled = false; bellEnabled = false; while (input[inputIndex] != '\0') { if (input[inputIndex] == '.') { pointEnabled = true; } else if (input[inputIndex] == '!') { bellEnabled = true; } else { return false; } ++inputIndex; } displayText[displayIndex] = '\0'; return true; } bool parseRawOutputCommand(const char* input, uint8_t& hvOutput) { if (strncmp(input, "RAW ", 4) != 0) { return false; } char* endPtr = nullptr; const long parsed = strtol(input + 4, &endPtr, 10); if (*endPtr != '\0' || parsed < 0 || parsed > kDriverBits) { return false; } hvOutput = static_cast(parsed); return true; } void commitSerialBuffer() { if (g_inputLength == 0) { return; } g_inputBuffer[g_inputLength] = '\0'; uint8_t rawOutput = 0; if (parseRawOutputCommand(g_inputBuffer, rawOutput)) { g_rawOutput = rawOutput; if (g_rawOutput == 0) { Serial.println(F("RAW mode OFF")); } else { Serial.print(F("RAW mode: HVOUT")); Serial.println(g_rawOutput); } g_inputLength = 0; return; } char displayText[16]; bool pointEnabled = false; bool bellEnabled = false; if (parseDisplayCommand(g_inputBuffer, displayText, sizeof(displayText), pointEnabled, bellEnabled)) { g_rawOutput = 0; writeTextToDisplay(displayText); g_pointEnabled = pointEnabled; g_bellEnabled = bellEnabled; Serial.print(F("Displaying: ")); Serial.println(displayText); Serial.print(F("Point: ")); Serial.println(g_pointEnabled ? F("ON") : F("OFF")); Serial.print(F("Bell: ")); Serial.println(g_bellEnabled ? F("ON") : F("OFF")); } else { Serial.print(F("Ignored invalid input: ")); Serial.println(g_inputBuffer); } g_inputLength = 0; } void pollSerial() { while (Serial.available() > 0) { const char incoming = static_cast(Serial.read()); if (incoming == '\r' || incoming == '\n') { commitSerialBuffer(); continue; } if (incoming == '\b' || incoming == 127) { if (g_inputLength > 0) { --g_inputLength; } continue; } if (g_inputLength < sizeof(g_inputBuffer) - 1) { g_inputBuffer[g_inputLength++] = incoming; } } } void refreshDisplay() { if (g_rawOutput != 0) { setDisplayBlanked(true); shiftDriverWord(maskForHvOutput(g_rawOutput)); setDisplayBlanked(false); delayMicroseconds(kDigitHoldMicros); return; } static uint8_t currentPhase = 0; setDisplayBlanked(true); if (currentPhase < kDigitCount) { renderDigit(currentPhase); } else if (g_pointEnabled || g_bellEnabled) { renderIndicator(); } else { blankDisplay(); } setDisplayBlanked(false); delayMicroseconds(kDigitHoldMicros); setDisplayBlanked(true); currentPhase = (currentPhase + 1) % (kDigitCount + 1); } } // namespace void setup() { pinMode(kHvDataPin, OUTPUT); pinMode(kHvClockPin, OUTPUT); pinMode(kHvLatchPin, OUTPUT); if (kHvBlankPin >= 0) { pinMode(kHvBlankPin, OUTPUT); } digitalWrite(kHvDataPin, LOW); digitalWrite(kHvClockPin, HIGH); digitalWrite(kHvLatchPin, HIGH); setDisplayBlanked(true); Serial.begin(kSerialBaud); writeTextToDisplay("0"); blankDisplay(); Serial.println(F("HV5812P VFD controller ready.")); Serial.println(F("Send an integer followed by newline.")); } void loop() { pollSerial(); refreshDisplay(); }