Cleanup, Refactoring, Silkscreen update

- Code refactor, still far from great but a bit less crappy
- Unify behavior - Mouse Zoom feature was made toggle and
  moved from ALT to Right ALT + Right CTRL to avoid interfering
  with OS
- Added optional screensaver "Pong" mode, prevents sleep and it's fun
- Updated README
- Added more visible pin1 marking on PCB for digital isolator
- Marked pins to solder with a star *
- Added checksum and version format support for flash config
This commit is contained in:
Hrvoje Cavrak 2024-01-21 01:45:22 +01:00
parent c21d5b14ea
commit 15da60bd95
35 changed files with 1462 additions and 1001 deletions

View File

@ -73,17 +73,23 @@ This will make the corresponding Pico board enter the bootloader upgrade mode an
## Misc features
#### Mouse slowdown
### Mouse slowdown
Ever tried to move that YT video slider to a specific position but your mouse moves too jumpy and suddenly you are moving your hand super-carefully like you're 5 and playing "Operation" all over again?
**Holding right ALT** while moving the mouse will slow it down considerably, enabling you to get the finer precision work done and still have your mouse moving quickly otherwise.
**Press right CTRL + right ALT** to toggle a slow-mouse mode. The mouse pointer will slow down considerably, enabling you to get the finer precision work done and still have your mouse moving normally by quickly pressing the same keys again.
#### Switch Lock
### Switch Lock
If you want to lock yourself to one screen, use ```RIGHT CTRL + L```.
This will make sure you won't accidentally leave your current screen. To turn off, press the same key combo again.
### Screensaver
Supposedly built in to prevent computer from entering standby, but truth be told - it is just fun to watch. Off by default, will make your mouse pointer bounce around the screen like a Pong ball. When enabled, it activates after a period of inactivity defined in user config header and automatically switches off as soon as you send any output towards that screen.
![Image](img/screensaver.gif)
## Hardware
[The circuit](schematics/DeskHop.pdf) is based on two Raspberry Pi Pico boards, chosen because they are cheap (4.10 € / pc), can be hand soldered and most suppliers have them in stock.
@ -152,22 +158,23 @@ The standard process to do that is using isopropyl alcohol and an old toothbrush
## Usage guide
#### Keyboard shortcuts
### Keyboard shortcuts
_Firmware upgrade_
- ```Right Shift + F12 + Left Shift + A``` - put board A in FW upgrade mode
- ```Right Shift + F12 + Left Shift + B``` - put board B in FW upgrade mode
_Usage_
- ```Right ALT``` - mouse slows down while it's pressed
- ```Right CTRL + Right ALT``` - Toggle slower mouse mode
- ```Right CTRL + L``` - Lock/Unlock mouse desktop switching
- ```Caps Lock``` - Switch between outputs
_Config_
- ```Right Shift + F12 + D``` - remove flash config
- ```Right Shift + F12 + Y``` - save screen switch offset
- ```Right Shift + F12 + S``` - turn on/off screensaver option
#### Switch cursor height calibration
### Switch cursor height calibration
This step is not required, but it can be handy if your screens are not perfectly aligned or differ in size. The objective is to have the mouse pointer come out at exactly the same height.
@ -177,11 +184,11 @@ Just park your mouse on the LARGER screen at the height of the smaller/lower scr
Repeat for the bottom border (if it's above the larger screen's border). This will get saved to flash and it should keep this calibration value from now on.
#### Other configuration
### Other configuration
Mouse speed can now be configured per output screen and per axis. If you have multiple displays under Linux, your X speed is probably too fast, so you need to configure it in user_config.h and rebuild. In the future, this will be configurable without having to do that.
#### Functional verification
### Functional verification
When you connect a new USB peripheral, the board will flash the led twice, and instruct the other board to do the same. This way you can test if USB and outgoing communication works for each board.
@ -239,7 +246,7 @@ There are several software alternatives you can use if that works in your partic
- Not tested with a wide variety of devices, I don't know how it will work with your hardware. There is a reasonable chance things might not work out-of-the-box.
- Advanced keyboards (with knobs, extra buttons or sliders) will probably face issues where this additional hardware doesn't work.
- Super-modern mice with 300 buttons might see some buttons not work as expected.
- NOTE: Both computers need to be connected and powered on for this to work (as each board gets powered by the computer it plugs into).
- NOTE: Both computers need to be connected and provide power to the USB for this to work (as each board gets powered by the computer it plugs into). Many desktops and laptops will provide power even when shut down nowadays. If you need to run with one board fully disconnected, you should be able to use a USB hub to plug both keyboard and mouse to a single port.
## Progress
@ -252,6 +259,7 @@ Planned features:
- ~~Support for USB hubs and single-sided operation~~
- Configurable screens (partially)
- ~~Permament configuration stored in flash~~
- Better support for keyboards with knobs and mice with mickeys
- Unified firmware for both Picos
- ... and more!

Binary file not shown.

Binary file not shown.

BIN
img/screensaver.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

View File

