Introduction: Atomic Force Microscope Model

A simple and cheap model of an Atomic Force Microscope. With 3D printable parts, and simple electronics. Suitable for every secondary school. Some stuff is in Dutch...

Supplies

Step 1: Print 3D Parts and Construct

Print all 3D parts. Used .4 nozzle and Prusament PLA.

construct using the m10 and m6 bolts and nuts and using double sided sticky tape.


Step 2: Connect Circuit

Step 3: Upload Arduino Code

Step 4: Run Python Jupyter Notebook Code

import serial

from mpl_toolkits.mplot3d import Axes3D

import matplotlib.pyplot as plt

import matplotlib.cm as cm

import time


# Seriële poortinstellingen

serial_port = '/dev/ttyACM0' # Pas dit aan naar de juiste poort

baud_rate = 9600


# Aantal datapunten om te plotten

max_data_points = 3000


# Lijsten om gegevens op te slaan

data = [] # data is recorded first in this array, and then split in the followin arrays:

x_data = []

y_data = []

voltage_data = []


# Maak een seriële verbinding

ser = serial.Serial(serial_port, baud_rate)


while True:

  try:

    ser.close()

    time.sleep(1)

    ser.open()

    print("serial opened")

    time.sleep(1)

    print("serial started")

    read=ser.readline()

    #print(read.decode('utf-8'))

    break # Exit this loop if start is successfull

  except:

    print("Failed to start or read serial. Retrying in 1 second...")

    time.sleep(1) # Wait for 1 second before retrying

     

while True:

  try:

    if ser.in_waiting > 0:

      line = ser.readline().decode().strip()

      #print(line) #this show all incoming data

      if line:

        data.append(line)

         

      if len(data) >= max_data_points: # Check if max. data points is recorded, then stop recording and plot

        break

  except UnicodeDecodeError:

    continue  

  except Exception as e:

    print(f"Exception occurred: {str(e)}")

    break


ser.close() # Close the serial connection     

print('Data collection completed.')


for line in data: 

  #range(max_data_points):

  # Lees een regel van de seriële poort

  #line = ser.readline().decode().strip()

   

  # Split de regel in x, y en voltage waarden

  values = line.split(',')

  x = int(values[0].split(':')[1].strip())

  y = int(values[1].split(':')[1].strip())

  voltage = float(values[2].split(':')[1].strip())

   

  # Voeg de waarden toe aan de lijsten

  x_data.append(x)

  y_data.append(y)

  voltage_data.append(voltage)


# Filter data based on voltage

filtered_x_data = []

filtered_y_data = []

filtered_voltage_data = []


for i, voltage in enumerate(voltage_data):

  if voltage >= 1:  #change to set groundlevel / remove noise 

    filtered_x_data.append(x_data[i])

    filtered_y_data.append(y_data[i])

    filtered_voltage_data.append(voltage)


# Plot the filtered data

fig = plt.figure(figsize=(12,12))

ax = fig.add_subplot(111, projection='3d')

ax.scatter(filtered_x_data, filtered_y_data, filtered_voltage_data, c=filtered_voltage_data, cmap='hot_r')


# Set the axes labels

ax.set_xlabel('X')

ax.set_ylabel('Y')

ax.set_zlabel('Voltage')


# Set up the color bar

norm = plt.Normalize(min(filtered_voltage_data), max(filtered_voltage_data))


cbar = plt.colorbar(cm.ScalarMappable(norm=norm, cmap='hot_r'))

cbar.set_label('Voltage (V)')


# Set the axis limits

ax.set_xlim(min(filtered_x_data), max(filtered_x_data))

ax.set_ylim(min(filtered_y_data), max(filtered_y_data))

ax.set_zlim(1, max(filtered_voltage_data)) # Set the minimum voltage to 1V


# Display the plot

plt.show()


Step 5: Manual, Example Data, Uses