Ulanzi TC001 Clock

good deal on this and it can be flashed with different firmware. on Aliexpress.
Not sure if there are drivers for it or not.
see this YT,

Works with HA, ordered one and will let you know how it goes.

3 Likes

That looks awesome!

I have an easy-to-repeat DIY Matrix LED project.
Project is based on Pixelblaze LED driver and Pixel LED matrix.
It is WiFi but 100% local control and of course, integrated with HE.
Matrix could be any size but I am using 8x64 (2 cascaded 8x32) LED panels.
It capable to display scrolling text and/or static messages. I am using both on
the same marix.

Just in case, here is Pixelblaze:

And here is LED matrix:
https://www.amazon.com/s?k=8x32+led+panel&crid=3EV04X1Q746QG&sprefix=8x32+l%2Caps%2C152&ref=nb_sb_ss_ts-doa-p_3_6

1 Like

Vitaliy_kh, thanks I have a couple pixel blaze units here. I would love to see your project.
Thank you for adding to the discussion.

B

OK, since you already have Pixelblaze units I assume you know how to connect LEDs
(strips and/or matrixes) plus how to configure and run Pixelblaze projects.
For the HE integration you will need to install Pixelblaze driver available through HPM:
image

On the Pixelblaze you will need to install custom patterns.

Here is a code for my Message Board pattern:

/*
  Message Board 2D for 8x64 LED Matrix

  Original Desing is "scrolling text marquee 2D"
  Author   : Jeff Vyduna (https://ngnr.org)
  Bugfixes : Zeb (https://forum.electromage.com/u/zeb)
*/

//------------------------------------------------------

// Number of Messages
var nMessages = 5

//------------------------------------------------------

// Message Display Time (mS)
export var displayTime = 5000

// Message Index
export var msgIdx = 0

// Digit to Display with Message Header
export var msgDigit = array(nMessages)

/*
msgDigit[0] = 500
msgDigit[1] = 90
msgDigit[2] = 60
msgDigit[3] = 432
msgDigit[4] = 81
*/

// Value Thresholds
export var valThreshold = array(nMessages)
for (idx = 0; idx < nMessages; idx++)
{
  valThreshold[idx] = array(2)
}

valThreshold[0][0] = 900  // CO2
valThreshold[0][1] = 1100 // CO2

valThreshold[1][0] = 77   // Tint
valThreshold[1][1] = 80   // Tint

valThreshold[2][0] = 85   // Tout
valThreshold[2][1] = 90   // Tout

valThreshold[3][0] = 50   // Hint
valThreshold[3][1] = 60   // Hint

valThreshold[4][0] = 60   // Hout
valThreshold[4][1] = 80   // Hout

//------------------------------------------------------

// Max Header Size
var headerSize = 6

// Message header length
var msgHeaderLength = 0

// Array of Message Headers
var msgArray = array(nMessages)
for (msg = 0; msg < nMessages; msg++)
{
  msgArray[msg] = array(headerSize)
}

// Pre-defined Message Headers
// (limited to 3 Characters)

// CO2
msgArray[0][0] = 67
msgArray[0][1] = 79
msgArray[0][2] = 50
msgArray[0][3] = 0

// Tint
msgArray[1][0] = 84
msgArray[1][1] = 105
msgArray[1][2] = 110
msgArray[1][3] = 116
msgArray[1][4] = 0

//Tout
msgArray[2][0] = 84
msgArray[2][1] = 111
msgArray[2][2] = 117
msgArray[2][3] = 116
msgArray[2][4] = 0

// Hint
msgArray[3][0] = 72
msgArray[3][1] = 105
msgArray[3][2] = 110
msgArray[3][3] = 116
msgArray[3][4] = 0

//Hex
msgArray[4][0] = 72
msgArray[4][1] = 111
msgArray[4][2] = 117
msgArray[4][3] = 116
msgArray[4][4] = 0

// Number of Characters in Message
var msgLength = 8

// Message Buffer
var msgBuffer = array(msgLength)

//------------------------------------------------------

// Max number of Digit Places
var nDigits = 4

// Array for the converting "displayDecValue" variable
// into String
var convertArray = array(nDigits)

//------------------------------------------------------

//
// Color Control Parameters
//

// Number of LED Colors
var nColors = 3

// Max Number of predefined pixel colors
var colorArraySize = 8

// Predefined Pixel Colors Array
var pixelColors = array(colorArraySize)[nColors] =
[
  // R     G     B
  [0x00, 0x00, 0x00], // Black, Background Color
  [0xff, 0x00, 0x00], // Red
  [0xff, 0x64, 0x00], // Yellow
  [0x00, 0xff, 0x00], // Green
  [0x00, 0xff, 0xff], // Cyan
  [0xff, 0x00, 0x7f], // Purple
  [0x00, 0x00, 0xff], // Blue
  [0xff, 0xff, 0xff]  // White
]

// Message Color Map
var msgColor = array(nMessages)[4] =
[
//Header, Digit Low, Mid, High 
  [7,     3, 2, 1],  // C02
  [4,     3, 2, 1],  // Tint
  [4,     3, 2, 1],  // Tout
  [5,     3, 2, 1],  // Hint
  [5,     3, 2, 1]   // Hout
]

//------------------------------------------------------

/* 
  ASCII Chart

  32      48 0    65 A   74 J    83 S    97  a    106 j    115 s
  33 !    49 1    66 B   75 K    84 T    98  b    107 k    116 t
  34 "    50 2    67 C   76 L    85 U    99  c    108 l    117 u
  35 #    51 3    68 D   77 M    86 V    100 d    109 m    118 v
  36 $    52 4    69 E   78 N    87 W    101 e    110 n    119 w
  37 %    53 5    70 F   79 O    88 X    102 f    111 o    120 x
  38 &    54 6    71 G   80 P    89 Y    103 g    112 p    121 y
  39 '    55 7    72 H   81 Q    90 Z    104 h    113 q    122 z
  40 (    56 8    73 I   82 R            105 i    114 r    
  41 )    57 9                                   
  42 *    58 :                   91 [                      123 {
  43 +    59 ;                   92 \                      124 |
  44 ,    60 <                   93 ]                      125 }
  45 -    61 =                   94 ^                      126 ~
  46 .    62 >                   95 _    
  47 /    63 ?                   96 `                
          64 @                                            
*/

// Define the font's character set bitmap.
// See "Font Implementation" below.
var charRows = 8 // Rows in a character.    1 array per row.
var charCols = 8 // Columns in a character. 1 bit per column.

// Max characters in the font. Must be a multiple of 4.
var fontCharCount = 128
var fontBitmap = array(charRows)
for (row = 0; row < charRows; row++)
{
  fontBitmap[row] = array(fontCharCount / 4)
}

// Global 8x8bit array for storing and fetching characters from fontBitmap
var character = array(charRows)

// Define the 2D matrix display. If your matrix is different dimentions, change
// these to match or use a smaller matrixRows to scale your text height to fill.
var matrixRows = 8
var matrixCols = 64
var renderBuffer = array(matrixRows)
for (row = 0; row < matrixRows; row++)
{
  renderBuffer[row] = array(matrixCols)
}

//
// ***** Frame Rate Timing *****
//

// Accumulates the ms between each beforeRender()
var timer = 0

// Calculate the ms between each left shift of the message across matrix columns
// var colShiftPeriod

export function beforeRender(delta)
{
  // Message Shift Timer
  timer += delta
  msgShiftPeriod = displayTime
  //colShiftPeriod = scrollSpeed(speed)
  if (timer > msgShiftPeriod)
  { 
    timer -= msgShiftPeriod
    msgHeaderLength     = 0
    adjustMessageLength = 0
    messageColPointer   = 0
    
    if (msgIdx < (nMessages - 1))
    {
      msgIdx++    
    }
    else
    {
      msgIdx = 0
    }
    
    updateDisplayValue()
  }
}

//
// ***** Display Pixel Buffer *****
//

export function render2D(index, x, y)
{
  // y is in world units of 0...1 where (0,0) is the top-left and y is +↓
  row = floor(y * matrixRows)
  //row = clamp(floor(y * matrixRows), 0, matrixRows - 1)

  // The column to render is like the row, but physical column 0 (the leftmost)
  // starts bufferPointer columns into the renderBuffer.
  col = (floor(x * matrixCols) + bufferPointer) % matrixCols

  // Exr\traxt Pixel Color from Render Buffer  
  pixelRGB = renderBuffer[row][col]
  
  // Display Pixel
  unpackRGB(pixelRGB)
  rgb((xR / 255), (xG / 255), (xB / 255))
}

//
// ***** Functions *****
//

// Update Display Value
function updateDisplayValue()
{
  clearBuffers()
  for (i = 0; i < headerSize; i++)
  {
    if (0 != msgArray[msgIdx][i])
    {
      msgBuffer[i] = msgArray[msgIdx][i]
      msgHeaderLength++
    }
    else
    {
      break
    }
  }
    
  convertDecimalValue(msgDigit[msgIdx])  

  for (Idx = 0; Idx < (msgLength * charCols); Idx++)
  {
    loadNextCol()
  }
}

// Clear Message and Render Buffers
function clearBuffers()
{
  // Clear Message Buffer
  for (i = 0; i < msgLength; i++)
  {
    msgBuffer[i] = 32
  }

  // Clear Render Buffer
  for (row = 0; row < matrixRows; row++)
  {
    for (col = 0; col < matrixCols; col++)
    {
      renderBuffer[row][col] = 0
    }
  }
}

// Pack 8-bit R, G, B values into
// single 16.16 Fixed Point RGB Variable
function packRGB(R, G, B)
{
  RGB = ((R * 256) + G + (B / 256))
  
  return RGB
}

// Unpack 8-bit RGB Values from
// single 16.16 Fixed Point RGB Variable
function unpackRGB(RGB)
{
  xR =  ((RGB / 256) & 0xFF) 
  xG =   (RGB & 0xFF)
  xB = (((RGB * 256) + 0.5) & 0xFF)
}

// Adjust Message Length according to the
// number of digits of Decimal Value
var adjustMessageLength = 0

// Convert Decimal Value to be displayed
// into ASCII Decimal String
function  convertDecimalValue(DecValue)
{
  adjustMessageLength = 0
  
  // Negative values are ignored
  if (DecValue > 0)
  {
    x = floor(abs(DecValue))
  
    convertInteger(x)

    // Check Fraction Portion
    if (frac(DecValue) != 0)
    {
      msgBuffer[msgHeaderLength + 1 + adjustMessageLength] = 46
      adjustMessageLength++  
      x = floor((frac(abs(DecValue)) * 100) + 0.5)
      convertInteger(x)
    }
  }
}

// Convert Integer variable to string
function convertInteger(Int)
{
  x = Int
  
  // Convert Integer to String
  for (i = 0; i < nDigits; i++)
  {
    convertArray[nDigits - 1 - i] = ((floor(x) % 10) + 48)
    x = x /10
  }
  
  // Add String to message array
  // and adjust message array length
  skipZeroChar = true
  
  for (i = 0; i < nDigits; i++)
  {
    if ((48 != convertArray[i]) || (false == skipZeroChar))
    {
      skipZeroChar = false
      msgBuffer[msgHeaderLength + 1 + adjustMessageLength] = convertArray[i]
      adjustMessageLength++
    }
  }
}

// When we render the renderBuffer, we start by loading the leftmost column of
// the matrix from the `bufferPointer` column in the renderBuffer.
var bufferPointer = 0 

/*
  E.g.: 8x8 matrix, rendering halfway through "AC": Right side of A, left of C

          renderbuffer[r][c]             Renders as:

  `bufferPointer` == 4 means leftmost column is here, and wraps around to 3
                     ↓       
       col = 0 1 2 3 4 5 6 7                0 1 2 3 4 5 6 7
    row = 0  . . . 1 1 . . .             0  1 . . . . . . 1  
          1  . . 1 1 1 1 . .             1  1 1 . . . . 1 1  
          2  . 1 1 . . 1 1 .             2  . 1 1 . . 1 1 .  
          3  . 1 1 . . 1 1 .             3  . 1 1 . . 1 1 .  
          4  . 1 1 . 1 1 1 .             4  1 1 1 . . 1 1 .  
          4  . . 1 1 . 1 1 .             4  . 1 1 . . . 1 1  
          5  . . . 1 . 1 1 .             5  . 1 1 . . . . 1  
          7  . . . . . . . .             7  . . . . . . . .  
                     ↑
                     This column will be replaced with the next column of "C",
                       ↑ then we'll advance `bufferPointer`
                     
Each element is a 16.16 fixed point number, so you could decide to pack HSV or 
RGB info into each byte, but this example is monochrome so each element just 
stores a 0 or 1, making rendering as simple as:

    if (renderBuffer[row][col]) hsv(0,0,1)
*/

// The next column of the overall message to load
var messageColPointer = 0

// Load the next column from `message` into `renderBuffer` at `bufferPointer`
function loadNextCol()
{
  messageIdx = floor(messageColPointer / charCols)
  charIndex  = msgBuffer[messageIdx]

  // loads global `character` with ASCII charIndex
  fetchCharacter(charIndex)
  
  colIndex = messageColPointer % charCols
  
  // Select Word Color
  if (0 == colIndex)
  {
    // Header Color
    if (messageIdx < msgHeaderLength)
    {
      wordColor = msgColor[msgIdx][0]
    }
    // Digit Color
    else
    {
      if (msgDigit[msgIdx] <  valThreshold[msgIdx][0])
      {
        wordColor = msgColor[msgIdx][1]
      }
      else if (msgDigit[msgIdx] <  valThreshold[msgIdx][1])
      {
        wordColor = msgColor[msgIdx][2]
      }
      else
      {
        wordColor = msgColor[msgIdx][3]
      }
    }
  }
  
//  wordColor = 3

  for (row = 0; row < charRows; row++)
  {
    bit = (((character[row] << colIndex) & 0b10000000) == 0b10000000)

    if (0 == bit)
    {
      // Background Pixel Collor
      // from Color Array
      colorIndex = 0
    }
    else
    {
      colorIndex = wordColor
    }

    R = pixelColors[colorIndex][0]
    G = pixelColors[colorIndex][1]
    B = pixelColors[colorIndex][2]

    RGB = packRGB(R, G, B)
    renderBuffer[row][bufferPointer] = RGB
  }

  messageCols = (msgLength + adjustMessageLength) * charCols
  bufferPointer = (bufferPointer + 1) % matrixCols
  messageColPointer = (messageColPointer + 1) % messageCols
}

/*
  Font Implementation
  
  Pixelblaze currently supports up to 64 arrays with 2048 array elements.
  
  To store a character set of 8x8 bit characters, we use 8 arrays, 
  one for each row.
  
  Four 8-bit maps are packed into each 32 bit array element. This makes the
  bitwise code a little hard to follow, but uses memory efficiently. The 8 most
  significant bits are referred to as "bank 0"; the next eight bits just left of
  the binary point are "bank 1", etc.
  
  Here's the scheme used to store the font bitmap. A period is a zero.
  
    ASCII character  A        B        C        D        E
        `charIndex`  65       66       67       68       69
      array element  [16]     [16]     [16]     [16]     [17]
               bank  0        1        2        3        0      
      fontBitmap[0]  ..11.... 111111.. ..1111.. 11111... 1111111.
      fontBitmap[1]  .1111... .11..11. .11..11. .11.11.. .11...1.
      fontBitmap[2]  11..11.. .11..11. 11...... .11..11. .11.1...
      fontBitmap[3]  11..11.. .11111.. 11...... .11..11. .1111...
      fontBitmap[4]  111111.. .11..11. 11...... .11..11. .11.1...
      fontBitmap[5]  11..11.. .11..11. .11..11. .11.11.. .11...1.
      fontBitmap[6]  11..11.. 111111.. ..1111.. 11111... 1111111.
      fontBitmap[7]  ........ ........ ........ ........ ........
  
  charIndex 0..31 (traditionally the ASCII control characters) are left 
  blank for user-defined custom characters.
*/

