led-controller/firmware/main.c

492 lines
15 KiB
C

/* 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 <http://www.gnu.org/licenses/>.
*
*/
/* 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 <stdint.h> /* Standard Integer Types */
#include <stdio.h> /* Standard IO facilities */
#include <stdlib.h> /* General utilities */
#include <stdbool.h> /* Boolean */
#include <string.h> /* Strings */
#include <avr/io.h> /* AVR device-specific IO definitions */
#include <util/delay.h> /* Convenience functions for busy-wait delay loops */
#include <avr/interrupt.h> /* Interrupts */
#include "main.h"
#include "uart.h"
#include "ir_nec.h"
#include "settings.h"
volatile uint8_t* PORTS[CHANNELS_1+CHANNELS_2] = {&PORTC,&PORTC,&PORTC,&PORTC,&PORTC,&PORTD,&PORTD,&PORTD,&PORTD,&PORTD};
volatile uint8_t* DDRS[CHANNELS_1+CHANNELS_2] = {&DDRC,&DDRC,&DDRC,&DDRC,&DDRC,&DDRD,&DDRD,&DDRD,&DDRD,&DDRD};
const uint8_t BITS[CHANNELS_1+CHANNELS_2] = {PC0,PC1,PC2,PC3,PC4};
/* 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 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; /* the tick counter for the channel PWM */
/* 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 pwm_flag = false; /* to trigger a PWM tick */
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 */
/* UART receive interrupt */
ISR(USART_RX_vect) {
input[input_i] = getchar(); /* save input */
input[input_i+1] = 0; /* always end the string */
if (input_i<INPUT_MAX) { /* next character, if space is available */
input_i++;
}
uart_flag = true; /* set flag */
}
/* power ok interrupt */
ISR(PCINT0_vect) { /* PCI0 Interrupt Vector for PCINT[7:0] */
if (pwr_ok!=(PINB&(1<<PWR_OK))) { /* did the PWR_OK pin state changed */
pwr_ok = PINB&(1<<PWR_OK); /* save new state */
power_flag = true;
} else if (ir!=(PINB&(1<<IR))) { /* did the IR pin state changed */
ir = PINB&(1<<IR); /* save new state */
if (pulse>0) { /* save pulse, except the first */
burst[pulse-1] = (TCNT1*1000UL)/ir_tick;
burst[pulse] = 0;
}
if (pulse<PULSE_MAX-1) { /* prepare to save next pulse */
pulse++;
}
TCNT1 = 0; /* clear timer 1 */
}
}
/* fan tachometer interrupt */
ISR(PCINT1_vect) { /* PCI0 Interrupt Vector for PCINT[14:8] */
if (fan!=(PINC&(1<<FAN))) { /* did the FAN pin state changed */
fan = PINC&(1<<FAN); /* save new state */
if (fan) { /* only react to rising edge */
tachometer = (uint16_t)(timer2_ovf<<8)+TCNT2; /* save time */
TCNT2 = 0; /* reset timer 2 */
timer2_ovf = 0; /* reset timer 2 */
}
}
}
/* timer 2 interrupt used to measure fan speed based on tachometer */
ISR(TIMER2_OVF_vect) { /* timer 2 overflow interrupt vector */
if (timer2_ovf<0xff) { /* prevent overflow */
timer2_ovf++; /* increase tachometer counter */
} else {
tachometer = 0; /* indicate no speed can be measured */
if (pwr_ok) { /* warn the fan is dead while the power in on */
power_flag = true;
}
timer2_ovf = 0;
}
}
/* timer 1 interrupt used to timeout IR burst */
ISR(TIMER1_COMPA_vect) { /* timer 1 OCR1A match interrupt vector */
if (pulse>0 && !ir_flag) { /* warm an burst is ready when the timeout triggered */
ir_flag = true;
}
}
/* timer 0 interrupt used generate a PWM for the channels */
ISR(TIMER0_COMPA_vect) { /* timer 0 OCR0A match interrupt vector */
ch_tick = (ch_tick+1)%LEVELS;
pwm_flag = true;
}
void ioinit(void)
{
/* configure power */
DDRB |= (1<<nPS_ON); /* nPS_ON is output */
PORTB |= (1<<nPS_ON); /* switch off power supply */
DDRB &= ~(1<<PWR_OK); /* PWR_ON (PB1/PCINT1) is input */
pwr_ok = PINB&(1<<PWR_OK); /* save state */
PCIFR &= ~(1<<PCIF0); /* clear interrupt flag */
PCICR |= (1<<PCIE0); /* enable interrupt for PCINT[7:0] */
PCMSK0 |= (1<<PCINT1); /* enable interrupt for PCINT1 */
/* configure LED (on PD6/OC0A) */
DDRD |= (1<<LED); /* LED is output */
PORTD &= ~(1<<LED); /* switch LED on */
/* configure FAN */
DDRC &= ~(1<<FAN); /* FAN (PC5/PCINT13) is input */
fan = PINC&(1<<FAN); /* save state */
PCIFR &= ~(1<<PCIF1); /* clear interrupt flag */
PCICR |= (1<<PCIE1); /* enable interrupt for PCINT[14:8] */
PCMSK1 |= (1<<PCINT13); /* enable interrupt for PCINT1 */
/* use timer 2 to measure the tachometer */
/* use normal mode */
TCCR2A &= ~((1<<WGM20)|(1<<WGM21));
TCCR2B &= ~(1<<WGM22);
/* clock/64 prescaler */
TCCR2B |= (1<<CS22);
TCCR2B &= ~((1<<CS21)|(1<<CS20));
TIFR2 = (1<<TOV2); /* clear timer 2 overflow interrupt flag */
TIMSK2 |= (1<<TOIE2); /* enable timer 2 overflow interrupt */
/* configure IR receiver */
DDRB &= ~(1<<IR); /* IR (PB0/PCINT0) receiver is input */
ir = PINB&(1<<IR); /* save state */
PCIFR = (1<<PCIF0); /* clear interrupt flag */
PCICR |= (1<<PCIE0); /* enable interrupt for PCINT[7:0] */
PCMSK0 |= (1<<PCINT0); /* enable interrupt for PCINT0 */
/* use timer 1 to measure IR pulse */
/* use CTC mode */
TCCR1A &= ~((1<<WGM10)|(1<<WGM11));
TCCR1B |= (1<<WGM12);
TCCR1B &= ~(1<<WGM13);
/* clock/8 prescaler, offers most precision for 15ms (up to 28.5ms) */
TCCR1B |= (1<<CS11);
TCCR1B &= ~((1<<CS12)|(1<<CS10));
uint16_t prescale = TIMER1_PRESCALE[(TCCR1B&((1<<CS12)|(1<<CS11)|(1<<CS10)))>>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<<OCF1A); /* clear timer 1 compare interrupt flag */
TIMSK1 |= (1<<OCIE1A); /* enable timer 1 compare interrupt */
/* configure channels (used for powering LEDs using an nMOS) */
for (uint8_t i=0; i<CHANNELS_1+CHANNELS_2; i++) {
*(DDRS[i]) |= (1<<BITS[i]);
*(PORTS[i]) &= ~(1<<BITS[i]);
}
/* use timer 0 as source for the channel PWM */
/* use CTC mode */
TCCR0A |= (1<<WGM01);
TCCR0A &= ~(1<<WGM00);
TCCR0B &= ~(1<<WGM02);
/* /64 prescale timer */
TCCR0B &= ~(1<<CS02);
TCCR0B |= (1<<CS01)|(1<<CS00);
OCR0A = 0xff; /* set PWM speed */
TIFR0 = (1<<OCF0A); /* clear timer 0 compare interrupt flag */
TIMSK0 |= (1<<OCIE0A); /* enable timer 0 compare interrupt */
/* use UART as terminal */
uart_init();
stdout = &uart_output;
stdin = &uart_input;
sei(); /* enable interrupts */
}
void help(void)
{
printf("commands:\n"\
"\thelp display this help\n"\
"\tpower show power state\n"\
"\tpower on switch power on\n"\
"\tpower off switch power off\n"\
"\tfan show fan speed\n"\
"\tch X Y show channel [1-2].[1-5] brightness\n"\
"\tch X Y Z set channel [1-2].[1-5] brightness [0-255]\n"\
"\tir learn power learn the IR command to power on/off\n"\
"\tir learn mode learn the IR command to change between modes\n"\
"\tir learn brightness up learn the IR command to increase brightness\n"\
"\tir learn brightness down learn the IR command to decrease brightness\n"\
"\tir learn channel next learn the IR command to select next channel\n"\
"\tir learn channel previous learn the IR command to select previous channel\n"\
);
}
void uart_action(char* str)
{
const char* delimiter = " ";
char* word = strtok(str,delimiter);
if (!word) {
goto error;
}
if (0==strcmp(word,"help")) {
help();
} else if (0==strcmp(word,"power")) {
word = strtok(NULL,delimiter);
if (!word) {
if (PINB&(1<<nPS_ON)) {
puts("power is off");
} else {
puts("power is on");
}
} else if (0==strcmp(word,"on")) {
PORTB &= ~(1<<nPS_ON);
} else if (0==strcmp(word,"off")) {
PORTB |= (1<<nPS_ON);
} else {
goto error;
}
} else if (0==strcmp(word,"fan")) {
if (tachometer) {
uint16_t prescale = TIMER2_PRESCALE[TCCR2B&((1<<CS22)|(1<<CS21)|(1<<CS20))];
if (prescale) {
if (timer2_ovf<0xff) {
uint32_t speed = ((60*F_CPU)/(prescale*(uint32_t)tachometer))/2; // calculate speed. 2 pulses per revolution
printf("fan speed: %lurpm\n",speed);
} else {
printf("fan is off (or not detected)\n");
}
} else {
printf("fan speed measurement not started\n");
}
} else {
printf("fan is off (or not detected)\n");
}
} 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 {
goto error;
}
if (learn_flag) {
puts("press button on remote to learn code");
}
return;
error:
puts("command not recognized");
}
int main(void)
{
ioinit(); /* initialize IOs */
uint8_t command_i = 0; /* command index */
struct nec ir_data; /* last IR data */
ir_data.valid = false;
ir_data.repeat = false;
ir_data.address = 0;
ir_data.command = 0;
uint8_t ir_repeat = 0; /* number of times the IR data has been repeated */
uint8_t on[CHANNELS_1+CHANNELS_2]; /* times when to switch on the output on the channels */
uint8_t off[CHANNELS_1+CHANNELS_2]; /* times when to switch off the output on the channels */
channel_flag = true; /* calculate above values later */
puts("LED dimmer up & running");
/* load settings */
if (!verify_settings()) {
initialize_settings();
save_settings();
if (!verify_settings()) {
puts("can't store setting");
return 0;
}
puts("settings created");
} else {
load_settings();
puts("settings loaded");
}
while (true) {
/* calculated PWM values */
while (channel_flag) {
uint8_t start = 0;
for (uint8_t i=0; i<CHANNELS_1+CHANNELS_2; i++) {
on[i] = (start)%LEVELS;
off[i] = (on[i]+brightness[mode][i])%LEVELS;
start = (start+brightness[mode][i])%LEVELS;
}
channel_flag = false;
}
/* generate PWM for channel */
while (pwm_flag) {
if (pwr_ok) {
for (int i=0; i<CHANNELS_1+CHANNELS_2; i++) {
if (on[i]==ch_tick) {
if (on[i]!=off[i]) {
*(PORTS[i]) |= (1<<BITS[i]);
} else if (brightness[mode][i]==0) {
*(PORTS[i]) &= ~(1<<BITS[i]);
} else {
*(PORTS[i]) |= (1<<BITS[i]);
}
} else if (off[i]==ch_tick) {
*(PORTS[i]) &= ~(1<<BITS[i]);
}
}
PIND |= (1<<LED);
}
pwm_flag = false;
}
/* handle UART input */
while (uart_flag) {
/* echo back */
char c = 0;
while (command_i<input_i) {
c = input[command_i++];
putchar(c);
}
if ('\n'==c || '\r'==c) {
if ('\r'==c) {
puts("");
}
if (command_i>1) {
input[command_i-1] = '\0';
uart_action(input);
}
input_i = command_i = 0;
}
uart_flag = false;
}
/* handle power state */
while (power_flag) {
if (pwr_ok) {
puts("power ok");
/* verify if FAN is present */
_delay_ms(500);
if (0==tachometer) {
puts("FAN not on, switching power off");
PORTB |= (1<<nPS_ON);
}
} else {
puts("power off");
}
power_flag = false;
}
/* handle IR input */
while (ir_flag) {
time2nec(burst,pulse-1); /* convert raw time burst in NEC format */
struct nec ir_tmp = nec2data(burst,pulse-1); /* decode NEC burst */
if (ir_tmp.valid) {
if (ir_tmp.repeat) {
if (ir_repeat<0xff) {
ir_repeat++;
}
} else {
ir_data = ir_tmp;
ir_repeat = 0;
}
if (ir_repeat==0 || ir_repeat>3) {
if (learn_flag) {
if (to_learn<IR_ACTION_END) {
ir_keys[to_learn][0] = ir_data.address;
ir_keys[to_learn][1] = ir_data.command;
}
save_settings();
puts("IR code learned");
to_learn = IR_ACTION_END;
learn_flag = false;
} else {
ir_action(ir_data.address,ir_data.command);
}
}
}
pulse = 0; /* reset burst */
ir_flag = false;
}
}
return 0;
}
void ir_action(uint8_t address, uint8_t command)
{
enum IR_ACTIONS ir_code = IR_ACTION_END;
for (ir_code=0; ir_code<IR_ACTION_END; ir_code++) {
if (ir_keys[ir_code][0]==address && ir_keys[ir_code][1]==command) {
break;
}
}
if (ir_code<IR_ACTION_END) {
switch (ir_code) {
case POWER:
printf("switching power supply ");
if (PINB&(1<<nPS_ON)) {
puts("on");
} else {
puts("off");
}
PINB |= (1<<nPS_ON);
break;
case MODE:
break;
case BRIGHTNESS_UP:
break;
case BRIGHTNESS_DOWN:
break;
case CHANNEL_NEXT:
break;
case CHANNEL_PREVIOUS:
break;
default:
printf("unhandled IR action: %u\n", ir_code);
break;
}
} else {
puts("IR command not learned");
}
}