Maker.io main logo

CircuitPython CLUE I Ching Caster

2024-07-26 | By Adafruit Industries

License: See Original Project Displays LCD / TFT

Courtesy of Adafruit

Guide by Carter Nelson

Overview

circuitpython_output

In this guide we will show you how you can use your Adafruit ‎CLUE to consult the ancient Chinese text known as the I Ching, or ‎Book Of Change. With a simple shake of your CLUE board, you can ‎tap into the mystic forces of the universe and come up with a ‎‎"random" hexagram that forms the basis of a reading used to ‎consult the I Ching.‎

So, grab a CLUE, load the code, balance your chi, and have some fun! ‎

Ommmmmmm.‎

Adafruit CLUE - nRF52840 Express with Bluetooth LE

clue_1

I Ching Summary

text_2

The I Ching is an ancient text. As such, there are numerous ‎descriptions of what it is and what it has become. To keep things fun ‎and simple, we are going to use a very brief description here.‎

The I Ching is comprised of 64 separate entries. Each entry is ‎associated with a hexagram along with a collection of text that reads ‎like poetry. Let's cover a few details.‎

Yin and Yang

The most fundamental basis for the I Ching is the concept of Yin and ‎Yang - the two intertwined forces that drive the universe. Can't have ‎one without the other. Light and dark. Sweet and sour. Etc.‎

A common way to represent Yin and Yang is with line segments like ‎this:‎

yinyang_3

These two lines are put together to create further symbols called ‎trigrams, a stack of 3, and hexagrams, a stack of 6.‎

Trigrams and Hexagrams

The two lines representing Yin and Yang are put together in groups ‎of 3 to comprise 8 trigrams which are considered to comprise the ‎basic elements, like "Earth" and "Mountain". The trigrams are then ‎stacked in groups of two to create the 64 unique hexagrams.‎

The basis here is essentially binary, 0=Yin and 1=Yang. The prefix "tri" ‎means 3 and "hex" means 6. So, the hexagrams are like a 6 bit value ‎‎(2^6 = 64). They are read from bottom to top, so you end up with ‎something like this:‎

yinyang_4

This binary interpretation is not really used in the I Ching. But it is ‎useful for our code. We don't need any fancy storage mechanism to ‎correlate a specific hexagram with a given number from 0 to 63 (64 ‎total). We can simply use the binary representation of that number ‎to generate the hexagram. For example, let's say we somehow came ‎up with 42 as our "reading". Then we can do something like:‎

Download File

Copy Code
>>> print("0b{:06b}".format(42))
0b101010
>>>

and use each of the bits to generate a corresponding line for the ‎hexagram. Each 0 becomes a dashed line Yin and each 1 becomes a ‎solid line Yang.‎

Consulting the I Ching

For each of the 64 hexagrams there is an entry in the I Ching with a ‎bunch of vague poetics like text. These 64 entries comprise the I ‎Ching - it's basically a reference text.‎

Since the I Ching itself is nothing but a collection of 64 entries, how is ‎it used? To "consult" the I Ching you must come up with a number ‎from 1 to 64. And then you look up the I Ching entry for that ‎hexagram number. While this process should be something ‎intrinsically random, the idea is that you should be involved in this ‎randomness and thus give a "reading" that is unique to you. This is ‎referred to a cleromancy.‎

There are approaches that involve shaking a bundle of sticks, ‎flipping coins, etc. But once you have the value, you then just look it ‎up in the I Ching.‎

I Ching Hexagram Table

Of course, making sense of what the actual words are trying to tell ‎you is another story. Seek out an old mystic living in a cave on a ‎mountain for help.‎

CircuitPython on CLUE

CircuitPython is a derivative of MicroPython designed to simplify ‎experimentation and education on low-cost microcontrollers. It ‎makes it easier than ever to get prototyping by requiring no upfront ‎desktop software downloads. Simply copy and edit files on ‎the CIRCUITPY flash drive to iterate.‎

The following instructions will show you how to install CircuitPython. ‎If you've already installed CircuitPython but are looking to update it ‎or reinstall it, the same steps work for that as well!‎

Set up CircuitPython Quick Start!‎

Follow this quick step-by-step for super-fast Python power :)‎

Download the latest version of CircuitPython for CLUE from ‎circuitpython.org

Click the link above to download the latest version of ‎CircuitPython for the CLUE.‎

Download and save it to your desktop (or wherever is handy).‎

click_5

Plug your CLUE into your computer using a known-good USB cable.‎

A lot of people end up using charge-only USB cables and it is very ‎frustrating! So, make sure you have a USB cable you know is good ‎for data sync.‎

Double-click the Reset button on the top (magenta arrow) on your ‎board, and you will see the NeoPixel RGB LED (green arrow) turn ‎green. If it turns red, check the USB cable, try another USB port, ‎etc. Note: The little red LED next to the USB connector will pulse red. ‎That's ok!‎

If double-clicking doesn't work the first time, try again. Sometimes it ‎can take a few tries to get the rhythm right!‎

board_6

You will see a new disk drive appear called CLUEBOOT.‎

Drag the adafruit-circuitpython-clue-etc.uf2 file to CLUEBOOT.‎

disk_7

disk_8

The LED will flash. Then, the CLUEBOOT drive will disappear, and a ‎new disk drive called CIRCUITPY will appear.‎

If this is the first time, you're installing CircuitPython or you're doing ‎a completely fresh install after erasing the filesystem, you will have ‎two files - boot_out.txt, and code.py, and one folder - lib on ‎your CIRCUITPY drive.‎

If CircuitPython was already installed, the files present before ‎reloading CircuitPython should still be present on ‎your CIRCUITPY drive. Loading CircuitPython will not create new ‎files if there was already a CircuitPython filesystem present.‎

That's it, you're done! :)

newdisk_9

Code

The code for the CLUE I Ching Caster is listed below. You can copy it ‎out and save it to a local file. Once you've done that, you can save ‎the file as code.py in your CIRCUITPY folder and it will run ‎automatically when powered up or reset. You'll also need to grab the ‎font file christopher_done_24.bdf.‎

christopher_done_24.bdf

You CIRCUITPY folder should look something like this:‎

folder_10

Libraries

You'll need to make sure the following libraries are installed in ‎your CIRCUITPY/lib folder:‎

  • adafruit_apds9960‎
  • adafruit_bitmap_font
  • adafruit_bus_device
  • adafruit_display_text
  • adafruit_register
  • adafruit_bmp280.mpy
  • adafruit_clue.mpy
  • adafruit_lis3mdl.mpy
  • adafruit_lsm6ds.mpy
  • adafruit_sht31d.mpy
  • neopixel.mpy

You can find more info on downloading and installing libraries here:‎

CircuitPython Libraries

When you're done, your CIRCUITPY/lib folder should look like this:‎

libraries_11

Download Project Bundle

Copy Code
# SPDX-FileCopyrightText: 2020 Carter Nelson for Adafruit Industries
#
# SPDX-License-Identifier: MIT

import time
import random
import displayio
from adafruit_bitmap_font import bitmap_font
from adafruit_display_text import label
from adafruit_clue import clue

#--| User Config |-------------------------------
BACKGROUND_COLOR = 0xCFBC17
HEXAGRAM_COLOR = 0xBB0000
FONT_COLOR = 0x005500
SHAKE_THRESHOLD = 20
MELODY = ( (1000, 0.1), # (freq, duration)
(1200, 0.1),
(1400, 0.1),
(1600, 0.2))
#--| User Config |-------------------------------

# Defined in order treating each hexagram as a 6 bit value.
HEXAGRAMS = (
"EARTH", "RETURN", "THE ARMY", "PREVAILING", "MODESTY", " CRYING\nPHEASANT",
"ASCENDANCE", "PEACE", "WEARINESS", "THUNDER", "LETTING\n LOOSE",
"MARRYING\n MAIDEN", " SMALL\nEXCESS", "ABUNDANCE", "STEADFASTNESS",
" GREAT\nINJURY", "SUPPORT", "RETRENCHMENT", "WATER", "FRUGALITY",
"ADMONISHMENT", "FULFILLMENT", "THE WELL", "WAITING", "ILLNESS",
"THE CHASE", "TRAPPED", "LAKE", "CUTTING", "REVOLUTION", " GREAT\nEXCESS",
"STRIDE", "LOSS", "THE CHEEKS", "BLINDNESS", "DECREASE", "MOUNTAIN",
"DECORATION", "WORK", " BIG\nCATTLE", "ADVANCE", "BITING", "UNFULFILLMENT",
"ABANDONED", "TRAVELER", "FIRE", " THE\nCAULDRON", " GREAT\nHARVEST",
"VIEW", "INCREASE", "FLOWING", "SINCERITY", "PROGRESS", "FAMILY", "WIND",
" SMALL\nCATTLE", "OBSTRUCTION", "PROPRIETY", "THE COURT", "TREADING",
"LITTLE\n PIG", "GATHERING", "RENDEZVOUS", "HEAVEN",
)

