From 21233d42588a811420a14dab48429aae3ee4f16a Mon Sep 17 00:00:00 2001 From: Arti Zirk Date: Sun, 22 Jan 2017 01:36:05 +0200 Subject: [PATCH] init --- README.md | 3 + conways_game_of_life.py | 139 +++++++++++++++++++ main.py | 70 ++++++++++ ntptime.py | 36 +++++ upcd8544.py | 287 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 535 insertions(+) create mode 100644 README.md create mode 100644 conways_game_of_life.py create mode 100644 main.py create mode 100644 ntptime.py create mode 100644 upcd8544.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..ab22817 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# ESP8266 toys + + diff --git a/conways_game_of_life.py b/conways_game_of_life.py new file mode 100644 index 0000000..66a0910 --- /dev/null +++ b/conways_game_of_life.py @@ -0,0 +1,139 @@ +from time import sleep_ms +from framebuf import FrameBuffer1 +from urandom import getrandbits + +class ConwaysGameOfLife: + def __init__(self, lcd): + # High score + self.best = 0 + # PCD8544 (Nokia 5110) LCD + self.lcd = lcd + self.width = 84 + self.height = 48 + self.pages = self.height // 8 + self.buffer = bytearray(self.pages * self.width) + self.framebuf = FrameBuffer1(self.buffer, self.width, self.height) + + def draw(self): + self.lcd.data(self.buffer) + + def intro(self): + self.framebuf.fill(0) + self.framebuf.text("Conway's", 10, 8, 1) + self.framebuf.text("Game", 26, 16, 1) + self.framebuf.text("of", 34, 24, 1) + self.framebuf.text("Life", 26, 32, 1) + self.draw() + + def end(self, score, best, size): + # The 8x8 font is too wide to fit "Generations", so I called it "Score" + self.framebuf.fill(0) + self.framebuf.text("Score", 0, 0, 1) + self.framebuf.text(str(score), 0, 8, 1) + self.framebuf.text("Best score", 0, 16, 1) + self.framebuf.text(str(best), 0, 24, 1) + self.framebuf.text("Pixel size", 0, 32, 1) + self.framebuf.text(str(size), 0, 40, 1) + self.draw() + + def begin(self, size=4, delay=20): + # Size of lifeforms in pixels + self.size = size + # Delay in ms between generations + self.delay = delay + + # Localised to avoid self lookups + # Possible performance optimisation, TBC + draw = self.draw + delay = self.delay + tick = self.tick + + # Randomise initial pixels + self.randomise() + + # Begin + generations = 0 + try: + while tick(): + generations = generations + 1 + draw() + sleep_ms(delay) + except KeyboardInterrupt: + pass + + # New high score? + if generations > self.best: + self.best = generations + + # End + self.end(generations, self.best, self.size) + + def randomise(self): + size = self.size + width = self.width + height = self.height + cell = self.cell + + self.framebuf.fill(0) + + for x in range(0, width, size): + for y in range(0, height, size): + # random bit: 0 = pixel off, 1 = pixel on + cell(x, y, getrandbits(1)) + + self.draw() + + def cell(self, x, y, colour): + size = self.size + buf = self.framebuf + + for i in range(size): + for j in range(size): + buf.pixel(x + i, y + j, colour) + + def get(self, x, y): + if not 0 <= x < self.width or not 0 <= y < self.height: + return 0 + return self.framebuf.pixel(x, y) & 1 + + def tick(self): + size = self.size + width = self.width + height = self.height + get = self.get + cell = self.cell + + # If no pixels are born or die, the game ends + something_happened = False + + for x in range(0, width, size): + for y in range(0, height, size): + + # Is the current cell alive + alive = get(x, y) + + # Count number of neighbours + neighbours = ( + get(x - size, y - size) + + get(x, y - size) + + get(x + size, y - size) + + get(x - size, y) + + get(x + size, y) + + get(x + size, y + size) + + get(x, y + size) + + get(x - size, y + size) + ) + + # Apply the game rules + if alive and not 2 <= neighbours <= 3: + # This pixel dies + cell(x, y, 0) + if not something_happened: + something_happened = True + elif not alive and neighbours == 3: + # A new pixel is born + cell(x, y, 1) + if not something_happened: + something_happened = True + + return something_happened diff --git a/main.py b/main.py new file mode 100644 index 0000000..594a866 --- /dev/null +++ b/main.py @@ -0,0 +1,70 @@ +import network +sta_if = network.WLAN(network.STA_IF) +ap_if = network.WLAN(network.AP_IF) + +from machine import Pin, SPI +import upcd8544 + +spi = SPI(1, baudrate=80000000, polarity=0, phase=0) +RST = Pin(2) +CE = Pin(15) +DC = Pin(0) +BC = Pin(16) + +lcd = upcd8544.PCD8544(spi, RST, CE, DC, BC) +import framebuf +width = 84 +height = 48 +pages = height // 8 +buffer = bytearray(pages * width) +framebuf = framebuf.FrameBuffer1(buffer, width, height) +framebuf.fill(0) +framebuf.text("EIK ROBO", 10, 0, 1) +lcd.data(buffer) +import time +framebuf.text("Wifi conn", 5, 10, 1) +lcd.data(buffer) +s = 0 +dots = ('|', '/', '-', '\\') +while not sta_if.isconnected(): + framebuf.fill_rect(30, 20, 8, 8, 0) + framebuf.text(dots[s], 30, 20, 1) + print(dots[s]) + lcd.data(buffer) + s += 1 + if s == len(dots): + s = 0 + time.sleep(0.05) +framebuf.fill_rect(30, 20, 8, 8, 0) +ip = sta_if.ifconfig()[0].split(".") +framebuf.text("IP:"+ip[0]+"."+ip[1], 0, 20, 1) +framebuf.text("."+ip[2]+"."+ip[3], 16, 30, 1) +print(sta_if.ifconfig()) +lcd.data(buffer) +framebuf.text("get time", 0, 40, 1) +lcd.data(buffer) +import ntptime +retry = 5 +while retry: + try: + ntptime.settime() + break + except: + retry -= 1 + time.sleep(0.5) + +if retry == 0: + framebuf.fill_rect(0, 40, width, 8, 0) + framebuf.text("error", 0, 40, 1) + time.sleep(5) +import utime +while True: + framebuf.fill_rect(0, 40, width, 8, 0) + t = time.localtime() + timezone = +2 + hours = t[3]+timezone + if hours > 23: + hours -= 24 + framebuf.text(str(hours)+":"+str(t[4])+":"+str(t[5]), 12, 40, 1) + lcd.data(buffer) + time.sleep(1) diff --git a/ntptime.py b/ntptime.py new file mode 100644 index 0000000..a97e08e --- /dev/null +++ b/ntptime.py @@ -0,0 +1,36 @@ +try: + import usocket as socket +except: + import socket +try: + import ustruct as struct +except: + import struct + +# (date(2000, 1, 1) - date(1900, 1, 1)).days * 24*60*60 +NTP_DELTA = 3155673600 + +host = "pool.ntp.org" + +def time(): + NTP_QUERY = bytearray(48) + NTP_QUERY[0] = 0x1b + addr = socket.getaddrinfo(host, 123)[0][-1] + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.settimeout(1) + res = s.sendto(NTP_QUERY, addr) + msg = s.recv(48) + s.close() + val = struct.unpack("!I", msg[40:44])[0] + return val - NTP_DELTA + +# There's currently no timezone support in MicroPython, so +# utime.localtime() will return UTC time (as if it was .gmtime()) +def settime(): + t = time() + import machine + import utime + tm = utime.localtime(t) + tm = tm[0:3] + (0,) + tm[3:6] + (0,) + machine.RTC().datetime(tm) + print(utime.localtime()) diff --git a/upcd8544.py b/upcd8544.py new file mode 100644 index 0000000..c637bf4 --- /dev/null +++ b/upcd8544.py @@ -0,0 +1,287 @@ +# -*- coding: utf-8 -*- +""" +MicroPython PCD8544 driver +(for Nokia 5110 displays) +""" + +__author__ = "Markus Birth" +__copyright__ = "Copyright 2015, Markus Birth" +__credits__ = ["Markus Birth"] +__license__ = "MIT" +__version__ = "1.0" +__maintainer__ = "Markus Birth" +__email__ = "markus@birth-online.de" +__status__ = "Production" + +# Datasheet: https://www.sparkfun.com/datasheets/LCD/Monochrome/Nokia5110.pdf +# Inspiration from: +# - https://github.com/inaugurator/upyd5110 +# - https://github.com/rm-hull/pcd8544/blob/master/src/lcd.py +# +# PINOUT +# WiPy/pyBoard display function +# +# 3V3 or any Pin => VCC 3.3V logic voltage (0=off, 1=on) +# MOSI => DIN data flow (Master out, Slave in) +# SCK => CLK SPI clock +# any Pin => RST Reset pin (0=reset, 1=normal) +# any Pin => CE Chip Enable (0=listen for input, 1=ignore input) +# any Pin => DC Data/Command (0=commands, 1=data) +# any Pin => LIGHT Light (0=on, 1=off) +# GND => GND +# +# pyBoard "Y" side +# SPI = pyb.SPI(1) +# RST = pyb.Pin('Y4') +# CE = pyb.Pin('Y5') +# DC = pyb.Pin('Y3') +# LIGHT = pyb.Pin('Y2') +# PWR = pyb.Pin('Y1') +# +# pyBoard "X" side +# SPI = pyb.SPI(2) +# RST = pyb.Pin('X4') +# CE = pyb.Pin('X5') +# DC = pyb.Pin('X3') +# LIGHT = pyb.Pin('X2') +# PWR = pyb.Pin('X1') +# +# WiPy (on Exp board, SD and User-LED jumper have to be removed!) +# SPI = machine.SPI(0) # GP14 (CLK) + GP16 (MOSI->DIN), User-LED jumper removed! +# RST = machine.Pin('GP24') +# CE = machine.Pin('GP12') +# DC = machine.Pin('GP22') +# LIGHT = machine.Pin('GP23') +# PWR = directly from 3V3 pin of the WiPy + +#try: +# import pyb as machine +#except: +# # WiPy +# import machine +# +#try: +# import struct +#except: +# # ESP8266 +# import ustruct as struct + +# ESP8266 +import machine +import ustruct as struct + +import time + +class PCD8544: + ADDRESSING_HORIZ = 0x00 + ADDRESSING_VERT = 0x02 + INSTR_BASIC = 0x00 + INSTR_EXT = 0x01 + POWER_UP = 0x00 + POWER_DOWN = 0x04 + DISPLAY_BLANK = 0x08 + DISPLAY_ALL = 0x09 + DISPLAY_NORMAL = 0x0c + DISPLAY_INVERSE = 0x0d + TEMP_COEFF_0 = 0x04 + TEMP_COEFF_1 = 0x05 + TEMP_COEFF_2 = 0x06 + TEMP_COEFF_3 = 0x07 + BIAS_1_4 = 0x17 # 1/4th + BIAS_1_5 = 0x16 # 1/5th + BIAS_1_6 = 0x15 # 1/6th + BIAS_1_7 = 0x14 # 1/7th + BIAS_1_8 = 0x13 # 1/8th + BIAS_1_9 = 0x12 # 1/9th + BIAS_1_10 = 0x11 # 1/10th + BIAS_1_11 = 0x10 # 1/11th + + def __init__(self, spi, rst, ce, dc, light, pwr=None): + self.width = 84 + self.height = 48 + self.power = self.POWER_DOWN + self.addressing = self.ADDRESSING_HORIZ + self.instr = self.INSTR_BASIC + self.display_mode = self.DISPLAY_BLANK + self.temp_coeff = self.TEMP_COEFF_0 + self.bias = self.BIAS_1_11 + self.voltage = 3060 + + # init the SPI bus and pins + #spi.init(spi.MASTER, baudrate=328125, bits=8, polarity=0, phase=1, firstbit=spi.MSB) + spi.init(baudrate=328125) + if "OUT_PP" in dir(rst): + # pyBoard style + rst.init(rst.OUT_PP, rst.PULL_NONE) # Reset line + ce.init(ce.OUT_PP, ce.PULL_NONE) # Chip Enable + dc.init(dc.OUT_PP, dc.PULL_NONE) # Data(1) / Command(0) mode + light.init(light.OUT_PP, light.PULL_NONE) + if pwr: + pwr.init(pwr.OUT_PP, pwr.PULL_NONE) + else: + # WiPy style + rst.init(rst.OUT, None) + ce.init(ce.OUT, None) + dc.init(dc.OUT, None) + light.init(light.OUT, None) + if pwr: + pwr.init(pwr.OUT, None) + + self.spi = spi + self.rst = rst + self.ce = ce + self.dc = dc + self.light = light + self.pwr = pwr + + self.light_off() + self.power_on() + self.ce.value(1) # set chip to disable (don't listen to input) + self.reset() + self.set_contrast(0xbf) + self.clear() + + def _set_function(self): + """ Write current power/addressing/instructionset values to lcd. """ + value = 0x20 | self.power | self.addressing | self.instr + self.command([value]) + + def set_power(self, power, set=True): + """ Sets the power mode of the LCD controller """ + assert power in [self.POWER_UP, self.POWER_DOWN], "Power must be POWER_UP or POWER_DOWN." + self.power = power + if set: + self._set_function() + + def set_adressing(self, addr, set=True): + """ Sets the adressing mode """ + assert addr in [self.ADDRESSING_HORIZ, self.ADDRESSING_VERT], "Addressing must be ADDRESSING_HORIZ or ADDRESSING_VERT." + self.addressing = addr + if set: + self._set_function() + + def set_instr(self, instr, set=True): + """ Sets instruction set (basic/extended) """ + assert instr in [self.INSTR_BASIC, self.INSTR_EXT], "Instr must be INSTR_BASIC or INSTR_EXT." + self.instr = instr + if set: + self._set_function() + + def set_display(self, display_mode): + """ Sets display mode (blank, black, normal, inverse) """ + assert display_mode in [self.DISPLAY_BLANK, self.DISPLAY_ALL, self.DISPLAY_NORMAL, self.DISPLAY_INVERSE], "Mode must be one of DISPLAY_BLANK, DISPLAY_ALL, DISPLAY_NORMAL or DISPLAY_INVERSE." + assert self.instr == self.INSTR_BASIC, "Please switch to basic instruction set first." + self.display_mode = display_mode + self.command([display_mode]) + + def set_temp_coeff(self, temp_coeff): + """ Sets temperature coefficient (0-3) """ + assert 4 <= temp_coeff < 8, "Temperature coefficient must be one of TEMP_COEFF_0..TEMP_COEFF_3." + assert self.instr == self.INSTR_EXT, "Please switch to extended instruction set first." + self.temp_coeff = temp_coeff + self.command([temp_coeff]) + + def set_bias(self, bias): + """ Sets the LCD bias. """ + assert 0x10 <= bias <= 0x17, "Bias must be one of BIAS_1_4..BIAS_1_11." + assert self.instr == self.INSTR_EXT, "Please switch to extended instruction set first." + self.bias = bias + self.command([bias]) + + def set_voltage(self, millivolts): + """ Sets the voltage of the LCD charge pump in millivolts. """ + assert 3060 <= millivolts <= 10680, "Voltage must be between 3,060 and 10,680 mV." + assert self.instr == self.INSTR_EXT, "Please switch to extended instruction set first." + self.voltage = millivolts + basevoltage = millivolts - 3060 + incrementor = basevoltage // 60 + code = 0x80 & incrementor + self.command([code]) + + def set_contrast(self, value): + """ set LCD voltage, i.e. contrast """ + assert 0x80 <= value <= 0xff, "contrast value must be between 0x80 and 0xff" + self.command([0x21, self.TEMP_COEFF_2, self.BIAS_1_7, value, 0x20, self.DISPLAY_NORMAL]) + # 0x21 - enter extended instruction set (H=1) + # 0x06 - set temperature coefficient 2 + # 0x14 - set BIAS system to n=3 (recomm. mux rate 1:40/1:34) + # value - (80-ff) - set Vop (80 = 3.00V, ff = 10.68V), 8b seems to work (0x3b/d70: 3.00+(70*0.06)=7.2V) + # 0x20 - back to basic instruction set + # 0x0c - normal display mode + + def position(self, x, y): + """ set cursor to bank y, column x """ + assert 0 <= x < self.width, "x must be between 0 and 83" + assert 0 <= y < self.height // 8, "y must be between 0 and 5" + assert self.instr == self.INSTR_BASIC, "Please switch to basic instruction set first." + self.command([x + 0x80, y + 0x40]) + + def clear(self): + """ clear screen """ + self.position(0, 0) + self.data([0] * (self.height * self.width // 8)) + self.position(0, 0) + + def sleep_ms(self, mseconds): + try: + time.sleep_ms(mseconds) + except AttributeError: + machine.delay(mseconds) + + def sleep_us(self, useconds): + try: + time.sleep_us(useconds) + except AttributeError: + machine.udelay(useconds) + + def power_on(self): + if self.pwr: + self.pwr.value(1) + self.reset() + + def reset(self): + """ issue reset impulse to reset the display """ + self.rst.value(0) # RST on + self.sleep_us(100) # reset impulse has to be >100 ns and <100 ms + self.rst.value(1) # RST off + # Defaults after reset: + self.power = self.POWER_DOWN + self.addressing = self.ADDRESSING_HORIZ + self.instr = self.INSTR_BASIC + self.display_mode = self.DISPLAY_BLANK + self.temp_coeff = self.TEMP_COEFF_0 + self.bias = self.BIAS_1_11 + self.voltage = 3060 + + def power_off(self): + self.clear() + self.command([0x20, 0x08]) + # 0x20 - basic instruction set + # 0x08 - set display to blank (doesn't delete contents) + self.sleep_ms(10) + if self.pwr: + self.pwr.value(0) # turn off power + + def command(self, arr): + """ send bytes in command mode """ + self.bitmap(arr, 0) + + def data(self, arr): + """ send bytes in data mode """ + self.bitmap(arr, 1) + + def bitmap(self, arr, dc): + self.dc.value(dc) + buf = struct.pack('B'*len(arr), *arr) + self.ce.value(0) # set chip to listening/enable + try: + self.spi.send(buf) + except AttributeError: + self.spi.write(buf) + self.ce.value(1) # set chip to disable + + def light_on(self): + self.light.value(0) # pull to GND + + def light_off(self): + self.light.value(1) # set to HIGH