home     Inhaltsverzeichnis
erste Version am 16.12.2017
letzte Änderung am 24.12.2017

LED-Streifen als Nachtlicht - Seite 3


Firmware

Jetzt ist die Hardware fertig aufgebaut. Der Bewegungsmelder sitzt zusammen mit der blauen Status-LED und dem Fototransistor im abgesetzten Gehäuse.
Das Flachbandkabel zwischen Schaltung und Gehäuse ist ca. 130cm lang und 10-poligig. Eigentlich werden ja nur fünf Leitungen gebraucht. Aber weil 10-polige Wannenstecker und Pfostenbuchsen die kleinsten waren, die ich vorrätig hatte, sind nun alle Leitungen redundant ;-) ausgelegt.
Foto vom Sensor-Gehäuse des ersten
        Nachtlichts

Ein bischen blöd ist, dass die blaue Status-LED innerhalb des Gehäuses den Fototransistor beleuchtet - und somit dessen Messwerte verfälscht.
Statt den Fototransistor gegen Licht von innen abzuschirmen, werde ich die LED wohl lieber nur dann einschalten, wenn gerade keine Helligkeit gemessen werden soll.

Tests bzw. Erkenntnisse:
  1. Der analogReference(INTERNAL) überlebt den Tiefschlaf bzw. muss danach nicht neu eingestellt werden.
  2. PWM-Pins werden im Tiefschlaf erwartungsgemäß nicht bedient. Allerdings kann der Aufruf von deepSleep() bei einem aktiven analogWrite(PIN, val) dazu führen, dass der LED-Streifen mit maximaler Helligkeit angesteuert wird, obwohl val nur auf z.B. 32 steht. Wahrscheinlich liegt es daran, dass das PWM-Signal beim Eintritt in den deepSleep() zufällig gerade auf HIGH stand. Dementsprechend braucht es einen analogWrite(PIN, 0) zu Beginn von deepSleep().

Mittlerweile läuft das Nachtlicht produktiv. Naja ... erstmal ...
Gerade habe ich den Schwellwert, ab welcher Umgebungshelligkeit die LEDs eingeschaltet werden, von 400 auf 200 geändert. Dazu ist nun doch die Variante Laptop + ArduinoUNO + ATmega328-Adapter zum Einsatz gekommen.
Also das hier:
ArduinoUNO mit ATmega328-Adapter

Zusätzlich wollte ich nämlich eine Messreihe erstellen, für die eine spezielle Firmware nötig war.
Die Daten dazu finden sich auf dieser ausgelagerten Seite.

Auf den ersten Blick sehen die Werte-Beziehungen zwischen den Messreihen noch halbwegs linear aus.
Zwecks genauerer Analyse ziehe ich mal ein paar Daten raus und stelle sie zueinander in Beziehung.
Das Format der ersten drei Spalten in folgender Tabelle lautet: (PWM-Wert, Referenz-Spannung, Lichtquelle[n])=gemessene Helligkeit
Fremdlicht ohne LEDs
ohne Fremdlicht / nur LEDs
Fremdlicht mit LEDs Delta
Abweichung
(0, INTERNAL, Tr)=129
(10, INTERNAL, dunkel)=108
(10, INTERNAL, Tr)=243
243-108=135
abs(135-129)=6
(0, INTERNAL, Tr+Fo)=359 (10, INTERNAL, dunkel)=108 (10, INTERNAL, Tr+Fo)=475 475-108=367
abs(367-359)=8
(0, INTERNAL, Tr)=129 (40, INTERNAL, dunkel)=462 (40, INTERNAL, Tr)=579 579-462=117
abs(117-129)=12
(0, INTERNAL, Tr)=129 (100, INTERNAL, dunkel)=870 (100, INTERNAL, Tr)=920 920-870=50
abs(50-129)=79
(0, INTERNAL, Tr)=129 (140, INTERNAL, dunkel)=997 (140, INTERNAL, Tr)=1013 1013-997=16
abs(16-129)=113





