# 8x8 LED Matrix with MAX7219 - Control with software SPI

i am currently trying to get a 8x8 LED Matrix, which is controlled by a MAX7219 IC, working. The chip supports SPI with DIN, CS and CLK pins. I tried to control the chip via software SPI with a STM32F103. This is my code:

#include "stm32f10x_gpio.h"

#define CS 4
#define SCK 5
#define MOSI 7

void delay(long cycles){
while(cycles > 0)
cycles--;
}

void initSPI(){
RCC->APB2ENR |= (1<<2); //Enable Port A clock
GPIOA->CRL |= (0x3<<(4*CS)); //General PP & 50MHz speed for PA4 (CS)
GPIOA->CRL |= (0x3<<(4*SCK)); //General PP & 50MHz speed for PA5 (SCK)
GPIOA->CRL |= (0x3<<(4*MOSI)); //General PP & 50MHz speed for PA7 (MOSI)
}

void transmitData(uint16_t reg, uint16_t data){
uint16_t serialData = (reg << 8) | (data & 0x00FF); //Last Bit First
GPIOA->ODR &=~(1<<SCK); //Turn SCK low
GPIOA->ODR &=~(1<<MOSI); //Turn MOSI low
GPIOA->ODR &=~(1<<CS); //Turn CS low
delay(10);
for(int i = 0; i < 16; i++){
if((serialData >> (15-i)) & 0x0001){ //Bit is '1'
GPIOA->ODR |= (1<<MOSI); //Turn MOSI high
} else{
GPIOA->ODR &=~(1<<MOSI); //Turn MOSI low
}
delay(10);
GPIOA->ODR |= (1<<SCK); //Turn SCK high
delay(10);
GPIOA->ODR &=~(1<<MOSI); //Turn MOSI Low
if(i == 15){
GPIOA->ODR |= (1<<CS); //Turn CS high
}
GPIOA->ODR &=~(1<<SCK); //Turn SCK low
}

delay(10);
GPIOA->ODR |= (1<<SCK); //Turn SCK high
GPIOA->ODR &=~(1<<CS); //Turn CS low
delay(10);
GPIOA->ODR |= (1<<CS); //Turn CS high
delay(50);
}

void initMAX7219(){
transmitData(0x0B,0x07); //Scan Limit
delay(50000);
transmitData(0x09,0x00); //Decode Mode
delay(50000);
transmitData(0x0C,0x01); //Shutdown Mode
delay(50000);
transmitData(0x0F,0x00); //Display Test
delay(50000);
transmitData(0x01,0x01); //Display '1'
delay(50000);
}

int main(void)
{
initSPI();
initMAX7219();
while(1){
;
}
}


And this is how the pin states look like, captured with a logic analyzer:

Even though i think i did everything correctly (bit state of the DIN pin is transfered on rising edge of CLK, full 16 bits are shifted on CS rising edge, which should appear at or after the 16th rising edge of CLK) it doesn't work. The longest timing is 100ns which should be fulfilled. Any help is very much appreciated.

Edit: This is how the PCB looks:

• Try calling initMAX7219() just once. In addition, put a significant (multiple-second) delay in between the transmitData() functions. Currently you are slamming the SPI with your five messages over and over in a loop. – bitsmack Sep 27 '16 at 19:30
• Check the DOUT too. Just for sanity check – Eugene Sh. Sep 27 '16 at 19:30
• I assume it is this: ebay.com/itm/MAX7219-LED-Dot-Matrix-Display-Module-for-Arduino-/… – jonk Sep 27 '16 at 23:31
• Just out of curiosity: Did you happen to notice that there is a "MAX7219 only" note regarding $t_{LDCK}$? The timing diagram shows it, too. It seems to imply that you will need one more clock rising edge there, since it ONLY identifies itself as occurring from the rising edge of $\overline{CS}$ to the rising edge of CLK. – jonk Sep 27 '16 at 23:43
• @binaryBigInt Okay. So another thing really annoys me about the datasheet. They specify LOAD for the MAX7219 and $\overline{\textrm{CS}}$ for the MAX7221. This makes me wonder about the active state for the line being possibly the opposite??? Have you tried to reverse the sense of this line? Also, the datasheet discusses the state at initial power-up. Are you putting a 1 in the SHUTDOWN register and making sure there is a 0 in the DISPLAYTEST register, too? (Never mind. I see your code there.) I'm just going all out here looking for an explanation. – jonk Sep 28 '16 at 17:48

Try replacing your transmitData() function, as follows:

// Assume CS=1, to start. Leave with CS=1, as well.
void transmitData( uint16_t reg, uint16_t data ) {
transmitByte( reg );
transmitByte( data );
GPIOA->ODR &= ~( 1 << CS );
delay(10);
GPIOA->ODR |= (1 << CS );
}

// Assume SCK=1, to start. Leave with SCK=1, as well.
// (MOSI is left in the last state of the data bit.)
void transmitByte( uint16_t data ) {
for ( uint16_t m= 0x80; m != 0; ) {
delay(10);
GPIOA->ODR &= ~( 1 << SCK );
if ( (data & m) == 0 ) GPIOA->ODR &= ~( 1 << MOSI );
else GPIOA->ODR |= ( 1 << MOSI );
m >>= 1;
delay(10);
GPIOA->ODR |= ( 1 << SCK );
}
}


Now, I don't have a compiler to check for stupid typing errors or forgetting to declare a needed variable. But it should get across the idea, anyway.

I've created an intermediate function to transmit a byte, since the communication appears to break down this way. The transmitByte() function handles the transmission of one byte. Each send-packet to the MAX7219 requires two bytes: one for the register and one for the data used to modify that register value.

You can change that uint16_t to a uint8_t, if you have such a thing available, in transmitByte(). I couldn't tell if you had one (though I expect you do.)

The code I wrote assumes that CLK and LOAD (your SCK and CS) lines are all '1' to start. So you'd need to initialize those according to this new plan. Something like this:

void initMAX7219( ) {
GPIOA->ODR |= ( 1 << SCK );
GPIOA->ODR |= (1 << CS );
transmitData( 0x0B, 0x07 ); //Scan Limit
delay( 50000 );
transmitData( 0x09, 0x00 ); //Decode Mode
delay( 50000 );
transmitData( 0x0C, 0x01 ); //Shutdown Mode
delay( 50000 );
transmitData( 0x0F, 0x00 ); //Display Test
delay( 50000 );
transmitData( 0x0A, 0x0F ); //Intensity to max value
delay( 50000 );
transmitData( 0x01, 0x01 ); //Display '1'
delay( 50000 );
}


Note that I've added an intensity register change?

Use the above to consider your code, or try it out. One thing I think is important is that you may have missed adjusting the intensity register value. So perhaps just change your code on that point and see if it gets you there. But, if it doesn't, try out the above changes and see if that makes a difference.

• Thanks a lot for your continuous help, i really appreciate it. I added the line of code which sets the intensity, but it didn't help. So i tried out your code. It looks like this on my logic analyzer: picload.org/view/rddappga/logic_software_spi.png.html But sadly, my 8x8 does nothing :( I read about fake MAX7219 ICs, could this be a possible explanation why it is not working? Or do i just have to use an arduino because there is a lib for everything? instructables.com/id/16x8-LED-dot-matrix-with-MAX7219-module Do you have a LED Matrix with a MAX7219 to test this? – binaryBigInt Sep 28 '16 at 18:47
• @binaryBigInt Hmm. At this point, I think you may need to validate your MAX7219 module. It looks like it is designed for an Arduino, so go buy one and plug it in and use the library. If that works, that validates the display unit for you and you are back to trying to find the differences. I don't know what you have as a logic analyzer, but if you do get an Arduino then it would be nice to capture and then compare the control lines. I'm getting really curious about this. I'd like to know if your module is weird. (The current-setting resistor looks "nominal" to me -- $10\:\textrm{k}\Omega$.) – jonk Sep 28 '16 at 18:51
• @binaryBigInt Okay. Well, I hate to say it... but there may be something wrong with the power or module. I'm actually kind of interested in the modules, myself. I've ordered some (at under \$2, shipped) to play, later. May not be in time for your needs (from Hong Kong.) I used to do a lot of this stuff as I worked as a contractor for Siemens' Osram group (back when Siemens owned it) doing optical binning and calibration for RGB LED modules used in those large exterior displays. – jonk Sep 28 '16 at 19:52
• Why leave with SCK = 1? I don't know if it really affects anything but the datasheet says the idle polarity is LOW. – TisteAndii Sep 28 '16 at 23:04
• @TisteAndii I was just "shaking things up" a little. There's a problem here and I've no idea what it is. Since it seemed like it might work, either way, and since it was different than what he was already doing, I decided to flip that over as well. No good reason. As I said, "I'm just going all out here looking for an explanation." Do you have any thoughts to add? I'm at a bit of a loss, right now. – jonk Sep 28 '16 at 23:09

it was solved?

I have an example of this

//OPTIONS
#define SCANLIMIT 7
#define INTENSITY_HIGH 12
#define INTENSITY_MED 7
#define INTENSITY_LOW 3
#define DP 0x80
#define MINUS 0x0a
#define BLANK 0x0f
#define DECODEMODE 0x00

//OPCODES
#define OP_NOOP   0x00
#define OP_DIGIT0 1
#define OP_DIGIT1 2
#define OP_DIGIT2 3
#define OP_DIGIT3 4
#define OP_DIGIT4 5
#define OP_DIGIT5 6
#define OP_DIGIT6 7
#define OP_DIGIT7 8
#define OP_DECODEMODE  9
#define OP_INTENSITY   10
#define OP_SCANLIMIT   11
#define OP_SHUTDOWN    12
#define OP_DISPLAYTEST 15

MAX7219_SPISend(OP_DISPLAYTEST,0);
MAX7219_SPISend(OP_SHUTDOWN,1);
MAX7219_SPISend(OP_INTENSITY, INTENSITY_LOW);
MAX7219_SPISend(OP_SCANLIMIT, SCANLIMIT);
MAX7219_SPISend(OP_DECODEMODE, DECODEMODE);


It helps?

• Is what you posted an answer to the question? Please edit your answer so that it explains the code and why it works. – Bort Mar 16 '18 at 15:32