Step 1 - Install CircuitPython
This guide requires CircuitPython be installed, click the button below to learn how to do that and install the latest version of CircuitPython.
Are you new to using CircuitPython? No worries, there is a full getting started guide here.
Adafruit suggests using the Mu editor to edit your code and have an interactive REPL in CircuitPython. You can learn about Mu and installation in this tutorial.
Step 2 - Install Libraries
First make sure you are running the latest version of Adafruit CircuitPython for the Feather nRF52840. You'll also need to install several libraries on your Feather:
- jepler_udecimal (from the Community Bundle)
- adafruit_hid
- adafruit_display_text
Carefully follow the steps to find and install these libraries from Adafruit's CircuitPython Library Bundle. Our CircuitPython starter guide has a great page on how to install libraries from the bundle.
Step 3 - Install code
Use the "Project Zip" link to download the rest of the files needed for the calculator, then unzip it inside the CIRCUITPY drive.
# SPDX-FileCopyrightText: 2020 Jeff Epler for Adafruit Industries
#
# SPDX-License-Identifier: MIT
# pylint: disable=redefined-outer-name,no-self-use,broad-except,try-except-raise,too-many-branches,too-many-statements,unused-import
import gc
import time
from adafruit_display_text.label import Label
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
from jepler_udecimal import Decimal, getcontext, localcontext
import jepler_udecimal.utrig # Needed for trig functions in Decimal
import board
import digitalio
import displayio
import framebufferio
import microcontroller
import sharpdisplay
import terminalio
try:
import usb_hid
except ImportError:
usb_hid = None
# Initialize the display, cleaning up after a display from the previous
# run if necessary
displayio.release_displays()
framebuffer = sharpdisplay.SharpMemoryFramebuffer(board.SPI(), board.RX, 400, 240)
display = framebufferio.FramebufferDisplay(framebuffer, auto_refresh=False)
def extraprec(add=8, num=0, den=1):
def inner(fn):
def wrapper(*args, **kw):
with localcontext() as ctx:
ctx.prec = ctx.prec + add + (ctx.prec * num + den - 1) // den
result = fn(*args, **kw)
return +result
return wrapper
return inner
class AngleConvert:
def __init__(self):
self.state = 0
def next_state(self):
self.state = (self.state + 1) % 3
def __str__(self):
return "DRG"[self.state]
@property
def factor(self):
return [360, None, 400][self.state]
def from_user(self, x):
factor = self.factor
if factor is None:
return x
x = x.remainder_near(factor)
pi_4 = Decimal("1.0").atan()
return x * pi_4 * 8 / factor
def to_user(self, x):
factor = self.factor
if factor is None:
return x
pi_4 = Decimal("1.0").atan()
return x * factor / pi_4 / 8
@extraprec(num=1)
def cos(self, x):
return self.from_user(x).cos()
@extraprec(num=1)
def sin(self, x):
return self.from_user(x).sin()
@extraprec(num=1)
def tan(self, x):
return self.from_user(x).tan()
@extraprec(num=1)
def acos(self, x):
return self.to_user(x.acos())
@extraprec(num=1)
def asin(self, x):
return self.to_user(x.asin())
@extraprec(num=1)
def atan(self, x):
return self.to_user(x.atan())
getcontext().prec = 14
getcontext().Emax = 99
getcontext().Emin = -99
def get_pin(x):
if isinstance(x, microcontroller.Pin):
return digitalio.DigitalInOut(x)
return x
class MatrixKeypadBase:
def __init__(self, row_pins, col_pins):
self.row_pins = [get_pin(p) for p in row_pins]
self.col_pins = [get_pin(p) for p in col_pins]
self.old_state = set()
self.state = set()
for r in self.row_pins:
r.switch_to_input(digitalio.Pull.UP)
for c in self.col_pins:
c.switch_to_output(False)
def scan(self):
self.old_state = self.state
state = set()
for c, cp in enumerate(self.col_pins):
cp.switch_to_output(False)
for r, rp in enumerate(self.row_pins):
if not rp.value:
state.add((r, c))
cp.switch_to_input()
self.state = state
return state
def rising(self):
old_state = self.old_state
new_state = self.state
return new_state - old_state
class LayerSelect:
def __init__(self, idx=1, next_layer=None):
self.idx = idx
self.next_layer = next_layer or self
LL0 = LayerSelect(0)
LL1 = LayerSelect(1)
LS1 = LayerSelect(1, LL0)
class MatrixKeypad:
def __init__(self, row_pins, col_pins, layers):
self.base = MatrixKeypadBase(row_pins, col_pins)
self.layers = layers
self.layer = LL0
self.pending = []
def getch(self):
if not self.pending:
self.base.scan()
for r, c in self.base.rising():
op = self.layers[self.layer.idx][r][c]
if isinstance(op, LayerSelect):
self.layer = op
else:
self.pending.extend(op)
self.layer = self.layer.next_layer
if self.pending:
return self.pending.pop(0)
return None
col_pins = (board.D10, board.D9, board.D6, board.TX)
row_pins = (board.A0, board.A1, board.A2, board.A3, board.A4, board.A5)
BS = '\x7f'
CR = '\n'
layers = (
(
('^', 'l', 'r', LS1),
('s', 'c', 't', '/'),
('7', '8', '9', '*'),
('4', '5', '6', '-'),
('1', '2', '3', '+'),
('0', '.', BS, CR)
),
(
('v', 'L', 'R', LL0),
('S', 'C', 'T', 'N'),
( '', '', '', ''),
( '', '', '', 'n'),
( '', '', '', ''),
('=', '@', BS, '~')
),
)
class Impl:
def __init__(self):
# incoming keypad
self.keypad = MatrixKeypad(row_pins, col_pins, layers)
# outgoing keypresses
self.keyboard = None
self.keyboard_layout = None
g = displayio.Group()
self.labels = labels = []
labels.append(Label(terminalio.FONT, scale=2, color=0))
labels.append(Label(terminalio.FONT, scale=3, color=0))
labels.append(Label(terminalio.FONT, scale=3, color=0))
labels.append(Label(terminalio.FONT, scale=3, color=0))
labels.append(Label(terminalio.FONT, scale=3, color=0))
labels.append(Label(terminalio.FONT, scale=3, color=0))
for li in labels:
g.append(li)
bitmap = displayio.Bitmap((display.width + 126)//127, (display.height + 126)//127, 1)
palette = displayio.Palette(1)
palette[0] = 0xffffff
tile_grid = displayio.TileGrid(bitmap, pixel_shader=palette)
bg = displayio.Group(scale=127)
bg.append(tile_grid)
g.insert(0, bg)
display.root_group = g
def getch(self):
while True:
time.sleep(.02)
c = self.keypad.getch()
if c is not None:
return c
def setline(self, i, text):
li = self.labels[i]
text = text[:31] or " "
if text == li.text:
return
li.text = text
li.anchor_point = (0,0)
li.anchored_position = (1, max(1, 41 * i - 7) + 6)
def refresh(self):
pass
def paste(self, text):
if self.keyboard is None:
if usb_hid:
self.keyboard = Keyboard(usb_hid.devices)
self.keyboard_layout = KeyboardLayoutUS(self.keyboard)
else:
return
if self.keyboard_layout is None:
raise ValueError("USB HID not available")
text = str(text)
self.keyboard_layout.write(text)
raise RuntimeError("Pasted")
def start_redraw(self):
display.auto_refresh = False
def end_redraw(self):
display.auto_refresh = True
def end(self):
pass
impl = Impl()
stack = []
entry = []
def do_op(arity, fun):
if arity > len(stack):
return "underflow"
res = fun(*stack[-arity:][::-1])
del stack[-arity:]
if isinstance(res, list):
stack.extend(res)
elif res is not None:
stack.append(res)
return None
angleconvert = AngleConvert()
def roll():
stack[:] = stack[1:] + stack[:1]
def rroll():
stack[:] = stack[-1:] + stack[:-1]
def swap():
stack[-2:] = [stack[-1], stack[-2]]
ops = {
'\'': (1, lambda x: -x),
'\\': (2, lambda x, y: x/y),
'#': (2, lambda x, y: y**(1/x)),
'*': (2, lambda x, y: y*x),
'+': (2, lambda x, y: y+x),
'-': (2, lambda x, y: y-x),
'/': (2, lambda x, y: y/x),
'^': (2, lambda x, y: y**x),
'v': (2, lambda x, y: y**(1/x)),
'_': (2, lambda x, y: x-y),
'@': angleconvert.next_state,
'C': (1, angleconvert.acos),
'c': (1, angleconvert.cos),
'L': (1, Decimal.exp),
'l': (1, Decimal.ln),
'q': (1, lambda x: x**.5),
'r': roll,
'R': rroll,
'S': (1, angleconvert.asin),
's': (1, angleconvert.sin),
'~': swap,
'T': (1, angleconvert.atan),
't': (1, angleconvert.tan),
'n': (1, lambda x: -x),
'N': (1, lambda x: 1/x),
'=': (1, impl.paste)
}
def pstack(msg):
impl.setline(0, f'[{angleconvert}] {msg}')
for i, reg in enumerate("TZYX"):
if len(stack) > 3-i:
val = stack[-4+i]
else:
val = ""
impl.setline(1+i, f"{reg} {val}")
def loop():
impl.start_redraw()
pstack(f'{gc.mem_free()} RPN bytes free')
impl.setline(5, "> " + "".join(entry) + "_")
impl.refresh()
impl.end_redraw()
while True:
do_pstack = False
do_pentry = False
message = ''
c = impl.getch()
if c in '\x7f\x08':
if entry:
entry.pop()
do_pentry = True
elif stack:
stack.pop()
do_pstack = True
if c == '\x1b':
del entry[:]
do_pentry = True
elif c in '0123456789.eE':
if c == '.' and '.' in entry:
c = 'e'
entry.append(c)
do_pentry = True
elif c == '\x04':
break
elif c in ' \n':
if entry:
try:
stack.append(Decimal("".join(entry)))
except Exception as e:
message = str(e)
del entry[:]
elif c == '\n' and stack:
stack.append(stack[-1])
do_pstack = True
elif c in ops:
if entry:
try:
stack.append(Decimal("".join(entry)))
except Exception as e:
message = str(e)
del entry[:]
op = ops.get(c)
try:
if callable(op):
message = op() or ''
else:
message = do_op(*op) or ''
except (KeyboardInterrupt, SystemExit):
raise
except Exception as e:
message = str(e)
do_pstack = True
impl.start_redraw()
if do_pstack:
pstack(message)
do_pentry = True
if do_pentry:
impl.setline(5, "> " + "".join(entry) + "_")
if do_pentry or do_pstack:
impl.refresh()
impl.end_redraw()
try:
loop()
finally:
impl.end()
Page last edited January 22, 2025
Text editor powered by tinymce.