(0, INTERNAL, Fo)=229 (10, INTERNAL, dunkel)=108 (10, INTERNAL, Fo)=346 346-108=238
abs(238-229)=9
(0, INTERNAL, Fo)=229 (40, INTERNAL, dunkel)=462 (40, INTERNAL, Fo)=670 670-462=208
abs(208-229)=21
(0, INTERNAL, Fo)=229 (100, INTERNAL, dunkel)=870 (100, INTERNAL, Fo)=957 957-870=87
abs(87-229)=142
(0, INTERNAL, Fo)=229 (140, INTERNAL, dunkel)=997 (140, INTERNAL, Fo)=1021 1021-997=24
abs(24-229)=205





(0, DEFAULT, Fo)=47
(10, DEFAULT, dunkel)=22 (10, DEFAULT, Fo)=72 72-22=50
abs(50-47)=3
(0, DEFAULT, Fo)=47 (40, DEFAULT, dunkel)=99 (40, DEFAULT, Fo)=150 150-99=51
abs(51-47)=4
(0, DEFAULT, Fo)=47 (100, DEFAULT, dunkel)=254 (100, DEFAULT, Fo)=303 303-254=49
abs(49-47)=2
(0, DEFAULT, Fo)=47 (140, DEFAULT, dunkel)=356 (140, DEFAULT, Fo)=404 404-356=48
abs(48-47)=1
(0, DEFAULT, Fo)=47 (180, DEFAULT, dunkel)=456 (180, DEFAULT, Fo)=504 504-456=48
abs(48-47)=1
(0, DEFAULT, Fo)=47 (210, DEFAULT, dunkel)=531 (210, DEFAULT, Fo)=578 578-531=47
abs(47-47)=0
(0, DEFAULT, Fo)=47 (255, DEFAULT, dunkel)=640 (255, DEFAULT, Fo)=686 686-640=45
abs(45-47)=2

Bei der (eigentlich vorgesehenen) Referenz-Spannung "INTERNAL" wird es ab einem PWM-Wert von 40 unschön, ab 100 passt es dann gar nicht mehr.
Spannenderweise bleibt es hingegen über den gesamten Wertebereich linear, wenn die Referenz-Spannung "DEFAULT" verwendet wird.

Grrrr.....also sollte ich die Messreihen wohl nochmal durchgängig mit "DEFAULT" wiederholen.
Wenn es dann bei anderen und mehreren Lichtquellen weiterhin linear bliebe, müsste nur ein Array im EEPROM abgelegt werden, in dem jedem PWM-Wert der gemessene Helligkeitswert(ohne Fremdlicht) zuordnet würde.
Mit Hilfe dieses Arrays und dem aktuell eingestellten PWM-Wert ließe sich aus einer gemessenen Helligkeits-Summe von LED- plus Fremdlicht-Helligkeit die derzeitige Fremdlicht-Helligkeit bestimmen - um darüber die LED-Helligkeit anpassen zu können, wenn sich die Helligkeits-Summe geändert haben sollte.

Der zweite Durchgang ist fertig. Dessen Daten haben wieder ihre eigene Seite.
Die Auswertung zeigt diesmal eine sehr viel bessere Linearität:
PWM
nur LED
Fo
Fo - LED
Fo+Tr
Fo+Tr - LED
Tr
Tr - LED
Fu
Fu - LED
Ta
Ta - LED
0
3
55
55-3=52
83
83-3=80
28
28-3=25
17
17-3=14
55
55-3=52
10
26
78
78-26=52
105
105-26=79
51
51-26=25
40
40-26=14
77
77-26=51
40
96
145
145-96=49
172
172-96=76
122
122-96=26
111
111-96=15
147
147-96=51
100
237
287
287-237=50
312
312-237=75
262
262-237=25
251
251-237=14
286
286-237=49
150
353
403
403-353=50
426
426-353=73
378
378-353=25
366
366-353=13
398
398-353=45
200
468
516
516-468=48
539
539-468=71
491
491-468=23
480
480-468=12
510
510-468=42
255
590
637
637-590=47
658
658-590=68
614
614-590=24
602
602-590=12
628
628-590=38












Streuung:

abs(47-52)=5

abs(68-80)=12

abs(23-26)=3

abs(12-15)=3

abs(38-52)=14
Sollwert:

55

83

28

17

55
Abweichung(min/max):
3 bis 8

3 bis 15

2 bis 5

2 bis 5

3 bis 17

