MyRaspberryAndMe

Tinkering with Raspberry (and other things)

132 LED-matrix with AS1130 and Python

2 Comments

The AMS AG (austriamicrosystems) does have a neat little (literally) chip called AS1130 on the market. This chip is able to drive 132 LEDs, arranged in a 12×11 cross-plexed matrix. It can store up to 36 individual frames (pictures) and up to 6 patterns for blinking and PWM control of every single LED in every single frame. The frames can be displayed as still images or as a movie, the chip even scrolls the frames without the need for doing any calculations on the controlling computer side.
I could not find any Python code for that chip so I dived into the datasheet and wrote my own driver. As always, the sources are available via GitHub. Here is a short video demonstrating the capabilities of that chip. I had to use some paper to shield the ultra-bright LEDs or the camera would have recorded just a bright white spot…

The AS1130 is one mighty little thing and not too easy to control. As it comes in a SSOP28 package the first caveat is soldering. If you have never soldered any SMD parts do not start with this chip, practice first. My electronics dealer did not have any break-out boards in stock, so I decided to give it a try and test if it would be possible to fabricate, etch and solder a SSOP28 adapter board at home.
I decided on using the “toner transfer method”, where you print the layout with a laser printer on some paper, use a flat iron to transfer the printout on some copper board and then etch the PCB. My hopes weren’t really high as SSOP28 structures are really small. But I succeeded on the first try. It does not look very high-end but it works. Here is a picture of the homemade PCB with the AS1130 soldered on:

as1130pcb

The soldering does not look too good, but that is mostly from reflections. In fact even the soldering spots on the chip are all shiny and there are no shorts. Directly above the “AS1130” text I had to correct a trace that had been etched away (I think I touched that spot after cleaning the copper area).

Controlling the AS1130

The AS1130 is controlled via the I2C protocol, so with just 4 lines (GND, VCC, SDA and SCL) you are able to light 132 LEDs. It uses a set of registers and (what I call) sub-registers. So in communicating with the chip you first select the register and sub-register and then write the value to the chip’s memory.

The key features of the chip:

  • control 132 LEDs via I2C (12×11 matrix)
  • set the LED current from 0-30mA
  • can dim every single LED (PWM)
  • operates from 2.7V to 5.5V
  • programmable IRQ pin
  • hardware scrolling

Up to 36 animation/picture frames and up to 6 PWM/blink sets can be stored in chip’s memory:

as1130diagram

Controlling the chip is done with the following sequence of operations:

  1. Power up and wait 5ms to let the chip initialize its I2C address
  2. define the RAM configuration (this sets the number of available image and PWM/blink frames)
  3. Write image/animation frames to memory
  4. Write PWM/blink frames to memory
  5. Set the Control Register (LED current, display options etc.)
  6. Set chip to normal operation mode to light LEDs

All these steps require setting single bits in lots of different registers. To make things easier I encapsulated all the functions and settings needed in my Python module. I decided on creating classes according to the above diagram.

The AS1130-class has all functions needed for manipulating registers, handling frame data and doing the I2C communication. Here’s the list:

  • def __init__(self, deviceAddress = 0x30, ramconfig=1, orientation=LEFT_TOP)
  • def __setRamConfig(self)
  • def resetAllFrameData(self)
  • def __isFrameNumberValid(self, number)
  • def __isPwmBlinkNumberValid(self, number)
  • def __writeCommand(self, register, subregister, data)
  • def writeOnOffFrameToMemory(self, framenumber)
  • def writeBlinkPwmFrameToMemory(self, framenumber)
  • def getOnOffFrame(self, framenumber)
  • def getBlinkFrame(self, framenumber)
  • def getPwmFrame(self, framenumber)
  • def setPictureRegister(self, blink, display, frameNumber)
  • def setMovieRegister(self, blink, display, startFrame)
  • def setMovieModeRegister(self, blink, end, framesPlayed)
  • def setTimeScrollRegister(self, fade, direction, blocksize, scroll, delay)
  • def setDisplayOptionRegister(self, loops, blinkfreq, scanlimit)
  • def setCurrentSource(self, current)
  • def setConfigRegister(self, vddrst, vddstat, ledcorr, dotcorr, commonaddr)
  • def setInterruptMaskRegister(self, selectedpic,…)
  • def setInterruptFrameDefinition(self, lastframe)
  • def setShutdownOpen(self, testall, autotest, manualtest, init, shtdwn)

