Python driver for Microchip EMC2305 5-channel PWM fan controller
Project description
microchip-emc2305
Python Driver for Microchip EMC2305 5-Channel PWM Fan Controller
A hardware-agnostic, production-ready Python driver for the Microchip EMC2305 fan controller with comprehensive feature support and robust I2C communication.
Features
Hardware Support
- Chip: Microchip EMC2305-1, EMC2305-2, EMC2305-3, EMC2305-4 (5-channel variants)
- Interface: I2C/SMBus with cross-process locking
- Platform: Any Linux system with I2C support (Raspberry Pi, Banana Pi, x86, etc.)
Fan Control
- ✅ 5 independent PWM channels - Control up to 5 fans simultaneously
- ✅ Dual control modes:
- PWM Mode: Direct duty cycle control (0-100%)
- FSC Mode: Closed-loop RPM control with PID (500-32,000 RPM)
- ✅ Per-fan PWM frequency - Individual frequency control per channel
- ✅ Configurable spin-up - Aggressive start for high-inertia fans
- ✅ RPM monitoring - Real-time tachometer reading
Advanced Features
- ✅ Fault detection: Stall, spin failure, aging fan detection
- ✅ SMBus Alert (ALERT#): Hardware interrupt support
- ✅ Software configuration lock - Protect settings in production (race-condition safe)
- ✅ Watchdog timer - Automatic failsafe
- ✅ Hardware capability detection - Auto-detect chip features
- ✅ Thread-safe operation - Concurrent access protection with atomic operations
- ✅ Comprehensive validation - I2C addresses (0x00-0x7F), registers (0x00-0xFF), SMBus block limits (32 bytes), and RPM bounds checking
Code Quality
- ✅ Full type hints (PEP 561)
- ✅ Comprehensive documentation
- ✅ Hardware-validated
- ✅ MIT licensed
Installation
From PyPI (Recommended)
pip install microchip-emc2305
From Source
git clone https://github.com/moffa90/python-emc2305.git
cd emc2305-python
pip install -e .
Optional Dependencies
# For YAML configuration file support
pip install microchip-emc2305[config]
# For development
pip install microchip-emc2305[dev]
Quick Start
Basic PWM Control
from emc2305.driver.i2c import I2CBus
from emc2305.driver.emc2305 import EMC2305, FanConfig
# Initialize I2C bus (with cross-process locking)
i2c_bus = I2CBus(bus_number=0)
# Initialize EMC2305 at default address 0x4D
fan_controller = EMC2305(i2c_bus, device_address=0x4D)
# Configure tachometer for your fan type (IMPORTANT for accurate RPM!)
# edges=3 for 1-pulse/rev, edges=5 for 2-pulse/rev (default), edges=9 for 4-pulse/rev
config = FanConfig(edges=3) # Adjust based on your fan's tachometer
fan_controller.configure_fan(channel=1, config=config)
# Set fan 1 to 75% duty cycle
fan_controller.set_pwm_duty_cycle(channel=1, percent=75.0)
# Read current RPM
rpm = fan_controller.get_current_rpm(channel=1)
print(f"Fan 1 speed: {rpm} RPM")
Closed-Loop RPM Control (FSC Mode)
from emc2305.driver.emc2305 import EMC2305, ControlMode, FanConfig
# Configure for FSC mode
config = FanConfig(
control_mode=ControlMode.FSC,
min_rpm=1000,
max_rpm=4000,
pid_gain_p=4, # Proportional gain
pid_gain_i=2, # Integral gain
pid_gain_d=1, # Derivative gain
)
fan_controller.configure_fan(channel=1, config=config)
fan_controller.set_target_rpm(channel=1, rpm=3000)
# Hardware PID will maintain 3000 RPM automatically
Fault Detection
from emc2305.driver.emc2305 import FanStatus
# Check fan status
status = fan_controller.get_fan_status(channel=1)
if status == FanStatus.STALLED:
print("Fan 1 is stalled!")
elif status == FanStatus.DRIVE_FAILURE:
print("Fan 1 is aging (drive failure)")
elif status == FanStatus.OK:
print("Fan 1 is operating normally")
Alert/Interrupt Handling
# Enable alerts for fan 1
fan_controller.configure_fan_alerts(channel=1, enabled=True)
# Check if any alerts are active
if fan_controller.is_alert_active():
# Get which fans have alerts
alerts = fan_controller.get_alert_status()
for channel, has_alert in alerts.items():
if has_alert:
print(f"Fan {channel} has an alert condition")
# Clear alert status
fan_controller.clear_alert_status()
Tachometer Configuration
Understanding the edges Parameter
The edges parameter is critical for accurate RPM readings. It must match your fan's tachometer signal:
| Fan Type | Pulses/Revolution | edges Setting |
|---|---|---|
| 1-pole | 1 | edges=3 |
| 2-pole | 2 | edges=5 (default) |
| 3-pole | 3 | edges=7 |
| 4-pole | 4 | edges=9 |
How to determine your fan's pulse count:
- Check the fan datasheet for "FG Signal" or "Tachometer" specification
- Or use trial and error: the correct setting gives RPM readings that scale linearly with PWM
Example: Configuring for Different Fan Types
from emc2305.driver.emc2305 import EMC2305, FanConfig
from emc2305.driver.i2c import I2CBus
bus = I2CBus(bus_number=0)
controller = EMC2305(i2c_bus=bus, device_address=0x4D)
# For a 1-pulse-per-revolution fan (common in high-speed fans)
config_1pole = FanConfig(edges=3)
controller.configure_fan(1, config_1pole)
# For a standard 2-pulse-per-revolution fan
config_2pole = FanConfig(edges=5)
controller.configure_fan(2, config_2pole)
# For a 4-pulse-per-revolution fan (some server fans)
config_4pole = FanConfig(edges=9)
controller.configure_fan(3, config_4pole)
Diagnosing Incorrect RPM Readings
If your RPM readings seem wrong (too high, too low, or not scaling with PWM):
# Test different edges configurations
import time
controller.set_pwm_duty_cycle(1, 100) # Set to full speed
time.sleep(2) # Wait for fan to stabilize
for edges in [3, 5, 7, 9]:
config = FanConfig(edges=edges)
controller.configure_fan(1, config)
time.sleep(0.5)
rpm = controller.get_current_rpm(1)
print(f"edges={edges}: {rpm} RPM")
# The correct setting will show a reasonable RPM that matches
# your fan's rated speed at 100% PWM
Hardware Requirements for Tachometer
- Pull-up resistor: EMC2305 TACH pins are open-drain and require a 10kΩ pull-up to 3.3V
- Signal voltage: TACH signal should swing from 0V to VDD (typically 3.3V)
- Wiring: Connect fan's TACH wire to EMC2305's TACHx pin for the corresponding channel
Hardware Setup
I2C Address Configuration
The EMC2305 I2C address is configurable via the ADDR_SEL pin:
| ADDR_SEL | Address |
|---|---|
| GND | 0x4C |
| VDD | 0x4D |
| SDA | 0x5C |
| SCL | 0x5D |
| Float | 0x5E/0x5F |
Default in this driver: 0x61 (adjust for your hardware)
I2C Bus Permissions
Ensure your user has I2C access:
# Add user to i2c group
sudo usermod -aG i2c $USER
# Or set permissions
sudo chmod 666 /dev/i2c-*
Verify Hardware
# Install i2c-tools
sudo apt-get install i2c-tools
# Scan I2C bus 0
i2cdetect -y 0
# You should see your EMC2305 at its configured address
Configuration File
Optional YAML configuration support:
# ~/.config/emc2305/emc2305.yaml
i2c:
bus: 0
lock_enabled: true
emc2305:
address: 0x61
pwm_frequency_hz: 26000
fans:
1:
name: "CPU Fan"
control_mode: "fsc"
min_rpm: 1000
max_rpm: 4500
default_target_rpm: 3000
pid_gain_p: 4
pid_gain_i: 2
pid_gain_d: 1
2:
name: "Case Fan"
control_mode: "pwm"
default_duty_percent: 50
Load configuration:
from emc2305.settings import ConfigManager
config_mgr = ConfigManager()
config = config_mgr.load()
# Use loaded configuration
fan_controller = EMC2305(
i2c_bus,
device_address=config.emc2305.address,
pwm_frequency=config.emc2305.pwm_frequency_hz
)
Architecture
┌─────────────────────────────────────┐
│ Application Code │
├─────────────────────────────────────┤
│ EMC2305 Driver (emc2305.py) │ ← High-level API
│ - Fan control │
│ - RPM monitoring │
│ - Fault detection │
├─────────────────────────────────────┤
│ I2C Communication (i2c.py) │ ← Low-level I/O
│ - SMBus operations │
│ - Cross-process locking │
├─────────────────────────────────────┤
│ Hardware (EMC2305 chip) │
└─────────────────────────────────────┘
API Documentation
Main Classes
EMC2305
Main driver class for fan control.
Methods:
set_pwm_duty_cycle(channel, percent)- Set PWM duty cycleset_target_rpm(channel, rpm)- Set target RPM (FSC mode)get_current_rpm(channel)- Read current RPMget_fan_status(channel)- Get fault statusconfigure_fan(channel, config)- Apply full configurationlock_configuration()- Lock settings (irreversible until reset)get_product_features()- Read hardware capabilities
FanConfig
Configuration dataclass for fan channels.
Fields:
control_mode: PWM or FSCmin_rpm,max_rpm: RPM limitsmin_drive_percent: Minimum PWM percentagepid_gain_p/i/d: PID tuning parametersspin_up_level_percent,spin_up_time_ms: Spin-up configurationpwm_divide: Per-fan PWM frequency divider
I2CBus
Low-level I2C communication with locking.
Methods:
read_byte(address, register)write_byte(address, register, value)read_block(address, register, length)write_block(address, register, data)
Examples
See examples/python/ directory:
test_fan_control.py- Basic PWM controltest_rpm_monitor.py- RPM monitoringtest_fsc_mode.py- Closed-loop controltest_fault_detection.py- Fault handling
Testing
# Run all tests
pytest tests/
# Run with coverage
pytest tests/ --cov=emc2305 --cov-report=html
# Run specific test
pytest tests/test_emc2305_init.py -v
Note: Most tests require actual EMC2305 hardware.
Compatibility
Supported Python Versions
- Python 3.9+
- Python 3.10+
- Python 3.11+
- Python 3.12+
Supported Platforms
- Linux (any distribution with I2C support)
- Raspberry Pi OS
- Banana Pi
- Generic embedded Linux
Hardware Requirements
- I2C bus interface
- Microchip EMC2305 (any variant: EMC2305-1/2/3/4)
- Appropriate fan connectors and power supply
Contributing
Contributions are welcome! This project aims to provide a comprehensive, hardware-agnostic driver for the EMC2305.
Development Setup
git clone https://github.com/moffa90/python-emc2305.git
cd emc2305-python
pip install -e ".[dev]"
Code Style
- Follow PEP 8
- Use type hints (PEP 484)
- Document all public APIs
- Run tests before submitting
License
MIT License - see LICENSE file for details.
Copyright (c) 2025 Contributors to the microchip-emc2305 project
References
Support
- Issues: GitHub Issues
- Documentation: GitHub Wiki
- Discussions: GitHub Discussions
Donate
If you find this project useful, consider supporting its development:
Acknowledgments
This driver implements the complete EMC2305 register map and feature set as documented in the Microchip datasheet. Special thanks to the community contributors who helped validate and improve this driver.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file microchip_emc2305-1.2.0.tar.gz.
File metadata
- Download URL: microchip_emc2305-1.2.0.tar.gz
- Upload date:
- Size: 1.5 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6ddf56298f6a8a41e0ea3a7e518504efb4a66c2618e482b8bf27ca5491bf6cb1
|
|
| MD5 |
d453176b8265c35db166f9de62274fdb
|
|
| BLAKE2b-256 |
5c2efa13debaee3472370b6dde888da6c0748dbe7d82911a0820d593dc5e6f8e
|
Provenance
The following attestation bundles were made for microchip_emc2305-1.2.0.tar.gz:
Publisher:
publish.yml on moffa90/python-emc2305
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
microchip_emc2305-1.2.0.tar.gz -
Subject digest:
6ddf56298f6a8a41e0ea3a7e518504efb4a66c2618e482b8bf27ca5491bf6cb1 - Sigstore transparency entry: 725396491
- Sigstore integration time:
-
Permalink:
moffa90/python-emc2305@6d3f02a1311f89c6f7868ac4d3c0828d14f494cd -
Branch / Tag:
refs/tags/v1.2.0 - Owner: https://github.com/moffa90
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6d3f02a1311f89c6f7868ac4d3c0828d14f494cd -
Trigger Event:
release
-
Statement type:
File details
Details for the file microchip_emc2305-1.2.0-py3-none-any.whl.
File metadata
- Download URL: microchip_emc2305-1.2.0-py3-none-any.whl
- Upload date:
- Size: 34.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
76dfb8a73f673849133500f436445a97774b2beaeb8292a9edf27fda01089b57
|
|
| MD5 |
211e1cd265e1f41113f3dad90dc115a7
|
|
| BLAKE2b-256 |
a60a93c85b39f1c356ce7d3265f8fe3ced2bd180a240d38803a7d81853fd8fc6
|
Provenance
The following attestation bundles were made for microchip_emc2305-1.2.0-py3-none-any.whl:
Publisher:
publish.yml on moffa90/python-emc2305
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
microchip_emc2305-1.2.0-py3-none-any.whl -
Subject digest:
76dfb8a73f673849133500f436445a97774b2beaeb8292a9edf27fda01089b57 - Sigstore transparency entry: 725396492
- Sigstore integration time:
-
Permalink:
moffa90/python-emc2305@6d3f02a1311f89c6f7868ac4d3c0828d14f494cd -
Branch / Tag:
refs/tags/v1.2.0 - Owner: https://github.com/moffa90
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6d3f02a1311f89c6f7868ac4d3c0828d14f494cd -
Trigger Event:
release
-
Statement type: