forked from viernullvier/neogrid
Compare commits
2 commits
9c57c04a00
...
e59e2818bf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e59e2818bf | ||
|
|
d253c37220 |
6 changed files with 392 additions and 7 deletions
2
firmware/.gitignore
vendored
Normal file
2
firmware/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
micropython
|
||||||
|
pico-sdk
|
||||||
|
|
@ -5,13 +5,18 @@ Custom micropython build for the Olimex RP2350B-XL. Comes with some features ena
|
||||||
## build instructions
|
## build instructions
|
||||||
|
|
||||||
0. Make sure to have a compiler toolchain supporting the RP2 architecture installed (Mac: `brew install gcc-arm-embedded`)
|
0. Make sure to have a compiler toolchain supporting the RP2 architecture installed (Mac: `brew install gcc-arm-embedded`)
|
||||||
1. Clone the `micropython` repo
|
1. Clone *[pico-sdk](https://github.com/raspberrypi/pico-sdk)* (to this folder or somewhere else on your machine)
|
||||||
2. From within the `micropython` repo root, run `make -C mpy-cross`
|
2. Change to the *pico-sdk* folder and run `git submodule update --init`
|
||||||
3. Copy the `OLIMEX_PICO2_XL` folder to `micropython/ports/rp2/boards` if it doesn't exist; overwrite it if it does
|
3. Point an environment variable `PICO_SDK_PATH` to the *pico-sdk* folder
|
||||||
4. Change to the `boards/rp2` folder
|
4. Install *[picotool](https://github.com/raspberrypi/picotool)*
|
||||||
5. Run `make BOARD=OLIMEX_PICO2_XL submodules` to install dependencies
|
5. Clone the `micropython` repo (to this folder or somewhere else on your machine)
|
||||||
6. Run `make BOARD=OLIMEX_PICO2_XL clean` to remove previous build artefacts (if any)
|
6. From within the `micropython` repo root, run `make -C mpy-cross`
|
||||||
7. Run `make BOARD=OLIMEX_PICO2_XL` to build the firmware
|
7. Copy the `OLIMEX_PICO2_XL` folder to `micropython/ports/rp2/boards` if it doesn't exist; overwrite it if it does
|
||||||
|
8. Change to the `micropython/ports/rp2` folder
|
||||||
|
9. Run `make BOARD=OLIMEX_PICO2_XL submodules` to install dependencies
|
||||||
|
10. Run `make BOARD=OLIMEX_PICO2_XL clean` to remove previous build artefacts (if any)
|
||||||
|
11. Run `make BOARD=OLIMEX_PICO2_XL` to build the firmware
|
||||||
|
12. To rebuild: `make BOARD=OLIMEX_PICO2_XL clean && make BOARD=OLIMEX_PICO2_XL`
|
||||||
|
|
||||||
## flashing
|
## flashing
|
||||||
|
|
||||||
|
|
|
||||||
20
firmware/examples/nanogrid-drum-rack/README.md
Normal file
20
firmware/examples/nanogrid-drum-rack/README.md
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# MIDI Drum Rack example
|
||||||
|
|
||||||
|
Press to send a MIDI note in the range from C1 to D#2. Buttons are
|
||||||
|
ordered in the same way the pads are in Ableton Drum Rack: with the USB
|
||||||
|
port poiting from the player left-to-right, bottom-to-top.
|
||||||
|
|
||||||
|
|
||||||
|
## build
|
||||||
|
|
||||||
|
1. Make a micropython build with the libraries added:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd $MICROPYTHON_PATH/ports/rp2
|
||||||
|
make BOARD=OLIMEX_PICO2_XL clean && make V=1 BOARD=OLIMEX_PICO2_XL FROZEN_MANIFEST={path-to-neogrid-project}/firmware/examples/nanogrid-drum-rack/manifest.py
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Flush the board with the resulting uf2 (see
|
||||||
|
[firmware/README.md](/firmware/README.md))
|
||||||
|
|
||||||
|
3. Copy the `main.py` to the board
|
||||||
210
firmware/examples/nanogrid-drum-rack/main.py
Normal file
210
firmware/examples/nanogrid-drum-rack/main.py
Normal file
|
|
@ -0,0 +1,210 @@
|
||||||
|
# MicroPython USB MIDI example
|
||||||
|
#
|
||||||
|
# This example demonstrates creating a custom MIDI device.
|
||||||
|
#
|
||||||
|
# To run this example:
|
||||||
|
#
|
||||||
|
# 1. Make sure `usb-device-midi` is installed via: mpremote mip install usb-device-midi
|
||||||
|
#
|
||||||
|
# 2. Run the example via: mpremote run midi_example.py
|
||||||
|
#
|
||||||
|
# 3. mpremote will exit with an error after the previous step, because when the
|
||||||
|
# example runs the existing USB device disconnects and then re-enumerates with
|
||||||
|
# the MIDI interface present. At this point, the example is running.
|
||||||
|
#
|
||||||
|
# 4. To see output from the example, re-connect: mpremote connect PORTNAME
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# MIT license; Copyright (c) 2023-2024 Angus Gratton
|
||||||
|
import usb.device
|
||||||
|
from usb.device.midi import MIDIInterface
|
||||||
|
import time
|
||||||
|
|
||||||
|
from machine import Pin
|
||||||
|
|
||||||
|
import array, time
|
||||||
|
import rp2
|
||||||
|
|
||||||
|
NUM_COL = 4
|
||||||
|
NUM_ROW = 4
|
||||||
|
NUM_LEDS = NUM_COL * NUM_ROW
|
||||||
|
PIN_NUM = 16
|
||||||
|
brightness = 0.2
|
||||||
|
|
||||||
|
@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT, autopull=True, pull_thresh=24)
|
||||||
|
def ws2812():
|
||||||
|
T1 = 2
|
||||||
|
T2 = 5
|
||||||
|
T3 = 3
|
||||||
|
wrap_target()
|
||||||
|
label("bitloop")
|
||||||
|
out(x, 1) .side(1) [T3 - 1]
|
||||||
|
jmp(not_x, "do_zero") .side(0) [T1 - 1]
|
||||||
|
jmp("bitloop") .side(0) [T2 - 1]
|
||||||
|
label("do_zero")
|
||||||
|
nop() .side(1) [T2 - 1]
|
||||||
|
wrap()
|
||||||
|
|
||||||
|
def init_leds():
|
||||||
|
# Create the StateMachine with the ws2812 program, outputting on pin
|
||||||
|
sm = rp2.StateMachine(0, ws2812, freq=8_000_000, sideset_base=Pin(PIN_NUM))
|
||||||
|
|
||||||
|
# Start the StateMachine, it will wait for data on its FIFO.
|
||||||
|
sm.active(1)
|
||||||
|
|
||||||
|
# Display a pattern on the LEDs via an array of LED RGB values.
|
||||||
|
ar = array.array("I", [0 for _ in range(NUM_LEDS)])
|
||||||
|
|
||||||
|
##########################################################################
|
||||||
|
def show_all():
|
||||||
|
dimmer_ar = array.array("I", [0 for _ in range(NUM_LEDS)])
|
||||||
|
for i, c in enumerate(ar):
|
||||||
|
r = int(((c >> 8) & 0xFF) * brightness)
|
||||||
|
g = int(((c >> 16) & 0xFF) * brightness)
|
||||||
|
b = int((c & 0xFF) * brightness)
|
||||||
|
dimmer_ar[i] = (g<<16) + (r<<8) + b
|
||||||
|
sm.put(dimmer_ar, 8)
|
||||||
|
|
||||||
|
def set_led(i, color):
|
||||||
|
row = i // NUM_COL
|
||||||
|
col = i - row * NUM_COL
|
||||||
|
new_col = NUM_COL - col - 1
|
||||||
|
new_row = NUM_ROW - row - 1 if new_col % 2 == 0 else row
|
||||||
|
ar[new_col * NUM_ROW + new_row] = (color[1]<<16) + (color[0]<<8) + color[2]
|
||||||
|
|
||||||
|
return (set_led, show_all)
|
||||||
|
|
||||||
|
class MIDIExample(MIDIInterface):
|
||||||
|
# Very simple example event handler functions, showing how to receive note
|
||||||
|
# and control change messages sent from the host to the device.
|
||||||
|
#
|
||||||
|
# If you need to send MIDI data to the host, then it's fine to instantiate
|
||||||
|
# MIDIInterface class directly.
|
||||||
|
|
||||||
|
def on_open(self):
|
||||||
|
super().on_open()
|
||||||
|
print("Device opened by host")
|
||||||
|
|
||||||
|
def on_note_on(self, channel, pitch, vel):
|
||||||
|
print(f"RX Note On channel {channel} pitch {pitch} velocity {vel}")
|
||||||
|
|
||||||
|
def on_note_off(self, channel, pitch, vel):
|
||||||
|
print(f"RX Note Off channel {channel} pitch {pitch} velocity {vel}")
|
||||||
|
|
||||||
|
def on_control_change(self, channel, controller, value):
|
||||||
|
print(f"RX Control channel {channel} controller {controller} value {value}")
|
||||||
|
|
||||||
|
|
||||||
|
m = MIDIExample()
|
||||||
|
# Remove builtin_driver=True if you don't want the MicroPython serial REPL available.
|
||||||
|
usb.device.get().init(m, builtin_driver=True)
|
||||||
|
|
||||||
|
print("Waiting for USB host to configure the interface...")
|
||||||
|
|
||||||
|
while not m.is_open():
|
||||||
|
time.sleep_ms(100)
|
||||||
|
|
||||||
|
# TX constants
|
||||||
|
CHANNEL = 0
|
||||||
|
PITCH_C1 = 37
|
||||||
|
CONTROLLER = 64
|
||||||
|
|
||||||
|
control_val = 0
|
||||||
|
|
||||||
|
def init_btns():
|
||||||
|
row_pins = [
|
||||||
|
Pin(0, Pin.OUT),
|
||||||
|
Pin(1, Pin.OUT),
|
||||||
|
Pin(2, Pin.OUT),
|
||||||
|
Pin(3, Pin.OUT),
|
||||||
|
]
|
||||||
|
|
||||||
|
col_pins = [
|
||||||
|
Pin(32, Pin.IN, Pin.PULL_UP),
|
||||||
|
Pin(33, Pin.IN, Pin.PULL_UP),
|
||||||
|
Pin(34, Pin.IN, Pin.PULL_UP),
|
||||||
|
Pin(35, Pin.IN, Pin.PULL_UP),
|
||||||
|
]
|
||||||
|
|
||||||
|
pressed_keys = set()
|
||||||
|
|
||||||
|
# reset cols
|
||||||
|
for pin in row_pins:
|
||||||
|
pin.value(1)
|
||||||
|
|
||||||
|
def loop():
|
||||||
|
pressed_keys.clear()
|
||||||
|
for row, row_pin in enumerate(row_pins):
|
||||||
|
# set only active pin low, keep rest high
|
||||||
|
row_pin.value(0)
|
||||||
|
for col, col_pin in enumerate(col_pins):
|
||||||
|
key = (
|
||||||
|
(NUM_ROW - row - 1) +
|
||||||
|
(NUM_COL - col - 1) * NUM_ROW
|
||||||
|
)
|
||||||
|
if not col_pin.value():
|
||||||
|
pressed_keys.add(key)
|
||||||
|
row_pin.value(1)
|
||||||
|
return set(pressed_keys)
|
||||||
|
|
||||||
|
return loop
|
||||||
|
|
||||||
|
btn_loop = init_btns()
|
||||||
|
turned_on = set()
|
||||||
|
|
||||||
|
(set_led, show_all) = init_leds()
|
||||||
|
|
||||||
|
# for i in range(NUM_LEDS):
|
||||||
|
# set_led(i, CYAN)
|
||||||
|
# for j in range(NUM_LEDS):
|
||||||
|
# if i != j:
|
||||||
|
# set_led(j, BLACK)
|
||||||
|
# show_all()
|
||||||
|
# time.sleep(2)
|
||||||
|
#
|
||||||
|
|
||||||
|
CYAN = (0, 255, 255)
|
||||||
|
BLACK = (0, 0, 0)
|
||||||
|
|
||||||
|
print("Starting loop...")
|
||||||
|
while m.is_open():
|
||||||
|
time.sleep_ms(5)
|
||||||
|
# print(f"TX Note On channel {CHANNEL} pitch {PITCH_C1}")
|
||||||
|
# m.note_on(CHANNEL, PITCH_C1) # Velocity is an optional third argument
|
||||||
|
# time.sleep(0.5)
|
||||||
|
# print(f"TX Note Off channel {CHANNEL} pitch {PITCH_C1}")
|
||||||
|
# m.note_off(CHANNEL, PITCH_C1)
|
||||||
|
# time.sleep(1)
|
||||||
|
# print(f"TX Control channel {CHANNEL} controller {CONTROLLER} value {control_val}")
|
||||||
|
# m.control_change(CHANNEL, CONTROLLER, control_val)
|
||||||
|
# control_val += 1
|
||||||
|
# if control_val == 0x7F:
|
||||||
|
# control_val = 0
|
||||||
|
# time.sleep(1)
|
||||||
|
|
||||||
|
pressed = btn_loop()
|
||||||
|
|
||||||
|
to_turn_on = pressed.difference(turned_on)
|
||||||
|
to_turn_off = turned_on.difference(pressed)
|
||||||
|
|
||||||
|
if len(to_turn_on):
|
||||||
|
print("to_turn_on", to_turn_on)
|
||||||
|
|
||||||
|
if len(to_turn_off):
|
||||||
|
print("to_turn_off", to_turn_off)
|
||||||
|
|
||||||
|
|
||||||
|
for key in to_turn_on:
|
||||||
|
m.note_on(CHANNEL, PITCH_C1 + key - 1)
|
||||||
|
set_led(key, CYAN)
|
||||||
|
turned_on.add(key)
|
||||||
|
|
||||||
|
for key in to_turn_off:
|
||||||
|
m.note_off(CHANNEL, PITCH_C1 + key - 1)
|
||||||
|
turned_on.remove(key)
|
||||||
|
set_led(key, BLACK)
|
||||||
|
|
||||||
|
show_all()
|
||||||
|
|
||||||
|
print("USB host has reset device, example done.")
|
||||||
|
|
||||||
4
firmware/examples/nanogrid-drum-rack/manifest.py
Normal file
4
firmware/examples/nanogrid-drum-rack/manifest.py
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
metadata(version="0.1.0")
|
||||||
|
include("$(PORT_DIR)/boards/manifest.py")
|
||||||
|
require("usb-device")
|
||||||
|
require("usb-device-midi")
|
||||||
144
firmware/examples/nanogrid-lights-demo/main.py
Executable file
144
firmware/examples/nanogrid-lights-demo/main.py
Executable file
|
|
@ -0,0 +1,144 @@
|
||||||
|
# Example using PIO to drive a set of WS2812 LEDs.
|
||||||
|
|
||||||
|
import array, time
|
||||||
|
from machine import Pin
|
||||||
|
import rp2
|
||||||
|
|
||||||
|
# Configure the number of WS2812 LEDs.
|
||||||
|
NUM_LEDS = 16
|
||||||
|
PIN_NUM = 16
|
||||||
|
brightness = 0.2
|
||||||
|
|
||||||
|
@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT, autopull=True, pull_thresh=24)
|
||||||
|
def ws2812():
|
||||||
|
T1 = 2
|
||||||
|
T2 = 5
|
||||||
|
T3 = 3
|
||||||
|
wrap_target()
|
||||||
|
label("bitloop")
|
||||||
|
out(x, 1) .side(1) [T3 - 1]
|
||||||
|
jmp(not_x, "do_zero") .side(0) [T1 - 1]
|
||||||
|
jmp("bitloop") .side(0) [T2 - 1]
|
||||||
|
label("do_zero")
|
||||||
|
nop() .side(1) [T2 - 1]
|
||||||
|
wrap()
|
||||||
|
|
||||||
|
|
||||||
|
# Create the StateMachine with the ws2812 program, outputting on pin
|
||||||
|
sm = rp2.StateMachine(0, ws2812, freq=8_000_000, sideset_base=Pin(PIN_NUM))
|
||||||
|
|
||||||
|
# Start the StateMachine, it will wait for data on its FIFO.
|
||||||
|
sm.active(1)
|
||||||
|
|
||||||
|
# Display a pattern on the LEDs via an array of LED RGB values.
|
||||||
|
ar = array.array("I", [0 for _ in range(NUM_LEDS)])
|
||||||
|
|
||||||
|
##########################################################################
|
||||||
|
def pixels_show():
|
||||||
|
dimmer_ar = array.array("I", [0 for _ in range(NUM_LEDS)])
|
||||||
|
for i,c in enumerate(ar):
|
||||||
|
r = int(((c >> 8) & 0xFF) * brightness)
|
||||||
|
g = int(((c >> 16) & 0xFF) * brightness)
|
||||||
|
b = int((c & 0xFF) * brightness)
|
||||||
|
dimmer_ar[i] = (g<<16) + (r<<8) + b
|
||||||
|
sm.put(dimmer_ar, 8)
|
||||||
|
time.sleep_ms(10)
|
||||||
|
|
||||||
|
def pixels_set(i, color):
|
||||||
|
ar[i] = (color[1]<<16) + (color[0]<<8) + color[2]
|
||||||
|
|
||||||
|
def pixels_fill(color):
|
||||||
|
for i in range(len(ar)):
|
||||||
|
pixels_set(i, color)
|
||||||
|
|
||||||
|
def color_chase(color, wait):
|
||||||
|
for i in range(NUM_LEDS):
|
||||||
|
pixels_set(i, color)
|
||||||
|
time.sleep(wait)
|
||||||
|
pixels_show()
|
||||||
|
time.sleep(0.2)
|
||||||
|
|
||||||
|
def wheel(pos):
|
||||||
|
# Input a value 0 to 255 to get a color value.
|
||||||
|
# The colours are a transition r - g - b - back to r.
|
||||||
|
if pos < 0 or pos > 255:
|
||||||
|
return (0, 0, 0)
|
||||||
|
if pos < 85:
|
||||||
|
return (255 - pos * 3, pos * 3, 0)
|
||||||
|
if pos < 170:
|
||||||
|
pos -= 85
|
||||||
|
return (0, 255 - pos * 3, pos * 3)
|
||||||
|
pos -= 170
|
||||||
|
return (pos * 3, 0, 255 - pos * 3)
|
||||||
|
|
||||||
|
def rainbow_cycle(wait):
|
||||||
|
for j in range(255):
|
||||||
|
for i in range(NUM_LEDS):
|
||||||
|
rc_index = (i * 256 // NUM_LEDS) + j
|
||||||
|
pixels_set(i, wheel(rc_index & 255))
|
||||||
|
pixels_show()
|
||||||
|
time.sleep(wait)
|
||||||
|
|
||||||
|
BLACK = (0, 0, 0)
|
||||||
|
RED = (255, 0, 0)
|
||||||
|
YELLOW = (255, 150, 0)
|
||||||
|
GREEN = (0, 255, 0)
|
||||||
|
CYAN = (0, 255, 255)
|
||||||
|
BLUE = (0, 0, 255)
|
||||||
|
PURPLE = (180, 0, 255)
|
||||||
|
WHITE = (255, 255, 255)
|
||||||
|
COLORS = (BLACK, RED, YELLOW, GREEN, CYAN, BLUE, PURPLE, WHITE)
|
||||||
|
|
||||||
|
print("fills")
|
||||||
|
for color in COLORS:
|
||||||
|
pixels_fill(color)
|
||||||
|
pixels_show()
|
||||||
|
time.sleep(0.2)
|
||||||
|
|
||||||
|
print("chases")
|
||||||
|
for color in COLORS:
|
||||||
|
color_chase(color, 0.01)
|
||||||
|
|
||||||
|
print("rainbow")
|
||||||
|
rainbow_cycle(0)
|
||||||
|
|
||||||
|
row_pins = [
|
||||||
|
Pin(0, Pin.OUT),
|
||||||
|
Pin(1, Pin.OUT),
|
||||||
|
Pin(2, Pin.OUT),
|
||||||
|
Pin(3, Pin.OUT),
|
||||||
|
]
|
||||||
|
|
||||||
|
col_pins = [
|
||||||
|
Pin(32, Pin.IN, Pin.PULL_UP),
|
||||||
|
Pin(33, Pin.IN, Pin.PULL_UP),
|
||||||
|
Pin(34, Pin.IN, Pin.PULL_UP),
|
||||||
|
Pin(35, Pin.IN, Pin.PULL_UP),
|
||||||
|
]
|
||||||
|
|
||||||
|
# reset cols
|
||||||
|
|
||||||
|
for pin in row_pins:
|
||||||
|
pin.value(1)
|
||||||
|
|
||||||
|
|
||||||
|
row_count = len(row_pins)
|
||||||
|
col_count = len(col_pins)
|
||||||
|
key_num = 0
|
||||||
|
pressed_keys = set()
|
||||||
|
while True:
|
||||||
|
pressed_keys.clear()
|
||||||
|
for row, row_pin in enumerate(row_pins):
|
||||||
|
# set only active pin low, keep rest high
|
||||||
|
row_pin.value(0)
|
||||||
|
# for col, col_pin in enumerate(col_pins):
|
||||||
|
# col_pin.value(1)
|
||||||
|
# time.sleep_ms(10)
|
||||||
|
for col, col_pin in enumerate(col_pins):
|
||||||
|
key = col_count * row + col + 1
|
||||||
|
if not col_pin.value():
|
||||||
|
pressed_keys.add(key)
|
||||||
|
# print("key", key, "pin", col, ":", row, col_pin.value())
|
||||||
|
row_pin.value(1)
|
||||||
|
print("pressed", pressed_keys)
|
||||||
|
time.sleep_ms(50)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue