Source file: /~heha/mb-iwp/Kleinkram/Sandkasten.zip/fs/fs-d.cpp

/* Programm für VR-Sandkasten mit Funktransceiver 433 MHz
Batteriebetrieb: Knopfzelle CR2032! Hardware: RFM12 (Pollin) sowie:
Das Gerät benutzt 4 Knöpfe und 1 Zweifarb-LED.
grün = Senden OK, rot = Senden ging schief (kein Empfänger, Empfänger zu weit o.ä.)
Kanalauswahl-Modus: Tasten 1+2 4 s gedrückt halten: LED = rot permanent
 zuerst Blink-Ausgabe der aktuellen Kanal-Nummer in Gelb zum Mitzählen
 Taste 1 schaltet Kanal um 1 weiter, LED gelb pro Tastendruck
 längeres LED gelb wenn Kanal von 12 auf 1 umschlägt
 Taste 2 loslassen:
  Taste 1 nicht gedrückt = Ende, Speichern und Aussenden Frequenz-Hopping-Paket
  Taste 1 gedrückt = nicht speichern (Abbruchmöglichkeit)
Gemessener Ruhestromverbrauch: 0,3 µA (210506)
Bei einer CR2032-Knopfzelle mit einer Kapazität von 200 mAh
und 0,1 µA Selbstentladung macht das eine Haltbarkeit von über 50 Jahren.
*210506	erstellt
-210611	Tasten länger entprellt
 
tabsize = 8, encoding = utf-8?, lineends = LF
 */

#include <string.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <avr/wdt.h>
#include <avr/sleep.h>
#include <avr/eeprom.h>
#include <avr/fuse.h>
#include <avr/signature.h>

#include "../rf12/rf12.h"

FUSES={
#if F_CPU == 128000
  0b11100100,	// 128-kHz-Oszillator ohne Taktteiler (geht nicht richtig und bringt keine Vorteile)
#elif F_CPU == 1000000
  0b01100010,	// unverändert: Interner Oszillator 8 MHz, 8:1, langsam starten
#else
# error Hier LoFuse für F_CPU finden und eintragen!
#endif
  0b01010111,	// nur EESAVE, BrownOut würde zu viel Strom fressen
  0b11111111,	// unverändert
};

/************
 * Hardware *
 ************/
/*
Verwendung der Ports des ATtiny24:
PA0	(13)	-	Taster T1
PA1	(12)	-	Taster T2
PA2	(11)	-	Taster T3
PA3	(10)	-	Taster T4
PA4	(9)	SCK	RFM12: SCK
PA5	(8)	MISO/DO	RFM12: SDI
PA6	(7)	MOSI/DI	RFM12: SDO
PA7	(6)	-	RFM12: !SEL
PB0	(2)	-	LED rot
PB1	(3)	-	LED grün
PB2	(5)	-	Taster T5
PB3	(4)	!RESET	Taster T6
*/

EMPTY_INTERRUPT(PCINT0_vect);	// Pegelwechsel an PINA (Tasten)
EMPTY_INTERRUPT(PCINT1_vect);	// Pegelwechsel an PINB (Tasten)
EMPTY_INTERRUPT(TIM0_OVF_vect);	// für sinnvoll funktionierenden SLEEP-Befehl (16 ms)

static byte keyPress() {
  return ~PINA&0x0F|~PINB<<2&0x30;
}

/*************************************
 * Initialisierung und Hauptschleife *
 *************************************/
static void PowerDown() {
  rf12::xfer(0);
  rf12::powerdown();
  rf12::ss_hi();	// !SEL = High
}

static void SleepForKey() {
  PRR  |= 0x02;		// USI aus
  MCUCR = 0x30;		// Power-Down: alles aus außer Pin-Change (Tasten)
  PCMSK0= 0x0F;		// 4 Tasten
  PCMSK1= 0x0C;		// 2 Tasten
  GIFR  = 0x30;		// anhängige Interrupts löschen
  GIMSK = 0x30;		// Pin-Change an PINA und PINB
  sleep_cpu();		// warten auf Tastendruck (Timer0 schläft)
  GIMSK = 0;		// Kein Interrupt auf Tastendruck mehr
  MCUCR = 0x20;		// Idle
  PRR  &=~0x02;		// USI aktivieren
}

static void PowerUp() {
  rf12::powerup();	// Quarz aktivieren (ca. 2 mA)
  rf12::init();		// Baudrate u.v.a.m. einstellen
  rf12::xfer(0);	// Status auslesen (probeweise, IRQ löschen)
}

static void HardwareInit() {
  ACSR  = 0x80;		// Analogvergleicher abschalten: –70µA
  PORTA = 0x8F;
  PORTB = 0x0C;
  DDRA  = 0xB0;		// !SEL, MISO, SCK als Ausgänge
  DDRB  = 0x03;		// LEDs als Ausgänge
  PRR   = 0x09;		// Timer0 und USI aktivieren
  TIMSK0= 0x01;
#if F_CPU <= 128000
  TCCR0B= 0x02;		// Vorteiler 8, Überlauf alle 16 ms
#else
  TCCR0B= 0x03;		// Vorteiler 64, Überlauf alle 16 ms (Tastenabfragetakt)
#endif
  MCUCR = 0x20;		// Idle
  PowerDown();
  sei();
}

static inline void red_on()	{PORTB|= 1;}
static inline void red_off()	{PORTB&=~1;}
static inline void green_on()	{PORTB|= 2;}
static inline void green_off()	{PORTB&=~2;}

// Wireless_ID ist (nur) mit EEPROM-Programmiergerät änderbar,
// Kanalnummer mit Taste 1 + 2
EEMEM struct ee_t{
 byte ch,id;
}ee={(WIRELESS_ID-1)%12+1,WIRELESS_ID};

static byte wireless_id=WIRELESS_ID;

int main() {
  byte keystate=0;	// Tastenstatus, Bit 7 = Toggle-Bit bei Wiederholungen
  byte keychange=0;
  byte resend_countdown=0;
// gedrückte Taste alle 1 s wiederholt aussenden, sonst denkt der Empfänger,
// der Sender sei tot, und nimmt den Tastendruck zurück
  byte retry_countdown=0;
// 3 Sendeversuche unternehmen (die mit einem erfolgreichen ACK enden müssen)
// sonst bleibt die rote LED an zur Fehleranzeige
  byte led_countdown=0;
  byte prog_countdown=0;
  byte b=eeprom_read_byte(&ee.id);
  if (byte(b-1)<15) wireless_id=b;
  if (byte(b-1)<12) rf12::channel=b;	// standardmäßig gleich
  b=eeprom_read_byte(&ee.ch);
  if (byte(b-1)<12) rf12::channel=b;	// Arbeitskanal setzen
  HardwareInit();
  for (;;) {	// Endlos-Hauptschleife
    if (keystate&0x7F || retry_countdown || led_countdown) {
      TCNT0=0;		// volle 16 ms
      sleep_cpu();	// Idle bis Timer0-Überlauf
      sleep_cpu();	// nochmal
      sleep_cpu();	// und nochmal: ca. 50 ms Entprellzeit sollte reichen
    }else SleepForKey();// PowerDown bis Tastendruck
    if (!keychange) {
      byte k=keyPress();
      keychange=(k^keystate)&0x7F;
      if (keychange) prog_countdown = k==3 ? 255 : 0;
      else if (!--resend_countdown) keychange=0x80;
    }
    if (prog_countdown && !--prog_countdown) {
// modale Schleife, Taste 2 muss die ganze Zeit gedrückt bleiben!
      red_on();	// rote LED permanent ein
// Kanalnummer ausspucken (grüne LED blinken lassen)
      b=rf12::channel;
      do{
        byte k=10; do {sleep_cpu(); if (PINA&2) goto raus;} while(--k);
        green_on();	// grüne LED ein
        k=10; do {sleep_cpu(); if (PINA&2) goto raus;} while(--k);
        green_off();	// grüne LED aus
      }while(--b);
// Tastendrücke auswerten
      b=rf12::channel;
      do{
        sleep_cpu();
	byte k=keyPress();
        keychange=(k^keystate)&0x7F;
        if (~keystate&keychange&1) {	// Taste 1 gedrückt
	  if (++b>12) b=1;
          green_on();	// grüne LED ein
          led_countdown=10;
          if (b==1) led_countdown=30;	// länger für Kanal 1
        }
        if (keystate&keychange&1) {		// obere Taste losgelassen
          led_countdown=1;
        }
        if (led_countdown && !--led_countdown) green_off();	// grüne LED aus
        keystate^=keychange;
        keychange=0;
      }while (!(PINA&2));	// mit dem Lösen von Taste 2 speichern
      if (PINA&1) {	// Taste 1 nicht gedrückt?
        eeprom_write_byte(&ee.ch,b);	// abspeichern
        PowerUp();
        rf12::buf.type_len=0x81;	// 1 Byte Daten
        rf12::buf.sendrecv=wireless_id<<4; // lokale ID = ID, Nachricht an Busmaster (0)
        rf12::buf.data[0]=b;
        rf12::txdata(rf12::buf);	// für Nahbereich: Empfänger informieren
        rf12::channel=b;		// Frequenz ändern
	rf12::setfreq();
	PowerDown();
      }
raus:
      green_off();
      red_off();
    }
    if (keychange) {
      PowerUp();		// mit retry_countdown bleibt RFM12 aktiviert
      retry_countdown=4;
      resend_countdown=61;	// Nach jeweils 1 Sekunde wiederholen (KeepAlive)
      keystate^=keychange;
      keychange=0;	
    }
    if (retry_countdown) {
      rf12::buf.type_len=0x21;	// NeedAck, 1 Byte Daten
      rf12::buf.sendrecv=wireless_id<<4; // lokale ID = ID, Nachricht an Busmaster (0)
      rf12::buf.data[0]=keystate;
      rf12::txdata(rf12::buf);
      byte tic=TCNT0;
      do if (rf12::data()) {	// dann Empfang ohne TimeOut möglich
        if (!rf12::rxdata(rf12::buf)	// CRC OK
// Die oberen Bits müssen gleich sein, RequestAck muss 0 sein,
// IsAck muss 1 sein, Länge muss (hier) 0 sein
        && rf12::buf.type_len==0x10	// ACK-Bit und Länge 0
// Sender und Empfänger müssen vertauscht sein
	&& rf12::buf.sendrecv==wireless_id) {// Sender 0, Empfänger ID
	  red_off();		// rote LED aus
	  green_on();		// grüne LED ein
	  retry_countdown=0;
	  PowerDown();
	  led_countdown=3;	// 50 ms
	  goto ok;
	}
        break;
      }while (byte(TCNT0-tic)<150);	// gültige Antwort muss in dieser Zeit kommen
      red_on();		// rote LED ein
      green_off();	// grüne LED aus
      if (!--retry_countdown) PowerDown();
      led_countdown=14;	// 0,2 s (erscheint länger wegen Wiederholungen)
    }
ok: if (led_countdown && !--led_countdown) {
      red_off();
      green_off();
    }
  }
}
Detected encoding: UTF-80