Interfacing a MAX7219 Driven LED Matrix with ATtiny85

LED Matrix 8x8 MAX7219 Assembling

The MAX7219 controller is manufactured by Maxim Integrated is compact, serial input/output common-cathode display driver that could interface microcontrollers to 64 individual LEDs, 7-segment numeric LED displays of up to 8 digits, bar-graph displays, etc. Included on-chip are a BCD code-B decoder, multiplex scan circuitry, segment and digit drivers and an 8×8 static RAM that stores each digit. 

The MAX7219 modules are very convenient to use with microcontrollers such as ATtiny85, or, in our case the Tinusaur Board.

The Hardware

The MAX7219 modules usually look like this:

MAX7219 Module LED Matrix 8x8
MAX7219 Module and LED Matrix 8×8

They have an input bus on one side and output bus on the other. This allows you to daisy chain 2 or more modules, i.e. one after another, to create more complicated setups.

The modules that we are using are capable of connecting in a chain using 5 small jumpers. See the picture below.

MAX7219 Module LED Matrix 8x8
2x MAX7219 Modules Connected

Pinout and Signals

MAX7219 module has 5 pins:

  • VCC – power (+)
  • GND – ground (-)
  • DIN – Data input
  • CS – Chip select
  • CLK – Clock

That means that we need 3 pins on the ATtiny85 microcontroller side to control the module. Those will be:

  • PB0 – connected to the CLK
  • PB1 – connected to the CS
  • PB2 – connected to the DIN

This is sufficient to connect to the MAX7219 module and program it.

The Protocol

Communicating with the MAX7219 is relatively easy – it uses a synchronous protocol which means that for every data bit we send there is a clock cycle that signifies the presence of that data bit.

MAX7219 Timing Diagram
MAX7219 Timing Diagram

In other words, we send 2 parallel sequences to bits – one for the clock and another for the data. This is what the software does.

The Software

The way this MAX7219 module works is this:

  • We write bytes to its internal register.
  • MAX7219 interprets the data.
  • MAX7219 controls the LEDs in the matrix.

That also means that we don’t have to circle through the array of LEDs all the time in order to light them up – the MAX7219 controller takes care of that. It could also manage the intensity of the LEDs.

So, to use the MAX7219 modules in a convenient way we need a library of functions to serve that purpose.

First, we need some basic functions in order to write to the MAX7219 registers.

  • Writing a byte to the MAX7219.
  • Writing a word (2 bytes) to the MAX7219.

The function that writes one byte to the controller looks like this:

void max7219_byte(uint8_t data) {
    for(uint8_t i = 8; i >= 1; i--) {
        PORTB &= ~(1 << MAX7219_CLK);   // Set CLK to LOW
        if (data & 0x80)                // Mask the MSB of the data
            PORTB |= (1 << MAX7219_DIN);    // Set DIN to HIGH
        else
            PORTB &= ~(1 << MAX7219_DIN);   // Set DIN to LOW
        PORTB |= (1 << MAX7219_CLK);        // Set CLK to HIGH
        data <<= 1;                     // Shift to the left
    }
}

Now that we can send bytes to the MAX7219 we can start sending commands. This is done by sending 2 byes – 1st for the address of the internal register and the 2nd for the data we’d like to send.

There is more than a dozen of register in the MAX7219 controller.

MAX7219 Registers and Commands
MAX7219 Registers and Commands

Sending a command, or a word, is basically sending 2 consecutive bytes. The function implementing that is very simple.

void max7219_word(uint8_t address, uint8_t data) {
    PORTB &= ~(1 << MAX7219_CS);    // Set CS to LOW
    max7219_byte(address);          // Sending the address
    max7219_byte(data);             // Sending the data
    PORTB |= (1 << MAX7219_CS);     // Set CS to HIGH
    PORTB &= ~(1 << MAX7219_CLK);   // Set CLK to LOW
}

It is important to note here the line where we bring the CS signal back to HIGH – this marks the end of the sequence – in this case, the end of the command. A similar technique is used when controlling more that one matrix connected in a chain.

Next step, before we start turning on and off the LEDs, is to initialize the MAX7219 controller. This is done by writing certain values to certain registers. For convenience, while coding it we could put the initialization sequence in an array.