# Grab the CLUE's display
display = clue.display

# Background fill
bg_bitmap = displayio.Bitmap(display.width, display.height, 1)
bg_palette = displayio.Palette(1)
bg_palette[0] = BACKGROUND_COLOR
background = displayio.TileGrid(bg_bitmap, pixel_shader=bg_palette)

# Hexagram setup
sprite_sheet = displayio.Bitmap(11, 4, 2)
palette = displayio.Palette(2)
palette.make_transparent(0)
palette[0] = 0x000000
palette[1] = HEXAGRAM_COLOR

for x in range(11):
sprite_sheet[x, 0] = 1 # - - 0 YIN
sprite_sheet[x, 1] = 0
sprite_sheet[x, 2] = 1 # --- 1 YANG
sprite_sheet[x, 3] = 0
sprite_sheet[5, 0] = 0

tile_grid = displayio.TileGrid(sprite_sheet, pixel_shader=palette,
width = 1,
height = 6,
tile_width = 11,
tile_height = 2)

hexagram = displayio.Group(x=60, y=15, scale=10)
hexagram.append(tile_grid)

# Hexagram name label
# font credit: https://www.instagram.com/cove703/
font = bitmap_font.load_font("/christopher_done_24.bdf")
font.load_glyphs(b'ABCDEFGHIJKLMNOPQRSTUVWXYZ')
hexname = label.Label(font, text=" "*40, color=FONT_COLOR)
# this will initially hold the "shake for reading" message
hexname.text = " SHAKE\n FOR\nREADING"
hexname.anchor_point = (0.5, 0.5)
hexname.anchored_position = (120, 120)

# Set up main display group (splash)
splash = displayio.Group()
display.root_group = splash

# Add background and text label
splash.append(background)
splash.append(hexname)

def show_hexagram(number):
for i in range(6):
tile_grid[5-i] = (number >> i) & 0x01

def show_name(number):
hexname.text = HEXAGRAMS[number]
hexname.anchored_position = (120, 180)

#===================================
# MAIN CODE
#===================================
print("shake")
# wait for shake
while not clue.shake(shake_threshold=SHAKE_THRESHOLD):
pass

# calibrate the mystic universe
x, y, z = clue.acceleration
random.seed(int(time.monotonic() + abs(x) + abs(y) + abs(z)))

# cast a reading
reading = random.randrange(64)
print("reading = ", reading, HEXAGRAMS[reading])

# play a melody
for note, duration in MELODY:
clue.play_tone(note, duration)

# prompt to show
display.auto_refresh = False
hexname.text = " GOT IT\n\nPRESS BUTTON\n TO SEE"
hexname.anchored_position = (120, 120)
display.auto_refresh = True
while not clue.button_a and not clue.button_b:
pass

# and then show it
display.auto_refresh = False
splash.append(hexagram)
show_hexagram(reading)
show_name(reading)
display.auto_refresh = True

# hold here until reset
while True:
pass

View on GitHub

Usage

Here's how to use.‎

  • Save the program as code.py
  • Press RESET
  • Should see screen that say "SHAKE FOR READING"‎

save_12

  • You should hear a little melody play
  • The screen will change to "PRESS BUTTON TO SEE"‎

press_13

  • Once you press either A or B button, you should see your ‎hexagram
  • If you want to try again, press RESET to start over

reset_14

Customizing

There are various settings you can change at the top of the code. ‎Look for these lines:‎

Download File

Copy Code
#--| User Config |-------------------------------
BACKGROUND_COLOR = 0xCFBC17
HEXAGRAM_COLOR = 0xBB0000
FONT_COLOR = 0x005500
SHAKE_THRESHOLD = 20
MELODY = ( (1000, 0.1), # (freq, duration)
(1200, 0.1),
(1400, 0.1),
(1600, 0.2))
#--| User Config |-------------------------------

