278 lines
9.4 KiB
Python
278 lines
9.4 KiB
Python
# -*- 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
|
|
|
|
import 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)
|
|
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
|