SPI
Über den SPI-Bus werden sowohl die Messwerte der Senke abgerufen als auch der Sollwert über den DAC eingestellt.
Der SPI_STC-Interrupt (Serial Transfer Complete) überträgt pausenlos und kontinuierlich den Sendepuffer und schreibt die Empfangenen Daten in den Empfangspuffer.
Bei 7.3728 MHz wird er mit einer Frequenz von ca. 12.2 kHz aufgerufen und benötigt etwa 900 µs um den kompletten Puffer zu senden.
Diese Zeit bestimmt auch die maximale Reaktionszeit auf kritische Ereignisse (SOA!). Es kann schlimmstenfalls 1.8 ms dauern bis Eload darauf reagiert.
Timer Interrupt
Für alle zeitgesteuerten Aktionen löst Timer 0 mit einer Frequenz von 800 Hz einen Interrupt aus.
Anfänglich hatte ich hier nur 100 Hz vorgesehen doch es erwies sich als
zu langsam für den Encoder. Dieser hat seine Schaltflanken nämlich, aus mir
unverständlichen Gründen, nicht zwischen den Steps, sondern währenddessen und erzeugt
damit während eines Klicks
zwei Flanken. Mit 10 ms zwischen den Abtastungen
ist das nicht zuverlässig machbar.
Warum ich nicht den Pin Change Interrupt benutzt habe sehen sie hier.
Tastatur
Die Tastatur wird über eine Callback-
Für den Encoder reicht das nicht, dieser wird alle 1.25 ms abgetastet um keinen Step zu verpassen.
Jeder Tastendruck und jede Encoder-
Menüs
Ein simples zeilenorientiertes Menü wurde implementiert.
Um auch mehr als drei Menüeinträge zu erlauben (die Titelleiste und das eigentliche Menü)
wurde es scrollfähig gestaltet. Ein Menü kann damit bis zu 127 Einträge
umfassen. Das wäre natürlich als UI untauglich aber mehr als drei sind schon
ab und zu nötig...
Anwählen eines Menüpunktes ruft eine entsprechende Callback-
Der Aufruf eines Menüs hält das aufrufende Programm bis zu seinem Ende an.
Flags (wie Timer- oder Kommunikationsflags) werden fortan innerhalb
der Menü-
Ein Menüpunkt startet i.d.R. einen Screen oder ein weiteres (Sub-) Menü, der/das seinerseits die Programmausführung bis zu seinem Ende anhält. Er/es ist in dieser Zeit ebenfalls für die Bearbeitung der Flags verantwortlich.
Die Routine doFlags
wird dazu nach jedem Sleep aufgerufen
und erledigt alles nötige.
Sonderaktionen wie z.B. die Kalibrierung, der Fertigungstest
oder die Programmierung von Zeitkonstanten
können nur über den USB-
PROGMEM
Soll dass Menü samt seiner Komponenten (der Menüeinträge) im Flash oder im
RAM gehalten werden?
Es gibt Argumente für beides. Ein Menü im RAM kann vom Hauptprogramm jederzeit angepasst,
um zusätzliche Einträge ergänzt oder um nicht sinnvolle Optionen gekürzt werden.
Allerdings ist der Speicherverbrauch beträchtlich, wenn unser System nur zwei
Kilobytes RAM besitzt.
Ich habe das Menü so programmiert, dass es komplett im Flash-
Screens
Die Bedienoberfläche ist in einzelne Bildschirme (Screens), aufgerufen durch ein Menü oder auch direkt durch einen anderen Screen, aufgeteilt.
Wenn ein Screen aufgerufen wird, hält er die Hauptschleife bis zu seinem Ende an. Die Hauptschleife reduziert sich deshalb auf eine Reihe von Screens oder Submenues, die nacheinander aufgerufen werden, evtl. abhängig vom Rückgabewert des jeweils vorherigen.
Ein Screen wird normalerweise durch Drücken des Enter
- oder Cancel
-
Es ist daher nicht garantiert, dass die Hauptschleife innerhalb einer
bestimmten Zeit durchläuft
.
Tatsächlich läuft die Hauptschleife im Idealfall überhaupt nicht durch
sondern stoppt mit dem Aufruf des Hauptmenüs, das niemals beendet werden sollte.
Flags wie z.B. das Sekundenflag oder Kommunikationsflags werden innerhalb des Screens abgehandelt.
Ein Screen enthält Widgets wie Texte, Buttons oder Eingabefelder.
Diese sind in einer C-
Ein Autorefresh-Flimmern
ohne zusätzlichen Informationsgehalt wahrgenommen,
wesentlich niedrigere würden die
Reaktionsfähigkeit verringern und den Benutzer ungeduldig
werden lassen.
2..3 Hz sind dafür ein guter Wert. Falls Ihr Gerät unter niedrigen Umgebungstemperaturen arbeiten muss evtl. auch weniger, da das Display dann träger wird und rasche Änderungen nicht lesbar abbilden kann.
Screen Manual Test
Dieser Screen dient zum manuellen Test der Quelle unter konstanter Last. Der Strom kann über die Tastatur eingegeben werden und/oder durch Drehen des Encoders.
Set Point: 1.2340A
1.234A 12.345V
15.23W 10.00Ω
TIC=27.4 THS:78.9 <
Set Point ist dabei der eingestellte Sollwert. In der Zeile darunter wird
der tatsächliche Strom und die anliegende Spannung angezeigt.
Darunter sehen Sie die daraus berechnete Verlustleistung der Senke
(nicht notwendigerweise der Quelle wenn da noch ohmsche Anteile vorhanden sind!)
sowie umgerechnet, den Widerstand den die Quelle sieht.
Darin enthalten ist auch der Leistungsverlust in der Sicherung
da der Spannungsabfall über dieser mit gemessen wird.
Unberücksichtigt bleibt, da unbekannt, die Leistung über der
restlichen Verkabelung, so dass die Stromquelle tatsächlich etwas
mehr Leistung einspeist.
In der dritten Zeile sehen wir die Temperatur des Kühlkörpers
(THS=Temp Heat Sink) sowie die Temperatur im Gehäuse
(TIC=Temp Inside Case).
Das <
-Zeichen ist der (abgekürzte) Back-
In diesem Screen kann, sofern nicht gerade das Set Point
-Set Point
Widget
befinden, der Strom durch Drehen des
Inkremental-
Der Preset 0 ist auf 0 mA gesetzt und kann nicht
verändert werden. So ist es stets möglich, den Strom
durch drücken der 0-
Ein langer Druck einer Zifferntaste (außer 0) speichert den aktuellen Strom als den entsprechenden Preset.
Dieser Screen eignet sich sehr gut um die Stromquelle an ihren Grenzen oder mittels der Presets gegen Lastsprünge zu testen.
Einstellen der Presets
Die zehn Presets können über die USB-
Sie werden dann direkt im EEPROM verewigt und stehen auch nach einem Neustart des Geräts zur Verfügung.
Status-Anzeige
Die Status-
1.234A 12.345V
15.23W 10.00Ω
THS=78.9°C TIC=23.4°C
P12=11.3V P5=4.93V
Auch hier sehen wir die anliegende Spannung, den fließenden Strom, die Verlustleistung in der Senke sowie den äquivalenten Widerstand, den die Senke für die Stromquelle darstellt. Zusätzlich sehen wir die Temperaturen am Kühlkörper sowie im Gehäuse und außerdem die Spannungen P12 und VCC (P5)
Der Stat-Screen wird durch Drücken einer beliebigen Taste beendet und führt zurück zum Menü.
Eingabefelder
Eingabefelder erlauben die Eingabe von numerischen Werten über die Tastatur und/
Das bringt uns in eine Zwickmühle. Einerseits sollen Vertipper keine ungewollte Änderung des Stromes bewirken, andererseits soll mit dem Encoder in Echtzeit eine Feinjustage des Stromes möglich sein. Was soll also passieren, wenn der User ein paar Stellen tippt und dann am Encoder dreht? Die aktuelle Software macht folgendes:
Wenn Zifferntasten gedrückt werden, wird der Wert des Feldes entsprechend geändert,
der tatsächlich fließende Strom wird jedoch noch nicht geändert.
Beim Drehen des Encoders wird der Wert in der aktuellen Cursorposition um
eins erhöht/
Beim Verlassen des Feldes nach links (durch Drücken von Cancel) wird der Wert vor dem Betreten wieder hergestellt und der Strom entsprechend eingestellt.
Beim Verlassen des Feldes nach rechts (durch Drücken von Enter) wird der eingestellte Wert als Strom gesetzt.
Das kann durchaus bewusst ausgenutzt werden um Lastsprünge manuell zu erzeugen: Stellen Sie den Strom für das Ende des Lastsprungs zunächst ein und verlassen Sie das Feld mit Enter. Dann betreten Sie das Feld erneut und stellen Sie den Wert für den Beginn des Lastsprungs mit dem Encoder ein. Wenn Sie nun das Feld mit Cancel verlassen wird der Strom schlagartig auf den ursprünglichen Wert zurückgestellt und Sie haben ihren Lastsprung!
USB
Die USB-Schnittstelle ist natürlich galvanisch vom Rest der Schaltung isoliert um Rückwirkungen und möglicherweise fatale Schleifenströme über den steuernden PC (dessen Masse ja im Regelfall geerdet ist) zu verhindern.
Die Trennung ist bis zu einem Wert von 500 V= zwischen der Eload-
Beachten Sie, dass auch Transienten diesen Wert nicht überschreiten dürfen!
USB-
Senden über den USB erfolgt klassisch über einen Sendepuffer im Gerät. Bei eingehenden Daten werden diese gesammelt bis ein LF eintrifft. Dann wird die gesamte Zeile als Befehl ausgewertet. Der Host sollte keine weiteren Daten senden bis er die Antwort auf diesen erhält da sonst Zeichen verloren gehen können.
Entschuldigung :-)
Für den Rest dieser Seite bin ich leider aus nicht mehr nachvollziehbaren
Gründen ins Englische gerutscht. Ich lasse es aber so bis das Eload-vorläufig endgültigen
Zustand erreicht hat. Sorry for any inconvenience...
Interrupts
Interrupts generally are a very sensitive point in designing software. This applies in a very special way to Atmel's AVRs since there is no effective priority mechanism, interrupts can normally not be interrupted, not even by interrupts of higher priority.
Timer Interrupt
The timer interrupt is called at 1.25 ms intervals and in this application has a lot of tasks:
- It runs the real time clock. The clock counts seconds, minutes and hours up to 255. The device though is fully operational up to nearly 256 hours after startup before the RTC overflows (what is more than 10 days of operation). Of course it still will operate correctly after that but time stamps are invalid.
- It scans the keyboard and dispatches keys into a queue.
- It scans the rotary encoder. Encoder actions are also dispatched
into the keyboard queue just like
normal
key presses. - It calls a USART callback function wich handles handshaking (if enabled).
The time spend in the ISR may be considerably long so this routine is the only one that sets the interrupt enable flag right on entry. This interrupt can thus be interrupted by any of the other ones.
One problem when doing this is that it could also be interrupted by itself (i.e. the next timer interrupt before the routine completes) and thereby might cause a stack explosion, but since the timing is predictable (1.25 ms) this can be safely excluded unless its worst case execution time would exceed 1.25 ms. Be aware that this time includes the execution time of the interrupting routines!
Another problem which can cause tricky errors really occured because the timer interrupt (especially the keyboard scanning part) modifies the same port as the spi interrupt. Although both routines modify only the bits they are intended to, the keyboard scan does this in a non-atomic way. I occasionally noticed the leds flashing and got spurious false measurements before I disabled interrupts during the scan...
Some tasks, like scanning the keyboard or setting the LEDs is done in 10 ms intervals.
The 100 Hz part is not done in one bulk
routine but I spreaded
different tasks to several of the 8 interrupts called every 10 ms.
So the worst case execution time is reduced and the CPU load is spreaded more
equal over time.
The time necessary for the switch() construct is negligible.
And what about the pin change interrupt?
This is available on some devices. It causes an interrupt every time the pin state changes. This is extremely dangerous and I would prefer it would not exist!
At a first glance, it seems to be perfectly useful for e.g. the rotary encoder but
mechanical contacts tend to bounce. You do not know how fast and how often.
I have witnessed a program collapsing because some comparator put a
megahertz where the developer only expected kilohertz. The CPU never
came out of the pin change interrupt and seemed to be frozen
.
Use it only on signals where no higher frequency may appear than
you expect!
This may be useful for a hardware debounced key but debouncing
it by software is usually cheaper!
SPI Interrupt
The SPI peripheral on ATmegas lacks the cooperation of a DMA unit. This is bad. You either have to handle the SPI inside your main loop what includes waiting for every single byte to be transferred before you can send the next one or you must accept the overhead of an ISR for every single byte and handle the thing via ISR.
If the SPI operates at high clock rates, the first version may be preferrable, at low clock rates the ISR version may consume less CPU power and spread the load more equal over time. In any case, the SPI is a device that annihilates significant CPU power.
I decided to use the second method and to reduce the SPI clock to an acceptable rate.
All SPI devices (the ADC, the DAC and the LED shift register) are handled inside the SPI interrupt. Since our device only has one SPI, this method is less error prone than to handle the ADC via interrupt and create some other routines that set up the DAC and the LEDs which must be sure that the ADC is currently not in use. The ADC in fact could be a little bit faster otherwise. Don't get you into trouble unless you need to...
The SPI interrupt at 7.3728 MHz is triggered roughly every 80 µs
and takes worst case 22.8 µs to execute. Read the paragraph
Timing Analysis
why this is only half of the truth. It can thus consume about one third
of the CPU power worst case. In average, it will be less than 20%.
This is the most important task in our circuit so it may do so...
ADC Interrupt
This is the ISR for the on chip ADC of the ATmega. It scans all lines (i.e. temperature and voltage sensors) regularly. Due to the design of the ISR it is essential that no ADC interrupt gets lost, else it would lead to values being transferred to the wrong channel what would be someway catastrophic. No other interrupt must take more time than a single ADC conversion!
USART Interrupt
RxD Interrupt
Every single byte is transferred to the receive buffer and collected until a <LF> is received. Then the complete line is dispatched as a host command and executed by the surrounding loop.
TxD Interrupt
The TxD-interrupt empties the transmit buffer. Bytes are transmitted as soon as possible until the transmit buffer is empty.
Timing analysis
Especially for interrupts it is necessary that you know exactly how much CPU power an interrupt will consume, in average as well as in worst case.
Usually you will do that by setting a port pin right on entry and reset it as a terminal action. With a scope or in the simulator you can measure the time it takes. A simple RC filter can show you the average load and a modern scope can tell you the maximum time it consumes.
But there is one thing you may overlook when doing so: the time the prolog and epilogue code takes! Before you can set the port pin the code has to save the registers it might touch and after you reset the pins, these values will be restored. The total execution time will be, e.g. in our SPI interrupt, about 9 µs longer than you will measure. You can check this out by using the disassembly window in the simulator by triggering on the very first machine inst.
This is more than the average execution time of the interrupt itself!
Optimization
Especially our SPI interrupt beares a great potential of optimization that could even be
done by the compiler (but unfortunately it is not):
in 63 out of 64 calls most of the registers that it saves on the stack are not even
touched! The compiler could save them just in branches where they were needed!
There is also a possibility of manual optimization. Create a subroutine for the 64th call. This would increase the worst case load (due to the call and its return) but it would decrease the average load due to the less number of registers to save in the other 63 calls.
It depends on your application if this approach is useful. In a real time application it might be better to reduce the worst case execution time, in an application with lots of other tasks running, it might be better to reduce the average load.
If you want it to be perfect, I'm afraid you still would have to write it in assembler code...
In our case, I decided for the minimum worst case execution time since our CPU has not much else to do.
OffTopic: 5x7 LCD Font
abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
The font I use here for visualizing display content is freely available for download as webfont or ttf. Have a look on my HTML and Javascript page to learn more.