Gewitter-Monitor           

von Stephan Laage-Witt    https://laagewitt.de/               
        
Elektronik-Labor  AVR  Projekte 

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 Hintergrund­beleuchtung 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 Gewitter­aktivitä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();
};
};
}



Elektronik-Labor  AVR  Projekte