diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..6b51bde
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,62 @@
+language: c
+sudo: false
+env:
+  # Target commit for https://github.com/pfalcon/esp-open-sdk/
+  OPENSDK_COMMIT=b44afa47
+  CROSS_ROOT="${HOME}/toolchain-${OPENSDK_COMMIT}"
+  CROSS_BINDIR="${CROSS_ROOT}/bin"
+  ESPTOOL2_COMMIT=92530bb8
+  ESPTOOL2_BINDIR="${HOME}/esptool2-${ESPTOOL2_COMMIT}"
+  PATH=${PATH}:${CROSS_BINDIR}:${ESPTOOL2_BINDIR}
+cache:
+  directories:
+    - ${CROSS_ROOT}
+    - ${HOME}/.ccache
+    - ${ESPTOOL2_BINDIR}
+addons:
+  apt:
+    packages:
+    - make
+    - unrar
+    - autoconf
+    - automake
+    - libtool
+    - gcc
+    - g++
+    - gperf
+    - flex
+    - bison
+    - texinfo
+    - gawk
+    - libncurses5-dev
+    - libexpat1-dev
+    - python
+    - python-serial
+    - sed
+    - git
+
+before_install:
+  # Install a toolchain using esp-open-sdk (parts we need for this are the GNU toolchain and libhal)
+  #
+  # Adds hack of "{$HAS_TC} || -Buildstep-" to avoid rebuilding toolchain if it's already
+  # installed from the cache. If this gets any more complex it should be spun out to a standalone shell script.
+  - export HAS_TC="test -d ${CROSS_BINDIR}"
+  - unset CC # Travis sets this due to "language: c", but it confuses autotools configure when cross-building
+  - ${HAS_TC} || git clone --recursive https://github.com/pfalcon/esp-open-sdk.git
+  - ${HAS_TC} || cd esp-open-sdk
+  - ${HAS_TC} || git reset --hard ${OPENSDK_COMMIT}
+  - ${HAS_TC} || git submodule update
+  - ${HAS_TC} || sed -i "s/2.69/2.68/" lx106-hal/configure.ac # this is a nasty hack as Ubuntu Precise only has autoconf 2.68 not 2.69...
+  - ${HAS_TC} || sed -r -i 's%TOOLCHAIN ?=.*%TOOLCHAIN=${CROSS_ROOT}%' Makefile
+  - ${HAS_TC} || make STANDALONE=n
+  - export HAS_ET2="test -f ${ESPTOOL2_BINDIR}/esptool2"
+  - ${HAS_ET2} || git clone https://github.com/raburton/esp8266 ${HOME}/raburton
+  - ${HAS_ET2} || make -C ${HOME}/raburton/esptool2
+  - ${HAS_ET2} || mkdir -p ${ESPTOOL2_BINDIR}
+  - ${HAS_ET2} || cp -a ${HOME}/raburton/esptool2/esptool2 ${ESPTOOL2_BINDIR}/
+
+script:
+  - cd ${TRAVIS_BUILD_DIR}
+  # Remove ssid_config requirement for examples
+  - sed -i "s%#warning%//#warning%" include/ssid_config.h
+  - make -C examples/ build-examples CROSS="ccache xtensa-lx106-elf-"
diff --git a/README.md b/README.md
index 1d1c456..7bfa2e0 100644
--- a/README.md
+++ b/README.md
@@ -4,15 +4,17 @@ A community developed open source [FreeRTOS](http://www.freertos.org/)-based fra
 
 Originally based on, but substantially different from, the [Espressif IOT RTOS SDK](https://github.com/espressif/esp_iot_rtos_sdk).
 
+[![Build Status](https://travis-ci.org/SuperHouse/esp-open-rtos.svg?branch=master)](https://travis-ci.org/SuperHouse/esp-open-rtos)
+
 ## Quick Start
 
 * Install [esp-open-sdk](https://github.com/pfalcon/esp-open-sdk/), build it with `make STANDALONE=n`, then edit your PATH and add the generated toolchain `bin` directory. (Despite the similar name esp-open-sdk has different maintainers - but we think it's fantastic!)
 
-    (Other toolchains will also work, as long as a gcc cross-compiler is available on the PATH. The proprietary Tensilica "xcc" compiler will probably not work.)
+    (Other toolchains may also work, as long as a gcc cross-compiler is available on the PATH and libhal (and libhal headers) are compiled and available to gcc. The proprietary Tensilica "xcc" compiler will probably not work.)
 
-* Install [esptool.py](https://github.com/themadinventor/esptool) and make it available on your PATH.
+* Install [esptool.py](https://github.com/themadinventor/esptool) and make it available on your PATH. If you used esp-open-sdk then this is done already.
 
-* The build process uses `GNU Make`, and the utilities `sed` and `grep`. Linux & OS X should have these already. Windows users can get these tools a variety of ways - [MingGW](http://www.mingw.org/wiki/mingw) is one option.
+* The esp-open-rtos build process uses `GNU Make`, and the utilities `sed` and `grep`. If you built esp-open-sdk then you probably have these already.
 
 * Use git to clone the esp-open-rtos project (note the `--recursive`):
 
diff --git a/core/esp_gpio_interrupts.c b/core/esp_gpio_interrupts.c
index 99c7640..ef0a47d 100644
--- a/core/esp_gpio_interrupts.c
+++ b/core/esp_gpio_interrupts.c
@@ -25,7 +25,7 @@
    OR
 
    - Implement a single function named gpio_interrupt_handler(). This
-     will need to manually check GPIO_STATUS_REG and clear any status
+     will need to manually check GPIO.STATUS and clear any status
      bits after handling interrupts. This gives you full control, but
      you can't combine it with the first approach.
 
@@ -67,14 +67,14 @@ const gpio_interrupt_handler_t gpio_interrupt_handlers[16] = {
 
 void __attribute__((weak)) IRAM gpio_interrupt_handler(void)
 {
-    uint32_t status_reg = GPIO_STATUS_REG;
-    GPIO_STATUS_CLEAR = status_reg;
+    uint32_t status_reg = GPIO.STATUS;
+    GPIO.STATUS_CLEAR = status_reg;
     uint8_t gpio_idx;
     while((gpio_idx = __builtin_ffs(status_reg)))
     {
         gpio_idx--;
         status_reg &= ~BIT(gpio_idx);
-        if(GPIO_CTRL_REG(gpio_idx) & GPIO_INT_MASK)
+        if(FIELD2VAL(GPIO_CONF_INTTYPE, GPIO.CONF[gpio_idx]))
             gpio_interrupt_handlers[gpio_idx]();
     }
 }
diff --git a/core/esp_timer.c b/core/esp_timer.c
index b00e086..bb23f51 100644
--- a/core/esp_timer.c
+++ b/core/esp_timer.c
@@ -14,12 +14,12 @@
  * the arguments aren't known at compile time (values are evaluated at
  * compile time otherwise.)
  */
-uint32_t _timer_freq_to_count_runtime(const timer_frc_t frc, const uint32_t freq, const timer_div_t div)
+uint32_t _timer_freq_to_count_runtime(const timer_frc_t frc, const uint32_t freq, const timer_clkdiv_t div)
 {
     return _timer_freq_to_count_impl(frc, freq, div);
 }
 
-uint32_t _timer_time_to_count_runtime(const timer_frc_t frc, uint32_t us, const timer_div_t div)
+uint32_t _timer_time_to_count_runtime(const timer_frc_t frc, uint32_t us, const timer_clkdiv_t div)
 {
     return _timer_time_to_count_runtime(frc, us, div);
 }
diff --git a/core/include/common_macros.h b/core/include/common_macros.h
index 3afd593..2b4d73b 100644
--- a/core/include/common_macros.h
+++ b/core/include/common_macros.h
@@ -16,6 +16,16 @@
 #define BIT(X) (1<<(X))
 #endif
 
+/* These macros convert values to/from bitfields specified by *_M and *_S (mask
+ * and shift) constants.  Used primarily with ESP8266 register access.
+ */
+
+#define VAL2FIELD(fieldname, value) (((value) & fieldname##_M) << fieldname##_S)
+#define FIELD2VAL(fieldname, regbits) (((regbits) >> fieldname##_S) & fieldname##_M)
+
+#define FIELD_MASK(fieldname) (fieldname##_M << fieldname##_S)
+#define SET_FIELD(regbits, fieldname, value) (((regbits) & ~FIELD_MASK(fieldname)) | VAL2FIELD(fieldname, value))
+
 /* Use this macro to store constant values in IROM flash instead
    of having them loaded into rodata (which resides in DRAM)
 
diff --git a/core/include/esp/dport_regs.h b/core/include/esp/dport_regs.h
new file mode 100644
index 0000000..44a3b5b
--- /dev/null
+++ b/core/include/esp/dport_regs.h
@@ -0,0 +1,125 @@
+/* esp/dport_regs.h
+ *
+ * ESP8266 DPORT0 register definitions
+ *
+ * Not compatible with ESP SDK register access code.
+ */
+
+#ifndef _ESP_DPORT_REGS_H
+#define _ESP_DPORT_REGS_H
+
+#include "esp/types.h"
+#include "common_macros.h"
+
+#define DPORT_BASE 0x3ff00000
+#define DPORT (*(struct DPORT_REGS *)(DPORT_BASE))
+
+/* DPORT registers
+
+   Control various aspects of core/peripheral interaction... Not well
+   documented or understood.
+*/
+
+struct DPORT_REGS {
+    uint32_t volatile DPORT0;              // 0x00  FIXME: need a better name for this
+    uint32_t volatile INT_ENABLE;          // 0x04
+    uint32_t volatile _unknown08;          // 0x08
+    uint32_t volatile SPI_READY;           // 0x0c
+    uint32_t volatile _unknown10;          // 0x10
+    uint32_t volatile CPU_CLOCK;           // 0x14
+    uint32_t volatile CLOCKGATE_WATCHDOG;  // 0x18
+    uint32_t volatile _unknown1c;          // 0x1c
+    uint32_t volatile SPI_INT_STATUS;      // 0x20
+    uint32_t volatile SPI_CACHE_RAM;       // 0x24
+    uint32_t volatile PERI_IO;             // 0x28
+    uint32_t volatile SLC_TX_DESC_DEBUG;   // 0x2c
+    uint32_t volatile _unknown30;          // 0x30
+    uint32_t volatile _unknown34;          // 0x34
+    uint32_t volatile _unknown38;          // 0x38
+    uint32_t volatile _unknown3c;          // 0x3c
+    uint32_t volatile _unknown40;          // 0x40
+    uint32_t volatile _unknown44;          // 0x44
+    uint32_t volatile _unknown48;          // 0x48
+    uint32_t volatile _unknown4c;          // 0x4c
+    uint32_t volatile OTP_MAC0;            // 0x50
+    uint32_t volatile OTP_MAC1;            // 0x54
+    uint32_t volatile OTP_CHIPID;          // 0x58
+    uint32_t volatile OTP_MAC2;            // 0x5c
+} __attribute__ (( packed ));
+
+_Static_assert(sizeof(struct DPORT_REGS) == 0x60, "DPORT_REGS is the wrong size");
+
+/* Details for DPORT0 register */
+
+/* Currently very little known about this register.  The following is based on analysis of the startup code in the Espressif SDK: */
+
+#define DPORT_DPORT0_FIELD0_M  0x0000007f
+#define DPORT_DPORT0_FIELD0_S  0
+
+/* Details for INT_ENABLE register */
+
+/* Set flags to enable CPU interrupts from some peripherals. Read/write.
+
+   bit 0 - INT_ENABLE_WDT (unclear exactly how this works.  Set by RTOS SDK startup code)
+   bit 1 - INT_ENABLE_TIMER0 allows TIMER 0 (FRC1) to trigger interrupt INUM_TIMER_FRC1.
+   bit 2 - INT_ENABLE_TIMER1 allows TIMER 1 (FRC2) to trigger interrupt INUM_TIMER_FRC2.
+
+   Espressif calls this register "EDGE_INT_ENABLE_REG". The "edge" in
+   question is (I think) the interrupt line from the peripheral, as
+   the interrupt status bit is set. There may be a similar register
+   for enabling "level" interrupts instead of edge triggering
+   - this is unknown.
+*/
+
+#define DPORT_INT_ENABLE_WDT     BIT(0)
+#define DPORT_INT_ENABLE_TIMER0  BIT(1)
+#define DPORT_INT_ENABLE_TIMER1  BIT(2)
+
+/* Aliases for the Espressif way of referring to TIMER0 (FRC1) and TIMER1
+ * (FRC2).. */
+#define DPORT_INT_ENABLE_FRC1  DPORT_INT_ENABLE_TIMER0
+#define DPORT_INT_ENABLE_FRC2  DPORT_INT_ENABLE_TIMER1
+
+/* Details for SPI_READY register */
+
+#define DPORT_SPI_READY_IDLE     BIT(9)
+
+/* Details for CPU_CLOCK register */
+
+#define DPORT_CPU_CLOCK_X2       BIT(0)
+
+/* Details for CLOCKGATE_WATCHDOG register */
+
+/* Comment found in pvvx/mp3_decode headers: "use clockgate_watchdog(flg) { if(flg) 0x3FF00018 &= 0x77; else 0x3FF00018 |= 8; }".  Not sure what this means or does. */
+
+#define DPORT_CLOCKGATE_WATCHDOG_DISABLE  BIT(3)
+
+/* Details for SPI_INT_STATUS register */
+
+#define DPORT_SPI_INT_STATUS_SPI0  BIT(4)
+#define DPORT_SPI_INT_STATUS_SPI1  BIT(7)
+#define DPORT_SPI_INT_STATUS_I2S   BIT(9)
+
+/* Details for SPI_CACHE_RAM register */
+
+#define DPORT_SPI_CACHE_RAM_BANK1  BIT(3)
+#define DPORT_SPI_CACHE_RAM_BANK0  BIT(4)
+
+/* Details for PERI_IO register */
+
+#define DPORT_PERI_IO_SWAP_UARTS       BIT(0)
+#define DPORT_PERI_IO_SWAP_SPIS        BIT(1)
+#define DPORT_PERI_IO_SWAP_UART0_PINS  BIT(2)
+#define DPORT_PERI_IO_SWAP_UART1_PINS  BIT(3)
+#define DPORT_PERI_IO_SPI1_PRIORITY    BIT(5)
+#define DPORT_PERI_IO_SPI1_SHARED      BIT(6)
+#define DPORT_PERI_IO_SPI0_SHARED      BIT(7)
+
+/* Details for SLC_TX_DESC_DEBUG register */
+
+#define SLC_TX_DESC_DEBUG_VALUE_M      0x0000ffff
+#define SLC_TX_DESC_DEBUG_VALUE_S      0
+
+#define SLC_TX_DESC_DEBUG_VALUE_MAGIC  0xcccc
+
+#endif /* _ESP_DPORT_REGS_H */
diff --git a/core/include/esp/gpio.h b/core/include/esp/gpio.h
index 57d6d9a..1f1ce14 100644
--- a/core/include/esp/gpio.h
+++ b/core/include/esp/gpio.h
@@ -9,7 +9,7 @@
 #ifndef _ESP_GPIO_H
 #define _ESP_GPIO_H
 #include <stdbool.h>
-#include "esp/registers.h"
+#include "esp/gpio_regs.h"
 #include "esp/iomux.h"
 #include "esp/cpu.h"
 #include "xtensa_interrupts.h"
@@ -32,37 +32,38 @@ INLINED void gpio_enable(const uint8_t gpio_num, const gpio_direction_t directio
     switch(direction) {
     case GPIO_INPUT:
         iomux_flags = 0;
-        ctrl_val = GPIO_SOURCE_GPIO;
+        ctrl_val = 0;
         break;
     case GPIO_OUTPUT:
-        iomux_flags = IOMUX_OE;
-        ctrl_val = GPIO_DRIVE_PUSH_PULL|GPIO_SOURCE_GPIO;
+        iomux_flags = IOMUX_PIN_OUTPUT_ENABLE;
+        ctrl_val = GPIO_CONF_PUSH_PULL;
         break;
     case GPIO_OUT_OPEN_DRAIN:
-        iomux_flags = IOMUX_OE;
-        ctrl_val = GPIO_DRIVE_OPEN_DRAIN|GPIO_SOURCE_GPIO;
+        iomux_flags = IOMUX_PIN_OUTPUT_ENABLE;
+        ctrl_val = 0;
         break;
     case GPIO_INPUT_PULLUP:
-        iomux_flags = IOMUX_PU;
-        ctrl_val = GPIO_SOURCE_GPIO;
+        iomux_flags = IOMUX_PIN_PULLUP;
+        ctrl_val = 0;
+        break;
     }
     iomux_set_gpio_function(gpio_num, iomux_flags);
-    GPIO_CTRL_REG(gpio_num) = (GPIO_CTRL_REG(gpio_num)&GPIO_INT_MASK) | ctrl_val;
-    if(direction == GPIO_OUTPUT)
-        GPIO_DIR_SET = BIT(gpio_num);
+    GPIO.CONF[gpio_num] = (GPIO.CONF[gpio_num] & FIELD_MASK(GPIO_CONF_INTTYPE)) | ctrl_val;
+    if (iomux_flags & IOMUX_PIN_OUTPUT_ENABLE)
+        GPIO.ENABLE_OUT_SET = BIT(gpio_num);
     else
-        GPIO_DIR_CLEAR = BIT(gpio_num);
+        GPIO.ENABLE_OUT_CLEAR = BIT(gpio_num);
 }
 
 /* Disable GPIO on the specified pin, and set it Hi-Z.
  *
  * If later muxing this pin to a different function, make sure to set
- * IOMUX_OE if necessary to enable the output buffer.
+ * IOMUX_PIN_OUTPUT_ENABLE if necessary to enable the output buffer.
  */
 INLINED void gpio_disable(const uint8_t gpio_num)
 {
-    GPIO_DIR_CLEAR = BIT(gpio_num);
-    *gpio_iomux_reg(gpio_num) &= ~IOMUX_OE;
+    GPIO.ENABLE_OUT_CLEAR = BIT(gpio_num);
+    *gpio_iomux_reg(gpio_num) &= ~IOMUX_PIN_OUTPUT_ENABLE;
 }
 
 /* Set output of a pin high or low.
@@ -72,9 +73,9 @@ INLINED void gpio_disable(const uint8_t gpio_num)
 INLINED void gpio_write(const uint8_t gpio_num, const bool set)
 {
     if(set)
-        GPIO_OUT_SET = BIT(gpio_num);
+        GPIO.OUT_SET = BIT(gpio_num);
     else
-        GPIO_OUT_CLEAR = BIT(gpio_num);
+        GPIO.OUT_CLEAR = BIT(gpio_num);
 }
 
 /* Toggle output of a pin
@@ -89,10 +90,10 @@ INLINED void gpio_toggle(const uint8_t gpio_num)
        get an invalid value. Prevents one task from clobbering another
        task's pins, without needing to disable/enable interrupts.
     */
-    if(GPIO_OUT_REG & BIT(gpio_num))
-        GPIO_OUT_CLEAR = BIT(gpio_num);
+    if(GPIO.OUT & BIT(gpio_num))
+        GPIO.OUT_CLEAR = BIT(gpio_num);
     else
-        GPIO_OUT_SET = BIT(gpio_num);
+        GPIO.OUT_SET = BIT(gpio_num);
 }
 
 /* Read input value of a GPIO pin.
@@ -102,38 +103,28 @@ INLINED void gpio_toggle(const uint8_t gpio_num)
  */
 INLINED bool gpio_read(const uint8_t gpio_num)
 {
-    return GPIO_IN_REG & BIT(gpio_num);
+    return GPIO.IN & BIT(gpio_num);
 }
 
-typedef enum {
-    INT_NONE = 0,
-    INT_RISING = GPIO_INT_RISING,
-    INT_FALLING = GPIO_INT_FALLING,
-    INT_CHANGE = GPIO_INT_CHANGE,
-    INT_LOW = GPIO_INT_LOW,
-    INT_HIGH = GPIO_INT_HIGH,
-} gpio_interrupt_t;
-
 extern void gpio_interrupt_handler(void);
 
 /* Set the interrupt type for a given pin
  *
- * If int_type is not INT_NONE, the gpio_interrupt_handler will be attached and unmasked.
+ * If int_type is not GPIO_INTTYPE_NONE, the gpio_interrupt_handler will be attached and unmasked.
  */
-INLINED void gpio_set_interrupt(const uint8_t gpio_num, const gpio_interrupt_t int_type)
+INLINED void gpio_set_interrupt(const uint8_t gpio_num, const gpio_inttype_t int_type)
 {
-    GPIO_CTRL_REG(gpio_num) = (GPIO_CTRL_REG(gpio_num)&~GPIO_INT_MASK)
-        | (int_type & GPIO_INT_MASK);
-    if(int_type != INT_NONE) {
+    GPIO.CONF[gpio_num] = SET_FIELD(GPIO.CONF[gpio_num], GPIO_CONF_INTTYPE, int_type);
+    if(int_type != GPIO_INTTYPE_NONE) {
         _xt_isr_attach(INUM_GPIO, gpio_interrupt_handler);
         _xt_isr_unmask(1<<INUM_GPIO);
     }
 }
 
 /* Return the interrupt type set for a pin */
-INLINED gpio_interrupt_t gpio_get_interrupt(const uint8_t gpio_num)
+INLINED gpio_inttype_t gpio_get_interrupt(const uint8_t gpio_num)
 {
-    return (gpio_interrupt_t)(GPIO_CTRL_REG(gpio_num) & GPIO_INT_MASK);
+    return FIELD2VAL(GPIO_CONF_INTTYPE, GPIO.CONF[gpio_num]);
 }
 
 #endif
diff --git a/core/include/esp/gpio_regs.h b/core/include/esp/gpio_regs.h
new file mode 100644
index 0000000..21e2c29
--- /dev/null
+++ b/core/include/esp/gpio_regs.h
@@ -0,0 +1,130 @@
+/* esp/gpio_regs.h
+ *
+ * ESP8266 GPIO register definitions
+ *
+ * Not compatible with ESP SDK register access code.
+ */
+
+#ifndef _ESP_GPIO_REGS_H
+#define _ESP_GPIO_REGS_H
+
+#include "esp/types.h"
+#include "common_macros.h"
+
+#define GPIO_BASE 0x60000300
+#define GPIO (*(struct GPIO_REGS *)(GPIO_BASE))
+
+/** GPIO output registers GPIO.OUT, GPIO.OUT_SET, GPIO.OUT_CLEAR:
+ *
+ * _SET and _CLEAR write-only registers set and clear bits in the main register,
+ * respectively.
+ *
+ * i.e.
+ *   GPIO.OUT_SET = BIT(3);
+ * and
+ *   GPIO.OUT |= BIT(3);
+ *
+ * ... are equivalent, but the former uses fewer CPU cycles.
+ *
+ * ENABLE_OUT / ENABLE_OUT_SET / ENABLE_OUT_CLEAR:
+ *
+ * Determine whether the corresponding GPIO has its output enabled or not.
+ * When clear, GPIO can function as an input.  When set, GPIO will drive its
+ * output (and IN register will simply reflect the output state).
+ *
+ * (_SET/_CLEAR function similarly to OUT registers)
+ *
+ * STATUS / STATUS_SET / STATUS_CLEAR:
+ *
+ * Indicates which GPIOs have triggered an interrupt.  Interrupt status should
+ * be reset by writing to STATUS or STATUS_CLEAR.
+ *
+ * (_SET/_CLEAR function similarly to OUT registers)
+ */
+
+struct GPIO_REGS {
+    uint32_t volatile OUT;              // 0x00
+    uint32_t volatile OUT_SET;          // 0x04
+    uint32_t volatile OUT_CLEAR;        // 0x08
+    uint32_t volatile ENABLE_OUT;       // 0x0c
+    uint32_t volatile ENABLE_OUT_SET;   // 0x10
+    uint32_t volatile ENABLE_OUT_CLEAR; // 0x14
+    uint32_t volatile IN;               // 0x18
+    uint32_t volatile STATUS;           // 0x1c
+    uint32_t volatile STATUS_SET;       // 0x20
+    uint32_t volatile STATUS_CLEAR;     // 0x24
+    uint32_t volatile CONF[16];         // 0x28 - 0x64
+    uint32_t volatile PWM;              // 0x68
+    uint32_t volatile RTC_CALIB;        // 0x6c
+    uint32_t volatile RTC_CALIB_RESULT; // 0x70
+} __attribute__ (( packed ));
+
+_Static_assert(sizeof(struct GPIO_REGS) == 0x74, "GPIO_REGS is the wrong size");
+
+/* Details for CONF[i] registers */
+
+/* GPIO.CONF[i] control the pin behavior for the corresponding GPIO in/output.
+ *
+ * GPIO_CONF_CONFIG (multi-value)
+ *     FIXME: Unclear what these do.  Need to find a better name.
+ *
+ * GPIO_CONF_WAKEUP_ENABLE (boolean)
+ *     Can an interrupt contion on this pin wake the processor from a sleep
+ *     state?
+ *
+ * GPIO_CONF_INTTYPE (multi-value)
+ *     Under what conditions this GPIO input should generate an interrupt.
+ *     (see gpio_inttype_t enum below for values)
+ *
+ * GPIO_CONF_PUSH_PULL (boolean)
+ *     When set, a high output state will pull the pin up to +Vcc (3.3V).  When
+ *     cleared, output functions in "open drain" mode (low state will pull down
+ *     to ground, but high state allows output to "float").
+ *
+ * GPIO_CONF_SOURCE_PWM (boolean)
+ *     When set, GPIO pin output will be connected to the sigma-delta PWM
+ *     generator (controlled by the GPIO.PWM register).  When cleared, pin
+ *     output will function as a normal GPIO output (controlled by the
+ *     GPIO.OUT* registers).
+ */
+
+#define GPIO_CONF_CONFIG_M       0x00000003
+#define GPIO_CONF_CONFIG_S       11
+#define GPIO_CONF_WAKEUP_ENABLE  BIT(10)
+#define GPIO_CONF_INTTYPE_M      0x00000007
+#define GPIO_CONF_INTTYPE_S      7
+#define GPIO_CONF_PUSH_PULL      BIT(2)
+#define GPIO_CONF_SOURCE_PWM     BIT(0)
+
+/* Valid values for the GPIO_CONF_INTTYPE field */
+typedef enum {
+    GPIO_INTTYPE_NONE       = 0,
+    GPIO_INTTYPE_EDGE_POS   = 1,
+    GPIO_INTTYPE_EDGE_NEG   = 2,
+    GPIO_INTTYPE_EDGE_ANY   = 3,
+    GPIO_INTTYPE_LEVEL_LOW  = 4,
+    GPIO_INTTYPE_LEVEL_HIGH = 5,
+} gpio_inttype_t;
+
+/* Details for PWM register */
+
+#define GPIO_PWM_ENABLE          BIT(16)
+#define GPIO_PWM_PRESCALER_M     0x000000ff
+#define GPIO_PWM_PRESCALER_S     8
+#define GPIO_PWM_TARGET_M        0x000000ff
+#define GPIO_PWM_TARGET_S        0
+
+/* Details for RTC_CALIB register */
+
+#define GPIO_RTC_CALIB_START     BIT(31)
+#define GPIO_RTC_CALIB_PERIOD_M  0x000003ff
+#define GPIO_RTC_CALIB_PERIOD_S  0
+
+/* Details for RTC_CALIB_RESULT register */
+
+#define GPIO_RTC_CALIB_RESULT_READY       BIT(31)
+#define GPIO_RTC_CALIB_RESULT_READY_REAL  BIT(30)
+#define GPIO_RTC_CALIB_RESULT_VALUE_M     0x000fffff
+#define GPIO_RTC_CALIB_RESULT_VALUE_S     0
+
+#endif /* _ESP_GPIO_REGS_H */
diff --git a/core/include/esp/iomux.h b/core/include/esp/iomux.h
index fd5a51d..f6715f4 100644
--- a/core/include/esp/iomux.h
+++ b/core/include/esp/iomux.h
@@ -8,8 +8,8 @@
  */
 #ifndef _ESP_IOMUX_H
 #define _ESP_IOMUX_H
-#include <stdint.h>
-#include "esp/registers.h"
+#include "esp/types.h"
+#include "esp/iomux_regs.h"
 
 /**
  * Convert a GPIO pin number to an iomux register index.
@@ -32,11 +32,11 @@ inline static uint8_t iomux_to_gpio(const uint8_t iomux_num);
 /**
  * Directly get the IOMUX register for a particular gpio number
  *
- * ie *gpio_iomux_reg(3) is equivalent to IOMUX_GP03
+ * ie *gpio_iomux_reg(3) is equivalent to IOMUX_GPIO3
  */
 inline static esp_reg_t gpio_iomux_reg(const uint8_t gpio_number)
 {
-    return &IOMUX_REG(gpio_to_iomux(gpio_number));
+    return &(IOMUX.PIN[gpio_to_iomux(gpio_number)]);
 }
 
 /**
@@ -45,153 +45,21 @@ inline static esp_reg_t gpio_iomux_reg(const uint8_t gpio_number)
  * This allows you to set pins to GPIO without knowing in advance the
  * exact register masks to use.
  *
- * flags can be any of IOMUX_OE, IOMUX_PU, IOMUX_PD, etc. Any other flags will be cleared.
+ * flags can be any of IOMUX_PIN_OUTPUT_ENABLE, IOMUX_PIN_PULLUP, IOMUX_PIN_PULLDOWN, etc. Any other flags will be cleared.
  *
  * Equivalent to a direct register operation if gpio_number is known at compile time.
  * ie the following are equivalent:
  *
- * iomux_set_gpio_function(12, IOMUX_OE);
- * IOMUX_GP12 = (IOMUX_GP12 & ~IOMUX_FUNC_MASK) | IOMUX_GP12_GPIO | IOMUX_OE;
+ * iomux_set_gpio_function(12, IOMUX_PIN_OUTPUT_ENABLE);
+ * IOMUX_GPIO12 = IOMUX_GPIO12_FUNC_GPIO | IOMUX_PIN_OUTPUT_ENABLE;
  */
-inline static void iomux_set_gpio_function(const uint8_t gpio_number, const uint8_t flags)
+inline static void iomux_set_gpio_function(const uint8_t gpio_number, const uint32_t flags)
 {
     const uint8_t reg_idx = gpio_to_iomux(gpio_number);
-    const esp_reg_t reg = &IOMUX_REG(reg_idx);
-    const uint32_t func = (reg_idx > 11 ? IOMUX_FUNC_A : IOMUX_FUNC_D) | flags;
-    const uint32_t val = *reg & ~(IOMUX_FUNC_MASK | IOMUX_FLAG_MASK);
-    *reg = val | func;
+    const uint32_t func = (reg_idx > 11 ? IOMUX_FUNC(0) : IOMUX_FUNC(3)) | flags;
+    IOMUX.PIN[reg_idx] = func | flags;
 }
 
-/**
- * Set an IOMUX register directly
- *
- * Shortcut for
- * IOMUX_GPxx = (IOMUX_GPxx & ~IOMUX_FUNC_MASK) | IOMUX_GPxx_func
- *
- * instead call
- * IOMUX_SET_FN(GPxx, func);
- * can also do
- * IOMUX_SET_FN(GP12, GPIO)|IOMUX_OE;
- * ... to set the OE flag if it was previously cleared.
- *
- * but a better option is:
- * IOMUX_SET(GP12, GPIO, IOMUX_OE);
- * ...which clears any other flags at the same time.
- */
-#define IOMUX_SET_FN(GP,FN) IOMUX_##GP = ((IOMUX_##GP & ~IOMUX_FUNC_MASK) | IOMUX_##GP##_##FN)
-#define IOMUX_SET(GP,FN,FLAGS) IOMUX_##GP = ((IOMUX_##GP & ~(IOMUX_FUNC_MASK|IOMUX_FLAG_MASK)) | IOMUX_##GP##_##FN|FLAGS)
-
-/* IOMUX register index 0, GPIO 12 */
-#define IOMUX_GP12           IOMUX_REG(0)
-#define IOMUX_GP12_MTDI      IOMUX_FUNC_A
-#define IOMUX_GP12_I2S_DIN   IOMUX_FUNC_B
-#define IOMUX_GP12_HSPI_MISO IOMUX_FUNC_C
-#define IOMUX_GP12_GPIO      IOMUX_FUNC_D
-#define IOMUX_GP12_UART0_DTR IOMUX_FUNC_E
-
-/* IOMUX register index 1, GPIO 13 */
-#define IOMUX_GP13           IOMUX_REG(1)
-#define IOMUX_GP13_MTCK      IOMUX_FUNC_A
-#define IOMUX_GP13_I2SI_BCK  IOMUX_FUNC_B
-#define IOMUX_GP13_HSPI_MOSI IOMUX_FUNC_C
-#define IOMUX_GP13_GPIO      IOMUX_FUNC_D
-#define IOMUX_GP13_UART0_CTS IOMUX_FUNC_E
-
-/* IOMUX register index 2, GPIO 14 */
-#define IOMUX_GP14           IOMUX_REG(2)
-#define IOMUX_GP14_MTMS      IOMUX_FUNC_A
-#define IOMUX_GP14_I2SI_WS   IOMUX_FUNC_B
-#define IOMUX_GP14_HSPI_CLK  IOMUX_FUNC_C
-#define IOMUX_GP14_GPIO      IOMUX_FUNC_D
-#define IOMUX_GP14_UART0_DSR IOMUX_FUNC_E
-
-/* IOMUX register index 3, GPIO 15 */
-#define IOMUX_GP15           IOMUX_REG(3)
-#define IOMUX_GP15_MTDO      IOMUX_FUNC_A
-#define IOMUX_GP15_I2SO_BCK  IOMUX_FUNC_B
-#define IOMUX_GP15_HSPI_CS0  IOMUX_FUNC_C
-#define IOMUX_GP15_GPIO      IOMUX_FUNC_D
-#define IOMUX_GP15_UART0_RTS IOMUX_FUNC_E
-
-/* IOMUX register index 4, GPIO 3 */
-#define IOMUX_GP03           IOMUX_REG(4)
-#define IOMUX_GP03_UART0_RX  IOMUX_FUNC_A
-#define IOMUX_GP03_I2SO_DATA IOMUX_FUNC_B
-#define IOMUX_GP03_GPIO      IOMUX_FUNC_D
-#define IOMUX_GP03_CLK_XTAL_BK IOMUX_FUNC_E
-
-/* IOMUX register index 5, GPIO 1 */
-#define IOMUX_GP01           IOMUX_REG(5)
-#define IOMUX_GP01_UART0_TX  IOMUX_FUNC_A
-#define IOMUX_GP01_SPICS1    IOMUX_FUNC_B
-#define IOMUX_GP01_GPIO      IOMUX_FUNC_D
-#define IOMUX_GP01_CLK_RTC_BK IOMUX_FUNC_E
-
-/* IOMUX register index 6, GPIO 6 */
-#define IOMUX_GP06           IOMUX_REG(6)
-#define IOMUX_GP06_SD_CLK    IOMUX_FUNC_A
-#define IOMUX_GP06_SP_ICLK   IOMUX_FUNC_B
-#define IOMUX_GP06_GPIO      IOMUX_FUNC_D
-#define IOMUX_GP06_UART1_CTS IOMUX_FUNC_E
-
-/* IOMUX register index 7, GPIO 7 */
-#define IOMUX_GP07           IOMUX_REG(7)
-#define IOMUX_GP07_SD_DATA0  IOMUX_FUNC_A
-#define IOMUX_GP07_SPIQ_MISO IOMUX_FUNC_B
-#define IOMUX_GP07_GPIO      IOMUX_FUNC_D
-#define IOMUX_GP07_UART1_TX  IOMUX_FUNC_E
-
-/* IOMUX register index 8, GPIO 8 */
-#define IOMUX_GP08           IOMUX_REG(8)
-#define IOMUX_GP08_SD_DATA1  IOMUX_FUNC_A
-#define IOMUX_GP08_SPID_MOSI IOMUX_FUNC_B
-#define IOMUX_GP08_GPIO      IOMUX_FUNC_D
-#define IOMUX_GP08_UART1_RX  IOMUX_FUNC_E
-
-/* IOMUX register index 9, GPIO 9 */
-#define IOMUX_GP09           IOMUX_REG(9)
-#define IOMUX_GP09_SD_DATA2  IOMUX_FUNC_A
-#define IOMUX_GP09_SPI_HD    IOMUX_FUNC_B
-#define IOMUX_GP09_GPIO      IOMUX_FUNC_D
-#define IOMUX_GP09_UFNC_HSPIHD IOMUX_FUNC_E
-
-/* IOMUX register index 10, GPIO 10 */
-#define IOMUX_GP10           IOMUX_REG(10)
-#define IOMUX_GP10_SD_DATA3  IOMUX_FUNC_A
-#define IOMUX_GP10_SPI_WP    IOMUX_FUNC_B
-#define IOMUX_GP10_GPIO      IOMUX_FUNC_D
-#define IOMUX_GP10_HSPIWP    IOMUX_FUNC_E
-
-/* IOMUX register index 11, GPIO 11 */
-#define IOMUX_GP11           IOMUX_REG(11)
-#define IOMUX_GP11_SD_CMD    IOMUX_FUNC_A
-#define IOMUX_GP11_SPI_CS0   IOMUX_FUNC_B
-#define IOMUX_GP11_GPIO      IOMUX_FUNC_D
-#define IOMUX_GP11_UART1_RTS IOMUX_FUNC_E
-
-/* IOMUX register index 12, GPIO 0 */
-#define IOMUX_GP00           IOMUX_REG(12)
-#define IOMUX_GP00_GPIO      IOMUX_FUNC_A
-#define IOMUX_GP00_SPI_CS2   IOMUX_FUNC_B
-#define IOMUX_GP00_CLK_OUT   IOMUX_FUNC_E
-
-/* IOMUX register index 13, GPIO 2 */
-#define IOMUX_GP02           IOMUX_REG(13)
-#define IOMUX_GP02_GPIO      IOMUX_FUNC_A
-#define IOMUX_GP02_I2SO_WS   IOMUX_FUNC_B
-#define IOMUX_GP02_UART1_TX  IOMUX_FUNC_C
-#define IOMUX_GP02_UART0_TX  IOMUX_FUNC_E
-
-/* IOMUX register index 14, GPIO 4 */
-#define IOMUX_GP04           IOMUX_REG(14)
-#define IOMUX_GP04_GPIO4     IOMUX_FUNC_A
-#define IOMUX_GP04_CLK_XTAL  IOMUX_FUNC_B
-
-/* IOMUX register index 15, GPIO 5 */
-#define IOMUX_GP05           IOMUX_REG(15)
-#define IOMUX_GP05_GPIO5     IOMUX_FUNC_A
-#define IOMUX_GP05_CLK_RTC   IOMUX_FUNC_B
-
 /* esp_iomux_private contains implementation parts of the inline functions
    declared above */
 #include "esp/iomux_private.h"
diff --git a/core/include/esp/iomux_regs.h b/core/include/esp/iomux_regs.h
new file mode 100644
index 0000000..d3b9653
--- /dev/null
+++ b/core/include/esp/iomux_regs.h
@@ -0,0 +1,151 @@
+/* esp/iomux_regs.h
+ *
+ * ESP8266 IOMUX register definitions
+ *
+ * Not compatible with ESP SDK register access code.
+ *
+ * Note that IOMUX register order is _not_ the same as GPIO order. See
+ * esp/iomux.h for programmer-friendly IOMUX configuration options.
+ */
+
+#ifndef _ESP_IOMUX_REGS_H
+#define _ESP_IOMUX_REGS_H
+
+#include "esp/types.h"
+#include "common_macros.h"
+
+#define IOMUX_BASE 0x60000800
+#define IOMUX (*(struct IOMUX_REGS *)(IOMUX_BASE))
+
+struct IOMUX_REGS {
+    uint32_t volatile CONF;    // 0x00
+    uint32_t volatile PIN[16]; // 0x04 - 0x40
+} __attribute__ (( packed ));
+
+_Static_assert(sizeof(struct IOMUX_REGS) == 0x44, "IOMUX_REGS is the wrong size");
+
+/* Details for CONF register */
+
+#define IOMUX_CONF_SPI0_CLOCK_EQU_SYS_CLOCK  BIT(8)
+#define IOMUX_CONF_SPI1_CLOCK_EQU_SYS_CLOCK  BIT(9)
+
+/* Details for PIN registers */
+
+#define IOMUX_PIN_OUTPUT_ENABLE        BIT(0)
+#define IOMUX_PIN_OUTPUT_ENABLE_SLEEP  BIT(1)
+#define IOMUX_PIN_PULLDOWN_SLEEP       BIT(2)
+#define IOMUX_PIN_PULLUP_SLEEP         BIT(3)
+#define IOMUX_PIN_FUNC_LOW_M           0x00000003
+#define IOMUX_PIN_FUNC_LOW_S           4
+#define IOMUX_PIN_PULLDOWN             BIT(6)
+#define IOMUX_PIN_PULLUP               BIT(7)
+#define IOMUX_PIN_FUNC_HIGH_M          0x00000004
+#define IOMUX_PIN_FUNC_HIGH_S          8
+
+#define IOMUX_PIN_FUNC_MASK            0x00001030
+
+/* WARNING: Macro evaluates argument twice */
+#define IOMUX_FUNC(val) (VAL2FIELD(IOMUX_PIN_FUNC_LOW, val) | VAL2FIELD(IOMUX_PIN_FUNC_HIGH, val))
+
+/* WARNING: Macro evaluates argument twice */
+#define IOMUX_FUNC_VALUE(regbits) (FIELD2VAL(IOMUX_PIN_FUNC_LOW, regbits) | FIELD2VAL(IOMUX_PIN_FUNC_HIGH, regbits))
+
+#define IOMUX_SET_FUNC(regbits, funcval) (((regbits) & ~IOMUX_PIN_FUNC_MASK) | (funcval))
+
+#define IOMUX_GPIO0   IOMUX.PIN[12]
+#define IOMUX_GPIO1   IOMUX.PIN[5]
+#define IOMUX_GPIO2   IOMUX.PIN[13]
+#define IOMUX_GPIO3   IOMUX.PIN[4]
+#define IOMUX_GPIO4   IOMUX.PIN[14]
+#define IOMUX_GPIO5   IOMUX.PIN[15]
+#define IOMUX_GPIO6   IOMUX.PIN[6]
+#define IOMUX_GPIO7   IOMUX.PIN[7]
+#define IOMUX_GPIO8   IOMUX.PIN[8]
+#define IOMUX_GPIO9   IOMUX.PIN[9]
+#define IOMUX_GPIO10  IOMUX.PIN[10]
+#define IOMUX_GPIO11  IOMUX.PIN[11]
+#define IOMUX_GPIO12  IOMUX.PIN[0]
+#define IOMUX_GPIO13  IOMUX.PIN[1]
+#define IOMUX_GPIO14  IOMUX.PIN[2]
+#define IOMUX_GPIO15  IOMUX.PIN[3]
+
+#define IOMUX_GPIO0_FUNC_GPIO              IOMUX_FUNC(0)
+#define IOMUX_GPIO0_FUNC_SPI0_CS2          IOMUX_FUNC(1)
+#define IOMUX_GPIO0_FUNC_CLOCK_OUT         IOMUX_FUNC(4)
+
+#define IOMUX_GPIO1_FUNC_UART0_TXD         IOMUX_FUNC(0)
+#define IOMUX_GPIO1_FUNC_SPI0_CS1          IOMUX_FUNC(1)
+#define IOMUX_GPIO1_FUNC_GPIO              IOMUX_FUNC(3)
+#define IOMUX_GPIO1_FUNC_CLOCK_RTC_BLINK   IOMUX_FUNC(4)
+
+#define IOMUX_GPIO2_FUNC_GPIO              IOMUX_FUNC(0)
+#define IOMUX_GPIO2_FUNC_I2SO_WS           IOMUX_FUNC(1)
+#define IOMUX_GPIO2_FUNC_UART1_TXD_BLINK   IOMUX_FUNC(2)
+#define IOMUX_GPIO2_FUNC_UART0_TXD_BLINK   IOMUX_FUNC(4)
+
+#define IOMUX_GPIO3_FUNC_UART0_RXD         IOMUX_FUNC(0)
+#define IOMUX_GPIO3_FUNC_I2SO_DATA         IOMUX_FUNC(1)
+#define IOMUX_GPIO3_FUNC_GPIO              IOMUX_FUNC(3)
+#define IOMUX_GPIO3_FUNC_CLOCK_XTAL_BLINK  IOMUX_FUNC(4)
+
+#define IOMUX_GPIO4_FUNC_GPIO              IOMUX_FUNC(0)
+#define IOMUX_GPIO4_FUNC_CLOCK_XTAL        IOMUX_FUNC(1)
+
+#define IOMUX_GPIO5_FUNC_GPIO              IOMUX_FUNC(0)
+#define IOMUX_GPIO5_FUNC_CLOCK_RTC         IOMUX_FUNC(1)
+
+#define IOMUX_GPIO6_FUNC_SD_CLK            IOMUX_FUNC(0)
+#define IOMUX_GPIO6_FUNC_SPI0_CLK          IOMUX_FUNC(1)
+#define IOMUX_GPIO6_FUNC_GPIO              IOMUX_FUNC(3)
+#define IOMUX_GPIO6_FUNC_UART1_CTS         IOMUX_FUNC(4)
+
+#define IOMUX_GPIO7_FUNC_SD_DATA0          IOMUX_FUNC(0)
+#define IOMUX_GPIO7_FUNC_SPI0_Q_MISO       IOMUX_FUNC(1)
+#define IOMUX_GPIO7_FUNC_GPIO              IOMUX_FUNC(3)
+#define IOMUX_GPIO7_FUNC_UART1_TXD         IOMUX_FUNC(4)
+
+#define IOMUX_GPIO8_FUNC_SD_DATA1          IOMUX_FUNC(0)
+#define IOMUX_GPIO8_FUNC_SPI0_D_MOSI       IOMUX_FUNC(1)
+#define IOMUX_GPIO8_FUNC_GPIO              IOMUX_FUNC(3)
+#define IOMUX_GPIO8_FUNC_UART1_RXD         IOMUX_FUNC(4)
+
+#define IOMUX_GPIO9_FUNC_SD_DATA2          IOMUX_FUNC(0)
+#define IOMUX_GPIO9_FUNC_SPI0_HD           IOMUX_FUNC(1)
+#define IOMUX_GPIO9_FUNC_GPIO              IOMUX_FUNC(3)
+#define IOMUX_GPIO9_FUNC_SPI1_HD           IOMUX_FUNC(4)
+
+#define IOMUX_GPIO10_FUNC_SD_DATA3         IOMUX_FUNC(0)
+#define IOMUX_GPIO10_FUNC_SPI0_WP          IOMUX_FUNC(1)
+#define IOMUX_GPIO10_FUNC_GPIO             IOMUX_FUNC(3)
+#define IOMUX_GPIO10_FUNC_SPI1_WP          IOMUX_FUNC(4)
+
+#define IOMUX_GPIO11_FUNC_SD_CMD           IOMUX_FUNC(0)
+#define IOMUX_GPIO11_FUNC_SPI0_CS0         IOMUX_FUNC(1)
+#define IOMUX_GPIO11_FUNC_GPIO             IOMUX_FUNC(3)
+#define IOMUX_GPIO11_FUNC_UART1_RTS        IOMUX_FUNC(4)
+
+#define IOMUX_GPIO12_FUNC_MTDI             IOMUX_FUNC(0)
+#define IOMUX_GPIO12_FUNC_I2SI_DATA        IOMUX_FUNC(1)
+#define IOMUX_GPIO12_FUNC_SPI1_Q_MISO      IOMUX_FUNC(2)
+#define IOMUX_GPIO12_FUNC_GPIO             IOMUX_FUNC(3)
+#define IOMUX_GPIO12_FUNC_UART0_DTR        IOMUX_FUNC(4)
+
+#define IOMUX_GPIO13_FUNC_MTCK             IOMUX_FUNC(0)
+#define IOMUX_GPIO13_FUNC_I2SI_BCK         IOMUX_FUNC(1)
+#define IOMUX_GPIO13_FUNC_SPI1_D_MOSI      IOMUX_FUNC(2)
+#define IOMUX_GPIO13_FUNC_GPIO             IOMUX_FUNC(3)
+#define IOMUX_GPIO13_FUNC_UART0_CTS        IOMUX_FUNC(4)
+
+#define IOMUX_GPIO14_FUNC_MTMS             IOMUX_FUNC(0)
+#define IOMUX_GPIO14_FUNC_I2SI_WS          IOMUX_FUNC(1)
+#define IOMUX_GPIO14_FUNC_SPI1_CLK         IOMUX_FUNC(2)
+#define IOMUX_GPIO14_FUNC_GPIO             IOMUX_FUNC(3)
+#define IOMUX_GPIO14_FUNC_UART0_DSR        IOMUX_FUNC(4)
+
+#define IOMUX_GPIO15_FUNC_MTDO             IOMUX_FUNC(0)
+#define IOMUX_GPIO15_FUNC_I2SO_BCK         IOMUX_FUNC(1)
+#define IOMUX_GPIO15_FUNC_SPI1_CS0         IOMUX_FUNC(2)
+#define IOMUX_GPIO15_FUNC_GPIO             IOMUX_FUNC(3)
+#define IOMUX_GPIO15_FUNC_UART0_RTS        IOMUX_FUNC(4)
+
+#endif /* _ESP_IOMUX_REGS_H */
diff --git a/core/include/esp/registers.h b/core/include/esp/registers.h
index 7468dfb..97e285b 100644
--- a/core/include/esp/registers.h
+++ b/core/include/esp/registers.h
@@ -14,295 +14,43 @@
 #ifndef _ESP_REGISTERS
 #define _ESP_REGISTERS
 #include "common_macros.h"
+#include "esp/types.h"
 
-typedef volatile uint32_t *esp_reg_t;
-
-/* Internal macro, only defined in header body */
-#define _REG(BASE, OFFSET) (*(esp_reg_t)((BASE)+(OFFSET)))
+#include "esp/uart_regs.h"
+#include "esp/spi_regs.h"
+#include "esp/iomux_regs.h"
+#include "esp/gpio_regs.h"
+#include "esp/timer_regs.h"
+#include "esp/wdt_regs.h"
+#include "esp/rtcmem_regs.h"
+#include "esp/dport_regs.h"
 
 /* Register base addresses
 
-   You shouldn't need to use these directly.
+   The base addresses below are ones which haven't been migrated to
+   the new register header style yet. For any commented out lines, see
+   the matching xxx_regs.h header file referenced above.
+
+   If you want to access registers that aren't mapped to the new
+   register header system yet, you can either use the deprecated
+   Espressif SDK headers (in include/espressif), or you can create a
+   new register header file and contribute it to the project (hooray!)
  */
 #define MMIO_BASE   0x60000000
-#define DPORT_BASE  0x3ff00000
+//#define DPORT_BASE  0x3ff00000
 
-#define UART0_BASE (MMIO_BASE + 0)
-#define SPI1_BASE  (MMIO_BASE + 0x0100)
-#define SPI_BASE   (MMIO_BASE + 0x0200)
-#define GPIO0_BASE (MMIO_BASE + 0x0300)
-#define TIMER_BASE (MMIO_BASE + 0x0600)
+//#define UART0_BASE (MMIO_BASE + 0)
+//#define SPI1_BASE  (MMIO_BASE + 0x0100)
+//#define SPI_BASE   (MMIO_BASE + 0x0200)
+//#define GPIO0_BASE (MMIO_BASE + 0x0300)
+//#define TIMER_BASE (MMIO_BASE + 0x0600)
 #define RTC_BASE   (MMIO_BASE + 0x0700)
-#define IOMUX_BASE (MMIO_BASE + 0x0800)
-#define WDT_BASE   (MMIO_BASE + 0x0900)
+//#define IOMUX_BASE (MMIO_BASE + 0x0800)
+//#define WDT_BASE   (MMIO_BASE + 0x0900)
 #define I2C_BASE   (MMIO_BASE + 0x0d00)
-#define UART1_BASE (MMIO_BASE + 0x0F00)
-#define RTCB_BASE  (MMIO_BASE + 0x1000)
-#define RTCS_BASE  (MMIO_BASE + 0x1100)
-#define RTCU_BASE  (MMIO_BASE + 0x1200)
-
-/*
- * iomux registers, apply to pin functions.
- *
- * Note that IOMUX register order is _not_ the same as GPIO order. See
- * esp_iomux.h for programmer-friendly IOMUX configuration options
- */
-#define IOMUX_REG(X) _REG(IOMUX_BASE,0x04+4*X)
-
-#define IOMUX_OE       BIT(0) /* iomux Output enable bit */
-#define IOMUX_OE_SLEEP BIT(1) /* iomux Output during sleep bit */
-
-#define IOMUX_PD       BIT(6) /* iomux soft pulldown bit */
-#define IOMUX_PD_SLEEP BIT(2) /* iomux soft pulldown during sleep bit */
-#define IOMUX_PU       BIT(7) /* iomux soft pullup bit */
-#define IOMUX_PU_SLEEP BIT(3) /* iomux soft pullup during sleep bit */
-
-#define IOMUX_FLAG_WAKE_MASK (IOMUX_OE|IOMUX_PD|IOMUX_PU)
-#define IOMUX_FLAG_SLEEP_MASK (IOMUX_OE_SLEEP|IOMUX_PD_SLEEP|IOMUX_PU_SLEEP)
-#define IOMUX_FLAG_MASK (IOMUX_FLAG_WAKE_MASK|IOMUX_FLAG_SLEEP_MASK)
-
-#define IOMUX_FUNC_MASK (BIT(4)|BIT(5)|BIT(12))
-
-/* All pins have FUNC_A on reset (unconfirmed) */
-#define IOMUX_FUNC_A (0)
-#define IOMUX_FUNC_B BIT(4)
-#define IOMUX_FUNC_C BIT(5)
-#define IOMUX_FUNC_D BIT(4)|BIT(5)
-#define IOMUX_FUNC_E BIT(12)
-
-
-/*
- * Based on descriptions by mamalala at https://github.com/esp8266/esp8266-wiki/wiki/gpio-registers
- */
-
-/** GPIO OUTPUT registers GPIO_OUT_REG, GPIO_OUT_SET, GPIO_OUT_CLEAR
- *
- * Registers for pin outputs.
- *
- * _SET and _CLEAR write-only registers set and clear bits in _REG,
- * respectively.
- *
- * ie
- * GPIO_OUT_REG |= BIT(3);
- * and
- * GPIO_OUT_SET = BIT(3);
- *
- * ... are equivalent, but latter uses less CPU cycles.
- */
-#define GPIO_OUT_REG   _REG(GPIO0_BASE, 0x00)
-#define GPIO_OUT_SET   _REG(GPIO0_BASE, 0x04)
-#define GPIO_OUT_CLEAR _REG(GPIO0_BASE, 0x08)
-
-/* GPIO DIR registers GPIO_DIR_REG, GPIO_DIR_SET, GPIO_DIR_CLEAR
- *
- * Set bit in DIR register for output pins. Writing to _SET and _CLEAR
- * registers set and clear bits in _REG, respectively.
-*/
-#define GPIO_DIR_REG   _REG(GPIO0_BASE, 0x0C)
-#define GPIO_DIR_SET   _REG(GPIO0_BASE, 0x10)
-#define GPIO_DIR_CLEAR _REG(GPIO0_BASE, 0x14)
-
-
-/* GPIO IN register GPIO_IN_REG
- *
- * Reads current input values.
- */
-#define GPIO_IN_REG    _REG(GPIO0_BASE, 0x18)
-
-/* GPIO interrupt 'status' flag
-
-   Bit set if interrupt has fired (see below for interrupt config
-   registers.
-
-   Lower 16 bits only are used.
-*/
-#define GPIO_STATUS_REG   _REG(GPIO0_BASE,0x1c)
-#define GPIO_STATUS_SET   _REG(GPIO0_BASE,0x20)
-#define GPIO_STATUS_CLEAR _REG(GPIO0_BASE,0x24)
-
-#define GPIO_STATUS_MASK  0x0000FFFFL
-
-/* GPIO pin control registers for GPIOs 0-15
- *
- */
-#define GPIO_CTRL_REG(GPNUM) _REG(GPIO0_BASE, 0x28+(GPNUM*4))
-
-#define GPIO_SOURCE_GPIO 0
-#define GPIO_SOURCE_DAC BIT(0) /* "Sigma-Delta" */
-#define GPIO_SOURCE_MASK BIT(0
-
-#define GPIO_DRIVE_PUSH_PULL 0
-#define GPIO_DRIVE_OPEN_DRAIN BIT(2)
-#define GPIO_DRIVE_MASK BIT(2)
-
-#define GPIO_INT_NONE 0
-#define GPIO_INT_RISING BIT(7)
-#define GPIO_INT_FALLING BIT(8)
-#define GPIO_INT_CHANGE (BIT(7)|BIT(8))
-#define GPIO_INT_LOW BIT(9)
-#define GPIO_INT_HIGH (BIT(7)|BIT(9))
-#define GPIO_INT_MASK (BIT(7)|BIT(8)|BIT(9))
-
-/* TIMER registers
- *
- * ESP8266 has two hardware(?) timer counters, FRC1 and FRC2.
- *
- * FRC1 is a 24-bit countdown timer, triggers interrupt when reaches zero.
- * FRC2 is a 32-bit countup timer, can set a variable match value to trigger an interrupt.
- *
- * FreeRTOS tick timer appears to come from XTensa core tick timer0,
- * not either of these.  FRC2 is used in the FreeRTOS SDK however. It
- * is set to free-run, interrupting periodically via updates to the
- * MATCH register. sdk_ets_timer_init configures FRC2 and assigns FRC2
- * interrupt handler at sdk_vApplicationTickHook+0x68
- */
-
-/* Load value for FRC1, read/write.
-
-   When TIMER_CTRL_RELOAD is cleared in TIMER_FRC1_CTRL_REG, FRC1 will
-   reload to TIMER_FRC1_MAX_LOAD once overflowed (unless the load
-   value is rewritten in the interrupt handler.)
-
-   When TIMER_CTRL_RELOAD is set in TIMER_FRC1_CTRL_REG, FRC1 will reload
-   from the load register value once overflowed.
-*/
-#define TIMER_FRC1_LOAD_REG   _REG(TIMER_BASE, 0x00)
-
-#define TIMER_FRC1_MAX_LOAD 0x7fffff
-
-/* Current count value for FRC1, read only? */
-#define TIMER_FRC1_COUNT_REG  _REG(TIMER_BASE, 0x04)
-
-/* Control register for FRC1, read/write.
-
-   See the bit definitions TIMER_CTRL_xxx lower down.
- */
-#define TIMER_FRC1_CTRL_REG  _REG(TIMER_BASE, 0x08)
-
-/* Reading this register always returns the value in
- * TIMER_FRC1_LOAD_REG.
- *
- * Writing zero to this register clears the FRC1
- * interrupt status.
- */
-#define TIMER_FRC1_CLEAR_INT_REG  _REG(TIMER_BASE, 0x0c)
-
-/* FRC2 load register.
- *
- * If TIMER_CTRL_RELOAD is cleared in TIMER_FRC2_CTRL_REG, writing to
- * this register will update the FRC2 COUNT value.
- *
- * If TIMER_CTRL_RELOAD is set in TIMER_FRC2_CTRL_REG, the behaviour
- * appears to be the same except that writing 0 to the load register
- * both sets the COUNT register to 0 and disables the timer, even if
- * the TIMER_CTRL_RUN bit is set.
- *
- * Offsets 0x34, 0x38, 0x3c all seem to read back the LOAD_REG value
- * also (but have no known function.)
- */
-#define TIMER_FRC2_LOAD_REG   _REG(TIMER_BASE, 0x20)
-
-/* FRC2 current count value. Read only? */
-#define TIMER_FRC2_COUNT_REG _REG(TIMER_BASE, 0x24)
-
-/* Control register for FRC2. Read/write.
-
-   See the bit definitions TIMER_CTRL_xxx lower down.
-*/
-#define TIMER_FRC2_CTRL_REG  _REG(TIMER_BASE, 0x28)
-
-/* Reading this value returns the current value of
- * TIMER_FRC2_LOAD_REG.
- *
- * Writing zero to this value clears the FRC2 interrupt status.
- */
-#define TIMER_FRC2_CLEAR_INT_REG  _REG(TIMER_BASE, 0x2c)
-
-/* Interrupt match value for FRC2. When COUNT == MATCH,
-   the interrupt fires.
-*/
-#define TIMER_FRC2_MATCH_REG _REG(TIMER_BASE, 0x30)
-
-/* Timer control bits to set clock divisor values.
-
-   Divider from master 80MHz APB_CLK (unconfirmed, see esp/clocks.h).
-*/
-#define TIMER_CTRL_DIV_1  0
-#define TIMER_CTRL_DIV_16 BIT(2)
-#define TIMER_CTRL_DIV_256 BIT(3)
-#define TIMER_CTRL_DIV_MASK (BIT(2)|BIT(3))
-
-/* Set timer control bits to trigger interrupt on "edge" or "level"
- *
- * Observed behaviour is like this:
- *
- *  * When TIMER_CTRL_INT_LEVEL is set, the interrupt status bit
- *    TIMER_CTRL_INT_STATUS remains set when the timer interrupt
- *    triggers, unless manually cleared by writing 0 to
- *    TIMER_FRCx_CLEAR_INT.  While the interrupt status bit stays set
- *    the timer will continue to run normally, but the interrupt
- *    (INUM_TIMER_FRC1 or INUM_TIMER_FRC2) won't trigger again.
- *
- *  * When TIMER_CTRL_INT_EDGE (default) is set, there's no need to
- *    manually write to TIMER_FRCx_CLEAR_INT. The interrupt status bit
- *    TIMER_CTRL_INT_STATUS automatically clears after the interrupt
- *    triggers, and the interrupt handler will run again
- *    automatically.
- *
- */
-#define TIMER_CTRL_INT_EDGE 0
-#define TIMER_CTRL_INT_LEVEL BIT(0)
-#define TIMER_CTRL_INT_MASK BIT(0)
-
-/* Timer auto-reload bit
-
-   This bit interacts with TIMER_FRC1_LOAD_REG & TIMER_FRC2_LOAD_REG
-   differently, see those registers for details.
-*/
-#define TIMER_CTRL_RELOAD BIT(6)
-
-/* Timer run bit */
-#define TIMER_CTRL_RUN BIT(7)
-
-/* Read-only timer interrupt status.
-
-   This bit gets set on FRC1 when interrupt fires, and cleared on a
-   write to TIMER_FRC1_CLEAR_INT (cleared automatically if
-   TIMER_CTRL_INT_LEVEL is not set).
-*/
-#define TIMER_CTRL_INT_STATUS BIT(8)
-
-/* WDT register(s)
-
-   Not fully understood yet. Writing 0 here disables wdt.
-
-   See ROM functions esp_wdt_xxx
- */
-#define WDT_CTRL       _REG(WDT_BASE, 0x00)
-
-/* DPORT registers
-
-   Control various aspects of core/peripheral interaction... Not well
-   documented or understood.
-*/
-
-/* Set flags to enable CPU interrupts from some peripherals. Read/write.
-
-   bit 0 - Is set by RTOS SDK startup code but function is unknown.
-   bit 1 - INT_ENABLE_FRC1 allows TIMER FRC1 to trigger interrupt INUM_TIMER_FRC1.
-   bit 2 - INT_ENABLE_FRC2 allows TIMER FRC2 to trigger interrupt INUM_TIMER_FRC2.
-
-   Espressif calls this register "EDGE_INT_ENABLE_REG". The "edge" in
-   question is (I think) the interrupt line from the peripheral, as
-   the interrupt status bit is set. There may be a similar register
-   for enabling "level" interrupts instead of edge triggering
-   - this is unknown.
-*/
-#define DP_INT_ENABLE_REG _REG(DPORT_BASE, 0x04)
-
-/* Set to enable interrupts from TIMER FRC1 */
-#define INT_ENABLE_FRC1 BIT(1)
-/* Set to enable interrupts interrupts from TIMER FRC2 */
-#define INT_ENABLE_FRC2 BIT(2)
+//#define UART1_BASE (MMIO_BASE + 0x0F00)
+//#define RTCB_BASE  (MMIO_BASE + 0x1000)
+//#define RTCS_BASE  (MMIO_BASE + 0x1100)
+//#define RTCU_BASE  (MMIO_BASE + 0x1200)
 
 #endif
diff --git a/core/include/esp/rtcmem_regs.h b/core/include/esp/rtcmem_regs.h
new file mode 100644
index 0000000..8462b0e
--- /dev/null
+++ b/core/include/esp/rtcmem_regs.h
@@ -0,0 +1,44 @@
+/* esp/rtcmem_regs.h
+ *
+ * ESP8266 RTC semi-persistent memory register definitions
+ *
+ * Not compatible with ESP SDK register access code.
+ */
+
+#ifndef _ESP_RTCMEM_REGS_H
+#define _ESP_RTCMEM_REGS_H
+
+#include "esp/types.h"
+#include "common_macros.h"
+
+/* The RTC memory is a range of 256 words (1 KB) of general-purpose memory
+ * within the Real Time Clock peripheral.  Because it's part of the RTC, it
+ * continues to be powered (and retains its contents) even when the ESP8266 is
+ * in its deepest sleep mode (and other RAM is lost).  It can therefore be
+ * useful for keeping data which must be persisted through sleep or a reset.
+ *
+ * Note, however, that it is not "battery backed", or flash memory, and thus
+ * will not keep its contents if power is removed entirely.
+ */
+
+// We could just define these as 'volatile uint32_t *', but doing things this
+// way means that the RTCMEM* defines will include array size information, so
+// the C compiler can do bounds-checking for static arguments.
+
+typedef volatile uint32_t rtcmem_array64_t[64];
+typedef volatile uint32_t rtcmem_array128_t[128];
+typedef volatile uint32_t rtcmem_array256_t[256];
+
+#define RTCMEM_BASE 0x60001000
+
+/* RTCMEM is an array covering the entire semi-persistent memory range */
+#define RTCMEM (*(rtcmem_array256_t *)(RTCMEM_BASE))
+
+/* RTCMEM_BACKUP / RTCMEM_SYSTEM / RTCMEM_USER are the same range, divided up
+ * into chunks by application/use, as defined by Espressif */
+
+#define RTCMEM_BACKUP (*(rtcmem_array64_t *)(RTCMEM_BASE))
+#define RTCMEM_SYSTEM (*(rtcmem_array64_t *)(RTCMEM_BASE + 0x100))
+#define RTCMEM_USER (*(rtcmem_array128_t *)(RTCMEM_BASE + 0x200))
+
+#endif /* _ESP_RTCMEM_REGS_H */
diff --git a/core/include/esp/spi_regs.h b/core/include/esp/spi_regs.h
new file mode 100644
index 0000000..7dcf4db
--- /dev/null
+++ b/core/include/esp/spi_regs.h
@@ -0,0 +1,244 @@
+/** esp/spi.h
+ *
+ * Configuration of SPI registers.
+ *
+ * Part of esp-open-rtos
+ * Copyright (C) 2015 Superhouse Automation Pty Ltd
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef _SPI_REGS_H
+#define _SPI_REGS_H
+
+#include "esp/types.h"
+#include "common_macros.h"
+
+/* Register definitions for the SPI peripherals on the ESP8266.
+ *
+ * There are twp SPI devices built into the ESP8266:
+ *   SPI(0) is at 0x60000200
+ *   SPI(1) is at 0x60000100
+ * (note that the device number order is reversed in memory)
+ *
+ * Each device is allocated a block of 64 32-bit registers (256 bytes of
+ * address space) to communicate with application code.
+ */
+
+#define SPI_BASE 0x60000200
+#define SPI(i) (*(struct SPI_REGS *)(0x60000200 - (i)*0x100))
+
+#define SPI0_BASE SPI_BASE
+#define SPI1_BASE (SPI_BASE - 0x100)
+
+struct SPI_REGS {
+    uint32_t volatile CMD;          // 0x00
+    uint32_t volatile ADDR;         // 0x04
+    uint32_t volatile CTRL0;        // 0x08
+    uint32_t volatile CTRL1;        // 0x0c
+    uint32_t volatile RSTATUS;      // 0x10
+    uint32_t volatile CTRL2;        // 0x14
+    uint32_t volatile CLOCK;        // 0x18
+    uint32_t volatile USER0;        // 0x1c
+    uint32_t volatile USER1;        // 0x20
+    uint32_t volatile USER2;        // 0x24
+    uint32_t volatile WSTATUS;      // 0x28
+    uint32_t volatile PIN;          // 0x2c
+    uint32_t volatile SLAVE0;       // 0x30
+    uint32_t volatile SLAVE1;       // 0x34
+    uint32_t volatile SLAVE2;       // 0x38
+    uint32_t volatile SLAVE3;       // 0x3c
+    uint32_t volatile W0;           // 0x40
+    uint32_t volatile W1;           // 0x44
+    uint32_t volatile W2;           // 0x48
+    uint32_t volatile W3;           // 0x4c
+    uint32_t volatile W4;           // 0x50
+    uint32_t volatile W5;           // 0x54
+    uint32_t volatile W6;           // 0x58
+    uint32_t volatile W7;           // 0x5c
+    uint32_t volatile W8;           // 0x60
+    uint32_t volatile W9;           // 0x64
+    uint32_t volatile W10;          // 0x68
+    uint32_t volatile W11;          // 0x6c
+    uint32_t volatile W12;          // 0x70
+    uint32_t volatile W13;          // 0x74
+    uint32_t volatile W14;          // 0x78
+    uint32_t volatile W15;          // 0x7c
+    uint32_t volatile _unused[28];  // 0x80 - 0xec
+    uint32_t volatile EXT0;         // 0xf0
+    uint32_t volatile EXT1;         // 0xf4
+    uint32_t volatile EXT2;         // 0xf8
+    uint32_t volatile EXT3;         // 0xfc
+} __attribute__ (( packed ));
+
+_Static_assert(sizeof(struct SPI_REGS) == 0x100, "SPI_REGS is the wrong size");
+
+/* Details for CMD register */
+
+#define SPI_CMD_USR                        BIT(18)
+
+/* Details for CTRL0 register */
+
+#define SPI_CTRL0_WR_BIT_ORDER             BIT(26)
+#define SPI_CTRL0_RD_BIT_ORDER             BIT(25)
+#define SPI_CTRL0_QIO_MODE                 BIT(24)
+#define SPI_CTRL0_DIO_MODE                 BIT(23)
+#define SPI_CTRL0_QOUT_MODE                BIT(20)
+#define SPI_CTRL0_DOUT_MODE                BIT(14)
+#define SPI_CTRL0_FASTRD_MODE              BIT(13)
+#define SPI_CTRL0_CLOCK_EQU_SYS_CLOCK      BIT(12)
+#define SPI_CTRL0_CLOCK_NUM_M              0x0000000F
+#define SPI_CTRL0_CLOCK_NUM_S              8
+#define SPI_CTRL0_CLOCK_HIGH_M             0x0000000F
+#define SPI_CTRL0_CLOCK_HIGH_S             4
+#define SPI_CTRL0_CLOCK_LOW_M              0x0000000F
+#define SPI_CTRL0_CLOCK_LOW_S              0
+
+/* Mask for the CLOCK_NUM/CLOCK_HIGH/CLOCK_LOW combined, in case one wants
+ * to set them all as a single value.
+ */
+#define SPI_CTRL0_CLOCK_M                  0x00000FFF
+#define SPI_CTRL0_CLOCK_S                  0
+
+/* Details for CTRL2 register */
+
+#define SPI_CTRL2_CS_DELAY_NUM_M           0x0000000F
+#define SPI_CTRL2_CS_DELAY_NUM_S           28
+#define SPI_CTRL2_CS_DELAY_MODE_M          0x00000003
+#define SPI_CTRL2_CS_DELAY_MODE_S          26
+#define SPI_CTRL2_MOSI_DELAY_NUM_M         0x00000007
+#define SPI_CTRL2_MOSI_DELAY_NUM_S         23
+#define SPI_CTRL2_MOSI_DELAY_MODE_M        0x00000003
+#define SPI_CTRL2_MOSI_DELAY_MODE_S        21
+#define SPI_CTRL2_MISO_DELAY_NUM_M         0x00000007
+#define SPI_CTRL2_MISO_DELAY_NUM_S         18
+#define SPI_CTRL2_MISO_DELAY_MODE_M        0x00000003
+#define SPI_CTRL2_MISO_DELAY_MODE_S        16
+
+/* Details for CLOCK register */
+
+#define SPI_CLOCK_EQU_SYS_CLOCK            BIT(31)
+#define SPI_CLOCK_DIV_PRE_M                0x00001FFF
+#define SPI_CLOCK_DIV_PRE_S                18
+#define SPI_CLOCK_COUNT_NUM_M              0x0000003F
+#define SPI_CLOCK_COUNT_NUM_S              12
+#define SPI_CLOCK_COUNT_HIGH_M             0x0000003F
+#define SPI_CLOCK_COUNT_HIGH_S             6
+#define SPI_CLOCK_COUNT_LOW_M              0x0000003F
+#define SPI_CLOCK_COUNT_LOW_S              0
+
+/* Mask for the COUNT_NUM/COUNT_HIGH/COUNT_LOW combined, in case one wants
+ * to set them all as a single value.
+ */
+#define SPI_CTRL0_COUNT_M                  0x0003FFFF
+#define SPI_CTRL0_COUNT_S                  0
+
+/* Details for USER0 register */
+
+#define SPI_USER0_COMMAND                  BIT(31)
+#define SPI_USER0_ADDR                     BIT(30)
+#define SPI_USER0_DUMMY                    BIT(29)
+#define SPI_USER0_MISO                     BIT(28)
+#define SPI_USER0_MOSI                     BIT(27)
+#define SPI_USER0_MOSI_HIGHPART            BIT(25)
+#define SPI_USER0_MISO_HIGHPART            BIT(24)
+#define SPI_USER0_SIO                      BIT(16)
+#define SPI_USER0_FWRITE_QIO               BIT(15)
+#define SPI_USER0_FWRITE_DIO               BIT(14)
+#define SPI_USER0_FWRITE_QUAD              BIT(13)
+#define SPI_USER0_FWRITE_DUAL              BIT(12)
+#define SPI_USER0_WR_BYTE_ORDER            BIT(11)
+#define SPI_USER0_RD_BYTE_ORDER            BIT(10)
+#define SPI_USER0_CLOCK_OUT_EDGE           BIT(7)
+#define SPI_USER0_CLOCK_IN_EDGE            BIT(6)
+#define SPI_USER0_CS_SETUP                 BIT(5)
+#define SPI_USER0_CS_HOLD                  BIT(4)
+#define SPI_USER0_FLASH_MODE               BIT(2)
+
+/* Details for USER1 register */
+
+#define SPI_USER1_ADDR_BITLEN_M            0x0000003F
+#define SPI_USER1_ADDR_BITLEN_S            26
+#define SPI_USER1_MOSI_BITLEN_M            0x000001FF
+#define SPI_USER1_MOSI_BITLEN_S            17
+#define SPI_USER1_MISO_BITLEN_M            0x000001FF
+#define SPI_USER1_MISO_BITLEN_S            8
+#define SPI_USER1_DUMMY_CYCLELEN_M         0x000000FF
+#define SPI_USER1_DUMMY_CYCLELEN_S         0
+
+/* Details for USER2 register */
+
+#define SPI_USER2_COMMAND_BITLEN_M         0x0000000F
+#define SPI_USER2_COMMAND_BITLEN_S         28
+#define SPI_USER2_COMMAND_VALUE_M          0x0000FFFF
+#define SPI_USER2_COMMAND_VALUE_S          0
+
+/* Details for PIN register */
+
+#define SPI_PIN_CS2_DISABLE                BIT(2)
+#define SPI_PIN_CS1_DISABLE                BIT(1)
+#define SPI_PIN_CS0_DISABLE                BIT(0)
+
+/* Details for SLAVE0 register */
+
+#define SPI_SLAVE0_SYNC_RESET              BIT(31)
+#define SPI_SLAVE0_MODE                    BIT(30)
+#define SPI_SLAVE0_WR_RD_BUF_EN            BIT(29)
+#define SPI_SLAVE0_WR_RD_STA_EN            BIT(28)
+#define SPI_SLAVE0_CMD_DEFINE              BIT(27)
+#define SPI_SLAVE0_TRANS_COUNT_M           0x0000000F
+#define SPI_SLAVE0_TRANS_COUNT_S           23
+#define SPI_SLAVE0_TRANS_DONE_EN           BIT(9)
+#define SPI_SLAVE0_WR_STA_DONE_EN          BIT(8)
+#define SPI_SLAVE0_RD_STA_DONE_EN          BIT(7)
+#define SPI_SLAVE0_WR_BUF_DONE_EN          BIT(6)
+#define SPI_SLAVE0_RD_BUF_DONE_EN          BIT(5)
+#define SPI_SLAVE0_INT_EN_M                0x0000001f
+#define SPI_SLAVE0_INT_EN_S                5
+#define SPI_SLAVE0_TRANS_DONE              BIT(4)
+#define SPI_SLAVE0_WR_STA_DONE             BIT(3)
+#define SPI_SLAVE0_RD_STA_DONE             BIT(2)
+#define SPI_SLAVE0_WR_BUF_DONE             BIT(1)
+#define SPI_SLAVE0_RD_BUF_DONE             BIT(0)
+
+/* Details for SLAVE1 register */
+
+#define SPI_SLAVE1_STATUS_BITLEN_M         0x0000001F
+#define SPI_SLAVE1_STATUS_BITLEN_S         27
+#define SPI_SLAVE1_BUF_BITLEN_M            0x000001FF
+#define SPI_SLAVE1_BUF_BITLEN_S            16
+#define SPI_SLAVE1_RD_ADDR_BITLEN_M        0x0000003F
+#define SPI_SLAVE1_RD_ADDR_BITLEN_S        10
+#define SPI_SLAVE1_WR_ADDR_BITLEN_M        0x0000003F
+#define SPI_SLAVE1_WR_ADDR_BITLEN_S        4
+#define SPI_SLAVE1_WRSTA_DUMMY_ENABLE      BIT(3)
+#define SPI_SLAVE1_RDSTA_DUMMY_ENABLE      BIT(2)
+#define SPI_SLAVE1_WRBUF_DUMMY_ENABLE      BIT(1)
+#define SPI_SLAVE1_RDBUF_DUMMY_ENABLE      BIT(0)
+
+/* Details for SLAVE2 register */
+
+#define SPI_SLAVE2_WRBUF_DUMMY_CYCLELEN_M  0x000000FF
+#define SPI_SLAVE2_WRBUF_DUMMY_CYCLELEN_S  24
+#define SPI_SLAVE2_RDBUF_DUMMY_CYCLELEN_M  0x000000FF
+#define SPI_SLAVE2_RDBUF_DUMMY_CYCLELEN_S  16
+#define SPI_SLAVE2_WRSTR_DUMMY_CYCLELEN_M  0x000000FF
+#define SPI_SLAVE2_WRSTR_DUMMY_CYCLELEN_S  8
+#define SPI_SLAVE2_RDSTR_DUMMY_CYCLELEN_M  0x000000FF
+#define SPI_SLAVE2_RDSTR_DUMMY_CYCLELEN_S  0
+
+/* Details for SLAVE3 register */
+
+#define SPI_SLAVE3_WRSTA_CMD_VALUE_M       0x000000FF
+#define SPI_SLAVE3_WRSTA_CMD_VALUE_S       24
+#define SPI_SLAVE3_RDSTA_CMD_VALUE_M       0x000000FF
+#define SPI_SLAVE3_RDSTA_CMD_VALUE_S       16
+#define SPI_SLAVE3_WRBUF_CMD_VALUE_M       0x000000FF
+#define SPI_SLAVE3_WRBUF_CMD_VALUE_S       8
+#define SPI_SLAVE3_RDBUF_CMD_VALUE_M       0x000000FF
+#define SPI_SLAVE3_RDBUF_CMD_VALUE_S       0
+
+/* Details for EXT3 register */
+
+#define SPI_EXT3_INT_HOLD_ENABLE_M         0x00000003
+#define SPI_EXT3_INT_HOLD_ENABLE_S         0
+
+#endif /* _SPI_REGS_H */
diff --git a/core/include/esp/timer.h b/core/include/esp/timer.h
index d1eea45..b83a0ee 100644
--- a/core/include/esp/timer.h
+++ b/core/include/esp/timer.h
@@ -11,12 +11,12 @@
 
 #include <stdbool.h>
 #include <xtensa_interrupts.h>
-#include "esp/registers.h"
+#include "esp/timer_regs.h"
 #include "esp/cpu.h"
 
 typedef enum {
-    TIMER_FRC1,
-    TIMER_FRC2,
+    FRC1 = 0,
+    FRC2 = 1,
 } timer_frc_t;
 
 /* Return current count value for timer. */
@@ -31,14 +31,8 @@ INLINED void timer_set_load(const timer_frc_t frc, const uint32_t load);
 /* Returns maximum load value for timer. */
 INLINED uint32_t timer_max_load(const timer_frc_t frc);
 
-typedef enum {
-    TIMER_DIV1,
-    TIMER_DIV16,
-    TIMER_DIV256,
-} timer_div_t;
-
 /* Set the timer divider value */
-INLINED void timer_set_divider(const timer_frc_t frc, const timer_div_t div);
+INLINED void timer_set_divider(const timer_frc_t frc, const timer_clkdiv_t div);
 
 /* Enable or disable timer interrupts
 
@@ -62,7 +56,7 @@ INLINED bool timer_get_reload(const timer_frc_t frc);
 /* Return a suitable timer divider for the specified frequency,
    or -1 if none is found.
  */
-INLINED timer_div_t timer_freq_to_div(uint32_t freq);
+INLINED timer_clkdiv_t timer_freq_to_div(uint32_t freq);
 
 /* Return the number of timer counts to achieve the specified
  * frequency with the specified divisor.
@@ -73,12 +67,12 @@ INLINED timer_div_t timer_freq_to_div(uint32_t freq);
  *
  * Compile-time evaluates if all arguments are available at compile time.
  */
-INLINED uint32_t timer_freq_to_count(const timer_frc_t frc, uint32_t freq, const timer_div_t div);
+INLINED uint32_t timer_freq_to_count(const timer_frc_t frc, uint32_t freq, const timer_clkdiv_t div);
 
 /* Return a suitable timer divider for the specified duration in
    microseconds or -1 if none is found.
  */
-INLINED timer_div_t timer_time_to_div(uint32_t us);
+INLINED timer_clkdiv_t timer_time_to_div(uint32_t us);
 
 /* Return the number of timer counts for the specified timer duration
  * in microseconds, when using the specified divisor.
@@ -89,7 +83,7 @@ INLINED timer_div_t timer_time_to_div(uint32_t us);
  *
  * Compile-time evaluates if all arguments are available at compile time.
  */
-INLINED uint32_t timer_time_to_count(const timer_frc_t frc, uint32_t us, const timer_div_t div);
+INLINED uint32_t timer_time_to_count(const timer_frc_t frc, uint32_t us, const timer_clkdiv_t div);
 
 /* Set a target timer interrupt frequency in Hz.
 
diff --git a/core/include/esp/timer_private.h b/core/include/esp/timer_private.h
index fad3283..530a396 100644
--- a/core/include/esp/timer_private.h
+++ b/core/include/esp/timer_private.h
@@ -10,6 +10,7 @@
 #include <limits.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include "esp/dport_regs.h"
 
 /* Timer divisor index to max frequency */
 #define _FREQ_DIV1  (80*1000*1000)
@@ -20,104 +21,90 @@ const static uint32_t IROM _TIMER_FREQS[] = { _FREQ_DIV1, _FREQ_DIV16, _FREQ_DIV
 /* Timer divisor index to divisor value */
 const static uint32_t IROM _TIMER_DIV_VAL[] = { 1, 16, 256 };
 
-/* Timer divisor to mask value */
-const static uint32_t IROM _TIMER_DIV_REG[] = { TIMER_CTRL_DIV_1, TIMER_CTRL_DIV_16, TIMER_CTRL_DIV_256 };
-
-INLINED esp_reg_t _timer_ctrl_reg(const timer_frc_t frc)
-{
-    return (frc == TIMER_FRC1) ? &TIMER_FRC1_CTRL_REG : &TIMER_FRC2_CTRL_REG;
-}
-
 INLINED uint32_t timer_get_count(const timer_frc_t frc)
 {
-    return (frc == TIMER_FRC1) ? TIMER_FRC1_COUNT_REG : TIMER_FRC2_COUNT_REG;
+    return TIMER(frc).COUNT;
 }
 
 INLINED uint32_t timer_get_load(const timer_frc_t frc)
 {
-    return (frc == TIMER_FRC1) ? TIMER_FRC1_LOAD_REG : TIMER_FRC2_LOAD_REG;
+    return TIMER(frc).LOAD;
 }
 
 INLINED void timer_set_load(const timer_frc_t frc, const uint32_t load)
 {
-    if(frc == TIMER_FRC1)
-        TIMER_FRC1_LOAD_REG = load;
-    else
-        TIMER_FRC2_LOAD_REG = load;
+    TIMER(frc).LOAD = load;
 }
 
 INLINED uint32_t timer_max_load(const timer_frc_t frc)
 {
-    return (frc == TIMER_FRC1) ? TIMER_FRC1_MAX_LOAD : UINT32_MAX;
+    return (frc == FRC1) ? TIMER_FRC1_MAX_LOAD : UINT32_MAX;
 }
 
-INLINED void timer_set_divider(const timer_frc_t frc, const timer_div_t div)
+INLINED void timer_set_divider(const timer_frc_t frc, const timer_clkdiv_t div)
 {
-    if(div < TIMER_DIV1 || div > TIMER_DIV256)
+    if(div < TIMER_CLKDIV_1 || div > TIMER_CLKDIV_256)
         return;
-    esp_reg_t ctrl = _timer_ctrl_reg(frc);
-    *ctrl = (*ctrl & ~TIMER_CTRL_DIV_MASK) | (_TIMER_DIV_REG[div] & TIMER_CTRL_DIV_MASK);
+    TIMER(frc).CTRL = SET_FIELD(TIMER(frc).CTRL, TIMER_CTRL_CLKDIV, div);
 }
 
 INLINED void timer_set_interrupts(const timer_frc_t frc, bool enable)
 {
-    const uint32_t dp_bit = (frc == TIMER_FRC1) ? INT_ENABLE_FRC1 : INT_ENABLE_FRC2;
-    const uint32_t int_mask = BIT((frc == TIMER_FRC1) ? INUM_TIMER_FRC1 : INUM_TIMER_FRC2);
+    const uint32_t dp_bit = (frc == FRC1) ? DPORT_INT_ENABLE_FRC1 : DPORT_INT_ENABLE_FRC2;
+    const uint32_t int_mask = BIT((frc == FRC1) ? INUM_TIMER_FRC1 : INUM_TIMER_FRC2);
     if(enable) {
-        DP_INT_ENABLE_REG |= dp_bit;
+        DPORT.INT_ENABLE |= dp_bit;
         _xt_isr_unmask(int_mask);
     } else {
-        DP_INT_ENABLE_REG &= ~dp_bit;
+        DPORT.INT_ENABLE &= ~dp_bit;
         _xt_isr_mask(int_mask);
     }
 }
 
 INLINED void timer_set_run(const timer_frc_t frc, const bool run)
 {
-    esp_reg_t ctrl = _timer_ctrl_reg(frc);
     if (run)
-        *ctrl |= TIMER_CTRL_RUN;
+        TIMER(frc).CTRL |= TIMER_CTRL_RUN;
     else
-        *ctrl &= ~TIMER_CTRL_RUN;
+        TIMER(frc).CTRL &= ~TIMER_CTRL_RUN;
 }
 
 INLINED bool timer_get_run(const timer_frc_t frc)
 {
-    return *_timer_ctrl_reg(frc) & TIMER_CTRL_RUN;
+    return TIMER(frc).CTRL & TIMER_CTRL_RUN;
 }
 
 INLINED void timer_set_reload(const timer_frc_t frc, const bool reload)
 {
-    esp_reg_t ctrl = _timer_ctrl_reg(frc);
     if (reload)
-        *ctrl |= TIMER_CTRL_RELOAD;
+        TIMER(frc).CTRL |= TIMER_CTRL_RELOAD;
     else
-        *ctrl &= ~TIMER_CTRL_RELOAD;
+        TIMER(frc).CTRL &= ~TIMER_CTRL_RELOAD;
 }
 
 INLINED bool timer_get_reload(const timer_frc_t frc)
 {
-    return *_timer_ctrl_reg(frc) & TIMER_CTRL_RELOAD;
+    return TIMER(frc).CTRL & TIMER_CTRL_RELOAD;
 }
 
-INLINED timer_div_t timer_freq_to_div(uint32_t freq)
+INLINED timer_clkdiv_t timer_freq_to_div(uint32_t freq)
 {
     /*
       try to maintain resolution without risking overflows.
       these values are a bit arbitrary at the moment! */
     if(freq > 100*1000)
-        return TIMER_DIV1;
+        return TIMER_CLKDIV_1;
     else if(freq > 100)
-        return TIMER_DIV16;
+        return TIMER_CLKDIV_16;
     else
-        return TIMER_DIV256;
+        return TIMER_CLKDIV_256;
 }
 
 /* timer_timer_to_count implementation - inline if all args are constant, call normally otherwise */
 
-INLINED uint32_t _timer_freq_to_count_impl(const timer_frc_t frc, const uint32_t freq, const timer_div_t div)
+INLINED uint32_t _timer_freq_to_count_impl(const timer_frc_t frc, const uint32_t freq, const timer_clkdiv_t div)
 {
-    if(div < TIMER_DIV1 || div > TIMER_DIV256)
+    if(div < TIMER_CLKDIV_1 || div > TIMER_CLKDIV_256)
         return 0; /* invalid divider */
 
     if(freq > _TIMER_FREQS[div])
@@ -127,9 +114,9 @@ INLINED uint32_t _timer_freq_to_count_impl(const timer_frc_t frc, const uint32_t
     return counts;
 }
 
-uint32_t _timer_freq_to_count_runtime(const timer_frc_t frc, const uint32_t freq, const timer_div_t div);
+uint32_t _timer_freq_to_count_runtime(const timer_frc_t frc, const uint32_t freq, const timer_clkdiv_t div);
 
-INLINED uint32_t timer_freq_to_count(const timer_frc_t frc, const uint32_t freq, const timer_div_t div)
+INLINED uint32_t timer_freq_to_count(const timer_frc_t frc, const uint32_t freq, const timer_clkdiv_t div)
 {
     if(__builtin_constant_p(frc) && __builtin_constant_p(freq) && __builtin_constant_p(div))
         return _timer_freq_to_count_impl(frc, freq, div);
@@ -137,33 +124,33 @@ INLINED uint32_t timer_freq_to_count(const timer_frc_t frc, const uint32_t freq,
         return _timer_freq_to_count_runtime(frc, freq, div);
 }
 
-INLINED timer_div_t timer_time_to_div(uint32_t us)
+INLINED timer_clkdiv_t timer_time_to_div(uint32_t us)
 {
     /*
       try to maintain resolution without risking overflows. Similar to
       timer_freq_to_div, these values are a bit arbitrary at the
       moment! */
     if(us < 1000)
-        return TIMER_DIV1;
+        return TIMER_CLKDIV_1;
     else if(us < 10*1000)
-        return TIMER_DIV16;
+        return TIMER_CLKDIV_16;
     else
-        return TIMER_DIV256;
+        return TIMER_CLKDIV_256;
 }
 
 /* timer_timer_to_count implementation - inline if all args are constant, call normally otherwise */
 
-INLINED uint32_t _timer_time_to_count_impl(const timer_frc_t frc, uint32_t us, const timer_div_t div)
+INLINED uint32_t _timer_time_to_count_impl(const timer_frc_t frc, uint32_t us, const timer_clkdiv_t div)
 {
-    if(div < TIMER_DIV1 || div > TIMER_DIV256)
+    if(div < TIMER_CLKDIV_1 || div > TIMER_CLKDIV_256)
         return 0; /* invalid divider */
 
     const uint32_t TIMER_MAX = timer_max_load(frc);
 
-    if(div != TIMER_DIV256) /* timer tick in MHz */
+    if(div != TIMER_CLKDIV_256) /* timer tick in MHz */
     {
         /* timer is either 80MHz or 5MHz, so either 80 or 5 MHz counts per us */
-        const uint32_t counts_per_us = ((div == TIMER_DIV1) ? _FREQ_DIV1 : _FREQ_DIV16)/1000/1000;
+        const uint32_t counts_per_us = ((div == TIMER_CLKDIV_1) ? _FREQ_DIV1 : _FREQ_DIV16)/1000/1000;
         if(us > TIMER_MAX/counts_per_us)
             return 0; /* Multiplying us by mhz_per_count will overflow TIMER_MAX */
         return us*counts_per_us;
@@ -186,9 +173,9 @@ INLINED uint32_t _timer_time_to_count_impl(const timer_frc_t frc, uint32_t us, c
     }
 }
 
-uint32_t _timer_time_to_count_runtime(const timer_frc_t frc, uint32_t us, const timer_div_t div);
+uint32_t _timer_time_to_count_runtime(const timer_frc_t frc, uint32_t us, const timer_clkdiv_t div);
 
-INLINED uint32_t timer_time_to_count(const timer_frc_t frc, uint32_t us, const timer_div_t div)
+INLINED uint32_t timer_time_to_count(const timer_frc_t frc, uint32_t us, const timer_clkdiv_t div)
 {
     if(__builtin_constant_p(frc) && __builtin_constant_p(us) && __builtin_constant_p(div))
         return _timer_time_to_count_impl(frc, us, div);
@@ -201,7 +188,7 @@ INLINED uint32_t timer_time_to_count(const timer_frc_t frc, uint32_t us, const t
 INLINED bool _timer_set_frequency_impl(const timer_frc_t frc, uint32_t freq)
 {
     uint32_t counts = 0;
-    timer_div_t div = timer_freq_to_div(freq);
+    timer_clkdiv_t div = timer_freq_to_div(freq);
 
     counts = timer_freq_to_count(frc, freq, div);
     if(counts == 0)
@@ -211,7 +198,7 @@ INLINED bool _timer_set_frequency_impl(const timer_frc_t frc, uint32_t freq)
     }
 
     timer_set_divider(frc, div);
-    if(frc == TIMER_FRC1)
+    if(frc == FRC1)
     {
         timer_set_load(frc, counts);
         timer_set_reload(frc, true);
@@ -219,7 +206,7 @@ INLINED bool _timer_set_frequency_impl(const timer_frc_t frc, uint32_t freq)
     else /* FRC2 */
     {
         /* assume that if this overflows it'll wrap, so we'll get desired behaviour */
-        TIMER_FRC2_MATCH_REG = counts + TIMER_FRC2_COUNT_REG;
+        TIMER(1).ALARM = counts + TIMER(1).COUNT;
     }
     return true;
 }
@@ -239,20 +226,20 @@ INLINED bool timer_set_frequency(const timer_frc_t frc, uint32_t freq)
 INLINED bool _timer_set_timeout_impl(const timer_frc_t frc, uint32_t us)
 {
     uint32_t counts = 0;
-    timer_div_t div = timer_time_to_div(us);
+    timer_clkdiv_t div = timer_time_to_div(us);
 
     counts = timer_time_to_count(frc, us, div);
     if(counts == 0)
         return false; /* can't set frequency */
 
     timer_set_divider(frc, div);
-    if(frc == TIMER_FRC1)
+    if(frc == FRC1)
     {
         timer_set_load(frc, counts);
     }
     else /* FRC2 */
     {
-        TIMER_FRC2_MATCH_REG = counts + TIMER_FRC2_COUNT_REG;
+        TIMER(1).ALARM = counts + TIMER(1).COUNT;
     }
 
     return true;
diff --git a/core/include/esp/timer_regs.h b/core/include/esp/timer_regs.h
new file mode 100644
index 0000000..f2aacfc
--- /dev/null
+++ b/core/include/esp/timer_regs.h
@@ -0,0 +1,125 @@
+/* esp/timer_regs.h
+ *
+ * ESP8266 Timer register definitions
+ *
+ * Not compatible with ESP SDK register access code.
+ */
+
+#ifndef _ESP_TIMER_REGS_H
+#define _ESP_TIMER_REGS_H
+
+#include "esp/types.h"
+#include "common_macros.h"
+
+#define TIMER_BASE 0x60000600
+#define TIMER(i) (*(struct TIMER_REGS *)(TIMER_BASE + (i)*0x20))
+#define TIMER_FRC1 TIMER(0)
+#define TIMER_FRC2 TIMER(1)
+
+/* TIMER registers
+ *
+ * ESP8266 has two hardware timer counters, FRC1 and FRC2.
+ *
+ * FRC1 is a 24-bit countdown timer, triggers interrupt when reaches zero.
+ * FRC2 is a 32-bit countup timer, can set a variable match value to trigger an interrupt.
+ *
+ * FreeRTOS tick timer appears to come from XTensa core tick timer0,
+ * not either of these.  FRC2 is used in the FreeRTOS SDK however. It
+ * is set to free-run, interrupting periodically via updates to the
+ * ALARM register. sdk_ets_timer_init configures FRC2 and assigns FRC2
+ * interrupt handler at sdk_vApplicationTickHook+0x68
+ */
+
+struct TIMER_REGS {            // FRC1  FRC2
+    uint32_t volatile LOAD;    // 0x00  0x20
+    uint32_t volatile COUNT;   // 0x04  0x24
+    uint32_t volatile CTRL;    // 0x08  0x28
+    uint32_t volatile STATUS;  // 0x0c  0x2c
+    uint32_t volatile ALARM;   //       0x30
+} __attribute__ (( packed ));
+
+_Static_assert(sizeof(struct TIMER_REGS) == 0x14, "TIMER_REGS is the wrong size");
+
+#define TIMER_FRC1_MAX_LOAD 0x7fffff
+
+/* Details for LOAD registers */
+
+/* Behavior for FRC1:
+ *
+ * When TIMER_CTRL_RELOAD is cleared in TIMER(0).CTRL, FRC1 will
+ * reload to its max value once underflowed (unless the load
+ * value is rewritten in the interrupt handler.)
+ *
+ * When TIMER_CTRL_RELOAD is set in TIMER(0).CTRL, FRC1 will reload
+ * from the load register value once underflowed.
+ *
+ * Behavior for FRC2:
+ *
+ * If TIMER_CTRL_RELOAD is cleared in TIMER(1).CTRL, writing to
+ * this register will update the FRC2 COUNT value.
+ *
+ * If TIMER_CTRL_RELOAD is set in TIMER(1).CTRL, the behaviour
+ * appears to be the same except that writing 0 to the load register
+ * both sets the COUNT register to 0 and disables the timer, even if
+ * the TIMER_CTRL_RUN bit is set.
+ *
+ * Offsets 0x34, 0x38, 0x3c all seem to read back the LOAD_REG value
+ * also (but have no known function.)
+ */
+
+/* Details for CTRL registers */
+
+/* Observed behaviour is like this:
+ *
+ *  * When TIMER_CTRL_INT_HOLD is set, the interrupt status bit
+ *    TIMER_CTRL_INT_STATUS remains set when the timer interrupt
+ *    triggers, unless manually cleared by writing 0 to
+ *    TIMER(x).STATUS.  While the interrupt status bit stays set
+ *    the timer will continue to run normally, but the interrupt
+ *    (INUM_TIMER_FRC1 or INUM_TIMER_FRC2) won't trigger again.
+ *
+ *  * When TIMER_CTRL_INT_HOLD is cleared (default), there's no need to
+ *    manually write to TIMER(x).STATUS. The interrupt status bit
+ *    TIMER_CTRL_INT_STATUS automatically clears after the interrupt
+ *    triggers, and the interrupt handler will run again
+ *    automatically.
+ */
+
+/* The values for TIMER_CTRL_CLKDIV control how many CPU clock cycles amount to
+ * one timer clock cycle.  For valid values, see the timer_clkdiv_t enum below.
+ */
+
+/* TIMER_CTRL_INT_STATUS gets set when interrupt fires, and cleared on a write
+ * to TIMER(x).STATUS (or cleared automatically if TIMER_CTRL_INT_HOLD is not
+ * set).
+ */
+
+#define TIMER_CTRL_INT_HOLD    BIT(0)
+#define TIMER_CTRL_CLKDIV_M    0x00000003
+#define TIMER_CTRL_CLKDIV_S    2
+#define TIMER_CTRL_RELOAD      BIT(6)
+#define TIMER_CTRL_RUN         BIT(7)
+#define TIMER_CTRL_INT_STATUS  BIT(8)
+
+typedef enum {
+    TIMER_CLKDIV_1 = 0,
+    TIMER_CLKDIV_16 = 1,
+    TIMER_CLKDIV_256 = 2,
+} timer_clkdiv_t;
+
+/* Details for STATUS registers */
+
+/* Reading this register always returns the value in
+ * TIMER(x).LOAD
+ *
+ * Writing zero to this register clears the FRC1
+ * interrupt status.
+ */
+
+/* Details for FRC2.ALARM register */
+
+/* Interrupt match value for FRC2. When COUNT == ALARM,
+   the interrupt fires.
+*/
+
+#endif /* _ESP_TIMER_REGS_H */
diff --git a/core/include/esp/types.h b/core/include/esp/types.h
new file mode 100644
index 0000000..3f0560a
--- /dev/null
+++ b/core/include/esp/types.h
@@ -0,0 +1,8 @@
+#ifndef _ESP_TYPES_H
+#define _ESP_TYPES_H
+
+#include <stdint.h>
+
+typedef volatile uint32_t *esp_reg_t;
+
+#endif /* _ESP_TYPES_H */
diff --git a/core/include/esp/uart_regs.h b/core/include/esp/uart_regs.h
new file mode 100644
index 0000000..fe8e03d
--- /dev/null
+++ b/core/include/esp/uart_regs.h
@@ -0,0 +1,186 @@
+/** esp/uart.h
+ *
+ * Configuration of UART registers.
+ *
+ * Part of esp-open-rtos
+ * Copyright (C) 2015 Superhouse Automation Pty Ltd
+ * BSD Licensed as described in the file LICENSE
+ */
+
+#ifndef _UART_REGS_H
+#define _UART_REGS_H
+
+#include "esp/types.h"
+#include "common_macros.h"
+
+/* Register definitions for the UART peripherals on the ESP8266.
+ *
+ * There are twp UART devices built into the ESP8266:
+ *   UART(0) is at 0x60000000
+ *   UART(1) is at 0x60000F00
+ *
+ * Each device is allocated a block of 64 32-bit registers (256 bytes of
+ * address space) to communicate with application code.
+ */
+
+#define UART_BASE 0x60000000
+#define UART(i) (*(struct UART_REGS *)(0x60000200 - (i)*0xf00))
+
+#define UART0_BASE UART_BASE
+#define UART1_BASE (UART_BASE + 0xf00)
+
+struct UART_REGS {
+    uint32_t volatile FIFO;           // 0x00
+    uint32_t volatile INT_RAW;        // 0x04
+    uint32_t volatile INT_STATUS;     // 0x08
+    uint32_t volatile INT_ENABLE;     // 0x0c
+    uint32_t volatile INT_CLEAR;      // 0x10
+    uint32_t volatile CLOCK_DIVIDER;  // 0x14
+    uint32_t volatile AUTOBAUD;       // 0x18
+    uint32_t volatile STATUS;         // 0x1c
+    uint32_t volatile CONF0;          // 0x20
+    uint32_t volatile CONF1;          // 0x24
+    uint32_t volatile LOW_PULSE;      // 0x28
+    uint32_t volatile HIGH_PULSE;     // 0x2c
+    uint32_t volatile PULSE_COUNT;    // 0x30
+    uint32_t volatile _unused[17];    // 0x34 - 0x74
+    uint32_t volatile DATE;           // 0x78
+    uint32_t volatile ID;             // 0x7c
+} __attribute__ (( packed ));
+
+_Static_assert(sizeof(struct UART_REGS) == 0x80, "UART_REGS is the wrong size");
+
+/* Details for FIFO register */
+
+#define UART_FIFO_DATA_M  0x000000ff
+#define UART_FIFO_DATA_S  0
+
+/* Details for INT_RAW register */
+
+#define UART_INT_RAW_RXFIFO_TIMEOUT          BIT(8)
+#define UART_INT_RAW_BREAK_DETECTED          BIT(7)
+#define UART_INT_RAW_CTS_CHANGED             BIT(6)
+#define UART_INT_RAW_DSR_CHANGED             BIT(5)
+#define UART_INT_RAW_RXFIFO_OVERFLOW         BIT(4)
+#define UART_INT_RAW_FRAMING_ERR             BIT(3)
+#define UART_INT_RAW_PARITY_ERR              BIT(2)
+#define UART_INT_RAW_TXFIFO_EMPTY            BIT(1)
+#define UART_INT_RAW_RXFIFO_FULL             BIT(0)
+
+/* Details for INT_STATUS register */
+
+#define UART_INT_STATUS_RXFIFO_TIMEOUT       BIT(8)
+#define UART_INT_STATUS_BREAK_DETECTED       BIT(7)
+#define UART_INT_STATUS_CTS_CHANGED          BIT(6)
+#define UART_INT_STATUS_DSR_CHANGED          BIT(5)
+#define UART_INT_STATUS_RXFIFO_OVERFLOW      BIT(4)
+#define UART_INT_STATUS_FRAMING_ERR          BIT(3)
+#define UART_INT_STATUS_PARITY_ERR           BIT(2)
+#define UART_INT_STATUS_TXFIFO_EMPTY         BIT(1)
+#define UART_INT_STATUS_RXFIFO_FULL          BIT(0)
+
+/* Details for INT_ENABLE register */
+
+#define UART_INT_ENABLE_RXFIFO_TIMEOUT       BIT(8)
+#define UART_INT_ENABLE_BREAK_DETECTED       BIT(7)
+#define UART_INT_ENABLE_CTS_CHANGED          BIT(6)
+#define UART_INT_ENABLE_DSR_CHANGED          BIT(5)
+#define UART_INT_ENABLE_RXFIFO_OVERFLOW      BIT(4)
+#define UART_INT_ENABLE_FRAMING_ERR          BIT(3)
+#define UART_INT_ENABLE_PARITY_ERR           BIT(2)
+#define UART_INT_ENABLE_TXFIFO_EMPTY         BIT(1)
+#define UART_INT_ENABLE_RXFIFO_FULL          BIT(0)
+
+/* Details for INT_CLEAR register */
+
+#define UART_INT_CLEAR_RXFIFO_TIMEOUT        BIT(8)
+#define UART_INT_CLEAR_BREAK_DETECTED        BIT(7)
+#define UART_INT_CLEAR_CTS_CHANGED           BIT(6)
+#define UART_INT_CLEAR_DSR_CHANGED           BIT(5)
+#define UART_INT_CLEAR_RXFIFO_OVERFLOW       BIT(4)
+#define UART_INT_CLEAR_FRAMING_ERR           BIT(3)
+#define UART_INT_CLEAR_PARITY_ERR            BIT(2)
+#define UART_INT_CLEAR_TXFIFO_EMPTY          BIT(1)
+#define UART_INT_CLEAR_RXFIFO_FULL           BIT(0)
+
+/* Details for CLOCK_DIVIDER register */
+
+#define UART_CLOCK_DIVIDER_VALUE_M           0x000fffff
+#define UART_CLOCK_DIVIDER_VALUE_S           0
+
+/* Details for AUTOBAUD register */
+
+#define UART_AUTOBAUD_GLITCH_FILTER_M        0x000000FF
+#define UART_AUTOBAUD_GLITCH_FILTER_S        8
+#define UART_AUTOBAUD_ENABLE                 BIT(0)
+
+/* Details for STATUS register */
+
+#define UART_STATUS_TXD                      BIT(31)
+#define UART_STATUS_RTS                      BIT(30)
+#define UART_STATUS_DTR                      BIT(29)
+#define UART_STATUS_TXFIFO_COUNT_M           0x000000ff
+#define UART_STATUS_TXFIFO_COUNT_S           16
+#define UART_STATUS_RXD                      BIT(15)
+#define UART_STATUS_CTS                      BIT(14)
+#define UART_STATUS_DSR                      BIT(13)
+#define UART_STATUS_RXFIFO_COUNT_M           0x000000ff
+#define UART_STATUS_RXFIFO_COUNT_S           0
+
+/* Details for CONF0 register */
+
+#define UART_CONF0_DTR_INVERTED              BIT(24)
+#define UART_CONF0_RTS_INVERTED              BIT(23)
+#define UART_CONF0_TXD_INVERTED              BIT(22)
+#define UART_CONF0_DSR_INVERTED              BIT(21)
+#define UART_CONF0_CTS_INVERTED              BIT(20)
+#define UART_CONF0_RXD_INVERTED              BIT(19)
+#define UART_CONF0_TXFIFO_RESET              BIT(18)
+#define UART_CONF0_RXFIFO_RESET              BIT(17)
+#define UART_CONF0_IRDA_ENABLE               BIT(16)
+#define UART_CONF0_TX_FLOW_ENABLE            BIT(15)
+#define UART_CONF0_LOOPBACK                  BIT(14)
+#define UART_CONF0_IRDA_RX_INVERTED          BIT(13)
+#define UART_CONF0_IRDA_TX_INVERTED          BIT(12)
+#define UART_CONF0_IRDA_WCTL                 BIT(11)
+#define UART_CONF0_IRDA_TX_ENABLE            BIT(10)
+#define UART_CONF0_IRDA_DUPLEX               BIT(9)
+#define UART_CONF0_TXD_BREAK                 BIT(8)
+#define UART_CONF0_SW_DTR                    BIT(7)
+#define UART_CONF0_SW_RTS                    BIT(6)
+#define UART_CONF0_STOP_BITS_M               0x00000003
+#define UART_CONF0_STOP_BITS_S               4
+#define UART_CONF0_BYTE_LEN_M                0x00000003
+#define UART_CONF0_BYTE_LEN_S                2
+#define UART_CONF0_PARITY_ENABLE             BIT(1)
+#define UART_CONF0_PARITY                    BIT(0) //FIXME: does this indicate odd or even?
+
+/* Details for CONF1 register */
+
+#define UART_CONF1_RX_TIMEOUT_ENABLE         BIT(31)
+#define UART_CONF1_RX_TIMEOUT_THRESHOLD_M    0x0000007f
+#define UART_CONF1_RX_TIMEOUT_THRESHOLD_S    24
+#define UART_CONF1_RX_FLOWCTRL_ENABLE        BIT(23)
+#define UART_CONF1_RX_FLOWCTRL_THRESHOLD_M   0x0000007f
+#define UART_CONF1_RX_FLOWCTRL_THRESHOLD_S   16
+#define UART_CONF1_TXFIFO_EMPTY_THRESHOLD_M  0x0000007f
+#define UART_CONF1_TXFIFO_EMPTY_THRESHOLD_S  8
+#define UART_CONF1_RXFIFO_FULL_THRESHOLD_M   0x0000007f
+#define UART_CONF1_RXFIFO_FULL_THRESHOLD_S   0
+
+/* Details for LOW_PULSE register */
+
+#define UART_LOW_PULSE_MIN_M                 0x000fffff
+#define UART_LOW_PULSE_MIN_S                 0
+
+/* Details for HIGH_PULSE register */
+
+#define UART_HIGH_PULSE_MIN_M                0x000fffff
+#define UART_HIGH_PULSE_MIN_S                0
+
+/* Details for PULSE_COUNT register */
+
+#define UART_PULSE_COUNT_VALUE_M             0x000003ff
+#define UART_PULSE_COUNT_VALUE_S             0
+
+#endif /* _UART_REGS_H */
diff --git a/core/include/esp/wdt_regs.h b/core/include/esp/wdt_regs.h
new file mode 100644
index 0000000..a9ee063
--- /dev/null
+++ b/core/include/esp/wdt_regs.h
@@ -0,0 +1,38 @@
+/* esp/wdt_regs.h
+ *
+ * ESP8266 Watchdog Timer register definitions
+ *
+ * Not compatible with ESP SDK register access code.
+ */
+
+#ifndef _ESP_WDT_REGS_H
+#define _ESP_WDT_REGS_H
+
+#include "esp/types.h"
+#include "common_macros.h"
+
+#define WDT_BASE 0x60000900
+#define WDT (*(struct WDT_REGS *)(WDT_BASE))
+
+/* WDT register(s)
+
+   Not fully understood yet. Writing 0 to CTRL disables WDT.
+
+   See ROM functions esp_wdt_xxx
+ */
+
+struct WDT_REGS {
+    uint32_t volatile CTRL;        // 0x00
+    uint32_t volatile REG1;        // 0x04
+    uint32_t volatile REG2;        // 0x08
+    uint32_t volatile _unused[2];  // 0x0c - 0x10
+    uint32_t volatile FEED;        // 0x14
+} __attribute__ (( packed ));
+
+_Static_assert(sizeof(struct WDT_REGS) == 0x18, "WDT_REGS is the wrong size");
+
+/* Writing WDT_FEED_MAGIC to WDT.FEED register "feeds the dog" and holds off
+ * triggering for another cycle (unconfirmed) */
+#define WDT_FEED_MAGIC  0x73
+
+#endif /* _ESP_WDT_REGS_H */
diff --git a/examples/Makefile b/examples/Makefile
index 8f7edb6..0436a13 100644
--- a/examples/Makefile
+++ b/examples/Makefile
@@ -1,28 +1,31 @@
+EXAMPLES = $(shell find $(dir $(lastword $(MAKEFILE_LIST))) -mindepth 2 -name Makefile | sed s/Makefile//g)
+# Generate some dummy .dummybuild/.dummyrebuild target files
+EXAMPLES_BUILD = $(patsubst %,%.dummybuild,$(EXAMPLES))
+EXAMPLES_REBUILD = $(patsubst %,%.dummyrebuild,$(EXAMPLES))
+
 warning:
 	@echo "******************************************************"
 	@echo "You may not want this Makefile, even though it's here!"
 	@echo "******************************************************"
 	@echo ""
 	@echo "SUGGESTIONS:"
-	@echo "Running 'make' in one of the subdirectories will build a single example."
-	@echo "Running 'make help' in one of the subdirectories will print some help."
+	@echo "Running 'make' in one of the subdirectories of examples/ will build a single example."
+	@echo "Running 'make help' in one of the subdirectories of examples/ will print some help."
 	@echo ""
 	@echo "OTHERWISE:"
 	@echo "This makefile is for building all of the examples at once, as a developer test."
 	@echo "To use it, run 'make build-examples' or 'make rebuild-examples'"
 	@echo
 
-build-examples:
-	set -e
-	for example in `find . -mindepth 2 -name Makefile | sed s/Makefile//)`; do
-	   $(MAKE) -C $$example
-	done
+build-examples: $(EXAMPLES_BUILD)
 
-rebuild-examples:
-	set -e
-	for example in `find . -mindepth 2 -name Makefile | sed s/Makefile//)`; do
-	   $(MAKE) -C $$example rebuild
-	done
+rebuild-examples: $(EXAMPLES_REBUILD)
+
+%.dummybuild:
+	make -C $(dir $@)
+
+%.dummyrebuild:
+	make -C $(dir $@) rebuild
 
 .PHONY: warning rebuild-examples build-examples
 .NOTPARALLEL:
diff --git a/examples/blink/blink.c b/examples/blink/blink.c
index be1c473..52d69a5 100644
--- a/examples/blink/blink.c
+++ b/examples/blink/blink.c
@@ -30,8 +30,8 @@ void blinkenTask(void *pvParameters)
 
 /* This task uses all raw register operations to set the pins.
 
-   It's not fully parameterised, as the IOMUX_SET macro requires the pin number
-   as part of the GPxx value.
+   It's not fully parameterised, as the IOMUX_GPIO# macros involve a non-linear
+   mapping from GPIO to IOMUX ports.
 
    There is no significant performance benefit to this way over the
    blinkenTask version, so it's probably better to use the blinkenTask
@@ -41,12 +41,12 @@ void blinkenTask(void *pvParameters)
 */
 void blinkenRegisterTask(void *pvParameters)
 {
-    GPIO_DIR_SET = BIT(gpio);
-    IOMUX_SET(GP14,GPIO,IOMUX_OE); /* change this line if you change 'gpio' */
+    GPIO.ENABLE_OUT_SET = BIT(gpio);
+    IOMUX_GPIO14 = IOMUX_GPIO14_FUNC_GPIO | IOMUX_PIN_OUTPUT_ENABLE; /* change this line if you change 'gpio' */
     while(1) {
-        GPIO_OUT_SET = BIT(gpio);
+        GPIO.OUT_SET = BIT(gpio);
         vTaskDelay(1000 / portTICK_RATE_MS);
-        GPIO_OUT_CLEAR = BIT(gpio);
+        GPIO.OUT_CLEAR = BIT(gpio);
         vTaskDelay(1000 / portTICK_RATE_MS);
     }
 }
diff --git a/examples/blink_timers/blink_timers.c b/examples/blink_timers/blink_timers.c
index c8baeb2..5b6b7a2 100644
--- a/examples/blink_timers/blink_timers.c
+++ b/examples/blink_timers/blink_timers.c
@@ -26,7 +26,7 @@ void frc1_interrupt_handler(void)
 void frc2_interrupt_handler(void)
 {
     /* FRC2 needs the match register updated on each timer interrupt */
-    timer_set_frequency(TIMER_FRC2, freq_frc2);
+    timer_set_frequency(FRC2, freq_frc2);
     frc2_count++;
     gpio_toggle(gpio_frc2);
 }
@@ -41,24 +41,24 @@ void user_init(void)
     gpio_write(gpio_frc1, 1);
 
     /* stop both timers and mask their interrupts as a precaution */
-    timer_set_interrupts(TIMER_FRC1, false);
-    timer_set_run(TIMER_FRC1, false);
-    timer_set_interrupts(TIMER_FRC2, false);
-    timer_set_run(TIMER_FRC2, false);
+    timer_set_interrupts(FRC1, false);
+    timer_set_run(FRC1, false);
+    timer_set_interrupts(FRC2, false);
+    timer_set_run(FRC2, false);
 
     /* set up ISRs */
     _xt_isr_attach(INUM_TIMER_FRC1, frc1_interrupt_handler);
     _xt_isr_attach(INUM_TIMER_FRC2, frc2_interrupt_handler);
 
     /* configure timer frequencies */
-    timer_set_frequency(TIMER_FRC1, freq_frc1);
-    timer_set_frequency(TIMER_FRC2, freq_frc2);
+    timer_set_frequency(FRC1, freq_frc1);
+    timer_set_frequency(FRC2, freq_frc2);
 
     /* unmask interrupts and start timers */
-    timer_set_interrupts(TIMER_FRC1, true);
-    timer_set_run(TIMER_FRC1, true);
-    timer_set_interrupts(TIMER_FRC2, true);
-    timer_set_run(TIMER_FRC2, true);
+    timer_set_interrupts(FRC1, true);
+    timer_set_run(FRC1, true);
+    timer_set_interrupts(FRC2, true);
+    timer_set_run(FRC2, true);
 
     gpio_write(gpio_frc1, 0);
 }
diff --git a/examples/button/button.c b/examples/button/button.c
index 042b838..bcca2b3 100644
--- a/examples/button/button.c
+++ b/examples/button/button.c
@@ -15,7 +15,7 @@
 /* pin config */
 const int gpio = 0;   /* gpio 0 usually has "PROGRAM" button attached */
 const int active = 0; /* active == 0 for active low */
-const gpio_interrupt_t int_type = INT_FALLING;
+const gpio_inttype_t int_type = GPIO_INTTYPE_EDGE_NEG;
 #define GPIO_HANDLER gpio00_interrupt_handler
 
 
diff --git a/examples/experiments/timers/timers.c b/examples/experiments/timers/timers.c
index efe5b2e..a840159 100644
--- a/examples/experiments/timers/timers.c
+++ b/examples/experiments/timers/timers.c
@@ -11,14 +11,15 @@
 #include "FreeRTOS.h"
 #include "task.h"
 #include "esp8266.h"
+#include "common_macros.h"
 
 #define DUMP_SZ 0x10 /* number of regs not size of buffer */
 
 IRAM void dump_frc1_seq(void)
 {
-    uint32_t f1_a = TIMER_FRC1_COUNT_REG;
-    uint32_t f1_b = TIMER_FRC1_COUNT_REG;
-    uint32_t f1_c = TIMER_FRC1_COUNT_REG;
+    uint32_t f1_a = TIMER(0).COUNT;
+    uint32_t f1_b = TIMER(0).COUNT;
+    uint32_t f1_c = TIMER(0).COUNT;
     printf("FRC1 sequence 0x%08lx 0x%08lx 0x%08lx\r\n", f1_a, f1_b, f1_c);
     printf("FRC1 deltas %ld %ld \r\n", f1_b-f1_a, f1_c-f1_b);
 }
@@ -33,9 +34,9 @@ IRAM void dump_frc2_seq(void)
      * /16 = 0 or 1 (usually 1)
      * 
      */
-    uint32_t f2_a = TIMER_FRC2_COUNT_REG;
-    uint32_t f2_b = TIMER_FRC2_COUNT_REG;
-    uint32_t f2_c = TIMER_FRC2_COUNT_REG;
+    uint32_t f2_a = TIMER(1).COUNT;
+    uint32_t f2_b = TIMER(1).COUNT;
+    uint32_t f2_c = TIMER(1).COUNT;
     printf("FRC2 sequence 0x%08lx 0x%08lx 0x%08lx\r\n", f2_a, f2_b, f2_c);
     printf("FRC2 deltas %ld %ld \r\n", f2_b-f2_a, f2_c-f2_b);
 }
@@ -99,20 +100,20 @@ void timerRegTask(void *pvParameters)
 IRAM void frc1_handler(void)
 {
     frc1_handler_call_count++;
-    frc1_last_count_val = TIMER_FRC1_COUNT_REG;
-    //TIMER_FRC1_LOAD_REG = 0x300000;
-    //TIMER_FRC1_CLEAR_INT = 0;
+    frc1_last_count_val = TIMER(0).COUNT;
+    //TIMER(0).LOAD = 0x300000;
+    //TIMER(0).STATUS = 0;
     //TIMER_FRC1_MATCH_REG = frc1_last_count_val + 0x100000;
 }
 
 void frc2_handler(void)
 {
     frc2_handler_call_count++;
-    frc2_last_count_val = TIMER_FRC2_COUNT_REG;
-    TIMER_FRC2_MATCH_REG = frc2_last_count_val + 0x100000;
-    //TIMER_FRC2_LOAD_REG = 0;
-    //TIMER_FRC2_LOAD_REG = 0x2000000;
-    //TIMER_FRC2_CLEAR_INT_REG = 0;
+    frc2_last_count_val = TIMER(1).COUNT;
+    TIMER(1).ALARM = frc2_last_count_val + 0x100000;
+    //TIMER(1).LOAD = 0;
+    //TIMER(1).LOAD = 0x2000000;
+    //TIMER(1).STATUS = 0;
 }
 
 void user_init(void)
@@ -120,19 +121,19 @@ void user_init(void)
     sdk_uart_div_modify(0, UART_CLK_FREQ / 115200);
     xTaskCreate(timerRegTask, (signed char *)"timerRegTask", 1024, NULL, 2, NULL);
 
-    TIMER_FRC1_CTRL_REG = TIMER_CTRL_DIV_256|TIMER_CTRL_INT_EDGE|TIMER_CTRL_RELOAD;
-    TIMER_FRC1_LOAD_REG = 0x200000;
+    TIMER(0).CTRL = VAL2FIELD(TIMER_CTRL_CLKDIV, TIMER_CLKDIV_256) | TIMER_CTRL_RELOAD;
+    TIMER(0).LOAD = 0x200000;
 
-    TIMER_FRC2_CTRL_REG = TIMER_CTRL_DIV_256|TIMER_CTRL_INT_EDGE;
+    TIMER(1).LOAD = VAL2FIELD(TIMER_CTRL_CLKDIV, TIMER_CLKDIV_256);
 
-    DP_INT_ENABLE_REG |= INT_ENABLE_FRC1|INT_ENABLE_FRC2;
+    DPORT.INT_ENABLE |= DPORT_INT_ENABLE_TIMER0 | DPORT_INT_ENABLE_TIMER1;
     _xt_isr_attach(INUM_TIMER_FRC1, frc1_handler);
     _xt_isr_unmask(1<<INUM_TIMER_FRC1);
     _xt_isr_attach(INUM_TIMER_FRC2, frc2_handler);
     _xt_isr_unmask(1<<INUM_TIMER_FRC2);
 
-    TIMER_FRC1_CTRL_REG |= TIMER_CTRL_RUN;
-    TIMER_FRC2_CTRL_REG |= TIMER_CTRL_RUN;
+    TIMER(0).CTRL |= TIMER_CTRL_RUN;
+    TIMER(1).CTRL |= TIMER_CTRL_RUN;
 
     dump_timer_regs("timer regs during user_init");
     dump_timer_regs("#2 timer regs during user_init");
diff --git a/include/espressif/esp_wifi.h b/include/espressif/esp_wifi.h
index c966f3d..5d9ba19 100644
--- a/include/espressif/esp_wifi.h
+++ b/include/espressif/esp_wifi.h
@@ -8,6 +8,10 @@
 
 #ifndef __ESP_WIFI_H__
 #define __ESP_WIFI_H__
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <lwip/ip_addr.h>
 
 #ifdef	__cplusplus
 extern "C" {
diff --git a/lwip/include/lwipopts.h b/lwip/include/lwipopts.h
index d958ba6..8f46c7d 100644
--- a/lwip/include/lwipopts.h
+++ b/lwip/include/lwipopts.h
@@ -39,9 +39,6 @@
 #define ESP_TIMEWAIT_THRESHOLD              10000
 #define LWIP_TIMEVAL_PRIVATE                0
 
-// Uncomment this line, and set the debug options you want below, for IP stack debug output
-//#define LWIP_DEBUG
-
 /*
    -----------------------------------------------
    ---------- Platform specific locking ----------
@@ -390,6 +387,10 @@
    ---------- Debugging options ----------
    ---------------------------------------
 */
+
+// Uncomment this line, and set the individual debug options you want, for IP stack debug output
+//#define LWIP_DEBUG
+
 /**
  * ETHARP_DEBUG: Enable debugging in etharp.c.
  */
@@ -430,11 +431,22 @@
  */
 #define TCP_OUTPUT_DEBUG                LWIP_DBG_OFF
 
+/**
+ * UDP_DEBUG: Enable debugging in udp.c.
+ */
+#define UDP_DEBUG                     LWIP_DBG_OFF
+
+/**
+ * ICMP_DEBUG: Enable debugging in udp.c.
+ */
+#define ICMP_DEBUG                     LWIP_DBG_OFF
+
 /**
  * TCPIP_DEBUG: Enable debugging in tcpip.c.
  */
 #define TCPIP_DEBUG                     LWIP_DBG_OFF
 
+
 /**
  * DHCP_DEBUG: Enable debugging in dhcp.c.
  */
diff --git a/lwip/include/netbuf_helpers.h b/lwip/include/netbuf_helpers.h
new file mode 100644
index 0000000..4f02ba5
--- /dev/null
+++ b/lwip/include/netbuf_helpers.h
@@ -0,0 +1,28 @@
+/* Some netbuf helpers that should probably be rolled into a patch to lwip soon */
+#ifndef _NETBUF_HELPERS_H
+#define _NETBUF_HELPERS_H
+#include "lwip/netbuf.h"
+
+/* Read a 16 bit wide unsigned integer, stored host order, from the netbuf */
+inline static u16_t netbuf_read_u16_h(struct netbuf *netbuf, u16_t offs)
+{
+    u16_t raw;
+    netbuf_copy_partial(netbuf, &raw, 2, offs);
+    return raw;
+}
+
+/* Read a 16 bit wide unsigned integer, stored network order, from the netbuf */
+inline static u16_t netbuf_read_u16_n(struct netbuf *netbuf, u16_t offs)
+{
+    return ntohs(netbuf_read_u16_h(netbuf, offs));
+}
+
+/* Read an 8 bit unsigned integer from the netbuf */
+inline static u8_t netbuf_read_u8(struct netbuf *netbuf, u16_t offs)
+{
+    u8_t result;
+    netbuf_copy_partial(netbuf, &result, 1, offs);
+    return result;
+}
+
+#endif