diff --git a/py/obj.h b/py/obj.h
index 839209a4054f1c25b43e2819f000d0366d6db44f..6a0cefd915aef777289ec4123af27995d18720db 100644
--- a/py/obj.h
+++ b/py/obj.h
@@ -87,9 +87,9 @@ struct _mp_obj_type_t {
     dynamic_type    instance
 
     compare_op
-    load_attr       instance class list
+    load_attr       module instance class list
     load_method     instance str gen list user
-    store_attr      instance class
+    store_attr      module instance class
     store_subscr    list dict
 
     len             str tuple list map
@@ -147,6 +147,7 @@ mp_obj_t mp_obj_new_set(int n_args, mp_obj_t *items);
 mp_obj_t mp_obj_new_bound_meth(mp_obj_t self, mp_obj_t meth);
 mp_obj_t mp_obj_new_class(struct _mp_map_t *class_locals);
 mp_obj_t mp_obj_new_instance(mp_obj_t clas);
+mp_obj_t mp_obj_new_module(qstr module_name);
 
 const char *mp_obj_get_type_str(mp_obj_t o_in);
 
@@ -238,5 +239,7 @@ mp_obj_t mp_obj_instance_load_attr(mp_obj_t self_in, qstr attr);
 void mp_obj_instance_load_method(mp_obj_t self_in, qstr attr, mp_obj_t *dest);
 void mp_obj_instance_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value);
 
-// temporary way of making C modules
-mp_obj_t mp_module_new(void);
+// module
+extern const mp_obj_type_t module_type;
+mp_obj_t mp_obj_new_module(qstr module_name);
+struct _mp_map_t *mp_obj_module_get_globals(mp_obj_t self_in);
diff --git a/py/objmodule.c b/py/objmodule.c
new file mode 100644
index 0000000000000000000000000000000000000000..addab14b7579b37fae729ca12929a2d83a92cd01
--- /dev/null
+++ b/py/objmodule.c
@@ -0,0 +1,48 @@
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <assert.h>
+
+#include "nlr.h"
+#include "misc.h"
+#include "mpconfig.h"
+#include "obj.h"
+#include "runtime.h"
+#include "map.h"
+
+typedef struct _mp_obj_module_t {
+    mp_obj_base_t base;
+    qstr name;
+    mp_map_t *globals;
+} mp_obj_module_t;
+
+void module_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in) {
+    mp_obj_module_t *self = self_in;
+    print(env, "<module '%s' from '-unknown-file-'>", qstr_str(self->name));
+}
+
+const mp_obj_type_t module_type = {
+    { &mp_const_type },
+    "module",
+    module_print, // print
+    NULL, // call_n
+    NULL, // unary_op
+    NULL, // binary_op
+    NULL, // getiter
+    NULL, // iternext
+    {{NULL, NULL},}, // method list
+};
+
+mp_obj_t mp_obj_new_module(qstr module_name) {
+    mp_obj_module_t *o = m_new_obj(mp_obj_module_t);
+    o->base.type = &module_type;
+    o->name = module_name;
+    o->globals = mp_map_new(MP_MAP_QSTR, 0);
+    return o;
+}
+
+mp_map_t *mp_obj_module_get_globals(mp_obj_t self_in) {
+    assert(MP_OBJ_IS_TYPE(self_in, &module_type));
+    mp_obj_module_t *self = self_in;
+    return self->globals;
+}
diff --git a/py/runtime.c b/py/runtime.c
index c3b3d7425941315a9a8ac8b497c945ff273a5e73..3fae61f6fe91d64432fb29159f141f1cfe848e30 100644
--- a/py/runtime.c
+++ b/py/runtime.c
@@ -779,11 +779,19 @@ mp_obj_t rt_load_attr(mp_obj_t base, qstr attr) {
     if (MP_OBJ_IS_TYPE(base, &class_type)) {
         mp_map_elem_t *elem = mp_qstr_map_lookup(mp_obj_class_get_locals(base), attr, false);
         if (elem == NULL) {
-            nlr_jump(mp_obj_new_exception_msg_2_args(rt_q_AttributeError, "'%s' object has no attribute '%s'", mp_obj_get_type_str(base), qstr_str(attr)));
+            // TODO what about generic method lookup?
+            goto no_attr;
         }
         return elem->value;
     } else if (MP_OBJ_IS_TYPE(base, &instance_type)) {
         return mp_obj_instance_load_attr(base, attr);
+    } else if (MP_OBJ_IS_TYPE(base, &module_type)) {
+        mp_map_elem_t *elem = mp_qstr_map_lookup(mp_obj_module_get_globals(base), attr, false);
+        if (elem == NULL) {
+            // TODO what about generic method lookup?
+            goto no_attr;
+        }
+        return elem->value;
     } else if (MP_OBJ_IS_OBJ(base)) {
         // generic method lookup
         mp_obj_base_t *o = base;
@@ -794,6 +802,8 @@ mp_obj_t rt_load_attr(mp_obj_t base, qstr attr) {
             }
         }
     }