Relevante Streuung kommt nur beim kombinierten Licht (Fo+Tr) und beim Tageslicht vor.
Bei der Tageslicht-Messung war es zwar konstant bedeckt, trotzdem hat sich die Helligkeit auch leicht geändert.
Das Treppenlicht war bei der Kombi-Messung frisch eingeschaltet. Die entsprechende Lampe ist noch eine sog. Energiespar-Lampe.... und diese zwangsverordneten Quecksilber-Schleudern brauchen ja bekanntermaßen einige Zeit, bis sie ihre endgültige Helligkeit erreicht haben. Bei der alleinigen Treppenlicht-Messung war das dann offenbar bereits der Fall.

Fazit: die oben angedachte Funktion zum Rausrechnen der LED-Helligkeit aus einer Gesamt-Helligkeit sollte machbar sein bzw. brauchbare Ergebnisse liefern.

Wobei sich da gleich die Frage stellt, wie ich den Inhalt des Arrays ins EEPROM bekomme. Entweder die bereits vorhandenen Daten per temporärem Spezial-Sketch reinladen oder alternativ die zukünftige Standard-Firmware gleich so bauen, dass sie die Daten selbst erfassen kann.
Letzteres gefällt mir (ins besondere in Hinsicht auf weitere Nachtlicht-Installationen) weitaus besser. Dazu wäre dann allerdings ein Trigger-Event nötig. Also vielleicht ein Taster, der beim PowerOn abgefragt wird. Wenn er bei PowerOn gedrückt gehalten wird, erfolgt eine [erneute] Einmessung. Vorher könnte die Status-LED noch eine Zeit lang hektisch flackern, um damit anzuzeigen, dass bald eine Messung beginnen wird - und man für die nächsten fünf Minuten jegliches Fremdlicht vom Installationsort fernhalten sollte.
Die Pins D4, D12 oder D13 wären für so einen Taster-Anschluss noch verfügbar. Eigentlich auch D6. Aber weil der PWM kann, will man sich den vielleicht lieber für zukünfigen entsprechenden Bedarf freihalten.

Wenn die LED-Helligkeit linear mit dem PWM-Wert steigt, braucht es dafür eigentlich kein Array.
Ein map(pwm_val, 0, 255, 3, 590) sollte ähnliche Werte liefern. Tatsächlich liefert er eine maximale Abweichung von 6, durchschnittlich 3.
Alle Abweichungen sind positiv und häufen sich zur Mitte hin.
Dementsprechend könnte ich zum Ergebnis von map() 3 addieren und käme auf eine maximale Abweichung von 3, durchschnittlich 0.
Andererseits würde ich mir damit die maximale Abweichung gerade in den unteren/relevanten Bereich schieben.
Ein dynamischer Korrekturwert wäre besser. Etwa so:
#!/usr/bin/env python
# -*- coding: utf-8 -*-

fn="/home/dede/messung2.txt"

# https://www.arduino.cc/reference/en/language/functions/math/map/
def mymap(x, in_min, in_max, out_min, out_max):
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min

d=dict()
for i in range(256):
d.update({i:["-", "-", "-", "-", "-", "-", "-"]})

idx=-1
fobj=open(fn, "r")
lns=fobj.readlines()
for lnr in lns:
ln=lnr.strip()
if ln.startswith(";"):
idx+=1
if ln.startswith("cur_led="):
wrdlst=ln.split()
cur_led=int(wrdlst[0][len("cur_led="):])
avg=wrdlst[-1]
lv=d[cur_led]
lv[idx]=avg
d.update({cur_led:lv})

m=ms=0
light_src=5 # 1=FlurOben, 2=FlurOben+Treppe, 3=Treppe, 4=FlurUnten, 5=Tageslicht
for pwm_val in range(256):
lum_sum=int(d[pwm_val][light_src])
led_lum=mymap(pwm_val, 0, 255, 3, 590)
if 32<=pwm_val<128:
led_lum+=mymap(pwm_val, 32, 127, 0, 6)
if 128<=pwm_val<=255:
led_lum+=mymap(pwm_val, 128, 255, 6, 0)

lum_base=lum_sum-led_lum
print pwm_val, int(d[pwm_val][0]), led_lum, int(d[pwm_val][0])-led_lum
m=max(int(d[pwm_val][0])-led_lum, m)
ms+=int(d[pwm_val][0])-led_lum
print m, ms/256

