The code is long enough (around 400 lines) that a line by line explanation would be too verbose. Here are some high level notes to give you an overview:

class AngleConvert: ...

The AngleConvert class helps implement the "degrees / radians / gradians" mode common on scientific calculators. It wraps each of the 6 trig functions, converting to/from radians as necessary. To improve the precision of the result, these steps are carried out with extra digits of precision using the `extraprec`

decorator function.

getcontext().prec = 14 getcontext().Emax = 99 getcontext().Emin = -99

Sets the default precision (number of decimal places), minimum and maximum exponents. These values were chosen so that any number should fit on the screen without being cut off, but any of these values can be increased or decreased if desired.

class MatrixKeypadBase: ... class MatrixKeypad: ... class LayerSelect: ... ... layers = ( ( ('^', 'l', 'r', LS1), ... ), ... )

These classes implement a matrix keyboard, including layer selection. The "layers" tuple has "layer 0" (normal layer) followed by "layer 1" (alternate layer). Except for the special values `LS1`

(layer shift 1) and `LL0`

(layer lock 0), each one specifies a (possibly empty) string that is handled later by the the dictionary of `ops`

or by the main `loop`

.

class Impl: ...

The "Impl" (implementation) class has the details of how to interact with the keyboard matrix and update the display. The calculator program originally ran on a PC in a terminal window, and this class is a vestige of trying to support for both CircuitPython and standard Python3 within a single program.

def do_op(arity, fun): ... ops = { '\'': (1, lambda x: -x), '\\': (2, lambda x, y: x/y), # keypad: SHIFT+/ ... '@': angleconvert.next_state, ... }

`ops`

is a dictionary where the **key** is a character produced by the key matrix and the **value** is either a **callable** or a tuple of **(arity, callable)**.

When an operation is a **callable**, it has to manage the stack itself. When it's a tuple, then the first number in the tuple (called the **arity**) specifies how many arguments the function expects. That number of arguments are passed as arguments to the function. If the function succeeds, the arguments are removed from the stack. Then, the return value, if it's not **None**, is put back on the stack.

def pstack(msg): ...

The pstack function updates the screen.

def loop(): ...

The loop function runs continuously, waiting for key to be entered. Some keys are processed specially (like the digits, "./E", backspace, and enter/dup), other keys are handled by looking up the operation in `ops`

.

### Adding new functions

To add a new function, you will need several things:

- The implementation of the mathematical function. This can be a function that you write, but for this example we will use the existing function
`Decimal.log10`

- The physical key location you choose for the function. Let's choose the ALT function of the key that is normally "+"
- The character you use to represent it. Let's choose "O" (capital Oscar), perhaps standing for the "o" of
**log10**but mostly because it is not used yet. - The arity of the function.
**log10**takes a single number (x), so its arity is 1

With all these pieces of information, we can start to modify the code. First, we have to modify the keymap, called **layers**:

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'), ( '', '', '', 'O'), # modify this line, changing '' to 'O' ('=', '@', BS, '~') ), )

Next, add `'O': Decimal.log10,`

to `ops`

:

ops = { 'O': (1, Decimal.log10), # Add this line '\'': (1, lambda x: -x), ... # Keep the other lines as-is }

When you save the file, CircuitPython will automatically restart. Type "1000 ALT +" and you should see the result "3", because **1000=10^3**.

Here are some other ideas about functions to add to the calculator:

- Unit conversions like inches to millimeters
- Constants like π or e. π can be computed as
`Decimal('1').atan() * 4`

, and e can be computed as`Decimal('1').exp()`

- Engineering functions, statistical functions, etc

## Other ideas for customization and improvement

- The Feather nRF52840 can also act as a BLE (bluetooth) keyboard. Convert the calculator to run from a rechargeable battery and make it paste over Bluetooth instead of or in addition to USB.
- Using the jepler_udecimal library, implement a standard "infix" calculator instead.
- Add the ability to enter a formula from the keypad and create a CircuitPython graphing calculator