You can change the colors, the shake threshold, as well as the little ‎melody that plays.‎

A Note on Translation

The original text of the I Ching was written in ancient Chinese. The ‎translation into English used here is based on the text by Kerson and ‎Rosemary Huang (ISBN 978-0894803192).‎

Drawing the Hexagram

Let's go into some detail about the approach taken to draw the ‎hexagram. This is a handy little trick that maybe you can reuse in ‎one of your projects. It uses a very simple and tiny sprite ‎sheet Bitmap to source the two basic line symbols used to generate ‎the hexagram. These are then used in a TileGrid to generate the ‎actual hexagram. To render this large on the display, the ‎displayio Group's scale feature is used.‎

Sprite Sheet

For a more in-depth overview of what a sprite sheet is, see this ‎section of the displayio guide:‎

Sprite Sheet

Look again at the two lines that comprise the hexagram. They are ‎very basic - just a line and dashed line. We can represent these with ‎a simple bitmap like this:‎

spritesheet_15

This bitmap is only 11 pixels wide by 4 pixels high. Each blue square ‎is a pixel. The two blank lines are there for padding. The width was ‎chosen for basic aesthetic reasons, generating a nice overall aspect ‎ratio for the lines. This bitmap is so simple that it is generated ‎programmatically. That's what these lines of code are doing:‎

Download File

Copy Code
for x in range(11):
sprite_sheet[x, 0] = 1 # - - 0 YIN
sprite_sheet[x, 1] = 0
sprite_sheet[x, 2] = 1 # --- 1 YANG
sprite_sheet[x, 3] = 0
sprite_sheet[5, 0] = 0

Using a Tile Grid for the Hexagram

The sprite sheet can then be used along with a TileGrid to generate ‎any of the 64 hexagrams. The TileGrid will be 1 column by 6 rows - ‎one row for each line of the hexagram. Each cell is 11 pixels wide by 2 ‎pixels high, corresponding to one of the symbols from the sprite ‎sheet.‎

Then it's a simple matter of setting the source index for each cell of ‎the TileGrid to point to one of the two available bitmaps from the ‎sprite sheet. For example, to make the 3rd line down a dashed (Yin) ‎symbol, we would do something like this:‎

index_16

To generate the entire hexagram, we just set each cell in the TileGrid ‎to a source from the sprite sheet. Like this:‎

generate_17

Some thought has been put into the arrangement of the sprite sheet. ‎By making the dashed (Yin) symbol be first, we can then generate a ‎hexagram from any integer 0 to 63 (64 total) by simply using each of ‎its bits to set the TileGrid's sources.‎

In the example above, that would be 0b100101 = 37. Remember, the ‎least significant bit is on the bottom.‎

That general approach is what is being down in ‎the show_hexagram function:‎

Download File

Copy Code
def show_hexagram(number):
for i in range(6):
tile_grid[5-i] = (number >> i) & 0x01

Using Scale to Embiggen

In its raw form, the sprite sheet is only 11 pixels wide by 4 pixels high. ‎The hexgram created from the sprite sheet ends up being 11 pixels ‎wide by 12 pixels high. That's pretty small. So how can we make it ‎bigger on the display? By using scale.‎

The scale parameter is an option for the displayio.Group class. It is ‎used as a multiplier when rendering the contents of the Group. Every ‎pixel is drawn "scale" number of times. So, if scale=3, then every 1 ‎pixel would become 3x3 pixels on the display.‎

A scale factor of 10 is used for the hexagram, which is specified in ‎this line of code:‎

Download File

Copy Code
hexagram = displayio.Group(max_size=1, x=60, y=15, scale=10)

So, the 11x12 hexagram ends up being drawn at an actual size of ‎‎110x120 pixels.‎

There's no fancy smoothing done, which can lead to a very coarse ‎look to the scaled-up bitmap. However, since our shapes are blocky ‎rectangles, this works out just fine.

制造商零件编号 4500
CLUE NRF52840 EXPRESS
Adafruit Industries LLC
Add all DigiKey Parts to Cart
TechForum

Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.

Visit TechForum