Compare commits

...

8 commits

39 changed files with 1276 additions and 34 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
*.FCBak *.FCBak
.jupyter_ystore.db .jupyter_ystore.db
.ipynb_checkpoints/ .ipynb_checkpoints/
__pycache__/

10
.pre-commit-config.yaml Normal file
View file

@ -0,0 +1,10 @@
repos:
- repo: local
hooks:
- id: nbconvert
name: Convert Jupyter Notebooks to Markdown
language: system
entry: ./.pre-commit/run-nbconvert.sh
files: ^.*\.ipynb$
pass_filenames: true
fail_fast: true

8
.pre-commit/run-nbconvert.sh Executable file
View file

@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -e
for nb in "$@"; do
echo "Converting $nb to markdown..." >&2
out_dir="$(dirname "$nb")"
jupyter nbconvert "$nb" --to markdown --output-dir "$out_dir/rendered"
done

View file

@ -1,8 +1,17 @@
{ {
"cells": [ "cells": [
{
"cell_type": "markdown",
"id": "e3b0d5f1-f107-4d98-adad-403bfd9bc2b9",
"metadata": {},
"source": [
"## Connector Plate Load Estimate\n",
"This notebook calculates an estimate maximum load for the connector plates used in the STM assembly."
]
},
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 20, "execution_count": 1,
"id": "e70e1154-7d5d-4c92-9f62-2dd2c734f586", "id": "e70e1154-7d5d-4c92-9f62-2dd2c734f586",
"metadata": { "metadata": {
"editable": true, "editable": true,
@ -24,7 +33,7 @@
"<Quantity(171.312324, 'kilogram')>" "<Quantity(171.312324, 'kilogram')>"
] ]
}, },
"execution_count": 20, "execution_count": 1,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
@ -54,9 +63,9 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "Python 3 (ipykernel)", "display_name": "stm-env",
"language": "python", "language": "python",
"name": "python3" "name": "stm-env"
}, },
"language_info": { "language_info": {
"codemirror_mode": { "codemirror_mode": {
@ -68,7 +77,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.13.3" "version": "3.13.7"
} }
}, },
"nbformat": 4, "nbformat": 4,

File diff suppressed because one or more lines are too long

BIN
Calculations/Laser-Wobble/output_14_0.png (Stored with Git LFS)

Binary file not shown.

View file

@ -0,0 +1,29 @@
## Connector Plate Load Estimate
This notebook calculates an estimate maximum load for the connector plates used in the STM assembly.
```python
from pint import UnitRegistry
unit = UnitRegistry()
unit.formatter.default_format = "~"
preload_m4 = 3000 * unit.N
n_screws = 4 # 2 on each side
friction_coeff = 0.21 # Al on Al
safety_factor = 1.5
max_load = preload_m4 * friction_coeff * n_screws / safety_factor / unit.standard_gravity
max_load.to(unit.kg)
```
171.312323780292 kg
```python
```

View file

