Source file: /~heha/mb-iwp/Bergwerk/fba-fw-230825.zip/fba.cpp

/* Fernbedien-Adapter für Muldenkipper und Radlader.
+ Verarbeitet Fernbedienbefehle von:
 - Original-Fernbedienungsempfänger via IBUS am UART
 - Raspberry via I²C-Slave
 - Pampelmuse mit RFM69 = 433-MHz-Transceiver via SPI
+ Leitet Fernbedienbefehle weiter an
 - PWM-Ausgänge am 4017 und in Folge den Servos
 - Raspberry via I²C
 - Pampelmuse via SPI
+ Vermittelt Sensorsignale (Potenziometerstände, Tacho, Batteriespannug) an
 - Original-Fernbedienungsempfänger via IBUS am UART
 - Raspberry via I²C
 - Pampelmuse via SPI
 - Ultraschall-Kollisionsdetektor via Input Capture
+ PI-Regler für Position/Geschwindigkeit sowie 2-3 Hydraulikventile
+ Not-Aus (lokal und von Pampelmuse)
+ Rückwärtsfahrschalter?
+ Odometrische Positionsberechnung
  
Hardware: ATmega88 oder größer
		 ╔════════════════════╗
ISP		─╢PC6/!RESET   SCL/PC5╟─ I²C-Slave Raspi
IBUS-Servo	─╢PD0/RxD      SDA/PC4╟─ I²C-Slave Raspi
IBUS-Sensor	─╢PD1/TxD     ADC3/PC3╟─ Bordspannung
Not-Aus nach Lo	─╢PD2/INT0    ADC2/PC2╟─ Poti Löffel / Verwindung
Drehimpuls	─╢PD3/INT1    ADC1/PC1╟─ Poti Hubarm / Ladefläche
Debug		─╢PD4         ADC0/PC0╟─ Poti Lenkung
<3P3>		─╢Ucc              GND╟─
		─╢GND             AREF╟─ Kondensator nach GND
Baudratenquarz	─╢PB6/XTO         AUcc╟─ <3P3> über Drossel
Baudratenquarz	─╢PB7/XTI      SCK/PB5╟─ RFM69 und ISP
SR04:Puls	─╢PD5/OC0B    MISO/PB4╟─ RFM69 und ISP
SR04:Senden	─╢PD6/OC0A/A0 MOSI/PB3╟─ RFM69 und ISP
4017:15 Reset	─╢PD7/A1   SS/OC1B/PB2╟─ RFM69 Slave-Select
SR04:Empfang	─╢PB0/ICP     OC1A/PB1╟─ 4017:14 Zählen
		 ╚════════════════════╝
Der 4017 dient als Portexpander für bis zu 9 PWM-Ausgänge.
Daran sind angeschlossen:
Q0: muss frei bleiben
Q1: Lenkservo [links ↔]
Q2: Fahrmotor [links ↕] (nicht mehr über Magic Controller, um geräuschlos fahren zu können)
Q3: Hubarm / Ladefläche [rechts ↕]
Q4: Löffel / frei [rechts ↔]
Q5: Hydraulikpumpe [Knopf Rückseite links]
Q6: Magic Controller gelber Draht (Anlassen; Hupe) [Drehrad links]
Q7: Magic Controller grüner Draht (Licht) [Drehrad rechts]
Q8: Rundumleuchte [Knopf Rückseite rechts]
Q9: Magic Controller blauer Draht (Motor- und Fahrgeräusch)

Verwendung der Timer:
Timer0: 8 Bit, Vorteiler 1, CTC
 OC0A: 153, Zeitbasis für Ultraschall-Impulsgenerator: F_CPU/48kHz, Interrupt
 OC0B: 77, PWM-Ausgang (für 8 Impulse 1:1)
Timer1: 16 bit, Vorteiler 8, Messumfang 71 ms, für Ultraschall 12 m
 OC1A: Zum 4017, Interrupt
 OC1B: Timeout für Ultraschallmessung
 ICP1: Für Ultraschallmessung
Timer2: 8 bit, Vorteiler 1024, Takt 7,2 kHz (138µs), Messumfang 35,55 ms
 OC2A: Inter-Character-Timeout für RxD
 OC2B: Timeout für I²C
Watchdog: frei

TEMPORÄR DEBUG
PD5 = -
PD6 = Debugausgang I²C-Verklemmer
*/
#include "fba.h"
#include <avr/sleep.h>	// sleep_cpu
#include <math.h>	// lround
#include <string.h>	// memset
#ifndef EEAR
# define EEAR EEARL	// für ATmega48
#endif

Uhr uhr NOINIT;
Eedata eedata NOINIT;

/***************
 * Zeitmessung *
 ***************/

