Problem accessing large PROGMEM structure

Hi,

I'd like to know if there is a practical limit to the size of a data structure (array) you can store in PROGMEM, or if there are any known problems once you get over a certain size.

I am working on a project using a couple of MAX6960 display driver ICs and some 8x8 LED displays.

I'm using the Arduino Diecimila and the hardware SPI interface to talk with the MAX6960 and this all works fine. I am able to write to the display and read back data without a problem.

The display drivers do not have built in character generation hardware so I have created my own font and I've stuck it in PROGMEM. I've made my custom extended ASCII set from character 1 to 255. Each character is 8x5 pixels in size, ie 5 bytes of data in a big array of 255 such blocks = 1275 bytes.

The problem I have is getting the data out of PROGMEM, more specifically, I am having a problem accessing data beyond character '' or character value 92 dec (0x5C).

Characters '!' to '' print ok (values 33 dec to 92 dec) but characters after '' are all offset by 1, ie 'a' prints as 'b' and 'b' prints out as 'c' etc.

For example I have some test code that writes the following string to the display "ab[]ab" and what comes out is "bc[^bc".

You can see that all the characters with value greater than '' are displayed incremented by 1.

I have checked my font data array and it appears correct to me, there are no repeated character blocks.

The entire character set won't fit here but here's how a section looks:

prog_uint8_t font_data[END_CHAR - START_CHAR + 1][CHAR_LEN] = {
      {0x00, 0x00, 0x00, 0x00, 0x00}, //   1 SOH
      {0x00, 0x00, 0x00, 0x00, 0x00}, //   2 STX
      {0x00, 0x00, 0x00, 0x00, 0x00}, //   3 ETX
      {0x00, 0x00, 0x00, 0x00, 0x00}, //   4 EOT
      {0x00, 0x00, 0x00, 0x00, 0x00}, //   5 ENQ
      {0x00, 0x00, 0x00, 0x00, 0x00}, //   6 ACK
      {0x00, 0x00, 0x00, 0x00, 0x00}, //   7 BEL
      {0x00, 0x00, 0x00, 0x00, 0x00}, //   8 BS
      {0x00, 0x00, 0x00, 0x00, 0x00}, //   9 HT
      {0x00, 0x00, 0x00, 0x00, 0x00}, //  10 LF
      {0x00, 0x00, 0x00, 0x00, 0x00}, //  11 VT
      {0x00, 0x00, 0x00, 0x00, 0x00}, //  12 FF
      {0x00, 0x00, 0x00, 0x00, 0x00}, //  13 CR
      {0x00, 0x00, 0x00, 0x00, 0x00}, //  14 SO
      {0x00, 0x00, 0x00, 0x00, 0x00}, //  15 SI
      {0x00, 0x00, 0x00, 0x00, 0x00}, //  16 DLE
      {0x00, 0x00, 0x00, 0x00, 0x00}, //  17 DC1
      {0x00, 0x00, 0x00, 0x00, 0x00}, //  18 DC2
      {0x00, 0x00, 0x00, 0x00, 0x00}, //  19 DC3
      {0x00, 0x00, 0x00, 0x00, 0x00}, //  20 DC4
      {0x00, 0x00, 0x00, 0x00, 0x00}, //  21 NAK
      {0x00, 0x00, 0x00, 0x00, 0x00}, //  22 SYN
      {0x00, 0x00, 0x00, 0x00, 0x00}, //  23 ETB
      {0x00, 0x00, 0x00, 0x00, 0x00}, //  24 CAN
      {0x00, 0x00, 0x00, 0x00, 0x00}, //  25 EM
      {0x00, 0x00, 0x00, 0x00, 0x00}, //  26 SUB
      {0x00, 0x00, 0x00, 0x00, 0x00}, //  27 ESC
      {0x00, 0x00, 0x00, 0x00, 0x00}, //  28 FS
      {0x00, 0x00, 0x00, 0x00, 0x00}, //  29 GS
      {0x00, 0x00, 0x00, 0x00, 0x00}, //  30 RS
      {0x00, 0x00, 0x00, 0x00, 0x00}, //  31 US
      {0x00, 0x00, 0x00, 0x00, 0x00}, //  32 SP
      {0x00, 0x00, 0x00, 0x5E, 0x00}, //  33 !
      {0x00, 0x06, 0x00, 0x06, 0x00}, //  34 "
      {0x14, 0x3E, 0x14, 0x3E, 0x14}, //  35 #
      {0x00, 0x24, 0x6A, 0x2B, 0x12}, //  36 $
      {0x4C, 0x2C, 0x10, 0x68, 0x64}, //  37 %
      {0x34, 0x4A, 0x52, 0x24, 0x10}, //  38 &
      {0x00, 0x00, 0x06, 0x00, 0x00}, //  39 '
      {0x00, 0x00, 0x3C, 0x42, 0x00}, //  40 (
      {0x00, 0x00, 0x42, 0x3C, 0x00}, //  41 )
      {0x00, 0x04, 0x0E, 0x04, 0x00}, //  42 *
      {0x08, 0x08, 0x3E, 0x08, 0x08}, //  43 +
      {0x00, 0x00, 0x40, 0x20, 0x00}, //  44 ,
      {0x00, 0x08, 0x08, 0x08, 0x08}, //  45 -
      {0x00, 0x00, 0x00, 0x20, 0x00}, //  46 .
      {0x00, 0x60, 0x18, 0x06, 0x00}, //  47 /
      {0x00, 0x3C, 0x42, 0x42, 0x3C}, //  48 0
      {0x00, 0x00, 0x04, 0x7E, 0x00}, //  49 1
      {0x00, 0x64, 0x52, 0x52, 0x4C}, //  50 2
      {0x00, 0x42, 0x4A, 0x4A, 0x34}, //  51 3
      {0x00, 0x18, 0x14, 0x7E, 0x10}, //  52 4
      {0x00, 0x4E, 0x4A, 0x4A, 0x32}, //  53 5
      {0x00, 0x3C, 0x4A, 0x4A, 0x32}, //  54 6
      {0x00, 0x02, 0x72, 0x0A, 0x06}, //  55 7
      {0x00, 0x34, 0x4A, 0x4A, 0x34}, //  56 8
      {0x00, 0x4C, 0x52, 0x52, 0x3C}, //  57 9
      {0x00, 0x00, 0x00, 0x24, 0x00}, //  58 :
      {0x00, 0x00, 0x40, 0x24, 0x00}, //  59 ;
      {0x00, 0x00, 0x08, 0x14, 0x22}, //  60 <
      {0x00, 0x14, 0x14, 0x14, 0x14}, //  61 =
      {0x00, 0x22, 0x14, 0x08, 0x00}, //  62 >
      {0x00, 0x04, 0x02, 0x52, 0x0C}, //  63 ?
      {0x00, 0x34, 0x54, 0x64, 0x38}, //  64 @
      {0x00, 0x7C, 0x0A, 0x0A, 0x7C}, //  65 A
      {0x00, 0x7E, 0x4A, 0x4A, 0x34}, //  66 B
      {0x00, 0x3C, 0x42, 0x42, 0x24}, //  67 C
      {0x00, 0x7E, 0x42, 0x42, 0x3C}, //  68 D
      {0x00, 0x3E, 0x4A, 0x4A, 0x42}, //  69 E
      {0x00, 0x7E, 0x0A, 0x0A, 0x02}, //  70 F
      {0x00, 0x3C, 0x42, 0x52, 0x32}, //  71 G
      {0x00, 0x7E, 0x08, 0x08, 0x7E}, //  72 H
      {0x00, 0x00, 0x00, 0x7E, 0x00}, //  73 I
      {0x00, 0x20, 0x40, 0x42, 0x3E}, //  74 J
      {0x00, 0x7E, 0x10, 0x28, 0x46}, //  75 K
      {0x00, 0x3E, 0x40, 0x40, 0x40}, //  76 L
      {0x7E, 0x04, 0x08, 0x04, 0x7E}, //  77 M
      {0x00, 0x7E, 0x04, 0x08, 0x7E}, //  78 N
      {0x00, 0x3C, 0x42, 0x42, 0x3C}, //  79 O
      {0x00, 0x7E, 0x0A, 0x0A, 0x04}, //  80 P
      {0x00, 0x3C, 0x42, 0x22, 0x5C}, //  81 Q
      {0x00, 0x7E, 0x12, 0x32, 0x4C}, //  82 R
      {0x00, 0x44, 0x4A, 0x4A, 0x32}, //  83 S
      {0x00, 0x02, 0x7E, 0x02, 0x00}, //  84 T
      {0x00, 0x3E, 0x40, 0x40, 0x3E}, //  85 U
      {0x00, 0x3E, 0x40, 0x20, 0x1E}, //  86 V
      {0x1E, 0x60, 0x18, 0x60, 0x1E}, //  87 W
      {0x00, 0x76, 0x08, 0x08, 0x76}, //  88 X
      {0x00, 0x46, 0x48, 0x48, 0x3E}, //  89 Y
      {0x00, 0x62, 0x52, 0x4A, 0x46}, //  90 Z
      {0x00, 0x7E, 0x42, 0x00, 0x00}, //  91 [
      {0x00, 0x06, 0x18, 0x60, 0x00}, //  92 \
      {0x00, 0x42, 0x7E, 0x00, 0x00}, //  93 ]
      {0x00, 0x04, 0x02, 0x04, 0x00}, //  94 ^
      {0x00, 0x80, 0x80, 0x80, 0x80}, //  95 _
      {0x00, 0x00, 0x01, 0x02, 0x00}, //  96 `
      {0x00, 0x30, 0x48, 0x28, 0x78}, //  97 a
      {0x00, 0x7E, 0x50, 0x48, 0x30}, //  98 b
      {0x00, 0x30, 0x48, 0x50, 0x00}, //  99 c
      {0x00, 0x30, 0x48, 0x50, 0x7E}, // 100 d
      {0x00, 0x30, 0x68, 0x58, 0x00}, // 101 e
      {0x00, 0x00, 0x7C, 0x0A, 0x00}, // 102 f
      {0x00, 0x18, 0xA4, 0xA8, 0x7C}, // 103 g
      {0x00, 0x7E, 0x10, 0x08, 0x70}, // 104 h
      {0x00, 0x00, 0x74, 0x00, 0x00}, // 105 i
      {0x00, 0x80, 0x88, 0x7A, 0x00}, // 106 j
      {0x00, 0x7E, 0x10, 0x28, 0x40}, // 107 k
      {0x00, 0x00, 0x3E, 0x40, 0x00}, // 108 l
      {0x78, 0x08, 0x70, 0x08, 0x70}, // 109 m
      {0x00, 0x78, 0x10, 0x08, 0x70}, // 110 n
      {0x00, 0x30, 0x48, 0x48, 0x30}, // 111 o
      {0x00, 0xFC, 0x28, 0x24, 0x18}, // 112 p
      {0x00, 0x18, 0x24, 0x28, 0xFC}, // 113 q
      {0x00, 0x78, 0x10, 0x08, 0x10}, // 114 r
      {0x00, 0x4C, 0x54, 0x64, 0x00}, // 115 s
      {0x00, 0x00, 0x3C, 0x48, 0x00}, // 116 t
      {0x00, 0x38, 0x40, 0x20, 0x78}, // 117 u
      {0x00, 0x38, 0x40, 0x20, 0x18}, // 118 v
      {0x38, 0x40, 0x30, 0x40, 0x38}, // 119 w
      {0x00, 0x68, 0x10, 0x68, 0x00}, // 120 x
      {0x00, 0x1C, 0xA0, 0x90, 0x7C}, // 121 y
      {0x00, 0x64, 0x54, 0x4C, 0x00}, // 122 z
      {0x00, 0x08, 0x36, 0x41, 0x00}, // 123 {
      {0x00, 0x00, 0x7F, 0x00, 0x00}, // 124 |
      {0x00, 0x41, 0x36, 0x08, 0x00}, // 125 }
      {0x00, 0x08, 0x04, 0x08, 0x04}, // 126 ~
      {0x00, 0x00, 0x00, 0x00, 0x00}, // 127 DEL
      {0x00, 0x10, 0x38, 0x54, 0x44}, // 128 ?
      {0x00, 0x00, 0xA0, 0x60, 0x00}, // 129 ,
      {0x00, 0x00, 0xA0, 0x60, 0x00}, // 130 ?
      {0x20, 0x48, 0x3E, 0x09, 0x02}, // 131 ?
      {0xA0, 0x60, 0x00, 0xA0, 0x60}, // 132 ?
      {0x40, 0x00, 0x40, 0x00, 0x40}, // 133 ?
      {0x00, 0x04, 0x3E, 0x04, 0x00}, // 134 ?
      {0x00, 0x14, 0x3E, 0x14, 0x00}, // 135 ?
      {0x00, 0x04, 0x02, 0x04, 0x00}, // 136 ?
      {0x62, 0xB5, 0x4A, 0xA4, 0x42}, // 137 ?
      {0x00, 0x49, 0x56, 0x55, 0x24}, // 138 ?
      {0x00, 0x10, 0x28, 0x44, 0x00}, // 139 ?
      {0x38, 0x44, 0x3C, 0x54, 0x44}, // 140 ?
      {0x00, 0x00, 0x00, 0x00, 0x00}, // 141 RI
      {0x00, 0x65, 0x56, 0x55, 0x4C}, // 142 ?
      {0x00, 0x00, 0x00, 0x00, 0x00}, // 143 SS3
      {0x00, 0x00, 0x00, 0x00, 0x00}, // 144 DCS
      {0x00, 0x00, 0x0A, 0x06, 0x00}, // 145 ?
      {0x00, 0x00, 0x06, 0x0A, 0x00}, // 146 ?
      {0x0C, 0x0A, 0x00, 0x0C, 0x0A}, // 147 ?
      {0x0A, 0x06, 0x00, 0x0A, 0x06}, // 148 ?
      {0x00, 0x00, 0x18, 0x18, 0x00}, // 149 ?
      {0x00, 0x00, 0x10, 0x10, 0x00}, // 150 ?
      {0x00, 0x10, 0x10, 0x10, 0x10}, // 151 ?
      {0x00, 0x04, 0x02, 0x04, 0x02}, // 152 ?
      {0x04, 0x1C, 0x1C, 0x08, 0x1C}, // 153 ?
      {0x00, 0x00, 0x59, 0x6A, 0x01}, // 154 ?
..etc..
      {0x00, 0xFE, 0x28, 0x24, 0x18}, // 254 þ
      {0x00, 0x1A, 0xA0, 0xA2, 0x78}, // 255 ÿ
};

