{"id":15142141,"url":"https://github.com/todbot/qtpy-tricks","last_synced_at":"2025-10-23T19:31:13.420Z","repository":{"id":66886496,"uuid":"301506943","full_name":"todbot/qtpy-tricks","owner":"todbot","description":"Some tips and tricks for CircuitPython, using a QT Py board","archived":false,"fork":false,"pushed_at":"2022-05-09T23:00:38.000Z","size":9343,"stargazers_count":42,"open_issues_count":0,"forks_count":7,"subscribers_count":6,"default_branch":"main","last_synced_at":"2024-09-27T09:23:27.555Z","etag":null,"topics":["circuitpython","qtpy","samd21","trinket","trinket-m0"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/todbot.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-10-05T18:42:34.000Z","updated_at":"2024-02-17T08:03:29.000Z","dependencies_parsed_at":"2023-05-12T20:30:54.526Z","dependency_job_id":null,"html_url":"https://github.com/todbot/qtpy-tricks","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/todbot%2Fqtpy-tricks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/todbot%2Fqtpy-tricks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/todbot%2Fqtpy-tricks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/todbot%2Fqtpy-tricks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/todbot","download_url":"https://codeload.github.com/todbot/qtpy-tricks/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":219867955,"owners_count":16555810,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["circuitpython","qtpy","samd21","trinket","trinket-m0"],"created_at":"2024-09-26T09:23:36.788Z","updated_at":"2025-10-23T19:31:11.006Z","avatar_url":"https://github.com/todbot.png","language":null,"readme":"# QT Py Tricks\n\nSome things to do on a stock [Adafruit QT Py](https://adafruit.com/qtpy)\nrunning [CircuitPython](https://circuitpython.org) 6.\nThis list is mostly to help me remmeber how to do things in CircuitPython.\nFore more general [CircuitPython tricks](https://github.com/todbot/circuitpython-tricks), check out my [CircuitPython Tricks page](https://github.com/todbot/circuitpython-tricks).\n\nThese code snippets will also work on a Trinket M0 and really just about\nCircuitPython-compatible board, but you may need to adjust some of the `board` pins.\n\nNotes:\n- You will need to copy needed libraries from the [CircuitPython libraries bundle](https://circuitpython.org/libraries) to your CIRCUITPY drive\n- When copying files to QT Py or Trinket M0, and you're on a Mac,\nbe 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\n- 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`)\n\n## Table of Contents\n\n* [Print \"time\" on OLED display](#print-time-on-oled-display)\n* [Disco Party on built-in Neopixel](#disco-party-on-built-in-neopixel)\n* [Output Farty Noises to DAC](#output-farty-noises-to-dac)\n* [Capsense Touch to Colors on Built-in LED](#capsense-touch-to-colors-on-built-in-led)\n* [Rotary Encoder to Built-in LED](#rotary-encoder-to-built-in-led)\n* [Fire Simulation on External Neopixel Strip](#fire-simulation-on-external-neopixel-strip)\n* [Two servos with Python Class for Easing / Sequencing](#two-servos-with-python-class-for-easing--sequencing)\n* [Spooky Eyes with Dual SSD1306 OLED displays](#spooky-eyes-with-dual-ssd1306-oled-displays)\n* [Use Capsense as Proximity Detector to Make Spooopy Ghost](#use-capsense-as-proximity-detector-to-make-spooopy-ghost)\n* [Get Size of Device's Flash Disk](#get-size-of-devices-flash-disk)\n* [Capsense Touch Sensor to USB keyboard](#capsense-touch-sensor-to-usb-keyboard)\n         \n\n## Print \"time\" on OLED display\n\n[Note: as of CircuitPython 7, this only works on QTPy RP2040, not QTPy M0 or QTPY M0 Haxpress]\n\n```py\nimport time\nimport board \nimport adafruit_ssd1306 # requires: adafruit_bus_device and adafruit_framebuf\ni2c = board.I2C()\noled = adafruit_ssd1306.SSD1306_I2C(width=128, height=32, i2c=i2c)\nwhile True:\n    oled.fill(0)\n    oled.text( \"hello world\", 0,0,1) # requires 'font5x8.bin'\n    oled.text(\"time:\"+str(time.monotonic()), 0,8,1)\n    oled.show()\n    time.sleep(1.0)\n```\n\u003cimg width=400 src=\"./imgs/qtpy-oled.jpg\"/\u003e\n\n## Disco Party on built-in Neopixel\n```py\nimport time\nimport board\nimport neopixel\nfrom random import randint\npixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)\nwhile True:\n    pixel.fill( (randint(0,255), randint(0,255), randint(0,255) ))\n    time.sleep(0.1)\n```\n\u003cimg width=400 src=\"./imgs/qtpy-neodisco.gif\" /\u003e\n\n## Output Farty Noises to DAC\n\n```py\nimport time\nimport board \nimport analogio\nimport random\ndac = analogio.AnalogOut(board.A0)\ni = 0\ndi = 40000\nlasttime = 0\nwhile True:\n    dac.value = i\n    i = (i+di) \u0026 0xffff\n    if time.monotonic() - lasttime \u003e 1:\n        lasttime = time.monotonic()\n        di = random.randrange(40000,80000)\n```\n\u003cimg width=400 src=\"./imgs/qtpy-farty.jpg\"/\u003e\n\n([hear it in action](https://twitter.com/todbot/status/1313223090181042177))\n\n\n\n## Capsense Touch to Colors on Built-in LED\n\n```py\nimport board\nfrom touchio import TouchIn\nimport neopixel\npixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)\ntouchA = TouchIn(board.A1)\ntouchB = TouchIn(board.A2)\nprint(\"hello\")\nwhile True:\n  pixel[0] = (int(touchA.value*255), 0, int(touchB.value*255))\n```\n\u003cimg width=400 src=\"./imgs/qtpy-capsense.gif\"/\u003e\n\n## Rotary Encoder to Built-in LED\n\n```py\nimport board\nimport time\nimport neopixel\nimport rotaryio\npixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=1.0)\nencoder = rotaryio.IncrementalEncoder( board.MOSI, board.MISO ) # any two pins\nwhile True:\n    b = (encoder.position % 32) * 8\n    print(encoder.position,b)\n    pixel.fill((0,0,b))\n    time.sleep(0.1)\n```\n\u003cimg width=400 src=\"./imgs/qtpy-encoder.gif\"/\u003e\n\n\n## Fire Simulation on External Neopixel Strip\n\n\u003cimg width=400 src=\"./imgs/qtpy-fire.gif\"/\u003e\n\nUses Python array operations and list comprehensions for conciseness.\n\n```py\nimport board\nimport time\nimport neopixel\nfrom random import randint\n# External Neopixel strip, can be on any pin\nleds = neopixel.NeoPixel(board.RX,8,brightness=0.2,auto_write=False)\nwhile True:\n    # reduce brightness of all pixels by (30,30,30)\n    leds[:] = [[max(i-30,0) for i in l] for l in leds]\n    # shift LED values up by one (0-\u003e1, 1-\u003e2, etc)\n    leds[1:] = leds[0:-1] # '-1' means len-1 here\n    # pick new random fire color for LED 0\n    leds[0] = (randint(150,255),randint(50,100),0) \n    leds.show()\n    time.sleep(0.1)\n```\n\nIf you want it \"flipped\", so the fire goes from the top LED down to LED 0:\n\n```py\nimport time, board, neopixel\nfrom random import randint\nleds = neopixel.NeoPixel(board.RX,8,brightness=0.2,auto_write=False)\nwhile True:\n    leds[:] = [[max(i-30,0) for i in l] for l in leds] # reduce brightness of all pixels by (30,30,30)\n    leds[0:-1] = leds[1:]                              # shift LED values down by one\n    leds[-1] = (randint(150,255),randint(50,100),0)    # pick new random fire color for LED N\n    leds.show()\n    time.sleep(0.1)\n```\n\n\n##  Two servos with Python Class for Easing / Sequencing\n\n```py\nimport board\nimport time\nfrom pulseio import PWMOut\nfrom adafruit_motor import servo\nfrom random import random\n\nservoA = servo.Servo(PWMOut(board.RX, duty_cycle=2**15, frequency=50))\nservoB = servo.Servo(PWMOut(board.TX, duty_cycle=2**15, frequency=50))\n\nclass AngleSequence:\n    def __init__(self,angles,speed):\n        self.angles = angles\n        self.aindex = 0\n        self.angle = angles[0]\n        self.speed = speed\n    def next_angle(self):\n        self.aindex = (self.aindex + 1) % len(self.angles)\n    def update(self):\n        new_angle = self.angles[self.aindex]\n        self.angle += (new_angle - self.angle) * self.speed  # simple easing\n        return self.angle\n\nseqA = AngleSequence( [90,70,90,90,90,90,60,80,100], 0.1) # set speed to taste\nseqB = AngleSequence( [90,90,90,80,70,90,120], 0.1) # set angles for desired animation\n\nlasttime = time.monotonic()\nwhile True:\n    if time.monotonic() - lasttime \u003e (0.2 + random()*4.0  ):  # creepy random timing\n        lasttime = time.monotonic()\n        seqA.next_angle() # go to next angle in list\n        seqB.next_angle()\n    servoA.angle = seqA.update() # get updated (eased) servo angle\n    servoB.angle = seqB.update()\n    time.sleep(0.02) # wait a bit, note this affects 'speed'\n```\n\u003cimg width=400 src=\"./imgs/qtpy-servoeye.gif\"/\u003e\n\n\n## Spooky Eyes with Dual SSD1306 OLED displays\n\nDisplays are at same address, so code can be much simpler\n\n[Note: as of CircuitPython 7, this only works on QTPy RP2040, not QTPy M0 or QTPY M0 Haxpress]\n\n```py\nimport time\nimport board \nimport random\nimport adafruit_ssd1306 # requires: adafruit_bus_device, adafruit_framebuf\ni2c = board.I2C()\noled = adafruit_ssd1306.SSD1306_I2C(width=128, height=64, i2c=i2c)\ni=0; inc=30\nwhile True:\n    oled.fill(0)\n    for d in (31,30,14,12,10,8):  # various diameters\n        oled.circle( 64+i,32, d,1)\n    i = random.randint(-30,30)\n    oled.show()\n    time.sleep( 0.1 + random.random() )\n```\n\u003cimg width=475 src=\"./imgs/qtpy-oledeyes.gif\" /\u003e\n\n\n## Use Capsense as Proximity Detector to Make Spooopy Ghost\nComputing the difference between current touch raw value anda baseline minimum\nprovides a kind of proximity detector, if your antenna is big enough.\n\n```py\nimport time\nimport board\nimport digitalio\nimport touchio\nfrom pulseio import PWMOut\nfrom adafruit_motor import servo\n\ntouchA = touchio.TouchIn(board.A2)\nservoA = servo.Servo(PWMOut(board.RX, duty_cycle=2**15, frequency=50))\ntouch_min = touchA.raw_value  # baseline for proximity\nservo_pos_last = 160\n\nwhile True:\n    # get proximity value, set within servo bounds (30-160)\n    proximity_val = (touchA.raw_value - touch_min)\n    servo_pos = 160 - min(160, max(30, proximity_val))\n    servo_pos_last += (servo_pos - servo_pos_last) * 0.01  # easing/smoothing\n    servoA.angle = servo_pos_last\n```\n\n\u003cimg width=400 src=\"./imgs/qtpy-capsense-ghost.gif\" /\u003e\n\n\n## Get Size of Device's Flash Disk\nsee [`os.statvfs()` docs](https://circuitpython.readthedocs.io/en/latest/shared-bindings/os/index.html#os.statvfs)\n\n```py\nimport os\nprint(\"\\nHello World!\")\nfs_stat = os.statvfs('/') \nprint(\"Disk size in MB\", fs_stat[0] * fs_stat[2] / 1024 / 1024)\nprint(\"Free space in MB\", fs_stat[0] * fs_stat[3] / 1024 / 1024)\nwhile True: pass\n```\n\u003cimg width=400 src=\"./imgs/qtpy-flashsize-2MB.png\"/\u003e\n\n\n## Capsense Touch Sensor to USB keyboard\n```py\nimport time\nimport board \nimport neopixel\nimport touchio\nimport usb_hid\nfrom adafruit_hid.keyboard import Keyboard\nfrom adafruit_hid.keycode import Keycode\ntouchA= touchio.TouchIn(board.A0)\npixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2, auto_write=False)\npixel[0] = (0,0,0)\ntime.sleep(1)  # Sleep for a bit to avoid a race condition on some systems\nkbd = Keyboard(usb_hid.devices)\nwhile True:\n    if touchA.value:\n        print(\"A press\")\n        pixel[0] = (255,0,0)\n        kbd.send(Keycode.RIGHT_ARROW)\n    pixel.show()\n    pixel[0] = (0,0,0)\n    time.sleep(0.2)\n```\n\n***\n\n# Really Helpful CircuitPython Links\n\n- https://circuitpython.readthedocs.io/projects/bundle/en/latest/drivers.html\n- https://circuitpython.readthedocs.io/en/latest/shared-bindings/support_matrix.html\n- https://learn.adafruit.com/circuitpython-essentials/circuitpython-essentials\n- https://learn.adafruit.com/welcome-to-circuitpython/overview\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftodbot%2Fqtpy-tricks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftodbot%2Fqtpy-tricks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftodbot%2Fqtpy-tricks/lists"}