Der blau markierte Bereich implementiert die Korrektur und führt zu einer maximalen Abweichung von 3, durchschnittlich 1.
Noch näher dran wäre vielleicht eine Sinus-artige Funktion.

Ach was solls.... das Array braucht kümmerliche 512 Byte.
Für den ersten Test nehme ich jetzt ein quasi-statisches Array in den Programmkode auf und initialisiere es mit den Daten der ersten Messreihe des zweiten Durchlaufs.

Und natürlich kam es, wie es kommen musste. Beim rausrechnen der LED-Helligkeit@PWM-Wert aus der Gesamthelligkeit bleibt häufig negative Helligkeit übrig. Entweder sind meine Messwerte der LED-Helligkeit zu hoch oder die aktuell gemessenen Helligkeitswerte sind zu niedrig. Oder vielleicht ist es auch so, dass man gar keine Helligkeits-Arithmetik betreiben kann.... also dass zwei gleich helle Lichtquellen nicht exakt die doppelte Helligkeit ergeben - vor allen Dingen dann, wenn sie mit unterschiedlichen Wellenlänge leuchten...!?
Hätte ich nur damals in Physik besser aufgepasst... ;-)

Also:


neuer Ansatz

Und zwar weg von vorberechneten PWM-Werten, hin zur schrittweisen Annäherung an einen definierten Wert.
Die Idee hatte ich ja schon mal und bin aus irgend einem Grund wieder davon abgekommen.

Was will ich eigentlich erreichen bzw. was ist das Problem?
Somit scheidet eine einzelne Soll-Helligkeit aus, an die sich angenähert werden könnte.
Je nach Fremdlicht-Helligkeit gilt eine andere LED-Helligkeit.
Es kann nur die Gesamt-Helligkeit als Summe aus Fremdlicht- und LED-Helligkeit gemessen werden.
Offenbar lässt sich die Fremd-Helligkeit nicht hinreichend genau aus Gesamt-Helligkeit abzüglich LED-Helligkeit berechnen.

Mit zwei Arrays, in denen jedem PWM-Wert eine Helligkeit zuordnen würde, könnte es klappen.
Ein Array gälte bei ausgeschalteten LEDs, das andere bei eingeschalteten LEDs.
Obwohl...ist Quatsch. Das erste Array habe ich ja bereits als Funktion calcLuminosity() abgebildet.
Und das zweite Array müsste zweidimensional sein bzw. neben dem neuen auch den bisherigen PWM-Wert enthalten. Abgesehen von dem dafür nicht vorhandenen Platz dürfte sich die Frage nach der Herkunft des Inhaltes als nicht-triviales Problem darstellen.


Vielleicht ließe sich was mit der Helligkeits-Differenz anfangen. Also die, die zwischen dem Wert, der nach dem Einschalten der LEDs und dem neuen Helligkeits-Wert besteht.
Derzeit ist der Schwellwert, ab dem die LEDs nicht eingeschaltet werden, auf 50 eingestellt. Ein weiterer Schwellwert steht derzeit auf 10 und stellt das minimale Helligkeits-Delta dar, ab dem ein neuer PWM-Wert für die LEDs bestimmt wird.
Wenn nur das Treppenlicht eingeschaltet ist, wird eine Helligkeit von 28 gemeldet. Ist das Treppenlicht zusammen mit dem Flur-Licht in der ersten Etage eingeschaltet, wird 83 gemeldet. Also betragen die derzeitigen Min/Max-Werte für alleiniges Fremdlicht 28 und 83.
Noch höhere Werte als 83 werden also nur erreicht, wenn zusätzlich die LEDs eingeschaltet sind.
Okay...im Hochsommer mag der Wert für Tageslicht von derzeit 55 auf vielleicht 100 ansteigen.
Ab einem PWM-Wert von 42 erreichen die LEDs alleine den Wert 100.

Dann will ich das mal an einem realen Fall (mit der bisherigen Firmware) durchspielen.
Folgende Sequenz findet sich im Log ... und hat die LEDs heller statt dunkler gestellt:
[...]
Tiefschlaf
wake from deepSleep()
runWithLight 34
  cur_sec=0  last_lum_g=435  motion_sensor=1
  cur_sec=1  last_lum_g=435  motion_sensor=0
  cur_sec=2  last_lum_g=435  motion_sensor=0
  cur_sec=3  last_lum_g=435  motion_sensor=0
  cur_sec=4  last_lum_g=435  motion_sensor=0
  cur_sec=5  last_lum_g=435  motion_sensor=0
  cur_sec=6  last_lum_g=435  motion_sensor=0
  cur_sec=0  last_lum_g=435  motion_sensor=1