I am using standard pgmspace.h library functions to access the data as shown below where START_CHAR = 1, END_CHAR = 255:

unsigned char * copy_char_data(unsigned char c, unsigned char * pos)
{
  unsigned char data = 0;
  if ((c < START_CHAR) || (c >= END_CHAR)) {
    return 0x00;
  }
  return (unsigned char *) memcpy_P(pos, &(font_data[(uint8_t)c - START_CHAR]), 5);
}

I have checked the code for generating the pointer to the start of each character block in the font_data structure and it seems to be generating the correct offset into the array, even for characters beyond ''.

In my loop () function I have the following code to convert a string to bitmap font data which is held in the planeData array before it is sent to the display driver IC:

i =0;
while (str[i] != 0) {
   copy_char_data((unsigned char) str[i], &planeData[k]);
   k = k + 5;
   i++;
}

This all works for characters less than or equal to '' but not for characters beyond that, they're all off set by one.

Anyone spot any obvious problem or experience anything similar and know what might be going on?

Is there an issue addressing large arrays in PROGMEM?

Thanks!

:slight_smile:

Easy fix! The problem is that in C, the trailing \ is a line continuation character. So the line

      {0x00, 0x42, 0x7E, 0x00, 0x00}, //  93 ]

is considered to be a continuation of the comment at the end of the previous line and therefore ignored. To demonstrate the veracity of this analysis, run the following simple program and see that it prints 1, 2, 3, and 5, but not 4.

