Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions hackpads/robopad/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**/.DS_Store
61 changes: 61 additions & 0 deletions hackpads/robopad/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Robopad

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.

## Features

- 2 Joysticks
- 2 Sliders
- 9 Key Switches
- 9 LEDs

## CAD Model

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.

![cad](assets/cad.png)

## PCB

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.

Schematic:\
![schematic](assets/schematic.png)

PCB:\
![pcb](assets/pcb.png)

## Firmware

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.

## BOM

Stuff from Hackclub:

- 9x Cherry MX Switches
- 9x DSA Keycaps
- 4x M3x5x4 Heatset inserts
- 4X M3x16mm SHCS Bolts
- 9x 1N4148 DO-35 Diodes.
- 9x SK6812MINI-E LEDs
- 1x XIAO RP2040
- 1x Case (2 printed parts)

Stuff from the $20 Grant:

- 1x PCB (black from JLCPCB)
- $2.00 fabrication + $1.50 shipping + $0.29 tax = $3.79
- 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))
- $2.92 (2 in one pack)
- 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))
- $1.62 \* 2 + $3.66 shipping = $6.90
- 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))
- $2.38
- 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))
- $2.87

Tax from Aliexpress: ($6.90 + $2.38 + $2.87 + $2.92) \* 0.0825 = $1.52
Total: $3.79 + $6.90 + $2.38 + $2.87 + $2.92 + $1.52 + $3.79 = $23.17

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.
Binary file added hackpads/robopad/assets/cad.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added hackpads/robopad/assets/pcb.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added hackpads/robopad/assets/schematic.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions hackpads/robopad/cad/assembled.scad
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
use <case_bottom.scad>
use <case_top.scad>

translate([0, 0, 5]) case_top();
translate([0, 0, -1.5]) case_bottom();
color("green") import("./components/pcb.stl");
18 changes: 18 additions & 0 deletions hackpads/robopad/cad/case_bottom.scad
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
include <BOSL2/std.scad>

module case_bottom() {
overlapBuffer = 0.1;
difference() {
cuboid([120, 120, 13], rounding=1, except=TOP);
translate([0, 0, 3]) cube([101, 101, 10 + overlapBuffer], center=true);
translate([55, -55, 0]) cylinder(r=1.7, h=13+overlapBuffer, center=true);
translate([-55, -55, 0]) cylinder(r=1.7, h=13+overlapBuffer, center=true);
translate([-55, 55, 0]) cylinder(r=1.7, h=13+overlapBuffer, center=true);
translate([55, 55, 0]) cylinder(r=1.7, h=13+overlapBuffer, center=true);

translate([0, 55, -1.5]) cuboid(size=[10, 10+overlapBuffer, 4], rounding=1, except=[FRONT, BACK]);
}
}

translate([0, 0, -1.5]) case_bottom();
color("green") import("./components/pcb.stl");
33 changes: 33 additions & 0 deletions hackpads/robopad/cad/case_top.scad
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
include <BOSL2/std.scad>

module case_top() {
overlapBuffer = 0.1;
height = 1.5;

difference() {
union() {
cuboid([120, 120, height], rounding=1, except=BOTTOM);
translate([-30, 30, height + 2.5]) cylinder(r=17, h=height + 5, center=true);
translate([30, 30, height + 2.5]) cylinder(r=17, h=height + 5, center=true);
}

translate([-30, 30, 0]) cylinder(r=15, h=30+overlapBuffer, center=true);
translate([30, 30, 0]) cylinder(r=15, h=30+overlapBuffer, center=true);
translate([38, -17.5, 0]) cube([10, 47, height + overlapBuffer], center=true);
translate([-37, -17.5, 0]) cube([10, 47, height + overlapBuffer], center=true);
for (i=[0:1:2]) {
for (j=[-2:1:0]) {
translate([i * 19.05 - 18.1706, j * 19.05 + 3.5719, 0]) cube([14, 14, height + overlapBuffer], center=true);
}
}

translate([55, -55, 0]) cylinder(r=1.7, h=13+overlapBuffer, center=true);
translate([-55, -55, 0]) cylinder(r=1.7, h=13+overlapBuffer, center=true);
translate([-55, 55, 0]) cylinder(r=1.7, h=13+overlapBuffer, center=true);
translate([55, 55, 0]) cylinder(r=1.7, h=13+overlapBuffer, center=true);
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);
}
}