/*
  Font and character functions

  The storeCharacter functions take the character index (< `fontCharCount`) and 
  8 rows of 8 bits. Each row is a byte representing 8 bits of on/off bitmap data
  to become the pixels of a character. Therefore, this implementation is 
  currently tightly coupled to 8-bit wide characters.
*/

/*
  At character index `charIndex`, store 8 bytes of row data specified as 
  sequential arguments r0-r7. This allows us to easily use the public domain 
  font specified as comma-delimited hex bytes at:
  
  https://github.com/rene-d/fontino/blob/master/font8x8_ib8x8u.ino
*/
function storeCharacter(charIndex, r0, r1, r2, r3, r4, r5, r6, r7)
{
  element = floor(charIndex / 4)
  bank = charIndex % 4
  packByte(0, element, bank, r0)
  packByte(1, element, bank, r1)
  packByte(2, element, bank, r2)
  packByte(3, element, bank, r3)
  packByte(4, element, bank, r4)
  packByte(5, element, bank, r5)
  packByte(6, element, bank, r6)
  packByte(7, element, bank, r7)
}

/*
  This alternate style stores the character using the 8 row global array named 
  `character`. It could be useful for storing sprites after transformations to
  create animations.
*/
function storeCharacter2(charIndex)
{
  element = floor(charIndex / 4)
  bank = charIndex % 4
  for (var row = 0; row < charRows; row++)
  {
    packByte(row, element, bank, character[row])
  }
}

// Loads the global `character` from the specified charIndex
function fetchCharacter(charIndex)
{
  element = floor(charIndex / 4)
  bank = charIndex % 4
  for (var row = 0; row < charRows; row++)
  {
    character[row] = unpackByte(row, element, bank)
  }
}

/*
  For a given row of a font's pixel data (fontBitmap[row]), there's a 
  (fontCharCount / 4) element long array that holds 32 bits per array element. 
  Thinking of each array element as a 4-byte word, the "bank" (0..3) specifies 
  which set of 8 bits we're storing for a particular character. Characters are 
  referred to by their charIndex (ASCII number), so:
  bank 0 in elements 0, 1, & 2 store the data for characters 0, 4, 8, etc;
  Bank 1 in elements 0, 1, & 2 store the data for characters 1, 5, 9, etc.
  
  The method below is used because the bitwise operators only work on 
  the top 16 bits.
*/
var byteHolder = array(4)
function packByte(row, element, bank, byte)
{
  original = fontBitmap[row][element]
  
  // Load a 4-element array with the individual bytes in this 32 bit 'word'
  for (_bank = 0; _bank < 4; _bank++)
  {
    byteHolder[_bank] = (((original << (_bank * 8)) & 0xFF00) >> 8) & 0xFF
  }
  
  // Override the 8 bits we're trying to store
  byteHolder[bank] = byte 
  
  // Reassemble the 32 bit 'word'
  fontBitmap[row][element] = (byteHolder[0] << 8) 
                            + byteHolder[1] 
                           + (byteHolder[2] >> 8) 
                           + (byteHolder[3] >> 16)
}

// Inverse of packByte()
function unpackByte(row, element, bank)
{
  word = fontBitmap[row][element]
  if (bank > 1)
  {
    byte = (word << (8 * (bank - 1)))
  }
  else if (bank == 0)
  {
    byte = (word >> 8)
  }
  else
  {
    byte = word
  }

  // Zero out all but the 8 bits left of the binary point
  return (byte & 0xFF)
}

