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