uint8_t initseq[] = {
    0x09, 0x00, // Decode-Mode Register, 00 = No decode
    0x0a, 0x01, // Intensity Register, 0x00 .. 0x0f
    0x0b, 0x07, // Scan-Limit Register, 0x07 to show all lines
    0x0c, 0x01, // Shutdown Register, 0x01 = Normal Operation
    0x0f, 0x00, // Display-Test Register, 0x00 = Normal Operation
};

We just need to send the 5 commands above in a sequence as address/data pairs.

Next step – lighting up a row of LEDs.

This is very simple – we just write one command where 1st byte is the address (from 0 to 7) and the 2nd byte is the 8 bits representing the 8 LEDs in the row.

void max7219_row(uint8_t address, uint8_t data) {
    if (address >= 1 && address <= 8) max7219_word(address, data);
}

It is important to note that this will work for 1 matrix only. If we connect more matrices in a chain they will all show the same data. The reason for this is that after sending the command we bring the CS signal back to HIGH which causes all the MAX7219 controllers in the chain to latch and show whatever the last command was.

Testing

This is a simple testing program that lights up a LED on the first row (r=1) on the right-most position, then moves that on the left until it reaches the left-most position, then does the same on one row up (r=2) )until it reaches the top (r=8).

max7219_init();
for (;;) {
    for (uint8_t r = 1; r <= 8; r++) {
        uint8_t d = 1;
        for (uint8_t i = 9; i > 0; i--) {
            max7219_row(r, d);
            d = d << 1;
            _delay_ms(50);
        }
    }
}
MAX7219 Testing MAX7219LED8x8 Library
MAX7219 Testing

This testing code doesn’t do much but it demonstrates how to communicate with the MAX7219 controller.

The MAX7219LED8x8 Library

All of the functions mentioned above are part of the MAX7219LED8x8 library. Its source code is available at https://bitbucket.org/tinusaur/max7219led8x8.

The Tinusaur Shield GAMEx3

If you already have a Tinusaur Board we have the Shield GAMEx3 for it to connect a MAX7219 module easier to your ATtiny85 microcontroller.

Shield GAMEx3
Shield GAMEx3

The Gametinu Project

The Gametinu is a small game platform that you could build yourself using the Shield GAMEx3 and a few more parts and tools.

Gametinu
Gametinu

References

MAX7219 specification and datasheet:


This article is a rewritten version of another article from 2014:
MAX7219 driver for LED Matrix 8×8.


Another two-day workshop about microcontrollers, soldering and Tinusaur

Another two-day workshop about microcontrollers, soldering and Tinusaur

There was another “Microcontrollers, soldering and Tinusaurworkshop in our town of Veliko Tarnovo, few days ago..

The first day we assembled some boards, the second day we wrote some programs.

For the younger kids, there were much simpler things to do – soldering blinking LED with 2 transistors, few other components, and a battery.

Day 1

Just assembling various boards.

Tinusaur Board

This is the Tinusaur Board from the Tinusaur Starter 2 kit.

Tinusaur Board Parts

It wasn’t difficult for anyone to do that. There are markings on the PCB that tell you where to put each component and in what direction.

The only important thing to know is that you solder the RESET button last, before that you solder the batter socket on the bottom side of the PCB.

Tinusaur Shield LEDx2

This is the Shield LEDx2 from the Tinusaur Starter 2 kit.

Tinusaur Shield LEDx2 Parts

This shield is an upgrade from the previous Tinusaur Starter where we had to solder the LED and the resistor to a tiny 2-pin male header. With the shield is so much easier and fun.

LED Matrix 8×8 with MAX7219 Controller

This is a LED matrix 8×8 with a MAX7219 controller.

LED Matrix 8x8 MAX7219 Controller

That was something new. They sell on eBay at very affordable price: http://www.ebay.com/itm/191736585164

Even while we’re soldering it people were coming with ideas what we could do with it.

Day 2

The second day was dedicated to programming what we’ve assembled the previous day.

Software and Arduino IDE Setup

That’s how we started the day 2.

Short guide about how to setup the Arduino IDE to work with the Tinusaur boards is available at Arduino IDE Setup page.

Blinking LED

The “Hello, World!” in the microcontrollers’ world.

Source code available at https://bitbucket.org/tinusaur/tutorials/src/default/tut004a_blinking_leds/.

Separate blog post and tutorial page will be available soon.

LED Matrix 8×8

The biggest challenge here was to make the MAX7219LED8x8 library to work in the Arduino IDE environment.