Then there are classes for the actual image data, called OnOffFrame (to use the same expression as the datasheet does) and BlinkFrame. Both these classes are derived from a generic SegmentRegister class. Here’s the list of their functions. Remember, OnOffFrame and BlinkFrame inherit all methods from SegmentRegister:

SegmentRegister class:

  • def __init__(self, orientation=AS1130.LEFT_TOP)
  • def __setLEDxy(self, x, y, onoff)
  • def __setLED(self, coords, onoff)
  • def getRegisterData(self)
  • def setAll(self)
  • def clearAll(self)
  • def writePixel(self, x, y, onoff)
  • def setPixel(self, x, y)
  • def clearPixel(self, x, y)
  • def writePattern(self, pattern)
  • def writePatternLine(self, line, pattern)
  • def drawLine(self, x0, y0, x1, y1, onoff=True)
  • def drawCircle(self, x0, y0, radius, onoff=True)

OnOffFrame class:

  • def __init__(self, orientation=AS1130.LEFT_TOP)
  • def __init__(self, orientation=AS1130.LEFT_TOP, pwmFrame=1)
  • def assignPwmFrame(self, pwmSet)
  • def clearPwmFrame(self)
  • def getPwmFrame(self)
  • def setAllPixels(self)
  • def clearAllPixels(self)

BlinkFrame class:

  • def __init__(self, orientation=AS1130.LEFT_TOP)
  • def setBlink(self, x,y)
  • def clearBlink(self, x,y)
  • def writeBlinkPattern(self, pattern)
  • def writeBlinkLine(self, line, pattern)
  • def setBlinkAll(self)
  • def clearBlinkAll(self)

Finally, there is the PwmFrame class, that is used to set the PWM value for an LED at a given position. This makes it possible to use patterns of different light intensities on different images (OnOffFrames).

PwmFrame class:

  • def __init__(self, orientation=AS1130.LEFT_TOP, intensity=255)
  • def setPwmValue(self, x, y, value)
  • def setAll(self, value)
  • def getRegisterData(self)
  • def writePwmPattern(self, pattern, pwmvalue)

How to use the module

As it is impossible to explain all functionality of the AS1130 chip in short words and a longer explanation would result in citing the datasheet, let’s look at some example code. Here is the source code of the as1130demo.py file. This code has been used to make the above video. I changed the license to The MIT License.

#!/usr/bin/python

#  The MIT License (MIT)
#
#  Copyright (c) 2014 Thomas Henkel (mypiandme@gmail.com)
#
#  Permission is hereby granted, free of charge, to any person obtaining a copy
#  of this software and associated documentation files (the "Software"), to deal
#  in the Software without restriction, including without limitation the rights
#  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#  copies of the Software, and to permit persons to whom the Software is
#  furnished to do so, subject to the following conditions:
#
#  The above copyright notice and this permission notice shall be included in all
#  copies or substantial portions of the Software.
#
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
#  SOFTWARE.

import as1130 as AS
import time
import random

"""
    Demonstration of various features of the AS1130 class for controlling a
    AS1130 driver chip from Austria Microsystems
    This chip is not easy to use, so please refer to the datasheet for details.
"""

# for ease of use variables are declared outside any scope
framedata = {}                  # dictionary holding image patterns
blinkdata = {}                  # same for blink patterns
pwmdata   = {}                  # same for PWM patterns
filename  = "framedata.txt"     # the file to read the patterns from
chip      = None                # this will be the instantiated AS1130 object

