#ifdef PT6311 //=========================================================================================== // Driver for the Princeton Technology PT6311 VFD controller/driver IC. // // Hardware / protocol: // The PT6311 is a "smart" VFD grid/segment controller: unlike a dumb shift-register driver // (e.g. MAX6921) it contains its own display RAM and internal multiplex/scan generator, so // the MCU only has to push a full frame into it after something changes -- the chip itself // keeps refreshing the tubes autonomously in the background. That is why writeDisplay() // (the timer-ISR hook used by "dumb" drivers) is an empty stub here and setup_pins() never // calls startTimer(): there is no ISR-driven multiplex loop for this driver, all display // updates go through the blocking writeDisplaySingle(). // 3-wire serial bus (similar to SPI, chip-specific framing): // PIN_LOAD (STB/CS) - LOW while a command/data byte is being shifted in, HIGH between // bytes to latch/strobe it. Pulled LOW again for every single byte, // i.e. each byte shifted is individually framed by its own STB pulse // (see shift()/sendCommand()/writeDisplaySingle()). // PIN_CLK - one pulse per bit. // PIN_DIN - serial data, LSB-first per byte (see shift(): loop starts at // bit 0), which is the opposite bit order of the MAX6921 driver. // Command bytes (C1..C4) select a mode: C1 sets 8-digit/20-segment display mode, C2 selects // "write display data, auto-increment address", C3 sets the RAM write address, C4 sets // brightness/on-off. Display RAM is addressed per digit in steps of 3 (see // C3_ADDRESS_SETTING + i*3 in writeDisplaySingle()) because the chip reserves 3 bytes per // digit position in its internal RAM regardless of how many of those bits are actually // wired to segments on this PCB. // // Used by (see clocks.h, search for "#define PT6311"): // CLOCK_17 - ESP8266 + PT6311, 4x IV-22 tubes, "russian clock" //=========================================================================================== char tubeDriver[] = "PT6311"; int maxDigits = sizeof(digitEnablePins); //digitEnablePins is a byte[] (1 byte/element), so sizeof() == element count here int wt = 1; //Serial timing //#define PIN_CLK 14 // Clock //#define PIN_DIN 13 // DataIN //#define PIN_LOAD 15 // STB pin #define SEGMENT8 //if not used, 17 segments or more //#define TESTMODE //for testing the segments of a new type VFD module //PT6311 Commands // Display-control command per brightness step 0(OFF)..10. Bits 3-1 of the low nibble select // one of the chip's 8 pulse-width/duty codes (000..111); with 10 non-zero UI brightness // levels mapped onto only 8 hardware codes, two steps had to collapse onto the same code. // POSSIBLE BUG: index 5 and 6 (below) both encode 0B10001100, and index 8 and 9 both encode // 0B10001110 -- i.e. brightness levels 5/6 and 8/9 are indistinguishable on the hardware. // This may be intentional (skipping a step to line the mapping up some other way) or a // copy-paste slip when the table was typed out; worth confirming against the datasheet. byte DISPLAY_MODE[11] = { 0B00000000, //OFF 0B10001000, //1 0B10001001, //2 0B10001010, //3 0B10001011, //4 0B10001100, //5 0B10001100, //6 // POSSIBLE BUG: identical to level 5 above -- see note above the array 0B10001101, //7 0B10001110, //8 0B10001110, //9 // POSSIBLE BUG: identical to level 8 above -- see note above the array 0B10001111, //10 }; #define C1_DISPLAY_MODE_SETTING B00000000 //8 digit, 20 segments #define C2_DATA_SETTING B01000000 //write data to display, addr increment #define C2_DATA_SETTING_TESTMODE B01001000 //write data to display, addr increment #define C3_ADDRESS_SETTING B11000000 //Address setting to 0 #define C4_DISPLAY_CONTROL B10001111 //max brightness, ON (kept for reference; actual brightness command is looked up from DISPLAY_MODE[] instead) #ifdef SEGMENT8 //------------------abcdefgDP---------------- definition of different characters // 8-bit segment mask per glyph, MSB-first as a,b,c,d,e,f,g,DP (bit7=a .. bit0=DP). // Indices 0-9 = digits, 10 = blank, 11 = minus, 12 = decimal point, 13+ = extra glyphs // (A,P,C,deg,%%,lower-circle,I,F) -- this indexing matches the digit[]/newDigit[] encoding // used by the core firmware (see ESP32_UniClock2.ino), so digit[] values map 1:1 into this // table without any translation layer (unlike the ASCII/16-segment branch below). #define MAXSEGMENTS 8 boolean ASCIImode = false; byte charDefinition[] = { B11111100, //0: abcdef B01100000, //1: bc B11011010, //2: abdeg B11110010, //3: abcdg B01100110, //4: bcfg B10110110, //5: acdfg B10111110, //6: acdefg B11100000, //7: abc B11111110, //8: abcdefg B11110110, //9: abcdfg B00000000, // : BLANK (10) B00000010, //-: minus (11) B00000001, // decimal point (12) B11101110, // A abcefg (13) B11001110, // P abefg (14) B10011100, // C adef (15) B11000110, //grad (upper circle) abfg (16) B10110100, //% acdf (17) B00111010, //lower circle cdeg (18) B01100000, //I bc (19) B10001110 //F aefg (20) }; //byte segmentEnablePins[] = {9,8,5,3,4,7,6,2}; //segment enable bits (a,b,c,d,e,f,g,DP) (You MUST define always 8 bits!!!) #else //16 segment + DP --------------------------------------------------------------------------------------------- #define C1_DISPLAY_MODE_SETTING B00000000 //8 digit, 20 segments #define MAXSEGMENTS 17 boolean ASCIImode = true; // Full 16-segment-star + DP font, indexed by ASCII code - 32 (i.e. index 0 = space/0x20). // Since digit[]/newDigit[] hold the compact 0-9/BLANK/minus/DP/glyph codes (not ASCII), the // ASCIImode path in writeDisplaySingle() first maps digit[i] through asciiConvert[] to get an // actual ASCII character, then subtracts 32 to index into this table. uint32_t charDefinition[96] = { 0b00000000000000000, /* (space) */ 0b10000000000001100, /* ! */ 0b00000001000000100, /* " */ // 100000001 0b01010101000111100, /* # */ 0b01010101010111011, /* $ */ 0b01110111010011001, /* % */ 0b01001001101110001, /* & */ 0b00000001000000000, /* ' */ 0b00001010000000000, /* ( */ 0b00100000100000000, /* ) */ 0b01111111100000000, /* * */ 0b01010101000000000, /* + */ 0b00100000000000000, /* , */ 0b01000100000000000, /* - */ 0b10000000000000000, /* . */ 0b00100010000000000, /* / */ 0b00100010011111111, /* 0 */ 0b00000010000001100, /* 1 */ 0b01000100001110111, /* 2 */ 0b00000100000111111, /* 3 */ 0b01000100010001100, /* 4 */ 0b01001000010110011, /* 5 */ 0b01000100011111011, /* 6 */ 0b00000000000001111, /* 7 */ 0b01000100011111111, /* 8 */ 0b01000100010111111, /* 9 */ 0b00010001000000000, /* : */ 0b00100001000000000, /* ; */ 0b01001010000000000, /* < */ 0b01000100000110000, /* = */ 0b00100100100000000, /* > */ 0b10010100000000111, /* ? */ 0b00000101011110111, /* @ */ 0b01000100011001111, /* A */ 0b00010101000111111, /* B */ 0b00000000011110011, /* C */ 0b00010001000111111, /* D */ 0b01000000011110011, /* E */ 0b01000000011000011, /* F */ 0b00000100011111011, /* G */ 0b01000100011001100, /* H */ 0b00010001000110011, /* I */ 0b00000000001111100, /* J */ 0b01001010011000000, /* K */ 0b00000000011110000, /* L */ 0b00000010111001100, /* M */ 0b00001000111001100, /* N */ 0b00000000011111111, /* O */ 0b01000100011000111, /* P */ 0b00001000011111111, /* Q */ 0b01001100011000111, /* R */ 0b01000100010111011, /* S */ 0b00010001000000011, /* T */ 0b00000000011111100, /* U */ 0b00100010011000000, /* V */ 0b00101000011001100, /* W */ 0b00101010100000000, /* X */ 0b01000100010111100, /* Y */ 0b00100010000110011, /* Z */ 0b00010001000010010, /* [ */ 0b00001000100000000, /* \ */ 0b00010001000100001, /* ] */ 0b00101000000000000, /* ^ */ 0b00000000000110000, /* _ */ 0b00000000100000000, /* ` */ 0b01010000001110000, /* a */ 0b01010000011100000, /* b */ 0b01000000001100000, /* c */ 0b00010100000011100, /* d */ 0b01100000001100000, /* e */ 0b01010101000000010, /* f */ 0b01010001010100001, /* g */ 0b01010000011000000, /* h */ 0b00010000000000000, /* i */ 0b00010001001100000, /* j */ 0b00011011000000000, /* k */ 0b00000000011000000, /* l */ 0b01010100001001000, /* m */ 0b01010000001000000, /* n */ 0b01010000001100000, /* o */ 0b01000001011000001, /* p */ 0b01010001010000001, /* q */ 0b01000000001000000, /* r */ 0b01010000010100001, /* s */ 0b01000000011100000, /* t */ 0b00010000001100000, /* u */ 0b00100000001000000, /* v */ 0b00101000001001000, /* w */ 0b00101010100000000, /* x */ 0b00000101000011100, /* y */ 0b01100000000100000, /* z */ 0b01010001000010010, /* { */ 0b00010001000000000, /* | */ 0b00010101000100001, /* } */ 0b01100110000000000, /* ~ */ 0b00000000000000000, /* (del) */ }; //byte segmentEnablePins[] = {16,7,0,1,2,3,4,5,6,8,9,11,10,12,13,14,15}; //segment enable bits #endif // Maps the special glyph codes (BLANK, minus, DP, A,P,C,deg,%,circle,I,F -- digit[] values // 10..20) onto printable ASCII characters, used only when ASCIImode is true (16-segment // branch) so the digit[i] index can be turned into an ASCII char before it is used to look up // charDefinition[]. Note this string is 1:1 positional with the 10..20 glyph codes, i.e. // asciiConvert[0] corresponds to digit value 0 ('0'), asciiConvert[10] (' ') to BLANK, etc. char asciiConvert[] = "0123456789 -.APC~%oIF"; #define MAXCHARS sizeof(charDefinition)/sizeof(charDefinition[0]) // Bit-bangs one byte out on PIN_DIN/PIN_CLK. NOTE: LSB-first (i starts at 0, tests bit 1<10) newBright = 10; //clamp: DISPLAY_MODE[] only has 11 entries (indices 0..10) digitalWrite(PIN_LOAD, LOW); //safety only delayMicroseconds(2); // C2_DATA_SETTING switches the chip into "write display data / auto-increment address" // mode; DISPLAY_MODE[newBright] is then a second command byte (not display data) setting // brightness/on-off -- both are sent back-to-back inside the same PIN_LOAD-low window // opened just above. sendCommand(C2_DATA_SETTING); sendCommand(DISPLAY_MODE[newBright]); for (int i=0;i= sizeof(asciiConvert), or dispChar==21 // landing on the string's NUL terminator) - that produced a negative index into // charDefinition[] a few lines down (out-of-bounds read). Now the subtraction only happens // together with a successful lookup; anything unmapped falls back to charDefinition[0], the // blank/space glyph, instead of indexing out of bounds. if (dispChar blank glyph (charDefinition[0]) } } bitBuffer = 0; digitalWrite(PIN_LOAD,LOW); delayMicroseconds(2); // Address increments by 3 per digit (not 1) -- the PT6311 reserves 3 RAM bytes per digit // position internally (enough for up to 24 segment bits), even though this build only // uses 1-2 of them (MAXSEGMENTS is 8 or 17, see charDefinition table above). shift(C3_ADDRESS_SETTING + i*3); //set display address for (int j=0;j>8); if (MAXSEGMENTS>16) shift(bitBuffer >>16); delayMicroseconds(2); digitalWrite(PIN_LOAD,HIGH); delayMicroseconds(2); } //end for i /* //TEST mode digitalWrite(PIN_LOAD, LOW); //upload data delayMicroseconds(2); shift(C3_ADDRESS_SETTING); for (int i=0;i<48;i++) { shift(0xff); // Data to fill table 5*16 = 80 bits //shift(0x00); // Data to fill table 5*16 = 80 bits //shift(0xff); // Data to fill table 5*16 = 80 bits } digitalWrite(PIN_LOAD, HIGH); delay(2); */ } /*********************** Send command without strobe/chip select******************************************/ // No-op: this driver has no timer-ISR multiplex loop (see file header) -- the PT6311 refreshes // its own tubes internally, so there is nothing for the periodic writeDisplay() ISR hook to do. void writeDisplay() {} // clearTubes() is a required part of the common driver interface, but nothing in the core .ino // calls it any more (its only former caller, disableDisplay(), has been removed entirely as dead // code) - kept as a no-op stub purely for interface compliance. If a synchronous immediate-blank is // ever needed again, this would have to actively send an all-blank frame instead. void clearTubes() {} #endif