# USB HID Commands and Responses
The Beyond main microcontroller (MCU) is connected to a PC as a USB Human Interface Device (HID) class. While HID is often used for devices like keyboards and mice, the MCU uses HID simply as a basic communication channel.

Due to a quirk of the ATSAMG55 USB hardware (specifically not having quite enough endpoints for my liking) the communication channel from PC -> Head Mounted Display (HMD) uses the HID "feature report" instead of standard reports. Feature reports use the default command endpoint 0, standard reports use their own endpoint. This saves us an endpoint for something else, like audio. But HMD -> PC communication is still a standard report using its own endpoint.

All this means is that when sending commands from PC -> HMD, you'll have to use functions like "send_feature_report" instead of "write".

All reports (feature or standard) are 64 bytes long. Not all commands will use the full 64 bytes, pad unused bytes with 0's.

## List of Commands from PC to HMD

| Command Code (ASCII) | Command Code (Hex) | Command | Description |
| ---- | ---- | ---- | ---- |
| * | 0x2A | [SW_VER](#sw_ver)                      | Get software version string |
| % | 0x25 | [SERIAL_NUM](#serial_num)              | Get serial number (PCBA) |
| & | 0x26 | [HMD_SERIAL_NUM](#hmd_serial_num)      | Get serial number (HMD assembly) |
| ~ | 0x7e | [TRACKING_SERIAL](#tracking_serial)    | Get serial number (Tracking module) |
| Z | 0x5a | [USAGE_TIMER_GET](#usage_timer_get)    | Get usage timer value |
| z | 0x7a | [USAGE_TIMER_SET](#usage_timer_set)    | Set usage timer value |
| S | 0x53 | [STEREO](#stereo)                      | Switch microphone between stereo and mono |
| G | 0x47 | [GAIN](#gain)                          | Set mic integer gain |
| g | 0x67 | [FRAC_GAIN](#frac_gain)                | Set mic fractional gain |
| F | 0x46 | [FAN](#fan)                            | Set fan speed (immediate) |
| f | 0x66 | [FAN_DEFERRED](#fan_deferred)          | Set fan speed (preset, takes effect when video on) |
| R | 0x52 | [RATE](#rate)                          | Set periodic report rate |
| P | 0x50 | [OLED_FLIP](#oled_flip)                | Flip OLED images vertically |
| I | 0x49 | [OLED_BRIGHNTESS](#oled_brighntess)    | Set OLED brightness (blanking period) |
| ^ | 0x5e | [OLED_ID](#oled_id)                    | Get OLED serial number string |
| B | 0x42 | [BOOTLOADER](#bootloader)              | Restart in bootloader mode |
| C | 0x43 | [RESET](#reset)                        | Reset MCU |
| U | 0x55 | [READ_SIG](#read_sig)                  | Read portion of user signature Flash region (config region) |
| L | 0x4c | [RGB_LED](#rgb_led)                    | Set RGB LED color |
| W | 0x57 | [WRITE_SIG](#write_sig)                | Write portion of user signature Flash region |
| V | 0x56 | [SAVE_SIG](#save_sig)                  | Commit previously written signature bytes to permanent memory |
| M | 0x4d | [PROX_SETTINGS](#prox_settings)        | Change proximity sensor settings |
| K | 0x4b | [VXR_CHECKSUM](#vxr_checksum)          | Compute checksum on VXR7200 firmware Flash memory |
| T | 0x54 | [VXR_TAGS](#vxr_tags)                  | Get VXR7200 Flash tags (shows active regions) |
| D | 0x44 | [VXR_DELETE](#vxr_delete)              | Delete VXR7200 firmware |
| A | 0x41 | [VXR_PROGRAM](#vxr_program)            | Program VXR7200 firmware |
| Y | 0x59 | [VXR_RESET](#vxr_reset)                | Reset VXR7200 |
| N | 0x4e | [VXR_FWNAME](#vxr_fwname)              | Get VXR7200 firmware name / software version string |
| J | 0x4a | [HW_TEST](#hw_test)                    | Perform hardware self-tests |
| @ | 0x40 | [FATP_MODE](#fatp_mode)                | Enter Final Assembly Test Plan video modes |
| o | 0x6f | [OLED_COMMAND](#oled_command)          | Send register write commands directly to OLED panels |
| d | 0x64 | [EDID_SWITCH](#edid_switch)            | Change available video modes |
| p | 0x70 | [PROX_DISABLE](#prox_disable)          | Disable proximity sensor (displays forced on) |
| [ | 0x5b | [PROX_ENABLE](#prox_enable)            | Re-enable proximity sensor |
| H | 0x48 | [DISPLAY_SLEEP](#display_sleep)        | Enter doze: warm shallow sleep (fw >= 0.4.0) |
| h | 0x68 | [DISPLAY_WAKE](#display_wake)          | Wake from doze (fw >= 0.4.0) |
| = | 0x3d | [COLORBARPATTERN](#colorbarpattern)    | Show a colorbar pattern on OLEDs |
| s | 0x73 | [STACK_LEVELS](#stack_levels)          | Get stack usage levels for all FreeRTOS tasks |
| - | 0x2d | [OLED_POWER_DISABLE](#oled_power_disable)  | Disable power to OLED panels (__only for BS1!__) |
| + | 0x2b | [OLED_POWER_ENABLE](#oled_power_enable)  | Re-enable power to OLED panels (__only for BS1!__) |
| e | 0x65 | [FPGA_COMMANDS](#fpga_commands)        | Send commands to eyetracking FPGA (__only for BS2!__) |
| , | 0x2C | [HARDFAULT](#hardfault)                | Trigger a hardfault (__goes to fault handler, debugging use only__) |
| . | 0x2E | [UNUSED_IRQ](#unused_irq)              | Trigger an unused interrupt routine (__goes to fault handler, debugging use only__) |
| > | 0x3E | [STACK_OVERFLOW](#stack_overflow)      | Cause a stack overflow in a FreeRTOS task (__goes to fault handler, debugging use only__) |

## Periodic data report format

| Byte# | Value |
| -- | -- |
| 0 | $ |
| 1 | Length (always 24) |
| 2-3 | Fan speed <br> 16 bit unsigned integer, MSB first (big endian) |
| 4-5 | Proximity distance <br> 16 bit unsigned integer, MSB first (big endian) |
| 6-7 | CC1 pin adc value <br> 16 bit unsigned integer, MSB first (big endian) |
| 8-9 | CC2 pin adc value <br> 16 bit unsigned integer, MSB first (big endian) |
| 10-13 | Board temperature, Kelvin <br> 4 byte packed float |
| 14-17 | Left display temperature, Celsius <br> 4 byte packed float |
| 18-21 | Right display temperature, Celsius <br> 4 byte packed float |
| 22-23 | Display brightness <br> 16 bit unsigned integer, MSB first (big endian) |
| 24-25 | Display resolution/framerate <br> Packed bits: <br> bit0: Displays on <br> bit1: Proximity on <br> bit2: Proximity timer expired <br> bit3: DSC enabled <br> bits4-7: video mode: <br> * 	Unknown = 0 <br> * H2544_V2544_75Hz = 1 <br> * H2544_V2544_72Hz = 2 <br> * H1920_V1920_90Hz = 3 <br> * H1920_V1920_60Hz = 4 <br> * H2560_V2560_30Hz = 5 <br> * H2544_V2544_60Hz = 6 <br> * Colorbar_Test = 7 <br> bits 8-11: DisplayPort link rate: <br> * unknown = 0 <br> * RBR (1.62Gbps) = 1 <br> * HBR (2.7Gbps) = 2 <br> * HBR2 (5.4Gbps) = 3 <br> * HBR3 (8.1Gbps) = 4 <br> bits 12-15: DisplayPort lane count |

## List of responses from HMD to PC
| Reply Code (ASCII)  | Reply Code (Hex) | Command | Description |
| ---- | ---- | ---- | ---- |
| # | 0x23 | DATA | Periodic data transfer from HMD -> PC |
| $ | 0x24 | SUCCESS | Last command was successful |
| E | 0x45 | ERROR | Last command had an error |

For PC->HMD commands that request information, the HMD->PC reply code is the same as the PC->HMD command code.

## Error codes
| Value | Description |
| --- | --- |
| 0 | Success |
| 1 | Invalid_Arg |
| 2 | Unsupported |
| 3 | Unknown_Error |
| 4 | Busy_Error |
| 6 | Timeout_Error |

## Command detailed descriptions

### SW_VER
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | * |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | * |
| 1-63 | Software version string, null-terminated |

### SERIAL_NUM
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | % |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | % |
| 1-63 | PCBA serial number, null-terminated |

### HMD_SERIAL_NUM
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | & |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | & |
| 1-63 | HMD serial number, null-terminated |

### TRACKING_SERIAL
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | ~ |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | ~ |
| 1-63 | Tracking module serial number, null-terminated |
### USAGE_TIMER_GET
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | Z |
| 1  | 0: total powered on time, <br>1: display on time (left panel), <br>2: longest continuous display on time, <br>3: display on time (right panel) |


HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | Z |
| 1-4 | Time in units of 10 minutes.<br>32 bit unsigned integer, LSB first (little endian) |
### USAGE_TIMER_SET
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | z |
| 1  | 0: total powered on time, <br>1: display on time (left panel), <br>2: longest continuous display on time, <br>3: display on time (right panel) |
| 2-5 | Time in units of 10 minutes.<br>32 bit unsigned integer, LSB first (little endian) |


HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | $ (sucess) or E (error) |
| 1 | Error code (if error) |

### STEREO
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | S |
| 1  | 0: set microphones to mono (mixer adds channels, but still appears as stereo mic to the PC), <br>1: set microphones to stereo |


HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | $ (sucess) or E (error) |
| 1 | Error code (if error) |
### GAIN
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | G |
| 1-2  | Integer gain value for microphones, range 0-32767 <br> 16 bit unsigned integer, MSB first (big endian) <br> Note: values over 32767 will be clipped to 32767.|

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | $ (sucess) or E (error) |
| 1 | Error code (if error) |
### FRAC_GAIN
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | g |
| 1-2  | Fractional gain value for microphones, -1 to +1 (*although negative values are pretty much useless*)<br> 16 bit signed fixed point, MSB first (big endian) <br> For fixed point, simply multiply the desired value by 32768. <br> Example: 0.65 * 32768 = 21229 |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | $ (sucess) or E (error) |
| 1 | Error code (if error) |
### FAN {#FAN}
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | F |
| 1  | New fan speed in percent. Maximum 100. Sending 0 turns off the fan. <br> Takes effect immediately. |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | $ (sucess) or E (error) |
| 1 | Error code (if error) |
### FAN_DEFERRED
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | f |
| 1  | New fan speed in percent. Maximum 100. Sending 0 turns off the fan. <br> Takes effect immediately if displays are already on, otherwise takes effect next time displays turn on. |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | $ (sucess) or E (error) |
| 1 | Error code (if error) |
### RATE
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | R |
| 1-2  | New rate for periodic data report, in milliseconds. <br> 16 bit unsigned integer, MSB first (big endian) <br> Minimum 10, for 10 ms between reports (100 Hz) |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | $ (sucess) or E (error) |
| 1 | Error code (if error) |

### OLED_FLIP
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | P |
| 1  | 0: Don't flip displays <br> 1: Flip displays vertically <br> Old debugging command, now just for fun. |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | $ (sucess) or E (error) |
| 1 | Error code (if error) |
### OLED_BRIGHNTESS
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | I |
| 1-2  | New brightness value. <br> Raw values between 0 (min brightness, barely visible) -> 1023 (max brightness). <br> 16 bit unsigned integer, MSB first (big endian) |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | $ (sucess) or E (error) |
| 1 | Error code (if error) |
### OLED_ID
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | ^ |
| 1  | 0: Left panel <br> 1: Right panel |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | ^ |
| 1 | Length (always 14) |
| 2-15 | OLED ID code.|
### BOOTLOADER
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | B |

No reply, MCU will reboot immediately.
### RESET
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | C |

No reply, MCU will reboot immediately.
### READ_SIG
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | U |
| 1  | Block number (0-15) |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | U |
| 1 | Length (always 32) |
| 2-33 | Raw user signature Flash page bytes for this block. |
### RGB_LED
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | L |
| 1  | Red LED color, 0-255. 8-bit unsigned integer. |
| 2  | Green LED color, 0-255. 8-bit unsigned integer. |
| 3  | Blue LED color, 0-255. 8-bit unsigned integer. |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | $ (sucess) or E (error) |
| 1 | Error code (if error) |

### WRITE_SIG
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | U |
| 1  | Block number (0-15) |
| 2-33 | New user signature Flash page bytes. |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | $ (sucess) or E (error) |
| 1 | Error code (if error) |

*Note on writing to the user signature Flash page:*
* The Flash page is 512 bytes long. It is split into 16 blocks of 32 bytes for writing.
* When writing with new values, overwrite all 512 bytes before saving. You'll need to make 16 __WRITE_SIG__ requests in total. 
* After completing all writes, issue one __SAVE_SIG__ request to make your changes permanent.

### SAVE_SIG
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | V |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | $ (sucess) or E (error) |
| 1 | Error code (if error) |
### PROX_SETTINGS
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | M |
| 1 | __PWEN__ <br> Wait time enable <br> 0: disabled, 1: enabled |
| 2 | __PRATE__ <br> Wait time between pulses, aka proximity sample duration <br> Time = (PRATE + 1)*88us |
| 3 | __PWLONG__ <br> Long wait time enable. <br> 0: disabled, wait time = normal <br> 1: enabled, wait time is multiplied by 12 |
| 4 | __PGAIN__ <br> Gain of the proximity IR sensor <br> 0: 1x <br> 1: 2x <br> 2: 4x <br> 3: 8x |
| 5 | __PPULSE__ <br> Maximum number of VCSEL pulses in a single cycle <br> 0 - 63 (max pulses = PPULSE + 1) |
| 6 | __PPULSE_LEN__ <br> Proximity pulse length <br> 0: 1us <br> 1: 2us <br> 2: 4us <br> 3: 8us <br> 4: 12us <br> 5: 16us <br> 6: 24us <br> 7: 32us |
| 7 | __PLDRIVE__ <br> Drive strength of IR VCSEL. 2-10mA in 1mA steps <br> 0: 2mA <br> ...<br> 8: 10mA|
| 8 | __PWTIME__ <br> Proximity wait time <br> Wait time between proximity samples in 2.78ms increments <br> 0 (0x00): 2.78ms (33.36ms with PWLONG) <br> 255 (0xFF): 711.68ms (8.54s with PWLONG) | 
| 9 | __PDSELECT__ <br> Selection of proximity photodiode <br> 1: Far photodiode <br> 2: Near photodiode <br> 3: Both photodiodes | 
| 10 | __PMAVG__ <br> Proximity moving average <br> 0: disabled <br> 1: 2 values <br> 2: 4 values <br> 3: 8 values | 
| 11 | __PROX_AVG__ <br> Number of samples collected and hardware averaged during a proximity cycle <br> 0: disable averaging <br> 1: 2 samples <br> 2: 4 samples <br> 3: 8 samples <br> 4: 16 samples <br> 5: 32 samples <br> 6: 64 samples <br> 7: 128 samples|

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | $ (sucess) or E (error) |
| 1 | Error code (if error) |

### VXR_CHECKSUM
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | K |
| 1-4 | Start address <br> 32 bit unsigned integer, LSB first (little endian) | 
| 5-8 | Length <br> 32 bit unsigned integer, LSB first (little endian) | 

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | K |
| 1 | Length (always 4) |
| 2-5 | Checksum value <br> 32 bit unsigned integer, LSB first (little endian) | 

### VXR_TAGS
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | T |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | T |
| 1 | Active tags, bitfield. <br> bit0: Config0 is active <br> bit1: Config1 is active <br> bit2: Firmware0 is active <br> bit3: Firmware1 is active |

### VXR_DELETE
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | D |
| 1 | Block to erase or sector code <br> For 64kB block erases, send 0 to 7 <br> For 4kB sector erases, send 0x80 and the sector in the next byte | 
| 2  | Sector to erase, 0 to 127. For block delete, value is ignored. |
| 3-11 | "VXRDELETE". This exact phrase must be sent or the delete command will be ignored. |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | $ (sucess) or E (error) |
| 1 | Error code (if error) |

### VXR_PROGRAM
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | A |
| 1 | Length in bytes (1 to 32) | 
| 2-5  | Starting address where to program <br> 32 bit unsigned integer, LSB first (little endian)|
| 6 - (6+Length-1) | New bytes to program |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | $ (sucess) or E (error) |
| 1 | Error code (if error) |

### VXR_RESET
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | Y |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | $ (sucess) or E (error) |
| 1 | Error code (if error) |

### VXR_FWNAME
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | N |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | N |
| 1-15 | VXR7200 Firmware name, always 15 bytes long |

### HW_TEST
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | J |
| 1 | Hardware test to perform: <br> 'H' = HWTest_USB_Hub: checks connection to USB hubs <br> 'L' = HWTest_RGB_LED: checks connection to RGB LED driver <br> 'S' = HWTest_USBC_Switch: checks connection to USB-C crossbar switch <br> 'V' = HWTest_VXR: checks connection to VXR7200 <br> 'P' = HWTest_Prox: checks connection to proximity sensor <br> 'O' = HWTest_OLED: checks connection to OLED panels <br> 'F' = HWTest_Fan: checks fan spins appropriately when commanded

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | J |
| 1 | Test result <br> 0: fail <br> 1: pass |

### FATP_MODE
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | @ |
| 1 | Requested video mode: <br> 0: Two non-DSC video modes, 3840x1920 @60Hz and 5120x2560 @30Hz <br> 1: One non-DSC video mode, 3840x1920 @60Hz <br> 2: One DSC video mode, 5088x2544 @60Hz <br> 255: back to normal mode |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | $ (sucess) or E (error) |
| 1 | Error code (if error) |

### OLED_COMMAND
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | o |
| 1 | 0: left display <br> 1: right display |
| 2 | Length in bytes to send, including register address |
| 3 - (3+Length-1) | Command data |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | $ (sucess) or E (error) |
| 1 | Error code (if error) |

### EDID_SWITCH
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | d |
| 1 | 0: default, both 75Hz and 90Hz <br> 1: only 90Hz <br> 2: only 75Hz |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | $ (sucess) or E (error) |
| 1 | Error code (if error) |

### PROX_DISABLE
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | p |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | $ (sucess) or E (error) |
| 1 | Error code (if error) |

### PROX_ENABLE
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | [ |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | $ (sucess) or E (error) |
| 1 | Error code (if error) |

### DISPLAY_SLEEP
*Doze — warm shallow sleep (firmware >= 0.4.0). The firmware sweeps emission to
the brightness floor (~300 ms, gamma-eased), turns the OLED display off for true
black, idles the fan (`FS_Idle`, overtemp-protected) and breathes the LED — all
while keeping `video_enabled` true and the VXR/DSC link locked, so wake skips the
cold retrain. Sets the doze-latch, which overrides the proximity emission gate
(so a doze-while-worn stays dark) and is force-cleared on video loss. If video is
not yet up, this just arms the latch so the next bring-up comes up dozed
("born-dozing"). The host stays the wake authority — the firmware never lights
itself out of doze on a prox read.*

PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | H |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | $ (success) or E (error — e.g. already dozing) |
| 1 | Error code (if error) |

### DISPLAY_WAKE
*Wake from doze (firmware >= 0.4.0). Re-enables the OLED display (~40 ms) if it
had reached true-black, then sweeps brightness back to `oled_current_brightness`
(~300 ms, gamma-eased) and re-couples the fan/LED to video, clearing the
doze-latch. Measured wake: ~95 ms to displays-on, ~400 ms to full brightness.*

PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | h |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | $ (success) or E (error — e.g. not dozing) |
| 1 | Error code (if error) |

### COLORBARPATTERN
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | = |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | $ (sucess) or E (error) |
| 1 | Error code (if error) |

### STACK_LEVELS
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | s |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | s |
| 1 | Length of reply (will be 4x the number of FreeRTOS tasks) |
| 2 - (2+Length-1) | Task stack levels <br> List of 32 bit unsigned integers, LSB first (little endian) |

### OLED_POWER_DISABLE
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | - |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | $ (sucess) or E (error) |
| 1 | Error code (if error) |

### OLED_POWER_ENABLE
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | + |

HMD -> PC reply
| Byte# | Value |
| -- | -- |
| 0 | $ (sucess) or E (error) |
| 1 | Error code (if error) |

### FPGA_COMMANDS
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | e |
| 1  | Command to run: <br> 'R': Reset FPGA (pulse reset pin) <br> 'B': Reconfig PFGA (pulse RECONFIGN pin) <br> 'I': Write over I2C <br> 'i': Read over I2C |
| 2  | *For I2C commands only:* <br> Write: length of bytes to write, including I2C register address <br> Read: length of bytes to read (not including register address) |
| 3  | *For I2C commands only:* <br> Register address |
| 4-63 | *For I2C __writes__ only:* <br> New values to write to registers. Note that first byte (4) is the register address in the FPGA. |

HMD -> PC reply *(Reset, reconfig, or I2C write)*
| Byte# | Value |
| -- | -- |
| 0 | $ (sucess) or E (error) |
| 1 | Error code (if error) |

HMD -> PC reply *(I2C read)*
| Byte# | Value |
| -- | -- |
| 0 | e |
| 1 | Length of bytes read |
| 2 - (2+Length-1) | Bytes read from FPGA |

### HARDFAULT
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | , |
| 1-8 | "CRASHNOW". This exact phrase must be entered or the command will be ignored |

No reply, MCU will enter crash recovery mode.

### UNUSED_IRQ
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | . |
| 1-8 | "CRASHNOW". This exact phrase must be entered or the command will be ignored |

No reply, MCU will enter crash recovery mode.

### STACK_OVERFLOW
PC -> HMD request
| Byte# | Value |
| -- | -- |
| 0  | > |
| 1-8 | "CRASHNOW". This exact phrase must be entered or the command will be ignored |

No reply, MCU will enter crash recovery mode.
