Positron8
Quiz Buttons Controller

A very good friend of mine is very fond of quizzes, and he asked if I could create a quiz controller that could indicate who pressed an answer button first and if the answer was correct or incorrect, so I came up with the program below.

The program first waits for a player's button to be pressed when a question is to be answered, then illuminates a block of WS2812B LEDs with the colour Yellow to indicate which player pressed the the button first. It then waits for the quiz master to press the
Correct or Incorrect buttons for the answer to the question, and if correct, it illuminates the player's LEDs Green, and if incorrect, it illuminates the player's LEDs Red. It then waits for the quiz master to reset the stage and extinguish all the LEDs for the next question. All very simple, but great fun to create for my friend.

'
' /\\\\\\\\\
' /\\\///////\\\
' \/\\\ \/\\\ /\\\ /\\\
' \/\\\\\\\\\\\/ /\\\\\ /\\\\\\\\\\ /\\\\\\\\ /\\\\\\\\\\\ /\\\\\\\\\\\ /\\\\\\\\\
' \/\\\//////\\\ /\\\///\\\ \/\\\////// /\\\/////\\\ \////\\\//// \////\\\//// \////////\\\
' \/\\\ \//\\\ /\\\ \//\\\ \/\\\\\\\\\\ /\\\\\\\\\\\ \/\\\ \/\\\ /\\\\\\\\\\
' \/\\\ \//\\\ \//\\\ /\\\ \////////\\\ \//\\/////// \/\\\ /\\ \/\\\ /\\ /\\\/////\\\
' \/\\\ \//\\\ \///\\\\\/ /\\\\\\\\\\ \//\\\\\\\\\\ \//\\\\\ \//\\\\\ \//\\\\\\\\/\\
' \/// \/// \///// \////////// \////////// \///// \///// \////////\//
' Let's find out together what makes a PIC Tick!
'
' Quiz buttons controller that will signal yellow on a set of WS2812B LEDs when a particular player's button is pressed.
' It will then wait for a button to be pressed for "correct" or "incorrect" and turn the LEDs red or green.
' It will then wait for the "buttons reset" button to be pressed before waiting for the next answer button presses.
'
' Written by Les Johnson for the Positron8 compiler.
' The code is suitable for 18F devices or enhanced 14-bit core devices, that have enough pins and suitable peripherals.
'

Device = 18F25K22 ' Tell the compiler what device to compile for
Declare Xtal = 64 ' Tell the compiler what frequency the device will be operating at
'
' Setup the Alphanumeric LCD
'

Declare LCD_Data4_Pin = PORTA.0 ' Connect to the LCD's D4 line
Declare LCD_Data5_Pin = PORTA.1 ' Connect to the LCD's D5 line
Declare LCD_Data6_Pin = PORTA.2 ' Connect to the LCD's D6 line
Declare LCD_Data7_Pin = PORTA.3 ' Connect to the LCD's D7 line
Declare LCD_RSPin = PORTA.4 ' Connect to the LCD's RS line
Declare LCD_ENPin = PORTA.5 ' Connect to the LCD's EN line
Declare LCD_Interface = 4 ' Use a 4 wire interface for the LCD
Declare LCD_Lines = 2 ' The LCD has 2 lines
Declare LCD_Type = Hitachi ' Interface with an Hitachi alphanumeric LCD
'
' Create the defines for the buttons
'

$define Button1_Pin PORTB.0 ' The button pin for player 1
$define Button2_Pin PORTB.1 ' The button pin for player 2
$define Button3_Pin PORTB.2 ' The button pin for player 3
$define Button4_Pin PORTB.3 ' The button pin for player 4
$define Buttons_Reset_Pin PORTB.4 ' The button pin for the "buttons reset"

$define Button_Correct_Pin PORTB.5 ' The button pin for a correct answer
$define Button_Wrong_Pin PORTB.6 ' The button pin for an incorrect answer
'
' Create some defines for the WS2812B LED strip
'
$define WS2812B_Pin PORTC.0 ' The pin that the WS2812B strip is connected too
$define WS2812B_Amount 16 ' The amount of WS2812B devices to control

Include "WS2812B.inc" ' Load the WS2812B library into the program


$define LED_cBlockSize 4 ' The amount of LEDs for a player button press


