Skip to content

Commit 45ca4f9

Browse files
authored
Merge pull request #615 from TheScientist101/main
Add robopad
2 parents 37481ea + 2bc2b2e commit 45ca4f9

23 files changed

+593220
-0
lines changed

hackpads/robopad/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
**/.DS_Store

hackpads/robopad/README.md

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Robopad
2+
3+
The Robopad was designed because no controllers are mapped correctly for robot simulation. The main features I needed were the two joysticks but I decided to add some sliders to allow me to control the brightness of my screen and some keyswitches to allow me to have more inputs when I need them. Along with the keyswitches, I added some LEDs which will (hopefully) make the macropad look cooler.
4+
5+
## Features
6+
7+
- 2 Joysticks
8+
- 2 Sliders
9+
- 9 Key Switches
10+
- 9 LEDs
11+
12+
## CAD Model
13+
14+
The case was designed using OpenSCAD and fits together using 4 M3 screws which go into threaded inserts. The case is designed to be 3D printed and the files can be found in the `case` and `production` folders.
15+
16+
![cad](assets/cad.png)
17+
18+
## PCB
19+
20+
My PCB was made in Kicad. It consists of the XIAO Seeed RP2040 microcontroller, 2 joysticks, 2 sliders, 9 keyswitches, and 9 LEDs. The PCB was designed to be 2 layers and the files can be found in the `pcb` folder.
21+
22+
Schematic:\
23+
![schematic](assets/schematic.png)
24+
25+
PCB:\
26+
![pcb](assets/pcb.png)
27+
28+
## Firmware
29+
30+
The firware was written in Python using KMK and some Adafruit HID stuff. I have a couple different random configurations for the keyswitches. The joysticks should appear as joysticks when plugged in to the computer, and the sliders should change volume and brightness.
31+
32+
## BOM
33+
34+
Stuff from Hackclub:
35+
36+
- 9x Cherry MX Switches
37+
- 9x DSA Keycaps
38+
- 4x M3x5x4 Heatset inserts
39+
- 4X M3x16mm SHCS Bolts
40+
- 9x 1N4148 DO-35 Diodes.
41+
- 9x SK6812MINI-E LEDs
42+
- 1x XIAO RP2040
43+
- 1x Case (2 printed parts)
44+
45+
Stuff from the $20 Grant:
46+
47+
- 1x PCB (black from JLCPCB)
48+
- $2.00 fabrication + $1.50 shipping + $0.29 tax = $3.79
49+
- 2x Joystick ([from aliexpress](https://www.aliexpress.us/item/3256807502491570.html?spm=a2g0o.cart.0.0.5f2f38daBj3AgQ&mp=1&pdp_npi=5%40dis%21USD%21USD%202.92%21USD%202.68%21%21%21%21%21%402103201917401184935476465efd24%2112000041983052805%21ct%21US%216221086412%21%211%210&_gl=1*zre4f*_gcl_aw*R0NMLjE3NDAwODAxMjkuQ2owS0NRaUF3dHU5QmhDOEFSSXNBSTlKSGFrRzQxQkk3cFFCNklUdUVZb0tieW1SSmxpVThvZzV0THo5OG0wWGpPelNsVjNZTUhob0pQMGFBcldPRUFMd193Y0I.*_gcl_dc*R0NMLjE3NDAwODAxMjkuQ2owS0NRaUF3dHU5QmhDOEFSSXNBSTlKSGFrRzQxQkk3cFFCNklUdUVZb0tieW1SSmxpVThvZzV0THo5OG0wWGpPelNsVjNZTUhob0pQMGFBcldPRUFMd193Y0I.*_gcl_au*MTE3ODQ3NjYwMy4xNzM4MzUzNDQ5*_ga*NTQwNjc0MjM3LjE3MzgzNTM0NDk.*_ga_VED1YSGNC7*MTc0MDExODMxOC4yMC4xLjE3NDAxMTg1NzIuMzguMC4w&gatewayAdapt=glo2usa))
50+
- $2.92 (2 in one pack)
51+
- 2x Slider ([from aliexpress](https://www.aliexpress.us/item/2255800687104982.html?spm=a2g0o.cart.0.0.5f2f38daBj3AgQ&mp=1&pdp_npi=5%40dis%21USD%21USD%202.00%21USD%201.62%21%21%21%21%21%402103201917401184935476465efd24%2110000010056121148%21ct%21US%216221086412%21%212%210&_gl=1*1mt7dhr*_gcl_aw*R0NMLjE3NDAwODAxMjkuQ2owS0NRaUF3dHU5QmhDOEFSSXNBSTlKSGFrRzQxQkk3cFFCNklUdUVZb0tieW1SSmxpVThvZzV0THo5OG0wWGpPelNsVjNZTUhob0pQMGFBcldPRUFMd193Y0I.*_gcl_dc*R0NMLjE3NDAwODAxMjkuQ2owS0NRaUF3dHU5QmhDOEFSSXNBSTlKSGFrRzQxQkk3cFFCNklUdUVZb0tieW1SSmxpVThvZzV0THo5OG0wWGpPelNsVjNZTUhob0pQMGFBcldPRUFMd193Y0I.*_gcl_au*MTE3ODQ3NjYwMy4xNzM4MzUzNDQ5*_ga*NTQwNjc0MjM3LjE3MzgzNTM0NDk.*_ga_VED1YSGNC7*MTc0MDExODMxOC4yMC4xLjE3NDAxMTg2NTEuNDIuMC4w&gatewayAdapt=glo2usa))
52+
- $1.62 \* 2 + $3.66 shipping = $6.90
53+
- 1x MCP3426 I2C ADC ([from aliexpress](https://www.aliexpress.us/item/3256806640672476.html?spm=a2g0o.cart.0.0.5f2f38daBj3AgQ&mp=1&pdp_npi=5%40dis%21USD%21USD%202.38%21USD%202.27%21%21%21%21%21%402103201917401184935476465efd24%2112000038429688044%21ct%21US%216221086412%21%211%210&_gl=1*fbhyet*_gcl_aw*R0NMLjE3NDAwODAxMjkuQ2owS0NRaUF3dHU5QmhDOEFSSXNBSTlKSGFrRzQxQkk3cFFCNklUdUVZb0tieW1SSmxpVThvZzV0THo5OG0wWGpPelNsVjNZTUhob0pQMGFBcldPRUFMd193Y0I.*_gcl_dc*R0NMLjE3NDAwODAxMjkuQ2owS0NRaUF3dHU5QmhDOEFSSXNBSTlKSGFrRzQxQkk3cFFCNklUdUVZb0tieW1SSmxpVThvZzV0THo5OG0wWGpPelNsVjNZTUhob0pQMGFBcldPRUFMd193Y0I.*_gcl_au*MTE3ODQ3NjYwMy4xNzM4MzUzNDQ5*_ga*NTQwNjc0MjM3LjE3MzgzNTM0NDk.*_ga_VED1YSGNC7*MTc0MDExODMxOC4yMC4xLjE3NDAxMTg0OTMuNDQuMC4w&gatewayAdapt=glo2usa))
54+
- $2.38
55+
- 1x MCP23008 I2C GPIO Expander ([from aliexpress](https://www.aliexpress.us/item/2251832814785988.html?spm=a2g0o.cart.0.0.5f2f38daBj3AgQ&mp=1&pdp_npi=5%40dis%21USD%21USD%202.87%21USD%200.99%21%21%21%21%21%402103201917401184935476465efd24%2167065260410%21ct%21US%216221086412%21%211%210&_gl=1*1c9ndtu*_gcl_aw*R0NMLjE3NDAwODAxMjkuQ2owS0NRaUF3dHU5QmhDOEFSSXNBSTlKSGFrRzQxQkk3cFFCNklUdUVZb0tieW1SSmxpVThvZzV0THo5OG0wWGpPelNsVjNZTUhob0pQMGFBcldPRUFMd193Y0I.*_gcl_dc*R0NMLjE3NDAwODAxMjkuQ2owS0NRaUF3dHU5QmhDOEFSSXNBSTlKSGFrRzQxQkk3cFFCNklUdUVZb0tieW1SSmxpVThvZzV0THo5OG0wWGpPelNsVjNZTUhob0pQMGFBcldPRUFMd193Y0I.*_gcl_au*MTE3ODQ3NjYwMy4xNzM4MzUzNDQ5*_ga*NTQwNjc0MjM3LjE3MzgzNTM0NDk.*_ga_VED1YSGNC7*MTc0MDExODMxOC4yMC4xLjE3NDAxMTg1MzQuMy4wLjA.&gatewayAdapt=glo2usa))
56+
- $2.87
57+
58+
Tax from Aliexpress: ($6.90 + $2.38 + $2.87 + $2.92) \* 0.0825 = $1.52
59+
Total: $3.79 + $6.90 + $2.38 + $2.87 + $2.92 + $1.52 + $3.79 = $23.17
60+
61+
If the sales and the stars align I will be able to fit this in the $20 budget. If not, I will pay the extra $3.17.

hackpads/robopad/assets/cad.png

222 KB
Loading

hackpads/robopad/assets/pcb.png

316 KB
Loading

hackpads/robopad/assets/schematic.png

514 KB
Loading

hackpads/robopad/cad/assembled.scad

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
use <case_bottom.scad>
2+
use <case_top.scad>
3+
4+
translate([0, 0, 5]) case_top();
5+
translate([0, 0, -1.5]) case_bottom();
6+
color("green") import("./components/pcb.stl");

hackpads/robopad/cad/case_bottom.scad

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
include <BOSL2/std.scad>
2+
3+
module case_bottom() {
4+
overlapBuffer = 0.1;
5+
difference() {
6+
cuboid([120, 120, 13], rounding=1, except=TOP);
7+
translate([0, 0, 3]) cube([101, 101, 10 + overlapBuffer], center=true);
8+
translate([55, -55, 0]) cylinder(r=1.7, h=13+overlapBuffer, center=true);
9+
translate([-55, -55, 0]) cylinder(r=1.7, h=13+overlapBuffer, center=true);
10+
translate([-55, 55, 0]) cylinder(r=1.7, h=13+overlapBuffer, center=true);
11+
translate([55, 55, 0]) cylinder(r=1.7, h=13+overlapBuffer, center=true);
12+
13+
translate([0, 55, -1.5]) cuboid(size=[10, 10+overlapBuffer, 4], rounding=1, except=[FRONT, BACK]);
14+
}
15+
}
16+
17+
translate([0, 0, -1.5]) case_bottom();
18+
color("green") import("./components/pcb.stl");

hackpads/robopad/cad/case_top.scad

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
include <BOSL2/std.scad>
2+
3+
module case_top() {
4+
overlapBuffer = 0.1;
5+
height = 1.5;
6+
7+
difference() {
8+
union() {
9+
cuboid([120, 120, height], rounding=1, except=BOTTOM);
10+
translate([-30, 30, height + 2.5]) cylinder(r=17, h=height + 5, center=true);
11+
translate([30, 30, height + 2.5]) cylinder(r=17, h=height + 5, center=true);
12+
}
13+
14+
translate([-30, 30, 0]) cylinder(r=15, h=30+overlapBuffer, center=true);
15+
translate([30, 30, 0]) cylinder(r=15, h=30+overlapBuffer, center=true);
16+
translate([38, -17.5, 0]) cube([10, 47, height + overlapBuffer], center=true);
17+
translate([-37, -17.5, 0]) cube([10, 47, height + overlapBuffer], center=true);
18+
for (i=[0:1:2]) {
19+
for (j=[-2:1:0]) {
20+
translate([i * 19.05 - 18.1706, j * 19.05 + 3.5719, 0]) cube([14, 14, height + overlapBuffer], center=true);
21+
}
22+
}
23+
24+
translate([55, -55, 0]) cylinder(r=1.7, h=13+overlapBuffer, center=true);
25+
translate([-55, -55, 0]) cylinder(r=1.7, h=13+overlapBuffer, center=true);
26+
translate([-55, 55, 0]) cylinder(r=1.7, h=13+overlapBuffer, center=true);
27+
translate([55, 55, 0]) cylinder(r=1.7, h=13+overlapBuffer, center=true);
28+
translate([0, -50, 0.5]) linear_extrude(1+overlapBuffer) text("robopad, made proudly by urjith mishra (thescientist101 on gh)", size=2, halign="center", valign="center", font="Arial:style=Bold", $fn=100);
29+
}
30+
}
31+
32+
translate([0, 0, 5]) case_top();
33+
color("green") import("./components/pcb.stl");
18.7 MB
Binary file not shown.
+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
https://github.com/coburnw/MCP342x
2+
adafruit hid

hackpads/robopad/firmware/main.py

+176
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import board
2+
import busio
3+
import analogio
4+
import neopixel
5+
6+
from adafruit_mcp3426 import MCP3426
7+
from adafruit_mcp23008 import MCP23008
8+
from kmk.kmk_keyboard import KMKKeyboard
9+
from kmk.keys import KC
10+
from kmk.scanners import DiodeOrientation
11+
12+
import usb_hid
13+
from adafruit_hid.consumer_control import ConsumerControl
14+
from adafruit_hid.consumer_control_code import ConsumerControlCode
15+
16+
import usb_hid
17+
18+
# Source: Adafruit
19+
# TODO: Test with mac to find the correct report descriptor
20+
# This is only one example of a gamepad report descriptor,
21+
# and may not suit your needs.
22+
GAMEPAD_REPORT_DESCRIPTOR = bytes((
23+
0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
24+
0x09, 0x05, # Usage (Game Pad)
25+
0xA1, 0x01, # Collection (Application)
26+
0x85, 0x04, # Report ID (4)
27+
0x05, 0x09, # Usage Page (Button)
28+
0x19, 0x01, # Usage Minimum (Button 1)
29+
0x29, 0x10, # Usage Maximum (Button 16)
30+
0x15, 0x00, # Logical Minimum (0)
31+
0x25, 0x01, # Logical Maximum (1)
32+
0x75, 0x01, # Report Size (1)
33+
0x95, 0x10, # Report Count (16)
34+
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
35+
0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
36+
0x15, 0x81, # Logical Minimum (-127)
37+
0x25, 0x7F, # Logical Maximum (127)
38+
0x09, 0x30, # Usage (X)
39+
0x09, 0x31, # Usage (Y)
40+
0x09, 0x32, # Usage (Z)
41+
0x09, 0x35, # Usage (Rz)
42+
0x75, 0x08, # Report Size (8)
43+
0x95, 0x04, # Report Count (4)
44+
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
45+
0xC0, # End Collection
46+
))
47+
48+
gamepad = usb_hid.Device(
49+
report_descriptor=GAMEPAD_REPORT_DESCRIPTOR,
50+
usage_page=0x01, # Generic Desktop Control
51+
usage=0x05, # Gamepad
52+
report_ids=(4,), # Descriptor uses report ID 4.
53+
in_report_lengths=(6,), # This gamepad sends 6 bytes in its report.
54+
out_report_lengths=(0,), # It does not receive any reports.
55+
)
56+
57+
usb_hid.enable(
58+
(usb_hid.Device.CONSUMER_CONTROL,
59+
gamepad)
60+
)
61+
62+
cc = ConsumerControl(usb_hid.devices)
63+
gp1 = gamepad(usb_hid.devices)
64+
gp2 = gamepad(usb_hid.devices)
65+
66+
i2c = busio.I2C(board.SCL, board.SDA)
67+
68+
adc = MCP3426(i2c)
69+
70+
mcp = MCP23008(i2c)
71+
72+
joystick_x_left = analogio.AnalogIn(board.GP26)
73+
joystick_y_left = analogio.AnalogIn(board.GP27)
74+
joystick_x_right = analogio.AnalogIn(board.GP28)
75+
joystick_y_right = analogio.AnalogIn(board.GP29)
76+
77+
keyboard = KMKKeyboard()
78+
79+
for pin in range(8):
80+
mcp.setup(pin, MCP23008.IN)
81+
82+
keyboard.col_pins = [mcp.get_pin(i) for i in range(3)]
83+
keyboard.row_pins = [mcp.get_pin(i + 3) for i in range(3)]
84+
85+
keyboard.keymap = [
86+
[
87+
[KC.MUTE, KC.VOLD, KC.VOLU], # Media controls: Mute, Volume Down, Volume Up
88+
[KC.PLAY_PAUSE, KC.PREV_TRACK, KC.NEXT_TRACK], # Media playback controls
89+
[KC.WEB_SEARCH, KC.NEW_TAB, KC.FIND], # Web/Browser shortcuts
90+
],
91+
[
92+
[KC.COPY, KC.PASTE, KC.CUT], # Editing controls
93+
[KC.UNDO, KC.REDO, KC.SELECT_ALL], # Editing shortcuts
94+
[KC.LCTRL, KC.LALT, KC.LSFT], # Modifier keys
95+
],
96+
[
97+
[KC.SCRN, KC.PRTSC, KC.CALC], # Screenshot and other utilities
98+
[KC.LGUI, KC.RGUI, KC.SPC], # GUI keys and space
99+
[KC.ESC, KC.TAB, KC.ENTER], # Control keys
100+
],
101+
]
102+
103+
keyboard.diode_orientation = DiodeOrientation.COL2ROW
104+
105+
def read_joystick(joystick):
106+
return int((joystick.value / 65535) * 255) - 127
107+
108+
def update_joystick():
109+
# Read the joystick values (left and right)
110+
x_left = read_joystick(joystick_x_left)
111+
y_left = read_joystick(joystick_y_left)
112+
x_right = read_joystick(joystick_x_right)
113+
y_right = read_joystick(joystick_y_right)
114+
115+
gp1.move_joysticks(
116+
x=x_left,
117+
y=y_left,
118+
)
119+
120+
gp2.move_joysticks(
121+
x=x_right,
122+
y=y_right,
123+
)
124+
125+
old_volume = 0
126+
127+
def update_volume():
128+
slider_0_value = adc.read_voltage(channel=0)
129+
volume_level = int((slider_0_value / 65535) * 100)
130+
# TODO: make this better
131+
if volume_level > old_volume:
132+
cc.send(ConsumerControlCode.VOLUME_INCREMENT)
133+
elif volume_level < old_volume:
134+
cc.send(ConsumerControlCode.VOLUME_DECREMENT)
135+
old_volume = volume_level
136+
137+
old_brightness = 0
138+
139+
def update_brightness():
140+
slider_1_value = adc.read_voltage(channel=1)
141+
brightness_level = int((slider_1_value / 65535) * 100)
142+
if brightness_level > old_brightness:
143+
cc.send(ConsumerControlCode.BRIGHTNESS_INCREMENT)
144+
elif brightness_level < old_brightness:
145+
cc.send(ConsumerControlCode.BRIGHTNESS_DECREMENT)
146+
old_brightness = brightness_level
147+
148+
pixel = neopixel.NeoPixel(board.GP2, 9, brightness=0.5, auto_write=True)
149+
150+
def fade_colors():
151+
colors = [
152+
(255, 0, 0), # Red
153+
(0, 255, 0), # Green
154+
(0, 0, 255), # Blue
155+
(255, 255, 0), # Yellow
156+
(0, 255, 255), # Cyan
157+
(255, 0, 255), # Magenta
158+
(255, 255, 255) # White
159+
]
160+
while True:
161+
for color in colors:
162+
for i in range(256):
163+
faded_color = tuple(int(c * i / 255) for c in color)
164+
pixel.fill(faded_color)
165+
time.sleep(0.01)
166+
167+
def update_inputs():
168+
update_joystick()
169+
update_volume()
170+
update_brightness()
171+
172+
if __name__ == '__main__':
173+
while True:
174+
update_inputs()
175+
keyboard.go()
176+
fade_colors()

hackpads/robopad/pcb/.gitignore

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# For PCBs designed using KiCad: https://www.kicad.org/
2+
# Format documentation: https://kicad.org/help/file-formats/
3+
4+
# Temporary files
5+
*.000
6+
*.bak
7+
*.bck
8+
*.kicad_pcb-bak
9+
*.kicad_sch-bak
10+
*-backups
11+
*.kicad_prl
12+
*.sch-bak
13+
*~
14+
_autosave-*
15+
*.tmp
16+
*-save.pro
17+
*-save.kicad_pcb
18+
fp-info-cache
19+
~*.lck
20+
\#auto_saved_files#
21+
22+
# Netlist files (exported from Eeschema)
23+
*.net
24+
25+
# Autorouter files (exported from Pcbnew)
26+
*.dsn
27+
*.ses
28+
29+
# Exported BOM files
30+
*.xml
31+
*.csv
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"EXTRA_LAYERS": "", "EXTEND_EDGE_CUT": false, "ALTERNATIVE_EDGE_CUT": false, "AUTO TRANSLATE": true, "AUTO FILL": true, "EXCLUDE DNP": false}

hackpads/robopad/pcb/fp-lib-table

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
(fp_lib_table
2+
(version 7)
3+
(lib (name "opl-seeed")(type "KiCad")(uri "${KIPRJMOD}/libraries/opl-seeed")(options "")(descr ""))
4+
(lib (name "digikey")(type "KiCad")(uri "${KIPRJMOD}/libraries/digikey")(options "")(descr ""))
5+
(lib (name "mouser")(type "KiCad")(uri "${KIPRJMOD}/libraries/mouser")(options "")(descr ""))
6+
)

0 commit comments

Comments
 (0)