diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..41b6f17 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,46 @@ +name: Build MicroPython + module + +on: + push: + branches: '*' + pull_request: + branches: '*' + +jobs: + build: + strategy: + matrix: + # macos-13 is x86_64, and macos-14 is arm64 + os: [ubuntu-22.04, macos-13, macos-14] + fail-fast: false + runs-on: ${{ matrix.os }} + name: build.py ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - uses: carlosperate/arm-none-eabi-gcc-action@v1 + with: + release: 10.3-2021.10 + - name: Install CMake v3.22 via PyPI + run: python -m pip install cmake==3.28.3 + - name: Check Versions + run: | + arm-none-eabi-gcc --version + cmake --version + python --version + uname -a + - name: Initialise micro:bit MicroPython submodules + run: git -C micropython-microbit-v2 submodule update --init + - name: Build mpy-cross + run: make -C micropython-microbit-v2/lib/micropython/mpy-cross -j2 + - name: Build MicroPython with C++ module + run: make -C micropython-microbit-v2/src USER_C_MODULES=../../.. -j2 + - name: Upload hex file + uses: actions/upload-artifact@v4 + with: + name: MICROBIT-MICROPYTHON-${{ github.sha }}-${{ matrix.os }}.hex + path: micropython-microbit-v2/src/build/MICROBIT.hex diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..d629523 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "micropython-microbit-v2"] + path = micropython-microbit-v2 + url = https://github.com/microbit-foundation/micropython-microbit-v2.git diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4c7aed0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Micro:bit Educational Foundation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..cc8c687 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# micropython-module-example +A template to create a MicroPython for micro:bit V2 C/C++ module + + +## Build instructions + +Clone this repository, initialise the git submodules and build MicroPython's +mpy-cross: +``` +git submodule update --init +git -C micropython-microbit-v2 submodule update --init +make -C micropython-microbit-v2/lib/micropython/mpy-cross +``` + +Build MicroPython with the C++ module included: + +``` +make -C micropython-microbit-v2/src USER_C_MODULES=../../.. +``` + +Hex file: `micropython-microbit-v2/src/build/MICROBIT.hex` diff --git a/micropython-microbit-v2 b/micropython-microbit-v2 new file mode 160000 index 0000000..558553f --- /dev/null +++ b/micropython-microbit-v2 @@ -0,0 +1 @@ +Subproject commit 558553fce13cd68507e74c98522ef6b16ce9f6d6 diff --git a/module/micropython.mk b/module/micropython.mk new file mode 100644 index 0000000..5f5e242 --- /dev/null +++ b/module/micropython.mk @@ -0,0 +1,13 @@ +CPPEXAMPLE_MOD_DIR := $(USERMOD_DIR) + +# Add our source files to the respective variables. +SRC_USERMOD += $(CPPEXAMPLE_MOD_DIR)/src/examplemodule.c +SRC_USERMOD_CXX += $(CPPEXAMPLE_MOD_DIR)/src/example.cpp + +# Add our module directory to the include path. +CFLAGS_USERMOD += -I$(CPPEXAMPLE_MOD_DIR)/src +CXXFLAGS_USERMOD += -I$(CPPEXAMPLE_MOD_DIR)/src -std=c++11 + + +# We use C++ features so have to link against the standard library. +LDFLAGS_USERMOD += -lstdc++ diff --git a/module/src/example.cpp b/module/src/example.cpp new file mode 100644 index 0000000..92b75d0 --- /dev/null +++ b/module/src/example.cpp @@ -0,0 +1,24 @@ +extern "C" { +#include +#include + +// Here we implement the function using C++ code, but since it's +// declaration has to be compatible with C everything goes in extern "C" scope. +mp_obj_t cppfunc(mp_obj_t a_obj, mp_obj_t b_obj) { + // The following no-ops are just here to verify the static assertions used in + // the public API all compile with C++. + MP_STATIC_ASSERT_STR_ARRAY_COMPATIBLE; + if (mp_obj_is_type(a_obj, &mp_type_BaseException)) { + } + + // Prove we have (at least) C++11 features. + const auto a = mp_obj_get_int(a_obj); + const auto b = mp_obj_get_int(b_obj); + const auto sum = [&]() { + return mp_obj_new_int(a + b); + } (); + // Prove we're being scanned for QSTRs. + mp_obj_t tup[] = {sum, MP_ROM_QSTR(MP_QSTR_hellocpp)}; + return mp_obj_new_tuple(2, tup); +} +} \ No newline at end of file diff --git a/module/src/examplemodule.c b/module/src/examplemodule.c new file mode 100644 index 0000000..5d4637b --- /dev/null +++ b/module/src/examplemodule.c @@ -0,0 +1,25 @@ +#include + +// Define a Python reference to the function we'll make available. +// See example.cpp for the definition. +static MP_DEFINE_CONST_FUN_OBJ_2(cppfunc_obj, cppfunc); + +// Define all attributes of the module. +// Table entries are key/value pairs of the attribute name (a string) +// and the MicroPython object reference. +// All identifiers and strings are written as MP_QSTR_xxx and will be +// optimized to word-sized integers by the build system (interned strings). +static const mp_rom_map_elem_t cppexample_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_cppexample) }, + { MP_ROM_QSTR(MP_QSTR_cppfunc), MP_ROM_PTR(&cppfunc_obj) }, +}; +static MP_DEFINE_CONST_DICT(cppexample_module_globals, cppexample_module_globals_table); + +// Define module object. +const mp_obj_module_t cppexample_user_cmodule = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&cppexample_module_globals, +}; + +// Register the module to make it available in Python. +MP_REGISTER_MODULE(MP_QSTR_cppexample, cppexample_user_cmodule); diff --git a/module/src/examplemodule.h b/module/src/examplemodule.h new file mode 100644 index 0000000..d89384a --- /dev/null +++ b/module/src/examplemodule.h @@ -0,0 +1,5 @@ +// Include MicroPython API. +#include "py/runtime.h" + +// Declare the function we'll make available in Python as cppexample.cppfunc(). +extern mp_obj_t cppfunc(mp_obj_t a_obj, mp_obj_t b_obj);