#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <avr/fuse.h>
#include <string.h>
#define STATIC_ASSERT(expr) extern char static_assert[ (!!(expr))*2 - 1]
#define elements_of(array) (sizeof(array) / sizeof(array[0]))
FUSES =
{
.low = LFUSE_DEFAULT,
.high = (FUSE_EESAVE & FUSE_SPIEN),
};
static volatile uint8_t red_led = 0;
static volatile uint8_t green_led = 0;
static volatile uint8_t red_led_1 = 0;
static volatile uint8_t green_led_1 = 0;
static volatile uint16_t volume_activated_timer = 0;
static volatile uint8_t timer1ovfl = 0;
enum EGO_X012 {
EGO_X012_INACTIVE = 1 * _BV(PA6) | 1 * _BV(PA5) | 1 * _BV(PA4),
EGO_X012_PLAY_UNHOOK = 0 * _BV(PA6) | 0 * _BV(PA5) | 0 * _BV(PA4),
EGO_X012_STOP_ONHOOK = 1 * _BV(PA6) | 0 * _BV(PA5) | 0 * _BV(PA4),
EGO_X012_VOL_UP = 0 * _BV(PA6) | 0 * _BV(PA5) | 1 * _BV(PA4),
EGO_X012_VOL_DOWN = 1 * _BV(PA6) | 0 * _BV(PA5) | 1 * _BV(PA4),
EGO_X012_NEXT = 0 * _BV(PA6) | 1 * _BV(PA5) | 0 * _BV(PA4),
EGO_X012_PREV = 1 * _BV(PA6) | 1 * _BV(PA5) | 0 * _BV(PA4),
};
#define EGO_RED_LED_IS_ON() (!(PINB & _BV(PB5)))
#define EGO_GREEN_LED_IS_ON() (!(PINA & _BV(PA7)))
#define EGO_MUTE_ON() (!(PINB & _BV(PB3)))
#define EGO_X012(x) PORTA = (PORTA & ~EGO_X012_INACTIVE) | ((x) & EGO_X012_INACTIVE)
#define EGO_INIT() DDRA &= ~(_BV(PA7));DDRA |= EGO_X012_INACTIVE; PORTA |= _BV(PA7); DDRB &= ~(_BV(PB5) | _BV(PB3)); PORTB |= _BV(PB5);
#define BTN1_LED_GREEN_ON() PORTA |= _BV(PA2)
#define BTN1_LED_GREEN_OFF() PORTA &= ~_BV(PA2)
#define BTN1_LED_GREEN_INIT() DDRA |= _BV(PA2);BTN1_LED_GREEN_OFF()
#define BTN2_LED_RED_ON() PORTA |= _BV(PA0)
#define BTN2_LED_RED_OFF() PORTA &= ~_BV(PA0)
#define BTN2_LED_RED_INIT() DDRA |= _BV(PA0);BTN2_LED_RED_OFF()
#define BTN_GREEN_PRESSED() (!(PINA & _BV(PA3)))
#define BTN_RED_PRESSED() (!(PINA & _BV(PA1)))
#define BTN_INIT() DDRA &= ~(_BV(PA3) | _BV(PA1));PORTA |= (_BV(PA3) | _BV(PA1))
enum {
STRW_UP_INPUT = 7,
STRW_DOWN_INPUT = 9
};
#define VOLTAGE_TO_ADC_VALUE(v) (uint16_t)(v * ((1UL<<16)-1) / (2.56 * 2))
#if 0
enum {
VOLTAGE1 = VOLTAGE_TO_ADC_VALUE(5.0 * 1/6),
VOLTAGE2 = VOLTAGE_TO_ADC_VALUE(5.0 * 3/6),
VOLTAGE3 = VOLTAGE_TO_ADC_VALUE(5.0 * 5/6)
};
#endif
enum {
VOLTAGE1 = VOLTAGE_TO_ADC_VALUE(0.5),
VOLTAGE2 = VOLTAGE_TO_ADC_VALUE(1.5),
VOLTAGE3 = VOLTAGE_TO_ADC_VALUE(3.0),
MUTE_ON_THRES = VOLTAGE_TO_ADC_VALUE(0.6)
};
volatile uint16_t voltages[] = {VOLTAGE1,VOLTAGE2,VOLTAGE3};
typedef enum {
STRW_INACTIVE = 0,
STRW_HIGH = 1,
STRW_LOW = 2,
STRW_GND = 3
} btn_t;
typedef enum {
STRW_NONE = (STRW_INACTIVE << 2) + STRW_INACTIVE,
STRW_UP = (STRW_LOW << 2) + STRW_INACTIVE,
STRW_DOWN = (STRW_INACTIVE << 2) + STRW_LOW,
STRW_VOL_UP = (STRW_HIGH << 2) + STRW_INACTIVE,
STRW_VOL_DOWN = (STRW_INACTIVE << 2) + STRW_HIGH,
STRW_MODE = (STRW_GND << 2) + STRW_INACTIVE,
STRW_PWR = (STRW_INACTIVE << 2) + STRW_GND,
STRW_DOWN_MODE = (STRW_GND << 2) + STRW_LOW,
} wheel_control_t;
static void adc_init(void)
{
ADMUX = _BV(REFS2) * 1 | _BV(REFS1) * 1 | _BV(REFS0) * 0 | _BV(ADLAR);
ADCSRA = _BV(ADEN) | _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0);
ADCSRB = _BV(REFS2) * 1;
}
static uint16_t measure_adc(uint8_t ch) {
ADMUX = (ADMUX & ~0x1F) | (ch & 0xF);
ADCSRA |= _BV(ADEN);
ADCSRA |= _BV(ADSC);
while (bit_is_set(ADCSRA, ADSC))
;
uint16_t value = ADC;
return value;
}
static btn_t convert_from_voltage(uint16_t measurement) {
if (measurement < VOLTAGE1) {
return STRW_GND;
}
else if (measurement < VOLTAGE2) {
return STRW_LOW;
}
else if (measurement < VOLTAGE3) {
return STRW_HIGH;
}
else {
return STRW_INACTIVE;
}
}
#define debug_voltage(v) \
{ \
red_led = v & 0x1;\
green_led = v & 0x2;\
}
static wheel_control_t get_wheel_control(void)
{
btn_t b1 = convert_from_voltage(measure_adc(STRW_UP_INPUT));
btn_t b2 = convert_from_voltage(measure_adc(STRW_DOWN_INPUT));
return (b1 << 2 | b2);
}
STATIC_ASSERT(F_CPU > 0);
#define T1_OVERFLOW_TIME_MS ((4UL*256UL*1000UL) / F_CPU)
#define MS_TO_TICKS(t) (t/T1_OVERFLOW_TIME_MS)
#define VOLUME_ACTIVATE_TIMEOUT MS_TO_TICKS(300)
STATIC_ASSERT(VOLUME_ACTIVATE_TIMEOUT > 1);
static void default_strwh_inactive(void)
{
if (BTN_RED_PRESSED()) {
EGO_X012(EGO_X012_STOP_ONHOOK);
}
else if (BTN_GREEN_PRESSED()) {
EGO_X012(EGO_X012_PLAY_UNHOOK);
}
else {
EGO_X012(EGO_X012_INACTIVE);
}
}
ISR(SIG_OVERFLOW1)
{
if (!green_led_1) BTN1_LED_GREEN_ON(); else BTN1_LED_GREEN_OFF();
if (!red_led_1) BTN2_LED_RED_ON(); else BTN2_LED_RED_OFF();
if (volume_activated_timer > 0) volume_activated_timer--;
timer1ovfl++;
}
ISR(SIG_OUTPUT_COMPARE1A)
{
if (!green_led) {
BTN1_LED_GREEN_OFF();
}
}
ISR(SIG_OUTPUT_COMPARE1B)
{
if (!red_led) {
BTN2_LED_RED_OFF();
}
}
int main(void)
{
wheel_control_t prev = STRW_NONE;
uint8_t volume_activated = 0;
adc_init();
BTN2_LED_RED_INIT();
BTN1_LED_GREEN_INIT();
BTN_INIT();
EGO_INIT();
EGO_X012(EGO_X012_INACTIVE);
PLLCSR = 0;
TCCR1A = 0;
TCCR1B = 3;
TCCR1D = 0;
TCCR1E = 0;
TIMSK |= _BV(TOIE1) | _BV(OCIE1A) | _BV(OCIE1B);
OCR1A = 2 * 256/100;
OCR1B = 3 * 256/100;
sei();
for (;;)
{
wdt_reset();
if (timer1ovfl > 20)
{
wheel_control_t ctrl = 0;
timer1ovfl = 0;
ctrl = get_wheel_control();
red_led = EGO_RED_LED_IS_ON();
green_led = EGO_GREEN_LED_IS_ON();
if (volume_activated_timer == 0) {
prev = STRW_NONE;
}
if (BTN_RED_PRESSED()) red_led_1 = 1; else red_led_1 = 0;
if (BTN_GREEN_PRESSED()) green_led_1 = 1; else green_led_1 = 0;
if (0) {
default_strwh_inactive();
}
else {
if (EGO_MUTE_ON() && EGO_RED_LED_IS_ON()) {
switch (ctrl){
default:
case STRW_NONE: default_strwh_inactive();break;
case STRW_VOL_UP: EGO_X012(EGO_X012_VOL_UP);break;
case STRW_VOL_DOWN: EGO_X012(EGO_X012_VOL_DOWN);break;
case STRW_UP: EGO_X012(EGO_X012_PLAY_UNHOOK);break;
case STRW_DOWN: EGO_X012(EGO_X012_STOP_ONHOOK);break;
case STRW_DOWN_MODE: EGO_X012(EGO_X012_STOP_ONHOOK); break;
}
}
else {
if (EGO_GREEN_LED_IS_ON() && !EGO_RED_LED_IS_ON()) {
switch (ctrl){
default:
case STRW_NONE:
default_strwh_inactive();
break;
case STRW_VOL_UP:
{
volume_activated_timer = VOLUME_ACTIVATE_TIMEOUT;
if (prev == STRW_VOL_DOWN) {
volume_activated = 1;
}
if (volume_activated) {
EGO_X012(EGO_X012_VOL_UP);
}
}
break;
case STRW_VOL_DOWN:
{
volume_activated_timer = VOLUME_ACTIVATE_TIMEOUT;
if (prev == STRW_VOL_UP) {
volume_activated = 1;
}
if (volume_activated) {
EGO_X012(EGO_X012_VOL_DOWN);
}
}
break;
case STRW_UP:
EGO_X012(EGO_X012_NEXT);
break;
case STRW_DOWN:
EGO_X012(EGO_X012_PREV);
break;
case STRW_DOWN_MODE:
EGO_X012(EGO_X012_STOP_ONHOOK);
break;
}
}
else {
switch (ctrl){
default:
case STRW_NONE:
default_strwh_inactive();
break;
case STRW_DOWN_MODE: EGO_X012(EGO_X012_STOP_ONHOOK);
break;
}
}
}
}
if (ctrl != STRW_NONE) {
prev = ctrl;
}
if (ctrl != STRW_VOL_DOWN || ctrl != STRW_VOL_UP) {
volume_activated = 0;
}
}
}
return 0;
}