/* 
  Font Data

  Public domain, courtesy of
  https://github.com/rene-d/fontino/blob/master/font8x8_ib8x8u.ino
*/
storeCharacter( 32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)  // 0x20 (space)
storeCharacter( 33, 0x30, 0x78, 0x78, 0x30, 0x30, 0x00, 0x30, 0x00)  // 0x21 (exclam)
storeCharacter( 34, 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00)  // 0x22 (quotedbl)
storeCharacter( 35, 0x6c, 0x6c, 0xfe, 0x6c, 0xfe, 0x6c, 0x6c, 0x00)  // 0x23 (numbersign)
storeCharacter( 36, 0x30, 0x7c, 0xc0, 0x78, 0x0c, 0xf8, 0x30, 0x00)  // 0x24 (dollar)
storeCharacter( 37, 0x00, 0xc6, 0xcc, 0x18, 0x30, 0x66, 0xc6, 0x00)  // 0x25 (percent)
storeCharacter( 38, 0x38, 0x6c, 0x38, 0x76, 0xdc, 0xcc, 0x76, 0x00)  // 0x26 (ampersand)
storeCharacter( 39, 0x60, 0x60, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00)  // 0x27 (quotesingle)
storeCharacter( 40, 0x18, 0x30, 0x60, 0x60, 0x60, 0x30, 0x18, 0x00)  // 0x28 (parenleft)
storeCharacter( 41, 0x60, 0x30, 0x18, 0x18, 0x18, 0x30, 0x60, 0x00)  // 0x29 (parenright)
storeCharacter( 42, 0x00, 0x66, 0x3c, 0xff, 0x3c, 0x66, 0x00, 0x00)  // 0x2a (asterisk)
storeCharacter( 43, 0x00, 0x30, 0x30, 0xfc, 0x30, 0x30, 0x00, 0x00)  // 0x2b (plus)
storeCharacter( 44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x60)  // 0x2c (comma)
storeCharacter( 45, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00)  // 0x2d (hyphen)
storeCharacter( 46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00)  // 0x2e (period)
storeCharacter( 47, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0x80, 0x00)  // 0x2f (slash)
storeCharacter( 48, 0x7c, 0xc6, 0xce, 0xde, 0xf6, 0xe6, 0x7c, 0x00)  // 0x30 (zero)
storeCharacter( 49, 0x30, 0x70, 0x30, 0x30, 0x30, 0x30, 0xfc, 0x00)  // 0x31 (one)
storeCharacter( 50, 0x78, 0xcc, 0x0c, 0x38, 0x60, 0xc4, 0xfc, 0x00)  // 0x32 (two)
storeCharacter( 51, 0x78, 0xcc, 0x0c, 0x38, 0x0c, 0xcc, 0x78, 0x00)  // 0x33 (three)
storeCharacter( 52, 0x1c, 0x3c, 0x6c, 0xcc, 0xfe, 0x0c, 0x1e, 0x00)  // 0x34 (four)
storeCharacter( 53, 0xfc, 0xc0, 0xf8, 0x0c, 0x0c, 0xcc, 0x78, 0x00)  // 0x35 (five)
storeCharacter( 54, 0x38, 0x60, 0xc0, 0xf8, 0xcc, 0xcc, 0x78, 0x00)  // 0x36 (six)
storeCharacter( 55, 0xfc, 0xcc, 0x0c, 0x18, 0x30, 0x30, 0x30, 0x00)  // 0x37 (seven)
storeCharacter( 56, 0x78, 0xcc, 0xcc, 0x78, 0xcc, 0xcc, 0x78, 0x00)  // 0x38 (eight)
storeCharacter( 57, 0x78, 0xcc, 0xcc, 0x7c, 0x0c, 0x18, 0x70, 0x00)  // 0x39 (nine)
storeCharacter( 58, 0x00, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00)  // 0x3a (colon)
storeCharacter( 59, 0x00, 0x30, 0x30, 0x00, 0x30, 0x30, 0x60, 0x00)  // 0x3b (semicolon)
storeCharacter( 60, 0x18, 0x30, 0x60, 0xc0, 0x60, 0x30, 0x18, 0x00)  // 0x3c (less)
storeCharacter( 61, 0x00, 0x00, 0xfc, 0x00, 0x00, 0xfc, 0x00, 0x00)  // 0x3d (equal)
storeCharacter( 62, 0x60, 0x30, 0x18, 0x0c, 0x18, 0x30, 0x60, 0x00)  // 0x3e (greater)
storeCharacter( 63, 0x78, 0xcc, 0x0c, 0x18, 0x30, 0x00, 0x30, 0x00)  // 0x3f (question)
storeCharacter( 64, 0x7c, 0xc6, 0xde, 0xde, 0xde, 0xc0, 0x78, 0x00)  // 0x40 (at)
storeCharacter( 65, 0x30, 0x78, 0xcc, 0xcc, 0xfc, 0xcc, 0xcc, 0x00)  // 0x41 (A)
storeCharacter( 66, 0xfc, 0x66, 0x66, 0x7c, 0x66, 0x66, 0xfc, 0x00)  // 0x42 (B)
storeCharacter( 67, 0x3c, 0x66, 0xc0, 0xc0, 0xc0, 0x66, 0x3c, 0x00)  // 0x43 (C)
storeCharacter( 68, 0xf8, 0x6c, 0x66, 0x66, 0x66, 0x6c, 0xf8, 0x00)  // 0x44 (D)
storeCharacter( 69, 0xfe, 0x62, 0x68, 0x78, 0x68, 0x62, 0xfe, 0x00)  // 0x45 (E)
storeCharacter( 70, 0xfe, 0x62, 0x68, 0x78, 0x68, 0x60, 0xf0, 0x00)  // 0x46 (F)
storeCharacter( 71, 0x3c, 0x66, 0xc0, 0xc0, 0xce, 0x66, 0x3e, 0x00)  // 0x47 (G)
storeCharacter( 72, 0xcc, 0xcc, 0xcc, 0xfc, 0xcc, 0xcc, 0xcc, 0x00)  // 0x48 (H)
storeCharacter( 73, 0x78, 0x30, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00)  // 0x49 (I)
storeCharacter( 74, 0x1e, 0x0c, 0x0c, 0x0c, 0xcc, 0xcc, 0x78, 0x00)  // 0x4a (J)
storeCharacter( 75, 0xe6, 0x66, 0x6c, 0x78, 0x6c, 0x66, 0xe6, 0x00)  // 0x4b (K)
storeCharacter( 76, 0xf0, 0x60, 0x60, 0x60, 0x62, 0x66, 0xfe, 0x00)  // 0x4c (L)
storeCharacter( 77, 0xc6, 0xee, 0xfe, 0xfe, 0xd6, 0xc6, 0xc6, 0x00)  // 0x4d (M)
storeCharacter( 78, 0xc6, 0xe6, 0xf6, 0xde, 0xce, 0xc6, 0xc6, 0x00)  // 0x4e (N)
storeCharacter( 79, 0x38, 0x6c, 0xc6, 0xc6, 0xc6, 0x6c, 0x38, 0x00)  // 0x4f (O)
storeCharacter( 80, 0xfc, 0x66, 0x66, 0x7c, 0x60, 0x60, 0xf0, 0x00)  // 0x50 (P)
storeCharacter( 81, 0x78, 0xcc, 0xcc, 0xcc, 0xdc, 0x78, 0x1c, 0x00)  // 0x51 (Q)
storeCharacter( 82, 0xfc, 0x66, 0x66, 0x7c, 0x6c, 0x66, 0xe6, 0x00)  // 0x52 (R)
storeCharacter( 83, 0x78, 0xcc, 0xe0, 0x70, 0x1c, 0xcc, 0x78, 0x00)  // 0x53 (S)
storeCharacter( 84, 0xfc, 0xb4, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00)  // 0x54 (T)
storeCharacter( 85, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xfc, 0x00)  // 0x55 (U)
storeCharacter( 86, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x78, 0x30, 0x00)  // 0x56 (V)
storeCharacter( 87, 0xc6, 0xc6, 0xc6, 0xd6, 0xfe, 0xee, 0xc6, 0x00)  // 0x57 (W)
storeCharacter( 88, 0xc6, 0xc6, 0x6c, 0x38, 0x38, 0x6c, 0xc6, 0x00)  // 0x58 (X)
storeCharacter( 89, 0xcc, 0xcc, 0xcc, 0x78, 0x30, 0x30, 0x78, 0x00)  // 0x59 (Y)
storeCharacter( 90, 0xfe, 0xc6, 0x8c, 0x18, 0x32, 0x66, 0xfe, 0x00)  // 0x5a (Z)
storeCharacter( 91, 0x78, 0x60, 0x60, 0x60, 0x60, 0x60, 0x78, 0x00)  // 0x5b (bracketleft)
storeCharacter( 92, 0xc0, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x02, 0x00)  // 0x5c (backslash)
storeCharacter( 93, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x78, 0x00)  // 0x5d (bracketright)
storeCharacter( 94, 0x10, 0x38, 0x6c, 0xc6, 0x00, 0x00, 0x00, 0x00)  // 0x5e (asciicircum)
storeCharacter( 95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff)  // 0x5f (underscore)
storeCharacter( 96, 0x30, 0x30, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00)  // 0x60 (grave)
storeCharacter( 97, 0x00, 0x00, 0x78, 0x0c, 0x7c, 0xcc, 0x76, 0x00)  // 0x61 (a)
storeCharacter( 98, 0xe0, 0x60, 0x60, 0x7c, 0x66, 0x66, 0xdc, 0x00)  // 0x62 (b)
storeCharacter( 99, 0x00, 0x00, 0x78, 0xcc, 0xc0, 0xcc, 0x78, 0x00)  // 0x63 (c)
storeCharacter(100, 0x1c, 0x0c, 0x0c, 0x7c, 0xcc, 0xcc, 0x76, 0x00)  // 0x64 (d)
storeCharacter(101, 0x00, 0x00, 0x78, 0xcc, 0xfc, 0xc0, 0x78, 0x00)  // 0x65 (e)
storeCharacter(102, 0x38, 0x6c, 0x60, 0xf0, 0x60, 0x60, 0xf0, 0x00)  // 0x66 (f)
storeCharacter(103, 0x00, 0x00, 0x76, 0xcc, 0xcc, 0x7c, 0x0c, 0xf8)  // 0x67 (g)
storeCharacter(104, 0xe0, 0x60, 0x6c, 0x76, 0x66, 0x66, 0xe6, 0x00)  // 0x68 (h)
storeCharacter(105, 0x30, 0x00, 0x70, 0x30, 0x30, 0x30, 0x78, 0x00)  // 0x69 (i)
storeCharacter(106, 0x0c, 0x00, 0x0c, 0x0c, 0x0c, 0xcc, 0xcc, 0x78)  // 0x6a (j)
storeCharacter(107, 0xe0, 0x60, 0x66, 0x6c, 0x78, 0x6c, 0xe6, 0x00)  // 0x6b (k)
storeCharacter(108, 0x70, 0x30, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00)  // 0x6c (l)
storeCharacter(109, 0x00, 0x00, 0xcc, 0xfe, 0xfe, 0xd6, 0xc6, 0x00)  // 0x6d (m)
storeCharacter(110, 0x00, 0x00, 0xf8, 0xcc, 0xcc, 0xcc, 0xcc, 0x00)  // 0x6e (n)
storeCharacter(111, 0x00, 0x00, 0x78, 0xcc, 0xcc, 0xcc, 0x78, 0x00)  // 0x6f (o)
storeCharacter(112, 0x00, 0x00, 0xdc, 0x66, 0x66, 0x7c, 0x60, 0xf0)  // 0x70 (p)
storeCharacter(113, 0x00, 0x00, 0x76, 0xcc, 0xcc, 0x7c, 0x0c, 0x1e)  // 0x71 (q)
storeCharacter(114, 0x00, 0x00, 0xdc, 0x76, 0x66, 0x60, 0xf0, 0x00)  // 0x72 (r)
storeCharacter(115, 0x00, 0x00, 0x7c, 0xc0, 0x78, 0x0c, 0xf8, 0x00)  // 0x73 (s)
storeCharacter(116, 0x10, 0x30, 0x7c, 0x30, 0x30, 0x34, 0x18, 0x00)  // 0x74 (t)
storeCharacter(117, 0x00, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0x76, 0x00)  // 0x75 (u)
storeCharacter(118, 0x00, 0x00, 0xcc, 0xcc, 0xcc, 0x78, 0x30, 0x00)  // 0x76 (v)
storeCharacter(119, 0x00, 0x00, 0xc6, 0xd6, 0xfe, 0xfe, 0x6c, 0x00)  // 0x77 (w)
storeCharacter(120, 0x00, 0x00, 0xc6, 0x6c, 0x38, 0x6c, 0xc6, 0x00)  // 0x78 (x)
storeCharacter(121, 0x00, 0x00, 0xcc, 0xcc, 0xcc, 0x7c, 0x0c, 0xf8)  // 0x79 (y)
storeCharacter(122, 0x00, 0x00, 0xfc, 0x98, 0x30, 0x64, 0xfc, 0x00)  // 0x7a (z)
storeCharacter(123, 0x1c, 0x30, 0x30, 0xe0, 0x30, 0x30, 0x1c, 0x00)  // 0x7b (braceleft)
storeCharacter(124, 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00)  // 0x7c (bar)
storeCharacter(125, 0xe0, 0x30, 0x30, 0x1c, 0x30, 0x30, 0xe0, 0x00)  // 0x7d (braceright)
storeCharacter(126, 0x76, 0xdc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)  // 0x7e (asciitilde)


