Skip to content

Commit 43cc811

Browse files
authored
Merge pull request #20 from stoklund/full_speed
Scale fan setting to match `PWM_F`
2 parents e70a5aa + aea50b1 commit 43cc811

File tree

3 files changed

+47
-21
lines changed

3 files changed

+47
-21
lines changed

adafruit_emc2101/__init__.py

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
_FAN_CONFIG = const(0x4A)
5454
_FAN_SPINUP = const(0x4B)
5555
_REG_FAN_SETTING = const(0x4C)
56+
_PWM_FREQ = const(0x4D)
5657

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

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

210-
dac_output_enabled = RWBit(_REG_CONFIG, 4)
211-
"""When set, the fan control signal is output as a DC voltage instead of a PWM signal"""
212-
212+
_dac_output_enabled = RWBit(_REG_CONFIG, 4)
213213
_conversion_rate = RWBits(4, 0x04, 0)
214214
# fan spin-up
215215
_spin_drive = RWBits(2, _FAN_SPINUP, 3)
@@ -222,13 +222,15 @@ def __init__(self, i2c_bus):
222222
if not self._part_id in [0x16, 0x28] or self._mfg_id != 0x5D:
223223
raise AttributeError("Cannot find a EMC2101")
224224

225+
self._full_speed_lsb = None # See _calculate_full_speed().
225226
self.initialize()
226227

227228
def initialize(self):
228229
"""Reset the controller to an initial default configuration"""
229230
self._tach_mode_enable = True
230231
self._enabled_forced_temp = False
231232
self._spin_tach_limit = False
233+
self._calculate_full_speed()
232234

233235
@property
234236
def internal_temperature(self):
@@ -255,27 +257,56 @@ def fan_speed(self):
255257
val |= self._tach_read_msb << 8
256258
return _FAN_RPM_DIVISOR / val
257259

260+
def _calculate_full_speed(self, pwm_f=None, dac=None):
261+
"""Determine the LSB value for a 100% fan setting"""
262+
if dac is None:
263+
dac = self.dac_output_enabled
264+
265+
if dac:
266+
# DAC mode is independent of PWM_F.
267+
self._full_speed_lsb = float(MAX_LUT_SPEED)
268+
return
269+
270+
# PWM mode reaches 100% duty cycle at a 2*PWM_F setting.
271+
if pwm_f is None:
272+
pwm_f = self._pwm_freq
273+
274+
# PWM_F=0 behaves like PWM_F=1.
275+
self._full_speed_lsb = 2.0 * max(1, pwm_f)
276+
277+
def _speed_to_lsb(self, percentage):
278+
"""Convert a fan speed percentage to a Fan Setting byte value"""
279+
return round((percentage / 100.0) * self._full_speed_lsb)
280+
258281
@property
259282
def manual_fan_speed(self):
260283
"""The fan speed used while the LUT is being updated and is unavailable. The speed is
261284
given as the fan's PWM duty cycle represented as a float percentage.
262285
The value roughly approximates the percentage of the fan's maximum speed"""
263286
raw_setting = self._fan_setting & MAX_LUT_SPEED
264-
return (raw_setting / MAX_LUT_SPEED) * 100
287+
return (raw_setting / self._full_speed_lsb) * 100
265288

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

271-
# convert from a percentage to an lsb value
272-
percentage = fan_speed / 100.0
273-
fan_speed_lsb = round(percentage * MAX_LUT_SPEED)
294+
fan_speed_lsb = self._speed_to_lsb(fan_speed)
274295
lut_disabled = self._fan_lut_prog
275296
self._fan_lut_prog = True
276297
self._fan_setting = fan_speed_lsb
277298
self._fan_lut_prog = lut_disabled
278299

300+
@property
301+
def dac_output_enabled(self):
302+
"""When set, the fan control signal is output as a DC voltage instead of a PWM signal"""
303+
return self._dac_output_enabled
304+
305+
@dac_output_enabled.setter
306+
def dac_output_enabled(self, value):
307+
self._dac_output_enabled = value
308+
self._calculate_full_speed(dac=value)
309+
279310
@property
280311
def lut_enabled(self):
281312
"""Enable or disable the internal look up table used to map a given temperature

adafruit_emc2101/emc2101_lut.py

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,25 +38,16 @@
3838
from adafruit_register.i2c_struct_array import StructArray
3939
from adafruit_register.i2c_struct import UnaryStruct
4040
from adafruit_register.i2c_bit import RWBit
41-
from adafruit_register.i2c_bits import RWBits
42-
from . import EMC2101
41+
from . import EMC2101, MAX_LUT_SPEED, MAX_LUT_TEMP
4342

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

4746
_FAN_CONFIG = const(0x4A)
48-
_PWM_FREQ = const(0x4D)
4947
_PWM_DIV = const(0x4E)
5048
_LUT_HYSTERESIS = const(0x4F)
5149
_LUT_BASE = const(0x50)
5250

53-
MAX_LUT_SPEED = 0x3F # 6-bit value
54-
MAX_LUT_TEMP = 0x7F # 7-bit
55-
56-
57-
def _speed_to_lsb(percentage):
58-
return round((percentage / 100.0) * MAX_LUT_SPEED)
59-
6051

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

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

155148
_fan_pwm_clock_select = RWBit(_FAN_CONFIG, 3)
156149
_fan_pwm_clock_override = RWBit(_FAN_CONFIG, 2)
157-
_pwm_freq = RWBits(5, _PWM_FREQ, 0)
158150
_pwm_freq_div = UnaryStruct(_PWM_DIV, "<B")
159151

160152
lut_temperature_hysteresis = UnaryStruct(_LUT_HYSTERESIS, "<B")
@@ -209,7 +201,8 @@ def pwm_frequency(self):
209201
def pwm_frequency(self, value):
210202
if value < 0 or value > 0x1F:
211203
raise AttributeError("pwm_frequency must be from 0-31")
212-
self._pwm_freq_div = value
204+
self._pwm_freq = value
205+
self._calculate_full_speed(pwm_f=value)
213206

214207
@property
215208
def pwm_frequency_divisor(self):

examples/emc2101_set_pwm_freq.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
emc = EMC2101(i2c)
1111
emc.set_pwm_clock(use_preset=False)
1212
# Datasheet recommends using the maximum value of 31 (0x1F)
13-
# to provide the highest effective resolution
13+
# to provide the highest effective resolution.
14+
# The PWM frequency must be set before changing `manual_fan_speed` or LUT entries.
15+
# Otherwise the PWM duty cycle won't be configured correctly.
1416
emc.pwm_frequency = 14
1517

1618
# This divides the pwm frequency down to a smaller number

0 commit comments

Comments
 (0)