We’ll do another post about that in the next few days.

 

The Tinusaur Plays Conway’s Game of Life

MAX7219 LED 8x8 ATtiny Tinusaur Conway’s Game of Life

I was playing with the MAX7219LED8x8 library and writing some code for how to use a simple scheduler to automate the task of outputting the buffer to the LED 8×8 matrix. So I was thinking … may be writing a simple game will illustrate the use of those libraries very well. Because just few days earlier I was looking at some Arduino projects implementing the Conway’s Game of Life I decided to write it for ATtiny85 and MAX7219/LED 8×8.

The Game of Life is a classical computer game and a cellular automaton created by the British mathematician John Horton Conway in 1970. This is a zero-player game which means that once it starts no input from user is required to play the game’s turns – it goes by itself.

Its simple rules (outlined below) allow to be implemented on very simple microprocessor systems and Tinusaur (and ATtiny systems in general) could be perfect platform for that.

Hardware

One Tinusaur Board connected to LED matrix 8×8 controlled by MAX7219.

Drivers

The MAX7219Led8x8 library is used to output the pixels to the LED 8×8 matrix.

MAX7219 LED 8x8 Conway’s Game of LifeThe Rules

The universe of the Game of Life is an infinite two-dimensional orthogonal grid of square cells, each of which is in one of two possible states, alive or dead. Every cell interacts with its eight neighbours, which are the cells that are horizontally, vertically, or diagonally adjacent. At each step in time, the following transitions occur:

  1. Any live cell with fewer than two live neighbours dies, as if caused by under-population.
  2. Any live cell with two or three live neighbours lives on to the next generation.
  3. Any live cell with more than three live neighbours dies, as if by overcrowding.
  4. Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.

The initial pattern constitutes the seed of the system. The first generation is created by applying the above rules simultaneously to every cell in the seed—births and deaths occur simultaneously, and the discrete moment at which this happens is sometimes called atick (in other words, each generation is a pure function of the preceding one). The rules continue to be applied repeatedly to create further generations.

(ref: Wikipedia/Conway’s_Game_of_Life)

The Program

The board is defined as a short byte array:

typedef uint8_t life_board[8];

An initial board is specified by the bits in that array:

life_board life_oscillators_blinkers = {
    0b00000000,
    0b00000000,
    0b01110000,
    0b00000000,
    0b00000100,
    0b00000100,
    0b00000100,
    0b00000000
};

Here are the most important functions that are implemented:

void life_board_init(life_board buffer);
void life_board_out(void);
uint8_t life_cell_count(uint8_t cx, uint8_t cy);
void life_board_turn(void);
void life_board2_copy(void);

The life_board_init function initializes the board with preset values and life_board_out outputs the content of the boards to the LED 8×8 matrix.

The life_cell_count function counts how many neighbors the specified cell has.

The life_board_turn performs one turn of the game based on the rules described above. It reads the data from the main buffer life_board_buffer array and stores the result in the life_board_buffer2 array. After that life_board2_copy copies the new data to the main buffer which then is outputted to the LED 8×8 matrix.

Here is the source code of the most important function – life_board_turn:

void life_board_turn(void) {
    for (uint8_t y = 0; y <= 7; y++) {
        for (uint8_t x = 0; x <= 7; x++) {
            uint8_t count = life_cell_count(x, y);
            if (LIFE_CELL_ISSET(x, y)) {
                if (count < 2)
                    LIFE_CELL_CLR(x, y);
                else if (count == 2 || count == 3)
                    LIFE_CELL_SET(x, y);
                else if (count > 3)
                    LIFE_CELL_CLR(x, y);
            } else {
                if (count == 3)
                    LIFE_CELL_SET(x, y);
                else
                    LIFE_CELL_CLR(x, y);
            }
        }
    }
}

This function implements the rules of the Game of Life.

That’s it.

In the program there is some more code that shows about 10 well know and interesting shapes such as blinker, toad, beacon, glider, etc.

The source code is part of the MAX7219LED8x8 library and it is available at https://bitbucket.org/tinusaur/max7219led8x8.

 

 

This article was permanently put at Conway’s Game of Life page.

UPDATE: MAX7219LED8x8 uses simple scheduler

max7219 LED 8x8 Tinusaur ATtiny85

The MAX7219LED8x8 library uses now simple scheduler to automate the task of outputting the buffer to the LED 8×8 matrix. This is not like a real task scheduler (in a real operating system) but it uses the ATtiny85 microcontroller‘s timer and its interrupt to do certain things on regular intervals.

