Bildquelle: Wikipedia
Ein atomic force microscope oder auf Deutsch Rasterkraftmikroskop dient zur Sichtbarmachung kleinster Oberflächenstrukturen, indem ein Cantileverarm mit einer sehr dünnen und spitzen Spitze an dessen Unterseite über eine zu untersuchende Probe geführt wird. Im Kontaktmodus berührt die Spitze die Oberfläche. Durch kleinste atomare Unebenheiten bewegt sich auch der Cantileverarm entsprechend. Auf dessen Oberseite befindet sich ein Spiegel und ein Laser ist auf diesen Spiegel gerichtet. Bewegt sich nun der Cantileverarm minimalst nach oben oder unten, wird der Laserstrahl geringfügig anders reflektiert und er trifft einen Lichtsensor an einer anderen Stelle. Infolgedessen regelt das AFM die z-Höhe des Objekts dermaßen, dass der Laserstrahl wieder zentrisch auf den Lichtsensor trifft. Die jeweils dafür notwendige z-Ansteuerung ist dann ein Maß für die atomaren Kräfte zwischen Spitze und Oberfläche bzw. für die Oberflächenstruktur.
Mehr Informationen: https://de.wikipedia.org/wiki/Rasterkraftmikroskop
Mein Aufbau unterscheidet sich ein wenig vom oben beschriebenen. Anstelle der Rückkopplung auf einen Piezo zur z-Veränderung des Objekttisches verwende ich einen Liniensensor für die Detektion des Laserstrahls und lasse die Höhe des Objekttisches unverändert. Eine Verbiegung des Cantilevers führt dann bei meinem Aufbau zu einem anderen Auftreffpunkt des Lasers auf dem Liniensensors. Dessen veränderte Position ist dann ein Maß für die Oberflächenstruktur.
Als Liniensensor verwende ich einen TSL1401. Dieser kam bereits bei meinem Arduino-Spektroskop (https://stoppi-homemade-physics.de/spektroskop-arduino/) zum Einsatz. Er besitzt auf einer Länge von rund 7.5 mm 128 Pixel; die Pixelgröße beträgt daher ca. 59 µm.
Damit der Laser auch immer meinen Sensor trifft, werde ich einen Linienlaser verwenden und dessen Strich horizontal ausrichten. Die Arduino-Software ermittelt dann das Pixel mit der größten Intensität. Die Pixelnummer wird dann graphisch in Graustufen umgewandelt und auf einem 320 x 480 Pixel Display dargestellt. Auf diese Weise erhalte ich dann das Bild der Oberfläche. Für die xy-Verstellung verwende ich zwei DVD-Laufwerke., welche ich günstig auf www.willhaben.at erstehen konnte.
Die beiden Laufwerke steuere ich mit EasyDriver Schrittmotortreibern an:
1000 Schritte bewegen den Schlitten ca. 26 mm, daher beträgt die xy-Auflösung meines AFM dann 26 µm/step. Damit wäre ich eigentlich zufrieden…
Für die Spitze verwende ich entweder einen dünnen Draht aus Nickel-Chrom oder Wolfram. Letzteren habe ich bereits auf ebay bestellt.
Ich habe mich mit 0.5 mm für einen relativ dicken Draht entschieden, da dieser dann deutlich steifer ist und den Cantileverarm sicher trägt ohne sich zu verbiegen. Um dennoch eine höhere Ortsauflösung zu erhalten, werde ich den Wolframdraht mit einem Dremel und einer Schleifscheibe anspitzen.
So, es gab eine kleine Kurskorrektur. Anstelle der selbst gemachten Spitzen aus Wolfram- bzw. Nickeldraht werde ich es einmal mit einer Zirkelspitze probieren. Dies auch deshalb, weil man Nickel und vor allem Wolfram schlecht/nicht löten kann. Vom Zirkel übernehme ich dann gleich auch den Klemmmechanismus der Spitze. Beim Künstlerbedarfshandel (boesner) habe ich mir daher heute einen Zirkel gekauft, wobei ich wie gesagt nur den Teil mit der Metallspitze bzw. das kleine separate Teil gebrauchen kann.
Den Cantileverarm werde ich wohl aus einem dünnen, leicht gebogenen Aluminiumblech fertigen (untere Skizze im Bild):
Die flexible Halterung für den Linienlaser und den Liniensensor werde ich mir aus einer „dritten Hand“ basteln. Das habe ich schon öfters so gemacht und funktionierte eigentlich sehr gut…
Inzwischen habe ich den Cantileverarm aus den dünnen Aluminiumblech und der Zirkelspitze gebastelt. Am Armende habe ich einen Oberflächenspiegel von Astromedia mit Klebeband fixiert.
Das Stativ für den Cantileverarm werde ich aus 15 x 15 mm Vierkantrohr und einer M8-Gewindestange fertigen, das müsste dann hoffentlich ausreichend steif sein.
Inzwischen ist die „dritte Hand“, welche als flexible Halterung für den Laser und den Liniensensor gedacht ist, und das zweite DVD-Laufwerk angekommen.
So, der xy-Tisch ist soweit fertig. Beim Löcherbohren durch den DVD-Schlitten ist dessen Halterung leider abgebrochen. Ich habe es mit 2-Komponentenkleber dann wieder gerichtet, hoffentlich hält alles. Als Reserve habe ich mir ein zweites baugleiches DVD-Laufwerk (NEC DV-5800C) gekauft. Zwei andere gekaufte DVD-Laufwerke waren leider ein Griff ins Klo, da erstens eine Schiene nicht aus Metall war und zweitens der Lesekopf keine ebene Auflage besaß.
Als Halterung für den Cantilever verwende ich ein Stahlvierkantprofil zusammen mit einer M8-Gewindestange:
Mittlerweile ist der TSL1401-Chip im DIP-8 Gehäuse angekommen. Leider funktioniert er nicht so wie gewollt.
Ich habe den Verdacht, dass es sich gar nicht um einen TSL1401, sondern um einen TSL201 mit nur 64 Pixel handelt…
Deshalb habe ich mir auf Aliexpress noch ein TSL1401-Modul mit Linse und zwei Stück TSL1401-Chips ohne Gehäuse gekauft. Diese kamen gestern bei mir an und ich konnte sie gleich erwartungsvoll testen.
Die überschaubare Pinbelegung:
Zum Glück tun sie genau das, was sie sollen und beide (sowohl Chip als auch das Modul) können ohne Probleme vom Arduino angesteuert werden. Der Linienlaser übersteuert aber deutlich den Liniensensor. Deshalb musste ich den Laserstrahl und das Umgebungslicht abschwächen. Dies gelang mir mit einem Neutraldichtefilter der Stärke 1 von Kodak, welcher die Intensität auf das 10^-1 -fache, also 10% abschwächt. Ich musste zwei von diesen übereinander postieren, damit der Peak vom Laser bei nur 1 ms Belichtungszeit nicht sättigt.
Da ich aber für dieses Projekt kein Farbspektrum unterhalb der Helligkeitskurve benötige, habe ich die Software dahingehend abgeändert. Jetzt wird das Helligkeitsprofil nur durch eine grüne Kurve dargestellt:
Da nun eigentlich sämtliche Teile für den Aufbau eingetroffen sind kann ich mich an den finalen, mechanischen Aufbau machen.
So, mittlerweile ist fast ein ganzes Jahr vergangen, in dem ich mich um andere Physikprojekte gekümmert habe. Nun juckte es aber gehörig in meinen Fingern und so begann ich endlich mit den Aufbau:
Für die Laserhalterung verwende ich eine 16 mm Rohrhalterung aus dem Baumarkt. Damit das 12 mm messende Lasergehäuse fest darin sitzt, musste ich es mit Isolierband umwickeln:
Das erste Testobjekt war eine 2 Euro Münze. Nicht gerade der einfachste Test, denn laut ChatGPT besitzt das Relief darauf nur eine Höhe zwischen 80 und 200 µm…
Hier der erste Scan:
Man kann tatsächlich schon etwas erkennen 🙂
Der Kontrast ist leider sehr gering. Ich werde daher wohl den Liniensensor weiter weg vom Scantisch postieren, damit die von der Laserlinie zurückgelegte Strecke zunimmt und so den Liniensensor mehr ausfüllt.
Dazu eine kurze Abschätzung: Gehen wir von einem 100 µm Höhenprofil aus. Dann neigt sich der Cantilever mit der Länge L um den Winkel 100 µm/L. Durch die Reflexion am Oberflächenspiegel wird dann die Laserlinie um den Winkel 2 · 100 µm/L abgelenkt. Trifft diese nach der Reflexion im Abstand d auf den Liniensensor, so beträgt die von der Laserlinie überstrichene Strecke 2 · 100 µm · d/L. Nehmen wir weiters an, es gelte das Verhältnis d/L = 5, sprich der Liniensensor ist 5-mal so weit vom Spiegel entfernt wie die Länge des Cantilevers. Dann legt die Laserlinie am Ort des Sensors die Strecke 2 · 100 µm · 5 = 1000 µm = 1 mm zurück. Das ist nicht wirklich viel, zumal der Sensor eine Länge von ca. 7.5 mm besitzt. Es wird also nur 1/7.5-tel des ganzen Sensors ausgenützt. Um diesen Wert zu erhöhen, kann ich entweder stärkere Profile untersuchen oder eben den Abstand Spiegel-Sensor erhöhen.
Ich stellte zudem fest, dass die Zirkelspitze beim Verfahren des Tisches an Kanten hängen blieb und sich seitlich neigte. Um dies zu verhindern bastelte ich mir aus einer dicken Aluleiste eine Führung. Mittels zweier M3 Schrauben nehme ich die Spitze in die Zange. Jetzt kann sich diese nur mehr nach oben und unten bewegen, so wie es sein soll…
So sieht es übrigens während des Bastelns in meiner Küche aus. Dank der großen unlängst durchgeführten Aufräumaktion kann ich mich aber nun in meiner Wohnung endlich wieder frei bewegen.
Die seitliche Führung der Zirkelspitze funktioniert recht gut, wodurch diese nicht mehr so stark an Kanten hängenbleibt. Scanne ich aber in beide x-Richtungen, also von links nach rechts und von rechts nach links, kommt es durch das geringe Hängenbleiben zu unscharfen Kanten im Bild:
Daher habe ich die Software dahingehend abgeändert, dass nun nur noch von links nach rechts gescannt wird. Als neues Testobjekt verwendete ich eine 1 cent Münze:
Zum Glück hatte ich mein Wohnzimmer/meine Wohnung kurze Zeit zuvor komplett aufgeräumt und mit Regalen ausgestattet. Dadurch war der Wohnzimmerboden nach mehr als 10 Jahren wieder frei geworden. Den Platz benötigte ich auch für dieses Experiment, denn der Abstand Liniensensor-Cantilever war doch schon erheblich:
Man erkennt nun im Bild deutlich schärfere Kanten, Heureka 😉
Mit den Millimetermaßstab im Bild konnte ich zunächst die Breite der 1 auf der Münze und dann den Maßstab der Abbildung auf dem Display ermitteln. Ich komme auf einen Wert von rund 10 µm/Pixel. Weiter oben habe ich bereits die Schrittweite der Motoren abgeschätzt und kam auf einen Wert von etwas mehr als 20 µm/step. Da ich aber pro Schritt immer ein 2 Pixel x 2 Pixel Quadrat zeichne, erklärt sich der Wert von 10 µm/Pixel.
Eine schnelle Kontrasterhöhung mit Gimp ergab dann folgendes Bild:
Zu guter Letzt habe ich dann noch einmal ChatGPD konsultiert:
Den Höhenunterschied von ca. 0.075 mm auf der Münze kann ich also mit meinem AFM-Nachbau ohne Probleme auflösen. Ich werde zum Abschluss noch eine kupferbeschichtete Platine untersuchen. Deren Kupferschicht sollte eine Stärke von 35 µm besitzen.
Den Aufbau habe ich inzwischen auch finalisieren können, wobei die Holzplatte rund 40 x 50 cm misst.
Wie versprochen habe ich noch eine kupferbeschichtete Lochrasterplatine untersucht. Man erkennt recht deutlich den Buchstaben K. Ich muss aber anmerken, dass die Bildqualität beim Abfotografieren des Displays erheblich leidet. In natura beim Betrachten des Displaybilds mit dem Auge sind die Konturen deutlich besser zu sehen…
Der Arduino-Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 |
#include <AH_EasyDriver.h> #include <UTFT.h> // Declare which fonts we will be using extern uint8_t SmallFont[]; UTFT myGLCD(ILI9486,38,39,40,41); //AH_EasyDriver(int RES, int DIR, int STEP); // RES -> RESOLUTION per full revolve // DIR -> DIRECTION PIN // STEP -> STEPPING PIN AH_EasyDriver stepper_x(200,9,8); // Initialisation AH_EasyDriver stepper_y(200,11,10); // Initialisation // x-Stepper: // =============== // DIR-Pin = Pin 9 // STEP-Pin = Pin 8 // y-Stepper: // =============== // DIR-Pin = Pin 11 // STEP-Pin = Pin 10 const int buttonPin = 12; // button-pin int i,j,k,l,m,n; // Zählvariablen int xPos, yPos; // Bildschirmposition int value; // grey-value #define clockPin 2 // clock #define siPin 3 // Start Integration #define VOUT A0 // pixel intensity in the analog channel A0 long exposure; int Helligkeiten[128]; int Pixel_max, Helligkeit_max; // ================ // === SETUP === // ================ void setup() { // Serial.begin(9600); pinMode(buttonPin, INPUT); pinMode(siPin, OUTPUT); pinMode(clockPin, OUTPUT); Serial.begin(115200); myGLCD.InitLCD(); myGLCD.setFont(SmallFont); myGLCD.clrScr(); myGLCD.setColor(255, 255, 0); myGLCD.fillRect(0, 0, 479, 13); myGLCD.setColor(0, 0, 0); myGLCD.setBackColor(255, 255, 0); myGLCD.print("Atomic Force Microscope - stoppi", CENTER, 1); stepper_x.setSpeedRPM(20); // RPM , rotations per minute stepper_y.setSpeedRPM(20); // RPM , rotations per minute // RPM = 1 entspricht einer Umlaufszeit von 30 Sekunden!!! // RPM = 10 entspricht einer Umlaufszeit von 3 Sekunden!!! // RPM = 20 entspricht einer Umlaufszeit von 1.5 Sekunden!!! // ======================================================= //stepper.setMicrostepping(3); // 0 -> Full Step // 1 -> 1/2 microstepping // 2 -> 1/4 microstepping // 3 -> 1/8 microstepping //stepper.setSpeedHz(100); // Hz, steps per second stepper_x.move(200,1); // Heimposition stepper_y.move(200,1); // Heimposition exposure = 0; // Belichtungszeit in ms } // =============== // === LOOP === // =============== void loop() { /* stepper_x.move(1000,0); // 0 = im Uhrzeigersinn, 1 = gegen Uhrzeigersinn stepper_x.move(1000,1); // 0 = im Uhrzeigersinn, 1 = gegen Uhrzeigersinn */ // move = 800 entspricht einer vollen Umdrehung!!! // =============================================== //stepper.rotate(180.0); // Rotiert um einen bestimmten Winkel, wobei 180 = eine volle Umdrehung!!! //stepper.revolve(1.0); // Rotiert vollständig herum eine bestimmte Anzahl, wobei 1 = 2 volle Umdrehungen!!! //delay(100); while(digitalRead(buttonPin)) { // wait for push start-button myGLCD.clrScr(); myGLCD.setColor(255, 255, 0); myGLCD.fillRect(0, 0, 479, 13); myGLCD.setColor(0, 0, 0); myGLCD.setBackColor(255, 255, 0); myGLCD.print("Atomic Force Microscope - stoppi", CENTER, 1); myGLCD.setColor(255, 255, 255); myGLCD.setBackColor(0, 0, 0); myGLCD.drawLine(10, 300, 400, 300); myGLCD.drawLine(10,300,10,305); myGLCD.drawLine(70,300,70,305); myGLCD.drawLine(130,300,130,305); myGLCD.drawLine(190,300,190,305); myGLCD.drawLine(250,300,250,305); myGLCD.drawLine(310,300,310,305); myGLCD.drawLine(370,300,370,305); myGLCD.drawLine(10, 40, 10, 300); myGLCD.printNumI(0, 7, 310); myGLCD.printNumI(20, 65, 310); myGLCD.printNumI(40, 125, 310); myGLCD.printNumI(60, 185, 310); myGLCD.printNumI(80, 245, 310); myGLCD.printNumI(100, 300, 310); myGLCD.printNumI(120, 360, 310); getCamera(); myGLCD.setColor(0, 255, 0); for(m = 0; m < 127; m++) { myGLCD.drawLine(10 + 3*m, 299 - 0.25 * Helligkeiten[m], 10 + 3*m + 3, 299 - 0.25 * Helligkeiten[m+1]); } delay(1000); } // Start des Scans // =============== myGLCD.clrScr(); myGLCD.setColor(255, 255, 0); myGLCD.fillRect(0, 0, 479, 13); myGLCD.setColor(0, 0, 0); myGLCD.setBackColor(255, 255, 0); myGLCD.print("Atomic Force Microscope - stoppi", CENTER, 1); for (j = 0; j <= 149; j++) { for (i = 0; i <= 199; i++) { getCamera(); Pixel_max = 0; // Pixel-Nummer mit der größten Helligkeit Helligkeit_max = 0; // maximale Helligkeit for (k = 0; k < 128; k++) { if (Helligkeiten[k] > Helligkeit_max) { Helligkeit_max = Helligkeiten[k]; Pixel_max = k; } } //value = random(1023); //value = map(value,0,1023,0,255); value = Pixel_max * 2; // grey-values between 0 and 254 myGLCD.setColor(value, value, value); myGLCD.fillRect(i * 2, 15 + j * 2, i * 2 + 1, 15 + j * 2 + 1); stepper_x.move(1,0); delay(2); } stepper_x.move(200,1); stepper_y.move(1,0); } stepper_x.move(200,1); // homing stepper_y.move(200,1); // homing while(digitalRead(buttonPin)) { // Wait for push button } myGLCD.clrScr(); delay(1000); } // ================================ // ===== Helligkeiten einlesen ==== // ================================ void getCamera() { digitalWrite(clockPin, LOW); digitalWrite(siPin, HIGH); digitalWrite(clockPin, HIGH); digitalWrite(siPin, LOW); digitalWrite(clockPin, LOW); for (l = 0; l < 128; l++) { digitalWrite(clockPin, HIGH); digitalWrite(clockPin, LOW); } //delayMicroseconds(exposure); // Belichtungszeit = Verzögerung in µs delay(exposure); // Belichtungszeit = Verzögerung in ms digitalWrite(siPin, HIGH); digitalWrite(clockPin, HIGH); digitalWrite(siPin, LOW); digitalWrite(clockPin, LOW); for (l = 0; l < 128; l++) { delayMicroseconds(20); Helligkeiten[l] = analogRead(VOUT); // Wertebereich [0,1023] digitalWrite(clockPin, HIGH); digitalWrite(clockPin, LOW); } delayMicroseconds(20); } |
Damit ist dieses Projekt abgeschlossen. Gekostet hat mich der ganze Spaß weniger als 100 Euro, also nur einen Bruchteil verglichen mit einem echten AFM. Man könnte bestimmt noch die Auflösung verbessern, indem man zum Beispiel Laufwerke mit einer geringeren Schrittweite auswählt. Ich verwende ja uralte DVD-ROM-Laufwerke. Mit den Ergebnissen bin ich aber dennoch sehr zufrieden 😉
Das Youtube-Video reiche ich wie immer nach…