def importPatterns():
    # Utility function that imports matrix-patterns to display from a file
    global filename
    global framedata
    global blinkdata
    global pwmdata

    databuffer = ""
    state = "START"
    f = open(filename, "r")
    for line in f:
        if (line.startswith(";")):
            continue
        if (line.startswith("FRAME") or line.startswith("BLINK") or line.startswith("PWM")):
            command = line.split('=')[0]
            # check if databuffer is not empty
            if databuffer != "":
                if state == "FRAME":
                    framedata[framenum] = databuffer[:-1]
                    #framedata.append(databuffer[:-1])
                    databuffer = ""
                if state == "BLINK":
                    blinkdata[framenum] = databuffer[:-1]
                    #blinkdata.append(databuffer[:-1])
                    databuffer = ""
                if state == "PWM":
                    pwmdata[framenum] = databuffer[:-1]
                    #blinkdata.append(databuffer[:-1])
                    databuffer = ""

            framenum = int(line.split('=')[1])
            state = command
        else:
            # just append line to databuffer
            databuffer += line
    #end of file reached, write remaining databuffer
    if databuffer != "":
        if state == "FRAME":
            framedata[framenum] = databuffer[:-1]
            #framedata.append(databuffer[:-1])
            databuffer = ""
        if state == "BLINK":
            blinkdata[framenum] = databuffer[:-1]
            #blinkdata.append(databuffer[:-1])
            databuffer = ""
        if state == "PWM":
            pwmdata[framenum] = databuffer[:-1]
            #blinkdata.append(databuffer[:-1])
            databuffer = ""
    f.close()

def pictureDemo(chip):
    """Demonstrate some picture-related features of the AS1130 chip
        Two pictures are saved to the chip and displayed consecutively. After
        simply displaying the pictures the picture data for one picture is changed
        on-the-fly. Then the use of the PWM pattern is demonstrated.
    """
###########################################################################
## Prepare object in memory

    # get OnOffFrame data from chip object and write patterns to data
    # but not yet to chip's memory
    frame = chip.getOnOffFrame(0)
    frame.clearAllPixels()              # make sure it's empty
    frame.writePattern(framedata[6])    # FRAME=6 from file
    frame = chip.getOnOffFrame(1)
    frame.clearAllPixels()              # make sure it's empty
    frame.writePattern(framedata[7])    # FRAME=7 from file

    # Every picture frame has an associated pair of blink and pwm frames, called
    # a Blink&PWM Set in the datasheet
    # On instantiating the AS1130 object the BLINK frames are filled with zeroes,
    # the PWM frame is filled with 255s (maximum intensity)

    # Assign the Blink/PWM set at index 0 to both picture frames
    (chip.getOnOffFrame(0)).assignPwmFrame(0)
    (chip.getOnOffFrame(1)).assignPwmFrame(0)

    # to this point the frame information is saved in the chip object only.
    # It is now time to write that information to the "real" circuit. The idea
    # behind that implementation is to have something like double buffering at
    # hand. Let your software compute all image data and save it to the object's
    # memory. Then write the final image data to the circuit
    chip.writeOnOffFrameToMemory(0)
    chip.writeOnOffFrameToMemory(1)
    chip.writeBlinkPwmFrameToMemory(0)

##########################################################################
## Configure the actual circuit and display a picture frame
##
## Part 1: Display Picture 0 for some time

    # set the segment current first (see AS1130 comments in method definition)
    chip.setCurrentSource(200)      # this is about 23mA
    # set pictureRegister to display OnOffFrame number 0 as picture
    chip.setPictureRegister(0,1,0)  # blink=0, display=1, frame number=0
    # now enable the display
    chip.setShutdownOpen(0, 0, 0, 1, 1)
    # keep displaying for 3 seconds
    time.sleep(3)
    # switch off display using the ShutDownOpen register. By just setting shdn=0
    # all other parameters/settings in the circuit are kept
    chip.setShutdownOpen(0, 0, 0, 1, 0)
    time.sleep(2)
    # switch on display again with the old configuration settings
    chip.setShutdownOpen(0, 0, 0, 1, 1)
    # keep displaying for 3 seconds
    time.sleep(3)
    # switch off display
    chip.setShutdownOpen(0, 0, 0, 1, 0)

## Part 2: Change the picture to be displayed to number 1

    # now let's display the second picture:
    # set pictureRegister to display OnOffFrame number 1 as picture
    chip.setPictureRegister(0,1,1)  # blink=0, display=1, frame number=1
    # now enable the display
    chip.setShutdownOpen(0, 0, 0, 1, 1)
    # keep displaying for 5 seconds
    time.sleep(5)
    # switch off display using the ShutDownOpen register. By just setting shdn=0
    # all other parameters/settings in the circuit are kept
    chip.setShutdownOpen(0, 0, 0, 1, 0)

## Part 3: Change the PWM pattern on the fly

    # start displaying the last picture
    chip.setShutdownOpen(0, 0, 0, 1, 1)
    # remember that both pictures use PwmBlink frame number 0
    for pwm in range(255,10,-10):
        frame = chip.getPwmFrame(0)
        frame.setAll(pwm)
        # as we changed the frame it must be written to the circuit again
        chip.writeBlinkPwmFrameToMemory(0)
        # wait for 1 seconds
        time.sleep(0.02)
        # switch off display using the ShutDownOpen register. By just setting shdn=0
        # all other parameters/settings in the circuit are kept
    chip.setShutdownOpen(0, 0, 0, 1, 0)

## Part 3a: Set different PWM values according to a defined pattern

    # it is even possible to use a pattern for the PWM data
    # first change the image number 0 data to allOn
    frame = chip.getOnOffFrame(0)
    frame.setAllPixels()
    chip.writeOnOffFrameToMemory(0)
    # set pictureRegister to display OnOffFrame number 0 as picture
    chip.setPictureRegister(0,1,0)  # blink=0, display=1, frame number=0
    # display picture number 0
    chip.setShutdownOpen(0, 0, 0, 1, 1)
    # the PWM pattern has been set to 10 above, so reset all values to 100
    # as a start
    frame = chip.getPwmFrame(0)
    frame.setAll(50)
    # then, using the "cross" pattern from image number 1 as an PWM pattern
    # change the pwm value only where the pattern string is not "0"
    for pwm in range(255,10,-10):
        frame = chip.getPwmFrame(0)
        frame.writePwmPattern(framedata[7], pwm)
        chip.writeBlinkPwmFrameToMemory(0)
        time.sleep(0.02)
    # and reset the image data to its orginal state
    frame = chip.getOnOffFrame(0)
    frame.clearAllPixels()              # make sure it's empty
    frame.writePattern(framedata[6])    # FRAME=6 from file
    chip.writeOnOffFrameToMemory(0)

    # set pictureRegister to display OnOffFrame number 1 as picture
    chip.setPictureRegister(0,1,1)  # blink=0, display=1, frame number=1

"""Uncomment this part to see the speed of image updating via i2c

## Part 4: How fast can we get?

    # and for the fun, let's see how fast we can change the PWM values and
    # toggle the image at the same time
    #frame = chip.getPwmFrame(0)
    for c in range(0,1000):
        #frame.setAll(random.randrange(5,250))
        #chip.writeBlinkPwmFrameToMemory(0)
        chip.setPictureRegister(0,1,c%2)  # blink=0, display=1, frame number=1
    # swicth display off
    chip.setShutdownOpen(0, 0, 0, 0, 0)
    # and reset PWM/Blink to default values
    frame = chip.getPwmFrame(0)
    frame.setAll(150)
    chip.writeBlinkPwmFrameToMemory(0)
"""

def movieDemo(chip):
    """Demonstrate movie mode related capabilities of AS1130 chip.
        6 images/frames are saved on the circuit and then played as a movie
        The use of the blink pattern is demonstrated and scrolling is configured
        and shown.
    """

#######################################################################
## Prepare objects in memory

    # writing frame pattern 0-7 from file into image data 0-7
    # all frames are assigned PWM/Blink set 0
    for c in range(0,8):
        frame = chip.getOnOffFrame(c)
        frame.clearAllPixels()              # make sure it's empty
        frame.writePattern(framedata[c])    # FRAME=c from file
        frame.assignPwmFrame(0)
        # this time transfer directly to circuit
        chip.writeOnOffFrameToMemory(c)
    # set PWM frame
    frame = chip.getPwmFrame(0)
    frame.setAll(150)
    chip.writeBlinkPwmFrameToMemory(0)

#######################################################################
## Prepare Registers for Movie Display

    # Reset the circuit's internal state machine
    chip.setShutdownOpen(0, 0, 0, 0, 0)

