diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt index 9afa1d69..0f1b0573 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -3,5 +3,6 @@ add_subdirectory(ltp305) add_subdirectory(st7789) add_subdirectory(msa301) add_subdirectory(rv3028) +add_subdirectory(trackball) add_subdirectory(vl53l1x) add_subdirectory(is31fl3731) diff --git a/drivers/trackball/CMakeLists.txt b/drivers/trackball/CMakeLists.txt new file mode 100644 index 00000000..0afcb041 --- /dev/null +++ b/drivers/trackball/CMakeLists.txt @@ -0,0 +1 @@ +include(trackball.cmake) \ No newline at end of file diff --git a/drivers/trackball/trackball.cmake b/drivers/trackball/trackball.cmake new file mode 100644 index 00000000..97e54570 --- /dev/null +++ b/drivers/trackball/trackball.cmake @@ -0,0 +1,10 @@ +set(DRIVER_NAME trackball) +add_library(${DRIVER_NAME} INTERFACE) + +target_sources(${DRIVER_NAME} INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/${DRIVER_NAME}.cpp) + +target_include_directories(${DRIVER_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR}) + +# Pull in pico libraries that we need +target_link_libraries(${DRIVER_NAME} INTERFACE pico_stdlib hardware_i2c) diff --git a/drivers/trackball/trackball.cpp b/drivers/trackball/trackball.cpp new file mode 100644 index 00000000..6d9d3f39 --- /dev/null +++ b/drivers/trackball/trackball.cpp @@ -0,0 +1,182 @@ +#include "trackball.hpp" +#include + +namespace pimoroni { + enum reg { + LED_RED = 0x00, + LED_GRN = 0x01, + LED_BLU = 0x02, + LED_WHT = 0x03, + + LEFT = 0x04, + RIGHT = 0x05, + UP = 0x06, + DOWN = 0x07, + SWITCH = 0x08, + + USER_FLASH = 0xD0, + FLASH_PAGE = 0xF0, + INT = 0xF9, + + CHIP_ID_L = 0xFA, + CHIP_ID_H = 0xFB, + VERSION = 0xFC, + I2C_ADDR = 0xFD, + CTRL = 0xFE, + }; + + static const uint8_t MSK_SWITCH_STATE = 0b10000000; + + enum int_mask { + MSK_INT_TRIGGERED = 0b00000001, + MSK_INT_OUT_EN = 0b00000010, + }; + + enum ctrl_mask { + MSK_CTRL_SLEEP = 0b00000001, + MSK_CTRL_RESET = 0b00000010, + MSK_CTRL_FREAD = 0b00000100, + MSK_CTRL_FWRITE = 0b00001000, + }; + + bool Trackball::init() { + bool succeeded = false; + + i2c_init(i2c, 100000); + + gpio_set_function(sda, GPIO_FUNC_I2C); + gpio_pull_up(sda); + gpio_set_function(scl, GPIO_FUNC_I2C); + gpio_pull_up(scl); + + if(interrupt != PIN_UNUSED) { + gpio_set_function(interrupt, GPIO_FUNC_SIO); + gpio_set_dir(interrupt, GPIO_IN); + gpio_pull_up(interrupt); + } + + uint16_t chip_id = ((uint16_t)i2c_reg_read_uint8(reg::CHIP_ID_H) << 8) | (uint16_t)i2c_reg_read_uint8(reg::CHIP_ID_L); + if(chip_id == CHIP_ID) { + enable_interrupt(); + succeeded = true; + } + + return succeeded; + } + + i2c_inst_t* Trackball::get_i2c() const { + return i2c; + } + + int Trackball::get_sda() const { + return sda; + } + + int Trackball::get_scl() const { + return scl; + } + + int Trackball::get_int() const { + return interrupt; + } + + void Trackball::change_address(uint8_t new_address) { + i2c_reg_write_uint8(reg::I2C_ADDR, new_address); + wait_for_flash(); + } + + void Trackball::enable_interrupt(bool use_interrupt) { + uint8_t value = i2c_reg_read_uint8(reg::INT); + value &= ~MSK_INT_OUT_EN; + if(use_interrupt) + value |= MSK_INT_OUT_EN; + + i2c_reg_write_uint8(reg::INT, value); + } + + bool Trackball::get_interrupt() { + bool value = false; + + if(interrupt != PIN_UNUSED) { + value = !gpio_get(interrupt); + } + else { + value = i2c_reg_read_uint8(reg::INT); + value &= MSK_INT_TRIGGERED; + } + return false; + } + + void Trackball::set_rgbw(uint8_t r, uint8_t g, uint8_t b, uint8_t w) { + i2c_reg_write_uint8(reg::LED_RED, r); + i2c_reg_write_uint8(reg::LED_GRN, g); + i2c_reg_write_uint8(reg::LED_BLU, b); + i2c_reg_write_uint8(reg::LED_WHT, w); + } + + void Trackball::set_red(uint8_t value) { + i2c_reg_write_uint8(reg::LED_RED, value); + } + + void Trackball::set_green(uint8_t value) { + i2c_reg_write_uint8(reg::LED_GRN, value); + } + + void Trackball::set_blue(uint8_t value) { + i2c_reg_write_uint8(reg::LED_BLU, value); + } + + void Trackball::set_white(uint8_t value) { + i2c_reg_write_uint8(reg::LED_WHT, value); + } + + Trackball::State Trackball::read() { + State state; + uint8_t sw_state; + state.left = i2c_reg_read_uint8(reg::LEFT); + state.right = i2c_reg_read_uint8(reg::RIGHT); + state.up = i2c_reg_read_uint8(reg::UP); + state.down = i2c_reg_read_uint8(reg::DOWN); + sw_state = i2c_reg_read_uint8(reg::SWITCH); + + state.sw_changed = sw_state & ~MSK_SWITCH_STATE; + state.sw_pressed = (sw_state & MSK_SWITCH_STATE) > 0; + return state; + } + + uint8_t Trackball::i2c_reg_read_uint8(uint8_t reg) { + uint8_t value; + i2c_write_blocking(i2c, address, ®, 1, true); + i2c_read_blocking(i2c, address, (uint8_t *)&value, 1, false); + return value; + } + + void Trackball::i2c_reg_write_uint8(uint8_t reg, uint8_t value) { + uint8_t buffer[2] = {reg, value}; + i2c_write_blocking(i2c, address, buffer, 2, false); + } + + void Trackball::wait_for_flash(void) { + unsigned long start_time = millis(); + while(get_interrupt()) { + if(millis() - start_time > timeout) { + printf("Timed out waiting for interrupt!\n"); + return; + } + sleep_ms(1); + } + + start_time = millis(); + while(!get_interrupt()) { + if(millis() - start_time > timeout) { + printf("Timed out waiting for interrupt!\n"); + return; + } + sleep_ms(1); + } + } + + uint32_t Trackball::millis() { + return to_ms_since_boot(get_absolute_time()); + } +} \ No newline at end of file diff --git a/drivers/trackball/trackball.hpp b/drivers/trackball/trackball.hpp new file mode 100644 index 00000000..ec43e96c --- /dev/null +++ b/drivers/trackball/trackball.hpp @@ -0,0 +1,98 @@ +#pragma once + +#include "hardware/i2c.h" +#include "hardware/gpio.h" + +namespace pimoroni { + + class Trackball { + //-------------------------------------------------- + // Constants + //-------------------------------------------------- + public: + static const uint8_t DEFAULT_I2C_ADDRESS = 0x0A; + static const uint8_t I2C_ADDRESS_ALTERNATIVE = 0x0B; + static const uint8_t DEFAULT_SDA_PIN = 20; + static const uint8_t DEFAULT_SCL_PIN = 21; + static const uint8_t DEFAULT_INT_PIN = 22; + static const uint32_t DEFAULT_TIMEOUT = 5; + static const uint8_t PIN_UNUSED = UINT8_MAX; + + private: + static const uint16_t CHIP_ID = 0xBA11; + static const uint8_t VERSION = 1; + + + //-------------------------------------------------- + // Substructures + //-------------------------------------------------- + public: + struct State { + uint8_t left; + uint8_t right; + uint8_t up; + uint8_t down; + bool sw_changed; + bool sw_pressed; + }; + + + //-------------------------------------------------- + // Variables + //-------------------------------------------------- + private: + i2c_inst_t *i2c = i2c0; + + // interface pins with our standard defaults where appropriate + int8_t address = DEFAULT_I2C_ADDRESS; + int8_t sda = DEFAULT_SDA_PIN; + int8_t scl = DEFAULT_SCL_PIN; + int8_t interrupt = DEFAULT_INT_PIN; + uint32_t timeout = DEFAULT_TIMEOUT; + + + //-------------------------------------------------- + // Constructors/Destructor + //-------------------------------------------------- + public: + Trackball() {}; + + Trackball(uint8_t address) : + address(address) {} + + Trackball(i2c_inst_t *i2c, uint8_t address, uint8_t sda, uint8_t scl, uint8_t interrupt = PIN_UNUSED, uint32_t timeout = DEFAULT_TIMEOUT) : + i2c(i2c), address(address), sda(sda), scl(scl), interrupt(interrupt), timeout(timeout) {} + + + //-------------------------------------------------- + // Methods + //-------------------------------------------------- + public: + bool init(); + + i2c_inst_t* get_i2c() const; + int get_sda() const; + int get_scl() const; + int get_int() const; + + void change_address(uint8_t new_address); + + void enable_interrupt(bool use_interrupt = true); + bool get_interrupt(); + void set_rgbw(uint8_t r, uint8_t g, uint8_t b, uint8_t w); + void set_red(uint8_t value); + void set_green(uint8_t value); + void set_blue(uint8_t value); + void set_white(uint8_t value); + State read(); + + private: + uint8_t i2c_reg_read_uint8(uint8_t reg); + void i2c_reg_write_uint8(uint8_t reg, uint8_t value); + + void wait_for_flash(); + + uint32_t millis(); + }; + +} diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index c74a1165..aa7f2580 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -2,6 +2,7 @@ add_subdirectory(breakout_dotmatrix) add_subdirectory(breakout_roundlcd) add_subdirectory(breakout_rgbmatrix5x5) add_subdirectory(breakout_matrix11x7) +add_subdirectory(breakout_trackball) add_subdirectory(pico_display) add_subdirectory(pico_unicorn) add_subdirectory(pico_unicorn_plasma) @@ -10,5 +11,6 @@ add_subdirectory(pico_explorer) add_subdirectory(pico_rgb_keypad) add_subdirectory(pico_rtc_display) add_subdirectory(pico_tof_display) +add_subdirectory(pico_trackball_display) add_subdirectory(pico_audio) add_subdirectory(pico_wireless) diff --git a/examples/breakout_trackball/CMakeLists.txt b/examples/breakout_trackball/CMakeLists.txt new file mode 100644 index 00000000..e5699de9 --- /dev/null +++ b/examples/breakout_trackball/CMakeLists.txt @@ -0,0 +1,12 @@ +set(OUTPUT_NAME trackball_demo) + +add_executable( + ${OUTPUT_NAME} + demo.cpp +) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} pico_stdlib hardware_i2c breakout_trackball) + +# create map/bin/hex file etc. +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/breakout_trackball/demo.cpp b/examples/breakout_trackball/demo.cpp new file mode 100644 index 00000000..2a1f7ed7 --- /dev/null +++ b/examples/breakout_trackball/demo.cpp @@ -0,0 +1,38 @@ +#include +#include "pico/stdlib.h" + +#include "breakout_trackball.hpp" + +using namespace pimoroni; + +static const uint8_t SENSITIVITY = 2; + + +BreakoutTrackball trackball; + +int main() { + trackball.init(); + + trackball.set_rgbw(0, 0, 0, 64); + + while(true) { + Trackball::State state = trackball.read(); + if(state.sw_pressed) + trackball.set_rgbw(0, 0, 0, 255); + else if(state.left > SENSITIVITY) + trackball.set_rgbw(0, 0, 255, 0); + else if(state.right > SENSITIVITY) + trackball.set_rgbw(255, 0, 0, 0); + else if(state.up > SENSITIVITY) + trackball.set_rgbw(255, 255, 0, 0); + else if(state.down > SENSITIVITY) + trackball.set_rgbw(0, 255, 0, 0); + else if(state.sw_changed) + trackball.set_rgbw(0, 0, 0, 64); + + + sleep_ms(20); + } + + return 0; +} diff --git a/examples/pico_trackball_display/CMakeLists.txt b/examples/pico_trackball_display/CMakeLists.txt new file mode 100644 index 00000000..e860fce5 --- /dev/null +++ b/examples/pico_trackball_display/CMakeLists.txt @@ -0,0 +1,16 @@ +set(OUTPUT_NAME trackball_display) + +add_executable( + ${OUTPUT_NAME} + demo.cpp +) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} pico_stdlib pico_explorer pico_display breakout_trackball) + +pico_enable_stdio_uart(${OUTPUT_NAME} 1) + +# create map/bin/hex file etc. +pico_add_extra_outputs(${OUTPUT_NAME}) + +#example_auto_set_url(${OUTPUT_NAME}) diff --git a/examples/pico_trackball_display/demo.cpp b/examples/pico_trackball_display/demo.cpp new file mode 100644 index 00000000..0250bad5 --- /dev/null +++ b/examples/pico_trackball_display/demo.cpp @@ -0,0 +1,168 @@ +#include +#include + +// ************************************************************************** +// Demonstrate the Pimoroni Trackball module +// Assumes that a Pico Display Pack (a 1.14inch IPS LCD screen with four +// useful buttons) is installed, and that the Trackball I2C module has +// sda, scl and int on GPIO 20, 21 and 22 +// Displays a set of coloured circles and a small cursor. +// Use the trackball to move the cursor around. The LED of the trackball +// will become the colour of whatever circle the cursosr is under. +// Pressing in on the trackball whilst over a circle will cause it to +// appear "pressed". Doing so on a "pressed" circle will make it "unpressed". +// ************************************************************************** + +// To use PicoExplorer rather than PicoDisplay, uncomment the following line +#define USE_PICO_EXPLORER 1 +// This: +// - Includes pico_explorer.hpp rather than pico_display.hpp +// - Replaces all PicoDisplay references with PicoExplorer +#ifdef USE_PICO_EXPLORER +#include "pico_explorer.hpp" +#else +#include "pico_display.hpp" +#endif +#include "breakout_trackball.hpp" + +using namespace pimoroni; + +struct TrackballColour { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t w; +}; + +#ifdef USE_PICO_EXPLORER +uint16_t buffer[PicoExplorer::WIDTH * PicoExplorer::HEIGHT]; +PicoExplorer pico_display(buffer); +const uint16_t screen_width = PicoExplorer::WIDTH; +const uint16_t screen_height = PicoExplorer::HEIGHT; +#else +uint16_t buffer[PicoDisplay::WIDTH * PicoDisplay::HEIGHT]; +PicoDisplay pico_display(buffer); +const uint16_t screen_width = PicoDisplay::WIDTH; +const uint16_t screen_height = PicoDisplay::HEIGHT; +#endif +const Point screen_centre(screen_width / 2, screen_height / 2); +const uint16_t circle_radius = std::min(screen_centre.x, screen_centre.y) / 4; +const float ring_radius_mult = 0.7f; +const uint16_t circle_border = 4; +const uint16_t cursor_radius = 8; +const uint16_t cursor_border = 4; + + +BreakoutTrackball trackball; + +const uint8_t NUM_CIRCLES = 6; +TrackballColour colour_circles[NUM_CIRCLES] = { + { 255, 0, 0, 0 }, + { 255, 255, 0, 0 }, + { 0, 255, 0, 0 }, + { 0, 255, 255, 0 }, + { 0, 0, 255, 0 }, + { 255, 0, 255, 0 }, +}; +bool circle_states[NUM_CIRCLES] = { false, false, false, false, false, false }; +bool centre_circle_state = false; + + +int main() { + int16_t x = screen_centre.x; + int16_t y = screen_centre.y; + pico_display.init(); + + trackball.init(); + + Point positions[NUM_CIRCLES]; + for(uint8_t i = 0; i < NUM_CIRCLES; i++) { + float angle_rad = (i * 2 * M_PI) / NUM_CIRCLES; + Point pos(screen_centre.x + (sinf(angle_rad) * screen_centre.x * ring_radius_mult), + screen_centre.y + (cosf(angle_rad) * screen_centre.y * ring_radius_mult)); + + positions[i] = pos; + } + + while(true) { + Trackball::State state = trackball.read(); + x = std::min(std::max(x - state.left + state.right, 0), (int)screen_width); + y = std::min(std::max(y - state.up + state.down, 0), (int)screen_height); + Point cursor_pos(x, y); + + pico_display.set_pen(0, 0, 0); + pico_display.clear(); + + //Draw a set of circles in a ring around the screen centre + for(uint8_t i = 0; i < NUM_CIRCLES; i++) { + TrackballColour col = colour_circles[i]; + + if(circle_states[i]) { + pico_display.set_pen(col.r, col.g, col.b); + pico_display.circle(positions[i], circle_radius + circle_border); + pico_display.set_pen(col.r >> 1, col.g >> 1, col.b >> 1); + pico_display.circle(positions[i], circle_radius); + } + else { + pico_display.set_pen(col.r >> 1, col.g >> 1, col.b >> 1); + pico_display.circle(positions[i], circle_radius + circle_border); + pico_display.set_pen(col.r, col.g, col.b); + pico_display.circle(positions[i], circle_radius); + } + } + + //Draw a centre circle + if(centre_circle_state) { + pico_display.set_pen(255, 255, 255); + pico_display.circle(screen_centre, circle_radius + circle_border); + pico_display.set_pen(128, 128, 128); + pico_display.circle(screen_centre, circle_radius); + } + else { + pico_display.set_pen(128, 128, 128); + pico_display.circle(screen_centre, circle_radius + circle_border); + pico_display.set_pen(255, 255, 255); + pico_display.circle(screen_centre, circle_radius); + } + + //Draw the cursor + pico_display.set_pen(0, 0, 0); + pico_display.circle(cursor_pos, cursor_radius + cursor_border); + pico_display.set_pen(212, 212, 212); + pico_display.circle(cursor_pos, cursor_radius); + + int16_t x_diff = cursor_pos.x - screen_centre.x; + int16_t y_diff = cursor_pos.y - screen_centre.y; + if((x_diff * x_diff) + (y_diff * y_diff) < circle_radius * circle_radius) { + trackball.set_rgbw(0, 0, 0, 255); + if(state.sw_changed && state.sw_pressed) + centre_circle_state = !centre_circle_state; + } + else { + bool colour_set = false; + for(uint8_t i = 0; i < NUM_CIRCLES; i++) { + uint8_t inv_i = NUM_CIRCLES - i - 1; + + TrackballColour col = colour_circles[inv_i]; + x_diff = cursor_pos.x - positions[inv_i].x; + y_diff = cursor_pos.y - positions[inv_i].y; + if((x_diff * x_diff) + (y_diff * y_diff) < circle_radius * circle_radius) { + trackball.set_rgbw(col.r, col.g, col.b, col.w); + colour_set = true; + if(state.sw_changed && state.sw_pressed) + circle_states[inv_i] = !circle_states[inv_i]; + break; + } + } + + if(!colour_set) { + trackball.set_rgbw(0, 0, 0, 0); + } + } + + // update screen + pico_display.update(); + } + + return 0; +} diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index 8bd92bf3..1b342892 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -2,6 +2,7 @@ add_subdirectory(breakout_dotmatrix) add_subdirectory(breakout_roundlcd) add_subdirectory(breakout_rgbmatrix5x5) add_subdirectory(breakout_matrix11x7) +add_subdirectory(breakout_trackball) add_subdirectory(pico_graphics) add_subdirectory(pico_display) add_subdirectory(pico_unicorn) diff --git a/libraries/breakout_trackball/CMakeLists.txt b/libraries/breakout_trackball/CMakeLists.txt new file mode 100644 index 00000000..e350df75 --- /dev/null +++ b/libraries/breakout_trackball/CMakeLists.txt @@ -0,0 +1 @@ +include(breakout_trackball.cmake) \ No newline at end of file diff --git a/libraries/breakout_trackball/breakout_trackball.cmake b/libraries/breakout_trackball/breakout_trackball.cmake new file mode 100644 index 00000000..ea8f8f17 --- /dev/null +++ b/libraries/breakout_trackball/breakout_trackball.cmake @@ -0,0 +1,11 @@ +set(LIB_NAME breakout_trackball) +add_library(${LIB_NAME} INTERFACE) + +target_sources(${LIB_NAME} INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/${LIB_NAME}.cpp +) + +target_include_directories(${LIB_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR}) + +# Pull in pico libraries that we need +target_link_libraries(${LIB_NAME} INTERFACE pico_stdlib hardware_i2c trackball) diff --git a/libraries/breakout_trackball/breakout_trackball.cpp b/libraries/breakout_trackball/breakout_trackball.cpp new file mode 100644 index 00000000..5a4a7d2a --- /dev/null +++ b/libraries/breakout_trackball/breakout_trackball.cpp @@ -0,0 +1,5 @@ +#include "breakout_trackball.hpp" + +namespace pimoroni { + +} diff --git a/libraries/breakout_trackball/breakout_trackball.hpp b/libraries/breakout_trackball/breakout_trackball.hpp new file mode 100644 index 00000000..46f10197 --- /dev/null +++ b/libraries/breakout_trackball/breakout_trackball.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include "../../drivers/trackball/trackball.hpp" + +namespace pimoroni { + + typedef Trackball BreakoutTrackball; +} diff --git a/micropython/examples/breakout_trackball/demo.py b/micropython/examples/breakout_trackball/demo.py new file mode 100644 index 00000000..e846b6c6 --- /dev/null +++ b/micropython/examples/breakout_trackball/demo.py @@ -0,0 +1,25 @@ +import time +from breakout_trackball import BreakoutTrackball + +sensitivity = 2 + +trackball = BreakoutTrackball() + +trackball.set_rgbw(0, 0, 0, 64) + +while True: + state = trackball.read() + if state[BreakoutTrackball.SW_PRESSED]: + trackball.set_rgbw(0, 0, 0, 255) + elif state[BreakoutTrackball.LEFT] > sensitivity: + trackball.set_rgbw(0, 0, 255, 0) + elif state[BreakoutTrackball.RIGHT] > sensitivity: + trackball.set_rgbw(255, 0, 0, 0) + elif state[BreakoutTrackball.UP] > sensitivity: + trackball.set_rgbw(255, 255, 0, 0) + elif state[BreakoutTrackball.DOWN] > sensitivity: + trackball.set_rgbw(0, 255, 0, 0) + elif state[BreakoutTrackball.SW_CHANGED]: + trackball.set_rgbw(0, 0, 0, 64) + + time.sleep(0.02) diff --git a/micropython/modules/breakout_trackball/breakout_trackball.c b/micropython/modules/breakout_trackball/breakout_trackball.c new file mode 100644 index 00000000..7b90cb95 --- /dev/null +++ b/micropython/modules/breakout_trackball/breakout_trackball.c @@ -0,0 +1,68 @@ +#include "breakout_trackball.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// BreakoutTrackball Class +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/***** Methods *****/ +MP_DEFINE_CONST_FUN_OBJ_KW(BreakoutTrackball_change_address_obj, 1, BreakoutTrackball_change_address); +MP_DEFINE_CONST_FUN_OBJ_KW(BreakoutTrackball_enable_interrupt_obj, 1, BreakoutTrackball_change_address); +MP_DEFINE_CONST_FUN_OBJ_1(BreakoutTrackball_get_interrupt_obj, BreakoutTrackball_get_interrupt); +MP_DEFINE_CONST_FUN_OBJ_KW(BreakoutTrackball_set_rgbw_obj, 1, BreakoutTrackball_set_rgbw); +MP_DEFINE_CONST_FUN_OBJ_KW(BreakoutTrackball_set_red_obj, 1, BreakoutTrackball_set_red); +MP_DEFINE_CONST_FUN_OBJ_KW(BreakoutTrackball_set_green_obj, 1, BreakoutTrackball_set_green); +MP_DEFINE_CONST_FUN_OBJ_KW(BreakoutTrackball_set_blue_obj, 1, BreakoutTrackball_set_blue); +MP_DEFINE_CONST_FUN_OBJ_KW(BreakoutTrackball_set_white_obj, 1, BreakoutTrackball_set_white); +MP_DEFINE_CONST_FUN_OBJ_1(BreakoutTrackball_read_obj, BreakoutTrackball_read); + +/***** Binding of Methods *****/ +STATIC const mp_rom_map_elem_t BreakoutTrackball_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_change_address), MP_ROM_PTR(&BreakoutTrackball_change_address_obj) }, + { MP_ROM_QSTR(MP_QSTR_enable_interrupt), MP_ROM_PTR(&BreakoutTrackball_enable_interrupt_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_interrupt), MP_ROM_PTR(&BreakoutTrackball_get_interrupt_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_rgbw), MP_ROM_PTR(&BreakoutTrackball_set_rgbw_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_red), MP_ROM_PTR(&BreakoutTrackball_set_red_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_green), MP_ROM_PTR(&BreakoutTrackball_set_green_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_blue), MP_ROM_PTR(&BreakoutTrackball_set_blue_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_white), MP_ROM_PTR(&BreakoutTrackball_set_white_obj) }, + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&BreakoutTrackball_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_LEFT), MP_ROM_INT(LEFT) }, + { MP_ROM_QSTR(MP_QSTR_RIGHT), MP_ROM_INT(RIGHT) }, + { MP_ROM_QSTR(MP_QSTR_UP), MP_ROM_INT(UP) }, + { MP_ROM_QSTR(MP_QSTR_DOWN), MP_ROM_INT(DOWN) }, + { MP_ROM_QSTR(MP_QSTR_SW_CHANGED), MP_ROM_INT(SW_CHANGED) }, + { MP_ROM_QSTR(MP_QSTR_SW_PRESSED), MP_ROM_INT(SW_PRESSED) }, +}; +STATIC MP_DEFINE_CONST_DICT(BreakoutTrackball_locals_dict, BreakoutTrackball_locals_dict_table); + +/***** Class Definition *****/ +const mp_obj_type_t breakout_trackball_BreakoutTrackball_type = { + { &mp_type_type }, + .name = MP_QSTR_breakout_trackball, + .print = BreakoutTrackball_print, + .make_new = BreakoutTrackball_make_new, + .locals_dict = (mp_obj_dict_t*)&BreakoutTrackball_locals_dict, +}; + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// breakout_trackball Module +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/***** Globals Table *****/ +STATIC const mp_map_elem_t breakout_trackball_globals_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_breakout_trackball) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_BreakoutTrackball), (mp_obj_t)&breakout_trackball_BreakoutTrackball_type }, +}; +STATIC MP_DEFINE_CONST_DICT(mp_module_breakout_trackball_globals, breakout_trackball_globals_table); + +/***** Module Definition *****/ +const mp_obj_module_t breakout_trackball_user_cmodule = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_breakout_trackball_globals, +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +MP_REGISTER_MODULE(MP_QSTR_breakout_trackball, breakout_trackball_user_cmodule, MODULE_BREAKOUT_TRACKBALL_ENABLED); +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/micropython/modules/breakout_trackball/breakout_trackball.cpp b/micropython/modules/breakout_trackball/breakout_trackball.cpp new file mode 100644 index 00000000..78a06c7b --- /dev/null +++ b/micropython/modules/breakout_trackball/breakout_trackball.cpp @@ -0,0 +1,287 @@ +#include "../../../pimoroni-pico/libraries/breakout_trackball/breakout_trackball.hpp" + +#define MP_OBJ_TO_PTR2(o, t) ((t *)(uintptr_t)(o)) + +// SDA/SCL on even/odd pins, I2C0/I2C1 on even/odd pairs of pins. +#define IS_VALID_SCL(i2c, pin) (((pin) & 1) == 1 && (((pin) & 2) >> 1) == (i2c)) +#define IS_VALID_SDA(i2c, pin) (((pin) & 1) == 0 && (((pin) & 2) >> 1) == (i2c)) + + +using namespace pimoroni; + +extern "C" { +#include "breakout_trackball.h" + +/***** Variables Struct *****/ +typedef struct _breakout_trackball_BreakoutTrackball_obj_t { + mp_obj_base_t base; + BreakoutTrackball *breakout; +} breakout_trackball_BreakoutTrackball_obj_t; + +/***** Print *****/ +void BreakoutTrackball_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; //Unused input parameter + breakout_trackball_BreakoutTrackball_obj_t *self = MP_OBJ_TO_PTR2(self_in, breakout_trackball_BreakoutTrackball_obj_t); + BreakoutTrackball* breakout = self->breakout; + mp_print_str(print, "BreakoutTrackball("); + + mp_print_str(print, "i2c = "); + mp_obj_print_helper(print, mp_obj_new_int((breakout->get_i2c() == i2c0) ? 0 : 1), PRINT_REPR); + + mp_print_str(print, ", sda = "); + mp_obj_print_helper(print, mp_obj_new_int(breakout->get_sda()), PRINT_REPR); + + mp_print_str(print, ", scl = "); + mp_obj_print_helper(print, mp_obj_new_int(breakout->get_scl()), PRINT_REPR); + + mp_print_str(print, ", int = "); + mp_obj_print_helper(print, mp_obj_new_int(breakout->get_int()), PRINT_REPR); + + mp_print_str(print, ")"); +} + +/***** Constructor *****/ +mp_obj_t BreakoutTrackball_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + breakout_trackball_BreakoutTrackball_obj_t *self = nullptr; + + if(n_args == 0) { + mp_arg_check_num(n_args, n_kw, 0, 0, true); + self = m_new_obj(breakout_trackball_BreakoutTrackball_obj_t); + self->base.type = &breakout_trackball_BreakoutTrackball_type; + self->breakout = new BreakoutTrackball(); + } + else if(n_args == 1) { + enum { ARG_address }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_address, MP_ARG_REQUIRED | MP_ARG_INT }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + self = m_new_obj(breakout_trackball_BreakoutTrackball_obj_t); + self->base.type = &breakout_trackball_BreakoutTrackball_type; + + self->breakout = new BreakoutTrackball(args[ARG_address].u_int); + } + else { + enum { ARG_i2c, ARG_address, ARG_sda, ARG_scl, ARG_interrupt }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_i2c, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_address, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_sda, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_scl, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_interrupt, MP_ARG_INT, {.u_int = BreakoutTrackball::PIN_UNUSED} }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // Get I2C bus. + int i2c_id = args[ARG_i2c].u_int; + if(i2c_id < 0 || i2c_id > 1) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2C(%d) doesn't exist"), i2c_id); + } + + int sda = args[ARG_sda].u_int; + if (!IS_VALID_SDA(i2c_id, sda)) { + mp_raise_ValueError(MP_ERROR_TEXT("bad SDA pin")); + } + + int scl = args[ARG_scl].u_int; + if (!IS_VALID_SCL(i2c_id, scl)) { + mp_raise_ValueError(MP_ERROR_TEXT("bad SCL pin")); + } + + self = m_new_obj(breakout_trackball_BreakoutTrackball_obj_t); + self->base.type = &breakout_trackball_BreakoutTrackball_type; + + i2c_inst_t *i2c = (i2c_id == 0) ? i2c0 : i2c1; + self->breakout = new BreakoutTrackball(i2c, args[ARG_address].u_int, sda, scl, args[ARG_interrupt].u_int); + } + + self->breakout->init(); + + return MP_OBJ_FROM_PTR(self); +} + +/***** Methods *****/ +mp_obj_t BreakoutTrackball_change_address(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_self, ARG_address }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_address, MP_ARG_REQUIRED | MP_ARG_INT }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + breakout_trackball_BreakoutTrackball_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, breakout_trackball_BreakoutTrackball_obj_t); + + self->breakout->change_address(args[ARG_address].u_int); + + return mp_const_none; +} + +mp_obj_t BreakoutTrackball_enable_interrupt(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_self, ARG_interrupt }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_interrupt, MP_ARG_BOOL, {.u_bool = false} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + breakout_trackball_BreakoutTrackball_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, breakout_trackball_BreakoutTrackball_obj_t); + + self->breakout->enable_interrupt(args[ARG_interrupt].u_bool); + + return mp_const_none; +} + +mp_obj_t BreakoutTrackball_get_interrupt(mp_obj_t self_in) { + breakout_trackball_BreakoutTrackball_obj_t *self = MP_OBJ_TO_PTR2(self_in, breakout_trackball_BreakoutTrackball_obj_t); + return mp_obj_new_bool(self->breakout->get_interrupt()); +} + + +mp_obj_t BreakoutTrackball_set_rgbw(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_self, ARG_r, ARG_g, ARG_b, ARG_w }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_r, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_g, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_b, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_w, MP_ARG_REQUIRED | MP_ARG_INT }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + breakout_trackball_BreakoutTrackball_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, breakout_trackball_BreakoutTrackball_obj_t); + + int r = args[ARG_r].u_int; + int g = args[ARG_g].u_int; + int b = args[ARG_b].u_int; + int w = args[ARG_w].u_int; + + if(r < 0 || r > 255) + mp_raise_ValueError("r out of range. Expected 0 to 255"); + else if(g < 0 || g > 255) + mp_raise_ValueError("g out of range. Expected 0 to 255"); + else if(b < 0 || b > 255) + mp_raise_ValueError("b out of range. Expected 0 to 255"); + else if(w < 0 || w > 255) + mp_raise_ValueError("w out of range. Expected 0 to 255"); + else + self->breakout->set_rgbw(r, g, b, w); + + return mp_const_none; +} + +mp_obj_t BreakoutTrackball_set_red(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_self, ARG_value }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_value, MP_ARG_REQUIRED | MP_ARG_INT }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + breakout_trackball_BreakoutTrackball_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, breakout_trackball_BreakoutTrackball_obj_t); + + int value = args[ARG_value].u_int; + + if(value < 0 || value > 255) + mp_raise_ValueError("value out of range. Expected 0 to 255"); + else + self->breakout->set_red(value); + + return mp_const_none; +} + +mp_obj_t BreakoutTrackball_set_green(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_self, ARG_value }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_value, MP_ARG_REQUIRED | MP_ARG_INT }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + breakout_trackball_BreakoutTrackball_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, breakout_trackball_BreakoutTrackball_obj_t); + + int value = args[ARG_value].u_int; + + if(value < 0 || value > 255) + mp_raise_ValueError("value out of range. Expected 0 to 255"); + else + self->breakout->set_green(value); + + return mp_const_none; +} + +mp_obj_t BreakoutTrackball_set_blue(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_self, ARG_value }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_value, MP_ARG_REQUIRED | MP_ARG_INT }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + breakout_trackball_BreakoutTrackball_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, breakout_trackball_BreakoutTrackball_obj_t); + + int value = args[ARG_value].u_int; + + if(value < 0 || value > 255) + mp_raise_ValueError("value out of range. Expected 0 to 255"); + else + self->breakout->set_blue(value); + + return mp_const_none; +} + +mp_obj_t BreakoutTrackball_set_white(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_self, ARG_value }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_value, MP_ARG_REQUIRED | MP_ARG_INT }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + breakout_trackball_BreakoutTrackball_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, breakout_trackball_BreakoutTrackball_obj_t); + + int value = args[ARG_value].u_int; + + if(value < 0 || value > 255) + mp_raise_ValueError("value out of range. Expected 0 to 255"); + else + self->breakout->set_white(value); + + return mp_const_none; +} + +mp_obj_t BreakoutTrackball_read(mp_obj_t self_in) { + breakout_trackball_BreakoutTrackball_obj_t *self = MP_OBJ_TO_PTR2(self_in, breakout_trackball_BreakoutTrackball_obj_t); + Trackball::State state = self->breakout->read(); + + mp_obj_t tuple[6]; + tuple[LEFT] = mp_obj_new_int(state.left); + tuple[RIGHT] = mp_obj_new_int(state.right); + tuple[UP] = mp_obj_new_int(state.up); + tuple[DOWN] = mp_obj_new_int(state.down); + tuple[SW_CHANGED] = mp_obj_new_int(state.sw_changed); + tuple[SW_PRESSED] = mp_obj_new_int(state.sw_pressed); + + return mp_obj_new_tuple(6, tuple); +} +} \ No newline at end of file diff --git a/micropython/modules/breakout_trackball/breakout_trackball.h b/micropython/modules/breakout_trackball/breakout_trackball.h new file mode 100644 index 00000000..2d005f0c --- /dev/null +++ b/micropython/modules/breakout_trackball/breakout_trackball.h @@ -0,0 +1,28 @@ +// Include MicroPython API. +#include "py/runtime.h" + +/***** Constants *****/ +enum { + LEFT = 0, + RIGHT, + UP, + DOWN, + SW_CHANGED, + SW_PRESSED +}; + +/***** Extern of Class Definition *****/ +extern const mp_obj_type_t breakout_trackball_BreakoutTrackball_type; + +/***** Extern of Class Methods *****/ +extern void BreakoutTrackball_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind); +extern mp_obj_t BreakoutTrackball_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args); +extern mp_obj_t BreakoutTrackball_change_address(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +extern mp_obj_t BreakoutTrackball_enable_interrupt(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +extern mp_obj_t BreakoutTrackball_get_interrupt(mp_obj_t self_in); +extern mp_obj_t BreakoutTrackball_set_rgbw(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +extern mp_obj_t BreakoutTrackball_set_red(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +extern mp_obj_t BreakoutTrackball_set_green(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +extern mp_obj_t BreakoutTrackball_set_blue(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +extern mp_obj_t BreakoutTrackball_set_white(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +extern mp_obj_t BreakoutTrackball_read(mp_obj_t self_in); \ No newline at end of file diff --git a/micropython/modules/breakout_trackball/micropython.cmake b/micropython/modules/breakout_trackball/micropython.cmake new file mode 100644 index 00000000..f4225827 --- /dev/null +++ b/micropython/modules/breakout_trackball/micropython.cmake @@ -0,0 +1,20 @@ +set(MOD_NAME breakout_trackball) +string(TOUPPER ${MOD_NAME} MOD_NAME_UPPER) +add_library(usermod_${MOD_NAME} INTERFACE) + +target_sources(usermod_${MOD_NAME} INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/${MOD_NAME}.c + ${CMAKE_CURRENT_LIST_DIR}/${MOD_NAME}.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../../libraries/${MOD_NAME}/${MOD_NAME}.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../../drivers/trackball/trackball.cpp +) + +target_include_directories(usermod_${MOD_NAME} INTERFACE + ${CMAKE_CURRENT_LIST_DIR} +) + +target_compile_definitions(usermod_${MOD_NAME} INTERFACE + -DMODULE_${MOD_NAME_UPPER}_ENABLED=1 +) + +target_link_libraries(usermod INTERFACE usermod_${MOD_NAME}) \ No newline at end of file diff --git a/micropython/modules/breakout_trackball/micropython.mk b/micropython/modules/breakout_trackball/micropython.mk new file mode 100755 index 00000000..f65631da --- /dev/null +++ b/micropython/modules/breakout_trackball/micropython.mk @@ -0,0 +1,13 @@ +set(MOD_NAME breakout_trackball) +BREAKOUT_MOD_DIR := $(USERMOD_DIR) + +# Add our source files to the respective variables. +SRC_USERMOD += $(BREAKOUT_MOD_DIR)/${MOD_NAME}.c +SRC_USERMOD_CXX += $(BREAKOUT_MOD_DIR)/${MOD_NAME}.cpp + +# Add our module directory to the include path. +CFLAGS_USERMOD += -I$(BREAKOUT_MOD_DIR) +CXXFLAGS_USERMOD += -I$(BREAKOUT_MOD_DIR) + +# We use C++ features so have to link against the standard library. +LDFLAGS_USERMOD += -lstdc++ \ No newline at end of file diff --git a/micropython/modules/micropython.cmake b/micropython/modules/micropython.cmake index c98611ef..d1426537 100644 --- a/micropython/modules/micropython.cmake +++ b/micropython/modules/micropython.cmake @@ -2,6 +2,7 @@ include(${CMAKE_CURRENT_LIST_DIR}/breakout_dotmatrix/micropython.cmake) include(${CMAKE_CURRENT_LIST_DIR}/breakout_roundlcd/micropython.cmake) include(${CMAKE_CURRENT_LIST_DIR}/breakout_rgbmatrix5x5/micropython.cmake) include(${CMAKE_CURRENT_LIST_DIR}/breakout_matrix11x7/micropython.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/breakout_trackball/micropython.cmake) include(${CMAKE_CURRENT_LIST_DIR}/pico_scroll/micropython.cmake) include(${CMAKE_CURRENT_LIST_DIR}/pico_rgb_keypad/micropython.cmake) include(${CMAKE_CURRENT_LIST_DIR}/pico_unicorn/micropython.cmake)