// Zur Kommunikation mit ISRs (max. 16 Bit)
struct Load{
 byte max,cnt;
 unsigned time;
 void reset() {time=cnt=max=0;}
}sleepload;

volatile Load isrload;	// cli()/sei() zum Auslesen und Löschen erforderlich

// Zur Kommunikation mit I²C (max. 32 Bit)
void Loadavg::prepare() {
 t1l+=TCNT1;
}

void Loadavg::reset() {
 memset(this,0,sizeof*this-4);
 cli();
 t1l = -TCNT1;		// unsauber aber genau genug
 t1h = 0;
 sei();
}

// Zur Load-LoadLong-Kommunikation (Bitbreiten aufblasend)
void Loadavg::LoadLong::on20ms(Load&src) {
 byte m1=max, m2=src.max; if (m1<m2) max=m2;
 cnt+=src.cnt;
 time+=src.time;
 src.reset();
}

inline void Loadavg::LoadLong::on20ms(volatile Load&src) {
 cli();
 on20ms(const_cast<Load&>(src));
 sei();
}

void Loadavg::on20ms() {
 isr.on20ms(isrload);		// mit cli()
 sleep.on20ms(sleepload);	// ohne cli()
}

Loadavg loadavg;

/******************
 * EEPROM-Zugriff *
 ******************/

void Eedata::read() {
 byte l=sizeof*this,*p=(byte*)this;
 byte a=0;
 do{
  EEAR=a;
  EECR|=1;		// Lesen auslösen
  *p++=EEDR;
  a++;
 }while(--l);
}

void Eedata::update() {
 if (EECR&2) return;	// beschäftigt
 byte l=sizeof*this;
 byte a = EEAR;
 do{
  if (a>=sizeof*this) a=0;
  EEAR=a;
  EECR|=1;		// Lesen auslösen
  byte o=EEDR, n=((byte*)this)[a];
  if (o!=n) {
   EECR&=~0x10; EECR&=~0x20;	// löschen+schreiben (3,4 ms)
   if (n==0xFF) EECR|=0x10;	// Nur löschen (1,8 ms)
   if (!(byte(~o)&n)) EECR|=0x20;// Nur schreiben (1,8 ms)
   EEDR=n;
   cli();
   EECR|=4;
   EECR|=2;
   sei();
   EECR|=8;			// Interrupt am Ende auslösen lassen
   return;
  }
  a++;
 }while(--l);
}

