A few years ago, I was sent a WS2812B matrix display board that has 16x16 chips on it, so it has a total of 256 RGB LEDs. I was asked if I could create a library for it in Positron8 for an inexpensive device, so I set about writing the code and found that I enjoyed seeing it operate so much, I created several demo programs for it, and embellished the library code's functionality so it operates just like a low resolution coloured OLED display with graphics and text capabilities. The board used can be seen below:
The library can be used on any Microchip PIC18F device, as long as it has enough RAM for the display's buffer. But most new PIC18F devices have sufficient RAM, and the display only requires a single pin to operate! The 16x16 matrix library source code, written in Positron8 BASIC is listed below:
$ifndef _WS2812B_MATRIX_
$define _WS2812B_MATRIX_
'
' /\\\\\\\\\
' /\\\///////\\\
' \/\\\ \/\\\ /\\\ /\\\
' \/\\\\\\\\\\\/ /\\\\\ /\\\\\\\\\\ /\\\\\\\\ /\\\\\\\\\\\ /\\\\\\\\\\\ /\\\\\\\\\
' \/\\\//////\\\ /\\\///\\\ \/\\\////// /\\\/////\\\ \////\\\//// \////\\\//// \////////\\\
' \/\\\ \//\\\ /\\\ \//\\\ \/\\\\\\\\\\ /\\\\\\\\\\\ \/\\\ \/\\\ /\\\\\\\\\\
' \/\\\ \//\\\ \//\\\ /\\\ \////////\\\ \//\\/////// \/\\\ /\\ \/\\\ /\\ /\\\/////\\\
' \/\\\ \//\\\ \///\\\\\/ /\\\\\\\\\\ \//\\\\\\\\\\ \//\\\\\ \//\\\\\ \//\\\\\\\\/\\
' \/// \/// \///// \////////// \////////// \///// \///// \////////\//
' Let's find out together what makes a PIC Tick!
'
' WS2812B RGB LED Matrix interface
' Because of the very tight pulse requirements of the WS2812B device, this code is for a PIC device operating at higher
' oscillation frequency
' The frequencies supported with this library are 12MHz, 16MHz, 32MHz, 40MHz, 48MHz and 64MHz
'
' The library uses the 24-bit Long variable for the RGB colours, to save memory and allow the code to run faster.
'
' The library requires three $defines placed before it is loaded into the main program:
'
' $define WS2812B_Pin Tells the library which pins the WS2812B strip is connected too
' $define Matrix_X_Size Tells the library the amount of X axis pixels on the matrix display
' $define Matrix_Y_Size Tells the library the amount of Y axis pixels on the matrix display
' $define Matrix_Brightness Is an optional constant to tell the Matrix_Update procedure to alter the brightness of the LEDs
' (0 is highest brightness, 16 is lowest brightness)
'
' Written by Les Johnson for the Positron8 BASIC Compiler.
'
' Set defaults for the library if the $defines are not added to the main program listing
'
$ifndef Matrix_Pin
$define Matrix_Pin PORTB.0 ' The default pin that the WS2812B chips are connected too
$SendWarning "$define Matrix_Pin not issued in the program. Using the default pin of PORTB.0"
$endif
$ifndef Matrix_X_Size
$SendWarning "$define Matrix_X_Size not issued in the main program. Using default of 16"
$define Matrix_X_Size 16 ' The default amount of the X LEDs on the matrix display
$endif
$ifndef Matrix_Y_Size
$SendWarning "$define Matrix_Y_Size not issued in the main program. Using the default of 16"
$define Matrix_Y_Size 16 ' The default amount of the Y LEDs on the matrix display
$endif
$define cMatrix_X_Max $eval (Matrix_X_Size - 1)
$define cMatrix_Y_Max $eval (Matrix_Y_Size - 1)
'
' The amount of WS2812B chips on the strip
'
$define cWS2812B_Amount $eval (Matrix_X_Size * Matrix_Y_Size)
$define cWS2812B_IndexAmount $eval (cWS2812B_Amount - 1)
$define cWS2812B_MaxAmount $eval (cWS2812B_Amount - 1)
'
' Create Global variables for the library here
'
Dim WS2812B_bBitIndex As Byte Access ' Used to access each bit in the WS2812B interface
Dim WS2812B_lRGB As Long Access ' Used to hold the Green, Red, and Blue bytes
'
' The colour of each LED is encoded as three LED brightness values, which must be sent in GRB (Green-Red-Blue) order.
'
Dim WS2812B_bGreen As WS2812B_lRGB.Byte2 ' Alias the green byte
Dim WS2812B_bRed As WS2812B_lRGB.Byte1 ' Alias the red byte
Dim WS2812B_bBlue As WS2812B_lRGB.Byte0 ' Alias the blue byte
Dim Matrix_wPixelPos As Word Access ' Holds the position within the display of a pixel's colour values
Dim Matrix_bPixelPos As Matrix_wPixelPos.Byte0 ' Holds the position within the display of a pixel's colour values
Dim Matrix_lColour As Long Access ' Used as an alias to hold the colour value for some procedures
Dim Matrix_lPenColour As Long ' Holds the font's pen colour
Dim Matrix_lPaperColour As Long ' Holds the font's paper colour
Dim Matrix_lDisplay[cWS2812B_Amount] As Long Heap ' Holds the matrix display's colour map for each pixel
'
' Alias some SFRs as 16-bit variables
'
Dim Matrix_wTblPtr As TBLPTRL.Word
Dim Matrix_wProd As PRODL.Word
Dim Matrix_wFSR0 As FSR0L.Word
Dim Matrix_wFSR1 As FSR1L.Word
Dim Matrix_wIndex As PRODL.Word
'
' WS2812B pulse timing constants for 12MHz, 16MHz, 32MHz, 40MHz, 48MHz and 64MHz oscillators
'
$if _xtal = 64 ' Is the device running at 64MHz?
Symbol cWS2812B_Zero = 6 ' Amount of cycles for a zero delay (approx 350ns)
Symbol cWS2812B_One = 16 ' Amount of cycles for a one delay (approx 900ns)
$elseif _xtal = 48 ' Is the device running at 48MHz?
Symbol cWS2812B_Zero = 4 ' Amount of cycles for a zero delay
Symbol cWS2812B_One = 10 ' Amount of cycles for a one delay
$elseif _xtal = 40 ' Is the device running at 40MHz?
Symbol cWS2812B_Zero = 3 ' Amount of cycles for a zero delay
Symbol cWS2812B_One = 9 ' Amount of cycles for a one delay
$elseif _xtal = 32 ' Is the device running at 32MHz?
Symbol cWS2812B_Zero = 2 ' Amount of cycles for a zero delay
Symbol cWS2812B_One = 7 ' Amount of cycles for a one delay
$elseif _xtal = 16 ' Is the device running at 16MHz?
Symbol cWS2812B_Zero = 0 ' Amount of cycles for a zero delay
Symbol cWS2812B_One = 2 ' Amount of cycles for a one delay
$elseif _xtal = 12 ' Is the device running at 12MHz?
Symbol cWS2812B_Zero = 0 ' Amount of cycles for a zero delay
Symbol cWS2812B_One = 1 ' Amount of cycles for a one delay
$else
$error "Only 12MHz, 16MHz, 32MHZ, 40MHz, 48MHz and 64MHz oscillators suitable for this code"
$endif
Symbol cWS2812B_ResetUs = 60 ' Amount of microseconds for a reset delay
'
' Flash memory table to Map the 16x16 matrix display's X and Y pixels to the lines of WS2812B chips
' The table below will need to be changed for a different X and Y size matrix
'
Dim Matrix_cMap As Flash8 = {15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, ' Line 0
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, ' Line 1
47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, ' Line 2
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, ' Line 3
79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, ' Line 4
80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, ' Line 5
111, 110, 109, 108, 107, 106, 105, 104, 103, 102, 101, 100, 99, 98, 97, 96, ' Line 6
112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, ' Line 7
143, 142, 141, 140, 139, 138, 137, 136, 135, 134, 133, 132, 131, 130, 129, 128, ' Line 8
144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, ' Line 9
175, 174, 173, 172, 171, 170, 169, 168, 167, 166, 165, 164, 163, 162, 161, 160, ' Line 10
176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, ' Line 11
207, 206, 205, 204, 203, 202, 201, 200, 199, 198, 197, 196, 195, 194, 193, 192, ' Line 12
208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, ' Line 13
239, 238, 237, 236, 235, 234, 233, 232, 231, 230, 229, 228, 227, 226, 225, 224, ' Line 14
240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255} ' Line 15
'---------------------------------------------------------------------------------------------
' Send a 0 (approx 0.35us) to the WS2812B device
'
$define WS2812B_SendZero() '
PinSet Matrix_Pin '
DelayCS cWS2812B_Zero '
PinClear Matrix_Pin
'---------------------------------------------------------------------------------------------
' Send a 1 (approx 0.9us) to the WS2812B device
'
$define WS2812B_SendOne() '
PinSet Matrix_Pin '
DelayCS cWS2812B_One '
PinClear Matrix_Pin
'---------------------------------------------------------------------------------------------
' Send a reset (60us) to the WS2812B device
'
$define WS2812B_Finish() '
PinClear Matrix_Pin '
DelayUS cWS2812B_ResetUs
'---------------------------------------------------------------------------------------------
' Interface to a single WS2812B RGB controller chip (with gamma control)
' Input : pRed holds the red value (0 to 255)
' : pGreen holds the green value (0 to 255)
' : pBlue holds the blue value (0 to 255)
' Output : None
' Notes : Sends the 24-bits MSB.
' : A zero bit is a high pulse for approx 350ns
' : A one bit is a high pulse for approx 900ns
'
Proc WS2812B_RGB_Gamma(pRed As WS2812B_bRed, pGreen As WS2812B_bGreen, pBlue As WS2812B_bBlue)
'
' Create a Gamma correction table
'
Dim Gamma8 As Flash8 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2,
2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5,
5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10,
10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16,
17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25,
25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36,
37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50,
51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68,
69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89,
90, 92, 93, 95, 96, 98, 99, 101, 102, 104, 105, 107, 109, 110, 112, 114,
115, 117, 119, 120, 122, 124, 126, 127, 129, 131, 133, 135, 137, 138, 140, 142,
144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 167, 169, 171, 173, 175,
177, 180, 182, 184, 186, 189, 191, 193, 196, 198, 200, 203, 205, 208, 210, 213,
215, 218, 220, 223, 225, 228, 231, 233, 236, 239, 241, 244, 247, 249, 252, 255}
pRed = CRead8 Gamma8[pRed] ' \
pGreen = CRead8 Gamma8[pGreen] ' | Add Gamma correction to the LEDs
pBlue = CRead8 Gamma8[pBlue] ' /
For WS2812B_bBitIndex = 23 DownTo 0 ' Create a loop for the 24-bits of data to send to a WS2812B
Rol pBlue ' WS2812B_dRGB.Byte0 \
Rol pRed ' WS2812B_dRGB.Byte1 | Rotate the 24 colour bits
Rol pGreen ' WS2812B_dRGB.Byte2 /
If STATUSbits_C = 1 Then ' Is the bit set?
WS2812B_SendOne() ' Yes. So send a 1 bit to the WS2812B chip
Else ' Otherwise... We have a zero bit. So...
WS2812B_SendZero() ' Send a 0 bit to the WS2812B chip
EndIf
Next
EndProc
'---------------------------------------------------------------------------------------------
' Interface to a single WS2812B RGB controller chip
' Input : pRed holds the red value (0 to 255)
' : pGreen holds the green value (0 to 255)
' : pBlue holds the blue value (0 to 255)
' Output : None
' Notes : Sends the 24-bits MSB.
' : A zero bit is a high pulse for approx 350ns
' : A one bit is a high pulse for approx 900ns
'
Proc WS2812B_RGB(pRed As WS2812B_bRed, pGreen As WS2812B_bGreen, pBlue As WS2812B_bBlue)
For WS2812B_bBitIndex = 23 DownTo 0 ' Create a loop for the 24-bits of data to send to a WS2812B
Rol pBlue ' WS2812B_lRGB.Byte0 \
Rol pRed ' WS2812B_lRGB.Byte1 | Rotate the 24 colour bits
Rol pGreen ' WS2812B_lRGB.Byte2 /
If STATUSbits_C = 1 Then ' Is the bit a 1?
WS2812B_SendOne() ' Yes. So send a 1 bit to the WS2812B chip
Else ' Otherwise... We have a zero bit. So...
WS2812B_SendZero() ' Send a 0 bit to the WS2812B chip
EndIf
Next
EndProc
'---------------------------------------------------------------------------------------------
' Update the matrix LEDs from the memory mapped display array
' Input : The constant value held in Matrix_Brightness, sets the brightness of the matrix LEDs (0 is max brightness, 64 is lowest brightness)
' Output : None
' Notes : None
'
Proc Matrix_Update()
Dim lColour As Long Access ' Holds the red, green and blue values for an LED
$if cWS2812B_Amount <= 256
'
' Alter the brightness of the LEDs, using a simple division mechanism
' Note that this can alter colours, because they may not mix the same when the LED's light is diminished
'
$ifdef Matrix_Brightness ' Has the brightness define been used in the program?
For Matrix_bPixelPos = cWS2812B_MaxAmount DownTo 0 ' Yes. So create a loop
lColour = Matrix_lDisplay[Matrix_bPixelPos] ' Read a value from the display array
If Matrix_Brightness > 1 Then ' Yes. So is the brightness value 1 or over?
lColour.Byte0 = lColour.Byte0 / Matrix_Brightness ' \
lColour.Byte1 = lColour.Byte1 / Matrix_Brightness ' | Yes. So divide the colour values by the brightness value
lColour.Byte2 = lColour.Byte2 / Matrix_Brightness ' /
EndIf
Matrix_lDisplay[Matrix_bPixelPos] = lColour ' Write the modified value back to the display array
Next
$endif
For Matrix_bPixelPos = 0 To cWS2812B_MaxAmount
lColour = Matrix_lDisplay[Matrix_bPixelPos] ' Read a 24-bit value from the display array
WS2812B_RGB(lColour.Byte2, lColour.Byte1, lColour.Byte0) ' Transfer it to a WS2812B chip
Next ' Loop for all the devices attached to the line
WS2812B_Finish() ' Bring the pin low for approx 50us to reset WS2812B devices
$else
'
' Alter the brightness of the LEDs, using a simple division mechanism
' Note that this can alter colours, because they may not mix the same when the LED's light is diminished
'
$ifdef Matrix_Brightness ' Has the brightness define been used in the program?
For Matrix_wPixelPos = cWS2812B_MaxAmount DownTo 0 ' Yes. So create a loop
lColour = Matrix_lDisplay[Matrix_wPixelPos] ' Read a 24-bit value from the display array
If Matrix_Brightness > 1 Then ' Yes. So is the brightness value 1 or over?
lColour.Byte0 = lColour.Byte0 / Matrix_Brightness ' \
lColour.Byte1 = lColour.Byte1 / Matrix_Brightness ' | Yes. So divide the colour values by brightness value
lColour.Byte2 = lColour.Byte2 / Matrix_Brightness ' /
EndIf
Matrix_lDisplay[Matrix_wPixelPos] = lColour ' Write the modified value back to the display array
Next
$endif
Matrix_wPixelPos = 0 ' \ Create a loop for amount of WS2812B devices on a line
Repeat ' /
lColour = Matrix_lDisplay[Matrix_wPixelPos] ' Read a value from the display array
WS2812B_RGB(lColour.Byte2, lColour.Byte1, lColour.Byte0) ' Transfer it to a WS2812B chip
Inc Matrix_wPixelPos
Until Matrix_wPixelPos >= cWS2812B_Amount ' Loop for all the devices attached to the line
WS2812B_Finish() ' Bring the pin low for approx 50us to reset the WS2812B devices
$endif ' cWS2812B_Amount < 256
EndProc
'---------------------------------------------------------------------------------------------
' Update the matrix LEDs from the memory mapped display array, using a Gamma control for the LEDs
' Input : The constant value held in Matrix_Brightness, sets the brightness of the matrix LEDs (0 is max, 8 is lowest)
' Output : None
' Notes : None
'
Proc Matrix_Update_Gamma()
Dim lColour As Long Access ' Holds the red, green and blue values for an LED
$if cWS2812B_Amount <= 256
'
' Alter the brightness of the LEDs, using a simple division mechanism
' Note that this can alter colours, because they may not mix the same when the LED's light is diminished
'
$ifdef Matrix_Brightness ' Has the brightness define been used in the program?
For Matrix_bPixelPos = cWS2812B_MaxAmount DownTo 0 ' Yes. So create a loop
lColour = Matrix_lDisplay[Matrix_bPixelPos] ' Read a value from the display array
If Matrix_Brightness > 1 Then ' Yes. So is the brightness value 1 or over?
lColour.Byte0 = lColour.Byte0 / Matrix_Brightness ' \
lColour.Byte1 = lColour.Byte1 / Matrix_Brightness ' | Yes. So divide the colour values by brightness value
lColour.Byte2 = lColour.Byte2 / Matrix_Brightness ' /
EndIf
Matrix_lDisplay[Matrix_bPixelPos] = lColour ' Write the modified value back to the display array
Next
$endif
For Matrix_bPixelPos = 0 To cWS2812B_MaxAmount
lColour = Matrix_lDisplay[Matrix_bPixelPos] ' Read a 24-bit value from the display array
WS2812B_RGB_Gamma(lColour.Byte2, lColour.Byte1, lColour.Byte0) ' Transfer it to a WS2812B chip
Next ' Loop for all the devices attached
WS2812B_Finish() ' Pin low for approx 50us to reset WS2812B devices
$else
'
' Alter the brightness of the LEDs, using a simple division mechanism
' Note that this can alter colours, because they may not mix the same when the LED's light is diminished
'
$ifdef Matrix_Brightness ' Has the brightness define been used in the program?
For Matrix_wPixelPos = cWS2812B_MaxAmount DownTo 0 ' Yes. So create a loop
lColour = Matrix_lDisplay[Matrix_wPixelPos] ' Read a value from the display array
If Matrix_Brightness > 1 Then ' Yes. So is the brightness value 1 or over?
lColour.Byte0 = lColour.Byte0 / Matrix_Brightness ' \
lColour.Byte1 = lColour.Byte1 / Matrix_Brightness ' | Yes. So divide colour values by brightness value
lColour.Byte2 = lColour.Byte2 / Matrix_Brightness ' /
EndIf
Matrix_lDisplay[Matrix_wPixelPos] = lColour ' Write the modified value back to the display array
Next
$endif
Matrix_wPixelPos = 0 ' \ Create a loop for the amount of WS2812B devices
Repeat ' /
lColour = Matrix_lDisplay[Matrix_wPixelPos] ' Read a value from the display array
WS2812B_RGB_Gamma(lColour.Byte2, lColour.Byte1, lColour.Byte0) ' Transfer it to a WS2812B chip
Inc Matrix_wPixelPos
Until Matrix_wPixelPos >= cWS2812B_Amount ' Loop for all the devices attached
WS2812B_Finish() ' Pin low for approx 50us to reset the WS2812B devices
$endif ' cWS2812B_Amount < 256
EndProc
'---------------------------------------------------------------------------------------------
' Change a pixel's colour inside the maxrix screen array
' Input : pXpos holds the X position of the pixel (0 to 15)
' : pYpos holds the Y position of the Pixel (0 to 15)
' : pColour holds the RGB colour for a pixel
' : The colour is: pColour.Byte2 = Red
' : pColour.Byte1 = Green
' : pColour.Byte0 = Blue
' Output : None
' Notes : The matrix line of WS2812B devices scans from right to left, then left to right
' : So a flash memory table of the pixel X/Y positions on the matrix display need to be looked up
'
Proc Matrix_SetShadowPixel(pXpos As SByte, pYpos As SByte, pColour As Matrix_lColour)
If pYpos < 0 Then ExitProc ' \
If pYpos >= Matrix_Y_Size Then ExitProc ' | Display Boundary checks
If pXpos < 0 Then ExitProc ' |
If pXpos >= Matrix_X_Size Then ExitProc ' /
'
' Optimisation of:
' Matrix_bPixelPos = (pYpos * Matrix_Y_Size) + pXpos
'
pYpos = pYpos * Matrix_Y_Size
Matrix_bPixelPos = pYpos + pXpos
'
' Find the location of the pixel's X/Y position in the line of WS2812B devices
'
Matrix_bPixelPos = CRead8 Matrix_cMap[Matrix_bPixelPos]
Matrix_lDisplay[Matrix_bPixelPos] = pColour
EndProc
'---------------------------------------------------------------------------------------------
' Change a pixel's colour on the matrix display
' Input : pXpos holds the X position of the pixel (0 to 15)
' : pYpos holds the Y position of the Pixel (0 to 15)
' : pColour holds the RGB colour for a pixel
' : pColour holds the colour of the circle
' : The colour is: pColour.Byte2 = Red
' : pColour.Byte1 = Green
' : pColour.Byte0 = Blue
' Output : None
' Notes : The matrix line of WS2812B devices scans from right to left, then left to right
' : So a flash memory table of the pixel X/Y positions on the matrix display need to be looked up
'
Proc Matrix_SetPixel(pXpos As SByte, pYpos As SByte, pColour As Matrix_lColour)
If pYpos < 0 Then ExitProc ' \
If pYpos >= Matrix_Y_Size Then ExitProc ' | Display Boundary checks
If pXpos < 0 Then ExitProc ' |
If pXpos >= Matrix_X_Size Then ExitProc ' /
'
' Optimisation of:
' Matrix_bPixelPos = (pYpos * Matrix_Y_Size) + pXpos
'
pYpos = pYpos * Matrix_Y_Size
Matrix_bPixelPos = pYpos + pXpos
'
' Find the location of the pixel's X/Y position in the line of WS2812B devices
'
Matrix_bPixelPos = CRead8 Matrix_cMap[Matrix_bPixelPos]
Matrix_lDisplay[Matrix_bPixelPos] = pColour
Matrix_Update() ' Update the matrix display from the display array
EndProc
'---------------------------------------------------------------------------------------------
' Read a pixel's colour from the WS2812B matrix display
' Input : pXpos holds the X position of the pixel (0 to 15)
' : pYpos holds the Y position of the Pixel (0 to 15)
' Output : Returns the RGB colour of a Pixel
' : The colour is: Result.Byte2 = Red
' : Result.Byte1 = Green
' : Result.Byte0 = Blue
' Notes : None
'
Proc Matrix_GetPixel(pXpos As SByte, pYpos As SByte), Dword
If pYpos < 0 Then ExitProc ' \
If pYpos >= Matrix_Y_Size Then ExitProc ' | Display Boundary checks
If pXpos < 0 Then ExitProc ' |
If pXpos >= Matrix_X_Size Then ExitProc ' /
'
' Optimisation of:
' Matrix_bPixelPos = (pYpos * Matrix_Y_Size) + pXpos
'
pYpos = pYpos * Matrix_Y_Size
Matrix_bPixelPos = pYpos + pXpos
'
' Find the location of the pixel's X/Y position in the line of WS2812B devices
'
Matrix_bPixelPos = CRead8 Matrix_cMap[Matrix_bPixelPos]
Result = Matrix_lDisplay[Matrix_bPixelPos]
EndProc
'-------------------------------------------------------------------------------------------------
' Draw a line from pStartXpos, pStartYpos to pEndXpos, pEndYpos
' Input : pStartXpos = Starting X position of the Line
' : pStartYpos = Starting Y position of the Line
' : pEndXpos = Ending X position of the Line
' : pEndYpos = Ending Y position of the Line
' : pColour holds the colour of the line
' : The colour is RGB: pColour.Byte2 = Red
' : pColour.Byte1 = Green
' : pColour.Byte0 = Blue
' Output : None
' Notes : Draws a line to the buffer array
'
Proc Matrix_Line(pStartXpos As Byte, pStartYpos As Byte, pEndXpos As Byte, pEndYpos As Byte, pColour As Matrix_lColour)
Dim sbDeltaX As SByte
Dim sbDeltaY As SByte
Dim sbStepX As SByte
Dim sbStepY As SByte
Dim sbDeviation As SByte
sbDeltaY = pEndYpos - pStartYpos ' Find the initial Y direction
sbDeltaX = pEndXpos - pStartXpos ' Find the initial X direction
sbStepY = 1 ' Default to a positive step for Ypos
If sbDeltaY < 0 Then ' Check if negative
sbDeltaY = -sbDeltaY ' Negate sbDeltaY
sbStepY = -1 ' And indicate a negative step
EndIf
sbStepX = 1 ' Default to a positive step for Xpos
If sbDeltaX < 0 Then ' Check if negative
sbDeltaX = -sbDeltaX ' Negate sbDeltaX
sbStepX = -1 ' And indicate a negative step
EndIf
Matrix_SetShadowPixel(pStartXpos, pStartYpos, pColour) ' The first pixel plot is a special case
If sbDeltaX > sbDeltaY Then
sbDeviation = sbDeltaY - sbDeltaX
While pStartXpos <> pEndXpos
If sbDeviation >= 0 Then ' Check if negative
pStartYpos = pStartYpos + sbStepY ' Move on the Y axis directed by sbStepY
sbDeviation = sbDeviation - sbDeltaX
EndIf
pStartXpos = pStartXpos + sbStepX ' Move on the X axis directed by sbStepX
sbDeviation = sbDeviation + sbDeltaY
Matrix_SetShadowPixel(pStartXpos, pStartYpos, pColour)
Wend
GoTo Update ' Return prematurely
EndIf
sbDeviation = sbDeltaX - sbDeltaY
While pStartYpos <> pEndYpos
If sbDeviation >= 0 Then ' Check if negative
pStartXpos = pStartXpos + sbStepX ' Move on the X axis directed by sbStepX
sbDeviation = sbDeviation - sbDeltaY
EndIf
pStartYpos = pStartYpos + sbStepY ' Move on the Y axis directed by sbStepY
sbDeviation = sbDeviation + sbDeltaX
Matrix_SetShadowPixel(pStartXpos, pStartYpos, pColour)
Wend
Update:
Matrix_Update() ' Update the matrix display from the display array
EndProc
'-------------------------------------------------------------------------------------------------
' Draw a horizontal line from pStartXpos, pStartYpos to pEndXpos, pEndYpos
' Input : pStartXpos = Starting X position of the Line
' : pStartYpos = Starting Y position of the Line
' : pEndXpos = Ending X position of the Line
' : pColour holds the colour of the line
' : The colour is RGB: pColour.Byte2 = Red
' : pColour.Byte1 = Green
' : pColour.Byte0 = Blue
' Output : None
' Notes : None
'
Proc Matrix_HorizLine(pStartXpos As Byte, pStartYpos As Byte, pEndXpos As Byte, pColour As Matrix_lColour)
Repeat
Matrix_SetShadowPixel(pStartXpos, pStartYpos, pColour)
Inc pStartXpos
Until pStartXpos >= pEndXpos
Matrix_Update() ' Update the matrix display from the display array
EndProc
'-------------------------------------------------------------------------------------------------
' Draw a vertical line from pStartYpos, pStartXpos to pEndYpos, pEndXpos
' Input : pStartXpos = Starting X position of the Line
' : pStartYpos = Starting Y position of the Line
' : pEndYpos = Ending X position of the Line
' : pColour holds the colour of the line
' : The colour is RGB: pColour.Byte2 = Red
' : pColour.Byte1 = Green
' : pColour.Byte0 = Blue
' Output : None
' Notes : None
'
Proc Matrix_VertLine(pStartXpos As Byte, pStartYpos As Byte, pEndYpos As Byte, pColour As Matrix_lColour)
Repeat
Matrix_SetShadowPixel(pStartXpos, pStartYpos, pColour)
Inc pStartYpos
Until pStartYpos >= pEndYpos
Matrix_Update() ' Update the matrix display from the display array
EndProc
'-------------------------------------------------------------------------------------------------
' Draw a Circle on the Matrix display
' Input : pXpos holds the starting X position
' : pYpos holds the starting Y position
' : pRadius holds the radius of the circle
' : pColour holds the colour of the circle
' : The colour is RGB: pColour.Byte2 = Red
' : pColour.Byte1 = Green
' : pColour.Byte0 = Blue
' Output : None
' Notes : None
'
Proc Matrix_Circle(pXpos As Byte, pYpos As Byte, pRadius As Byte, pColour As Matrix_lColour)
Dim bDD As Byte
Dim bXX As Byte
Dim bYY As Byte
Dim bTR As Byte
Dim bXpos_S As Byte
Dim bQuadA As Byte
Dim bQuadB As Byte
Dim bQuadC As Byte
Dim bQuadD As Byte
bXpos_S = pXpos ' Transfer pXpos into its working variable
If pRadius = 0 Then ExitProc ' Trap a radius of 0
bDD = pYpos - bXpos_S
bXX = 0
bYY = pRadius
bTR = 3 - (2 * pRadius)
While bXX <= bYY
bQuadA = bXpos_S + bXX
bQuadB = pYpos + bYY
bQuadC = bXpos_S - bXX
bQuadD = pYpos - bYY
GoSub PlotIt ' Plot 4 locations simultaneously
bQuadA = pYpos + bYY
bQuadA = bQuadA - bDD
bQuadB = pYpos + bXX
bQuadC = pYpos - bYY
bQuadC = bQuadC - bDD
bQuadD = pYpos - bXX
GoSub PlotIt ' Plot 4 locations simultaneously
If bTR.7 = 1 Then
bTR = bTR + 6
bTR = bTR + (bXX * 4)
Else
bTR = bTR + 10
bTR = bTR + ((bXX - bYY) * 4)
Dec bYY
EndIf
Inc bXX
Wend
Matrix_Update() ' Update the matrix display from the display buffer array
ExitProc
'
' Plot 4 locations simultaneously
'
PlotIt:
Matrix_SetShadowPixel(bQuadA, bQuadB, pColour)
Matrix_SetShadowPixel(bQuadA, bQuadD, pColour)
Matrix_SetShadowPixel(bQuadC, bQuadB, pColour)
Matrix_SetShadowPixel(bQuadC, bQuadD, pColour)
EndProc
'---------------------------------------------------------------------------------------------
' Clear the Matrix display
' Input : pColour holds the colour of the display when cleared
' : The colour is RGB: pColour.Byte2 = Red
' : pColour.Byte1 = Green
' : pColour.Byte0 = Blue
' Output : None
' Notes : None
'
Proc Matrix_Cls(pColour As Matrix_lColour)
For Matrix_wPixelPos = Bound(Matrix_lDisplay) DownTo 0
Matrix_lDisplay[Matrix_wPixelPos] = pColour ' Set all the elements of Matrix_lDisplay to the colour chosen
Next
Matrix_Update() ' Transfer the pixels from the screen buffer array to the WS2812B chips
EndProc
'-------------------------------------------------------------------------------------
' Copy a character glyph from the font data table to the LED matrix, with its upper left at the given coordinate
' Input : pX holds the X position of the character
' pY holds the Y position of the character
' pCh holds the character to display
' Output : None
' Notes : Uses Matrix_SetShadowPixel to draw each dot of the character
'
Proc Matrix_DisplayChar(pX As Byte, pY As Byte, pCh As Byte)
Dim bRow As Byte
Dim bCol As Byte
Dim wOffset As Word
Dim FontBits As Byte
'
' Move character definition, pixel by pixel, onto the display
' Fonts are defined as one byte per column
'
' Characters's Address = pCh * 6
' pCh holds the byte to display
' Add the offset for the address of font: wOffset + font label's address
' This is the code that requires altering for different sized fonts
'
bCol = 0
Repeat ' Create a loop for all the bytes in the character's font
wOffset = pCh * 6 ' 6 bytes per font
wOffset = wOffset + bCol ' Add the offset to the character's value
FontBits = CRead8 Font_Table[wOffset] ' Read the byte from the font table
'
' Send the value in FontBits to the Display
'
bRow = 7
Repeat
If FontBits.6 = 1 Then
Matrix_lColour = Matrix_lPenColour
Else
Matrix_lColour = Matrix_lPaperColour
EndIf
Matrix_SetShadowPixel(pX + bCol, pY + bRow, Matrix_lColour)
FontBits = FontBits << 1
Dec bRow
Until bRow = 0
Inc bCol ' Next byte in the font
Until bCol >= 5 ' Have we done 5 writes yet?
Matrix_Update() ' Transfer the pixels from the screen buffer to the WS2812B chips
ExitProc
EndProc
'-------------------------------------------------------------------------------------
' Display a string of characters from a String variable on the matrix
' Input : pX holds the X position of the character
' pY holds the Y position of the character
' SFR pair FSR1L\H hold the address of the string to display
' Output : None
' Notes : None
'
Proc Matrix_DisplayString(pX As Byte, pY As Byte, ByRef pStr As Matrix_wFSR1)
Dim bIndex As Byte
Dim bChar As Byte
bIndex = 0 ' Reset a counter
Repeat ' Form a loop
bChar = POSTINC1 ' Transfer the element of the string into bChar and increment position
If bChar = 0 Then Break
Matrix_DisplayChar(pX, pY, bChar) ' Display the character held in bChar
pX = pX + 6 ' Move 6 pixels to the right (width of each character)
Inc bIndex ' Increment the counter
Until bIndex = 0 ' Until 256 iterations have occured
EndProc
'-------------------------------------------------------------------------------------
' Display a string of characters from a character string on the matrix
' Input : pX holds the X position of the character
' pY holds the Y position of the character
' pStr holds the address of the character block to display
' Output : None
' Notes : None
'
Proc Matrix_DisplayText(pX As Byte, pY As Byte, BycRef pStr As Matrix_wTblPtr)
Dim bIndex As Byte
Dim bChar As Byte
Dim Save_wTBLPTR As Word
bIndex = 0 ' Reset a counter
Repeat ' Create a loop
Tblrd*+
bChar = TABLAT ' Transfer the element of the string into bChar and increment position
If bChar = 0 Then Break
Save_wTBLPTR = Matrix_wTblPtr
Matrix_DisplayChar(pX, pY, bChar) ' Display the character held in bChar
Matrix_wTblPtr = Save_wTBLPTR
pX = pX + 6 ' Move 6 pixels to the right (width of each character)
Inc bIndex ' Increment the counter
Until bIndex = 0 ' Until 256 iterations have occured
EndProc
'---------------------------------------------------------------------------------------------
' Set the pen colour for the font characters
' Input : pColour holds the colour of the display when cleared
' : The colour is RGB: pColour.Byte2 = Red
' : pColour.Byte1 = Green
' : pColour.Byte0 = Blue
' Output : None
' Notes : None
'
$define Matrix_PenColour(pColour) Matrix_lPenColour = pColour
'---------------------------------------------------------------------------------------------
' Set the paper colour for the font characters
' Input : pColour holds the colour of the display when cleared
' : The colour is RGB: pColour.Byte2 = Red
' : pColour.Byte1 = Green
' : pColour.Byte0 = Blue
' Output : None
' Notes : None
'
$define Matrix_PaperColour(pColour) Matrix_lPaperColour = pColour
'---------------------------------------------------------------------------------------------
' Setup the interface with WS2812B RGB controller chips
' Input : None
' Output : None
' Notes : None
'
Proc Matrix_Setup()
PinLow Matrix_Pin ' Make the pin that connects to the WS2812B chip a low output
DelayMS 10 ' And reset the WS2812B
Clear Matrix_lDisplay ' Set all the elements of Matrix_lDisplay to 0
EndProc
$endif ' _WS2812B_MATRIX_
Below is the circuit diagram for the 16x16 matrix display. See how simple the circuit is? The newer PIC18F devices have an internal oscillator that is capable of running at 64MHz, so there is no need for an external resonator or crystal at all, and the single pin for the matrix display can be any of the device's pins because the code does not use any special peripherals, just good old bit-bashing created by a fast and efficient compiler. i.e. Positron8.
With some slight library code changes, the matrix can also be extended to larger or wider using more matrix boards joined together serially, for a scrolling message board, or a very large colour graphic LCD.
The Positron8 library code and the demos can be downloaded from here:
Positron8 WS2812B Matrix Library and Demos
If you build the above circuit with the matrix board, make sure you have a 5 Volt Power Supply Unit that is capable of 4 Amps, or over, because when all the LEDs are illuminated fully, the display draws a lot of current, as I found out to my surprise.
Below are a few videos of the demo programs I created, working within the Proteus/Isis simulator. The Isis simulator makes a good job of the WS2812B devices, but they do not show the colours as they appear on the real chips, which are nice and bright and clear. Also, the simulation is slower than the real thing, but, hey, it works nicely and shows what is possible with such a simple circuit and Positron8 BASIC code that is easy to follow and use:
Below is a video of the 16x16 WS2812B matrix running a 'Game Of Life' simulation on a PIC18F26K22 device, sitting in an Easy Driver Amicus8 board. The code uses a true random number generator to seed the cells, so they are always seeded differently each time. The true random generator takes the random bit-0 from an open ADC input, and combines them to make a 16-bit integer value: