diff --git a/curriculum/Microcontrollers Textbook.odt b/curriculum/Microcontrollers Textbook.odt
new file mode 100644
index 0000000000000000000000000000000000000000..023d9358d124b6d4cfc24cda9ed681fbf2ce6a8d
Binary files /dev/null and b/curriculum/Microcontrollers Textbook.odt differ
diff --git a/curriculum/notes.txt b/curriculum/notes.txt
new file mode 100644
index 0000000000000000000000000000000000000000..a18b23b1015862b61bb29630c55193d7d170aa4d
--- /dev/null
+++ b/curriculum/notes.txt
@@ -0,0 +1 @@
+https://github.com/mcauser/awesome-micropython
diff --git a/esp32-20220618-v1.19.1.bin b/esp32-20220618-v1.19.1.bin
new file mode 100644
index 0000000000000000000000000000000000000000..72d584679580bb383b07edca0285cab1fdef2317
Binary files /dev/null and b/esp32-20220618-v1.19.1.bin differ
diff --git a/examples/oled_example.py b/examples/oled_example.py
new file mode 100644
index 0000000000000000000000000000000000000000..f970ab36d9a8ddc058fe049026667cebab387fbc
--- /dev/null
+++ b/examples/oled_example.py
@@ -0,0 +1,10 @@
+from machine import Pin, I2C
+import ssd1306
+
+# using default address 0x3C
+i2c = I2C(sda=Pin(21), scl=Pin(22))
+display = ssd1306.SSD1306_I2C(128, 64, i2c)
+
+display.text('Hello, World!', 0, 0, 1)
+display.show()
+print("henlo")
\ No newline at end of file
diff --git a/libraries/ssd1306.py b/libraries/ssd1306.py
new file mode 100644
index 0000000000000000000000000000000000000000..592d183805951f0c48e87dcbc1621bcaf61f6113
--- /dev/null
+++ b/libraries/ssd1306.py
@@ -0,0 +1,163 @@
+# MicroPython SSD1306 OLED driver, I2C and SPI interfaces
+
+from micropython import const
+import framebuf
+
+
+# register definitions
+SET_CONTRAST = const(0x81)
+SET_ENTIRE_ON = const(0xA4)
+SET_NORM_INV = const(0xA6)
+SET_DISP = const(0xAE)
+SET_MEM_ADDR = const(0x20)
+SET_COL_ADDR = const(0x21)
+SET_PAGE_ADDR = const(0x22)
+SET_DISP_START_LINE = const(0x40)
+SET_SEG_REMAP = const(0xA0)
+SET_MUX_RATIO = const(0xA8)
+SET_IREF_SELECT = const(0xAD)
+SET_COM_OUT_DIR = const(0xC0)
+SET_DISP_OFFSET = const(0xD3)
+SET_COM_PIN_CFG = const(0xDA)
+SET_DISP_CLK_DIV = const(0xD5)
+SET_PRECHARGE = const(0xD9)
+SET_VCOM_DESEL = const(0xDB)
+SET_CHARGE_PUMP = const(0x8D)
+
+# Subclassing FrameBuffer provides support for graphics primitives
+# http://docs.micropython.org/en/latest/pyboard/library/framebuf.html
+class SSD1306(framebuf.FrameBuffer):
+    def __init__(self, width, height, external_vcc):
+        self.width = width
+        self.height = height
+        self.external_vcc = external_vcc
+        self.pages = self.height // 8
+        self.buffer = bytearray(self.pages * self.width)
+        super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB)
+        self.init_display()
+
+    def init_display(self):
+        for cmd in (
+            SET_DISP,  # display off
+            # address setting
+            SET_MEM_ADDR,
+            0x00,  # horizontal
+            # resolution and layout
+            SET_DISP_START_LINE,  # start at line 0
+            SET_SEG_REMAP | 0x01,  # column addr 127 mapped to SEG0
+            SET_MUX_RATIO,
+            self.height - 1,
+            SET_COM_OUT_DIR | 0x08,  # scan from COM[N] to COM0
+            SET_DISP_OFFSET,
+            0x00,
+            SET_COM_PIN_CFG,
+            0x02 if self.width > 2 * self.height else 0x12,
+            # timing and driving scheme
+            SET_DISP_CLK_DIV,
+            0x80,
+            SET_PRECHARGE,
+            0x22 if self.external_vcc else 0xF1,
+            SET_VCOM_DESEL,
+            0x30,  # 0.83*Vcc
+            # display
+            SET_CONTRAST,
+            0xFF,  # maximum
+            SET_ENTIRE_ON,  # output follows RAM contents
+            SET_NORM_INV,  # not inverted
+            SET_IREF_SELECT,
+            0x30,  # enable internal IREF during display on
+            # charge pump
+            SET_CHARGE_PUMP,
+            0x10 if self.external_vcc else 0x14,
+            SET_DISP | 0x01,  # display on
+        ):  # on
+            self.write_cmd(cmd)
+        self.fill(0)
+        self.show()
+
+    def poweroff(self):
+        self.write_cmd(SET_DISP)
+
+    def poweron(self):
+        self.write_cmd(SET_DISP | 0x01)
+
+    def contrast(self, contrast):
+        self.write_cmd(SET_CONTRAST)
+        self.write_cmd(contrast)
+
+    def invert(self, invert):
+        self.write_cmd(SET_NORM_INV | (invert & 1))
+
+    def rotate(self, rotate):
+        self.write_cmd(SET_COM_OUT_DIR | ((rotate & 1) << 3))
+        self.write_cmd(SET_SEG_REMAP | (rotate & 1))
+
+    def show(self):
+        x0 = 0
+        x1 = self.width - 1
+        if self.width != 128:
+            # narrow displays use centred columns
+            col_offset = (128 - self.width) // 2
+            x0 += col_offset
+            x1 += col_offset
+        self.write_cmd(SET_COL_ADDR)
+        self.write_cmd(x0)
+        self.write_cmd(x1)
+        self.write_cmd(SET_PAGE_ADDR)
+        self.write_cmd(0)
+        self.write_cmd(self.pages - 1)
+        self.write_data(self.buffer)
+
+
+class SSD1306_I2C(SSD1306):
+    def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False):
+        self.i2c = i2c
+        self.addr = addr
+        self.temp = bytearray(2)
+        self.write_list = [b"\x40", None]  # Co=0, D/C#=1
+        super().__init__(width, height, external_vcc)
+
+    def write_cmd(self, cmd):
+        self.temp[0] = 0x80  # Co=1, D/C#=0
+        self.temp[1] = cmd
+        self.i2c.writeto(self.addr, self.temp)
+
+    def write_data(self, buf):
+        self.write_list[1] = buf
+        self.i2c.writevto(self.addr, self.write_list)
+
+
+class SSD1306_SPI(SSD1306):
+    def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
+        self.rate = 10 * 1024 * 1024
+        dc.init(dc.OUT, value=0)
+        res.init(res.OUT, value=0)
+        cs.init(cs.OUT, value=1)
+        self.spi = spi
+        self.dc = dc
+        self.res = res
+        self.cs = cs
+        import time
+
+        self.res(1)
+        time.sleep_ms(1)
+        self.res(0)
+        time.sleep_ms(10)
+        self.res(1)
+        super().__init__(width, height, external_vcc)
+
+    def write_cmd(self, cmd):
+        self.spi.init(baudrate=self.rate, polarity=0, phase=0)
+        self.cs(1)
+        self.dc(0)
+        self.cs(0)
+        self.spi.write(bytearray([cmd]))
+        self.cs(1)
+
+    def write_data(self, buf):
+        self.spi.init(baudrate=self.rate, polarity=0, phase=0)
+        self.cs(1)
+        self.dc(1)
+        self.cs(0)
+        self.spi.write(buf)
+        self.cs(1)
\ No newline at end of file
diff --git a/libraries/tcs34725.py b/libraries/tcs34725.py
new file mode 100644
index 0000000000000000000000000000000000000000..06ba11185746ba757f5ec77438315809e4390f7e
--- /dev/null
+++ b/libraries/tcs34725.py
@@ -0,0 +1,168 @@
+import time
+import ustruct
+
+#const = lambda x:x
+
+_COMMAND_BIT = const(0x80)
+
+_REGISTER_ENABLE = const(0x00)
+_REGISTER_ATIME = const(0x01)
+
+_REGISTER_AILT = const(0x04)
+_REGISTER_AIHT = const(0x06)
+
+_REGISTER_ID = const(0x12)
+
+_REGISTER_APERS = const(0x0c)
+
+_REGISTER_CONTROL = const(0x0f)
+
+_REGISTER_SENSORID = const(0x12)
+
+_REGISTER_STATUS = const(0x13)
+_REGISTER_CDATA = const(0x14)
+_REGISTER_RDATA = const(0x16)
+_REGISTER_GDATA = const(0x18)
+_REGISTER_BDATA = const(0x1a)
+
+_ENABLE_AIEN = const(0x10)
+_ENABLE_WEN = const(0x08)
+_ENABLE_AEN = const(0x02)
+_ENABLE_PON = const(0x01)
+
+_GAINS = (1, 4, 16, 60)
+_CYCLES = (0, 1, 2, 3, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60)
+
+
+class TCS34725:
+    def __init__(self, i2c, address=0x29):
+        self.i2c = i2c
+        self.address = address
+        self._active = False
+        self.integration_time(2.4)
+        sensor_id = self.sensor_id()
+        if sensor_id not in (0x44, 0x10):
+            raise RuntimeError("wrong sensor id 0x{:x}".format(sensor_id))
+
+    def _register8(self, register, value=None):
+        register |= _COMMAND_BIT
+        if value is None:
+            return self.i2c.readfrom_mem(self.address, register, 1)[0]
+        data = ustruct.pack('<B', value)
+        self.i2c.writeto_mem(self.address, register, data)
+
+    def _register16(self, register, value=None):
+        register |= _COMMAND_BIT
+        if value is None:
+            data = self.i2c.readfrom_mem(self.address, register, 2)
+            return ustruct.unpack('<H', data)[0]
+        data = ustruct.pack('<H', value)
+        self.i2c.writeto_mem(self.address, register, data)
+
+    def active(self, value=None):
+        if value is None:
+            return self._active
+        value = bool(value)
+        if self._active == value:
+            return
+        self._active = value
+        enable = self._register8(_REGISTER_ENABLE)
+        if value:
+            self._register8(_REGISTER_ENABLE, enable | _ENABLE_PON)
+            time.sleep_ms(3)
+            self._register8(_REGISTER_ENABLE,
+                enable | _ENABLE_PON | _ENABLE_AEN)
+        else:
+            self._register8(_REGISTER_ENABLE,
+                enable & ~(_ENABLE_PON | _ENABLE_AEN))
+
+    def sensor_id(self):
+        return self._register8(_REGISTER_SENSORID)
+
+    def integration_time(self, value=None):
+        if value is None:
+            return self._integration_time
+        value = min(614.4, max(2.4, value))
+        cycles = int(value / 2.4)
+        self._integration_time = cycles * 2.4
+        return self._register8(_REGISTER_ATIME, 256 - cycles)
+
+    def gain(self, value):
+        if value is None:
+            return _GAINS[self._register8(_REGISTER_CONTROL)]
+        if value not in _GAINS:
+            raise ValueError("gain must be 1, 4, 16 or 60")
+        return self._register8(_REGISTER_CONTROL, _GAINS.index(value))
+
+    def _valid(self):
+        return bool(self._register8(_REGISTER_STATUS) & 0x01)
+
+    def read(self, raw=False):
+        was_active = self.active()
+        self.active(True)
+        while not self._valid():
+            time.sleep_ms(int(self._integration_time + 0.9))
+        data = tuple(self._register16(register) for register in (
+            _REGISTER_RDATA,
+            _REGISTER_GDATA,
+            _REGISTER_BDATA,
+            _REGISTER_CDATA,
+        ))
+        self.active(was_active)
+        if raw:
+            return data
+        return self._temperature_and_lux(data)
+
+    def _temperature_and_lux(self, data):
+        r, g, b, c = data
+        x = -0.14282 * r + 1.54924 * g + -0.95641 * b
+        y = -0.32466 * r + 1.57837 * g + -0.73191 * b
+        z = -0.68202 * r + 0.77073 * g +  0.56332 * b
+        d = x + y + z
+        n = (x / d - 0.3320) / (0.1858 - y / d)
+        cct = 449.0 * n**3 + 3525.0 * n**2 + 6823.3 * n + 5520.33
+        return cct, y
+
+    def threshold(self, cycles=None, min_value=None, max_value=None):
+        if cycles is None and min_value is None and max_value is None:
+            min_value = self._register16(_REGISTER_AILT)
+            max_value = self._register16(_REGISTER_AILT)
+            if self._register8(_REGISTER_ENABLE) & _ENABLE_AIEN:
+                cycles = _CYCLES[self._register8(_REGISTER_APERS) & 0x0f]
+            else:
+                cycles = -1
+            return cycles, min_value, max_value
+        if min_value is not None:
+            self._register16(_REGISTER_AILT, min_value)
+        if max_value is not None:
+            self._register16(_REGISTER_AIHT, max_value)
+        if cycles is not None:
+            enable = self._register8(_REGISTER_ENABLE)
+            if cycles == -1:
+                self._register8(_REGISTER_ENABLE, enable & ~(_ENABLE_AIEN))
+            else:
+                self._register8(_REGISTER_ENABLE, enable | _ENABLE_AIEN)
+                if cycles not in _CYCLES:
+                    raise ValueError("invalid persistence cycles")
+                self._register8(_REGISTER_APERS, _CYCLES.index(cycles))
+
+    def interrupt(self, value=None):
+        if value is None:
+            return bool(self._register8(_REGISTER_STATUS) & _ENABLE_AIEN)
+        if value:
+            raise ValueError("interrupt can only be cleared")
+        self.i2c.writeto(self.address, b'\xe6')
+
+
+def html_rgb(data):
+    r, g, b, c = data
+    red = pow((int((r/c) * 256) / 255), 2.5) * 255
+    green = pow((int((g/c) * 256) / 255), 2.5) * 255
+    blue = pow((int((b/c) * 256) / 255), 2.5) * 255
+    return red, green, blue
+
+def html_hex(data):
+    r, g, b = html_rgb(data)
+    return "{0:02x}{1:02x}{2:02x}".format(int(r),
+                             int(g),
+                             int(b))
\ No newline at end of file