RCLight

Die RCLight Platine ist ein ist ein minimalistischer RC gesteuerter Schalter, mit Timing Funktionen. Das ganze ist um einen Atmel ATtiny13 aufgebaut. Um grössere Schaltströme von bis zu 3A zu verkraften, sind die vier Ausgänge mit Leistungs FETs ausgestattet. Der IRF F7101 enthält 2 NMOS FETs mit je 3,5A, alternativ tut's auch ein IRF F7103 mit maximal je 2,3A.

Die Software steuert die Ausgänge über eine Tabelle an. Die Tabelle hat 36 * 3 = 108 für jeden der Ausgänge. Zu jeder Zeit gilt einer der 36 Triplet-Einträge die einmal pro Sekunde durchgetaktet werden. D.h. ein Eintrag gilt für 1 / 36 = 0.027sec. In Abhängigkeit vom Impluseingang, gilt je nach Knüppelstellung 'low', 'mid' und 'high' einer der drei Einträge.

Dadurch kann man jeden Ausgang einzeln steuern. Die Blinkmuster können mit ~0.03sec Auflösung variiert werden, um möglichst realistische Blink/Blitz folgen zu erhalten.

Das gesamte C-Programm hat in 1KByte Flash des ATtiny Platz. Die Tabelle und die restlichen Variablen mussten in den nur 64 Byte (nicht KByte !) Platz finden. Deshalb sind in einem Byte zwei Einträge untergebracht.

Die Schaltung ist mit der freien Version von Eagle entwickelt. Der Schaltplan und die Platinendaten als PDF liegen hier und hier.

Die Platine ist wurde bei Olimex bestellt.

Die Firmware ist Atmels AVR-Studio entwickelt und dann mit USBasp geflasht. Als Programmierstecker dient ein halbierter Floppydisk Platinenstecker, der nur einige Sekunden auf die Platinenpads gehalten werden muss.

Der C-Source Code ist hier als Download. Die nicht kommerzielle Nutzung der Daten ist gestattet.

/*
 * RcLight I/O
 * Author: Heinz Bruederlin
 * 02.11.09 First implemented
 * 12.12.09 Fuses fixed
 * 28.12.09 Timeout disabled, ServoWidth adjusted
 *
 * CPU:       ATtiny 13
 *
 * Clock:     9.6MHz
 * Prescaler: 256 => 37.5KHz
 * Timer0:    26.67usec
 * Overflow0: 256 * 26.67usec => 6,83msec
 * Blink rate: 4 * 6,83msec => 0,027sec
 * Blink table: 36 Entries => 36 * 0.027sec => ~1sec
 * ServoTimeOut: 250 * 0,027 = 6,75 sec
 *
 * Servo pulse table (with 26.7usec)
 *	0.0msec =   0
 *	0.5msec =  19
 *	1.0msec =  37
 *	1.5msec =  56
 *	2.0msec =  74
 *	2.5msec =  93
 *	3.0msec = 112
 *
 * OUT0 : not used       (permanent)		(not switchable)
 * OUT1 : ACL            (short-short-short)    (MID|HIG)
 * OUT2 : position light (permanent)		(MID|HIG)
 * OUT3 : not used       (off)			(MID|HIG)
 * OUT4 : landing light  (permanent)		(HIG)
 *
 */
#ifndef F_CPU
#define F_CPU (9600000/8)  /* Clock in Hz */
#endif
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

FUSES = {
    .low  = 0xff & (FUSE_CKSEL0 &   /* internal RC 9.6MHz */
		    FUSE_SUT0   &   /* max startup time */
		    FUSE_SPIEN),    /* SPI enabled */
    .high = 0xff & (FUSE_BODLEVEL1) /* Brown-out 2.7V */
};

/* PORTB defines */
#define OUT4  _BV(PB0) /* connected to FET */
#define SERVO _BV(PB1) /* connected to RC Receiver or HAL Sensor*/
#define OUT1  _BV(PB2) /* connected to FET */
#define OUT2  _BV(PB3) /* connected to FET */
#define OUT3  _BV(PB4) /* connected to FET */

/*
 * globals
 *
 */
typedef struct { uint8_t l,h; } bytelh_t;
typedef union {
    bytelh_t _;
    uint16_t __;
} uint8_16_t; 

volatile uint8_t    TcntH;	 // clear hight byte (overflow counter)
volatile uint8_16_t ServoStart;	 // rising edge of servo impluse
volatile uint8_t    BlinkRate;	 // blink divide rate
volatile uint8_t    BlinkIdx;	 // pointer into blink table
volatile uint8_t    ServoSwitch; // bitmask switched by servo impluse value
volatile uint8_t    ServoTimeOut;// bitmask switched by servo impluse value

/*
 * configuration values
 * TODO: move to eeprom
 */
uint8_t ServoRangeLow;		 // lowest servo value
uint8_t ServoRangeHigh;		 // highest servo value
uint8_t ServoDepends[4];	 // bitvalue wether OUT1-4 are switched by servo value
enum { SERVO_LOW=0x01, SERVO_MID=0x02, SERVO_HIG=0x04 };

enum { O1=0x01, O2=0x02, O3=0x04, O4=0x08 };
uint8_t table[18] = {
    (   O2|   O4) |	//00
    (   O2|   O4)<<4,	//01
    (   O2|   O4) |	//02
    (   O2|   O4)<<4,	//03
    (   O2|   O4) |	//04
    (   O2|   O4)<<4,	//05
    (   O2|   O4) |	//06
    (   O2|   O4)<<4,	//07
    (   O2|   O4) |	//08
    (   O2|   O4)<<4,	//09
    (   O2|   O4) |	//10
    (   O2|   O4)<<4,	//11
    (   O2|   O4) |	//12
    (   O2|   O4)<<4,	//13
    (   O2|   O4) |	//14
    (   O2|   O4)<<4,	//15
    (   O2|   O4) |	//16
    (   O2|   O4)<<4,	//17
    (   O2|   O4) |	//18
    (   O2|   O4)<<4,	//19
    (   O2|   O4) |	//20
    (   O2|   O4)<<4,	//21
    (   O2|   O4) |	//22
    (   O2|   O4)<<4,	//23
    (   O2|   O4) |	//24
    (   O2|   O4)<<4,	//25
    (O1|O2|   O4) |	//26
    (O1|O2|   O4)<<4,	//27
    (   O2|   O4) |	//28
    (   O2|   O4)<<4,	//29
    (O1|O2|   O4) |	//30
    (O1|O2|   O4)<<4,	//31
    (   O2|   O4) |	//32
    (   O2|   O4)<<4,	//33
    (O1|O2|   O4) |	//34
    (O1|O2|   O4)<<4	//35
};

/*
 * io_init -
 *	init io ports
 */
void io_init(void) {
   /* switch off outputs and set pullup at input */
   PORTB = SERVO;
   /* Set PORT B outputs */
   DDRB  = OUT1 | OUT2 | OUT3 | OUT4;
}

/*
 * timer_init -
 *	init timer 0
 */
void timer_init(void) {
    TCCR0B |= _BV(CS02); // set timer0 prescaler to 256
    TCNT0  = 0;		 // clear timer0
    TIFR0 |= _BV(TOV0);	 // clear pending overflow 
    TcntH  = 0;		 // clear high byte
}

/*
 * global_init -
 *	init globals
 */
void global_init(void) {
    ServoStart.__   = 0;	 // init ServoStart
    BlinkIdx        = 0;
    BlinkRate       = 0;

    ServoTimeOut    = 0;
    ServoRangeLow   = 37;
    ServoRangeHigh  = 74;
    ServoDepends[0] = SERVO_MID|SERVO_HIG;
    ServoDepends[1] = SERVO_MID|SERVO_HIG;
    ServoDepends[2] = SERVO_MID|SERVO_HIG;
    ServoDepends[3] = SERVO_HIG;
    ServoSwitch     = 0;
}


/*
 * intr_init -
 *	enable timer0 overflow and int0 interrupts
 */
void intr_init(void) {
    TIMSK0 |= _BV(TOIE0);	     // enable timer0 overflow interrupt
    MCUCR  |= _BV(ISC01)|_BV(ISC00); // rising edge generates an interrupt
    GIMSK  |= _BV(INT0); 	     // enable external interrupt for SERVO
    sei();		 	     // enable interupts
}

/*
 * TIM0_OVF_vect -
 *	timer 0 overflow interrupt handler
 */
ISR(TIM0_OVF_vect) {
    TcntH++;
    BlinkRate++;
    if (BlinkRate==4) {
	uint8_t t, o;
	BlinkRate = 0;
	BlinkIdx++;
	if (BlinkIdx >= sizeof(table)*2) BlinkIdx = 0;
	if (BlinkIdx % 2) {
	    t = (table[BlinkIdx/2] & 0xF0)>>4;
	} else {
	    t = (table[BlinkIdx/2] & 0x0F);
	}
	o = 0x00;
	if (t & O1) o |= OUT1;
	if (t & O2) o |= OUT2;
	if (t & O3) o |= OUT3;
	if (t & O4) o |= OUT4;
      // if (ServoTimeOut<250)  // disable timeout !
	{ // demo mode reached ?
	    o &= ServoSwitch;
	}
	PORTB = (PORTB & ~(OUT1|OUT2|OUT3|OUT4)) | o;
    }
    if (ServoTimeOut<250) {
	ServoTimeOut++;
    }
}

/*
 * INT0_vect -
 *	external interrupt 0
 *	called on each rising/falling edge of SERVO
 */
ISR(INT0_vect) {
    if (MCUCR & _BV(ISC00)) {
	MCUCR &= ~_BV(ISC00);	// next look for falling edge
    	ServoStart._.l = TCNT0;
	ServoStart._.h = TcntH;
    } else {
	uint8_16_t servoEnd;
	uint16_t   servoWidth;
	uint8_t    servoIdx;
	uint8_t    servoMask;

	MCUCR |= _BV(ISC00);	// next look for rising edge
	servoEnd._.l = TCNT0;
	servoEnd._.h = TcntH;

	// convert servo value to 0,1,2
	servoWidth = servoEnd.__ - ServoStart.__;
	if (servoWidth < ServoRangeLow || servoWidth > ServoRangeHigh) 
	    return;
	servoIdx = ((servoWidth-ServoRangeLow) * 3) /
		    (ServoRangeHigh - ServoRangeLow + 1);
	switch(servoIdx) {
	    case 0:  servoMask = SERVO_LOW; break;
	    case 1:  servoMask = SERVO_MID; break;
	    default: servoMask = SERVO_HIG; break;
	}
	ServoSwitch = 0;
	if (ServoDepends[0] & servoMask) ServoSwitch |= OUT1;	
	if (ServoDepends[1] & servoMask) ServoSwitch |= OUT2;	
	if (ServoDepends[2] & servoMask) ServoSwitch |= OUT3;	
	if (ServoDepends[3] & servoMask) ServoSwitch |= OUT4;
	ServoTimeOut = 0;
    }
}

/*
 * main -
 *	init everything and go to endlees loop
 */
int main(void) {
    io_init();
    timer_init();
    global_init();
    intr_init();

    for(;;) {
    }
}