last_lum_g=435  cur_lum=405  led_lum_g[]=409  cur_lum_noLED=-4  led_val=174  led_val2=239
  cur_sec=1  last_lum_g=408  motion_sensor=0
[...]

Die Einschalt-Helligkeit ist 34 (Treppenlicht ist an, LEDs noch aus).
Daraus folgt ein PWM-Wert von 174. Das führt zu einer Gesamt-Helligkeit (mit LEDs) von 435.
Nach kurzer Zeit sinkt die Gesamt-Helligkeit um 30 auf 405 (Treppenlicht wird ausgeschaltet).
Die LED-Helligkeit zum PWM-Wert 174 beträgt 409. 409-30=379
Der PWM-Wert zur LED-Helligkeit 379 lautet 161.
Und das passt gut zum Helligkeits-Wert für Treppenlicht + LED-Helligkeit@161 von 403.

Also müsste die Funktion zur Anpassung des PWM-Wertes nach Helligkeitsänderung den derzeitigen PWM-Wert und das Helligkeits-Delta übergeben bekommen.
In der Funktion wird dann die LED-Helligkeit zum PWM-Wert (hier 174) aus dem Array geholt (hier 409).
Von der LED-Helligkeit aus dem Array-Wert würde nun das Delta abgezogen werden (hier also 409-30=379).
Nun würde im Array vom Index 174 soweit rückwärts gesucht, bis der Wert 379 erreicht oder unterschritten würde (hier also Index=161, Wert=379).
Der gefundene Index wäre der neu einzustellende PWM-Wert (also 161).

Und nachdem ich das eingebaut habe, klappt es jetzt richtig gut! :-)
Es klappt sogar so gut, dass ich mal testen sollte, den Algorithmus auch bei zunehmender Helligkeit anzuwenden (und die LEDs entsprechend heller zu schalten).
Das Optimum wäre ja, wenn die eingeschalteten LEDs nach einer Helligkeitsänderung auf genau den Helligkeits-Wert eingestellt würden, der eingestellt worden wäre, wenn die neue Helligkeit bereits vor dem Einschalten der LEDs bestanden hätte.

Bevor ich das einbaue, könnte ich mir vielleicht noch ein paar Gedanken über die letzte Unerfreulichkeit machen.
Wenn man am Helligkeits-Sensor vorbei geht, kommt es häufig mal vor, dass die LEDs eingeschaltet werden oder sich zumindest ihre Helligkeit ändert. Nicht dann, wenn die LEDs die einzige Lichtquelle sind. Aber wenn man sich gerade zwischen dem Sensor und der Fremdlicht-Quelle befindet, erkennt der Sensor korrekterweise eine Helligkeits-Abnahme.
Einfach nur Schwellwerte zu ändern, wird das Problem nicht beheben. Höchstens verschieben.
Das Einbeziehen einer Mindestdauer für Helligkeitsänderungen ist auch keine Lösung, weil das Nachtlicht dadurch künstlich träge würde.
Ein zweiter Helligkeits-Sensor könnte das Problem beheben. Der müsste so positioniert werden, dass es bezüglich sämtlicher Fremdlicht-Quellen keine Position gibt, an der man beide Sensoren abschatten kann. Zumindest bei einer einzelnen Person.
Analog-Pins dafür wären am ATmega328 noch reichlich verfügbar.
Voraussichtlich bräuchte jeder Sensor sein eigenes Array für die Relation "PWM-Wert zu Sensor-Wert".
Wie aber soll sich das Programm entscheiden, wenn die beiden Sensor-Werte zu zwei unterschiedlichen PWM-Werten führen würden?
Den Mittelwert zu bilden, wäre kontraproduktiv....

Ach was....ich hab jetzt gerade keine Lust auf Hardware-Änderungen.
Daher kommt erstmal nur die Helligkeits-Korrektur für beide Richtungen rein.
Und damit ich was sehe, klemme ich noch das Board mit dem ESP-01 an und erweitere die Firmware um das Logging via WLAN.



