├── imgs ├── qtpy-farty.jpg ├── qtpy-fire.gif ├── qtpy-oled.jpg ├── qtpy-capsense.gif ├── qtpy-encoder.gif ├── qtpy-neodisco.gif ├── qtpy-oledeyes.gif ├── qtpy-servoeye.gif ├── qtpy-flashsize-2MB.png └── qtpy-capsense-ghost.gif └── README.md /imgs/qtpy-farty.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/todbot/qtpy-tricks/HEAD/imgs/qtpy-farty.jpg -------------------------------------------------------------------------------- /imgs/qtpy-fire.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/todbot/qtpy-tricks/HEAD/imgs/qtpy-fire.gif -------------------------------------------------------------------------------- /imgs/qtpy-oled.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/todbot/qtpy-tricks/HEAD/imgs/qtpy-oled.jpg -------------------------------------------------------------------------------- /imgs/qtpy-capsense.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/todbot/qtpy-tricks/HEAD/imgs/qtpy-capsense.gif -------------------------------------------------------------------------------- /imgs/qtpy-encoder.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/todbot/qtpy-tricks/HEAD/imgs/qtpy-encoder.gif -------------------------------------------------------------------------------- /imgs/qtpy-neodisco.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/todbot/qtpy-tricks/HEAD/imgs/qtpy-neodisco.gif -------------------------------------------------------------------------------- /imgs/qtpy-oledeyes.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/todbot/qtpy-tricks/HEAD/imgs/qtpy-oledeyes.gif -------------------------------------------------------------------------------- /imgs/qtpy-servoeye.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/todbot/qtpy-tricks/HEAD/imgs/qtpy-servoeye.gif -------------------------------------------------------------------------------- /imgs/qtpy-flashsize-2MB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/todbot/qtpy-tricks/HEAD/imgs/qtpy-flashsize-2MB.png -------------------------------------------------------------------------------- /imgs/qtpy-capsense-ghost.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/todbot/qtpy-tricks/HEAD/imgs/qtpy-capsense-ghost.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QT Py Tricks 2 | 3 | Some things to do on a stock [Adafruit QT Py](https://adafruit.com/qtpy) 4 | running [CircuitPython](https://circuitpython.org) 6. 5 | This list is mostly to help me remmeber how to do things in CircuitPython. 6 | Fore more general [CircuitPython tricks](https://github.com/todbot/circuitpython-tricks), check out my [CircuitPython Tricks page](https://github.com/todbot/circuitpython-tricks). 7 | 8 | These code snippets will also work on a Trinket M0 and really just about 9 | CircuitPython-compatible board, but you may need to adjust some of the `board` pins. 10 | 11 | Notes: 12 | - You will need to copy needed libraries from the [CircuitPython libraries bundle](https://circuitpython.org/libraries) to your CIRCUITPY drive 13 | - When copying files to QT Py or Trinket M0, and you're on a Mac, 14 | be sure to [use the `xattr` trick described here](https://todbot.com/blog/2020/10/03/prevent-annoying-mac-_-files-being-created-on-circuitpy/) to save flash space 15 | - Or, just use [`circup`](https://learn.adafruit.com/keep-your-circuitpython-libraries-on-devices-up-to-date-with-circup/install-circup) to install libraries. It's very nice! (on Mac, do `pip3 install circup`) 16 | 17 | ## Table of Contents 18 | 19 | * [Print "time" on OLED display](#print-time-on-oled-display) 20 | * [Disco Party on built-in Neopixel](#disco-party-on-built-in-neopixel) 21 | * [Output Farty Noises to DAC](#output-farty-noises-to-dac) 22 | * [Capsense Touch to Colors on Built-in LED](#capsense-touch-to-colors-on-built-in-led) 23 | * [Rotary Encoder to Built-in LED](#rotary-encoder-to-built-in-led) 24 | * [Fire Simulation on External Neopixel Strip](#fire-simulation-on-external-neopixel-strip) 25 | * [Two servos with Python Class for Easing / Sequencing](#two-servos-with-python-class-for-easing--sequencing) 26 | * [Spooky Eyes with Dual SSD1306 OLED displays](#spooky-eyes-with-dual-ssd1306-oled-displays) 27 | * [Use Capsense as Proximity Detector to Make Spooopy Ghost](#use-capsense-as-proximity-detector-to-make-spooopy-ghost) 28 | * [Get Size of Device's Flash Disk](#get-size-of-devices-flash-disk) 29 | * [Capsense Touch Sensor to USB keyboard](#capsense-touch-sensor-to-usb-keyboard) 30 | 31 | 32 | ## Print "time" on OLED display 33 | 34 | [Note: as of CircuitPython 7, this only works on QTPy RP2040, not QTPy M0 or QTPY M0 Haxpress] 35 | 36 | ```py 37 | import time 38 | import board 39 | import adafruit_ssd1306 # requires: adafruit_bus_device and adafruit_framebuf 40 | i2c = board.I2C() 41 | oled = adafruit_ssd1306.SSD1306_I2C(width=128, height=32, i2c=i2c) 42 | while True: 43 | oled.fill(0) 44 | oled.text( "hello world", 0,0,1) # requires 'font5x8.bin' 45 | oled.text("time:"+str(time.monotonic()), 0,8,1) 46 | oled.show() 47 | time.sleep(1.0) 48 | ``` 49 | 50 | 51 | ## Disco Party on built-in Neopixel 52 | ```py 53 | import time 54 | import board 55 | import neopixel 56 | from random import randint 57 | pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) 58 | while True: 59 | pixel.fill( (randint(0,255), randint(0,255), randint(0,255) )) 60 | time.sleep(0.1) 61 | ``` 62 | 63 | 64 | ## Output Farty Noises to DAC 65 | 66 | ```py 67 | import time 68 | import board 69 | import analogio 70 | import random 71 | dac = analogio.AnalogOut(board.A0) 72 | i = 0 73 | di = 40000 74 | lasttime = 0 75 | while True: 76 | dac.value = i 77 | i = (i+di) & 0xffff 78 | if time.monotonic() - lasttime > 1: 79 | lasttime = time.monotonic() 80 | di = random.randrange(40000,80000) 81 | ``` 82 | 83 | 84 | ([hear it in action](https://twitter.com/todbot/status/1313223090181042177)) 85 | 86 | 87 | 88 | ## Capsense Touch to Colors on Built-in LED 89 | 90 | ```py 91 | import board 92 | from touchio import TouchIn 93 | import neopixel 94 | pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) 95 | touchA = TouchIn(board.A1) 96 | touchB = TouchIn(board.A2) 97 | print("hello") 98 | while True: 99 | pixel[0] = (int(touchA.value*255), 0, int(touchB.value*255)) 100 | ``` 101 | 102 | 103 | ## Rotary Encoder to Built-in LED 104 | 105 | ```py 106 | import board 107 | import time 108 | import neopixel 109 | import rotaryio 110 | pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=1.0) 111 | encoder = rotaryio.IncrementalEncoder( board.MOSI, board.MISO ) # any two pins 112 | while True: 113 | b = (encoder.position % 32) * 8 114 | print(encoder.position,b) 115 | pixel.fill((0,0,b)) 116 | time.sleep(0.1) 117 | ``` 118 | 119 | 120 | 121 | ## Fire Simulation on External Neopixel Strip 122 | 123 | 124 | 125 | Uses Python array operations and list comprehensions for conciseness. 126 | 127 | ```py 128 | import board 129 | import time 130 | import neopixel 131 | from random import randint 132 | # External Neopixel strip, can be on any pin 133 | leds = neopixel.NeoPixel(board.RX,8,brightness=0.2,auto_write=False) 134 | while True: 135 | # reduce brightness of all pixels by (30,30,30) 136 | leds[:] = [[max(i-30,0) for i in l] for l in leds] 137 | # shift LED values up by one (0->1, 1->2, etc) 138 | leds[1:] = leds[0:-1] # '-1' means len-1 here 139 | # pick new random fire color for LED 0 140 | leds[0] = (randint(150,255),randint(50,100),0) 141 | leds.show() 142 | time.sleep(0.1) 143 | ``` 144 | 145 | If you want it "flipped", so the fire goes from the top LED down to LED 0: 146 | 147 | ```py 148 | import time, board, neopixel 149 | from random import randint 150 | leds = neopixel.NeoPixel(board.RX,8,brightness=0.2,auto_write=False) 151 | while True: 152 | leds[:] = [[max(i-30,0) for i in l] for l in leds] # reduce brightness of all pixels by (30,30,30) 153 | leds[0:-1] = leds[1:] # shift LED values down by one 154 | leds[-1] = (randint(150,255),randint(50,100),0) # pick new random fire color for LED N 155 | leds.show() 156 | time.sleep(0.1) 157 | ``` 158 | 159 | 160 | ## Two servos with Python Class for Easing / Sequencing 161 | 162 | ```py 163 | import board 164 | import time 165 | from pulseio import PWMOut 166 | from adafruit_motor import servo 167 | from random import random 168 | 169 | servoA = servo.Servo(PWMOut(board.RX, duty_cycle=2**15, frequency=50)) 170 | servoB = servo.Servo(PWMOut(board.TX, duty_cycle=2**15, frequency=50)) 171 | 172 | class AngleSequence: 173 | def __init__(self,angles,speed): 174 | self.angles = angles 175 | self.aindex = 0 176 | self.angle = angles[0] 177 | self.speed = speed 178 | def next_angle(self): 179 | self.aindex = (self.aindex + 1) % len(self.angles) 180 | def update(self): 181 | new_angle = self.angles[self.aindex] 182 | self.angle += (new_angle - self.angle) * self.speed # simple easing 183 | return self.angle 184 | 185 | seqA = AngleSequence( [90,70,90,90,90,90,60,80,100], 0.1) # set speed to taste 186 | seqB = AngleSequence( [90,90,90,80,70,90,120], 0.1) # set angles for desired animation 187 | 188 | lasttime = time.monotonic() 189 | while True: 190 | if time.monotonic() - lasttime > (0.2 + random()*4.0 ): # creepy random timing 191 | lasttime = time.monotonic() 192 | seqA.next_angle() # go to next angle in list 193 | seqB.next_angle() 194 | servoA.angle = seqA.update() # get updated (eased) servo angle 195 | servoB.angle = seqB.update() 196 | time.sleep(0.02) # wait a bit, note this affects 'speed' 197 | ``` 198 | 199 | 200 | 201 | ## Spooky Eyes with Dual SSD1306 OLED displays 202 | 203 | Displays are at same address, so code can be much simpler 204 | 205 | [Note: as of CircuitPython 7, this only works on QTPy RP2040, not QTPy M0 or QTPY M0 Haxpress] 206 | 207 | ```py 208 | import time 209 | import board 210 | import random 211 | import adafruit_ssd1306 # requires: adafruit_bus_device, adafruit_framebuf 212 | i2c = board.I2C() 213 | oled = adafruit_ssd1306.SSD1306_I2C(width=128, height=64, i2c=i2c) 214 | i=0; inc=30 215 | while True: 216 | oled.fill(0) 217 | for d in (31,30,14,12,10,8): # various diameters 218 | oled.circle( 64+i,32, d,1) 219 | i = random.randint(-30,30) 220 | oled.show() 221 | time.sleep( 0.1 + random.random() ) 222 | ``` 223 | 224 | 225 | 226 | ## Use Capsense as Proximity Detector to Make Spooopy Ghost 227 | Computing the difference between current touch raw value anda baseline minimum 228 | provides a kind of proximity detector, if your antenna is big enough. 229 | 230 | ```py 231 | import time 232 | import board 233 | import digitalio 234 | import touchio 235 | from pulseio import PWMOut 236 | from adafruit_motor import servo 237 | 238 | touchA = touchio.TouchIn(board.A2) 239 | servoA = servo.Servo(PWMOut(board.RX, duty_cycle=2**15, frequency=50)) 240 | touch_min = touchA.raw_value # baseline for proximity 241 | servo_pos_last = 160 242 | 243 | while True: 244 | # get proximity value, set within servo bounds (30-160) 245 | proximity_val = (touchA.raw_value - touch_min) 246 | servo_pos = 160 - min(160, max(30, proximity_val)) 247 | servo_pos_last += (servo_pos - servo_pos_last) * 0.01 # easing/smoothing 248 | servoA.angle = servo_pos_last 249 | ``` 250 | 251 | 252 | 253 | 254 | ## Get Size of Device's Flash Disk 255 | see [`os.statvfs()` docs](https://circuitpython.readthedocs.io/en/latest/shared-bindings/os/index.html#os.statvfs) 256 | 257 | ```py 258 | import os 259 | print("\nHello World!") 260 | fs_stat = os.statvfs('/') 261 | print("Disk size in MB", fs_stat[0] * fs_stat[2] / 1024 / 1024) 262 | print("Free space in MB", fs_stat[0] * fs_stat[3] / 1024 / 1024) 263 | while True: pass 264 | ``` 265 | 266 | 267 | 268 | ## Capsense Touch Sensor to USB keyboard 269 | ```py 270 | import time 271 | import board 272 | import neopixel 273 | import touchio 274 | import usb_hid 275 | from adafruit_hid.keyboard import Keyboard 276 | from adafruit_hid.keycode import Keycode 277 | touchA= touchio.TouchIn(board.A0) 278 | pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2, auto_write=False) 279 | pixel[0] = (0,0,0) 280 | time.sleep(1) # Sleep for a bit to avoid a race condition on some systems 281 | kbd = Keyboard(usb_hid.devices) 282 | while True: 283 | if touchA.value: 284 | print("A press") 285 | pixel[0] = (255,0,0) 286 | kbd.send(Keycode.RIGHT_ARROW) 287 | pixel.show() 288 | pixel[0] = (0,0,0) 289 | time.sleep(0.2) 290 | ``` 291 | 292 | *** 293 | 294 | # Really Helpful CircuitPython Links 295 | 296 | - https://circuitpython.readthedocs.io/projects/bundle/en/latest/drivers.html 297 | - https://circuitpython.readthedocs.io/en/latest/shared-bindings/support_matrix.html 298 | - https://learn.adafruit.com/circuitpython-essentials/circuitpython-essentials 299 | - https://learn.adafruit.com/welcome-to-circuitpython/overview 300 | --------------------------------------------------------------------------------