switch4ch.c
/*
* switch4ch.c
* ATtiny13 mit 1,25 MHz internal RC resonator
*
* This small program parses the signal of an RC receiver output and switches
* 4 functions according to the input signal. The pcb will be connected
* exactly like a standard servo.
* The servo signal usually repeats every 20ms and has a high value of the
* signal ranges from 0.5ms to 2.5ms maximum and 1ms to 2ms at a standard
* RC control.
*
* Usage:
* A 'click' your RC-sender pult channel to 100% a short time
* (<0.5s) will toggle channel 1
* A click to -100% will toggle channel 2
* If you stay at 100% for more than a half second, channel 3 will be set
* until you release the channel again.
* The same happens to channel 4 if you stay at -100% for more than a half
* second - currently this generates a funny 'morse code' signal for a
* small tweeter.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND
* This file is licensed under the GNU GENERAL PUBLIC LICENSE Version 2
*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/signal.h>
#include <avr/wdt.h>
/**
* The pin where we get the servo signal from the receiver
*/
#define PIN_PULSE_IN PB3
#define MAX_CLICK_TIME 25
/**
* the ouputs
*/
#define OUT1 PB0
#define OUT2 PB1
#define OUT3 PB2
#define OUT4 PB4
/**
* Higher byte of the timer. this will be incremented by the int SIG_OVERFLOW0
* This is needed since the TIMER0 is running without any prescaler.
* This variable is defined as volatile to avoid that C is optimising it to a register
* which cannot be shared between the interrupt service routine and the mail loop.
*/
volatile uint8_t timerHighByte;
/**
* counts the number of cycles the high value has been reached.
* If this value is 0 then no high value has been detected previously.
* If the value is > 50 (~1 second) then stop counting and switch on SW4.
*/
uint8_t highCounter;
/**
* counts the number of cycles the low value has been reached.
* If this value is 0 then no low value has been detected previously.
*/
uint8_t lowCounter;
/**
* If this variable is > 0 then play a sound. If 0 then stop playing.
* This is used as a ship horn which is output by SW2.
*/
uint8_t soundRunning;
/**
* This interrupt gets triggered if the timer0 is running and an overflow (from 0xff to 0x00) happens
*/
SIGNAL (SIG_OVERFLOW0) {
timerHighByte++; // incrementing timerHighByte
}
/**
* reset the timer value to 0x0000 and start the timer
*/
void restartTimer() {
timerHighByte = 0; // higher Byte of the timer
TCNT0 = 0; // lower Byte of the timer
TCCR0B |= (1 << CS00); // set Prescaler to 1:1 (count with clkspeed of ~1MHz)
}
/**
* stop the timer0
*/
void stopTimer() {
TCCR0B &= ~(1 << CS00); // set Prescaler to 0 will stop the timer
}
/**
* This function is called if a valid servo pulse has been detected.
* The timerValue is the pulse length in microseconds
*/
void performAction(uint16_t timerValue) {
if (timerValue < 1500) {
if (lowCounter >= MAX_CLICK_TIME) {
// the button has been pressed for aprox 1 second -> switch on SW2
//X disabled. SW2 is now used for playing sound: PORTB |= (1<<OUT2);
soundRunning = 1;
} else {
// simply continue counting
lowCounter++;
}
} else if (timerValue > 2100) {
if (highCounter >= MAX_CLICK_TIME) {
// the button has been pressed for aprox 1 second -> switch on SW4
PORTB |= (1<<OUT4);
} else {
// simply continue counting
highCounter++;
}
} else {
// a mid value (zero position)
if (highCounter > 3 && highCounter < MAX_CLICK_TIME) {
// single 'click' detected -> toggle switch SW3
PORTB ^= (1<<OUT3);
} else if (highCounter == MAX_CLICK_TIME) {
// so its now time to switch off SW4 again
PORTB &= ~(1<<OUT4);
}
//X TODO toggle SW2 also
if (lowCounter > 3 && lowCounter < MAX_CLICK_TIME) {
// single 'click' detected -> toggle switch SW1
PORTB ^= (1<<OUT1);
} else if (lowCounter == MAX_CLICK_TIME) {
// so its now time to switch off SW2 again
//X disabled. SW2 is now used for playing sound: PORTB &= ~(1<<OUT2);
soundRunning = 0;
}
// reset the counters
lowCounter = 0;
highCounter = 0;
}
return;
}
/**
* This function is called if an invalid valid servo pulse has been detected.
*/
void performActionOnInvalidPulse() {
// switch out all ports
PORTB = 0;
}
/**
* The main loop does the following:
* .) initialise all ports
* .) restart the timer
*
* in an endless loop the following things will be done:
* .) check if the PIN_PULSE_IN is high and if so start the timer
* .) if the timer went low again, stop the timer and read the current timer value
* .) depending on the timervalue (should be from 1ms (timer=1000) to 2ms (timer=2000) perform actions
*/
int main() {
uint16_t timerValue=0;
uint8_t soundCounter = 0;
// enable the watchdogtimer and set it to 120 ms timeout
//X seems to have a bug with tiny13 wdt_enable(WDTO_500MS);
// on startup there is always an invalid pulse
performActionOnInvalidPulse();
PORTB = 0x00; // clear the PORTB to avoid glitches
DDRB = 0x17; // set bits 0,1,2 and 4 of PORTB as output
TIMSK0 |= (1 << TOIE0); // Timer 0 Overflow Interrupt enable
sei(); // enable Interrupts
for(;;) { // main loop
// stay in loop as long as PIN_PULSE_IN is low
while( !(PINB & (1 << PIN_PULSE_IN) ) ){
soundCounter++;
if (soundRunning > 0 && soundCounter & (1<<5)) {
PORTB ^= (1<<OUT2);
}
}
// switch off the tweeter
PORTB &= ~(1<<OUT2);
restartTimer(); // if PIN_PULSE_IN went high, we start the timer from 0
// stay in loop as long as PIN_PULSE_IN is high
while( (PINB & (1 << PIN_PULSE_IN) ) ) {
}
// if all signals are available, this should have lasted only ~25 ms,
// so tell the watchdog timer that all is ok!
//X seems to have a bug with tiny13 wdt_reset();
stopTimer(); // if PIN_PULSE_IN went low again, we stop the timer
timerValue = (timerHighByte << 8) + TCNT0;// fill the 16bit timerValue
if (timerValue > 800 && timerValue < 3000) {
performAction(timerValue);
} else {
performActionOnInvalidPulse();
}
} // end of main loop
}