Concepts and Code Snippets

I just realized that none of the documentation is actually on this site! It's all buried in the code. So, here's some random snippets from the code, mostly commentary on concepts.

These are extracted from _commonCode/lcdStuff/0.90ncf/lcdStuff.c:

3V3TTL -> LVDS "level-shifter"

// VCC3V3 VCC3V3

// | | ______________

// +---\ \¯¯¯-_ | LCD (1 channel

// | | ¯- | of 4)

// | | XOR >------> RXinN/clk- | ----.

// AVR, etc. | | _- | |

// 3.3V TTL >---+---/ /___-¯ | \ 100

// output | | /

// | | \ ohms

// | | /

// `---\ \¯¯¯-_ | |

// | | ¯- | |

// | | XOR >------> RXinN/clk+ | ----'

// | | _- |

// +---/ /___-¯ |

// | | |

// GND GND

Believe it or not, this works great with a 74LS86 from 1980. Newer chips with higher drive-strengths might require 100-ohm resistors in series with both outputs.

// LVDS/FPD-Link timing:

// |<--- (LCDdirectLVDS: "pixel") --->|

// Timer1: |<-- One Timer1 Cycle (OCR1C=6) -->|

// TCNT: | 0 1 2 3 4 5 6 | 0 1 2 3

// |____.____.____.____ |____.____.____.____

// RXclk+: / | \ . . / | \

// | | ¯¯¯¯ ¯¯¯¯ ¯¯¯¯| |

// One Pixel: | |<--- One FPD-Link Pixel Cycle --->|

// | |

// "Blue/DVH" |____ ____v____ ____ ____v____ ____|____ ____

// RXin2: X B3 X B2 X DE X /V X /H X B5 X B4 X B3 X B2 X ...

// |¯¯¯¯ ¯¯¯¯^¯¯¯¯ ¯¯¯¯ ¯¯¯¯^¯¯¯¯ ¯¯¯¯|¯¯¯¯ ¯¯¯¯

// | |<--Not Blue-->| |

// | |

// "Green" |____ ____v____ ____v____ ____ ____|____ ____

// RXin1: X G2 X G1 X B1 X B0 X G5 X G4 X G3 X G2 X G1 X ...

// |¯¯¯¯ ¯¯¯¯^¯¯¯¯ ¯¯¯¯^¯¯¯¯ ¯¯¯¯ ¯¯¯¯|¯¯¯¯ ¯¯¯¯

// | |<------->|-Not Green |

//

// "Red" |____ ____v____v____ ____ ____ ____|____ ____

// RXin0: X R1 X R0 X G0 X R5 X R4 X R3 X R2 X R1 X R0 X ...

// |¯¯¯¯ ¯¯¯¯^¯¯¯¯^¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯

// | |<-->|-Not Red

FPD-Link (aka, poorly, "LVDS") can be (poorly) simulated with PWM outputs, as long as those PWM channels have "Dead-Time Generators."