$define cPressed 0 ' The value for a button pressed (active low)
$define cNotPressed 1 ' The value for a button not pressed (active low)
'
' Create some variables
'

Dim Global_bButtons As Byte ' Holds a snapshot of PORTB for testing button presses
Dim tButton_Player1_Pressed As Global_bButtons.0 ' Holds the value for player button 1 pressed
Dim tButton_Player2_Pressed As Global_bButtons.1 ' Holds the value for player button 2 pressed
Dim tButton_Player3_Pressed As Global_bButtons.2 ' Holds the value for player button 3 pressed
Dim tButton_Player4_Pressed As Global_bButtons.3 ' Holds the value for player button 3 pressed
Dim tButton_Yes_Pressed As Global_bButtons.5 ' Holds the value for master "answer correct" pressed
Dim tButton_No_Pressed As Global_bButtons.6 ' Holds the value for master "answer incorrect" pressed

'------------------------------------------------------------------------------------
' The main program starts here
'

Main:
Setup() ' Setup the program
Cls ' Clear the LCD's display
Print At 1, 1, " ASK QUESTION "
Do ' Create a loop
Global_bButtons = PORTB
' Read PORTB into Global_bButtons
If tButton_Player1_Pressed = cPressed Then ' Is player button 1 pressed?
Display_Button_Press(0) ' Yes. So illuminate the corresponing LEDs and wait for responses
ElseIf tButton_Player2_Pressed = cPressed Then ' Is player button 2 pressed?
Display_Button_Press(1) ' Yes. So illuminate the corresponing LEDs and wait for responses
ElseIf tButton_Player3_Pressed = cPressed Then ' Is player button 3 pressed?
Display_Button_Press(2) ' Yes. So illuminate the corresponing LEDs and wait for responses
ElseIf tButton_Player4_Pressed = cPressed Then ' Is player button 4 pressed?
Display_Button_Press(3) ' Yes. So illuminate the corresponing LEDs and wait for responses
EndIf
Loop
'------------------------------------------------------------------------------------
' Illuminate the block of LEDs that represent the player button pressed
' Input : pBut holds which button was pressed
' Output : None
' Notes : Uses a strip of 4x4 WS2812B chips for the LEDs
'

Proc Display_Button_Press(pBut As Byte)
LEDs_Illuminate(pBut, $FFFF00) ' Illuminate the corresponding LED block yellow
Print At 1, 1, " PLAYER ", Dec1 pBut + 1, " ", ' Display on the LCD, which button was pressed first
At 2, 1, " YES or NO " ' And that it requires a yes or no response to the question answered
'
' Wait for the yes or no buttons to be pressed before continuing
'

Do ' Create a loop
Global_bButtons = PORTB
' Read PORTB into Global_bButtons
If tButton_Yes_Pressed = cPressed Then ' Is the "yes" button pressed?
DelayMS 50 ' Yes. So wait for debounce
If tButton_Yes_Pressed = cPressed Then ' Is the "yes" button still pressed?
LEDs_Illuminate(pBut, $00FF00) ' Yes. So set the corresponding block of LEDs to green
Print At 2, 1, " CORRECT " ' Display "CORRECT" on the LCD
Break ' Exit the loop
EndIf
ElseIf tButton_No_Pressed = cPressed Then ' Is the "no" button pressed?
DelayMS 50 ' Yes. So wait for debounce
If tButton_No_Pressed = cPressed Then ' Is the "no" button still pressed?
LEDs_Illuminate(pBut, $FF0000) ' Yes. So set the corresponding block of LEDs to red
Print At 2, 1, " INCORRECT " ' Display "INCORRECT" on the LCD
Break ' Exit the loop
EndIf
EndIf
Loop
'
' Wait for the "buttons reset" button to be pressed before continuing
'

Do ' Create a loop
If Buttons_Reset_Pin = cPressed Then ' Is the button pressed?
DelayMS 50 ' Yes. So wait for debounce
If Buttons_Reset_Pin = cPressed Then ' Is the button still pressed?
Cls ' Yes. So clear the LCD's display
LEDs_Extinguish() ' Extinguish the LEDs
Repeat ' \
DelayMS 50 ' | Wait for the "buttons reset" button to be released
Until Buttons_Reset_Pin = cNotPressed ' /
Print At 1, 1, " ASK QUESTION "
Break ' Exit the loop
EndIf
EndIf
Loop
EndProc
'------------------------------------------------------------------------------------
' Illuminate a specific block of WS2812B LEDs
' Input : pBlock holds the block of LEDs represented by the button pressed (0 to 3)
' : pColour holds the colour of the LED block
' Output : None
' Notes : None
'

