Skip to content

add support for micropython on RP2040 boards #14

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.mpy
!micropython/dist/**/*.mpy
129 changes: 129 additions & 0 deletions micropython/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# micropython distribution of Qwiic_Py package
limited support for the Qwiic_Py modules on micropython platforms

you may use pre-compiled ```*.mpy``` files to add Qwiic drivers / support to your micropython board. using the pre-compiled files saves RAM on systems with limited resources such as microcontrollers. ```*.mpy``` is a micropython specific format and will not work with regular Python.

**supported boards**
* [SparkFun Pro Micro - RP2040](https://www.sparkfun.com/products/17717)

## quick start

**installing prerequisites**
the ```/dist``` directory contains pre-compiled bytecode files. to use a driver your target board must have some prerequisite files onboard in a particular file structure:

target file | source | purpose
-----------|--------|--------
```__future__.mpy``` | ```dist/micropython/src/__future__.mpy``` | provides limited ```__future__``` module functionality
```enum.mpy``` | ```dist/micropython/src/enum.mpy``` | provides limited ```enum``` module functionality
```board.mpy``` | ```dist/micropython/src/boards/${BOARDNAME}/board.mpy``` | provides board pin definitions of the Qwiic connector + i2c port
```qwiic_i2c/__init__.mpy``` | ```dist/qwiic_i2c/__init__.mpy``` | module definition for ```import qwiic_i2c```
```qwiic_i2c/i2c_driver.mpy``` | ```dist/qwiic_i2c/qwiic_i2c/i2c_driver.mpy``` | defines an interface which driver modules utilize
```qwiic_i2c/micropython_rp2040_i2c.mpy``` | ```dist/qwiic_i2c/qwiic_i2c/micropython_rp2040_i2c.mpy``` | this is the i2c driver that actually applies to the RP2040
```qwiic_i2c/circuitpy_i2c.mpy``` | ```dist/qwiic_i2c/qwiic_i2c/circuitpy_i2c.mpy``` | needed b/c it is imported by ```__init__.mpy```
```qwiic_i2c/linux_i2c.mpy``` | ```dist/qwiic_i2c/qwiic_i2c/linux_i2c.mpy``` | needed b/c it is imported by ```__init__.mpy```

**we will use *[rshell](https://github.com/dhylands/rshell)* to copy the files onto the board** (though you can use whatever methods you like)

here's a cheat sheet for [```rshell```](https://github.com/dhylands/rshell) commands to copy the prereq files over.

**note:** use the ```pico``` branch of rshell because:
* it auto-connects to the pico board
* it appears to solve some [issues](https://github.com/dhylands/rshell/issues/144)

to do so you may need to use git. clone [rshell](https://github.com/dhylands/rshell) and switch to the ```pico``` branch. then run the main script like this:

```./rshell/rshell/main.py -a```
(the ```-a``` flag is very important)

**note:** the on-board directories are referred to by the board name. this will change depending on which board you are using. we use ```${BOARDNAME}``` to indicate a variable that contains the board name, such as ```BOARDNAME=rp2040_promicro```

### initial setup (repeat when core drivers are updated)

**first**

copy the appropriate ```board.py``` or ```board.mpy``` file exists on the board. you can use the precompiled bytecode to save memory or you can use the ```.py``` file in case you want to easily edit the file on-board.

this configures the ```${BOARDNAME}``` as well as the default I2C bus settings for Qwiic (such as which pins and peripheral module to use)

board files for supported boards are located within the ```micropython/src/boards``` directory. custom or 3rd party boards may require a specialized board file that you provide.

**then**

use the following commands (with appropriate variable substitutions) to upload the core drivers

```
cd Qwiic_Py
./${PATH_TO_RSHELL}/r.py -a
rm -rf /${BOARDNAME}/qwiic_i2c
rm -f /${BOARDNAME}/__future__.mpy
rm -f /${BOARDNAME}/enum.mpy
mkdir /${BOARDNAME}/qwiic_i2c
cp micropython/dist/micropython/src/__future__.mpy /${BOARDNAME}/__future__.mpy
cp micropython/dist/micropython/src/boards/${BOARDNAME}/board.mpy /${BOARDNAME}/board.mpy
cp micropython/dist/qwiic_i2c/qwiic_i2c/__init__.mpy /${BOARDNAME}/qwiic_i2c/__init__.mpy
cp micropython/dist/qwiic_i2c/qwiic_i2c/i2c_driver.mpy /${BOARDNAME}/qwiic_i2c/i2c_driver.mpy
cp micropython/dist/qwiic_i2c/qwiic_i2c/micropython_rp2040_i2c.mpy /${BOARDNAME}/qwiic_i2c/micropython_rp2040_i2c.mpy
cp micropython/dist/qwiic_i2c/qwiic_i2c/linux_i2c.mpy /${BOARDNAME}/qwiic_i2c/linux_i2c.mpy
cp micropython/dist/qwiic_i2c/qwiic_i2c/circuitpy_i2c.mpy /${BOARDNAME}/qwiic_i2c/circuitpy_i2c.mpy
##
```

**shortcut:**

using rshell's scripting feature (```-f FILENAME```) we can automate these steps. use the script that matches your desired boardname:
```./${PATH_TO_RSHELL}/rshell/main.py -a -f micropython/tools/qwiic-mpy/push-drivers/${BOARDNAME}.rshell```

### using a driver

the drivers (located in ```Qwiic_Py/qwiic/drivers```) are interfaces to particular sensors, actuators, and other peripheral devices that depend solely on the ```qwiic_i2c``` interface. once the prerequisites are available on your target board you can copy the bytecode driver for the device you want to control. it should exist at the root of the target's filesystem so that other code (e.g. examples) can import it as expected.

here's an example of how to add a driver to your board, using the ```qwiic_adxl313``` module.

**rshell**
```
cp micropython/dist/qwiic/drivers/qwiic_adxl313/qwiic_adxl313.mpy /${BOARDNAME}/qwiic_adxl313.mpy
```

you can now:
**repl**
```
>>> import qwiic_adxl313
```

### using examples

the examples import any necessary drivers which you should have already added by following the instructions above

**rshell**
```
cp micropython/dist/qwiic/drivers/qwiic_adxl313/examples/ex1_qwiic_adxl313_basic_readings.mpy /${BOARDNAME}/ex1_qwiic_adxl313_basic_readings.mpy
```

you can now:
**repl**
```
>>> import ex1_qwiic_adxl313_basic_readings
>>> ex1_qwiic_adxl313_basic_readings.runExample()
```

## generating precompiled .mpy files

this package includes pre-compiled files for convenience. making changes requires re-compiling the files using mpy-cross. the ```tools/qwiic-mpy/mpygen.py``` script is included to easily regenerate modified files.

for complete usage details use the help menu

```tools/qwiic-mpy/mpygen.py --help```

## supported platforms
**note:** currently only the RP2040 is supported. the distributed ```.mpy``` bytecode files have been built with flags that are specific to the RP2040. other platforms are not expected to work
(however the system is relatively flexible and adding support for other platforms in the future is a possibility)

## issues

driver | level | description
-------|-------|------------
titan gps | error | the ```pynmea2``` module does not have a upy port
micro oled | error | usage of the ```__file__``` keyword to find binary fonts fails and requires driver modification
proximity | error | ```OSError 5``` occurs on reads
tca9548a | error | the tca9548a does not comply with the driver as most other drivers are expected to work. because it is a single-register device it does not take an offset (commandCode) value for reads/writes. it is likely that a change to the api will be required to handle such cases. there is also an inconsistency with the tca9548a driver importing ```qwiic``` instead of ```qwiic_TCA9548A``` thus imparting a dependency on the full package

Binary file added micropython/dist/micropython/src/__future__.mpy
Binary file not shown.
Binary file not shown.
Binary file added micropython/dist/micropython/src/enum.mpy
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added micropython/dist/qwiic_i2c/qwiic_i2c/__init__.mpy
Binary file not shown.
Binary file not shown.
Binary file added micropython/dist/qwiic_i2c/qwiic_i2c/i2c_driver.mpy
Binary file not shown.
Binary file not shown.
Binary file not shown.
8 changes: 8 additions & 0 deletions micropython/src/__future__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# this file provides the __future__ module for micropython boards

def print_function(*args):
print(*args)

def division(*args):
print(*args)
raise Exception('division is not implemented yet for micropython qwiic. please consider writing an implementation...')
12 changes: 12 additions & 0 deletions micropython/src/boards/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# boards files
the ```board``` files are used to configure the ```${BOARDNAME}``` for rshell as well as driver-specific definitions.

for example the **micropython_rp2040_i2c** driver requires values for *qwii_id*, *qwiic_scl*, and *qwiic_sda*.

``` python
from machine import Pin
name='rp2040_promicro' # provides the name used in rshell
qwiic_id=0 # indicates which i2c peripheral to use
qwiic_scl=Pin(17) # which pin for scl
qwiic_sda=Pin(16) # which pin for sda
```
5 changes: 5 additions & 0 deletions micropython/src/boards/rp2040_promicro/board.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from machine import Pin
name='rp2040_promicro'
qwiic_id=0
qwiic_scl=Pin(17)
qwiic_sda=Pin(16)
3 changes: 3 additions & 0 deletions micropython/src/enum.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# this file provides the enum module for micropython boards

Enum = None
18 changes: 18 additions & 0 deletions micropython/tools/qwiic-mpy/cfg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"patterns": [
"qwiic_i2c/qwiic_i2c/*.py",
"qwiic/drivers/**/*.py",
"micropython/src/*.py",
"micropython/src/boards/**/*.py"
],
"exclude": [
"**/docs/conf.py",
"**/setup.py"
],
"mpy-cross": {
"flags": [
"-march=armv7m",
"-mno-unicode"
]
}
}
134 changes: 134 additions & 0 deletions micropython/tools/qwiic-mpy/mpygen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#!/usr/bin/env python
import argparse
import subprocess
import shutil
import json
import glob
import os
from braceexpand import braceexpand

class bcolors:
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKCYAN = '\033[96m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'

def main ():
print('')
if args.clean:
print('cleaning output dir: ' + args.dest)
shutil.rmtree(args.dest)
print('done')
return

print('loading config: ' + args.options)
with open(args.options, 'r') as f:
cfg = json.load(f)
print('')

print('generating exclude patterns: ')
expanded = []
for pattern in cfg['exclude']:
expanded.extend(braceexpand(pattern))
patterns = map(lambda p: os.path.join(args.root, p), expanded)
exclude = []
for pattern in patterns:
exclude.extend(glob.iglob(pattern, recursive=True))
print('excluding ' + str(len(exclude)) + ' files')
print('use -v option to see all excluded files')
for f in exclude:
verboseprint('\t', f)
print('')

def include (p): # use closure of 'exclude' for simple boolean check if a file can be included
if p in exclude:
return False
return True

print('generating .mpy files:')
expanded = []
for pattern in cfg['patterns']:
expanded.extend(braceexpand(pattern))
patterns = map(lambda p: os.path.join(args.root, p), expanded)
for pattern in patterns:
print('globbing pattern: ' + pattern)
included = filter(include, glob.iglob(pattern, recursive=True)) # use 'include' function to filter out excluded files from glob pattern

for src in included:
rel = os.path.relpath(src, os.path.commonpath([src, args.root])) # relative path from root to src file
dependent = os.path.join(args.dest, rel) # dependent filepath (under construction from relative path)
dep_base, dep_ext = os.path.splitext(dependent) # intermediate var to help build dependent filepath
dependent = dep_base + '.mpy' # dependent file path (think of 'target' file - this is the .mpy file we will generate from the src file)
enclosing_dir = os.path.dirname(dependent) # enclosing dir of dependent file

if not os.path.exists(enclosing_dir):
os.makedirs(enclosing_dir) # mkdir -p

regen = True # assume regeneration is necessary
if os.path.exists(dependent): # if dependent file exists
src_mtime = os.path.getmtime(src)
dependent_mtime = os.path.getmtime(dependent)
if dependent_mtime >= src_mtime: # and is not older than the src file
regen = False # then we do not need to regen

if regen: # use the subprocess module to run the mpy-cross cross compiler on the src file to
cmd = [args.mpy_cross] # generate the target file according to config settings from the configuration file (loaded at startup)
cmd.extend(cfg['mpy-cross']['flags'])
cmd.extend([src])
cmd.extend(['-o', dependent])

print(f'\t{bcolors.OKBLUE}regen: {bcolors.ENDC}' + dependent)
verbose_output = ['\t']
verbose_output.extend(list(map(lambda s: str(s) + ' ', cmd)))
verboseprint(*verbose_output)

results = subprocess.run( cmd, capture_output=True)
stdout = str(results.stdout)
if stdout != "b''":
verboseprint(str(stdout))
stderror = str(results.stderr)
if stderror != "b''":
print(f'{bcolors.FAIL}error: {str(results.stderr)}{bcolors.ENDC}')


# ******************************************************************************
#
# Main program flow
#
# ******************************************************************************
if __name__ == '__main__':

parser = argparse.ArgumentParser(description='generate compiled bytecode for micropython from Qwiic_Py')

parser.add_argument('-m', '--mpy-cross', dest='mpy_cross', required=True, help='path to mpy-cross executable')
parser.add_argument('-r', '--root', dest='root', required=False, default='.', help='path to root of Qwiic_Py repo')
parser.add_argument('-d', '--dest', dest='dest', required=False, default='./micropython/dist', help='path to root folder of output - contents will be overwritten')
parser.add_argument('-o', '--options', dest='options', required=False, default='./micropython/tools/qwiic-mpy/cfg.json', help='path to configuration json file')
parser.add_argument('-c', '--clean', required=False, default=0, help='clean the output directory', action='store_true')
parser.add_argument('-v', '--verbose', required=False, default=0, help='enable verbose output', action='store_true')

args = parser.parse_args()

# Create print function for verbose output if caller deems it: https://stackoverflow.com/questions/5980042/how-to-implement-the-verbose-or-v-option-into-a-script
if args.verbose:
def verboseprint(*args):
# Print each argument separately so caller doesn't need to
# stuff everything to be printed into a single string
for arg in args:
print(arg, end='', flush=True)
print()
else:
verboseprint = lambda *a: None # do-nothing function

def twopartprint(verbosestr, printstr):
if args.verbose:
print(verbosestr, end = '')

print(printstr)

main()
14 changes: 14 additions & 0 deletions micropython/tools/qwiic-mpy/push-drivers/rp2040_promicro.rshell
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# this script assumes the board is called 'rp2040_promicro'
# to use with another board you must change the boardname in commands
rm -rf /rp2040_promicro/qwiic_i2c
rm -f /rp2040_promicro/__future__.mpy
rm -f /rp2040_promicro/enum.mpy
mkdir /rp2040_promicro/qwiic_i2c
cp micropython/dist/micropython/src/__future__.mpy /rp2040_promicro/__future__.mpy
cp micropython/dist/micropython/src/enum.mpy /rp2040_promicro/enum.mpy
cp micropython/dist/micropython/src/boards/rp2040_promicro/board.mpy /rp2040_promicro/board.mpy
cp micropython/dist/qwiic_i2c/qwiic_i2c/__init__.mpy /rp2040_promicro/qwiic_i2c/__init__.mpy
cp micropython/dist/qwiic_i2c/qwiic_i2c/i2c_driver.mpy /rp2040_promicro/qwiic_i2c/i2c_driver.mpy
cp micropython/dist/qwiic_i2c/qwiic_i2c/micropython_rp2040_i2c.mpy /rp2040_promicro/qwiic_i2c/micropython_rp2040_i2c.mpy
cp micropython/dist/qwiic_i2c/qwiic_i2c/linux_i2c.mpy /rp2040_promicro/qwiic_i2c/linux_i2c.mpy
cp micropython/dist/qwiic_i2c/qwiic_i2c/circuitpy_i2c.mpy /rp2040_promicro/qwiic_i2c/circuitpy_i2c.mpy
1 change: 1 addition & 0 deletions micropython/tools/qwiic-mpy/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
braceexpand
2 changes: 1 addition & 1 deletion qwiic/drivers/qwiic_gpio
2 changes: 1 addition & 1 deletion qwiic_i2c