This entire project was coded in C following the Events and Service framework.
The code is available on BitBucket.
Governs the overall system of the main board. Takes charge of 20s timeout timer and 60s task timer.
Module: LeaderFSM
Constants:
ONE_SEC = 1000
TWENTY_SEC = ONE_SEC * 20
SIXTY_SEC = ONE_SEC * 60
NUM_AD_CHANNELS = 3
AD_CHANNEL = BIT2HI | BIT12HI | BIT11HI
Variables:
CurrentState: LeaderState_t
LastOnOffButton: boolean
MyPriority: uint8_t
LastAD_Values[NUM_AD_CHANNELS]: array of uint32_t
Functions:
InitLeaderFSM(Priority): boolean
MyPriority = Priority
Clear screen
Print "BubbyBot is here. Welcome home!"
SPI setup
Display initialization
Analog sensor initialization
Capacitive touch, joystick, IR wave, limit switch initialization
Servo motor output pins initialization
Set CurrentState to LeaderInitState
Post ES_INIT event
PostLeaderFSM(ThisEvent): boolean
Post the given event to the state machine's queue
RunLeaderFSM(ThisEvent): ES_Event_t
Initialize ReturnEvent to ES_NO_EVENT
Switch(CurrentState)
Case LeaderInitState:
If ThisEvent is ES_INIT
Set CurrentState to LeaderOffState
Turn off OnOffLED
Case LeaderOffState:
Turn off OnOffLED
Switch(ThisEvent)
Case ES_WELCOME:
Start Leader_Overall_Timer (60 seconds)
Post ES_NEW_STUFF event with EventParam = 0
Set CurrentState to LeaderWelcomeState
Turn on OnOffLED
Default: Do nothing
Case LeaderWelcomeState:
Turn on OnOffLED
Switch(ThisEvent)
Case ES_OFFMODE:
Stop timers
Set CurrentState to LeaderOffState
Turn off OnOffLED
Post ES_NEW_STUFF event with EventParam = 4
Case ES_TIMEOUT:
If ThisEvent.EventParam is Leader_Overall_Timer
Stop timers
Set CurrentState to LeaderOffState
Turn off OnOffLED
Else If ThisEvent.EventParam is Leader_Action_Timer
Stop Leader_Action_Timer
Set CurrentState to LeaderOffState
Turn off OnOffLED
Default:
Start Leader_Overall_Timer (60 seconds)
Set CurrentState to LeaderBusyState
Case LeaderBusyState:
Switch(ThisEvent)
Case ES_OFFMODE:
Stop timers
Set CurrentState to LeaderOffState
Turn off OnOffLED
Post ES_NEW_STUFF event with EventParam = 4
Case ES_TIMEOUT:
If ThisEvent.EventParam is Leader_Overall_Timer
Stop timers
Set CurrentState to LeaderOffState
Turn off OnOffLED
Else If ThisEvent.EventParam is Leader_Action_Timer
Stop Leader_Action_Timer
Set CurrentState to LeaderOffState
Turn off OnOffLED
Default:
Stop Leader_Action_Timer
Start Leader_Action_Timer (20 seconds)
Default: Do nothing
Return ReturnEvent
QueryLeaderFSM(): LeaderState_t
Return CurrentState
GetLastOnOffButton(): boolean
Return LastOnOffButton
SendLastOnOffButton(State): void
Set LastOnOffButton to State
Heart of the system. Updates the LED matrices according to other services' commands.
Module: LEDFSM
Constants:
ONE_SEC = 1000
QUARTER_SEC = ONE_SEC / 4
HALF_SEC = ONE_SEC / 2
TWO_SEC = ONE_SEC * 2
FIVE_SEC = ONE_SEC * 5
Variables:
CurrentState: LEDState_t
ClawPos[2]: array of uint8_t
MonsterPos: uint8_t
score: uint8_t
MyPriority: uint8_t
CharDeferralQueue[3 + 1]: array of ES_Event_t
Functions:
InitLEDFSM(Priority): boolean
MyPriority = Priority
Print initialization message
Initialize CharDeferralQueue
Set CurrentState to LEDInitState
Post ES_INIT event
PostLEDFSM(ThisEvent): boolean
Post the given event to the state machine's queue
RunLEDFSM(ThisEvent): ES_Event_t
Initialize ReturnEvent to ES_NO_EVENT
Switch(CurrentState)
Case LEDInitState:
If ThisEvent is ES_INIT
Print LED Service message
Set CurrentState to LEDWaitState
Initialize LEDService timer
Post ES_NEW_STUFF event with EventParam = 4 (OffMode)
Case LEDWaitState:
Switch(ThisEvent)
Case ES_NEW_STUFF:
Switch on EventParam
Case 5:
UpdateEndBufferWithScore(score)
Case 6:
GetClawMonsPos(ClawPos, MonsterPos)
If ifScored() is true
Increment score
UpdateBufferWithGameParam(ClawPos, MonsterPos, score)
Case 7:
GetClawMonsPos(ClawPos, MonsterPos)
Reset score to 0
UpdateBufferWithGameParam(ClawPos, MonsterPos, score)
Default:
UpdateBufferWithTemplate(EventParam)
Create newEvent with ES_UPDATE_DISPLAY and ' ' as EventParam
Post newEvent
Set CurrentState to LEDUpdateState
Case ES_TIMEOUT:
If EventParam is 2 (LEDSERVICE_TIMER)
Initialize LEDService timer
UpdateBufferWithTick()
Create newEvent with ES_UPDATE_DISPLAY and ' ' as EventParam
Post newEvent
Set CurrentState to LEDUpdateState
Case LEDUpdateState:
Switch(ThisEvent)
Case ES_UPDATE_DISPLAY:
If DM_TakeDisplayUpdateStep() is true
Set CurrentState to LEDWaitState
Recall deferred events from CharDeferralQueue
Else
Create newEvent with ES_UPDATE_DISPLAY and ' ' as EventParam
Post newEvent
Case ES_NEW_STUFF:
If DeferEvent to CharDeferralQueue is successful
Create newEvent with ES_UPDATE_DISPLAY and ' ' as EventParam
Post newEvent
Case ES_TIMEOUT:
If EventParam is 2 (LEDSERVICE_TIMER)
Initialize LEDService timer
UpdateBufferWithTick()
Create newEvent with ES_UPDATE_DISPLAY and ' ' as EventParam
Post newEvent
Default:
Set CurrentState to LEDWaitState
Return ReturnEvent
QueryLEDFSM(): LEDState_t
Return CurrentState
ifScored(): boolean
If ClawPos[1] is 3 and MonsterPos is within valid range
If MonsterPos + 1 is within ClawPos[0] - 4 and ClawPos[0] + 2
Return true
Else if MonsterPos is 29
If ClawPos[0] is 2, 28, or 29
Return true
Else if MonsterPos is 30
If ClawPos[0] is 2, 3, or 29
Return true
Else if MonsterPos is 31
If ClawPos[0] is 2, 3, or 4
Return true
Return false
Stick out or retract the arm. Or, you can also call it
'the key rack' :)
Module: ArmHandFSM
Constants:
ONE_SEC = 1000
FIFTEEN_MS = 15
TWENTY_FIVE_MS = 25
TICS_PER_MS = 2500
POS_180 = 0.7 * TICS_PER_MS
POS_0 = 2.25 * TICS_PER_MS
POS_90 = 1.7 * TICS_PER_MS
TICKS_PER_STEP = (POS_0 - POS_180) / 100
DIRECTION_CW = true
DIRECTION_CCW = false
SERVO_CHAN = 4
Variables:
CurrentPosition: uint16_t
timeStep: uint16_t
isMoving: boolean
moveDirection: boolean
targetPosition: uint16_t
CurrentState: ArmHandState_t
handLoaded: boolean
MyPriority: uint8_t
CharDeferralQueue[3 + 1]: ES_Event_t array
Functions:
InitArmHandFSM(Priority): boolean
MyPriority = Priority
If PWMSetup_BasicConfig(4) or
PWMSetup_SetPeriodOnTimer(10000, _Timer2_) or
PWMSetup_AssignChannelToTimer(SERVO_CHAN, _Timer2_) or
PWMSetup_MapChannelToOutputPin(SERVO_CHAN, PWM_RPA2) or
PWMOperate_SetPulseWidthOnChannel(POS_90, SERVO_CHAN) returns false
Return false
End If
CurrentPosition = POS_90
targetPosition = POS_90
ES_InitDeferralQueueWith(CharDeferralQueue, ARRAY_SIZE(CharDeferralQueue))
CurrentState = ArmInitState
PostArmHandFSM with EventType of ES_INIT
Return true
PostArmHandFSM(ThisEvent): boolean
Return ES_PostToService(MyPriority, ThisEvent)
RunArmHandFSM(ThisEvent): ES_Event_t
ReturnEvent = ES_NO_EVENT
Switch CurrentState
Case ArmInitState:
If ThisEvent is ES_INIT
CurrentState = ArmUpState
End If
Case ArmUpState:
Switch ThisEvent.EventType
Case ES_WAVE:
targetPosition = POS_180
moveDirection = DIRECTION_CW
isMoving = true
CurrentState = ArmMovingState
SetTimer(SERVO_ARM_TIMER, timeStep)
End Switch
Case ArmDownState:
Switch ThisEvent.EventType
Case ES_WAVE:
If not handLoaded
targetPosition = POS_90
moveDirection = DIRECTION_CCW
isMoving = true
CurrentState = ArmMovingState
SetTimer(SERVO_ARM_TIMER, timeStep)
Else
Do Nothing
End If
End Switch
Case ArmMovingState:
Switch ThisEvent.EventType
Case ES_TIMEOUT:
If SERVO_ARM_TIMER is ThisEvent.EventParam and isMoving
TakeMoveArmStep()
If isMoving
SetTimer(SERVO_ARM_TIMER, timeStep)
Else
PostArmHandFSM with EventType of ES_ARM_MOVE_DONE
End If
End If
Case ES_ARM_MOVE_DONE:
If abs(CurrentPosition - POS_180) <= TICKS_PER_STEP
CurrentState = ArmDownState
ElseIf abs(CurrentPosition - POS_90) <= TICKS_PER_STEP
CurrentState = ArmUpState
Else
CurrentState = ArmUpState
End If
End Switch
End Switch
Return ReturnEvent
QueryArmHandFSM(): ArmHandState_t
Return CurrentState
End Module
# GameFSM Module
# Constants
ONE_SEC = 1000
QUARTER_SEC = ONE_SEC / 4
HALF_SEC = ONE_SEC / 2
TWENTY_SEC = ONE_SEC * 20 + 5
FIVE_SEC = ONE_SEC * 5
SEVEN_SEC = ONE_SEC * 7
# Module Variables
CurrentState
MyPriority
ClawPos[2] # x from 2 to 29, y from 0 to 3
MonsterPos # from 0 to 31
JoystickEventTable[5] # {-2, -1, 0, 2, 2}
DownStepTable[6] # {1, 2, 3, 2, 1, 0}
JoystickEvent
# Module Functions
function InitGameFSM(Priority):
MyPriority = Priority
CurrentState = GameInitState
PostInitEventToService(MyPriority)
function PostGameFSM(ThisEvent):
return PostToService(MyPriority, ThisEvent)
function RunGameFSM(ThisEvent):
ReturnEvent
ReturnEvent = ES_NO_EVENT
switch CurrentState:
case GameInitState:
if ThisEvent.EventType is ES_INIT:
CurrentState = GameIdleState
case GameIdleState:
switch ThisEvent.EventType:
case ES_GAME_START:
PostEventToLEDFSM with EventType of ES_NEW_STUFF, EventParam of 7
Initialize GAMESERVICE_TIMER with TWENTY_SEC + 2500
Initialize GAMEUPDATE_TIMER with QUARTER_SEC
ClawPos[0] = 15
ClawPos[1] = 0
MonsterPos = 24
CurrentState = GameOnState
case GameOnState:
switch ThisEvent.EventType:
case ES_JOYSTICK:
JoystickEvent = ThisEvent.EventParam
case ES_TIMEOUT:
if ThisEvent.EventParam is GAMESERVICE_TIMER:
Initialize GAMESERVICE_TIMER with SEVEN_SEC
CurrentState = GameEndState
PostEventToLEDFSM with EventType of ES_NEW_STUFF, EventParam of 5
PostEventToEncodeFSM with EventType of ES_ENCODE, EventParam of 5
PostEventToDispenserService with EventType of ES_DISPENSE
else if ThisEvent.EventParam is GAMEUPDATE_TIMER:
Initialize GAMEUPDATE_TIMER with QUARTER_SEC
Call UpdateClawMonPos(JoystickEvent)
PostEventToLEDFSM with EventType of ES_NEW_STUFF, EventParam of 6
case GameEndState:
switch ThisEvent.EventType:
case ES_TIMEOUT:
if ThisEvent.EventParam is GAMESERVICE_TIMER:
PostEventToLeaderFSM with EventType of ES_OFFMODE
PostEventToLEDFSM with EventType of ES_NEW_STUFF, EventParam of 4
PostEventToEncodeFSM with EventType of ES_ENCODE, EventParam of 7
CurrentState = GameIdleState
return ReturnEvent
function QueryGameFSM():
return CurrentState
function GetClawMonsPos(DesClawPos, DesMonsPos):
DesClawPos[0] = ClawPos[0]
DesClawPos[1] = ClawPos[1]
DesMonsPos = MonsterPos
# Private Functions
function UpdateClawMonPos(JoyStickEvent):
DownStep = 0
DownFlag = 0
if JoyStickEvent is 'd':
DownFlag = 1
# y pos
if DownFlag is 1:
ClawPos[1] = DownStepTable[DownStep]
DownStep++
if DownStep is 5:
DownFlag = 0
DownStep = 0
# x pos
else if JoyStickEvent is between 1 and 5:
if ClawPos[0] is between 3 and 28:
ClawPos[0] += JoystickEventTable[JoyStickEvent - 1]
if ClawPos[0] is 3:
if JoyStickEvent is 1 or 2:
ClawPos[0] = 2
else if JoyStickEvent is 4 or 5:
ClawPos[0] += JoystickEventTable[JoyStickEvent - 1]
if ClawPos[0] is 28:
if JoyStickEvent is 4 or 5:
ClawPos[0] = 29
else if JoyStickEvent is 1 or 2:
ClawPos[0] += JoystickEventTable[JoyStickEvent - 1]
if ClawPos[0] is 2:
if JoyStickEvent is 4 or 5:
ClawPos[0] += JoystickEventTable[JoyStickEvent - 1]
if ClawPos[0] is 29:
if JoyStickEvent is 1 or 2:
ClawPos[0] += JoystickEventTable[JoyStickEvent - 1]
# update monster
MonsterPos = (MonsterPos + 31 - 1) % 31
# MoodFSM Module
# Constants
InitPState = 0
WaitingForButton = 1
# Module Variables
CurrentState
LastHappyState
LastNeutralState
LastSadState
MyPriority
# Module Functions
function InitMoodFSM(Priority):
ThisEvent
MyPriority = Priority
CurrentState = InitPState
ThisEvent.EventType = ES_INIT
# Initialize mood buttons
ANSELBbits.ANSB3 = 0 # happy
TRISBbits.TRISB3 = 1 # happy
LastHappyState = PORTBbits.RB3
LastNeutralState = PORTBbits.RB4
LastSadState = PORTBbits.RB8
if ES_PostToService(MyPriority, ThisEvent) is true:
return true
else:
return false
function PostMoodFSM(ThisEvent):
return ES_PostToService(MyPriority, ThisEvent)
function RunMoodFSM(ThisEvent):
ReturnEvent
ReturnEvent.EventType = ES_NO_EVENT
switch CurrentState:
case InitPState:
if ThisEvent.EventType == ES_INIT:
CurrentState = WaitingForButton
break
case WaitingForButton:
if ThisEvent.EventType == ES_MOOD_BUTTON:
newEvent
newEvent.EventType = ES_NEW_STUFF
newEvent.EventParam = ThisEvent.EventParam
PostLEDFSM(newEvent)
newEvent.EventType = ES_ENCODE
newEvent.EventParam = ThisEvent.EventParam
PostEncodeFSM(newEvent)
return ReturnEvent
function QueryMoodFSM():
return CurrentState
function GetLastButtonState(type):
switch type:
case 1:
return LastHappyState
case 2:
return LastNeutralState
case 3:
return LastSadState
function SendLastButtonState(State, type):
switch type:
case 1:
LastHappyState = State
case 2:
LastNeutralState = State
case 3:
LastSadState = State
Left half of the bridge between two PICs. The service that connected with the most modules.
Module: EncodeFSM
# Module Constants
DELAY_SHORT = 35
DELAY_LONG = 70
DELAY_BETWEEN = 20
DELAY_DEFER = 150
# Module Variables
CurrentState
MyPriority
signalNum
EncodeDeferralQueue
CurrentPinState
# Initialize EncodeFSM
function InitEncodeFSM(Priority) -> Success
Set TRISB5 as output
Set ENCODE_PIN_SET to false
Initialize EncodeDeferralQueue
MyPriority = Priority
CurrentState = InitEncodeState
signalNum = 10
Post ES_INIT event to this module
return Success
end function
# Post an event to EncodeFSM
function PostEncodeFSM(ThisEvent) -> Success
return Post ES event to this module with ThisEvent
end function
# Run EncodeFSM based on incoming events
function RunEncodeFSM(ThisEvent) -> ReturnEvent
Set ReturnEvent to ES_NO_EVENT
switch CurrentState
case InitEncodeState:
if ThisEvent is ES_INIT
signalNum = 10
Set CurrentState to WritingSignalState
end if
end case
case WritingSignalState:
switch ThisEvent
case ES_ENCODE:
signalNum = ThisEvent.EventParam
Set ENCODE_PIN_SET to true
Start timer with DELAY_LONG
Set CurrentState to SendSignalState
end switch
end switch
end case
case DelayState:
switch ThisEvent
case ES_TIMEOUT:
if ThisEvent.EventParam is ENCODER_TIMER
signalNum = 10
Recall deferred events
Set CurrentState to WritingSignalState
end if
end switch
case ES_ENCODE:
Defer ThisEvent
end switch
end switch
end case
case SendSignalState:
switch ThisEvent
case ES_TIMEOUT:
if ThisEvent.EventParam is ENCODER_TIMER
if ENCODE_PIN_STAT is true
Set ENCODE_PIN_SET to false
Start timer with DELAY_BETWEEN
else
if signalNum > 0
Set ENCODE_PIN_SET to true
signalNum = signalNum - 1
Start timer with DELAY_SHORT
else if signalNum is 0
Set ENCODE_PIN_SET to true
signalNum = signalNum - 1
Start timer with DELAY_BETWEEN
else
Set CurrentState to DelayState
Start timer with DELAY_DEFER
end if
end if
end if
end switch
case ES_ENCODE:
Defer ThisEvent
end switch
end switch
end case
# Add more cases as needed
end switch
return ReturnEvent
end function
# Query the current state of EncodeFSM
function QueryEncodeSM() -> CurrentState
return CurrentState
end function
Right half of the bridge between two PICs. Always waits for a signal 🫡.
Module: DecodingFSM
Constants:
DELAY_SHORT 35
DELAY_LONG 70
DELAY_LAST_PULSE 20
DELAY_BEFER_RECALL 150
TOLERANCE 3
Variables:
MyPriority: uint8_t
CurrentState: DecodingState_t
LastInputState: boolean
TimeOfLastRise: uint16_t
TimeOfLastFall: uint16_t
Functions:
InitDecodingFSM(Priority): boolean
MyPriority = Priority
Post ES_INIT
Configure pin RB15 as digital input
Set LastInputSignal to PORTBbits.RB15
Set CurrentState to InitPState
PostDecodingFSM(ThisEvent): boolean
Return ES_PostToService(MyPriority, ThisEvent)
RunDecodingFSM(ThisEvent): ES_Event_t
ReturnEvent: ES_Event_t
ReturnEvent.EventType = ES_NO_EVENT
PulseNumber: uint8_t = 0
LastSignal: boolean = false
Switch CurrentState
Case InitPState:
If EventType is ES_INIT
Set CurrentState to Waiting
Case Waiting:
Switch ThisEvent.EventType
Case ES_RISING_EDGE:
Set TimeOfLastRise to ThisEvent.EventParam
Case ES_FALLING_EDGE:
Set TimeOfLastFall to ThisEvent.EventParam
If (TimeOfLastFall - TimeOfLastRise) is within DELAY_LONG +/- TOLERANCE
Set PulseNumber to 1
Else if (TimeOfLastFall - TimeOfLastRise) is within DELAY_SHORT +/- TOLERANCE
Increment PulseNumber by 1
Else if (TimeOfLastFall - TimeOfLastRise) is within DELAY_LAST_PULSE +/-
TOLERANCE
AudioEvent: ES_Event_t
Set AudioEvent.EventType to ES_PLAY_AUDIO
Set AudioEvent.EventParam to PulseNumber
Post AudioEvent to AudioService
End Switch
End Switch
Return ReturnEvent
End Module
Module: DispenserService
Constants:
ONE_SEC = 1000
FIFTEEN_MS = 15
TICS_PER_MS = 2500
POS_180 = (uint16_t)(0.7 * TICS_PER_MS)
POS_0 = (uint16_t)(2.25 * TICS_PER_MS)
TICKS_PER_STEP = (POS_0 - POS_180) / 100
DIRECTION_CW = true
DIRECTION_CCW = false
SERVO_CHAN = 3
Variables:
MyPriority: uint8_t
CurrentPosition: uint16_t
timeStep: uint16_t
isMoving: boolean
moveDirection: boolean
targetPosition: uint16_t
reversed: boolean
Functions:
InitDispenserService(Priority): boolean
MyPriority = Priority
If PWMSetup_BasicConfig(4) or
PWMSetup_SetPeriodOnTimer(10000, _Timer3_) or
PWMSetup_AssignChannelToTimer(SERVO_CHAN, _Timer3_) or
PWMSetup_MapChannelToOutputPin(SERVO_CHAN, PWM_RPA3) or
PWMOperate_SetPulseWidthOnChannel(AngleToServoDispenserPos(180), SERVO_CHAN) returns false
Return false
End If
CurrentPosition = AngleToServoDispenserPos(180)
targetPosition = AngleToServoDispenserPos(180)
PostDispenserService(ThisEvent): boolean
Return ES_PostToService(MyPriority, ThisEvent)
RunDispenserService(ThisEvent): ES_Event_t
ReturnEvent: ES_Event_t
ReturnEvent.EventType = ES_NO_EVENT
Switch ThisEvent.EventType
Case ES_TIMEOUT:
If Servo_Dispenser_Timer is ThisEvent.EventParam and isMoving
TakeMoveDispenserStep()
If isMoving
If CurrentPosition > AngleToServoDispenserPos(3) and not reversed
moveDirection = DIRECTION_CW
targetPosition = AngleToServoDispenserPos(180)
reversed = true
Initialize Servo_Dispenser_Timer with timeStep
Else If CurrentPosition < AngleToServoDispenserPos(177) and reversed
moveDirection = DIRECTION_CCW
targetPosition = CurrentPosition
reversed = false
isMoving = false
Else
Initialze Servo_Dispenser_Timer with timeStep
End If
End If
End If
Case ES_DISPENSE:
newPosition = AngleToServoDispenserPos(0)
moveDirection = DIRECTION_CCW
targetPosition = newPosition
reversed = false
isMoving = true
Initialize Servo_Dispenser_Timer with timeStep
End Switch
Return ReturnEvent
TakeMoveDispenserStep(): void
If moveDirection is DIRECTION_CCW
CurrentPosition += TICKS_PER_STEP
If targetPosition > CurrentPosition and targetPosition <= POS_0
call PWMOperate_SetPulseWidthOnChannel(CurrentPosition, SERVO_CHAN)
Else
CurrentPosition -= TICKS_PER_STEP
isMoving = false
Else If moveDirection is DIRECTION_CW
CurrentPosition -= TICKS_PER_STEP
If targetPosition < CurrentPosition and targetPosition >= POS_180
call PWMOperate_SetPulseWidthOnChannel(CurrentPosition, SERVO_CHAN)
Else
CurrentPosition += TICKS_PER_STEP
isMoving = false
Module: AudioService
Constants:
WelcomeHome LATBbits.LATB2
HappyMoodSound LATBbits.LATB3
NeutralMoodSound LATBbits.LATB4
SadMoodSound LATBbits.LATB5
GameStart LATBbits.LATB8
GameOver LATBbits.LATB9
LoadCellReading LATBbits.LATB10
GoodNight LATBbits.LATB11
SadMoodLED LATBbits.LATB12
NeutralMoodLED LATBbits.LATB13
HappyMoodLED LATAbits.LATA1
ONE_SEC 1000
HALF_SEC (ONE_SEC / 2)
WELCOME_HOME_DURATION 4983+300
HAPPY_DURATION 6015+300
NEUTRAL_DURATION 5687+300
SAD_DURATION 14460+300
GAME_START_DURATION 22159+300
GAME_END_DURATION 6793+200
LOAD_CELL_DURATION 2183+300
GOOD_NIGHT_DURATION 3788+300
Variables:
MyPriority: uint8_t
Functions:
InitAudioService(Priority): boolean
MyPriority = Priority
Post ES_INIT
PostAudioService(ThisEvent): boolean
Return ES_PostToService(MyPriority, ThisEvent)
RunAudioService(ThisEvent): ES_Event_t
ReturnEvent: ES_Event_t
ReturnEvent.EventType = ES_NO_EVENT
Switch ThisEvent.EventType
Case ES_INIT:
SPI setup
Ports setup
Set all sound pins to 1
Set all LED pins to 0
Case ES_PLAY_AUDIO:
Switch ThisEvent.EventParam
Case 1:
Set WelcomeHome to 0
Set all other sound pins to 1
Stop AUDIO_TIMER
Start AUDIO_TIMER for WELCOME_HOME_DURATION
Case 2:
Set HappyMoodSound to 0
Set all other sound pins to 1
Stop AUDIO_TIMER
Start AUDIO_TIMER for HAPPY_DURATION
Case 3:
Set NeutralMoodSound to 0
Set all other sound pins to 1
Stop AUDIO_TIMER
Start AUDIO_TIMER for NEUTRAL_DURATION
Case 4:
Set SadMoodSound to 0
Set all other sound pins to 1
Stop AUDIO_TIMER
Start AUDIO_TIMER for SAD_DURATION
Case 5:
Set GameStart to 0
Set all other sound pins to 1
Stop AUDIO_TIMER
Start AUDIO_TIMER for GAME_START_DURATION
Case 6:
Set GameOver to 0
Set all other sound pins to 1
Stop AUDIO_TIMER
Start AUDIO_TIMER for GAME_END_DURATION
Case 7:
Set LoadCellReading to 0
Set all other sound pins to 1
Stop AUDIO_TIMER
Start AUDIO_TIMER for LOAD_CELL_DURATION
Case 8:
Set GoodNight to 0
Set all other sound pins to 1
Stop AUDIO_TIMER
Start AUDIO_TIMER for GOOD_NIGHT_DURATION
End Switch
Case ES_TIMEOUT:
Set all sound pins to 1
End Switch
Return ReturnEvent
Module: CapTouchChecker
Constants:
ONE_SEC = 1000
TWO_SEC = ONE_SEC * 2
HALF_SEC = ONE_SEC * 0.5
QUARTER_SEC = ONE_SEC * 0.25
Variables:
LastCapTouchState: boolean
LastCapTouchTime: uint16_t
Functions:
InitCapTouchStatus(): void
Set TRISBbits.TRISB11 to 1 // Cap touch switch input
LastCapTouchState = PORTBbits.RB11
LastCapTouchTime = ES_Timer_GetTime()
CheckCapTouchEvents(): boolean
ReturnVal = false
CurrentCapTouchState: boolean
flag: boolean
CurrentCapTouchState = PORTBbits.RB11
If CurrentCapTouchState is not equal to LastCapTouchState
If CurrentCapTouchState is false
ReturnVal = true
NewCapTouchTime = ES_Timer_GetTime()
If absolute value of (ES_Timer_GetTime() - LastCapTouchTime) is less than QUARTER_SEC
LastCapTouchTime = NewCapTouchTime
newEvent: ES_Event_t
newEvent.EventType = ES_GAME_START
PostGameFSM(newEvent)
newEvent.EventType = ES_ENCODE
newEvent.EventParam = 4
Post newEvent to EncodeFSM
flag = 0
Else
flag = 1
LastCapTouchTime = NewCapTouchTime
If flag is 1 and (ES_Timer_GetTime() - LastCapTouchTime) is greater than QUARTER_SEC
newEvent: ES_Event_t
newEvent.EventType = ES_WELCOME
Post newEvent to LeaderFSM
newEvent.EventType = ES_ENCODE
newEvent.EventParam = 0
Post newEvent to EncodeFSM
flag = 0
LastCapTouchState = CurrentCapTouchState
Return ReturnVal
Module: EventCheckers
# Event Checker for Lock
function CheckForLockEvent() -> bool
if (currentLockState != lastLockState) and (currentLockState == HIGH)
createAndPostEvent(LOCK_EVENT, 1)
return true
end if
updateLastLockState(currentLockState)
return false
end function
# Event Checker for Keystroke
function CheckForKeyStrokeEvent() -> bool
if isNewKeyReady()
createAndPostEvent(NEW_KEY_EVENT, getNewKey())
return true
end if
return false
end function
# Event Checker for Happy Button
function CheckForHappyButtonEvent() -> bool
if leaderFSMState != LeaderOffState
currentButtonState = readHappyButtonState()
if currentButtonState != lastHappyButtonState
createAndPostMoodEvent(1) # Happy Mood
return true
end if
updateLastButtonState(currentButtonState)
end if
return false
end function
# Event Checker for Neutral Button
function CheckForNeutralButtonEvent() -> bool
# Similar structure for other button check functions
end function
# Event Checker for Sad Button
function CheckForSadButtonEvent() -> bool
# Similar structure for other button check functions
end function
# Event Checker for On/Off Button
function CheckForOnOffButtonEvent() -> bool
currentButtonState = readOnOffButtonState()
if currentButtonState != lastOnOffButtonState
if currentButtonState == PRESSED
if LEDState == OFF
createAndPostWelcomeEvent()
else
createAndPostOffModeEvent()
end if
end if
postLeaderFSMEvent()
postEncodeFSMEvent()
return true
end if
updateLastOnOffButtonState(currentButtonState)
return false
end function
# Helper Function: Create and Post an Event
function createAndPostEvent(eventType, eventParam)
newEvent = createEvent(eventType, eventParam)
postEventToAllServices(newEvent)
end function
# Helper Function: Create and Post a Mood Event
function createAndPostMoodEvent(moodType)
moodEvent = createMoodEvent(moodType)
postMoodFSMEvent(moodEvent)
end function
# IRWaveChecker Module
# Constants
ONE_SEC = 1000
TWO_SEC = ONE_SEC * 2
# Module Variables
LastWaveCheckState1
LastWaveCheckState2
LastWaveTime1
LastWaveTime2
# Module Functions
function InitIRWaveStatus():
Configure ANSELBbits.ANSB15 as digital
Set TRISAbits.TRISA4 as input # IR sensor input 1
Set TRISBbits.TRISB15 as input # IR sensor input 2
LastWaveCheckState1 = PORTAbits.RA4
LastWaveCheckState2 = PORTBbits.RB15
LastWaveTime2 = GetTime()
LastWaveTime1 = GetTime()
function CheckIRWaveEvents():
ReturnVal = false
if Leader is LeaderOffState:
return ReturnVal
CurrentWaveCheckState1 = PORTAbits.RA4
CurrentWaveCheckState2 = PORTBbits.RB15
if CurrentWaveCheckState1 is not LastWaveCheckState1:
NewWaveTime1 = GetTime()
LastWaveCheckState1 = CurrentWaveCheckState1
if CurrentWaveCheckState1 is true:
LastWaveTime1 = NewWaveTime1
if CurrentWaveCheckState2 is true:
LastWaveTime1 = NewWaveTime1
if (NewWaveTime1 - LastWaveTime2) is between 0 and ONE_SEC:
newEvent.EventType = ES_WAVE
PostArmHandFSM(newEvent)
if CurrentWaveCheckState2 is not LastWaveCheckState2:
NewWaveTime2 = GetTime()
LastWaveCheckState2 = CurrentWaveCheckState2
if CurrentWaveCheckState2 is true:
LastWaveTime2 = NewWaveTime2
if (NewWaveTime2 - LastWaveTime1) is between 0 and ONE_SEC:
newEvent.EventType = ES_WAVE
PostArmHandFSM(newEvent)
return ReturnVal
# JoystickChecker Module
# Constants
NUM_AD_CHANNELS = 3
AD_CHANNEL = (BIT2HI | BIT12HI | BIT11HI)
# Module Variables
LastAD_Values[NUM_AD_CHANNELS]
# Module Functions
function InitJoystickStatus():
Configure ANSELBbits.ANSB2 as analog input
Configure ANSELBbits.ANSB12 as analog input # AN12, RB12
Configure ANSELBbits.ANSB13 as analog input # AN11, RB13
Set TRISBbits.TRISB12 as input # joystick input
Set TRISBbits.TRISB13 as input # joystick input
Set TRISBbits.TRISB2 as input
ADC_ConfigAutoScan(AD_CHANNEL) # Configure ADC for auto-scan on specified channels
ADC_MultiRead(LastAD_Values) # Read initial ADC values
function CheckJoystickEvents():
ReturnVal = false
CurrentAD_Values[NUM_AD_CHANNELS]
DifferenceInAD_Result[NUM_AD_CHANNELS]
Call ADC_MultiRead(CurrentAD_Values)
for i = 1 to NUM_AD_CHANNELS - 1:
if abs(CurrentAD_Values[i] - LastAD_Values[i]) >= 100:
ReturnVal = true
break
if ReturnVal is true:
newEvent.EventType = ES_JOYSTICK
if CurrentAD_Values[1] > 800:
newEvent.EventParam = 'd'
else:
if CurrentAD_Values[2] < 205:
newEvent.EventParam = 1
else if CurrentAD_Values[2] < 410:
newEvent.EventParam = 2
else if CurrentAD_Values[2] < 615:
newEvent.EventParam = 3
else if CurrentAD_Values[2] < 820:
newEvent.EventParam = 4
else:
newEvent.EventParam = 5
Post newEvent to GameFSM
for i = 1 to NUM_AD_CHANNELS - 1:
LastAD_Values[i] = CurrentAD_Values[i]
return ReturnVal
Module: EventCheckers
Functions:
CheckSignalChange(void): boolean
ReturnVal: bool = false
CurrentInputSignal: bool = PORTBbits.RB15
ThisEvent: ES_Event_t
If CurrentInputSignal is different from LastInputSignal:
If CurrentInputSignal is 1:
Set ThisEvent.EventType to ES_RISING_EDGE
Set ThisEvent.EventParam to current time
Else:
Set ThisEvent.EventType to ES_FALLING_EDGE
Set ThisEvent.EventParam to current time
Post ThisEvent to DecodingFSM
Set ReturnVal to true
Update last input signal to CurrentInputSignal
Return ReturnVal
Module: DM_Display
# Include Files
include "PIC32_SPI_HAL.pseudo"
include "DM_Display.pseudo"
include "FontStuff.pseudo"
# Module Defines
NumModules = 4
NumLEDDisplays = 3
NUM_ROWS = 8
NUM_ROWS_IN_FONT = 5
DM_START_SHUTDOWN = 0x0C00
DM_END_SHUTDOWN = 0x0C01
DM_DISABLE_CODEB = 0x0900
DM_ENABLE_SCAN = 0x0B07
DM_SET_BRIGHT = 0x0A00
# Module Types
union DM_Row_t
FullRow: uint32_t
ByBytes: uint8_t[NumModules]
enum InitStep_t
DM_StepStartShutdown = 0
DM_StepFillBufferZeros
DM_StepDisableCodeB
DM_StepEnableScanAll
DM_StepSetBrightness
DM_StepCopyBuffer2Display
DM_StepEndShutdown
# Module Functions
function sendCmd(Cmd2Send: uint16_t)
function sendRow(RowNum: uint8_t, RowData: DM_Row_t)
# Module Variables
DM_Display: array[NUM_ROWS][NumLEDDisplays] of DM_Row_t
CurrentInitStep: InitStep_t = DM_StepStartShutdown
# Bit reversal lookup table
BitReverseTable256: array of uint8_t
ClawWalls: uint32_t = 0b11000000000000000000000000000011
# Module Code
# DM_TakeInitDisplayStep Function
function DM_TakeInitDisplayStep() -> bool
rowIndex: uint8_t = 0
ReturnVal: bool = false
switch CurrentInitStep
case DM_StepStartShutdown
sendCmd(DM_START_SHUTDOWN)
CurrentInitStep++
case DM_StepFillBufferZeros
DM_ClearDisplayBuffer()
CurrentInitStep++
case DM_StepDisableCodeB
sendCmd(DM_DISABLE_CODEB)
CurrentInitStep++
case DM_StepEnableScanAll
sendCmd(DM_ENABLE_SCAN)
CurrentInitStep++
case DM_StepSetBrightness
sendCmd(DM_SET_BRIGHT)
CurrentInitStep++
case DM_StepCopyBuffer2Display
if DM_TakeDisplayUpdateStep() is true
CurrentInitStep++
else
# Do nothing
end if
case DM_StepEndShutdown
sendCmd(DM_END_SHUTDOWN)
ReturnVal = true
CurrentInitStep = DM_StepStartShutdown
otherwise
# Do nothing
end switch
return ReturnVal
# DM_TakeDisplayUpdateStep Function
function DM_TakeDisplayUpdateStep() -> bool
ReturnVal: bool = false
WhichRow: int8_t = 0
sendRow(WhichRow, DM_Display[WhichRow])
if WhichRow < 8
WhichRow++
else
ReturnVal = true
WhichRow = 0
end if
return ReturnVal
# DM_ScrollDisplayBuffer Function
function DM_ScrollDisplayBuffer(NumCols2Scroll: uint8_t, WhichLED: uint8_t)
WhichRow: uint8_t
for WhichRow = 0 to NUM_ROWS - 1
Bitshift WhichRow of DM_Display by NumCols2Scroll
end for
end function
# DM_AddChar2DisplayBuffer Function
function DM_AddChar2DisplayBuffer(Char2Display: unsigned char, WhichLED: uint8_t)
WhichRow: uint8_t
for WhichRow = 0 to NUM_ROWS_IN_FONT - 1
Set display buffer's corresponding row with getFontLine(Char2Display, WhichRow)
end for
end function
# DM_ClearDisplayBuffer Function
function DM_ClearDisplayBuffer()
rowIndex: uint8_t
LEDIndex: uint8_t
for rowIndex = 0 to NUM_ROWS - 1
for LEDIndex = 0 to NumLEDDisplays - 1
Call DM_PutDataIntoBufferRow(0x0000, rowIndex, LEDIndex)
end for
end for
end function
# DM_PutDataIntoBufferRow Function
function DM_PutDataIntoBufferRow(Data2Insert: uint32_t, WhichRow: uint8_t, WhichLED: uint8_t) -> bool
ReturnVal: bool = true
if WhichRow < 0 or WhichRow >= NUM_ROWS or WhichLED < 0 or WhichLED >= NumLEDDisplays
ReturnVal = false
else
Set display buffer's WhichRow as Data2Insert
end if
return ReturnVal
end function
# DM_QueryRowData Function
function DM_QueryRowData(WhichLED: uint8_t, RowToQuery: uint8_t, pReturnValue: uint32_t) -> bool
ReturnVal: bool = true
if RowToQuery < 0 or RowToQuery >= NUM_ROWS or WhichLED < 0 or WhichLED > NumLEDDisplays
ReturnVal = false
else
Set pReturnValue as display buffer's corresponding row data
end if
return ReturnVal
end function
# UpdateBufferWithTemplate Function
function UpdateBufferWithTemplate(BotMode: uint8_t) -> bool
ReturnVal: bool = true
TempIndex: uint8_t = 0
if BotMode < 0 or BotMode > 6
ReturnVal = false
else
if BotMode == 0 or BotMode == 4
for i = 0 to NumLEDDisplays - 1
for WhichRow = 0 to NUM_ROWS - 1
call DM_PutDataIntoBufferRow to update the buffer with template data of BotMode
TempIndex++
end for
end for
else
// keep progress bar
Progress_1: uint32_t = DM_Display[5][0].FullRow
Progress_2: uint32_t = DM_Display[6][0].FullRow
for i = 0 to NumLEDDisplays - 1
for WhichRow = 0 to NUM_ROWS - 1
call DM_PutDataIntoBufferRow to update the buffer with template data of BotMode
TempIndex++
end for
end for
Call DM_PutDataIntoBufferRow(Progress_1, 5, 0)
Call DM_PutDataIntoBufferRow(Progress_2, 6, 0)
end if
end if
return ReturnVal
end function
# UpdateBufferWithTick Function
function UpdateBufferWithTick()
Bitshift the progress bar rows while keeping the frame unchanged
end function
# UpdateBufferWithGameParam Function
function UpdateBufferWithGameParam(ClawPos: uint8_t*, MonsterPos: uint8_t, Score: uint8_t)
// keep track of progress
Progress_1: uint32_t = DM_Display[5][0].FullRow
Progress_2: uint32_t = DM_Display[6][0].FullRow
UpdateBufferWithTemplate(6)
// update claw
Call DM_PutDataIntoBufferRow() to update the claw's position with ClawPos var
Different cases for different LED matrix rows:
Manually update the buffer data for the last 2 rows of the first LED matrix
Use for loop to update the buffer data for the first 5 rows of the second LED matrix
Manually update the buffer data for the 6th row of the second LED matrix and do OR operation with monster data
// update monster
Call DM_PutDataIntoBufferRow() to update the monster's position with MonsterPos var
Different cases for different LED matrix rows:
Use for loop to update the buffer data for the 6th and 7th rows of the second LED matrix
Manually update the buffer data for the 1st row of the third LED matrix
Call AddScoreToGameBuffer(Score)
// keep progress bar the same
DM_PutDataIntoBufferRow(Progress_1, 5, 0)
DM_PutDataIntoBufferRow(Progress_2, 6, 0)
end function
# AddScoreToGameBuffer Function
function AddScoreToGameBuffer(score: uint8_t) -> bool
ReturnVal: bool = true
if score < 0 or score > 99
ReturnVal = false
else
if score < 10
for i = 0 to 3
Clean the outdated score
Update score data with template
end for
else
for i = 0 to 3
Clean the outdated score
Update score data with template:
First Update with ten's number, then bitshift, then update one's number
end for
end if
end if
return ReturnVal
end function
# AddScoreToEndBuffer Function
function AddScoreToEndBuffer(score: uint8_t) -> bool
ReturnVal: bool = true
if score < 0 or score > 99
ReturnVal = false
else
// store 'GOOD'
TempLED0: uint32_t[8]
for i = 0 to NUM_ROWS - 1
Call DM_QueryRowData(2, i, TempLED0[i])
Call DM_PutDataIntoBufferRow(0x0000, i, 2) to clean the buffer
end for
tenNum: char = '0' + score / 10
oneNum: char = '0' + score % 10
if score < 10
Call DM_AddChar2DisplayBuffer(oneNum, 2)
Call DM_ScrollDisplayBuffer(1, 2)
else
Call DM_AddChar2DisplayBuffer(tenNum, 2)
Call DM_ScrollDisplayBuffer(4, 2)
Call DM_AddChar2DisplayBuffer(oneNum, 2)
Call DM_ScrollDisplayBuffer(1, 2)
end if
for i = 0 to NUM_ROWS - 1
Use OR operation to put stored data into buffer
end for
end if
return ReturnVal
end function
# UpdateEndBufferWithScore Function
function UpdateEndBufferWithScore(score: uint8_t)
// keep progress bar the same
Progress_1: uint32_t = DM_Display[5][0].FullRow
Progress_2: uint32_t = DM_Display[6][0].FullRow
Call UpdateBufferWithTemplate(5)
Call AddScoreToEndBuffer(score)
end function
# sendCmd Function
function sendCmd(Cmd2Send: uint16_t)
index: uint8_t
for index = 0 to NumModules * NumLEDDisplays - 2
Call SPIOperate_SPI1_Send16(Cmd2Send)
Wait until SPIBUF is available to send data
end for
Call SPIOperate_SPI1_Send16Wait(Cmd2Send)
end function
# sendRow Function
function sendRow(RowNum: uint8_t, RowData: DM_Row_t[])
index: uint8_t
// The rows on the display are mirrored relative to the rows in the memory
RowNum = NUM_ROWS - (RowNum + 1) // this will swap them top to bottom
// loop through, sending the first 11 values as fast as possible
for i = 0 to NumLEDDisplays - 1
if i < NumLEDDisplays - 1
for index = 0 to NumModules - 1
SPIOperate_SPI1_Send16((((uint16_t)RowNum + 1) << 8) | BitReverseTable256[RowData[i].ByBytes[index]])
while SPI1STATbits.SPITBF == 1
end while
end for
else
for index = 0 to NumModules - 2
SPIOperate_SPI1_Send16((((uint16_t)RowNum + 1) << 8) | BitReverseTable256[RowData[i].ByBytes[index]])
while SPI1STATbits.SPITBF == 1
end while
end for
// then send the final byte and wait for the SS line to rise
SPIOperate_SPI1_Send16Wait((((uint16_t)RowNum + 1) << 8) | BitReverseTable256[RowData[i].ByBytes[index]])
// while SPI1STATbits.SPITBF == 1
// end while
end if
end for
end function
# ES_Configure Module
# Constants
MAX_NUM_SERVICES = 16
NUM_SERVICES = 7
# Service 0 Definitions
SERV_0_HEADER = "LeaderFSM.h"
SERV_0_INIT = InitLeaderFSM
SERV_0_RUN = RunLeaderFSM
SERV_0_QUEUE_SIZE = 5
# Service 1 Definitions
SERV_1_HEADER = "LEDDisplayFSM.h"
SERV_1_INIT = InitLEDFSM
SERV_1_RUN = RunLEDFSM
SERV_1_QUEUE_SIZE = 3
# Service 2 Definitions
SERV_2_HEADER = "DispenserService.h"
SERV_2_INIT = InitDispenserService
SERV_2_RUN = RunDispenserService
SERV_2_QUEUE_SIZE = 3
# Service 3 Definitions
SERV_3_HEADER = "ArmHandFSM.h"
SERV_3_INIT = InitArmHandFSM
SERV_3_RUN = RunArmHandFSM
SERV_3_QUEUE_SIZE = 3
# Service 4 Definitions
SERV_4_HEADER = "GameFSM.h"
SERV_4_INIT = InitGameFSM
SERV_4_RUN = RunGameFSM
SERV_4_QUEUE_SIZE = 3
# Service 5 Definitions
SERV_5_HEADER = "MoodFSM.h"
SERV_5_INIT = InitMoodFSM
SERV_5_RUN = RunMoodFSM
SERV_5_QUEUE_SIZE = 3
# Service 6 Definitions
SERV_6_HEADER = "EncodeFSM.h"
SERV_6_INIT = InitEncodeFSM
SERV_6_RUN = RunEncodeFSM
SERV_6_QUEUE_SIZE = 3
# Event Types
ES_NO_EVENT = 0
ES_ERROR
ES_INIT
ES_TIMEOUT
ES_SHORT_TIMEOUT
# User-defined events
ES_NEW_KEY
ES_NEW_STUFF
ES_UPDATE_DISPLAY
ES_OFFMODE
ES_WELCOME
ES_JOYSTICK
ES_GAME_START
ES_WAVE
ES_LOAD_CELL
ES_ARM_MOVE_DONE
ES_ARM_MOVE
ES_DISPENSE
ES_MOOD_BUTTON
ES_ENCODE
# Distribution Lists
NUM_DIST_LISTS = 0
# Event Checking Functions
EVENT_CHECK_LIST = Check4Keystroke, CheckCapTouchEvents, CheckJoystickEvents, CheckIRWaveEvents, CheckHappyButton, CheckNeutralButton, CheckSadButton, CheckOnOffButton, CheckLimitSwitchEvents
# Timer Response Functions
TIMER_UNUSED = 0
TIMER0_RESP_FUNC = PostLeaderFSM
TIMER1_RESP_FUNC = PostLeaderFSM
TIMER2_RESP_FUNC = PostLEDFSM
TIMER3_RESP_FUNC = PostDispenserService
TIMER4_RESP_FUNC = PostGameFSM
TIMER5_RESP_FUNC = PostGameFSM
TIMER6_RESP_FUNC = PostGameFSM
TIMER7_RESP_FUNC = PostArmHandFSM
TIMER8_RESP_FUNC = PostEncodeFSM
# Timer Numbers
Leader_Overall_Timer = 0
Leader_Action_Timer = 1
LEDSERVICE_TIMER = 2
Servo_Dispenser_Timer = 3
GAMESERVICE_TIMER = 4
GAMEEND_TIMER = 5
GAMEUPDATE_TIMER = 6
SERVO_ARM_TIMER = 7
ENCODER_TIMER = 8