translate([0, 0, 5]) case_top();
color("green") import("./components/pcb.stl");
Binary file added hackpads/robopad/cad/components/pcb.stl
Binary file not shown.
2 changes: 2 additions & 0 deletions hackpads/robopad/firmware/libraries.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
https://github.com/coburnw/MCP342x
adafruit hid
176 changes: 176 additions & 0 deletions hackpads/robopad/firmware/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import board
import busio
import analogio
import neopixel

from adafruit_mcp3426 import MCP3426
from adafruit_mcp23008 import MCP23008
from kmk.kmk_keyboard import KMKKeyboard
from kmk.keys import KC
from kmk.scanners import DiodeOrientation

import usb_hid
from adafruit_hid.consumer_control import ConsumerControl
from adafruit_hid.consumer_control_code import ConsumerControlCode

import usb_hid

# Source: Adafruit
# TODO: Test with mac to find the correct report descriptor
# This is only one example of a gamepad report descriptor,
# and may not suit your needs.
GAMEPAD_REPORT_DESCRIPTOR = bytes((
0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
0x09, 0x05, # Usage (Game Pad)
0xA1, 0x01, # Collection (Application)
0x85, 0x04, # Report ID (4)
0x05, 0x09, # Usage Page (Button)
0x19, 0x01, # Usage Minimum (Button 1)
0x29, 0x10, # Usage Maximum (Button 16)
0x15, 0x00, # Logical Minimum (0)
0x25, 0x01, # Logical Maximum (1)
0x75, 0x01, # Report Size (1)
0x95, 0x10, # Report Count (16)
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
0x15, 0x81, # Logical Minimum (-127)
0x25, 0x7F, # Logical Maximum (127)
0x09, 0x30, # Usage (X)
0x09, 0x31, # Usage (Y)
0x09, 0x32, # Usage (Z)
0x09, 0x35, # Usage (Rz)
0x75, 0x08, # Report Size (8)
0x95, 0x04, # Report Count (4)
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, # End Collection
))

gamepad = usb_hid.Device(
report_descriptor=GAMEPAD_REPORT_DESCRIPTOR,
usage_page=0x01, # Generic Desktop Control
usage=0x05, # Gamepad
report_ids=(4,), # Descriptor uses report ID 4.
in_report_lengths=(6,), # This gamepad sends 6 bytes in its report.
out_report_lengths=(0,), # It does not receive any reports.
)

usb_hid.enable(
(usb_hid.Device.CONSUMER_CONTROL,
gamepad)
)

cc = ConsumerControl(usb_hid.devices)
gp1 = gamepad(usb_hid.devices)
gp2 = gamepad(usb_hid.devices)

i2c = busio.I2C(board.SCL, board.SDA)

adc = MCP3426(i2c)

mcp = MCP23008(i2c)

joystick_x_left = analogio.AnalogIn(board.GP26)
joystick_y_left = analogio.AnalogIn(board.GP27)
joystick_x_right = analogio.AnalogIn(board.GP28)
joystick_y_right = analogio.AnalogIn(board.GP29)

keyboard = KMKKeyboard()

for pin in range(8):
mcp.setup(pin, MCP23008.IN)

keyboard.col_pins = [mcp.get_pin(i) for i in range(3)]
keyboard.row_pins = [mcp.get_pin(i + 3) for i in range(3)]

keyboard.keymap = [
[
[KC.MUTE, KC.VOLD, KC.VOLU], # Media controls: Mute, Volume Down, Volume Up
[KC.PLAY_PAUSE, KC.PREV_TRACK, KC.NEXT_TRACK], # Media playback controls
[KC.WEB_SEARCH, KC.NEW_TAB, KC.FIND], # Web/Browser shortcuts
],
[
[KC.COPY, KC.PASTE, KC.CUT], # Editing controls
[KC.UNDO, KC.REDO, KC.SELECT_ALL], # Editing shortcuts
[KC.LCTRL, KC.LALT, KC.LSFT], # Modifier keys
],
[
[KC.SCRN, KC.PRTSC, KC.CALC], # Screenshot and other utilities
[KC.LGUI, KC.RGUI, KC.SPC], # GUI keys and space
[KC.ESC, KC.TAB, KC.ENTER], # Control keys
],
]

