POV/Prototype 1

From Mouser

The first prototype of the RGB POV display was shipped off for fabrication on 2006.02.03. The very next day, I discovered that I had the MOSI SPI pin from the processor connected to the SO SPI pin on the driver daisy chain, and the MISO pin connected to SI. The fix is a simple matter of two trace cuts and two jumper wires. The fix is documented in prot1_layoutfix_001.png and has been corrected in version 1.1 of the layout (see pov0002.pcb).

Table of contents

Layout problems with pov0001.pcb

  • MISO and MOSI lines swapped; requires two trace cuts and two jumpers as described in prot1_layoutfix_001.png
  • polarity mark for power supply input is on back of board
  • power supply Phoenix connector has two through-hole alignment pins for which there were no holes in the board.
  • thru-hole cap option for 0.33uF caps on regulators was not used; inadequate pad space for a 1206 chip cap.
  • LM3940 part takes 5.0V nominal input, not 12V. Have to jumper the input here to the 7805's output at cut the 12V trace.
  • Missing 4.7k pullup on JTAG:TCK (between pins 1 and 4 on the JTAG header) (required?)
  • NSS cannot be used as an output for single-master systems on the 'F007 (it can on the 'F120), so this pin needs to be driven high, the /CS trace cut, and P0.6 needs to be jumpered to the MAX6966 /CS line.

All of these issues have been resolved in layout pov0002.pcb with the exception of the LM3940, which has no affect on the layout itself. Replace the LM3940 with an LM1086.

MAX6966 Initialization

When the MAX6966 starts up, all 10 channels are high-Z. Channel registers are set to 0xFF by default (LED off). The constant current drivers are all set to 10mA instead of their maximum of 20mA. The PWM staggering is off and the device is in shutdown mode. Before we begin turning LEDs on and off, we need to initialize the 6966 to get it into the desired mode. The following commands need to be sent to each driver in the chain:

0x13FF  - Set current for chanenls 0-7 to 20mA
0x1403  - Set current for channels 8-9 to 20mA
0x1021  - Enable PWM stagger and put the device into RUN mode

Once this is complete, all LEDs will still be off, but the 6966 is ready to accept commands. From this point onwards, the only registers that should need to be accessed are the 10 channel registers, 0x00 - 0x09.

Mapping image data in memory to driver command word ordering

The connection of LED pins to driver channel pins on the 6966 was done in such a way to simplify the layout, not to make an intuitive mapping of channel address space to image data space. The three-driver group of 10 pixels is pinned out with the following channel to pixel/color element mapping: (THIS IS ONLY TRUE AFTER prot1_layoutfix_001 HAS BEEN IMPLEMENTED)

D0:P0 - L3:G
D0:P1 - L2:B
D0:P2 - L2:R
D0:P3 - L2:G
D0:P4 - L1:B
D0:P5 - L1:R
D0:P6 - L1:G
D0:P7 - L0:B
D0:P8 - L0:R
D0:P9 - L0:G

D1:P0 - L6:R
D1:P1 - L6:G
D1:P2 - L5:B
D1:P3 - L5:R
D1:P4 - L5:G
D1:P5 - L4:B
D1:P6 - L4:R
D1:P7 - L4:G
D1:P8 - L3:B
D1:P9 - L3:R

D2:P0 - L9:B
D2:P1 - L9:R
D2:P2 - L9:G
D2:P3 - L8:B
D2:P4 - L8:R
D2:P5 - L8:G
D2:P6 - L7:B
D2:P7 - L7:R
D2:P8 - L7:G
D2:P9 - L6:B

where Dx:Py represents driver chip #x, channel #y and Lx:{RGB} = LED #x, color. For the purposes of this discussion, driver chips are numbered in the order they receive data from the SPI bus (in a normal world, this would mean that the closest chip to the CPU was D0, but due to the mixup of MISO and MOSI in the layout, this is reversed and D0 is the furthest from the CPU). The LEDs are numbered left to right with the LED in the upper-left of the board being L0.

In order to send image data to the drivers in a sensible way, we need a mapping between image data memory addresses and the driver and port that the data should end up at. For the purposes of keeping the firmware simple, assume that the image data will be stored in a sensible way in the CPU's memory. Barring a reason to do otherwise, we will use the standard 24bit color bitmap byte ordering which, for a one-dimensional image, would look like this:

Byte:      0    1    2    3    4    5    6    7    8   ...
Contents: L0:R L0:G L0:B L1:R L1:G L1:B L2:R L2:G L2:B ...

Each LED's data takes up 3 bytes, so a triad of drivers (10 pixels) requires 30 bytes. For a moment let's abstract away from Prototype 1 and think of this in a broader, extensible context. Assume we will have an integer number (N) of driver triads and therefore N*10 RGB pixels.

  • Let n = which driver triad we are interested in
  • Let l = the LED number (0-9) for triad n that we are interested in
  • Let c = 0 if red, 1 if green, and 2 if blue
  • Assume memory addresses are given relative to the start of the array (addr. 0 is the first element)

The memory location of the intensity data for color element n:l:c is: (30*n) + (3*l) + c

Now return to Prototype 1, where there is only a single triad, so we can drop the first term because n=0. We can now calculate the memory address for each driver/channel combination:

Triad 0
DRV/CHN COLOR ELEMENT             MEMORY ADDRESS
D0:P0   L3:G = (3*3) + 1	= 10
D0:P1   L2:B = (3*2) + 2	= 8
D0:P2   L2:R = (3*2)		= 6
D0:P3   L2:G = (3*2) + 1	= 7
D0:P4   L1:B = (3*1) + 2	= 5
D0:P5   L1:R = (3*1) + 0	= 3
D0:P6   L1:G = (3*1) + 1	= 4
D0:P7   L0:B = 2		= 2
D0:P8   L0:R = 0		= 0
D0:P9   L0:G = 1		= 1

D1:P0   L6:R = (3*6)		= 18
D1:P1   L6:G = (3*6) + 1	= 19
D1:P2   L5:B = (3*5) + 2	= 17
D1:P3   L5:R = (3*5)		= 15
D1:P4   L5:G = (3*5) + 1	= 16
D1:P5   L4:B = (3*4) + 2	= 14
D1:P6   L4:R = (3*4)		= 12
D1:P7   L4:G = (3*4) + 1	= 13
D1:P8   L3:B = (3*3) + 2	= 11
D1:P9   L3:R = (3*3)		= 9

D2:P0   L9:B = (3*9) + 2	= 29
D2:P1   L9:R = (3*9)		= 27
D2:P2   L9:G = (3*9) + 1	= 28
D2:P3   L8:B = (3*8) + 2	= 26
D2:P4   L8:R = (3*8)		= 24
D2:P5   L8:G = (3*8) + 1	= 25
D2:P6   L7:B = (3*7) + 2	= 23
D2:P7   L7:R = (3*7)		= 21
D2:P8   L7:G = (3*7) + 1	= 22
D2:P9   L6:B = (3*6) + 2	= 20

Each of the memory locations 0-29 are represented exactly once. This is a proper mapping of memory location to the driver and port to which the data belongs. This mapping extends easily to multiple triads; a second triad would simply impose a 30 byte offset to each of these numbers.

A full update cycle will consist of 10 command cycles, one for each port. The command cycle for a given port will update that port on ALL driver chips simultaneously. So we need to push out the data for each of the P0 channels on all drivers in a row. Since the above pattern will repeat for each driver triad, but with a constant offset of 30*n, we can just write the last column in the above table to an array called byteorder[] and then push data like this:

							// BEGIN FULL UPDATE CYCLE
for(channel=0; channel<10; ++channel)			// for each channel in the driver

  spi_cs(0);						// begin a new command cycle

  for(triad=N-1; triad>=0; --triad) {			// for each driver triad in the chain
    for(driver=2; driver>=0; --driver) {		// for each driver in the triad

      spipush((unsigned char)channel);			// the register address is channel # 
      spipush(data[(30*triad) + byteorder[driver*10 + channel]]);
    }
  }

  spi_cs(1);						// end the current command cycle
  
}							// END FULL UPDATE CYCLE

Note that the triad and driver counts are decremented rather than incremented. This is a requirement of the fact that we are daisy-chaining our drivers and the first data written to the SPI bus will be in the farthest driver when we commit the command buffer by bringing /CS high. It's really just an artifact of the definition of the numbering scheme for driver chips... if we wanted D0 to be the chip farthest from the CPU on the SPI bus, then we would have to make the triad and driver loops in the above pseudocode increment rather than decrement.


Converting color intensity to PWM duty cycle

The MAX6966 has a slightly obtuse system for determining the PWM duty cycle (and thus brightness) of an LED connected to one of its output channels. Because each channel can also be configured as a GPIO pin, a couple of the bit patterns for the channel configuration register are not available as PWM duty cycles. Furthermore, the bit patterns for 100% duty cycle (full ON) and 0% duty cycle (full OFF) are completely unintuitive. Here are the available bit patterns and their effect:

Register  Duty     Percent
Value     Cycle    Brightness
--------  -------  ----------
  0x02    256/256    100%  
  0x03      3/256      1% 
  0x04      4/256      2% 
  0x05      5/256      2% 
    . 
    .
    .
  0xFC    252/256     98% 
  0xFD    253/256     99% 
  0xFE    254/256     99%  
  0xFF      0/256      0% 
--------  -------  ---------   

Bit patterns 0x00 and 0x01 are reserved for GPIO channel configurations and won't be used for this project. Why they chose to put 100% and 0% where they did, I will never understand. But that's how it works and we have to account for that in the POV device. Probably the best way to do this is to pre-process the image data so that what is stored in device memory already accounts for the 6966 weirdness.

Take the original artwork and shift all of the intensities up by 0x02 so that the intensities span the range from 0x02 to 0xFF (regions of 0xFD and 0xFE intensity in the original artwork get promoted to 0xFF, but the change most likely wont be noticable in the POV output). Prior to loading data into the device (or perhaps during transfer), replace any occurance of 0xFF with 0x02 and vice versa. This will ensure that the intensity data conforms to the bizzare MAX6966 standard for channel register values.