├── .gitignore ├── boot.py ├── conways_game_of_life.py ├── license.txt ├── pcd8544.py ├── photos ├── end.jpg ├── intro.jpg ├── life.jpg └── test.jpg └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /boot.py: -------------------------------------------------------------------------------- 1 | # This file is executed on every boot (including wake-boot from deepsleep) 2 | #import esp 3 | #esp.osdebug(None) 4 | import gc 5 | #import webrepl 6 | #webrepl.start() 7 | gc.collect() 8 | -------------------------------------------------------------------------------- /conways_game_of_life.py: -------------------------------------------------------------------------------- 1 | from time import sleep_ms 2 | from framebuf import FrameBuffer1 3 | from urandom import getrandbits 4 | 5 | class ConwaysGameOfLife: 6 | def __init__(self, lcd): 7 | # High score 8 | self.best = 0 9 | # PCD8544 (Nokia 5110) LCD 10 | self.lcd = lcd 11 | self.width = 84 12 | self.height = 48 13 | self.buffer = bytearray((self.height // 8) * self.width) 14 | self.framebuf = FrameBuffer1(self.buffer, self.width, self.height) 15 | 16 | def draw(self): 17 | self.lcd.data(self.buffer) 18 | 19 | def intro(self): 20 | self.framebuf.fill(0) 21 | self.framebuf.text("Conway's", 10, 8, 1) 22 | self.framebuf.text("Game", 26, 16, 1) 23 | self.framebuf.text("of", 34, 24, 1) 24 | self.framebuf.text("Life", 26, 32, 1) 25 | self.draw() 26 | 27 | def end(self, score, best, size): 28 | # The 8x8 font is too wide to fit "Generations", so I called it "Score" 29 | self.framebuf.fill(0) 30 | self.framebuf.text("Score", 0, 0, 1) 31 | self.framebuf.text(str(score), 0, 8, 1) 32 | self.framebuf.text("Best score", 0, 16, 1) 33 | self.framebuf.text(str(best), 0, 24, 1) 34 | self.framebuf.text("Pixel size", 0, 32, 1) 35 | self.framebuf.text(str(size), 0, 40, 1) 36 | self.draw() 37 | 38 | def begin(self, size=4, delay=20): 39 | # Size of lifeforms in pixels 40 | self.size = size 41 | # Delay in ms between generations 42 | self.delay = delay 43 | 44 | # Localised to avoid self lookups 45 | # Possible performance optimisation, TBC 46 | draw = self.draw 47 | delay = self.delay 48 | tick = self.tick 49 | 50 | # Randomise initial pixels 51 | self.randomise() 52 | 53 | # Begin 54 | generations = 0 55 | try: 56 | while tick(): 57 | generations = generations + 1 58 | draw() 59 | sleep_ms(delay) 60 | except KeyboardInterrupt: 61 | pass 62 | 63 | # New high score? 64 | if generations > self.best: 65 | self.best = generations 66 | 67 | # End 68 | self.end(generations, self.best, self.size) 69 | 70 | def randomise(self): 71 | size = self.size 72 | width = self.width 73 | height = self.height 74 | cell = self.cell 75 | 76 | self.framebuf.fill(0) 77 | 78 | for x in range(0, width, size): 79 | for y in range(0, height, size): 80 | # random bit: 0 = pixel off, 1 = pixel on 81 | cell(x, y, getrandbits(1)) 82 | 83 | self.draw() 84 | 85 | def cell(self, x, y, colour): 86 | size = self.size 87 | buf = self.framebuf 88 | 89 | for i in range(size): 90 | for j in range(size): 91 | buf.pixel(x + i, y + j, colour) 92 | 93 | def get(self, x, y): 94 | if not 0 <= x < self.width or not 0 <= y < self.height: 95 | return 0 96 | return self.framebuf.pixel(x, y) & 1 97 | 98 | def tick(self): 99 | size = self.size 100 | width = self.width 101 | height = self.height 102 | get = self.get 103 | cell = self.cell 104 | 105 | # If no pixels are born or die, the game ends 106 | something_happened = False 107 | 108 | for x in range(0, width, size): 109 | for y in range(0, height, size): 110 | 111 | # Is the current cell alive 112 | alive = get(x, y) 113 | 114 | # Count number of neighbours 115 | neighbours = ( 116 | get(x - size, y - size) + 117 | get(x, y - size) + 118 | get(x + size, y - size) + 119 | get(x - size, y) + 120 | get(x + size, y) + 121 | get(x + size, y + size) + 122 | get(x, y + size) + 123 | get(x - size, y + size) 124 | ) 125 | 126 | # Apply the game rules 127 | if alive and not 2 <= neighbours <= 3: 128 | # This pixel dies 129 | cell(x, y, 0) 130 | if not something_happened: 131 | something_happened = True 132 | elif not alive and neighbours == 3: 133 | # A new pixel is born 134 | cell(x, y, 1) 135 | if not something_happened: 136 | something_happened = True 137 | 138 | return something_happened 139 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Mike Causer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pcd8544.py: -------------------------------------------------------------------------------- 1 | """ 2 | MicroPython Nokia 5110 PCD8544 84x48 LCD driver 3 | https://github.com/mcauser/micropython-pcd8544 4 | 5 | MIT License 6 | Copyright (c) 2016 Mike Causer 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | """ 26 | 27 | from micropython import const 28 | from ustruct import pack 29 | from utime import sleep_us 30 | 31 | # Function set 0010 0xxx 32 | FUNCTION_SET = const(0x20) 33 | POWER_DOWN = const(0x04) 34 | ADDRESSING_VERT = const(0x02) 35 | EXTENDED_INSTR = const(0x01) 36 | 37 | # Display control 0000 1x0x 38 | DISPLAY_BLANK = const(0x08) 39 | DISPLAY_ALL = const(0x09) 40 | DISPLAY_NORMAL = const(0x0c) 41 | DISPLAY_INVERSE = const(0x0d) 42 | 43 | # Temperature control 0000 01xx 44 | TEMP_COEFF_0 = const(0x04) 45 | TEMP_COEFF_1 = const(0x05) 46 | TEMP_COEFF_2 = const(0x06) # default 47 | TEMP_COEFF_3 = const(0x07) 48 | 49 | # Bias system 0001 0xxx 50 | BIAS_1_100 = const(0x10) 51 | BIAS_1_80 = const(0x11) 52 | BIAS_1_65 = const(0x12) 53 | BIAS_1_48 = const(0x13) 54 | BIAS_1_40 = const(0x14) # default 55 | BIAS_1_24 = const(0x15) 56 | BIAS_1_18 = const(0x16) 57 | BIAS_1_10 = const(0x17) 58 | 59 | # Set operation voltage 60 | SET_VOP = const(0x80) 61 | 62 | # DDRAM addresses 63 | COL_ADDR = const(0x80) # x pos (0~83) 64 | BANK_ADDR = const(0x40) # y pos, in banks of 8 rows (0~5) 65 | 66 | class PCD8544: 67 | def __init__(self, spi, cs, dc, rst=None): 68 | self.width = 84 69 | self.height = 48 70 | 71 | self.spi = spi 72 | self.cs = cs # chip enable, active LOW 73 | self.dc = dc # data HIGH, command LOW 74 | self.rst = rst # reset, active LOW 75 | 76 | self.cs.init(self.cs.OUT, value=1) 77 | self.dc.init(self.dc.OUT, value=0) 78 | 79 | if self.rst: 80 | self.rst.init(self.rst.OUT, value=1) 81 | self.reset() 82 | self.init() 83 | 84 | def init(self, horizontal=True, contrast=0x3f, bias=BIAS_1_40, temp=TEMP_COEFF_2): 85 | # power up, horizontal addressing, basic instruction set 86 | self.fn = FUNCTION_SET 87 | 88 | # switch to vertical addressing 89 | if not horizontal: 90 | self.fn |= ADDRESSING_VERT 91 | 92 | self.contrast(contrast, bias, temp) 93 | self.cmd(DISPLAY_NORMAL) 94 | self.clear() 95 | 96 | def reset(self): 97 | # issue reset impulse to reset the display 98 | self.rst(1) 99 | sleep_us(100) 100 | self.rst(0) 101 | sleep_us(100) # reset impulse has to be >100 ns and <100 ms 102 | self.rst(1) 103 | sleep_us(100) 104 | 105 | def power_on(self): 106 | self.cs(1) 107 | self.fn &= ~POWER_DOWN 108 | self.cmd(self.fn) 109 | 110 | def power_off(self): 111 | self.fn |= POWER_DOWN 112 | self.cmd(self.fn) 113 | 114 | def contrast(self, contrast=0x3f, bias=BIAS_1_40, temp=TEMP_COEFF_2): 115 | for cmd in ( 116 | # extended instruction set is required to set temp, bias and vop 117 | self.fn | EXTENDED_INSTR, 118 | # set temperature coefficient 119 | temp, 120 | # set bias system (n=3 recommended mux rate 1:40/1:34) 121 | bias, 122 | # set contrast with operating voltage (0x00~0x7f) 123 | # 0x00 = 3.00V, 0x3f = 6.84V, 0x7f = 10.68V 124 | # starting at 3.06V, each bit increments voltage by 0.06V at room temperature 125 | SET_VOP | contrast, 126 | # revert to basic instruction set 127 | self.fn & ~EXTENDED_INSTR): 128 | self.cmd(cmd) 129 | 130 | def invert(self, invert): 131 | self.cmd(DISPLAY_INVERSE if invert else DISPLAY_NORMAL) 132 | 133 | def clear(self): 134 | # clear DDRAM, reset x,y position to 0,0 135 | self.data([0] * (self.height * self.width // 8)) 136 | self.position(0, 0) 137 | 138 | def position(self, x, y): 139 | # set cursor to column x (0~83), bank y (0~5) 140 | self.cmd(COL_ADDR | x) # set x pos (0~83) 141 | self.cmd(BANK_ADDR | y) # set y pos (0~5) 142 | 143 | def cmd(self, command): 144 | self.dc(0) 145 | self.cs(0) 146 | self.spi.write(bytearray([command])) 147 | self.cs(1) 148 | 149 | def data(self, data): 150 | self.dc(1) 151 | self.cs(0) 152 | self.spi.write(pack('B'*len(data), *data)) 153 | self.cs(1) 154 | -------------------------------------------------------------------------------- /photos/end.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcauser/MicroPython-ESP8266-Nokia-5110-Conways-Game-of-Life/0d40137f26f27d4c9832c17fa60de264ecd960b4/photos/end.jpg -------------------------------------------------------------------------------- /photos/intro.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcauser/MicroPython-ESP8266-Nokia-5110-Conways-Game-of-Life/0d40137f26f27d4c9832c17fa60de264ecd960b4/photos/intro.jpg -------------------------------------------------------------------------------- /photos/life.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcauser/MicroPython-ESP8266-Nokia-5110-Conways-Game-of-Life/0d40137f26f27d4c9832c17fa60de264ecd960b4/photos/life.jpg -------------------------------------------------------------------------------- /photos/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcauser/MicroPython-ESP8266-Nokia-5110-Conways-Game-of-Life/0d40137f26f27d4c9832c17fa60de264ecd960b4/photos/test.jpg -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # MicroPython ESP8266 Nokia 5110 Conway's Game of Life 2 | 3 | ![Game](https://raw.github.com/mcauser/MicroPython-ESP8266-Nokia-5110-Conways-Game-of-Life/master/photos/intro.jpg) 4 | 5 | ## Parts 6 | 7 | * [WeMos D1 Mini](http://www.aliexpress.com/store/product/D1-mini-Mini-NodeMcu-4M-bytes-Lua-WIFI-Internet-of-Things-development-board-based-ESP8266/1331105_32529101036.html) $4.00 USD 8 | * [Nokia 5110 module](http://www.aliexpress.com/item/1pc-Lowest-Price-84-48-84x84-LCD-Module-White-backlight-adapter-PCB-for-Nokia-5110-Newest/32401396134.html) $2.10 USD 9 | * [Breadboard 400 point](http://www.aliexpress.com/item/Quality-mini-bread-board-breadboard-8-5CM-x-5-5CM-400-holes/32347239015.html) $1.02 USD 10 | * [Jumper wire](http://www.aliexpress.com/item/Free-Shipping-140pcs-in-one-package-convenient-New-Solderless-Flexible-Breadboard-Jumper-wires-Cables-HOT-Sale/2044172287.html) $1.72 USD 11 | 12 | ## Installation 13 | 14 | Start by cloning this repo. 15 | 16 | ``` 17 | $ git clone git@github.com:mcauser/MicroPython-ESP8266-Nokia-5110-Conways-Game-of-Life.git 18 | ``` 19 | 20 | * [Install MicroPython on your ESP8266 device](#install-micropython-on-your-esp8266-device) 21 | * [Install python](#install-python) 22 | * [Download latest MicroPython firmware](#download-latest-micropython-firmware) 23 | * [Flash firmware with esptool](#flash-firmware-with-esptool) 24 | * [Verify firmware](#verify-firmware) 25 | * [Install game with WebREPL](#install-game-with-webrepl) 26 | * [Configure Access Point](#configure-access-point) 27 | * [Start WebREPL](#start-webrepl) 28 | * [Connect to Access Point](#connect-to-access-point) 29 | * [Upload files with WebREPL](#upload-files-with-webrepl) 30 | * [Setup and test Nokia 5110 display](#setup-and-test-nokia-5110-display) 31 | * [Play Conway's Game of Life](#play-conways-game-of-life) 32 | 33 | FYI code blocks below. 34 | 35 | * Commands starting with `$` are expected to be entered in a terminal **without** the leading `$` character. 36 | * Commands starting with `>>>` are expected to be entered into a REPL **without** the leading `>>>` characters. 37 | 38 | ## Install MicroPython on your ESP8266 device 39 | 40 | I am using a [WeMos D1 Mini](http://www.wemos.cc/Products/d1_mini.html), but you can use any ESP8266 device. 41 | 42 | The WeMos D1 Mini features an ESP-12F with 4MB flash. 43 | 44 | If you already have [MicroPython](http://micropython.org/) [v1.8.x](https://github.com/micropython/micropython/releases) installed on your device, you can skip down to [installing the game](#install-game-with-webrepl). 45 | 46 | ### Install Python 47 | 48 | If you are using [brew](http://brew.sh/), installing Python 2.7 is a one-liner. Install [pip](https://pip.pypa.io/en/stable/) to manage python packages. 49 | 50 | ``` 51 | $ brew install python 52 | $ pip install --upgrade pip setuptools 53 | ``` 54 | 55 | You can use pip to install Python packages. eg. 56 | 57 | ``` 58 | $ pip search SomePackage 59 | $ pip install SomePackage 60 | $ pip install --upgrade SomePackage 61 | $ pip uninstall SomePackage 62 | ``` 63 | 64 | Install [esptool](https://github.com/themadinventor/esptool/) with pip 65 | 66 | ``` 67 | $ pip install esptool 68 | ``` 69 | 70 | ### Download latest MicroPython firmware 71 | 72 | Open [http://micropython.org/download/#esp8266](http://micropython.org/download/#esp8266) 73 | 74 | Download the latest firmware, currently v1.8.3-61 75 | 76 | ``` 77 | $ wget http://micropython.org/resources/firmware/esp8266-20160827-v1.8.3-61-g531217a.bin 78 | ``` 79 | 80 | ### Flash firmware with esptool 81 | 82 | The WeMos D1 mini uses a [CH340G](http://www.wch.cn/download/CH341SER_MAC_ZIP.html) USB-TTL driver, which shows up on my MacBook Pro as either `/dev/tty.wchusbserial1410` or `/dev/tty.wchusbserial1420`. 83 | 84 | If you are using another device, you may find yours to be `/dev/ttyUSB0`. Find yours with: 85 | 86 | ``` 87 | $ ls /dev/tty* 88 | ``` 89 | 90 | When deploying new firmware, it's best to completely erase all previous versions. The first run executes suspiciously quick, so I run it twice. 91 | 92 | ``` 93 | $ esptool.py -p /dev/tty.wchusbserial1420 erase_flash 94 | esptool.py v1.1 95 | Connecting... 96 | Erasing flash (this may take a while)... 97 | ``` 98 | 99 | Upload the new MicroPython firmware. 100 | 101 | ``` 102 | $ esptool.py -p /dev/tty.wchusbserial1420 write_flash -fm dio -fs 32m 0 esp8266-20160827-v1.8.3-61-g531217a.bin 103 | esptool.py v1.1 104 | Connecting... 105 | Running Cesanta flasher stub... 106 | Flash params set to 0x0240 107 | Writing 532480 @ 0x0... 43008 (8 %) 108 | ... 109 | Writing 532480 @ 0x0... 316416 (59 %) 110 | ... 111 | Writing 532480 @ 0x0... 532480 (100 %) 112 | Wrote 532480 bytes at 0x0 in 46.0 seconds (92.6 kbit/s)... 113 | Leaving... 114 | ``` 115 | 116 | More info in the [MicroPython docs](http://docs.micropython.org/en/latest/esp8266/esp8266/tutorial/intro.html#deploying-the-firmware) on flashing the firmware. 117 | 118 | ### Verify firmware 119 | 120 | ``` 121 | $ screen /dev/tty.wchusbserial1420 115200 122 | ``` 123 | 124 | Click your hardware Reset button or `Control+D` in screen. 125 | 126 | ```python 127 | (lots of funny characters) ets_task(40100390, 3, 3fff6300, 4) 128 | could not open file 'main.py' for reading 129 | 130 | MicroPython v1.8.3-61-g531217a on 2016-08-27; ESP module with ESP8266 131 | Type "help()" for more information. 132 | >>> 133 | ``` 134 | 135 | Check the firmware md5 matches. You should see True. If not, `erase_flash` and `write_flash` again, or try different `write_flash` arguments after reading the [esptool](https://github.com/themadinventor/esptool/) readme. 136 | 137 | ```python 138 | >>> import esp 139 | >>> esp.check_fw() 140 | size: 531032 141 | md5: f54e36598b104f8c5dec883181080aaa 142 | True 143 | ``` 144 | 145 | To exit screen run: `Control+A` then `Control+\`. 146 | 147 | 148 | ## Install game with WebREPL 149 | 150 | I was receiving `MemoryErrors` when trying to upload the game via REPL paste mode. 151 | 152 | Using WebREPL `Send a file`, I'm able to upload the game successfully, but with WebREPL enabled, not enough ram to run it. 153 | 154 | Reboot with WebREPL disabled and the game has enough resources to run. 155 | 156 | ### Configure Access Point 157 | 158 | The default settings will give your device the ip 192.168.4.1 159 | 160 | ```python 161 | >>> import network 162 | >>> ap_if = network.WLAN(network.AP_IF) 163 | >>> ap_if.active(True) 164 | ``` 165 | 166 | Yep, it's 192.168.4.1. (ip,netmask,gateway,dns) 167 | 168 | ```python 169 | >>> ap_if.ifconfig() 170 | ('192.168.4.1', '255.255.255.0', '192.168.4.1', '208.67.222.222') 171 | ``` 172 | 173 | ### Start WebREPL 174 | 175 | ```python 176 | >>> import webrepl 177 | >>> webrepl.start() 178 | WebREPL daemon started on ws://192.168.4.1:8266 179 | Started webrepl in setup mode 180 | ``` 181 | 182 | ### Connect to Access Point 183 | 184 | Before leaving your working internet connection, open the WebREPL http://micropython.org/webrepl/ but don't connect yet. 185 | 186 | Join the MicroPython-xxxxxx network. Password is `micropythoN`. Uppercase N is not a typo. 187 | 188 | You should see something like this in the REPL: 189 | 190 | ```python 191 | >>> add 1 192 | aid 1 193 | station: 78:31:c1:bb:cc:dd join, AID = 1 194 | ``` 195 | 196 | Switch back to WebREPL tab in your browser and click `Connect`. 197 | 198 | It will ask you to set a password. 199 | 200 | ``` 201 | Welcome to MicroPython! 202 | Welcome to MicroPython WebREPL! 203 | 204 | This is the first time you connect to WebREPL, so please set a password 205 | to use for the following WebREPL sessions. Once you enter the password 206 | twice, your board will reboot with WebREPL running in active mode. On 207 | some boards, you may need to press reset button or reconnect power. 208 | 209 | New password: 210 | ``` 211 | 212 | Enter a password twice and it will save the password and reboot. 213 | 214 | ``` 215 | New password: ******** 216 | Confirm password: ******** 217 | Password successfully set, restarting... 218 | Disconnected 219 | ``` 220 | 221 | WebREPL will be disabled when it reboots, so you will need to start it again. 222 | 223 | Later, to make it always on, you can add the start command to boot.py. In this case, we do not want it started by default. 224 | 225 | Switch back to your terminal and start WebREPL again 226 | 227 | ```python 228 | >>> import webrepl 229 | >>> webrepl.start() 230 | WebREPL daemon started on ws://192.168.4.1:8266 231 | Started webrepl in normal mode 232 | ``` 233 | 234 | Notice this time, it says `normal mode`. FYI - the WebREPL password you entered is saved in `port_config.py` in the root. 235 | 236 | ```python 237 | >>> import os 238 | >>> os.listdir() 239 | ['boot.py', 'port_config.py'] 240 | ``` 241 | 242 | Click `Connect` and enter your password. 243 | 244 | ```python 245 | Welcome to MicroPython! 246 | Password: 247 | WebREPL connected 248 | >>> 249 | ``` 250 | 251 | ### Upload files with WebREPL 252 | 253 | Under `Send a file` on the right, choose the file `pcd8544.py`. It will list the file as `pcd8544.py - 9787 bytes`. 254 | 255 | Click `Send to device`. At the bottom it should say `Sent pcd8544.py, 9787 bytes`. 256 | 257 | Repeat for the file `conways_game_of_life.py`. It should say `Sent conways_game_of_life.py, 4130 bytes` 258 | 259 | Click `Disconnect`. Click your hardware Reset button, or use `machine.reset()`. 260 | 261 | ```python 262 | >>> import machine 263 | >>> machine.reset() 264 | ``` 265 | 266 | After rebooting, if you do not need the Access Point anymore, you can disable it with: 267 | 268 | ```python 269 | >>> import network 270 | >>> ap_if = network.WLAN(network.AP_IF) 271 | >>> ap_if.active(False) 272 | station: 78:31:c1:bb:cc:dd leave, AID = 1 273 | rm 1 274 | bcn 0 275 | del if1 276 | usl 277 | mode : null 278 | ``` 279 | 280 | ### Setup and test Nokia 5110 display 281 | 282 | Connections: 283 | 284 | WeMos D1 Mini (ESP8266) | Nokia 5110 PCD8544 LCD | Description 285 | ----------------------- | ---------------------- | ---------------------------------------------- 286 | D3 (GPIO0) | 0 RST | Output from ESP to reset display 287 | D4 (GPIO2) | 1 CE | Output from ESP to chip select/enable display 288 | D8 (GPIO15) | 2 DC | Output from display data/command to ESP 289 | D7 (GPIO13) | 3 Din | Output from ESP SPI MOSI to display data input 290 | D5 (GPIO14) | 4 Clk | Output from ESP SPI clock 291 | 3V3 | 5 Vcc | 3.3V from ESP to display 292 | D6 (GPIO12) | 6 BL | 3.3V to turn backlight on, or PWM 293 | G | 7 Gnd | Ground 294 | 295 | Test the display: 296 | 297 | ```python 298 | >>> from machine import Pin, SPI 299 | >>> import time 300 | >>> import pcd8544 301 | 302 | >>> spi = SPI(1, baudrate=80000000, polarity=0, phase=0) 303 | >>> cs = Pin(2) 304 | >>> dc = Pin(15) 305 | >>> rst = Pin(0) 306 | 307 | >>> bl = Pin(12, Pin.OUT, value=1) 308 | >>> lcd = pcd8544.PCD8544(spi, cs, dc, rst) 309 | ``` 310 | 311 | Switch off the backlight: 312 | 313 | ```python 314 | >>> bl.value(0) 315 | ``` 316 | 317 | Switch on the backlight: 318 | 319 | ```python 320 | >>> bl.value(1) 321 | ``` 322 | 323 | Use a framebuffer to store the 4032 pixels (84x48): 324 | 325 | ```python 326 | >>> import framebuf 327 | >>> buffer = bytearray((lcd.height // 8) * lcd.width) 328 | >>> framebuf = framebuf.FrameBuffer1(buffer, lcd.width, lcd.height) 329 | ``` 330 | 331 | Light every pixel: 332 | 333 | ```python 334 | >>> framebuf.fill(1) 335 | >>> lcd.data(buffer) 336 | ``` 337 | 338 | Clear screen: 339 | 340 | ```python 341 | >>> framebuf.fill(0) 342 | >>> lcd.data(buffer) 343 | ``` 344 | 345 | Print `Hello, World!` using the 8x8 font: 346 | 347 | ```python 348 | >>> framebuf.text("Hello,", 0, 0, 1) 349 | >>> framebuf.text("World!", 0, 9, 1) 350 | >>> lcd.data(buffer) 351 | ``` 352 | 353 | If all this works, let's play. 354 | 355 | ![Test](https://raw.github.com/mcauser/MicroPython-ESP8266-Nokia-5110-Conways-Game-of-Life/master/photos/test.jpg) 356 | 357 | ### Play game 358 | 359 | Get the current frequency of the CPU. 360 | 361 | ```python 362 | >>> import machine 363 | >>> machine.freq() 364 | 80,000,000 365 | ``` 366 | 367 | 80,000,000 means 80MHz. If you want Moar Speed, set the CPU frequency to 160 MHz using: 368 | 369 | ```python 370 | >>> machine.freq(160000000) 371 | ``` 372 | 373 | Import libraries 374 | 375 | ```python 376 | >>> from machine import Pin, SPI 377 | >>> import time 378 | >>> import pcd8544 379 | >>> from conways_game_of_life import ConwaysGameOfLife 380 | ``` 381 | 382 | Connect to the Nokia 5110 display (PCD8544) using hardware SPI. 383 | 384 | ```python 385 | >>> spi = SPI(1, baudrate=80000000, polarity=0, phase=0) 386 | >>> cs = Pin(2) 387 | >>> dc = Pin(15) 388 | >>> rst = Pin(0) 389 | 390 | >>> bl = Pin(12, Pin.OUT, value=1) 391 | >>> lcd = pcd8544.PCD8544(spi, cs, dc, rst) 392 | ``` 393 | 394 | Switch off the backlight: 395 | 396 | ```python 397 | >>> bl.value(0) 398 | ``` 399 | 400 | Load the game, display the splash screen. 401 | 402 | ```python 403 | >>> game = ConwaysGameOfLife(lcd) 404 | >>> game.intro() 405 | ``` 406 | 407 | Start a game with 6x6 px lifeforms with zero delay between generations. 408 | 409 | Syntax: `game.begin(pixel size, delay between generations)` 410 | 411 | ```python 412 | >>> game.begin(6,0) 413 | ``` 414 | 415 | You can interrupt with Control+C. 416 | 417 | ![Gameplay](https://raw.github.com/mcauser/MicroPython-ESP8266-Nokia-5110-Conways-Game-of-Life/master/photos/life.jpg) 418 | 419 | You can even fake a high score. 420 | 421 | Syntax: `game.end(score, best score, pixel size)` 422 | 423 | ```python 424 | >>> game.end(31337,8008135,2) 425 | ``` 426 | 427 | ![Scores](https://raw.github.com/mcauser/MicroPython-ESP8266-Nokia-5110-Conways-Game-of-Life/master/photos/end.jpg) 428 | 429 | ## Links 430 | 431 | * [MicroPython PCD8544 Driver](https://github.com/mcauser/micropython-pcd8544) 432 | * [WeMos D1 Mini](http://www.wemos.cc/Products/d1_mini.html) 433 | * [micropython.org](http://micropython.org) 434 | * [Hardware SPI docs](http://docs.micropython.org/en/latest/esp8266/esp8266/quickref.html#hardware-spi-bus) 435 | * [hackaday project](https://hackaday.io/project/13351-nokia-5110-conways-game-of-life) 436 | 437 | ## License 438 | 439 | Licensed under the [MIT License](http://opensource.org/licenses/MIT). 440 | --------------------------------------------------------------------------------