Import ESP_UniClock (upstream 1189810)
Vendored from https://github.com/gpeter62/ESP_UniClock: the sketch source, the web UI and the libraries the ESP8266 build needs, unpacked from the bundled zips. Adds a PlatformIO project for the ESP-12. The source is unchanged; local changes follow as separate commits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8efa810e4e
commit
b347f1c63f
843 files changed
+122061
No files matched your search
@@ -0,0 +1,625 @@
|
||||
// ============================================================================
|
||||
// VQC10 driver
|
||||
// ----------------------------------------------------------------------------
|
||||
// Drives 8x VQC10 tubes/modules, each a 5x7 dot-matrix (LED or VFD-style)
|
||||
// character display (see Datasheets/VQC10_Funkamateur.pdf and
|
||||
// "VQC10 timing.jpeg" for the original hardware reference - VQC10 is an
|
||||
// Eastern-European 5x7 dot-matrix indicator tube, not a classic 7-segment
|
||||
// Nixie/VFD tube). Because each character cell is a dot matrix rather than
|
||||
// a fixed set of segments, this driver renders full ASCII text (via the
|
||||
// bitmap _font[] table below) instead of just digits.
|
||||
//
|
||||
// Wiring / protocol: the 8 character cells are driven through a chain of
|
||||
// 74HC595 serial-in/parallel-out shift registers (latchPin=RCK/STCP,
|
||||
// clockPin=SCK/SHCP, dataPin=DIN/SER - classic 3-wire SPI-like shift-out,
|
||||
// no MISO). Unlike most other drivers in this project (which multiplex
|
||||
// digit-by-digit), the VQC10 hardware is scanned ROW-by-ROW: for every one
|
||||
// of the NUMROWS=7 dot-rows, the column data for all NUMDIGITS=8 characters
|
||||
// is shifted out and latched digit-by-digit, then a single row-select line
|
||||
// is pulsed on for a PWM-timed on-period before moving to the next row.
|
||||
// Refresh timing is fully cooperative/blocking (see writeDisplay2() below)
|
||||
// rather than driven from the hardware timer ISR - see the note above
|
||||
// writeDisplay() for why.
|
||||
//
|
||||
// Used by clocks.h configuration(s): CLOCK_71 ("2x VQC10 clock by UNFI").
|
||||
// ============================================================================
|
||||
#ifdef VQC10
|
||||
char tubeDriver[] = "VQC10";
|
||||
#define NUMDIGITS 8
|
||||
int maxDigits = NUMDIGITS;
|
||||
|
||||
//Please, set the pin numbers in clock.h!
|
||||
//#define latchPin 5 //D2 RCK
|
||||
//#define clockPin 4 //D1 SCK
|
||||
//#define dataPin 2 //D4 DIN
|
||||
//#define ledOffpin 0 //D5
|
||||
//#define D5pin 14
|
||||
|
||||
|
||||
#define NUMCOLS 5 // font glyph width in dots (columns per character cell)
|
||||
#define NUMROWS 7 // font glyph height in dots (rows per character cell) - also the number of physical row-select lines scanned in writeDisplay2()
|
||||
|
||||
boolean _upsidedown = false; // flips the dot-matrix rendering 180 degrees (for tubes mounted upside down); never set to true anywhere in this codebase - effectively dead/unused, kept for parity with wordclock.ino which shares this same font/transpose code
|
||||
byte row[NUMROWS]; // scratch buffer filled by show()/transposePattern(): one row-bitmap per dot-row for the character currently being rendered
|
||||
|
||||
// Maps the shared digit[]/newDigit[] buffer codes (see core file: 0-9=digit, 10=BLANK,
|
||||
// 11=minus, 12=decimal point, 13+=driver-specific extra glyphs) to an index into the
|
||||
// ASCII-ordered _font[] table below, i.e. this converts the generic "digit code" alphabet
|
||||
// used by the core firmware into actual character codes this driver can render.
|
||||
char asciiConvert[] = "0123456789 -.APC~%oIF";
|
||||
char DPchar = 0xFF; //decimal point - index of the dedicated 1-dot "decimal point" glyph appended at the very end of _font[] (see 0xFF entry below)
|
||||
|
||||
char cDigit[NUMDIGITS+1] = "76543210"; // NOTE: declared/initialized but never referenced anywhere else in this file or the project - dead code (candidate for removal)
|
||||
byte dat[NUMDIGITS][NUMROWS]; // final row-bitmaps actually shifted out to hardware (post-animation-blend result)
|
||||
byte oldDat[NUMDIGITS][NUMROWS]; // row-bitmaps for the outgoing (old) character, built fresh each writeDisplaySingle() call
|
||||
byte newDat[NUMDIGITS][NUMROWS]; // row-bitmaps for the incoming (new) character, built fresh each writeDisplaySingle() call
|
||||
|
||||
// standard ascii 5x7 font incl. the IBM-PC extension:
|
||||
// already defined in DotMatrx5x7.h
|
||||
// Format: NUMCOLS(5) bytes per glyph, indexed as glyph_code*NUMCOLS in show() above; each
|
||||
// byte is one column of the cell, with dot rows packed in its low NUMROWS(7) bits (bit0=row0
|
||||
// top .. bit6=row6 bottom in normal orientation). Glyph 0xFF at the very end of the table is
|
||||
// repurposed as a dedicated "decimal point" glyph (see DPchar above) rather than a real
|
||||
// character, so it only lights a single dot instead of a printable symbol.
|
||||
//#define aUML "\x84" // ä = 0x84
|
||||
//#define oUML "\x94" // ö = 0x94
|
||||
//#define uUML "\x81" // ü = 0x81
|
||||
//#define sZET "\xE1" // ß = 0xE1
|
||||
//#define AUML "\x8E" // Ä = 0x8E
|
||||
//#define OUML "\x99" // Ö = 0x99
|
||||
//#define UUML "\x9A" // Ü = 0x9A
|
||||
|
||||
byte _font[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, // 0x00 empty
|
||||
0x3E, 0x5B, 0x4F, 0x5B, 0x3E, // 0x01 sad
|
||||
0x3E, 0x6B, 0x4F, 0x6B, 0x3E, // 0x02 happy
|
||||
0x1C, 0x3E, 0x7C, 0x3E, 0x1C, // 0x03 heart
|
||||
0x18, 0x3C, 0x7E, 0x3C, 0x18, // 0x04 diamond
|
||||
0x1C, 0x57, 0x7D, 0x57, 0x1C, // 0x05 clover
|
||||
0x1C, 0x5E, 0x7F, 0x5E, 0x1C, // 0x06 spade
|
||||
0x00, 0x18, 0x3C, 0x18, 0x00, // 0x07 small full circle
|
||||
0xFF, 0xE7, 0xC3, 0xE7, 0xFF, // 0x08 inverted small circle
|
||||
0x06, 0x09, 0x06, 0x00, 0x00, // 0x09 small empty circle //old: 0x00, 0x18, 0x24, 0x18, 0x00, // 0x09 small empty circle
|
||||
0xFF, 0xE7, 0xDB, 0xE7, 0xFF, // 0x0A block (linefeed)
|
||||
0x30, 0x48, 0x3A, 0x06, 0x0E, // 0x0B male
|
||||
0x26, 0x29, 0x79, 0x29, 0x26, // 0x0C female
|
||||
0x40, 0x7F, 0x05, 0x05, 0x07, // 0x0D note (carriage return)
|
||||
0x40, 0x7F, 0x05, 0x25, 0x3F, // 0x0E two notes
|
||||
0x5A, 0x3C, 0xE7, 0x3C, 0x5A, // 0x0F sun
|
||||
0x7F, 0x3E, 0x1C, 0x1C, 0x08, // 0x10 right triangle
|
||||
0x08, 0x1C, 0x1C, 0x3E, 0x7F, // 0x11 left triangle
|
||||
0x14, 0x22, 0x7F, 0x22, 0x14, // 0x12 up/down arrow
|
||||
0x5F, 0x5F, 0x00, 0x5F, 0x5F, // 0x13 double exclamation mark
|
||||
0x06, 0x09, 0x7F, 0x01, 0x7F, // 0x14 paragraph end sign (pilcrow)
|
||||
// 0x00, 0x66, 0x89, 0x95, 0x6A, // 0x15 paragraph sign original
|
||||
0x00, 0x26, 0x49, 0x55, 0x28, // 0x15 paragraph sign
|
||||
0x60, 0x60, 0x60, 0x60, 0x60, // 0x16 double underline
|
||||
0x54, 0x62, 0x7F, 0x62, 0x54, // 0x17 up/down arrow with underline
|
||||
0x08, 0x04, 0x7E, 0x04, 0x08, // 0x18 up arrow
|
||||
0x10, 0x20, 0x7E, 0x20, 0x10, // 0x19 down arrow
|
||||
0x08, 0x08, 0x2A, 0x1C, 0x08, // 0x1A right arrow
|
||||
0x08, 0x1C, 0x2A, 0x08, 0x08, // 0x1B left arrow
|
||||
0x14, 0x3E, 0x55, 0x41, 0x22, // 0x1C euro
|
||||
0x63, 0x75, 0x69, 0x75, 0x63, // 0x1D timer
|
||||
0x30, 0x38, 0x3E, 0x38, 0x30, // 0x1E up triangle
|
||||
0x06, 0x0E, 0x3E, 0x0E, 0x06, // 0x1F down triangle
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, // 0x20 space
|
||||
0x00, 0x00, 0x5F, 0x00, 0x00, // 0x21 !
|
||||
0x00, 0x07, 0x00, 0x07, 0x00, // 0x22 "
|
||||
0x14, 0x7F, 0x14, 0x7F, 0x14, // 0x23 #
|
||||
0x24, 0x2A, 0x7F, 0x2A, 0x12, // 0x24 $
|
||||
0x23, 0x13, 0x08, 0x64, 0x62, // 0x25 %
|
||||
0x36, 0x49, 0x56, 0x20, 0x50, // 0x26 &
|
||||
0x00, 0x08, 0x07, 0x03, 0x00, // 0x27 ´
|
||||
0x00, 0x1C, 0x22, 0x41, 0x00, // 0x28 (
|
||||
0x00, 0x41, 0x22, 0x1C, 0x00, // 0x29 )
|
||||
0x2A, 0x1C, 0x7F, 0x1C, 0x2A, // 0x2A *
|
||||
0x08, 0x08, 0x3E, 0x08, 0x08, // 0x2B +
|
||||
0x00, 0x80, 0x70, 0x30, 0x00, // 0x2C ,
|
||||
0x08, 0x08, 0x08, 0x08, 0x08, // 0x2D -
|
||||
0x00, 0x00, 0x60, 0x60, 0x00, // 0x2E .
|
||||
0x20, 0x10, 0x08, 0x04, 0x02, // 0x2F /
|
||||
0x3E, 0x51, 0x49, 0x45, 0x3E, // 0x30 0
|
||||
0x00, 0x42, 0x7F, 0x40, 0x00, // 0x31 1
|
||||
0x72, 0x49, 0x49, 0x49, 0x46, // 0x32 2
|
||||
0x21, 0x41, 0x49, 0x4D, 0x33, // 0x33 3
|
||||
0x18, 0x14, 0x12, 0x7F, 0x10, // 0x34 4
|
||||
0x27, 0x45, 0x45, 0x45, 0x39, // 0x35 5
|
||||
0x3C, 0x4A, 0x49, 0x49, 0x31, // 0x36 6
|
||||
0x41, 0x21, 0x11, 0x09, 0x07, // 0x37 7
|
||||
0x36, 0x49, 0x49, 0x49, 0x36, // 0x38 8
|
||||
0x46, 0x49, 0x49, 0x29, 0x1E, // 0x39 9
|
||||
0x00, 0x00, 0x14, 0x00, 0x00, // 0x3A :
|
||||
0x00, 0x40, 0x34, 0x00, 0x00, // 0x3B ;
|
||||
0x00, 0x08, 0x14, 0x22, 0x41, // 0x3C <
|
||||
0x14, 0x14, 0x14, 0x14, 0x14, // 0x3D =
|
||||
0x00, 0x41, 0x22, 0x14, 0x08, // 0x3E >
|
||||
0x02, 0x01, 0x59, 0x09, 0x06, // 0x3F ?
|
||||
0x3E, 0x41, 0x5D, 0x59, 0x4E, // 0x40 @
|
||||
0x7C, 0x12, 0x11, 0x12, 0x7C, // 0x41 A
|
||||
0x7F, 0x49, 0x49, 0x49, 0x36, // 0x42 B
|
||||
0x3E, 0x41, 0x41, 0x41, 0x22, // 0x43 C
|
||||
0x7F, 0x41, 0x41, 0x41, 0x3E, // 0x44 D
|
||||
0x7F, 0x49, 0x49, 0x49, 0x41, // 0x45 E
|
||||
0x7F, 0x09, 0x09, 0x09, 0x01, // 0x46 F
|
||||
0x3E, 0x41, 0x41, 0x51, 0x73, // 0x47 G
|
||||
0x7F, 0x08, 0x08, 0x08, 0x7F, // 0x48 H
|
||||
0x00, 0x41, 0x7F, 0x41, 0x00, // 0x49 I
|
||||
0x20, 0x40, 0x41, 0x3F, 0x01, // 0x4A J
|
||||
0x7F, 0x08, 0x14, 0x22, 0x41, // 0x4B K
|
||||
0x7F, 0x40, 0x40, 0x40, 0x40, // 0x4C L
|
||||
0x7F, 0x02, 0x1C, 0x02, 0x7F, // 0x4D M
|
||||
0x7F, 0x04, 0x08, 0x10, 0x7F, // 0x4E N
|
||||
0x3E, 0x41, 0x41, 0x41, 0x3E, // 0x4F O
|
||||
0x7F, 0x09, 0x09, 0x09, 0x06, // 0x50 P
|
||||
0x3E, 0x41, 0x51, 0x21, 0x5E, // 0x51 Q
|
||||
0x7F, 0x09, 0x19, 0x29, 0x46, // 0x52 R
|
||||
0x26, 0x49, 0x49, 0x49, 0x32, // 0x53 S
|
||||
0x03, 0x01, 0x7F, 0x01, 0x03, // 0x54 T
|
||||
0x3F, 0x40, 0x40, 0x40, 0x3F, // 0x55 U
|
||||
0x1F, 0x20, 0x40, 0x20, 0x1F, // 0x56 V
|
||||
0x3F, 0x40, 0x38, 0x40, 0x3F, // 0x57 W
|
||||
0x63, 0x14, 0x08, 0x14, 0x63, // 0x58 X
|
||||
0x03, 0x04, 0x78, 0x04, 0x03, // 0x59 Y
|
||||
0x61, 0x59, 0x49, 0x4D, 0x43, // 0x5A Z
|
||||
0x00, 0x7F, 0x41, 0x41, 0x41, // 0x5B [
|
||||
0x02, 0x04, 0x08, 0x10, 0x20, // 0x5C '\'
|
||||
0x00, 0x41, 0x41, 0x41, 0x7F, // 0x5D ]
|
||||
0x04, 0x02, 0x01, 0x02, 0x04, // 0x5E ^
|
||||
0x40, 0x40, 0x40, 0x40, 0x40, // 0x5F _
|
||||
0x00, 0x03, 0x07, 0x08, 0x00, // 0x60 `
|
||||
0x20, 0x54, 0x54, 0x78, 0x40, // 0x61 a
|
||||
0x7F, 0x28, 0x44, 0x44, 0x38, // 0x62 b
|
||||
0x38, 0x44, 0x44, 0x44, 0x00, // 0x63 c
|
||||
0x38, 0x44, 0x44, 0x28, 0x7F, // 0x64 d
|
||||
0x38, 0x54, 0x54, 0x54, 0x18, // 0x65 e
|
||||
0x00, 0x08, 0x7E, 0x09, 0x02, // 0x66 f
|
||||
// 0x18, 0xA4, 0xA4, 0x9C, 0x78, // 0x67 g (old)
|
||||
0x0C, 0x52, 0x52, 0x4E, 0x3C, // 0x67 g
|
||||
0x7F, 0x08, 0x04, 0x04, 0x78, // 0x68 h
|
||||
0x00, 0x44, 0x7D, 0x40, 0x00, // 0x69 i
|
||||
0x20, 0x40, 0x40, 0x3D, 0x00, // 0x6A j
|
||||
0x7F, 0x10, 0x28, 0x44, 0x00, // 0x6B k
|
||||
0x00, 0x41, 0x7F, 0x40, 0x00, // 0x6C l
|
||||
0x7C, 0x04, 0x78, 0x04, 0x78, // 0x6D m
|
||||
0x7C, 0x08, 0x04, 0x04, 0x78, // 0x6E n
|
||||
0x38, 0x44, 0x44, 0x44, 0x38, // 0x6F o
|
||||
0xFC, 0x18, 0x24, 0x24, 0x18, // 0x70 p
|
||||
// 0x18, 0x24, 0x24, 0x18, 0xFC, // 0x71 q (old)
|
||||
0x0C, 0x12, 0x12, 0x0C, 0x7E, // 0x71 q
|
||||
0x7C, 0x08, 0x04, 0x04, 0x08, // 0x72 r
|
||||
0x48, 0x54, 0x54, 0x54, 0x24, // 0x73 s
|
||||
0x04, 0x04, 0x3F, 0x44, 0x24, // 0x74 t
|
||||
0x3C, 0x40, 0x40, 0x20, 0x7C, // 0x75 u
|
||||
0x1C, 0x20, 0x40, 0x20, 0x1C, // 0x76 v
|
||||
0x3C, 0x40, 0x30, 0x40, 0x3C, // 0x77 w
|
||||
0x44, 0x28, 0x10, 0x28, 0x44, // 0x78 x
|
||||
0x0C, 0x10, 0x10, 0x50, 0x3C, // 0x79 y
|
||||
0x44, 0x64, 0x54, 0x4C, 0x44, // 0x7A z
|
||||
0x00, 0x08, 0x36, 0x41, 0x00, // 0x7B {
|
||||
0x00, 0x00, 0x7F, 0x00, 0x00, // 0x7C |
|
||||
0x00, 0x41, 0x36, 0x08, 0x00, // 0x7D }
|
||||
0x02, 0x01, 0x02, 0x04, 0x02, // 0x7E ~
|
||||
0x3C, 0x26, 0x23, 0x26, 0x3C, // 0x7F house
|
||||
0x1E, 0xA1, 0xA1, 0x61, 0x12, // 0x81
|
||||
0x3A, 0x40, 0x40, 0x20, 0x7A, // 0x81 ü
|
||||
0x38, 0x54, 0x54, 0x55, 0x59, // 0x82
|
||||
0x21, 0x55, 0x55, 0x79, 0x41, // 0x83
|
||||
0x21, 0x54, 0x54, 0x78, 0x41, // 0x84 ä
|
||||
0x21, 0x55, 0x54, 0x78, 0x40, // 0x85
|
||||
0x20, 0x54, 0x55, 0x79, 0x40, // 0x86
|
||||
0x0C, 0x1E, 0x52, 0x72, 0x12, // 0x87
|
||||
0x39, 0x55, 0x55, 0x55, 0x59, // 0x88
|
||||
0x39, 0x54, 0x54, 0x54, 0x59, // 0x89
|
||||
0x39, 0x55, 0x54, 0x54, 0x58, // 0x8A
|
||||
0x00, 0x00, 0x45, 0x7C, 0x41, // 0x8B
|
||||
0x00, 0x02, 0x45, 0x7D, 0x42, // 0x8C
|
||||
0x00, 0x01, 0x45, 0x7C, 0x40, // 0x8D
|
||||
0xF0, 0x29, 0x24, 0x29, 0xF0, // 0x8E Ä
|
||||
0xF0, 0x28, 0x25, 0x28, 0xF0, // 0x8F
|
||||
0x7C, 0x54, 0x55, 0x45, 0x00, // 0x90
|
||||
0x20, 0x54, 0x54, 0x7C, 0x54, // 0x91
|
||||
0x7C, 0x0A, 0x09, 0x7F, 0x49, // 0x92
|
||||
0x32, 0x49, 0x49, 0x49, 0x32, // 0x93
|
||||
0x32, 0x48, 0x48, 0x48, 0x32, // 0x94 ö
|
||||
0x32, 0x4A, 0x48, 0x48, 0x30, // 0x95
|
||||
0x3A, 0x41, 0x41, 0x21, 0x7A, // 0x96
|
||||
0x3A, 0x42, 0x40, 0x20, 0x78, // 0x97
|
||||
0x00, 0x9D, 0xA0, 0xA0, 0x7D, // 0x98
|
||||
//0x39, 0x44, 0x44, 0x44, 0x39, // 0x99 Ö (old)
|
||||
0x3D, 0x42, 0x42, 0x42, 0x3D, // 0x99 Ö
|
||||
0x3D, 0x40, 0x40, 0x40, 0x3D, // 0x9A Ü
|
||||
0x3C, 0x24, 0xFF, 0x24, 0x24, // 0x9B cent
|
||||
0x48, 0x7E, 0x49, 0x43, 0x66, // 0x9C pound
|
||||
0x2B, 0x2F, 0xFC, 0x2F, 0x2B, // 0x9D
|
||||
0xFF, 0x09, 0x29, 0xF6, 0x20, // 0x9E
|
||||
0xC0, 0x88, 0x7E, 0x09, 0x03, // 0x9F
|
||||
0x20, 0x54, 0x54, 0x79, 0x41, // 0xA0
|
||||
0x00, 0x00, 0x44, 0x7D, 0x41, // 0xA1
|
||||
0x30, 0x48, 0x48, 0x4A, 0x32, // 0xA2
|
||||
0x38, 0x40, 0x40, 0x22, 0x7A, // 0xA3
|
||||
0x00, 0x7A, 0x0A, 0x0A, 0x72, // 0xA4
|
||||
0x7D, 0x0D, 0x19, 0x31, 0x7D, // 0xA5
|
||||
0x26, 0x29, 0x29, 0x2F, 0x28, // 0xA6
|
||||
0x26, 0x29, 0x29, 0x29, 0x26, // 0xA7
|
||||
0x30, 0x48, 0x4D, 0x40, 0x20, // 0xA8
|
||||
0x38, 0x08, 0x08, 0x08, 0x08, // 0xA9
|
||||
0x08, 0x08, 0x08, 0x08, 0x38, // 0xAA
|
||||
0x2F, 0x10, 0xC8, 0xAC, 0xBA, // 0xAB
|
||||
0x2F, 0x10, 0x28, 0x34, 0xFA, // 0xAC
|
||||
0x00, 0x00, 0x7B, 0x00, 0x00, // 0xAD exclamation mark upside down
|
||||
0x08, 0x14, 0x2A, 0x14, 0x22, // 0xAE less less
|
||||
0x22, 0x14, 0x2A, 0x14, 0x08, // 0xAF greater greater
|
||||
0xAA, 0x00, 0x55, 0x00, 0xAA, // 0xB0
|
||||
0xAA, 0x55, 0xAA, 0x55, 0xAA, // 0xB1
|
||||
0xAA, 0x55, 0xAA, 0x55, 0xAA, // 0xB2
|
||||
0x00, 0x00, 0x00, 0xFF, 0x00, // 0xB3
|
||||
0x10, 0x10, 0x10, 0xFF, 0x00, // 0xB4
|
||||
0x14, 0x14, 0x14, 0xFF, 0x00, // 0xB5
|
||||
0x10, 0x10, 0xFF, 0x00, 0xFF, // 0xB6
|
||||
0x10, 0x10, 0xF0, 0x10, 0xF0, // 0xB7
|
||||
0x14, 0x14, 0x14, 0xFC, 0x00, // 0xB8
|
||||
0x14, 0x14, 0xF7, 0x00, 0xFF, // 0xB9
|
||||
0x00, 0x00, 0xFF, 0x00, 0xFF, // 0xBA
|
||||
0x14, 0x14, 0xF4, 0x04, 0xFC, // 0xBB
|
||||
0x14, 0x14, 0x17, 0x10, 0x1F, // 0xBC
|
||||
0x10, 0x10, 0x1F, 0x10, 0x1F, // 0xBD
|
||||
0x14, 0x14, 0x14, 0x1F, 0x00, // 0xBE
|
||||
0x10, 0x10, 0x10, 0xF0, 0x00, // 0xBF
|
||||
0x00, 0x00, 0x00, 0x1F, 0x10, // 0xC0
|
||||
0x10, 0x10, 0x10, 0x1F, 0x10, // 0xC1
|
||||
0x10, 0x10, 0x10, 0xF0, 0x10, // 0xC2
|
||||
0x00, 0x00, 0x00, 0xFF, 0x10, // 0xC3
|
||||
0x10, 0x10, 0x10, 0x10, 0x10, // 0xC4
|
||||
0x10, 0x10, 0x10, 0xFF, 0x10, // 0xC5
|
||||
0x00, 0x00, 0x00, 0xFF, 0x14, // 0xC6
|
||||
0x00, 0x00, 0xFF, 0x00, 0xFF, // 0xC7
|
||||
0x00, 0x00, 0x1F, 0x10, 0x17, // 0xC8
|
||||
0x00, 0x00, 0xFC, 0x04, 0xF4, // 0xC9
|
||||
0x14, 0x14, 0x17, 0x10, 0x17, // 0xCA
|
||||
0x14, 0x14, 0xF4, 0x04, 0xF4, // 0xCB
|
||||
0x00, 0x00, 0xFF, 0x00, 0xF7, // 0xCC
|
||||
0x14, 0x14, 0x14, 0x14, 0x14, // 0xCD
|
||||
0x14, 0x14, 0xF7, 0x00, 0xF7, // 0xCE
|
||||
0x14, 0x14, 0x14, 0x17, 0x14, // 0xCF
|
||||
0x10, 0x10, 0x1F, 0x10, 0x1F, // 0xD0
|
||||
0x14, 0x14, 0x14, 0xF4, 0x14, // 0xD1
|
||||
0x10, 0x10, 0xF0, 0x10, 0xF0, // 0xD2
|
||||
0x00, 0x00, 0x1F, 0x10, 0x1F, // 0xD3
|
||||
0x00, 0x00, 0x00, 0x1F, 0x14, // 0xD4
|
||||
0x00, 0x00, 0x00, 0xFC, 0x14, // 0xD5
|
||||
0x00, 0x00, 0xF0, 0x10, 0xF0, // 0xD6
|
||||
0x10, 0x10, 0xFF, 0x10, 0xFF, // 0xD7
|
||||
0x14, 0x14, 0x14, 0xFF, 0x14, // 0xD8
|
||||
0x10, 0x10, 0x10, 0x1F, 0x00, // 0xD9
|
||||
0x00, 0x00, 0x00, 0xF0, 0x10, // 0xDA
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xDB
|
||||
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, // 0xDC
|
||||
0xFF, 0xFF, 0xFF, 0x00, 0x00, // 0xDD
|
||||
0x00, 0x00, 0x00, 0xFF, 0xFF, // 0xDE
|
||||
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, // 0xDF
|
||||
0x38, 0x44, 0x44, 0x38, 0x44, // 0xE0 alpha
|
||||
0x7C, 0x2A, 0x2A, 0x3E, 0x14, // 0xE1 beta / ß
|
||||
0x7E, 0x02, 0x02, 0x06, 0x06, // 0xE2 Gamma
|
||||
0x02, 0x7E, 0x02, 0x7E, 0x02, // 0xE3 Pi
|
||||
0x63, 0x55, 0x49, 0x41, 0x63, // 0xE4 Sigma
|
||||
0x38, 0x44, 0x44, 0x3C, 0x04, // 0xE5 sigma
|
||||
0x40, 0x7E, 0x20, 0x1E, 0x20, // 0xE6 mu
|
||||
0x06, 0x02, 0x7E, 0x02, 0x02, // 0xE7 tau
|
||||
0x99, 0xA5, 0xE7, 0xA5, 0x99, // 0xE8 Phi
|
||||
0x1C, 0x2A, 0x49, 0x2A, 0x1C, // 0xE9 theta
|
||||
0x4C, 0x72, 0x01, 0x72, 0x4C, // 0xEA Omega
|
||||
0x30, 0x4A, 0x4D, 0x4D, 0x30, // 0xEB delta
|
||||
0x30, 0x48, 0x78, 0x48, 0x30, // 0xEC phi?
|
||||
0xBC, 0x62, 0x5A, 0x46, 0x3D, // 0xED empty set
|
||||
0x3E, 0x49, 0x49, 0x49, 0x00, // 0xEE epsilon
|
||||
0x7E, 0x01, 0x01, 0x01, 0x7E, // 0xEF
|
||||
0x2A, 0x2A, 0x2A, 0x2A, 0x2A, // 0xF0 equivalence
|
||||
0x44, 0x44, 0x5F, 0x44, 0x44, // 0xF1 plus minus
|
||||
0x40, 0x51, 0x4A, 0x44, 0x40, // 0xF2 greater or equal
|
||||
0x40, 0x44, 0x4A, 0x51, 0x40, // 0xF3 less or equal
|
||||
0x00, 0x00, 0xFF, 0x01, 0x03, // 0xF4
|
||||
0xE0, 0x80, 0xFF, 0x00, 0x00, // 0xF5
|
||||
0x08, 0x08, 0x6B, 0x6B, 0x08, // 0xF6 divided by
|
||||
0x36, 0x12, 0x36, 0x24, 0x36, // 0xF7 approx
|
||||
0x06, 0x0F, 0x09, 0x0F, 0x06, // 0xF8 degree
|
||||
0x00, 0x00, 0x18, 0x18, 0x00, // 0xF9 big dot
|
||||
0x00, 0x00, 0x10, 0x10, 0x00, // 0xFA small dot
|
||||
0x30, 0x40, 0xFF, 0x01, 0x01, // 0xFB square root
|
||||
0x00, 0x1F, 0x01, 0x01, 0x1E, // 0xFC
|
||||
0x00, 0x19, 0x1D, 0x17, 0x12, // 0xFD power of two
|
||||
0x00, 0x3C, 0x3C, 0x3C, 0x3C, // 0xFE blob
|
||||
//0x00, 0x00, 0x00, 0x60, 0x60, // 0xFF: Decimal point: 4 points
|
||||
0x00, 0x00, 0x00, 0x00, 0x40, // 0xFF: Decimal point, only 1 point
|
||||
};
|
||||
|
||||
void setup_pins() {
|
||||
DPRINTLN("VQC10 Clock - setup 74HC595 pins");
|
||||
pinMode(latchPin, OUTPUT); regPin(latchPin,"latchPin");
|
||||
pinMode(dataPin,OUTPUT); regPin(dataPin,"dataPin");
|
||||
pinMode(clockPin,OUTPUT); regPin(clockPin,"clockPin");
|
||||
pinMode(D5pin,INPUT); regPin(D5pin,"D5pin"); // NOTE: configured and registered but never read anywhere in this driver or the core file - purpose (spare sensor/button input?) is undocumented, see open questions
|
||||
digitsOnly = false; // this driver can render full ASCII text (dot-matrix font), not just 0-9, so let the core firmware know
|
||||
asciiConvert[16] = 9; // runtime override: digit-buffer code 16 (originally mapped to '~' above) is remapped to raw font index 9 ("small empty circle" glyph, see _font table) for a driver-specific extra symbol
|
||||
// Deliberately does NOT call startTimer() here (unlike most other drivers): the hardware
|
||||
// timer ISR (writeDisplay(), see below) is intentionally a no-op for VQC10, and indeed
|
||||
// startTimer() is never invoked anywhere for this driver in the whole project. All actual
|
||||
// refresh work happens cooperatively via writeDisplay2(), called repeatedly from loop()/
|
||||
// Fdelay() in the core file - see the comment above writeDisplay() for why.
|
||||
}
|
||||
|
||||
void inline shiftOutForward(byte myDataOut) {
|
||||
// This shifts 8 bits out MSB first, on the rising edge of the clock, clock idles low
|
||||
// Used for the "digit select" (Z) and "row select" shift-register bytes, whose bit
|
||||
// order must match how those select lines are physically wired to the 74HC595 outputs.
|
||||
for (int i=7; i>=0; i--) {
|
||||
digitalWrite(clockPin, 0);
|
||||
digitalWrite(dataPin, myDataOut & (1<<i)); //register shifts bits on upstroke of clock pin
|
||||
digitalWrite(clockPin, 1);
|
||||
digitalWrite(dataPin, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void inline shiftOutReverse(byte myDataOut) {
|
||||
// This shifts 8 bits out LSB first, on the rising edge of the clock, clock idles low
|
||||
// Used for the pixel/column DATA byte only - the opposite bit order from
|
||||
// shiftOutForward() is required here because the data byte travels through the same
|
||||
// shift-register chain but ends up latched onto a different (mirrored) set of output
|
||||
// pins than the select bytes; using the wrong helper here would mirror the glyph.
|
||||
for (int i=0; i<8; i++) {
|
||||
digitalWrite(clockPin, 0);
|
||||
digitalWrite(dataPin, myDataOut & (1<<i)); //register shifts bits on upstroke of clock pin
|
||||
digitalWrite(clockPin, 1);
|
||||
digitalWrite(dataPin, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#define PWMrefresh 8000l // NOTE: defined but not referenced anywhere in this file - dead macro (leftover from an earlier PWM implementation?)
|
||||
|
||||
// Required interface stub only: the hardware timer ISR that drives every other driver's
|
||||
// multiplex refresh is intentionally left EMPTY for VQC10. The real refresh (writeDisplay2()
|
||||
// below) needs delayMicroseconds() calls per row for PWM brightness dimming, which is far too
|
||||
// slow/blocking to run safely inside a timer ISR context. Instead, writeDisplay2() is called
|
||||
// cooperatively from the main loop() and from Fdelay() in the core file. Consistent with this,
|
||||
// setup_pins() above never calls startTimer(), so this ISR is in fact never even attached for
|
||||
// this driver - it exists purely to satisfy the common driver interface (extern declaration in
|
||||
// the core file, and the VQC10 special-case in changeDigit()'s animation code).
|
||||
void ICACHE_RAM_ATTR writeDisplay() {}
|
||||
|
||||
|
||||
int PWM_min = 0;
|
||||
int PWM_max = 800; // row on-time ceiling in microseconds, at full brightness (see PWMtimeBrightness below)
|
||||
|
||||
// Blocking, cooperative multiplex refresh for the whole 8x5x7 display - one full call
|
||||
// scans every row once. Called repeatedly from loop()/Fdelay() in the core file (NOT from
|
||||
// an ISR - see the note above writeDisplay()). Brightness is implemented as simple PWM: each
|
||||
// row is held "on" for PWMtimeBrightness microseconds out of a PWM_max budget, then forced
|
||||
// off for the remainder, so total per-row time is constant regardless of brightness (keeps
|
||||
// the row scan rate/refresh flicker frequency stable while only the mark/space ratio changes).
|
||||
void writeDisplay2() {
|
||||
static int PWMtimeBrightness;
|
||||
//static uint32_t refresh = 0;
|
||||
//static uint32_t runtim = 0;
|
||||
//static uint32_t laststart = 0;
|
||||
boolean offState;
|
||||
int brightness;
|
||||
int iRow,loopMax;
|
||||
|
||||
|
||||
//refresh = millis()-laststart;
|
||||
//laststart = millis();
|
||||
//runtim = laststart;
|
||||
|
||||
brightness = displayON ? prm.dayBright : prm.nightBright;
|
||||
if (brightness>MAXBRIGHTNESS) brightness = MAXBRIGHTNESS; //only for safety
|
||||
|
||||
if (autoBrightness && displayON) {
|
||||
PWMtimeBrightness = max(PWM_min,PWM_max*lx/MAXIMUM_LUX); // ambient-light-driven dimming: scale row on-time by measured lux vs. MAXIMUM_LUX
|
||||
}
|
||||
else
|
||||
PWMtimeBrightness = max(PWM_min,PWM_max*brightness/MAXBRIGHTNESS); // manual brightness: scale row on-time by configured day/night brightness level
|
||||
|
||||
iRow = 0;
|
||||
// FIXED (was "loopMax = maxDigits;", i.e. NUMDIGITS=8): this loop indexes dot-matrix ROWS, which
|
||||
// only range 0..NUMROWS-1 (NUMROWS=7, i.e. dat[i] is sized [NUMROWS]=[7]), not digit columns. The
|
||||
// old bound made the loop run iRow = 0..7 (8 iterations) instead of 0..6 (7 iterations), so on the
|
||||
// last iteration (iRow==7) "dat[i][iRow]" below read one element past the end of each digit's
|
||||
// 7-byte row array - for i<NUMDIGITS-1 this silently read dat[i+1][0] (the next digit's row-0
|
||||
// data, due to row-major layout) instead of a real row-7 pattern, and for the last digit
|
||||
// (i==NUMDIGITS-1) it read out of the whole `dat` array into whatever memory followed it
|
||||
// (oldDat[0][0]). It also drove a nonexistent 8th row-select line via shiftOutForward(~(1<<iRow))
|
||||
// below. Net effect was an extra, garbage 8th "row" scanned on every refresh - flicker/ghosting
|
||||
// risk and a stolen slice of the row-time PWM budget.
|
||||
loopMax = NUMROWS;
|
||||
do {
|
||||
// NOTE: the "iRow == maxDigits" term here can never be true (maxDigits==NUMDIGITS==8, but iRow now
|
||||
// only ever reaches up to NUMROWS-1==6 before the loop exits via `while(iRow<loopMax)`) - it looks
|
||||
// like it was meant to add an extra "sync/blank" row at the end of each sweep, but as written it's
|
||||
// dead weight in the OR. Left as-is (not an out-of-bounds/indexing issue, just an inert condition).
|
||||
offState = ( (brightness == 0) || (!radarON) || (iRow == maxDigits));
|
||||
|
||||
for (int i=0;i<NUMDIGITS;i++) { //fill one line with dots
|
||||
// Two-phase shift+latch per digit: first phase asserts this digit's select line
|
||||
// together with its row data; the second phase re-latches the SAME data but with
|
||||
// ALL digit-select lines forced back off (0xFF = all deselected, active-low select).
|
||||
// This immediately blanks the just-lit digit again before the loop moves on to the
|
||||
// next one, which prevents the still-settling row data from bleeding/ghosting onto
|
||||
// the previously selected digit while the next digit's select+data is being shifted
|
||||
// in (VFD/tube grids can retain enough charge to glow briefly if left selected while
|
||||
// data changes underneath them).
|
||||
shiftOutForward(0xFF); //deselect Z
|
||||
shiftOutForward(~(1<<i)); //select digit
|
||||
shiftOutReverse(offState ? 0 : dat[i][iRow]); // set data line
|
||||
digitalWrite(latchPin, HIGH); //Write shift register to latch
|
||||
digitalWrite(latchPin, LOW);
|
||||
asm volatile ("nop");
|
||||
|
||||
//Finish latching
|
||||
shiftOutForward(0xFF); //deselect Z
|
||||
shiftOutForward(0xFF); //All digit select lines are HIGH
|
||||
shiftOutReverse(offState ? 0 : dat[i][iRow]); //keep data line
|
||||
digitalWrite(latchPin, HIGH); //Write shift register to latch
|
||||
digitalWrite(latchPin, LOW);
|
||||
asm volatile ("nop");
|
||||
}
|
||||
|
||||
// All 8 digits now hold row iRow's column data latched into their driver ICs (but with
|
||||
// digit-select left off from the blanking step above). Now pulse on the single row-select
|
||||
// line for this row - this is what actually makes the row iRow of every digit light up
|
||||
// simultaneously - for a PWM-timed on-period, implementing brightness control.
|
||||
shiftOutForward(~(1<<iRow)); //enable row
|
||||
shiftOutForward(0xFF); //All digit select lines are HIGH
|
||||
shiftOutForward(0); //anything
|
||||
digitalWrite(latchPin, HIGH); //Write shift register to latch
|
||||
digitalWrite(latchPin, LOW);
|
||||
if (!offState)
|
||||
delayMicroseconds(PWMtimeBrightness);
|
||||
else
|
||||
delayMicroseconds(PWM_max-PWMtimeBrightness);
|
||||
|
||||
iRow++;
|
||||
} while(iRow<loopMax);
|
||||
|
||||
//runtim = millis()-runtim;
|
||||
//DPRINT(refresh); DPRINT("/"); DPRINTLN(runtim);
|
||||
yield(); // hand control back to the RTOS/WiFi stack between refresh passes - important since this whole function runs blocking on the main loop, not in an ISR
|
||||
}
|
||||
|
||||
|
||||
// Looks up character c's glyph in the column-major _font[] table (5 bytes per glyph, one
|
||||
// byte per column, each byte's low 7 bits = one dot per row) and converts it into the
|
||||
// row-major representation (row[NUMROWS], one byte per row, one bit per column) that the
|
||||
// row-scanned hardware needs. c is a plain ASCII code (0x00-0xFF), already translated from
|
||||
// the core firmware's digit-buffer code via asciiConvert[] by the caller.
|
||||
void show(byte c){
|
||||
byte loccol[NUMCOLS];
|
||||
for (byte i=0; i < NUMCOLS; i++)
|
||||
loccol[i] = _font[c*NUMCOLS+i];
|
||||
transposePattern(loccol,row);
|
||||
}
|
||||
|
||||
// Transposes a column-major 5x7 glyph (cols[NUMCOLS], one bit per row within each column
|
||||
// byte) into a row-major bitmap (rows[NUMROWS], one bit per column within each row byte).
|
||||
// Each output row byte ends up with its NUMCOLS=5 column bits packed into bits [2..6]
|
||||
// (via the final "<< (7-NUMCOLS)" shift), bit0/bit1/bit7 left as 0 - this alignment must
|
||||
// match how those bits are wired through the 74HC595 chain to the physical column drivers.
|
||||
// When _upsidedown is set, both the row scan direction (cmask shifted right instead of
|
||||
// left, starting from bit6 instead of bit0) and the column read order are reversed, giving
|
||||
// a full 180-degree flip for tubes mounted upside down (see _upsidedown declaration above -
|
||||
// currently unused/dead in this project, always false).
|
||||
void transposePattern(byte cols[], volatile byte rows[]){
|
||||
byte cmask;
|
||||
cmask = (_upsidedown ? 0x40 : 1);
|
||||
for (byte r=0; r < NUMROWS; r++) {
|
||||
rows[r] = 0;
|
||||
for (byte c=0; c < NUMCOLS; c++) {
|
||||
if (cmask&cols[(_upsidedown ? (NUMCOLS-c-1) : c)]) rows[r] |= 1;
|
||||
rows[r] = rows[r] << 1;
|
||||
}
|
||||
if (_upsidedown) cmask = cmask >> 1;
|
||||
else cmask = cmask << 1;
|
||||
rows[r] = rows[r] << (7-NUMCOLS);
|
||||
}
|
||||
}
|
||||
|
||||
// Debug helper: prints one byte as 8 ASCII '0'/'1' characters (MSB first).
|
||||
void bPrint(byte in) {
|
||||
for (int i=7;i>=0;i--) {
|
||||
boolean b = in & (1<<i);
|
||||
DPRINT(b ? '1' : '0');
|
||||
}
|
||||
}
|
||||
|
||||
// Debug helper: dumps the current dat[][] buffer (the exact bitmap about to be scanned out
|
||||
// by writeDisplay2()) to Serial as ASCII art, for bring-up/troubleshooting. Not called in
|
||||
// normal operation (see the commented-out call at the end of writeDisplaySingle() below).
|
||||
void printBitmap() { //print bitmap for testing
|
||||
for (int j=0;j<NUMROWS;j++) {
|
||||
for (int i=0;i<NUMDIGITS;i++) {
|
||||
bPrint(dat[i][j]); Serial.print(" ");
|
||||
}
|
||||
DPRINTLN(" ");
|
||||
}
|
||||
DPRINTLN("__________________________________");
|
||||
}
|
||||
|
||||
|
||||
// No-op: unlike several other drivers, VQC10 has no dedicated "all outputs off" hardware
|
||||
// shortcut here - blanking a digit position is instead achieved through the normal buffer
|
||||
// path by writing digit-buffer code 10 (BLANK) into digit[]/newDigit[], which asciiConvert[]
|
||||
// maps to a space character (empty glyph). The core .ino no longer calls clearTubes() from
|
||||
// anywhere at all (its former caller, disableDisplay(), has been removed entirely as dead code),
|
||||
// so this stub is purely for interface compliance at this point.
|
||||
void clearTubes() {}
|
||||
|
||||
// Blocking, one-shot render: rebuilds oldDat[]/newDat[] (bitmaps for the outgoing and
|
||||
// incoming character of every digit position) from the shared digit[]/newDigit[] buffers,
|
||||
// then blends them into dat[] according to each position's animMask[] wipe-animation
|
||||
// progress. dat[] is what writeDisplay2() actually scans out to the hardware - this
|
||||
// function does not touch the hardware itself.
|
||||
void writeDisplaySingle() {
|
||||
char dispChar;
|
||||
// Physical-to-logical digit position remapping: the 8 cells are wired/driven as two
|
||||
// groups of 4 in reverse order (3,2,1,0 then 7,6,5,4), consistent with two physical
|
||||
// VQC10 modules (see CLOCK_71 "2x VQC10 clock") each internally mirrored/chained back to
|
||||
// front relative to the logical left-to-right digit[]/newDigit[] buffer order.
|
||||
byte tubeShift[] = {3,2,1,0,7,6,5,4};
|
||||
byte a;
|
||||
for (int i=0;i<NUMDIGITS;i++) { //generate new line -- NOTE: comment says "new" but this loop actually builds oldDat[] (the OUTGOING/current character) from digit[]; see the next loop for newDat[]/newDigit[]. Comment/variable naming appears swapped from actual old/new roles, though the logic itself is correct (oldDat<-digit[], newDat<-newDigit[] matches the core file's old/new buffer convention).
|
||||
dispChar = digit[tubeShift[i]];
|
||||
if (dispChar<sizeof(asciiConvert)) dispChar = asciiConvert[(byte)dispChar]; // translate digit-buffer code to ASCII via asciiConvert[]; note dispChar is signed char compared against unsigned sizeof() - harmless here since digit-buffer codes are always small non-negative values, but a latent signed/unsigned mismatch
|
||||
show(dispChar);
|
||||
for (int k=0;k<NUMROWS;k++) {
|
||||
oldDat[i][k] = row[k];
|
||||
}
|
||||
if (digitDP[tubeShift[i]]) {
|
||||
show(DPchar); // overlay the decimal-point/separator dot glyph
|
||||
for (int k=0;k<NUMROWS;k++) {
|
||||
oldDat[i][k] |= row[k]; // OR the DP dot pattern onto the character pattern so both render together in the same cell
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i=0;i<NUMDIGITS;i++) { //generate old line -- NOTE: comment says "old" but this loop builds newDat[] (the INCOMING/target character) from newDigit[]; see note on the previous loop.
|
||||
dispChar = newDigit[tubeShift[i]];
|
||||
if (dispChar<sizeof(asciiConvert)) dispChar = asciiConvert[(byte)dispChar];
|
||||
show(dispChar);
|
||||
for (int k=0;k<NUMROWS;k++) {
|
||||
newDat[i][k] = row[k];
|
||||
}
|
||||
if (digitDP[tubeShift[i]]) {
|
||||
show(DPchar);
|
||||
for (int k=0;k<NUMROWS;k++) {
|
||||
newDat[i][k] |= row[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Vertical wipe/scroll transition: blend oldDat[] and newDat[] into dat[] based on
|
||||
// animMask[] (set by the core file's changeDigit() animation state machine, incrementing
|
||||
// roughly 1..19 over the transition - see the VQC10 special-case in that function). a=0
|
||||
// means no animation in progress yet (show old character unchanged); as a increases
|
||||
// (halved and capped to NUMROWS-1=7 dot-rows) the display effectively scrolls the old
|
||||
// character's rows upward out of view while the new character's rows scroll in from the
|
||||
// bottom, row-by-row, producing a smooth vertical wipe rather than an instant character
|
||||
// swap (this is the driver-specific animation timing referenced by the VQC10 special-case
|
||||
// in the core file's changeDigit()).
|
||||
for (int i=0;i<NUMDIGITS;i++) { //combine animation
|
||||
a = animMask[tubeShift[i]]/2; if (a>7) a=7;
|
||||
for (int k=0;k<NUMROWS;k++) {
|
||||
if (a==0) dat[i][k] = oldDat[i][k];
|
||||
else { //Animation!!!
|
||||
if ((a+k)<NUMROWS) {
|
||||
dat[i][k] = oldDat[i][k+a]; // still within old character's remaining (not yet scrolled out) rows
|
||||
}
|
||||
else {
|
||||
dat[i][k] = newDat[i][(k+a)-NUMROWS]; // wrapped past the bottom: pull in rows of the new character instead
|
||||
}
|
||||
} //endif Animation!!
|
||||
} //end for i
|
||||
} //end for
|
||||
//printBitmap();
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in new issue
Block a user