/* 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, either version 3 of the License, or * (at your option) any later version. * * 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, see . * */ /* This is the main part of the LED light controller program. * It handles all peripherals (power, fan, channels, IR, serial) */ /* This program is specifically designed for hardware version A, * with schematic revision 2, and layout revision 5. */ #include /* Standard Integer Types */ #include /* Standard IO facilities */ #include /* General utilities */ #include /* Boolean */ #include /* Strings */ #include /* AVR device-specific IO definitions */ #include /* Convenience functions for busy-wait delay loops */ #include /* Interrupts */ #include /* Watchdog timer handling */ #include /* Program Space Utilities */ #include "main.h" #include "uart.h" #include "ir_nec.h" #include "settings.h" /* help strings */ const char help_00[] PROGMEM = "commands:\n"; const char help_01[] PROGMEM = "\thelp display this help\n"; const char help_02[] PROGMEM = "\treset reset boad and settings\n"; const char help_03[] PROGMEM = "\tpower show power state\n"; const char help_04[] PROGMEM = "\tpower on switch power on\n"; const char help_05[] PROGMEM = "\tpower off switch power off\n"; const char help_06[] PROGMEM = "\tfan show fan speed\n"; const char help_07[] PROGMEM = "\ttemp show temperature\n"; const char help_08[] PROGMEM = "\tmode show current mode\n"; const char help_09[] PROGMEM = "\tmode N select mode\n"; const char help_10[] PROGMEM = "\tch X Y show channel [1-2].[1-5] brightness\n"; const char help_11[] PROGMEM = "\tch X Y Z set channel [1-2].[1-5] brightness [0-255]\n"; const char help_12[] PROGMEM = "\tir learn power learn the IR command to power on/off\n"; const char help_13[] PROGMEM = "\tir learn mode learn the IR command to change between modes\n"; const char help_14[] PROGMEM = "\tir learn brightness up learn the IR command to increase brightness\n"; const char help_15[] PROGMEM = "\tir learn brightness down learn the IR command to decrease brightness\n"; const char help_16[] PROGMEM = "\tir learn channel next learn the IR command to select next channel\n"; const char help_17[] PROGMEM = "\tir learn channel previous learn the IR command to select previous channel\n"; PGM_P const help_table[] PROGMEM = { help_00, help_01, help_02, help_03, help_04, help_05, help_06, help_07, help_08, help_09, help_10, help_11, help_12, help_13, help_14, help_15, help_16, help_17 }; //volatile uint8_t* PORTS[CHANNELS_1+CHANNELS_2] = {&PORTC,&PORTC,&PORTC,&PORTC,&PORTC,&PORTD,&PORTD,&PORTD,&PORTD,&PORTD}; volatile uint8_t* PORTS[CHANNELS_1+CHANNELS_2] = {&PORTC,&PORTC,&PORTC,&PORTD,&PORTD,&PORTD}; //volatile uint8_t* DDRS[CHANNELS_1+CHANNELS_2] = {&DDRC,&DDRC,&DDRC,&DDRC,&DDRC,&DDRD,&DDRD,&DDRD,&DDRD,&DDRD}; volatile uint8_t* DDRS[CHANNELS_1+CHANNELS_2] = {&DDRC,&DDRC,&DDRC,&DDRD,&DDRD,&DDRD}; //const uint8_t BITS[CHANNELS_1+CHANNELS_2] = {PC0,PC1,PC2,PC3,PC4,PD2,PD3,PD4,PD5,PD7}; const uint8_t BITS[CHANNELS_1+CHANNELS_2] = {PC0,PC1,PC2,PD2,PD3,PD4}; /* global variables */ #define INPUT_MAX 255 /* max length for user input string */ char input[INPUT_MAX+2]; /* user input from USART */ volatile uint8_t input_i = 0; /* user input index */ volatile uint8_t pwr_ok; /* is power ok */ volatile uint8_t fan; /* fan signal state, to measure tachometer */ volatile uint8_t timer2_ovf = 0; /* to measure fan speed through the tachometer using timer 2 */ const uint16_t TIMER2_PRESCALE[8] = {0,1,8,32,64,128,256,1024}; /* timer 2 CS2[2:0] values */ volatile uint16_t tachometer = 0; /* the tachometer time (from timer) */ volatile uint8_t ir; /* IR signal state, to measure IR code */ const uint16_t TIMER1_PRESCALE[8] = {0,1,8,64,256,1024,0,0}; /* timer 1 CS1[2:0] values */ volatile uint16_t ir_tick; /* number of counter ticks per millisecond */ volatile uint8_t pulse = 0; /* pulse index within the burst */ #define PULSE_MAX 128 /* maximum number of pulses to save */ uint16_t burst[PULSE_MAX]; /* pulse times forming a burst (from timer) */ /* channel variables */ #define LEVELS 10 /* the number of PWM levels */ volatile uint8_t ch_tick = 0; /* to tick for the channel PWM */ uint8_t on[CHANNELS_1+CHANNELS_2]; /* at which tick turn the channel on */ uint8_t off[CHANNELS_1+CHANNELS_2]; /* at which tick turn the channel off */ /* flags, set in the interrupts and handled in the main program */ volatile bool uart_flag = false; /* an incoming activity on the UART */ volatile bool power_flag = false; /* a change in the power or fan */ volatile bool ir_flag = false; /* to process a burst */ volatile bool channel_flag = false; /* indicate a change in the channel PWM values */ volatile bool learn_flag = false; /* learn an IR command for an action */ enum IR_ACTIONS to_learn = IR_ACTION_END; /* IR action to learn */ /* save the UART input into a string */ ISR(USART_RX_vect) { /* UART receive interrupt */ input[input_i] = getchar(); /* save input */ input[input_i+1] = 0; /* always end the string */ if (input_i0) { /* save pulse, except the first */ burst[pulse-1] = (TCNT1*1000UL)/ir_tick; /* pulse time in µs */ burst[pulse] = 0; /* end the burst with 0 */ } if (pulse0 && !ir_flag) { /* warm a burst is ready to be processed */ ir_flag = true; } } /* generate a PWM for the channel outputs * this is a bit long for an interrupt, but it's time critical */ ISR(TIMER0_COMPA_vect) { /* timer 0 OCR0A match interrupt vector */ ch_tick++; if (pwr_ok) { /* only generate PWM if power is on */ for (int i=0; i>CS10]; /* timer 1 presacler */ if (0!=prescale) { ir_tick = F_CPU/(1000*prescale); /* ticks per ms */ OCR1A = (uint32_t)(15*F_CPU)/(1000*prescale); /* set clear time to 15 ms (no IR toggle should be longer) */ } TIFR1 &= ~(1<1) { input[command_i-1] = '\0'; uart_action(input); save_settings(); } input_i = command_i = 0; /* reset input buffer */ } uart_flag = false; PORTD &= ~(1<3) { /* process command if new or repeated */ if (learn_flag) { /* learn command */ if (to_learnMODES) { goto error; } mode = mode_temp; channel_flag = true; } } else if (0==strcmp(word,"ir")) { word = strtok(NULL,delimiter); if (0==strcmp(word,"learn")) { word = strtok(NULL,delimiter); if (0==strcmp(word,"power")) { to_learn = POWER; learn_flag = true; } else if (0==strcmp(word,"mode")) { to_learn = MODE; learn_flag = true; } else if (0==strcmp(word,"brightness")) { word = strtok(NULL,delimiter); if (0==strcmp(word,"up")) { to_learn = BRIGHTNESS_UP; learn_flag = true; } else if (0==strcmp(word,"down")) { to_learn = BRIGHTNESS_DOWN; learn_flag = true; } else { goto error; } } else if (0==strcmp(word,"channel")) { word = strtok(NULL,delimiter); if (0==strcmp(word,"next")) { to_learn = CHANNEL_NEXT; learn_flag = true; } else if (0==strcmp(word,"previous")) { to_learn = CHANNEL_PREVIOUS; learn_flag = true; } else { goto error; } } else { goto error; } } else { goto error; } } else if (0==strcmp(word,"ch")) { word = strtok(NULL,delimiter); if (!word) { /* expecting channel group */ goto error; } if (strlen(word)!=1) { /* expecting one digit channel group X */ goto error; } uint8_t group = 0; switch (word[0]) { /* expecting channel group X 1 or 2 */ case '1': group = 0; break; case '2': group = 1; break; default: goto error; } /* get channel output */ word = strtok(NULL,delimiter); if (!word) { goto error; } /* expecting one digit channel output Y */ if (strlen(word)!=1) { goto error; } /* expecting channel group Y 1-CHANNELS_X */ if (word[0]<'1') { goto error; } uint8_t output = word[0]-'1'; if (group==0 && output>CHANNELS_1) { goto error; } else if (group==1 && output>CHANNELS_2) { goto error; } uint8_t channel = group*CHANNELS_1+output; /* brightness setting */ word = strtok(NULL,delimiter); if (!word) { printf("%u\n",brightness[mode][channel]); } else { if (strlen(word)>3) { goto error; } uint16_t br = atoi(word); if (br>0xff) { goto error; } brightness[mode][channel] = (uint8_t)br; channel_flag = true; } } else { goto error; } if (learn_flag) { puts("press button on remote to learn code"); } return; error: puts("command not recognized"); } void ir_action(uint8_t address, uint8_t command) { enum IR_ACTIONS ir_code = IR_ACTION_END; static uint8_t channel = CHANNELS_1+CHANNELS_2; uint8_t step = 0xff/LEVELS; /* the brightness increase/decrease steps */ for (ir_code=0; ir_codestep) { brightness[mode][channel] -= step; } else { brightness[mode][channel] = 0x00; } printf("decreasing brightness ch %u %u: %u\n",(channel/CHANNELS_1)+1,(channel%CHANNELS_1)+1,brightness[mode][channel]); } else if (channel==CHANNELS_1+CHANNELS_2) { /* decrease all brightness if no channel is selected */ for (uint8_t i=0; istep) { brightness[mode][i] -= step; } else { brightness[mode][i] = 0x00; } } printf("decreasing brightness all channels\n"); } channel_flag = true; break; case CHANNEL_NEXT: if (channel<=CHANNELS_1+CHANNELS_2) { channel = (channel+1)%(CHANNELS_1+CHANNELS_2+1); } else { channel = 0x00; } printf("selecting channel ch %u %u (%u)\n",(channel/CHANNELS_1)+1,(channel%CHANNELS_1)+1,brightness[mode][channel]); break; case CHANNEL_PREVIOUS: if (0==channel) { channel = CHANNELS_1+CHANNELS_2; } else { channel--; } printf("selecting channel ch %u %u (%u)\n",(channel/CHANNELS_1)+1,(channel%CHANNELS_1)+1,brightness[mode][channel]); break; default: printf("unhandled IR action: %u\n", ir_code); break; } } else { puts("unknown IR command"); } }