+
+no_attr:
     nlr_jump(mp_obj_new_exception_msg_2_args(rt_q_AttributeError, "'%s' object has no attribute '%s'", mp_obj_get_type_str(base), qstr_str(attr)));
 }
 
@@ -832,6 +842,10 @@ void rt_store_attr(mp_obj_t base, qstr attr, mp_obj_t value) {
         mp_qstr_map_lookup(locals, attr, true)->value = value;
     } else if (MP_OBJ_IS_TYPE(base, &instance_type)) {
         mp_obj_instance_store_attr(base, attr, value);
+    } else if (MP_OBJ_IS_TYPE(base, &module_type)) {
+        // TODO CPython allows STORE_ATTR to a module, but is this the correct implementation?
+        mp_map_t *globals = mp_obj_module_get_globals(base);
+        mp_qstr_map_lookup(globals, attr, true)->value = value;
     } else {
         nlr_jump(mp_obj_new_exception_msg_2_args(rt_q_AttributeError, "'%s' object has no attribute '%s'", mp_obj_get_type_str(base), qstr_str(attr)));
     }
diff --git a/stm/Makefile b/stm/Makefile
index 33d738d78fc709431fcd5f87882045d09c8596a1..174b14c3ee945ebe034f67dd9d5c8ba3932bbe1a 100644
--- a/stm/Makefile
+++ b/stm/Makefile
@@ -72,6 +72,7 @@ PY_O = \
 	objgenerator.o \
 	objinstance.o \
 	objlist.o \
+	objmodule.o \
 	objnone.o \
 	objrange.o \
 	objset.o \