Proc LEDs_Illuminate(pBlock As Byte, pColour As Long)
If pBlock = 0 Then ' Is LED block 0 to be illuminated?
WS2812B_Colour(0, pColour.Byte2, pColour.Byte1, pColour.Byte0) ' \
WS2812B_Colour(1, pColour.Byte2, pColour.Byte1, pColour.Byte0) ' | Yes. So illuminate the corresponding LEDs
WS2812B_Colour(2, pColour.Byte2, pColour.Byte1, pColour.Byte0) ' |
WS2812B_Colour(3, pColour.Byte2, pColour.Byte1, pColour.Byte0) ' /

ElseIf pBlock = 1 Then ' Is LED block 1 to be illuminated?
WS2812B_Colour(4, pColour.Byte2, pColour.Byte1, pColour.Byte0) ' \
WS2812B_Colour(5, pColour.Byte2, pColour.Byte1, pColour.Byte0) ' | Yes. So illuminate the corresponding LEDs
WS2812B_Colour(6, pColour.Byte2, pColour.Byte1, pColour.Byte0) ' |
WS2812B_Colour(7, pColour.Byte2, pColour.Byte1, pColour.Byte0) ' /

ElseIf pBlock = 2 Then ' Is LED block 2 to be illuminated?
WS2812B_Colour(8, pColour.Byte2, pColour.Byte1, pColour.Byte0) ' \
WS2812B_Colour(9, pColour.Byte2, pColour.Byte1, pColour.Byte0) ' | Yes. So illuminate the corresponding LEDs
WS2812B_Colour(10, pColour.Byte2, pColour.Byte1, pColour.Byte0) ' |
WS2812B_Colour(11, pColour.Byte2, pColour.Byte1, pColour.Byte0) ' /

ElseIf pBlock = 3 Then ' Is LED block 3 to be illuminated?
WS2812B_Colour(12, pColour.Byte2, pColour.Byte1, pColour.Byte0) ' \
WS2812B_Colour(13, pColour.Byte2, pColour.Byte1, pColour.Byte0) ' | Yes. So illuminate the corresponding LEDs
WS2812B_Colour(14, pColour.Byte2, pColour.Byte1, pColour.Byte0) ' |
WS2812B_Colour(15, pColour.Byte2, pColour.Byte1, pColour.Byte0) ' /
EndIf
EndProc
'------------------------------------------------------------------------------------
' Extinguish all the WS2812B LED player blocks
' Input : None
' Output : None
' Notes : None
'

Proc LEDs_Extinguish()
Dim bLED As Byte

For bLED = 0 To WS2812B_Amount - 1 ' Create a loop for the amount of WS2812B devices
WS2812B_Colour(bLED, 0, 0, 0) ' Extinguish the RGB LEDs
Next ' Close the loop
DelayMS 100 ' A small delay before exiting
EndProc
'------------------------------------------------------------------------------------
' Setup the program
' Input : None
' Output : None
' Notes : None
'

Proc Setup()
Oscillator_64MHz() ' Setup the microcontroller's internal oscillator for 64MHz
PinMode(Button1_Pin, Input_PullUp) ' Make the button 1 pin an input, with internal pull-up resistor enabled
PinMode(Button2_Pin, Input_PullUp) ' Make the button 2 pin an input, with internal pull-up resistor enabled
PinMode(Button3_Pin, Input_PullUp) ' Make the button 3 pin an input, with internal pull-up resistor enabled
PinMode(Button4_Pin, Input_PullUp) ' Make the button 4 pin an input, with internal pull-up resistor enabled
PinMode(Buttons_Reset_Pin, Input_PullUp) ' Make the "reset buttons" pin an input, with internal pull-up resistor enabled
PinMode(Button_Correct_Pin, Input_PullUp)' Make the "correct answer" pin an input, with internal pull-up resistor enabled
PinMode(Button_Wrong_Pin, Input_PullUp) ' Make the "wrong answer" pin an input, with internal pull-up resistor enabled
WS2812B_Setup() ' Setup the WS2812B LEDs
EndProc
'--------------------------------------------------------------------
' Set the PIC18F25K22 microcontroller for internal 64MHz
' Input : None
' Output : None
' Notes : None
'