int main() {
 byte mcusr = MCUSR;	// Reset-Ursache einfangen
 MCUSR = 0;
// Ports initialisieren
 ACSR  = 0x80;
 SMCR  = 0b00000001;	// Sleep-Freigabe: Stoppt CPU
 PORTB =   0b010100;	// RFM69 noch nicht ansprechen; kein Pullup an ICP (vom LM324)
 DDRB  =   0b101111;
 PORTC =  0b1110000;	// Pullups an I²C (zusätzlich zu R4 und R5)
 DDRC  =  0b0000000;
 PORTD = 0b10010111;	// Freie Pins mit Pullups, ansonsten gibt's externe Pulldowns
 DDRD  = 0b11110000;	// TxD inaktiv
 DIDR0 = 0b00001111;	// Analogeingänge ohne Digitalfunktion
 eedata.read();
 if (mcusr&0x01) {	// Spannung unter 1,3 V?
// Vieles ist in .noinit, muss nur bei PowerOn-Reset gelöscht werden
  l4.kmz = 0;		// Daher bleibt bei BrownOut der Speicherinhalt erhalten
  uhr.reset();		// … und die Uhr stehen;
  l4.lamps = eedata.lamps;// Die anderen drei Lampenzustände werden vom Magic Controller gemerkt
  memset(&odo,0,sizeof odo);
 }			// bei PowerOn ist der Inhalt von .noinit zufällig.
// Bei Reset-Ursache: Reset-Pin ändert sich der Zustand der Rundumleuchte nicht.
// Ansonsten wird l4.flags initialisiert und l4.lamps vom EEPROM geladen
 if (mcusr&0x05) {	// PowerOn oder BrownOut = Spannung unter 2,7 V?
  l4.flags = 0x20;	// Rundumleuchte-Zustand: ein, rotierend (beim Einschalten)
			// Zustand der Zündung: ausgeschaltet (Bit 3 = 0)
			// Zustand des Blinkgeräusches: Wird ausgeschaltet angenommen
 }
// Reset-Zähler (4 Bytes) nutzen
 for (byte i=0; i<4; i++,mcusr>>=1) eedata.debug[i]+=mcusr&1;
// Timer1 vorbereiten (TODO: Capture-Interrupt auswerten)
 TCCR1A= 0b01000000;	// OC1A aktivieren: Toggeln
 TIMSK1= 0b00000011;	// Interruptfreigabe OC1A und bei Überlauf
 TCCR1B= 0b00000010;	// Kein CTC-Modus, Vorteiler 8; fallende Flanke an ICP triggert, ohne Filter
// Serielle Schnittstelle für IBUS-Servo aktivieren
 UBRR0 = F_CPU/115200/16-1;	// kommt 3 heraus
 UCSR0B= 0b10010000;	// Empfänger aktivieren mit Interrupt
 UCSR0C= 0b00001110;	// 2 Stoppbit, 8 Datenbits
// Impulszähler und Abstandsmessung aktivieren (an PD3 = INT1)
 spdCnt= 0;
 EICRA = 0b00001100;	// INT1 steigende Flanke
 EIMSK|= 1<<1;		// aktivieren
// Ultraschall-Sendeimpulsgenerator initialisieren (noch nicht starten)
 OCR0A = F_CPU/40000;	// abgerundet: 184
 OCR0B = F_CPU/40000/2;	// abgerundet: 92
 TCCR0A= 0b00110011;	// invertierte PWM am Ausgang OC0B=PD5 (erstmal Low)
 TIMSK0= 0b00000010;	// OCIE0A: Pulse zählen per Software
// Timer2 laufen lassen
 OCR2A = F_CPU/1024/1000;// (7) 1 ms Inaktivität bei TxD feststellen lassen
 TCCR2B= 0b00000111;	// Laufen lassen mit Vorteiler 1024
 TIMSK2= 1<<1;		// Interrupt bei Inaktivität = Lücke
#if 0
 WDTCSR= 1<<4;		// WDCE setzen
 WDTCSR= 0b11000100;	// Watchdog: Interrupt nach 250 ms
#endif
// A/D-Wandler starten
 l4.adcstart();
 L2::t1init();		// Sofort zur CompareA-Interruptbehandlung (Assembler)
 I2C::init();
 char ticks = 0;	// vzb. Ticks pro 20 ms (nicht gemittelt)
 byte stage = 0;
 for(;;) {
  if (!ADCSRA) {	// A/D-Wandler fertig? (ISR setzt ADCSRA auf Null)
   ADCSRA = 0b00011110;	// Immer noch aus, Hauptsache ≠0
// assert(!stage);
   ticks = l4.save();
// Da der A/D-Wandler während der Reset-Phase des 4017 fertig wird,
// werden hier die neuen PWM-Werte gesetzt.
// Möglicherweise keine gute Idee; besser: Reset-Phase gesondert in ISR erfassen!
   L2::k7hold_handler();	// Wirklich 50 Hz?
   L2::autopuls_handler();
   L2::countdown();
  }
  if (GPIOR0&1<<1) {	// Simulierter CTC-Interrupt Timer1? (50 Hz)
   GPIOR0&=~(1<<1);
   ++uhr;		// Mit exakt 50 Hz weiterzählen
   l4.adcstart();	// Start der Akkumulierung
   byte f=l4.flags&0xF8;
   if (l4.lin[2]<-200) f|=1<<0;		// Bit 0: Rückfahrtaster
   if (!notaus::released()) f|=1<<1;	// Bit 1: Notaus-Stellung (Pin ist Lo)
   if (notaus::forced()) f|=1<<2;	// Bit 2: Notaus-Forcierung (Pin ist Lo-Ausgang)
   l4.flags=f;
   stage = 3;
  }
  l3.checkIbusRx();
  I2C::poll();
// Die Rechenlast der weiteren Verarbeitungsschritte ist aufgeteilt.
// So als ob jedesmal dazwischen die Flags abgecheckt werden.
  switch (stage) {
   case 3: l4.on20ms(ticks); ++stage; break;	// Linearisierung
   case 4: odo.lenkTo(l4.lin[0]); ++stage; break;
   case 5: Regler::on20ms(); ++stage; break;	// Regler
   case 6: odo.on20ms(ticks); ++stage; break;	// Odometrie
   case 7: L7::on20ms(ticks); ++stage; break;	// Job-Abarbeitung
   case 8: eedata.update(); stage=9; break;	// EEPROM aktualisieren (niedrig priorisiert)
   case 9: loadavg.on20ms(); stage=0; break;
  }
  if (!stage) {
   cli();
   sleepload.time-=TCNT1;
   GPIOR0|=1<<3;	// Aufweckende ISR stoppt die Zeit
   PORTD&=~(1<<4);	// Rechenlast an PD4 anzeigen
   sei();
   sleep_cpu();
   byte c=sleepload.cnt++;
   if (c) sleepload.cnt=c;	// nicht (zu Null) überlaufen lassen
  }
 }
}
Detected encoding: UTF-80