// Other user-defined custom characters

// ASCII 63 is the question mark - here's an alternative from the 
// Sinclair ZX81 font, stored in custom slot 30
storeCharacter(30,
  0b00000000,
  0b00111100,
  0b01000010,
  0b00000100,
  0b00001000,
  0b00000000,
  0b00001000,
  0b00000000
)

// This demonstrates copying the character, altering it, then storing it in the 
// next slot. You could use this for programmatic animation.
fetchCharacter(30)
character[7] = 0b00001000
storeCharacter2(31)

And here is a code for Scrolling Text:

/*
  Scrolling text marquee 2D
  
  This pattern animates ASCII characters scrolling across an LED matrix.
  
    Demo: https://youtu.be/668eQjiqSRQ
  
  The default settings work well with the $14 8x8 matrix sold here:
  
    https://www.tindie.com/products/electromage/electromage-8x8-led-matrix/
  
  Use the mapper to define how the matrix is wired (for example, zig-zag). 
  The 8x8 grid above works with the default "Matrix" example on the Mapper tab.
  
  With no map and just a 1D strip, you can use this to light paint text:
  
    https://photos.app.goo.gl/vU2BQsP6V84Zr6Df7
  
  Author   : Jeff Vyduna (https://ngnr.org)
  Bugfixes : Zeb (https://forum.electromage.com/u/zeb)
*/

// Character's Scrolling Speed (Ms)
export var speed = 40

//
// Select Color Mode
// 0 - Each Character could be assigned individial color
//     by putting Color Index in the messageCharRGB[] array
//
// 1 - Color for each Word cycles through the Collor Array
//     (Assuming each Word is separated by Space character
//
// 2 - Color for each Message cycles through the Collor Array
//
export var selectColorMode = 1

// Max Message Buffer Size (N Charecters)
var bufferSize = 50

// Actual Current Message Length (N Charecters)
export var messageLength = 22

// Holds actuall Scrolling Message
// By default Message Array is Zeroed
export var message = array(bufferSize)

// Holds Character Color Index
var messageCharRGB = array(bufferSize)

// message : Welcome to Boho Place
message[0]  = 87;   // W
message[1]  = 101;  // e
message[2]  = 108;  // l
message[3]  = 99;   // c
message[4]  = 111;  // o
message[5]  = 109;  // m
message[6]  = 101;  // e

message[7]  = 32;   // Space

message[8]  = 116;  // t
message[9]  = 111;  // o

message[10] = 32;   // Space

message[11] = 66;   // B
message[12] = 111;  // o
message[13] = 104;  // h 
message[14] = 111;  // o 

message[15] = 32;   // Space

message[16] = 80;   // P
message[17] = 108;  // l
message[18] = 97;   // a
message[19] = 99;   // c
message[20] = 101;  // e

message[21]  = 32;   // Space

// Charachter's Colors
messageCharRGB[0]  = 1
messageCharRGB[1]  = 2
messageCharRGB[2]  = 3
messageCharRGB[3]  = 4
messageCharRGB[4]  = 5
messageCharRGB[5]  = 6
messageCharRGB[6]  = 7

messageCharRGB[7]  = 0

messageCharRGB[8]  = 1
messageCharRGB[9]  = 2

messageCharRGB[10] = 0

messageCharRGB[11] = 3
messageCharRGB[12] = 4
messageCharRGB[13] = 5
messageCharRGB[14] = 6

messageCharRGB[15] = 0

messageCharRGB[16] = 7
messageCharRGB[17] = 1
messageCharRGB[18] = 2
messageCharRGB[19] = 3
messageCharRGB[20] = 4

messageCharRGB[21] = 0

//
// Color Control Parameters
//

// Number of LED Colors
var nColors = 3

// Max Number of predefined pixel colors
var colorArraySize = 8

// Predefined Pixel Colors Array
var pixelColors = array(colorArraySize)[nColors] =
[
  // R     G     B
  [0x00, 0x00, 0x00], // Black, Background Color
  [0xff, 0x00, 0x00], // Red
  [0xff, 0x64, 0x00], // Yellow
  [0x00, 0xff, 0x00], // Green
  [0x00, 0xff, 0xff], // Cyan
  [0xff, 0x00, 0x7f], // Purple
  [0x00, 0x00, 0xff], // Blue
  [0xff, 0xff, 0xff]  // White
]

/* 
  ASCII Chart

  32      48 0    65 A   74 J    83 S    97  a    106 j    115 s
  33 !    49 1    66 B   75 K    84 T    98  b    107 k    116 t
  34 "    50 2    67 C   76 L    85 U    99  c    108 l    117 u
  35 #    51 3    68 D   77 M    86 V    100 d    109 m    118 v
  36 $    52 4    69 E   78 N    87 W    101 e    110 n    119 w
  37 %    53 5    70 F   79 O    88 X    102 f    111 o    120 x
  38 &    54 6    71 G   80 P    89 Y    103 g    112 p    121 y
  39 '    55 7    72 H   81 Q    90 Z    104 h    113 q    122 z
  40 (    56 8    73 I   82 R            105 i    114 r    
  41 )    57 9                                   
  42 *    58 :                   91 [                      123 {
  43 +    59 ;                   92 \                      124 |
  44 ,    60 <                   93 ]                      125 }
  45 -    61 =                   94 ^                      126 ~
  46 .    62 >                   95 _    
  47 /    63 ?                   96 `                
          64 @                                            
*/


// Define the font's character set bitmap.
// See "Font Implementation" below.
var charRows = 8 // Rows in a character.    1 array per row.
var charCols = 8 // Columns in a character. 1 bit per column.

// Max characters in the font. Must be a multiple of 4.
var fontCharCount = 128
var fontBitmap = array(charRows)
for (row = 0; row < charRows; row++)
{
  fontBitmap[row] = array(fontCharCount / 4)
}

// Global 8x8bit array for storing and fetching characters from fontBitmap
var character = array(charRows)

// Define the 2D matrix display. If your matrix is different dimentions, change
// these to match or use a smaller matrixRows to scale your text height to fill.
var matrixRows = 8
var matrixCols = 64
var renderBuffer = array(matrixRows)
for (row = 0; row < matrixRows; row++)
{
  renderBuffer[row] = array(matrixCols)
}

//
// ***** Frame Rate Timing *****
//

// Accumulates the ms between each beforeRender()
var timer = 0

// Calculate the ms between each left shift of the message across matrix columns
var colShiftPeriod

export function beforeRender(delta)
{
  timer += delta
  colShiftPeriod = speed
  //colShiftPeriod = scrollSpeed(speed)
  if (timer > colShiftPeriod)
  { 
    timer -= colShiftPeriod
    // Shift and load a new column every colShiftPeriod (mS)    
    loadNextCol()
  }
}