@ -0,0 +1,139 @@
```python
import magpylib as magpy
from scipy.spatial.transform import Rotation as R
import pyvista as pv
import numpy as np
# Creation of our magnets including place and orientation in the space (all dimensions guessed, need to put in correct ones)
# magnetic polarization of 1.5 T pointing in x-direction
# Dimensions assumed are 0.5, 1.5 and 3 cm (x,y,z)
# Question: Is there a more elegant way to do this? Should I make a for-loop for creating our magnet array?
magnet1 = magpy.magnet.Cuboid(polarization=(1.5, 0, 0), dimension=(0.005, 0.015, 0.03))
magnet1.position = (0, 0, 0)
magnet1.orientation = R.from_euler("y", 90, degrees=True)
magnet2 = magpy.magnet.Cuboid(polarization=(1.5, 0, 0), dimension=(0.005, 0.015, 0.03))
magnet2.position = (0.04, 0, 0)
magnet2.orientation = R.from_euler("y", 90, degrees=True)
magnet3 = magpy.magnet.Cuboid(polarization=(1.5, 0, 0), dimension=(0.005, 0.015, 0.03))
magnet3.position = (0, 0.04, 0)
magnet3.orientation = R.from_euler("y", 90, degrees=True)
magnet4 = magpy.magnet.Cuboid(polarization=(1.5, 0, 0), dimension=(0.005, 0.015, 0.03))
magnet4.position = (0.04, 0.04, 0)
magnet4.orientation = R.from_euler("y", 90, degrees=True)
magnet5 = magpy.magnet.Cuboid(polarization=(1.5, 0, 0), dimension=(0.005, 0.015, 0.03))
magnet5.position = (0, 0.08, 0)
magnet5.orientation = R.from_euler("y", 90, degrees=True)
magnet6 = magpy.magnet.Cuboid(polarization=(1.5, 0, 0), dimension=(0.005, 0.015, 0.03))
magnet6.position = (0.04, 0.08, 0)
magnet6.orientation = R.from_euler("y", 90, degrees=True)
magnet7 = magpy.magnet.Cuboid(polarization=(1.5, 0, 0), dimension=(0.005, 0.015, 0.03))
magnet7.position = (0, 0.12, 0)
magnet7.orientation = R.from_euler("y", 90, degrees=True)
magnet8 = magpy.magnet.Cuboid(polarization=(1.5, 0, 0), dimension=(0.005, 0.015, 0.03))
magnet8.position = (0.04, 0.12, 0)
magnet8.orientation = R.from_euler("y", 90, degrees=True)
# Grouping the magnets to collection
coll = magpy.Collection(magnet1, magnet2, magnet3, magnet4, magnet5, magnet6, magnet7, magnet8)
# Plotting the magnet array
magpy.show(coll, backend="plotly")
```
```python
# Just for visualization: Showing the magnetic field vectors in 3D
spacing = np.array([0.003, 0.003, 0.003]) # defines the grid where the magnetic field is calculated
# The following is for getting the right dimensions and position of the grid
magnets = [magnet1, magnet2, magnet3, magnet4, magnet5, magnet6, magnet7, magnet8]
all_mins = []
all_maxs = []
for m in magnets:
pos = np.array(m.position)
dim = np.array(m.dimension) / 2
all_mins.append(pos - dim)
all_maxs.append(pos + dim)
global_min = np.min(all_mins, axis=0) - 0.02 # add 2cm as buffer
global_max = np.max(all_maxs, axis=0) + 0.02
dimensions = ((global_max - global_min) / spacing).astype(int)
grid = pv.ImageData(
spacing=tuple(spacing),
dimensions=tuple(dimensions),
origin=(-0.02, -0.02, -0.02),
)
# Calculation of the B-field in mT
grid["B"] = coll.getB(grid.points) * 1000
pl = pv.Plotter()
pl.add_mesh(grid.outline(), color="blue", line_width=1)
pl.add_points(grid.points, render_points_as_spheres=True, point_size=2, color='black')
# Add magnetic field vectors as arrows (glyphs)
pl.add_mesh(
grid.glyph(orient="B", scale=True, factor=0.00001), # use "B" vectors, scaling according to field strength, factor to make arrows smaller for visibility
color="blue",
label="Magnetic field vectors"
)
magpy.show(coll, canvas=pl, units_length="m", backend="pyvista")
pl.show()
```
Widget(value='<iframe src="http://localhost:40551/index.html?ui=P_0x7d5c280f7fa0_15&reconnect=auto" class="pyv…
```python
# Introducing the aluminium plate
z_position=0.01 # z_position = distance to the magnets (put in real number here!)
plate_center = [0, 0, z_position]
plate_size = [0.15, 0.40, 0.005] # [b,l, h]
plate = pv.Box(bounds=(
plate_center[0] - plate_size[0]/2, plate_center[0] + plate_size[0]/2,
plate_center[1] - plate_size[1]/2, plate_center[1] + plate_size[1]/2,
plate_center[2] - plate_size[2]/2, plate_center[2] + plate_size[2]/2
))
pl.add_mesh(plate, color="silver", opacity=0.5, label="Aluminum plate")
pl.show()
```
A view with name (P_0x7d5c280f7fa0_15) is already registered
=> returning previous one
Widget(value='<iframe src="http://localhost:40551/index.html?ui=P_0x7d5c280f7fa0_15&reconnect=auto" class="pyv…
```python
```

View file

@ -87,66 +87,66 @@ print(f"Video processing took {processing_time:.2f} seconds ({processing_time /
Snapshot capture of frame 2331 at 77.70s: Intensity 12.612 Snapshot capture of frame 2331 at 77.70s: Intensity 12.612
Snapshot capture of frame 2664 at 88.80s: Intensity 12.257 Snapshot capture of frame 2664 at 88.80s: Intensity 12.257
Snapshot capture of frame 2997 at 99.90s: Intensity 12.728 Snapshot capture of frame 2997 at 99.90s: Intensity 12.728
Video processing took 257.32 seconds (0.086 seconds per frame). Video processing took 300.84 seconds (0.100 seconds per frame).
![png](output_7_1.png) ![png](Laser-Wobble_files/Laser-Wobble_7_1.png)
![png](output_7_2.png) ![png](Laser-Wobble_files/Laser-Wobble_7_2.png)
![png](output_7_3.png) ![png](Laser-Wobble_files/Laser-Wobble_7_3.png)
![png](output_7_4.png) ![png](Laser-Wobble_files/Laser-Wobble_7_4.png)
![png](output_7_5.png) ![png](Laser-Wobble_files/Laser-Wobble_7_5.png)
![png](output_7_6.png) ![png](Laser-Wobble_files/Laser-Wobble_7_6.png)
![png](output_7_7.png) ![png](Laser-Wobble_files/Laser-Wobble_7_7.png)
![png](output_7_8.png) ![png](Laser-Wobble_files/Laser-Wobble_7_8.png)
![png](output_7_9.png) ![png](Laser-Wobble_files/Laser-Wobble_7_9.png)
![png](output_7_10.png) ![png](Laser-Wobble_files/Laser-Wobble_7_10.png)
@ -194,7 +194,7 @@ pass
![png](output_11_0.png) ![png](Laser-Wobble_files/Laser-Wobble_11_0.png)
@ -231,22 +231,111 @@ for a in ax:
a.grid(axis="x", which="both") a.grid(axis="x", which="both")
a.grid(axis="y", which="major") a.grid(axis="y", which="major")
a.axvline(x=1.317, color="red")
a.annotate(" pendulum mode", xy=(1.317, 1080 / 2 - 150))
pass pass
``` ```
![png](output_14_0.png) ![png](Laser-Wobble_files/Laser-Wobble_14_0.png)
Of note is the highlighted _theoretical spring resonance_ which we had previously calculated using the known spring constant and mass of the system. Seeing a very visible peak at this frequency along the Y axis (the axis of the springs) is very nice. Of note is the highlighted _theoretical spring resonance_ which we had previously calculated using the known spring constant and mass of the system. Seeing a very visible peak at this frequency along the Y axis (the axis of the springs) is very nice.
In addition, the pendulum swinging resonance of the assembly was also roughly calculated and drawn into the diagrams. A peak is visible near this frequency as well.
```python ```python
# If you wish to save the extracted displacement data, use this:
# np.save("./laser-results/2025-09-07-011400-analyzed.npy", laser_position) # np.save("./laser-results/2025-09-07-011400-analyzed.npy", laser_position)
``` ```
### Theoretical Transmissibility vs. Measurement
As a further step, the code below calculates the theoretical transmissibility curve and then plots it overlayed with the laser displacement amplitude spectral density in Y direction.
This gives a nice comparison, but the results should be interpreted with care: We do not have information on the spectrum of the excitation that was present during the measurement. For sure it was not even density broadband noise. Additionally, towards lower amplitudes, the measurement data gets increasingly noisy because the signal reaches the limit of the camera resolution.
```python
import math
import numpy as np
from pint import UnitRegistry
unit = UnitRegistry()
unit.formatter.default_format = "~"
# Parameters
spring_constant = 1.1 * 4 * unit.N / unit.mm
spring_length_resting = 112 * unit.mm
weight_total = 14 * unit.kg
dampening = 1 * unit.N / (unit.m / unit.s)
def spring_length_at(weight):
return (weight * unit.standard_gravity / spring_constant + spring_length_resting).to(unit.mm)
def resonant_freq_at(weight):
return (1 / (2 * math.pi) * np.sqrt(spring_constant / weight)).to(unit.Hz)
spring_length = spring_length_at(weight_total)
f0 = resonant_freq_at(weight_total)
print(f"Length: {spring_length:~.1f}")
print(f"Freq: {f0:~.3f}")
def lehr_dampening_factor(d, k, m):
return d / (2 * np.sqrt(m * k))
lehr_dampening = lehr_dampening_factor(dampening, spring_constant, weight_total)
lehr_dampening.ito_reduced_units()
def amplitude_ratio(lehr, f0, f):
eta = f / f0
return 1 / np.sqrt((1 - eta**2)**2 + (2 * eta * lehr)**2)
f_in = np.geomspace(0.5, 90, 100) * unit.Hz
ratio = amplitude_ratio(lehr_dampening, f0, f_in)
```
Length: 143.2 mm
Freq: 2.822 Hz
```python
fig, ax = plt.subplots()
ax.axvline(x=2.822, color="red")
# ax.annotate(" theoretical spring resonance", xy=(2.822, 1080 / 2 - 150))
ax.loglog(frequency_y, amplitude_spectral_density_y, label="laser")
ax.loglog(f_in, ratio * 8, label="transmissibility")
a = ax
a.set_xlabel("frequency / Hz")
# a.set_ylabel("amplitude spectral density")
a.set_xlim(0.5, 15)
a.set_ylim(0.1, 1080 / 2)
a.legend()
a.grid(axis="x", which="both")
a.grid(axis="y", which="major")
pass
```
![png](Laser-Wobble_files/Laser-Wobble_19_0.png)
One more note about this graph: As the input amplitude is unknown, the theoretical curve was roughly scaled to sit ontop of the measurement data (`ratio * 8` in the code above). This is not a proper curve fit.
Also keep in mind that the measurement data gets pretty meaningless below an amplitude of 1 (pixel) due to the resolution limit of the camera.
```python ```python

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,132 @@
```python
import imageio.v2 as imageio
import matplotlib.pyplot as plt
import numpy as np
from scipy import ndimage
# sensor horizontal size
sensor_width = 7.75
# image file
laser = imageio.imread("laser-img/2025-09-07-012621-2mm-aperture.jpg")
```
```python
dimensions = np.array([sensor_width * laser.shape[0] / laser.shape[1], sensor_width])
half_size = dimensions / 2
# Mean of all color channels as an approximation of the monochrome image
laser_monochrome = np.mean(laser, axis=2)
# Normalized intensity map
intensity_map = laser_monochrome / np.max(laser_monochrome)
# Summed intensity in each axis
intensity_x = np.sum(laser_monochrome, axis=0)
intensity_y = np.sum(laser_monochrome, axis=1)
# Center of mass of the laser beam
center = (
(ndimage.center_of_mass(laser_monochrome) / np.array(laser_monochrome.shape) - 0.5)
* dimensions
* [-1, 1]
)
print(f"Center of the beam is at {center[0]:.3f}/{center[1]:.3f}")
# Calculate FWHM in each axis
def fwhm(curve, width):
assert curve.ndim == 1
half_max = (np.max(curve) + np.min(curve)) / 2
diff = curve - half_max
indices = np.where(diff > 0)[0]
return (indices[-1] - indices[0]) / curve.size * width
fwhm_y = fwhm(intensity_y, dimensions[0])
fwhm_x = fwhm(intensity_x, dimensions[1])
print(f"FWHM in X/Y: {fwhm_x:.2f}/{fwhm_y:.2f}")
eccentricity = np.sqrt(1 - (min(fwhm_y, fwhm_x) ** 2 / max(fwhm_y, fwhm_x) ** 2))
print(f"Eccentricity (along cartesian axes): {eccentricity:.5f}")
```
Center of the beam is at -0.088/0.238
FWHM in X/Y: 2.16/1.46
Eccentricity (along cartesian axes): 0.73687
```python
with plt.style.context("dark_background"):
fig, ax = plt.subplots(figsize=(14, 14))
im = ax.imshow(
intensity_map,
cmap="magma",
extent=(-half_size[1], half_size[1], -half_size[0], half_size[0]),
interpolation="none",
vmin=0,
)
ax.set_xlim(-np.max(half_size), np.max(half_size))
ax.set_ylim(-np.max(half_size), np.max(half_size))
xy_scale = max(np.max(intensity_x), np.max(intensity_y))
ax.plot(
np.linspace(-half_size[1], half_size[1], intensity_x.size),
intensity_x / xy_scale - np.max(half_size),
color="red",
)
ax.plot(
intensity_y / xy_scale - np.max(half_size),
np.linspace(half_size[0], -half_size[0], intensity_y.size),
color="red",
)
# reticle
ax.axvline(x=center[1], color="white", linestyle=":")
ax.axhline(y=center[0], color="white", linestyle=":")
ax.axvline(x=center[1] - fwhm_x / 2, color="gray", linestyle=":")
ax.axvline(x=center[1] + fwhm_x / 2, color="gray", linestyle=":")
ax.axhline(y=center[0] - fwhm_y / 2, color="gray", linestyle=":")
ax.axhline(y=center[0] + fwhm_y / 2, color="gray", linestyle=":")
cax = fig.add_axes(
[
ax.get_position().x1 + 0.01,
ax.get_position().y0,
0.02,
ax.get_position().height,
]
)
fig.colorbar(im, cax=cax)
text = (
f"FWHM X/Y: {fwhm_x:.2f} mm/{fwhm_y:.2f} mm\n"
f"Eccentricity: {eccentricity:.5f}\n"
f"Offset X/Y: {center[1]:+.3f} mm/{center[0]:+.3f} mm"
)
fig.text(0.5, 0.05, text, ha="center")
fig
```
![png](Laserbeam_files/Laserbeam_2_0.png)
![png](Laserbeam_files/Laserbeam_2_1.png)
```python
```

BIN
Calculations/rendered/Laserbeam_files/Laserbeam_2_0.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Calculations/rendered/Laserbeam_files/Laserbeam_2_1.png (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -0,0 +1,52 @@
```python
import matplotlib.pyplot as plt
import numpy as np
from pint import UnitRegistry
unit = UnitRegistry()
unit.formatter.default_format = "~"
unit.setup_matplotlib()
gain_first_stage = 10e6 * unit.V / unit.A
gain_second_stage = 100e3 / 1e3
tunnel_current_in = np.linspace(1e-12, 10e-9, 100) * unit.A
volts_out = tunnel_current_in * gain_first_stage * gain_second_stage
plt.xscale("log")
plt.plot(tunnel_current_in, volts_out)
xticks = [1e-12, 10e-12, 100e-12, 1e-9, 10e-9] * unit.A
plt.xticks(xticks, [f'{t.to("nA"):.3f~}' for t in xticks], minor=False)
plt.xlabel("Tunneling Current")
plt.yscale("log")
yticks = [0.001, 0.010, 0.1, 1.0, 5] * unit.V
plt.yticks(yticks, [f'{t.to("V"):.3f~}' for t in yticks], minor=False)
plt.ylabel("Preamp Voltage")
plt.grid(True, "both")
```
![png](Preamp-Current_files/Preamp-Current_0_0.png)
```python
v = 0.359 * unit.V
a = v / (gain_first_stage * gain_second_stage)
a.to("pA")
```
359.0 pA
```python
```

Binary file not shown.

View file

@ -0,0 +1,138 @@
```python
import math
import numpy as np
from pint import UnitRegistry
unit = UnitRegistry()
unit.formatter.default_format = "~"
# Parameters
spring_constant = 1.1 * 4 * unit.N / unit.mm
spring_length_resting = 112 * unit.mm
weight_total = 14 * unit.kg
dampening = 1 * unit.N / (unit.m / unit.s)
def spring_length_at(weight):
return (weight * unit.standard_gravity / spring_constant + spring_length_resting).to(unit.mm)
def resonant_freq_at(weight):
return (1 / (2 * math.pi) * np.sqrt(spring_constant / weight)).to(unit.Hz)
spring_length = spring_length_at(weight_total)
f0 = resonant_freq_at(weight_total)
print(f"Length: {spring_length:~.1f}")
print(f"Freq: {f0:~.3f}")
```
Length: 143.2 mm
Freq: 2.822 Hz
```python
def lehr_dampening_factor(d, k, m):
return d / (2 * np.sqrt(m * k))
lehr_dampening = lehr_dampening_factor(dampening, spring_constant, weight_total)
lehr_dampening.ito_reduced_units()
lehr_dampening
```
0.00201455741006345
```python
def amplitude_ratio(lehr, f0, f):
eta = f / f0
return 1 / np.sqrt((1 - eta**2)**2 + (2 * eta * lehr)**2)
f_in = np.geomspace(0.5, 90, 100) * unit.Hz
ratio = amplitude_ratio(lehr_dampening, f0, f_in)
import matplotlib.pyplot as plt
plt.plot(f_in, ratio)
def transmissibility_plot_setup(plt):
plt.xscale('log')
xticks = [1, 3, 10, 50]
plt.xticks(xticks, [f"{t} Hz" for t in xticks], minor=False)
plt.xlabel("Excitation Frequency")
plt.ylim(0.001, 10)
plt.yscale('log')
plt.ylabel("Transmissibility Ratio")
plt.grid(True, "both")
transmissibility_plot_setup(plt)
```
/usr/lib/python3.13/site-packages/matplotlib/cbook.py:1355: UnitStrippedWarning: The unit of the quantity is stripped when downcasting to ndarray.
return np.asarray(x, float)
![png](Spring-Dimensioning_files/Spring-Dimensioning_2_1.png)
```python
def ratio_for_dkm(d, k, m, f_in):
f0 = (1 / (2 * math.pi) * np.sqrt(k / m)).to(unit.Hz)
lehr = lehr_dampening_factor(d, k, m)
return amplitude_ratio(lehr, f0, f_in)
d = dampening
k = [
spring_constant,
spring_constant / 4,
]
m = [
weight_total,
24 * unit.kg,
]
import matplotlib.pyplot as plt
for k_ in k:
m_ = m[0]
plt.plot(f_in, ratio_for_dkm(d, k_, m_, f_in), label=f"{k_:~.1f}, {m_:~.1f}")
for m_ in m[1:]:
k_ = k[0]
plt.plot(f_in, ratio_for_dkm(d, k_, m_, f_in), label=f"{k_:~.1f}, {m_:~.1f}")
ref_thorlabs_f = [3, 4, 4.42, 5, 6, 7, 8, 9, 10, 40]
ref_thorlabs_r = [2, 5, 15.0, 4, 1.5, 0.7, 0.5, 0.35, 0.28, 0.018]
plt.plot(ref_thorlabs_f, ref_thorlabs_r, label="Thorlabs PTP702 (Passive)")
ref_thorlabs_f = [1, 1.35, 2, 3, 5, 9, 20, 27, 30]
ref_thorlabs_r = [2, 3, 0.9, 0.3, 0.1, 0.02, 0.009, 0.007, 0.0023]
plt.plot(ref_thorlabs_f, ref_thorlabs_r, label="Thorlabs PTS601 (Active)")
plt.legend(loc="upper right")
transmissibility_plot_setup(plt)
```
/usr/lib/python3.13/site-packages/matplotlib/cbook.py:1355: UnitStrippedWarning: The unit of the quantity is stripped when downcasting to ndarray.
return np.asarray(x, float)
![png](Spring-Dimensioning_files/Spring-Dimensioning_3_1.png)
```python
```

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,72 @@
```python
import matplotlib.pyplot as plt
import numpy as np
from pint import UnitRegistry
# Set up unit system
unit = UnitRegistry()
unit.formatter.default_format = "~"
unit.setup_matplotlib()
# Physical constants
e=-1.602176634e-19 * unit.C # electron charge
m=9.109e-31 * unit.kg # electron mass
hbar=6.62607015e-34/2/np.pi * unit.joule * unit.second # Planck constant
phi=4 * unit.eV # Work function (see table)
phi_joule=phi.to("joule")
U=5 *unit.V
# Table working functions different metals
# Metal F(eV)
# (Work Function)
# Ag (silver) 4.26
# Al (aluminum) 4.28
# Au (gold) 5.1
# Cs (cesium) 2.14
# Cu (copper) 4.65
# Li (lithium) 2.9
# Pb (lead) 4.25
# Sn (tin) 4.42
# Chromium 4.6
# Molybdenum 4.37
# Stainless Steel 4.4
# Gold 4.8
# Tungsten 4.5
# Copper 4.5
# Nickel 4.6
# Distance range
Distance_tip_sample=np.linspace(10e-13,2e-10,100)* unit.m
Tunneling_current=U*np.exp(-2*np.sqrt(2*m*phi_joule)/hbar*Distance_tip_sample) /unit.V #please note: This is not the tunneling current as this formular gives just the proportionality. Calculating the current constant is difficult as there are for us unknown parameters
Distance_tip_sample_nm=Distance_tip_sample.to("nm")
plt.plot(Distance_tip_sample_nm, Tunneling_current)
plt.xlabel(f"Distance tip sample [{Distance_tip_sample_nm.units:~P}]")
plt.ylabel(f"Tunneling-Proportionality [arb. Unit]")
plt.xticks(ticks=np.linspace(0, 0.2, 5), labels=[f"{x:.2f}" for x in np.linspace(0, 0.2, 5)])
#plt.yscale("log")
```
([<matplotlib.axis.XTick at 0x7e10b04929b0>,
<matplotlib.axis.XTick at 0x7e10b04b0310>,
<matplotlib.axis.XTick at 0x7e10b034ba60>,
<matplotlib.axis.XTick at 0x7e10b0328670>,
<matplotlib.axis.XTick at 0x7e10b0329360>],
[Text(0.0, 0, '0.00'),
Text(0.05, 0, '0.05'),
Text(0.1, 0, '0.10'),
Text(0.15000000000000002, 0, '0.15'),
Text(0.2, 0, '0.20')])
![png](Tunneling-Current-Distance_files/Tunneling-Current-Distance_0_1.png)

View file

@ -0,0 +1,125 @@
```python
import time
import numpy as np
from matplotlib import pyplot as plt
from rp_overlay import overlay
import rp
fpga = overlay()
rp.rp_Init()
print("Red Pitaya initialized.")
```
Check FPGA [OK].
Red Pitaya initialized.
```python
rp.rp_GenReset()
rp.rp_AcqReset()
print("Reset generator and acquisition.")
```
Reset generator and acquisition.
```python
# Generator parameters
channel = rp.RP_CH_1
channel2 = rp.RP_CH_2
waveform = rp.RP_WAVEFORM_SINE
freq = 1
ampl = 1.0
print("Gen_start")
rp.rp_GenWaveform(channel, waveform)
rp.rp_GenFreqDirect(channel, freq)
rp.rp_GenAmp(channel, ampl)
rp.rp_GenWaveform(channel2, waveform)
rp.rp_GenFreqDirect(channel2, freq)
rp.rp_GenAmp(channel2, ampl)
rp.rp_GenTriggerSource(channel, rp.RP_GEN_TRIG_SRC_INTERNAL)
rp.rp_GenOutEnableSync(True)
rp.rp_GenSynchronise()
print("Started generator.")
```
Gen_start
Generator started.
```python
# Set Decimation
rp.rp_AcqSetDecimationFactor(16384)
rp.rp_AcqSetAveraging(True)
rp.rp_AcqSetGain(rp.RP_CH_1, rp.RP_HIGH)
V=rp.rp_AcqGetGainV(rp.RP_CH_1)
print("GainVoltage", V)
# Set trigger level and delay
rp.rp_AcqSetTriggerLevel(rp.RP_T_CH_1, 0.5)
rp.rp_AcqSetTriggerDelay(0)
# Start Acquisition
print("Acq_start")
rp.rp_AcqStart()
time.sleep(3)
# Specify trigger - immediately
rp.rp_AcqSetTriggerSrc(rp.RP_TRIG_SRC_NOW)
# Trigger state
while 1:
trig_state = rp.rp_AcqGetTriggerState()[1]
if trig_state == rp.RP_TRIG_STATE_TRIGGERED:
break
# Fill state
while 1:
if rp.rp_AcqGetBufferFillState()[1]:
break
### Get data ###
# Volts
N = 16384
fbuff = rp.fBuffer(N)
res = rp.rp_AcqGetOldestDataV(rp.RP_CH_1, N, fbuff)[1]
data_V = np.zeros(N, dtype = float)
X = np.arange(0, N, 1)
for i in range(0, N, 1):
data_V[i] = fbuff[i]
plt.plot(X, data_V)
plt.show()
```
GainVoltage [0, 20.0]
Acq_start
![png](STM_Control_files/STM_Control_3_1.png)
Only run the `rp_Release()` below when you do not want to further acquire data.
```python
# Release resources
rp.rp_Release()
```

BIN
Control/rendered/STM_Control_files/STM_Control_3_1.png (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -0,0 +1,225 @@
# Synchronised one-pulse signal generation and acquisition
Now that we are familiar with generating and acquiring signals with Red Pitaya, we can finally learn a few tricks for combining both and use them in practice. In this example, the acquisition is triggered together with the generation, which is used for measuring cable length and other applications where signal propagation delay is important.
**Note:**
The voltage range of fast analog inputs on the Red Pitaya depends on the input jumper position. HV sets the input range to ±20 V, while LV sets the input range to ±1 V. For more information, please read the following [chapter](https://redpitaya.readthedocs.io/en/latest/developerGuide/hardware/125-14/fastIO.html#analog-inputs).
As previously, create a loop-back from fast analog outputs to fast analog inputs, as shown in the picture below.
Please make sure the jumpers are set to ±1 V (LV).
![Fast loop back](../img/FastIOLoopBack.png "Example of the fast loop back.")
## Libraries and FPGA image
```python
import time
import numpy as np
from matplotlib import pyplot as plt
from rp_overlay import overlay
import rp
fpga = overlay()
rp.rp_Init()
```
Check FPGA [OK].
0
## Marcos
Throughout this tutorial we will mention macros multiple times. Here is a complete list of macros that will come in handy when customising this notebook. The marcos are a part of the **rp** library.
**GENERATION**
- **Waveforms** - RP_WAVEFORM_SINE, RP_WAVEFORM_SQUARE, RP_WAVEFORM_TRIANGLE, RP_WAVEFORM_RAMP_UP, RP_WAVEFORM_RAMP_DOWN, RP_WAVEFORM_DC, RP_WAVEFORM_PWM, RP_WAVEFORM_ARBITRARY, RP_WAVEFORM_DC_NEG, RP_WAVEFORM_SWEEP
- **Generator modes** - RP_GEN_MODE_CONTINUOUS, RP_GEN_MODE_BURST
- **Sweep direction** - RP_GEN_SWEEP_DIR_NORMAL, RP_GEN_SWEEP_DIR_UP_DOWN
- **Sweep mode** - RP_GEN_SWEEP_MODE_LINEAR, RP_GEN_SWEEP_MODE_LOG
- **Generator trigger source** - RP_GEN_TRIG_SRC_INTERNAL, RP_GEN_TRIG_SRC_EXT_PE, RP_GEN_TRIG_SRC_EXT_NE
- **Generator triggers** - RP_T_CH_1, RP_T_CH_2, RP_T_CH_EXT
- **Rise and fall times** - RISE_FALL_MIN_RATIO, RISE_FALL_MAX_RATIO
**ACQUISITION**
- **Decimation** - RP_DEC_1, RP_DEC_2, RP_DEC_4, RP_DEC_8, RP_DEC_16, RP_DEC_32, RP_DEC_64, RP_DEC_128, RP_DEC_256, RP_DEC_512, RP_DEC_1024, RP_DEC_2048, RP_DEC_4096, RP_DEC_8192, RP_DEC_16384, RP_DEC_32768, RP_DEC_65536
- **Acquisition trigger** - RP_TRIG_SRC_DISABLED, RP_TRIG_SRC_NOW, RP_TRIG_SRC_CHA_PE, RP_TRIG_SRC_CHA_NE, RP_TRIG_SRC_CHB_PE, RP_TRIG_SRC_CHB_NE, RP_TRIG_SRC_EXT_PE, RP_TRIG_SRC_EXT_NE, RP_TRIG_SRC_AWG_PE, RP_TRIG_SRC_AWG_NE
- **Acquisition trigger state** - RP_TRIG_STATE_TRIGGERED, RP_TRIG_STATE_WAITING
- **Buffer size** - ADC_BUFFER_SIZE, DAC_BUFFER_SIZE
- **Fast analog channels** - RP_CH_1, RP_CH_2
SIGNALlab 250-12 only:
- **Input coupling** - RP_DC, RP_AC
- **Generator gain** - RP_GAIN_1X, RP_GAIN_5X
STEMlab 125-14 4-Input only:
- **Fast analog channels** - RP_CH_3, RP_CH_4
- **Acquisition trigger** - RP_TRIG_SRC_CHC_PE, RP_TRIG_SRC_CHC_NE, RP_TRIG_SRC_CHD_PE, RP_TRIG_SRC_CHD_NE
## Synchronising generation and acquisition
The example itself is relatively trivial in comparison to others already presented during the tutorials. The only practical change is the **RP_TRIG_SOUR_AWG_PE**.
Parameters:
```python
###### Generation #####
channel = rp.RP_CH_1 # rp.RP_CH_2
waveform = rp.RP_WAVEFORM_RAMP_UP
freq = 0.1164153218269348 * 2
ampl = 1.0
ncyc = 1
nor = 1
period = 10
gen_trig_sour = rp.RP_GEN_TRIG_SRC_INTERNAL
##### Acquisition #####
trig_lvl = 0.5
trig_dly = 0
dec = rp.RP_DEC_65536
acq_trig_sour = rp.RP_TRIG_SRC_AWG_PE
N = 32768 // 2
# Reset Generation and Acquisition
rp.rp_GenReset()
rp.rp_AcqReset()
```
0
Setting up both the generation and acquisition.
```python
###### Generation #####
print("Gen_start")
rp.rp_GenWaveform(channel, waveform)
rp.rp_GenFreqDirect(channel, freq)
rp.rp_GenAmp(channel, ampl)
# Change to burst mode
rp.rp_GenMode(channel, rp.RP_GEN_MODE_BURST)
rp.rp_GenBurstCount(channel, ncyc) # Ncyc
rp.rp_GenBurstRepetitions(channel, nor) # Nor
rp.rp_GenBurstPeriod(channel, period) # Period
# Specify generator trigger source
rp.rp_GenTriggerSource(channel, gen_trig_sour)
# Enable output synchronisation
rp.rp_GenOutEnableSync(True)
##### Acquisition #####
# Set Decimation
rp.rp_AcqSetDecimation(dec)
rp.rp_AcqSetAveraging(True)
rp.rp_AcqSetGain(rp.RP_CH_1, rp.RP_HIGH)
# Set trigger level and delay
rp.rp_AcqSetTriggerLevel(rp.RP_T_CH_1, trig_lvl)
rp.rp_AcqSetTriggerDelay(trig_dly)
```
Gen_start
0
Starting acquisition and capturing data.
```python
# Start Acquisition
print("Acq_start")
rp.rp_AcqStart()
# Specify trigger - input 1 positive edge
rp.rp_AcqSetTriggerSrc(acq_trig_sour)
time.sleep(5)
rp.rp_GenTriggerOnly(channel) # Trigger generator
print(f"Trigger state: {rp.rp_AcqGetTriggerState()[1]}")
# Trigger state
while 1:
trig_state = rp.rp_AcqGetTriggerState()[1]
if trig_state == rp.RP_TRIG_STATE_TRIGGERED:
break
# Fill state
print(f"Fill state: {rp.rp_AcqGetBufferFillState()[1]}")
while 1:
if rp.rp_AcqGetBufferFillState()[1]:
break
### Get data ###
# Volts
fbuff = rp.fBuffer(N)
res = rp.rp_AcqGetOldestDataV(rp.RP_CH_1, N, fbuff)[1]
data_V = np.zeros(N // 2, dtype = float)
for i in range(0, N // 2, 1):
data_V[i] = fbuff[i + N // 2]
plt.plot(data_V)
plt.show()
```
Acq_start
Trigger state: 0
Fill state: False
![png](STM_Sweep_files/STM_Sweep_9_1.png)
```python
# Release resources
rp.rp_Release()
```
0
One of the applications for this example is to measure how many samples are taken from the triggering moment to the start of the captured pulse, which corresponds to propagation delay on the transmission line.
### Note
There are a lot of different commands for the Acquisition. The list of available functions is quite an achievement to read through, so from now on, please refer to the *C and Python API section* of the [SCPI & API command list](https://redpitaya.readthedocs.io/en/latest/appsFeatures/remoteControl/command_list.html#list-of-supported-scpi-api-commands) for all available commands.

BIN
Control/rendered/STM_Sweep_files/STM_Sweep_9_1.png (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -0,0 +1,9 @@
import FreeCAD
doc = FreeCAD.open("STM-Frame.FCStd")
magnets = doc.findObjects(Label="Magnet.*")
for m in magnets:
if m.TypeId != "App::Link":
continue
pos = m.Placement.Base
print(f"{pos.x:.5f}, {pos.y:.5f}, {pos.z:.5f}")

View file

@ -0,0 +1,53 @@
```python
from matplotlib import pyplot as plt
import pandas as pd
df = pd.read_csv('20250824-temperature-02.csv')
df["Time"] = df["Time [ms]"] / 1000. / 60.
fig, ax = plt.subplots(figsize=(24, 8))
ax.set_ylim(25.4, df["Raw"].max() + 0.01)
ax.plot(df["Time"], df["Raw"])
ax.plot(df["Time"], df["Temperature"])
ax.set_ylabel("Temperature / °C")
ax.set_xlabel("Time / minutes")
ax.legend(["Raw Temperature", "Temperature"])
ax.grid()
def event(time_minutes, label, yoff=0):
y = ax.get_ylim()[1] - yoff
ax.annotate(label, xy=(time_minutes, y))
ax.axvline(x=time_minutes, color="red")
event(3330 / 60, "Start install aluminum")
event(4440 / 60, "Start remove aluminum")
event(5208 / 60, "New tip")
event(5450 / 60, "Aluminum back on STM")
event(5720 / 60, "First tunneling", 0.03)
event(6860 / 60, "Stable tunneling started", 0.26)
event(7100 / 60, "Stable tunneling stopped", 0.23)
event(7200 / 60, "Heated up the STM externally")
event(7669 / 60, "Stable tunneling start 80pA", 0.42)
event(7780 / 60, "Changed to 210pA", 0.26)
event(7990 / 60, "Drifted out of range", 0.38)
event(8300 / 60, "Heated again")
event(8327 / 60, "STM spontaneously starts tunneling", 0.52)
event(8500 / 60, "Very noisy but long term tunneling", 0.2)
event(8950 / 60, "End tunneling", 0.25)
```
![png](Temperature-Data_files/Temperature-Data_0_0.png)
```python
```
```python
```

Binary file not shown.