Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scale fan setting to match PWM_F #20

Merged
merged 4 commits into from
Jan 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 38 additions & 7 deletions adafruit_emc2101/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
_FAN_CONFIG = const(0x4A)
_FAN_SPINUP = const(0x4B)
_REG_FAN_SETTING = const(0x4C)
_PWM_FREQ = const(0x4D)

_REG_PARTID = const(0xFD) # 0x16
_REG_MFGID = const(0xFE) # 0xFF16
Expand Down Expand Up @@ -202,14 +203,13 @@ class to add those features, at the cost of increased memory usage.
`forced_ext_temp`"""

_fan_setting = UnaryStruct(_REG_FAN_SETTING, "<B")
_pwm_freq = RWBits(5, _PWM_FREQ, 0)
_fan_lut_prog = RWBit(_FAN_CONFIG, 5)
invert_fan_output = RWBit(_FAN_CONFIG, 4)
"""When set to True, the magnitude of the fan output signal is inverted, making 0 the maximum
value and 100 the minimum value"""

dac_output_enabled = RWBit(_REG_CONFIG, 4)
"""When set, the fan control signal is output as a DC voltage instead of a PWM signal"""

_dac_output_enabled = RWBit(_REG_CONFIG, 4)
_conversion_rate = RWBits(4, 0x04, 0)
# fan spin-up
_spin_drive = RWBits(2, _FAN_SPINUP, 3)
Expand All @@ -222,13 +222,15 @@ def __init__(self, i2c_bus):
if not self._part_id in [0x16, 0x28] or self._mfg_id != 0x5D:
raise AttributeError("Cannot find a EMC2101")

self._full_speed_lsb = None # See _calculate_full_speed().
self.initialize()

def initialize(self):
"""Reset the controller to an initial default configuration"""
self._tach_mode_enable = True
self._enabled_forced_temp = False
self._spin_tach_limit = False
self._calculate_full_speed()

@property
def internal_temperature(self):
Expand All @@ -255,27 +257,56 @@ def fan_speed(self):
val |= self._tach_read_msb << 8
return _FAN_RPM_DIVISOR / val

def _calculate_full_speed(self, pwm_f=None, dac=None):
"""Determine the LSB value for a 100% fan setting"""
if dac is None:
dac = self.dac_output_enabled

if dac:
# DAC mode is independent of PWM_F.
self._full_speed_lsb = float(MAX_LUT_SPEED)
return

# PWM mode reaches 100% duty cycle at a 2*PWM_F setting.
if pwm_f is None:
pwm_f = self._pwm_freq

# PWM_F=0 behaves like PWM_F=1.
self._full_speed_lsb = 2.0 * max(1, pwm_f)

def _speed_to_lsb(self, percentage):
"""Convert a fan speed percentage to a Fan Setting byte value"""
return round((percentage / 100.0) * self._full_speed_lsb)

@property
def manual_fan_speed(self):
"""The fan speed used while the LUT is being updated and is unavailable. The speed is
given as the fan's PWM duty cycle represented as a float percentage.
The value roughly approximates the percentage of the fan's maximum speed"""
raw_setting = self._fan_setting & MAX_LUT_SPEED
return (raw_setting / MAX_LUT_SPEED) * 100
return (raw_setting / self._full_speed_lsb) * 100

@manual_fan_speed.setter
def manual_fan_speed(self, fan_speed):
if fan_speed not in range(0, 101):
raise AttributeError("manual_fan_speed must be from 0-100")

# convert from a percentage to an lsb value
percentage = fan_speed / 100.0
fan_speed_lsb = round(percentage * MAX_LUT_SPEED)
fan_speed_lsb = self._speed_to_lsb(fan_speed)
lut_disabled = self._fan_lut_prog
self._fan_lut_prog = True
self._fan_setting = fan_speed_lsb
self._fan_lut_prog = lut_disabled

@property
def dac_output_enabled(self):
"""When set, the fan control signal is output as a DC voltage instead of a PWM signal"""
return self._dac_output_enabled

@dac_output_enabled.setter
def dac_output_enabled(self, value):
self._dac_output_enabled = value
self._calculate_full_speed(dac=value)

@property
def lut_enabled(self):
"""Enable or disable the internal look up table used to map a given temperature
Expand Down
19 changes: 6 additions & 13 deletions adafruit_emc2101/emc2101_lut.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,25 +38,16 @@
from adafruit_register.i2c_struct_array import StructArray
from adafruit_register.i2c_struct import UnaryStruct
from adafruit_register.i2c_bit import RWBit
from adafruit_register.i2c_bits import RWBits
from . import EMC2101
from . import EMC2101, MAX_LUT_SPEED, MAX_LUT_TEMP

__version__ = "0.0.0-auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_EMC2101.git"

_FAN_CONFIG = const(0x4A)
_PWM_FREQ = const(0x4D)
_PWM_DIV = const(0x4E)
_LUT_HYSTERESIS = const(0x4F)
_LUT_BASE = const(0x50)

MAX_LUT_SPEED = 0x3F # 6-bit value
MAX_LUT_TEMP = 0x7F # 7-bit


def _speed_to_lsb(percentage):
return round((percentage / 100.0) * MAX_LUT_SPEED)


class FanSpeedLUT:
"""A class used to provide a dict-like interface to the EMC2101's Temperature to Fan speed
Expand Down Expand Up @@ -131,7 +122,9 @@ def _update_lut(self):
# we want to assign the lowest temperature to the lowest LUT slot, so we sort the keys/temps
# get and sort the new lut keys so that we can assign them in order
for idx, current_temp in enumerate(sorted(self.lut_values.keys())):
current_speed = _speed_to_lsb(self.lut_values[current_temp])
# We don't want to make `_speed_to_lsb()` public, it is only needed here.
# pylint: disable=protected-access
current_speed = self.emc_fan._speed_to_lsb(self.lut_values[current_temp])
self._set_lut_entry(idx, current_temp, current_speed)

# Set the remaining LUT entries to the default (Temp/Speed = max value)
Expand All @@ -154,7 +147,6 @@ class EMC2101_LUT(EMC2101): # pylint: disable=too-many-instance-attributes

_fan_pwm_clock_select = RWBit(_FAN_CONFIG, 3)
_fan_pwm_clock_override = RWBit(_FAN_CONFIG, 2)
_pwm_freq = RWBits(5, _PWM_FREQ, 0)
_pwm_freq_div = UnaryStruct(_PWM_DIV, "<B")

lut_temperature_hysteresis = UnaryStruct(_LUT_HYSTERESIS, "<B")
Expand Down Expand Up @@ -209,7 +201,8 @@ def pwm_frequency(self):
def pwm_frequency(self, value):
if value < 0 or value > 0x1F:
raise AttributeError("pwm_frequency must be from 0-31")
self._pwm_freq_div = value
self._pwm_freq = value
self._calculate_full_speed(pwm_f=value)

@property
def pwm_frequency_divisor(self):
Expand Down
4 changes: 3 additions & 1 deletion examples/emc2101_set_pwm_freq.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
emc = EMC2101(i2c)
emc.set_pwm_clock(use_preset=False)
# Datasheet recommends using the maximum value of 31 (0x1F)
# to provide the highest effective resolution
# to provide the highest effective resolution.
# The PWM frequency must be set before changing `manual_fan_speed` or LUT entries.
# Otherwise the PWM duty cycle won't be configured correctly.
emc.pwm_frequency = 14

# This divides the pwm frequency down to a smaller number
Expand Down