//
// ***** Display Pixel Buffer *****
//

export function render2D(index, x, y)
{
  // y is in world units of 0...1 where (0,0) is the top-left and y is +↓
  row = floor(y * matrixRows)
  //row = clamp(floor(y * matrixRows), 0, matrixRows - 1)

  // The column to render is like the row, but physical column 0 (the leftmost)
  // starts bufferPointer columns into the renderBuffer.
  col = (floor(x * matrixCols) + bufferPointer) % matrixCols

  // Exr\traxt Pixel Color from Render Buffer  
  pixelRGB = renderBuffer[row][col]
  
  // Display Pixel
  unpackRGB(pixelRGB)
  rgb((xR / 255), (xG / 255), (xB / 255))
}

//
// ***** Functions *****
//

// Pack 8-bit R, G, B values into
// single 16.16 Fixed Point RGB Variable
function packRGB(R, G, B)
{
  RGB = ((R * 256) + G + (B / 256))
  
  return RGB
}

// Unpack 8-bit RGB Values from
// single 16.16 Fixed Point RGB Variable
function unpackRGB(RGB)
{
  xR = ((RGB / 256) & 0xFF) 
  xG =  (RGB & 0xFF)
  xB = ((RGB * 256 + 0.5) & 0xFF)
}

// When we render the renderBuffer, we start by loading the leftmost column of
// the matrix from the `bufferPointer` column in the renderBuffer.
var bufferPointer = 0 

/*
  E.g.: 8x8 matrix, rendering halfway through "AC": Right side of A, left of C

          renderbuffer[r][c]             Renders as:

  `bufferPointer` == 4 means leftmost column is here, and wraps around to 3
                     ↓       
       col = 0 1 2 3 4 5 6 7                0 1 2 3 4 5 6 7
    row = 0  . . . 1 1 . . .             0  1 . . . . . . 1  
          1  . . 1 1 1 1 . .             1  1 1 . . . . 1 1  
          2  . 1 1 . . 1 1 .             2  . 1 1 . . 1 1 .  
          3  . 1 1 . . 1 1 .             3  . 1 1 . . 1 1 .  
          4  . 1 1 . 1 1 1 .             4  1 1 1 . . 1 1 .  
          4  . . 1 1 . 1 1 .             4  . 1 1 . . . 1 1  
          5  . . . 1 . 1 1 .             5  . 1 1 . . . . 1  
          7  . . . . . . . .             7  . . . . . . . .  
                     ↑
                     This column will be replaced with the next column of "C",
                       ↑ then we'll advance `bufferPointer`
                     
Each element is a 16.16 fixed point number, so you could decide to pack HSV or 
RGB info into each byte, but this example is monochrome so each element just 
stores a 0 or 1, making rendering as simple as:

    if (renderBuffer[row][col]) hsv(0,0,1)
*/

// The next column of the overall message to load
var messageColPointer = 0

// Load the next column from `message` into `renderBuffer` at `bufferPointer`
function loadNextCol()
{
  messageIdx = floor(messageColPointer / charCols)
  charIndex  = message[messageIdx]

  // loads global `character` with ASCII charIndex
  fetchCharacter(charIndex)
  
  colIndex = messageColPointer % charCols
  
  // Increment Color Counter at the beginning
  // of every Word OR every Message
  if (
          ((32 == charIndex) && (0 == colIndex) && (1 ==selectColorMode))
       || ((0 == messageIdx) && (0 == colIndex) && (2 ==selectColorMode))
     )
  {
    incColorIdx()
  }

  for (row = 0; row < charRows; row++)
  {
    bit = (((character[row] << colIndex) & 0b10000000) == 0b10000000)

    if (0 == bit)
    {
      // Background Pixel Collor
      // from Color Array
      colorIndex = 0
    }
    else if (0 ==selectColorMode)
    {
      colorIndex = messageCharRGB[messageIdx]
    }
    else
    {
      colorIndex = colorIdxCounter
    }

    R = pixelColors[colorIndex][0]
    G = pixelColors[colorIndex][1]
    B = pixelColors[colorIndex][2]

    RGB = packRGB(R, G, B)
    renderBuffer[row][bufferPointer] = RGB
  }

  messageCols = messageLength * charCols
  bufferPointer = (bufferPointer + 1) % matrixCols
  messageColPointer = (messageColPointer + 1) % messageCols
}

// Increment Color Index Counter
var colorIdxCounter = 1

function incColorIdx()
{
  if (colorIdxCounter < (colorArraySize - 1))
  {
    colorIdxCounter++
  }
  else
  {
    colorIdxCounter = 1
  }
}

/*
  Font Implementation
  
  Pixelblaze currently supports up to 64 arrays with 2048 array elements.
  
  To store a character set of 8x8 bit characters, we use 8 arrays, 
  one for each row.
  
  Four 8-bit maps are packed into each 32 bit array element. This makes the
  bitwise code a little hard to follow, but uses memory efficiently. The 8 most
  significant bits are referred to as "bank 0"; the next eight bits just left of
  the binary point are "bank 1", etc.
  
  Here's the scheme used to store the font bitmap. A period is a zero.
  
    ASCII character  A        B        C        D        E
        `charIndex`  65       66       67       68       69
      array element  [16]     [16]     [16]     [16]     [17]
               bank  0        1        2        3        0      
      fontBitmap[0]  ..11.... 111111.. ..1111.. 11111... 1111111.
      fontBitmap[1]  .1111... .11..11. .11..11. .11.11.. .11...1.
      fontBitmap[2]  11..11.. .11..11. 11...... .11..11. .11.1...
      fontBitmap[3]  11..11.. .11111.. 11...... .11..11. .1111...
      fontBitmap[4]  111111.. .11..11. 11...... .11..11. .11.1...
      fontBitmap[5]  11..11.. .11..11. .11..11. .11.11.. .11...1.
      fontBitmap[6]  11..11.. 111111.. ..1111.. 11111... 1111111.
      fontBitmap[7]  ........ ........ ........ ........ ........
  
  charIndex 0..31 (traditionally the ASCII control characters) are left 
  blank for user-defined custom characters.
*/

/*
  Font and character functions

  The storeCharacter functions take the character index (< `fontCharCount`) and 
  8 rows of 8 bits. Each row is a byte representing 8 bits of on/off bitmap data
  to become the pixels of a character. Therefore, this implementation is 
  currently tightly coupled to 8-bit wide characters.
*/

/*
  At character index `charIndex`, store 8 bytes of row data specified as 
  sequential arguments r0-r7. This allows us to easily use the public domain 
  font specified as comma-delimited hex bytes at:
  
  https://github.com/rene-d/fontino/blob/master/font8x8_ib8x8u.ino
*/
function storeCharacter(charIndex, r0, r1, r2, r3, r4, r5, r6, r7)
{
  element = floor(charIndex / 4)
  bank = charIndex % 4
  packByte(0, element, bank, r0)
  packByte(1, element, bank, r1)
  packByte(2, element, bank, r2)
  packByte(3, element, bank, r3)
  packByte(4, element, bank, r4)
  packByte(5, element, bank, r5)
  packByte(6, element, bank, r6)
  packByte(7, element, bank, r7)
}

/*
  This alternate style stores the character using the 8 row global array named 
  `character`. It could be useful for storing sprites after transformations to
  create animations.
*/
function storeCharacter2(charIndex)
{
  element = floor(charIndex / 4)
  bank = charIndex % 4
  for (var row = 0; row < charRows; row++)
  {
    packByte(row, element, bank, character[row])
  }
}

// Loads the global `character` from the specified charIndex
function fetchCharacter(charIndex)
{
  element = floor(charIndex / 4)
  bank = charIndex % 4
  for (var row = 0; row < charRows; row++)
  {
    character[row] = unpackByte(row, element, bank)
  }
}