diff --git a/stm/main.c b/stm/main.c
index 8c2c96af2aa60de3c486cf55c5b3209577c49225..f16ce252d2e1ff6e47e3abadbc3a01196caafafb 100644
--- a/stm/main.c
+++ b/stm/main.c
@@ -453,7 +453,7 @@ int readline(vstr_t *line, const char *prompt) {
 }
 
 void do_repl(void) {
-    stdout_tx_str("Micro Python 0.1; STM32F405RG; PYBv3\r\n");
+    stdout_tx_str("Micro Python build <git hash> on 2/1/2014; PYBv3 with STM32F405RG\r\n");
     stdout_tx_str("Type \"help()\" for more information.\r\n");
 
     vstr_t line;
@@ -587,38 +587,53 @@ mp_obj_t pyb_gc(void) {
     return mp_const_none;
 }
 
-#define MMA_ADDR (0x4c)
+mp_obj_t pyb_gpio(int n_args, mp_obj_t *args) {
+    //assert(1 <= n_args && n_args <= 2);
 
-int mma_buf[12];
-
-mp_obj_t pyb_mma_read(void) {
-    for (int i = 0; i <= 6; i += 3) {
-        mma_buf[0 + i] = mma_buf[0 + i + 3];
-        mma_buf[1 + i] = mma_buf[1 + i + 3];
-        mma_buf[2 + i] = mma_buf[2 + i + 3];
+    const char *pin_name = qstr_str(mp_obj_get_qstr(args[0]));
+    GPIO_TypeDef *port;
+    switch (pin_name[0]) {
+        case 'A': case 'a': port = GPIOA; break;
+        case 'B': case 'b': port = GPIOB; break;
+        case 'C': case 'c': port = GPIOC; break;
+        default: goto pin_error;
     }
-
-    mma_start(MMA_ADDR, 1);
-    mma_send_byte(0);
-    mma_restart(MMA_ADDR, 0);
-    for (int i = 0; i <= 2; i++) {
-        int v = mma_read_ack() & 0x3f;
-        if (v & 0x20) {
-            v |= ~0x1f;
+    uint pin_num = 0;
+    for (const char *s = pin_name + 1; *s; s++) {
+        if (!('0' <= *s && *s <= '9')) {
+            goto pin_error;
         }
-        mma_buf[9 + i] = v;
+        pin_num = 10 * pin_num + *s - '0';
+    }
+    if (!(0 <= pin_num && pin_num <= 15)) {
+        goto pin_error;
     }
-    int jolt_info = mma_read_nack();
 
-    mp_obj_t data[4];
-    data[0] = mp_obj_new_int(jolt_info);
-    data[1] = mp_obj_new_int(mma_buf[2] + mma_buf[5] + mma_buf[8] + mma_buf[11]);
-    data[2] = mp_obj_new_int(mma_buf[1] + mma_buf[4] + mma_buf[7] + mma_buf[10]);
-    data[3] = mp_obj_new_int(mma_buf[0] + mma_buf[3] + mma_buf[6] + mma_buf[9]);
+    if (n_args == 1) {
+        // get pin
+        if ((port->IDR & (1 << pin_num)) != (uint32_t)Bit_RESET) {
+            return MP_OBJ_NEW_SMALL_INT(1);
+        } else {
+            return MP_OBJ_NEW_SMALL_INT(0);
+        }
+    } else {
+        // set pin
+        if (rt_is_true(args[1])) {
+            // set pin high
+            port->BSRRL = 1 << pin_num;
+        } else {
+            // set pin low
+            port->BSRRH = 1 << pin_num;
+        }
+        return mp_const_none;
+    }
 
-    return rt_build_tuple(4, data); // items in reverse order in data
+pin_error:
+    nlr_jump(mp_obj_new_exception_msg_1_arg(rt_q_ValueError, "pin %s does not exist", pin_name));
 }
 
+MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_gpio_obj, 1, 2, pyb_gpio);
+
 mp_obj_t pyb_hid_send_report(mp_obj_t arg) {
     mp_obj_t *items = mp_obj_get_array_fixed_n(arg, 4);
     uint8_t data[4];
@@ -855,7 +870,7 @@ soft_reset:
     {
         rt_store_name(qstr_from_str_static("help"), rt_make_function_0(pyb_help));
 
-        mp_obj_t m = mp_module_new();
+        mp_obj_t m = mp_obj_new_module(qstr_from_str_static("pyb"));
         rt_store_attr(m, qstr_from_str_static("info"), rt_make_function_0(pyb_info));
         rt_store_attr(m, qstr_from_str_static("sd_test"), rt_make_function_0(pyb_sd_test));
         rt_store_attr(m, qstr_from_str_static("stop"), rt_make_function_0(pyb_stop));
@@ -869,7 +884,9 @@ soft_reset:
         rt_store_attr(m, qstr_from_str_static("switch"), rt_make_function_0(pyb_sw));
         rt_store_attr(m, qstr_from_str_static("servo"), rt_make_function_2(pyb_servo_set));
         rt_store_attr(m, qstr_from_str_static("pwm"), rt_make_function_2(pyb_pwm_set));
-        rt_store_attr(m, qstr_from_str_static("accel"), rt_make_function_0(pyb_mma_read));
+        rt_store_attr(m, qstr_from_str_static("accel"), (mp_obj_t)&pyb_mma_read_obj);
+        rt_store_attr(m, qstr_from_str_static("mma_read"), (mp_obj_t)&pyb_mma_read_all_obj);
+        rt_store_attr(m, qstr_from_str_static("mma_mode"), (mp_obj_t)&pyb_mma_write_mode_obj);
         rt_store_attr(m, qstr_from_str_static("hid"), rt_make_function_1(pyb_hid_send_report));
         rt_store_attr(m, qstr_from_str_static("time"), rt_make_function_0(pyb_rtc_read));
         rt_store_attr(m, qstr_from_str_static("uout"), rt_make_function_1(pyb_usart_send));
@@ -879,6 +896,7 @@ soft_reset:
         rt_store_attr(m, qstr_from_str_static("Led"), rt_make_function_1(pyb_Led));
         rt_store_attr(m, qstr_from_str_static("Servo"), rt_make_function_1(pyb_Servo));
 	rt_store_attr(m, qstr_from_str_static("I2C"), rt_make_function_2(pyb_I2C));
+        rt_store_attr(m, qstr_from_str_static("gpio"), (mp_obj_t)&pyb_gpio_obj);
         rt_store_name(qstr_from_str_static("pyb"), m);
 
         rt_store_name(qstr_from_str_static("open"), rt_make_function_2(pyb_io_open));
@@ -985,56 +1003,6 @@ soft_reset:
     if (first_soft_reset) {
         // init and reset address to zero
         mma_init();
-        mma_start(MMA_ADDR, 1);
-        mma_send_byte(0);
-        mma_stop();
-
-        /*
-        // read and print all 11 registers
-        mma_start(MMA_ADDR, 1);
-        mma_send_byte(0);
-        mma_restart(MMA_ADDR, 0);
-        for (int i = 0; i <= 0xa; i++) {
-            int data;
-            if (i == 0xa) {
-                data = mma_read_nack();
-            } else {
-                data = mma_read_ack();
-            }
-            printf(" %02x", data);
-        }
-        printf("\n");
-        */
-
-        // put into active mode
-        mma_start(MMA_ADDR, 1);
-        mma_send_byte(7); // mode
-        mma_send_byte(1); // active mode
-        mma_stop();
-
-        /*
-        // infinite loop to read values
-        for (;;) {
-            sys_tick_delay_ms(500);
-
-            mma_start(MMA_ADDR, 1);
-            mma_send_byte(0);
-            mma_restart(MMA_ADDR, 0);
-            for (int i = 0; i <= 3; i++) {
-                int data;
-                if (i == 3) {
-                    data = mma_read_nack();
-                    printf(" %02x\n", data);
-                } else {
-                    data = mma_read_ack() & 0x3f;
-                    if (data & 0x20) {
-                        data |= ~0x1f;
-                    }
-                    printf(" % 2d", data);
-                }
-            }
-        }
-        */
     }
 
     // turn boot-up LED off
@@ -1220,9 +1188,9 @@ soft_reset:
             } else {
                 data[0] = 0x00;
             }
-            mma_start(MMA_ADDR, 1);
+            mma_start(0x4c /* MMA_ADDR */, 1);
             mma_send_byte(0);
-            mma_restart(MMA_ADDR, 0);
+            mma_restart(0x4c /* MMA_ADDR */, 0);
             for (int i = 0; i <= 1; i++) {
                 int v = mma_read_ack() & 0x3f;
                 if (v & 0x20) {
diff --git a/stm/mma.c b/stm/mma.c
index 937da1cf7e9abb59949a349c50a91b2d27437305..f5409ae1202f9b2275b29624d67657f6b3d86b70 100644
--- a/stm/mma.c
+++ b/stm/mma.c
@@ -5,9 +5,14 @@
 #include <stm32f4xx_gpio.h>
 
 #include "misc.h"
+#include "mpconfig.h"
 #include "systick.h"
+#include "obj.h"
+#include "runtime.h"
 #include "mma.h"
 
+#define MMA_ADDR (0x4c)
+
 void mma_init(void) {
     RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; // enable I2C1
 
@@ -67,6 +72,58 @@ void mma_init(void) {
     sys_tick_delay_ms(20);
 
     // set START bit in CR1 to generate a start cond!
+
+    // init the chip via I2C commands
+    mma_start(MMA_ADDR, 1);
+    mma_send_byte(0);
+    mma_stop();
+
+    /*
+    // read and print all 11 registers
+    mma_start(MMA_ADDR, 1);
+    mma_send_byte(0);
+    mma_restart(MMA_ADDR, 0);
+    for (int i = 0; i <= 0xa; i++) {
+        int data;
+        if (i == 0xa) {
+            data = mma_read_nack();
+        } else {
+            data = mma_read_ack();
+        }
+        printf(" %02x", data);
+    }
+    printf("\n");
+    */
+
+    // put into active mode
+    mma_start(MMA_ADDR, 1);
+    mma_send_byte(7); // mode
+    mma_send_byte(1); // active mode
+    mma_stop();
+
+    /*
+    // infinite loop to read values
+    for (;;) {
+        sys_tick_delay_ms(500);
+
+        mma_start(MMA_ADDR, 1);
+        mma_send_byte(0);
+        mma_restart(MMA_ADDR, 0);
+        for (int i = 0; i <= 3; i++) {
+            int data;
+            if (i == 3) {
+                data = mma_read_nack();
+                printf(" %02x\n", data);
+            } else {
+                data = mma_read_ack() & 0x3f;
+                if (data & 0x20) {
+                    data |= ~0x1f;
+                }
+                printf(" % 2d", data);
+            }
+        }
+    }
+    */
 }
 
 static uint32_t i2c_get_sr(void) {
@@ -179,3 +236,65 @@ void mma_stop(void) {
     // send stop condition
     I2C1->CR1 |= I2C_CR1_STOP;
 }
+
+/******************************************************************************/
+/* Micro Python bindings                                                      */
+
+int mma_buf[12];
+
+mp_obj_t pyb_mma_read(void) {
+    for (int i = 0; i <= 6; i += 3) {
+        mma_buf[0 + i] = mma_buf[0 + i + 3];
+        mma_buf[1 + i] = mma_buf[1 + i + 3];
+        mma_buf[2 + i] = mma_buf[2 + i + 3];
+    }
+
+    mma_start(MMA_ADDR, 1);
+    mma_send_byte(0);
+    mma_restart(MMA_ADDR, 0);
+    for (int i = 0; i <= 2; i++) {
+        int v = mma_read_ack() & 0x3f;
+        if (v & 0x20) {
+            v |= ~0x1f;
+        }
+        mma_buf[9 + i] = v;
+    }
+    int jolt_info = mma_read_nack();
+
+    mp_obj_t data[4];
+    data[0] = mp_obj_new_int(jolt_info);
+    data[1] = mp_obj_new_int(mma_buf[2] + mma_buf[5] + mma_buf[8] + mma_buf[11]);
+    data[2] = mp_obj_new_int(mma_buf[1] + mma_buf[4] + mma_buf[7] + mma_buf[10]);
+    data[3] = mp_obj_new_int(mma_buf[0] + mma_buf[3] + mma_buf[6] + mma_buf[9]);
+
+    return rt_build_tuple(4, data); // items in reverse order in data
+}
+
+MP_DEFINE_CONST_FUN_OBJ_0(pyb_mma_read_obj, pyb_mma_read);
+
+mp_obj_t pyb_mma_read_all(void) {
+    mp_obj_t data[11];
+    mma_start(MMA_ADDR, 1);
+    mma_send_byte(0);
+    mma_restart(MMA_ADDR, 0);
+    for (int i = 0; i <= 9; i++) {
+        data[10 - i] = mp_obj_new_int(mma_read_ack());
+    }
+    data[0] = mp_obj_new_int(mma_read_nack());
+
+    return rt_build_tuple(11, data); // items in reverse order in data
+}
+
+MP_DEFINE_CONST_FUN_OBJ_0(pyb_mma_read_all_obj, pyb_mma_read_all);
+
+mp_obj_t pyb_mma_write_mode(mp_obj_t o_int, mp_obj_t o_mode) {
+    mma_start(MMA_ADDR, 1);
+    mma_send_byte(6); // start at int
+    mma_send_byte(mp_obj_get_int(o_int));
+    mma_send_byte(mp_obj_get_int(o_mode));
+    mma_stop();
+    return mp_const_none;
+}
+
+MP_DEFINE_CONST_FUN_OBJ_2(pyb_mma_write_mode_obj, pyb_mma_write_mode);
+
diff --git a/stm/mma.h b/stm/mma.h
index f43d18295475b639014d00c1ca4feaaf7dcd0435..0bdf5d1009393c6703b31c9b820617bdd330fdb7 100644
--- a/stm/mma.h
+++ b/stm/mma.h
@@ -5,3 +5,7 @@ void mma_send_byte(uint8_t data);
 uint8_t mma_read_ack(void);
 uint8_t mma_read_nack(void);
 void mma_stop(void);
+
+MP_DECLARE_CONST_FUN_OBJ(pyb_mma_read_obj);
+MP_DECLARE_CONST_FUN_OBJ(pyb_mma_read_all_obj);
+MP_DECLARE_CONST_FUN_OBJ(pyb_mma_write_mode_obj);
diff --git a/unix-cpy/Makefile b/unix-cpy/Makefile
index ef1999a1b0016467167b09e0f9dd6974313e02e7..9399a765c3aa131ea2e7a213e8f9e8dffbd63408 100644
--- a/unix-cpy/Makefile
+++ b/unix-cpy/Makefile
@@ -39,6 +39,7 @@ PY_O = \
 	objgenerator.o \
 	objinstance.o \
 	objlist.o \
+	objmodule.o \
 	objnone.o \
 	objrange.o \
 	objset.o \
diff --git a/unix/Makefile b/unix/Makefile
index 492962c8c74e7a9edba88d517d7cff0ebaac9329..b8955d11a834e3cfa172acd4d8555edfd65b77c8 100644
--- a/unix/Makefile
+++ b/unix/Makefile
@@ -46,6 +46,7 @@ PY_O = \
 	objgenerator.o \
 	objinstance.o \
 	objlist.o \
+	objmodule.o \
 	objnone.o \
 	objrange.o \
 	objset.o \