Skip to content

Instantly share code, notes, and snippets.

@lordmatt
Last active October 11, 2022 15:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lordmatt/8ebf9bff0bc2fdd5f04f86ae78156d33 to your computer and use it in GitHub Desktop.
Save lordmatt/8ebf9bff0bc2fdd5f04f86ae78156d33 to your computer and use it in GitHub Desktop.
This is the class that I wrote to control a MAX7219 8 Digit Seven Segment Display Module using undocumented or poorly documented chip features.
# This is the class that I wrote to control a MAX7219 8 Digit Seven Segment Display Module which
# you can find on Amazon here: https://amzn.to/3g0ls8P
#
# This class makes use of a feature that I do not think was documented. To do this, I devided
# the list of serial instructions into columns one for each display module in use which I use
# to replace no-op codes and addrress other chips where the no-op padding should be.
#
# The datasheet I worked from is here: https://cdn-shop.adafruit.com/datasheets/MAX7219.pdf
# The blog post telling how I made it is here: https://lordmatt.co.uk/max7219-story
#
# The class assumes the following pin connections
#
# Pin-15 #CS/LOAD
# Pin-14 #DIN
# Pin-18 #CLK
#
# I'm releasing this under the GNU GPL 3 even though I am certain it is far from the best code
# you will see. It works for me with two display modules.
from machine import Pin
from time import sleep
from picozero import pico_led
# If you are reading this code, please forgive me for how poor it is.
# This is my first python script.
# Everything runs right to left
# Digit-0 is rightmost
# Digit 7 is leftmost
# Col/Board 0 is the last in the chain
# Col/Board 1 (if you have two) is first
junk = [0,1,1,0]
def dp(hexbit):
hexbit[0]=1
return hexbit
register = dict()
register['no-op']=junk+[0,0,0,0]
register['no-op2']=register['no-op'] + register['no-op']
register['digit0']=junk+[0,0,0,1]
register['digit1']=junk+[0,0,1,0]
register['digit2']=junk+[0,0,1,1]
register['digit3']=junk+[0,1,0,0]
register['digit4']=junk+[0,1,0,1]
register['digit5']=junk+[0,1,1,0]
register['digit6']=junk+[0,1,1,1]
register['digit7']=junk+[1,0,0,0]
register['decode mode']=junk+[1,0,0,1]
register['intensity']=junk+[1,0,1,0]
register['scan limit']=junk+[1,0,1,1]
register['shutdown']=junk+[1,1,0,0]
register['test']=junk+[1,1,1,1]
command = dict()
command['shutdown'] = register['shutdown'] + junk + [0,0,0,0] # shutdown mode
command['normal'] = register['shutdown'] + junk + [0,0,0,1] # Normal Opperation
command['reset'] = [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0]
# Mode B for digits
command['none'] = register['decode mode'] + [0,0,0,0,0,0,0,0]
# others in here if I can be bothered later
command['all'] = register['decode mode'] + [1,1,1,1,1,1,1,1]
codeBFont = dict()
codeBFont['0'] = junk+[0,0,0,0]
codeBFont['1'] = junk+[0,0,0,1]
codeBFont['2'] = junk+[0,0,1,0]
codeBFont['3'] = junk+[0,0,1,1]
codeBFont['4'] = junk+[0,1,0,0]
codeBFont['5'] = junk+[0,1,0,1]
codeBFont['6'] = junk+[0,1,1,0]
codeBFont['7'] = junk+[0,1,1,1]
codeBFont['8'] = junk+[1,0,0,0]
codeBFont['9'] = junk+[1,0,0,1]
codeBFont['-'] = junk+[1,0,1,0]
codeBFont['E'] = junk+[1,0,1,1]
codeBFont['H'] = junk+[1,1,0,0]
codeBFont['L'] = junk+[1,1,0,1]
codeBFont['P'] = junk+[1,1,1,0]
codeBFont[' '] = junk+[1,1,1,1]
codeBFont['blank'] = codeBFont[' ']
intensity = dict()
intensity[0] = junk+[0,0,0,0]
intensity[1] = junk+[0,0,0,1]
intensity[2] = junk+[0,0,1,0]
intensity[3] = junk+[0,0,1,1]
intensity[4] = junk+[0,1,0,0]
intensity[5] = junk+[0,1,0,1]
intensity[6] = junk+[0,1,1,0]
intensity[7] = junk+[0,1,1,1]
intensity[8] = junk+[1,0,0,0]
intensity[9] = junk+[1,0,0,1]
intensity[10] = junk+[1,0,1,0]
intensity[11] = junk+[1,0,1,1]
intensity[12] = junk+[1,1,0,0]
intensity[13] = junk+[1,1,0,1]
intensity[14] = junk+[1,1,1,0]
intensity[15] = junk+[1,1,1,1]
scanlimit = dict()
scanlimit[0] = junk+[0,0,0,0] # 0 only
scanlimit[1] = junk+[0,0,0,1] # 0 - 1
scanlimit[2] = junk+[0,0,1,0] # 0 - 2
scanlimit[3] = junk+[0,0,1,1] # 0 - 3
scanlimit[4] = junk+[0,1,0,0] # 0 - 4
scanlimit[5] = junk+[0,1,0,1] # 0 - 5
scanlimit[6] = junk+[0,1,1,0] # 0 - 6
scanlimit[7] = junk+[0,1,1,1] # 0 - 7
testmode = dict()
testmode['normal'] = junk+[0,0,0,0]
testmode['display'] = junk+[0,0,0,1]
class Seg7Display:
colSize = 2 # number of command columns (chips/boards in use)
cols = dict() # command columns (for correct no-op padding)
baudr = 5*(10^-8) # you might get away with making this faster but YMMV
def __init__(self,colSize=2,prime=False):
self.colSizeDef(colSize)
if prime:
self.shutDown()
self.reset()
self.allUseFont()
#start of chainable methods
def colSizeDef(self,colSize):
self.colSize=colSize
for col in range(0, (colSize)):
self.cols[col]=dict()
return self
def changeDigit(self,digit,ShowAs,col=0):
self.useCommand(col,register['digit'+str(digit)] + codeBFont[str(ShowAs)])
return self
# this method assumes you send a valid command array
def useCommand(self,col,command):
#print(f"{self.cols}")
nexti = len(self.cols[col])
self.cols[col][nexti] = command
return self
def normalDisplay(self,col):
self.useCommand(col,command['normal'])
return self
def shutDown(self):
for col in range(0, self.colSize):
self.useCommand(col,command['shutdown'])
return self
def reset(self):
for col in range(0, self.colSize):
self.useCommand(col,command['reset'])
return self
def scanLimit(self,col,limit):
if limit<0:
limit = 0
if limit > 7:
limit = 7
self.useCommand(col,register['scan limit'] +scanlimit[limit])
return self
def intensity(self,col,level):
if level < 0:
level = 0
if level > 15:
level = 15
self.useCommand(col,register['intensity']+intensity[level])
return self
def allUseFont(self):
for col in range(0, self.colSize):
self.useCommand(col,command['all'])
return self
def insertNormalCommand(self):
for col in range(0, self.colSize):
self.normalDisplay(col)
return self
def setDigitInCol(self,col,digit,value):
self.useCommand(col,register[f'digit{digit}'] + codeBFont[f'{value}'])
return self
# end of chainable mthods
def getOrNoOp(self,col,row):
try:
self.cols[col][row]
#print(f"C-{col}/R-{row}:GOOD::[{self.cols[col][row]}]")
except KeyError:
#print(f"C-{col}/R-{row}::NO-OP")
return register['no-op2']
return self.cols[col][row]
def doIt(self,insertNormal=True,waitAtEnd=True):
if insertNormal:
self.insertNormalCommand()
size=0
for col in range(0, self.colSize):
sizeOf = len(self.cols[col])
if sizeOf>size :
size=sizeOf
instructions = []
for row in range(0,size-1):
for col in range(0, self.colSize):
instructions = instructions + self.getOrNoOp(col,row)
bitsPerCycle = self.colSize*16
self.SendBits(instructions,bitsPerCycle,waitAtEnd)
#self.colSize.clear()
self.colSizeDef(self.colSize) # reset the buffer
def SendBits(self,bits,highon=16,waitAtEnd=True):
baudr = self.baudr # side effect of refactoring
p_cs = Pin(15,Pin.OUT) #CS/LOAD
p_din = Pin(14,Pin.OUT) #DIN
p_clk = Pin(18,Pin.OUT) #CLK
p_clk.value(0)
sleep(baudr)
counter = 0
for bit in bits:
counter += 1
p_clk.value(0) #clock low
pico_led.off()
p_cs.value(0) # CS low
sleep(baudr) #Regulate clock
p_din.value(bit) # send bit
p_clk.value(1) #clock pulse
pico_led.on()
if(counter>(highon-1)): # CS high on 16th rising edge
p_cs.value(1) # load
counter = 0
sleep(baudr) #Regulate clock
p_cs.value(0) # reset
p_din.value(0) # reset
if waitAtEnd:
sleep(baudr*highon*2) # wait for two entire command cycles
pico_led.off()
# Note: this method does not use multichannel methods
def SetADigit(digit,value):
if(digit>7):
digit=digit-8
action = []
action = action + register[f'digit{digit}']
action = action + codeBFont[f'{value}']
Cnormal = command['normal']
# now we set the right number of no-ops
ii=0
byteboi = 16
mybyteboi = byteboi
while(ii<chaind):
action = action + register['no-op2']
Cnormal + register['no-op2']
ii=ii+1
mybyteboi = mybyteboi + byteboi
# now we set the digit
sendBits(action+Cnormal,mybyteboi)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment