Source code for automationshield.shields.magnetoshield
import time
from math import log
from .baseshield import BaseShield
[docs]
class MagnetoShield(BaseShield):
"""Class for Magnetoshield device. Inherits from BaseShield.
The Magnetoshield device is a control experiment using magnetic levitation. The actuator is an electromagnet over which the voltage can be controlled. \
The electromagnet attracts a permanent magnet placed below the electromagnet. The position of the permanent magnet is estimated using a Hall effect sensor.
Interface:
* Actuator input should be provided in percent by default.
* Potentiometer is provided in percent by default.
* Sensor values are provided in Gauss by default. This class provides an additional method :py:meth:`MagnetoShield.magnet_position`, which calculates the distance of the permanent magnet from the electromagnet.
This class does not use the :py:attr:`BaseShield.zero_reference` attribute.
"""
script = "magnetoshield"
shield_id = "MG"
actuator_bits = 12
potentiometer_bits = 10
sensor_bits = 10
[docs]
class PlotInfo(BaseShield.PlotInfo):
sensor_unit = "mm"
sensor_type = "Height"
sensor_min = 12
sensor_max = 17
emagnet_height: int = 20
"""Height of the electromagnet above ground in mm."""
magnet_low: int = 3
"""Top of the magnet from ground - distance from Hall element in mm when the magnet is at the bottom of the tube."""
magnet_high: int = 8
"""Top of the magnet from ground - distance from Hall element in mm when the magnet is at the top of the tube."""
default_p1: float = 3.233100
"""Default value for calculating the magnet position. See :py:meth:`MagnetoShield.magnet_position`."""
default_p2: float = 0.220571
"""Default value for calculating the magnet position. See :py:meth:`MagnetoShield.magnet_position`."""
def __init__(self, port: str | None = None) -> None:
super().__init__(port)
self._p1 = None
self._p2 = None
[docs]
def convert_sensor_reading(self, sensor: int) -> float:
"""Converts the n-bit sensor reading of the Hall effect sensor to Gauss. \
The constants in this method are for release 4 of the MagnetoShield. Conversion is done using
.. math::
B = \\left(2.5 - s \\cdot \\frac{3.3}{2^{10} - 1}\\right) \\cdot 800
The sensor value :math:`s` is scaled with the ratio of the ADC reference voltage (:math:`3.3` (:math:`V`)) over the AD converter resolution (:math:`10` bits, i.e. :math:`1023`). \
The :math:`2.5` (:math:`V`) bias for zero magnetic flux is subtracted and the result is scaled with the sensitivity of the Hall effect sensor (:math:`1.25 \\frac{mV}{G} = 800 \\frac{G}{V}`)
:param sensor: Raw sensor value.
:return: Sensor value in Gauss.
"""
return (2.5 - sensor*(3.3/(2**self.potentiometer_bits - 1)))*800
[docs]
def calibrate_sensor_reading(self, sensor: int) -> int:
"""Return sensor value as is. No calibration is performed.
:param sensor: Raw sensor value.
:return: Raw sensor value.
"""
return sensor
@property
def p1(self) -> float:
"""Return :math:`p_1` constant calculated during calibration. If not available, return :py:attr:`~MagnetoShield.default_p1`.
:return: :math:`p_1` constant.
"""
if self._p1:
return self._p1
return self.default_p1
@p1.setter
def p1(self, value: float):
self._p1 = value
@property
def p2(self) -> float:
"""Return :math`p_2` constant calculated during calibration. If not available, return :py:attr:`~MagnetoShield.default_p2`.
:return: :math:`p_2` constant.
"""
if self._p2:
return self._p2
return self.default_p2
@p2.setter
def p2(self, value: float):
self._p2 = value
[docs]
def calibrate(self):
"""Perform sensor calibration for conversion from Gauss to magnet distance from electromagnet. \
The sensor values are read in the lowest and highest magnet position, :math:`s_{0}` and :math:`s_{1}` respectively. \
The constants :math:`p_1` and :math:`p2` are calculated as follows:
.. math::
p_2 &= \\frac{ \\log{ \\left( z_{e} - z_{0} \\right) } - \\log{ \\left( z_{e} - z_{1} \\right) } } { \\log{ s_{0} } - \\log{ s_{1} } } \\\\
p_1 &= \\frac{ z_{e} - z_{1} } { \\left( s_{1} \\right)^{p_2} }
:math:`z_{e}`, :math:`z_{0}` and :math:`z_{1}` correspond to :py:attr:`~MagnetoShield.emagnet_height`, :py:attr:`~MagnetoShield.magnet_low` and :py:attr:`~MagnetoShield.magnet_high`, respectively.
"""
self.write(self.RUN, 0)
time.sleep(.5)
self.read()
low = 0
for _ in range(100):
self.write(self.RUN, 100)
_, val = self.read()
if val > low:
low = val
self.write(self.RUN, 100)
time.sleep(.5)
self.read()
high = 1e3
for _ in range(100):
self.write(self.RUN, 100)
_, val = self.read()
if val < high:
high = val
self.write(self.STOP, 0)
time.sleep(.5)
self.p2 = log((self.emagnet_height - self.magnet_low) / (self.emagnet_height - self.magnet_high)) / log(low/high)
self.p1 = (self.emagnet_height - self.magnet_high) / (high ** self.p2)
[docs]
def magnet_position(self, sensor: float) -> float:
"""Calculate magnet distance from electromagnet using
.. math::
y = p_1 \cdot B^{p_2}
where :math:`B` is the magnetic flux density in Gauss. :math:`p_1` and :math:`p_2` are constants which are calculated in :py:meth:`~MagnetoShield.calibrate`.
The distance can vary between approximately :math:`12 mm` and :math:`17 mm`.
:param sensor: Sensor value in Gauss.
:return: Magnet distance from electromagnet.
"""
return self.p1 * sensor ** self.p2