/*
  For a given row of a font's pixel data (fontBitmap[row]), there's a 
  (fontCharCount / 4) element long array that holds 32 bits per array element. 
  Thinking of each array element as a 4-byte word, the "bank" (0..3) specifies 
  which set of 8 bits we're storing for a particular character. Characters are 
  referred to by their charIndex (ASCII number), so:
  bank 0 in elements 0, 1, & 2 store the data for characters 0, 4, 8, etc;
  Bank 1 in elements 0, 1, & 2 store the data for characters 1, 5, 9, etc.
  
  The method below is used because the bitwise operators only work on 
  the top 16 bits.
*/
var byteHolder = array(4)
function packByte(row, element, bank, byte)
{
  original = fontBitmap[row][element]
  
  // Load a 4-element array with the individual bytes in this 32 bit 'word'
  for (_bank = 0; _bank < 4; _bank++)
  {
    byteHolder[_bank] = (((original << (_bank * 8)) & 0xFF00) >> 8) & 0xFF
  }
  
  // Override the 8 bits we're trying to store
  byteHolder[bank] = byte 
  
  // Reassemble the 32 bit 'word'
  fontBitmap[row][element] = (byteHolder[0] << 8) 
                            + byteHolder[1] 
                           + (byteHolder[2] >> 8) 
                           + (byteHolder[3] >> 16)
}

// Inverse of packByte()
function unpackByte(row, element, bank)
{
  word = fontBitmap[row][element]
  if (bank > 1)
  {
    byte = (word << (8 * (bank - 1)))
  }
  else if (bank == 0)
  {
    byte = (word >> 8)
  }
  else
  {
    byte = word
  }

  // Zero out all but the 8 bits left of the binary point
  return (byte & 0xFF)
}

