├── .gitignore
├── LICENSE.TXT
├── README.md
├── larger-tricks
├── README.md
├── audiomixer_demo.py
├── audiomixer_demo_i2s.py
├── beatfader.py
├── beatfader_i2s.py
├── beatfader_wavs
│ ├── amen_22k16b_160bpm.wav
│ ├── dnb21580_22k16b_160bpm.wav
│ ├── drumloopA_22k16b_160bpm.wav
│ └── femvoc_330662_22k16b_160bpm.wav
├── beatslicer_idea.py
├── bmps
│ ├── emoji_spritesheet_27x2_28x28-1.bmp
│ ├── emoji_spritesheet_27x2_28x28-2.bmp
│ ├── emoji_spritesheet_27x2_28x28-3.bmp
│ ├── emoji_spritesheet_27x2_28x28.bmp
│ ├── emoji_spritesheet_27x7_28x28.bmp
│ └── fireworks_spritesheet.bmp
├── bouncy_balls_vectorio.py
├── breakbeat_sampleplayer.py
├── breakbeat_wav
│ ├── amen1_22k_s16.wav
│ ├── amen2_22k_s16.wav
│ ├── amen3_22k_s16.wav
│ ├── amen4_22k_s16.wav
│ ├── amen5_22k_s16.wav
│ ├── amen6_22k_s16.wav
│ ├── amen7_22k_s16.wav
│ ├── amen8_22k_s16.wav
│ ├── amenfull_22k_s16.wav
│ └── ohohoh2.wav
├── chikkenplayer.py
├── chikkenplayer_wav
│ ├── chikken1_161_22k16b.wav
│ ├── chikken2_161_22k16b.wav
│ ├── chikken3_161_22k16b.wav
│ ├── chikken4_161_22k16b.wav
│ ├── chikken5_161_22k16b.wav
│ └── chikken6_161_22k16b.wav
├── cpx_midi_controller.py
├── docs
│ ├── breakbeat_sampleplayer.fzz
│ └── breakbeat_sampleplayer_wiring.png
├── dvdlogo_vectorio_code.py
├── emoji_flipper.py
├── eyeballs
│ ├── code.py
│ ├── imgs
│ │ ├── eye0_ball2.bmp
│ │ ├── eye0_iris0.bmp
│ │ ├── eyelid_spritesheet.bmp
│ │ └── eyelid_spritesheet2.bmp
│ ├── qteye.py
│ ├── qteye_blink_esp32s3.py
│ ├── qteye_blink_qualia.py
│ └── qteye_person_sensor.py
├── fireworks_sprites.py
├── i2s_sdcard_pico.py
├── midi_forward.py
├── pidaydrummachine.py
├── pidaydrummachine_wavs
│ ├── 544413_punch_22k16b.wav
│ ├── 544587_splash1a_22k16b.wav
│ ├── 544682_snare_22k16b.wav
│ ├── 544684_dubhat1_22k16b.wav
│ └── 544694_toggle_22k16b.wav
├── pidaydrummachine_wiring.fzz
├── pidaydrummachine_wiring.png
├── pin_state.py
├── robust_keyboard.py
├── sdcard_max_wavs
│ ├── code.py
│ └── sine_wavs
│ │ ├── 01.wav
│ │ ├── 02.wav
│ │ ├── 03.wav
│ │ ├── 04.wav
│ │ ├── 05.wav
│ │ ├── 06.wav
│ │ ├── 07.wav
│ │ ├── 08.wav
│ │ ├── 09.wav
│ │ ├── 10.wav
│ │ ├── 11.wav
│ │ ├── 12.wav
│ │ ├── 13.wav
│ │ ├── 14.wav
│ │ ├── 15.wav
│ │ ├── 16.wav
│ │ ├── 17.wav
│ │ ├── 18.wav
│ │ ├── 19.wav
│ │ ├── 20.wav
│ │ ├── 21.wav
│ │ ├── 22.wav
│ │ ├── 23.wav
│ │ ├── 24.wav
│ │ ├── 25.wav
│ │ └── make_wavs.sh
├── vectorio_rotate_example.py
└── wav
│ ├── drumsacuff_22k_s16.wav
│ ├── laser2.wav
│ ├── laser20.wav
│ ├── silence-2sec.wav
│ ├── snowpeaks_22k_s16.wav
│ └── vocalchops476663_22k_128k.mp3
└── synthio
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | *.mpy
2 | .idea
3 | __pycache__
4 | _build
5 | *.pyc
6 | .env
7 | bundles
8 | *.DS_Store
9 | .eggs
10 |
--------------------------------------------------------------------------------
/LICENSE.TXT:
--------------------------------------------------------------------------------
1 |
2 | The MIT License (MIT)
3 |
4 | Copyright (c) 2021 Tod Kurt
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # circuitpython-tricks
3 |
4 | A small list of tips & tricks I find myself needing when working with CircuitPython.
5 | I find these examples useful when picking up a new project and I just want some boilerplate to get started.
6 | Also see the [circuitpython-tricks/larger-tricks](larger-tricks) directory for additional ideas.
7 |
8 | An older version of this page is a [Learn Guide on Adafruit](https://learn.adafruit.com/todbot-circuitpython-tricks?view=all) too!
9 |
10 | If you're new to CircuitPython overall, there's no single reference, but:
11 | - [The Python Tutorial](https://docs.python.org/3/tutorial/) on Python.org,
12 | since "CircuitPython is Python" mostly. (approx. Python 3.4)
13 | - [CircuitPython API reference](https://docs.circuitpython.org/en/latest/docs/), particularly the ["Core Modules > Modules" section](https://docs.circuitpython.org/en/latest/shared-bindings/index.html#modules) in the left sidebar
14 | - for compiled-in libraries like `displayio`, `usb`, `audioio`, `ulab.numpy`
15 | - [Pure-Python libraries in Adafruit Library Bundle](https://github.com/adafruit/Adafruit_CircuitPython_Bundle) for [drivers](https://github.com/adafruit/Adafruit_CircuitPython_Bundle/tree/main/libraries/drivers) & [helpers](https://github.com/adafruit/Adafruit_CircuitPython_Bundle/tree/main/libraries/helpers) libraries like `board`, `neopixel` & `ble`
16 | - and [CircuitPython Essentials Learn Guide](https://learn.adafruit.com/circuitpython-essentials) of course
17 |
18 |
19 | Table of Contents
20 | =================
21 |
22 | But it's probably easiest to do a Cmd-F/Ctrl-F find on keyword of idea you want.
23 |
24 | * [Inputs](#inputs)
25 | * [Read a digital input as a Button](#read-a-digital-input-as-a-button)
26 | * [Read a Potentiometer](#read-a-potentiometer)
27 | * [Read a Touch Pin / Capsense](#read-a-touch-pin--capsense)
28 | * [Read a Rotary Encoder](#read-a-rotary-encoder)
29 | * [Debounce a pin / button](#debounce-a-pin--button)
30 | * [Detect button double-click](#detect-button-double-click)
31 | * [Set up and debounce a list of pins](#set-up-and-debounce-a-list-of-pins)
32 | * [Outputs](#outputs)
33 | * [Output HIGH / LOW on a pin (like an LED)](#output-high--low-on-a-pin-like-an-led)
34 | * [Output Analog value on a DAC pin](#output-analog-value-on-a-dac-pin)
35 | * [Output a "Analog" value on a PWM pin](#output-a-analog-value-on-a-pwm-pin)
36 | * [Control Neopixel / WS2812 LEDs](#control-neopixel--ws2812-leds)
37 | * [Control a servo, with animation list](#control-a-servo-with-animation-list)
38 | * [Neopixels / Dotstars](#neopixels--dotstars)
39 | * [Light each LED in order](#light-each-led-in-order)
40 | * [Moving rainbow on built-in board.NEOPIXEL](#moving-rainbow-on-built-in-boardneopixel)
41 | * [Make moving rainbow gradient across LED strip](#make-moving-rainbow-gradient-across-led-strip)
42 | * [Fade all LEDs by amount for chase effects](#fade-all-leds-by-amount-for-chase-effects)
43 | * [Audio](#audio)
44 | * [Making simple tones](#making-simple-tones)
45 | * [Play a WAV file](#play-a-wav-file)
46 | * [Audio out using PWM](#audio-out-using-pwm)
47 | * [Audio out using DAC](#audio-out-using-dac)
48 | * [Audio out using I2S](#audio-out-using-i2s)
49 | * [Use audiomixer to prevent audio crackles](#use-audiomixer-to-prevent-audio-crackles)
50 | * [Play multiple sounds with audiomixer](#play-multiple-sounds-with-audiomixer)
51 | * [Playing MP3 files](#playing-mp3-files)
52 | * [USB](#usb)
53 | * [Rename CIRCUITPY drive to something new](#rename-circuitpy-drive-to-something-new)
54 | * [Detect if USB is connected or not](#detect-if-usb-is-connected-or-not)
55 | * [Get CIRCUITPY disk size and free space](#get-circuitpy-disk-size-and-free-space)
56 | * [Programmatically reset to UF2 bootloader](#programmatically-reset-to-uf2-bootloader)
57 | * [USB Serial](#usb-serial)
58 | * [Print to USB Serial](#print-to-usb-serial)
59 | * [Read user input from USB Serial, blocking](#read-user-input-from-usb-serial-blocking)
60 | * [Read user input from USB Serial, non-blocking (mostly)](#read-user-input-from-usb-serial-non-blocking-mostly)
61 | * [Read keys from USB Serial](#read-keys-from-usb-serial)
62 | * [Read user input from USB serial, non-blocking](#read-user-input-from-usb-serial-non-blocking)
63 | * [USB MIDI](#usb-midi)
64 | * [Sending MIDI with adafruit_midi](#sending-midi-with-adafruit_midi)
65 | * [Sending MIDI with bytearray](#sending-midi-with-bytearray)
66 | * [MIDI over Serial UART](#midi-over-serial-uart)
67 | * [Receiving MIDI](#receiving-midi)
68 | * [Receiving MIDI USB and MIDI Serial UART together](#receiving-midi-usb-and-midi-serial-uart-together)
69 | * [Enable USB MIDI in boot.py (for ESP32-S2 and STM32F4)](#enable-usb-midi-in-bootpy-for-esp32-s2-and-stm32f4)
70 | * [WiFi / Networking](#wifi--networking)
71 | * [Scan for WiFi Networks, sorted by signal strength](#scan-for-wifi-networks-sorted-by-signal-strength)
72 | * [Join WiFi network with highest signal strength](#join-wifi-network-with-highest-signal-strength)
73 | * [Ping an IP address](#ping-an-ip-address)
74 | * [Get IP address of remote host](#get-ip-address-of-remote-host)
75 | * [Fetch a JSON file](#fetch-a-json-file)
76 | * [Serve a webpage via HTTP](#serve-a-webpage-via-http)
77 | * [Set RTC time from NTP](#set-rtc-time-from-ntp)
78 | * [Set RTC time from time service](#set-rtc-time-from-time-service)
79 | * [What the heck is settings.toml?](#what-the-heck-is-settingstoml)
80 | * [What the heck is secrets.py?](#what-the-heck-is-secretspy)
81 | * [Displays (LCD / OLED / E-Ink) and displayio](#displays-lcd--oled--e-ink-and-displayio)
82 | * [Get default display and change display rotation](#get-default-display-and-change-display-rotation)
83 | * [Display an image](#display-an-image)
84 | * [Display background bitmap](#display-background-bitmap)
85 | * [Image slideshow](#image-slideshow)
86 | * [Dealing with E-Ink "Refresh Too Soon" error](#dealing-with-e-ink-refresh-too-soon-error)
87 | * [Turn off REPL on built-in display](#turn-off-repl-on-built-in-display)
88 | * [I2C](#i2c)
89 | * [Scan I2C bus for devices](#scan-i2c-bus-for-devices)
90 | * [Speed up I2C bus](#speed-up-i2c-bus)
91 | * [Timing](#timing)
92 | * [Measure how long something takes](#measure-how-long-something-takes)
93 | * [More accurate timing with ticks_ms(), like Arduino millis()](#more-accurate-timing-with-ticks_ms-like-arduino-millis)
94 | * [Control garbage collection for reliable timing](#control-garbage-collection-for-reliable-timing)
95 | * [Converting milliseconds to seconds: 0.004 * 1000 != 4, sometimes](#converting-milliseconds-to-seconds-0004--1000--4-sometimes)
96 | * [Board Info](#board-info)
97 | * [Get CPU speed (and set it!)](#get-cpu-speed-and-set-it)
98 | * [Display amount of free RAM](#display-amount-of-free-ram)
99 | * [Show microcontroller.pin to board mappings](#show-microcontrollerpin-to-board-mappings)
100 | * [Determine which board you're on](#determine-which-board-youre-on)
101 | * [Support multiple boards with one code.py](#support-multiple-boards-with-one-codepy)
102 | * [Computery Tasks](#computery-tasks)
103 | * [Formatting strings](#formatting-strings)
104 | * [Formatting strings with f-strings](#formatting-strings-with-f-strings)
105 | * [Using regular expressions to "findall" strings](#using-regular-expressions-to-findall-strings)
106 | * [Make and use a config file](#make-and-use-a-config-file)
107 | * [Run different code.py on startup](#run-different-codepy-on-startup)
108 | * [Coding Techniques](#coding-techniques)
109 | * [Map an input range to an output range](#map-an-input-range-to-an-output-range)
110 | * [Constrain an input to a min/max](#constrain-an-input-to-a-minmax)
111 | * [Turn a momentary value into a toggle](#turn-a-momentary-value-into-a-toggle)
112 | * [Do something every N seconds without sleep()](#do-something-every-n-seconds-without-sleep)
113 | * [System error handling](#system-error-handling)
114 | * [Preventing Ctrl-C from stopping the program](#preventing-ctrl-c-from-stopping-the-program)
115 | * [Prevent auto-reload when CIRCUITPY is touched](#prevent-auto-reload-when-circuitpy-is-touched)
116 | * [Raspberry Pi Pico boot.py Protection](#raspberry-pi-pico-bootpy-protection)
117 | * [Using the REPL](#using-the-repl)
118 | * [Display built-in modules / libraries](#display-built-in-modules--libraries)
119 | * [Turn off built-in display to speed up REPL printing](#turn-off-built-in-display-to-speed-up-repl-printing)
120 | * [Useful REPL one-liners](#useful-repl-one-liners)
121 | * [Python tricks](#python-tricks)
122 | * [Create list with elements all the same value](#create-list-with-elements-all-the-same-value)
123 | * [Convert RGB tuples to int and back again](#convert-rgb-tuples-to-int-and-back-again)
124 | * [Storing multiple values per list entry](#storing-multiple-values-per-list-entry)
125 | * [Python info](#python-info)
126 | * [Display which (not built-in) libraries have been imported](#display-which-not-built-in-libraries-have-been-imported)
127 | * [List names of all global variables](#list-names-of-all-global-variables)
128 | * [Display the running CircuitPython release](#display-the-running-circuitpython-release)
129 | * [Host-side tasks](#host-side-tasks)
130 | * [Installing CircuitPython libraries](#installing-circuitpython-libraries)
131 | * [Installing libraries with circup](#installing-libraries-with-circup)
132 | * [Copying libraries by hand with cp](#copying-libraries-by-hand-with-cp)
133 | * [Preparing images for CircuitPython](#preparing-images-for-circuitpython)
134 | * [Online](#online)
135 | * [Command-line: using ImageMagick](#command-line-using-imagemagick)
136 | * [Command-line: using GraphicsMagick](#command-line-using-graphicsmagick)
137 | * [Making images smaller or for E-Ink displays](#making-images-smaller-or-for-e-ink-displays)
138 | * [NodeJs: using gm](#nodejs-using-gm)
139 | * [Python: using PIL / pillow](#python-using-pil--pillow)
140 | * [Preparing audio files for CircuitPython](#preparing-audio-files-for-circuitpython)
141 | * [WAV files](#wav-files)
142 | * [MP3 files](#mp3-files)
143 | * [Getting sox](#getting-sox)
144 | * [Circup hacks](#circup-hacks)
145 | * [Finding where circup stores its files](#finding-where-circup-stores-its-files)
146 | * [Building CircuitPython](#building-circuitpython)
147 | * [About this guide](#about-this-guide)
148 |
149 |
150 | ## Inputs
151 |
152 | ### Read a digital input as a Button
153 |
154 | ```py
155 | import board
156 | from digitalio import DigitalInOut, Pull
157 | button = DigitalInOut(board.D3) # defaults to input
158 | button.pull = Pull.UP # turn on internal pull-up resistor
159 | print(button.value) # False == pressed
160 | ```
161 |
162 | Can also do:
163 |
164 | ```py
165 | import time, board, digitalio
166 | button = digitalio.DigitalInOut(board.D3)
167 | button.switch_to_input(digitalio.Pull.UP)
168 | while True:
169 | print("button pressed:", button.value == False) # False == pressed
170 | time.sleep(0.1)
171 | ```
172 |
173 | But you probably want to use `keypad` to get debouncing and press/release events.
174 | You can use it for a single button!
175 |
176 | ```py
177 | import board, keypad
178 | keys = keypad.Keys((board.D3,), value_when_pressed=False, pull=True)
179 | while True:
180 | if key := keys.events.get():
181 | if key.pressed:
182 | print("pressed key!")
183 |
184 | ```
185 | Note: be sure to add the comma when using a single button (e.g. `(board.D3,)`)
186 |
187 |
188 | ### Read a Potentiometer
189 |
190 | ```py
191 | import board
192 | import analogio
193 | potknob = analogio.AnalogIn(board.A1)
194 | position = potknob.value # ranges from 0-65535
195 | pos = potknob.value // 256 # make 0-255 range
196 | ```
197 |
198 | Note: While `AnalogIn.value` is 16-bit (0-65535) corresponding to 0 V to 3.3V,
199 | the MCU ADCs can have limitations in resolution and voltage range.
200 | This reduces what CircuitPython sees.
201 | For example, the ESP32 ADCs are 12-bit w/ approx 0.1 V to 2.5 V range
202 | (e.g. `value` goes from around 200 to 50,000, in steps of 16)
203 |
204 | ### Read a Touch Pin / Capsense
205 |
206 | ```py
207 | import touchio
208 | import board
209 | touch_pin = touchio.TouchIn(board.GP6)
210 | # on Pico / RP2040, need 1M pull-down on each input
211 | if touch_pin.value:
212 | print("touched!")
213 | ```
214 |
215 | You can also get an "analog" touch value with `touch_pin.raw_value` to do
216 | basic proximity detection or even [theremin-like behavior](https://gist.github.com/todbot/bb4ec9c509f8c301e4787e5cb26ec870).
217 |
218 | ### Read a Rotary Encoder
219 |
220 | ```py
221 | import board
222 | import rotaryio
223 | encoder = rotaryio.IncrementalEncoder(board.GP0, board.GP1) # must be consecutive on Pico
224 | print(encoder.position) # starts at zero, goes neg or pos
225 | ```
226 |
227 | ### Debounce a pin / button
228 |
229 | But you probably want to use `keypad` to get debouncing and press/release events.
230 | You can use it for a single button!
231 |
232 | ```py
233 | import board, keypad
234 | keys = keypad.Keys((board.D3,), value_when_pressed=False, pull=True)
235 | while True:
236 | if key := keys.events.get():
237 | if key.pressed:
238 | print("pressed key!", key.key_number)
239 | if key.released:
240 | print("released key!", key.key_number)
241 |
242 | ```
243 | Note: be sure to add the comma when using a single button (e.g. `(board.D3,)`)
244 |
245 | If your board doesn't have `keypad`, you can use `adafruit_debouncer` from
246 | the bundle.
247 |
248 | ```py
249 | import board
250 | from digitalio import DigitalInOut, Pull
251 | from adafruit_debouncer import Debouncer
252 | button_in = DigitalInOut(board.D3) # defaults to input
253 | button_in.pull = Pull.UP # turn on internal pull-up resistor
254 | button = Debouncer(button_in)
255 | while True:
256 | button.update()
257 | if button.fell:
258 | print("press!")
259 | if button.rose:
260 | print("release!")
261 | ```
262 |
263 | Note: Most boards have the native `keypad` module that can do keypad debouncing in a much more
264 | efficient way. See [Set up and debounce a list of pins](#set-up-and-debounce-a-list-of-pins)
265 |
266 |
267 | ### Detect button double-click
268 |
269 | ```py
270 | import board
271 | from digitalio import DigitalInOut, Pull
272 | from adafruit_debouncer import Button
273 | button_in = DigitalInOut(board.D3) # defaults to input
274 | button_in.switch_to_input(Pull.UP) # turn on internal pull-up resistor
275 | button = Button(button_in)
276 | while True:
277 | button.update()
278 | if button.pressed:
279 | print("press!")
280 | if button.released:
281 | print("release!")
282 | if button.short_count > 1: # detect multi-click
283 | print("multi-click: click count:", button.short_count)
284 | ```
285 |
286 | ### Set up and debounce a list of pins
287 |
288 | If your board's CircuitPython has the `keypad` library (most do),
289 | then I recommend using it. It's not just for key matrixes! And it's more efficient
290 | and, since it's built-in, reduces a library dependency.
291 |
292 | ```py
293 | import board
294 | import keypad
295 | button_pins = (board.GP0, board.GP1, board.GP2, board.GP3, board.GP4)
296 | buttons = keypad.Keys(button_pins, value_when_pressed=False, pull=True)
297 |
298 | while True:
299 | button = buttons.events.get() # see if there are any key events
300 | if button: # there are events!
301 | if button.pressed:
302 | print("button", button.key_number, "pressed!")
303 | if button.released:
304 | print("button", button.key_number, "released!")
305 | ```
306 |
307 | Otherwise, you can use `adafruit_debouncer`:
308 |
309 | ```py
310 | import board
311 | from digitalio import DigitalInOut, Pull
312 | from adafruit_debouncer import Debouncer
313 | button_pins = (board.GP0, board.GP1, board.GP2, board.GP3, board.GP4)
314 | buttons = [] # will hold list of Debouncer objects
315 | for pin in button_pins: # set up each pin
316 | tmp_pin = DigitalInOut(pin) # defaults to input
317 | tmp_pin.pull = Pull.UP # turn on internal pull-up resistor
318 | buttons.append( Debouncer(tmp_pin) )
319 | while True:
320 | for i in range(len(buttons)):
321 | buttons[i].update()
322 | if buttons[i].fell:
323 | print("button",i,"pressed!")
324 | if buttons[i].rose:
325 | print("button",i,"released!")
326 | ```
327 |
328 | And you can use `adafruit_debouncer` on touch pins too:
329 |
330 | ```py
331 | import board, touchio, adafruit_debouncer
332 | touchpad = adafruit_debouncer.Debouncer(touchio.TouchIn(board.GP1))
333 | while True:
334 | touchpad.update()
335 | if touchpad.rose: print("touched!")
336 | if touchpad.fell: print("released!")
337 | ```
338 |
339 |
340 | ## Outputs
341 |
342 | ### Output HIGH / LOW on a pin (like an LED)
343 |
344 | ```py
345 | import board
346 | import digitalio
347 | ledpin = digitalio.DigitalInOut(board.D2)
348 | ledpin.direction = digitalio.Direction.OUTPUT
349 | ledpin.value = True
350 | ```
351 |
352 | Can also do:
353 | ```py
354 | ledpin = digitalio.DigitalInOut(board.D2)
355 | ledpin.switch_to_output(value=True)
356 | ```
357 |
358 | ### Output Analog value on a DAC pin
359 |
360 | Different boards have DAC on different pins
361 |
362 | ```py
363 | import board
364 | import analogio
365 | dac = analogio.AnalogOut(board.A0) # on Trinket M0 & QT Py
366 | dac.value = 32768 # mid-point of 0-65535
367 | ```
368 |
369 | ### Output a "Analog" value on a PWM pin
370 |
371 | ```py
372 | import board
373 | import pwmio
374 | out1 = pwmio.PWMOut(board.MOSI, frequency=25000, duty_cycle=0)
375 | out1.duty_cycle = 32768 # mid-point 0-65535 = 50 % duty-cycle
376 | ```
377 |
378 | ### Control Neopixel / WS2812 LEDs
379 |
380 | ```py
381 | import neopixel
382 | leds = neopixel.NeoPixel(board.NEOPIXEL, 16, brightness=0.2)
383 | leds[0] = 0xff00ff # first LED of 16 defined
384 | leds[0] = (255,0,255) # equivalent
385 | leds.fill( 0x00ff00 ) # set all to green
386 | ```
387 |
388 | ### Control a servo, with animation list
389 |
390 | ```py
391 | # servo_animation_code.py -- show simple servo animation list
392 | import time, random, board
393 | from pwmio import PWMOut
394 | from adafruit_motor import servo
395 |
396 | # your servo will likely have different min_pulse & max_pulse settings
397 | servoA = servo.Servo(PWMOut(board.RX, frequency=50), min_pulse=500, max_pulse=2250)
398 |
399 | # the animation to play
400 | animation = (
401 | # (angle, time to stay at that angle)
402 | (0, 2.0),
403 | (90, 2.0),
404 | (120, 2.0),
405 | (180, 2.0)
406 | )
407 | ani_pos = 0 # where in list to start our animation
408 |
409 | while True:
410 | angle, secs = animation[ ani_pos ]
411 | print("servo moving to", angle, secs)
412 | servoA.angle = angle
413 | time.sleep( secs )
414 | ani_pos = (ani_pos + 1) % len(animation) # go to next, loop if at end
415 | ```
416 |
417 |
418 | ## Neopixels / Dotstars
419 |
420 | ### Light each LED in order
421 |
422 | You can access each LED with Python array methods on the `leds` object.
423 | And you can set the LED color with either an RGB tuple (`(255,0,80)`) or an
424 | RGB hex color as a 24-bit number (`0xff0050`)
425 |
426 | ```py
427 | import time, board, neopixel
428 |
429 | led_pin = board.GP5 # which pin the LED strip is on
430 | num_leds = 10
431 | colors = ( (255,0,0), (0,255,0), (0,0,255), 0xffffff, 0x000000 )
432 |
433 | leds = neopixel.NeoPixel(led_pin, num_leds, brightness=0.1)
434 |
435 | i = 0
436 | while True:
437 | print("led:",i)
438 | for c in colors:
439 | leds[i] = c
440 | time.sleep(0.2)
441 | i = (i+1) % num_leds
442 | ```
443 |
444 | ### Moving rainbow on built-in `board.NEOPIXEL`
445 |
446 | In CircuitPython 7, the `rainbowio` module has a `colorwheel()` function.
447 | Unfortunately, the `rainbowio` module is not available in all builds.
448 | In CircuitPython 6, `colorwheel()` is a built-in function part of `_pixelbuf` or `adafruit_pypixelbuf`.
449 |
450 | The `colorwheel()` function takes a single value 0-255 hue and returns an `(R,G,B)` tuple
451 | given a single 0-255 hue. It's not a full HSV_to_RGB() function but often all you need
452 | is "hue to RGB", wher you assume saturation=255 and value=255.
453 | It can be used with `neopixel`, `adafruit_dotstar`, or any place you need a (R,G,B) 3-byte tuple.
454 | Here's one way to use it.
455 |
456 | ```py
457 | # CircuitPython 7 with or without rainbowio module
458 | import time, board, neopixel
459 | try:
460 | from rainbowio import colorwheel
461 | except:
462 | def colorwheel(pos):
463 | if pos < 0 or pos > 255: return (0, 0, 0)
464 | if pos < 85: return (255 - pos * 3, pos * 3, 0)
465 | if pos < 170: pos -= 85; return (0, 255 - pos * 3, pos * 3)
466 | pos -= 170; return (pos * 3, 0, 255 - pos * 3)
467 |
468 | led = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.4)
469 | while True:
470 | led.fill( colorwheel((time.monotonic()*50)%255) )
471 | time.sleep(0.05)
472 | ```
473 |
474 |
475 | ### Make moving rainbow gradient across LED strip
476 |
477 | See [demo of it in this tweet](https://twitter.com/todbot/status/1397992493833097218).
478 |
479 | ```py
480 | import time, board, neopixel, rainbowio
481 | num_leds = 16
482 | leds = neopixel.NeoPixel(board.D2, num_leds, brightness=0.4, auto_write=False )
483 | delta_hue = 256//num_leds
484 | speed = 10 # higher numbers = faster rainbow spinning
485 | i=0
486 | while True:
487 | for l in range(len(leds)):
488 | leds[l] = rainbowio.colorwheel( int(i*speed + l * delta_hue) % 255 )
489 | leds.show() # only write to LEDs after updating them all
490 | i = (i+1) % 255
491 | time.sleep(0.05)
492 | ```
493 |
494 | A shorter version using a Python list comprehension. The `leds[:]` trick is a way to assign
495 | a new list of colors to all the LEDs at once.
496 |
497 | ```py
498 | import supervisor, board, neopixel, rainbowio
499 | num_leds = 16
500 | speed = 10 # lower is faster, higher is slower
501 | leds = neopixel.NeoPixel(board.D2, 16, brightness=0.4)
502 | while True:
503 | t = supervisor.ticks_ms() / speed
504 | leds[:] = [rainbowio.colorwheel( t + i*(255/len(leds)) ) for i in range(len(leds))]
505 |
506 | ```
507 |
508 | ### Fade all LEDs by amount for chase effects
509 | ```py
510 | import time
511 | import board, neopixel
512 | num_leds = 16
513 | leds = neopixel.NeoPixel(board.D2, num_leds, brightness=0.4, auto_write=False )
514 | my_color = (55,200,230)
515 | dim_by = 20 # dim amount, higher = shorter tails
516 | pos = 0
517 | while True:
518 | leds[pos] = my_color
519 | leds[:] = [[max(i-dim_by,0) for i in l] for l in leds] # dim all by (dim_by,dim_by,dim_by)
520 | pos = (pos+1) % num_leds # move to next position
521 | leds.show() # only write to LEDs after updating them all
522 | time.sleep(0.05)
523 | ```
524 |
525 | ## Audio
526 |
527 | If you're used to Arduino, making sound was mostly constrained to simple beeps
528 | using the Arduino `tone()` function. You can do that in CircuitPython too with
529 | `pwmio` and `simpleio`, but CircuitPython can also play WAV and MP3
530 | files and become a [fully-fledged audio synthesizer with `synthio`](https://github.com/todbot/circuitpython-synthio-tricks).
531 |
532 | In CircuitPython, there are multiple core module libraries available to output audio:
533 |
534 | - `pwmio` -- use almost any GPIO pin to output simple beeps, no WAV/MP3/synthio
535 | - `audioio` -- uses built-in DAC to output WAV, MP3, synthio
536 | - `audiopwmio` -- like above, but uses PWM like arduino `analogWrite()`, requires RC filter to convert to analog
537 | - `audiobusio` -- outputs high-quality I2S audio data stream, requires external I2S decoder hardware
538 |
539 | Different devices will have different audio modules available. Generally, the
540 | pattern is:
541 |
542 | - SAMD51 (e.g. "M4" boards) -- `audioio` (DAC) and `audiobusio` (I2S)
543 | - RP2040 (e.g. Pico) -- `audiopwmio` (PWM) and `audiobusio` (I2S)
544 | - ESP32 (e.g. QTPy ESP32) -- `audiobusio` (I2S) only
545 |
546 | To play WAV and MP3 files, they usually must be resaved in a format parsable by CircuitPython,
547 | see [Preparing Audio Files for CircuitPython](#preparing-audio-files-for-circuitpython)
548 |
549 | ### Making simple tones
550 |
551 | For devices that only have `pwmio` capability, you can make simple tones.
552 | The [`simpleio`](https://docs.circuitpython.org/projects/simpleio/en/latest/examples.html#id1) library can be used for this:
553 |
554 | ```py
555 | # a short piezo song using tone()
556 | import time, board, simpleio
557 | while True:
558 | for f in (262, 294, 330, 349, 392, 440, 494, 523):
559 | simpleio.tone(board.A0, f, 0.25)
560 | time.sleep(1)
561 | ```
562 |
563 | ### Play a WAV file
564 |
565 | WAV files are easiest for CircuitPython to play.
566 | The shortest code to play a WAV file on Pico RP2040 is:
567 |
568 | ```py
569 | import time, board, audiocore, audiopwmio
570 | audio = audiopwmio.PWMAudioOut(board.GP0)
571 | wave = audiocore.WaveFile("laser2.wav")
572 | audio.play(wave)
573 | while True:
574 | pass # wait for audio to finish playing
575 | ```
576 | Details and other ways below.
577 |
578 | ### Audio out using PWM
579 |
580 | This uses the `audiopwmio` library, only available for RP2040 boards like Raspberry Pi Pico and NRF52840-based boards like Adafruit Feather nRF52840 Express.
581 | On RP2040-based boards, any pin can be PWM Audio pin.
582 | See the [audiopwomio Support Matrix](https://circuitpython.readthedocs.io/en/latest/shared-bindings/support_matrix.html?filter=audiopwmio) for which boards support `audiopwmio`.
583 |
584 | ```py
585 | import time, board
586 | from audiocore import WaveFile
587 | from audiopwmio import PWMAudioOut as AudioOut
588 | wave = WaveFile("laser2.wav") # can also be filehandle from open()
589 | audio = AudioOut(board.GP0) # must be PWM-capable pin
590 | while True:
591 | print("audio is playing:",audio.playing)
592 | if not audio.playing:
593 | audio.play(wave)
594 | wave.sample_rate = int(wave.sample_rate * 0.90) # play 10% slower each time
595 | time.sleep(0.1)
596 | ```
597 |
598 | Notes:
599 |
600 | - There will be a small *pop* when audio starts playing as the PWM driver
601 | takes the GPIO line from not being driven to being PWM'ed.
602 | There's currently no way around this. If playing multiple WAVs, consider using
603 | [`AudioMixer`](#use-audiomixer-to-prevent-audio-crackles) to keep the audio system
604 | running between WAVs. This way, you'll only have the startup pop.
605 |
606 | - If you want *stereo* output on boards that support it
607 | then you can pass in two pins, like:
608 | `audio = audiopwmio.PWMAudioOut(left_channel=board.GP14, right_channel=board.GP15)`
609 |
610 | - PWM output must be filtered and converted to line-level to be usable.
611 | Use an RC circuit to accomplish this, see [this simple circuit](https://github.com/todbot/circuitpython-tricks/blob/main/larger-tricks/docs/breakbeat_sampleplayer_wiring.png) or [this twitter thread for details](https://twitter.com/todbot/status/1403451581593374720).
612 |
613 | - The `WaveFile()` object can take either a filestream
614 | (the output of `open('filewav','rb')`) or can take a string filename (`wav=WaveFile("laser2.wav")`).
615 |
616 |
617 | ### Audio out using DAC
618 |
619 | Some CircuitPython boards (SAMD51 "M4" & SAMD21 "M0") have built-in DACs that are supported.
620 | The code is the same as above, with just the import line changing.
621 | See the [audioio Support Matrix](https://circuitpython.readthedocs.io/en/latest/shared-bindings/support_matrix.html?filter=audioio) for which boards support `audioio`.
622 |
623 | ```py
624 | import time, board
625 | import audiocore, audioio # DAC
626 | wave_file = open("laser2.wav", "rb")
627 | wave = audiocore.WaveFile(wave_file)
628 | audio = audioio.AudioOut(board.A0) # must be DAC-capable pin, A0 on QTPy Haxpress
629 | while True:
630 | print("audio is playing:",audio.playing)
631 | if not audio.playing:
632 | audio.play(wave)
633 | wave.sample_rate = int(wave.sample_rate * 0.90) # play 10% slower each time
634 | time.sleep(0.1)
635 | ```
636 |
637 | Note: if you want *stereo* output on boards that support it (SAMD51 "M4" mostly),
638 | then you can pass in two pins, like:
639 | `audio = audioio.AudioOut(left_channel=board.A0, right_channel=board.A1)`
640 |
641 |
642 | ### Audio out using I2S
643 |
644 | Unlike PWM or DAC, most CircuitPython boards support driving an external I2S audio board.
645 | This will also give you higher-quality sound output than DAC or PWM.
646 | See the [audiobusio Support Matrix](https://circuitpython.readthedocs.io/en/latest/shared-bindings/support_matrix.html?filter=audiobusio) for which boards support `audiobusio`.
647 |
648 | ```py
649 | # for e.g. Pico RP2040 pins bit_clock & word_select pins must be adjacent
650 | import board, audiobusio, audiocore
651 | audio = audiobusio.I2SOut(bit_clock=board.GP0, word_select=board.GP1, data=board.GP2)
652 | audio.play( audiocore.WaveFile("laser2.wav") )
653 | ```
654 |
655 | ### Use audiomixer to prevent audio crackles
656 |
657 | The default buffer used by the audio system is quite small.
658 | This means you'll hear corrupted audio if CircuitPython is doing anything else
659 | (having CIRCUITPY written to, updating a display). To get around this, you can
660 | use `audiomixer` to make the audio buffer larger. Try `buffer_size=2048` to start.
661 | A larger buffer means a longer lag between when a sound is triggered when its heard.
662 |
663 | AudioMixer is also great if you want to play multiple WAV files at the same time.
664 |
665 | ```py
666 | import time, board
667 | from audiocore import WaveFile
668 | from audioio import AudioOut
669 | import audiomixer
670 | wave = WaveFile("laser2.wav", "rb")
671 | audio = AudioOut(board.A0) # assuming QTPy M0 or Itsy M4
672 | mixer = audiomixer.Mixer(voice_count=1, sample_rate=22050, channel_count=1,
673 | bits_per_sample=16, samples_signed=True, buffer_size=2048)
674 | audio.play(mixer) # never touch "audio" after this, use "mixer"
675 | while True:
676 | print("mixer voice is playing:", mixer.voice[0].playing)
677 | if not mixer.voice[0].playing:
678 | time.sleep(1)
679 | print("playing again")
680 | mixer.voice[0].play(wave)
681 | time.sleep(0.1)
682 | ```
683 |
684 | ### Play multiple sounds with audiomixer
685 |
686 | This example assumes WAVs that are mono 22050 Hz sample rate, w/ signed 16-bit samples.
687 |
688 | ```py
689 | import time, board, audiocore, audiomixer
690 | from audiopwmio import PWMAudioOut as AudioOut
691 |
692 | wav_files = ("loop1.wav", "loop2.wav", "loop3.wav")
693 | wavs = [None] * len(wav_files) # holds the loaded WAVs
694 |
695 | audio = AudioOut(board.GP2) # RP2040 example
696 | mixer = audiomixer.Mixer(voice_count=len(wav_files), sample_rate=22050, channel_count=1,
697 | bits_per_sample=16, samples_signed=True, buffer_size=2048)
698 | audio.play(mixer) # attach mixer to audio playback
699 |
700 | for i in range(len(wav_files)):
701 | print("i:",i)
702 | wavs[i] = audiocore.WaveFile(open(wav_files[i], "rb"))
703 | mixer.voice[i].play( wavs[i], loop=True) # start each one playing
704 |
705 | while True:
706 | print("doing something else while all loops play")
707 | time.sleep(1)
708 | ```
709 |
710 | Note: M0 boards do not have `audiomixer`
711 |
712 | Note: Number of simultaneous sounds is limited sample rate and flash read speed.
713 | Rules of thumb:
714 | - Built-in flash: 10 22kHz sounds simultanously
715 | - SPI SD cards: 2 22kHz sounds simultaneously
716 |
717 |
718 | Also see the many examples in [larger-tricks](./larger-tricks/).
719 |
720 | ### Playing MP3 files
721 |
722 | Once you have set up audio output (either directly or via AudioMixer), you can play WAVs or
723 | MP3s through it, or play both simultaneously.
724 |
725 | For instance, here's an example that uses an I2SOut to a PCM5102 on a Raspberry Pi Pico RP2040
726 | to simultaneously play both a WAV and an MP3:
727 |
728 | ```py
729 | import board, audiobusio, audiocore, audiomp3
730 | num_voices = 2
731 |
732 | i2s_bclk, i2s_wsel, i2s_data = board.GP9, board.GP10, board.GP11 # BCLK, LCLK, DIN on PCM5102
733 |
734 | audio = audiobusio.I2SOut(bit_clock=i2s_bclk, word_select=i2s_wsel, data=i2s_data)
735 | mixer = audiomixer.Mixer(voice_count=num_voices, sample_rate=22050, channel_count=1,
736 | bits_per_sample=16, samples_signed=True)
737 | audio.play(mixer) # attach mixer to audio playback
738 |
739 | wav_file = "/amen1_22k_s16.wav" # in 'circuitpython-tricks/larger-tricks/breakbeat_wavs'
740 | mp3_file = "/vocalchops476663_22k_128k.mp3" # in 'circuitpython-tricks/larger-tricks/wav'
741 | # https://freesound.org/people/f-r-a-g-i-l-e/sounds/476663/
742 |
743 | wave = audiocore.WaveFile(open(wav_file, "rb"))
744 | mp3 = audiomp3.MP3Decoder(open(mp3_file, "rb"))
745 | mixer.voice[0].play( wave )
746 | mixer.voice[1].play( mp3 )
747 |
748 | while True:
749 | pass # both audio files play
750 |
751 | ```
752 |
753 | __Note:__ For MP3 files, be aware that since this is doing software MP3 decoding,
754 | you will likely need to re-encode the MP3s to lower bitrate and sample rate
755 | (max 128 kbps and 22,050 Hz) to be playable the lower-end CircuitPython devices
756 | like the Pico / RP2040.
757 |
758 | __Note:__ For MP3 files and setting `loop=True` when playing, there is a small delay
759 | when looping. WAV files loop seemlessly.
760 |
761 |
762 |
763 | An example of boards with `pwmio` but no audio are ESP32-S2-based boards like
764 | [FunHouse](https://www.adafruit.com/product/4985),
765 | where you cannot play WAV files, but you can make beeps.
766 | A larger example is this gist: https://gist.github.com/todbot/f35bb5ceed013a277688b2ca333244d5
767 |
768 |
769 | ## USB
770 |
771 |
772 | ### Rename CIRCUITPY drive to something new
773 |
774 | For instance, if you have multiple of the same device.
775 | The `label` can be up to 11 characters.
776 | This goes in `boot.py` not `code.py` and you must powercycle board.
777 |
778 | ```py
779 | # this goes in boot.py not code.py!
780 | new_name = "TRINKEYPY0"
781 | import storage
782 | storage.remount("/", readonly=False)
783 | m = storage.getmount("/")
784 | m.label = new_name
785 | storage.remount("/", readonly=True)
786 | ```
787 |
788 | ### Detect if USB is connected or not
789 |
790 | ```py
791 | import supervisor
792 | if supervisor.runtime.usb_connected:
793 | led.value = True # USB
794 | else:
795 | led.value = False # no USB
796 | ```
797 |
798 | An older way that tries to mount CIRCUITPY read-write and if it fails, USB connected:
799 |
800 | ```py
801 | def is_usb_connected():
802 | import storage
803 | try:
804 | storage.remount('/', readonly=False) # attempt to mount readwrite
805 | storage.remount('/', readonly=True) # attempt to mount readonly
806 | except RuntimeError as e:
807 | return True
808 | return False
809 | is_usb = "USB" if is_usb_connected() else "NO USB"
810 | print("USB:", is_usb)
811 | ```
812 |
813 | ### Get CIRCUITPY disk size and free space
814 | ```py
815 | import os
816 | fs_stat = os.statvfs('/')
817 | print("Disk size in MB", fs_stat[0] * fs_stat[2] / 1024 / 1024)
818 | print("Free space in MB", fs_stat[0] * fs_stat[3] / 1024 / 1024)
819 | ```
820 |
821 | ### Programmatically reset to UF2 bootloader
822 | ```py
823 | import microcontroller
824 | microcontroller.on_next_reset(microcontroller.RunMode.UF2)
825 | microcontroller.reset()
826 | ```
827 |
828 | Note: in older CircuitPython use `RunMode.BOOTLOADER` and for boards with multiple
829 | bootloaders (like ESP32-S2):
830 |
831 | ```py
832 | import microcontroller
833 | microcontroller.on_next_reset(microcontroller.RunMode.BOOTLOADER)
834 | microcontroller.reset()
835 | ```
836 |
837 |
838 | ## USB Serial
839 |
840 | ### Print to USB Serial
841 |
842 | ```py
843 | print("hello there") # prints a newline
844 | print("waiting...", end='') # does not print newline
845 | for i in range(256): print(i, end=', ') # comma-separated numbers
846 | ```
847 |
848 |
849 |
850 | ### Read user input from USB Serial, blocking
851 | ```py
852 | while True:
853 | print("Type something: ", end='')
854 | my_str = input() # type and press ENTER or RETURN
855 | print("You entered: ", my_str)
856 | ```
857 |
858 | ### Read user input from USB Serial, non-blocking (mostly)
859 | ```py
860 | import time
861 | import supervisor
862 | print("Type something when you're ready")
863 | last_time = time.monotonic()
864 | while True:
865 | if supervisor.runtime.serial_bytes_available:
866 | my_str = input()
867 | print("You entered:", my_str)
868 | if time.monotonic() - last_time > 1: # every second, print
869 | last_time = time.monotonic()
870 | print(int(last_time),"waiting...")
871 | ```
872 |
873 | ### Read keys from USB Serial
874 | ```py
875 | import time, sys, supervisor
876 | print("type charactcers")
877 | while True:
878 | n = supervisor.runtime.serial_bytes_available
879 | if n > 0: # we read something!
880 | s = sys.stdin.read(n) # actually read it in
881 | # print both text & hex version of recv'd chars (see control chars!)
882 | print("got:", " ".join("{:s} {:02x}".format(c,ord(c)) for c in s))
883 | time.sleep(0.01) # do something else
884 | ```
885 |
886 | ### Read user input from USB serial, non-blocking
887 | ```py
888 | class USBSerialReader:
889 | """ Read a line from USB Serial (up to end_char), non-blocking, with optional echo """
890 | def __init__(self):
891 | self.s = ''
892 | def read(self,end_char='\n', echo=True):
893 | import sys, supervisor
894 | n = supervisor.runtime.serial_bytes_available
895 | if n > 0: # we got bytes!
896 | s = sys.stdin.read(n) # actually read it in
897 | if echo: sys.stdout.write(s) # echo back to human
898 | self.s = self.s + s # keep building the string up
899 | if s.endswith(end_char): # got our end_char!
900 | rstr = self.s # save for return
901 | self.s = '' # reset str to beginning
902 | return rstr
903 | return None # no end_char yet
904 |
905 | usb_reader = USBSerialReader()
906 | print("type something and press the end_char")
907 | while True:
908 | mystr = usb_reader.read() # read until newline, echo back chars
909 | #mystr = usb_reader.read(end_char='\t', echo=False) # trigger on tab, no echo
910 | if mystr:
911 | print("got:",mystr)
912 | time.sleep(0.01) # do something time critical
913 | ```
914 |
915 | ## USB MIDI
916 |
917 | CircuitPython can be a MIDI controller, or respond to MIDI!
918 | Adafruit provides an [`adafruit_midi`](https://github.com/adafruit/Adafruit_CircuitPython_MIDI)
919 | class to make things easier, but it's rather complex for how simple MIDI actually is.
920 |
921 | For outputting MIDI, you can opt to deal with raw `bytearray`s, since most MIDI messages
922 | are just 1,2, or 3 bytes long. For reading MIDI,
923 | you may find [Winterbloom's SmolMIDI](https://github.com/wntrblm/Winterbloom_SmolMIDI) to be faster
924 | to parse MIDI messages, since by design it does less.
925 |
926 | ### Sending MIDI with adafruit_midi
927 |
928 | ```py
929 | import usb_midi
930 | import adafruit_midi
931 | from adafruit_midi.note_on import NoteOn
932 | from adafruit_midi.note_off import NoteOff
933 | midi_out_channel = 3 # human version of MIDI out channel (1-16)
934 | midi = adafruit_midi.MIDI( midi_out=usb_midi.ports[1], out_channel=midi_out_channel-1)
935 |
936 | def play_note(note,velocity=127):
937 | midi.send(NoteOn(note, velocity)) # 127 = highest velocity
938 | time.sleep(0.1)
939 | midi.send(NoteOff(note, 0)) # 0 = lowest velocity
940 | ```
941 |
942 | Note: This pattern works for sending serial (5-pin) MIDI too, see below
943 |
944 | ### Sending MIDI with bytearray
945 |
946 | Sending MIDI with a lower-level `bytearray` is also pretty easy and
947 | could gain some speed for timing-sensitive applications.
948 | This code is equivalent to the above, without `adafruit_midi`
949 |
950 | ```py
951 | import usb_midi
952 | midi_out = usb_midi.ports[1]
953 | midi_out_channel = 3 # MIDI out channel (1-16)
954 | note_on_status = (0x90 | (midi_out_channel-1))
955 | note_off_status = (0x80 | (midi_out_channel-1))
956 |
957 | def play_note(note,velocity=127):
958 | midi_out.write( bytearray([note_on_status, note, velocity]) )
959 | time.sleep(0.1)
960 | midi_out.write( bytearray([note_off_status, note, 0]) )
961 | ```
962 |
963 | ### MIDI over Serial UART
964 |
965 | Not exactly USB, but it is MIDI!
966 | Both `adafruit_midi` and the bytearray technique works for Serial MIDI (aka "5-pin MIDI") too.
967 | With a [simple MIDI out circuit](https://learn.adafruit.com/qt-py-rp2040-usb-to-serial-midi-friends/circuit-diagram)
968 | you can control old hardware synths.
969 |
970 | ```py
971 | import busio
972 | midi_out_channel = 3 # MIDI out channel (1-16)
973 | note_on_status = (0x90 | (midi_out_channel-1))
974 | note_off_status = (0x80 | (midi_out_channel-1))
975 | # must pick board pins that are UART TX and RX pins
976 | midi_uart = busio.UART(tx=board.GP16, rx=board.GP17, baudrate=31250)
977 |
978 | def play_note(note,velocity=127):
979 | midi_uart.write( bytearray([note_on_status, note, velocity]) )
980 | time.sleep(0.1)
981 | midi_uart.write( bytearray([note_off_status, note, 0]) )
982 | ```
983 |
984 | ### Receiving MIDI
985 |
986 | ```py
987 | import usb_midi # built-in library
988 | import adafruit_midi # install with 'circup install adafruit_midi'
989 | from adafruit_midi.note_on import NoteOn
990 | from adafruit_midi.note_off import NoteOff
991 |
992 | midi_usb = adafruit_midi.MIDI(midi_in=usb_midi.ports[0])
993 | while True:
994 | msg = midi_usb.receive()
995 | if msg:
996 | if isinstance(msg, NoteOn):
997 | print("usb noteOn:",msg.note, msg.velocity)
998 | elif isinstance(msg, NoteOff):
999 | print("usb noteOff:",msg.note, msg.velocity)
1000 | ```
1001 | Note with `adafruit_midi` you must `import` each kind of MIDI Message you want to handle.
1002 |
1003 | ### Receiving MIDI USB and MIDI Serial UART together
1004 |
1005 | MIDI is MIDI, so you can use either the `midi_uart` or the `usb_midi.ports[]` created above with `adafruit_midi`.
1006 | Here's an example receiving MIDI from both USB and Serial on a QTPy RP2040.
1007 | Note for receiving serial MIDI, you need an appropriate optoisolator input circuit,
1008 | like [this one for QTPys](https://www.denki-oto.com/store/p74/MICROMIDITRS-USB.html#/)
1009 | or [this one for MacroPad RP2040](https://www.tindie.com/products/todbot/macropadsynthplug-turn-rp2040-into-a-synth/).
1010 |
1011 | ```py
1012 | import board, busio
1013 | import usb_midi # built-in library
1014 | import adafruit_midi # install with 'circup install adafruit_midi'
1015 | from adafruit_midi.note_on import NoteOn
1016 | from adafruit_midi.note_off import NoteOff
1017 |
1018 | uart = busio.UART(tx=board.TX, rx=board.RX, baudrate=31250, timeout=0.001)
1019 | midi_usb = adafruit_midi.MIDI( midi_in=usb_midi.ports[0], midi_out=usb_midi.ports[1] )
1020 | midi_serial = adafruit_midi.MIDI( midi_in=uart, midi_out=uart )
1021 |
1022 | while True:
1023 | msg = midi_usb.receive()
1024 | if msg:
1025 | if isinstance(msg, NoteOn):
1026 | print("usb noteOn:",msg.note, msg.velocity)
1027 | elif isinstance(msg, NoteOff):
1028 | print("usb noteOff:",msg.note, msg.velocity)
1029 | msg = midi_serial.receive()
1030 | if msg:
1031 | if isinstance(msg, NoteOn):
1032 | print("serial noteOn:",msg.note, msg.velocity)
1033 | elif isinstance(msg, NoteOff):
1034 | print("serial noteOff:",msg.note, msg.velocity)
1035 | ```
1036 |
1037 | If you don't care about the source of the MIDI messages, you can combine
1038 | the two if blocks using the "walrus operator" (`:=`)
1039 |
1040 | ```py
1041 | while True:
1042 | while msg := midi_usb.receive() or midi_uart.receive():
1043 | if isinstance(msg, NoteOn) and msg.velocity != 0:
1044 | note_on(msg.note, msg.velocity)
1045 | elif isinstance(msg,NoteOff) or isinstance(msg,NoteOn) and msg.velocity==0:
1046 | note_off(msg.note, msg.velocity)
1047 | ```
1048 |
1049 |
1050 | ### Enable USB MIDI in boot.py (for ESP32-S2 and STM32F4)
1051 |
1052 | Some CircuitPython devices like ESP32-S2 based ones, do not have enough
1053 | [USB endpoints to enable all USB functions](https://learn.adafruit.com/customizing-usb-devices-in-circuitpython/how-many-usb-devices-can-i-have), so USB MIDI is disabled by default.
1054 | To enable it, the easiest is to disable USB HID (keyboard/mouse) support.
1055 | This must be done in `boot.py` and the board power cycled.
1056 |
1057 | ```py
1058 | # boot.py
1059 | import usb_hid
1060 | import usb_midi
1061 | usb_hid.disable()
1062 | usb_midi.enable()
1063 | print("enabled USB MIDI, disabled USB HID")
1064 | ```
1065 |
1066 |
1067 | ## WiFi / Networking
1068 |
1069 | ### Scan for WiFi Networks, sorted by signal strength
1070 |
1071 | Note: this is for boards with native WiFi (ESP32)
1072 |
1073 | ```py
1074 | import wifi
1075 | networks = []
1076 | for network in wifi.radio.start_scanning_networks():
1077 | networks.append(network)
1078 | wifi.radio.stop_scanning_networks()
1079 | networks = sorted(networks, key=lambda net: net.rssi, reverse=True)
1080 | for network in networks:
1081 | print("ssid:",network.ssid, "rssi:",network.rssi)
1082 | ```
1083 |
1084 | ### Join WiFi network with highest signal strength
1085 |
1086 | ```py
1087 | import wifi
1088 |
1089 | def join_best_network(good_networks, print_info=False):
1090 | """join best network based on signal strength of scanned nets"""
1091 | networks = []
1092 | for network in wifi.radio.start_scanning_networks():
1093 | networks.append(network)
1094 | wifi.radio.stop_scanning_networks()
1095 | networks = sorted(networks, key=lambda net: net.rssi, reverse=True)
1096 | for network in networks:
1097 | if print_info: print("network:",network.ssid)
1098 | if network.ssid in good_networks:
1099 | if print_info: print("connecting to WiFi:", network.ssid)
1100 | try:
1101 | wifi.radio.connect(network.ssid, good_networks[network.ssid])
1102 | return True
1103 | except ConnectionError as e:
1104 | if print_info: print("connect error:",e)
1105 | return False
1106 |
1107 | good_networks = {"todbot1":"FiOnTheFly", # ssid, password
1108 | "todbot2":"WhyFlyWiFi",}
1109 | connected = join_best_network(good_networks, print_info=True)
1110 | if connected:
1111 | print("connected!")
1112 |
1113 | ```
1114 |
1115 | ### Ping an IP address
1116 |
1117 | Note: this is for boards with native WiFi (ESP32)
1118 |
1119 | ```py
1120 | import os
1121 | import time
1122 | import wifi
1123 | import ipaddress
1124 |
1125 | ip_to_ping = "1.1.1.1"
1126 |
1127 | wifi.radio.connect(ssid=os.getenv('CIRCUITPY_WIFI_SSID'),
1128 | password=os.getenv('CIRCUITPY_WIFI_PASSWORD'))
1129 |
1130 | print("my IP addr:", wifi.radio.ipv4_address)
1131 | print("pinging ",ip_to_ping)
1132 | ip1 = ipaddress.ip_address(ip_to_ping)
1133 | while True:
1134 | print("ping:", wifi.radio.ping(ip1))
1135 | time.sleep(1)
1136 | ```
1137 |
1138 | ### Get IP address of remote host
1139 |
1140 | ```py
1141 | import os, wifi, socketpool
1142 |
1143 | wifi.radio.connect(ssid=os.getenv('CIRCUITPY_WIFI_SSID'),
1144 | password=os.getenv('CIRCUITPY_WIFI_PASSWORD'))
1145 | print("my IP addr:", wifi.radio.ipv4_address)
1146 |
1147 | hostname = "todbot.com"
1148 |
1149 | pool = socketpool.SocketPool(wifi.radio)
1150 | addrinfo = pool.getaddrinfo(host=hostname, port=443) # port is required
1151 | print("addrinfo", addrinfo)
1152 |
1153 | ipaddr = addrinfo[0][4][0]
1154 |
1155 | print(f"'{hostname}' ip address is '{ipaddr}'")
1156 | ```
1157 |
1158 |
1159 | ### Fetch a JSON file
1160 |
1161 | Note: this is for boards with native WiFi (ESP32)
1162 |
1163 | ```py
1164 | import os
1165 | import time
1166 | import wifi
1167 | import socketpool
1168 | import ssl
1169 | import adafruit_requests
1170 |
1171 | wifi.radio.connect(ssid=os.getenv('CIRCUITPY_WIFI_SSID'),
1172 | password=os.getenv('CIRCUITPY_WIFI_PASSWORD'))
1173 | print("my IP addr:", wifi.radio.ipv4_address)
1174 | pool = socketpool.SocketPool(wifi.radio)
1175 | session = adafruit_requests.Session(pool, ssl.create_default_context())
1176 | while True:
1177 | response = session.get("https://todbot.com/tst/randcolor.php")
1178 | data = response.json()
1179 | print("data:",data)
1180 | time.sleep(5)
1181 | ```
1182 |
1183 |
1184 | ### Serve a webpage via HTTP
1185 |
1186 | Note: this is for boards with native WiFi (ESP32)
1187 |
1188 | The [`adafruit_httpserver`](https://github.com/adafruit/Adafruit_CircuitPython_HTTPServer) library
1189 | makes this pretty easy, and has good examples. You can tell it to either `server.serve_forver()`
1190 | and do all your computation in your `@server.route()` functions, or use `server.poll()` inside a while-loop.
1191 | There is also the [Ampule library](https://github.com/deckerego/ampule).
1192 |
1193 | ```py
1194 | import time, os, wifi, socketpool
1195 | from adafruit_httpserver.server import HTTPServer
1196 | from adafruit_httpserver.response import HTTPResponse
1197 |
1198 | my_port = 1234 # set this to your liking
1199 | wifi.radio.connect(ssid=os.getenv('CIRCUITPY_WIFI_SSID'),
1200 | password=os.getenv('CIRCUITPY_WIFI_PASSWORD'))
1201 | server = HTTPServer(socketpool.SocketPool(wifi.radio))
1202 |
1203 | @server.route("/") # magic that attaches this function to "server" object
1204 | def base(request):
1205 | my_str = f"
Hello! Current time.monotonic is {time.monotonic()}
"
1206 | return HTTPResponse(body=my_str, content_type="text/html")
1207 | # or for static content: return HTTPResponse(filename="/index.html")
1208 |
1209 | print(f"Listening on http://{wifi.radio.ipv4_address}:{my_port}")
1210 | server.serve_forever(str(wifi.radio.ipv4_address), port=my_port) # never returns
1211 |
1212 | ```
1213 |
1214 |
1215 | ### Set RTC time from NTP
1216 |
1217 | Note: this is for boards with native WiFi (ESP32)
1218 |
1219 | Note: You need to set `my_tz_offset` to match your region
1220 |
1221 | ```py
1222 | # copied from:
1223 | # https://docs.circuitpython.org/projects/ntp/en/latest/examples.html
1224 | import time, os, rtc
1225 | import socketpool, wifi
1226 | import adafruit_ntp
1227 |
1228 | my_tz_offset = -7 # PDT
1229 |
1230 | wifi.radio.connect(ssid=os.getenv('CIRCUITPY_WIFI_SSID'),
1231 | password=os.getenv('CIRCUITPY_WIFI_PASSWORD'))
1232 | print("Connected, getting NTP time")
1233 | pool = socketpool.SocketPool(wifi.radio)
1234 | ntp = adafruit_ntp.NTP(pool, tz_offset=my_tz_offset)
1235 |
1236 | rtc.RTC().datetime = ntp.datetime
1237 |
1238 | while True:
1239 | print("current datetime:", time.localtime())
1240 | time.sleep(5)
1241 | ```
1242 |
1243 | ### Set RTC time from time service
1244 |
1245 | Note: this is for boards with native WiFi (ESP32)
1246 |
1247 | This uses the awesome and free [WorldTimeAPI.org site](http://worldtimeapi.org/pages/examples),
1248 | and this example will fetch the current local time (including timezone and UTC offset)
1249 | based on the geolocated IP address of your device.
1250 |
1251 | ```py
1252 | import time, os, rtc
1253 | import wifi, ssl, socketpool
1254 | import adafruit_requests
1255 |
1256 | wifi.radio.connect(ssid=os.getenv('CIRCUITPY_WIFI_SSID'),
1257 | password=os.getenv('CIRCUITPY_WIFI_PASSWORD'))
1258 | print("Connected, getting WorldTimeAPI time")
1259 | pool = socketpool.SocketPool(wifi.radio)
1260 | request = adafruit_requests.Session(pool, ssl.create_default_context())
1261 |
1262 | print("Getting current time:")
1263 | response = request.get("http://worldtimeapi.org/api/ip")
1264 | time_data = response.json()
1265 | tz_hour_offset = int(time_data['utc_offset'][0:3])
1266 | tz_min_offset = int(time_data['utc_offset'][4:6])
1267 | if (tz_hour_offset < 0):
1268 | tz_min_offset *= -1
1269 | unixtime = int(time_data['unixtime'] + (tz_hour_offset * 60 * 60)) + (tz_min_offset * 60)
1270 |
1271 | print(time_data)
1272 | print("URL time: ", response.headers['date'])
1273 |
1274 | rtc.RTC().datetime = time.localtime( unixtime ) # create time struct and set RTC with it
1275 |
1276 | while True:
1277 | print("current datetime: ", time.localtime()) # time.* now reflects current local time
1278 | time.sleep(5)
1279 | ```
1280 |
1281 | Also see [this more concise version from @deilers78](https://github.com/todbot/circuitpython-tricks/issues/14#issuecomment-1489181920).
1282 |
1283 |
1284 | ### What the heck is `settings.toml`?
1285 |
1286 | It's a config file that lives next to your `code.py` and is used to store
1287 | WiFi credentials and other global settings. It is also used (invisibly)
1288 | by many Adafruit libraries that do WiFi.
1289 | You can use it (as in the examples above) without those libraries.
1290 | The settings names used by CircuitPython are documented in
1291 | [CircuitPython Web Workflow](https://docs.circuitpython.org/en/latest/docs/workflows.html#web).
1292 |
1293 | Note: You can use any variable names for your WiFI credentials
1294 | (a common pair is `WIFI_SSID` and `WIFI_PASSWORD`), but if you use the
1295 | `CIRCUITPY_WIFI_*` names that will also start up the
1296 | [Web Workflow](https://docs.circuitpython.org/en/latest/docs/workflows.html#web)
1297 |
1298 |
1299 | You use it like this for basic WiFi connectivity:
1300 |
1301 | ```py
1302 | # settings.toml
1303 | CIRCUITPY_WIFI_SSID = "PrettyFlyForAWiFi"
1304 | CIRCUITPY_WIFI_PASSWORD = "mysecretpassword"
1305 |
1306 | # code.py
1307 | import os, wifi
1308 | print("connecting...")
1309 | wifi.radio.connect(ssid=os.getenv('CIRCUITPY_WIFI_SSID'),
1310 | password=os.getenv('CIRCUITPY_WIFI_PASSWORD'))
1311 | print("my IP addr:", wifi.radio.ipv4_address)
1312 |
1313 | ```
1314 |
1315 |
1316 | ### What the heck is `secrets.py`?
1317 | It's an older version of the `settings.toml` idea.
1318 | You may see older code that uses it.
1319 |
1320 |
1321 | ## Displays (LCD / OLED / E-Ink) and displayio
1322 |
1323 | [displayio](https://circuitpython.readthedocs.io/en/latest/shared-bindings/displayio/#)
1324 | is the native system-level driver for displays in CircuitPython. Several CircuitPython boards
1325 | (FunHouse, MagTag, PyGamer, CLUE) have `displayio`-based displays and a
1326 | built-in `board.DISPLAY` object that is preconfigured for that display.
1327 | Or, you can add your own [I2C](https://circuitpython.readthedocs.io/en/latest/shared-bindings/displayio/#displayio.I2CDisplay) or [SPI](https://circuitpython.readthedocs.io/en/latest/shared-bindings/displayio/#displayio.FourWire) display.
1328 |
1329 | ### Get default display and change display rotation
1330 |
1331 | Boards like FunHouse, MagTag, PyGamer, CLUE have built-in displays.
1332 | `display.rotation` works with all displays, not just built-in ones.
1333 |
1334 | ```py
1335 | import board
1336 | display = board.DISPLAY
1337 | print(display.rotation) # print current rotation
1338 | display.rotation = 0 # valid values 0,90,180,270
1339 | ```
1340 |
1341 | ### Display an image
1342 |
1343 | __Using `displayio.OnDiskBitmap`__
1344 |
1345 | CircuitPython has a built-in BMP parser called `displayio.OnDiskBitmap`:
1346 | The images should be in non-compressed, paletized BMP3 format.
1347 | ([how to make BMP3 images](#preparing-images-for-circuitpython))
1348 |
1349 | ```py
1350 | import board, displayio
1351 | display = board.DISPLAY
1352 |
1353 | maingroup = displayio.Group() # everything goes in maingroup
1354 | display.root_group = maingroup # show our maingroup (clears the screen)
1355 |
1356 | bitmap = displayio.OnDiskBitmap(open("my_image.bmp", "rb"))
1357 | image = displayio.TileGrid(bitmap, pixel_shader=bitmap.pixel_shader)
1358 | maingroup.append(image) # shows the image
1359 | ```
1360 |
1361 | __Using `adafruit_imageload`__
1362 |
1363 | You can also use the `adafruit_imageload` library that supports slightly more kinds of BMP files,
1364 | (but should still be [paletized BMP3 format](#preparing-images-for-circuitpython)
1365 | as well as paletized PNG and GIF files. Which file format to choose?
1366 | * BMP images are larger but faster to load
1367 | * PNG images are about 2x smaller than BMP and almost as fast to load
1368 | * GIF images are a little bigger than PNG but *much* slower to load
1369 |
1370 |
1371 | ```py
1372 | import board, displayio
1373 | import adafruit_imageload
1374 | display = board.DISPLAY
1375 | maingroup = displayio.Group() # everything goes in maingroup
1376 | display.root_group = maingroup # set the root group to display
1377 | bitmap, palette = adafruit_imageload.load("my_image.png")
1378 | image = displayio.TileGrid(bitmap, pixel_shader=palette)
1379 | maingroup.append(image) # shows the image
1380 | ```
1381 |
1382 | __How `displayio` is structured__
1383 |
1384 | CircuitPython's `displayio` library works like:
1385 | - an image `Bitmap` (and its `Palette`) goes inside a `TileGrid`
1386 | - a `TileGrid` goes inside a `Group`
1387 | - a `Group` is shown on a `Display`.
1388 |
1389 |
1390 |
1391 | ### Display background bitmap
1392 |
1393 | Useful for display a solid background color that can be quickly changed.
1394 |
1395 | ```py
1396 | import time, board, displayio
1397 | display = board.DISPLAY # get default display (FunHouse,Pygamer,etc)
1398 | maingroup = displayio.Group() # Create a main group to hold everything
1399 | display.root_group = maingroup # put it on the display
1400 |
1401 | # make bitmap that spans entire display, with 3 colors
1402 | background = displayio.Bitmap(display.width, display.height, 3)
1403 |
1404 | # make a 3 color palette to match
1405 | mypal = displayio.Palette(3)
1406 | mypal[0] = 0x000000 # set colors (black)
1407 | mypal[1] = 0x999900 # dark yellow
1408 | mypal[2] = 0x009999 # dark cyan
1409 |
1410 | # Put background into main group, using palette to map palette ids to colors
1411 | maingroup.append(displayio.TileGrid(background, pixel_shader=mypal))
1412 |
1413 | time.sleep(2)
1414 | background.fill(2) # change background to dark cyan (mypal[2])
1415 | time.sleep(2)
1416 | background.fill(1) # change background to dark yellow (mypal[1])
1417 | ```
1418 |
1419 | Another way is to use
1420 | [`vectorio`](https://docs.circuitpython.org/en/latest/shared-bindings/vectorio/index.html):
1421 |
1422 | ```py
1423 | import board, displayio, vectorio
1424 |
1425 | display = board.DISPLAY # built-in display
1426 | maingroup = displayio.Group() # a main group that holds everything
1427 | display.root_group = maingroup # put maingroup on the display
1428 |
1429 | mypal = displayio.Palette(1)
1430 | mypal[0] = 0x999900
1431 | background = vectorio.Rectangle(pixel_shader=mypal, width=display.width, height=display.height, x=0, y=0)
1432 | maingroup.append(background)
1433 | ```
1434 |
1435 | Or can also use
1436 | [`adafruit_display_shapes`](https://docs.circuitpython.org/projects/display-shapes/en/latest/index.html):
1437 |
1438 | ```py
1439 | import board, displayio
1440 | from adafruit_display_shapes.rect import Rect
1441 |
1442 | display = board.DISPLAY
1443 | maingroup = displayio.Group() # a main group that holds everything
1444 | display.root_group = maingroup # add it to display
1445 |
1446 | background = Rect(0,0, display.width, display.height, fill=0x000000 ) # background color
1447 | maingroup.append(background)
1448 | ```
1449 |
1450 | ### Image slideshow
1451 |
1452 | ```py
1453 | import time, board, displayio
1454 | import adafruit_imageload
1455 |
1456 | display = board.DISPLAY # get display object (built-in on some boards)
1457 | screen = displayio.Group() # main group that holds all on-screen content
1458 | display.root_group = screen # add it to display
1459 |
1460 | file_names = [ '/images/cat1.bmp', '/images/cat2.bmp' ] # list of filenames
1461 |
1462 | screen.append(displayio.Group()) # placeholder, will be replaced w/ screen[0] below
1463 | while True:
1464 | for fname in file_names:
1465 | image, palette = adafruit_imageload.load(fname)
1466 | screen[0] = displayio.TileGrid(image, pixel_shader=palette)
1467 | time.sleep(1)
1468 | ```
1469 |
1470 | __Note:__ Images must be in palettized BMP3 format.
1471 | For more details, see [Preparing images for CircuitPython](#preparing-images-for-circuitpython)
1472 |
1473 |
1474 | ### Dealing with E-Ink "Refresh Too Soon" error
1475 |
1476 | E-Ink displays are damaged if refreshed too frequently.
1477 | CircuitPython enforces this, but also provides `display.time_to_refresh`,
1478 | the number of seconds you need to wait before the display can be refreshed.
1479 | One solution is to sleep a little longer than that and you'll never get the error.
1480 | Another would be to wait for `time_to_refresh` to go to zero, as show below.
1481 |
1482 | ```py
1483 | import time, board, displayio, terminalio
1484 | from adafruit_display_text import label
1485 | mylabel = label.Label(terminalio.FONT, text="demo", x=20,y=20,
1486 | background_color=0x000000, color=0xffffff )
1487 | display = board.DISPLAY # e.g. for MagTag
1488 | display.root_group = mylabel
1489 | while True:
1490 | if display.time_to_refresh == 0:
1491 | display.refresh()
1492 | mylabel.text = str(time.monotonic())
1493 | time.sleep(0.1)
1494 | ```
1495 |
1496 | ### Turn off REPL on built-in display
1497 |
1498 | If you have a board with a built-in display (like Feather TFT, Cardputer, FunHouse, etc),
1499 | CircuitPython will set up the display for you and print the REPL to it.
1500 | But if you want a more polished look for your project, you can turn off the REPL
1501 | from printing on the built-in display by putting this at at the top of both
1502 | your `boot.py` and `code.py`
1503 |
1504 | ```py
1505 | # put at top of both boot.py & code.py
1506 | import board
1507 | board.DISPLAY.root_group = None
1508 | ```
1509 |
1510 |
1511 | ## I2C
1512 |
1513 | ### Scan I2C bus for devices
1514 | from:
1515 | [CircuitPython I2C Guide: Find Your Sensor](https://learn.adafruit.com/circuitpython-essentials/circuitpython-i2c#find-your-sensor-2985153-11)
1516 |
1517 | ```py
1518 | import board
1519 | i2c = board.I2C() # or busio.I2C(pin_scl,pin_sda)
1520 | while not i2c.try_lock(): pass
1521 | print("I2C addresses found:", [hex(device_address)
1522 | for device_address in i2c.scan()])
1523 | i2c.unlock()
1524 | ```
1525 |
1526 | One liner to copy-n-paste into REPL for quicky I2C scan:
1527 |
1528 | ```py
1529 | import board; i2c=board.I2C(); i2c.try_lock(); [hex(a) for a in i2c.scan()]; i2c.unlock()
1530 | ```
1531 |
1532 | ### Speed up I2C bus
1533 |
1534 | CircuitPython defaults to 100 kHz I2C bus speed. This will work for all devices,
1535 | but some devices can go faster. Common faster speeds are 200 kHz and 400 kHz.
1536 |
1537 | ```py
1538 | import board
1539 | import busio
1540 | # instead of doing
1541 | # i2c = board.I2C()
1542 | i2c = busio.I2C( board.SCL, board.SDA, frequency=200_000)
1543 | # then do something with 'i2c' object as before, like:
1544 | oled = adafruit_ssd1306.SSD1306_I2C(width=128, height=32, i2c=i2c)
1545 | ```
1546 |
1547 | ## Timing
1548 |
1549 | ### Measure how long something takes
1550 |
1551 | Generally use `time.monotonic()` to get the current "uptime" of a board in fractional seconds.
1552 | So to measure the duration it takes CircuitPython to do something like:
1553 |
1554 | ```py
1555 | import time
1556 | start_time = time.monotonic()
1557 | # put thing you want to measure here, like:
1558 | import neopixel
1559 | stop_time = time.monotonic()
1560 | print("elapsed time = ", stop_time - start_time)
1561 | ```
1562 |
1563 | Note that on the "small" versions of CircuitPython in the QT Py M0, Trinket M0, etc.,
1564 | the floating point value of seconds will become less accurate as uptime increases.
1565 |
1566 | ### More accurate timing with `ticks_ms()`, like Arduino `millis()`
1567 |
1568 | If you want something more like Arduino's `millis()` function, the `supervisor.ticks_ms()`
1569 | function returns an integer, not a floating point value. It is more useful for sub-second
1570 | timing tasks and you can still convert it to floating-point seconds for human consumption.
1571 |
1572 | ```py
1573 | import supervisor
1574 | start_msecs = supervisor.ticks_ms()
1575 | import neopixel
1576 | stop_msecs = supervisor.ticks_ms()
1577 | print("elapsed time = ", (stop_msecs - start_msecs)/1000)
1578 | ```
1579 |
1580 | ### Control garbage collection for reliable timing
1581 |
1582 | The CircuitPython garbage collector makes it so you don't have to deal with
1583 | memory allocations like you do in languages like C/C++. But when it runs, it can pause your
1584 | program for tens of milliseconds in some cases. For timing-sensitive applications,
1585 | you can exhibit some control over the garbage collector so it only runs when
1586 | you want it (like in the "shadow" after a timing-critical event)
1587 |
1588 | Here's one way to do this.
1589 | ```py
1590 | import gc
1591 | from adafruit_ticks import ticks_ms, ticks_add, ticks_less
1592 | gc.collect() # collect any garbage before we...
1593 | gc.disable() # disable automatic garbage collection
1594 | dmillis = 10 # how many millis between explicit gc
1595 | deadline = ticks_add(ticks_ms(), dmillis)
1596 | while True:
1597 | now = ticks_ms()
1598 | if ticks_diff(now, deadline) >= 0:
1599 | deadline = ticks_add(now, dmillis)
1600 | gc.collect() # explicitly run a garbage collection cycle
1601 | # do timing-sensitive thing here
1602 | ```
1603 |
1604 | ### Converting milliseconds to seconds: 0.004 * 1000 != 4, sometimes
1605 |
1606 | When doing timing-sensitive tasks in CircuitPython, you may have code that looks like this (say from the above):
1607 | ```py
1608 | from adafruit_ticks import ticks_ms, ticks_add, ticks_less
1609 |
1610 | check_secs = 0.004 # check_secs is seconds between checks
1611 | check_millis = check_secs * 1000 # convert to millis
1612 | deadline = ticks_add(ticks_ms(), check_millis)
1613 | while True:
1614 | now = ticks_ms()
1615 | if ticks_less(now,deadline) >= 0:
1616 | deadline = ticks_add(now, check_millis)
1617 | do_periodic_task() # do timing-critical thing every 'check_secs'
1618 | ```
1619 | This seems more accurate than using `time.monotonic()` since it's using the millisecond-accurate `supervisor.ticks_ms` property, the timing resolution of CircuitPython.
1620 | This seems to work, until you pass in `check_secs = 0.004`, because the `ticks_*()` functions expect an integer and `int(0.004*1000) = 3`. If you were
1621 | using the above code to output an accurate timing signal, it's now going to be 25% off from what you expect. This is ultimately because [CircuitPython has reduced floating point precision (30-bit instead of 32-bit)](https://learn.adafruit.com/welcome-to-circuitpython/frequently-asked-questions#faq-3129274) ([further discusion here](https://github.com/adafruit/circuitpython/issues/9237)).
1622 | In short: stick to integer milliseconds.
1623 |
1624 |
1625 | ## Board Info
1626 |
1627 | ### Get CPU speed (and set it!)
1628 |
1629 | CircuitPython provides a way to get the microcontroller's CPU frequency with
1630 | [`microcontroller.cpu.frequency`](https://docs.circuitpython.org/en/latest/shared-bindings/microcontroller/index.html#microcontroller.Processor.frequency).
1631 |
1632 | ```py
1633 | import microcontroller
1634 | print("CPU speed:", microcontroller.cpu.frequency)
1635 | ```
1636 |
1637 | On some chips (most notably Pico P2040), you can also **set** this value. Overclock your Pico!
1638 | It's safe to double the RP2040 speed, Raspberry Pi officially supports up to 200 MHz.
1639 | CircuitPython will adjust its internal timings, but you should
1640 | do this change before creating any peripheral objects like UARTs or displays.
1641 |
1642 | ```py
1643 | import microcontroller
1644 | microcontroller.cpu.frequency = 250_000_000 # run at 250 MHz instead of 125 MHz
1645 | ```
1646 |
1647 |
1648 | ### Display amount of free RAM
1649 |
1650 | from: https://learn.adafruit.com/welcome-to-circuitpython/frequently-asked-questions
1651 | ```py
1652 | import gc
1653 | print( gc.mem_free() )
1654 | ```
1655 |
1656 | ### Show microcontroller.pin to board mappings
1657 | from https://gist.github.com/anecdata/1c345cb2d137776d76b97a5d5678dc97
1658 | ```py
1659 |
1660 | import microcontroller
1661 | import board
1662 |
1663 | for pin in dir(microcontroller.pin):
1664 | if isinstance(getattr(microcontroller.pin, pin), microcontroller.Pin):
1665 | print("".join(("microcontroller.pin.", pin, "\t")), end=" ")
1666 | for alias in dir(board):
1667 | if getattr(board, alias) is getattr(microcontroller.pin, pin):
1668 | print("".join(("", "board.", alias)), end=" ")
1669 | print()
1670 | ```
1671 |
1672 | ### Determine which board you're on
1673 |
1674 | ```py
1675 | import os
1676 | print(os.uname().machine)
1677 | 'Adafruit ItsyBitsy M4 Express with samd51g19'
1678 | ```
1679 |
1680 | Another way is the `board.board_id`. This is the "port" name used when
1681 | compiling CircuitPython. To find a list of valid board IDs,
1682 | you can look in the circuitpython core repo inside of: "ports/[some_port]/boards/".
1683 | i.e. for espressif boards find the list of directories in:
1684 | [ports/espressif/boards/](https://github.com/adafruit/circuitpython/tree/main/ports/espressif/boards)
1685 | The "board_id is used as the argument for
1686 | `circuitpython_setboard` in [`circuitpython-stubs`](https://pypi.org/project/circuitpython-stubs/)
1687 |
1688 | ```py
1689 | import board
1690 | print(board.board_id)
1691 | 'raspberry_pi_pico'
1692 | ```
1693 |
1694 | To get the chip family:
1695 |
1696 | ```py
1697 | import os
1698 | print(os.uname().sysname)
1699 | 'ESP32S2'
1700 | ```
1701 |
1702 | ### Support multiple boards with one `code.py`
1703 | ```py
1704 | import os
1705 | board_type = os.uname().machine
1706 | if 'QT Py M0' in board_type:
1707 | tft_clk = board.SCK
1708 | tft_mosi = board.MOSI
1709 | spi = busio.SPI(clock=tft_clk, MOSI=tft_mosi)
1710 | elif 'ItsyBitsy M4' in board_type:
1711 | tft_clk = board.SCK
1712 | tft_mosi = board.MOSI
1713 | spi = busio.SPI(clock=tft_clk, MOSI=tft_mosi)
1714 | elif 'Pico' in board_type:
1715 | tft_clk = board.GP10 # must be a SPI CLK
1716 | tft_mosi= board.GP11 # must be a SPI TX
1717 | spi = busio.SPI(clock=tft_clk, MOSI=tft_mosi)
1718 | else:
1719 | print("unsupported board", board_type)
1720 | ```
1721 |
1722 |
1723 | ## Computery Tasks
1724 |
1725 | ### Formatting strings
1726 | ```py
1727 | name = "John"
1728 | fav_color = 0x003366
1729 | body_temp = 98.65
1730 | fav_number = 123
1731 | print("name:%s color:%06x temp:%2.1f num:%d" % (name,fav_color,body_temp,fav_number))
1732 | # 'name:John color:ff3366 temp:98.6 num:123'
1733 | ```
1734 |
1735 | ### Formatting strings with f-strings
1736 | (doesn't work on 'small' CircuitPythons like QTPy M0)
1737 |
1738 | ```py
1739 | name = "John"
1740 | fav_color = 0xff3366
1741 | body_temp = 98.65
1742 | fav_number = 123
1743 | print(f"name:{name} color:{fav_color:06x} temp:{body_temp:2.1f} num:{fav_number}")
1744 | # 'name:John color:ff3366 temp:98.6 num:123'
1745 | ```
1746 |
1747 | ### Using regular expressions to "findall" strings
1748 |
1749 | Regular expressions are a really powerful way to match information in and parse data
1750 | from strings. While CircuitPython has a version of the `re` regex module you may know
1751 | from desktop Python, it is very limited. Specifcally it doesn't have the very useful
1752 | `re.findall()` function. Below is a semi-replacement for `findall()`.
1753 |
1754 | ```py
1755 | import re
1756 | def find_all(regex, some_str):
1757 | matches = []
1758 | while m := regex.search(some_str):
1759 | matches.append( m.groups() )
1760 | some_str = some_str[ m.end(): ] # get past match
1761 | return matches
1762 |
1763 | my_str = "thing1 I want thing2 I want thing3 I want"
1764 | regex1 = re.compile('(.*?)<\/thing>')
1765 | my_matches = find_all( regex1, my_str )
1766 | print("matches:", my_matches)
1767 |
1768 | ```
1769 |
1770 |
1771 | ### Make and use a config file
1772 |
1773 | ```py
1774 | # my_config.py
1775 | config = {
1776 | "username": "Grogu Djarin",
1777 | "password": "ig88rules",
1778 | "secret_key": "3a3d9bfaf05835df69713c470427fe35"
1779 | }
1780 |
1781 | # code.py
1782 | from my_config import config
1783 | print("secret:", config['secret_key'])
1784 | # 'secret: 3a3d9bfaf05835df69713c470427fe35'
1785 | ```
1786 |
1787 | ### Run different `code.py` on startup
1788 |
1789 | Use `microcontroller.nvm` to store persistent state across
1790 | resets or between `boot.py` and `code.py`, and declare that
1791 | the first byte of `nvm` will be the `startup_mode`.
1792 | Now if you create multiple code.py files (say) `code1.py`, `code2.py`, etc.
1793 | you can switch between them based on `startup_mode`.
1794 |
1795 | ```py
1796 | import time
1797 | import microcontroller
1798 | startup_mode = microcontroller.nvm[0]
1799 | if startup_mode == 1:
1800 | import code1 # runs code in `code1.py`
1801 | if startup_mode == 2:
1802 | import code2 # runs code in `code2.py`
1803 | # otherwise runs 'code.py`
1804 | while True:
1805 | print("main code.py")
1806 | time.sleep(1)
1807 |
1808 | ```
1809 |
1810 | **Note:** in CircuitPyton 7+ you can use [`supervisor.set_next_code_file()`](https://circuitpython.readthedocs.io/en/latest/shared-bindings/supervisor/index.html#supervisor.set_next_code_file)
1811 | to change which .py file is run on startup.
1812 | This changes only what happens on reload, not hardware reset or powerup.
1813 | Using it would look like:
1814 | ```py
1815 | import supervisor
1816 | supervisor.set_next_code_file('code_awesome.py')
1817 | # and then if you want to run it now, trigger a reload
1818 | supervisor.reload()
1819 | ```
1820 |
1821 | ## Coding Techniques
1822 |
1823 | ### Map an input range to an output range
1824 |
1825 | ```py
1826 | # simple range mapper, like Arduino map()
1827 | def map_range(s, a1, a2, b1, b2):
1828 | return b1 + ((s - a1) * (b2 - b1) / (a2 - a1))
1829 |
1830 | # example: map 0-0123 value to 0.0-1.0 value
1831 | val = 768
1832 | outval = map_range( val, 0,1023, 0.0,1.0 )
1833 | # outval = 0.75
1834 | ```
1835 |
1836 | ### Constrain an input to a min/max
1837 | The Python built-in `min()` and `max()` functions can be used together
1838 | to make something like Arduino's `constrain()` to clamp an input between two values.
1839 |
1840 | ```py
1841 | # constrain a value to be 0-255
1842 | outval = min(max(val, 0), 255)
1843 | # constrain a value to be 0-255 integer
1844 | outval = int(min(max(val, 0), 255))
1845 | # constrain a value to be -1 to +1
1846 | outval = min(max(val, -1), 1)
1847 | ```
1848 |
1849 | ### Turn a momentary value into a toggle
1850 |
1851 | ```py
1852 | import touchio
1853 | import board
1854 |
1855 | touch_pin = touchio.TouchIn(board.GP6)
1856 | last_touch_val = False # holds last measurement
1857 | toggle_value = False # holds state of toggle switch
1858 |
1859 | while True:
1860 | touch_val = touch_pin.value
1861 | if touch_val != last_touch_val:
1862 | if touch_val:
1863 | toggle_value = not toggle_value # flip toggle
1864 | print("toggle!", toggle_value)
1865 | last_touch_val = touch_val
1866 | ```
1867 |
1868 | ### Do something every N seconds without `sleep()`
1869 |
1870 | Also known as "blink-without-delay"
1871 |
1872 | ```py
1873 | import time
1874 | last_time1 = time.monotonic() # holds when we did something #1
1875 | last_time2 = time.monotonic() # holds when we did something #2
1876 | while True:
1877 | if time.monotonic() - last_time1 > 2.0: # every 2 seconds
1878 | last_time1 = time.monotonic() # save when we do the thing
1879 | print("hello!") # do thing #1
1880 | if time.monotonic() - last_time2 > 5.0: # every 5 seconds
1881 | last_time2 = time.monotonic() # save when we do the thing
1882 | print("world!") # do thing #2
1883 |
1884 | ```
1885 |
1886 | Note: a more accurate of this [uses `ticks_ms()`](#more-accurate-timing-with-ticks_ms-like-arduino-millis)
1887 | and maybe turning off gc / garbage collection.
1888 |
1889 |
1890 | ## System error handling
1891 |
1892 | ### Preventing Ctrl-C from stopping the program
1893 |
1894 | Put a `try`/`except KeyboardInterrupt` to catch the Ctrl-C
1895 | on the inside of your main loop.
1896 |
1897 | ```py
1898 | while True:
1899 | try:
1900 | print("Doing something important...")
1901 | time.sleep(0.1)
1902 | except KeyboardInterrupt:
1903 | print("Nice try, human! Not quitting.")
1904 | ```
1905 |
1906 | Also useful for graceful shutdown (turning off neopixels, say) on Ctrl-C.
1907 |
1908 | ```py
1909 | import time, random
1910 | import board, neopixel, rainbowio
1911 | leds = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.4 )
1912 | while True:
1913 | try:
1914 | rgb = rainbowio.colorwheel(int(time.monotonic()*75) % 255)
1915 | leds.fill(rgb)
1916 | time.sleep(0.05)
1917 | except KeyboardInterrupt:
1918 | print("shutting down nicely...")
1919 | leds.fill(0)
1920 | break # gets us out of the while True
1921 | ```
1922 |
1923 | ### Prevent auto-reload when CIRCUITPY is touched
1924 |
1925 | Normally, CircuitPython restarts anytime the CIRCUITPY drive is written to.
1926 | This is great normally, but is frustrating if you want your code to keep running,
1927 | and you want to control exactly when a restart happens.
1928 |
1929 | ```py
1930 | import supervisor
1931 | supervisor.runtime.autoreload = False # CirPy 8 and above
1932 | #supervisor.disable_autoreload() # CirPy 7 and below
1933 | ```
1934 |
1935 | To trigger a reload, do a Ctrl-C + Ctrl-D in the REPL or reset your board.
1936 |
1937 | ### Raspberry Pi Pico boot.py Protection
1938 |
1939 | Also works on other RP2040-based boards like QTPy RP2040.
1940 | From https://gist.github.com/Neradoc/8056725be1c209475fd09ffc37c9fad4
1941 |
1942 | Also see [getting into Safe Mode with a REPL one-liner](#useful-repl-one-liners).
1943 |
1944 | ```py
1945 | # Copy this as 'boot.py' in your Pico's CIRCUITPY drive
1946 | # Useful in case Pico locks up (which it's done a few times on me)
1947 | import board
1948 | import time
1949 | from digitalio import DigitalInOut,Pull
1950 |
1951 | led = DigitalInOut(board.LED)
1952 | led.switch_to_output()
1953 |
1954 | safe = DigitalInOut(board.GP14) # <-- choose your button pin
1955 | safe.switch_to_input(Pull.UP)
1956 |
1957 | def reset_on_pin():
1958 | if safe.value is False:
1959 | import microcontroller
1960 | microcontroller.on_next_reset(microcontroller.RunMode.SAFE_MODE)
1961 | microcontroller.reset()
1962 |
1963 | led.value = False
1964 | for x in range(16):
1965 | reset_on_pin()
1966 | led.value = not led.value # toggle LED on/off as notice
1967 | time.sleep(0.1)
1968 |
1969 | ```
1970 |
1971 |
1972 | ## Using the REPL
1973 |
1974 | The "serial" REPL is the most useful diagnostic tools in CircuitPython.
1975 | Always have it open when saving your code to see any errors.
1976 | If you use a separate terminal program instead of an IDE, I recommend [`tio`](https://github.com/tio/tio).
1977 |
1978 | ### Display built-in modules / libraries
1979 | ```
1980 | Adafruit CircuitPython 6.2.0-beta.2 on 2021-02-11; Adafruit Trinket M0 with samd21e18
1981 | >>> help("modules")
1982 | __main__ digitalio pulseio supervisor
1983 | analogio gc pwmio sys
1984 | array math random time
1985 | board microcontroller rotaryio touchio
1986 | builtins micropython rtc usb_hid
1987 | busio neopixel_write storage usb_midi
1988 | collections os struct
1989 | Plus any modules on the filesystem
1990 | ```
1991 |
1992 | ### Turn off built-in display to speed up REPL printing
1993 |
1994 | By default CircuitPython will echo the REPL to the display of those boards with built-in displays.
1995 | This can slow down the REPL. So one way to speed the REPL up is to hide the `displayio.Group` that
1996 | contains all the REPL output.
1997 |
1998 | ```py
1999 | import board, displayio
2000 | board.DISPLAY.root_group = None # turn off REPL printing
2001 | board.DISPLAY.root_group = displayio.CIRCUITPYTHON_TERMINAL # turn back on REPL printing
2002 | ```
2003 |
2004 | In CircuitPython 8.x, you could do the below. In 9.x, the `root_group` is read-only
2005 | after it's been assigned.
2006 |
2007 | ```py
2008 | import board
2009 | board.DISPLAY.root_group.hidden = True
2010 | board.DISPLAY.root_group.hidden = False # to turn it back on
2011 | ```
2012 |
2013 | ### Useful REPL one-liners
2014 |
2015 | (yes, semicolons are legal in Python)
2016 |
2017 | ```py
2018 | # get into Safe Mode if you have REPL access
2019 | import microcontroller; microcontroller.on_next_reset(microcontroller.RunMode.SAFE_MODE); microcontroller.reset()
2020 |
2021 | # load common libraries (for later REPL experiments)
2022 | import time, board, analogio, touchio; from digitalio import DigitalInOut,Pull
2023 |
2024 | # create a pin and set a pin LOW (if you've done the above)
2025 | pin = DigitalInOut(board.GP0); pin.switch_to_output(value=False)
2026 |
2027 | # print out board pins and objects (like `I2C`, `STEMMA_I2C`, `DISPLAY`, if present)
2028 | import board; dir(board)
2029 |
2030 | # print out microcontroller pins (chip pins, not the same as board pins)
2031 | import microcontroller; dir(microcontroller.pin)
2032 |
2033 | # release configured / built-in display
2034 | import displayio; displayio.release_displays()
2035 |
2036 | # turn off auto-reload when CIRCUITPY drive is touched
2037 | import supervisor; supervisor.runtime.autoreload = False
2038 |
2039 | # test neopixel strip, make them all purple
2040 | import board, neopixel; leds = neopixel.NeoPixel(board.GP3, 8, brightness=0.2); leds.fill(0xff00ff)
2041 | leds.deinit() # releases pin
2042 |
2043 | # scan I2C bus
2044 | import board; i2c=board.I2C(); i2c.try_lock(); [hex(a) for a in i2c.scan()]; i2c.unlock()
2045 |
2046 | ```
2047 |
2048 |
2049 | ## Python tricks
2050 |
2051 | These are general Python tips that may be useful in CircuitPython.
2052 |
2053 | ### Create list with elements all the same value
2054 |
2055 | ```py
2056 | blank_array = [0] * 50 # creats 50-element list of zeros
2057 | ```
2058 |
2059 | ### Convert RGB tuples to int and back again
2060 |
2061 | Thanks to @Neradoc for this tip:
2062 |
2063 | ```py
2064 | rgb_tuple = (255, 0, 128)
2065 | rgb_int = int.from_bytes(rgb_tuple, byteorder='big')
2066 |
2067 | rgb_int = 0xFF0080
2068 | rgb_tuple2 = tuple((rgb_int).to_bytes(3,"big"))
2069 |
2070 | rgb_tuple2 == rgb_tuple
2071 | ```
2072 |
2073 | ### Storing multiple values per list entry
2074 |
2075 | Create simple data structures as config to control your program.
2076 | Unlike Arduino, you can store multiple values per list/array entry.
2077 |
2078 | ```py
2079 | mycolors = (
2080 | # color val, name
2081 | (0x0000FF, "blue"),
2082 | (0x00FFFF, "cyan"),
2083 | (0xFF00FF, "purple"),
2084 | )
2085 | for i in range(len(mycolors)):
2086 | (val, name) = mycolors[i]
2087 | print("my color ", name, "has the value", val)
2088 | ```
2089 |
2090 |
2091 | ## Python info
2092 |
2093 | How to get information about Python inside of CircuitPython.
2094 |
2095 | ### Display which (not built-in) libraries have been imported
2096 | ```py
2097 | import sys
2098 | print(sys.modules.keys())
2099 | # 'dict_keys([])'
2100 | import board
2101 | import neopixel
2102 | import adafruit_dotstar
2103 | print(sys.modules.keys())
2104 | prints "dict_keys(['neopixel', 'adafruit_dotstar'])"
2105 | ```
2106 |
2107 | ### List names of all global variables
2108 | ```py
2109 | a = 123
2110 | b = 'hello there'
2111 | my_globals = sorted(dir)
2112 | print(my_globals)
2113 | # prints "['__name__', 'a', 'b']"
2114 | if 'a' in my_globals:
2115 | print("you have a variable named 'a'!")
2116 | if 'c' in my_globals:
2117 | print("you have a variable named 'c'!")
2118 | ```
2119 |
2120 | ### Display the running CircuitPython release
2121 |
2122 | With an established serial connection, press `Ctrl+c`:
2123 |
2124 | ```sh
2125 | Adafruit CircuitPython 7.1.1 on 2022-01-14; S2Pico with ESP32S2-S2FN4R2
2126 | >>>
2127 | ```
2128 |
2129 | Without connection or code running, check the `boot_out.txt` file in your CIRCUITPY drive.
2130 |
2131 | ```py
2132 | import os
2133 | print(os.uname().release)
2134 | '7.1.1'
2135 | print(os.uname().version)
2136 | '7.1.1 on 2022-01-14'
2137 | ```
2138 |
2139 | ## Host-side tasks
2140 |
2141 | Things you might need to do on your computer when using CircuitPython.
2142 |
2143 | ### Installing CircuitPython libraries
2144 |
2145 | The below examples are for MacOS / Linux. Similar commands are used for Windows
2146 |
2147 | #### Installing libraries with `circup`
2148 |
2149 | `circup` can be used to easily install and update modules
2150 |
2151 | ```sh
2152 | $ pip3 install --user circup
2153 | $ circup install adafruit_midi
2154 | $ circup update # updates all modules
2155 | ```
2156 |
2157 | Freshly update all modules to latest version (e.g. when going from CP 6 -> CP 7)
2158 | (This is needed because `circup update` doesn't actually seem to work reliably)
2159 |
2160 | ```sh
2161 | circup freeze > mymodules.txt
2162 | rm -rf /Volumes/CIRCUITPY/lib/*
2163 | circup install -r mymodules.txt
2164 | ```
2165 |
2166 |
2167 | And updating circup when a new version of CircuitPython comes out:
2168 | ```sh
2169 | $ pip3 install --upgrade circup
2170 | ```
2171 |
2172 |
2173 | #### Copying libraries by hand with `cp`
2174 |
2175 | To install libraries by hand from the
2176 | [CircuitPython Library Bundle](https://circuitpython.org/libraries)
2177 | or from the [CircuitPython Community Bundle](https://github.com/adafruit/CircuitPython_Community_Bundle/releases) (which circup doesn't support), get the bundle, unzip it and then use `cp -rX`.
2178 |
2179 | ```sh
2180 | cp -rX bundle_folder/lib/adafruit_midi /Volumes/CIRCUITPY/lib
2181 | ```
2182 |
2183 | **Note:** on limited-memory boards like Trinkey, Trinket, QTPy, you must use the `-X` option on MacOS
2184 | to save space. You may also need to omit unused parts of some libraries (e.g. `adafruit_midi/system_exclusive` is not needed if just sending MIDI notes)
2185 |
2186 |
2187 | ### Preparing images for CircuitPython
2188 |
2189 | There's two ways to load images for use with `displayio`:
2190 | * The built-in [`displayio.OnDiskBitmap()`](https://docs.circuitpython.org/en/latest/shared-bindings/displayio/#displayio.OnDiskBitmap)
2191 | * The library [`adafruit_imageload`](https://docs.circuitpython.org/projects/imageload/en/latest/index.html)
2192 |
2193 | `displayio.OnDisBitmap()`:
2194 | * can load "indexed" (aka "palette") non-compressed BMP3 images
2195 | * doesn't load image into RAM (great for TileGrids)
2196 |
2197 | `adafruit_imageload`
2198 | * can load BMP3 images with RLE compression
2199 | * loads entire image into RAM (thus you may run out of memory)
2200 | * an load palette PNG images and GIF images
2201 | * PNG image loading is almost as fast as BMP and uses 1/2 the disk space
2202 | * GIF loading is very slow
2203 |
2204 | To make images load faster generally, you can reduce the number of colors in the image.
2205 | The maximum number of colors is 256, but try reducing colors to 64 or even 2 if
2206 | it's a black-n-white image.
2207 |
2208 | Some existing Learn Guides:
2209 | - https://learn.adafruit.com/creating-your-first-tilemap-game-with-circuitpython/indexed-bmp-graphics
2210 | - https://learn.adafruit.com/preparing-graphics-for-e-ink-displays
2211 |
2212 | And here's some ways to do the conversions.
2213 |
2214 |
2215 | #### Online
2216 |
2217 | Most online image converters do not create BMPs in the proper format:
2218 | BMP3, non-compressed, up to 256 colors in an 8-bit palette.
2219 |
2220 | However @Neradoc found the site [convert2bmp](https://online-converting.com/image/convert2bmp/)
2221 | will work when you set "Color:" mode to "8 (Indexed)". Some initial tests show this works!
2222 | I'd recommend also trying out one of the following techniques too to have finer control.
2223 |
2224 | The site https://cancerberosgx.github.io/magic/playground/ lets you
2225 | use any of the ImageMagick commands below to convert images. It's really handy if you can't
2226 | install ImageMagick locally.
2227 |
2228 |
2229 | #### Command-line: using ImageMagick
2230 |
2231 | [ImageMagick](https://imagemagick.org/) is a command-line image manipulation tool. With it,
2232 | you can convert any image format to BMP3 format. The main ImageMagick CLI command is `convert`.
2233 |
2234 | ```sh
2235 | convert myimage.jpg -resize 240x240 -type palette -colors 64 -compress None BMP3:myimage_for_cirpy.bmp
2236 | ```
2237 |
2238 | You can also use this technique to create reduced-color palette PNGs:
2239 |
2240 | ```sh
2241 | convert myimage.jpg -resize 240x240 -type palette -colors 64 myimage.png
2242 | ```
2243 |
2244 | #### Command-line: using GraphicsMagick
2245 |
2246 | [GraphicsMagick](http://www.graphicsmagick.org/) is a slimmer, lower-requirement
2247 | clone of ImageMagick. All GraphicsMagick commands are accessed via the `gm` CLI command.
2248 |
2249 | ```sh
2250 | gm convert myimage.jpg -resize 240x240 -colors 64 -type palette -compress None BMP3:myimage_for_cirpy.bmp
2251 | ```
2252 |
2253 | #### Making images smaller or for E-Ink displays
2254 |
2255 | To make images smaller (and load faster), reduce number of colors from 256.
2256 | If your image is a monochrome (or for use with E-Ink displays like MagTag), use 2 colors.
2257 | The ["-dither" options](https://legacy.imagemagick.org/Usage/quantize/#colors)
2258 | are really helpful for monochrome:
2259 | ```
2260 | convert cat.jpg -dither FloydSteinberg -colors 2 -type palette BMP3:cat.bmp
2261 | ```
2262 |
2263 |
2264 | #### NodeJs: using gm
2265 |
2266 | There is a nice wrapper around GraphicsMagick / Imagemagick with the [`gm library`](https://github.com/aheckmann/gm).
2267 | A small NodeJs program to convert images could look like this:
2268 |
2269 | ```js
2270 | var gm = require('gm');
2271 | gm('myimage.jpg')
2272 | .resize(240, 240)
2273 | .colors(64)
2274 | .type("palette")
2275 | .compress("None")
2276 | .write('BMP3:myimage_for_cirpy.bmp', function (err) {
2277 | if (!err) console.log('done1');
2278 | });
2279 | ```
2280 |
2281 | #### Python: using PIL / pillow
2282 |
2283 | The [Python Image Library (PIL) fork `pillow`](https://pillow.readthedocs.io/en/stable/index.html)
2284 | seems to work the best. It's unclear how to toggle compression.
2285 |
2286 | ```py
2287 | from PIL import Image
2288 | size = (240, 240)
2289 | num_colors = 64
2290 | img = Image.open('myimage.jpg')
2291 | img = img.resize(size)
2292 | newimg = img.convert(mode='P', colors=num_colors)
2293 | newimg.save('myimage_for_cirpy.bmp')
2294 | ```
2295 |
2296 | ### Preparing audio files for CircuitPython
2297 |
2298 | CircuitPython can play both WAV files and MP3 files, but there are specific
2299 | variants of these files that will work better, as some options require much more
2300 | processor demand. WAV files are much easier to play but take up more disk space.
2301 |
2302 | #### WAV files
2303 |
2304 | For WAV files, I've found the best trade-off in quality / flash-space / compatibility to be:
2305 |
2306 | - PCM 16-bit signed PCM
2307 | - Mono (but stereo will work if using I2S or SAMD51)
2308 | - 22050 Hz sample rate
2309 |
2310 | And remember that these settings must match how you're setting up the `audiomixer` object.
2311 | So for the above settings, you'd create an `audiomixer.Mixer` like:
2312 |
2313 | ```py
2314 | mixer = audiomixer.Mixer(voice_count=1, sample_rate=22050, channel_count=1,
2315 | bits_per_sample=16, samples_signed=True)
2316 | ```
2317 |
2318 | To convert WAVs for CircuitPython, I like to use Audacity or the `sox` command-line tool.
2319 | Sox can convert just about any audio to the correct WAV format:
2320 |
2321 | ```sh
2322 | sox loop.mp3 -b 16 -c 1 -r 22050 loop.wav
2323 | ```
2324 |
2325 | #### MP3 files
2326 |
2327 | MP3 files require a lot more CPU to decode so in general you will want to
2328 | re-encode MP3s to be a lower bit-rate and lower sample-rate. These settings
2329 | seem to work okay on an lower-end chip like the Pico /RP2040:
2330 |
2331 | * 128 kbps data rate CBR or lower
2332 | * 22050 Hz sample rate or lower
2333 | * Mono
2334 |
2335 | In `sox`, you can do this conversion with:
2336 |
2337 | ```sh
2338 | sox loop.mp3 -c 1 -r 22050 -C 128 loop_22k_128kbps.mp3
2339 | ```
2340 |
2341 |
2342 | #### Getting sox
2343 |
2344 | To get `sox` on various platforms:
2345 | - Linux: `sudo apt install sox libsox-fmt-mp3`
2346 | - macOS: `brew install sox`
2347 | - Windows: Use installer at http://sox.sourceforge.net/
2348 |
2349 | Some audio Learn Guide links:
2350 | - https://learn.adafruit.com/circuitpython-essentials/circuitpython-audio-out#play-a-wave-file-2994862-6
2351 | - https://learn.adafruit.com/adafruit-wave-shield-audio-shield-for-arduino/convert-files
2352 |
2353 |
2354 | ### Circup hacks
2355 |
2356 | [`circup`](https://learn.adafruit.com/keep-your-circuitpython-libraries-on-devices-up-to-date-with-circup?view=all)
2357 | is a great tool to help you install CircuitPython libraries. Think of it like `pip` or `npm` for CircuitPython.
2358 |
2359 | #### Finding where `circup` stores its files
2360 |
2361 | Instead of downloading the bundles by hand, `circup` has it already downloaded an unzipped.
2362 | Here's how to find that directory:
2363 |
2364 | ```sh
2365 | circup_dir=`python3 -c 'import appdirs; print(appdirs.user_data_dir(appname="circup", appauthor="adafruit"))'`
2366 | ls $circup_dir
2367 | ```
2368 |
2369 | ### Building CircuitPython
2370 |
2371 | If you want to build CircuitPython yourself, you can! It's not too bad.
2372 | There's a very good ["Building CircuitPython" Learn Guide](https://learn.adafruit.com/building-circuitpython/build-circuitpython) that I refer to all the time, since it goes through the main reasons
2373 | why you might want to build your own version of CircuitPython, including:
2374 | - Adding "Frozen" Modules (libraries built-in to the firmware)
2375 | - Setting different SPI flash chips (if your custom board uses a different kind of flash)
2376 | - Adding a new board to CircuitPython
2377 |
2378 | But if you just want a quick list of the commands to use to build, here's what I use
2379 | (as of Jun 2024) to build CircuitPython for rp2040.
2380 |
2381 | ```sh
2382 | git clone https://github.com/todbot/circuitpython circuitpython-todbot
2383 | cd circuitpython-todbot
2384 | pip3 install --upgrade -r requirements-dev.txt # do occasionally, after 'git pull'
2385 | pip3 install --upgrade -r requirements-doc.txt # do occasionally, after 'git pull'
2386 | cd ports/raspberrypi
2387 | make fetch-port-submodules # takes a long time the first time ran, do after 'git pull' too
2388 | make BOARD=raspberry_pi_pico # or other board name listed in ports/raspberrypi/boards/
2389 | # make -C ../../mpy-cross # if you need mpy-cross
2390 | ```
2391 |
2392 | And for Espressif / ESP32 builds:
2393 |
2394 | ```sh
2395 | git clone https://github.com/todbot/circuitpython circuitpython-todbot
2396 | cd circuitpython-todbot
2397 | pip3 install --upgrade -r requirements-dev.txt # do occasionally, after 'git pull'
2398 | pip3 install --upgrade -r requirements-doc.txt # do occasionally, after 'git pull'
2399 | cd ports/espressif
2400 | make fetch-port-submodules # takes a long time the first time ran, do after 'git pull' too
2401 | ./esp-idf/install.sh
2402 | . ./esp-idf/export.sh
2403 | pip3 install --upgrade -r requirements-dev.txt # because now we're using a new python
2404 | pip3 install --upgrade -r requirements-doc.txt # because now we're using a new python
2405 | make BOARD=adafruit_qtpy_esp32s3
2406 | ```
2407 |
2408 | Note, this assumes you've [already installed the system-level prerequisites](https://learn.adafruit.com/building-circuitpython/introduction).
2409 | On MacOS, this is what I do to get those:
2410 |
2411 | ```sh
2412 | brew install git git-lfs python3 gettext uncrustify cmake
2413 | brew install gcc-arm-embedded # (the cask, not 'arm-none-eabi-gcc')
2414 | ```
2415 |
2416 | ## About this guide
2417 |
2418 | * Many of the code snippets are not considered "well-formatted"
2419 | by Python linters. This guide favors brevity over strict style adherence.
2420 |
2421 | * The precursor to this guide is [QTPy Tricks](https://github.com/todbot/qtpy-tricks),
2422 | which has similar but different (and still valid) fun things to do in CircuitPython.
2423 |
2424 | * This guide is the result of trying to learn Python via CircuitPython and
2425 | from very enlightening discussions with John Edgar Park. I have a good background in
2426 | other languages, but mostly C/C++, and have
2427 | [taught classes in Arduino](https://todbot.com/blog/bionicarduino/) for several years.
2428 | This informs how I've been thinking about things in this guide.
2429 |
--------------------------------------------------------------------------------
/larger-tricks/README.md:
--------------------------------------------------------------------------------
1 |
2 | # circuitpython-tricks / larger-tricks
3 |
4 | These are larger tricks that are too big to fit in the
5 | [circuitpython-tricks](https://github.com/todbot/circuitpython-tricks) page.
6 |
7 | This directory contains a fairly un-ordered list, but here are the main tricks.
8 |
9 |
10 | ## Display and images
11 |
12 | * **emoji_flipper** - demonstrate how to use `displayio` and spritesheets
13 |
14 | * **fireworks_sprites** - demonstrate using `displayio` spritesheets to do animation and simple phyics engine
15 |
16 | * **dvdlogo_vectorio** - demonstrate `vectorio` to display bouncing DVD logo
17 |
18 | * **vectorio_rotate_example** - some `vectorio` tools to rotate & scale vectorio point sets
19 |
20 |
21 | ## Audio playing
22 |
23 | * **audiomixer_demo** - show how to use `audiomixer` to play multiple WAVs at different volumes
24 |
25 | * **pidaydrummachine** - show use of `keypad` and `audiomixer` to make a drum machine
26 |
27 | * **beatfader** - using `audiomixer` to fade between different WAVs
28 |
29 | * **beatfader_i2s** - using I2S DAC and `audiomixer` to fade between different WAVs
30 |
31 | * **i2s_sdcard_pico** - how to set up I2S audio out and SD card reading on Raspberry Pi Pico RP2040
32 |
33 |
34 | ## MIDI
35 |
36 | * **midi_forward** - simple USB MIDI to Serial MIDI forwarder using `adafruit_midi` and `usb_midi`
37 |
38 |
39 | ## USB
40 |
41 | * **robost_keyboard** - demonstrate how to deal with USB disconnect for battery-powered USB HID keyboard devices
42 |
--------------------------------------------------------------------------------
/larger-tricks/audiomixer_demo.py:
--------------------------------------------------------------------------------
1 | # audiomixer_demo.py -- show how to fade up and down playing loops
2 | # note this causes glitches and crashes on RP2040
3 | # 9 Feb 2022 - @todbot / Tod Kurt
4 |
5 | import time
6 | import board
7 | import audiocore
8 | import audiomixer
9 | #from audiopwmio import PWMAudioOut as AudioOut # for RP2040 etc
10 | from audioio import AudioOut as AudioOut # for SAMD51/M4 etc
11 |
12 | num_voices = 2
13 |
14 | # audio pin is almost any pin on RP2040, let's do A0 (RP2040 GPIO226) or RX (RP2040 GPIO1)
15 | # audio pin is A0 on SAMD51 (Trelllis M4, Itsy M4, etc)
16 | audio = AudioOut(board.A0)
17 | mixer = audiomixer.Mixer(voice_count=num_voices, sample_rate=22050, channel_count=1,
18 | bits_per_sample=16, samples_signed=True)
19 | # attach mixer to audio playback
20 | audio.play(mixer)
21 |
22 | mixer.voice[0].level = 1.0
23 | mixer.voice[1].level = 0.0
24 |
25 | # start drum loop playing
26 | wave0 = audiocore.WaveFile(open("wav/drumsacuff_22k_s16.wav","rb"))
27 | mixer.voice[0].play( wave0, loop=True )
28 |
29 | # start synth loop playing
30 | wave1 = audiocore.WaveFile(open("wav/snowpeaks_22k_s16.wav","rb"))
31 | mixer.voice[1].play( wave1, loop=True )
32 |
33 | time.sleep(1.0) # let drums play a bit
34 |
35 | # fade each channel up and down
36 | up_down_inc = 0.01
37 | while True:
38 | mixer.voice[1].level = min(max(mixer.voice[1].level + up_down_inc, 0), 1)
39 | mixer.voice[0].level = min(max(mixer.voice[0].level - up_down_inc, 0), 1)
40 | if mixer.voice[0].level == 0 or mixer.voice[1].level == 0:
41 | up_down_inc = -up_down_inc
42 | time.sleep(0.1)
43 |
44 |
--------------------------------------------------------------------------------
/larger-tricks/audiomixer_demo_i2s.py:
--------------------------------------------------------------------------------
1 | # audiomixer_demo_i2s.py -- show how to fade up and down playing loops
2 | # Code based on that of https://github.com/todbot/circuitpython-tricks/blob/main/larger-tricks/audiomixer_demo.py
3 | # where you can also find the WAV files used
4 | # 30 Nov 2022 - @todbot / Tod Kurt
5 |
6 | import time
7 | import board
8 | import audiocore
9 | import audiomixer
10 | import audiobusio
11 |
12 | i2s_bclk = board.GP9 # BCK on PCM5102 I2S DAC (SCK pin to Gnd)
13 | i2s_wsel = board.GP10 # LCLK on PCM5102
14 | i2s_data = board.GP11 # DIN on PCM5102
15 | num_voices = 2
16 |
17 | audio = audiobusio.I2SOut(bit_clock=i2s_bclk, word_select=i2s_wsel, data=i2s_data)
18 |
19 | mixer = audiomixer.Mixer(voice_count=num_voices, sample_rate=22050, channel_count=1,
20 | bits_per_sample=16, samples_signed=True)
21 | audio.play(mixer) # attach mixer to audio playback
22 |
23 | print("audio is now playing")
24 |
25 | # set some initial levels
26 | mixer.voice[0].level = 1.0 # drums on
27 | mixer.voice[1].level = 0.0 # synth off
28 |
29 | # load wav and start drum loop playing
30 | wave0 = audiocore.WaveFile(open("wav/drumsacuff_22k_s16.wav","rb"))
31 | mixer.voice[0].play( wave0, loop=True )
32 |
33 | # load wav and start synth loop playing
34 | wave1 = audiocore.WaveFile(open("wav/snowpeaks_22k_s16.wav","rb"))
35 | mixer.voice[1].play( wave1, loop=True )
36 |
37 | time.sleep(1.0) # let drums play a bit
38 |
39 | # fade each channel up and down
40 | up_down_inc = 0.01
41 | while True:
42 | print("voice 0 level=%1.2f" % mixer.voice[0].level)
43 | mixer.voice[1].level = min(max(mixer.voice[1].level + up_down_inc, 0), 1)
44 | mixer.voice[0].level = min(max(mixer.voice[0].level - up_down_inc, 0), 1)
45 | if mixer.voice[0].level == 0 or mixer.voice[1].level == 0:
46 | up_down_inc = -up_down_inc
47 | time.sleep(0.1)
48 |
--------------------------------------------------------------------------------
/larger-tricks/beatfader.py:
--------------------------------------------------------------------------------
1 | # beatfader.py -- fade between different drum loops
2 | # 18 Feb 2022 - @todbot / Tod Kurt
3 | #
4 | # Tested on Raspberry Pi Pico, uses PWM Audio
5 | #
6 | # Copy this file to CIRCUITPY/code.py
7 | # Copy the "beatfader_wavs" directory to CIRCUITPY
8 | #
9 | # Note: CircuitPython will hang or freeze sometimes with audiopwmio
10 | # especially if USB access is happening (e.g. saving files)
11 | # To minimize this, stop the program (Ctrl-C in REPL) before
12 | # saving.
13 | #
14 | # Circuit as in ./docs/breakbeat_sampleplayer.fzz
15 | #
16 |
17 | import time
18 | import board
19 | import analogio
20 | import audiocore
21 | import audiomixer
22 | from audiopwmio import PWMAudioOut as AudioOut
23 |
24 | wav_files = (
25 | # filename, loop?
26 | ('beatfader_wavs/amen_22k16b_160bpm.wav', True),
27 | ('beatfader_wavs/dnb21580_22k16b_160bpm.wav', True),
28 | ('beatfader_wavs/drumloopA_22k16b_160bpm.wav', True),
29 | ('beatfader_wavs/amen_22k16b_160bpm.wav', True),
30 | )
31 |
32 | potknob = analogio.AnalogIn(board.A2)
33 |
34 | time.sleep(3) # helps prevent CirPy from crashing from USB + audio
35 |
36 | audio = AudioOut(board.GP1)
37 | mixer = audiomixer.Mixer(voice_count=len(wav_files), sample_rate=22050, channel_count=1,
38 | bits_per_sample=16, samples_signed=True)
39 | audio.play(mixer) # attach mixer to audio playback
40 |
41 | for i in range(len(wav_files)):
42 | (wav_file, loopit) = wav_files[i]
43 | wave = audiocore.WaveFile(open(wav_file,"rb"))
44 | mixer.voice[i].level = 0.0 # start quiet
45 | mixer.voice[i].play(wave, loop=loopit) # start playing
46 |
47 | cnt = len(wav_files)
48 | n = cnt * 0.8 # amount of overlap
49 | m = 1/(cnt-1) # size of slice
50 |
51 | while True:
52 | potval = potknob.value
53 | frac = potknob.value / 65535
54 | for i in range(cnt):
55 | l = min(max( 1 - (n * (frac - m*i))**2, 0), 1)
56 | mixer.voice[i].level = l
57 | print("%1.2f" % l, end=" ")
58 | print("%0.2f", frac)
59 | time.sleep(0.05)
60 |
--------------------------------------------------------------------------------
/larger-tricks/beatfader_i2s.py:
--------------------------------------------------------------------------------
1 | # beatfader_i2s.py -- fade between different drum loops
2 | # 13 Dec 2022 - @todbot / Tod Kurt
3 | #
4 | # Tested on Lolin S2 Mini ESP32-S2 https://circuitpython.org/board/lolin_s2_mini/
5 | #
6 | # Copy this file to CIRCUITPY/code.py
7 | # Copy the "beatfader_wavs" dir to CIRCUITPY
8 | #
9 | # Wiring is:
10 | # - Hook a PCM5102 I2S DAC to S2 Mini like:
11 | # - IO39 - BCK on PCM5102
12 | # - IO37 - LCLK on PCM5102
13 | # - IO35 - DIN on PCM5102
14 | # - Hook up a potentiometer knob to IO3
15 | # - That's it!
16 | #
17 |
18 | import time
19 | import board
20 | import analogio
21 | import audiocore, audiomixer, audiobusio
22 |
23 | wav_files = (
24 | # filename, loop?
25 | ('beatfader_wavs/amen_22k16b_160bpm.wav', True),
26 | ('beatfader_wavs/dnb21580_22k16b_160bpm.wav', True),
27 | ('beatfader_wavs/drumloopA_22k16b_160bpm.wav', True),
28 | ('beatfader_wavs/amen_22k16b_160bpm.wav', True),
29 | )
30 | num_voices = len(wav_files)
31 |
32 | potknob = analogio.AnalogIn(board.IO3)
33 |
34 | i2s_bclk = board.IO39 # BCK on PCM5102 I2S DAC (SCK pin to Gnd)
35 | i2s_wsel = board.IO37 # LCLK on PCM5102
36 | i2s_data = board.IO35 # DIN on PCM5102
37 |
38 | audio = audiobusio.I2SOut(bit_clock=i2s_bclk, word_select=i2s_wsel, data=i2s_data)
39 |
40 | mixer = audiomixer.Mixer(voice_count=num_voices, sample_rate=22050, channel_count=1,
41 | bits_per_sample=16, samples_signed=True)
42 | audio.play(mixer) # attach mixer to audio playback
43 |
44 | for i in range(num_voices):
45 | (wav_file, loopit) = wav_files[i]
46 | wave = audiocore.WaveFile(open(wav_file,"rb"))
47 | mixer.voice[i].level = 0.0 # turn down volume
48 | mixer.voice[i].play(wave, loop=loopit) # start wav playing
49 |
50 | n = num_voices * 0.8 # amount of overlap between samples
51 | m = 1/(num_voices-1) # size of slice
52 |
53 | while True:
54 | potval = potknob.value
55 | frac = potknob.value / 65535
56 | for i in range(num_voices):
57 | l = min(max( 1 - (n * (frac - m*i))**2, 0), 1) # fancy math to for nice mixing
58 | mixer.voice[i].level = l
59 | print("%1.2f" % l, end=" ")
60 | print("%0.2f", frac)
61 | time.sleep(0.05)
62 |
--------------------------------------------------------------------------------
/larger-tricks/beatfader_wavs/amen_22k16b_160bpm.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/beatfader_wavs/amen_22k16b_160bpm.wav
--------------------------------------------------------------------------------
/larger-tricks/beatfader_wavs/dnb21580_22k16b_160bpm.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/beatfader_wavs/dnb21580_22k16b_160bpm.wav
--------------------------------------------------------------------------------
/larger-tricks/beatfader_wavs/drumloopA_22k16b_160bpm.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/beatfader_wavs/drumloopA_22k16b_160bpm.wav
--------------------------------------------------------------------------------
/larger-tricks/beatfader_wavs/femvoc_330662_22k16b_160bpm.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/beatfader_wavs/femvoc_330662_22k16b_160bpm.wav
--------------------------------------------------------------------------------
/larger-tricks/beatslicer_idea.py:
--------------------------------------------------------------------------------
1 | # beatslicer_idea.py -- ideas on doing beatslicer on Trellis M4
2 | # 24 Feb 2022 - @todbot / Tod Kurt
3 |
4 | import time
5 | import board
6 | import audiocore
7 | import audiomixer
8 | from audioio import AudioOut as AudioOut
9 | import adafruit_trellism4
10 |
11 | time.sleep(2) # to give us time for when it crashes
12 |
13 | # loop files must be tempo-sync'd!
14 | # loops from https://github.com/todbot/circuitpython-tricks/tree/main/larger-tricks/beatfader_wavs
15 | loop_files = (
16 | "wav/amen_22k16b_160bpm.wav", # mono 160 bpm
17 | "wav/dnb21580_22k16b_160bpm.wav", # mono 160 bpm
18 | "wav/drumloopA_22k16b_160bpm.wav", # mono 160 bpm
19 | "wav/femvoc_330662_22k16b_160bpm.wav", # mono 160 bpm
20 | )
21 | bpm = 160.00
22 |
23 | num_loops = len(loop_files)
24 | num_slices = 8
25 |
26 | millis_per_beat = 60_000 / bpm
27 | millis_per_measure = millis_per_beat * num_slices
28 |
29 | #
30 | loop_slices = [[False] * num_loops for i in range(num_slices)]
31 |
32 | beat_pos = 0
33 | last_beat_millis = 0
34 | current_press = set()
35 |
36 | trellis = adafruit_trellism4.TrellisM4Express(rotation=0)
37 |
38 | # audio pin is A0 and A1 on SAMD51 (Trelllis M4, Itsy M4, etc)
39 | audio = AudioOut(board.A0)
40 | #audio = AudioOut(left_channel=board.A1, right_channel=board.A0)
41 | mixer = audiomixer.Mixer(voice_count=num_loops, sample_rate=22050, channel_count=1,
42 | bits_per_sample=16, samples_signed=True)
43 | audio.play(mixer) # attach mixer to audio playback
44 |
45 | def start_loops():
46 | # start drum loop playing, but silently
47 | for i in range(num_loops):
48 | wave = audiocore.WaveFile(open(loop_files[i],"rb"))
49 | mixer.voice[i].play( wave, loop=True )
50 | mixer.voice[i].level = 0.0
51 |
52 | def millis(): return time.monotonic()*1000 # I like millis
53 |
54 | while True:
55 | # handle keypresses
56 | pressed = set(trellis.pressed_keys)
57 | for press in pressed - current_press: # I think I understand this now
58 | x, y = press
59 | if not loop_slices[x][y]:
60 | loop_slices[x][y] = True
61 | trellis.pixels[x,y] = 0xff00ff # more responsive UI
62 | else:
63 | loop_slices[x][y] = False
64 | trellis.pixels[x,y] = 0x000000 # more responsive UI
65 | current_press = pressed
66 |
67 | now = millis()
68 | if now - last_beat_millis >= millis_per_beat :
69 | last_beat_millis = now
70 | print("beat:", beat_pos)
71 |
72 | # if we're at the top, restart all loops so we stay in sync
73 | if beat_pos == 0:
74 | start_loops()
75 |
76 | # update mixer volumes
77 | for i in range(num_loops): # rows
78 | if loop_slices[ beat_pos ][i]:
79 | mixer.voice[i].level = 1.0
80 | else:
81 | mixer.voice[i].level = 0.0
82 |
83 | # update LEDs
84 | # show which slices are selected
85 | for x in range(num_slices):
86 | for y in range(num_loops):
87 | if loop_slices[x][y]:
88 | trellis.pixels[x,y] = 0xff00ff
89 | else:
90 | trellis.pixels[x,y] = 0x000000
91 | # set beat marker
92 | for i in range(num_loops):
93 | trellis.pixels[beat_pos,i] = 0x333333
94 |
95 | # go to next slice
96 | beat_pos = (beat_pos+1) % num_slices
97 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/larger-tricks/bmps/emoji_spritesheet_27x2_28x28-1.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/bmps/emoji_spritesheet_27x2_28x28-1.bmp
--------------------------------------------------------------------------------
/larger-tricks/bmps/emoji_spritesheet_27x2_28x28-2.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/bmps/emoji_spritesheet_27x2_28x28-2.bmp
--------------------------------------------------------------------------------
/larger-tricks/bmps/emoji_spritesheet_27x2_28x28-3.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/bmps/emoji_spritesheet_27x2_28x28-3.bmp
--------------------------------------------------------------------------------
/larger-tricks/bmps/emoji_spritesheet_27x2_28x28.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/bmps/emoji_spritesheet_27x2_28x28.bmp
--------------------------------------------------------------------------------
/larger-tricks/bmps/emoji_spritesheet_27x7_28x28.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/bmps/emoji_spritesheet_27x7_28x28.bmp
--------------------------------------------------------------------------------
/larger-tricks/bmps/fireworks_spritesheet.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/bmps/fireworks_spritesheet.bmp
--------------------------------------------------------------------------------
/larger-tricks/bouncy_balls_vectorio.py:
--------------------------------------------------------------------------------
1 | # bouncy_balls_vectorio.py - use displayio to make simple bouncy balls
2 | # 12 Nov 2024 - @todbot / Tod Kurt. Based off bouncy_balls1.py
3 | # video demo at https://gist.github.com/todbot/d216cdfd0c13774c713482395429da16
4 | # works on any CircuitPython device with a display, just create a 'display'
5 | import time, random
6 | import board, busio, displayio, i2cdisplaybus
7 | import vectorio
8 | import adafruit_displayio_ssd1306
9 |
10 | # configuration options
11 | num_balls = 12
12 | ball_size = 5
13 | rand_vx, rand_vy = 2.5, 2
14 | scl_pin, sda_pin = board.GP15, board.GP14 # pins your display is on
15 | dw,dh = 128,64 # or whatever your display is
16 |
17 | # set up the display (change for your setup)
18 | displayio.release_displays()
19 | disp_i2c = busio.I2C(scl=scl_pin, sda=sda_pin, frequency=400_000)
20 | display_bus = i2cdisplaybus.I2CDisplayBus(disp_i2c, device_address=0x3C) # or 0x3D
21 | display = adafruit_displayio_ssd1306.SSD1306(display_bus, width=dw, height=dh, rotation=180)
22 |
23 | # set up the displayio objects that go on the display
24 | maingroup = displayio.Group()
25 | display.root_group = maingroup
26 |
27 | # holder of x,y position and vx,vy velocity of a ball
28 | # and the update method to update the position based on velocity
29 | class Ball:
30 | def __init__(self,x,y, vx,vy, shape):
31 | self.x, self.y, self.vx, self.vy = x,y,vx,vy
32 | self.shape = shape
33 | def update(self):
34 | self.x = self.x + self.vx # update ball position
35 | self.y = self.y + self.vy # update ball position
36 | if self.x <= 0 or self.x > display.width:
37 | self.vx = -self.vx # bounce!
38 | if self.y <= 0 or self.y > display.height:
39 | self.vy = -self.vy # bounce!
40 | self.shape.x = int(self.x)
41 | self.shape.y = int(self.y)
42 |
43 | ballgroup = displayio.Group() # group of ballshapes (will fill out later)
44 | maingroup.append(ballgroup) # add ballshapes to screen
45 | pal = displayio.Palette(1)
46 | pal[0] = 0xffffff # only two colors in OLEDland: white & black
47 |
48 | balls = []
49 | for i in range(num_balls):
50 | # start in middle of screen
51 | x, y = display.width//2, display.height//2
52 | # random initial velocity
53 | vx, vy = random.uniform(-rand_vx,rand_vx), random.uniform(-rand_vy,rand_vy)
54 | ballshape = vectorio.Circle(pixel_shader=pal, radius=ball_size, x=x, y=y)
55 | balls.append( Ball(x,y, vx,vy, ballshape) ) # our perfect math balls
56 | ballgroup.append(ballshape) # our actual drawn ball shapes
57 |
58 | while True:
59 | for ball in balls:
60 | ball.update()
61 | time.sleep(0.01)
62 |
63 |
--------------------------------------------------------------------------------
/larger-tricks/breakbeat_sampleplayer.py:
--------------------------------------------------------------------------------
1 | # breakbeat_sampleplayer.py -- play samples
2 | #
3 | # 8 Feb 2022 - @todbot / Tod Kurt
4 | #
5 | # Part of circuitpython-tricks: https://github.com/todbot/circuitpython-tricks
6 | #
7 | # For wiring diagram, see:
8 | # ./docs/breakbeat_sampleplayer_wiring.png
9 | #
10 | # Convert samples for use with SoX, like:
11 | # sox loop.mp3 -b 16 -c 1 -r 22050 loop.wav
12 | #
13 | # Or copy "breakbeat_wavs" folder as CIRCUITPY/wav
14 | #
15 |
16 | import random
17 | import time
18 | import board
19 | import supervisor
20 |
21 | import audiocore
22 | import audiomixer
23 | from audiopwmio import PWMAudioOut as AudioOut
24 |
25 | import neopixel
26 |
27 | leds = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
28 |
29 | #def millis(): return supervisor.ticks_ms() # I like millis
30 | def millis(): return time.monotonic()*1000 # I like millis
31 |
32 | wav_files = (
33 | # filename, loop?
34 | ('wav/amenfull_22k_s16.wav', True), # 137.72 bpm
35 | ('wav/amen2_22k_s16.wav', False),
36 | ('wav/amen3_22k_s16.wav', False),
37 | ('wav/amen4_22k_s16.wav', False),
38 | ('wav/amen5_22k_s16.wav', False),
39 | ('wav/amen6_22k_s16.wav', False),
40 | ('wav/amen7_22k_s16.wav', False),
41 | ('wav/amen8_22k_s16.wav', False),
42 | ('wav/ohohoh2.wav', False),
43 | )
44 |
45 | # audio pin is RX (pin 1) (RP2040 GPIO1)
46 | audio = AudioOut(board.RX)
47 | mixer = audiomixer.Mixer(voice_count=len(wav_files), sample_rate=22050, channel_count=1,
48 | bits_per_sample=16, samples_signed=True)
49 | # attach mixer to audio playback
50 | audio.play(mixer)
51 |
52 | bpm = 137.72
53 | beat_count = 4
54 | millis_per_beat = 60_000 / bpm
55 | millis_per_measure = millis_per_beat * beat_count
56 | last_millis = 0
57 | last_led_millis = 0
58 |
59 | wav_num = 0
60 | while True:
61 | leds[0] = 0x000000
62 | time.sleep(0.0001)
63 | now = millis()
64 | # a little blinky
65 | if now - last_led_millis >= millis_per_beat :
66 | last_led_millis = now
67 | leds[0] = 0x00ff00
68 |
69 | if now - last_millis >= millis_per_measure :
70 | last_millis = now
71 | print("playing wav", wav_num)
72 | leds[0] = 0xff00ff
73 | voice = mixer.voice[wav_num]
74 | (wav_file,loopit) = wav_files[wav_num]
75 | wave = audiocore.WaveFile(open(wav_file,"rb"))
76 | voice.play(wave,loop=loopit)
77 | # pick random for next time
78 | wav_num = random.randint(0,len(wav_files)-1)
79 |
80 |
81 |
--------------------------------------------------------------------------------
/larger-tricks/breakbeat_wav/amen1_22k_s16.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/breakbeat_wav/amen1_22k_s16.wav
--------------------------------------------------------------------------------
/larger-tricks/breakbeat_wav/amen2_22k_s16.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/breakbeat_wav/amen2_22k_s16.wav
--------------------------------------------------------------------------------
/larger-tricks/breakbeat_wav/amen3_22k_s16.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/breakbeat_wav/amen3_22k_s16.wav
--------------------------------------------------------------------------------
/larger-tricks/breakbeat_wav/amen4_22k_s16.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/breakbeat_wav/amen4_22k_s16.wav
--------------------------------------------------------------------------------
/larger-tricks/breakbeat_wav/amen5_22k_s16.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/breakbeat_wav/amen5_22k_s16.wav
--------------------------------------------------------------------------------
/larger-tricks/breakbeat_wav/amen6_22k_s16.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/breakbeat_wav/amen6_22k_s16.wav
--------------------------------------------------------------------------------
/larger-tricks/breakbeat_wav/amen7_22k_s16.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/breakbeat_wav/amen7_22k_s16.wav
--------------------------------------------------------------------------------
/larger-tricks/breakbeat_wav/amen8_22k_s16.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/breakbeat_wav/amen8_22k_s16.wav
--------------------------------------------------------------------------------
/larger-tricks/breakbeat_wav/amenfull_22k_s16.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/breakbeat_wav/amenfull_22k_s16.wav
--------------------------------------------------------------------------------
/larger-tricks/breakbeat_wav/ohohoh2.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/breakbeat_wav/ohohoh2.wav
--------------------------------------------------------------------------------
/larger-tricks/chikkenplayer.py:
--------------------------------------------------------------------------------
1 | # chikkenplayer.py - StreetChicken Remixer by todbot
2 | # 21 Mar 2021 - @todbot / Tod Kurt, from @jedgarpark idea
3 | #
4 | import time
5 | import board
6 | import analogio
7 | import keypad
8 | import audiocore
9 | import audiomixer
10 | from audiopwmio import PWMAudioOut as AudioOut
11 |
12 | time.sleep(5) # wait a bit to reduce noise from CIRCUITPY access
13 |
14 | wav_files = (
15 | "wav/chikken1_161_22k16b.wav", # slowed down & hacked StreetChicken
16 | "wav/chikken2_161_22k16b.wav", # fat distorty bassline
17 | "wav/chikken3_161_22k16b.wav", # reversed & slowed & warped StreetChicken
18 | "wav/chikken4_161_22k16b.wav", # 808 drum pattern (kick + snare only)
19 | "wav/chikken5_161_22k16b.wav", # chunky guitar power chords
20 | "wav/chikken6_161_22k16b.wav", # ethereal space arpeggios
21 | )
22 |
23 | # knob to adjust loop levels
24 | knob = analogio.AnalogIn(board.A3)
25 |
26 | # what keys to use
27 | keys = keypad.Keys((board.RX, board.SCK, board.MISO, board.MOSI, board.SCL, board.SDA),
28 | value_when_pressed=False, pull=True)
29 | # audio output
30 | audio = AudioOut(board.TX) # board.A0 for SAMD chips, any for rp2040 pico
31 | mixer = audiomixer.Mixer(voice_count=len(wav_files), sample_rate=22050, channel_count=1,
32 | bits_per_sample=16, samples_signed=True)
33 | audio.play(mixer) # attach mixer to audio playback, play with mixer.voice[n].play()
34 |
35 | # start loops playing, but silently
36 | for i in range(len(wav_files)):
37 | wave = audiocore.WaveFile(open(wav_files[i],"rb"))
38 | mixer.voice[i].play(wave, loop=True)
39 | mixer.voice[i].level = 0.0
40 |
41 | print("chikkenplayer ready")
42 |
43 | edit_voices = [False] * len(wav_files)
44 | pickup_voices = [False] * len(wav_files)
45 |
46 | while True:
47 | event = keys.events.get()
48 | if event:
49 | voice_num = event.key_number
50 | if event.pressed:
51 | edit_voices[voice_num] = True
52 | if event.released:
53 | edit_voices[voice_num] = False
54 | pickup_voices[voice_num] = False
55 |
56 | for i in range(len(wav_files)):
57 | new_val = knob.value
58 | if edit_voices[i]: # only edit voices with pressed buttons
59 | if pickup_voices[i]: # have we crossed old value?
60 | mixer.voice[i].level = new_val / 65535 # convert to 0-1.0
61 | else:
62 | old_val = int(mixer.voice[i].level * 65535) # convert 0-1 to 0-65535
63 | if abs(new_val - old_val) < 100: # if we get close to old value,
64 | pickup_voices[i] = True # flip the pickup switch until key release
65 |
--------------------------------------------------------------------------------
/larger-tricks/chikkenplayer_wav/chikken1_161_22k16b.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/chikkenplayer_wav/chikken1_161_22k16b.wav
--------------------------------------------------------------------------------
/larger-tricks/chikkenplayer_wav/chikken2_161_22k16b.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/chikkenplayer_wav/chikken2_161_22k16b.wav
--------------------------------------------------------------------------------
/larger-tricks/chikkenplayer_wav/chikken3_161_22k16b.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/chikkenplayer_wav/chikken3_161_22k16b.wav
--------------------------------------------------------------------------------
/larger-tricks/chikkenplayer_wav/chikken4_161_22k16b.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/chikkenplayer_wav/chikken4_161_22k16b.wav
--------------------------------------------------------------------------------
/larger-tricks/chikkenplayer_wav/chikken5_161_22k16b.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/chikkenplayer_wav/chikken5_161_22k16b.wav
--------------------------------------------------------------------------------
/larger-tricks/chikkenplayer_wav/chikken6_161_22k16b.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/chikkenplayer_wav/chikken6_161_22k16b.wav
--------------------------------------------------------------------------------
/larger-tricks/cpx_midi_controller.py:
--------------------------------------------------------------------------------
1 | # quick-n-dirty MIDI controller with Circuit Playground Express
2 | # 30 Apr 2024 - @todbot / Tod Kurt
3 | # Works on Circuit Playground Express in CircuitPython 9,
4 | # but will also work on also any board with touchio, like Trinket M0 or QTPy M0
5 | # Uses the minimal number of libraries to save RAM on the tiny M0 in the CPX
6 | # (e.g. cannot load `adafruit_circuitpython`, `adafruit_midi`, and `adafruit_debouncer` on CPX)
7 |
8 | import board
9 | import touchio
10 | import usb_midi
11 | import neopixel
12 |
13 | # which MIDI notes to send
14 | midi_notenums = (40, 41, 42, 43)
15 |
16 | # which pins for each of the above notes
17 | touch_pins = (board.A1, board.A2, board.A3, board.A4)
18 |
19 | # which MIDI channel to transmit on
20 | midi_out_channel = 1
21 |
22 | # set up the status byte constants for sending MIDI later
23 | note_on_status = (0x90 | (midi_out_channel-1))
24 | note_off_status = (0x80 | (midi_out_channel-1))
25 |
26 | # get a MIDI out port
27 | midi_out = usb_midi.ports[1]
28 | # make ready the neopixels so we can blink when a pad is touched
29 | leds = neopixel.NeoPixel(board.NEOPIXEL, 10, brightness=0.1)
30 |
31 | # set up the touch pins
32 | touchins = []
33 | for i, pin in enumerate(touch_pins):
34 | touchins.append( touchio.TouchIn(pin) )
35 | last_touch = [False] * len(touchins)
36 |
37 | print("Welcome! Touch a pad!")
38 | while True:
39 | for i, touchin in enumerate(touchins):
40 | touch = touchin.value # get current touch value
41 | if touch != last_touch[i]: # was there a change in touch?
42 | last_touch[i] = touch # save for next time
43 | notenum = midi_notenums[i] # get MIDI note for this pad
44 | if touch : # pressed!
45 | print("touch!", i)
46 | midi_out.write(bytearray([note_on_status, notenum, 127]))
47 | leds.fill(0x330044)
48 | else:
49 | print("release!",i)
50 | midi_out.write(bytearray([note_off_status, notenum, 0]))
51 | leds.fill(0)
52 |
--------------------------------------------------------------------------------
/larger-tricks/docs/breakbeat_sampleplayer.fzz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/docs/breakbeat_sampleplayer.fzz
--------------------------------------------------------------------------------
/larger-tricks/docs/breakbeat_sampleplayer_wiring.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/docs/breakbeat_sampleplayer_wiring.png
--------------------------------------------------------------------------------
/larger-tricks/dvdlogo_vectorio_code.py:
--------------------------------------------------------------------------------
1 | # dvdlogo_vectorio_code.py - Bouncing DVD logo in CircuitPython vectorio
2 | # 25 Mar 2022 - @todbot / Tod Kurt
3 | # a vectorio rethink of https://github.com/todbot/circuitpython_screensaver
4 | # video in action: https://twitter.com/todbot/status/1507516909528358916?s=21
5 | # use https://shinao.github.io/PathToPoints/ to get a list of points
6 | # then https://gist.github.com/todbot/05d8e64ba8c6a7a584c1956c817a8779 to normalize those points (see below)
7 |
8 | import time
9 | import board
10 | import displayio
11 | import vectorio
12 | import rainbowio
13 |
14 | class DVDLogo:
15 | def make_logo(width, height, color=0xFFFFFF, x=0, y=0):
16 | """ make a vectorio based DVD logo """
17 | pal = displayio.Palette(1)
18 | pal[0] = color
19 | points0 = []
20 | for i in range(len(DVDLogo.path0)):
21 | (dx,dy) = DVDLogo.path0[i]
22 | dx = int(dx * width)
23 | dy = int(dy * height)
24 | points0.append( (dx,dy) )
25 | dvdlogo0 = vectorio.Polygon(pixel_shader=pal, points=points0, x=x, y=y)
26 |
27 | points1 = []
28 | for i in range(len(DVDLogo.path1)):
29 | (dx,dy) = DVDLogo.path1[i]
30 | dx = int(dx * width)
31 | dy = int(dy * height)
32 | points1.append( (dx,dy) )
33 | dvdlogo1 = vectorio.Polygon(pixel_shader=pal, points=points1, x=x, y=y)
34 |
35 | dvdlogo = displayio.Group()
36 | dvdlogo.append(dvdlogo0)
37 | dvdlogo.append(dvdlogo1)
38 | return dvdlogo
39 |
40 | def change_color(logogroup, color):
41 | """ change color of logo, convenience function """
42 | logogroup[0].pixel_shader[0] = color
43 |
44 | # vector paths from SVG at https://upload.wikimedia.org/wikipedia/commons/9/9b/DVD_logo.svg
45 | # normalized to 1.0 on both x & y, original aspect ratio is 2:1
46 | path0 = (
47 | (0.580, 0.214), (0.550, 0.297), (0.521, 0.382), (0.509, 0.325), (0.494, 0.225), (0.479, 0.125), (0.455, 0.050), (0.406, 0.050), (0.358, 0.050), (0.309, 0.050), (0.260, 0.050), (0.211, 0.050), (0.162, 0.050), (0.114, 0.050), (0.098, 0.141), (0.132, 0.167), (0.181, 0.167), (0.230, 0.168), (0.276, 0.197), (0.301, 0.282), (0.285, 0.380), (0.246, 0.440), (0.198, 0.463), (0.160, 0.448), (0.171, 0.345), (0.182, 0.243), (0.152, 0.210), (0.103, 0.210), (0.083, 0.288), (0.072, 0.391), (0.061, 0.493), (0.057, 0.583), (0.106, 0.583), (0.155, 0.583), (0.203, 0.581), (0.252, 0.566), (0.298, 0.532), (0.340, 0.481), (0.376, 0.409), (0.398, 0.316), (0.397, 0.212), (0.403, 0.214), (0.419, 0.314), (0.435, 0.413), (0.451, 0.512), (0.467, 0.612), (0.483, 0.703), (0.515, 0.624), (0.547, 0.544), (0.579, 0.465), (0.611, 0.385), (0.643, 0.306), (0.675, 0.226), (0.712, 0.168), (0.760, 0.167), (0.809, 0.167), (0.857, 0.179), (0.896, 0.238), (0.897, 0.340), (0.865, 0.419), (0.820, 0.457), (0.772, 0.465), (0.766, 0.392), (0.777, 0.289), (0.774, 0.210), (0.725, 0.210), (0.688, 0.241), (0.677, 0.344), (0.666, 0.446), (0.655, 0.549), (0.684, 0.583), (0.733, 0.583), (0.782, 0.583), (0.830, 0.575), (0.877, 0.550), (0.922, 0.507), (0.961, 0.445), (0.990, 0.361), (1.000, 0.259), (0.981, 0.163), (0.943, 0.098), (0.898, 0.063), (0.849, 0.050), (0.800, 0.050), (0.752, 0.050), (0.703, 0.050), (0.654, 0.050), (0.619, 0.112), (0.588, 0.194), )
48 | path1 = (
49 | (0.485, 0.709), (0.436, 0.710), (0.387, 0.712), (0.339, 0.716), (0.290, 0.722), (0.241, 0.730), (0.193, 0.741), (0.145, 0.756), (0.097, 0.775), (0.050, 0.805), (0.031, 0.877), (0.075, 0.919), (0.123, 0.942), (0.171, 0.958), (0.220, 0.970), (0.268, 0.980), (0.317, 0.987), (0.366, 0.992), (0.414, 0.995), (0.463, 0.997), (0.512, 0.996), (0.561, 0.995), (0.6909, 0.991), (0.658, 0.986), (0.707, 0.979), (0.755, 0.969), (0.804, 0.957), (0.852, 0.940), (0.899, 0.916), (0.943, 0.870), (0.916, 0.802), (0.869, 0.773), (0.821, 0.754), (0.773, 0.740), (0.725, 0.730), (0.676, 0.722), (0.627, 0.716), (0.578, 0.712), (0.530, 0.709), (0.465, 0.904), (0.416, 0.897), (0.369, 0.871), (0.397, 0.820), (0.446, 0.808), (0.494, 0.809), (0.543, 0.821), (0.565, 0.874), (0.518, 0.898), (0.469, 0.904), )
50 |
51 |
52 | display = board.DISPLAY
53 |
54 | maingroup = displayio.Group()
55 | display.root_group = maingroup # put main group on display, everything goes in maingroup
56 |
57 | dvdlogo_w = 80
58 | dvdlogo_h = 40
59 | x = 0
60 | y = 0
61 | xvel = 1.9
62 | yvel = 1.4
63 |
64 | dvdlogo = DVDLogo.make_logo( dvdlogo_w, dvdlogo_h, color=0xFF00FF, x=x,y=0 )
65 | maingroup.append(dvdlogo) # put the logo on the display via the main group
66 |
67 | while True:
68 | x = x + xvel
69 | y = y + yvel
70 | # if we hit an edge, bounce & change color!
71 | if x < 0 or x > (display.width - dvdlogo_w):
72 | xvel = -xvel
73 | DVDLogo.change_color(dvdlogo, rainbowio.colorwheel(time.monotonic()*50))
74 | if y < 0 or y > (display.height - dvdlogo_h):
75 | yvel = -yvel
76 | DVDLogo.change_color(dvdlogo, rainbowio.colorwheel(time.monotonic()*50))
77 |
78 | dvdlogo.x = int(x)
79 | dvdlogo.y = int(y)
80 |
81 | time.sleep(0.01)
82 |
--------------------------------------------------------------------------------
/larger-tricks/emoji_flipper.py:
--------------------------------------------------------------------------------
1 | # emoji_flipper.py - show a bunch of emojis from a sprite sheet
2 | # 23 Jun 2022 - @todbot / Tod Kurt
3 | import time
4 | import board
5 | import displayio
6 | import adafruit_imageload
7 |
8 | sprite_fname = "emoji_spritesheet_27x7_28x28.bmp"
9 | sprite_cnt = 27*7
10 | sprite_w,sprite_h = 28,28
11 |
12 | sprite_sheet,sprite_palette = adafruit_imageload.load(sprite_fname)
13 | #sprite_sheet = displayio.OnDiskBitmap(open(sprite_fname, "rb"))
14 | #sprite_palette = sprite_sheet.pixel_shader
15 | sprite_palette.make_transparent(0) # make background color transparent
16 | sprite = displayio.TileGrid(sprite_sheet, pixel_shader=sprite_palette,
17 | width = 1, height = 1,
18 | tile_width = sprite_w, tile_height = sprite_h)
19 | display = board.DISPLAY # our board has built-in display
20 |
21 | maingroup = displayio.Group(scale=4) # make 4x big
22 | maingroup.append(sprite)
23 | display..root_group = maingroup
24 |
25 | sprite_index = 0 # where in the sprite sheet we currently are
26 | while True:
27 | sprite[0] = sprite_index
28 | sprite_index = (sprite_index + 1) % sprite_cnt
29 | time.sleep(0.1)
30 |
--------------------------------------------------------------------------------
/larger-tricks/eyeballs/code.py:
--------------------------------------------------------------------------------
1 | # gc9a01_multi_eyeball_code.py --
2 | # 14 Oct 2022 - @todbot / Tod Kurt
3 | # Part of circuitpython-tricks/larger-tricks
4 | # Requires rebuilt CircuitPython with CIRCUITPY_DISPLAY_LIMIT set to 3
5 | # see: https://todbot.com/blog/2022/05/19/multiple-displays-in-circuitpython-compiling-custom-circuitpython/
6 |
7 | import time, math, random
8 | import board, busio
9 | import displayio
10 | import adafruit_imageload
11 | import gc9a01
12 |
13 | displayio.release_displays()
14 |
15 | dw, dh = 240,240 # display dimensions
16 |
17 | eyeball_bitmap, eyeball_pal = adafruit_imageload.load("imgs/eye0_ball2.bmp")
18 | iris_bitmap, iris_pal = adafruit_imageload.load("imgs/eye0_iris0.bmp")
19 | iris_pal.make_transparent(0)
20 |
21 | iris_w, iris_h = iris_bitmap.width, iris_bitmap.height # iris is normally 110x110
22 | iris_cx, iris_cy = dw//2 - iris_w//2, dh//2 - iris_h//2
23 |
24 | tft0_clk = board.GP18
25 | tft0_mosi = board.GP19
26 |
27 | tft1_clk = board.GP10
28 | tft1_mosi = board.GP11
29 |
30 | tft_L0_rst = board.GP21
31 | tft_L0_dc = board.GP22
32 | tft_L0_cs = board.GP20
33 |
34 | tft_R0_rst = board.GP26
35 | tft_R0_dc = board.GP27
36 | tft_R0_cs = board.GP28
37 |
38 | tft_L1_rst = board.GP14
39 | tft_L1_dc = board.GP12
40 | tft_L1_cs = board.GP13
41 |
42 | tft_R1_rst = board.GP3
43 | tft_R1_dc = board.GP4
44 | tft_R1_cs = board.GP5
45 |
46 | spi0 = busio.SPI(clock=tft0_clk, MOSI=tft0_mosi)
47 | spi1 = busio.SPI(clock=tft1_clk, MOSI=tft1_mosi)
48 |
49 | def eye_init(spi, dc, cs, rst, rot):
50 | display_bus = displayio.FourWire(spi, command=dc, chip_select=cs, reset=rst)
51 | display = gc9a01.GC9A01(display_bus, width=dw, height=dh, rotation=rot)
52 | main = displayio.Group()
53 | display.root_group = main
54 | eyeball = displayio.TileGrid(eyeball_bitmap, pixel_shader=eyeball_pal)
55 | iris = displayio.TileGrid(iris_bitmap, pixel_shader=iris_pal, x = iris_cx, y = iris_cy )
56 | main.append(eyeball)
57 | main.append(iris)
58 | return (display, eyeball, iris)
59 |
60 |
61 | (display_L0, eyeball_L0, iris_L0) = eye_init( spi0, tft_L0_dc, tft_L0_cs, tft_L0_rst, rot=0)
62 | (display_R0, eyeball_R0, iris_R0) = eye_init( spi0, tft_R0_dc, tft_R0_cs, tft_R0_rst, rot=0)
63 | (display_L1, eyeball_L1, iris_L1) = eye_init( spi1, tft_L1_dc, tft_L1_cs, tft_L1_rst, rot=0)
64 | # can't do four yet
65 | #(display_R1, eyeball_R1, iris_R1) = eye_init( spi1, tft_R1_dc, tft_R1_cs, tft_R1_rst, rot=0)
66 |
67 | the_eyes = [
68 | # x,y, tx,ty, next_time
69 | [display_L0, eyeball_L0, iris_L0, iris_cx, iris_cy, 0, 0, 0 ],
70 | [display_R0, eyeball_R0, iris_R0, iris_cx, iris_cy, 0, 0, 0 ],
71 | [display_L1, eyeball_L1, iris_L1, iris_cx, iris_cy, 0, 0, 0 ],
72 | #[display_R1, eyeball_R1, iris_R1, iris_cx, iris_cy, 0, 0, 0 ], # can't do four yet
73 | ]
74 |
75 |
76 | r = 17 # allowable deviation from center for iris
77 |
78 | while True:
79 | for i in range(len(the_eyes)):
80 | (display, eyeball, iris, x,y, tx,ty, next_time) = the_eyes[i]
81 | x = x * 0.5 + tx * 0.5 # "easing"
82 | y = y * 0.5 + ty * 0.5
83 | iris.x = int( x )
84 | iris.y = int( y )
85 | the_eyes[i][3] = x
86 | the_eyes[i][4] = y
87 | if time.monotonic() > next_time:
88 | next_time = time.monotonic() + random.uniform(0,2)
89 | tx = iris_cx + random.uniform(-r,r)
90 | ty = iris_cy + random.uniform(-r,r)
91 | the_eyes[i][5] = tx
92 | the_eyes[i][6] = ty
93 | the_eyes[i][7] = next_time # FIXME
94 | print("change!")
95 | #display.refresh( target_frames_per_second=20 )
96 | display.refresh()
97 |
--------------------------------------------------------------------------------
/larger-tricks/eyeballs/imgs/eye0_ball2.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/eyeballs/imgs/eye0_ball2.bmp
--------------------------------------------------------------------------------
/larger-tricks/eyeballs/imgs/eye0_iris0.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/eyeballs/imgs/eye0_iris0.bmp
--------------------------------------------------------------------------------
/larger-tricks/eyeballs/imgs/eyelid_spritesheet.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/eyeballs/imgs/eyelid_spritesheet.bmp
--------------------------------------------------------------------------------
/larger-tricks/eyeballs/imgs/eyelid_spritesheet2.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/eyeballs/imgs/eyelid_spritesheet2.bmp
--------------------------------------------------------------------------------
/larger-tricks/eyeballs/qteye.py:
--------------------------------------------------------------------------------
1 | # qteye.py - a stand-alone GC9A01 round LCD "eye" on a QTPy
2 | # 23 Oct 2022 - @todbot / Tod Kurt
3 | # Part of circuitpython-tricks/larger-tricks/eyeballs
4 | # also see: https://todbot.com/blog/2022/05/19/multiple-displays-in-circuitpython-compiling-custom-circuitpython/
5 |
6 | import time, math, random
7 | import board, busio
8 | import displayio
9 | import adafruit_imageload
10 | import gc9a01
11 |
12 | displayio.release_displays()
13 |
14 | dw, dh = 240,240 # display dimensions
15 |
16 | # load our eye and iris bitmaps
17 | eyeball_bitmap, eyeball_pal = adafruit_imageload.load("imgs/eye0_ball2.bmp")
18 | iris_bitmap, iris_pal = adafruit_imageload.load("imgs/eye0_iris0.bmp")
19 | iris_pal.make_transparent(0)
20 |
21 | # compute or declare some useful info about the eyes
22 | iris_w, iris_h = iris_bitmap.width, iris_bitmap.height # iris is normally 110x110
23 | iris_cx, iris_cy = dw//2 - iris_w//2, dh//2 - iris_h//2
24 | r = 20 # allowable deviation from center for iris
25 |
26 | # wiring for QT Py, should work on any QT Py or XIAO board
27 | tft0_clk = board.SCK
28 | tft0_mosi = board.MOSI
29 |
30 | tft_L0_rst = board.MISO
31 | tft_L0_dc = board.RX
32 | tft_L0_cs = board.TX
33 |
34 | spi0 = busio.SPI(clock=tft0_clk, MOSI=tft0_mosi)
35 |
36 | # class to help us track eye info (not needed for this use exactly, but I find it interesting)
37 | class Eye:
38 | def __init__(self, spi, dc, cs, rst, rot=0, eye_speed=0.25, twitch=2):
39 | display_bus = displayio.FourWire(spi, command=dc, chip_select=cs, reset=rst)
40 | display = gc9a01.GC9A01(display_bus, width=dw, height=dh, rotation=rot)
41 | main = displayio.Group()
42 | display.root_group = main
43 | self.display = display
44 | self.eyeball = displayio.TileGrid(eyeball_bitmap, pixel_shader=eyeball_pal)
45 | self.iris = displayio.TileGrid(iris_bitmap, pixel_shader=iris_pal, x=iris_cx,y=iris_cy)
46 | main.append(self.eyeball)
47 | main.append(self.iris)
48 | self.x, self.y = iris_cx, iris_cy
49 | self.tx, self.ty = self.x, self.y
50 | self.next_time = time.monotonic()
51 | self.eye_speed = eye_speed
52 | self.twitch = twitch
53 |
54 | def update(self):
55 | self.x = self.x * (1-self.eye_speed) + self.tx * self.eye_speed # "easing"
56 | self.y = self.y * (1-self.eye_speed) + self.ty * self.eye_speed
57 | self.iris.x = int( self.x )
58 | self.iris.y = int( self.y )
59 | if time.monotonic() > self.next_time:
60 | t = random.uniform(0.25,self.twitch)
61 | self.next_time = time.monotonic() + t
62 | self.tx = iris_cx + random.uniform(-r,r)
63 | self.ty = iris_cy + random.uniform(-r,r)
64 | self.display.refresh()
65 |
66 | # a list of all the eyes, in this case, only one
67 | the_eyes = [
68 | Eye( spi0, tft_L0_dc, tft_L0_cs, tft_L0_rst, rot=0),
69 | ]
70 |
71 | while True:
72 | for eye in the_eyes:
73 | eye.update()
74 |
--------------------------------------------------------------------------------
/larger-tricks/eyeballs/qteye_blink_esp32s3.py:
--------------------------------------------------------------------------------
1 | # qteye_blink_esp32s3.py - a stand-alone GC9A01 round LCD "eye" on a QTPy ESP32-S3
2 | # 16 Oct 2023 - @todbot / Tod Kurt
3 | # Part of circuitpython-tricks/larger-tricks/eyeballs
4 | # also see: https://todbot.com/blog/2022/05/19/multiple-displays-in-circuitpython-compiling-custom-circuitpython/
5 |
6 | import time, math, random
7 | import board, busio
8 | import displayio
9 | import adafruit_imageload
10 | import gc9a01
11 |
12 | # config: behaviors
13 | eye_twitch_time = 2 # bigger is less twitchy
14 | eye_twitch_amount = 20 # allowable deviation from center for iris
15 | eye_blink_time = 0.8 # bigger is slower
16 | eye_rotation = 0 # 0 or 180, don't do 90 or 270 because too slow
17 |
18 | # config: wiring for QT Py, should work on any QT Py or XIAO board, but ESP32-S3 is fastest
19 | tft0_clk = board.SCK
20 | tft0_mosi = board.MOSI
21 |
22 | tft_L0_rst = board.MISO
23 | tft_L0_dc = board.RX
24 | tft_L0_cs = board.TX
25 |
26 | displayio.release_displays()
27 |
28 | dw, dh = 240, 240 # display dimensions
29 |
30 | # load our eye and iris bitmaps
31 | ## static so load from disk (also can't have it it RAM and eyelids in RAM too)
32 | eyeball_bitmap = displayio.OnDiskBitmap(open("/imgs/eye0_ball2.bmp", "rb"))
33 | eyeball_pal = eyeball_bitmap.pixel_shader
34 | ## moves around, so load into RAM
35 | iris_bitmap, iris_pal = adafruit_imageload.load("imgs/eye0_iris0.bmp")
36 | iris_pal.make_transparent(0)
37 | ## also moves, so load into RAM (hopefully)
38 | try:
39 | eyelid_bitmap, eyelid_pal = adafruit_imageload.load("/imgs/eyelid_spritesheet2.bmp")
40 | except Exception as e:
41 | print("couldn't load",e)
42 | eyelid_bitmap = displayio.OnDiskBitmap(open("/imgs/eyelid_spritesheet2.bmp", "rb"))
43 | eyelid_pal = eyelid_bitmap.pixel_shader
44 | eyelid_sprite_cnt = eyelid_bitmap.width // dw # should be 16
45 | eyelid_pal.make_transparent(1)
46 |
47 | # compute or declare some useful info about the eyes
48 | iris_w, iris_h = iris_bitmap.width, iris_bitmap.height # iris is normally 110x110
49 | iris_cx, iris_cy = dw//2 - iris_w//2, dh//2 - iris_h//2
50 |
51 |
52 | spi0 = busio.SPI(clock=tft0_clk, MOSI=tft0_mosi)
53 |
54 | # class to help us track eye info (not needed for this use exactly, but I find it interesting)
55 | class Eye:
56 | """
57 | global variables used:
58 | - iris_cx, iris_cy
59 | - eye_twitch_amount
60 | - eye_twitch_time
61 | - eye_blink_time
62 | """
63 | def __init__(self, spi, dc, cs, rst, rot=0, eye_speed=0.25):
64 | # ends up being 80 MHz on ESP32-S3, nice
65 | display_bus = displayio.FourWire(spi, command=dc, chip_select=cs, reset=rst, baudrate=64_000_000)
66 | display = gc9a01.GC9A01(display_bus, width=dw, height=dh, rotation=rot)
67 | display.auto_refresh = False
68 | main = displayio.Group()
69 | display.root_group = main
70 | self.display = display
71 | self.eyeball = displayio.TileGrid(eyeball_bitmap, pixel_shader=eyeball_pal)
72 | self.iris = displayio.TileGrid(iris_bitmap, pixel_shader=iris_pal, x=iris_cx,y=iris_cy)
73 | self.lids = displayio.TileGrid(eyelid_bitmap, pixel_shader=eyelid_pal, x=0, y=0, tile_width=dw, tile_height=dh)
74 | main.append(self.eyeball)
75 | main.append(self.iris)
76 | main.append(self.lids)
77 | self.x, self.y = iris_cx, iris_cy
78 | self.tx, self.ty = self.x, self.y
79 | self.next_time = 0
80 | self.eye_speed = eye_speed
81 | self.lidpos = 0
82 | self.lidpos_inc = 1
83 | self.lid_next_time = 0
84 |
85 | def update(self):
86 | # make the eye twitch around
87 | self.x = self.x * (1-self.eye_speed) + self.tx * self.eye_speed # "easing"
88 | self.y = self.y * (1-self.eye_speed) + self.ty * self.eye_speed
89 | self.iris.x = int( self.x )
90 | self.iris.y = int( self.y ) # + 10 # have it look down a bit FIXME
91 | if time.monotonic() > self.next_time:
92 | # pick a new "target" for the eye to look at
93 | t = random.uniform(0.25, eye_twitch_time)
94 | self.next_time = time.monotonic() + t
95 | self.tx = iris_cx + random.uniform(-eye_twitch_amount,eye_twitch_amount)
96 | self.ty = iris_cy + random.uniform(-eye_twitch_amount,eye_twitch_amount)
97 | # elif to minimize display changes per update
98 | elif time.monotonic() > self.lid_next_time:
99 | # make the eye blink its eyelids
100 | self.lid_next_time = time.monotonic() + random.uniform( eye_blink_time*0.5, eye_blink_time*1.5)
101 | #self.lid_next_time = time.monotonic() + 100
102 | self.lidpos = self.lidpos + self.lidpos_inc
103 | self.lids[0] = self.lidpos
104 | if self.lidpos == 0 or self.lidpos == eyelid_sprite_cnt-1:
105 | self.lidpos_inc *= -1 # change direction
106 |
107 | self.display.refresh()
108 |
109 | # a list of all the eyes, in this case, only one
110 | the_eyes = [
111 | Eye( spi0, tft_L0_dc, tft_L0_cs, tft_L0_rst, rot=eye_rotation),
112 | ]
113 |
114 | while True:
115 | for eye in the_eyes:
116 | eye.update()
117 |
--------------------------------------------------------------------------------
/larger-tricks/eyeballs/qteye_blink_qualia.py:
--------------------------------------------------------------------------------
1 | # qteye_blink_qualia.py - a stand-alone round LCD "eye" on a ESP32-S3 Qualia board
2 | # 16 Oct 2023 - @todbot / Tod Kurt
3 | # Part of circuitpython-tricks/larger-tricks/eyeballs
4 | # also see: https://todbot.com/blog/2022/05/19/multiple-displays-in-circuitpython-compiling-custom-circuitpython/
5 |
6 | import time, math, random
7 | import board, busio
8 | import displayio
9 | import adafruit_imageload
10 |
11 | # config: behaviors
12 | eye_twitch_time = 2 # bigger is less twitchy
13 | eye_twitch_amount = 20 # allowable deviation from center for iris
14 | eye_blink_time = 1.8 # bigger is slower
15 | eye_rotation = 00 # 0 or 180, don't do 90 or 270 because too slow (on gc9a01)
16 |
17 |
18 | # config: wiring for QT Py, should work on any QT Py or XIAO board, but ESP32-S3 is fastest
19 | # import gc9a01
20 | # spi0 = busio.SPI(clock=tft0_clk, MOSI=tft0_mosi)
21 | # tft0_clk = board.SCK
22 | # tft0_mosi = board.MOSI
23 | # tft_L0_rst = board.MISO
24 | # tft_L0_dc = board.RX
25 | # tft_L0_cs = board.TX
26 | # display_bus = displayio.FourWire(spi, command=dc, chip_select=cs, reset=rst, baudrate=64_000_000)
27 | # display = gc9a01.GC9A01(display_bus, width=dw, height=dh, rotation=rot)
28 | # display.auto_refresh = False
29 | # dw, dh = 240, 240 # display dimensions
30 |
31 | # config: for Qualia ESP32S3 board and 480x480 round display
32 | # followed the instructions here:
33 | # https://learn.adafruit.com/adafruit-qualia-esp32-s3-for-rgb666-displays/circuitpython-display-setup
34 | # and cribbed from @dexter/@rsbohn's work:
35 | # https://gist.github.com/rsbohn/26a8e69c8fe80112a24e7de09177e8d9
36 | import dotclockframebuffer
37 | from framebufferio import FramebufferDisplay
38 |
39 | init_sequence_TL021WVC02 = bytes((
40 | b'\xff\x05w\x01\x00\x00\x10'
41 | b'\xc0\x02;\x00'
42 | b'\xc1\x02\x0b\x02'
43 | b'\xc2\x02\x00\x02'
44 | b'\xcc\x01\x10'
45 | b'\xcd\x01\x08'
46 | b'\xb0\x10\x02\x13\x1b\r\x10\x05\x08\x07\x07$\x04\x11\x0e,3\x1d'
47 | b'\xb1\x10\x05\x13\x1b\r\x11\x05\x08\x07\x07$\x04\x11\x0e,3\x1d'
48 | b'\xff\x05w\x01\x00\x00\x11'
49 | b'\xb0\x01]'
50 | b'\xb1\x01C'
51 | b'\xb2\x01\x81'
52 | b'\xb3\x01\x80'
53 | b'\xb5\x01C'
54 | b'\xb7\x01\x85'
55 | b'\xb8\x01 '
56 | b'\xc1\x01x'
57 | b'\xc2\x01x'
58 | b'\xd0\x01\x88'
59 | b'\xe0\x03\x00\x00\x02'
60 | b'\xe1\x0b\x03\xa0\x00\x00\x04\xa0\x00\x00\x00 '
61 | b'\xe2\r\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
62 | b'\xe3\x04\x00\x00\x11\x00'
63 | b'\xe4\x02"\x00'
64 | b'\xe5\x10\x05\xec\xa0\xa0\x07\xee\xa0\xa0\x00\x00\x00\x00\x00\x00\x00\x00'
65 | b'\xe6\x04\x00\x00\x11\x00'
66 | b'\xe7\x02"\x00'
67 | b'\xe8\x10\x06\xed\xa0\xa0\x08\xef\xa0\xa0\x00\x00\x00\x00\x00\x00\x00\x00'
68 | b'\xeb\x07\x00\x00@@\x00\x00\x00'
69 | b'\xed\x10\xff\xff\xff\xba\n\xbfE\xff\xffT\xfb\xa0\xab\xff\xff\xff'
70 | b'\xef\x06\x10\r\x04\x08?\x1f'
71 | b'\xff\x05w\x01\x00\x00\x13'
72 | b'\xef\x01\x08'
73 | b'\xff\x05w\x01\x00\x00\x00'
74 | b'6\x01\x00'
75 | b':\x01`'
76 | b'\x11\x80d'
77 | b')\x802'
78 | ))
79 |
80 | # I'm just guessing at all of this
81 | tft_timings = {
82 | "frequency": 6_500_000,
83 | "width": 480,
84 | "height": 480,
85 | "hsync_pulse_width": 20,
86 | "hsync_front_porch": 40,
87 | "hsync_back_porch": 40,
88 | "vsync_pulse_width": 10,
89 | "vsync_front_porch": 40,
90 | "vsync_back_porch": 40,
91 | "hsync_idle_low": False,
92 | "vsync_idle_low": False,
93 | "de_idle_high": False,
94 | "pclk_active_high": False,
95 | "pclk_idle_high": False,
96 | }
97 |
98 | displayio.release_displays()
99 | board.I2C().deinit()
100 | i2c = busio.I2C(board.SCL, board.SDA, frequency=400_000)
101 | dotclockframebuffer.ioexpander_send_init_sequence(
102 | i2c, init_sequence_TL021WVC02, **(dict(board.TFT_IO_EXPANDER)))
103 | i2c.deinit()
104 |
105 | fb = dotclockframebuffer.DotClockFramebuffer(
106 | **(dict(board.TFT_PINS)), **tft_timings)
107 | display = FramebufferDisplay(fb, rotation=eye_rotation)
108 | display.auto_refresh=False
109 |
110 | dw_real, dh_real = 480, 480
111 | dw, dh = 240,240 # for emulation with 240x240 displays
112 |
113 | # load our eye and iris bitmaps
114 | ## static so load from disk (also can't have it it RAM and eyelids in RAM too)
115 | eyeball_bitmap = displayio.OnDiskBitmap(open("/imgs/eye0_ball2.bmp", "rb"))
116 | eyeball_pal = eyeball_bitmap.pixel_shader
117 | ## moves around, so load into RAM
118 | iris_bitmap, iris_pal = adafruit_imageload.load("imgs/eye0_iris0.bmp")
119 | iris_pal.make_transparent(0)
120 | ## also moves, so load into RAM (hopefully)
121 | try:
122 | eyelid_bitmap, eyelid_pal = adafruit_imageload.load("/imgs/eyelid_spritesheet2.bmp")
123 | except Exception as e:
124 | print("couldn't load",e)
125 | eyelid_bitmap = displayio.OnDiskBitmap(open("/imgs/eyelid_spritesheet2.bmp", "rb"))
126 | eyelid_pal = eyelid_bitmap.pixel_shader
127 | eyelid_sprite_cnt = eyelid_bitmap.width // dw # should be 16
128 | eyelid_pal.make_transparent(1)
129 |
130 | # compute or declare some useful info about the eyes
131 | iris_w, iris_h = iris_bitmap.width, iris_bitmap.height # iris is normally 110x110
132 | iris_cx, iris_cy = dw//2 - iris_w//2, dh//2 - iris_h//2
133 |
134 |
135 | # class to help us track eye info (not needed for this use exactly, but I find it interesting)
136 | class Eye:
137 | """
138 | global variables used:
139 | - iris_cx, iris_cy
140 | - eye_twitch_amount
141 | - eye_twitch_time
142 | - eye_blink_time
143 | """
144 | def __init__(self, display, eye_speed=0.25):
145 | # ends up being 80 MHz on ESP32-S3, nice
146 | main = displayio.Group(scale=2)
147 | display.root_group = main
148 | self.display = display
149 | self.eyeball = displayio.TileGrid(eyeball_bitmap, pixel_shader=eyeball_pal)
150 | self.iris = displayio.TileGrid(iris_bitmap, pixel_shader=iris_pal, x=iris_cx,y=iris_cy)
151 | self.lids = displayio.TileGrid(eyelid_bitmap, pixel_shader=eyelid_pal, x=0, y=0, tile_width=dw, tile_height=dw)
152 | main.append(self.eyeball)
153 | main.append(self.iris)
154 | main.append(self.lids)
155 | self.x, self.y = iris_cx, iris_cy
156 | self.tx, self.ty = self.x, self.y
157 | self.next_time = 0
158 | self.eye_speed = eye_speed
159 | self.lidpos = 0
160 | self.lidpos_inc = 1
161 | self.lid_next_time = 0
162 |
163 | def update(self):
164 | # make the eye twitch around
165 | self.x = self.x * (1-self.eye_speed) + self.tx * self.eye_speed # "easing"
166 | self.y = self.y * (1-self.eye_speed) + self.ty * self.eye_speed
167 | self.iris.x = int( self.x )
168 | self.iris.y = int( self.y ) # + 10 # have it look down a bit FIXME
169 | if time.monotonic() > self.next_time:
170 | # pick a new "target" for the eye to look at
171 | t = random.uniform(0.25, eye_twitch_time)
172 | self.next_time = time.monotonic() + t
173 | self.tx = iris_cx + random.uniform(-eye_twitch_amount,eye_twitch_amount)
174 | self.ty = iris_cy + random.uniform(-eye_twitch_amount,eye_twitch_amount)
175 | # elif to minimize display changes per update
176 | elif time.monotonic() > self.lid_next_time:
177 | # make the eye blink its eyelids
178 | self.lid_next_time = time.monotonic() + random.uniform( eye_blink_time*0.5, eye_blink_time*1.5)
179 | self.lidpos = self.lidpos + self.lidpos_inc
180 | self.lids[0] = self.lidpos
181 | if self.lidpos == 0 or self.lidpos == eyelid_sprite_cnt-1:
182 | self.lidpos_inc *= -1 # change direction
183 |
184 | self.display.refresh()
185 |
186 |
187 |
188 | # a list of all the eyes, in this case, only one
189 | the_eyes = [
190 | Eye( display ),
191 | ]
192 |
193 | while True:
194 | for eye in the_eyes:
195 | eye.update()
196 |
--------------------------------------------------------------------------------
/larger-tricks/eyeballs/qteye_person_sensor.py:
--------------------------------------------------------------------------------
1 | # qteye_person_sensor.py -- quick hacking of Person Sensor by Useful Sensors onto qteye
2 | # 23 Oct 2022 - @todbot / Tod Kurt
3 | # Part of circuitpython-tricks/larger-tricks/eyeballs
4 | # For more information on Person Sensor:
5 | # https://github.com/usefulsensors/person_sensor_docs/blob/main/README.md
6 | # https://github.com/usefulsensors/person_sensor_screen_lock
7 | # https://www.hackster.io/petewarden/auto-lock-your-laptop-screen-with-a-person-sensor-7e0a35
8 |
9 | import time, math, random, struct
10 | import board, busio
11 | import displayio
12 | import adafruit_imageload
13 | import gc9a01
14 |
15 | displayio.release_displays()
16 |
17 | debug_face = True
18 |
19 | dw, dh = 240,240 # display dimensions
20 |
21 | eyeball_bitmap, eyeball_pal = adafruit_imageload.load("imgs/eye0_ball2.bmp")
22 | iris_bitmap, iris_pal = adafruit_imageload.load("imgs/eye0_iris0.bmp")
23 | iris_pal.make_transparent(0)
24 |
25 | iris_w, iris_h = iris_bitmap.width, iris_bitmap.height # iris is normally 110x110
26 | iris_cx, iris_cy = dw//2 - iris_w//2, dh//2 - iris_h//2
27 | r = 20 # allowable deviation from center for iris
28 |
29 | tft0_clk = board.SCK
30 | tft0_mosi = board.MOSI
31 |
32 | tft_L0_rst = board.MISO
33 | tft_L0_dc = board.RX
34 | tft_L0_cs = board.TX
35 |
36 | spi0 = busio.SPI(clock=tft0_clk, MOSI=tft0_mosi)
37 |
38 | class Eye:
39 | def __init__(self, spi, dc, cs, rst, rot=0, eye_speed=0.25, twitch=2):
40 | display_bus = displayio.FourWire(spi, command=dc, chip_select=cs, reset=rst)
41 | display = gc9a01.GC9A01(display_bus, width=dw, height=dh, rotation=rot)
42 | main = displayio.Group()
43 | display.root_group = main
44 | self.display = display
45 | self.eyeball = displayio.TileGrid(eyeball_bitmap, pixel_shader=eyeball_pal)
46 | self.iris = displayio.TileGrid(iris_bitmap, pixel_shader=iris_pal, x = iris_cx, y = iris_cy )
47 | main.append(self.eyeball)
48 | main.append(self.iris)
49 | self.x, self.y = iris_cx, iris_cy
50 | self.tx, self.ty = self.x, self.y
51 | self.next_time = time.monotonic()
52 | self.eye_speed = eye_speed
53 | self.twitch = twitch
54 |
55 | def update(self, do_random=False, ntx=None, nty=None):
56 | self.x = self.x * (1-self.eye_speed) + self.tx * self.eye_speed # "easing"
57 | self.y = self.y * (1-self.eye_speed) + self.ty * self.eye_speed
58 | self.iris.x = int( self.x )
59 | self.iris.y = int( self.y )
60 |
61 | if ntx is not None and nty is not None:
62 | self.tx = iris_cx + ntx
63 | self.ty = iris_cx + nty
64 | else:
65 | if do_random and time.monotonic() > self.next_time:
66 | t = random.uniform(0.25,self.twitch)
67 | self.next_time = time.monotonic() + t
68 | self.tx = iris_cx + random.uniform(-r,r)
69 | self.ty = iris_cy + random.uniform(-r,r)
70 | print("change!",t )
71 | self.display.refresh()
72 |
73 | the_eye = Eye( spi0, tft_L0_dc, tft_L0_cs, tft_L0_rst, rot=0)
74 |
75 |
76 | i2c = board.STEMMA_I2C()
77 |
78 | # Wait until we can access the bus.
79 | while not i2c.try_lock():
80 | pass
81 |
82 | last_person_sensor_time = 0
83 | # Keep looping and reading the person sensor results.
84 | def get_faces():
85 | global last_person_sensor_time
86 |
87 | # The person sensor has the I2C ID of hex 62, or decimal 98.
88 | PERSON_SENSOR_I2C_ADDRESS = 0x62
89 |
90 | # We will be reading raw bytes over I2C, and we'll need to decode them into
91 | # data structures. These strings define the format used for the decoding, and
92 | # are derived from the layouts defined in the developer guide.
93 | PERSON_SENSOR_I2C_HEADER_FORMAT = "BBH"
94 | PERSON_SENSOR_I2C_HEADER_BYTE_COUNT = struct.calcsize(
95 | PERSON_SENSOR_I2C_HEADER_FORMAT)
96 |
97 | PERSON_SENSOR_FACE_FORMAT = "BBBBBBbB"
98 | PERSON_SENSOR_FACE_BYTE_COUNT = struct.calcsize(PERSON_SENSOR_FACE_FORMAT)
99 |
100 | PERSON_SENSOR_FACE_MAX = 4
101 | PERSON_SENSOR_RESULT_FORMAT = PERSON_SENSOR_I2C_HEADER_FORMAT + \
102 | "B" + PERSON_SENSOR_FACE_FORMAT * PERSON_SENSOR_FACE_MAX + "H"
103 | PERSON_SENSOR_RESULT_BYTE_COUNT = struct.calcsize(PERSON_SENSOR_RESULT_FORMAT)
104 |
105 | # How long to pause between sensor polls.
106 | PERSON_SENSOR_DELAY = 0.3
107 |
108 | # How large a face needs to be to count.
109 | MAIN_FACE_MIN_WIDTH = 16 # was 32
110 | MAIN_FACE_MIN_HEIGHT = 16
111 |
112 | if time.monotonic() - last_person_sensor_time < PERSON_SENSOR_DELAY:
113 | return []
114 | last_person_sensor_time = time.monotonic()
115 |
116 | read_data = bytearray(PERSON_SENSOR_RESULT_BYTE_COUNT)
117 | i2c.readfrom_into(PERSON_SENSOR_I2C_ADDRESS, read_data)
118 |
119 | offset = 0
120 | (pad1, pad2, payload_bytes) = struct.unpack_from(
121 | PERSON_SENSOR_I2C_HEADER_FORMAT, read_data, offset)
122 | offset = offset + PERSON_SENSOR_I2C_HEADER_BYTE_COUNT
123 |
124 | (num_faces) = struct.unpack_from("B", read_data, offset)
125 | num_faces = int(num_faces[0])
126 | offset = offset + 1
127 |
128 | faces = []
129 | for i in range(num_faces):
130 | (box_confidence, box_left, box_top, box_right, box_bottom, id_confidence, id,
131 | is_facing) = struct.unpack_from(PERSON_SENSOR_FACE_FORMAT, read_data, offset)
132 | offset = offset + PERSON_SENSOR_FACE_BYTE_COUNT
133 | face = {
134 | "box_confidence": box_confidence,
135 | "box_left": box_left,
136 | "box_top": box_top,
137 | "box_right": box_right,
138 | "box_bottom": box_bottom,
139 | "id_confidence": id_confidence,
140 | "id": id,
141 | "is_facing": is_facing,
142 | }
143 | faces.append(face)
144 | checksum = struct.unpack_from("H", read_data, offset)
145 |
146 | has_main_face = False
147 | has_lookie_loo = False
148 | for face in faces:
149 | width = face["box_right"] - face["box_left"]
150 | height = face["box_bottom"] - face["box_top"]
151 | big_enough_face = (
152 | width > MAIN_FACE_MIN_WIDTH and height > MAIN_FACE_MIN_HEIGHT)
153 | if big_enough_face:
154 | if not has_main_face:
155 | has_main_face = True
156 | else:
157 | if face["is_facing"] and face["box_confidence"] > 90:
158 | has_lookie_loo = True
159 |
160 | if debug_face: print("faces:",faces)
161 | return faces
162 |
163 | def map_range(s, a1, a2, b1, b2):
164 | return b1 + ((s - a1) * (b2 - b1) / (a2 - a1))
165 |
166 | while True:
167 | faces = []
168 | faces = get_faces()
169 |
170 | facex, facey = None,None
171 | if len(faces) > 0:
172 | facex0 = (faces[0]['box_right'] - faces[0]['box_left']) // 2 + faces[0]['box_left']
173 | facex = map_range(facex0, 0,255, 30,-30)
174 | facey = 0
175 | print("facex: ",facex0,facex)
176 |
177 | the_eye.update( ntx=facex, nty=facey )
178 |
--------------------------------------------------------------------------------
/larger-tricks/fireworks_sprites.py:
--------------------------------------------------------------------------------
1 | # fireworks_sprites.py - show a bunch of fireworks
2 | # the "fireworks_spritesheet.bmp' file can be found in the "bmps" directory
3 | # more info and video demo at this gist:
4 | # https://gist.github.com/todbot/f5a013918c208451424cea9d701f99c5
5 | # 4 Jul 2022 - @todbot / Tod Kurt
6 | import time, random
7 | import board
8 | import displayio
9 | import rainbowio
10 | import adafruit_imageload
11 |
12 | fireworks_count = 5
13 | sprite_fname = "fireworks_spritesheet.bmp"
14 | sprite_count = 30*1
15 | sprite_w,sprite_h = 64,64
16 |
17 | display = board.DISPLAY # our board has built-in display
18 | dw,dh = display.width, display.height # convenience values
19 |
20 | sprite_sheet, sprite_palette = adafruit_imageload.load(sprite_fname)
21 |
22 | maingroup = displayio.Group() # all fireworks go on 'maingroup'
23 | display.root_group = maingroup
24 |
25 | def random_xy():
26 | return random.randint(0, dw-sprite_w), random.randint(dh//2, dh//2+20)
27 |
28 | def copy_palette(p): # this is fastest way to copy a palette it seems
29 | new_p = displayio.Palette(len(p))
30 | for i in range(len(p)): new_p[i] = p[i]
31 | return new_p
32 |
33 | def new_colors(p): # first two colors after bg seem to be the "tails"
34 | p[1] = rainbowio.colorwheel( random.randint(0,255) )
35 | p[2] = rainbowio.colorwheel( random.randint(0,255) )
36 |
37 |
38 | # holds list of sprites,sprite_index,x,y,launch_time
39 | fireworks = [(None,0,0,0, 0) ] * fireworks_count
40 | # make our fireworks
41 | for i in range(len(fireworks)):
42 | pal = copy_palette(sprite_palette)
43 | pal.make_transparent(0) # make background color transparent
44 | x,y = random_xy()
45 | start_index = random.randint(0,sprite_count-1) # pick random point in fireworks life to start
46 | fwsprite = displayio.TileGrid(sprite_sheet, pixel_shader=pal,
47 | width = 1, height = 1,
48 | tile_width = sprite_w, tile_height = sprite_h)
49 | fireworks[i]= (fwsprite, start_index, x, y, pal) # add sprite, and initial index, x,y, palette
50 | maingroup.append(fwsprite)
51 |
52 | while True:
53 | for i in range(len(fireworks)):
54 | (sprite, sprite_index, x,y, palette) = fireworks[i] # get firework state
55 | # update firework state
56 | launching = sprite_index == 0 and random.random() < 0.9
57 | if launching:
58 | y = y - 3 # move towards top of screen
59 | else:
60 | sprite_index = sprite_index + 1 # explode!
61 | # firework finished
62 | if sprite_index == sprite_count:
63 | sprite_index = 0 # firework is reborn, make a new x,y for it
64 | x,y = random_xy() #random.randint(dw//4, 3*dw//4), random.randint(dh//2,dh//2+20)
65 | new_colors(palette)
66 | # update firework on screen
67 | sprite[0] = sprite_index # set next sprite in animation
68 | sprite.x, sprite.y = x,y # set its x,y
69 | # save the firework state
70 | fireworks[i] = (sprite, sprite_index, x,y, palette)
71 | time.sleep(0.008) # determines our framerate
72 |
--------------------------------------------------------------------------------
/larger-tricks/i2s_sdcard_pico.py:
--------------------------------------------------------------------------------
1 | # i2s_sdcard_pico.py -- I2S Audio from SD Card on RP2040 Pico
2 | # 20 May 2022 - @todbot / Tod Kurt
3 |
4 | import time
5 | import board, busio
6 | import audiocore, audiomixer, audiobusio
7 | import sdcardio, storage, os
8 |
9 | # pin definitions
10 | i2s_bclk = board.GP9 # BCK on PCM5102 (connect PCM5102 SCK pin to Gnd)
11 | i2s_wsel = board.GP10 # LCK on PCM5102
12 | i2s_data = board.GP11 # DIN on PCM5102
13 | sd_mosi = board.GP19
14 | sd_sck = board.GP18
15 | sd_miso = board.GP16
16 | sd_cs = board.GP17
17 |
18 | # sd card setup
19 | sd_spi = busio.SPI(clock=sd_sck, MOSI=sd_mosi, MISO=sd_miso)
20 | sdcard = sdcardio.SDCard(sd_spi, sd_cs)
21 | vfs = storage.VfsFat(sdcard)
22 | storage.mount(vfs, "/sd")
23 |
24 | # audio setup
25 | audio = audiobusio. I2SOut(bit_clock=i2s_bclk, word_select=i2s_wsel, data=i2s_data)
26 | mixer = audiomixer.Mixer(voice_count=1, sample_rate=22050, channel_count=1,
27 | bits_per_sample=16, samples_signed=True)
28 | audio.play(mixer) # attach mixer to audio playback
29 |
30 | # find all WAV files on SD card
31 | wav_fnames =[]
32 | for filename in os.listdir('/sd'):
33 | if filename.lower().endswith('.wav') and not filename.startswith('.'):
34 | wav_fnames.append("/sd/"+filename)
35 | wav_fnames.sort() # sort alphanumerically for mixtape numbered order
36 |
37 | print("found WAVs:")
38 | for fname in wav_fnames:
39 | print(" ", fname)
40 |
41 | while True:
42 | # play WAV file one after the other
43 | for fname in wav_fnames:
44 | print("playing WAV", fname)
45 | wave = audiocore.WaveFile(open(fname, "rb"))
46 | mixer.voice[0].play(wave, loop=False )
47 | time.sleep(3) # let WAV play a bit
48 | mixer.voice[0].stop()
49 |
--------------------------------------------------------------------------------
/larger-tricks/midi_forward.py:
--------------------------------------------------------------------------------
1 | #
2 | # midi_forward.py -- forward MIDI between MIDI USB and MIDI serial
3 | #
4 | # 8 Jan 2022 - @todbot / Tod Kurt
5 | #
6 |
7 | import time
8 | import board
9 | import busio
10 | import usb_midi
11 | import adafruit_midi
12 |
13 | print("Hello World!")
14 |
15 | midi_usb = adafruit_midi.MIDI( midi_in=usb_midi.ports[0],
16 | midi_out=usb_midi.ports[1] )
17 |
18 | class MIDI_Forward:
19 | def __init__(self, serial_tx_pin=board.TX, serial_rx_pin=board.RX, timeout=0.01):
20 | uart = busio.UART(tx=serial_tx_pin, rx=serial_rx_pin,
21 | baudrate=31250, timeout=timeout)
22 | self.midiusb_in = usb_midi.ports[0]
23 | self.midiusb_out = usb_midi.ports[1]
24 | self.midiser_in = uart
25 | self.midiser_out = uart
26 |
27 | def forward(self):
28 | bytes_usb = self.midiusb_in.read(30) # 30 from adafruit_midi
29 | if bytes_usb:
30 | self.midiser_out.write(bytes_usb, len(bytes_usb))
31 | bytes_ser = self.midiser_in.read(30)
32 | if bytes_ser:
33 | selfmidiusb_out.write(bytes_ser, len(bytes_ser))
34 |
35 | # can specify 'serial_tx_pin=' and 'serial_rx_pin=', or accept defaults
36 | midi_forward = MIDI_Forward()
37 |
38 | while True:
39 | midi_forward.forward()
40 |
--------------------------------------------------------------------------------
/larger-tricks/pidaydrummachine.py:
--------------------------------------------------------------------------------
1 | # pidaydrummachine.py - Pi Day Drum Machine
2 | # 14 Mar 2021 - @todbot / Tod Kurt
3 | # samples from: https://freesound.org/people/BaDoink/packs/30682/
4 | #
5 | import time
6 | import board
7 | import keypad
8 | import audiocore
9 | import audiomixer
10 | from audiopwmio import PWMAudioOut as AudioOut
11 |
12 | time.sleep(3) # wait a bit to reduce noise from CIRCUITPY access
13 |
14 | wav_files = (
15 | "wav/544413_punch_22k16b.wav",
16 | "wav/544682_snare_22k16b.wav",
17 | "wav/544684_dubhat1_22k16b.wav",
18 | "wav/544694_toggle_22k16b.wav",
19 | "wav/544587_splash1a_22k16b.wav",
20 | )
21 |
22 | # what keys to use as our drum machine
23 | keys = keypad.Keys((board.GP16, board.GP17, board.GP18, board.GP19, board.GP20),
24 | value_when_pressed=False, pull=True)
25 |
26 | audio = AudioOut(board.GP15)
27 | mixer = audiomixer.Mixer(voice_count=len(wav_files), sample_rate=22050, channel_count=1,
28 | bits_per_sample=16, samples_signed=True)
29 | audio.play(mixer) # attach mixer to audio playback, play with mixer.voice[n].play()
30 |
31 | while True:
32 | event = keys.events.get()
33 | if event and event.pressed:
34 | n = event.key_number
35 | mixer.voice[n].play( audiocore.WaveFile(open(wav_files[n],"rb")) )
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/larger-tricks/pidaydrummachine_wavs/544413_punch_22k16b.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/pidaydrummachine_wavs/544413_punch_22k16b.wav
--------------------------------------------------------------------------------
/larger-tricks/pidaydrummachine_wavs/544587_splash1a_22k16b.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/pidaydrummachine_wavs/544587_splash1a_22k16b.wav
--------------------------------------------------------------------------------
/larger-tricks/pidaydrummachine_wavs/544682_snare_22k16b.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/pidaydrummachine_wavs/544682_snare_22k16b.wav
--------------------------------------------------------------------------------
/larger-tricks/pidaydrummachine_wavs/544684_dubhat1_22k16b.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/pidaydrummachine_wavs/544684_dubhat1_22k16b.wav
--------------------------------------------------------------------------------
/larger-tricks/pidaydrummachine_wavs/544694_toggle_22k16b.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/pidaydrummachine_wavs/544694_toggle_22k16b.wav
--------------------------------------------------------------------------------
/larger-tricks/pidaydrummachine_wiring.fzz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/pidaydrummachine_wiring.fzz
--------------------------------------------------------------------------------
/larger-tricks/pidaydrummachine_wiring.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/pidaydrummachine_wiring.png
--------------------------------------------------------------------------------
/larger-tricks/pin_state.py:
--------------------------------------------------------------------------------
1 | # pin_state.py -- look at state of all pins in `board`
2 | # 6 Jul 2022 - @todbot / Tod Kurt
3 | #
4 | import time, board, microcontroller, digitalio
5 |
6 | def get_pin_states():
7 | pin_states = {}
8 | for name in sorted(dir(board)):
9 | maybe_pin = getattr(board, name)
10 | if isinstance(maybe_pin, microcontroller.Pin):
11 | try:
12 | test_pin = digitalio.DigitalInOut( maybe_pin )
13 | test_pin.direction = digitalio.Direction.INPUT
14 | pin_states[ name ] = test_pin.value
15 | except:
16 | pin_states[ name ] = None # in use
17 |
18 | return pin_states
19 |
20 | while True:
21 | pin_states = get_pin_states()
22 | for name,value in pin_states.items():
23 | print(f"{name:<15} = {value}")
24 | time.sleep(1)
25 |
--------------------------------------------------------------------------------
/larger-tricks/robust_keyboard.py:
--------------------------------------------------------------------------------
1 | # robust_keyboard_code.py -- demonstrate how to deal with USB disconnect on startup and while running
2 | # 27 Jul 2022 - @todbot / Tod Kurt
3 |
4 | import supervisor
5 | import time
6 | import board
7 | import digitalio
8 | import usb_hid
9 | from adafruit_hid.keyboard import Keyboard
10 | from adafruit_hid.keycode import Keycode
11 | from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
12 |
13 | def usb_connected(): return supervisor.runtime.usb_connected
14 |
15 | if usb_connected(): # only attempt keyboard creation if USB
16 | keyboard = Keyboard(usb_hid.devices)
17 | keyboard_layout = KeyboardLayoutUS(keyboard)
18 |
19 | # make up some buttons. This is for a FunHouse, but could be any keys
20 | buttons = []
21 | for pin in (board.BUTTON_UP, board.BUTTON_SELECT, board.BUTTON_DOWN):
22 | switch = digitalio.DigitalInOut(pin)
23 | switch.pull = digitalio.Pull.DOWN # defaults to input
24 | buttons.append(switch)
25 |
26 | # what keycodes to send for each button
27 | keys = [Keycode.A, Keycode.B, Keycode.C]
28 |
29 | while True:
30 | print("usb:", usb_connected(), "buttons:", [b.value for b in buttons] )
31 | for i in range(len(buttons)):
32 | if buttons[i].value:
33 | if usb_connected():
34 | try:
35 | keyboard.send( keys[i] )
36 | except OSError: # also if USB disconnected
37 | pass
38 |
39 | time.sleep(0.1)
40 |
--------------------------------------------------------------------------------
/larger-tricks/sdcard_max_wavs/code.py:
--------------------------------------------------------------------------------
1 | # sdcard_max_wavs_code.py -- Find out how many WAVs can play at once from SD card
2 | # 30 Jul 2023 - @todbot / Tod Kurt
3 | #
4 | # Plays I2S Audio from SD Card. Finds all WAVs in dir,
5 | # plays one after the other until CircuitPython crashes
6 | # On RP2040 Pico, maximum number of usable WAVs is about 10, with crashing at 14
7 | # Be sure to use fast SD card
8 | # To use:
9 | # - Copy this file to CIRCUITPY drive
10 | # - Copy the "sine_wavs" folder to an SD card. It contains 25 WAV files of different pitched sine waves
11 | # - Adjust the pin definitions below to match your board
12 | # - Plug SD card into your setup
13 | # - Start up CircuitPython board, watch the REPL and listen to the audio output to see when things crash
14 | #
15 |
16 | import time
17 | import board, busio
18 | import audiocore, audiomixer, audiobusio
19 | import sdcardio, storage, os
20 |
21 | wavdir="/sd/sine_wavs" # where we put our WAV files (without the "SD"
22 |
23 | # pin definitions
24 | i2s_bclk = board.GP9 # BCK on PCM5102 (be sure to connect PCM5102 SCK pin to Gnd)
25 | i2s_wsel = board.GP10 # LCK on PCM5102
26 | i2s_data = board.GP11 # DIN on PCM5102
27 | sd_mosi = board.GP19
28 | sd_sck = board.GP18
29 | sd_miso = board.GP16
30 | sd_cs = board.GP17
31 |
32 | # sd card setup
33 | sd_spi = busio.SPI(clock=sd_sck, MOSI=sd_mosi, MISO=sd_miso)
34 | sdcard = sdcardio.SDCard(sd_spi, sd_cs, baudrate=32_000_000)
35 | vfs = storage.VfsFat(sdcard)
36 | storage.mount(vfs, "/sd")
37 |
38 | # find all WAV files on SD card
39 | wav_fnames =[]
40 | for filename in os.listdir(wavdir):
41 | if filename.lower().endswith('.wav') and not filename.startswith('.'):
42 | wav_fnames.append(wavdir+"/"+filename)
43 |
44 | wav_fnames.sort() # sort alphanumerically for mixtape numbered order
45 | print("found WAVs:")
46 | for fname in wav_fnames:
47 | print(" ", fname)
48 |
49 | # audio setup, voice count is max number of WAVs found
50 | audio = audiobusio. I2SOut(bit_clock=i2s_bclk, word_select=i2s_wsel, data=i2s_data)
51 | mixer = audiomixer.Mixer(voice_count=len(wav_fnames), sample_rate=22050, channel_count=1,
52 | bits_per_sample=16, samples_signed=True, buffer_size=1024)
53 | audio.play(mixer) # attach mixer to audio playback
54 |
55 | wavs = []
56 | for i in range(len(wav_fnames)):
57 | fname = wav_fnames[i]
58 | print('opening',fname)
59 | wave = audiocore.WaveFile(wav_fnames[i]) #, bytearray(1024)) # added max buffer, doesn't help on RP2040
60 | wavs.append(wave)
61 |
62 | print("spi:", sd_spi.frequency) # should be about 32 MHz
63 |
64 | # play WAV file one after the other
65 | while True:
66 | i=0
67 | for i in range(len(wav_fnames)):
68 | print("playing WAV", wav_fnames[i])
69 | mixer.voice[i].play(wavs[i], loop=True )
70 | time.sleep(2) # let WAV play a bit
71 |
--------------------------------------------------------------------------------
/larger-tricks/sdcard_max_wavs/sine_wavs/01.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/sdcard_max_wavs/sine_wavs/01.wav
--------------------------------------------------------------------------------
/larger-tricks/sdcard_max_wavs/sine_wavs/02.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/sdcard_max_wavs/sine_wavs/02.wav
--------------------------------------------------------------------------------
/larger-tricks/sdcard_max_wavs/sine_wavs/03.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/sdcard_max_wavs/sine_wavs/03.wav
--------------------------------------------------------------------------------
/larger-tricks/sdcard_max_wavs/sine_wavs/04.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/sdcard_max_wavs/sine_wavs/04.wav
--------------------------------------------------------------------------------
/larger-tricks/sdcard_max_wavs/sine_wavs/05.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/sdcard_max_wavs/sine_wavs/05.wav
--------------------------------------------------------------------------------
/larger-tricks/sdcard_max_wavs/sine_wavs/06.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/sdcard_max_wavs/sine_wavs/06.wav
--------------------------------------------------------------------------------
/larger-tricks/sdcard_max_wavs/sine_wavs/07.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/sdcard_max_wavs/sine_wavs/07.wav
--------------------------------------------------------------------------------
/larger-tricks/sdcard_max_wavs/sine_wavs/08.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/sdcard_max_wavs/sine_wavs/08.wav
--------------------------------------------------------------------------------
/larger-tricks/sdcard_max_wavs/sine_wavs/09.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/sdcard_max_wavs/sine_wavs/09.wav
--------------------------------------------------------------------------------
/larger-tricks/sdcard_max_wavs/sine_wavs/10.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/sdcard_max_wavs/sine_wavs/10.wav
--------------------------------------------------------------------------------
/larger-tricks/sdcard_max_wavs/sine_wavs/11.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/sdcard_max_wavs/sine_wavs/11.wav
--------------------------------------------------------------------------------
/larger-tricks/sdcard_max_wavs/sine_wavs/12.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/sdcard_max_wavs/sine_wavs/12.wav
--------------------------------------------------------------------------------
/larger-tricks/sdcard_max_wavs/sine_wavs/13.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/sdcard_max_wavs/sine_wavs/13.wav
--------------------------------------------------------------------------------
/larger-tricks/sdcard_max_wavs/sine_wavs/14.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/sdcard_max_wavs/sine_wavs/14.wav
--------------------------------------------------------------------------------
/larger-tricks/sdcard_max_wavs/sine_wavs/15.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/sdcard_max_wavs/sine_wavs/15.wav
--------------------------------------------------------------------------------
/larger-tricks/sdcard_max_wavs/sine_wavs/16.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/sdcard_max_wavs/sine_wavs/16.wav
--------------------------------------------------------------------------------
/larger-tricks/sdcard_max_wavs/sine_wavs/17.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/sdcard_max_wavs/sine_wavs/17.wav
--------------------------------------------------------------------------------
/larger-tricks/sdcard_max_wavs/sine_wavs/18.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/sdcard_max_wavs/sine_wavs/18.wav
--------------------------------------------------------------------------------
/larger-tricks/sdcard_max_wavs/sine_wavs/19.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/sdcard_max_wavs/sine_wavs/19.wav
--------------------------------------------------------------------------------
/larger-tricks/sdcard_max_wavs/sine_wavs/20.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/sdcard_max_wavs/sine_wavs/20.wav
--------------------------------------------------------------------------------
/larger-tricks/sdcard_max_wavs/sine_wavs/21.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/sdcard_max_wavs/sine_wavs/21.wav
--------------------------------------------------------------------------------
/larger-tricks/sdcard_max_wavs/sine_wavs/22.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/sdcard_max_wavs/sine_wavs/22.wav
--------------------------------------------------------------------------------
/larger-tricks/sdcard_max_wavs/sine_wavs/23.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/sdcard_max_wavs/sine_wavs/23.wav
--------------------------------------------------------------------------------
/larger-tricks/sdcard_max_wavs/sine_wavs/24.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/sdcard_max_wavs/sine_wavs/24.wav
--------------------------------------------------------------------------------
/larger-tricks/sdcard_max_wavs/sine_wavs/25.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/sdcard_max_wavs/sine_wavs/25.wav
--------------------------------------------------------------------------------
/larger-tricks/sdcard_max_wavs/sine_wavs/make_wavs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/zsh
2 |
3 | # make a bunch of test waves
4 | NOTE_START=32
5 | NOTE_END=60
6 | WAVE_TYPE=sin
7 |
8 | for n in $(seq $NOTE_START $NOTE_END) ; do
9 | freq=$(( (440.0/32.0) * (2**(($n-9.0)/12.0)) )) # midi to hz
10 | fn=`printf "%03d.wav" ${n}`
11 | printf "note:%2d freq:%4.1f fn:%s\n" $n $freq $fn
12 | sox -V -r 22050 -n -b 16 -c 1 $fn synth 1 $WAVE_TYPE $freq vol -10dB
13 | done
14 |
--------------------------------------------------------------------------------
/larger-tricks/vectorio_rotate_example.py:
--------------------------------------------------------------------------------
1 | # vectorio_rotate_example.py
2 | # 21 Jul 2022 - @todbot / Tod Kurt
3 | # Rotation of a vectorio polygon
4 |
5 | import time
6 | import board
7 | import displayio
8 | import vectorio
9 | import math
10 |
11 | display = board.DISPLAY # assume we have built-in display
12 |
13 | #path0 = [(-20.0,-20.0), (-20.0,20.0), (20.0,20.0), (20.0,-20.0)]
14 | path0_w = 50.000
15 | path0_h = 50.000
16 | path0 = [(26.000,1.003),(26.775,5.948),(27.548,10.879),(28.321,15.813),(31.002,13.640),(34.145,9.746),(37.280,5.861),(37.399,7.204),(35.601,11.864),(33.800,16.532),(34.700,18.496),(39.374,16.693),(44.044,14.891),(46.777,14.205),(42.882,17.349),(38.986,20.492),(35.380,23.553),(40.324,24.328),(45.260,25.101),(50.196,25.874),(46.855,26.649),(41.914,27.423),(36.972,28.198),(37.724,30.489),(41.619,33.633),(45.514,36.776),(45.548,37.689),(40.877,35.887),(36.226,34.093),(33.213,33.946),(35.016,38.619),(36.814,43.281),(38.300,47.402),(35.161,43.513),(32.021,39.623),(28.877,35.726),(27.799,39.514),(27.025,44.458),(26.251,49.400),(25.476,47.654),(24.703,42.719),(23.928,37.776),(22.016,37.098),(18.881,40.982),(15.738,44.877),(14.020,46.302),(15.821,41.634),(17.616,36.982),(18.802,32.924),(14.141,34.722),(9.477,36.522),(4.812,38.322),(7.861,35.666),(11.740,32.536),(15.629,29.397),(13.275,27.923),(8.334,27.149),(3.396,26.375),(3.535,25.603),(8.472,24.829),(13.411,24.056),(15.539,22.530),(11.641,19.384),(7.745,16.240),(4.933,13.725),(9.602,15.526),(14.266,17.326),(18.937,19.128),(17.570,14.898),(15.772,10.238),(13.970,5.569),(15.813,7.216),(18.954,11.108),(22.102,15.009),(23.951,14.082),(24.724,9.142),(25.497,4.212),]
17 |
18 | shape0_w = 50 # not necessarily min/max of path
19 | shape0_h = 50
20 | shape0_color = 0xff00ff # preferred color
21 |
22 | ### vectorio_tools
23 |
24 | def rotate_points(pts, a):
25 | """Rotate a list of points pts by angle a around origin"""
26 | sa, ca = math.sin(a), math.cos(a) # do this computation only once
27 | return [ (p[0]*ca - p[1]*sa, p[1]*ca + p[0]*sa) for p in pts ] # p[0]=x, p[1]=y
28 |
29 | def recenter_points(pts, c=(0,0)):
30 | """Center points around new origin c"""
31 | return [(p[0] - c[0], p[1] - c[1]) for p in pts]
32 |
33 | def rescale_points(pts, orig_w, orig_h, new_w, new_h):
34 | """Rescale point path to new w,h"""
35 | return [ (new_w * p[0] / orig_w, new_h * p[1] / orig_h ) for p in pts]
36 |
37 | def int_points(pts):
38 | """Convert array of flooat points to int, for vectorio"""
39 | return [(int(p[0]),int(p[1])) for p in pts]
40 |
41 | ###
42 |
43 | maingroup = displayio.Group()
44 | display.root_group = maingroup # put main group on display, everything goes in maingroup
45 |
46 | path0 = recenter_points(path0, c=(path0_w//2, path0_h//2))
47 |
48 | # create vectorio shape, put in a group, put that on maingroup
49 | pal = displayio.Palette(1)
50 | pal[0] = shape0_color
51 | shape0 = vectorio.Polygon(pixel_shader=pal, points=int_points(path0))
52 | shapeg = displayio.Group() #scale=1, x = 0, y=0)
53 | shapeg.append(shape0)
54 | maingroup.append(shapeg)
55 |
56 | x,y = 80, 80 # starting location for our shape
57 | theta = 0
58 | xvel, yvel = 1.8, 1.0 # xy velocity
59 | theta_vel = 0.04
60 |
61 | last_time = 0
62 | while True:
63 |
64 | elapsed_time = time.monotonic()
65 |
66 | # rotate about shape origin
67 | shape0.points = int_points( rotate_points(path0, theta) )
68 |
69 | elapsed_time = time.monotonic() - elapsed_time
70 |
71 | if time.monotonic() - last_time > 0.5:
72 | last_time = time.monotonic()
73 | print("elapsed millis %d" % (elapsed_time * 1000))
74 |
75 | # update position
76 | x,y = x + xvel, y + yvel
77 | theta = theta + theta_vel
78 | shapeg.x = int(x)
79 | shapeg.y = int(y)
80 |
81 | # bounce on screen edge hit
82 | if x < shape0_w//2 or x > (display.width - shape0_w//2):
83 | xvel = -xvel
84 | if y < shape0_h//2 or y > (display.height - shape0_h//2):
85 | yvel = -yvel
86 |
87 | time.sleep(0.05)
88 |
--------------------------------------------------------------------------------
/larger-tricks/wav/drumsacuff_22k_s16.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/wav/drumsacuff_22k_s16.wav
--------------------------------------------------------------------------------
/larger-tricks/wav/laser2.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/wav/laser2.wav
--------------------------------------------------------------------------------
/larger-tricks/wav/laser20.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/wav/laser20.wav
--------------------------------------------------------------------------------
/larger-tricks/wav/silence-2sec.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/wav/silence-2sec.wav
--------------------------------------------------------------------------------
/larger-tricks/wav/snowpeaks_22k_s16.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/wav/snowpeaks_22k_s16.wav
--------------------------------------------------------------------------------
/larger-tricks/wav/vocalchops476663_22k_128k.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/todbot/circuitpython-tricks/0f3023a762e59c159049a795172146eaadbc0642/larger-tricks/wav/vocalchops476663_22k_128k.mp3
--------------------------------------------------------------------------------
/synthio/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Synthio Tricks
4 | ===============
5 |
6 | `synthio-tricks` has it's own repository now!
7 |
8 | [https://github.com/todbot/circuitpython-synthio-tricks](https://github.com/todbot/circuitpython-synthio-tricks)
9 |
--------------------------------------------------------------------------------