int arr[] = {
1, // 1
2, // 2
3, //
4, // 4
5, // 5
};

void setup()
{
Serial.begin(9600);
for (int i=0; i<sizeof(arr) / sizeof(int); ++i)
Serial.println(arr*);*
}
void loop(){}[/quote]
Mikal

I can't believe that was the problem....

I removed the '' character from the comment and it now works properly.

Thanks! You don't know how much time I've wasted trying to fix this!

Easy fix! The problem is that in C, the trailing \ is a line continuation character.

This must have happened to you before; that's the sort of thing you can stare at for hours and not see...

(It wouldn't happen with /* C-style comments */ )

This must have happened to you before; --westw

The catalog of my past mistakes is indeed quite hefty. :slight_smile:

Mikal

hi letaage!
I´m working in a project using the same scheme: 1 arduino diecimila + 1 max6960eval.
Could you share the code for the arduino to drive the max6960?

Thanks!

OK, I did some research, wrote my own code, and it works!
It´s a simple example of using Arduino to talk to the MAX6960

#define DATAOUT 11//MOSI
#define DATAIN  12//MISO 
#define SPICLOCK  13//sck
#define SLAVESELECT 10//ss
#define RESET 9 //reset

byte clr;

void setup()
{
  pinMode(DATAOUT, OUTPUT);
  pinMode(DATAIN, INPUT);
  pinMode(SPICLOCK,OUTPUT);
  pinMode(SLAVESELECT,OUTPUT);
  pinMode(RESET,OUTPUT);

  // SPCR = 01010000
  //interrupt disabled,spi enabled,msb 1st,master,clk low when idle,
  //sample on leading edge of clk,system clock/4 rate (fastest)
  SPCR = (1<<SPE)|(1<<MSTR);
  clr=SPSR;
  clr=SPDR;
  delay(10); 

  digitalWrite(SLAVESELECT,HIGH);  
  digitalWrite(RESET,HIGH); 
  delay(5);  
  digitalWrite(RESET,LOW);
  delay(5);
  digitalWrite(RESET,HIGH);
  delay(200);


//Panel Configuration Register
  digitalWrite(SLAVESELECT,LOW);
  spi_transfer(0x0d);
  spi_transfer(0x01);  
  digitalWrite(SLAVESELECT,HIGH);
    
//Panel Intensity Register    
  digitalWrite(SLAVESELECT,LOW);
  spi_transfer(0x02);
  spi_transfer(0x30);  
  digitalWrite(SLAVESELECT,HIGH);
  
//Number of Cascaded Devices Register  
  digitalWrite(SLAVESELECT,LOW);
  spi_transfer(0x0e);
  spi_transfer(0x03);  
  digitalWrite(SLAVESELECT,HIGH);
 
//Number of Display Rows Register  
  digitalWrite(SLAVESELECT,LOW);
  spi_transfer(0x0f);
  spi_transfer(0x01);  
  digitalWrite(SLAVESELECT,HIGH);

  delay (500);

  digitalWrite(SLAVESELECT,LOW);
  spi_transfer(0x00);
  spi_transfer(0x00);
  spi_transfer(0x88);  
  digitalWrite(SLAVESELECT,HIGH);
while (1)
  {
  };
}

char spi_transfer(volatile char data)
{
  SPDR = data;                    // Start the transmission
  while (!(SPSR & (1<<SPIF)))     // Wait for the end of the transmission
  {
  };
  return SPDR;                    // return the received byte
}

void loop()
{
while (1);
  {
  };
}

I am confused how the memory addressing scheme works in the 6960.
If I have two Max6960s controlling 4 8x8 Led Matrix in one row.
Do I treat the display memory as though the first byte controls the top 8 pixels of the first LED matrix, and the 2nd byte controllers the top 8 pixels of the 2nd LED matrix? OR are the 1st and 2nd bytes written controlling the 1st and 2nd lines of the 1st LED matrix?

Also, I find the data sheet to be rather confusing. Does anyone know of a better resource for understanding this part?
Thanks in advance,
Dog

The way you write to the memory is entirely dependent on how your display is configured and the orientation of the LED modules.

I have a similar display set up working with with my arduino. I also found the data sheet to be a pain, I even found a bug in the MAX6960 which Maxim has confirmed. I have written a library for the MAX696x family which would be useful for understanding how the MAX6960 works. I should get around to putting it out for people to use some time. I have started to document the library and my display on my website but I've not had the time to write up all the details on how to use it yet.

You can find this information here : http://expat.dyndns.org/arduino/

Can you describe how your display is wired up to the MAX6960s?

I'll try and describe how my display is wired up and how I write to the pixels. It's fairly straight forward if you've wired the display up correctly...

Take a look at the image of my display here http://expat.dyndns.org/arduino/max6960-led-display/.

The arduino connects to both MAX6960s via the 4-wire SPI bus, and the two MAX6960s are interconnected via their own 3-wire display bus (ADDIN, ADDOUT, ADCLK). The MAX6960 with ADDIN tied high is the first device in the chain and in my display is used control the left 2 8x8 LED modules. The 2nd MAX6960 controls the right 2 LED modules.

I used these 8x8 LED modules: http://www.futurlec.com/LED/LEDMS88R.shtml

If you orientate them as shown in the data sheet, then the top left pixel corresponds to the 1st column, 1st row. So I connected the first MAX6960 row 1-8 and column 1-8 pins to the first 8x8 LED module, and row 1-8 and column 9-16 to the second 8x8 LED module, and repeated this for the 2nd MAX6960 and the remaining 2 LED modules.

So that's how the MAX6960s are connected to the LED modules in my display. This is all by way of background so you can understand how the geometry and connections to the LED modules is important for addressing the displays.

With the display wired up like this it becomes fairly simple to write to the display. First of all make sure you put it in MONOCOLOUR mode ie 1-bit per pixel to start with. I have got 2-bit per pixel working but the addressing in this mode is much more complicated. Set 1-bit per pixel by writing to the GLOBAL PANEL CONFIGURATION register (0x0D) and set it as per the instructions in Tables 21 and 22 in the data sheet. Also disable MUXFLIP as described in Table 20. There is a bug with this function that I found which screws up writing to the display so turn it off.

I am assuming you have got the arduino talking to the MAX6960 devices and are able to set all the other registers up correctly, but let me know if you need help with this.

Now the MAX6960 has 4 separate display memory banks if you've set the display up to 1-bit per pixel. This allows you to write to a memory bank in the background while displaying another so as not to see side effects of editing pixel data on the display, such as flickering pixels. When you have finished updating the display memory in the background then you can switch the memory banks over and display the new pixel data. The memory banks for all devices get mapped into a flat address space, meaning you don't have to address each MAX6960 individually, you can just write all the data to the first MAX6960 and they sort our between themselves what data goes with which device. This makes writing to the display a lot easier.

Each of the display memory banks lives at a fixed address offset from each other, which is related to the maximum number of MAX6960 devices you can daisy-chain together (256) and the display memory size in each display memory pane (16 bytes) = 4096 (0x1000) bytes. The addresses of the display memory panes are 0x0000, 0x1000, 0x2000 and 0x3000. You need to begin by selecting one of these planes by writing to the GLOBAL DISPLAY INDIRECT ADDRESS LSB and MSB registers (Table 29). This sets the MAX6960 to the beginning of a memory area relating to a particular display memory pane. For simplicity, set these two registers both to 0x00 to select plane 0.

Now you need to set the MAX6960 to display a particular display memory plane. Again, for simplicity set it to 0 to match the memory plane we've selected. This is done by setting the GLOBAL PANEL CONFIGURATION register. Table 18 in the data sheet shows that we need to set bits DP0 and DP1 both to 0. Also make sure the S bit is set to enable as per Table 16. Also for the sake of simplicity, set bit 6 (auto/manual) in the GLOBAL PLANE COUNTER REGISTER (table 30) to 0 to put the display in manual plane switching mode. This prevents the display from automatically switching between memory planes. So now the display is enabled and set to show the display memory plane 0.

To write to the display now becomes a fairly simple excercise. I will describe how I do it using the indirect memory write, which is the fastest way.

Firstly, the display pixels are arranged in groups of 8, with 1 bit per pixel. The grouping is arranged down each LED module, ie the first byte in the memory pane corresponds to the left most 8 LEDs down the display, not across. In the orientation of my display the LSB maps to the top left pixel and the MSB maps to the bottom left pixel. The 2nd byte in the display memory maps to the 2nd column and so on up to the 16th byte which maps to the right most column of the 2nd LED module. The 17th byte goes to the left most column of the 3rd LED module and the 32nd byte goes to the right most column of the 4th LED module. Remember that the MAX6960s sort out what data goes with which display automatically. If you write past the 32nd byte it doesn't matter it won't be displayed.

So in the arduino code, I set up an array of 32 bytes and fill this with what I want to display. Each bit corresponds to a pixel. If you have set the display up as described above, all you need to do is write each byte to the array using an 8-bit SPI data transfer. The MAX6960 knows that an 8-bit SPI transaction is an indirect display memory write and puts that data into the display memory plane we selected (0) at the address offset we selected (0x0000). Since the MAX6960 is set up to display this memory plane, the data should appear on the display!

As you write each byte of the 32-byte to the display, the MAX6960s automatically move their internal address counters along so each byte gets written to the next column in the display.

To write more data to the display you need to set the GLOBAL DISPLAY INDIRECT ADDRESS LSB and MSB registers back to 0x00 to move back to the start of the display memory then repeat the process.

Once you have this working you can experiment with writing to a different display memory pane in the background, then switching it over to be displayed, and even doing this automatically.

This is a long and wordy explanation, I hope it's not too confusing! The data sheet does cover all this but it's not very well laid out and some areas are not clear.

I'll get around to posting the driver library soon... it does all this and more, even 2-bit per pixel mode which is an entirely differnet kettle of fish.

Lately I've just been too busy porting arduino to the ATMega128 amongst other things...

THANKS SO MUCH! This is exactly what I was looking for. I wish they would put something like your explanation in the data sheet. I totally get it now. Please let me know if and when your driver/lib will be ready as it would prove to be most helpful I am sure.
Thanks again.
Dog.

Hello all,

I've tried the library with arduino and stripped down my whole setup to one max6960 chip driving two 8x8 monocolor displays. Instead of an 8MHz oscillator, I am using a 4.0MHz one. I've changed some lines in max696x_conf.h:

#define NUMBER_OF_DRIVER_DEVICES    (1)
#define NUMBER_OF_DRIVERS_PER_ROW   (1)
#define MAX696x_OSC_FREQ            (4000000)

I removed the max696x.o file in [your_arduino_path_]/hardware/libraries/max696x/ folder and compiled the arduino sketch. This recompiles max696x.cpp and you will have a new max696x.o file in your folder!

This is my code for the Arduino:

#include <max696x.h>
#include <max696x_conf.h>

max696x Max696x;

byte fault;

void setup()
{
  Serial.begin(9600);
  // (GLOBAL PANEL REGISTER 0x0D, PANEL INTENSITY 0x02, PIXEL_INTENSITY 0x01, GLOBAL DRIVER DEVICES 0x0e, GLOBAL DRIVER ROWS 0x0f, GLOBAL PLANE COUNTER 0x0b
  Max696x.init('0x01','0xff','0x00','0x00','0x01','0x00');
  delay(250);
  
  //check display
  fault = Max696x.display_test();
  Serial.print(fault,HEX);
  // print global register values
  Max696x.reg_dump();
}

 
 
 
void loop()
{
  while (1) {  
     delay(100);
     Max696x.indirect_mem_wr('0x80');
  }
}

The initialization of the chip happens here:

Max696x.init('0x01','0xff','0x00','0x00','0x01','0x00');

GLOBAL PANEL REGISTER 0x0D -> 0x01 (sets shutdown mode bit)
PANEL INTENSITY 0x02 -> 0xff (sets full brightness)
PIXEL_INTENSITY 0x01 -> 0x00 (monocolor)
GLOBAL DRIVER DEVICES 0x0e -> 0x00 (using only one device)
GLOBAL DRIVER ROWS 0x0f -> 0x00 (using only one row)
GLOBAL PLANE COUNTER 0x0b -> 0x00 (Manual selection to plane 0)

This line:

Max696x.indirect_mem_wr('0x80');

should actually light up the first/last row (depends on your displays) as far as i understood, but it doesn't really work (it always lights up the third and forth row..)

Also, it appears that the global registers are not correctly set. This command reads all global register

Max696x.reg_dump();

but my output looks like this:

GLOBAL DRIVER INDIREST ADDRESS      (0x08) : 0x0
GLOBAL DISPLAY INDIRECT ADDRESS LSB (0x09) : 0x0
GLOBAL DISPLAY INDIRECT ADDRESS MSB (0x0a) : 0x0
GLOBAL PLANE COUNTER                (0x0b) : 0x30
CLEAR PLANES                        (0x0c) : 0x0
GLOBAL PANEL CONFIG                 (0x0d) : 0x31
GLOBAL DRIVER DEVICES               (0x0e) : 0x2F
GLOBAL DRIVER ROWS                  (0x0f) : 0x30

LOCAL REIGSTERS
DRIVER ADDRESS                      (0x00) : 0x0
PIXEL INTENSITY SCALE               (0x01) : 0x0
PANEL INTENSITY                     (0x02) : 0x66
DIGIT 0 INTENSITY                   (0x03) : 0xFF
DIGIT 1 INTENSITY                   (0x04) : 0xFF
FAULT                               (0x05) : 0x81

I am not sure, but it seems that the global registers are not correctly set during the initialization process. Still, the SPI communication IMHO should be setup correctly, because following code lights up all my LEDs on both displays!:

Max696x.display_test();

Thus, "command_wr" and "command_rd" function work, because i can read the fault if some LEDs are not connected!

I also tried switching between software and hardware SPI, but to no avail!

Anyone had success with this issue?

Thanks to all thinkers and tinkers and especially to letaage who made this possible. I was already thinking of dumping my whole max6960 setup, but the end seems very close now :slight_smile:

I am not sure why it isn't working, but I am pretty sure that you might not be passing parameters correctly to the library functions if the code you posted is what you're using, ie:

Max696x.init('0x01','0xff','0x00','0x00','0x01','0x00');

and

Max696x.indirect_mem_wr('0x80');

I am pretty sure these should be:

Max696x.init(0x01, 0xff, 0x00, 0x00, 0x01, 0x00);

and

Max696x.indirect_mem_wr(0x80);

I think that the way you've written it with the 'quotes' is passing a string or character to the function not the hex value for the byte..?

I will post some example code of what I've done to get the library working in the next day or so, it's 2am here right now!

GREAT! It works :slight_smile:

Thank you so much! Looking forward to your example code. Meanwhile we'll play around with the library!