This page details the design choices and implementation of all the software used in the completion of this project. In particular, the boatFramework consists of four main services.
Given that we are using XBee modules to communicate via the Zigbee protocol between two PIC32s we decided to create two copies of the Framework4PIC32. One for the PIC32 on the boat called "boatFramework" and another for the PIC32 on the controller called "controllerFramework". This helped with organization of the events and services that each PIC32 is concerned about. For each framework their will be an overall description of the services used and the pseudo-code for them. All the detailed code can be found in the GitHub repository found in this link.
Following the communcations protocol, the main messages used on the boat have the following structure and logic.
Receiving pairing message from mallard modules and pair:
0x7E - 0x00 - 0x09 (Length LSB) - 0x81 - (Source MSB & LSB) - (RSSI Byte) - (OptByte) - 0x02 (Status) - (Source MSB & LSB) - 0x00 - CheckSum
Responding with fuel status (ChargeVal is 0xFF before pairing and the calculated charge value after):
0x7E - 0x00 - 0x06 (Length LSB) - 0x01 - 0x00 - (DestinationMSB & LSB) - 0x01 (OptByte) - ChargeVal - CheckSum
Receiving driving messages from mallard modules:
0x7E - 0x00 - 0x09 (Length LSB) - 0x81 - (Paired MSB & LSB) - (RSSI Byte) - (OptByte) - 0x00 (Driving) - (Joy1) - (Joy2) - (Digi)- CheckSum
Note: For our boat the we only use bit 1 of the "Digi" byte to represent shooting (1) or not shooting (0)
Receiving charging message from mallard modules:
0x7E - 0x00 - 0x09 (Length LSB) - 0x81 - (Paired MSB & LSB) - (RSSI Byte) - (OptByte) - 0x01 (Charging) - 0x00 - 0x00 - 0x00 - CheckSum
Handles wireless communication between the Quackraft and the Mallard controller module over UART. It receives and validates command packets, manages controller pairing and connection timeouts, forwards driving and cannon-control commands to the appropriate services, and tracks the boat’s charge/fuel state. It also sends status updates, including the current charge level, back to the controller.
Module-level variables: MyPriority, pairedStatus, pairedAddressLSB, ChargeVal, InitializeFuel, rxBuf[], txBuf[], receivedByte, CheckSumVal, newMessageStarted
/*------------------------------ Module Code ------------------------------*/
Function InitCommunicationServiceService(Priority)
Set MyPriority to Priority
Print initialization messages
Post ES_INIT event to this queue
Return true if successful, false otherwise
Function PostCommunicationServiceService(ThisEvent)
Post event to this queue
Function RunCommunicationServiceService(ThisEvent)
Initialize ReturnEvent as ES_NO_EVENT
switch (ThisEvent.EventType)
case ES_INIT:
Initialize UART hardware
Enable UART receive interrupts
break
case ES_TIMEOUT:
if(EventParam is UNPAIRING_TIMER)
Set pairedStatus to false
Post ES_UNPAIRED to PairedServoService
Post ES_UNPAIRED to DrivingService
break
case ES_RX_BYTE:
if(received byte is START_BYTE)
Clear receive buffer
Begin assembling a new message
Store START_BYTE
else if(new message is being assembled)
Shift receive buffer
Add newest byte
if(receive buffer contains complete message)
Validate checksum and packet fields
if(message is valid)
Interpret message contents
Send response packet containing current ChargeVal
Stop assembling current message
break
return ReturnEvent
// Helper Functions
Function InitUART()
Configure UART2 for 9600 baud
Configure TX and RX pins
Enable transmitter and receiver
Enable UART RX interrupts
Enable global interrupts
Enable UART
// Using interrupts for receiving messages
Function UART_RX_ISR()
While UART receive buffer contains data
Read received byte
Post ES_RX_BYTE event containing received byte
Clear UART interrupt flag
Function ComputeCheckSum(DataLength)
Sum all bytes in data frame
Compute checksum as 0xFF minus sum
Store checksum in CheckSumVal
Function ValidReceivedMessage()
Compute expected checksum
If checksum does not match
Return false
Verify:
Start byte
Length bytes
API identifier
Destination address
Return true if all fields are valid
Function InterpretMessage()
// Pairing request
If not paired and received pairing message
Save controller address
Set pairedStatus true
Start UNPAIRING_TIMER
Reset ChargeVal
Enable fuel initialization
Post ES_PAIRED to PairedServoService
// Messages from paired controller
Else if paired and source address matches
If charging message
Restart UNPAIRING_TIMER
Increase ChargeVal by 8
Limit ChargeVal to maximum value
Post ES_CHARGING
Else if driving message
If first drive message after pairing
Initialize ChargeVal to full
Clear initialization flag
Restart UNPAIRING_TIMER
Extract:
Throttle joystick value
Steering joystick value
Digital shoot command
If ChargeVal is zero
Force throttle to neutral
Force steering to neutral
Post ES_CANNON_STOP
Else
If boat is moving
Decrease ChargeVal
If shoot button pressed
Decrease ChargeVal
Post ES_CANNON_START
Else
Post ES_CANNON_STOP
Combine steering and throttle values
Post ES_DRIVE to DrivingService
Function SendMsgToMallardModule(Charge)
Construct transmit packet:
Start byte
Length bytes
API fields
Paired controller address
Current Charge value
Compute checksum
Append checksum
Transmit packet byte-by-byte through UART
Converts throttle and steering commands into PWM duty cycles for the boat's two drive motors. It uses differential steering to mix throttle and direction inputs, controls the gate mechanism based on steering direction, stops the boat while charging or when communication is lost, and drives the motors through the PIC32 PWM hardware.
This service uses Timer 2 with Output Compare Channels 1 and 2 for the left and right BLDC thruster motors, respectively.
Module-level variables: MyPriority, CurrentThrottle, CurrentDirection, CurrentDutyCyclePercent1, CurrentDutyCyclePercent2, ThrottleMidPoint, DirectionMidPoint
/*------------------------------ Module Code ------------------------------*/
Function InitDrivingService(Priority)
Set MyPriority to Priority
Post ES_INIT event to this queue
Return true if successful, false otherwise
Return true if the event posted successfully, false otherwise
Function PostDrivingService(ThisEvent)
Post event to this queue
Function RunDrivingService(ThisEvent)
Initialize ReturnEvent as ES_NO_EVENT
switch(ThisEvent.EventType)
case ES_INIT:
Set throttle and direction midpoints
Initialize throttle and direction to neutral
Initialize motor PWM hardware
Calculate neutral duty cycles
Drive both motors with neutral duty cycles
break
case ES_CHARGING:
Set throttle and direction to neutral
Calculate neutral duty cycles
Drive both motors with neutral duty cycles
break
case ES_DRIVE:
Extract throttle and direction from EventParam
If direction is less than midpoint
Post ES_GATE_CLOSE
Else
Post ES_GATE_OPEN
Save throttle and direction values
Convert throttle and direction into motor duty cycles
Update Motor 1 PWM
Update Motor 2 PWM
break
case ES_UNPAIRED:
Post ES_CANNON_STOP
Create neutral drive command
Post ES_DRIVE to self
break
return ReturnEvent
// Helper Functions
Function _InitMotorPWM()
Configure Timer 2 for PWM generation
Configure PWM channel for Motor 1
Configure PWM channel for Motor 2
Configure motor pins as digital outputs
Map PWM channels to output pins
Function _SetDutyCycleFromThrottleAndDirection(Throttle, Direction)
Limit throttle and direction to valid ranges
Convert throttle to signed forward/reverse offset
Convert direction to signed steering offset
Calculate differential steering:
Motor1Duty = Neutral + ThrottleOffset - DirectionOffset
Motor2Duty = Neutral + ThrottleOffset + DirectionOffset
Clamp both duty cycles to valid PWM range
Store resulting duty cycles
Function _DriveMotor(MotorChannel, DutyCyclePercent)
Set PWM duty cycle on requested motor channel
Controls the boat’s secondary mechanisms: water cannon and the gate servo. It initializes the PWM hardware for the servo, responds to gate open/close commands by adjusting the servo position, and turns the water cannon on or off in response to shooting events.
Module-level variables: MyPriority
/*------------------------------ Module Code ------------------------------*/
Function InitBoatActionsService(Priority)
Set MyPriority to Priority
Print initialization message
Post ES_INIT event to this queue
Return true if successful, false otherwise
Function PostBoatActionsService(ThisEvent)
Post event to this queue
Function RunBoatActionsService(ThisEvent)
Initialize ReturnEvent as ES_NO_EVENT
switch(ThisEvent.EventType)
case ES_INIT:
Configure Timer 3 for 50 Hz servo PWM
Configure PWM channel for gate servo
Map PWM output to gate servo pin
Set gate servo to default position
Configure cannon control pin as digital output
Turn cannon off
break
case ES_GATE_OPEN:
Set servo pulse width to OPEN position
break
case ES_GATE_CLOSE:
Set servo pulse width to CLOSED position
break
case ES_CANNON_START:
Turn cannon output ON
break
case ES_CANNON_STOP:
Turn cannon output OFF
break
default:
break
return ReturnEvent
Starts in neutral position and moves the indicator to either down or up based on when CommunicationsService identifies unpaired or paired status respectively. Uses channel 4 with timer 3.
Module-level variables: MyPriority
/*------------------------------ Module Code ------------------------------*/
Function InitPairedServoService(Priority)
set MyPriority to Priority
Initialize PWM for servo
Setup timer 3 with prescaler 64 and 6250 ticks for the required 50Hz
Setup channel 3 for this servo
Map RB2 to this output
Set pulse width to neutral position
Post ES_INIT event to this queue
Return true if the event posted successfully, false otherwise
Function PostPairedServoService(ThisEvent)
Post event to this queue
Function RunPairedServoService(ThisEvent)
Initialize ReturnEvent as ES_NO_EVENT
switch (ThisEvent.EventType)
case ES_PAIRED:
Set Duty Cycle to corresponding length for going fully up indicating paired status
break;
case ES_UNPAIRED:
Set Duty Cycle to corresponding length for going fully down indicating unpaired status
break;
return ReturnEvent
PIC32_PWM_Lib: Output compare library to set up timers, channels, modes, and mapping pins. Primary use is servo PWM.
See github for full source code on this link.