diff --git a/hw-tests/upy-minimal/meson.build b/hw-tests/upy-minimal/meson.build
index 5c0b5214906f39818ac9373a3d398f16381f759b..c2b94425ee9160e3756eb4d521aee8f3efb02158 100644
--- a/hw-tests/upy-minimal/meson.build
+++ b/hw-tests/upy-minimal/meson.build
@@ -1,5 +1,11 @@
 name = 'upy-minimal'
 
+modsrc = files(
+  'modules/buzzer.c',
+  'modules/leds.c',
+  'modules/utime.c',
+)
+
 #################################
 # MicroPython Generated Headers #
 #################################
@@ -18,14 +24,14 @@ version_h = custom_target(
 modules_h = custom_target(
   'moduledefs.h',
   output: 'moduledefs.h',
-  input: micropython_sources,
+  input: [micropython_sources, modsrc],
   command: [micropython_gen_modules, '@OUTPUT@', '@INPUT@'],
 )
 
 qstr_h = custom_target(
   'qstrdefs.generated.h',
   output: 'qstrdefs.generated.h',
-  input: [modules_h, version_h, micropython_sources],
+  input: [modules_h, version_h, micropython_sources, 'modules/qstr.h'],
   command: [micropython_gen_qstr, meson.current_source_dir(), '@OUTPUT@', '@INPUT@'],
 )
 
@@ -48,6 +54,7 @@ executable(
   'main.c',
   'uart.c',
   'systick.c',
+  modsrc,
   qstr_h,
   include_directories: micropython_includes,
   dependencies: [libcard10, max32665_startup],
diff --git a/hw-tests/upy-minimal/modules/buzzer.c b/hw-tests/upy-minimal/modules/buzzer.c
new file mode 100644
index 0000000000000000000000000000000000000000..e2c8fcaecbcaf0570121921b86c41c6dd6aea3e7
--- /dev/null
+++ b/hw-tests/upy-minimal/modules/buzzer.c
@@ -0,0 +1,44 @@
+#include "py/obj.h"
+#include "py/runtime.h"
+#include "py/builtin.h"
+#include <stdio.h>
+#include "gpio.h"
+
+static const gpio_cfg_t motor_pin = {
+	PORT_0, PIN_8, GPIO_FUNC_OUT, GPIO_PAD_NONE
+};
+
+STATIC mp_obj_t buzzer_set(mp_obj_t state_obj)
+{
+	if (state_obj == mp_const_true) {
+		printf("Buzzer ON!\n");
+		GPIO_OutSet(&motor_pin);
+	} else if (state_obj == mp_const_false){
+		printf("Buzzer OFF!\n");
+		GPIO_OutClr(&motor_pin);
+	} else {
+		mp_raise_TypeError("expected bool");
+	}
+	return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(buzzer_set_obj, buzzer_set);
+
+// Define all properties of the example 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 buzzer_module_globals_table[] = {
+	{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_buzzer) },
+	{ MP_ROM_QSTR(MP_QSTR_set), MP_ROM_PTR(&buzzer_set_obj) },
+};
+STATIC MP_DEFINE_CONST_DICT(buzzer_module_globals, buzzer_module_globals_table);
+
+// Define module object.
+const mp_obj_module_t buzzer_module = {
+	.base = { &mp_type_module },
+	.globals = (mp_obj_dict_t*)&buzzer_module_globals,
+};
+
+// Register the module to make it available in Python
+MP_REGISTER_MODULE(MP_QSTR_buzzer, buzzer_module, MODULE_BUZZER_ENABLED);
diff --git a/hw-tests/upy-minimal/modules/leds.c b/hw-tests/upy-minimal/modules/leds.c
new file mode 100644
index 0000000000000000000000000000000000000000..f7a670fe974129c8154935736bbe300e85182470
--- /dev/null
+++ b/hw-tests/upy-minimal/modules/leds.c
@@ -0,0 +1,63 @@
+#include "py/obj.h"
+#include "py/runtime.h"
+#include "py/builtin.h"
+#include <stdio.h>
+
+STATIC mp_obj_t mp_leds_set(size_t n_args, const mp_obj_t *args)
+{
+	assert (n_args == 4);
+	int led = mp_obj_get_int (args[0]);
+	int r = mp_obj_get_int (args[1]);
+	int g = mp_obj_get_int (args[2]);
+	int b = mp_obj_get_int (args[3]);
+
+	if ( (0 > led || 256 < led) &&
+	     (0 >   r || 256 <   r) &&
+	     (0 >   g || 256 <   g) &&
+	     (0 >   b || 256 <   b) )
+	{
+		mp_raise_ValueError("out of bounds");
+	}
+	printf("Set %u to (%x %x %x)\n", led, r, g, b);
+	leds_set (led, r, g, b);
+	leds_update ();
+
+	return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(leds_set_obj, 4, 4, mp_leds_set);
+
+STATIC mp_obj_t mp_leds_set_dim(mp_obj_t led_obj, mp_obj_t dim_obj)
+{
+	int led = mp_obj_get_int (led_obj);
+	int dim = mp_obj_get_int (dim_obj);
+
+	if ( (0 > led || 256 < led) &&
+	     (0 > dim || 256 < dim) )
+	{
+		mp_raise_ValueError("out of bounds");
+	}
+	leds_set_dim (led, dim);
+	return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_2(leds_set_dim_obj, mp_leds_set_dim);
+
+// Define all properties of the example 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 leds_module_globals_table[] = {
+	{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_leds) },
+	{ MP_ROM_QSTR(MP_QSTR_set), MP_ROM_PTR(&leds_set_obj) },
+	{ MP_ROM_QSTR(MP_QSTR_set_dim), MP_ROM_PTR(&leds_set_dim_obj) },
+};
+STATIC MP_DEFINE_CONST_DICT(leds_module_globals, leds_module_globals_table);
+
+// Define module object.
+const mp_obj_module_t leds_module = {
+	.base = { &mp_type_module },
+	.globals = (mp_obj_dict_t*)&leds_module_globals,
+};
+
+// Register the module to make it available in Python
+MP_REGISTER_MODULE(MP_QSTR_leds, leds_module, MODULE_LEDS_ENABLED);
diff --git a/hw-tests/upy-minimal/modules/qstr.h b/hw-tests/upy-minimal/modules/qstr.h
new file mode 100644
index 0000000000000000000000000000000000000000..0fc7efa17a9a86de2f20034d380fd4968cd9c0e7
--- /dev/null
+++ b/hw-tests/upy-minimal/modules/qstr.h
@@ -0,0 +1,12 @@
+MP_QSTR_leds
+MP_QSTR_set_dim
+MP_QSTR_utime
+MP_QSTR_sleep
+MP_QSTR_sleep_ms
+MP_QSTR_sleep_us
+MP_QSTR_ticks_ms
+MP_QSTR_ticks_us
+MP_QSTR_ticks_cpu
+MP_QSTR_ticks_add
+MP_QSTR_ticks_diff
+MP_QSTR_buzzer
diff --git a/hw-tests/upy-minimal/modules/utime.c b/hw-tests/upy-minimal/modules/utime.c
new file mode 100644
index 0000000000000000000000000000000000000000..98aca15aa1dc12af058019e82a966ce92196f789
--- /dev/null
+++ b/hw-tests/upy-minimal/modules/utime.c
@@ -0,0 +1,30 @@
+#include <stdio.h>
+#include <string.h>
+
+#include "py/runtime.h"
+#include "py/smallint.h"
+#include "py/obj.h"
+#include "lib/timeutils/timeutils.h"
+#include "extmod/utime_mphal.h"
+
+STATIC const mp_rom_map_elem_t time_module_globals_table[] = {
+    { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_utime) },
+    { MP_ROM_QSTR(MP_QSTR_sleep), MP_ROM_PTR(&mp_utime_sleep_obj) },
+    { MP_ROM_QSTR(MP_QSTR_sleep_ms), MP_ROM_PTR(&mp_utime_sleep_ms_obj) },
+    { MP_ROM_QSTR(MP_QSTR_sleep_us), MP_ROM_PTR(&mp_utime_sleep_us_obj) },
+    { MP_ROM_QSTR(MP_QSTR_ticks_ms), MP_ROM_PTR(&mp_utime_ticks_ms_obj) },
+    { MP_ROM_QSTR(MP_QSTR_ticks_us), MP_ROM_PTR(&mp_utime_ticks_us_obj) },
+    { MP_ROM_QSTR(MP_QSTR_ticks_cpu), MP_ROM_PTR(&mp_utime_ticks_cpu_obj) },
+    { MP_ROM_QSTR(MP_QSTR_ticks_add), MP_ROM_PTR(&mp_utime_ticks_add_obj) },
+    { MP_ROM_QSTR(MP_QSTR_ticks_diff), MP_ROM_PTR(&mp_utime_ticks_diff_obj) },
+};
+
+STATIC MP_DEFINE_CONST_DICT(time_module_globals, time_module_globals_table);
+
+const mp_obj_module_t mp_module_utime = {
+    .base = { &mp_type_module },
+    .globals = (mp_obj_dict_t*)&time_module_globals,
+};
+
+// Register the module to make it available in Python
+MP_REGISTER_MODULE(MP_QSTR_utime, mp_module_utime, MODULE_UTIME_ENABLED);
diff --git a/hw-tests/upy-minimal/mpconfigport.h b/hw-tests/upy-minimal/mpconfigport.h
index 39a2bdcbbb5859aee248ce6dc4475fe72d00ac66..5629a65b7a7fb48b5265fcad25b51661f27c175f 100644
--- a/hw-tests/upy-minimal/mpconfigport.h
+++ b/hw-tests/upy-minimal/mpconfigport.h
@@ -61,6 +61,7 @@
 #define MICROPY_PY_UTIME_MP_HAL     (1)
 #define MODULE_BUZZER_ENABLED       (1)
 #define MODULE_UTIME_ENABLED        (1)
+#define MODULE_LEDS_ENABLED        (1)
 
 // type definitions for the specific machine
 
diff --git a/lib/micropython/meson.build b/lib/micropython/meson.build
index 9cff4dfa70143d328cb1f541acea9614fe3d0bcd..27b847033d5497fa6eac7794f18e3beafbff504e 100644
--- a/lib/micropython/meson.build
+++ b/lib/micropython/meson.build
@@ -18,6 +18,7 @@ micropython_gen_qstr = [
 micropython_includes = include_directories(
   './micropython/',
   './micropython/lib/utils',
+  './micropython/extmod/',
 )
 
 micropython_sources = files(
@@ -139,4 +140,5 @@ micropython_additional_sources = files(
   'micropython/lib/utils/stdout_helpers.c',
   'micropython/lib/utils/pyexec.c',
   'micropython/lib/mp-readline/readline.c',
+  'micropython/extmod/utime_mphal.c',
 )