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.
@@ -0,0 +1,4 @@
1
+ """QCM HAL module"""
2
+
3
+ from .version import __version__
4
+ from .base import GatedFTM
@@ -0,0 +1,3 @@
1
+ """Base QCM classes to be inherited in particular device implementation."""
2
+
3
+ from .gated_ftm import GatedFTM
@@ -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"""
@@ -0,0 +1,3 @@
1
+ """RS485 devices."""
2
+
3
+ from .gated_ftm import RS485GatedFTM