@ -210,11 +210,11 @@
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp 5fd3f62a-d98c-4462-b185-53ee62a95831)
)
(fp_text user "GP12" (at -13.2 13.97 45) (layer "F.SilkS")
(fp_text user " GP12*" (at -13.2 13.97 45) (layer "F.SilkS")
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp 654bded7-dc3d-4110-b5c8-9933d7968d40)
)
(fp_text user "GND" (at -12.8 19.05 45) (layer "F.SilkS")
(fp_text user " GND*" (at -12.8 19.05 45) (layer "F.SilkS")
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp 7a76e090-db64-49ef-9c3a-8fddc8374f01)
)
@ -234,19 +234,19 @@
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp 9af6d6e3-843a-4bca-90bd-2c3b9f316a84)
)
(fp_text user "GP15" (at -13.054 24.13 45) (layer "F.SilkS")
(fp_text user " GP15*" (at -13.054 24.13 45) (layer "F.SilkS")
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp 9da49d49-7de1-42a4-91fb-7720c8535bf6)
)
(fp_text user "GND" (at 12.8 19.05 45) (layer "F.SilkS")
(fp_text user " GND*" (at 12.8 19.05 45) (layer "F.SilkS")
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp 9ec3ebbf-2742-4ec9-9764-07554be8b7fa)
)
(fp_text user "GND" (at -12.8 -6.35 45) (layer "F.SilkS")
(fp_text user " GND*" (at -12.8 -6.35 45) (layer "F.SilkS")
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp a268ddcc-2c85-484d-bb30-2c57a598aa04)
)
(fp_text user "GP13" (at -13.054 16.51 45) (layer "F.SilkS")
(fp_text user " GP13*" (at -13.054 16.51 45) (layer "F.SilkS")
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp a57e760a-aacf-4400-923a-ac969c8333f6)
)
@ -286,11 +286,11 @@
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp c5d36558-3396-4928-b429-c319485ac106)
)
(fp_text user "VBUS" (at 13.3 -24.2 45) (layer "F.SilkS")
(fp_text user " VBUS*" (at 13.3 -24.2 45) (layer "F.SilkS")
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp c5dbf120-2592-4aa5-ba0e-17d002b6126a)
)
(fp_text user "GND" (at -12.8 -19.05 45) (layer "F.SilkS")
(fp_text user " GND*" (at -12.8 -19.05 45) (layer "F.SilkS")
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp ced39d21-201b-4efd-a857-444103e82d38)
)
@ -306,11 +306,11 @@
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp e7db89ba-8097-461c-922f-efe95c69b626)
)
(fp_text user "3V3" (at 12.9 -13.9 45) (layer "F.SilkS")
(fp_text user " 3V3*" (at 12.9 -13.9 45) (layer "F.SilkS")
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp eba83c67-b501-49f2-b261-9eabd4774e8b)
)
(fp_text user "GND" (at 12.8 -19.05 45) (layer "F.SilkS")
(fp_text user " GND*" (at 12.8 -19.05 45) (layer "F.SilkS")
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp ebec5c26-3696-4fb8-bce4-7240f2a1628b)
)
@ -318,7 +318,7 @@
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp ee08acb9-4187-403c-977d-30480b9f54e8)
)
(fp_text user "GP14" (at -13.1 21.59 45) (layer "F.SilkS")
(fp_text user " GP14*" (at -13.1 21.59 45) (layer "F.SilkS")
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp f416675b-3513-4287-a967-d510ec90166f)
)
@ -600,7 +600,7 @@
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp 04258d86-db8d-4f2f-83cf-9be5258ae56e)
)
(fp_text user "GND" (at 12.8 -19.05 45) (layer "F.SilkS")
(fp_text user " GND*" (at 12.8 -19.05 45) (layer "F.SilkS")
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp 06d9de45-f0bc-4bab-bbd3-4d7fd75dc0de)
)
@ -608,7 +608,7 @@
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp 080eb970-3605-4dea-8a86-d0f5e211360c)
)
(fp_text user "GP15" (at -13.054 24.13 45) (layer "F.SilkS")
(fp_text user " GP15*" (at -13.054 24.13 45) (layer "F.SilkS")
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp 0eb03dac-5681-444f-b662-bd902e4a78d6)
)
@ -616,7 +616,7 @@
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp 244993d6-b401-400c-9c81-52250a283542)
)
(fp_text user "VBUS" (at 13.3 -24.2 45) (layer "F.SilkS")
(fp_text user " VBUS*" (at 13.3 -24.2 45) (layer "F.SilkS")
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp 29420bbe-3d5e-48c4-8f10-f791ece49ffd)
)
@ -640,7 +640,7 @@
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp 42ae899d-cb96-4cf6-b779-72e6029879ca)
)
(fp_text user "GND" (at -12.8 19.05 45) (layer "F.SilkS")
(fp_text user " GND*" (at -12.8 19.05 45) (layer "F.SilkS")
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp 4dba1d28-fd16-4cfc-a36d-02e78edede5b)
)
@ -652,11 +652,11 @@
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp 5ae03de5-12f1-445f-a4d7-0534f3b50b2c)
)
(fp_text user "GND" (at -12.8 -19.05 45) (layer "F.SilkS")
(fp_text user " GND*" (at -12.8 -19.05 45) (layer "F.SilkS")
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp 5e0af269-21d7-4b63-b85d-a427da8e3c7e)
)
(fp_text user "GP17" (at 13.054 21.59 45) (layer "F.SilkS")
(fp_text user " GP17*" (at 13.054 21.59 45) (layer "F.SilkS")
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp 60192e7d-24f9-415f-972e-4eab77a5b5e7)
)
@ -664,7 +664,7 @@
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp 69e44965-b51f-4579-a420-34c32d41c682)
)
(fp_text user "GND" (at -12.8 6.35 45) (layer "F.SilkS")
(fp_text user " GND*" (at -12.8 6.35 45) (layer "F.SilkS")
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp 6a08809b-e049-49ec-a689-fecdb644c04e)
)
@ -672,7 +672,7 @@
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp 6eac192f-b906-44eb-85a6-d39b2e567c1d)
)
(fp_text user "GP14" (at -13.1 21.59 45) (layer "F.SilkS")
(fp_text user " GP14*" (at -13.1 21.59 45) (layer "F.SilkS")
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp 704211ed-8e53-4fc9-8c55-04f6d1e97075)
)
@ -700,15 +700,15 @@
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp 924973f6-cb24-4d5e-9b4f-944cd7b8d3c2)
)
(fp_text user "3V3" (at 12.9 -13.9 45) (layer "F.SilkS")
(fp_text user " 3V3*" (at 12.9 -13.9 45) (layer "F.SilkS")
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp 9916d5ae-1ea5-4b41-aa70-6a7f9b6c33e5)
)
(fp_text user "GP16" (at 13.054 24.13 45) (layer "F.SilkS")
(fp_text user " GP16*" (at 13.054 24.13 45) (layer "F.SilkS")
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp 99bb43bd-b729-4948-9bb2-e8ae89878b0c)
)
(fp_text user "GND" (at -12.8 -6.35 45) (layer "F.SilkS")
(fp_text user " GND*" (at -12.8 -6.35 45) (layer "F.SilkS")
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp bae5cebf-f67e-458f-bc62-d2ede84b1ad3)
)
@ -732,7 +732,7 @@
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp dcfd0f66-777c-43b2-b1d9-87dd102c9f3f)
)
(fp_text user "GND" (at 12.8 19.05 45) (layer "F.SilkS")
(fp_text user " GND*" (at 12.8 19.05 45) (layer "F.SilkS")
(effects (font (size 0.8 0.8) (thickness 0.15)))
(tstamp e1a37e64-c2ff-4550-aad7-7eee1cfd9296)
)
@ -1054,7 +1054,7 @@
(property "ki_keywords" "cap capacitor")
(path "/3c58f69e-1b44-4a9e-8696-820df6d6d00b")
(attr smd)
(fp_text reference "C2" (at 3.5429 0.0254) (layer "F.SilkS")
(fp_text reference "C2" (at 0.134809 1.830335) (layer "F.SilkS")
(effects (font (size 1 1) (thickness 0.15)))
(tstamp 06cb8b2c-1271-446d-a93d-130a270fe442)
)
@ -1602,7 +1602,7 @@
(property "ki_keywords" "cap capacitor")
(path "/fb534856-14b0-4379-bd15-7952c4f76624")
(attr smd)
(fp_text reference "C1" (at 3.556 0.0508) (layer "F.SilkS")
(fp_text reference "C1" (at -0.099564 -1.727201) (layer "F.SilkS")
(effects (font (size 1 1) (thickness 0.15)))
(tstamp 2a770326-88ad-4947-9ace-cdc0b6ff3bb8)
)
@ -5352,6 +5352,8 @@
)
(stroke (width 0) (type solid)) (fill solid) (layer "F.SilkS") (tstamp c5383451-e798-427e-8631-6dd983e845c0))
(gr_circle (center 64.048728 81.041328) (end 64.455128 81.041328)
(stroke (width 0.12) (type solid)) (fill solid) (layer "F.SilkS") (tstamp d88e0c1e-780a-4330-9e02-0d8f43cbcef0))
(gr_poly
(pts
(xy 71.346351 110.357574)
@ -6069,6 +6071,9 @@
(gr_text "DeskHop - Keyboard/Mouse Switch\n\nby Hrvoje Cavrak, 12/2023\n\nhttps://github.com/hrvach" (at 64.9732 96.266) (layer "B.SilkS") (tstamp e86cc18b-e914-42f8-adc9-6ea2bbb55ee5)
(effects (font (size 1.1 1.1) (thickness 0.15)) (justify left bottom mirror))
)
(gr_text "*" (at 60.4774 76.708) (layer "F.SilkS") (tstamp 0c6253e3-41c8-4ad8-a815-57aa39b7d021)
(effects (font (size 1 1) (thickness 0.15)) (justify left bottom))
)
(gr_text "PC 1" (at 83.9216 46.7868) (layer "F.SilkS") (tstamp 179b2c6b-34d3-4f8d-9920-6b082e4f9180)
(effects (font (size 0.8 0.8) (thickness 0.15)) (justify left bottom))
)
@ -6084,6 +6089,9 @@
(gr_text "DATA" (at 63.0428 99.9744 45) (layer "F.SilkS") (tstamp 6a8636ed-7847-4f79-be68-902fb28046b3)
(effects (font (size 0.8 0.8) (thickness 0.15)))
)
(gr_text "*" (at 72.9488 76.708) (layer "F.SilkS") (tstamp 7bf68895-0aeb-4dda-aa66-265c186f64a3)
(effects (font (size 1 1) (thickness 0.15)) (justify left bottom))
)
(gr_text "A" (at 68.834 53.34) (layer "F.SilkS") (tstamp cb5b8470-d35e-410c-b050-6e152f7e6a4c)
(effects (font (size 2 2) (thickness 0.15)) (justify left bottom))
)

View File

@ -1,6 +1,6 @@
{
"board": {
"active_layer": 0,
"active_layer": 37,
"active_layer_preset": "All Layers",
"auto_track_width": true,
"hidden_netclasses": [],

View File

@ -1,12 +1,12 @@
%TF.GenerationSoftware,KiCad,Pcbnew,7.0.9*%
%TF.CreationDate,2023-12-24T22:12:45+01:00*%
%TF.CreationDate,2024-01-21T01:35:16+01:00*%
%TF.ProjectId,DeskHop,4465736b-486f-4702-9e6b-696361645f70,rev?*%
%TF.SameCoordinates,Original*%
%TF.FileFunction,Copper,L2,Bot*%
%TF.FilePolarity,Positive*%
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Created by KiCad (PCBNEW 7.0.9) date 2023-12-24 22:12:45*
G04 Created by KiCad (PCBNEW 7.0.9) date 2024-01-21 01:35:16*
%MOMM*%
%LPD*%
G01*

View File

@ -1,12 +1,12 @@
%TF.GenerationSoftware,KiCad,Pcbnew,7.0.9*%
%TF.CreationDate,2023-12-24T22:12:45+01:00*%
%TF.CreationDate,2024-01-21T01:35:17+01:00*%
%TF.ProjectId,DeskHop,4465736b-486f-4702-9e6b-696361645f70,rev?*%
%TF.SameCoordinates,Original*%
%TF.FileFunction,Soldermask,Bot*%
%TF.FilePolarity,Negative*%
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Created by KiCad (PCBNEW 7.0.9) date 2023-12-24 22:12:45*
G04 Created by KiCad (PCBNEW 7.0.9) date 2024-01-21 01:35:17*
%MOMM*%
%LPD*%
G01*

View File

@ -1,12 +1,12 @@
%TF.GenerationSoftware,KiCad,Pcbnew,7.0.9*%
%TF.CreationDate,2023-12-24T22:12:45+01:00*%
%TF.CreationDate,2024-01-21T01:35:17+01:00*%
%TF.ProjectId,DeskHop,4465736b-486f-4702-9e6b-696361645f70,rev?*%
%TF.SameCoordinates,Original*%
%TF.FileFunction,Paste,Bot*%
%TF.FilePolarity,Positive*%
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Created by KiCad (PCBNEW 7.0.9) date 2023-12-24 22:12:45*
G04 Created by KiCad (PCBNEW 7.0.9) date 2024-01-21 01:35:17*
%MOMM*%
%LPD*%
G01*

View File

@ -1,12 +1,12 @@
%TF.GenerationSoftware,KiCad,Pcbnew,7.0.9*%
%TF.CreationDate,2023-12-24T22:12:45+01:00*%
%TF.CreationDate,2024-01-21T01:35:17+01:00*%
%TF.ProjectId,DeskHop,4465736b-486f-4702-9e6b-696361645f70,rev?*%
%TF.SameCoordinates,Original*%
%TF.FileFunction,Legend,Bot*%
%TF.FilePolarity,Positive*%
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Created by KiCad (PCBNEW 7.0.9) date 2023-12-24 22:12:45*
G04 Created by KiCad (PCBNEW 7.0.9) date 2024-01-21 01:35:17*
%MOMM*%
%LPD*%
G01*

View File

@ -1,11 +1,11 @@
%TF.GenerationSoftware,KiCad,Pcbnew,7.0.9*%
%TF.CreationDate,2023-12-24T22:12:45+01:00*%
%TF.CreationDate,2024-01-21T01:35:17+01:00*%
%TF.ProjectId,DeskHop,4465736b-486f-4702-9e6b-696361645f70,rev?*%
%TF.SameCoordinates,Original*%
%TF.FileFunction,Profile,NP*%
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Created by KiCad (PCBNEW 7.0.9) date 2023-12-24 22:12:45*
G04 Created by KiCad (PCBNEW 7.0.9) date 2024-01-21 01:35:17*
%MOMM*%
%LPD*%
G01*

View File

@ -1,12 +1,12 @@
%TF.GenerationSoftware,KiCad,Pcbnew,7.0.9*%
%TF.CreationDate,2023-12-24T22:12:44+01:00*%
%TF.CreationDate,2024-01-21T01:35:16+01:00*%
%TF.ProjectId,DeskHop,4465736b-486f-4702-9e6b-696361645f70,rev?*%
%TF.SameCoordinates,Original*%
%TF.FileFunction,Copper,L1,Top*%
%TF.FilePolarity,Positive*%
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Created by KiCad (PCBNEW 7.0.9) date 2023-12-24 22:12:44*
G04 Created by KiCad (PCBNEW 7.0.9) date 2024-01-21 01:35:16*
%MOMM*%
%LPD*%
G01*

View File

@ -1,12 +1,12 @@
%TF.GenerationSoftware,KiCad,Pcbnew,7.0.9*%
%TF.CreationDate,2023-12-24T22:12:45+01:00*%
%TF.CreationDate,2024-01-21T01:35:17+01:00*%
%TF.ProjectId,DeskHop,4465736b-486f-4702-9e6b-696361645f70,rev?*%
%TF.SameCoordinates,Original*%
%TF.FileFunction,Soldermask,Top*%
%TF.FilePolarity,Negative*%
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Created by KiCad (PCBNEW 7.0.9) date 2023-12-24 22:12:45*
G04 Created by KiCad (PCBNEW 7.0.9) date 2024-01-21 01:35:17*
%MOMM*%
%LPD*%
G01*

View File

@ -1,12 +1,12 @@
%TF.GenerationSoftware,KiCad,Pcbnew,7.0.9*%
%TF.CreationDate,2023-12-24T22:12:45+01:00*%
%TF.CreationDate,2024-01-21T01:35:17+01:00*%
%TF.ProjectId,DeskHop,4465736b-486f-4702-9e6b-696361645f70,rev?*%
%TF.SameCoordinates,Original*%
%TF.FileFunction,Paste,Top*%
%TF.FilePolarity,Positive*%
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Created by KiCad (PCBNEW 7.0.9) date 2023-12-24 22:12:45*
G04 Created by KiCad (PCBNEW 7.0.9) date 2024-01-21 01:35:17*
%MOMM*%
%LPD*%
G01*

View File

@ -1,19 +1,20 @@
%TF.GenerationSoftware,KiCad,Pcbnew,7.0.9*%
%TF.CreationDate,2023-12-24T22:12:45+01:00*%
%TF.CreationDate,2024-01-21T01:35:17+01:00*%
%TF.ProjectId,DeskHop,4465736b-486f-4702-9e6b-696361645f70,rev?*%
%TF.SameCoordinates,Original*%
%TF.FileFunction,Legend,Top*%
%TF.FilePolarity,Positive*%
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Created by KiCad (PCBNEW 7.0.9) date 2023-12-24 22:12:45*
G04 Created by KiCad (PCBNEW 7.0.9) date 2024-01-21 01:35:17*
%MOMM*%
%LPD*%
G01*
G04 APERTURE LIST*
%ADD10C,0.000000*%
%ADD11C,0.120000*%
%ADD12C,0.150000*%
%ADD12C,0.466400*%
%ADD13C,0.150000*%
G04 APERTURE END LIST*
D10*
G36*
@ -2924,6 +2925,13 @@ X72829522Y-109711596D01*
X72833573Y-109711461D01*
X72839898Y-109711622D01*
G37*
D12*
X64281928Y-81041328D02*
G75*
G03*
X64281928Y-81041328I-233200J0D01*
G01*
D10*
G36*
X71346351Y-110357574D02*
G01*
@ -3631,7 +3639,17 @@ X73688095Y-110998032D01*
X73692147Y-110997865D01*
X73698471Y-110998026D01*
G37*
D12*
D13*
X60957036Y-75577819D02*
X60957036Y-75815914D01*
X60718941Y-75720676D02*
X60957036Y-75815914D01*
X60957036Y-75815914D02*
X61195131Y-75720676D01*
X60814179Y-76006390D02*
X60957036Y-75815914D01*
X60957036Y-75815914D02*
X61099893Y-76006390D01*
X84210760Y-46681095D02*
X84210760Y-45881095D01*
X84210760Y-45881095D02*
@ -3870,6 +3888,16 @@ X63851198Y-99678364D02*
X63474074Y-98924117D01*
X63474074Y-98924117D02*
X64228321Y-99301241D01*
X73428436Y-75577819D02*
X73428436Y-75815914D01*
X73190341Y-75720676D02*
X73428436Y-75815914D01*
X73428436Y-75815914D02*
X73666531Y-75720676D01*
X73285579Y-76006390D02*
X73428436Y-75815914D01*
X73428436Y-75815914D02*
X73571293Y-76006390D01*
X69313636Y-52516009D02*
X70266017Y-52516009D01*
X69123160Y-53087438D02*
@ -4978,6 +5006,16 @@ X73046679Y-81767186D02*
X73046679Y-82413683D01*
X73046679Y-82413683D02*
X73396865Y-82063497D01*
X73154428Y-81174563D02*
X73289115Y-81309250D01*
X73100554Y-81390062D02*
X73289115Y-81309250D01*
X73289115Y-81309250D02*
X73369928Y-81120688D01*
X73316053Y-81497811D02*
X73289115Y-81309250D01*
X73289115Y-81309250D02*
X73477677Y-81336187D01*
X71830435Y-88032431D02*
X71749623Y-88059368D01*
X71749623Y-88059368D02*
@ -5040,6 +5078,16 @@ X73419741Y-87466746D02*
X73365867Y-87574495D01*
X73365867Y-87574495D02*
X73231180Y-87709182D01*
X73311992Y-86496999D02*
X73446679Y-86631686D01*
X73258117Y-86712498D02*
X73446679Y-86631686D01*
X73446679Y-86631686D02*
X73527491Y-86443124D01*
X73473616Y-86820248D02*
X73446679Y-86631686D01*
X73446679Y-86631686D02*
X73635241Y-86658624D01*
X71857372Y-44825494D02*
X71776560Y-44852431D01*
X71776560Y-44852431D02*
@ -5510,6 +5558,16 @@ X73300428Y-92465934D02*
X73219616Y-92492871D01*
X73219616Y-92492871D02*
X73165741Y-92492871D01*
X73300428Y-91334563D02*
X73435115Y-91469250D01*
X73246554Y-91550062D02*
X73435115Y-91469250D01*
X73435115Y-91469250D02*
X73515928Y-91280688D01*
X73462053Y-91657811D02*
X73435115Y-91469250D01*
X73435115Y-91469250D02*
X73623677Y-91496187D01*
X97430435Y-88032431D02*
X97349623Y-88059368D01*
X97349623Y-88059368D02*
@ -5572,6 +5630,16 @@ X99019741Y-87466746D02*
X98965867Y-87574495D01*
X98965867Y-87574495D02*
X98831180Y-87709182D01*
X98911992Y-86496999D02*
X99046679Y-86631686D01*
X98858117Y-86712498D02*
X99046679Y-86631686D01*
X99046679Y-86631686D02*
X99127491Y-86443124D01*
X99073616Y-86820248D02*
X99046679Y-86631686D01*
X99046679Y-86631686D02*
X99235241Y-86658624D01*
X71830435Y-62632431D02*
X71749623Y-62659368D01*
X71749623Y-62659368D02*
@ -5634,6 +5702,16 @@ X73419741Y-62066746D02*
X73365867Y-62174495D01*
X73365867Y-62174495D02*
X73231180Y-62309182D01*
X73311992Y-61096999D02*
X73446679Y-61231686D01*
X73258117Y-61312498D02*
X73446679Y-61231686D01*
X73446679Y-61231686D02*
X73527491Y-61043124D01*
X73473616Y-61420248D02*
X73446679Y-61231686D01*
X73446679Y-61231686D02*
X73635241Y-61258624D01*
X71333998Y-85734868D02*
X71253185Y-85761805D01*
X71253185Y-85761805D02*
@ -5722,6 +5800,16 @@ X73273491Y-84872871D02*
X73192679Y-84899808D01*
X73192679Y-84899808D02*
X73138804Y-84899808D01*
X73300428Y-83714563D02*
X73435115Y-83849250D01*
X73246554Y-83930062D02*
X73435115Y-83849250D01*
X73435115Y-83849250D02*
X73515928Y-83660688D01*
X73462053Y-84037811D02*
X73435115Y-83849250D01*
X73435115Y-83849250D02*
X73623677Y-83876187D01*
X97441998Y-65424868D02*
X97361185Y-65451805D01*
X97361185Y-65451805D02*
@ -6574,6 +6662,16 @@ X99102211Y-43556780D02*
X99236898Y-43422093D01*
X99236898Y-43422093D02*
X99344648Y-43368218D01*
X99640959Y-43018032D02*
X99775646Y-43152719D01*
X99587085Y-43233531D02*
X99775646Y-43152719D01*
X99775646Y-43152719D02*
X99856459Y-42964157D01*
X99802584Y-43341280D02*
X99775646Y-43152719D01*
X99775646Y-43152719D02*
X99964208Y-43179656D01*
X71830435Y-49932431D02*
X71749623Y-49959368D01*
X71749623Y-49959368D02*
@ -6636,6 +6734,16 @@ X73419741Y-49366746D02*
X73365867Y-49474495D01*
X73365867Y-49474495D02*
X73231180Y-49609182D01*
X73311992Y-48396999D02*
X73446679Y-48531686D01*
X73258117Y-48612498D02*
X73446679Y-48531686D01*
X73446679Y-48531686D02*
X73527491Y-48343124D01*
X73473616Y-48720248D02*
X73446679Y-48531686D01*
X73446679Y-48531686D02*
X73635241Y-48558624D01*
X71757372Y-47355494D02*
X71676560Y-47382431D01*
X71676560Y-47382431D02*
@ -6930,6 +7038,16 @@ X98904242Y-54786120D02*
X98823430Y-54813057D01*
X98823430Y-54813057D02*
X98769555Y-54813057D01*
X98931179Y-53627812D02*
X99065866Y-53762499D01*
X98877305Y-53843311D02*
X99065866Y-53762499D01*
X99065866Y-53762499D02*
X99146679Y-53573937D01*
X99092804Y-53951060D02*
X99065866Y-53762499D01*
X99065866Y-53762499D02*
X99254428Y-53789436D01*
X97430435Y-49932431D02*
X97349623Y-49959368D01*
X97349623Y-49959368D02*
@ -6992,6 +7110,16 @@ X99019741Y-49366746D02*
X98965867Y-49474495D01*
X98965867Y-49474495D02*
X98831180Y-49609182D01*
X98911992Y-48396999D02*
X99046679Y-48531686D01*
X98858117Y-48612498D02*
X99046679Y-48531686D01*
X99046679Y-48531686D02*
X99127491Y-48343124D01*
X99073616Y-48720248D02*
X99046679Y-48531686D01*
X99046679Y-48531686D02*
X99235241Y-48558624D01*
X98182651Y-70777710D02*
X97724716Y-70696898D01*
X97859403Y-71100959D02*
@ -7106,6 +7234,16 @@ X72715680Y-89279436D02*
X72985054Y-89818184D01*
X72985054Y-89818184D02*
X73335240Y-89467998D01*
X73254428Y-88794563D02*
X73389115Y-88929250D01*
X73200554Y-89010062D02*
X73389115Y-88929250D01*
X73389115Y-88929250D02*
X73469928Y-88740688D01*
X73416053Y-89117811D02*
X73389115Y-88929250D01*
X73389115Y-88929250D02*
X73577677Y-88956187D01*
X97441998Y-93354868D02*
X97361185Y-93381805D01*
X97361185Y-93381805D02*
@ -7460,6 +7598,16 @@ X62561264Y-49350995D02*
X62507390Y-49458744D01*
X62507390Y-49458744D02*
X62372703Y-49593431D01*
X62453515Y-48381248D02*
X62588202Y-48515935D01*
X62399640Y-48596747D02*
X62588202Y-48515935D01*
X62588202Y-48515935D02*
X62669014Y-48327373D01*
X62615139Y-48704497D02*
X62588202Y-48515935D01*
X62588202Y-48515935D02*
X62776764Y-48542873D01*
X43329333Y-96536353D02*
X43291237Y-96574449D01*
X43291237Y-96574449D02*
@ -7592,6 +7740,16 @@ X36841951Y-92450183D02*
X36761139Y-92477120D01*
X36761139Y-92477120D02*
X36707264Y-92477120D01*
X36841951Y-91318812D02*
X36976638Y-91453499D01*
X36788077Y-91534311D02*
X36976638Y-91453499D01*
X36976638Y-91453499D02*
X37057451Y-91264937D01*
X37003576Y-91642060D02*
X36976638Y-91453499D01*
X36976638Y-91453499D02*
X37165200Y-91480436D01*
X60983521Y-85719117D02*
X60902708Y-85746054D01*
X60902708Y-85746054D02*
@ -7812,6 +7970,16 @@ X62643734Y-43541029D02*
X62778421Y-43406342D01*
X62778421Y-43406342D02*
X62886171Y-43352467D01*
X63182482Y-43002281D02*
X63317169Y-43136968D01*
X63128608Y-43217780D02*
X63317169Y-43136968D01*
X63317169Y-43136968D02*
X63397982Y-42948406D01*
X63344107Y-43325529D02*
X63317169Y-43136968D01*
X63317169Y-43136968D02*
X63505731Y-43163905D01*
X61724174Y-70761959D02*
X61266239Y-70681147D01*
X61400926Y-71085208D02*
@ -8284,6 +8452,16 @@ X36961264Y-87450995D02*
X36907390Y-87558744D01*
X36907390Y-87558744D02*
X36772703Y-87693431D01*
X36853515Y-86481248D02*
X36988202Y-86615935D01*
X36799640Y-86696747D02*
X36988202Y-86615935D01*
X36988202Y-86615935D02*
X37069014Y-86427373D01*
X37015139Y-86804497D02*
X36988202Y-86615935D01*
X36988202Y-86615935D02*
X37176764Y-86642873D01*
X35398895Y-65129743D02*
X35318083Y-65156680D01*
X35318083Y-65156680D02*
@ -8552,6 +8730,16 @@ X36961264Y-49350995D02*
X36907390Y-49458744D01*
X36907390Y-49458744D02*
X36772703Y-49593431D01*
X36853515Y-48381248D02*
X36988202Y-48515935D01*
X36799640Y-48596747D02*
X36988202Y-48515935D01*
X36988202Y-48515935D02*
X37069014Y-48327373D01*
X37015139Y-48704497D02*
X36988202Y-48515935D01*
X36988202Y-48515935D02*
X37176764Y-48542873D01*
X60983521Y-90799117D02*
X60902708Y-90826054D01*
X60902708Y-90826054D02*
@ -8618,6 +8806,16 @@ X62276516Y-89452247D02*
X62653640Y-89075123D01*
X62653640Y-89075123D02*
X62976889Y-89883245D01*
X62949951Y-88778812D02*
X63084638Y-88913499D01*
X62896077Y-88994311D02*
X63084638Y-88913499D01*
X63084638Y-88913499D02*
X63165451Y-88724937D01*
X63111576Y-89102060D02*
X63084638Y-88913499D01*
X63084638Y-88913499D02*
X63273200Y-88940436D01*
X35398895Y-57509743D02*
X35318083Y-57536680D01*
X35318083Y-57536680D02*
@ -8738,6 +8936,16 @@ X36961264Y-74750995D02*
X36907390Y-74858744D01*
X36907390Y-74858744D02*
X36772703Y-74993431D01*
X36853515Y-73781248D02*
X36988202Y-73915935D01*
X36799640Y-73996747D02*
X36988202Y-73915935D01*
X36988202Y-73915935D02*
X37069014Y-73727373D01*
X37015139Y-74104497D02*
X36988202Y-73915935D01*
X36988202Y-73915935D02*
X37176764Y-73942873D01*
X35398895Y-70209743D02*
X35318083Y-70236680D01*
X35318083Y-70236680D02*
@ -8920,6 +9128,16 @@ X36257203Y-89263685D02*
X36526577Y-89802433D01*
X36526577Y-89802433D02*
X36876763Y-89452247D01*
X36795951Y-88778812D02*
X36930638Y-88913499D01*
X36742077Y-88994311D02*
X36930638Y-88913499D01*
X36930638Y-88913499D02*
X37011451Y-88724937D01*
X36957576Y-89102060D02*
X36930638Y-88913499D01*
X36930638Y-88913499D02*
X37119200Y-88940436D01*
X61233707Y-57929178D02*
X61503081Y-57659804D01*
X61341457Y-58144677D02*
@ -9406,6 +9624,16 @@ X62445765Y-54770369D02*
X62364953Y-54797306D01*
X62364953Y-54797306D02*
X62311078Y-54797306D01*
X62472702Y-53612061D02*
X62607389Y-53746748D01*
X62418828Y-53827560D02*
X62607389Y-53746748D01*
X62607389Y-53746748D02*
X62688202Y-53558186D01*
X62634327Y-53935309D02*
X62607389Y-53746748D01*
X62607389Y-53746748D02*
X62795951Y-53773685D01*
X60983521Y-93339117D02*
X60902708Y-93366054D01*
X60902708Y-93366054D02*
@ -9510,6 +9738,16 @@ X62599765Y-92153871D02*
X62599765Y-92207746D01*
X62599765Y-92207746D02*
X62626702Y-92288558D01*
X62949951Y-91318812D02*
X63084638Y-91453499D01*
X62896077Y-91534311D02*
X63084638Y-91453499D01*
X63084638Y-91453499D02*
X63165451Y-91264937D01*
X63111576Y-91642060D02*
X63084638Y-91453499D01*
X63084638Y-91453499D02*
X63273200Y-91480436D01*
X35371958Y-62616680D02*
X35291146Y-62643617D01*
X35291146Y-62643617D02*
@ -9572,6 +9810,16 @@ X36961264Y-62050995D02*
X36907390Y-62158744D01*
X36907390Y-62158744D02*
X36772703Y-62293431D01*
X36853515Y-61081248D02*
X36988202Y-61215935D01*
X36799640Y-61296747D02*
X36988202Y-61215935D01*
X36988202Y-61215935D02*
X37069014Y-61027373D01*
X37015139Y-61404497D02*
X36988202Y-61215935D01*
X36988202Y-61215935D02*
X37176764Y-61242873D01*
X60983521Y-73019117D02*
X60902708Y-73046054D01*
X60902708Y-73046054D02*
@ -9998,6 +10246,16 @@ X62561264Y-87450995D02*
X62507390Y-87558744D01*
X62507390Y-87558744D02*
X62372703Y-87693431D01*
X62453515Y-86481248D02*
X62588202Y-86615935D01*
X62399640Y-86696747D02*
X62588202Y-86615935D01*
X62588202Y-86615935D02*
X62669014Y-86427373D01*
X62615139Y-86804497D02*
X62588202Y-86615935D01*
X62588202Y-86615935D02*
X62776764Y-86642873D01*
X35398895Y-54969743D02*
X35318083Y-54996680D01*
X35318083Y-54996680D02*
@ -10558,56 +10816,56 @@ X62896076Y-58691309D02*
X62842202Y-58691309D01*
X62842202Y-58691309D02*
X62761389Y-58718247D01*
X73980424Y-75262645D02*
X73932805Y-75310265D01*
X73932805Y-75310265D02*
X73789948Y-75357884D01*
X73789948Y-75357884D02*
X73694710Y-75357884D01*
X73694710Y-75357884D02*
X73551853Y-75310265D01*
X73551853Y-75310265D02*
X73456615Y-75215026D01*
X73456615Y-75215026D02*
X73408996Y-75119788D01*
X73408996Y-75119788D02*
X73361377Y-74929312D01*
X73361377Y-74929312D02*
X73361377Y-74786455D01*
X73361377Y-74786455D02*
X73408996Y-74595979D01*
X73408996Y-74595979D02*
X73456615Y-74500741D01*
X73456615Y-74500741D02*
X73551853Y-74405503D01*
X73551853Y-74405503D02*
X73694710Y-74357884D01*
X73694710Y-74357884D02*
X73789948Y-74357884D01*
X73789948Y-74357884D02*
X73932805Y-74405503D01*
X73932805Y-74405503D02*
X73980424Y-74453122D01*
X74361377Y-74453122D02*
X74408996Y-74405503D01*
X74408996Y-74405503D02*
X74504234Y-74357884D01*
X74504234Y-74357884D02*
X74742329Y-74357884D01*
X74742329Y-74357884D02*
X74837567Y-74405503D01*
X74837567Y-74405503D02*
X74885186Y-74453122D01*
X74885186Y-74453122D02*
X74932805Y-74548360D01*
X74932805Y-74548360D02*
X74932805Y-74643598D01*
X74932805Y-74643598D02*
X74885186Y-74786455D01*
X74885186Y-74786455D02*
X74313758Y-75357884D01*
X74313758Y-75357884D02*
X74932805Y-75357884D01*
X70572333Y-77067580D02*
X70524714Y-77115200D01*
X70524714Y-77115200D02*
X70381857Y-77162819D01*
X70381857Y-77162819D02*
X70286619Y-77162819D01*
X70286619Y-77162819D02*
X70143762Y-77115200D01*
X70143762Y-77115200D02*
X70048524Y-77019961D01*
X70048524Y-77019961D02*
X70000905Y-76924723D01*
X70000905Y-76924723D02*
X69953286Y-76734247D01*
X69953286Y-76734247D02*
X69953286Y-76591390D01*
X69953286Y-76591390D02*
X70000905Y-76400914D01*
X70000905Y-76400914D02*
X70048524Y-76305676D01*
X70048524Y-76305676D02*
X70143762Y-76210438D01*
X70143762Y-76210438D02*
X70286619Y-76162819D01*
X70286619Y-76162819D02*
X70381857Y-76162819D01*
X70381857Y-76162819D02*
X70524714Y-76210438D01*
X70524714Y-76210438D02*
X70572333Y-76258057D01*
X70953286Y-76258057D02*
X71000905Y-76210438D01*
X71000905Y-76210438D02*
X71096143Y-76162819D01*
X71096143Y-76162819D02*
X71334238Y-76162819D01*
X71334238Y-76162819D02*
X71429476Y-76210438D01*
X71429476Y-76210438D02*
X71477095Y-76258057D01*
X71477095Y-76258057D02*
X71524714Y-76353295D01*
X71524714Y-76353295D02*
X71524714Y-76448533D01*
X71524714Y-76448533D02*
X71477095Y-76591390D01*
X71477095Y-76591390D02*
X70905667Y-77162819D01*
X70905667Y-77162819D02*
X71524714Y-77162819D01*
X49044266Y-97498819D02*
X49044266Y-98213104D01*
X49044266Y-98213104D02*
@ -10734,46 +10992,46 @@ X61350089Y-97271266D02*
X60778661Y-97842695D01*
X60778661Y-97842695D02*
X61397708Y-97842695D01*
X60287369Y-75238779D02*
X60239750Y-75286399D01*
X60239750Y-75286399D02*
X60096893Y-75334018D01*
X60096893Y-75334018D02*
X60001655Y-75334018D01*
X60001655Y-75334018D02*
X59858798Y-75286399D01*
X59858798Y-75286399D02*
X59763560Y-75191160D01*
X59763560Y-75191160D02*
X59715941Y-75095922D01*
X59715941Y-75095922D02*
X59668322Y-74905446D01*
X59668322Y-74905446D02*
X59668322Y-74762589D01*
X59668322Y-74762589D02*
X59715941Y-74572113D01*
X59715941Y-74572113D02*
X59763560Y-74476875D01*
X59763560Y-74476875D02*
X59858798Y-74381637D01*
X59858798Y-74381637D02*
X60001655Y-74334018D01*
X60001655Y-74334018D02*
X60096893Y-74334018D01*
X60096893Y-74334018D02*
X60239750Y-74381637D01*
X60239750Y-74381637D02*
X60287369Y-74429256D01*
X61239750Y-75334018D02*
X60668322Y-75334018D01*
X60954036Y-75334018D02*
X60954036Y-74334018D01*
X60954036Y-74334018D02*
X60858798Y-74476875D01*
X60858798Y-74476875D02*
X60763560Y-74572113D01*
X60763560Y-74572113D02*
X60668322Y-74619732D01*
X63942933Y-77016780D02*
X63895314Y-77064400D01*
X63895314Y-77064400D02*
X63752457Y-77112019D01*
X63752457Y-77112019D02*
X63657219Y-77112019D01*
X63657219Y-77112019D02*
X63514362Y-77064400D01*
X63514362Y-77064400D02*
X63419124Y-76969161D01*
X63419124Y-76969161D02*
X63371505Y-76873923D01*
X63371505Y-76873923D02*
X63323886Y-76683447D01*
X63323886Y-76683447D02*
X63323886Y-76540590D01*
X63323886Y-76540590D02*
X63371505Y-76350114D01*
X63371505Y-76350114D02*
X63419124Y-76254876D01*
X63419124Y-76254876D02*
X63514362Y-76159638D01*
X63514362Y-76159638D02*
X63657219Y-76112019D01*
X63657219Y-76112019D02*
X63752457Y-76112019D01*
X63752457Y-76112019D02*
X63895314Y-76159638D01*
X63895314Y-76159638D02*
X63942933Y-76207257D01*
X64895314Y-77112019D02*
X64323886Y-77112019D01*
X64609600Y-77112019D02*
X64609600Y-76112019D01*
X64609600Y-76112019D02*
X64514362Y-76254876D01*
X64514362Y-76254876D02*
X64419124Y-76350114D01*
X64419124Y-76350114D02*
X64323886Y-76397733D01*
D11*
%TO.C,U1*%
X74844000Y-43334000D02*

View File

@ -1,7 +1,7 @@
M48
; DRILL file {KiCad 7.0.9} date Sunday, December 24, 2023 at 10:12:50PM
; DRILL file {KiCad 7.0.9} date Sunday, January 21, 2024 at 01:35:19AM
; FORMAT={-:-/ absolute / inch / decimal}
; #@! TF.CreationDate,2023-12-24T22:12:50+01:00
; #@! TF.CreationDate,2024-01-21T01:35:19+01:00
; #@! TF.GenerationSoftware,Kicad,Pcbnew,7.0.9
; #@! TF.FileFunction,NonPlated,1,2,NPTH
FMAT,2

View File

@ -1,7 +1,7 @@
M48
; DRILL file {KiCad 7.0.9} date Sunday, December 24, 2023 at 10:12:50PM
; DRILL file {KiCad 7.0.9} date Sunday, January 21, 2024 at 01:35:19AM
; FORMAT={-:-/ absolute / inch / decimal}
; #@! TF.CreationDate,2023-12-24T22:12:50+01:00
; #@! TF.CreationDate,2024-01-21T01:35:19+01:00
; #@! TF.GenerationSoftware,Kicad,Pcbnew,7.0.9
; #@! TF.FileFunction,Plated,1,2,PTH
FMAT,2

View File

@ -5,7 +5,7 @@
"Application": "Pcbnew",
"Version": "7.0.9"
},
"CreationDate": "2023-12-24T22:12:45+01:00"
"CreationDate": "2024-01-21T01:35:17+01:00"
},
"GeneralSpecs": {
"ProjectId": {

View File

@ -2,10 +2,11 @@
/* Default configuration */
const config_t default_config = {
.magic_header = 0x0B00B1E5,
.output[ACTIVE_OUTPUT_A] =
.magic_header = 0xB00B1E5,
.version = CURRENT_CONFIG_VERSION,
.output[OUTPUT_A] =
{
.number = ACTIVE_OUTPUT_A,
.number = OUTPUT_A,
.speed_x = MOUSE_SPEED_A_FACTOR_X,
.speed_y = MOUSE_SPEED_A_FACTOR_Y,
.border = {
@ -14,10 +15,10 @@ const config_t default_config = {
},
.screen_count = 1,
.screen_index = 0,
},
.output[ACTIVE_OUTPUT_B] =
},
.output[OUTPUT_B] =
{
.number = ACTIVE_OUTPUT_B,
.number = OUTPUT_B,
.speed_x = MOUSE_SPEED_B_FACTOR_X,
.speed_y = MOUSE_SPEED_B_FACTOR_Y,
.border = {
@ -27,4 +28,5 @@ const config_t default_config = {
.screen_count = 1,
.screen_index = 0,
},
.screensaver_enabled = SCREENSAVER_ENABLED,
};

View File

@ -21,77 +21,77 @@
* ============ Hotkey Handler Routines ============ *
* =================================================== */
void output_toggle_hotkey_handler(device_state_t* state) {
/* This is the main hotkey for switching outputs */
void output_toggle_hotkey_handler(device_t *state) {
/* If switching explicitly disabled, return immediately */
if (state->switch_lock)
return;
state->active_output ^= 1;
switch_output(state->active_output);
switch_output(state, state->active_output);
};
/* This key combo records switch y top coordinate for different-size monitors */
void screen_border_hotkey_handler(device_state_t* state) {
void screen_border_hotkey_handler(device_t *state) {
border_size_t *border = &state->config.output[state->active_output].border;
/* To deal away with 2 different keys, if we're above half, it's the top coord and vice versa */
/* To avoid having 2 different keys, if we're above half, it's the top coord */
if (state->mouse_y > (MAX_SCREEN_COORD / 2))
border->bottom = state->mouse_y;
else
border->top = state->mouse_y;
send_packet((uint8_t*)border, SYNC_BORDERS_MSG, sizeof(border_size_t));
save_config();
send_packet((uint8_t *)border, SYNC_BORDERS_MSG, sizeof(border_size_t));
save_config(state);
};
/* This key combo puts board A in firmware upgrade mode */
void fw_upgrade_hotkey_handler_A(device_state_t* state) {
void fw_upgrade_hotkey_handler_A(device_t *state) {
reset_usb_boot(1 << PICO_DEFAULT_LED_PIN, 0);
};
/* This key combo puts board B in firmware upgrade mode */
void fw_upgrade_hotkey_handler_B(device_state_t* state) {
void fw_upgrade_hotkey_handler_B(device_t *state) {
send_value(ENABLE, FIRMWARE_UPGRADE_MSG);
};
/* This key combo prevents mouse from switching outputs */
void switchlock_hotkey_handler(device_state_t* state) {
void switchlock_hotkey_handler(device_t *state) {
state->switch_lock ^= 1;
send_value(state->switch_lock, SWITCH_LOCK_MSG);
}
/* When pressed, erases stored config in flash and loads defaults */
void wipe_config_hotkey_handler(device_state_t* state) {
/* When pressed, erases stored config in flash and loads defaults on both boards */
void wipe_config_hotkey_handler(device_t *state) {
wipe_config();
load_config();
load_config(state);
send_value(ENABLE, WIPE_CONFIG_MSG);
}
void mouse_zoom_hotkey_handler(device_state_t* state) {
if (state->mouse_zoom)
return;
void screensaver_hotkey_handler(device_t *state) {
state->config.screensaver_enabled ^= 1;
send_value(state->config.screensaver_enabled, SCREENSAVER_MSG);
}
send_value(ENABLE, MOUSE_ZOOM_MSG);
state->mouse_zoom = true;
};
void all_keys_released_handler(device_state_t* state) {
if (state->mouse_zoom) {
state->mouse_zoom = false;
send_value(DISABLE, MOUSE_ZOOM_MSG);
}
/* When pressed, toggles the current mouse zoom mode state */
void mouse_zoom_hotkey_handler(device_t *state) {
state->mouse_zoom ^= 1;
send_value(state->mouse_zoom, MOUSE_ZOOM_MSG);
};
/**==================================================== *
* ========== UART Message Handling Routines ======== *
* ==================================================== */
void handle_keyboard_uart_msg(uart_packet_t* packet, device_state_t* state) {
queue_kbd_report((hid_keyboard_report_t*)packet->data, state);
/* Function handles received keypresses from the other board */
void handle_keyboard_uart_msg(uart_packet_t *packet, device_t *state) {
queue_kbd_report((hid_keyboard_report_t *)packet->data, state);
state->last_activity[BOARD_ROLE] = time_us_64();
}
void handle_mouse_abs_uart_msg(uart_packet_t* packet, device_state_t* state) {
hid_abs_mouse_report_t* mouse_report = (hid_abs_mouse_report_t*)packet->data;
/* Function handles received mouse moves from the other board */
void handle_mouse_abs_uart_msg(uart_packet_t *packet, device_t *state) {
mouse_abs_report_t *mouse_report = (mouse_abs_report_t *)packet->data;
queue_mouse_report(mouse_report, state);
state->mouse_x = mouse_report->x;
@ -100,52 +100,69 @@ void handle_mouse_abs_uart_msg(uart_packet_t* packet, device_state_t* state) {
state->last_activity[BOARD_ROLE] = time_us_64();
}
void handle_output_select_msg(uart_packet_t* packet, device_state_t* state) {
/* Function handles request to switch output */
void handle_output_select_msg(uart_packet_t *packet, device_t *state) {
state->active_output = packet->data[0];
if (state->tud_connected)
stop_pressing_any_keys(&global_state);
release_all_keys(state);
restore_leds(state);
}
/* On firmware upgrade message, reboot into the BOOTSEL fw upgrade mode */
void handle_fw_upgrade_msg(void) {
void handle_fw_upgrade_msg(uart_packet_t *packet, device_t *state) {
reset_usb_boot(1 << PICO_DEFAULT_LED_PIN, 0);
}
void handle_mouse_zoom_msg(uart_packet_t* packet, device_state_t* state) {
/* Comply with request to turn mouse zoom mode on/off */
void handle_mouse_zoom_msg(uart_packet_t *packet, device_t *state) {
state->mouse_zoom = packet->data[0];
}
void handle_set_report_msg(uart_packet_t* packet, device_state_t* state) {
/* Only board B sends LED state through this message type */
/* Process request to update keyboard LEDs */
void handle_set_report_msg(uart_packet_t *packet, device_t *state) {
state->keyboard_leds[BOARD_ROLE] = packet->data[0];
restore_leds(state);
}
void handle_switch_lock_msg(uart_packet_t* packet, device_state_t* state) {
/* Process request to block mouse from switching, update internal state */
void handle_switch_lock_msg(uart_packet_t *packet, device_t *state) {
state->switch_lock = packet->data[0];
}
/* Handle border syncing message that lets the other device know about monitor height offset */
void handle_sync_borders_msg(uart_packet_t* packet, device_state_t* state) {
border_size_t *border = &state->config.output[state->active_output].border;
memcpy(border, packet->data, sizeof(border_size_t));
save_config();
void handle_sync_borders_msg(uart_packet_t *packet, device_t *state) {
border_size_t *border = &state->config.output[state->active_output].border;
memcpy(border, packet->data, sizeof(border_size_t));
save_config(state);
}
/* When this message is received, flash the locally attached LED to verify serial comms */
void handle_flash_led_msg(uart_packet_t* packet, device_state_t* state) {
void handle_flash_led_msg(uart_packet_t *packet, device_t *state) {
blink_led(state);
}
/* When this message is received, wipe the local flash config */
void handle_wipe_config_msg(uart_packet_t *packet, device_t *state) {
wipe_config();
load_config(state);
}
void handle_screensaver_msg(uart_packet_t *packet, device_t *state) {
state->config.screensaver_enabled = packet->data[0];
}
/**==================================================== *
* ============== Output Switch Routines ============ *
* ==================================================== */
/* Update output variable, set LED on/off and notify the other board so they are in sync. */
void switch_output(uint8_t new_output) {
global_state.active_output = new_output;
restore_leds(&global_state);
void switch_output(device_t *state, uint8_t new_output) {
state->active_output = new_output;
restore_leds(state);
send_value(new_output, OUTPUT_SELECT_MSG);
/* If we were holding a key down and drag the mouse to another screen, the key gets stuck.
Changing outputs = no more keypresses on the previous system. */
stop_pressing_any_keys(&global_state);
release_all_keys(state);
}

View File

@ -1,63 +1,62 @@
/*
/*
* This file is part of DeskHop (https://github.com/hrvach/deskhop).
* Copyright (c) 2024 Hrvoje Cavrak
*
* Based on the TinyUSB HID parser routine and the amazing USB2N64
*
* Based on the TinyUSB HID parser routine and the amazing USB2N64
* adapter (https://github.com/pdaxrom/usb2n64-adapter)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "main.h"
#include "hid_parser.h"
#define IS_BLOCK_END (collection.start == collection.end)
#define MAX_BUTTONS 16
#define MAX_BUTTONS 16
enum { SIZE_0_BIT = 0, SIZE_8_BIT = 1, SIZE_16_BIT = 2, SIZE_32_BIT = 3 };
/* Size is 0, 1, 2, or 3, describing cases of no data, 8-bit, 16-bit,
/* Size is 0, 1, 2, or 3, describing cases of no data, 8-bit, 16-bit,
or 32-bit data. */
uint32_t get_descriptor_value(uint8_t const *report, int size) {
switch (size) {
case SIZE_8_BIT:
return report[0];
case SIZE_16_BIT:
return tu_u16(report[1], report[0]);
case SIZE_32_BIT:
return tu_u32(report[3], report[2], report[1], report[0]);
default:
return 0;
}
switch (size) {
case SIZE_8_BIT:
return report[0];
case SIZE_16_BIT:
return tu_u16(report[1], report[0]);
case SIZE_32_BIT:
return tu_u32(report[3], report[2], report[1], report[0]);
default:
return 0;
}
}
/* We store all globals as unsigned to avoid countless switch/cases.
In case of e.g. min/max, we need to treat some data as signed retroactively. */
int32_t to_signed(globals_t *data) {
switch (data->hdr.size) {
case SIZE_8_BIT:
return (int8_t)data->val;
case SIZE_16_BIT:
return (int16_t)data->val;
default:
return data->val;
}
switch (data->hdr.size) {
case SIZE_8_BIT:
return (int8_t)data->val;
case SIZE_16_BIT:
return (int16_t)data->val;
default:
return data->val;
}
}
/* Given a value struct with size and offset in bits,
/* Given a value struct with size and offset in bits,
find and return a value from the HID report */
int32_t get_report_value(uint8_t* report, report_val_t *val) {
int32_t get_report_value(uint8_t *report, report_val_t *val) {
/* Calculate the bit offset within the byte */
uint8_t offset_in_bits = val->offset % 8;
@ -78,7 +77,7 @@ int32_t get_report_value(uint8_t* report, report_val_t *val) {
result |= report[++byte_offset] << remaining_bits;
remaining_bits += 8;
}
/* Apply the mask to retain only the desired number of bits */
result = result & mask;
@ -92,119 +91,135 @@ int32_t get_report_value(uint8_t* report, report_val_t *val) {
return result;
}
/* This method is far from a generalized HID descriptor parsing, but should work
* well enough to find the basic values we care about to move the mouse around.
* Your descriptor for a mouse with 2 wheels and 264 buttons might not parse correctly.
**/
uint8_t parse_report_descriptor(mouse_t *mouse, uint8_t arr_count,
uint8_t const *report, uint16_t desc_len) {
void update_usage(parser_state_t *parser, int i) {
/* If we don't have as many usages as elements, the usage for the previous element applies */
if (i && i >= parser->usage_count) {
*(parser->p_usage + i) = *(parser->p_usage + parser->usage_count - 1);
}
}
/* Get these elements and store them in the proper place in the mouse struct
* For example, to match wheel, we want collection usage to be HID_USAGE_DESKTOP_MOUSE, page to be HID_USAGE_PAGE_DESKTOP,
* usage to be HID_USAGE_DESKTOP_WHEEL, then if all of that is matched we store the value to mouse->wheel */
const usage_map_t usage_map[] = {
{HID_USAGE_DESKTOP_MOUSE, HID_USAGE_PAGE_BUTTON, HID_USAGE_DESKTOP_POINTER, &mouse->buttons},
{HID_USAGE_DESKTOP_MOUSE, HID_USAGE_PAGE_DESKTOP, HID_USAGE_DESKTOP_X, &mouse->move_x},
{HID_USAGE_DESKTOP_MOUSE, HID_USAGE_PAGE_DESKTOP, HID_USAGE_DESKTOP_Y, &mouse->move_y},
{HID_USAGE_DESKTOP_MOUSE, HID_USAGE_PAGE_DESKTOP, HID_USAGE_DESKTOP_WHEEL, &mouse->wheel},
void find_and_store_element(parser_state_t *parser, int map_len, int i) {
usage_map_t *map = &parser->map[0];
for (int j = 0; j < map_len; j++, map++) {
/* Filter based on usage criteria */
if (map->report_usage == parser->global_usage
&& map->usage_page == parser->globals[RI_GLOBAL_USAGE_PAGE].val
&& map->usage == *(parser->p_usage + i)) {
/* Buttons are the ones that appear multiple times, aggregate for now */
if (map->element->size) {
map->element->size++;
continue;
}
/* Store the found element's attributes */
map->element->offset = parser->offset_in_bits;
map->element->size = parser->globals[RI_GLOBAL_REPORT_SIZE].val;
map->element->min = to_signed(&parser->globals[RI_GLOBAL_LOGICAL_MIN]);
map->element->max = to_signed(&parser->globals[RI_GLOBAL_LOGICAL_MAX]);
}
}
}
void handle_global_item(parser_state_t *parser, header_t *header, uint32_t data, mouse_t *mouse) {
/* There are just 16 possible tags, store any one that comes along to an array
instead of doing switch and 16 cases */
parser->globals[header->tag].val = data;
parser->globals[header->tag].hdr = *header;
if (header->tag == RI_GLOBAL_REPORT_ID) {
/* Important to track, if report IDs are used reports are preceded/offset by a 1-byte ID value */
if (parser->global_usage == HID_USAGE_DESKTOP_MOUSE)
mouse->report_id = data;
mouse->uses_report_id = true;
}
}
void handle_local_item(parser_state_t *parser, header_t *header, uint32_t data) {
if (header->tag == RI_LOCAL_USAGE) {
/* If we are not within a collection, the usage tag applies to the entire section */
if (parser->collection.start == parser->collection.end) {
parser->global_usage = data;
} else {
*(parser->p_usage + parser->usage_count++) = data;
}
}
}
void handle_main_item(parser_state_t *parser, header_t *header, int map_len) {
/* Update Collection */
parser->collection.start += (header->tag == RI_MAIN_COLLECTION);
parser->collection.end += (header->tag == RI_MAIN_COLLECTION_END);
if (header->tag == RI_MAIN_INPUT) {
for (int i = 0; i < parser->globals[RI_GLOBAL_REPORT_COUNT].val; i++) {
update_usage(parser, i);
find_and_store_element(parser, map_len, i);
/* Iterate <count> times and increase offset by <size> amount, moving by <count> x <size> bits */
parser->offset_in_bits += parser->globals[RI_GLOBAL_REPORT_SIZE].val;
}
/* Advance the usage array pointer by global report count and reset the count variable */
parser->p_usage += parser->globals[RI_GLOBAL_REPORT_COUNT].val;
parser->usage_count = 0;
}
}
/* This method is sub-optimal and far from a generalized HID descriptor parsing, but should
* hopefully work well enough to find the basic values we care about to move the mouse around.
* Your descriptor for a mouse with 2 wheels and 264 buttons might not parse correctly.
**/
uint8_t parse_report_descriptor(mouse_t *mouse, uint8_t arr_count, uint8_t const *report, uint16_t desc_len) {
usage_map_t usage_map[] = {
{.report_usage = HID_USAGE_DESKTOP_MOUSE,
.usage_page = HID_USAGE_PAGE_BUTTON,
.usage = HID_USAGE_DESKTOP_POINTER,
.element = &mouse->buttons},
{.report_usage = HID_USAGE_DESKTOP_MOUSE,
.usage_page = HID_USAGE_PAGE_DESKTOP,
.usage = HID_USAGE_DESKTOP_X,
.element = &mouse->move_x},
{.report_usage = HID_USAGE_DESKTOP_MOUSE,
.usage_page = HID_USAGE_PAGE_DESKTOP,
.usage = HID_USAGE_DESKTOP_Y,
.element = &mouse->move_y},
{.report_usage = HID_USAGE_DESKTOP_MOUSE,
.usage_page = HID_USAGE_PAGE_DESKTOP,
.usage = HID_USAGE_DESKTOP_WHEEL,
.element = &mouse->wheel},
};
/* Some variables used for keeping tabs on parsing */
uint8_t usage_count = 0;
uint8_t g_usage = 0;
uint32_t offset_in_bits = 0;
parser_state_t parser = {0};
parser.p_usage = parser.usages;
parser.map = usage_map;
uint8_t usages[64] = {0};
uint8_t* p_usage = usages;
while (desc_len > 0) {
header_t header = *(header_t *)report++;
uint32_t data = get_descriptor_value(report, header.size);
collection_t collection = {0};
switch (header.type) {
case RI_TYPE_MAIN:
handle_main_item(&parser, &header, ARRAY_SIZE(usage_map));
break;
/* as tag is 4 bits, there can be 16 different tags in global header type */
globals_t globals[16] = {0};
case RI_TYPE_GLOBAL:
handle_global_item(&parser, &header, data, mouse);
break;
for (int len = desc_len; len > 0; len--) {
header_t header = *(header_t *)report++;
uint32_t data = get_descriptor_value(report, header.size);
switch (header.type) {
case RI_TYPE_MAIN:
// Keep count of collections, starts and ends
collection.start += (header.tag == RI_MAIN_COLLECTION);
collection.end += (header.tag == RI_MAIN_COLLECTION_END);
if (header.tag == RI_MAIN_INPUT) {
for (int i = 0; i < globals[RI_GLOBAL_REPORT_COUNT].val; i++) {
/* If we don't have as many usages as elements, the usage for the previous
element applies */
if (i && i >= usage_count ) {
*(p_usage + i) = *(p_usage + usage_count - 1);
}
const usage_map_t *map = usage_map;
/* Only focus on the items we care about (buttons, x and y, wheels, etc) */
for (int j=0; j<sizeof(usage_map)/sizeof(usage_map[0]); j++, map++) {
/* Filter based on usage criteria */
if (map->report_usage == g_usage &&
map->usage_page == globals[RI_GLOBAL_USAGE_PAGE].val &&
map->usage == *(p_usage + i)) {
/* Buttons are the ones that appear multiple times, will handle them properly
For now, let's just aggregate the length and combine them into one :) */
if (map->element->size) {
map->element->size++;
continue;
}
/* Store the found element's attributes */
map->element->offset = offset_in_bits;
map->element->size = globals[RI_GLOBAL_REPORT_SIZE].val;
map->element->min = to_signed(&globals[RI_GLOBAL_LOGICAL_MIN]);
map->element->max = to_signed(&globals[RI_GLOBAL_LOGICAL_MAX]);
}
};
/* Iterate <count> times and increase offset by <size> amount, moving by <count> x <size> bits */
offset_in_bits += globals[RI_GLOBAL_REPORT_SIZE].val;
case RI_TYPE_LOCAL:
handle_local_item(&parser, &header, data);
break;
}
/* Advance the usage array pointer by global report count and reset the count variable */
p_usage += globals[RI_GLOBAL_REPORT_COUNT].val;
usage_count = 0;
}
break;
case RI_TYPE_GLOBAL:
/* There are just 16 possible tags, store any one that comes along to an array instead of doing
switch and 16 cases */
globals[header.tag].val = data;
globals[header.tag].hdr = header;
if (header.tag == RI_GLOBAL_REPORT_ID) {
/* Important to track, if report IDs are used reports are preceded/offset by a 1-byte ID value */
if(g_usage == HID_USAGE_DESKTOP_MOUSE)
mouse->report_id = data;
mouse->uses_report_id = true;
}
break;
case RI_TYPE_LOCAL:
if (header.tag == RI_LOCAL_USAGE) {
/* If we are not within a collection, the usage tag applies to the entire section */
if (IS_BLOCK_END)
g_usage = data;
else
*(p_usage + usage_count++) = data;
}
break;
/* Move to the next position and decrement size by header length + data length */
report += header.size;
desc_len -= header.size + 1;
}
/* If header specified some non-zero length data, move by that much to get to the new byte
we should interpret as a header element */
report += header.size;
len -= header.size;
}
return 0;
return 0;
}

View File

@ -18,9 +18,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "main.h"
#define MAX_REPORTS 32
/* Counts how many collection starts and ends we've seen, when they equalize
/* Counts how many collection starts and ends we've seen, when they equalize
(and not zero), we are at the end of a block */
typedef struct {
uint8_t start;
@ -52,8 +55,8 @@ typedef struct {
/* Describes where can we find a value in a HID report */
typedef struct {
uint16_t offset; // In bits
uint8_t size; // In bits
uint16_t offset; // In bits
uint8_t size; // In bits
int32_t min;
int32_t max;
} report_val_t;
@ -61,15 +64,14 @@ typedef struct {
/* Defines information about HID report format for the mouse. */
typedef struct {
report_val_t buttons;
report_val_t move_x;
report_val_t move_y;
report_val_t wheel;
bool uses_report_id;
uint8_t report_id;
uint8_t protocol;
bool uses_report_id;
} mouse_t;
/* For each element type we're interested in there is an entry
@ -79,5 +81,18 @@ typedef struct {
uint8_t report_usage;
uint8_t usage_page;
uint8_t usage;
report_val_t* element;
report_val_t *element;
} usage_map_t;
typedef struct {
uint8_t usage_count;
uint8_t global_usage;
uint32_t offset_in_bits;
uint8_t usages[64];
uint8_t *p_usage;
collection_t collection;
usage_map_t *map;
globals_t globals[16]; /* as tag is 4 bits, there can be 16 different tags in global header type */
} parser_state_t;

View File

@ -18,63 +18,114 @@
#include "main.h"
/* ==================================================== *
* Hotkeys to trigger actions via the keyboard
* Hotkeys to trigger actions via the keyboard.
* ==================================================== */
hotkey_combo_t hotkeys[] = {
/* Main keyboard switching hotkey */
{.modifier = 0,
.keys = {HOTKEY_TOGGLE},
.key_count = 1,
{.modifier = 0,
.keys = {HOTKEY_TOGGLE},
.key_count = 1,
.pass_to_os = false,
.action_handler = &output_toggle_hotkey_handler},
/* Holding down right ALT slows the mouse down */
{.modifier = KEYBOARD_MODIFIER_RIGHTALT,
.keys = {},
.key_count = 0,
.pass_to_os = true,
/* Pressing right ALT + right CTRL toggles the slow mouse mode */
{.modifier = KEYBOARD_MODIFIER_RIGHTALT | KEYBOARD_MODIFIER_RIGHTCTRL,
.keys = {},
.key_count = 0,
.pass_to_os = true,
.acknowledge = true,
.action_handler = &mouse_zoom_hotkey_handler},
/* Switch lock */
{.modifier = KEYBOARD_MODIFIER_RIGHTCTRL,
.keys = {HID_KEY_L},
.key_count = 1,
.acknowledge = true,
{.modifier = KEYBOARD_MODIFIER_RIGHTCTRL,
.keys = {HID_KEY_L},
.key_count = 1,
.acknowledge = true,
.action_handler = &switchlock_hotkey_handler},
/* Erase stored config */
{.modifier = KEYBOARD_MODIFIER_RIGHTSHIFT,
.keys = {HID_KEY_F12, HID_KEY_D},
.key_count = 2,
.acknowledge = true,
{.modifier = KEYBOARD_MODIFIER_RIGHTSHIFT,
.keys = {HID_KEY_F12, HID_KEY_D},
.key_count = 2,
.acknowledge = true,
.action_handler = &wipe_config_hotkey_handler},
/* Toggle screensaver function */
{.modifier = KEYBOARD_MODIFIER_RIGHTSHIFT,
.keys = {HID_KEY_F12, HID_KEY_S},
.key_count = 2,
.acknowledge = true,
.action_handler = &screensaver_hotkey_handler},
/* Record switch y coordinate */
{.modifier = KEYBOARD_MODIFIER_RIGHTSHIFT,
.keys = {HID_KEY_F12, HID_KEY_Y},
.key_count = 2,
.acknowledge = true,
{.modifier = KEYBOARD_MODIFIER_RIGHTSHIFT,
.keys = {HID_KEY_F12, HID_KEY_Y},
.key_count = 2,
.acknowledge = true,
.action_handler = &screen_border_hotkey_handler},
/* Hold down left shift + right shift + F12 + A ==> firmware upgrade mode for board A (kbd) */
{.modifier = KEYBOARD_MODIFIER_RIGHTSHIFT | KEYBOARD_MODIFIER_LEFTSHIFT,
.keys = {HID_KEY_F12, HID_KEY_A},
.key_count = 2,
.acknowledge = true,
{.modifier = KEYBOARD_MODIFIER_RIGHTSHIFT | KEYBOARD_MODIFIER_LEFTSHIFT,
.keys = {HID_KEY_F12, HID_KEY_A},
.key_count = 2,
.acknowledge = true,
.action_handler = &fw_upgrade_hotkey_handler_A},
/* Hold down left shift + right shift + F12 + B ==> firmware upgrade mode for board B (mouse) */
{.modifier = KEYBOARD_MODIFIER_RIGHTSHIFT | KEYBOARD_MODIFIER_LEFTSHIFT,
.keys = {HID_KEY_F12, HID_KEY_B},
.key_count = 2,
.acknowledge = true,
{.modifier = KEYBOARD_MODIFIER_RIGHTSHIFT | KEYBOARD_MODIFIER_LEFTSHIFT,
.keys = {HID_KEY_F12, HID_KEY_B},
.key_count = 2,
.acknowledge = true,
.action_handler = &fw_upgrade_hotkey_handler_B}};
/* ============================================================ *
* Detect if any hotkeys were pressed
* ============================================================ */
/* Tries to find if the keyboard report contains key, returns true/false */
bool key_in_report(uint8_t key, const hid_keyboard_report_t *report) {
for (int j = 0; j < KEYS_IN_USB_REPORT; j++) {
if (key == report->keycode[j]) {
return true;
}
}
return false;
}
/* Check if the current report matches a specific hotkey passed on */
bool check_specific_hotkey(hotkey_combo_t keypress, const hid_keyboard_report_t *report) {
/* We expect all modifiers specified to be detected in the report */
if (keypress.modifier != (report->modifier & keypress.modifier))
return false;
for (int n = 0; n < keypress.key_count; n++) {
if (!key_in_report(keypress.keys[n], report)) {
return false;
}
}
/* Getting here means all of the keys were found. */
return true;
}
/* Go through the list of hotkeys, check if any of them match. */
hotkey_combo_t *check_all_hotkeys(hid_keyboard_report_t *report, device_t *state) {
for (int n = 0; n < ARRAY_SIZE(hotkeys); n++) {
if (check_specific_hotkey(hotkeys[n], report)) {
return &hotkeys[n];
}
}
return NULL;
}
/* ==================================================== *
* Keyboard Queue Section
* ==================================================== */
void process_kbd_queue_task(device_state_t* state) {
void process_kbd_queue_task(device_t *state) {
hid_keyboard_report_t report;
/* If we're not connected, we have nowhere to send reports to. */
@ -93,7 +144,7 @@ void process_kbd_queue_task(device_state_t* state) {
queue_try_remove(&state->kbd_queue, &report);
}
void queue_kbd_report(hid_keyboard_report_t* report, device_state_t* state) {
void queue_kbd_report(hid_keyboard_report_t *report, device_t *state) {
/* It wouldn't be fun to queue up a bunch of messages and then dump them all on host */
if (!state->tud_connected)
return;
@ -101,18 +152,18 @@ void queue_kbd_report(hid_keyboard_report_t* report, device_state_t* state) {
queue_try_add(&state->kbd_queue, report);
}
void stop_pressing_any_keys(device_state_t* state) {
void release_all_keys(device_t *state) {
static hid_keyboard_report_t no_keys_pressed_report = {0, 0, {0}};
queue_try_add(&state->kbd_queue, &no_keys_pressed_report);
}
/* If keys need to go locally, queue packet to kbd queue, else send them through UART */
void send_key(hid_keyboard_report_t* report, device_state_t* state) {
if (state->active_output == BOARD_ROLE) {
void send_key(hid_keyboard_report_t *report, device_t *state) {
if (CURRENT_BOARD_IS_ACTIVE_OUTPUT) {
queue_kbd_report(report, state);
state->last_activity[BOARD_ROLE] = time_us_64();
} else {
send_packet((uint8_t*)report, KEYBOARD_REPORT_MSG, KBD_REPORT_LENGTH);
send_packet((uint8_t *)report, KEYBOARD_REPORT_MSG, KBD_REPORT_LENGTH);
}
}
@ -120,81 +171,30 @@ void send_key(hid_keyboard_report_t* report, device_state_t* state) {
* Parse and interpret the keys pressed on the keyboard
* ==================================================== */
bool no_keys_are_pressed(const hid_keyboard_report_t* report) {
if (report->modifier != 0)
return false;
for (int n = 0; n < KEYS_IN_USB_REPORT; n++) {
if (report->keycode[n] != 0)
return false;
}
return true;
}
hotkey_combo_t* check_hotkeys(hid_keyboard_report_t* report, int length, device_state_t* state) {
/* Go through the list of hotkeys, check if any are pressed, then execute their handler */
for (int n = 0; n < sizeof(hotkeys) / sizeof(hotkeys[0]); n++) {
if (is_key_pressed(hotkeys[n], report)) {
return &hotkeys[n];
}
}
return NULL;
}
void process_keyboard_report(uint8_t* raw_report, int length, device_state_t* state) {
hid_keyboard_report_t* keyboard_report = (hid_keyboard_report_t*)raw_report;
hotkey_combo_t* hotkey = NULL;
void process_keyboard_report(uint8_t *raw_report, int length, device_t *state) {
hid_keyboard_report_t *keyboard_report = (hid_keyboard_report_t *)raw_report;
hotkey_combo_t *hotkey = NULL;
if (length < KBD_REPORT_LENGTH)
return;
/* If no keys are pressed anymore, take care of checking and deactivating stuff */
if(no_keys_are_pressed(keyboard_report))
all_keys_released_handler(state);
else
/* Check if it was a hotkey, makes sense only if a key is pressed */
hotkey = check_hotkeys(keyboard_report, length, state);
/* Check if any hotkey was pressed */
hotkey = check_all_hotkeys(keyboard_report, state);
/* ... and take appropriate action */
if(hotkey != NULL) {
if (hotkey != NULL) {
/* Provide visual feedback we received the action */
if (hotkey->acknowledge)
blink_led(state);
/* Execute the corresponding handler */
hotkey->action_handler(state);
/* And pass the key to the output PC if configured to do so. */
if (!hotkey->pass_to_os)
return;
}
/* This method will decide if the key gets queued locally or sent through UART */
send_key(keyboard_report, state);
}
/* ============================================================ *
* Check if a specific key combination is present in the report
* ============================================================ */
bool is_key_pressed(hotkey_combo_t keypress, const hid_keyboard_report_t* report) {
int matches = 0;
/* We expect all modifiers specified to be detected in the report */
if (keypress.modifier != (report->modifier & keypress.modifier))
return false;
for (int n = 0; n < keypress.key_count; n++) {
for (int j = 0; j < KEYS_IN_USB_REPORT; j++) {
if (keypress.keys[n] == report->keycode[j]) {
matches++;
break;
}
}
/* If any of the keys are not found, we can bail out early. */
if (matches < n + 1) {
return false;
}
}
/* Getting here means all of the keys were found. */
return true;
}

View File

@ -21,35 +21,40 @@
* ========== Update pico and keyboard LEDs ========== *
* ==================================================== */
void set_keyboard_leds(uint8_t requested_led_state, device_state_t* state) {
void set_keyboard_leds(uint8_t requested_led_state, device_t *state) {
static uint8_t new_led_value;
new_led_value = requested_led_state;
if (state->keyboard_connected) {
tuh_hid_set_report(state->kbd_dev_addr, state->kbd_instance, 0, HID_REPORT_TYPE_OUTPUT,
&new_led_value, sizeof(uint8_t));
tuh_hid_set_report(state->kbd_dev_addr,
state->kbd_instance,
0,
HID_REPORT_TYPE_OUTPUT,
&new_led_value,
sizeof(uint8_t));
}
}
void restore_leds(device_state_t* state) {
void restore_leds(device_t *state) {
/* Light up on-board LED if current board is active output */
state->onboard_led_state = (state->active_output == BOARD_ROLE);
gpio_put(GPIO_LED_PIN, state->onboard_led_state);
/* Light up appropriate keyboard leds (if it's connected locally) */
if (state->keyboard_connected) {
uint8_t leds = state->keyboard_leds[state->active_output];
set_keyboard_leds(leds, state);
}
}
void blink_led(device_state_t* state) {
void blink_led(device_t *state) {
/* Since LEDs might be ON previously, we go OFF, ON, OFF, ON, OFF */
state->blinks_left = 5;
state->blinks_left = 5;
state->last_led_change = time_us_32();
}
void led_blinking_task(device_state_t* state) {
/* 80 ms off, 80 ms on */
const int blink_interval_us = 80000;
void led_blinking_task(device_t *state) {
const int blink_interval_us = 80000; /* 80 ms off, 80 ms on */
static uint8_t leds;
/* If there is no more blinking to be done, exit immediately */
@ -77,4 +82,4 @@ void led_blinking_task(device_state_t* state) {
/* Restore LEDs in the last pass */
if (state->blinks_left == 0)
restore_leds(state);
}
}

View File

@ -1,24 +1,25 @@
/*
/*
* This file is part of DeskHop (https://github.com/hrvach/deskhop).
* Copyright (c) 2024 Hrvoje Cavrak
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "main.h"
/********* Global Variable **********/
device_state_t global_state = {0};
device_t global_state = {0};
device_t *device = &global_state;
/**================================================== *
* ============== Main Program Loops ============== *
@ -29,25 +30,24 @@ void main(void) {
sleep_ms(10);
// Initial board setup
initial_setup();
initial_setup(device);
// Initial state, A is the default output
switch_output(ACTIVE_OUTPUT_A);
switch_output(device, OUTPUT_A);
while (true) {
// USB device task, needs to run as often as possible
tud_task();
// Verify core1 is still running and if so, reset watchdog timer
kick_watchdog();
kick_watchdog(device);
// Check if there were any keypresses and send them
process_kbd_queue_task(&global_state);
process_kbd_queue_task(device);
// Check if there were any mouse movements and send them
process_mouse_queue_task(&global_state);
process_mouse_queue_task(device);
}
}
void core1_main() {
@ -55,17 +55,20 @@ void core1_main() {
while (true) {
// Update the timestamp, so core0 can figure out if we're dead
global_state.core1_last_loop_pass = time_us_64();
device->core1_last_loop_pass = time_us_64();
// USB host task, needs to run as often as possible
if (tuh_inited())
tuh_task();
// Receives data over serial from the other board
receive_char(&in_packet, &global_state);
receive_char(&in_packet, device);
// Check if LED needs blinking
led_blinking_task(&global_state);
led_blinking_task(device);
// Mouse screensaver task
screensaver_task(device);
}
}

View File

@ -17,47 +17,48 @@
#pragma once
#include "pico/stdlib.h"
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "hardware/flash.h"
#include "hardware/sync.h"
#include "hardware/watchdog.h"
#include "hid_parser.h"
#include "pico/bootrom.h"
#include "pico/multicore.h"
#include "pico/stdlib.h"
#include "pico/util/queue.h"
#include "pio_usb.h"
#include "tusb.h"
#include "usb_descriptors.h"
#include "user_config.h"
#include <hardware/flash.h>
#include <hardware/sync.h>
#include <hardware/watchdog.h>
#include <pico/bootrom.h>
#include <pico/multicore.h>
#include <pico/stdlib.h>
#include <pico/util/queue.h>
/********* Misc definitions for better readability **********/
#define PICO_A 0
#define PICO_B 1
#define ACTIVE_OUTPUT_A 0
#define ACTIVE_OUTPUT_B 1
#define OUTPUT_A 0
#define OUTPUT_B 1
#define ENABLE 1
#define ENABLE 1
#define DISABLE 0
#define DIRECTION_X 0
#define DIRECTION_Y 1
#define MAX_REPORT_ITEMS 16
#define MAX_REPORT_ITEMS 16
#define MOUSE_BOOT_REPORT_LEN 4
#define NUM_SCREENS 2 // Will be more in the future
#define NUM_SCREENS 2 // Will be more in the future
#define MOUSE_ZOOM_SCALING_FACTOR 2
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
#define CURRENT_BOARD_IS_ACTIVE_OUTPUT (global_state.active_output == BOARD_ROLE)
/********* Pinout definitions **********/
#define PIO_USB_DP_PIN 14 // D+ is pin 14, D- is pin 15
#define GPIO_LED_PIN 25 // LED is connected to pin 25 on a PICO
#define PIO_USB_DP_PIN 14 // D+ is pin 14, D- is pin 15
#define GPIO_LED_PIN 25 // LED is connected to pin 25 on a PICO
#if BOARD_ROLE == PICO_B
#define SERIAL_TX_PIN 16
@ -68,17 +69,17 @@
#endif
/********* Serial port definitions **********/
#define SERIAL_UART uart0
#define SERIAL_UART uart0
#define SERIAL_BAUDRATE 3686400
#define SERIAL_DATA_BITS 8
#define SERIAL_STOP_BITS 1
#define SERIAL_PARITY UART_PARITY_NONE
#define SERIAL_PARITY UART_PARITY_NONE
/********* Watchdog definitions **********/
#define WATCHDOG_TIMEOUT 500 // In milliseconds => needs to be reset every 500 ms
#define WATCHDOG_PAUSE_ON_DEBUG 1 // When using a debugger, disable watchdog
#define CORE1_HANG_TIMEOUT_US 500000 // In microseconds, wait up to 0.5s to declare core1 dead
#define WATCHDOG_TIMEOUT 500 // In milliseconds => needs to be reset every 500 ms
#define WATCHDOG_PAUSE_ON_DEBUG 1 // When using a debugger, disable watchdog
#define CORE1_HANG_TIMEOUT_US 500000 // In microseconds, wait up to 0.5s to declare core1 dead
/********* Protocol definitions *********
*
@ -91,15 +92,17 @@
*/
enum packet_type_e {
KEYBOARD_REPORT_MSG = 1,
MOUSE_REPORT_MSG = 2,
OUTPUT_SELECT_MSG = 3,
KEYBOARD_REPORT_MSG = 1,
MOUSE_REPORT_MSG = 2,
OUTPUT_SELECT_MSG = 3,
FIRMWARE_UPGRADE_MSG = 4,
MOUSE_ZOOM_MSG = 5,
KBD_SET_REPORT_MSG = 6,
SWITCH_LOCK_MSG = 7,
SYNC_BORDERS_MSG = 8,
FLASH_LED_MSG = 9,
MOUSE_ZOOM_MSG = 5,
KBD_SET_REPORT_MSG = 6,
SWITCH_LOCK_MSG = 7,
SYNC_BORDERS_MSG = 8,
FLASH_LED_MSG = 9,
SCREENSAVER_MSG = 10,
WIPE_CONFIG_MSG = 11,
};
/*
@ -112,45 +115,48 @@ enum packet_type_e {
/* Data structure defining packets of information transferred */
typedef struct {
uint8_t type; // Enum field describing the type of packet
uint8_t data[8]; // Data goes here (type + payload + checksum)
uint8_t checksum; // Checksum, a simple XOR-based one
uint8_t type; // Enum field describing the type of packet
uint8_t data[8]; // Data goes here (type + payload + checksum)
uint8_t checksum; // Checksum, a simple XOR-based one
} uart_packet_t;
/********* Packet parameters **********/
#define START1 0xAA
#define START2 0x55
#define START1 0xAA
#define START2 0x55
#define START_LENGTH 2
#define TYPE_LENGTH 1
#define PACKET_DATA_LENGTH 8 // For simplicity, all packet types are the same length
#define CHECKSUM_LENGTH 1
#define TYPE_LENGTH 1
#define PACKET_DATA_LENGTH 8 // For simplicity, all packet types are the same length
#define CHECKSUM_LENGTH 1
#define PACKET_LENGTH (TYPE_LENGTH + PACKET_DATA_LENGTH + CHECKSUM_LENGTH)
#define PACKET_LENGTH (TYPE_LENGTH + PACKET_DATA_LENGTH + CHECKSUM_LENGTH)
#define RAW_PACKET_LENGTH (START_LENGTH + PACKET_LENGTH)
#define KBD_QUEUE_LENGTH 128
#define KBD_QUEUE_LENGTH 128
#define MOUSE_QUEUE_LENGTH 2048
#define KEYS_IN_USB_REPORT 6
#define KBD_REPORT_LENGTH 8
#define KEYS_IN_USB_REPORT 6
#define KBD_REPORT_LENGTH 8
#define MOUSE_REPORT_LENGTH 7
/********* Screen **********/
#define MIN_SCREEN_COORD 0
#define MAX_SCREEN_COORD 32767
/********* Configuration storage definitions **********/
#define CURRENT_CONFIG_VERSION 2
typedef struct {
int top; // When jumping from a smaller to a bigger screen, go to THIS top height
int bottom; // When jumping from a smaller to a bigger screen, go to THIS bottom
// height
int top; // When jumping from a smaller to a bigger screen, go to THIS top height
int bottom; // When jumping from a smaller to a bigger screen, go to THIS bottom
// height
} border_size_t;
/* Define output parameters */
typedef struct {
int number; // Number of this output (e.g. ACTIVE_OUTPUT_A = 0 etc)
int number; // Number of this output (e.g. OUTPUT_A = 0 etc)
int screen_count; // How many monitors per output (e.g. Output A is Windows with 3 monitors)
int screen_index; // Current active screen
int speed_x; // Mouse speed per output, in direction X
@ -161,8 +167,11 @@ typedef struct {
/* Data structure defining how configuration is stored */
typedef struct {
uint32_t magic_header;
uint32_t version;
uint8_t force_mouse_boot_mode;
output_t output[NUM_SCREENS];
uint8_t screensaver_enabled;
// Keep checksum at the end of the struct
uint32_t checksum;
} config_t;
@ -175,13 +184,18 @@ extern config_t ADDR_CONFIG[];
typedef void (*action_handler_t)();
typedef struct { // Maps message type -> message handler function
enum packet_type_e type;
action_handler_t handler;
} uart_handler_t;
typedef struct {
uint8_t modifier; // Which modifier is pressed
uint8_t keys[6]; // Which keys need to be pressed
uint8_t key_count; // How many keys are pressed
action_handler_t action_handler; // What to execute when the key combination is detected
bool pass_to_os; // True if we are to pass the key to the OS too
bool acknowledge; // True if we are to notify the user about registering keypress
uint8_t modifier; // Which modifier is pressed
uint8_t keys[6]; // Which keys need to be pressed
uint8_t key_count; // How many keys are pressed
action_handler_t action_handler; // What to execute when the key combination is detected
bool pass_to_os; // True if we are to pass the key to the OS too
bool acknowledge; // True if we are to notify the user about registering keypress
} hotkey_combo_t;
typedef struct TU_ATTR_PACKED {
@ -190,120 +204,118 @@ typedef struct TU_ATTR_PACKED {
int16_t y;
int8_t wheel;
int8_t pan;
} hid_abs_mouse_report_t;
} mouse_abs_report_t;
typedef enum { IDLE, READING_PACKET, PROCESSING_PACKET } receiver_state_t;
typedef struct {
uint8_t kbd_dev_addr; // Address of the keyboard device
uint8_t kbd_instance; // Keyboard instance (d'uh - isn't this a useless comment)
uint8_t kbd_dev_addr; // Address of the keyboard device
uint8_t kbd_instance; // Keyboard instance (d'uh - isn't this a useless comment)
uint8_t keyboard_leds[NUM_SCREENS]; // State of keyboard LEDs (index 0 = A, index 1 = B)
uint64_t last_activity[NUM_SCREENS]; // Timestamp of the last input activity (-||-)
receiver_state_t receiver_state; // Storing the state for the simple receiver state machine
uint8_t keyboard_leds[NUM_SCREENS]; // State of keyboard LEDs (index 0 = A, index 1 = B)
uint64_t last_activity[NUM_SCREENS]; // Timestamp of the last input activity (-||-)
receiver_state_t receiver_state; // Storing the state for the simple receiver state machine
uint64_t core1_last_loop_pass; // Timestamp of last core1 loop execution
uint8_t active_output; // Currently selected output (0 = A, 1 = B)
uint64_t core1_last_loop_pass; // Timestamp of last core1 loop execution
uint8_t active_output; // Currently selected output (0 = A, 1 = B)
int16_t mouse_x; // Store and update the location of our mouse pointer
int16_t mouse_x; // Store and update the location of our mouse pointer
int16_t mouse_y;
config_t config; // Device configuration, loaded from flash or defaults used
mouse_t mouse_dev; // Mouse device specifics, e.g. stores locations for keys in report
queue_t kbd_queue; // Queue that stores keyboard reports
queue_t mouse_queue; // Queue that stores mouse reports
config_t config; // Device configuration, loaded from flash or defaults used
mouse_t mouse_dev; // Mouse device specifics, e.g. stores locations for keys in report
queue_t kbd_queue; // Queue that stores keyboard reports
queue_t mouse_queue; // Queue that stores mouse reports
/* Connection status flags */
bool tud_connected; // True when TinyUSB device successfully connects
bool keyboard_connected; // True when our keyboard is connected locally
bool mouse_connected; // True when a mouse is connected locally
bool tud_connected; // True when TinyUSB device successfully connects
bool keyboard_connected; // True when our keyboard is connected locally
bool mouse_connected; // True when a mouse is connected locally
/* Feature flags */
bool mouse_zoom; // True when "mouse zoom" is enabled
bool switch_lock; // True when device is prevented from switching
bool onboard_led_state; // True when LED is ON
bool mouse_zoom; // True when "mouse zoom" is enabled
bool switch_lock; // True when device is prevented from switching
bool onboard_led_state; // True when LED is ON
/* Onboard LED blinky (provide feedback when e.g. mouse connected) */
int32_t blinks_left; // How many blink transitions are left
int32_t last_led_change; // Timestamp of the last time led state transitioned
int32_t blinks_left; // How many blink transitions are left
int32_t last_led_change; // Timestamp of the last time led state transitioned
} device_state_t;
} device_t;
/********* Setup **********/
void initial_setup(void);
void initial_setup(device_t *);
void serial_init(void);
void core1_main(void);
/********* Keyboard **********/
bool is_key_pressed(hotkey_combo_t, const hid_keyboard_report_t*);
void process_keyboard_report(uint8_t*, int, device_state_t*);
void stop_pressing_any_keys(device_state_t*);
void queue_kbd_report(hid_keyboard_report_t*, device_state_t*);
void process_kbd_queue_task(device_state_t*);
void send_key(hid_keyboard_report_t*, device_state_t*);
bool check_specific_hotkey(hotkey_combo_t, const hid_keyboard_report_t *);
void process_keyboard_report(uint8_t *, int, device_t *);
void release_all_keys(device_t *);
void queue_kbd_report(hid_keyboard_report_t *, device_t *);
void process_kbd_queue_task(device_t *);
void send_key(hid_keyboard_report_t *, device_t *);
/********* Mouse **********/
bool tud_hid_abs_mouse_report(uint8_t report_id,
uint8_t buttons,
int16_t x,
int16_t y,
int8_t vertical,
int8_t horizontal);
bool tud_hid_abs_mouse_report(
uint8_t report_id, uint8_t buttons, int16_t x, int16_t y, int8_t vertical, int8_t horizontal);
void process_mouse_report(uint8_t*, int, device_state_t*);
uint8_t parse_report_descriptor(mouse_t* mouse,
uint8_t arr_count,
uint8_t const* desc_report,
uint16_t desc_len);
int32_t get_report_value(uint8_t* report, report_val_t* val);
void process_mouse_queue_task(device_state_t*);
void queue_mouse_report(hid_abs_mouse_report_t*, device_state_t*);
void send_mouse(hid_abs_mouse_report_t*, device_state_t*);
void process_mouse_report(uint8_t *, int, device_t *);
uint8_t
parse_report_descriptor(mouse_t *mouse, uint8_t arr_count, uint8_t const *desc_report, uint16_t desc_len);
int32_t get_report_value(uint8_t *report, report_val_t *val);
void process_mouse_queue_task(device_t *);
void queue_mouse_report(mouse_abs_report_t *, device_t *);
void output_mouse_report(mouse_abs_report_t *, device_t *);
/********* UART **********/
void receive_char(uart_packet_t*, device_state_t*);
void send_packet(const uint8_t*, enum packet_type_e, int);
void receive_char(uart_packet_t *, device_t *);
void send_packet(const uint8_t *, enum packet_type_e, int);
void send_value(const uint8_t, enum packet_type_e);
/********* LEDs **********/
void restore_leds(device_state_t*);
void blink_led(device_state_t*);
void led_blinking_task(device_state_t*);
void restore_leds(device_t *);
void blink_led(device_t *);
void led_blinking_task(device_t *);
/********* Checksum **********/
uint8_t calc_checksum(const uint8_t*, int);
bool verify_checksum(const uart_packet_t*);
uint8_t calc_checksum(const uint8_t *, int);
bool verify_checksum(const uart_packet_t *);
/********* Watchdog **********/
void kick_watchdog(void);
void kick_watchdog(device_t *);
/********* Configuration **********/
void load_config(void);
void save_config(void);
void load_config(device_t *);
void save_config(device_t *);
void wipe_config(void);
/********* Misc **********/
void screensaver_task(device_t *);
/********* Handlers **********/
void output_toggle_hotkey_handler(device_state_t*);
void screen_border_hotkey_handler(device_state_t*);
void fw_upgrade_hotkey_handler_A(device_state_t*);
void fw_upgrade_hotkey_handler_B(device_state_t*);
void mouse_zoom_hotkey_handler(device_state_t*);
void all_keys_released_handler(device_state_t*);
void switchlock_hotkey_handler(device_state_t*);
void wipe_config_hotkey_handler(device_state_t*);
void output_toggle_hotkey_handler(device_t *);
void screen_border_hotkey_handler(device_t *);
void fw_upgrade_hotkey_handler_A(device_t *);
void fw_upgrade_hotkey_handler_B(device_t *);
void mouse_zoom_hotkey_handler(device_t *);
void all_keys_released_handler(device_t *);
void switchlock_hotkey_handler(device_t *);
void wipe_config_hotkey_handler(device_t *);
void screensaver_hotkey_handler(device_t *);
void handle_keyboard_uart_msg(uart_packet_t*, device_state_t*);
void handle_mouse_abs_uart_msg(uart_packet_t*, device_state_t*);
void handle_output_select_msg(uart_packet_t*, device_state_t*);
void handle_mouse_zoom_msg(uart_packet_t*, device_state_t*);
void handle_set_report_msg(uart_packet_t*, device_state_t*);
void handle_switch_lock_msg(uart_packet_t*, device_state_t*);
void handle_sync_borders_msg(uart_packet_t*, device_state_t*);
void handle_flash_led_msg(uart_packet_t*, device_state_t*);
void handle_fw_upgrade_msg(void);
void handle_keyboard_uart_msg(uart_packet_t *, device_t *);
void handle_mouse_abs_uart_msg(uart_packet_t *, device_t *);
void handle_output_select_msg(uart_packet_t *, device_t *);
void handle_mouse_zoom_msg(uart_packet_t *, device_t *);
void handle_set_report_msg(uart_packet_t *, device_t *);
void handle_switch_lock_msg(uart_packet_t *, device_t *);
void handle_sync_borders_msg(uart_packet_t *, device_t *);
void handle_flash_led_msg(uart_packet_t *, device_t *);
void handle_fw_upgrade_msg(uart_packet_t *, device_t *);
void handle_wipe_config_msg(uart_packet_t *, device_t *);
void handle_screensaver_msg(uart_packet_t *, device_t *);
void switch_output(uint8_t);
void switch_output(device_t *, uint8_t);
/********* Global variables (don't judge) **********/
extern device_state_t global_state;
extern device_t global_state;

View File

@ -17,205 +17,187 @@
#include "main.h"
int get_mouse_offset(int32_t movement, const int direction) {
int offset = 0;
output_t *active_output =
&global_state.config.output[global_state.active_output];
/* Move mouse coordinate 'position' by 'offset', but don't fall off the screen */
int32_t move_and_keep_on_screen(int position, int offset) {
/* Lowest we can go is 0 */
if (position + offset < MIN_SCREEN_COORD)
return MIN_SCREEN_COORD;
if (direction == DIRECTION_X)
offset = movement * active_output->speed_x;
else
offset = movement * active_output->speed_y;
/* Highest we can go is MAX_SCREEN_COORD */
else if (position + offset > MAX_SCREEN_COORD)
return MAX_SCREEN_COORD;
/* Holding a special hotkey enables mouse to slow down as much as possible
when you need that extra precision */
if (global_state.mouse_zoom)
offset = offset >> 2;
return offset;
/* We're still on screen, all good */
return position + offset;
}
void keep_cursor_on_screen(int16_t *position, const int32_t *movement,
const int direction) {
int16_t offset = get_mouse_offset(*movement, direction);
void update_mouse_position(device_t *state, mouse_values_t *values) {
output_t *current = &state->config.output[state->active_output];
uint8_t reduce_speed = 0;
/* Lowest we can go is 0 */
if (*position + offset < 0)
*position = 0;
/* Check if we are configured to move slowly */
if (state->mouse_zoom)
reduce_speed = MOUSE_ZOOM_SCALING_FACTOR;
/* Highest we can go is MAX_SCREEN_COORD */
else if (*position + offset > MAX_SCREEN_COORD)
*position = MAX_SCREEN_COORD;
/* Calculate movement */
int offset_x = values->move_x * (current->speed_x >> reduce_speed);
int offset_y = values->move_y * (current->speed_y >> reduce_speed);
/* We're still on screen, all good */
else
*position += offset;
/* Update movement */
state->mouse_x = move_and_keep_on_screen(state->mouse_x, offset_x);
state->mouse_y = move_and_keep_on_screen(state->mouse_y, offset_y);
}
/* If mouse needs to go locally, queue packet to mouse queue, else send them
* through UART */
void send_mouse(hid_abs_mouse_report_t *report, device_state_t *state) {
if (state->active_output == BOARD_ROLE) {
queue_mouse_report(report, state);
state->last_activity[BOARD_ROLE] = time_us_64();
} else {
send_packet((uint8_t *)report, MOUSE_REPORT_MSG, MOUSE_REPORT_LENGTH);
}
/* If we are active output, queue packet to mouse queue, else send them through UART */
void output_mouse_report(mouse_abs_report_t *report, device_t *state) {
if (CURRENT_BOARD_IS_ACTIVE_OUTPUT) {
queue_mouse_report(report, state);
state->last_activity[BOARD_ROLE] = time_us_64();
} else {
send_packet((uint8_t *)report, MOUSE_REPORT_MSG, MOUSE_REPORT_LENGTH);
}
}
int16_t scale_y_coord(output_t *from, output_t *to, device_state_t *state) {
int size_to = to->border.bottom - to->border.top;
int size_from = from->border.bottom - from->border.top;
/* Calculate and return Y coordinate when moving from screen out_from to screen out_to */
int16_t scale_y_coordinate(int screen_from, int screen_to, device_t *state) {
output_t *from = &state->config.output[screen_from];
output_t *to = &state->config.output[screen_to];
/* If sizes match, there is nothing to do */
if (size_from == size_to)
return state->mouse_y;
int size_to = to->border.bottom - to->border.top;
int size_from = from->border.bottom - from->border.top;
/* If sizes match, there is nothing to do */
if (size_from == size_to)
return state->mouse_y;
/* Moving from smaller ==> bigger screen
y_a = top + (((bottom - top) * y_b) / HEIGHT) */
if (size_from > size_to) {
return to->border.top + ((size_to * state->mouse_y) / MAX_SCREEN_COORD);
}
/* Moving from bigger ==> smaller screen
y_b = ((y_a - top) * HEIGHT) / (bottom - top) */
/* Moving from bigger ==> smaller screen */
if (size_from < size_to) {
if (state->mouse_y < from->border.top)
return 0;
return MIN_SCREEN_COORD;
if (state->mouse_y > from->border.bottom)
return MAX_SCREEN_COORD;
return MAX_SCREEN_COORD;
/* y_b = ((y_a - top) * HEIGHT) / (bottom - top) */
return ((state->mouse_y - from->border.top) * MAX_SCREEN_COORD) /
size_from;
}
/* Moving from smaller ==> bigger screen,
y_a = top + (((bottom - top) * y_b) / HEIGHT) */
return to->border.top + ((size_to * state->mouse_y) / MAX_SCREEN_COORD);
return ((state->mouse_y - from->border.top) * MAX_SCREEN_COORD) / size_from;
}
void check_mouse_switch(const mouse_values_t *values, device_state_t *state) {
hid_abs_mouse_report_t report = {.y = 0, .x = MAX_SCREEN_COORD};
void switch_screen(device_t *state, int new_x, int output_from, int output_to) {
mouse_abs_report_t hidden_pointer = {.y = MIN_SCREEN_COORD, .x = MAX_SCREEN_COORD};
/* No switching allowed if explicitly disabled */
if (state->switch_lock)
return;
output_mouse_report(&hidden_pointer, state);
switch_output(state, output_to);
state->mouse_x = (output_to == OUTPUT_A) ? MIN_SCREEN_COORD : MAX_SCREEN_COORD;
state->mouse_y = scale_y_coordinate(output_from, output_to, state);
}
/* End of screen left switches screen A->B */
bool jump_from_A_to_B =
(state->mouse_x + values->move_x < -MOUSE_JUMP_THRESHOLD &&
state->active_output == ACTIVE_OUTPUT_A);
void check_screen_switch(const mouse_values_t *values, device_t *state) {
int new_x = state->mouse_x + values->move_x;
/* End of screen right switches screen B->A */
bool jump_from_B_to_A = (state->mouse_x + values->move_x >
MAX_SCREEN_COORD + MOUSE_JUMP_THRESHOLD &&
state->active_output == ACTIVE_OUTPUT_B);
/* No switching allowed if explicitly disabled */
if (state->switch_lock)
return;
if (jump_from_A_to_B || jump_from_B_to_A) {
/* Hide mouse pointer in the upper right corner on the system we are
switching FROM If the mouse is locally attached to the current board or
notify other board if not */
send_mouse(&report, state);
if (jump_from_A_to_B) {
switch_output(ACTIVE_OUTPUT_B);
state->mouse_x = MAX_SCREEN_COORD;
state->mouse_y = scale_y_coord(&state->config.output[ACTIVE_OUTPUT_A],
&state->config.output[ACTIVE_OUTPUT_B],
state);
} else {
switch_output(ACTIVE_OUTPUT_A);
state->mouse_x = 0;
state->mouse_y = scale_y_coord(&state->config.output[ACTIVE_OUTPUT_B],
&state->config.output[ACTIVE_OUTPUT_A],
state);
/* End of screen left switches screen A->B TODO: make configurable */
if (new_x < MIN_SCREEN_COORD - JUMP_THRESHOLD && state->active_output == OUTPUT_A) {
switch_screen(state, new_x, OUTPUT_A, OUTPUT_B);
}
/* End of screen right switches screen B->A TODO: make configurable */
else if (new_x > MAX_SCREEN_COORD + JUMP_THRESHOLD && state->active_output == OUTPUT_B) {
switch_screen(state, new_x, OUTPUT_B, OUTPUT_A);
}
}
}
void extract_values_report_protocol(uint8_t *report, device_state_t *state,
mouse_values_t *values) {
/* If Report ID is used, the report is prefixed by the report ID so we have to
* move by 1 byte */
if (state->mouse_dev.uses_report_id) {
/* Move past the ID to parse the report */
report++;
}
void extract_report_values(uint8_t *raw_report, device_t *state, mouse_values_t *values) {
/* Interpret values depending on the current protocol used. */
if (state->mouse_dev.protocol == HID_PROTOCOL_BOOT) {
hid_mouse_report_t *mouse_report = (hid_mouse_report_t *)raw_report;
values->move_x = get_report_value(report, &state->mouse_dev.move_x);
values->move_y = get_report_value(report, &state->mouse_dev.move_y);
values->wheel = get_report_value(report, &state->mouse_dev.wheel);
values->buttons = get_report_value(report, &state->mouse_dev.buttons);
values->move_x = mouse_report->x;
values->move_y = mouse_report->y;
values->wheel = mouse_report->wheel;
values->buttons = mouse_report->buttons;
return;
}
/* If HID Report ID is used, the report is prefixed by the report ID so we have to move by 1 byte */
if (state->mouse_dev.uses_report_id)
raw_report++;
values->move_x = get_report_value(raw_report, &state->mouse_dev.move_x);
values->move_y = get_report_value(raw_report, &state->mouse_dev.move_y);
values->wheel = get_report_value(raw_report, &state->mouse_dev.wheel);
values->buttons = get_report_value(raw_report, &state->mouse_dev.buttons);
}
void extract_values_boot_protocol(uint8_t *report, device_state_t *state,
mouse_values_t *values) {
hid_mouse_report_t *mouse_report = (hid_mouse_report_t *)report;
values->move_x = mouse_report->x;
values->move_y = mouse_report->y;
values->wheel = mouse_report->wheel;
values->buttons = mouse_report->buttons;
mouse_abs_report_t create_mouse_report(device_t *state, mouse_values_t *values) {
mouse_abs_report_t abs_mouse_report = {.buttons = values->buttons,
.x = state->mouse_x,
.y = state->mouse_y,
.wheel = values->wheel,
.pan = 0};
return abs_mouse_report;
}
void process_mouse_report(uint8_t *raw_report, int len, device_state_t *state) {
mouse_values_t values = {0};
void process_mouse_report(uint8_t *raw_report, int len, device_t *state) {
mouse_values_t values = {0};
/* Interpret values depending on the current protocol used */
if (state->mouse_dev.protocol == HID_PROTOCOL_BOOT)
extract_values_boot_protocol(raw_report, state, &values);
else
extract_values_report_protocol(raw_report, state, &values);
/* Interpret the mouse HID report, extract and save values we need. */
extract_report_values(raw_report, state, &values);
/* We need to enforce the cursor doesn't go off-screen, that would be bad. */
keep_cursor_on_screen(&state->mouse_x, &values.move_x, DIRECTION_X);
keep_cursor_on_screen(&state->mouse_y, &values.move_y, DIRECTION_Y);
/* Calculate and update mouse pointer movement. */
update_mouse_position(state, &values);
hid_abs_mouse_report_t abs_mouse_report = {.buttons = values.buttons,
.x = state->mouse_x,
.y = state->mouse_y,
.wheel = values.wheel,
.pan = 0};
/* Create the report for the output PC based on the updated values */
mouse_abs_report_t report = create_mouse_report(state, &values);
/* Move the mouse, depending where the output is supposed to go */
send_mouse(&abs_mouse_report, state);
/* Move the mouse, depending where the output is supposed to go */
output_mouse_report(&report, state);
/* We use the mouse to switch outputs, the logic is in check_mouse_switch() */
check_mouse_switch(&values, state);
/* We use the mouse to switch outputs, the logic is in check_screen_switch() */
check_screen_switch(&values, state);
}
/* ==================================================== *
* Mouse Queue Section
* ==================================================== */
void process_mouse_queue_task(device_state_t *state) {
hid_abs_mouse_report_t report = {0};
void process_mouse_queue_task(device_t *state) {
mouse_abs_report_t report = {0};
/* We need to be connected to the host to send messages */
if (!state->tud_connected)
return;
/* We need to be connected to the host to send messages */
if (!state->tud_connected)
return;
/* Peek first, if there is anything there... */
if (!queue_try_peek(&state->mouse_queue, &report))
return;
/* Peek first, if there is anything there... */
if (!queue_try_peek(&state->mouse_queue, &report))
return;
/* If we are suspended, let's wake the host up */
if (tud_suspended())
tud_remote_wakeup();
/* If we are suspended, let's wake the host up */
if (tud_suspended())
tud_remote_wakeup();
/* ... try sending it to the host, if it's successful */
bool succeeded =
tud_hid_abs_mouse_report(REPORT_ID_MOUSE, report.buttons, report.x,
report.y, report.wheel, report.pan);
/* ... try sending it to the host, if it's successful */
bool succeeded = tud_hid_abs_mouse_report(
REPORT_ID_MOUSE, report.buttons, report.x, report.y, report.wheel, report.pan);
/* ... then we can remove it from the queue */
if (succeeded)
queue_try_remove(&state->mouse_queue, &report);
/* ... then we can remove it from the queue */
if (succeeded)
queue_try_remove(&state->mouse_queue, &report);
}
void queue_mouse_report(hid_abs_mouse_report_t *report, device_state_t *state) {
/* It wouldn't be fun to queue up a bunch of messages and then dump them all
* on host */
if (!state->tud_connected)
return;
void queue_mouse_report(mouse_abs_report_t *report, device_t *state) {
/* It wouldn't be fun to queue up a bunch of messages and then dump them all on host */
if (!state->tud_connected)
return;
queue_try_add(&state->mouse_queue, report);
queue_try_add(&state->mouse_queue, report);
}

View File

@ -1,17 +1,17 @@
/*
/*
* This file is part of DeskHop (https://github.com/hrvach/deskhop).
* Copyright (c) 2024 Hrvoje Cavrak
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
@ -52,9 +52,9 @@ void serial_init() {
* ================================================== */
void pio_usb_host_config(void) {
/* tuh_configure() must be called before tuh_init() */
/* tuh_configure() must be called before tuh_init() */
static pio_usb_configuration_t config = PIO_USB_DEFAULT_CONFIG;
config.pin_dp = PIO_USB_DP_PIN_DEFAULT;
config.pin_dp = PIO_USB_DP_PIN_DEFAULT;
tuh_configure(BOARD_TUH_RHPORT, TUH_CFGID_RPI_PIO_USB_CONFIGURATION, &config);
/* Initialize and configure TinyUSB Host */
@ -65,12 +65,12 @@ void pio_usb_host_config(void) {
* Perform initial board/usb setup
* ================================================== */
void initial_setup(void) {
void initial_setup(device_t *state) {
/* PIO USB requires a clock multiple of 12 MHz, setting to 120 MHz */
set_sys_clock_khz(120000, true);
/* Search the persistent storage sector in flash for valid config or use defaults */
load_config();
load_config(state);
/* Init and enable the on-board LED GPIO as output */
gpio_init(GPIO_LED_PIN);
@ -80,9 +80,9 @@ void initial_setup(void) {
serial_init();
/* Initialize keyboard and mouse queues */
queue_init(&global_state.kbd_queue, sizeof(hid_keyboard_report_t), KBD_QUEUE_LENGTH);
queue_init(&global_state.mouse_queue, sizeof(hid_abs_mouse_report_t), MOUSE_QUEUE_LENGTH);
queue_init(&state->kbd_queue, sizeof(hid_keyboard_report_t), KBD_QUEUE_LENGTH);
queue_init(&state->mouse_queue, sizeof(mouse_abs_report_t), MOUSE_QUEUE_LENGTH);
/* Setup RP2040 Core 1 */
multicore_reset_core1();
multicore_launch_core1(core1_main);
@ -94,8 +94,8 @@ void initial_setup(void) {
pio_usb_host_config();
/* Update the core1 initial pass timestamp before enabling the watchdog */
global_state.core1_last_loop_pass = time_us_64();
state->core1_last_loop_pass = time_us_64();
/* Setup the watchdog so we reboot and recover from a crash */
watchdog_enable(WATCHDOG_TIMEOUT, WATCHDOG_PAUSE_ON_DEBUG);
}

View File

@ -27,23 +27,23 @@
#define _TUSB_CONFIG_H_
#ifdef __cplusplus
extern "C" {
extern "C" {
#endif
//--------------------------------------------------------------------
// COMMON CONFIGURATION
//--------------------------------------------------------------------
#define CFG_TUSB_OS OPT_OS_PICO
#define CFG_TUSB_OS OPT_OS_PICO
// Enable device stack
#define CFG_TUD_ENABLED 1
#define CFG_TUD_ENABLED 1
// RHPort number used for device is port 0
#define BOARD_TUD_RHPORT 0
#define BOARD_TUD_RHPORT 0
// RHPort number used for host is port 1
#define BOARD_TUH_RHPORT 1
#define BOARD_TUH_RHPORT 1
// Enable host stack with pio-usb if Pico-PIO-USB library is available
#define CFG_TUH_ENABLED 1
@ -51,28 +51,29 @@
// defined by board.mk
#ifndef CFG_TUSB_MCU
#error CFG_TUSB_MCU must be defined
#error CFG_TUSB_MCU must be defined
#endif
// RHPort number used for device can be defined by board.mk, default to port 0
#ifndef BOARD_DEVICE_RHPORT_NUM
#define BOARD_DEVICE_RHPORT_NUM 0
#define BOARD_DEVICE_RHPORT_NUM 0
#endif
// RHPort max operational speed can defined by board.mk
// Default to Highspeed for MCU with internal HighSpeed PHY (can be port specific), otherwise FullSpeed
#ifndef BOARD_DEVICE_RHPORT_SPEED
#if (CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX || \
CFG_TUSB_MCU == OPT_MCU_NUC505 || CFG_TUSB_MCU == OPT_MCU_CXD56 || CFG_TUSB_MCU == OPT_MCU_SAMX7X)
#define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_HIGH_SPEED
#else
#define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_FULL_SPEED
#endif
#if (CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX \
|| CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX || CFG_TUSB_MCU == OPT_MCU_NUC505 \
|| CFG_TUSB_MCU == OPT_MCU_CXD56 || CFG_TUSB_MCU == OPT_MCU_SAMX7X)
#define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_HIGH_SPEED
#else
#define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_FULL_SPEED
#endif
#endif
// Device mode with rhport and speed defined by board.mk
#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED)
#define CFG_TUSB_RHPORT1_MODE (OPT_MODE_HOST | BOARD_DEVICE_RHPORT_SPEED)
#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED)
#define CFG_TUSB_RHPORT1_MODE (OPT_MODE_HOST | BOARD_DEVICE_RHPORT_SPEED)
// CFG_TUSB_DEBUG is defined by compiler in DEBUG build
// #define CFG_TUSB_DEBUG 0
@ -89,7 +90,7 @@
#endif
#ifndef CFG_TUSB_MEM_ALIGN
#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4)))
#define CFG_TUSB_MEM_ALIGN __attribute__((aligned(4)))
#endif
//--------------------------------------------------------------------
@ -97,18 +98,18 @@
//--------------------------------------------------------------------
#ifndef CFG_TUD_ENDPOINT0_SIZE
#define CFG_TUD_ENDPOINT0_SIZE 64
#define CFG_TUD_ENDPOINT0_SIZE 64
#endif
//------------- CLASS -------------//
#define CFG_TUD_HID 1
#define CFG_TUD_CDC 0
#define CFG_TUD_MSC 0
#define CFG_TUD_MIDI 0
#define CFG_TUD_VENDOR 0
#define CFG_TUD_HID 1
#define CFG_TUD_CDC 0
#define CFG_TUD_MSC 0
#define CFG_TUD_MIDI 0
#define CFG_TUD_VENDOR 0
// HID buffer size Should be sufficient to hold ID (if any) + Data
#define CFG_TUD_HID_EP_BUFSIZE 32
#define CFG_TUD_HID_EP_BUFSIZE 32
//--------------------------------------------------------------------
// HOST CONFIGURATION
@ -117,16 +118,16 @@
// Size of buffer to hold descriptors and other data used for enumeration
#define CFG_TUH_ENUMERATION_BUFSIZE 256
#define CFG_TUH_HUB 1
#define CFG_TUH_HUB 1
// max device support (excluding hub device)
#define CFG_TUH_DEVICE_MAX (CFG_TUH_HUB ? 4 : 1) // hub typically has 4 ports
#define CFG_TUH_DEVICE_MAX (CFG_TUH_HUB ? 4 : 1) // hub typically has 4 ports
#define CFG_TUH_HID 4
#define CFG_TUH_HID_EPIN_BUFSIZE 64
#define CFG_TUH_HID_EPOUT_BUFSIZE 64
#define CFG_TUH_HID 3 * CFG_TUH_DEVICE_MAX
#define CFG_TUH_HID_EPIN_BUFSIZE 64
#define CFG_TUH_HID_EPOUT_BUFSIZE 64
#ifdef __cplusplus
}
}
#endif
#endif /* _TUSB_CONFIG_H_ */

View File

@ -21,7 +21,7 @@
* =============== Sending Packets ================ *
* ================================================== */
void send_packet(const uint8_t* data, enum packet_type_e packet_type, int length) {
void send_packet(const uint8_t *data, enum packet_type_e packet_type, int length) {
uint8_t raw_packet[RAW_PACKET_LENGTH] = {[0] = START1,
[1] = START2,
[2] = packet_type,
@ -31,60 +31,42 @@ void send_packet(const uint8_t* data, enum packet_type_e packet_type, int length
if (length > 0)
memcpy(&raw_packet[START_LENGTH + TYPE_LENGTH], data, length);
/* Packets are short, fixed length, high speed and there is no flow control to block this */
uart_write_blocking(SERIAL_UART, raw_packet, RAW_PACKET_LENGTH);
}
void send_value(const uint8_t value, enum packet_type_e packet_type) {
const uint8_t data[8] = {[0] = value};
send_packet((uint8_t*)&data, packet_type, 1);
const uint8_t data = value;
send_packet(&data, packet_type, sizeof(uint8_t));
}
/**================================================== *
* =============== Parsing Packets ================ *
* ================================================== */
void process_packet(uart_packet_t* packet, device_state_t* state) {
if (!verify_checksum(packet)) {
const uart_handler_t uart_handler[] = {
{.type = KEYBOARD_REPORT_MSG, .handler = handle_keyboard_uart_msg},
{.type = MOUSE_REPORT_MSG, .handler = handle_mouse_abs_uart_msg},
{.type = OUTPUT_SELECT_MSG, .handler = handle_output_select_msg},
{.type = FIRMWARE_UPGRADE_MSG, .handler = handle_fw_upgrade_msg},
{.type = MOUSE_ZOOM_MSG, .handler = handle_mouse_zoom_msg},
{.type = KBD_SET_REPORT_MSG, .handler = handle_set_report_msg},
{.type = SWITCH_LOCK_MSG, .handler = handle_switch_lock_msg},
{.type = SYNC_BORDERS_MSG, .handler = handle_sync_borders_msg},
{.type = FLASH_LED_MSG, .handler = handle_flash_led_msg},
{.type = SCREENSAVER_MSG, .handler = handle_screensaver_msg},
{.type = WIPE_CONFIG_MSG, .handler = handle_wipe_config_msg},
};
void process_packet(uart_packet_t *packet, device_t *state) {
if (!verify_checksum(packet))
return;
}
switch (packet->type) {
case KEYBOARD_REPORT_MSG:
handle_keyboard_uart_msg(packet, state);
break;
case MOUSE_REPORT_MSG:
handle_mouse_abs_uart_msg(packet, state);
break;
case OUTPUT_SELECT_MSG:
handle_output_select_msg(packet, state);
break;
case FIRMWARE_UPGRADE_MSG:
handle_fw_upgrade_msg();
break;
case MOUSE_ZOOM_MSG:
handle_mouse_zoom_msg(packet, state);
break;
case KBD_SET_REPORT_MSG:
handle_set_report_msg(packet, state);
break;
case SWITCH_LOCK_MSG:
handle_switch_lock_msg(packet, state);
break;
case SYNC_BORDERS_MSG:
handle_sync_borders_msg(packet, state);
break;
case FLASH_LED_MSG:
handle_flash_led_msg(packet, state);
break;
for (int i = 0; i < ARRAY_SIZE(uart_handler); i++) {
if (uart_handler[i].type == packet->type) {
uart_handler[i].handler(packet, state);
return;
}
}
}
@ -92,41 +74,59 @@ void process_packet(uart_packet_t* packet, device_state_t* state) {
* ============== Receiving Packets =============== *
* ================================================== */
void receive_char(uart_packet_t* packet, device_state_t* state) {
uint8_t* raw_packet = (uint8_t*)packet;
static int count = 0;
/* We are in IDLE state until we detect the packet start (0xAA 0x55) */
void handle_idle_state(uint8_t *raw_packet, device_t *state) {
if (!uart_is_readable(SERIAL_UART)) {
return;
}
raw_packet[0] = raw_packet[1]; /* Remember the previous byte received */
raw_packet[1] = uart_getc(SERIAL_UART); /* Try to match packet start */
/* If we found 0xAA 0x55, we're in sync and can move on to read/process the packet */
if (raw_packet[0] == START1 && raw_packet[1] == START2) {
state->receiver_state = READING_PACKET;
}
}
/* Read a character off the line until we reach fixed packet length */
void handle_reading_state(uint8_t *raw_packet, device_t *state, int *count) {
if (!uart_is_readable(SERIAL_UART)) {
return;
}
/* Read and store the incoming byte */
raw_packet[(*count)++] = uart_getc(SERIAL_UART);
/* Check if a complete packet is received */
if (*count >= PACKET_LENGTH) {
state->receiver_state = PROCESSING_PACKET;
}
}
/* Process that packet, restart counters and state machine to have it back to IDLE */
void handle_processing_state(uart_packet_t *packet, device_t *state, int *count) {
process_packet(packet, state);
state->receiver_state = IDLE;
*count = 0;
}
/* Very simple state machine to receive and process packets over serial */
void receive_char(uart_packet_t *packet, device_t *state) {
uint8_t *raw_packet = (uint8_t *)packet;
static int count = 0;
switch (state->receiver_state) {
case IDLE:
if (uart_is_readable(SERIAL_UART)) {
raw_packet[0] = raw_packet[1]; /* Remember the previous byte received */
raw_packet[1] = uart_getc(SERIAL_UART); /* ... and try to match packet start */
/* If we found 0xAA 0x55, we're in sync and can move on to read/process the packet
*/
if (raw_packet[0] == START1 && raw_packet[1] == START2) {
state->receiver_state = READING_PACKET;
}
}
handle_idle_state(raw_packet, state);
break;
case READING_PACKET:
if (uart_is_readable(SERIAL_UART)) {
raw_packet[count++] = uart_getc(SERIAL_UART);
/* Check if a complete packet is received */
if (count >= PACKET_LENGTH) {
state->receiver_state = PROCESSING_PACKET;
}
}
handle_reading_state(raw_packet, state, &count);
break;
case PROCESSING_PACKET:
process_packet(packet, state);
/* Cleanup and return to IDLE when done */
count = 0;
state->receiver_state = IDLE;
handle_processing_state(packet, state, &count);
break;
}
}

101
src/usb.c
View File

@ -1,17 +1,17 @@
/*
/*
* This file is part of DeskHop (https://github.com/hrvach/deskhop).
* Copyright (c) 2024 Hrvoje Cavrak
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
@ -21,14 +21,14 @@
* =========== TinyUSB Device Callbacks =========== *
* ================================================== */
/* Invoked when we get GET_REPORT control request.
* We are expected to fill buffer with the report content, update reqlen
/* Invoked when we get GET_REPORT control request.
* We are expected to fill buffer with the report content, update reqlen
* and return its length. We return 0 to STALL the request. */
uint16_t tud_hid_get_report_cb(uint8_t instance,
uint8_t report_id,
hid_report_type_t report_type,
uint8_t* buffer,
uint16_t reqlen) {
uint8_t *buffer,
uint16_t request_len) {
return 0;
}
@ -42,31 +42,29 @@ uint16_t tud_hid_get_report_cb(uint8_t instance,
void tud_hid_set_report_cb(uint8_t instance,
uint8_t report_id,
hid_report_type_t report_type,
uint8_t const* buffer,
uint8_t const *buffer,
uint16_t bufsize) {
if (report_id == REPORT_ID_KEYBOARD && bufsize == 1 && report_type == HID_REPORT_TYPE_OUTPUT) {
/**
* If we are using caps lock LED to indicate the chosen output, we will
* override whatever is sent through the SetReport message.
*/
uint8_t leds = buffer[0];
if (report_id != REPORT_ID_KEYBOARD || bufsize != 1 || report_type != HID_REPORT_TYPE_OUTPUT)
return;
if (KBD_LED_AS_INDICATOR) {
leds = leds & 0xFD; /* 1111 1101 (Clear Caps Lock bit) */
uint8_t leds = buffer[0];
if (global_state.active_output)
leds |= KEYBOARD_LED_CAPSLOCK;
}
/* If we are using caps lock LED to indicate the chosen output, that has priority */
if (KBD_LED_AS_INDICATOR) {
leds = leds & 0xFD; /* 1111 1101 (Clear Caps Lock bit) */
global_state.keyboard_leds[global_state.active_output] = leds;
/* If we are board without the keyboard hooked up directly, we need to send this information
to the other one since that one has the keyboard connected to it (and LEDs you can turn on :)) */
if (global_state.keyboard_connected)
restore_leds(&global_state);
else
send_value(leds, KBD_SET_REPORT_MSG);
if (global_state.active_output)
leds |= KEYBOARD_LED_CAPSLOCK;
}
global_state.keyboard_leds[global_state.active_output] = leds;
/* If the board doesn't have the keyboard hooked up directly, we need to relay this information
to the other one that has (and LEDs you can turn on). */
if (global_state.keyboard_connected)
restore_leds(&global_state);
else
send_value(leds, KBD_SET_REPORT_MSG);
}
/* Invoked when device is mounted */
@ -85,6 +83,7 @@ void tud_umount_cb(void) {
void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) {
uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance);
switch (itf_protocol) {
case HID_ITF_PROTOCOL_KEYBOARD:
global_state.keyboard_connected = false;
@ -94,23 +93,20 @@ void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) {
global_state.mouse_connected = false;
/* Clear this so reconnecting a mouse doesn't try to continue in HID REPORT protocol */
memset(&global_state.mouse_dev, 0, sizeof(global_state.mouse_dev));
memset(&global_state.mouse_dev, 0, sizeof(global_state.mouse_dev));
break;
}
}
void tuh_hid_mount_cb(uint8_t dev_addr,
uint8_t instance,
uint8_t const* desc_report,
uint16_t desc_len) {
uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance);
void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *desc_report, uint16_t desc_len) {
uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance);
switch (itf_protocol) {
case HID_ITF_PROTOCOL_KEYBOARD:
/* Keeping this is needed for setting leds from device set_report callback */
global_state.kbd_dev_addr = dev_addr;
global_state.kbd_instance = instance;
global_state.keyboard_connected = true;
/* Keeping this is required for setting leds from device set_report callback */
global_state.kbd_dev_addr = dev_addr;
global_state.kbd_instance = instance;
global_state.keyboard_connected = true;
break;
case HID_ITF_PROTOCOL_MOUSE:
@ -121,35 +117,30 @@ void tuh_hid_mount_cb(uint8_t dev_addr,
}
parse_report_descriptor(&global_state.mouse_dev, MAX_REPORTS, desc_report, desc_len);
global_state.mouse_connected = true;
global_state.mouse_connected = true;
break;
}
/* Flash local led to indicate a device was connected */
blink_led(&global_state);
blink_led(&global_state);
/* Also signal the other board to flash LED, to enable easy verification if serial works */
send_value(ENABLE, FLASH_LED_MSG);
/* Kick off the report querying */
tuh_hid_receive_report(dev_addr, instance);
}
/* Invoked when received report from device via interrupt endpoint */
void tuh_hid_report_received_cb(uint8_t dev_addr,
uint8_t instance,
uint8_t const* report,
uint16_t len) {
(void)len;
void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *report, uint16_t len) {
uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance);
switch (itf_protocol) {
case HID_ITF_PROTOCOL_KEYBOARD:
process_keyboard_report((uint8_t*)report, len, &global_state);
process_keyboard_report((uint8_t *)report, len, &global_state);
break;
case HID_ITF_PROTOCOL_MOUSE:
process_mouse_report((uint8_t*)report, len, &global_state);
process_mouse_report((uint8_t *)report, len, &global_state);
break;
}
@ -158,9 +149,7 @@ void tuh_hid_report_received_cb(uint8_t dev_addr,
}
/* Set protocol in a callback. If we were called, command succeeded. We're only
doing this for the mouse anyway, so we can only be called about the mouse */
doing this for the mouse for now, so we can only be called about the mouse */
void tuh_hid_set_protocol_complete_cb(uint8_t dev_addr, uint8_t idx, uint8_t protocol) {
(void) dev_addr;
(void) idx;
global_state.mouse_dev.protocol = protocol;
}

View File

@ -23,36 +23,36 @@
*
*/
#include "main.h"
#include "usb_descriptors.h"
#include "main.h"
#include "tusb.h"
//--------------------------------------------------------------------+
// Device Descriptors
//--------------------------------------------------------------------+
tusb_desc_device_t const desc_device = {.bLength = sizeof(tusb_desc_device_t),
tusb_desc_device_t const desc_device = {.bLength = sizeof(tusb_desc_device_t),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = 0x00,
.bcdUSB = 0x0200,
.bDeviceClass = 0x00,
.bDeviceSubClass = 0x00,
.bDeviceProtocol = 0x00,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
// https://github.com/raspberrypi/usb-pid
.idVendor = 0x2E8A,
.idVendor = 0x2E8A,
.idProduct = 0x107C,
.bcdDevice = 0x0100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01};
// Invoked when received GET DEVICE DESCRIPTOR
// Application return pointer to descriptor
uint8_t const* tud_descriptor_device_cb(void) {
return (uint8_t const*)&desc_device;
uint8_t const *tud_descriptor_device_cb(void) {
return (uint8_t const *)&desc_device;
}
//--------------------------------------------------------------------+
@ -65,29 +65,24 @@ uint8_t const desc_hid_report[] = {TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(RE
// Invoked when received GET HID REPORT DESCRIPTOR
// Application return pointer to descriptor
// Descriptor contents must exist long enough for transfer to complete
uint8_t const* tud_hid_descriptor_report_cb(uint8_t instance) {
uint8_t const *tud_hid_descriptor_report_cb(uint8_t instance) {
(void)instance;
return desc_hid_report;
}
bool tud_hid_n_abs_mouse_report(uint8_t instance,
uint8_t report_id,
uint8_t buttons,
int16_t x,
int16_t y,
int8_t vertical,
int8_t horizontal) {
hid_abs_mouse_report_t report = {
.buttons = buttons, .x = x, .y = y, .wheel = vertical, .pan = horizontal};
uint8_t report_id,
uint8_t buttons,
int16_t x,
int16_t y,
int8_t vertical,
int8_t horizontal) {
mouse_abs_report_t report = {.buttons = buttons, .x = x, .y = y, .wheel = vertical, .pan = horizontal};
return tud_hid_n_report(instance, report_id, &report, sizeof(report));
}
bool tud_hid_abs_mouse_report(uint8_t report_id,
uint8_t buttons,
int16_t x,
int16_t y,
int8_t vertical,
int8_t horizontal) {
bool tud_hid_abs_mouse_report(
uint8_t report_id, uint8_t buttons, int16_t x, int16_t y, int8_t vertical, int8_t horizontal) {
return tud_hid_n_abs_mouse_report(0, report_id, buttons, x, y, vertical, horizontal);
}
@ -103,12 +98,7 @@ enum { ITF_NUM_HID, ITF_NUM_TOTAL };
uint8_t const desc_configuration[] = {
// Config number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1,
ITF_NUM_TOTAL,
0,
CONFIG_TOTAL_LEN,
TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP,
500),
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 500),
// Interface number, string index, protocol, report descriptor len, EP In address, size &
// polling interval
@ -129,34 +119,33 @@ uint8_t desc_other_speed_config[CONFIG_TOTAL_LEN];
// device qualifier is mostly similar to device descriptor since we don't change configuration based
// on speed
tusb_desc_device_qualifier_t const desc_device_qualifier = {
.bLength = sizeof(tusb_desc_device_qualifier_t),
.bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,
.bcdUSB = USB_BCD,
tusb_desc_device_qualifier_t const desc_device_qualifier = {.bLength = sizeof(tusb_desc_device_qualifier_t),
.bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,
.bcdUSB = USB_BCD,
.bDeviceClass = 0x00,
.bDeviceSubClass = 0x00,
.bDeviceProtocol = 0x00,
.bDeviceClass = 0x00,
.bDeviceSubClass = 0x00,
.bDeviceProtocol = 0x00,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.bNumConfigurations = 0x01,
.bReserved = 0x00};
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.bNumConfigurations = 0x01,
.bReserved = 0x00};
// Invoked when received GET DEVICE QUALIFIER DESCRIPTOR request
// Application return pointer to descriptor, whose contents must exist long enough for transfer to
// complete. device_qualifier descriptor describes information about a high-speed capable device
// that would change if the device were operating at the other speed. If not highspeed capable stall
// this request.
uint8_t const* tud_descriptor_device_qualifier_cb(void) {
return (uint8_t const*)&desc_device_qualifier;
uint8_t const *tud_descriptor_device_qualifier_cb(void) {
return (uint8_t const *)&desc_device_qualifier;
}
// Invoked when received GET OTHER SEED CONFIGURATION DESCRIPTOR request
// Application return pointer to descriptor, whose contents must exist long enough for transfer to
// complete Configuration descriptor in the other speed e.g if high speed then this is for full
// speed and vice versa
uint8_t const* tud_descriptor_other_speed_configuration_cb(uint8_t index) {
(void)index; // for multiple configurations
uint8_t const *tud_descriptor_other_speed_configuration_cb(uint8_t index) {
(void)index; // for multiple configurations
// other speed config is basically configuration with type = OHER_SPEED_CONFIG
memcpy(desc_other_speed_config, desc_configuration, CONFIG_TOTAL_LEN);
@ -166,13 +155,13 @@ uint8_t const* tud_descriptor_other_speed_configuration_cb(uint8_t index) {
return desc_other_speed_config;
}
#endif // highspeed
#endif // highspeed
// Invoked when received GET CONFIGURATION DESCRIPTOR
// Application return pointer to descriptor
// Descriptor contents must exist long enough for transfer to complete
uint8_t const* tud_descriptor_configuration_cb(uint8_t index) {
(void)index; // for multiple configurations
uint8_t const *tud_descriptor_configuration_cb(uint8_t index) {
(void)index; // for multiple configurations
// This example use the same configuration for both high and full speed mode
return desc_configuration;
@ -191,11 +180,11 @@ enum {
};
// array of pointer to string descriptors
char const* string_desc_arr[] = {
(const char[]){0x09, 0x04}, // 0: is supported language is English (0x0409)
"Hrvoje Cavrak", // 1: Manufacturer
"DeskHop Switch", // 2: Product
"0", // 3: Serials, should use chip ID
char const *string_desc_arr[] = {
(const char[]){0x09, 0x04}, // 0: is supported language is English (0x0409)
"Hrvoje Cavrak", // 1: Manufacturer
"DeskHop Switch", // 2: Product
"0", // 3: Serials, should use chip ID
};
static uint16_t _desc_str[32];
@ -203,7 +192,7 @@ static uint16_t _desc_str[32];
// Invoked when received GET STRING DESCRIPTOR request
// Application return pointer to descriptor, whose contents must exist long enough for transfer to
// complete
uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
(void)langid;
uint8_t chr_count;
@ -218,7 +207,7 @@ uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
if (!(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0])))
return NULL;
const char* str = string_desc_arr[index];
const char *str = string_desc_arr[index];
// Cap at max char
chr_count = strlen(str);

View File

@ -30,16 +30,14 @@ enum
REPORT_ID_KEYBOARD = 1,
REPORT_ID_MOUSE,
REPORT_ID_CONSUMER_CONTROL,
REPORT_ID_GAMEPAD,
REPORT_ID_COUNT
};
#define TUD_HID_REPORT_DESC_ABSMOUSE(...) \
HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\
HID_USAGE ( HID_USAGE_DESKTOP_MOUSE ) ,\
HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\
/* Report ID if any */\
/* Report ID */\
__VA_ARGS__ \
HID_USAGE ( HID_USAGE_DESKTOP_POINTER ) ,\
HID_COLLECTION ( HID_COLLECTION_PHYSICAL ) ,\
@ -48,15 +46,18 @@ HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\
HID_USAGE_MAX ( 5 ) ,\
HID_LOGICAL_MIN ( 0 ) ,\
HID_LOGICAL_MAX ( 1 ) ,\
\
/* Left, Right, Middle, Backward, Forward buttons */ \
HID_REPORT_COUNT( 5 ) ,\
HID_REPORT_SIZE ( 1 ) ,\
HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\
\
/* 3 bit padding */ \
HID_REPORT_COUNT( 1 ) ,\
HID_REPORT_SIZE ( 3 ) ,\
HID_INPUT ( HID_CONSTANT ) ,\
HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\
\
/* X, Y absolute position [0, 32767] */ \
HID_USAGE ( HID_USAGE_DESKTOP_X ) ,\
HID_USAGE ( HID_USAGE_DESKTOP_Y ) ,\
@ -65,6 +66,7 @@ HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\
HID_REPORT_SIZE ( 16 ) ,\
HID_REPORT_COUNT ( 2 ) ,\
HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\
\
/* Vertical wheel scroll [-127, 127] */ \
HID_USAGE ( HID_USAGE_DESKTOP_WHEEL ) ,\
HID_LOGICAL_MIN ( 0x81 ) ,\
@ -73,7 +75,8 @@ HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\
HID_REPORT_SIZE ( 8 ) ,\
HID_INPUT ( HID_DATA | HID_VARIABLE | HID_RELATIVE ) ,\
HID_USAGE_PAGE ( HID_USAGE_PAGE_CONSUMER ), \
/* Horizontal wheel scroll [-127, 127] */ \
\
/* Horizontal wheel scroll [-127, 127] */ \
HID_USAGE_N ( HID_USAGE_CONSUMER_AC_PAN, 2 ), \
HID_LOGICAL_MIN ( 0x81 ), \
HID_LOGICAL_MAX ( 0x7f ), \
@ -83,4 +86,80 @@ HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\
HID_COLLECTION_END , \
HID_COLLECTION_END \
/* Generated report */
/* */
/* 0x05, 0x01, Usage Page (Desktop), */
/* 0x09, 0x06, Usage (Keyboard), */
/* 0xA1, 0x01, Collection (Application), */
/* 0x85, 0x01, Report ID (1), */
/* 0x05, 0x07, Usage Page (Keyboard), */
/* 0x19, 0xE0, Usage Minimum (KB Leftcontrol), */
/* 0x29, 0xE7, Usage Maximum (KB Right GUI), */
/* 0x15, 0x00, Logical Minimum (0), */
/* 0x25, 0x01, Logical Maximum (1), */
/* 0x95, 0x08, Report Count (8), */
/* 0x75, 0x01, Report Size (1), */
/* 0x81, 0x02, Input (Variable), */
/* 0x95, 0x01, Report Count (1), */
/* 0x75, 0x08, Report Size (8), */
/* 0x81, 0x01, Input (Constant), */
/* 0x05, 0x08, Usage Page (LED), */
/* 0x19, 0x01, Usage Minimum (01h), */
/* 0x29, 0x05, Usage Maximum (05h), */
/* 0x95, 0x05, Report Count (5), */
/* 0x75, 0x01, Report Size (1), */
/* 0x91, 0x02, Output (Variable), */
/* 0x95, 0x01, Report Count (1), */
/* 0x75, 0x03, Report Size (3), */
/* 0x91, 0x01, Output (Constant), */
/* 0x05, 0x07, Usage Page (Keyboard), */
/* 0x19, 0x00, Usage Minimum (None), */
/* 0x2A, 0xFF, 0x00, Usage Maximum (FFh), */
/* 0x15, 0x00, Logical Minimum (0), */
/* 0x26, 0xFF, 0x00, Logical Maximum (255), */
/* 0x95, 0x06, Report Count (6), */
/* 0x75, 0x08, Report Size (8), */
/* 0x81, 0x00, Input, */
/* 0xC0, End Collection, */
/* 0x05, 0x01, Usage Page (Desktop), */
/* 0x09, 0x02, Usage (Mouse), */
/* 0xA1, 0x01, Collection (Application), */
/* 0x85, 0x02, Report ID (2), */
/* 0x09, 0x01, Usage (Pointer), */
/* 0xA1, 0x00, Collection (Physical), */
/* 0x05, 0x09, Usage Page (Button), */
/* 0x19, 0x01, Usage Minimum (01h), */
/* 0x29, 0x05, Usage Maximum (05h), */
/* 0x15, 0x00, Logical Minimum (0), */
/* 0x25, 0x01, Logical Maximum (1), */
/* 0x95, 0x05, Report Count (5), */
/* 0x75, 0x01, Report Size (1), */
/* 0x81, 0x02, Input (Variable), */
/* 0x95, 0x01, Report Count (1), */
/* 0x75, 0x03, Report Size (3), */
/* 0x81, 0x01, Input (Constant), */
/* 0x05, 0x01, Usage Page (Desktop), */
/* 0x09, 0x30, Usage (X), */
/* 0x09, 0x31, Usage (Y), */
/* 0x15, 0x00, Logical Minimum (0), */
/* 0x26, 0xFF, 0x7F, Logical Maximum (32767), */
/* 0x75, 0x10, Report Size (16), */
/* 0x95, 0x02, Report Count (2), */
/* 0x81, 0x02, Input (Variable), */
/* 0x09, 0x38, Usage (Wheel), */
/* 0x15, 0x81, Logical Minimum (-127), */
/* 0x25, 0x7F, Logical Maximum (127), */
/* 0x95, 0x01, Report Count (1), */
/* 0x75, 0x08, Report Size (8), */
/* 0x81, 0x06, Input (Variable, Relative), */
/* 0x05, 0x0C, Usage Page (Consumer), */
/* 0x0A, 0x38, 0x02, Usage (AC Pan), */
/* 0x15, 0x81, Logical Minimum (-127), */
/* 0x25, 0x7F, Logical Maximum (127), */
/* 0x95, 0x01, Report Count (1), */
/* 0x75, 0x08, Report Size (8), */
/* 0x81, 0x06, Input (Variable, Relative), */
/* 0xC0, End Collection, */
/* 0xC0 End Collection */
#endif /* USB_DESCRIPTORS_H_ */

View File

@ -35,11 +35,11 @@
*
* MOUSE_SPEED_A_FACTOR_X: [1-128], mouse moves at this speed in X direction
* MOUSE_SPEED_A_FACTOR_Y: [1-128], mouse moves at this speed in Y direction
*
* MOUSE_JUMP_THRESHOLD: [0-32768], sets the "force" you need to use to drag the
*
* JUMP_THRESHOLD: [0-32768], sets the "force" you need to use to drag the
* mouse to another screen, 0 meaning no force needed at all, and ~500 some force
* needed, ~1000 no accidental jumps, you need to really mean it.
*
*
* This is now configurable per-screen.
*
* */
@ -52,5 +52,22 @@
#define MOUSE_SPEED_B_FACTOR_X 16
#define MOUSE_SPEED_B_FACTOR_Y 16
#define MOUSE_JUMP_THRESHOLD 0
#define JUMP_THRESHOLD 0
/**================================================== *
* ============== Screensaver Config ============== *
* ==================================================
*
* Defines how long does an output need to be idle for screensaver to kick in.
* With this function, after being left idle for a certain amount of time (defined below),
* mouse cursor starts moving around like a bouncy-ball in pong. No clicking, of course.
* Move mouse on that active output to stop.
*
* SCREENSAVER_ENABLED: [0 or 1] 0 means screensaver is disabled, 1 means it is enabled.
* SCREENSAVER_TIME_SEC: time in seconds
*
* */
#define SCREENSAVER_ENABLED 0
#define SCREENSAVER_TIME_SEC 240

View File

@ -21,7 +21,7 @@
* ============== Checksum Functions ============== *
* ================================================== */
uint8_t calc_checksum(const uint8_t* data, int length) {
uint8_t calc_checksum(const uint8_t *data, int length) {
uint8_t checksum = 0;
for (int i = 0; i < length; i++) {
@ -31,7 +31,7 @@ uint8_t calc_checksum(const uint8_t* data, int length) {
return checksum;
}
bool verify_checksum(const uart_packet_t* packet) {
bool verify_checksum(const uart_packet_t *packet) {
uint8_t checksum = calc_checksum(packet->data, PACKET_DATA_LENGTH);
return checksum == packet->checksum;
}
@ -40,12 +40,12 @@ bool verify_checksum(const uart_packet_t* packet) {
* ============== Watchdog Functions ============== *
* ================================================== */
void kick_watchdog(void) {
void kick_watchdog(device_t *state) {
/* Read the timer AFTER duplicating the core1 timestamp,
so it doesn't get updated in the meantime. */
uint64_t core1_last_loop_pass = global_state.core1_last_loop_pass;
uint64_t current_time = time_us_64();
uint64_t core1_last_loop_pass = state->core1_last_loop_pass;
uint64_t current_time = time_us_64();
/* If core1 stops updating the timestamp, we'll stop kicking the watchog and reboot */
if (current_time - core1_last_loop_pass < CORE1_HANG_TIMEOUT_US)
@ -62,24 +62,85 @@ void wipe_config(void) {
restore_interrupts(ints);
}
void load_config(void) {
config_t* config = ADDR_CONFIG_BASE_ADDR;
void load_config(device_t *state) {
const config_t *config = ADDR_CONFIG_BASE_ADDR;
config_t *running_config = &state->config;
/* If no config is detected, copy default values to our struct. TODO checksum */
if (config->magic_header != 0x0B00B1E5) {
config = (config_t*)&default_config;
}
/* Load the flash config first, including the checksum */
memcpy(running_config, config, sizeof(config_t));
memcpy(&global_state.config, config, sizeof(config_t));
/* Calculate and update checksum, size without checksum */
uint8_t checksum = calc_checksum((uint8_t *)running_config, sizeof(config_t) - sizeof(uint32_t));
/* We expect a certain byte to start the config header */
bool magic_header_fail = (running_config->magic_header != 0xB00B1E5);
/* We expect the checksum to match */
bool checksum_fail = (running_config->checksum != checksum);
/* We expect the config version to match exactly, to avoid erroneous values */
bool version_fail = (running_config->version != CURRENT_CONFIG_VERSION);
/* On any condition failing, we fall back to default config */
if (magic_header_fail || checksum_fail || version_fail)
memcpy(running_config, &default_config, sizeof(config_t));
}
void save_config(void) {
void save_config(device_t *state) {
uint8_t buf[FLASH_PAGE_SIZE];
memcpy(buf, &global_state.config, sizeof(config_t));
uint8_t *raw_config = (uint8_t *)&state->config;
/* Calculate and update checksum, size without checksum */
uint8_t checksum = calc_checksum(raw_config, sizeof(config_t) - sizeof(uint32_t));
state->config.checksum = checksum;
/* Copy the config to buffer and wipe the old one */
memcpy(buf, raw_config, sizeof(config_t));
wipe_config();
/* Disable interrupts, then write the flash page and re-enable */
uint32_t ints = save_and_disable_interrupts();
flash_range_program(PICO_FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE, buf, FLASH_PAGE_SIZE);
restore_interrupts(ints);
}
}
/* Have something fun and entertaining when idle */
void screensaver_task(device_t *state) {
const uint64_t idle_timeout_us = SCREENSAVER_TIME_SEC * 1000000;
const int mouse_move_delay = 5000;
static mouse_abs_report_t report = {.x = 0, .y = 0};
static int last_pointer_move = 0;
/* "Randomly" chosen initial values */
static int dx = 20;
static int dy = 25;
/* If we're not enabled, nothing to do here. */
if (!state->config.screensaver_enabled)
return;
/* We are enabled, but idle time still too small to activate. */
if (time_us_64() - state->last_activity[BOARD_ROLE] < idle_timeout_us)
return;
/* We're active! Now check if it's time to move the cursor yet. */
if ((time_us_32()) - last_pointer_move < mouse_move_delay)
return;
/* Check if we are bouncing off the walls and reverse direction in that case. */
if (report.x + dx < MIN_SCREEN_COORD || report.x + dx > MAX_SCREEN_COORD)
dx = -dx;
if (report.y + dy < MIN_SCREEN_COORD || report.y + dy > MAX_SCREEN_COORD)
dy = -dy;
report.x += dx;
report.y += dy;
/* Move mouse pointer */
queue_mouse_report(&report, state);
/* Update timer of the last pointer move */
last_pointer_move = time_us_32();
}