Proc Oscillator_64MHz()
OSCCON = $70
OSCCON2 = $04
OSCTUNE = $40
DelayMS 100
EndProc
'------------------------------------------------------------------------------------------------
' Setup the fuses to use the internal oscillator on a PIC18F25K22. With RA6 and RA7 as I/O lines
'

Config_Start
FOSC = INTIO67 ' Internal oscillator block
PLLCFG = OFF ' 4X PLL disable. Oscillator used directly
PRICLKEN = On ' Primary clock enabled
FCMEN = Off ' Fail-Safe Clock Monitor disabled
IESO = Off ' Internal/External Oscillator Switchover mode disabled
PWRTEN = On ' Power up timer enabled
BOREN = SBORDIS ' Brown-out Reset enabled in hardware only (SBOREN is disabled)
BORV = 190 ' Brown Out Reset Voltage set to 1.90 V nominal
WDTEN = Off ' Watch dog timer is always disabled. SWDTEN has no effect.
WDTPS = 128 ' Watchdog Timer Postscale 1:128
CCP2MX = PORTC1 ' CCP2 input/output is multiplexed with RC1
PBADEN = Off ' PORTB<5:0> pins are configured as digital I/O on Reset
CCP3MX = PORTB5 ' P3A/CCP3 input/output is multiplexed with RB5
HFOFST = On ' HFINTOSC output and ready status are not delayed by the oscillator stable status
T3CMX = PORTC0 ' Timer3 Clock Input (T3CKI) is on RC0
P2BMX = PORTB5 ' ECCP2 B (P2B) is on RB5
MCLRE = EXTMCLR ' MCLR pin enabled, RE3 input pin disabled
STVREN = Off ' Stack full/underflow will not cause Reset
LVP = Off ' Single-Supply ICSP disabled
XINST = Off ' Instruction set extension and Indexed Addressing mode disabled (Legacy mode)
DEBUG = Off ' Disabled
Cp0 = Off ' Block 0 (000800-001FFF) not code-protected
CP1 = Off ' Block 1 (002000-003FFF) not code-protected
CP2 = Off ' Block 2 (004000-005FFF) not code-protected
CP3 = Off ' Block 3 (006000-007FFF) not code-protected
CPB = Off ' Boot block (000000-0007FF) not code-protected
CPD = Off ' Data EEPROM not code-protected
WRT0 = Off ' Block 0 (000800-001FFF) not write-protected
WRT1 = Off ' Block 1 (002000-003FFF) not write-protected
WRT2 = Off ' Block 2 (004000-005FFF) not write-protected
WRT3 = Off ' Block 3 (006000-007FFF) not write-protected
WRTC = Off ' Configuration registers (300000-3000FF) not write-protected
WRTB = Off ' Boot Block (000000-0007FF) not write-protected
WRTD = Off ' Data EEPROM not write-protected
EBTR0 = Off ' Block 0 (000800-001FFF) not protected from table reads executed in other blocks
EBTR1 = Off ' Block 1 (002000-003FFF) not protected from table reads executed in other blocks
EBTR2 = Off ' Block 2 (004000-005FFF) not protected from table reads executed in other blocks
EBTR3 = Off ' Block 3 (006000-007FFF) not protected from table reads executed in other blocks
EBTRB = Off ' Boot Block (000000-0007FF) not protected from table reads executed in other blocks
Config_End

The above program will work on any Microchip PIC18F device or an Enhanced 14-bit core device, as long as it has enough pins for the buttons, LCD and WS2812B RGB LED controllers. However, if the device used does not have control of the individual internal pull-up resistors, set all of PORTB with them, or add external 10K pull-up resistors.

Also note that it is only the more recent 18F devices that have internal oscillators capable of 64MHz. For an enhanced 14-bit core device, or an older 18F device, alter the program's fuses to suit the oscillator method used on the device, and alter the Declare Xtal for the frequency the device is operating at.

Below is a video of the quiz controller firmware working in the Isis simulator:

The firmware source code and hex file and the simulator project can be downloaded from here:
Positron Quiz Controller