/* 
  Font Data

  Public domain, courtesy of
  https://github.com/rene-d/fontino/blob/master/font8x8_ib8x8u.ino
*/
storeCharacter( 32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)  // 0x20 (space)
storeCharacter( 33, 0x30, 0x78, 0x78, 0x30, 0x30, 0x00, 0x30, 0x00)  // 0x21 (exclam)
storeCharacter( 34, 0x6c, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00)  // 0x22 (quotedbl)
storeCharacter( 35, 0x6c, 0x6c, 0xfe, 0x6c, 0xfe, 0x6c, 0x6c, 0x00)  // 0x23 (numbersign)
storeCharacter( 36, 0x30, 0x7c, 0xc0, 0x78, 0x0c, 0xf8, 0x30, 0x00)  // 0x24 (dollar)
storeCharacter( 37, 0x00, 0xc6, 0xcc, 0x18, 0x30, 0x66, 0xc6, 0x00)  // 0x25 (percent)
storeCharacter( 38, 0x38, 0x6c, 0x38, 0x76, 0xdc, 0xcc, 0x76, 0x00)  // 0x26 (ampersand)
storeCharacter( 39, 0x60, 0x60, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00)  // 0x27 (quotesingle)
storeCharacter( 40, 0x18, 0x30, 0x60, 0x60, 0x60, 0x30, 0x18, 0x00)  // 0x28 (parenleft)
storeCharacter( 41, 0x60, 0x30, 0x18, 0x18, 0x18, 0x30, 0x60, 0x00)  // 0x29 (parenright)
storeCharacter( 42, 0x00, 0x66, 0x3c, 0xff, 0x3c, 0x66, 0x00, 0x00)  // 0x2a (asterisk)
storeCharacter( 43, 0x00, 0x30, 0x30, 0xfc, 0x30, 0x30, 0x00, 0x00)  // 0x2b (plus)
storeCharacter( 44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x60)  // 0x2c (comma)
storeCharacter( 45, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00)  // 0x2d (hyphen)
storeCharacter( 46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00)  // 0x2e (period)
storeCharacter( 47, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0x80, 0x00)  // 0x2f (slash)
storeCharacter( 48, 0x7c, 0xc6, 0xce, 0xde, 0xf6, 0xe6, 0x7c, 0x00)  // 0x30 (zero)
storeCharacter( 49, 0x30, 0x70, 0x30, 0x30, 0x30, 0x30, 0xfc, 0x00)  // 0x31 (one)
storeCharacter( 50, 0x78, 0xcc, 0x0c, 0x38, 0x60, 0xc4, 0xfc, 0x00)  // 0x32 (two)
storeCharacter( 51, 0x78, 0xcc, 0x0c, 0x38, 0x0c, 0xcc, 0x78, 0x00)  // 0x33 (three)
storeCharacter( 52, 0x1c, 0x3c, 0x6c, 0xcc, 0xfe, 0x0c, 0x1e, 0x00)  // 0x34 (four)
storeCharacter( 53, 0xfc, 0xc0, 0xf8, 0x0c, 0x0c, 0xcc, 0x78, 0x00)  // 0x35 (five)
storeCharacter( 54, 0x38, 0x60, 0xc0, 0xf8, 0xcc, 0xcc, 0x78, 0x00)  // 0x36 (six)
storeCharacter( 55, 0xfc, 0xcc, 0x0c, 0x18, 0x30, 0x30, 0x30, 0x00)  // 0x37 (seven)
storeCharacter( 56, 0x78, 0xcc, 0xcc, 0x78, 0xcc, 0xcc, 0x78, 0x00)  // 0x38 (eight)
storeCharacter( 57, 0x78, 0xcc, 0xcc, 0x7c, 0x0c, 0x18, 0x70, 0x00)  // 0x39 (nine)
storeCharacter( 58, 0x00, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00)  // 0x3a (colon)
storeCharacter( 59, 0x00, 0x30, 0x30, 0x00, 0x30, 0x30, 0x60, 0x00)  // 0x3b (semicolon)
storeCharacter( 60, 0x18, 0x30, 0x60, 0xc0, 0x60, 0x30, 0x18, 0x00)  // 0x3c (less)
storeCharacter( 61, 0x00, 0x00, 0xfc, 0x00, 0x00, 0xfc, 0x00, 0x00)  // 0x3d (equal)
storeCharacter( 62, 0x60, 0x30, 0x18, 0x0c, 0x18, 0x30, 0x60, 0x00)  // 0x3e (greater)
storeCharacter( 63, 0x78, 0xcc, 0x0c, 0x18, 0x30, 0x00, 0x30, 0x00)  // 0x3f (question)
storeCharacter( 64, 0x7c, 0xc6, 0xde, 0xde, 0xde, 0xc0, 0x78, 0x00)  // 0x40 (at)
storeCharacter( 65, 0x30, 0x78, 0xcc, 0xcc, 0xfc, 0xcc, 0xcc, 0x00)  // 0x41 (A)
storeCharacter( 66, 0xfc, 0x66, 0x66, 0x7c, 0x66, 0x66, 0xfc, 0x00)  // 0x42 (B)
storeCharacter( 67, 0x3c, 0x66, 0xc0, 0xc0, 0xc0, 0x66, 0x3c, 0x00)  // 0x43 (C)
storeCharacter( 68, 0xf8, 0x6c, 0x66, 0x66, 0x66, 0x6c, 0xf8, 0x00)  // 0x44 (D)
storeCharacter( 69, 0xfe, 0x62, 0x68, 0x78, 0x68, 0x62, 0xfe, 0x00)  // 0x45 (E)
storeCharacter( 70, 0xfe, 0x62, 0x68, 0x78, 0x68, 0x60, 0xf0, 0x00)  // 0x46 (F)
storeCharacter( 71, 0x3c, 0x66, 0xc0, 0xc0, 0xce, 0x66, 0x3e, 0x00)  // 0x47 (G)
storeCharacter( 72, 0xcc, 0xcc, 0xcc, 0xfc, 0xcc, 0xcc, 0xcc, 0x00)  // 0x48 (H)
storeCharacter( 73, 0x78, 0x30, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00)  // 0x49 (I)
storeCharacter( 74, 0x1e, 0x0c, 0x0c, 0x0c, 0xcc, 0xcc, 0x78, 0x00)  // 0x4a (J)
storeCharacter( 75, 0xe6, 0x66, 0x6c, 0x78, 0x6c, 0x66, 0xe6, 0x00)  // 0x4b (K)
storeCharacter( 76, 0xf0, 0x60, 0x60, 0x60, 0x62, 0x66, 0xfe, 0x00)  // 0x4c (L)
storeCharacter( 77, 0xc6, 0xee, 0xfe, 0xfe, 0xd6, 0xc6, 0xc6, 0x00)  // 0x4d (M)
storeCharacter( 78, 0xc6, 0xe6, 0xf6, 0xde, 0xce, 0xc6, 0xc6, 0x00)  // 0x4e (N)
storeCharacter( 79, 0x38, 0x6c, 0xc6, 0xc6, 0xc6, 0x6c, 0x38, 0x00)  // 0x4f (O)
storeCharacter( 80, 0xfc, 0x66, 0x66, 0x7c, 0x60, 0x60, 0xf0, 0x00)  // 0x50 (P)
storeCharacter( 81, 0x78, 0xcc, 0xcc, 0xcc, 0xdc, 0x78, 0x1c, 0x00)  // 0x51 (Q)
storeCharacter( 82, 0xfc, 0x66, 0x66, 0x7c, 0x6c, 0x66, 0xe6, 0x00)  // 0x52 (R)
storeCharacter( 83, 0x78, 0xcc, 0xe0, 0x70, 0x1c, 0xcc, 0x78, 0x00)  // 0x53 (S)
storeCharacter( 84, 0xfc, 0xb4, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00)  // 0x54 (T)
storeCharacter( 85, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xfc, 0x00)  // 0x55 (U)
storeCharacter( 86, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x78, 0x30, 0x00)  // 0x56 (V)
storeCharacter( 87, 0xc6, 0xc6, 0xc6, 0xd6, 0xfe, 0xee, 0xc6, 0x00)  // 0x57 (W)
storeCharacter( 88, 0xc6, 0xc6, 0x6c, 0x38, 0x38, 0x6c, 0xc6, 0x00)  // 0x58 (X)
storeCharacter( 89, 0xcc, 0xcc, 0xcc, 0x78, 0x30, 0x30, 0x78, 0x00)  // 0x59 (Y)
storeCharacter( 90, 0xfe, 0xc6, 0x8c, 0x18, 0x32, 0x66, 0xfe, 0x00)  // 0x5a (Z)
storeCharacter( 91, 0x78, 0x60, 0x60, 0x60, 0x60, 0x60, 0x78, 0x00)  // 0x5b (bracketleft)
storeCharacter( 92, 0xc0, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x02, 0x00)  // 0x5c (backslash)
storeCharacter( 93, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x78, 0x00)  // 0x5d (bracketright)
storeCharacter( 94, 0x10, 0x38, 0x6c, 0xc6, 0x00, 0x00, 0x00, 0x00)  // 0x5e (asciicircum)
storeCharacter( 95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff)  // 0x5f (underscore)
storeCharacter( 96, 0x30, 0x30, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00)  // 0x60 (grave)
storeCharacter( 97, 0x00, 0x00, 0x78, 0x0c, 0x7c, 0xcc, 0x76, 0x00)  // 0x61 (a)
storeCharacter( 98, 0xe0, 0x60, 0x60, 0x7c, 0x66, 0x66, 0xdc, 0x00)  // 0x62 (b)
storeCharacter( 99, 0x00, 0x00, 0x78, 0xcc, 0xc0, 0xcc, 0x78, 0x00)  // 0x63 (c)
storeCharacter(100, 0x1c, 0x0c, 0x0c, 0x7c, 0xcc, 0xcc, 0x76, 0x00)  // 0x64 (d)
storeCharacter(101, 0x00, 0x00, 0x78, 0xcc, 0xfc, 0xc0, 0x78, 0x00)  // 0x65 (e)
storeCharacter(102, 0x38, 0x6c, 0x60, 0xf0, 0x60, 0x60, 0xf0, 0x00)  // 0x66 (f)
storeCharacter(103, 0x00, 0x00, 0x76, 0xcc, 0xcc, 0x7c, 0x0c, 0xf8)  // 0x67 (g)
storeCharacter(104, 0xe0, 0x60, 0x6c, 0x76, 0x66, 0x66, 0xe6, 0x00)  // 0x68 (h)
storeCharacter(105, 0x30, 0x00, 0x70, 0x30, 0x30, 0x30, 0x78, 0x00)  // 0x69 (i)
storeCharacter(106, 0x0c, 0x00, 0x0c, 0x0c, 0x0c, 0xcc, 0xcc, 0x78)  // 0x6a (j)
storeCharacter(107, 0xe0, 0x60, 0x66, 0x6c, 0x78, 0x6c, 0xe6, 0x00)  // 0x6b (k)
storeCharacter(108, 0x70, 0x30, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00)  // 0x6c (l)
storeCharacter(109, 0x00, 0x00, 0xcc, 0xfe, 0xfe, 0xd6, 0xc6, 0x00)  // 0x6d (m)
storeCharacter(110, 0x00, 0x00, 0xf8, 0xcc, 0xcc, 0xcc, 0xcc, 0x00)  // 0x6e (n)
storeCharacter(111, 0x00, 0x00, 0x78, 0xcc, 0xcc, 0xcc, 0x78, 0x00)  // 0x6f (o)
storeCharacter(112, 0x00, 0x00, 0xdc, 0x66, 0x66, 0x7c, 0x60, 0xf0)  // 0x70 (p)
storeCharacter(113, 0x00, 0x00, 0x76, 0xcc, 0xcc, 0x7c, 0x0c, 0x1e)  // 0x71 (q)
storeCharacter(114, 0x00, 0x00, 0xdc, 0x76, 0x66, 0x60, 0xf0, 0x00)  // 0x72 (r)
storeCharacter(115, 0x00, 0x00, 0x7c, 0xc0, 0x78, 0x0c, 0xf8, 0x00)  // 0x73 (s)
storeCharacter(116, 0x10, 0x30, 0x7c, 0x30, 0x30, 0x34, 0x18, 0x00)  // 0x74 (t)
storeCharacter(117, 0x00, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0x76, 0x00)  // 0x75 (u)
storeCharacter(118, 0x00, 0x00, 0xcc, 0xcc, 0xcc, 0x78, 0x30, 0x00)  // 0x76 (v)
storeCharacter(119, 0x00, 0x00, 0xc6, 0xd6, 0xfe, 0xfe, 0x6c, 0x00)  // 0x77 (w)
storeCharacter(120, 0x00, 0x00, 0xc6, 0x6c, 0x38, 0x6c, 0xc6, 0x00)  // 0x78 (x)
storeCharacter(121, 0x00, 0x00, 0xcc, 0xcc, 0xcc, 0x7c, 0x0c, 0xf8)  // 0x79 (y)
storeCharacter(122, 0x00, 0x00, 0xfc, 0x98, 0x30, 0x64, 0xfc, 0x00)  // 0x7a (z)
storeCharacter(123, 0x1c, 0x30, 0x30, 0xe0, 0x30, 0x30, 0x1c, 0x00)  // 0x7b (braceleft)
storeCharacter(124, 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00)  // 0x7c (bar)
storeCharacter(125, 0xe0, 0x30, 0x30, 0x1c, 0x30, 0x30, 0xe0, 0x00)  // 0x7d (braceright)
storeCharacter(126, 0x76, 0xdc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)  // 0x7e (asciitilde)


// Other user-defined custom characters

// ASCII 63 is the question mark - here's an alternative from the 
// Sinclair ZX81 font, stored in custom slot 30
storeCharacter(30,
  0b00000000,
  0b00111100,
  0b01000010,
  0b00000100,
  0b00001000,
  0b00000000,
  0b00001000,
  0b00000000
)

// This demonstrates copying the character, altering it, then storing it in the 
// next slot. You could use this for programmatic animation.
fetchCharacter(30)
character[7] = 0b00001000
storeCharacter2(31)

The above patters are adjusted for my use case but easily customizable.

And here is a RM rule sending data from Ecowitt sensors for displaying on the LED matrix:

I took a short video showing Message Board in action but I have no idea how to attached it here.

1 Like

thank you, will dive into this on the weekend and have a look.

B

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.