So this is how I’ve got the idea …

While I was working on some code that uses the MAX7219LED8x8 library I figured out that the task of writing the content of the memory buffer to the MAX7219 could be automated by hooking some code to the ATtiny85 timer.

The modification and additions could be broken down into 2 parts:

First, the scheduling part that initializes the ATtiny85 timer, starts it and handles the hardware interrupt.

Second, the MAX7219LED8x8 library functions for setting/clearing pixels and outputting the buffer that now should work with the scheduler.

There are only 3 functions to handle the scheduling:

void scheduler_init(void);
void scheduler_start(uint8_t max);
void scheduler_userfunc(void);

The scheduler_init function initializes the timer and the scheduler_start starts the timer task to be executed on equal interval defined by the max parameter.

The scheduler_userfunc function should be implemented in the application so it could be called on regular intervals.

There is not “stop” function at the moment since it was not needed in this particular case.

The code for initializing and starting the ATtiny85 timer is very simple:

void scheduler_init(void) {
    // Setup Timer
    TCCR0A |= (1 << WGM01); // set timer in CTC mode
    TIMSK |= (1 << OCIE0A); // set Bit 4 – OCIE0A:
    // ... Timer/Counter0 Output Compare Match A Interrupt Enable
}

void scheduler_start(uint8_t max) {
    // IMPORTANT: Requires TIMER0_COMPA_vect to be setup.
    sei(); //  Enable global interrupts
    OCR0A = max;    // set value for OCR0A - Output Compare Register A
    // Prescale and start timer: 1/1024-th
    TCCR0B |= (1 << CS02) | (0 << CS01) | (1 << CS00);
}

// Define interrupt vector
ISR(TIMER0_COMPA_vect)
{
    scheduler_userfunc();
    // Note: No need to clear flags in TIFR - done automatically
}

On the MAX7219LED8x8 side there are only 3 functions currently implemented:

void max7219s_init(void);
void max7219s_start(void);
void max7219s_buffer_out(void);

The max7219s_buffer_out function is the one called within the scheduler_userfunc timer handler.

Everything else remains the same – we use MAX7219_buffer_set(x, y) to set pixel and MAX7219_buffer_clr(x, y) to clear pixel.

What could be improved and optimized: output the buffer only when it is changed.

More information is available at the MAX7219LED8x8 library page.

The source code of the MAX7219LED8x8 library, the scheduler and everything else is available at https://bitbucket.org/tinusaur/max7219led8x8.

 

MAX7219 driver for LED Matrix 8×8

MAX7219LED8x8 is a C library for working with the MAX7219 display driver to control 8×8 LED matrix. It is intended to be used with the Tinusaur board but should also work with any other board based on Atmel ATtiny85 or similar microcontroller.

MAX7219 with LED matrix 8x8

 

The MAX7219 is manufactured by Maxim Integrated is compact, serial input display driver that could interface microcontrollers to 64 individual LEDs such as 8×8 LED matrix. Only one external resistor is required to set the segment current for all LEDs.

MAX7219 Timing Diagram

To put that in simpler words – with the MAX7219 driver it is possible to control 8×8 LED matrix using just 2 wires serial interface – one for the sync clock and one for the data. There is another wire that could be used to enable/disable the communication with the chip. The maximum frequency for the serial interface is 10MHz.

The LED matrix 8×8 is connected almost diretcly to the MAX7219 driver – only few external components are required.

Working with MAX7219 is very simple – turning on and off individual LEDs is done by sending 2-bytes command to the driver containing the row and the byte which bits define which LED value to set.

There are also few other command that are needed during the initialization process.

The library supports short buffer – only 8 bytes in size – to keep the values before sending them to the driver.

MAX7219LED8x8 is written in plain C and does not require any additional libraries to function except those that come with the WinAVR SDK.

Using it is very simple …

    MAX7219_init();
    MAX7219_buffer_set(2, 3); // Set pixel
    MAX7219_buffer_clr(4, 5); // Clear pixel
    MAX7219_buffer_out(); // Output the buffer

Please continue to MAX7219LED8x8 page to see full source code the rest of the article.

External Resources

The source code of the MAX7219LED8x8 library is available at https://bitbucket.org/tinusaur/max7219led8x8

MAX7219 specification and datasheet: