/* 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-8 | 0
|