MPM controlled 16ch RGB LED dimmer
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

575 lines
16 KiB

/***************************************************************************
* 16ch RGB 8bit PWM controller *
* *
* Copyright (C) 2006 - 20011 by Olaf Rempel *
* razzor AT kopf MINUS tisch DOT de *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; version 2 of the License, *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdio.h>
/*
* using ATmega32 @8MHz:
* Fuse H: 0xD9 (no bootloader, jtag disabled)
* Fuse L: 0xD4 (int. 8MHz Osz, fast rising power, no BOD)
*
* PA0..7 -> COL1..8
* PC0..7 -> COL9..16
* PB0 / PD7(OC2) -> ROW4
* PB1 / PD5(OC1A) -> ROW3
* PB2 / PD4(OC1B) -> ROW2
* PB3(OC0) / PD6 -> ROW1
* PD0 -> RXD
* PD1 -> TXD
* PD2 -> /RX_TX
* PD3 -> /LED
*/
#define TARGET_HW 1
#define ASM_IRQS 1
#if (TARGET_HW)
#define F_CPU 8000000
#else
#define F_CPU 7372800
#endif
#include <util/delay.h>
#define ROW1 PORTB3
#define ROW2 PORTB2
#define ROW3 PORTB1
#define ROW4 PORTB0
#define RXTX PORTD2
#define LED PORTD3
//#define BAUDRATE 115200
#if (!TARGET_HW) && (BAUDRATE)
#define UART_CALC_BAUDRATE(baudRate) (((uint32_t)F_CPU) / (((uint32_t)baudRate)*16) -1)
static int uart_putchar(char c, FILE *stream)
{
if (c == '\n') {
loop_until_bit_is_set(UCSRA, UDRE);
UDR = '\r';
}
loop_until_bit_is_set(UCSRA, UDRE);
UDR = c;
return 0;
}
static FILE uart = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);
#endif /* (!TARGET_HW) && (BAUDRATE) */
static uint8_t valueR[16];
static uint8_t valueG[16];
static uint8_t valueB[16];
/* keep color in r4 */
register uint8_t nextColor asm("r4");
/* keep pointer in r2:r3 */
register uint8_t * pCurrentStep asm("r2");
/* 16 +1 * 4 values (portA, portC, OCR0, flags) per color */
static uint8_t data[17 * 4 * 3];
/* offsets in data array */
#define RED_OFFSET (17 * 4 * 0)
#define GREEN_OFFSET (17 * 4 * 1)
#define BLUE_OFFSET (17 * 4 * 2)
#if ASM_IRQS
void __attribute__ ((naked)) SIG_OVERFLOW0 (void)
{
asm volatile(
/* save registers 2+1+2+2+2 = 9 */
"push r24 \n\t"
"in r24, __SREG__ \n\t"
"push r24 \n\t"
"push r30 \n\t"
"push r31 \n\t"
::
);
asm volatile(
"clr r24 \n\t"
"out %0, r24 \n\t" /* PORTA = 0x00; */
"out %1, r24 \n\t" /* PORTC = 0x00; */
:
: "I" (_SFR_IO_ADDR(PORTA)),
"I" (_SFR_IO_ADDR(PORTC))
);
asm volatile(
/* switch color, assign pCurrentStep = data + RED/GREEN/BLUE_OFFSET */
"mov r24, %0 \n\t"
"inc %0 \n\t" /* nextColor++ */
"cpi r24, 1 \n\t"
"brlo L_red%= \n\t" /* if (nextColor < 1) -> red */
"breq L_green%= \n\t" /* if (nextColor == 1) -> green */
"clr %0 \n\t" /* else set nextColor = 0, add BLUE_OFFSET */
#if (TARGET_HW)
"ldi r24, 8 \n\t"
"out %5, r24 \n\t"
#endif /* (TARGET_HW) */
"ldi r24, %4 \n\t"
"rjmp L_add%= \n\t"
"L_red%=: \n\t" /* red: add RED_OFFSET (do nothing) */
#if (TARGET_HW)
"ldi r24, 2 \n\t"
"out %5, r24 \n\t"
#endif /* (TARGET_HW) */
"rjmp L_skip%= \n\t"
"L_green%=: \n\t" /* green: add GREEN_OFFSET */
#if (TARGET_HW)
"ldi r24, 1 \n\t"
"out %5, r24 \n\t"
#endif /* (TARGET_HW) */
"ldi r24, %3 \n\t"
"L_add%=: \n\t"
"add r30, r24 \n\t"
"ldi r24, 0 \n\t"
"adc r31, r24 \n\t"
"L_skip%=: \n\t"
"movw %2, r30 \n\t"
:
: "r" (nextColor),
"z" (data),
"r" (pCurrentStep),
"M" (GREEN_OFFSET),
"M" (BLUE_OFFSET),
"I" (_SFR_IO_ADDR(PORTB))
: "r24"
);
asm volatile(
/* load table values */
"movw r30, %0 \n\t"
"ld r24, z+ \n\t"
"out %1, r24 \n\t" /* PORTA = *pCurrentStep++; */
"ld r24, z+ \n\t"
"out %2, r24 \n\t" /* PORTC = *pCurrentStep++; */
"ld r24, z+ \n\t"
"out %3, r24 \n\t" /* OCR0 = *pCurrentStep++; */
"ld r24, z+ \n\t"
"movw %4, r30 \n\t"
/* check if IRQ must be enabled */
"or r24, r24 \n\t" /* if (*pCurrentStep++) { */
"breq L_skip%= \n\t"
"ldi r24, %5 \n\t" /* TIFR = (1<<OCF0); */
"out %6, r24 \n\t"
"in r24, %7 \n\t" /* TIMSK |= (1<<OCIE0); */
"ori r24, %8 \n\t"
"out %7, r24 \n\t"
"L_skip%=: \n\t" /* } */
: "=r" (pCurrentStep)
: "I" (_SFR_IO_ADDR(PORTA)),
"I" (_SFR_IO_ADDR(PORTC)),
"I" (_SFR_IO_ADDR(OCR0)),
"0" (pCurrentStep),
"M" ((1<<OCF0)),
"I" (_SFR_IO_ADDR(TIFR)),
"I" (_SFR_IO_ADDR(TIMSK)),
"M" ((1<<OCIE0))
: "r24", "r30", "r31"
);
asm volatile(
/* restore registers 2+2+2+1+2+4 = 13 */
"pop r31 \n\t"
"pop r30 \n\t"
"pop r24 \n\t"
"out __SREG__, r24 \n\t"
"pop r24 \n\t"
"reti \n\t"
::
);
}
void __attribute__ ((naked)) SIG_OUTPUT_COMPARE0 (void)
{
asm volatile(
/* save registers 2+1+2+2+2 = 9 */
"push r24 \n\t"
"in r24, __SREG__ \n\t"
"push r24 \n\t"
"push r30 \n\t"
"push r31 \n\t"
::
);
asm volatile(
/* load table values 1+2+1+2+1+2+1+2+1 = 13 */
"movw r30, %0 \n\t"
"ld r24, z+ \n\t"
"out %1, r24 \n\t" /* PORTA = *pCurrentStep++; */
"ld r24, z+ \n\t"
"out %2, r24 \n\t" /* PORTC = *pCurrentStep++; */
"ld r24, z+ \n\t"
"out %3, r24 \n\t" /* OCR0 = *pCurrentStep++; */
"ld r24, z+ \n\t"
"movw %4, r30 \n\t"
/* check if IRQ must be disabled 1+1+1+1+1 = 5 */
"or r24, r24 \n\t" /* if (!(*pCurrentStep++)) */
"brne L_skip%= \n\t"
"in r24, %5 \n\t" /* TIMSK &= ~(1<<OCIE0); */
"andi r24, %6 \n\t"
"out %5, r24 \n\t"
"L_skip%=: \n\t"
: "=r" (pCurrentStep)
: "I" (_SFR_IO_ADDR(PORTA)),
"I" (_SFR_IO_ADDR(PORTC)),
"I" (_SFR_IO_ADDR(OCR0)),
"0" (pCurrentStep),
"I" (_SFR_IO_ADDR(TIMSK)),
"M" (0xFD) /* ~(1<<OCIE0) */
: "r24", "r30", "r31"
);
asm volatile(
/* restore registers 2+2+2+1+2+4 = 13 */
"pop r31 \n\t"
"pop r30 \n\t"
"pop r24 \n\t"
"out __SREG__, r24 \n\t"
"pop r24 \n\t"
"reti \n\t"
::
);
}
#else /* !ASM_IRQS */
ISR(SIG_OVERFLOW0)
{
switch (nextColor++) {
case 0:
pCurrentStep = data + RED_OFFSET;
#if (TARGET_HW)
PORTB = 0x02;
#endif /* (TARGET_HW) */
break;
case 1:
pCurrentStep = data + GREEN_OFFSET;
#if (TARGET_HW)
PORTB = 0x01;
#endif /* (TARGET_HW) */
break;
default:
pCurrentStep = data + BLUE_OFFSET;
nextColor = 0;
#if (TARGET_HW)
PORTB = 0x08;
#endif /* (TARGET_HW) */
break;
}
PORTA = *pCurrentStep++;
PORTC = *pCurrentStep++;
OCR0 = *pCurrentStep++;
if (*pCurrentStep++) {
TIFR = (1<<OCF0);
TIMSK |= (1<<OCIE0);
}
}
ISR(SIG_OUTPUT_COMPARE0)
{
PORTA = *pCurrentStep++;
PORTC = *pCurrentStep++;
OCR0 = *pCurrentStep++;
if (!(*pCurrentStep++)) {
TIMSK &= ~(1<<OCIE0);
}
}
#endif /* ASM_IRQS */
static void calculate_timer_values(uint8_t *value, uint8_t *pDataStart)
{
uint8_t *pData = pDataStart +4; /* skip first entry (init) */
uint8_t index = 0;
uint16_t chan_used = 0xFFFF;
uint16_t chan_init = 0xFFFF;
/* loop until all channels are calculated */
while (chan_used) {
uint8_t i;
uint8_t min_value = 0xFF;
uint16_t chan_tmp = chan_used;
uint16_t chan_mask = 0x0001;
for (i = 0; i < 16; i++) {
/* skip if channel already used */
if (chan_used & chan_mask)
{
/* channel is not used (value 0x00) */
if (value[i] == 0x00) {
chan_init &= (~chan_mask);
chan_used &= (~chan_mask);
/* found a new lower value */
} else if (value[i] < min_value) {
min_value = value[i];
chan_tmp = chan_used & (~chan_mask);
/* found another value with the same value */
} else if (value[i] == min_value) {
chan_tmp &= (~chan_mask);
}
}
chan_mask <<= 1;
}
chan_used &= chan_tmp;
if (min_value < 0xFF) {
/* set new outputs */
*pData++ = (chan_used & 0xFF); /* PORTA */
*pData++ = ((chan_used >> 8) & 0xFF); /* PORTC */
/* previous step needs timervalue and enable IRQ */
*(pData++ -4) = min_value -0; /* OCR0 */ /* FIXME: -1 ? */
*(pData++ -4) = 0x01; /* flags */
}
index++;
}
/* fill all remaining slots */
while (index < 16) {
/* repeat enabled outputs */
*pData++ = (chan_used & 0xFF); /* PORTA */
*pData++ = ((chan_used >> 8) & 0xFF); /* PORTC */
/* previous step was last one (no timevalue / disable IRQ) */
*(pData++ -4) = 0x00; /* OCR0 */
*(pData++ -4) = 0x00; /* flags */
index++;
}
/* first slot/init: enable only channels that are > 0 */
pData = pDataStart;
*pData++ = (chan_init & 0xFF); /* PORTA */
*pData++ = ((chan_init >> 8) & 0xFF); /* PORTC */
}
#if (!TARGET_HW) && (BAUDRATE)
void print_values(uint8_t *data)
{
uint8_t i;
for (i = 0; i < 17; i++) {
fprintf(&uart, "%2d: %02X %02X %02X %02X\n", i,
data[(i<<2)], data[(i<<2) +1],
data[(i<<2) +2], data[(i<<2) +3]);
if (data[(i<<2) +3] == 0x00)
break;
}
}
#endif /* (!TARGET_HW) && (BAUDRATE) */
int main(void)
{
/* 16 PWM Outputs */
PORTA = 0x00;
DDRA = 0xFF;
#if (TARGET_HW)
PORTB = 0x00;
DDRB = (1<<ROW1) | (1<<ROW2) | (1<<ROW3) | (1<<ROW4);
#endif /* (TARGET_HW) */
PORTC = 0x00;
DDRC = 0xFF;
#if (TARGET_HW)
PORTD = (1<<RXTX) | (1<<LED);
DDRD = (1<<RXTX) | (1<<LED);
#endif /* (TARGET_HW) */
/* timer0, FCPU/64, overflow interrupt */
#if (ASM_IRQS)
TCCR0 = (1<<CS01) | (1<<CS00); /* FCPU/64 */
#else /* (!ASM_IRQS) */
TCCR0 = (1<<CS02); /* FCPU/256 */
#endif /* (ASM_IRQS) */
TIMSK = (1<<TOIE0);
TCNT0 = 0x00;
#if (!TARGET_HW) && (BAUDRATE)
/* Set baud rate */
UBRRH = (UART_CALC_BAUDRATE(BAUDRATE)>>8) & 0xFF;
UBRRL = (UART_CALC_BAUDRATE(BAUDRATE) & 0xFF);
/* enable usart with 8n1 */
UCSRB = (1<<TXEN) | (1<<RXEN);
UCSRC = (1<<URSEL) | (1<<UCSZ1) | (1<<UCSZ0);
fprintf(&uart, "good morning dave\n");
#endif /* (!TARGET_HW) && (BAUDRATE) */
sei();
uint8_t x = 0;
uint8_t xdir = 1;
uint8_t ramp = 0;
uint8_t color[3] = { 0xFF, 0x00, 0x00 };
uint8_t step = 0;
while (1) {
uint8_t color_update = 0x07;
while (color_update) {
if ((color_update & 0x01) && (nextColor == 2)) {
calculate_timer_values(valueR, data + RED_OFFSET);
color_update &= ~(0x01);
#if (!TARGET_HW) && (BAUDRATE)
print_values(data + RED_OFFSET);
#endif /* (!TARGET_HW) && (BAUDRATE) */
} else if ((color_update & 0x02) && (nextColor == 0)) {
calculate_timer_values(valueG, data + GREEN_OFFSET);
color_update &= ~(0x02);
#if (!TARGET_HW) && (BAUDRATE)
print_values(data + GREEN_OFFSET);
#endif /* (!TARGET_HW) && (BAUDRATE) */
} else if ((color_update & 0x04) && (nextColor == 1)) {
calculate_timer_values(valueB, data + BLUE_OFFSET);
color_update &= ~(0x04);
#if (!TARGET_HW) && (BAUDRATE)
print_values(data + BLUE_OFFSET);
#endif /* (!TARGET_HW) && (BAUDRATE) */
}
}
#if (TARGET_HW)
PORTD ^= (1<<LED);
#endif /* (TARGET_HW) */
step++;
if (step == 16) {
step = 0;
if (xdir) {
x++;
if (x == 0x05)
x = 0x08;
else if (x == 0x0C)
xdir = 0;
} else {
x--;
if (x == 0x00)
xdir = 1;
else if (x == 0x07)
x = 0x04;
}
}
switch (ramp) {
case 0: /* red: on, green: ramp up, blue: off */
color[1]++;
if (color[1] == 0xFF)
ramp++;
break;
case 1: /* red: ramp down, green: on, blue:off */
color[0]--;
if (color[0] == 0x00)
ramp++;
break;
case 2: /* red: off, green: on, blue: ramp up */
color[2]++;
if (color[2] == 0xFF)
ramp++;
break;
case 3: /* red: off, green: ramp down: blue: on */
color[1]--;
if (color[1] == 0x00)
ramp++;
break;
case 4: /* red: ramp up, green: off, blue: on */
color[0]++;
if (color[0] == 0xFF)
ramp++;
break;
case 5: /* red: on, green: off, blue: ramp down */
color[2]--;
if (color[2] == 0x00)
ramp = 0;
break;
}
uint8_t i;
for (i = 0; i < 16; i++) {
#if 0
if (x == i) {
valueR[i] = color[0];
valueG[i] = color[1];
valueB[i] = color[2];
} else {
// valueR[i] = 0;
// valueG[i] = 0;
// valueB[i] = 0;
if (valueR[i] > 0) {
uint8_t tmp = (valueR[i] >> 5);
valueR[i] -= (tmp > 0) ? tmp : 1;
}
if (valueG[i] > 0) {
uint8_t tmp = (valueG[i] >> 5);
valueG[i] -= (tmp > 0) ? tmp : 1;
}
if (valueB[i] > 0) {
uint8_t tmp = (valueB[i] >> 5);
valueB[i] -= (tmp > 0) ? tmp : 1;
}
}
#else
valueR[i] = color[0];
valueG[i] = color[1];
valueB[i] = color[2];
#endif
}
}
return 0;
}