## Part 1: Just simply display the images 0-7 as a movie

    # set the movie register to display a movie and set start of movie
    chip.setMovieRegister(0,1,0)    # blink=0, display_movie=1, startFrame = 0
    # set movie mode register.
    # attention num_frames starts with value 0 for 1 frame...
    chip.setMovieModeRegister(0,1,7) # blink disabled, end_last=1, num_frames=7
    # configure fading, scrolling and frame times
    chip.setTimeScrollRegister(0, 0, 0, 0, 6) # fade=0, scroll=right, block=full matrix
                                              # scrolling=0, frame delay=195ms
    # configure display options register
    chip.setDisplayOptionRegister(7, 0, 11)   # loops=endless, blink=1.5s,
                                              # segments displayed=all
    # now show the movie
    chip.setShutdownOpen(0, 0, 0, 1, 1)
    time.sleep(5)
    chip.setShutdownOpen(0, 0, 0, 1, 0)

## Part 2: Change the movie speed on the fly

    # To change the speed the frame_delay needs to be changed. Valid values
    # are 0-15. Milliseconds = value*32.5ms

    #switch display on again
    chip.setShutdownOpen(0, 0, 0, 1, 1)
    for delay in range(1,16):
        chip.setTimeScrollRegister(0, 0, 0, 0, delay)
        time.sleep(1)
    chip.setTimeScrollRegister(0, 0, 0, 0, 6)

## Part 3: Let there be scrolling

    # Up to now all changes in parameters could happen on the fly without
    # re-writing any other configuration register data.
    # With scrolling this is different!
    # Enable or disable scrolling does only work when the following registers are
    # rewritten:
    #    MovieRegister
    #    MovieModeRegister
    #    TimeScrollRegister
    #    DisplayOptionregister

    # Reset the circuit's internal state machine
    chip.setShutdownOpen(0, 0, 0, 0, 0)
    # set Movie registers again
    chip.setMovieRegister(0,1,0)
    chip.setMovieModeRegister(0,1,7)
    # configure scrolling
    chip.setTimeScrollRegister(0, 0, 0, 1, 2) # 4th parameter: scrolling=1
    # set display option register again
    chip.setDisplayOptionRegister(7, 0, 11)   # 3rd parameter: scroll endlessly
    # and finally enable the display
    chip.setShutdownOpen(0, 0, 0, 1, 1)
    # some time to watch
    time.sleep(9)
    # reset state machine again
    chip.setShutdownOpen(0, 0, 0, 0, 0)
    chip.setMovieRegister(0,1,0)
    chip.setMovieModeRegister(0,1,7)
    # this time scroll to the left
    chip.setTimeScrollRegister(0, 1, 0, 1, 2)
    chip.setDisplayOptionRegister(7, 0, 11)
    chip.setShutdownOpen(0, 0, 0, 1, 1)
    time.sleep(9)
    # finally switch display off
    chip.setShutdownOpen(0, 0, 0, 1, 0)

    # finally switch display off and reset circuit
    chip.setShutdownOpen(0, 0, 0, 0, 0)

def main():
    # import all patterns
    importPatterns()
    # instantiate AS1130 with default address and ram configuration 1
    global chip
    chip = AS.AS1130(0x30,1)
    # start picture demo
    pictureDemo(chip)
    time.sleep(2)
    movieDemo(chip)

if __name__ == '__main__':
    main()

I hope I added enough comments. The actual as1130.py module file has even more and longer comments, so please check that out. Together with the datasheet this should give you an idea on how to work with the chip.

If you have questions please post them in the comments. And be patient, I do have a day job and it may take some time for me to answer…

Oh yes, I forgot that: this is what a cross-plexed 132-LED matrix looks like when soldered on prototyping board:

ledmatrix

 

Final words on connecting the AS1130 chip

Just go with the description in the datasheet. Remember to use pull-up resistors on the I2C bus (if you have no other I2C device connected). One wewird thing I discovered while testing the chip:
As one would expect, all GND-pins are internally connected in the chip. Supply voltage (VDD) needs to be applied to Pin 18 and any of the other VDD-pins! For some strange reason, Pin 18 is not internally connected to the other VDD-inputs…

Advertisements

2 thoughts on “132 LED-matrix with AS1130 and Python

  1. How many AS1130 can be connected and controlled simultaneously to build RGB 10ft x 10ft video wall?