keyboard.diode_orientation = DiodeOrientation.COL2ROW

def read_joystick(joystick):
return int((joystick.value / 65535) * 255) - 127

def update_joystick():
# Read the joystick values (left and right)
x_left = read_joystick(joystick_x_left)
y_left = read_joystick(joystick_y_left)
x_right = read_joystick(joystick_x_right)
y_right = read_joystick(joystick_y_right)

gp1.move_joysticks(
x=x_left,
y=y_left,
)

gp2.move_joysticks(
x=x_right,
y=y_right,
)

old_volume = 0

def update_volume():
slider_0_value = adc.read_voltage(channel=0)
volume_level = int((slider_0_value / 65535) * 100)
# TODO: make this better
if volume_level > old_volume:
cc.send(ConsumerControlCode.VOLUME_INCREMENT)
elif volume_level < old_volume:
cc.send(ConsumerControlCode.VOLUME_DECREMENT)
old_volume = volume_level

old_brightness = 0

def update_brightness():
slider_1_value = adc.read_voltage(channel=1)
brightness_level = int((slider_1_value / 65535) * 100)
if brightness_level > old_brightness:
cc.send(ConsumerControlCode.BRIGHTNESS_INCREMENT)
elif brightness_level < old_brightness:
cc.send(ConsumerControlCode.BRIGHTNESS_DECREMENT)
old_brightness = brightness_level

pixel = neopixel.NeoPixel(board.GP2, 9, brightness=0.5, auto_write=True)

def fade_colors():
colors = [
(255, 0, 0), # Red
(0, 255, 0), # Green
(0, 0, 255), # Blue
(255, 255, 0), # Yellow
(0, 255, 255), # Cyan
(255, 0, 255), # Magenta
(255, 255, 255) # White
]
while True:
for color in colors:
for i in range(256):
faded_color = tuple(int(c * i / 255) for c in color)
pixel.fill(faded_color)
time.sleep(0.01)

def update_inputs():
update_joystick()
update_volume()
update_brightness()

if __name__ == '__main__':
while True:
update_inputs()
keyboard.go()
fade_colors()
31 changes: 31 additions & 0 deletions hackpads/robopad/pcb/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# For PCBs designed using KiCad: https://www.kicad.org/
# Format documentation: https://kicad.org/help/file-formats/

# Temporary files
*.000
*.bak
*.bck
*.kicad_pcb-bak
*.kicad_sch-bak
*-backups
*.kicad_prl
*.sch-bak
*~
_autosave-*
*.tmp
*-save.pro
*-save.kicad_pcb
fp-info-cache
~*.lck
\#auto_saved_files#

# Netlist files (exported from Eeschema)
*.net

# Autorouter files (exported from Pcbnew)
*.dsn
*.ses

# Exported BOM files
*.xml
*.csv
1 change: 1 addition & 0 deletions hackpads/robopad/pcb/fabrication-toolkit-options.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"EXTRA_LAYERS": "", "EXTEND_EDGE_CUT": false, "ALTERNATIVE_EDGE_CUT": false, "AUTO TRANSLATE": true, "AUTO FILL": true, "EXCLUDE DNP": false}
6 changes: 6 additions & 0 deletions hackpads/robopad/pcb/fp-lib-table
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
(fp_lib_table
(version 7)
(lib (name "opl-seeed")(type "KiCad")(uri "${KIPRJMOD}/libraries/opl-seeed")(options "")(descr ""))
(lib (name "digikey")(type "KiCad")(uri "${KIPRJMOD}/libraries/digikey")(options "")(descr ""))
(lib (name "mouser")(type "KiCad")(uri "${KIPRJMOD}/libraries/mouser")(options "")(descr ""))
)
Loading