BTW: das nächste Projekt könnte eine Art WLAN-Syslog-Adapter mit dem ESP-12 werden. Der jetzige Adapter bremst nämlich den Programm-Ablauf des ATmega328, weil sich der ATmega328 selbst um die komplette Steuerung kümmern muss. Folglich reagiert das Nachtlicht z.Zt. mit einer Verzögerung von ca. einer Sekunde auf Bewegung. Schöner wäre es, wenn man dem Adapter jederzeit die zu sendenden Daten schicken könnte und der sich dann autonom bzw. asynchron um alles Andere (Bufferung, Connect, Reconnect, Timeouts, SocketServer-Kommunikation) kümmern würde.


erster Einsatz

Nun ist das Nachtlicht seit über 12 Stunden mit der neuen Firmware gelaufen und der SocketServer hat bereits ein halbes Megabyte an Logfile geschrieben.
Die meisten Daten sind langweilig, weil nach Einschalten der LEDs keine Helligkeits-Änderung auftrat.
Der letzte spannende Bereich war gestern Abend um 22:25 Uhr:
[...]
2017.12.23 22:25:16 Info: last_lum_g=2
2017.12.23 22:25:16 Info: runWithLight()
2017.12.23 22:25:16 Info: cur_sec=0  last_lum_g=32  cur_lum=34
2017.12.23 22:25:21 Info: last_lum_g=32  cur_lum=39  led_val=14  led_val2=17
2017.12.23 22:25:22 Info: cur_sec=1  last_lum_g=50  cur_lum=44
2017.12.23 22:25:22 Info: last_lum_g=50  cur_lum=44  led_val=17  led_val2=14
2017.12.23 22:25:23 Info: cur_sec=2  last_lum_g=33  cur_lum=33
2017.12.23 22:25:24 Info: cur_sec=3  last_lum_g=33  cur_lum=33
[...]
2017.12.23 22:26:15 Info: cur_sec=28  last_lum_g=33  cur_lum=34
2017.12.23 22:26:16 Info: cur_sec=29  last_lum_g=33  cur_lum=35
2017.12.23 22:26:18 Info: deepSleep()
2017.12.23 22:26:18 disconnect
[...]


Also lag die Einschalt-Helligkeit bei 2. Das hat zu einem PWM-Wert für die LEDs von 14 geführt. Dadurch stieg die Gesamt-Helligkeit auf 32 (laut Array hätte es 35 sein sollen).
Fünf Sekunden später stieg die Helligkeit auf 39 oder auch +7. 35+7=42. Der PWM-Wert zu 42 lautet 17.
Der PWM-Wert 17 hat zur Helligkeit 50 geführt.
Dann wurde es wieder dunkler. Jetzt 44 statt 50. Also -6. 42-6=36. Der PWM-Wert zu 36 liegt zwischen 14 und 15 - als Integer also 14.

Offenbar passt alles ... mehr oder weniger. Der Inhalt des Arrays liegt etwas daneben.
Wie gut, dass ich gestern keine Lust mehr hatte, mich mit der Idee des zweiten Helligkeits-Sensors zu befassen. Der ist jetzt nicht mehr nötig.
Jedoch muss ich die Daten unbedingt nochmal neu erfassen, sobald die LED-Leiste und vor allen Dingen die Sensor-Einheit final installiert ist.
Noch ist alles sehr provisorisch befestigt, weil ich die gesamte Einheit bei jedem Tausch des ATmega328 vom Handlauf abbauen muss.
So sieht es aus:
Nachtlicht - provisorische Installation mit Debug-Adapter
Links im Eck hängt der temporär angeschlossene WLAN-Adapter runter.
Rechts oben sitzt die Sensor-Einheit und wird von zwei Nägeln gehalten. Wegen der Steifheit des Flachbandkabels und dem geringen Gewicht des Gehäuses sitzt es nie plan an der Wand bzw. hat nach jeder Reinstallation einen geringfügig anderen Winkel zur Wand. Final soll es natürlich mit Schrauben befestigt werden.

Jetzt braucht es also noch den Taster, mit dem beim PowerOn der Einmess-Vorgang eingeleitet werden kann.
Damit geht es auf der nächsten Seite weiter.