diff --git a/.gitignore b/.gitignore index 13d1490..d5e2589 100644 --- a/.gitignore +++ b/.gitignore @@ -129,3 +129,5 @@ dmypy.json # Pyre type checker .pyre/ +# Backups +*~ diff --git a/main.py b/main.py new file mode 100644 index 0000000..9ca3aa0 --- /dev/null +++ b/main.py @@ -0,0 +1,33 @@ +from machine import I2C, Pin +import ssd1306 +import time +from mpu6050 import accel + +screen = {} +screen['rst'] = Pin(16, Pin.OUT) +screen['rst'].value(1) +screen['scl'] = Pin(15, Pin.OUT, Pin.PULL_UP) +screen['sda'] = Pin(4, Pin.OUT, Pin.PULL_UP) +screen['i2c'] = I2C(scl=screen['scl'], sda=screen['sda'], freq=450000) +oled = ssd1306.SSD1306_I2C(128, 64, screen['i2c'], addr=0x3c) + +oled.fill(0) +oled.text('Initializing',5,5) +oled.show() + +mpu = {} +mpu['scl'] = Pin(19, Pin.OUT, Pin.PULL_UP) +mpu['sda'] = Pin(23, Pin.OUT, Pin.PULL_UP) +mpu['i2c'] = I2C(scl=mpu['scl'], sda=mpu['sda']) +accelerometer = accel(mpu['i2c']) + +def output_positions(arg): + oled.fill(0) + oled.text('X: %(AcX)+05d' % arg, 5, 5) + oled.text('Y: %(AcY)+05d' % arg, 5, 15) + oled.text('Z: %(AcZ)+05d' % arg, 5, 25) + oled.show() + +for i in range(0,240): + output_positions(accelerometer.get_values()) + time.sleep_ms(250) diff --git a/mpu6050.py b/mpu6050.py new file mode 100644 index 0000000..dcff6fd --- /dev/null +++ b/mpu6050.py @@ -0,0 +1,41 @@ +import machine + + +class accel(): + def __init__(self, i2c, addr=0x68): + self.iic = i2c + self.addr = addr + self.iic.start() + self.iic.writeto(self.addr, bytearray([107, 0])) + self.iic.stop() + + def get_raw_values(self): + self.iic.start() + a = self.iic.readfrom_mem(self.addr, 0x3B, 14) + self.iic.stop() + return a + + def get_ints(self): + b = self.get_raw_values() + c = [] + for i in b: + c.append(i) + return c + + def bytes_toint(self, firstbyte, secondbyte): + if not firstbyte & 0x80: + return firstbyte << 8 | secondbyte + return - (((firstbyte ^ 255) << 8) | (secondbyte ^ 255) + 1) + + def get_values(self): + raw_ints = self.get_raw_values() + vals = {} + vals["AcX"] = self.bytes_toint(raw_ints[0], raw_ints[1]) + vals["AcY"] = self.bytes_toint(raw_ints[2], raw_ints[3]) + vals["AcZ"] = self.bytes_toint(raw_ints[4], raw_ints[5]) + vals["Tmp"] = self.bytes_toint(raw_ints[6], raw_ints[7]) / 340.00 + 36.53 + vals["GyX"] = self.bytes_toint(raw_ints[8], raw_ints[9]) + vals["GyY"] = self.bytes_toint(raw_ints[10], raw_ints[11]) + vals["GyZ"] = self.bytes_toint(raw_ints[12], raw_ints[13]) + return vals # returned in range of Int16 + # -32768 to 32767 diff --git a/notes.org b/notes.org new file mode 100644 index 0000000..b4ed1ae --- /dev/null +++ b/notes.org @@ -0,0 +1,67 @@ + +#+TITLE: MicroPython Implementation Notes + + +* Circuit Layout + +| Pin | Used For | +| 37 | SDA - MPU6050 | +| 36 | SCL - MPU6050 | +| | | + + +* SSD 1306 + +#+BEGIN_SRC python +from machine import I2C, Pin +import ssd1306 + +rst = Pin(16, Pin.OUT) +rst.value(1) +scl = Pin(15, Pin.OUT, Pin.PULL_UP) +sda = Pin(4, Pin.OUT, Pin.PULL_UP) +i2c = I2C(scl=scl, sda=sda, freq=450000) +oled = ssd1306.SSD1306_I2C(128, 64, i2c, addr=0x3c) + +oled.fill(0) +oled.text('ESP32', 45, 5) +oled.text('MicroPython', 20, 20) +oled.show() +#+END_SRC + +* MPU 6050 + +#+BEGIN_SRC python +from machine import I2C, Pin +from mpu6050 import accel + +scl=Pin(36, Pin.OUT) +sda=Pin(37, Pin.OUT) +i2c = I2C(scl=scl, sda=sda, freq=450000) +a = accel(i2c) +a.get_values() +#+END_SRC + + +* Connections + +Pin 23 ESP -> SDA MPU +Pin 19 ESP -> SCL MPU + +MPU AD0 -> - Rail +MPU GND -> - Rail +MPU Vcc -> + Rail + +ESP GND -> - Rail +ESP VEXT -> +Rail + +* Things to look at + + - Can we turn off the MPU with VEXT Control? (ESP Pin 21). + - Connecting to WiFi - Client Mode + - Connecting to WiFi - Direct Mode + - BLE? + - NTP? + - How to store the data appropriately + - How to retrieve data over WiFi + - How to detect battery low diff --git a/ssd1306.py b/ssd1306.py new file mode 100644 index 0000000..e949137 --- /dev/null +++ b/ssd1306.py @@ -0,0 +1,168 @@ +# MicroPython SSD1306 OLED driver, I2C and SPI interfaces + +import time +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_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) + + +class SSD1306: + def __init__(self, width, height, external_vcc): + self.width = width + self.height = height + self.external_vcc = external_vcc + self.pages = self.height // 8 + # Note the subclass must initialize self.framebuf to a framebuffer. + # This is necessary because the underlying data buffer is different + # between I2C and SPI implementations (I2C needs an extra byte). + self.poweron() + self.init_display() + + def init_display(self): + for cmd in ( + SET_DISP | 0x00, # off + # address setting + SET_MEM_ADDR, 0x00, # horizontal + # resolution and layout + SET_DISP_START_LINE | 0x00, + 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.height == 32 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 + # charge pump + SET_CHARGE_PUMP, 0x10 if self.external_vcc else 0x14, + SET_DISP | 0x01): # on + self.write_cmd(cmd) + self.fill(0) + self.show() + + def poweroff(self): + self.write_cmd(SET_DISP | 0x00) + + 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 show(self): + x0 = 0 + x1 = self.width - 1 + if self.width == 64: + # displays with width of 64 pixels are shifted by 32 + x0 += 32 + x1 += 32 + 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_framebuf() + + def fill(self, col): + self.framebuf.fill(col) + + def pixel(self, x, y, col): + self.framebuf.pixel(x, y, col) + + def scroll(self, dx, dy): + self.framebuf.scroll(dx, dy) + + def text(self, string, x, y, col=1): + self.framebuf.text(string, x, y, col) + + +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) + # Add an extra byte to the data buffer to hold an I2C data/command byte + # to use hardware-compatible I2C transactions. A memoryview of the + # buffer is used to mask this byte from the framebuffer operations + # (without a major memory hit as memoryview doesn't copy to a separate + # buffer). + self.buffer = bytearray(((height // 8) * width) + 1) + self.buffer[0] = 0x40 # Set first byte of data buffer to Co=0, D/C=1 + self.framebuf = framebuf.FrameBuffer1(memoryview(self.buffer)[1:], width, height) + 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_framebuf(self): + # Blast out the frame buffer using a single I2C transaction to support + # hardware I2C interfaces. + self.i2c.writeto(self.addr, self.buffer) + + def poweron(self): + pass + + +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 + self.buffer = bytearray((height // 8) * width) + self.framebuf = framebuf.FrameBuffer1(self.buffer, width, height) + super().__init__(width, height, external_vcc) + + def write_cmd(self, cmd): + self.spi.init(baudrate=self.rate, polarity=0, phase=0) + self.cs.high() + self.dc.low() + self.cs.low() + self.spi.write(bytearray([cmd])) + self.cs.high() + + def write_framebuf(self): + self.spi.init(baudrate=self.rate, polarity=0, phase=0) + self.cs.high() + self.dc.high() + self.cs.low() + self.spi.write(self.buffer) + self.cs.high() + + def poweron(self): + self.res.high() + time.sleep_ms(1) + self.res.low() + time.sleep_ms(10) + self.res.high()