(Depending on the PWM peripheral I've been able to achieve 48 or 64 colors, roughly evenly-spaced in the color-palette)

The idea is that the PWM pulses, inherently, are a single pulse during each "pixel" transmission (each PWM-cycle). The width of that pulse can be varied by increasing or decreasing the duty-cycle (typical of PWM). But, of equal importance, the position of that pulse can be shifted left-or-right using the dead-time-generators.

Thus, as long as you're OK with serial-data wherein each "byte" consists only of high-bits which are grouped-together, then you can effectively send serial-data via the PWM outputs.

Another key-factor is that for displays (LCDs) it's *very* common to have the same data-stream repeat over and over in each serial transmission... E.G. the Hsync pulse might last for 100 pixel-clocks, which means the same data-transmission must occur 100 times... Whelp, that's exactly what happens when a PWM peripheral is set-up... it repeats.

Here are some examples for generating the Timing pulses...

// DE/V/H Timing (LCDdirectLVDS):

//

//

// | 0 1 2 3 4 5 6 All: set @ 0

// |____.____.____.____ OCR1C = 6

// Clock: / \ . . / Complementary-

// ¯¯¯¯ ¯¯¯¯ ¯¯¯¯ Output Mode

// required for DT

// Signal: B3 B2 DE /V /H B5 B4 | B3

// ____ ____ ____ ____ ____ ____ ____|____

// DE: X B3 X B2 / . . \ B5 X B4 X B3 X

// state2 ¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯|¯¯¯¯

// DE_BLUE: >¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯| DT=X, OCR=0xff

// Watch the transition!! -------^

// DE_NORM: >_________/¯¯¯¯¯¯¯¯¯¯¯¯¯¯\_________| DT=2, OCR=4

// DC_DISABLED:

// maxBlue: >¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯\____| DT=0, OCR=5

// See below for more blue settings...

//

// ____ ____ ____ ____ ____|____

// H (only): X xx X xx \ / \ / xx X xx X xx X

// state1 ¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯|¯¯¯¯

// >______________/¯¯¯¯\______________| DT=3, OCR=3

// DC_DISABLED:

// Not much can be done...

//

// ____ ____ ____ ____ ____|____

// V w/o H: X xx X xx \ . / \ xx X xx X xx X

// state3 ¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯|¯¯¯¯

// >___________________/¯¯¯¯\_________| DT=4, OCR=4 (+?)

// DC_DISABLED:

// >___________________/¯¯¯¯¯¯¯¯¯\____| DT=4, OCR=5

// ____ ____ ____ ____|____

// V w/ H: X xx X xx \ . . / xx X xx X xx X

// state4 ¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯|¯¯¯¯

// >__________________________________| DT=X, OCR=0

// TransitionWatch!!! -------^

// Shouldn't matter... DT from no-edge

// DC_DISABLED:

// >¯¯¯¯¯¯¯¯¯\________________________| DT=0, OCR=1

// ____ ____ ____ ____ ____ ____|____

// Nada: X xx X xx \ / . \ xx X xx X xx X

// state0 ¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯|¯¯¯¯

// >______________/¯¯¯¯¯¯¯¯¯\_________| DT=3, OCR=4 (+?)

// DC_DISABLED:

// >______________/¯¯¯¯¯¯¯¯¯¯¯¯¯¯\____| DT=3, OCR=5

By noting the transition-order (Hsync Active -> Hsync Inactive -> DataEnable...) it's possible to reduce most transitions to a single register-write (a single instruction-clock cycle) so there are few if any glitches which might confuse a display.

// The typical patterns look like this (not at all to scale):

// ----______------------------------------------------______-- V

// ....

// --_--_--_--_--_--_--_--_--_--_--_--_--_--_--_--_--_--_--_--_ H

// ....

// __________-__-__-__-__-__-__-__-__-__-__________________ DE

//

// ^^^^\ //blah

// 1234 5?

// Pixels are sent during DE High (basically all the CPU will be used here)

//Ideally,

// there won't be any glitches when changing from one state to another

//

// Init (pre 1):

// DeadTimerRising=1

//

// The states are:

// (Not necessarily accurate, just looking into necessary changes)

// (from Vsync L->H)

//

// 1 NothingActive (long, No DE, VporchFrontTimes)

//

// 2 Hsync

//

// 3 NothingActive (short)

//

// 4 DE

//

// 5 NothingActive(?)

//

// 7 Repeat 2-5 for each row

//

// 8 NothingActive (long, No DE, VporchBackTimes)

//

// 9 V w/o H

//

// 10 V w/ H

//

// 11 Repeat 9-10 for Vsync time...

(From lvds_attiny85.h... this is not the right source, since it only has one PWM channel for BLUE)

// OCR1B = 3

// TCNT: | 0 1 2 3 | 4 5 6

//

// CLKideal: /¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯\______________/

//

// |____ ____ ____.____v |

// CLK: D0>/ D1>/ D2>/ \ . . /

// OC1B |¯¯¯¯ ¯¯¯¯ |¯¯¯¯ ¯¯¯¯ ¯¯¯¯|

// | |

// | G2 G1 B1 B0 | G5 G4 G3

// Green: | |____ ____ ____

// /OC1B \ . . . d0>/ d1>/ d2>/ d3?\ //

// |¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯

// |

// | R1 R0 G0 R5 R4 R3 R2

// Red: |____ ____ ____ ____ ____ ____ ____

// OC1D D0>/ D1>/ D2>/ C2>\ C3>\ C4>\ C5>\ C6...

// ¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯

// ____ ____ ____ ____ ____ ____ ____

// /OC1D \ ^OCR1D>=6^ L>/ M>/ N>/ O>/ P?>...

// ¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯

//

// B3 B2 DE /V /H B5 B4

// "Blue/DVH" |____ ____ ____.____.____ ____ ____

// OC1A: D0>/ D1>/ D2>/ C4>\ C5>\ C6...

// |¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯

// ____ ____ ____ ____ ____ ____ ____

// /OC1A: \ ^^^--OCR1A>=6--^^^ X>/ Y>/ Z?>\ //

// (usable?) ¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯ ¯¯¯¯

// X: OCR1A=4, DTL=0

// Y: OCR1A=4, DTL=1; OCR1A=5, DTL=0

// Z: OCR1A=4, DTL=2; OCR1A=5, DTL=1; OCR1A>=6

Colors... Typical FPD-Link is 6-bits per color (64 shades)

This table shows the values available with the pseudo-FPD-Link implementation...

Note that each color (R/G/B) has *roughly* the same shades available... so you can get roughly three shades of gray (that actually look gray, as opposed to skewed in hue).

// For easier viewing:

// Red: (+OC1D => RX0+)

// Off (0/63): OCR1D = 0

// 35/63: OCR1D = 3

// 51/63: OCR1D = 4 (FOUR_SHADES only)

// 63/63: OCR1D >= 6

// Green: (/OC1B => RX1-) (B1,0 Active, as well as G2,1)

// Off (6/63): DTL1 = 0

// 38-39/63: DTL1 = 1

// 54-55/63: DTL1 = 2 (FOUR_SHADES only)

// 62-63/63: DTL1 = 3

// Blue: (+OC1A => RX2+) (B3,2 Active from here down)

// Off (15/63): OCR1A=4

// 47/63: OCR1A=5

// 63/63: OCR1A=6

The above information is from various sources, for various AVRs, different AVRs have different PWM/Dead-Timer implementations, so can achieve different results (and have different register-names)...

Interestingly, some AVRs have a PWM=0 value that actually results in a single pulse, whereas others result in no pulse at all. Likewise, some cannot have 100% PWM, they'll be low for one single pulse. This effects the pseudo-LVDS implementation dramatically! In one case only 3 shades of blue is possible, in another case 4 are...

You may have noticed the Tiny85 is also mentioned as working with LVDS displays... How's that possible when it only has one PWM channel? Whelp, dead-time-generators work differently on each of the complementary PWM outputs. So it's possible to use one output for "DVH/Blue" and the other for the pixel-clock. The remaining channels (Red and Green) are then handled with regular ol' GPIOs, dithering is achieved by alternating rows between full-off and full-on in these colors. Thus, we're still able to achieve roughly 64 colors for e.g. where the frame-buffer pixels are much larger than the screen-pixels.

When using the PLL for the timers, it's possible for some AVRs' timers to count at up to 128MHz when the AVR's instruction-clock is running at 16MHz (8x faster than the system-clock). That corresponds to 128Mb/s of pseudo-FPD-Link data. FPD-Link has 7bits per pixel-clock, which means that each AVR instruction corresponds to 8/7ths of a pixel (slightly more than one pixel). So, it's difficult (but not impossible) to align instructions' changing of registers with a specific pixel. This can be seen, sometimes, with rows being displayed shifted by one pixel here or there. This can be overcome to some extent by knowing the 8/7ths math and inserting delays as necessary. But, of course, it's not possible to change *each* pixel's color with this setup, which is fine when, again, the frame-buffer has much larger pixels than the screen.

This is just a random dump of some useful implenetation-notes... It's all buried (and poorly organized, it would seem) in the code which you're more than welcome to download.

Any questions, please ask!