{"id":13594484,"url":"https://github.com/todbot/circuitpython-tricks","last_synced_at":"2025-05-15T11:06:23.978Z","repository":{"id":43489990,"uuid":"342734038","full_name":"todbot/circuitpython-tricks","owner":"todbot","description":"Some CircuitPython tricks, mostly reminders to myself","archived":false,"fork":false,"pushed_at":"2025-05-07T18:03:02.000Z","size":5978,"stargazers_count":689,"open_issues_count":6,"forks_count":74,"subscribers_count":43,"default_branch":"main","last_synced_at":"2025-05-08T02:18:26.071Z","etag":null,"topics":["circuitpython","displayio","itsybitsy","itsybitsym4","pico","qtpy","raspberrypipico","rp2040","tips-and-tricks"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","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":"LICENSE.TXT","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":"2021-02-27T00:20:51.000Z","updated_at":"2025-05-07T18:03:06.000Z","dependencies_parsed_at":"2023-10-11T03:34:43.087Z","dependency_job_id":"745e3b6a-cbe8-4522-a650-a5b942c3cb23","html_url":"https://github.com/todbot/circuitpython-tricks","commit_stats":{"total_commits":218,"total_committers":3,"mean_commits":72.66666666666667,"dds":"0.013761467889908285","last_synced_commit":"32ee90f394e23c15bcadcf38b4c4da5d4d995f23"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/todbot%2Fcircuitpython-tricks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/todbot%2Fcircuitpython-tricks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/todbot%2Fcircuitpython-tricks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/todbot%2Fcircuitpython-tricks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/todbot","download_url":"https://codeload.github.com/todbot/circuitpython-tricks/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254328385,"owners_count":22052632,"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","displayio","itsybitsy","itsybitsym4","pico","qtpy","raspberrypipico","rp2040","tips-and-tricks"],"created_at":"2024-08-01T16:01:34.348Z","updated_at":"2025-05-15T11:06:23.957Z","avatar_url":"https://github.com/todbot.png","language":"Python","readme":"\n# circuitpython-tricks\n\nA small list of tips \u0026 tricks I find myself needing when working with CircuitPython.\nI find these examples useful when picking up a new project and I just want some boilerplate to get started.\nAlso see the [circuitpython-tricks/larger-tricks](larger-tricks) directory for additional ideas.\n\nAn older version of this page is a [Learn Guide on Adafruit](https://learn.adafruit.com/todbot-circuitpython-tricks?view=all) too!\n\nIf you're new to CircuitPython overall, there's no single reference, but:\n- [The Python Tutorial](https://docs.python.org/3/tutorial/) on Python.org,\n   since \"CircuitPython is Python\" mostly. (approx. Python 3.4)\n- [CircuitPython API reference](https://docs.circuitpython.org/en/latest/docs/), particularly the [\"Core Modules \u003e Modules\" section](https://docs.circuitpython.org/en/latest/shared-bindings/index.html#modules) in the left sidebar\n    - for compiled-in libraries like `displayio`, `usb`, `audioio`, `ulab.numpy`\n- [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) \u0026 [helpers](https://github.com/adafruit/Adafruit_CircuitPython_Bundle/tree/main/libraries/helpers) libraries like `board`, `neopixel` \u0026 `ble`\n- and [CircuitPython Essentials Learn Guide](https://learn.adafruit.com/circuitpython-essentials) of course\n\n\nTable of Contents\n=================\n\nBut it's probably easiest to do a Cmd-F/Ctrl-F find on keyword of idea you want.\n\n* [Inputs](#inputs)\n   * [Read a digital input as a Button](#read-a-digital-input-as-a-button)\n   * [Read a Potentiometer](#read-a-potentiometer)\n   * [Read a Touch Pin / Capsense](#read-a-touch-pin--capsense)\n   * [Read a Rotary Encoder](#read-a-rotary-encoder)\n   * [Debounce a pin / button](#debounce-a-pin--button)\n   * [Detect button double-click](#detect-button-double-click)\n   * [Set up and debounce a list of pins](#set-up-and-debounce-a-list-of-pins)\n* [Outputs](#outputs)\n   * [Output HIGH / LOW on a pin (like an LED)](#output-high--low-on-a-pin-like-an-led)\n   * [Output Analog value on a DAC pin](#output-analog-value-on-a-dac-pin)\n   * [Output a \"Analog\" value on a PWM pin](#output-a-analog-value-on-a-pwm-pin)\n   * [Control Neopixel / WS2812 LEDs](#control-neopixel--ws2812-leds)\n   * [Control a servo, with animation list](#control-a-servo-with-animation-list)\n* [Neopixels / Dotstars](#neopixels--dotstars)\n   * [Light each LED in order](#light-each-led-in-order)\n   * [Moving rainbow on built-in board.NEOPIXEL](#moving-rainbow-on-built-in-boardneopixel)\n   * [Make moving rainbow gradient across LED strip](#make-moving-rainbow-gradient-across-led-strip)\n   * [Fade all LEDs by amount for chase effects](#fade-all-leds-by-amount-for-chase-effects)\n* [Audio](#audio)\n   * [Making simple tones](#making-simple-tones)\n   * [Play a WAV file](#play-a-wav-file)\n   * [Audio out using PWM](#audio-out-using-pwm)\n   * [Audio out using DAC](#audio-out-using-dac)\n   * [Audio out using I2S](#audio-out-using-i2s)\n   * [Use audiomixer to prevent audio crackles](#use-audiomixer-to-prevent-audio-crackles)\n   * [Play multiple sounds with audiomixer](#play-multiple-sounds-with-audiomixer)\n   * [Playing MP3 files](#playing-mp3-files)\n* [USB](#usb)\n   * [Rename CIRCUITPY drive to something new](#rename-circuitpy-drive-to-something-new)\n   * [Detect if USB is connected or not](#detect-if-usb-is-connected-or-not)\n   * [Get CIRCUITPY disk size and free space](#get-circuitpy-disk-size-and-free-space)\n   * [Programmatically reset to UF2 bootloader](#programmatically-reset-to-uf2-bootloader)\n* [USB Serial](#usb-serial)\n   * [Print to USB Serial](#print-to-usb-serial)\n   * [Read user input from USB Serial, blocking](#read-user-input-from-usb-serial-blocking)\n   * [Read user input from USB Serial, non-blocking (mostly)](#read-user-input-from-usb-serial-non-blocking-mostly)\n   * [Read keys from USB Serial](#read-keys-from-usb-serial)\n   * [Read user input from USB serial, non-blocking](#read-user-input-from-usb-serial-non-blocking)\n* [USB MIDI](#usb-midi)\n   * [Sending MIDI with adafruit_midi](#sending-midi-with-adafruit_midi)\n   * [Sending MIDI with bytearray](#sending-midi-with-bytearray)\n   * [MIDI over Serial UART](#midi-over-serial-uart)\n   * [Receiving MIDI](#receiving-midi)\n   * [Receiving MIDI USB and MIDI Serial UART together](#receiving-midi-usb-and-midi-serial-uart-together)\n   * [Enable USB MIDI in boot.py (for ESP32-S2 and STM32F4)](#enable-usb-midi-in-bootpy-for-esp32-s2-and-stm32f4)\n* [WiFi / Networking](#wifi--networking)\n   * [Scan for WiFi Networks, sorted by signal strength](#scan-for-wifi-networks-sorted-by-signal-strength)\n   * [Join WiFi network with highest signal strength](#join-wifi-network-with-highest-signal-strength)\n   * [Ping an IP address](#ping-an-ip-address)\n   * [Get IP address of remote host](#get-ip-address-of-remote-host)\n   * [Fetch a JSON file](#fetch-a-json-file)\n   * [Serve a webpage via HTTP](#serve-a-webpage-via-http)\n   * [Set RTC time from NTP](#set-rtc-time-from-ntp)\n   * [Set RTC time from time service](#set-rtc-time-from-time-service)\n   * [What the heck is settings.toml?](#what-the-heck-is-settingstoml)\n   * [What the heck is secrets.py?](#what-the-heck-is-secretspy)\n* [Displays (LCD / OLED / E-Ink) and displayio](#displays-lcd--oled--e-ink-and-displayio)\n   * [Get default display and change display rotation](#get-default-display-and-change-display-rotation)\n   * [Display an image](#display-an-image)\n   * [Display background bitmap](#display-background-bitmap)\n   * [Image slideshow](#image-slideshow)\n   * [Dealing with E-Ink \"Refresh Too Soon\" error](#dealing-with-e-ink-refresh-too-soon-error)\n   * [Turn off REPL on built-in display](#turn-off-repl-on-built-in-display)\n* [I2C](#i2c)\n   * [Scan I2C bus for devices](#scan-i2c-bus-for-devices)\n   * [Speed up I2C bus](#speed-up-i2c-bus)\n* [Timing](#timing)\n   * [Measure how long something takes](#measure-how-long-something-takes)\n   * [More accurate timing with ticks_ms(), like Arduino millis()](#more-accurate-timing-with-ticks_ms-like-arduino-millis)\n   * [Control garbage collection for reliable timing](#control-garbage-collection-for-reliable-timing)\n   * [Converting milliseconds to seconds: 0.004 * 1000 != 4, sometimes](#converting-milliseconds-to-seconds-0004--1000--4-sometimes)\n* [Board Info](#board-info)\n   * [Get CPU speed (and set it!)](#get-cpu-speed-and-set-it)\n   * [Display amount of free RAM](#display-amount-of-free-ram)\n   * [Show microcontroller.pin to board mappings](#show-microcontrollerpin-to-board-mappings)\n   * [Determine which board you're on](#determine-which-board-youre-on)\n   * [Support multiple boards with one code.py](#support-multiple-boards-with-one-codepy)\n* [Computery Tasks](#computery-tasks)\n   * [Formatting strings](#formatting-strings)\n   * [Formatting strings with f-strings](#formatting-strings-with-f-strings)\n   * [Using regular expressions to \"findall\" strings](#using-regular-expressions-to-findall-strings)\n   * [Make and use a config file](#make-and-use-a-config-file)\n   * [Run different code.py on startup](#run-different-codepy-on-startup)\n* [Coding Techniques](#coding-techniques)\n   * [Map an input range to an output range](#map-an-input-range-to-an-output-range)\n   * [Constrain an input to a min/max](#constrain-an-input-to-a-minmax)\n   * [Turn a momentary value into a toggle](#turn-a-momentary-value-into-a-toggle)\n   * [Do something every N seconds without sleep()](#do-something-every-n-seconds-without-sleep)\n* [System error handling](#system-error-handling)\n   * [Preventing Ctrl-C from stopping the program](#preventing-ctrl-c-from-stopping-the-program)\n   * [Prevent auto-reload when CIRCUITPY is touched](#prevent-auto-reload-when-circuitpy-is-touched)\n   * [Raspberry Pi Pico boot.py Protection](#raspberry-pi-pico-bootpy-protection)\n* [Using the REPL](#using-the-repl)\n   * [Display built-in modules / libraries](#display-built-in-modules--libraries)\n   * [Turn off built-in display to speed up REPL printing](#turn-off-built-in-display-to-speed-up-repl-printing)\n   * [Useful REPL one-liners](#useful-repl-one-liners)\n* [Python tricks](#python-tricks)\n   * [Create list with elements all the same value](#create-list-with-elements-all-the-same-value)\n   * [Convert RGB tuples to int and back again](#convert-rgb-tuples-to-int-and-back-again)\n   * [Storing multiple values per list entry](#storing-multiple-values-per-list-entry)\n* [Python info](#python-info)\n   * [Display which (not built-in) libraries have been imported](#display-which-not-built-in-libraries-have-been-imported)\n   * [List names of all global variables](#list-names-of-all-global-variables)\n   * [Display the running CircuitPython release](#display-the-running-circuitpython-release)\n* [Host-side tasks](#host-side-tasks)\n   * [Installing CircuitPython libraries](#installing-circuitpython-libraries)\n      * [Installing libraries with circup](#installing-libraries-with-circup)\n      * [Copying libraries by hand with cp](#copying-libraries-by-hand-with-cp)\n   * [Preparing images for CircuitPython](#preparing-images-for-circuitpython)\n      * [Online](#online)\n      * [Command-line: using ImageMagick](#command-line-using-imagemagick)\n      * [Command-line: using GraphicsMagick](#command-line-using-graphicsmagick)\n      * [Making images smaller or for E-Ink displays](#making-images-smaller-or-for-e-ink-displays)\n      * [NodeJs: using gm](#nodejs-using-gm)\n      * [Python: using PIL / pillow](#python-using-pil--pillow)\n   * [Preparing audio files for CircuitPython](#preparing-audio-files-for-circuitpython)\n      * [WAV files](#wav-files)\n      * [MP3 files](#mp3-files)\n      * [Getting sox](#getting-sox)\n   * [Circup hacks](#circup-hacks)\n      * [Finding where circup stores its files](#finding-where-circup-stores-its-files)\n   * [Building CircuitPython](#building-circuitpython)\n* [About this guide](#about-this-guide)\n\n\n## Inputs\n\n### Read a digital input as a Button\n\n```py\nimport board\nfrom digitalio import DigitalInOut, Pull\nbutton = DigitalInOut(board.D3) # defaults to input\nbutton.pull = Pull.UP # turn on internal pull-up resistor\nprint(button.value)  # False == pressed\n```\n\nCan also do:\n\n```py\nimport time, board, digitalio\nbutton = digitalio.DigitalInOut(board.D3)\nbutton.switch_to_input(digitalio.Pull.UP)\nwhile True:\n    print(\"button pressed:\", button.value == False) # False == pressed\n    time.sleep(0.1)\n```\n\nBut you probably want to use `keypad` to get debouncing and press/release events.\nYou can use it for a single button!\n\n```py\nimport board, keypad\nkeys = keypad.Keys((board.D3,), value_when_pressed=False, pull=True)\nwhile True:\n  if key := keys.events.get():\n    if key.pressed: \n      print(\"pressed key!\")\n\n```\nNote: be sure to add the comma when using a single button (e.g. `(board.D3,)`)\n\n\n### Read a Potentiometer\n\n```py\nimport board\nimport analogio\npotknob = analogio.AnalogIn(board.A1)\nposition = potknob.value  # ranges from 0-65535\npos = potknob.value // 256  # make 0-255 range\n```\n\nNote: While `AnalogIn.value` is 16-bit (0-65535) corresponding to 0 V to 3.3V,\nthe MCU ADCs can have limitations in resolution and voltage range.\nThis reduces what CircuitPython sees.\nFor example, the ESP32 ADCs are 12-bit w/ approx 0.1 V to 2.5 V range\n(e.g. `value` goes from around 200 to 50,000, in steps of 16)\n\n### Read a Touch Pin / Capsense\n\n```py\nimport touchio\nimport board\ntouch_pin = touchio.TouchIn(board.GP6)\n# on Pico / RP2040, need 1M pull-down on each input\nif touch_pin.value:\n    print(\"touched!\")\n```\n\nYou can also get an \"analog\" touch value with `touch_pin.raw_value` to do\nbasic proximity detection or even [theremin-like behavior](https://gist.github.com/todbot/bb4ec9c509f8c301e4787e5cb26ec870).\n\n### Read a Rotary Encoder\n\n```py\nimport board\nimport rotaryio\nencoder = rotaryio.IncrementalEncoder(board.GP0, board.GP1) # must be consecutive on Pico\nprint(encoder.position)  # starts at zero, goes neg or pos\n```\n\n### Debounce a pin / button\n\nBut you probably want to use `keypad` to get debouncing and press/release events.\nYou can use it for a single button!\n\n```py\nimport board, keypad\nkeys = keypad.Keys((board.D3,), value_when_pressed=False, pull=True)\nwhile True:\n  if key := keys.events.get():\n    if key.pressed: \n      print(\"pressed key!\", key.key_number)\n    if key.released:\n      print(\"released key!\", key.key_number)\n\n```\nNote: be sure to add the comma when using a single button (e.g. `(board.D3,)`)\n\nIf your board doesn't have `keypad`, you can use `adafruit_debouncer` from \nthe bundle. \n\n```py\nimport board\nfrom digitalio import DigitalInOut, Pull\nfrom adafruit_debouncer import Debouncer\nbutton_in = DigitalInOut(board.D3) # defaults to input\nbutton_in.pull = Pull.UP # turn on internal pull-up resistor\nbutton = Debouncer(button_in)\nwhile True:\n    button.update()\n    if button.fell:\n        print(\"press!\")\n    if button.rose:\n      print(\"release!\")\n```\n\nNote: Most boards have the native `keypad` module that can do keypad debouncing in a much more\nefficient way.  See [Set up and debounce a list of pins](#set-up-and-debounce-a-list-of-pins)\n\n\n### Detect button double-click\n\n```py\nimport board\nfrom digitalio import DigitalInOut, Pull\nfrom adafruit_debouncer import Button\nbutton_in = DigitalInOut(board.D3) # defaults to input\nbutton_in.switch_to_input(Pull.UP) # turn on internal pull-up resistor\nbutton = Button(button_in)\nwhile True:\n    button.update()\n    if button.pressed:\n        print(\"press!\")\n    if button.released:\n      print(\"release!\")\n    if button.short_count \u003e 1:  # detect multi-click\n      print(\"multi-click: click count:\", button.short_count)\n```\n\n### Set up and debounce a list of pins\n\nIf your board's CircuitPython has the `keypad` library (most do),\nthen I recommend using it. It's not just for key matrixes! And it's more efficient\nand, since it's built-in, reduces a library dependency.\n\n```py\nimport board\nimport keypad\nbutton_pins = (board.GP0, board.GP1, board.GP2, board.GP3, board.GP4)\nbuttons = keypad.Keys(button_pins, value_when_pressed=False, pull=True)\n\nwhile True:\n    button = buttons.events.get()  # see if there are any key events\n    if button:                      # there are events!\n      if button.pressed:\n        print(\"button\", button.key_number, \"pressed!\")\n      if button.released:\n        print(\"button\", button.key_number, \"released!\")\n```\n\nOtherwise, you can use `adafruit_debouncer`:\n\n```py\nimport board\nfrom digitalio import DigitalInOut, Pull\nfrom adafruit_debouncer import Debouncer\nbutton_pins = (board.GP0, board.GP1, board.GP2, board.GP3, board.GP4)\nbuttons = []   # will hold list of Debouncer objects\nfor pin in button_pins:   # set up each pin\n    tmp_pin = DigitalInOut(pin) # defaults to input\n    tmp_pin.pull = Pull.UP      # turn on internal pull-up resistor\n    buttons.append( Debouncer(tmp_pin) )\nwhile True:\n    for i in range(len(buttons)):\n        buttons[i].update()\n        if buttons[i].fell:\n            print(\"button\",i,\"pressed!\")\n        if buttons[i].rose:\n            print(\"button\",i,\"released!\")\n```\n\nAnd you can use `adafruit_debouncer` on touch pins too:\n\n```py\nimport board, touchio, adafruit_debouncer\ntouchpad = adafruit_debouncer.Debouncer(touchio.TouchIn(board.GP1))\nwhile True:\n    touchpad.update()\n    if touchpad.rose:  print(\"touched!\")\n    if touchpad.fell:  print(\"released!\")\n```\n\n\n## Outputs\n\n### Output HIGH / LOW on a pin (like an LED)\n\n```py\nimport board\nimport digitalio\nledpin = digitalio.DigitalInOut(board.D2)\nledpin.direction = digitalio.Direction.OUTPUT\nledpin.value = True\n```\n\nCan also do:\n```py\nledpin = digitalio.DigitalInOut(board.D2)\nledpin.switch_to_output(value=True)\n```\n\n### Output Analog value on a DAC pin\n\nDifferent boards have DAC on different pins\n\n```py\nimport board\nimport analogio\ndac = analogio.AnalogOut(board.A0)  # on Trinket M0 \u0026 QT Py\ndac.value = 32768   # mid-point of 0-65535\n```\n\n### Output a \"Analog\" value on a PWM pin\n\n```py\nimport board\nimport pwmio\nout1 = pwmio.PWMOut(board.MOSI, frequency=25000, duty_cycle=0)\nout1.duty_cycle = 32768  # mid-point 0-65535 = 50 % duty-cycle\n```\n\n### Control Neopixel / WS2812 LEDs\n\n```py\nimport neopixel\nleds = neopixel.NeoPixel(board.NEOPIXEL, 16, brightness=0.2)\nleds[0] = 0xff00ff  # first LED of 16 defined\nleds[0] = (255,0,255)  # equivalent\nleds.fill( 0x00ff00 )  # set all to green\n```\n\n### Control a servo, with animation list\n\n```py\n# servo_animation_code.py -- show simple servo animation list\nimport time, random, board\nfrom pwmio import PWMOut\nfrom adafruit_motor import servo\n\n# your servo will likely have different min_pulse \u0026 max_pulse settings\nservoA = servo.Servo(PWMOut(board.RX, frequency=50), min_pulse=500, max_pulse=2250)\n\n# the animation to play\nanimation = (\n    # (angle, time to stay at that angle)\n    (0, 2.0),\n    (90, 2.0),\n    (120, 2.0),\n    (180, 2.0)\n)\nani_pos = 0 # where in list to start our animation\n\nwhile True:\n    angle, secs = animation[ ani_pos ]\n    print(\"servo moving to\", angle, secs)\n    servoA.angle = angle\n    time.sleep( secs )\n    ani_pos = (ani_pos + 1) % len(animation) # go to next, loop if at end\n```\n\n\n## Neopixels / Dotstars\n\n### Light each LED in order\n\nYou can access each LED with Python array methods on the `leds` object.\nAnd you can set the LED color with either an RGB tuple (`(255,0,80)`) or an\nRGB hex color as a 24-bit number (`0xff0050`)\n\n```py\nimport time, board, neopixel\n\nled_pin = board.GP5   # which pin the LED strip is on\nnum_leds = 10\ncolors = ( (255,0,0), (0,255,0), (0,0,255), 0xffffff, 0x000000 )\n\nleds = neopixel.NeoPixel(led_pin, num_leds, brightness=0.1)\n\ni = 0\nwhile True:\n    print(\"led:\",i)\n    for c in colors:\n        leds[i] = c\n        time.sleep(0.2)\n    i = (i+1) % num_leds\n```\n\n### Moving rainbow on built-in `board.NEOPIXEL`\n\nIn CircuitPython 7, the `rainbowio` module has a `colorwheel()` function.\nUnfortunately, the `rainbowio` module is not available in all builds.\nIn CircuitPython 6, `colorwheel()` is a built-in function part of `_pixelbuf` or `adafruit_pypixelbuf`.\n\nThe `colorwheel()` function takes a single value 0-255 hue and returns an `(R,G,B)` tuple\ngiven a single 0-255 hue.  It's not a full HSV_to_RGB() function but often all you need\nis \"hue to RGB\", wher you assume saturation=255 and value=255.\nIt can be used with `neopixel`, `adafruit_dotstar`, or any place you need a (R,G,B) 3-byte tuple.\nHere's one way to use it.\n\n```py\n# CircuitPython 7 with or without rainbowio module\nimport time, board, neopixel\ntry:\n    from rainbowio import colorwheel\nexcept:\n    def colorwheel(pos):\n        if pos \u003c 0 or pos \u003e 255:  return (0, 0, 0)\n        if pos \u003c 85: return (255 - pos * 3, pos * 3, 0)\n        if pos \u003c 170: pos -= 85; return (0, 255 - pos * 3, pos * 3)\n        pos -= 170; return (pos * 3, 0, 255 - pos * 3)\n\nled = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.4)\nwhile True:\n    led.fill( colorwheel((time.monotonic()*50)%255) )\n    time.sleep(0.05)\n```\n\n\n### Make moving rainbow gradient across LED strip\n\nSee [demo of it in this tweet](https://twitter.com/todbot/status/1397992493833097218).\n\n```py\nimport time, board, neopixel, rainbowio\nnum_leds = 16\nleds = neopixel.NeoPixel(board.D2, num_leds, brightness=0.4, auto_write=False )\ndelta_hue = 256//num_leds\nspeed = 10  # higher numbers = faster rainbow spinning\ni=0\nwhile True:\n  for l in range(len(leds)):\n    leds[l] = rainbowio.colorwheel( int(i*speed + l * delta_hue) % 255  )\n  leds.show()  # only write to LEDs after updating them all\n  i = (i+1) % 255\n  time.sleep(0.05)\n```\n\nA shorter version using a Python list comprehension. The `leds[:]` trick is a way to assign\na new list of colors to all the LEDs at once.\n\n```py\nimport supervisor, board, neopixel, rainbowio\nnum_leds = 16\nspeed = 10  # lower is faster, higher is slower\nleds = neopixel.NeoPixel(board.D2, 16, brightness=0.4)\nwhile True:\n  t = supervisor.ticks_ms() / speed\n  leds[:] = [rainbowio.colorwheel( t + i*(255/len(leds)) ) for i in range(len(leds))]\n\n```\n\n### Fade all LEDs by amount for chase effects\n```py\nimport time\nimport board, neopixel\nnum_leds = 16\nleds = neopixel.NeoPixel(board.D2, num_leds, brightness=0.4, auto_write=False )\nmy_color = (55,200,230)\ndim_by = 20  # dim amount, higher = shorter tails\npos = 0\nwhile True:\n  leds[pos] = my_color\n  leds[:] = [[max(i-dim_by,0) for i in l] for l in leds] # dim all by (dim_by,dim_by,dim_by)\n  pos = (pos+1) % num_leds  # move to next position\n  leds.show()  # only write to LEDs after updating them all\n  time.sleep(0.05)\n```\n\n## Audio\n\nIf you're used to Arduino, making sound was mostly constrained to simple beeps\nusing the Arduino `tone()` function. You can do that in CircuitPython too with\n`pwmio` and `simpleio`, but CircuitPython can also play WAV and MP3\nfiles and become a [fully-fledged audio synthesizer with `synthio`](https://github.com/todbot/circuitpython-synthio-tricks).\n\nIn CircuitPython, there are multiple core module libraries available to output audio:\n\n- `pwmio`  -- use almost any GPIO pin to output simple beeps, no WAV/MP3/synthio\n- `audioio` -- uses built-in DAC to output WAV, MP3, synthio\n- `audiopwmio` -- like above, but uses PWM like arduino `analogWrite()`, requires RC filter to convert to analog\n- `audiobusio` -- outputs high-quality I2S audio data stream, requires external I2S decoder hardware\n\nDifferent devices will have different audio modules available. Generally, the\npattern is:\n\n- SAMD51 (e.g. \"M4\" boards) -- `audioio` (DAC) and `audiobusio` (I2S)\n- RP2040 (e.g. Pico) -- `audiopwmio` (PWM) and `audiobusio` (I2S)\n- ESP32 (e.g. QTPy ESP32) -- `audiobusio` (I2S) only\n\nTo play WAV and MP3 files, they usually must be resaved in a format parsable by CircuitPython,\nsee [Preparing Audio Files for CircuitPython](#preparing-audio-files-for-circuitpython)\n\n### Making simple tones\n\nFor devices that only have `pwmio` capability, you can make simple tones.\nThe [`simpleio`](https://docs.circuitpython.org/projects/simpleio/en/latest/examples.html#id1) library can be used for this:\n\n```py\n# a short piezo song using tone()\nimport time, board, simpleio\nwhile True:\n    for f in (262, 294, 330, 349, 392, 440, 494, 523):\n        simpleio.tone(board.A0, f, 0.25)\n    time.sleep(1)\n```\n\n### Play a WAV file\n\nWAV files are easiest for CircuitPython to play.\nThe shortest code to play a WAV file on Pico RP2040 is:\n\n```py\nimport time, board, audiocore, audiopwmio\naudio = audiopwmio.PWMAudioOut(board.GP0)\nwave = audiocore.WaveFile(\"laser2.wav\")\naudio.play(wave)\nwhile True:\n  pass   # wait for audio to finish playing\n```\nDetails and other ways below.\n\n### Audio out using PWM\n\nThis uses the `audiopwmio` library, only available for RP2040 boards like Raspberry Pi Pico and NRF52840-based boards like Adafruit Feather nRF52840 Express.\nOn RP2040-based boards, any pin can be PWM Audio pin.\nSee the [audiopwomio Support Matrix](https://circuitpython.readthedocs.io/en/latest/shared-bindings/support_matrix.html?filter=audiopwmio) for which boards support `audiopwmio`.\n\n```py\nimport time, board\nfrom audiocore import WaveFile\nfrom audiopwmio import PWMAudioOut as AudioOut\nwave = WaveFile(\"laser2.wav\")  # can also be filehandle from open()\naudio = AudioOut(board.GP0) # must be PWM-capable pin\nwhile True:\n    print(\"audio is playing:\",audio.playing)\n    if not audio.playing:\n      audio.play(wave)\n      wave.sample_rate = int(wave.sample_rate * 0.90) # play 10% slower each time\n    time.sleep(0.1)\n```\n\nNotes:\n\n- There will be a small *pop* when audio starts playing as the PWM driver\ntakes the GPIO line from not being driven to being PWM'ed.\nThere's currently no way around this. If playing multiple WAVs, consider using\n[`AudioMixer`](#use-audiomixer-to-prevent-audio-crackles) to keep the audio system\nrunning between WAVs. This way, you'll only have the startup pop.\n\n- If you want *stereo* output on boards that support it\nthen you can pass in two pins, like:\n`audio = audiopwmio.PWMAudioOut(left_channel=board.GP14, right_channel=board.GP15)`\n\n- PWM output must be filtered and converted to line-level to be usable.\nUse 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).\n\n- The `WaveFile()` object can take either a filestream\n(the output of `open('filewav','rb')`) or can take a string filename (`wav=WaveFile(\"laser2.wav\")`).\n\n\n### Audio out using DAC\n\nSome CircuitPython boards (SAMD51 \"M4\" \u0026 SAMD21 \"M0\") have built-in DACs that are supported.\nThe code is the same as above, with just the import line changing.\nSee the [audioio Support Matrix](https://circuitpython.readthedocs.io/en/latest/shared-bindings/support_matrix.html?filter=audioio) for which boards support `audioio`.\n\n```py\nimport time, board\nimport audiocore, audioio # DAC\nwave_file = open(\"laser2.wav\", \"rb\")\nwave = audiocore.WaveFile(wave_file)\naudio = audioio.AudioOut(board.A0)  # must be DAC-capable pin, A0 on QTPy Haxpress\nwhile True:\n  print(\"audio is playing:\",audio.playing)\n  if not audio.playing:\n    audio.play(wave)\n    wave.sample_rate = int(wave.sample_rate * 0.90) # play 10% slower each time\n  time.sleep(0.1)\n```\n\nNote: if you want *stereo* output on boards that support it (SAMD51 \"M4\" mostly),\nthen you can pass in two pins, like:\n`audio = audioio.AudioOut(left_channel=board.A0, right_channel=board.A1)`\n\n\n### Audio out using I2S\n\nUnlike PWM or DAC, most CircuitPython boards support driving an external I2S audio board.\nThis will also give you higher-quality sound output than DAC or PWM.\nSee the [audiobusio Support Matrix](https://circuitpython.readthedocs.io/en/latest/shared-bindings/support_matrix.html?filter=audiobusio) for which boards support `audiobusio`.\n\n```py\n# for e.g. Pico RP2040 pins bit_clock \u0026 word_select pins must be adjacent\nimport board, audiobusio, audiocore\naudio = audiobusio.I2SOut(bit_clock=board.GP0, word_select=board.GP1, data=board.GP2)\naudio.play( audiocore.WaveFile(\"laser2.wav\") )\n```\n\n### Use audiomixer to prevent audio crackles\n\nThe default buffer used by the audio system is quite small.\nThis means you'll hear corrupted audio if CircuitPython is doing anything else\n(having CIRCUITPY written to, updating a display). To get around this, you can\nuse `audiomixer` to make the audio buffer larger. Try `buffer_size=2048` to start.\nA larger buffer means a longer lag between when a sound is triggered when its heard.\n\nAudioMixer is also great if you want to play multiple WAV files at the same time.\n\n```py\nimport time, board\nfrom audiocore import WaveFile\nfrom audioio import AudioOut\nimport audiomixer\nwave = WaveFile(\"laser2.wav\", \"rb\")\naudio = AudioOut(board.A0) # assuming QTPy M0 or Itsy M4\nmixer = audiomixer.Mixer(voice_count=1, sample_rate=22050, channel_count=1,\n                         bits_per_sample=16, samples_signed=True, buffer_size=2048)\naudio.play(mixer)  # never touch \"audio\" after this, use \"mixer\"\nwhile True:\n    print(\"mixer voice is playing:\", mixer.voice[0].playing)\n    if not mixer.voice[0].playing:\n      time.sleep(1)\n      print(\"playing again\")\n      mixer.voice[0].play(wave)\n    time.sleep(0.1)\n```\n\n### Play multiple sounds with audiomixer\n\nThis example assumes WAVs that are mono 22050 Hz sample rate, w/ signed 16-bit samples.\n\n```py\nimport time, board, audiocore, audiomixer\nfrom audiopwmio import PWMAudioOut as AudioOut\n\nwav_files = (\"loop1.wav\", \"loop2.wav\", \"loop3.wav\")\nwavs = [None] * len(wav_files)  # holds the loaded WAVs\n\naudio = AudioOut(board.GP2)  # RP2040 example\nmixer = audiomixer.Mixer(voice_count=len(wav_files), sample_rate=22050, channel_count=1,\n                         bits_per_sample=16, samples_signed=True, buffer_size=2048)\naudio.play(mixer)  # attach mixer to audio playback\n\nfor i in range(len(wav_files)):\n    print(\"i:\",i)\n    wavs[i] = audiocore.WaveFile(open(wav_files[i], \"rb\"))\n    mixer.voice[i].play( wavs[i], loop=True) # start each one playing\n\nwhile True:\n    print(\"doing something else while all loops play\")\n    time.sleep(1)\n```\n\nNote: M0 boards do not have `audiomixer`\n\nNote: Number of simultaneous sounds is limited sample rate and flash read speed.\nRules of thumb:\n- Built-in flash: 10 22kHz sounds simultanously\n- SPI SD cards: 2 22kHz sounds simultaneously\n\n\nAlso see the many examples in [larger-tricks](./larger-tricks/).\n\n### Playing MP3 files\n\nOnce you have set up audio output (either directly or via AudioMixer), you can play WAVs or\nMP3s through it, or play both simultaneously.\n\nFor instance, here's an example that uses an I2SOut to a PCM5102 on a Raspberry Pi Pico RP2040\nto simultaneously play both a WAV and an MP3:\n\n```py\nimport board, audiobusio, audiocore, audiomp3\nnum_voices = 2\n\ni2s_bclk, i2s_wsel, i2s_data = board.GP9, board.GP10, board.GP11 # BCLK, LCLK, DIN on PCM5102\n\naudio = audiobusio.I2SOut(bit_clock=i2s_bclk, word_select=i2s_wsel, data=i2s_data)\nmixer = audiomixer.Mixer(voice_count=num_voices, sample_rate=22050, channel_count=1,\n                         bits_per_sample=16, samples_signed=True)\naudio.play(mixer) # attach mixer to audio playback\n\nwav_file = \"/amen1_22k_s16.wav\" # in 'circuitpython-tricks/larger-tricks/breakbeat_wavs'\nmp3_file = \"/vocalchops476663_22k_128k.mp3\" # in 'circuitpython-tricks/larger-tricks/wav'\n# https://freesound.org/people/f-r-a-g-i-l-e/sounds/476663/\n\nwave = audiocore.WaveFile(open(wav_file, \"rb\"))\nmp3 = audiomp3.MP3Decoder(open(mp3_file, \"rb\"))\nmixer.voice[0].play( wave )\nmixer.voice[1].play( mp3 )\n\nwhile True:\n    pass   # both audio files play\n\n```\n\n__Note:__ For MP3 files, be aware that since this is doing software MP3 decoding,\nyou will likely need to re-encode the MP3s to lower bitrate and sample rate\n(max 128 kbps and 22,050 Hz) to be playable the lower-end CircuitPython devices\nlike the Pico / RP2040.\n\n__Note:__ For MP3 files and setting `loop=True` when playing, there is a small delay\nwhen looping.  WAV files loop seemlessly.\n\n\n\nAn example of boards with `pwmio` but no audio are ESP32-S2-based boards like\n[FunHouse](https://www.adafruit.com/product/4985),\nwhere you cannot play WAV files, but you can make beeps.\nA larger example is this gist: https://gist.github.com/todbot/f35bb5ceed013a277688b2ca333244d5\n\n\n## USB\n\n\n### Rename CIRCUITPY drive to something new\n\nFor instance, if you have multiple of the same device.\nThe `label` can be up to 11 characters.\nThis goes in `boot.py` not `code.py` and you must powercycle board.\n\n```py\n# this goes in boot.py not code.py!\nnew_name = \"TRINKEYPY0\"\nimport storage\nstorage.remount(\"/\", readonly=False)\nm = storage.getmount(\"/\")\nm.label = new_name\nstorage.remount(\"/\", readonly=True)\n```\n\n### Detect if USB is connected or not\n\n```py\nimport supervisor\nif supervisor.runtime.usb_connected:\n  led.value = True   # USB\nelse:\n  led.value = False  # no USB\n```\n\nAn older way that tries to mount CIRCUITPY read-write and if it fails, USB connected:\n\n```py\ndef is_usb_connected():\n    import storage\n    try:\n        storage.remount('/', readonly=False)  # attempt to mount readwrite\n        storage.remount('/', readonly=True)  # attempt to mount readonly\n    except RuntimeError as e:\n        return True\n    return False\nis_usb = \"USB\" if is_usb_connected() else \"NO USB\"\nprint(\"USB:\", is_usb)\n```\n\n### Get CIRCUITPY disk size and free space\n```py\nimport os\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)\n```\n\n### Programmatically reset to UF2 bootloader\n```py\nimport microcontroller\nmicrocontroller.on_next_reset(microcontroller.RunMode.UF2)\nmicrocontroller.reset()\n```\n\nNote: in older CircuitPython use `RunMode.BOOTLOADER` and for boards with multiple\nbootloaders (like ESP32-S2):\n\n```py\nimport microcontroller\nmicrocontroller.on_next_reset(microcontroller.RunMode.BOOTLOADER)\nmicrocontroller.reset()\n```\n\n\n## USB Serial\n\n### Print to USB Serial\n\n```py\nprint(\"hello there\")  # prints a newline\nprint(\"waiting...\", end='')   # does not print newline\nfor i in range(256):  print(i, end=', ')   # comma-separated numbers\n```\n\n\n\n### Read user input from USB Serial, blocking\n```py\nwhile True:\n    print(\"Type something: \", end='')\n    my_str = input()  # type and press ENTER or RETURN\n    print(\"You entered: \", my_str)\n```\n\n### Read user input from USB Serial, non-blocking (mostly)\n```py\nimport time\nimport supervisor\nprint(\"Type something when you're ready\")\nlast_time = time.monotonic()\nwhile True:\n    if supervisor.runtime.serial_bytes_available:\n        my_str = input()\n        print(\"You entered:\", my_str)\n    if time.monotonic() - last_time \u003e 1:  # every second, print\n        last_time = time.monotonic()\n        print(int(last_time),\"waiting...\")\n```\n\n### Read keys from USB Serial\n```py\nimport time, sys, supervisor\nprint(\"type charactcers\")\nwhile True:\n    n = supervisor.runtime.serial_bytes_available\n    if n \u003e 0:  # we read something!\n        s = sys.stdin.read(n)  # actually read it in\n        # print both text \u0026 hex version of recv'd chars (see control chars!)\n        print(\"got:\", \" \".join(\"{:s} {:02x}\".format(c,ord(c)) for c in s))\n    time.sleep(0.01) # do something else\n```\n\n### Read user input from USB serial, non-blocking\n```py\nclass USBSerialReader:\n    \"\"\" Read a line from USB Serial (up to end_char), non-blocking, with optional echo \"\"\"\n    def __init__(self):\n        self.s = ''\n    def read(self,end_char='\\n', echo=True):\n        import sys, supervisor\n        n = supervisor.runtime.serial_bytes_available\n        if n \u003e 0:                    # we got bytes!\n            s = sys.stdin.read(n)    # actually read it in\n            if echo: sys.stdout.write(s)  # echo back to human\n            self.s = self.s + s      # keep building the string up\n            if s.endswith(end_char): # got our end_char!\n                rstr = self.s        # save for return\n                self.s = ''          # reset str to beginning\n                return rstr\n        return None                  # no end_char yet\n\nusb_reader = USBSerialReader()\nprint(\"type something and press the end_char\")\nwhile True:\n    mystr = usb_reader.read()  # read until newline, echo back chars\n    #mystr = usb_reader.read(end_char='\\t', echo=False) # trigger on tab, no echo\n    if mystr:\n        print(\"got:\",mystr)\n    time.sleep(0.01)  # do something time critical\n```\n\n## USB MIDI\n\nCircuitPython can be a MIDI controller, or respond to MIDI!\nAdafruit provides an [`adafruit_midi`](https://github.com/adafruit/Adafruit_CircuitPython_MIDI)\nclass to make things easier, but it's rather complex for how simple MIDI actually is.\n\nFor outputting MIDI, you can opt to deal with raw `bytearray`s, since most MIDI messages\nare just 1,2, or 3 bytes long.  For reading MIDI,\nyou may find [Winterbloom's SmolMIDI](https://github.com/wntrblm/Winterbloom_SmolMIDI) to be faster\nto parse MIDI messages, since by design it does less.\n\n### Sending MIDI with adafruit_midi\n\n```py\nimport usb_midi\nimport adafruit_midi\nfrom adafruit_midi.note_on import NoteOn\nfrom adafruit_midi.note_off import NoteOff\nmidi_out_channel = 3 # human version of MIDI out channel (1-16)\nmidi = adafruit_midi.MIDI( midi_out=usb_midi.ports[1], out_channel=midi_out_channel-1)\n\ndef play_note(note,velocity=127):\n    midi.send(NoteOn(note, velocity))  # 127 = highest velocity\n    time.sleep(0.1)\n    midi.send(NoteOff(note, 0))  # 0 = lowest velocity\n```\n\nNote: This pattern works for sending serial (5-pin) MIDI too, see below\n\n### Sending MIDI with bytearray\n\nSending MIDI with a lower-level `bytearray` is also pretty easy and\ncould gain some speed for timing-sensitive applications.\nThis code is equivalent to the above, without `adafruit_midi`\n\n```py\nimport usb_midi\nmidi_out = usb_midi.ports[1]\nmidi_out_channel = 3   # MIDI out channel (1-16)\nnote_on_status = (0x90 | (midi_out_channel-1))\nnote_off_status = (0x80 | (midi_out_channel-1))\n\ndef play_note(note,velocity=127):\n    midi_out.write( bytearray([note_on_status, note, velocity]) )\n    time.sleep(0.1)\n    midi_out.write( bytearray([note_off_status, note, 0]) )\n```\n\n### MIDI over Serial UART\n\nNot exactly USB, but it is MIDI!\nBoth `adafruit_midi` and the bytearray technique works for Serial MIDI (aka \"5-pin MIDI\") too.\nWith a [simple MIDI out circuit](https://learn.adafruit.com/qt-py-rp2040-usb-to-serial-midi-friends/circuit-diagram)\nyou can control old hardware synths.\n\n```py\nimport busio\nmidi_out_channel = 3  # MIDI out channel (1-16)\nnote_on_status = (0x90 | (midi_out_channel-1))\nnote_off_status = (0x80 | (midi_out_channel-1))\n# must pick board pins that are UART TX and RX pins\nmidi_uart = busio.UART(tx=board.GP16, rx=board.GP17, baudrate=31250)\n\ndef play_note(note,velocity=127):\n    midi_uart.write( bytearray([note_on_status, note, velocity]) )\n    time.sleep(0.1)\n    midi_uart.write( bytearray([note_off_status, note, 0]) )\n```\n\n### Receiving MIDI\n\n```py\nimport usb_midi        # built-in library\nimport adafruit_midi   # install with 'circup install adafruit_midi'\nfrom adafruit_midi.note_on import NoteOn\nfrom adafruit_midi.note_off import NoteOff\n\nmidi_usb = adafruit_midi.MIDI(midi_in=usb_midi.ports[0])\nwhile True:\n    msg = midi_usb.receive()\n    if msg:\n        if isinstance(msg, NoteOn):\n            print(\"usb noteOn:\",msg.note, msg.velocity)\n        elif isinstance(msg, NoteOff):\n            print(\"usb noteOff:\",msg.note, msg.velocity)\n```\nNote with `adafruit_midi` you must `import` each kind of MIDI Message you want to handle.\n\n### Receiving MIDI USB and MIDI Serial UART together\n\nMIDI is MIDI, so you can use either the `midi_uart` or the `usb_midi.ports[]` created above with `adafruit_midi`.\nHere's an example receiving MIDI from both USB and Serial on a QTPy RP2040.\nNote for receiving serial MIDI, you need an appropriate optoisolator input circuit,\nlike [this one for QTPys](https://www.denki-oto.com/store/p74/MICROMIDITRS-USB.html#/)\nor [this one for MacroPad RP2040](https://www.tindie.com/products/todbot/macropadsynthplug-turn-rp2040-into-a-synth/).\n\n```py\nimport board, busio\nimport usb_midi        # built-in library\nimport adafruit_midi   # install with 'circup install adafruit_midi'\nfrom adafruit_midi.note_on import NoteOn\nfrom adafruit_midi.note_off import NoteOff\n\nuart = busio.UART(tx=board.TX, rx=board.RX, baudrate=31250, timeout=0.001)\nmidi_usb = adafruit_midi.MIDI( midi_in=usb_midi.ports[0],  midi_out=usb_midi.ports[1] )\nmidi_serial = adafruit_midi.MIDI( midi_in=uart, midi_out=uart )\n\nwhile True:\n    msg = midi_usb.receive()\n    if msg:\n        if isinstance(msg, NoteOn):\n            print(\"usb noteOn:\",msg.note, msg.velocity)\n        elif isinstance(msg, NoteOff):\n            print(\"usb noteOff:\",msg.note, msg.velocity)\n    msg = midi_serial.receive()\n    if msg:\n        if isinstance(msg, NoteOn):\n            print(\"serial noteOn:\",msg.note, msg.velocity)\n        elif isinstance(msg, NoteOff):\n            print(\"serial noteOff:\",msg.note, msg.velocity)\n```\n\nIf you don't care about the source of the MIDI messages, you can combine\nthe two if blocks using the \"walrus operator\" (`:=`)\n\n```py\nwhile True:\n    while msg := midi_usb.receive() or midi_uart.receive():\n        if isinstance(msg, NoteOn) and msg.velocity != 0:\n            note_on(msg.note, msg.velocity)\n        elif isinstance(msg,NoteOff) or isinstance(msg,NoteOn) and msg.velocity==0:\n            note_off(msg.note, msg.velocity)\n```\n\n\n### Enable USB MIDI in boot.py (for ESP32-S2 and STM32F4)\n\nSome CircuitPython devices like ESP32-S2 based ones, do not have enough\n[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.\nTo enable it, the easiest is to disable USB HID (keyboard/mouse) support.\nThis must be done in `boot.py` and the board power cycled.\n\n```py\n# boot.py\nimport usb_hid\nimport usb_midi\nusb_hid.disable()\nusb_midi.enable()\nprint(\"enabled USB MIDI, disabled USB HID\")\n```\n\n\n## WiFi / Networking\n\n### Scan for WiFi Networks, sorted by signal strength\n\nNote: this is for boards with native WiFi (ESP32)\n\n```py\nimport wifi\nnetworks = []\nfor network in wifi.radio.start_scanning_networks():\n    networks.append(network)\nwifi.radio.stop_scanning_networks()\nnetworks = sorted(networks, key=lambda net: net.rssi, reverse=True)\nfor network in networks:\n    print(\"ssid:\",network.ssid, \"rssi:\",network.rssi)\n```\n\n### Join WiFi network with highest signal strength\n\n```py\nimport wifi\n\ndef join_best_network(good_networks, print_info=False):\n    \"\"\"join best network based on signal strength of scanned nets\"\"\"\n    networks = []\n    for network in wifi.radio.start_scanning_networks():\n        networks.append(network)\n    wifi.radio.stop_scanning_networks()\n    networks = sorted(networks, key=lambda net: net.rssi, reverse=True)\n    for network in networks:\n        if print_info: print(\"network:\",network.ssid)\n        if network.ssid in good_networks:\n            if print_info: print(\"connecting to WiFi:\", network.ssid)\n            try:\n                wifi.radio.connect(network.ssid, good_networks[network.ssid])\n                return True\n            except ConnectionError as e:\n                if print_info: print(\"connect error:\",e)\n    return False\n\ngood_networks = {\"todbot1\":\"FiOnTheFly\",  # ssid, password\n                 \"todbot2\":\"WhyFlyWiFi\",}\nconnected = join_best_network(good_networks, print_info=True)\nif connected:\n    print(\"connected!\")\n\n```\n\n### Ping an IP address\n\nNote: this is for boards with native WiFi (ESP32)\n\n```py\nimport os\nimport time\nimport wifi\nimport ipaddress\n\nip_to_ping = \"1.1.1.1\"\n\nwifi.radio.connect(ssid=os.getenv('CIRCUITPY_WIFI_SSID'),\n                   password=os.getenv('CIRCUITPY_WIFI_PASSWORD'))\n\nprint(\"my IP addr:\", wifi.radio.ipv4_address)\nprint(\"pinging \",ip_to_ping)\nip1 = ipaddress.ip_address(ip_to_ping)\nwhile True:\n    print(\"ping:\", wifi.radio.ping(ip1))\n    time.sleep(1)\n```\n\n### Get IP address of remote host\n\n```py\nimport os, wifi, socketpool\n\nwifi.radio.connect(ssid=os.getenv('CIRCUITPY_WIFI_SSID'),\n                   password=os.getenv('CIRCUITPY_WIFI_PASSWORD'))\nprint(\"my IP addr:\", wifi.radio.ipv4_address)\n\nhostname = \"todbot.com\"\n\npool = socketpool.SocketPool(wifi.radio)\naddrinfo = pool.getaddrinfo(host=hostname, port=443) # port is required\nprint(\"addrinfo\", addrinfo)\n\nipaddr = addrinfo[0][4][0]\n\nprint(f\"'{hostname}' ip address is '{ipaddr}'\")\n```\n\n\n### Fetch a JSON file\n\nNote: this is for boards with native WiFi (ESP32)\n\n```py\nimport os\nimport time\nimport wifi\nimport socketpool\nimport ssl\nimport adafruit_requests\n\nwifi.radio.connect(ssid=os.getenv('CIRCUITPY_WIFI_SSID'),\n                   password=os.getenv('CIRCUITPY_WIFI_PASSWORD'))\nprint(\"my IP addr:\", wifi.radio.ipv4_address)\npool = socketpool.SocketPool(wifi.radio)\nsession = adafruit_requests.Session(pool, ssl.create_default_context())\nwhile True:\n    response = session.get(\"https://todbot.com/tst/randcolor.php\")\n    data = response.json()\n    print(\"data:\",data)\n    time.sleep(5)\n```\n\n\n### Serve a webpage via HTTP\n\nNote: this is for boards with native WiFi (ESP32)\n\nThe [`adafruit_httpserver`](https://github.com/adafruit/Adafruit_CircuitPython_HTTPServer) library\nmakes this pretty easy, and has good examples. You can tell it to either `server.serve_forver()`\nand do all your computation in your `@server.route()` functions, or use `server.poll()` inside a while-loop.\nThere is also the [Ampule library](https://github.com/deckerego/ampule).\n\n```py\nimport time, os, wifi, socketpool\nfrom adafruit_httpserver.server import HTTPServer\nfrom adafruit_httpserver.response import HTTPResponse\n\nmy_port = 1234  # set this to your liking\nwifi.radio.connect(ssid=os.getenv('CIRCUITPY_WIFI_SSID'),\n                   password=os.getenv('CIRCUITPY_WIFI_PASSWORD'))\nserver = HTTPServer(socketpool.SocketPool(wifi.radio))\n\n@server.route(\"/\")  # magic that attaches this function to \"server\" object\ndef base(request):\n    my_str = f\"\u003chtml\u003e\u003cbody\u003e\u003ch1\u003e Hello! Current time.monotonic is {time.monotonic()}\u003c/h1\u003e\u003c/body\u003e\u003c/html\u003e\"\n    return HTTPResponse(body=my_str, content_type=\"text/html\")\n    # or for static content: return HTTPResponse(filename=\"/index.html\")\n\nprint(f\"Listening on http://{wifi.radio.ipv4_address}:{my_port}\")\nserver.serve_forever(str(wifi.radio.ipv4_address), port=my_port) # never returns\n\n```\n\n\n### Set RTC time from NTP\n\nNote: this is for boards with native WiFi (ESP32)\n\nNote: You need to set `my_tz_offset` to match your region\n\n```py\n# copied from:\n# https://docs.circuitpython.org/projects/ntp/en/latest/examples.html\nimport time, os, rtc\nimport socketpool, wifi\nimport adafruit_ntp\n\nmy_tz_offset = -7  # PDT\n\nwifi.radio.connect(ssid=os.getenv('CIRCUITPY_WIFI_SSID'),\n                   password=os.getenv('CIRCUITPY_WIFI_PASSWORD'))\nprint(\"Connected, getting NTP time\")\npool = socketpool.SocketPool(wifi.radio)\nntp = adafruit_ntp.NTP(pool, tz_offset=my_tz_offset)\n\nrtc.RTC().datetime = ntp.datetime\n\nwhile True:\n    print(\"current datetime:\", time.localtime())\n    time.sleep(5)\n```\n\n### Set RTC time from time service\n\nNote: this is for boards with native WiFi (ESP32)\n\nThis uses the awesome and free [WorldTimeAPI.org site](http://worldtimeapi.org/pages/examples),\nand this example will fetch the current local time (including timezone and UTC offset)\nbased on the geolocated IP address of your device.\n\n```py\nimport time, os, rtc\nimport wifi, ssl, socketpool\nimport adafruit_requests\n\nwifi.radio.connect(ssid=os.getenv('CIRCUITPY_WIFI_SSID'),\n                   password=os.getenv('CIRCUITPY_WIFI_PASSWORD'))\nprint(\"Connected, getting WorldTimeAPI time\")\npool = socketpool.SocketPool(wifi.radio)\nrequest = adafruit_requests.Session(pool, ssl.create_default_context())\n\nprint(\"Getting current time:\")\nresponse = request.get(\"http://worldtimeapi.org/api/ip\")\ntime_data = response.json()\ntz_hour_offset = int(time_data['utc_offset'][0:3])\ntz_min_offset = int(time_data['utc_offset'][4:6])\nif (tz_hour_offset \u003c 0):\n    tz_min_offset *= -1\nunixtime = int(time_data['unixtime'] + (tz_hour_offset * 60 * 60)) + (tz_min_offset * 60)\n\nprint(time_data)\nprint(\"URL time: \", response.headers['date'])\n\nrtc.RTC().datetime = time.localtime( unixtime ) # create time struct and set RTC with it\n\nwhile True:\n  print(\"current datetime: \", time.localtime()) # time.* now reflects current local time\n  time.sleep(5)\n```\n\nAlso see [this more concise version from @deilers78](https://github.com/todbot/circuitpython-tricks/issues/14#issuecomment-1489181920).\n\n\n### What the heck is `settings.toml`?\n\nIt's a config file that lives next to your `code.py` and is used to store\nWiFi credentials and other global settings.  It is also used (invisibly)\nby many Adafruit libraries that do WiFi.\nYou can use it (as in the examples above) without those libraries.\nThe settings names used by CircuitPython are documented in\n[CircuitPython Web Workflow](https://docs.circuitpython.org/en/latest/docs/workflows.html#web).\n\nNote: You can use any variable names for your WiFI credentials\n(a common pair is `WIFI_SSID` and `WIFI_PASSWORD`), but if you use the\n`CIRCUITPY_WIFI_*` names that will also start up the\n[Web Workflow](https://docs.circuitpython.org/en/latest/docs/workflows.html#web)\n\n\nYou use it like this for basic WiFi connectivity:\n\n```py\n# settings.toml\nCIRCUITPY_WIFI_SSID = \"PrettyFlyForAWiFi\"\nCIRCUITPY_WIFI_PASSWORD = \"mysecretpassword\"\n\n# code.py\nimport os, wifi\nprint(\"connecting...\")\nwifi.radio.connect(ssid=os.getenv('CIRCUITPY_WIFI_SSID'),\n                   password=os.getenv('CIRCUITPY_WIFI_PASSWORD'))\nprint(\"my IP addr:\", wifi.radio.ipv4_address)\n\n```\n\n\n### What the heck is `secrets.py`?\nIt's an older version of the `settings.toml` idea.\nYou may see older code that uses it.\n\n\n## Displays (LCD / OLED / E-Ink) and displayio\n\n[displayio](https://circuitpython.readthedocs.io/en/latest/shared-bindings/displayio/#)\nis the native system-level driver for displays in CircuitPython. Several CircuitPython boards\n(FunHouse, MagTag, PyGamer, CLUE) have `displayio`-based displays and a\nbuilt-in `board.DISPLAY` object that is preconfigured for that display.\nOr, 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.\n\n### Get default display and change display rotation\n\nBoards like FunHouse, MagTag, PyGamer, CLUE have built-in displays.\n`display.rotation` works with all displays, not just built-in ones.\n\n```py\nimport board\ndisplay = board.DISPLAY\nprint(display.rotation) # print current rotation\ndisplay.rotation = 0    # valid values 0,90,180,270\n```\n\n### Display an image\n\n__Using `displayio.OnDiskBitmap`__\n\nCircuitPython has a built-in BMP parser called `displayio.OnDiskBitmap`:\nThe images should be in non-compressed, paletized BMP3 format.\n([how to make BMP3 images](#preparing-images-for-circuitpython))\n\n```py\nimport board, displayio\ndisplay = board.DISPLAY\n\nmaingroup = displayio.Group() # everything goes in maingroup\ndisplay.root_group = maingroup # show our maingroup (clears the screen)\n\nbitmap = displayio.OnDiskBitmap(open(\"my_image.bmp\", \"rb\"))\nimage = displayio.TileGrid(bitmap, pixel_shader=bitmap.pixel_shader)\nmaingroup.append(image) # shows the image\n```\n\n__Using `adafruit_imageload`__\n\nYou can also use the `adafruit_imageload` library that supports slightly more kinds of BMP files,\n(but should still be [paletized BMP3 format](#preparing-images-for-circuitpython)\nas well as paletized PNG and GIF files. Which file format to choose?\n* BMP images are larger but faster to load\n* PNG images are about 2x smaller than BMP and almost as fast to load\n* GIF images are a little bigger than PNG but *much* slower to load\n\n\n```py\nimport board, displayio\nimport adafruit_imageload\ndisplay = board.DISPLAY\nmaingroup = displayio.Group() # everything goes in maingroup\ndisplay.root_group = maingroup # set the root group to display\nbitmap, palette = adafruit_imageload.load(\"my_image.png\")\nimage = displayio.TileGrid(bitmap, pixel_shader=palette)\nmaingroup.append(image) # shows the image\n```\n\n__How `displayio` is structured__\n\nCircuitPython's `displayio` library works like:\n- an image `Bitmap` (and its `Palette`) goes inside a `TileGrid`\n- a `TileGrid` goes inside a `Group`\n- a `Group` is shown on a `Display`.\n\n\n\n### Display background bitmap\n\nUseful for display a solid background color that can be quickly changed.\n\n```py\nimport time, board, displayio\ndisplay = board.DISPLAY         # get default display (FunHouse,Pygamer,etc)\nmaingroup = displayio.Group()   # Create a main group to hold everything\ndisplay.root_group = maingroup  # put it on the display\n\n# make bitmap that spans entire display, with 3 colors\nbackground = displayio.Bitmap(display.width, display.height, 3)\n\n# make a 3 color palette to match\nmypal = displayio.Palette(3)\nmypal[0] = 0x000000 # set colors (black)\nmypal[1] = 0x999900 # dark yellow\nmypal[2] = 0x009999 # dark cyan\n\n# Put background into main group, using palette to map palette ids to colors\nmaingroup.append(displayio.TileGrid(background, pixel_shader=mypal))\n\ntime.sleep(2)\nbackground.fill(2)  # change background to dark cyan (mypal[2])\ntime.sleep(2)\nbackground.fill(1)  # change background to dark yellow (mypal[1])\n```\n\nAnother way is to use\n[`vectorio`](https://docs.circuitpython.org/en/latest/shared-bindings/vectorio/index.html):\n\n```py\nimport board, displayio, vectorio\n\ndisplay = board.DISPLAY  # built-in display\nmaingroup = displayio.Group()   # a main group that holds everything\ndisplay.root_group = maingroup  # put maingroup on the display\n\nmypal = displayio.Palette(1)\nmypal[0] = 0x999900\nbackground = vectorio.Rectangle(pixel_shader=mypal, width=display.width, height=display.height, x=0, y=0)\nmaingroup.append(background)\n```\n\nOr can also use\n[`adafruit_display_shapes`](https://docs.circuitpython.org/projects/display-shapes/en/latest/index.html):\n\n```py\nimport board, displayio\nfrom adafruit_display_shapes.rect import Rect\n\ndisplay = board.DISPLAY\nmaingroup = displayio.Group()   # a main group that holds everything\ndisplay.root_group = maingroup  # add it to display\n\nbackground = Rect(0,0, display.width, display.height, fill=0x000000 ) # background color\nmaingroup.append(background)\n```\n\n### Image slideshow\n\n```py\nimport time, board, displayio\nimport adafruit_imageload\n\ndisplay = board.DISPLAY      # get display object (built-in on some boards)\nscreen = displayio.Group()   # main group that holds all on-screen content\ndisplay.root_group = screen  # add it to display\n\nfile_names = [ '/images/cat1.bmp', '/images/cat2.bmp' ]  # list of filenames\n\nscreen.append(displayio.Group())  # placeholder, will be replaced w/ screen[0] below\nwhile True:\n    for fname in file_names:\n        image, palette = adafruit_imageload.load(fname)\n        screen[0] = displayio.TileGrid(image, pixel_shader=palette)\n        time.sleep(1)\n```\n\n__Note:__ Images must be in palettized BMP3 format.\nFor more details, see [Preparing images for CircuitPython](#preparing-images-for-circuitpython)\n\n\n### Dealing with E-Ink \"Refresh Too Soon\" error\n\nE-Ink displays are damaged if refreshed too frequently.\nCircuitPython enforces this, but also provides `display.time_to_refresh`,\nthe number of seconds you need to wait before the display can be refreshed.\nOne solution is to sleep a little longer than that and you'll never get the error.\nAnother would be to wait for `time_to_refresh` to go to zero, as show below.\n\n```py\nimport time, board, displayio, terminalio\nfrom adafruit_display_text import label\nmylabel = label.Label(terminalio.FONT, text=\"demo\", x=20,y=20,\n                      background_color=0x000000, color=0xffffff )\ndisplay = board.DISPLAY  # e.g. for MagTag\ndisplay.root_group = mylabel\nwhile True:\n    if display.time_to_refresh == 0:\n        display.refresh()\n    mylabel.text = str(time.monotonic())\n    time.sleep(0.1)\n```\n\n### Turn off REPL on built-in display \n\nIf you have a board with a built-in display (like Feather TFT, Cardputer, FunHouse, etc),\nCircuitPython will set up the display for you and print the REPL to it.\nBut if you want a more polished look for your project, you can turn off the REPL\nfrom printing on the built-in display by putting this at at the top of both\nyour `boot.py` and `code.py`\n\n```py\n# put at top of both boot.py \u0026 code.py \nimport board\nboard.DISPLAY.root_group = None\n```\n\n\n## I2C\n\n### Scan I2C bus for devices\nfrom:\n[CircuitPython I2C Guide: Find Your Sensor](https://learn.adafruit.com/circuitpython-essentials/circuitpython-i2c#find-your-sensor-2985153-11)\n\n```py\nimport board\ni2c = board.I2C() # or busio.I2C(pin_scl,pin_sda)\nwhile not i2c.try_lock():  pass\nprint(\"I2C addresses found:\", [hex(device_address)\n    for device_address in i2c.scan()])\ni2c.unlock()\n```\n\nOne liner to copy-n-paste into REPL for quicky I2C scan:\n\n```py\nimport board; i2c=board.I2C(); i2c.try_lock(); [hex(a) for a in i2c.scan()]; i2c.unlock()\n```\n\n### Speed up I2C bus\n\nCircuitPython defaults to 100 kHz I2C bus speed. This will work for all devices,\nbut some devices can go faster. Common faster speeds are 200 kHz and 400 kHz.\n\n```py\nimport board\nimport busio\n# instead of doing\n# i2c = board.I2C()\ni2c = busio.I2C( board.SCL, board.SDA, frequency=200_000)\n# then do something with 'i2c' object as before, like:\noled = adafruit_ssd1306.SSD1306_I2C(width=128, height=32, i2c=i2c)\n```\n\n## Timing\n\n### Measure how long something takes\n\nGenerally use `time.monotonic()` to get the current \"uptime\" of a board in fractional seconds.\nSo to measure the duration it takes CircuitPython to do something like:\n\n```py\nimport time\nstart_time = time.monotonic()\n# put thing you want to measure here, like:\nimport neopixel\nstop_time = time.monotonic()\nprint(\"elapsed time = \", stop_time - start_time)\n```\n\nNote that on the \"small\" versions of CircuitPython in the QT Py M0, Trinket M0, etc.,\nthe floating point value of seconds will become less accurate as uptime increases.\n\n### More accurate timing with `ticks_ms()`, like Arduino `millis()`\n\nIf you want something more like Arduino's `millis()` function, the `supervisor.ticks_ms()`\nfunction returns an integer, not a floating point value. It is more useful for sub-second\ntiming tasks and you can still convert it to floating-point seconds for human consumption.\n\n```py\nimport supervisor\nstart_msecs = supervisor.ticks_ms()\nimport neopixel\nstop_msecs = supervisor.ticks_ms()\nprint(\"elapsed time = \", (stop_msecs - start_msecs)/1000)\n```\n\n### Control garbage collection for reliable timing\n\nThe CircuitPython garbage collector makes it so you don't have to deal with\nmemory allocations like you do in languages like C/C++. But when it runs, it can pause your\nprogram for tens of milliseconds in some cases. For timing-sensitive applications,\nyou can exhibit some control over the garbage collector so it only runs when\nyou want it (like in the \"shadow\" after a timing-critical event)\n\nHere's one way to do this.\n```py\nimport gc\nfrom adafruit_ticks import ticks_ms, ticks_add, ticks_less\ngc.collect()  # collect any garbage before we...\ngc.disable()  # disable automatic garbage collection\ndmillis = 10  # how many millis between explicit gc\ndeadline = ticks_add(ticks_ms(), dmillis)\nwhile True:\n    now = ticks_ms()\n    if ticks_diff(now, deadline) \u003e= 0:\n        deadline = ticks_add(now, dmillis)\n        gc.collect()  # explicitly run a garbage collection cycle\n    # do timing-sensitive thing here\n```\n\n### Converting milliseconds to seconds: 0.004 * 1000 != 4, sometimes\n\nWhen doing timing-sensitive tasks in CircuitPython, you may have code that looks like this (say from the above):\n```py\nfrom adafruit_ticks import ticks_ms, ticks_add, ticks_less\n\ncheck_secs = 0.004  # check_secs is seconds between checks\ncheck_millis = check_secs * 1000  # convert to millis\ndeadline = ticks_add(ticks_ms(), check_millis)\nwhile True:\n  now = ticks_ms()\n  if ticks_less(now,deadline) \u003e= 0:\n    deadline = ticks_add(now, check_millis)\n    do_periodic_task()  # do timing-critical thing every 'check_secs'\n```\nThis seems more accurate than using `time.monotonic()` since it's using the millisecond-accurate `supervisor.ticks_ms` property, the timing resolution of CircuitPython.\nThis 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\nusing 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)).\nIn short: stick to integer milliseconds.\n\n\n## Board Info\n\n### Get CPU speed (and set it!)\n\nCircuitPython provides a way to get the microcontroller's CPU frequency with\n[`microcontroller.cpu.frequency`](https://docs.circuitpython.org/en/latest/shared-bindings/microcontroller/index.html#microcontroller.Processor.frequency).\n\n```py\nimport microcontroller\nprint(\"CPU speed:\", microcontroller.cpu.frequency)\n```\n\nOn some chips (most notably Pico P2040), you can also **set** this value. Overclock your Pico!\nIt's safe to double the RP2040 speed, Raspberry Pi officially supports up to 200 MHz.\nCircuitPython will adjust its internal timings, but you should\ndo this change before creating any peripheral objects like UARTs or displays.\n\n```py\nimport microcontroller\nmicrocontroller.cpu.frequency = 250_000_000  # run at 250 MHz instead of 125 MHz\n```\n\n\n### Display amount of free RAM\n\nfrom: https://learn.adafruit.com/welcome-to-circuitpython/frequently-asked-questions\n```py\nimport gc\nprint( gc.mem_free() )\n```\n\n### Show microcontroller.pin to board mappings\nfrom https://gist.github.com/anecdata/1c345cb2d137776d76b97a5d5678dc97\n```py\n\nimport microcontroller\nimport board\n\nfor pin in dir(microcontroller.pin):\n    if isinstance(getattr(microcontroller.pin, pin), microcontroller.Pin):\n        print(\"\".join((\"microcontroller.pin.\", pin, \"\\t\")), end=\" \")\n        for alias in dir(board):\n            if getattr(board, alias) is getattr(microcontroller.pin, pin):\n                print(\"\".join((\"\", \"board.\", alias)), end=\" \")\n    print()\n```\n\n### Determine which board you're on\n\n```py\nimport os\nprint(os.uname().machine)\n'Adafruit ItsyBitsy M4 Express with samd51g19'\n```\n\nAnother way is the `board.board_id`.  This is the \"port\" name used when \ncompiling CircuitPython. To find a list of valid board IDs,\nyou can look in the circuitpython core repo inside of: \"ports/[some_port]/boards/\".\ni.e. for espressif boards find the list of directories in: \n[ports/espressif/boards/](https://github.com/adafruit/circuitpython/tree/main/ports/espressif/boards)\nThe \"board_id is used as the argument for \n`circuitpython_setboard` in [`circuitpython-stubs`](https://pypi.org/project/circuitpython-stubs/)\n\n```py\nimport board\nprint(board.board_id)\n'raspberry_pi_pico'\n```\n\nTo get the chip family:\n\n```py\nimport os\nprint(os.uname().sysname)\n'ESP32S2'\n```\n\n### Support multiple boards with one `code.py`\n  ```py\n  import os\n  board_type = os.uname().machine\n  if 'QT Py M0' in board_type:\n    tft_clk  = board.SCK\n    tft_mosi = board.MOSI\n    spi = busio.SPI(clock=tft_clk, MOSI=tft_mosi)\n  elif 'ItsyBitsy M4' in board_type:\n    tft_clk  = board.SCK\n    tft_mosi = board.MOSI\n    spi = busio.SPI(clock=tft_clk, MOSI=tft_mosi)\n  elif 'Pico' in board_type:\n    tft_clk = board.GP10 # must be a SPI CLK\n    tft_mosi= board.GP11 # must be a SPI TX\n    spi = busio.SPI(clock=tft_clk, MOSI=tft_mosi)\n  else:\n    print(\"unsupported board\", board_type)\n  ```\n\n\n## Computery Tasks\n\n### Formatting strings\n```py\nname = \"John\"\nfav_color = 0x003366\nbody_temp = 98.65\nfav_number = 123\nprint(\"name:%s color:%06x temp:%2.1f num:%d\" % (name,fav_color,body_temp,fav_number))\n# 'name:John color:ff3366 temp:98.6 num:123'\n```\n\n### Formatting strings with f-strings\n(doesn't work on 'small' CircuitPythons like QTPy M0)\n\n```py\nname = \"John\"\nfav_color = 0xff3366\nbody_temp = 98.65\nfav_number = 123\nprint(f\"name:{name} color:{fav_color:06x} temp:{body_temp:2.1f} num:{fav_number}\")\n# 'name:John color:ff3366 temp:98.6 num:123'\n```\n\n### Using regular expressions to \"findall\" strings\n\nRegular expressions are a really powerful way to match information in and parse data\nfrom strings.  While CircuitPython has a version of the `re` regex module you may know\nfrom desktop Python, it is very limited. Specifcally it doesn't have the very useful\n`re.findall()` function.  Below is a semi-replacement for `findall()`.\n\n```py\nimport re\ndef find_all(regex, some_str):\n    matches = []\n    while m := regex.search(some_str):\n        matches.append( m.groups() )\n        some_str = some_str[ m.end(): ] # get past match\n    return matches\n\nmy_str = \"\u003cthing\u003ething1 I want\u003c/thing\u003e \u003cthing\u003ething2 I want\u003c/thing\u003e  \u003cthing\u003ething3 I want\u003c/thing\u003e\"\nregex1 = re.compile('\u003cthing.*?\u003e(.*?)\u003c\\/thing\u003e')\nmy_matches = find_all( regex1, my_str )\nprint(\"matches:\", my_matches)\n\n```\n\n\n### Make and use a config file\n\n```py\n# my_config.py\nconfig = {\n    \"username\": \"Grogu Djarin\",\n    \"password\": \"ig88rules\",\n    \"secret_key\": \"3a3d9bfaf05835df69713c470427fe35\"\n}\n\n# code.py\nfrom my_config import config\nprint(\"secret:\", config['secret_key'])\n# 'secret: 3a3d9bfaf05835df69713c470427fe35'\n```\n\n### Run different `code.py` on startup\n\nUse `microcontroller.nvm` to store persistent state across\nresets or between `boot.py` and `code.py`, and declare that\nthe first byte of `nvm` will be the `startup_mode`.\nNow if you create multiple code.py files (say) `code1.py`, `code2.py`, etc.\nyou can switch between them based on `startup_mode`.\n\n```py\nimport time\nimport microcontroller\nstartup_mode = microcontroller.nvm[0]\nif startup_mode == 1:\n    import code1      # runs code in `code1.py`\nif startup_mode == 2:\n    import code2      # runs code in `code2.py`\n# otherwise runs 'code.py`\nwhile True:\n    print(\"main code.py\")\n    time.sleep(1)\n\n```\n\n**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)\nto change which .py file is run on startup.\nThis changes only what happens on reload, not hardware reset or powerup.\nUsing it would look like:\n```py\nimport supervisor\nsupervisor.set_next_code_file('code_awesome.py')\n# and then if you want to run it now, trigger a reload\nsupervisor.reload()\n```\n\n## Coding Techniques\n\n### Map an input range to an output range\n\n```py\n# simple range mapper, like Arduino map()\ndef map_range(s, a1, a2, b1, b2):\n    return  b1 + ((s - a1) * (b2 - b1) / (a2 - a1))\n\n# example: map 0-0123 value to 0.0-1.0 value\nval = 768\noutval = map_range( val, 0,1023, 0.0,1.0 )\n# outval = 0.75\n```\n\n### Constrain an input to a min/max\nThe Python built-in `min()` and `max()` functions can be used together\nto make something like Arduino's `constrain()` to clamp an input between two values.\n\n```py\n# constrain a value to be 0-255\noutval = min(max(val, 0), 255)\n# constrain a value to be 0-255 integer\noutval = int(min(max(val, 0), 255))\n# constrain a value to be -1 to +1\noutval = min(max(val, -1), 1)\n```\n\n### Turn a momentary value into a toggle\n\n```py\nimport touchio\nimport board\n\ntouch_pin = touchio.TouchIn(board.GP6)\nlast_touch_val = False  # holds last measurement\ntoggle_value = False  # holds state of toggle switch\n\nwhile True:\n  touch_val = touch_pin.value\n  if touch_val != last_touch_val:\n    if touch_val:\n      toggle_value = not toggle_value   # flip toggle\n      print(\"toggle!\", toggle_value)\n  last_touch_val = touch_val\n  ```\n\n### Do something every N seconds without `sleep()`\n\nAlso known as \"blink-without-delay\"\n\n```py\nimport time\nlast_time1 = time.monotonic()  # holds when we did something #1\nlast_time2 = time.monotonic()  # holds when we did something #2\nwhile True:\n  if time.monotonic() - last_time1 \u003e 2.0:  # every 2 seconds\n    last_time1 = time.monotonic() # save when we do the thing\n    print(\"hello!\")  # do thing #1\n  if time.monotonic() - last_time2 \u003e 5.0:  # every 5 seconds\n    last_time2 = time.monotonic() # save when we do the thing\n    print(\"world!\")  # do thing #2\n\n```\n\nNote: a more accurate of this [uses `ticks_ms()`](#more-accurate-timing-with-ticks_ms-like-arduino-millis)\nand maybe turning off gc / garbage collection.\n\n\n## System error handling\n\n### Preventing Ctrl-C from stopping the program\n\nPut a `try`/`except KeyboardInterrupt` to catch the Ctrl-C\non the inside of your main loop.\n\n```py\nwhile True:\n  try:\n    print(\"Doing something important...\")\n    time.sleep(0.1)\n  except KeyboardInterrupt:\n    print(\"Nice try, human! Not quitting.\")\n```\n\nAlso useful for graceful shutdown (turning off neopixels, say) on Ctrl-C.\n\n```py\nimport time, random\nimport board, neopixel, rainbowio\nleds = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.4 )\nwhile True:\n  try:\n    rgb = rainbowio.colorwheel(int(time.monotonic()*75) % 255)\n    leds.fill(rgb)\n    time.sleep(0.05)\n  except KeyboardInterrupt:\n    print(\"shutting down nicely...\")\n    leds.fill(0)\n    break  # gets us out of the while True\n```\n\n### Prevent auto-reload when CIRCUITPY is touched\n\nNormally, CircuitPython restarts anytime the CIRCUITPY drive is written to.\nThis is great normally, but is frustrating if you want your code to keep running,\nand you want to control exactly when a restart happens.\n\n```py\nimport supervisor\nsupervisor.runtime.autoreload = False  # CirPy 8 and above\n#supervisor.disable_autoreload()  # CirPy 7 and below\n```\n\nTo trigger a reload, do a Ctrl-C + Ctrl-D in the REPL or reset your board.\n\n### Raspberry Pi Pico boot.py Protection\n\nAlso works on other RP2040-based boards like QTPy RP2040.\nFrom https://gist.github.com/Neradoc/8056725be1c209475fd09ffc37c9fad4\n\nAlso see [getting into Safe Mode with a REPL one-liner](#useful-repl-one-liners).\n\n```py\n# Copy this as 'boot.py' in your Pico's CIRCUITPY drive\n# Useful in case Pico locks up (which it's done a few times on me)\nimport board\nimport time\nfrom digitalio import DigitalInOut,Pull\n\nled = DigitalInOut(board.LED)\nled.switch_to_output()\n\nsafe = DigitalInOut(board.GP14)  # \u003c-- choose your button pin\nsafe.switch_to_input(Pull.UP)\n\ndef reset_on_pin():\n    if safe.value is False:\n        import microcontroller\n        microcontroller.on_next_reset(microcontroller.RunMode.SAFE_MODE)\n        microcontroller.reset()\n\nled.value = False\nfor x in range(16):\n\treset_on_pin()\n\tled.value = not led.value  # toggle LED on/off as notice\n\ttime.sleep(0.1)\n\n```\n\n\n## Using the REPL\n\nThe \"serial\" REPL is the most useful diagnostic tools in CircuitPython.\nAlways have it open when saving your code to see any errors.\nIf you use a separate terminal program instead of an IDE, I recommend [`tio`](https://github.com/tio/tio).\n\n### Display built-in modules / libraries\n  ```\n  Adafruit CircuitPython 6.2.0-beta.2 on 2021-02-11; Adafruit Trinket M0 with samd21e18\n  \u003e\u003e\u003e help(\"modules\")\n  __main__          digitalio         pulseio           supervisor\n  analogio          gc                pwmio             sys\n  array             math              random            time\n  board             microcontroller   rotaryio          touchio\n  builtins          micropython       rtc               usb_hid\n  busio             neopixel_write    storage           usb_midi\n  collections       os                struct\n  Plus any modules on the filesystem\n  ```\n\n### Turn off built-in display to speed up REPL printing\n\nBy default CircuitPython will echo the REPL to the display of those boards with built-in displays.\nThis can slow down the REPL. So one way to speed the REPL up is to hide the `displayio.Group` that\ncontains all the REPL output.\n\n```py\nimport board, displayio\nboard.DISPLAY.root_group = None  # turn off REPL printing\nboard.DISPLAY.root_group = displayio.CIRCUITPYTHON_TERMINAL  # turn back on REPL printing\n```\n\nIn CircuitPython 8.x, you could do the below. In 9.x, the `root_group` is read-only\nafter it's been assigned. \n\n```py\nimport board\nboard.DISPLAY.root_group.hidden = True\nboard.DISPLAY.root_group.hidden = False  # to turn it back on\n```\n\n### Useful REPL one-liners\n\n(yes, semicolons are legal in Python)\n\n```py\n# get into Safe Mode if you have REPL access\nimport microcontroller; microcontroller.on_next_reset(microcontroller.RunMode.SAFE_MODE); microcontroller.reset()\n\n# load common libraries (for later REPL experiments)\nimport time, board, analogio, touchio; from digitalio import DigitalInOut,Pull\n\n# create a pin and set a pin LOW (if you've done the above)\npin = DigitalInOut(board.GP0); pin.switch_to_output(value=False)\n\n# print out board pins and objects (like `I2C`, `STEMMA_I2C`, `DISPLAY`, if present)\nimport board; dir(board)\n\n# print out microcontroller pins (chip pins, not the same as board pins)\nimport microcontroller; dir(microcontroller.pin)\n\n# release configured / built-in display\nimport displayio; displayio.release_displays()\n\n# turn off auto-reload when CIRCUITPY drive is touched\nimport supervisor; supervisor.runtime.autoreload = False\n\n# test neopixel strip, make them all purple\nimport board, neopixel; leds = neopixel.NeoPixel(board.GP3, 8, brightness=0.2); leds.fill(0xff00ff)\nleds.deinit()  # releases pin\n\n# scan I2C bus\nimport board; i2c=board.I2C(); i2c.try_lock(); [hex(a) for a in i2c.scan()]; i2c.unlock()\n\n```\n\n\n## Python tricks\n\nThese are general Python tips that may be useful in CircuitPython.\n\n### Create list with elements all the same value\n\n```py\nblank_array = [0] * 50   # creats 50-element list of zeros\n```\n\n### Convert RGB tuples to int and back again\n\nThanks to @Neradoc for this tip:\n\n```py\nrgb_tuple = (255, 0, 128)\nrgb_int = int.from_bytes(rgb_tuple, byteorder='big')\n\nrgb_int = 0xFF0080\nrgb_tuple2 = tuple((rgb_int).to_bytes(3,\"big\"))\n\nrgb_tuple2 == rgb_tuple\n```\n\n### Storing multiple values per list entry\n\nCreate simple data structures as config to control your program.\nUnlike Arduino, you can store multiple values per list/array entry.\n\n```py\nmycolors = (\n    # color val, name\n    (0x0000FF, \"blue\"),\n    (0x00FFFF, \"cyan\"),\n    (0xFF00FF, \"purple\"),\n)\nfor i in range(len(mycolors)):\n    (val, name) = mycolors[i]\n    print(\"my color \", name, \"has the value\", val)\n```\n\n\n## Python info\n\nHow to get information about Python inside of CircuitPython.\n\n### Display which (not built-in) libraries have been imported\n```py\nimport sys\nprint(sys.modules.keys())\n# 'dict_keys([])'\nimport board\nimport neopixel\nimport adafruit_dotstar\nprint(sys.modules.keys())\nprints \"dict_keys(['neopixel', 'adafruit_dotstar'])\"\n```\n\n### List names of all global variables\n```py\na = 123\nb = 'hello there'\nmy_globals = sorted(dir)\nprint(my_globals)\n# prints \"['__name__', 'a', 'b']\"\nif 'a' in my_globals:\n  print(\"you have a variable named 'a'!\")\nif 'c' in my_globals:\n  print(\"you have a variable named 'c'!\")\n```\n\n### Display the running CircuitPython release\n\nWith an established serial connection, press `Ctrl+c`:\n\n```sh\nAdafruit CircuitPython 7.1.1 on 2022-01-14; S2Pico with ESP32S2-S2FN4R2\n\u003e\u003e\u003e\n```\n\nWithout connection or code running, check the `boot_out.txt` file in your CIRCUITPY drive.\n\n```py\nimport os\nprint(os.uname().release)\n'7.1.1'\nprint(os.uname().version)\n'7.1.1 on 2022-01-14'\n```\n\n## Host-side tasks\n\nThings you might need to do on your computer when using CircuitPython.\n\n### Installing CircuitPython libraries\n\nThe below examples are for MacOS / Linux.  Similar commands are used for Windows\n\n#### Installing libraries with `circup`\n\n`circup` can be used to easily install and update modules\n\n```sh\n$ pip3 install --user circup\n$ circup install adafruit_midi\n$ circup update   # updates all modules\n```\n\nFreshly update all modules to latest version (e.g. when going from CP 6 -\u003e CP 7)\n(This is needed because `circup update` doesn't actually seem to work reliably)\n\n```sh\ncircup freeze \u003e mymodules.txt\nrm -rf /Volumes/CIRCUITPY/lib/*\ncircup install -r mymodules.txt\n```\n\n\nAnd updating circup when a new version of CircuitPython comes out:\n```sh\n$ pip3 install --upgrade circup\n```\n\n\n#### Copying libraries by hand with `cp`\n\nTo install libraries by hand from the\n[CircuitPython Library Bundle](https://circuitpython.org/libraries)\nor 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`.\n\n```sh\ncp -rX bundle_folder/lib/adafruit_midi /Volumes/CIRCUITPY/lib\n```\n\n**Note:** on limited-memory boards like Trinkey, Trinket, QTPy, you must use the `-X` option on MacOS\nto 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)\n\n\n### Preparing images for CircuitPython\n\nThere's two ways to load images for use with `displayio`:\n* The built-in [`displayio.OnDiskBitmap()`](https://docs.circuitpython.org/en/latest/shared-bindings/displayio/#displayio.OnDiskBitmap)\n* The library [`adafruit_imageload`](https://docs.circuitpython.org/projects/imageload/en/latest/index.html)\n\n`displayio.OnDisBitmap()`:\n* can load \"indexed\" (aka \"palette\") non-compressed BMP3 images\n* doesn't load image into RAM (great for TileGrids)\n\n`adafruit_imageload`\n* can load BMP3 images with RLE compression\n* loads entire image into RAM (thus you may run out of memory)\n* an load palette PNG images and GIF images\n* PNG image loading is almost as fast as BMP and uses 1/2 the disk space\n* GIF loading is very slow\n\nTo make images load faster generally, you can reduce the number of colors in the image.\nThe maximum number of colors is 256, but try reducing colors to 64 or even 2 if\nit's a black-n-white image.\n\nSome existing Learn Guides:\n- https://learn.adafruit.com/creating-your-first-tilemap-game-with-circuitpython/indexed-bmp-graphics\n- https://learn.adafruit.com/preparing-graphics-for-e-ink-displays\n\nAnd here's some ways to do the conversions.\n\n\n#### Online\n\nMost online image converters do not create BMPs in the proper format:\nBMP3, non-compressed, up to 256 colors in an 8-bit palette.\n\nHowever @Neradoc found the site [convert2bmp](https://online-converting.com/image/convert2bmp/)\nwill work when you set \"Color:\" mode to \"8 (Indexed)\". Some initial tests show this works!\nI'd recommend also trying out one of the following techniques too to have finer control.\n\nThe site https://cancerberosgx.github.io/magic/playground/ lets you\nuse any of the ImageMagick commands below to convert images. It's really handy if you can't\ninstall ImageMagick locally.\n\n\n#### Command-line: using ImageMagick\n\n[ImageMagick](https://imagemagick.org/) is a command-line image manipulation tool. With it,\nyou can convert any image format to BMP3 format. The main ImageMagick CLI command is `convert`.\n\n```sh\nconvert myimage.jpg -resize 240x240 -type palette -colors 64 -compress None BMP3:myimage_for_cirpy.bmp\n```\n\nYou can also use this technique to create reduced-color palette PNGs:\n\n```sh\nconvert myimage.jpg -resize 240x240 -type palette -colors 64 myimage.png\n```\n\n#### Command-line: using GraphicsMagick\n\n[GraphicsMagick](http://www.graphicsmagick.org/) is a slimmer, lower-requirement\nclone of ImageMagick. All GraphicsMagick commands are accessed via the `gm` CLI command.\n\n```sh\ngm convert myimage.jpg -resize 240x240 -colors 64 -type palette -compress None BMP3:myimage_for_cirpy.bmp\n```\n\n#### Making images smaller or for E-Ink displays\n\nTo make images smaller (and load faster), reduce number of colors from 256.\nIf your image is a monochrome (or for use with E-Ink displays like MagTag), use 2 colors.\nThe [\"-dither\" options](https://legacy.imagemagick.org/Usage/quantize/#colors)\nare really helpful for monochrome:\n```\nconvert cat.jpg -dither FloydSteinberg -colors 2 -type palette BMP3:cat.bmp\n```\n\n\n#### NodeJs: using gm\n\nThere is a nice wrapper around GraphicsMagick / Imagemagick with the [`gm library`](https://github.com/aheckmann/gm).\nA small NodeJs program to convert images could look like this:\n\n```js\nvar gm = require('gm');\ngm('myimage.jpg')\n    .resize(240, 240)\n    .colors(64)\n    .type(\"palette\")\n    .compress(\"None\")\n    .write('BMP3:myimage_for_cirpy.bmp', function (err) {\n        if (!err) console.log('done1');\n    });\n```\n\n#### Python: using PIL / pillow\n\nThe [Python Image Library (PIL) fork `pillow`](https://pillow.readthedocs.io/en/stable/index.html)\nseems to work the best.  It's unclear how to toggle compression.\n\n```py\nfrom PIL import Image\nsize = (240, 240)\nnum_colors = 64\nimg = Image.open('myimage.jpg')\nimg = img.resize(size)\nnewimg = img.convert(mode='P', colors=num_colors)\nnewimg.save('myimage_for_cirpy.bmp')\n```\n\n### Preparing audio files for CircuitPython\n\nCircuitPython can play both WAV files and MP3 files, but there are specific\nvariants of these files that will work better, as some options require much more\nprocessor demand. WAV files are much easier to play but take up more disk space.\n\n#### WAV files\n\nFor WAV files, I've found the best trade-off in quality / flash-space / compatibility to be:\n\n- PCM 16-bit signed PCM\n- Mono (but stereo will work if using I2S or SAMD51)\n- 22050 Hz sample rate\n\nAnd remember that these settings must match how you're setting up the `audiomixer` object.\nSo for the above settings, you'd create an `audiomixer.Mixer` like:\n\n```py\nmixer = audiomixer.Mixer(voice_count=1, sample_rate=22050, channel_count=1,\n                         bits_per_sample=16, samples_signed=True)\n```\n\nTo convert WAVs for CircuitPython, I like to use Audacity or the `sox` command-line tool.\nSox can convert just about any audio to the correct WAV format:\n\n```sh\nsox loop.mp3 -b 16 -c 1 -r 22050 loop.wav\n```\n\n#### MP3 files\n\nMP3 files require a lot more CPU to decode so in general you will want to\nre-encode MP3s to be a lower bit-rate and lower sample-rate.  These settings\nseem to work okay on an lower-end chip like the Pico  /RP2040:\n\n* 128 kbps data rate CBR or lower\n* 22050 Hz sample rate or lower\n* Mono\n\nIn `sox`, you can do this conversion with:\n\n```sh\nsox loop.mp3 -c 1 -r 22050 -C 128 loop_22k_128kbps.mp3\n```\n\n\n#### Getting sox\n\nTo get `sox` on various platforms:\n- Linux: `sudo apt install sox libsox-fmt-mp3`\n- macOS: `brew install sox`\n- Windows: Use installer at http://sox.sourceforge.net/\n\nSome audio Learn Guide links:\n- https://learn.adafruit.com/circuitpython-essentials/circuitpython-audio-out#play-a-wave-file-2994862-6\n- https://learn.adafruit.com/adafruit-wave-shield-audio-shield-for-arduino/convert-files\n\n\n### Circup hacks\n\n[`circup`](https://learn.adafruit.com/keep-your-circuitpython-libraries-on-devices-up-to-date-with-circup?view=all)\nis a great tool to help you install CircuitPython libraries. Think of it like `pip` or `npm` for CircuitPython.\n\n#### Finding where `circup` stores its files\n\nInstead of downloading the bundles by hand, `circup` has it already downloaded an unzipped.\nHere's how to find that directory:\n\n```sh\ncircup_dir=`python3 -c 'import appdirs; print(appdirs.user_data_dir(appname=\"circup\", appauthor=\"adafruit\"))'`\nls $circup_dir\n```\n\n### Building CircuitPython\n\nIf you want to build CircuitPython yourself, you can! It's not too bad.\nThere'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\nwhy you might want to build your own version of CircuitPython, including:\n- Adding \"Frozen\" Modules (libraries built-in to the firmware)\n- Setting different SPI flash chips (if your custom board uses a different kind of flash)\n- Adding a new board to CircuitPython\n\nBut if you just want a quick list of the commands to use to build, here's what I use\n(as of Jun 2024) to build CircuitPython for rp2040.\n\n```sh\ngit clone https://github.com/todbot/circuitpython circuitpython-todbot\ncd circuitpython-todbot\npip3 install --upgrade -r requirements-dev.txt  # do occasionally, after 'git pull'\npip3 install --upgrade -r requirements-doc.txt  # do occasionally, after 'git pull'\ncd ports/raspberrypi\nmake fetch-port-submodules     # takes a long time the first time ran, do after 'git pull' too\nmake BOARD=raspberry_pi_pico   # or other board name listed in ports/raspberrypi/boards/\n# make -C ../../mpy-cross  # if you need mpy-cross\n```\n\nAnd for Espressif / ESP32 builds:\n\n```sh\ngit clone https://github.com/todbot/circuitpython circuitpython-todbot\ncd circuitpython-todbot\npip3 install --upgrade -r requirements-dev.txt  # do occasionally, after 'git pull'\npip3 install --upgrade -r requirements-doc.txt  # do occasionally, after 'git pull'\ncd ports/espressif\nmake fetch-port-submodules     # takes a long time the first time ran, do after 'git pull' too\n./esp-idf/install.sh\n. ./esp-idf/export.sh\npip3 install --upgrade -r requirements-dev.txt  # because now we're using a new python\npip3 install --upgrade -r requirements-doc.txt  # because now we're using a new python\nmake BOARD=adafruit_qtpy_esp32s3\n```\n\nNote, this assumes you've [already installed the system-level prerequisites](https://learn.adafruit.com/building-circuitpython/introduction).\nOn MacOS, this is what I do to get those:\n\n```sh\nbrew install git git-lfs python3 gettext uncrustify cmake\nbrew install gcc-arm-embedded   # (the cask, not 'arm-none-eabi-gcc')\n```\n\n## About this guide\n\n* Many of the code snippets are not considered \"well-formatted\"\nby Python linters. This guide favors brevity over strict style adherence.\n\n* The precursor to this guide is [QTPy Tricks](https://github.com/todbot/qtpy-tricks),\nwhich has similar but different (and still valid) fun things to do in CircuitPython.\n\n* This guide is the result of trying to learn Python via CircuitPython and\nfrom very enlightening discussions with John Edgar Park.  I have a good background in\nother languages, but mostly C/C++, and have\n[taught classes in Arduino](https://todbot.com/blog/bionicarduino/) for several years.\nThis informs how I've been thinking about things in this guide.\n","funding_links":[],"categories":["Python","Code"],"sub_categories":["Educational"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftodbot%2Fcircuitpython-tricks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftodbot%2Fcircuitpython-tricks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftodbot%2Fcircuitpython-tricks/lists"}