# -*- 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