Heute das Handtuch nicht vergessen:
Montag, 11. August 2025
Quellimpedanz für ADC
Wenn man ein Eingangssignal für einen ADC mit einem Spannungsteiler anpassen will, dann findet man bei den meisten µC Forderungen nach einer recht niedrigen Quellimpedanz. Bei den AVR-Controllern findet man was im 10 kOhm Bereich, beim Teensy 3.5 liegt die Quellimpedanz laut Datenblatt für AnalogIn bei max. 5 kOhm.
Wenn man jetzt die Datenblätter näher anschaut, sieht man aber (wie bei CMOS-Schaltungen zu erwarten) für den statischen Eingangswiderstand Werte deutlich über 10 MOhm (wg. Leckströmen). Der geforderte niedrige Wert für die Quellimpedanz kommt also nicht von diesem Eingangswiderstand.
Die Forderung nach der niedrigen Quellimpedanz kommt stattdessen vom Aufbau des ADC als Ladungsteiler: zu Beginn der Wandlung wird aus der Quelle für kurze Zeit ein Samplekondensator geladen, und diese Ladung wird dann weiterverarbeitet. Der Samplekondensator muss also aus der Quelle in kurzer Zeit weit genug aufgeladen oder entladen werden und zwar auf den Bruchteil eines LSB an die Quellspannung.
Wenn man die Eingangsbeschaltung des ADC einfach aus einem Spannungteiler macht, dann bekommt man ein RC-Glied aus dem Innenwiderstand des Spannungsteilers und dem Samplekondensator. Und dann kann man rechnen: wenn z.B. die Sampledauer 1 µs ist und der Samplekondensator 20 pF hat und eine Ladezeit von 7 tau für die erlaubte Abweichung eines Bruchteils des LSB nötig ist, dann darf der Quellwiderstand höchstens
1us = 7tau = 7*R*20pF R = 1us / 140pF = 7,1kOhm
sein.
Also muss des ohmschen Eingangswiderstands mit vielen MOhm die Eingangsbeschaltung wegen dieser Sample&Hold-Abtastung irgendwie unnötig niederohmig ausgelegt werden.
Nach ein wenig Nachdenken kommt einem dann die Sache mit der Ladungsverteilung bei der Parallelschaltung von Kondensatoren in den Sinn: wenn ein Kondensator mit 10 pF und 0V zu einem Kondesnator mit 10nF und 5V parallel geschaltet wird, dann ergibt sich an dieser Parallelschaltung eine Spnnung von
U = (5V*10nF + 0V*10pF) / (10nF + 10pF) = 4,995 V
Fazit dieser Überlegungen ist dann: es reicht bei einem 12-Bit Wandler (4096 Incremente) für eine Abweichung von kleiner 1/2 LSB (=1/8192) aus, wenn dem Samplekondensator ein externer Pufferkondensator mit 10000x höherem Wert parallel geschaltet wird. Also reicht hier ein auf die Messspannung geladener externer 100nF Kondensator aus.
Man kann also einem sehr hochohmigen Spannungsteiler durch einen 100nF-Kondensator die nötige niedrige Quellimpedanz verleihen.
Beim Teensy 4.1 kann die Quellimpedanz sogar "ausgewählt" werden: 7 kOhm, 15 kOhm und 30 kOhm sind dort laut DB einstellbar. Wie funktioniert nun diese "Umschaltung"?
Die Lösung ist ein einfacher Dreisatz: man ändert einfach die Sampledauer, bis der Samplekondensator des ADC auf genauer als 1/2 LSB an die Endpannung geladen ist. Und weil sich dann bei tau=R*C mit konstantem C der R in Abhängigkeit von tau ändert, gibt es diese unterschiedlichen Werte.
Dort im microcontroller.net wurde im Forumthread Attiny85 ADC Eingangswiderstand - wie hoch der ADC eines AVR-Controllers an Beispielen hinterleuchtet und untersucht, wie sich Quellimpedanzen auf das Messergebnis auswirken und wie man diese Sache mit der Sample&Hold-Stufe möglicherweise auch anders angehen kann.
Was dort ebenfalls diskutiert wird, ist der Effekt, den so mancher schon beobachtet hat: bei zu hochohmiger/hochimpedanter Auslegung des Quellsignals für den ADC haben Kanäle "geisterhaften Einfluss" aufeinander: wenn sich ein Sensor- oder Potiwert ändert, dann ändert sich auch der nachfolgend gewandelte ADC-Kanal.
Wenn man jetzt den Pufferkondensator eingebaut hat und sich trotz hochohmiger Quelle in Sicherheit wiegt, dann kann man von der zweiten Fußangel erwischt werden: bei schneller Wandlung mehrerer Kanäle kann es sein, dass der Pufferkondensator von der Quelle langsamer nachgeladen wird, als der Samplekondensator ihn entlädt. Denn wenn z.B. 2 hochohmige Quellspannungen mit z.B. 5V ud 0V jeweils über einen Pufferkondensator an 2 ADC-Kanäle angeschlossen sind und die dann laufend abwechselnd gewandelt werden, dann muss der Samplekondensator ja ständig auf 5V ge- und auf 0V entladen werden. Er entnimmt dem Pufferkondensator der 5V-Quelle bei jeder Wandlung ein wenig Ladung und führt sie dem Pufferkondensator der 0V-Quelle zu. Und wenn nun der Innenwiderstand dieser Quellen zu hochohmig ist, können die nach so einer Wandlung die nötige Ladung nicht schnell genug nachführen. Fazit: die beiden gemessenen Spannungen beeinflussen sich wieder gegenseitig. Auch das wird im Thread Attiny85 ADC Eingangswiderstand - wie hoch untersucht und abgehandelt.
Dienstag, 25. April 2023
Berlin-Uhr alias "Mengenlehreuhr"
Die Berlin-Uhr (Mengenlehreuhr) aus dem Jahr 1975 eignet sich natürlich ideal für eine kleine VHDL-Spielerei.
Hier gibt es Infos dazu: Berlin-Uhr – Wikipedia
Hinter der Uhr steckt ein Fünfersystem bei den Einerstellen von Minuten und Stunden: es werden mit 4 Lampen jeweils 0..4 Minuten bzw. 0..4 Stunden gezählt, dann kommt ein Überlauf in die nächst höhere Stelle. Dort gibt es dann 11 Lampen für die 5er-Minuten , womit 55 Minuten aufsummiert werden können. Und es gibt 4 Lampen für die 5er-Stunden, was in Summe 20 Stunden ergibt.
Das ursprüngliche Design war asynchron mit Schieberegistern aufgebaut: das Überlauf-Bit der jeweils vorhergehenden Stufe sorgte dafür, dass die nachfolgende Stufe hochgezählt und die vorhergehende Stufe zurückgesetzt wird. So ein asynchrones Design hat prinzipiell das Problem, dass der Rücksetz-Impuls im Grunde nur ein kurzer Glitch ist, dieser Glitch aber auch als Zähl-Impuls verwendet werden muss. Es muss also z.B. durch eine Verzögerung des Rücksetzens garantiert werden, dass dieser Glitch ausreichend lang für das Weiterschieben/Hochzählen der nachfolgenden Stufe ist.
So etwas lässt sich nicht ohne berechtigte Warnungen aus VHDL-Code in ein FPGA synthetisieren, deshalb habe ich hier ein taktsynchrones Design für einen 50 MHz Takt aufgesetzt, das diese Glitch-Problematik sicher umgeht:
library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity berlinuhr is Generic ( fosc : integer := 50000000 ); Port ( clk : in STD_LOGIC; setslow: in STD_LOGIC; setfast: in STD_LOGIC; sec : buffer STD_LOGIC := '0'; min : buffer STD_LOGIC_VECTOR (3 downto 0) := (others=>'0'); min5 : buffer STD_LOGIC_VECTOR (10 downto 0) := (others=>'0'); hr : buffer STD_LOGIC_VECTOR (3 downto 0) := (others=>'0'); hr5 : buffer STD_LOGIC_VECTOR (3 downto 0) := (others=>'0') ); end berlinuhr; architecture verhalten of berlinuhr is signal spresc : integer range 0 to fosc-1 := 0; -- Prescaler für Sekunde signal mpresc : integer range 0 to 60-1 := 0; -- Prescaler für Minute = 60 Sekunden signal cntup : std_logic; begin process begin wait until rising_edge(clk); cntup <= '0'; if spresc < fosc-1 then spresc <= spresc + 1; else -- 1 s vorbei spresc <= 0; sec <= not sec; if mpresc < 60-1 then mpresc <= mpresc +1; else cntup <= '1'; -- 1 min vorbei mpresc <= 0; end if; end if; -- Uhrzeit stellen hat Vorrang if (setslow='1' and spresc=fosc-1) -- langsam stellen / 1 min vorwärts pro sec or (setfast='1' and spresc>=(fosc/60)-1) -- schnell stellen / 60 min (= 1 h) pro sec then cntup <= '1'; spresc <= 0; mpresc <= 0; sec <= '0'; end if; end process; process begin wait until rising_edge(clk); if cntup='1' then min <= min(2 downto 0) & '1'; if min(3)='1' then min <= (others=>'0'); min5 <= min5(9 downto 0) & '1'; if min5(10) = '1' then min5 <= (others=>'0'); hr <= hr(2 downto 0) & '1'; if hr(3) = '1' then hr <= (others=>'0'); hr5 <= hr5(2 downto 0) & '1'; end if; if hr5(3)='1' and hr(2)='1' then -- Tagesüberlauf extra und bevorzugt abhandeln hr <= (others=>'0'); hr5 <= (others=>'0'); end if; end if; end if; end if; end process; end verhalten;
Mit dieser Testbench lässt sich dann kontrollieren, ob die Uhr wie erwartet reagiert:
LIBRARY ieee; USE ieee.std_logic_1164.ALL; USE ieee.numeric_std.ALL; ENTITY tb_berlinuhr IS Generic ( fosc : integer := 5000; -- debug tosc : time := 200 us); -- debug END tb_berlinuhr; ARCHITECTURE behavior OF tb_berlinuhr IS COMPONENT berlinuhr GENERIC ( fosc : integer := fosc); PORT( clk, setslow,setfast : IN std_logic; sec : BUFFER std_logic; min : BUFFER std_logic_vector(3 downto 0); min5 : BUFFER std_logic_vector(10 downto 0); hr : BUFFER std_logic_vector(3 downto 0); hr5 : BUFFER std_logic_vector(3 downto 0) ); END COMPONENT; signal clk : std_logic := '0'; signal setslow : std_logic := '0'; signal setfast : std_logic := '0'; signal sec : std_logic; signal min : std_logic_vector(3 downto 0); signal min5 : std_logic_vector(10 downto 0); signal hr : std_logic_vector(3 downto 0); signal hr5 : std_logic_vector(3 downto 0); BEGIN uut: berlinuhr PORT MAP ( clk => clk, setslow => setslow, setfast => setfast, sec => sec, min => min, min5 => min5, hr => hr, hr5 => hr5 ); clk <= not clk after tosc/2; set_test: process begin wait for 1000000 ms; setfast <= '1'; wait for 100000 ms; setfast <= '0'; wait for 1000000 ms; setslow <= '1'; wait for 100000 ms; setslow <= '0'; wait; end process; END;
In der Testbench wird der Takt auf 5 kHz reduziert, damit die Simulation nicht so viele Taktflanken zu bearbeiten hat und deshalb langsam wird. Weil diese Taktzykluszeit über ein Genric an die Uhr gegeben wird, kann die ihre Zähler und Skalierungen entsprechend anpassen und so wird in der Testbench trotzdem alles in realen Zeiten angezeigt.
Auf dem Mikrocontroller.net wird das Thema im Thread Berlin Uhr aus ICs bauen etwas ausgiebiger diskutiert.
Dort ist der Code für einen 32,768 kHz Takt ausgelegt und braucht deswegen natürlich wesentlich weniger Flipflops. Natürlich könne man auch im obigen Code eine Taktfrequenz von 32 kHz angeben, dann kommen gleich viele Flipflops heraus, allerdings wird trotzdem mehr Logik für die Rücksetzbedingungen benötigt, als wie auf der expliziten Lösung auf µC.net nur 1 Bit abzufragen.
Dienstag, 10. Januar 2023
Drehgeberauswertung mit Beschleunigung
Hier eine Drehgeberauswertung, die auf der Basis von Peter Danneggers Code (auf www.mikrocontroller.net) zusätzlich eine dynamische Beschleunigung realisiert. Je schneller am Knopf gedreht wird, umso schneller ändert sich der Wert. Das ergibt ein gutes haptisches Gefühl, und man kann größere Bereiche einstellen, ohne dass umgegriffen werden muß.
volatile int16_t enc_pos; #define PHASE_A (PINA & 1<<PA1) #define PHASE_B (PINA & 1<<PA2) // ISR wird z.B. jede ms aufgerufen ISR(SIG_OUTPUT_COMPARE1A) { #define DYNAMIK 40 static uint8_t enc_ab = 0x01; // speichert Encoderzustand 0..3 static uint8_t enc_accel = 0; // speichert die aktuelle Beschleunigung int16_t p; // (position) lokale Variable für Positionsberechnung int8_t m = 0; // (moved) lokale Variable für Bewegungsberechnung if (PHASE_A) m = 1; // Gray nach binär wandeln if (PHASE_B) m ^= 3; m -= enc_ab; // Differenz zeigt an, ob sich der Encoder bewegt hat // gültige Werte für Differenz (m): -1, 0, +1 // sonst ist die Abtastfrequenz zu niedrig! if (enc_accel) enc_accel--; // solange >0: pro ms um 1 "entschleunigen" if (m) { // wenn m ungleich 0 --> Positionsänderung am Encoder enc_ab += m; // Bewegungsdifferenz auf alten Encoderzustand aufaddieren p = enc_pos; // nicht auf globaler Variable (enc_pos) herumrechnen if (enc_accel<255-DYNAMIK) // neuen Beschleunigungswert berechnen: enc_accel += DYNAMIK; // solange <255 Beschleunigungswert aufaddieren else enc_accel = 255; // sonst auf 255 begrenzen (diese Zeile kann weggelassen werden) if (m&2) // Bit 1 = Vorzeichen = Richtung (+/-) p += 1+(enc_accel>>6); // Erst Beschleunigungswerte >64 (wegen >>6) else // werden berücksichtigt. p -= 1+(enc_accel>>6); // Diese Division (Shift 6) evtl. anpassen... enc_pos = p; // lokale Variable auf globale Variable zurückkopieren } : // ab hier kommt sonstiger Code im Timerinterrupt }
Im Beispiel sind die beiden Spuren an den Portpins 1 und 2 vom Port A angeschlossen. In der Variablen m steht ein Wert für die Bewegung: -1, 0, +1 (11, 00 und 01). Ein Wert ungleich 0 bedeutet also: Bewegung. PeDa fragt das Bit 0 explizit ab (m&1), ich habe das reduziert auf m!=0. Im Bit 1 (m&2) steckt dann die Bewegungsrichtung, mit der die Position hoch- oder runtergezählt wird.
Mit dem #define DYNAMIK lässt sich der "Biss", also die Beschleunigung einstellen. Leider funktioniert das z.B. mit den billigen Pollin-Encodern nur eingeschränkt, weil die pro Raste zwei Schritte machen. Also ist da etwas Spielen und Ausprobieren mit dem Wert angesagt. Ich habe noch jedesmal einen brauchbaren Wert gefunden. Mit einem uint16_t für enc_accel kann der Dynamikbereich wesentlich ausgeweitet werden.
Die Umwandlung vom Gray- in den Binärcode erfolgt mit dem XOR Verfahren wie auf Software/Graycode im letzten Beispiel beschrieben.
Im PDF CY8CKIT-049-4xxx PSoC 4 Rotary Encoder für Cypress Mikrocontroller hat Reiner Wehner das Ganze ab Seite 37 fein säuberlich analysiert. Und im Forum dort wird das im Thread Quadrature decoder at PSoC 4 ausdiskutiert, dort sind dann auch Links zur Umsetzung in Software zu finden.
Donnerstag, 25. November 2021
Entkopplung
Was ist "Entkopplung"
Ein heisses Thema sind Blockkondensatoren oder Entkopplungskondensatoren oder Pufferkondensatoren an integrierten Schaltkreisen. Manch einer nimmt noch immer den altbewährten 100nF und dazu eine Anschlusstechnik aus einem Lehrbuch von 1980: irgendwie in die Nähe des ICs. Das hat bei den damals verfügbaren langsamen und wenig komplexen ICs auch ausgereicht ("langsam" bedeutet hier übrigens nicht "niedrige Taktfrequenz", sondern "langsame Flankenansteigszeit wegen hochohmiger Transistoren").
Heute haben ICs aber viele interne Komponenten und dementsprechend viele Flipflops, die gleichzeitig schalten und beim Umschalten durch das Umladen der nachfolgenden Eingangskapazitäten Stromspitzen erzeugen. Die Versorgung des ICs muss 1. diese Stromspitzen bereitstellen und 2. dafür sorgen, dass sie nicht in die weite Welt hinaus abgestrahlt werden. Für beide Aufgaben ist hier der Entkopplungskondensator wichtig. Im 1. Fall stellt er niederimpedant den Strom für die Funktion des ICs zur Verfügung, und im 2. Fall sorgt er dafür, dass diese schnellen Stromänderungen nur ganz lokal passieren.
Wie auch beim Layout von Schaltreglern geht es darum, die Stromschleifen, auf denen solche schnellen Stromänderungen passieren, möglichst klein zu halten, dass sie niederimpedant sind (der Amerikaner sagt: "Each mm has its nH") und sie aufgrund ihrer geringen Größe nicht als Störsender wirken können.
Dazu einige wichtige Grundlagen (es kann nicht schaden, diese allgemein gültigen Regeln zu kennen und IMMER im Hinterkopf zu behalten):
1. Regel
Wo ein Strom rausfließt muss auch ein genau gleich großer wieder rein.
Diese "beiden Ströme" sind ein und der selbe Strom.
Dieser Strom soll möglichst ohne Umwege "im Kreis" fließen können.
2. Regel
Leiterbahnschleifen sind Spulen und Spulen sind Elektromagnete und Antennen.
Im Allgemeinen wollen wir keine Elektromagnete und Funkgeräte auf Platinen bauen.
3. Regel
Ein Kondensator ist gleichzeitig auch ein Widerstand und eine Spule.
Seine Impedanzkurve hat deswegen eine Frequenzabhängigkeit.
Entkoppelkondensatoren versorgen ICs im ns-Bereich mit Strom. Sie puffern (deshalb auch Pufferkondensator) die Stromspitzen, die entstehen, wenn Ausgänge von CMOS-Flipflops im IC ihren Pegel umschalten. Deren Ausgangstreiberstufen müssen die Umladeströme für den nächsten CMOS-Eingangs-Kondensator liefern. Weil meist im ganzen IC ein Takt (oder davon abgeleitete synchrone Takte) verwendet werden, schalten viele Flipflops im IC gleichzeitig. Daraus resultieren schnelle Stromänderungen an den Versorgungsanschlüssen des ICs, die einen kurzen und induktivitätsarmen Anschluss des Entkoppelkondensators erfordern.
Für einen "durchschnittlichen" Schaltkreis wie z.B. einen uC, in dem viele Komponenten (Speicher, Alu, Schnittstellen, Timer...), untergebracht sind, gelten diese Betrachtungen:
So sollte man es nicht machen:
Das rechte Beispiel birgt im Besonderen die Gefahr, dass der Blockkondensator den Bezug zu "seinen beiden" Versorgungspins verliert, weil er im Laufe des Layouts (wenn man immer mehr Leitungen zwischen den Kondensator und "seine" Versorgungsanschlüsse zwängen muss) irgendwie immer weiter vom IC wegwandert.
So sieht z.B. eine brauchbare Entkopplung eines ICs aus:
Hier darf der Tantalkondensator natürlich auch weiter weg auf der Platine sitzen, da er nur noch niederfrequente Stromänderungen zu bedienen hat.
Bei vielen ICs sind für eine einfache Entkopplung die Versorgungspins paarweise herausgeführt. Das haben die Chipdesigner nicht zum Zeitvertreib gemacht, sondern damit die Blockkondensatoren optimal angeschlossen werden können. Eine gute Abblockung eines uCs gegen die Versorgung könnte so aussehen:

Diese Desings sind 4-lagig, die Durchkontaktierungen gehen direkt auf die jeweilige Versorgungslage.
SMD-Kondensatoren im 0402 oder auch 0201 Format erlauben eine Platzierung ganz dicht an den Bauteilpins:
Natürlich ist das ganze Thema namens Power Integrity bändeweise buchfüllend und die Entkopplung muss bei anderen Anforderungen auch entsprechend angepasst werden. So ist z.B. beim Anschluss von BGAs logischerweise ein anderes Entkopplungs- und Versorgungskonzept nötig, weil dort ein Versorgungspin natürlich erst auf die Versorgungsfläche durchkontaktiert und dann diese lokale Versorgungsfläche entkoppelt wird. Aber die Grundlagen sind die selben: kleine Stromschleifen, kleine Impedanzen.
Ströme über IO-Pins
Natürlich darf bei der Betrachtung des Themas "Entkopplung" nicht vergessen werden, dass auch aus den IC-Augangspins Ströme heraus und in andere IC-Eingangspins hinein fließen. Auch diese Ströme müssen wieder zurück zum treibenden IC, weil jeder Stromkreis geschlossen ist. Bei sehr IO-lastigen Anwendungen mit schnellen (steilflankigen) Ein- und Ausgängen (z.B. DDRAM) muss dementsprechend die Entkopplung einen Schritt zurücktreten und es müssen vorrangig die Strompfade auf der Masse und in der Versorgungslage zwischen den beteiligten ICs optimiert und verkürzt werden. In diesem Fall ist dann auch nicht der IC der potentielle Störer, sondern die Datenübertragung zwischen den ICs.
Versorgungskonzept
Darüber hinaus muss VOR dem Entkoppeln der ICs von der Versorgung ein vernünftiges Versorgungskonzept auf der Platine erstellt sein. "Versorgung" bedeutet übrigens nicht nur "Masse", sondern eben auch Vcc (oder V+ oder Vdd oder wie die auch immer heißen mag). Es hilft nicht, wenn der Pufferkondensator auf 1mm an die Versorgungspins platziert wurde, wenn dann die Verbindung zwischen mehreren solcher Versorgungspärchen auf der Leiterplatte nur mit dünnen Leitungen über Umwege erfolgt. In diesem Fall können sich zwischen den Versorgungspärchen Potentialverschiebungen ergeben, die die Funktion des ICs beeinträchtigen.
Zusammengefasst:
Es muss also beim Layout zuallererst eine "stabile" Masse- und Vcc-Anbindung hergestellt werden.
Von dieser Versorgung wird dann der angeschlossene IC mit den oben beschriebenen Maßnahmen entkoppelt.
Ein Kompromiss
Weil jedes Layout irgendwie immer einen Kompromiss darstellt, sollte man das Ganze nicht unnötig auf die Spitze treiben. Für eine "übliche" Mikrocontrollerschaltung z.B.
mit einem AVR oder PIC Controller reicht es meist aus, wenn überhaupt ein Blockkondensator
in der Nähe der ICs sitzt. Besser als gar nichts ist das auf jeden
Fall...
Zu diesem Thema finden sich interessante und durchaus kontroverse Diskussionen auf dem mikrocontroller.net:
www.mikrocontroller.net: Abblockkondensatoren, wie routen?
und den
www.mikrocontroller.net: Abblockkondensator 1 µF oder 100 nF besser?
und auch den hier
www.mikrocontroller.net: Abblockkondensator bei gegenüberliegenden Pins für VCC und GND
Original vom 9.10.2008


