scietex.hal.qcm 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scietex/hal/qcm/__init__.py +4 -0
- scietex/hal/qcm/base/__init__.py +3 -0
- scietex/hal/qcm/base/data.py +84 -0
- scietex/hal/qcm/base/ftm_calculation.py +21 -0
- scietex/hal/qcm/base/ftm_materials_data.py +63 -0
- scietex/hal/qcm/base/gated_ftm.py +235 -0
- scietex/hal/qcm/base/rs485/__init__.py +3 -0
- scietex/hal/qcm/base/rs485/gated_ftm.py +326 -0
- scietex/hal/qcm/base/serial/__init__.py +3 -0
- scietex/hal/qcm/base/serial/gated_ftm.py +323 -0
- scietex/hal/qcm/cyky/__init__.py +3 -0
- scietex/hal/qcm/cyky/tm106b.py +411 -0
- scietex/hal/qcm/scietex/__init__.py +4 -0
- scietex/hal/qcm/scietex/ftm_one.py +480 -0
- scietex/hal/qcm/scietex/ftm_one_usb.py +618 -0
- scietex/hal/qcm/scietex/utils.py +21 -0
- scietex/hal/qcm/version.py +8 -0
- scietex_hal_qcm-1.0.0.dist-info/METADATA +90 -0
- scietex_hal_qcm-1.0.0.dist-info/RECORD +22 -0
- scietex_hal_qcm-1.0.0.dist-info/WHEEL +5 -0
- scietex_hal_qcm-1.0.0.dist-info/licenses/LICENSE +21 -0
- scietex_hal_qcm-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Data models for the QCM."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
import msgspec
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class OutCTRLMode(Enum):
|
|
8
|
+
"""Modes of the control output."""
|
|
9
|
+
|
|
10
|
+
HIGH = 1
|
|
11
|
+
LOW = 2
|
|
12
|
+
MANUAL = 3
|
|
13
|
+
DISABLED = 4
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PwmCTRLMode(Enum):
|
|
17
|
+
"""Modes of the PWM output."""
|
|
18
|
+
|
|
19
|
+
ABSOLUTE = 1
|
|
20
|
+
RELATIVE = 2
|
|
21
|
+
MANUAL = 3
|
|
22
|
+
DISABLED = 4
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# pylint: disable=too-few-public-methods
|
|
26
|
+
class QCMError(msgspec.Struct, frozen=True):
|
|
27
|
+
"""Model of a QCM error with code and message."""
|
|
28
|
+
|
|
29
|
+
code: int
|
|
30
|
+
message: str = "Unknown error"
|
|
31
|
+
|
|
32
|
+
def __str__(self) -> str:
|
|
33
|
+
return f"QCMError(code={self.code}, message='{self.message}')"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# pylint: disable=too-few-public-methods
|
|
37
|
+
class FTMParameters(msgspec.Struct, frozen=True):
|
|
38
|
+
"""Model for FTM operational parameters."""
|
|
39
|
+
|
|
40
|
+
frequency: float = 0.0 # Sensor frequency
|
|
41
|
+
frequency_std: float = 0.0 # Sensor frequency standard deviation
|
|
42
|
+
averaging_window: int = 1 # Averaging window size
|
|
43
|
+
averaging_progress: int = 1 # Averaging accumulation progress
|
|
44
|
+
rate: float = 0.0 # Deposition rate A/s
|
|
45
|
+
thickness: float = 0.0 # Film thickness A
|
|
46
|
+
thickness_std: float = 0.0 # Film thickness standard deviation A
|
|
47
|
+
target: float = 0.0 # Film target thickness A
|
|
48
|
+
material_density: float = 0.0 # Material density g/cm^3
|
|
49
|
+
material_z_ratio: float = 0.0 # Material Z-ratio
|
|
50
|
+
running: bool = False # Current FTM measurement running state
|
|
51
|
+
scale: float = 1.0 # Scale (tooling) factor
|
|
52
|
+
|
|
53
|
+
def __str__(self) -> str:
|
|
54
|
+
return (
|
|
55
|
+
f"FTMParameters(frequency={self.frequency:g} +- {self.frequency_std:g} Hz, "
|
|
56
|
+
f"averaging={self.averaging_progress}/{self.averaging_window}, "
|
|
57
|
+
f"deposition rate={self.rate} A/s, "
|
|
58
|
+
f"film thickness={self.thickness} +- {self.thickness_std} A, "
|
|
59
|
+
f"target thickness={self.target} A, "
|
|
60
|
+
f"material density={self.material_density} g/cm^3, "
|
|
61
|
+
f"material Z-ratio={self.material_z_ratio}, "
|
|
62
|
+
f"running={self.running})"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# pylint: disable=too-few-public-methods
|
|
67
|
+
class FTMStartCMD(msgspec.Struct, frozen=True):
|
|
68
|
+
"""FTM start command with parameters."""
|
|
69
|
+
|
|
70
|
+
reset: bool = True # Reset thickness at start
|
|
71
|
+
|
|
72
|
+
def __str__(self) -> str:
|
|
73
|
+
return f"FTMStartCMD(reset={self.reset})"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# pylint: disable=too-few-public-methods
|
|
77
|
+
class Material(msgspec.Struct, frozen=True):
|
|
78
|
+
"""Material parameters."""
|
|
79
|
+
|
|
80
|
+
density: float # Density g/cm^3
|
|
81
|
+
z_ratio: float # Z-ratio
|
|
82
|
+
|
|
83
|
+
def __str__(self) -> str:
|
|
84
|
+
return f"Material(density={self.density}, Z-ratio={self.z_ratio})"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Functions for film thickness calculation"""
|
|
2
|
+
|
|
3
|
+
import math as m
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
NQ = 1.668e5 # Hz*cm Frequency const of AT-cut quartz
|
|
7
|
+
RQ = 2.648 # g/cm^3 Density of quartz
|
|
8
|
+
MQ = 2.947e11 # g/(cm*s^2) Shear modulus of quartz
|
|
9
|
+
|
|
10
|
+
NRQ = 140593.14771292824 # NQ * RQ / m.pi [Hz * g / cm^2]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def freq_change_to_mass_per_cm2(f0, f1, z):
|
|
14
|
+
"""Calculate film mass per cm2 (g / cm^2)"""
|
|
15
|
+
return NRQ / (z * f1) * m.atan(z * m.tan(m.pi * (f0 - f1) / f0)) # g / cm^2
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def freq_change_to_thickness(f0, f1, rho, z):
|
|
19
|
+
"""Calculate film thickness (A)"""
|
|
20
|
+
m_per_cm2 = freq_change_to_mass_per_cm2(f0, f1, z)
|
|
21
|
+
return m_per_cm2 / rho * 1e8 # Angstrom
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Reference dict for different materials.
|
|
3
|
+
Density is given in g/cm^3.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
materials = {
|
|
7
|
+
"Ag": {
|
|
8
|
+
"density": 10.5,
|
|
9
|
+
"z_ratio": 0.53,
|
|
10
|
+
},
|
|
11
|
+
"Al": {
|
|
12
|
+
"density": 2.7,
|
|
13
|
+
"z_ratio": 1.08,
|
|
14
|
+
},
|
|
15
|
+
"Au": {
|
|
16
|
+
"density": 19.3,
|
|
17
|
+
"z_ratio": 0.38,
|
|
18
|
+
},
|
|
19
|
+
"C": {
|
|
20
|
+
"density": 2.25,
|
|
21
|
+
"z_ratio": 3.26,
|
|
22
|
+
},
|
|
23
|
+
"Cr": {
|
|
24
|
+
"density": 7.2,
|
|
25
|
+
"z_ratio": 0.31,
|
|
26
|
+
},
|
|
27
|
+
"Cu": {
|
|
28
|
+
"density": 8.93,
|
|
29
|
+
"z_ratio": 0.44,
|
|
30
|
+
},
|
|
31
|
+
"Mo": {
|
|
32
|
+
"density": 10.2,
|
|
33
|
+
"z_ratio": 0.26,
|
|
34
|
+
},
|
|
35
|
+
"Ni": {
|
|
36
|
+
"density": 8.91,
|
|
37
|
+
"z_ratio": 0.33,
|
|
38
|
+
},
|
|
39
|
+
"Pd": {
|
|
40
|
+
"density": 12.04,
|
|
41
|
+
"z_ratio": 0.36,
|
|
42
|
+
},
|
|
43
|
+
"Pt": {
|
|
44
|
+
"density": 21.4,
|
|
45
|
+
"z_ratio": 0.25,
|
|
46
|
+
},
|
|
47
|
+
"Ti": {
|
|
48
|
+
"density": 4.5,
|
|
49
|
+
"z_ratio": 0.63,
|
|
50
|
+
},
|
|
51
|
+
"V": {
|
|
52
|
+
"density": 5.96,
|
|
53
|
+
"z_ratio": 0.53,
|
|
54
|
+
},
|
|
55
|
+
"W": {
|
|
56
|
+
"density": 19.3,
|
|
57
|
+
"z_ratio": 0.16,
|
|
58
|
+
},
|
|
59
|
+
"Zn": {
|
|
60
|
+
"density": 7.04,
|
|
61
|
+
"z_ratio": 0.51,
|
|
62
|
+
},
|
|
63
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""Gated FTM abstract interface."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
|
|
5
|
+
from .data import QCMError, PwmCTRLMode, OutCTRLMode, FTMParameters, Material
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# pylint: disable=too-many-public-methods
|
|
9
|
+
class GatedFTM(ABC):
|
|
10
|
+
"""Gated Film Thickness Monitor interface class."""
|
|
11
|
+
|
|
12
|
+
error_codes: dict[int, QCMError] = {0: QCMError(code=0, message="No error")}
|
|
13
|
+
error_codes_com: dict[int, QCMError] = {0: QCMError(code=0, message="No error")}
|
|
14
|
+
|
|
15
|
+
# Error processing methods
|
|
16
|
+
@abstractmethod
|
|
17
|
+
async def get_error_code(self, comm: bool = False) -> int:
|
|
18
|
+
"""Read error code from the FTM"""
|
|
19
|
+
|
|
20
|
+
def parse_error_code(self, code: int, comm: bool = False) -> QCMError:
|
|
21
|
+
"""Parse error code from the FTM"""
|
|
22
|
+
if comm:
|
|
23
|
+
if code in self.error_codes_com:
|
|
24
|
+
return self.error_codes_com[code]
|
|
25
|
+
else:
|
|
26
|
+
if code in self.error_codes:
|
|
27
|
+
return self.error_codes[code]
|
|
28
|
+
return QCMError(code=code, message="Unknown error")
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
async def clear_error(self, comm: bool = False) -> int:
|
|
32
|
+
"""Clear error from the FTM"""
|
|
33
|
+
|
|
34
|
+
# Device information methods
|
|
35
|
+
@abstractmethod
|
|
36
|
+
async def get_vendor(self) -> str:
|
|
37
|
+
"""Get QCM vendor name."""
|
|
38
|
+
|
|
39
|
+
@abstractmethod
|
|
40
|
+
async def get_product_name(self) -> str:
|
|
41
|
+
"""Get QCM product name."""
|
|
42
|
+
|
|
43
|
+
@abstractmethod
|
|
44
|
+
async def get_serial_number(self) -> str:
|
|
45
|
+
"""Get QCM serial number."""
|
|
46
|
+
|
|
47
|
+
@abstractmethod
|
|
48
|
+
async def get_version(self) -> str:
|
|
49
|
+
"""get QCM firmware version"""
|
|
50
|
+
|
|
51
|
+
# FTM measurement setup
|
|
52
|
+
|
|
53
|
+
@abstractmethod
|
|
54
|
+
async def get_mcu_frequency(self) -> int:
|
|
55
|
+
"""GET MCU frequency."""
|
|
56
|
+
|
|
57
|
+
@abstractmethod
|
|
58
|
+
async def get_gate_time(self) -> float:
|
|
59
|
+
"""Get gate time in ms"""
|
|
60
|
+
|
|
61
|
+
@abstractmethod
|
|
62
|
+
async def set_gate_time(self, gate_time_ms: float) -> float:
|
|
63
|
+
"""Set gate time in ms"""
|
|
64
|
+
|
|
65
|
+
@abstractmethod
|
|
66
|
+
async def get_averaging(self) -> int:
|
|
67
|
+
"""Get averaging window"""
|
|
68
|
+
|
|
69
|
+
@abstractmethod
|
|
70
|
+
async def set_averaging(self, averaging_window: int) -> int:
|
|
71
|
+
"""Set averaging window."""
|
|
72
|
+
|
|
73
|
+
# FTM frequency measurement data access methods
|
|
74
|
+
|
|
75
|
+
@abstractmethod
|
|
76
|
+
async def get_counter(self) -> int:
|
|
77
|
+
"""Get FTM raw counts."""
|
|
78
|
+
|
|
79
|
+
@abstractmethod
|
|
80
|
+
async def get_frequency_instant(self) -> float:
|
|
81
|
+
"""Get instant frequency."""
|
|
82
|
+
|
|
83
|
+
@abstractmethod
|
|
84
|
+
async def get_averaging_progress(self) -> int:
|
|
85
|
+
"""Get averaging accumulation progress."""
|
|
86
|
+
|
|
87
|
+
@abstractmethod
|
|
88
|
+
async def get_frequency(self) -> float:
|
|
89
|
+
"""Get frequency."""
|
|
90
|
+
|
|
91
|
+
@abstractmethod
|
|
92
|
+
async def get_frequency_std(self) -> float:
|
|
93
|
+
"""Get frequency standard deviation."""
|
|
94
|
+
|
|
95
|
+
# Thickness measurement methods
|
|
96
|
+
|
|
97
|
+
@abstractmethod
|
|
98
|
+
async def get_thickness(self) -> float:
|
|
99
|
+
"""Get thickness (Angstrom) value."""
|
|
100
|
+
|
|
101
|
+
@abstractmethod
|
|
102
|
+
async def get_thickness_std(self) -> float:
|
|
103
|
+
"""Parse thickness (Angstrom) standard deviation."""
|
|
104
|
+
|
|
105
|
+
@abstractmethod
|
|
106
|
+
async def get_rate(self) -> float:
|
|
107
|
+
"""Parse rate (Angstrom/s) value from register data"""
|
|
108
|
+
|
|
109
|
+
@abstractmethod
|
|
110
|
+
async def get_material_density(self) -> float:
|
|
111
|
+
"""Get material density g/cm3"""
|
|
112
|
+
|
|
113
|
+
@abstractmethod
|
|
114
|
+
async def set_material_density(self, density: float) -> float:
|
|
115
|
+
"""Set material density g/cm3."""
|
|
116
|
+
|
|
117
|
+
@abstractmethod
|
|
118
|
+
async def get_material_z_ratio(self) -> float:
|
|
119
|
+
"""Get material Z-ratio."""
|
|
120
|
+
|
|
121
|
+
@abstractmethod
|
|
122
|
+
async def set_material_z_ratio(self, z_ratio: float) -> float:
|
|
123
|
+
"""Set material Z-ratio."""
|
|
124
|
+
|
|
125
|
+
@abstractmethod
|
|
126
|
+
async def get_material(self) -> Material:
|
|
127
|
+
"""Get deposition material density and Z-ratio"""
|
|
128
|
+
|
|
129
|
+
@abstractmethod
|
|
130
|
+
async def set_material(self, material: Material) -> Material:
|
|
131
|
+
"""Set deposition material density and Z-ratio"""
|
|
132
|
+
|
|
133
|
+
@abstractmethod
|
|
134
|
+
async def get_ftm_scale(self) -> float:
|
|
135
|
+
"""Get FTM scale factor."""
|
|
136
|
+
|
|
137
|
+
@abstractmethod
|
|
138
|
+
async def set_ftm_scale(self, scale: float) -> float:
|
|
139
|
+
"""Set FTM scale factor."""
|
|
140
|
+
|
|
141
|
+
@abstractmethod
|
|
142
|
+
async def get_target_thickness(self) -> float:
|
|
143
|
+
"""Get target thickness (Angstrom)."""
|
|
144
|
+
|
|
145
|
+
@abstractmethod
|
|
146
|
+
async def set_target_thickness(self, target: float) -> float:
|
|
147
|
+
"""Set target thickness (Angstrom)."""
|
|
148
|
+
|
|
149
|
+
async def get_target_nm(self) -> float:
|
|
150
|
+
"""Get target thickness (nm)."""
|
|
151
|
+
return await self.get_target_thickness() / 10
|
|
152
|
+
|
|
153
|
+
async def set_target_nm(self, target: float) -> float:
|
|
154
|
+
"""Set target thickness (nm)."""
|
|
155
|
+
return await self.set_target_thickness(target * 10)
|
|
156
|
+
|
|
157
|
+
async def get_target_um(self) -> float:
|
|
158
|
+
"""Get target thickness (nm)."""
|
|
159
|
+
return await self.get_target_thickness() / 10_000
|
|
160
|
+
|
|
161
|
+
async def set_target_um(self, target: float) -> float:
|
|
162
|
+
"""Set target thickness (um)."""
|
|
163
|
+
return await self.set_target_thickness(target * 10_000)
|
|
164
|
+
|
|
165
|
+
@abstractmethod
|
|
166
|
+
async def reset_thickness(self) -> None:
|
|
167
|
+
"""Reset measured thickness."""
|
|
168
|
+
|
|
169
|
+
# CTRL OUT parameters
|
|
170
|
+
|
|
171
|
+
@abstractmethod
|
|
172
|
+
async def get_ctrl_out_mode(self) -> OutCTRLMode:
|
|
173
|
+
"""Get the mode of the control output."""
|
|
174
|
+
|
|
175
|
+
@abstractmethod
|
|
176
|
+
async def set_ctrl_out_mode(self, mode: OutCTRLMode) -> OutCTRLMode:
|
|
177
|
+
"""Set the mode of the control output."""
|
|
178
|
+
|
|
179
|
+
@abstractmethod
|
|
180
|
+
async def get_ctrl_out_value(self) -> bool:
|
|
181
|
+
"""Get control output value."""
|
|
182
|
+
|
|
183
|
+
@abstractmethod
|
|
184
|
+
async def set_ctrl_out_value(self, value: bool) -> bool:
|
|
185
|
+
"""Set control output value."""
|
|
186
|
+
|
|
187
|
+
@abstractmethod
|
|
188
|
+
async def get_ctrl_pwm_mode(self) -> PwmCTRLMode:
|
|
189
|
+
"""Get the mode of the PWM output."""
|
|
190
|
+
|
|
191
|
+
@abstractmethod
|
|
192
|
+
async def set_ctrl_pwm_mode(self, mode: PwmCTRLMode) -> PwmCTRLMode:
|
|
193
|
+
"""Set the mode of the PWM output."""
|
|
194
|
+
|
|
195
|
+
@abstractmethod
|
|
196
|
+
async def get_ctrl_pwm_value(self) -> float:
|
|
197
|
+
"""Get PWM output value."""
|
|
198
|
+
|
|
199
|
+
@abstractmethod
|
|
200
|
+
async def set_ctrl_pwm_value(self, value: float) -> float:
|
|
201
|
+
"""Set PWM output value."""
|
|
202
|
+
|
|
203
|
+
@abstractmethod
|
|
204
|
+
async def get_ctrl_pwm_scale(self) -> int:
|
|
205
|
+
"""Get PWM output scale."""
|
|
206
|
+
|
|
207
|
+
@abstractmethod
|
|
208
|
+
async def set_ctrl_pwm_scale(self, scale: int) -> int:
|
|
209
|
+
"""Set PWM output scale."""
|
|
210
|
+
|
|
211
|
+
# MEASUREMENT START/STOP
|
|
212
|
+
|
|
213
|
+
@abstractmethod
|
|
214
|
+
async def get_running_state(self) -> bool:
|
|
215
|
+
"""Get running state."""
|
|
216
|
+
|
|
217
|
+
@abstractmethod
|
|
218
|
+
async def set_running_state(self, running: bool) -> bool:
|
|
219
|
+
"""Set running state."""
|
|
220
|
+
|
|
221
|
+
async def start_measurement(self, reset: bool = True) -> bool:
|
|
222
|
+
"""Start measurement"""
|
|
223
|
+
if reset:
|
|
224
|
+
await self.reset_thickness()
|
|
225
|
+
return await self.set_running_state(True)
|
|
226
|
+
|
|
227
|
+
async def stop_measurement(self) -> bool:
|
|
228
|
+
"""Stop measurement."""
|
|
229
|
+
return await self.set_running_state(False)
|
|
230
|
+
|
|
231
|
+
# Read FTM parameters in single call
|
|
232
|
+
|
|
233
|
+
@abstractmethod
|
|
234
|
+
async def read_parameters(self) -> FTMParameters:
|
|
235
|
+
"""Read FTM Parameters"""
|