-
Notifications
You must be signed in to change notification settings - Fork 0
/
avr_tlc5940.cpp
137 lines (120 loc) · 4.14 KB
/
avr_tlc5940.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#include <util/delay.h> // F_CPU should come from the makefile...
#include <avr/io.h>
#include <util/twi.h>
#include <avr/interrupt.h>
#include "avr_tlc5940.hpp"
// This is an AVR interface to a TLC5940
// Uses:
// TIMER0 to generate the GSCLOCK on PB7
// TIMER1 to generate the BLANK strobe (clocked by GSCLOCK on the T1 pin)
// USART0 to clock the 'greyscale' data out to the chip (in MSPI mode)
// PC3 for TLC5940 XLAT signal (to write the data into the PWM registers)
// PD4 for the greyscale data clock (SCLK)
// PD5 clock input to TIMER1, from GSCLOCK
// PC2 for the BLANK signal (to shut off output and to start the next PWM cycle)
// PD7 for the VPRG
//
//
namespace avr_tlc5940
{
uint8_t gsdata[24 * NUMBER_OF_TLC5940];
bool pending_gsdata;
bool need_xlat;
void setup(void)
{
//==============================================================
// tlc5940 interface
// use Timer0 to generate a 4 MHz GSCLK on OC0A
cli();
TCCR0A = _BV(COM0A0) | _BV(WGM01); // Clear Timer on Compare match mode, toggle OC0A on match
TCCR0B = _BV(CS00); // prescaler div = 1
TIMSK0 = 0; // make sure it generates no interrupts
OCR0A = 0; // f=8e6/(2*(0+1)) = 4 MHz
DDRD |= _BV(PD6); // enable the OC0A output
// use Timer1 to count up 4096 clocks to restart the gsdata cycle
// ext clock source on T1, rising edge, CTC mode; reset on OCR1A match
TCCR1A = 0;
TCCR1B = _BV(WGM12) | _BV(CS12) | _BV(CS11) | _BV(CS10);
OCR1A = 4096;
TIMSK1 |= _BV(OCIE1A); // Interrupt on output compare on A
// BLANK XLAT
DDRC |= _BV(PC2) | _BV(PC3);
PORTC |= _BV(PC2); // start with blank high to keep lights off
PORTC &= ~_BV(PC3);
// VPRG
DDRD |= _BV(PD7);
PORTD &= ~_BV(PD7);
// Set up USART0/MSPI
DDRD |= _BV(PD4); // use XCK for the SCLK
UBRR0 = 0; // baud=8e6/(2*(0+1)) = 4 MHz baud rate
UCSR0C = _BV(UMSEL01) | _BV(UMSEL00); // operate USART 0 in MSPIM mode, MSB first
UCSR0B = _BV(RXEN0) | _BV(TXEN0); // give the pins the correct dir..
UBRR0 = 0; // set the baud /again/. cause they say to
for (unsigned i=0; i<sizeof(gsdata); i++)
gsdata[i]=0;
need_xlat=false;
pending_gsdata=true;
output_gsdata();
PORTD |= _BV(PD4);
PORTD &= ~_BV(PD4); // additional SCLK pulse
sei();
}
void set_channel(int chan, int value)
{
size_t lb = sizeof(gsdata) - 1 - ((3*chan)>>1);
uint16_t pwm = (chan & 1) ? value<<4 : value;
uint16_t mask = (chan & 1) ? ~0xfff0: ~0x0fff;
uint8_t pwm_l = pwm;
uint8_t pwm_h = pwm>>8;
uint8_t mask_l = mask & 0xff;
uint8_t mask_h = mask>>8;
gsdata[lb] &= mask_l;
gsdata[lb-1] &= mask_h;
gsdata[lb] |= pwm_l;
gsdata[lb-1] |= pwm_h;
pending_gsdata=true;
}
unsigned get_channel(int chan)
{
size_t lb = sizeof(gsdata) - 1 - ((3*chan)>>1);
uint16_t pwm;
pwm = gsdata[lb];
pwm |= gsdata[lb-1] << 8;
if (chan & 1)
pwm >>= 4;
return pwm & 0x0fff;
}
void output_gsdata(void)
{
if (!pending_gsdata)
return;
need_xlat=false;
// Now to send out the grayscale data
// 24 bytes at 4 MHz takes 24 us + setup time
for (unsigned i=0; i<sizeof(gsdata); i++)
{
while ( !(UCSR0A & _BV(UDRE0)) ) ; // wait for tx buffer to be clear
UDR0 = gsdata[i];
while ( !(UCSR0A & _BV(RXC0)) ) ;
}
pending_gsdata=false;
need_xlat=true; // indicate that there is new data to be latched
}
// Pulse BLANK every 4096 GSCLK cycles
// should go off 4096 / 4 MHz = 1.024 ms
// While BLANK is active, pulse XLAT if needed
ISR (TIMER1_COMPA_vect)
{
PORTC |= _BV(PC2); // set BLANK
TCCR0B = 0; // turns off the GSCLK
if (need_xlat)
{
PORTC |= _BV(PC3); // pulse XLAT
PORTC &= ~_BV(PC3);
need_xlat = false;
}
TCNT1 = 0; // reset the GSCLK counter in case any extra pulses game
TCCR0B = _BV(CS00); // turns GSCLK back on
PORTC &= ~_BV(PC2); // clear BLANK
}
}