Nach
Auskunft der Meteorologen lässt der Klimawandel es häufiger
blitzen. Mit jedem Grad globaler Erwärmung steigt die Zahl der
Blitze. Dabei haben die gewaltigen
elektrostatischen Entladungen mit den entfesselten Naturkräften
nichts von ihrer Faszination verloren. Um so mehr ein Grund, sich als
Hobby-Elektroniker mit diesem Thema zu beschäftigen.
So
habe ich mit viel Interesse die Diskussionen und Entwicklungen im
Elektronik-Labor verfolgt. Der resultierende Franzis-Bausatz
„Gewitterwarner“ ist ein schönes Gerät mit
allerlei genialen Ideen, zum Beispiel, den AM-Empfänger TA7642
zum Empfang zu verwenden, die Empfindlichkeit durch die
Betriebsspannung einzustellen, und die Blitze durch einen
Flankendetektor im Mikrocontroller zu erfassen. Super!
Für
meine eigenen Versuche wollte ich einen Schritt weiter gehen und den
Analog-Digital-Konverter (ADC) des Mikrocontrollers dazu verwenden,
Blitze mit hoher Empfindlichkeit über weite Entfernungen zu
erfassen und quantitativ auszuwerten. Außerdem wollte ich den
Verlauf der Intensität und Häufigkeit der Blitze über
die Zeit aufzuzeichnen. Herausgekommen ist mein „Gewitter-Monitor“,
den ich hier beschreibe.
Bild
1: Der Gewitter-Monitor besteht aus zwei Baugruppen: Links der
Empfangsteil mit zwei rechtwinklig angeordneten Ferritstäben und
rechts das eigentliche Gerät mit Mikrocontroller und graphischen
Display
Die
Idee war, ein Gerät zu entwickeln, das
elektrostatische Aktivitäten in der Atmosphäre beobachtet
und Auskunft über die Häufigkeit und Stärke von
Blitzen gibt. Mir ging es dabei um große Reichweite, so dass
Gewitter lange vor dem Erscheinen von Blitz und Donner am Ort erkannt
werden. Außerdem wollte ich den Verlauf aufzeichnen, zum
Beispiel um zu sehen, ob die Blitz-Aktivitäten zu- oder
abnehmen.
Das
Design ist einfach: Die elektromagnetischen Impulse von Blitzen
werden mit dem AM-Radio-Chip TA7642 empfangen, verstärkt und mit
dem ADC eines ATmega ausgewertet. Der ADC erfasst für jeden
registrierten Blitz den maximalen Pegel. Der Mikrocontroller summiert
die Maxima und berechnet die Summe pro Minute. Ein graphisches
LC-Display (GLCD) zeigt den Verlauf, und mit einer Leuchtdiode wird
ein Warnlevel angezeigt.
Analogteil:
Hier eine kurze Beschreibung der Schaltung. Das Signal der Blitze
erreicht die Schaltung über die Antennen. Ich habe die
Eingangsschaltung vom Franzis-Gewitterwarner mit TA7642 und
nachgeschaltetem Transistor weitgehend übernommen. Allerdings
wollte ich die Empfangsfrequenz so tief wie möglich legen, um
näher an das Frequenzmaximum von Gewitterblitzen zu kommen. Der
Antennenschwingkreis besteht aus einer Spule von 10 cm Länge auf
einem Ferritstab, 0.3 mm Kupferdraht, und zwei parallel-geschalteten
470 pF Kondensatoren. Die Resonanzfrequenz liegt ungefähr bei
100 kHz. Das ist so ziemlich am unteren Rand des Frequenzbereiches,
den der TA7642 noch sinnvoll verstärkt (siehe „Frequenzgang
des TA7642“, Elektronik-Labor). Aber es reicht noch aus für
gute Empfindlichkeit.
Ferritantennen
haben eine Richtwirkung. Deshalb kommen zwei Antennen zum Einsatz,
die rechtwinklig zu einander und horizontal montiert sind. Die
horizontale Anordnung bewirkt, dass die häufigeren
Wolke-zu-Wolke Blitze erfasst werden. Die bereits zitierten
Meteorologen sagen, dass im Durchschnitt 60% der Blitze zwischen den
Wolken stattfinden, also horizontal orientiert sind, während die
verbleibenden 40% zwischen Wolke und Erde verlaufen. Der eine
Ferritstab hat seine höchste Empfindlichkeit in
Nord-Süd-Richtung, der andere in Ost-West-Richtung. Die
Richtungsinformation, die sich daraus ergibt, lässt sich
allerdings nur bedingt verwerten, da die Maxima der Ferritantennen
sehr breit sind. Das GLCD zeigt deshalb die Summe über beide
Kanäle.
Bild
2: Antennen über Kreuz: Hier werden alle Himmelsrichtungen
erfasst. Die Ferritstäbe sind zusammen mit den TA7642 auf
eigenen Platinen montiert. Betriebsspannung und Ausgangssignale
werden über ein kurzes Flachbandkabel an das Gerät
angekoppelt.
Die
Spannungsversorgung der beiden TA7642 stellt gleichzeitig die
Empfangsempfindlichkeit ein. Ich verwende eine
Konstantspannungsquelle mit einer Bandgap-Referenz TL431, die als
einstellbare Zenerdiode fungiert. Mit einem Trimmer lässt sich
die Spannung und damit die grundsätzliche Empfindlichkeit der
Antennen einstellen. Ich habe mit Spannungswerten zwischen 1.5 und
1.6 Volt am 1 kOhm-Widerstand vor dem Empfänger-IC (siehe
Schaltung) die besten Erfahrungen gesammelt.
Blitze
sind seltene und sehr kurze Ereignisse. Um sie zuverlässig mit
dem ADC zu erfassen, müsste man den ADC mit einer hohen
Abtastfrequenz betreiben. Ich habe mich für einen anderen Weg
entschieden und mit Hilfe des Operationsverstärkers MCP601 einen
Maximalwerte-Speicher dazwischen geschaltet. Solche
Peak-Detection-Schaltungen waren früher bei
Audio-Aussteuerungsanzeigen, z.B. LED-Balken-Anzeigen, verbreitet.
Der Ausgang des OpAmp lädt über eine Diode eine
RC-Kombination (22 nF und 1 MOhm), die sich dann entsprechend ihrer
Zeitkonstante wieder entlädt. Der Spannungswert steht damit
ausreichend lange am ADC-Eingang zur Verfügung, so dass eine
gemächliche ADC-Abtastrate von 250 Hz pro Kanal gut
funktioniert. Da beide Antennen im Wechsel abgefragt werden, läuft
der ADC mit 500 Hz, was immer noch ein gut verträgliches Tempo
für den ATmega ist. Bild 3 zeigt ein Oszilloskop-Bild am Eingang
und Ausgang der Peak-Detection Schaltung.
Bild
3: Messung am Peak-Detektor: Kanal 1 (blau) zeigt das Eingangssignal
am OpAmp mit zwei kurzen Blitz-Impulsen. Kanal 2 (gelb) zeigt das
langsam abfallende Ausgangssignal. In jedes 10 mS Intervall fallen im
Schnitt 2.5 ADC-Wandlungen pro Kanal.
Schließlich
enthält der Analogteil noch einen kleinen Audioverstärker,
den man als akustischen Monitor zuschalten kann. Das ist zum Beispiel
sehr nützlich, um einen geeigneten Aufstellungsort mit geringen
Störsignalen zu finden.
Bild
4: Schaltung des Analogteils
Der
Digitalteil ist um den bewährten Atmega168 herum
aufgebaut. Der Mikrocontroller wird mit einem 8 MHz-Quarz getaktet.
Für den Betrieb der graphischen Anzeige ist eine ganze Menge
Festkomma-Arithmetik notwendig, die zum Teil zeitkritisch ist.
Deshalb ist die Taktrate von 8 MHz durchaus angemessen.
Die
Ausgangsspannungen vom Analogteil erreichen den Mikrocontroller an
den Eingängen ADC0 und ADC1. Der ADC wird so konfiguriert, dass
er die interne Spannungsreferenz von 1.1V verwendet. Dieser
Spannungslevel passt sehr gut zu den Ausgängen des Analogteils
und kann ohne weitere Anpassung direkt verwendet werden.
Zwei
weiße Leuchtdioden an Port C4 und C5 blinken kurz auf, wenn ein
Blitz registriert wird. Die rote Leuchtdiode als Anzeige der zweiten
Warnstufe wird an Port C3 betrieben. Port C2 schaltet über einen
PNP-Transistor die Hintergrundbeleuchtung des Displays, die
automatisch per Software aktiviert wird, wenn die erste Warnstufe
erreicht ist. Für neugierige Zeitgenossen wie mich gibt es einer
Taste an Port B0, der die Hintergrundbeleuchtung manuell für
30 Sekunden aktiviert.
Bild
5: Schaltung des Digitalteils
Das
Graphik-Display von Sitronix war ein günstiger Kauf im Internet
und lag schon eine ganze Weile auf meinem Arbeitstisch in Erwartung
von interessanten Aufgaben. Seit einiger Zeit werden diese Displays
mit einer Auflösung von 128 x 64 Pixeln für wenig Geld
angeboten. Für mich war es das erste Projekt mit diesem
Displaytyp, und es geht erstaunlich einfach. Der Display-Controller
ST7920 hat die angenehme Eigenschaft, ein paralleles und ein
serielles Interface mitzubringen (siehe GLCD Datenblatt). Wenn der
Anschluss PSB auf Masse liegt, wird das Interface auf serielles SPI
geschaltet. Dann sind nur 3 Leitungen zur Signalübertragung
erforderlich. Zusammen mit den Stromversorgungen für Display und
Hintergrundbeleuchtung kann man das Display mit nur 6 Leitungen am
Mikrocontroller betreiben. Einfacher geht es nicht!
Die
andere nützliche Eigenschaft des Displays ist, dass es dazu eine
fix- und fertige Software-Bibliothek in C gibt, u8glib, die eine
umfangreiche Sammlung von Treibern und Graphik-Primitiven anbietet
(siehe U8glib). In meinem Programm kommen die Funktionen DrawPixel,
DrawLine, und DrawString zum Einsatz. Tatsächlich funktionierte
alles auf Anhieb. Ein Dank an die Macher dieser vielseitigen
Bibliothek!
Bild
6: Das Graphik-Display mit SPI-Interface benötigt nur 6
Leitungen zum Controller.
Die
Software wurde in C
entwickelt und wird direkt mit einem AVR-Programmer in den
Flash-Speicher des Mikrocontroller geschrieben. Kernstück des
Programms ist der regelmäßige Timer-Interrupt, der die
Interrupt-Routine mit einer Frequenz von 500 Hz abarbeitet. Dort
werden abwechselnd die beiden Antennensignale über die
ADC-Kanäle eingelesen. Ein einfacher Software-Flankendetektor
sucht nach Sprüngen zwischen zwei Werten, die einen Grenzwert
(hier 150) übersteigen. Wenn ein solcher Sprung gefunden ist,
sucht die Routine das Maximum des Blitzes und meldet an das
Hauptprogramm, dass es etwas zu tun gibt. Das Hauptprogramm zeigt den
gefundenen Maximalwert im Display auf der rechten Seite an. Außerdem
berechnet es die Summe der Maxima pro Minute und produziert die
Balkengraphik mit einer senkrechten Pixelreihe pro Minute. Das
Display bietet Platz für die Aufzeichnung von 100 Minuten, wobei
die alten Werte im Minutenrhythmus nach links geschoben werden.
Schließlich wir einmal pro Minute eine lineare Regression der
letzten 30 Minuten berechnet. Die resultierende Steigung wird unten
rechts im Display angezeigt und ist eine Trendanalyse, ein Maß,
ob der Pegel der Blitze in den letzten 30 Minuten angestiegen (=
positive Werte) oder abgefallen (= negative Werte) ist. Allerdings
ist der praktische Nutzen dieser Trendanalyse durchwachsen. Da gibt
es noch Raum für Experimente.
Die
Skalierung muss noch erklärt werden: Der ADC liefert
Blitz-Maxima mit Werten zwischen 150 und 1024. Diese Werte werden auf
15 bis 100 skaliert. Der Wert 100 heißt also, dass ein Blitz
den ADC maximal ausgesteuert hat. Die Werte werden pro Minute
summiert. Eine Minuten-Summe von 500 zum Beispiel bedeutet, dass 5
maximal-starke Blitze registriert wurden, oder entsprechend eine
größere Zahl schwächerer Blitze . Die Skala der
Y-Achse reicht im Normalfall 0 bis 600 mit Markierungen in
100er-Schritten.
Es
gibt zwei Warnstufen: Wenn eine Minutensumme von 250 oder mehr
erreicht wird, schaltet sich das Display automatisch ein. In der
Regel ist dann von einem Gewitter noch lange nichts zu sehen oder zu
hören. Die Signale kommen (noch) aus großer Entfernung.
Wenn die Minutensumme 500 übersteigt, dann wird zusätzlich
die rote Leuchtdiode aktiviert. Außerdem wird die Skala der
Graphik auf einen Bereich von 0 bis 1200 umgeschaltet. Bei diesen
Pegelwerten ist das Gewitter schon näher, zum Beispiel mit
Wetterleuchten in der Ferne. Die Warnstufen werden nach 5 Minuten mit
geringeren Pegeln wieder zurückgesetzt.
Zum
Testen von Hard- und Software braucht man nicht auf das nächste
Gewitter zu warten. Ich habe mir mit einer weitgehend verbrauchten
9-Volt Block-Batterie einen „Blitz-Generator“ gebaut.
Zwei etwa 10 cm lange Drahtstücke an die Batterieklemmen gelötet
verwandeln die Batterie. Wenn man die blanken Drahtenden kurz
miteinander in Kontakt bringt und das in 10 oder 20 cm Entfernung
macht, dann sollte dieses Minigewitter Signale auf der Anzeige
produzieren.
Praxiserfahrungen:
Ich habe das Gerät jetzt seit zwei Monaten in Betrieb und
beobachte natürlich gespannt die Wetteraktivitäten. Der
erste Eindruck: Die Empfindlichkeit ist enorm, besonders bei Nacht,
wenn langwellige Radiosignale eine weite Ausbreitung haben. Wir
hatten einige Nächte mit heftigen Gewittern in Südfrankreich
oder Italien, immerhin 600 km oder mehr von meinem Wohnort entfernt.
Ich konnte die Gewitteraktivitäten mit Signalpegeln von 200
oder 300 pro Minute gut beobachten. Im August und September gab es
kaum eine Nacht ohne empfangene Gewittersignale. Erst im Oktober
wurde es ruhiger und brachte dann auch Tage und Nächte ganz ohne
Aktivitäten auf der Anzeige.
Der
Durchzug eines Wetterwechsels kündigt sich fast immer mit
Gewitteraktivitäten an, die schon von weiter Entfernung als
Signale auf der Anzeige erscheinen. Wenn ein Gewitter näher
kommt und wir zum Beispiel am Abend auf der Terrasse Wetterleuchten
beobachten können, dann zeigt das Gerät Pegel von 500 und
mehr pro Minute. Wenn Gewittergrollen zu hören und ein Gewitter
in direkter Nähe ist, dann ist das Gerät vollständig
aus- und zum Teil auch übersteuert. Es werden durchgängig
Maximalwerte angezeigt. Die Bilder 7 und 8 zeigen, wie die Anzeige
funktioniert und einige Beispiele.
Im
Laufe der Zeit hat sich das Gerät als eine schöne
Erweiterung zu den verbreiteten Wetterstationen etabliert und gibt
bereitwillig Auskunft über die atmosphärischen Aktivitäten
und die Wetterlage.
Bild
7: Viel Information auf kleinem Raum: So sieht die Anzeige an einem
gewittrigen Abend aus. Die Gewitterfront ist mehrere hundert km weit
weg, kommt aber stetig näher.
Bild
8: Im linken Bild ist eine Gewitterzelle in der Ferne vorbeigezogen,
ohne wirklich näher zu kommen. Im rechten Bild hat sich
innerhalb von 40 Minuten ein heftiges lokales Gewitter entwickelt.
Die rote Leuchtdiode wurde gesetzt, und die Skala auf den größeren
Bereich umgeschaltet.
Referenzen
C-Quelltext:
Gewitter-Monitor.zip/*----------------------------------------------------------------------------------------------
Gewitter-Monitor
Stephan Laage-Witt - Oktober 2014
Version 1.09
---------------------------------------------------------------------------------------------------*/
#define F_CPU 8000000 // 8 MHz Quarz
#define IRQ_FREQ 500 // Anzahl Interrupts pro Sekunde
#define STRIKE_LED_TIME IRQ_FREQ / 2 // Blinkzeit der LED: eine halbe Sekunde
#define STEEPNESS_THRESHOLD 150 // Schwellwert für Flankendetektor
#define ALARM_TIME 300 // Zeit, nach der die Alarm-Level zurückgesetzt wird (sec)
#define BACKLIGHT_TIME 30 // LCD-Backlight-Zeit nach Tastendruck (sec)
#define ALARM_1_THRESHOLD 250 // Schwellwert für Alarm-Level 1
#define ALARM_2_THRESHOLD 500 // Schwellwert für Alarm-Level 2
// Pin-Belegung
#define PIN_STRIKE_LED_NS 4
#define PIN_STRIKE_LED_OW 5
#define PIN_ALARM_LED 3
#define PIN_LCD_BACKLIGHT 2
#define PIN_LCD_BACKLIGHT_SWITCH 0
#include "u8g.h"
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/sleep.h>
// Globale Variablen, die von der INterruptroutine verändert werden
volatile uint8_t to_do_flag; // zur Kommunikation zwischen Interrupt und Hauptprogramm
volatile uint16_t max_ns = 0, // Peak-Werte der Blitze und Summe pro Minute
max_sum_ns = 0,
max_ow = 0,
max_sum_ow = 0;
volatile uint16_t backlight_counter = ALARM_TIME; // Verbleibende Zeit der LCD-Hintergrundbeleuchtung
volatile uint8_t sec = 0; // Sekundenzähler
// Globale Variablen ohne Interrupt-Beteiligung
u8g_t u8g; // Graphik-Handle, wird von U8glib verwaltet
uint8_t strike_array_ns[100], // Umgerechnete Summe pro Minute für Display
strike_array_ow[100];
uint8_t strike_array_pointer = 0; // Pointer für strike_arrays
char time_string[3] = "00"; // Display: Sekundenzähler
char string_max_ns[6], // Display: letzter registrierter Blitz
string_max_sum_ns[6], // Display: Summe für die laufende Minute
string_max_ow[6], // Display: letzter registrierter Blitz
string_max_sum_ow[6], // Display: Summe für die laufende Minute
string_b[6] = " 0.0"; // Display: Steigung der linearen Regression
/* Interrupt Service Handler für 16 Bit Timer 1 --------------------------------------------------
Fragt ADC ab, abwechselnd für die beiden Kanäle. Sucht ansteigende Flanke und Maximum.
Schaltet Strike-LEDs.
*/
ISR(TIMER1_COMPA_vect) {
static uint16_t counter = 0, old_value_ns, old_value_ow;
static uint16_t strike_LED_counter_ns = 0, strike_LED_counter_ow = 0;
static uint8_t channel = 0, mode_ns, mode_ow;
uint16_t value;
if (++counter == IRQ_FREQ) { // Einmal pro Sekunde das Hauptprogramm aktivieren
to_do_flag |= 1;
counter = 0;
if (++sec == 60) sec = 0;
};
if ((PINB & (1<<PIN_LCD_BACKLIGHT_SWITCH)) == 0) { // Taste für LCD-Beleuchtung gedrückt?
PORTC &= ~(1 << PIN_LCD_BACKLIGHT); // LCD-Beleuchtung einschalten
backlight_counter = BACKLIGHT_TIME;
}
value = ADCW; // ADC auslesen
if (value > 1000) value = 1000; // Werte werden auf 1000 begrenzt
if (channel == 0) { // Nord-Süd abfragen
switch (mode_ns) {
case 0: // Ansteigende Flanke suchen
if (value > (old_value_ns + STEEPNESS_THRESHOLD)) {
mode_ns = 1;
};
break;
case 1: // Maximum suchen
if (value < old_value_ns) { // Maximum erreicht?
max_ns = old_value_ns / 10; // Maximum auf 0...100 reduzieren
max_sum_ns += max_ns;
mode_ns = 0;
PORTC &= ~(1 << PIN_STRIKE_LED_NS); // Strike-LED einschalten
strike_LED_counter_ns = STRIKE_LED_TIME;
};
break;
}
} else { // Ost-West abfragen
switch (mode_ow) {
case 0: // Ansteigende Flanke suchen
if (value > (old_value_ow + STEEPNESS_THRESHOLD)) {
mode_ow = 1;
};
break;
case 1: // Maximum suchen
if (value < old_value_ow) { // Maximum erreicht?
max_ow = old_value_ow / 10; // Maximum auf 0...100 reduzieren
max_sum_ow += max_ow;
mode_ow = 0;
PORTC &= ~(1 << PIN_STRIKE_LED_OW); // Strike-LED einschalten
strike_LED_counter_ow = STRIKE_LED_TIME;
};
break;
};
};
// Strike-LEDs checken und gegebenenfalls abschalten
if (strike_LED_counter_ns > 0) --strike_LED_counter_ns;
else PORTC |= (1 << PIN_STRIKE_LED_NS); // Strike-LED 1 ausschalten
if (strike_LED_counter_ow > 0) --strike_LED_counter_ow;
else PORTC |= (1 << PIN_STRIKE_LED_OW); // Strike-LED 2 ausschalten
// ADC neu starten
ADMUX = (1<<REFS1) | (0<<ADLAR) | channel; // Kanal setzen
ADCSRA |= (1<<ADSC); // ADC starten
// Vorherigen Wert sichern und ADC-Kanal setzen
if (channel == 0) {
old_value_ns = value;
channel = 1;
} else {
old_value_ow = value;
channel = 0;
};
}
/* Draw-Routine für u8g-Graphik ----------------------------------------------------------- */
void draw(void)
{
uint8_t i;
uint8_t strike_value_ns, strike_value_ow; // Lokale Kopie der Strike-Values
// Aktuelle Daten auf der rechten Display-Seite ausgeben
u8g_SetFont(&u8g, u8g_font_5x8);
u8g_DrawStr(&u8g, 119, 6, time_string);
u8g_DrawLine(&u8g, 119, 8, 127, 8);
u8g_DrawStr(&u8g, 114, 18, string_max_ow);
u8g_DrawStr(&u8g, 109, 26, string_max_sum_ow);
u8g_DrawLine(&u8g, 114, 28, 127, 28);
u8g_DrawStr(&u8g, 114, 38, string_max_ns);
u8g_DrawStr(&u8g, 109, 46, string_max_sum_ns);
u8g_DrawLine(&u8g, 109, 48, 127, 48);
u8g_DrawStr(&u8g, 104, 58, string_b);
// Graphik ausgeben
for (i = 0; i < 100; ++i) {
strike_value_ns = strike_array_ns[strike_array_pointer];
strike_value_ow = strike_array_ow[strike_array_pointer];
// Falls Alarm-Level-2 gesetzt ist, Werte halbieren für erweiterte Skala (0 ... 1200)
if ((PORTC & (1<<PIN_ALARM_LED)) == 0) {
strike_value_ns /= 2;
strike_value_ow /= 2;
};
// Werte auf Displaygröße begrenzen (0 ... 63)
if ((strike_value_ns + strike_value_ow) > 63) {
strike_value_ns = 63;
strike_value_ow = 0;
};
// Senkrechte Pixelreihe zeichnen
u8g_DrawLine(&u8g, i, 63, i, 63 - strike_value_ns - strike_value_ow);
if (strike_value_ns > 0) {
u8g_SetColorIndex(&u8g, 0);
u8g_DrawPixel(&u8g, i, 63 - strike_value_ns);
u8g_SetColorIndex(&u8g, 1);
};
// Pointer heraufzählen
if (++strike_array_pointer == 100)
strike_array_pointer = 0;
};
// Y-Skalenpunkte und Beschriftung zeichnen
u8g_SetFont(&u8g, u8g_font_4x6);
if ((PORTC & (1<<PIN_ALARM_LED)) == 0) {
for (i = 1; i < 13; ++i)
u8g_DrawPixel(&u8g, 100, 63 - i * 5);
u8g_DrawStr(&u8g, 102, 6, "1200");
u8g_DrawStr(&u8g, 102, 36, "600");
} else {
for (i = 1; i < 7; ++i)
u8g_DrawPixel(&u8g, 100, 63 - i * 10);
u8g_DrawStr(&u8g, 102, 6, "600");
u8g_DrawStr(&u8g, 102, 36, "300");
};
// X-Skalenpunkte alle 10 Minuten zeichnen
for (i = 0; i < 100; i += 10) {
u8g_DrawPixel(&u8g, i, 0);
};
}
/* lcd_dec_unit32 --------------------------------------------------------------------------------*/
/* Gibt einen 32-Bit-Wert als Dezimalzahl aus. */
/* positions -> Anzahl der Stellen (2 ... 7) */
/* dec_point -> Anzahl der Nachkommastellen (0 ... positions) */
void lcd_dec_uint32(uint32_t value, char string_array[], uint8_t positions, const uint8_t dec_point)
{
uint8_t count, blank_flag = 0, pos = 0;
uint32_t denominator = 10;
for (count = 2; count < positions; ++count)
denominator *= 10;
while (positions > 0) {
count = value / denominator;
value -= count * denominator;
if (positions == dec_point + 1) blank_flag = 1;
if (positions == dec_point) {
string_array[pos] = '.';
++pos;
};
if ((count == 0) && (blank_flag == 0)) {
string_array[pos] =' ';
++pos;
} else {
string_array[pos] = '0' + count;
++pos;
blank_flag = 1;
};
--positions;
denominator /= 10;
};
string_array[pos] = 0;
}
/* Calculate_B ------------------------------------------------------------------------------------*/
/* Simplifizierte lineare Regression über die letzten 30 Minuten */
/* Gibt den berechneten Steigungskoeffizienten B zurück */
int16_t calculate_b (uint8_t pointer) {
int8_t i, value;
int32_t sum_value, sum_valuexi;
int16_t result;
sum_value = 0;
sum_valuexi = 0;
for (i = 0; i < 30; ++i) {
value = strike_array_ns[pointer] + strike_array_ow[pointer];
sum_value += value;
sum_valuexi += (value * i);
if (pointer == 0) pointer = 99;
else --pointer;
};
result = (87 * sum_value - 6 * sum_valuexi) / 135;
return(result);
};
/* Hauptprogramm -----------------------------------------------------------------------------------*/
int main(void) {
uint8_t i;
int16_t b;
uint16_t alarm_counter = 0;
// Input/Output-Register setzen
DDRB = (0 << PIN_LCD_BACKLIGHT_SWITCH); // Pin ist Input für LCD-Beleuchtungs-Taster
PORTB = (1 << PIN_LCD_BACKLIGHT_SWITCH); // Pull-Up Widerstand einschalten
DDRC = 0b11111100; // ADC-Eingänge sind Input
PORTC = 0b11111100; // Alle Funktionen ausschalten
// Sleep Mode setzen: Timer und Interrupts laufen weiter
set_sleep_mode(SLEEP_MODE_IDLE);
// ADC initialisieren
ADMUX = (1<<REFS0) | (0<<ADLAR); // 10 bit, right adjusted. Interne Referenz verwenden
ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0); // scaling factor: 128 -> 125 kHz (page 257)
ADMUX = (1<<REFS1) | (0<<ADLAR) | 0; // Kanal setzen
ADCSRA |= (1<<ADSC); // ADC starten
// U8GLIB initialisieren
u8g_InitSPI(&u8g, &u8g_dev_st7920_128x64_4x_hw_spi, PN(1, 5), PN(1, 3), PN(1, 2), PN(1, 1), U8G_PIN_NONE);
// Strike-Arrays initialisieren
for (i = 0; i < 100; ++i) {
strike_array_ns[i] = 0;
strike_array_ow[i] = 0;
};
// 16-Bit Timer1 starten, 8000000 / 64 / 250 => 500 Hz
OCR1A = 250-1;
TCCR1B |= (1<<WGM12)| (0<<CS02) | (1<<CS01) |(1<<CS00); // CPU-Frequenz / 64, CTC1 = Clear Timer on Compare
TIMSK1 |= (1 << OCIE1A);
sei(); // Interrupt aktivieren - los geht's
while (1) {
if (to_do_flag) { // einmal pro Sekunde ...
to_do_flag = 0; // To-Do-Flag löschen
time_string[0] = (sec / 10) + '0'; // Sekundenzähler als String für die Ausgabe vorbereiten
time_string[1] = (sec % 10) + '0';
if (sec == 0) { // einmal pro Minute ...
strike_array_ns[strike_array_pointer] = max_sum_ns / 10; // Summen pro Minute auf Graphik-Skala umrechnen ...
strike_array_ow[strike_array_pointer] = max_sum_ow / 10; // ... und in das Strike-Array übernehmen
max_sum_ns = 0;
max_sum_ow = 0;
if (++strike_array_pointer == 100) // Strike-Array-Pointer heraufzählen
strike_array_pointer = 0;
// Lineare Regression berechnen und String-Ausgabe mit Vorzeichen als String vorbereiten
b = calculate_b(strike_array_pointer);
if (b < 0) {
b = -b;
string_b[0] = '-';
} else if (b > 0) {
string_b[0] = '+';
} else {
string_b[0] = ' ';
};
lcd_dec_uint32(b, string_b + 1, 3, 1);
};
// LCD-Backlight und Alarm-LED setzen
if ((max_sum_ns + max_sum_ow) >= ALARM_1_THRESHOLD) {
PORTC &= ~(1 << PIN_LCD_BACKLIGHT); // LCD-Hintergrundbeleuchtung einschalten
backlight_counter = ALARM_TIME;
};
if ((max_sum_ns + max_sum_ow) > ALARM_2_THRESHOLD) {
PORTC &= ~(1 << PIN_ALARM_LED);
alarm_counter = ALARM_TIME;
};
if (!(PORTC & (1 << PIN_LCD_BACKLIGHT))) { // Display-Ausgabe nur, wenn die LCD-Beleuchtung aktiv ist
// Strike-Werte als String für die Ausgabe vorbereiten
lcd_dec_uint32(max_ns, string_max_ns, 3, 0);
lcd_dec_uint32(max_sum_ns, string_max_sum_ns, 4, 0);
lcd_dec_uint32(max_ow, string_max_ow, 3, 0);
lcd_dec_uint32(max_sum_ow, string_max_sum_ow, 4, 0);
// Graphik-Ausgabe
u8g_FirstPage(&u8g);
do {
draw();
} while (u8g_NextPage(&u8g));
};
// LCD-Backlight und Alarm-LED checken und gegebenenfalls ausschalten
if (backlight_counter > 0) --backlight_counter;
else PORTC |= (1 << PIN_LCD_BACKLIGHT);
if (alarm_counter > 0) --alarm_counter;
else PORTC |= (1 << PIN_ALARM_LED);
} else {
sleep_mode();
};
};
}