Repository files navigation

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.

PyPI versionLicense: MITPythonPlatformCICode style: blackDownloads


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

fromemc2305.driver.i2cimportI2CBusfromemc2305.driver.emc2305importEMC2305, FanConfig# Initialize I2C bus (with cross-process locking)i2c_bus=I2CBus(bus_number=0)
# Initialize EMC2305 at default address 0x4Dfan_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/revconfig=FanConfig(edges=3) # Adjust based on your fan's tachometerfan_controller.configure_fan(channel=1, config=config)
# Set fan 1 to 75% duty cyclefan_controller.set_pwm_duty_cycle(channel=1, percent=75.0)
# Read current RPMrpm=fan_controller.get_current_rpm(channel=1)
print(f"Fan 1 speed: {rpm} RPM")

Closed-Loop RPM Control (FSC Mode)

fromemc2305.driver.emc2305importEMC2305, ControlMode, FanConfig# Configure for FSC modeconfig=FanConfig(
control_mode=ControlMode.FSC,
min_rpm=1000,
max_rpm=4000,
pid_gain_p=4, # Proportional gainpid_gain_i=2, # Integral gainpid_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

fromemc2305.driver.emc2305importFanStatus# Check fan statusstatus=fan_controller.get_fan_status(channel=1)
ifstatus==FanStatus.STALLED:
print("Fan 1 is stalled!")
elifstatus==FanStatus.DRIVE_FAILURE:
print("Fan 1 is aging (drive failure)")
elifstatus==FanStatus.OK:
print("Fan 1 is operating normally")

Alert/Interrupt Handling

# Enable alerts for fan 1fan_controller.configure_fan_alerts(channel=1, enabled=True)
# Check if any alerts are activeiffan_controller.is_alert_active():
# Get which fans have alertsalerts=fan_controller.get_alert_status()
forchannel, has_alertinalerts.items():
ifhas_alert:
print(f"Fan {channel} has an alert condition")
# Clear alert statusfan_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 TypePulses/Revolutionedges Setting
1-pole1edges=3
2-pole2edges=5 (default)
3-pole3edges=7
4-pole4edges=9

How to determine your fan's pulse count:

  1. Check the fan datasheet for "FG Signal" or "Tachometer" specification
  2. Or use trial and error: the correct setting gives RPM readings that scale linearly with PWM

Example: Configuring for Different Fan Types

fromemc2305.driver.emc2305importEMC2305, FanConfigfromemc2305.driver.i2cimportI2CBusbus=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 fanconfig_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 configurationsimporttimecontroller.set_pwm_duty_cycle(1, 100) # Set to full speedtime.sleep(2) # Wait for fan to stabilizeforedgesin [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_SELAddress
GND0x4C
VDD0x4D
SDA0x5C
SCL0x5D
Float0x5E/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.yamli2c:
bus: 0lock_enabled: trueemc2305:
address: 0x61pwm_frequency_hz: 26000fans:
1:
name: "CPU Fan"control_mode: "fsc"min_rpm: 1000max_rpm: 4500default_target_rpm: 3000pid_gain_p: 4pid_gain_i: 2pid_gain_d: 12:
name: "Case Fan"control_mode: "pwm"default_duty_percent: 50

Load configuration:

fromemc2305.settingsimportConfigManagerconfig_mgr=ConfigManager()
config=config_mgr.load()
# Use loaded configurationfan_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 cycle
  • set_target_rpm(channel, rpm) - Set target RPM (FSC mode)
  • get_current_rpm(channel) - Read current RPM
  • get_fan_status(channel) - Get fault status
  • configure_fan(channel, config) - Apply full configuration
  • lock_configuration() - Lock settings (irreversible until reset)
  • get_product_features() - Read hardware capabilities

FanConfig

Configuration dataclass for fan channels.

Fields:

  • control_mode: PWM or FSC
  • min_rpm, max_rpm: RPM limits
  • min_drive_percent: Minimum PWM percentage
  • pid_gain_p/i/d: PID tuning parameters
  • spin_up_level_percent, spin_up_time_ms: Spin-up configuration
  • pwm_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 control
  • test_rpm_monitor.py - RPM monitoring
  • test_fsc_mode.py - Closed-loop control
  • test_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


Donate

If you find this project useful, consider supporting its development:

PayPal


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.

About

Python driver for Microchip EMC2305 5-channel PWM fan controller

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

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.

PyPI versionLicense: MITPythonPlatformCICode style: blackDownloads


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

fromemc2305.driver.i2cimportI2CBusfromemc2305.driver.emc2305importEMC2305, FanConfig# Initialize I2C bus (with cross-process locking)i2c_bus=I2CBus(bus_number=0)
# Initialize EMC2305 at default address 0x4Dfan_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/revconfig=FanConfig(edges=3) # Adjust based on your fan's tachometerfan_controller.configure_fan(channel=1, config=config)
# Set fan 1 to 75% duty cyclefan_controller.set_pwm_duty_cycle(channel=1, percent=75.0)
# Read current RPMrpm=fan_controller.get_current_rpm(channel=1)
print(f"Fan 1 speed: {rpm} RPM")

Closed-Loop RPM Control (FSC Mode)

fromemc2305.driver.emc2305importEMC2305, ControlMode, FanConfig# Configure for FSC modeconfig=FanConfig(
control_mode=ControlMode.FSC,
min_rpm=1000,
max_rpm=4000,
pid_gain_p=4, # Proportional gainpid_gain_i=2, # Integral gainpid_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

fromemc2305.driver.emc2305importFanStatus# Check fan statusstatus=fan_controller.get_fan_status(channel=1)
ifstatus==FanStatus.STALLED:
print("Fan 1 is stalled!")
elifstatus==FanStatus.DRIVE_FAILURE:
print("Fan 1 is aging (drive failure)")
elifstatus==FanStatus.OK:
print("Fan 1 is operating normally")

Alert/Interrupt Handling

# Enable alerts for fan 1fan_controller.configure_fan_alerts(channel=1, enabled=True)
# Check if any alerts are activeiffan_controller.is_alert_active():
# Get which fans have alertsalerts=fan_controller.get_alert_status()
forchannel, has_alertinalerts.items():
ifhas_alert:
print(f"Fan {channel} has an alert condition")
# Clear alert statusfan_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 TypePulses/Revolutionedges Setting
1-pole1edges=3
2-pole2edges=5 (default)
3-pole3edges=7
4-pole4edges=9

How to determine your fan's pulse count:

  1. Check the fan datasheet for "FG Signal" or "Tachometer" specification
  2. Or use trial and error: the correct setting gives RPM readings that scale linearly with PWM

Example: Configuring for Different Fan Types

fromemc2305.driver.emc2305importEMC2305, FanConfigfromemc2305.driver.i2cimportI2CBusbus=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 fanconfig_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 configurationsimporttimecontroller.set_pwm_duty_cycle(1, 100) # Set to full speedtime.sleep(2) # Wait for fan to stabilizeforedgesin [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_SELAddress
GND0x4C
VDD0x4D
SDA0x5C
SCL0x5D
Float0x5E/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.yamli2c:
bus: 0lock_enabled: trueemc2305:
address: 0x61pwm_frequency_hz: 26000fans:
1:
name: "CPU Fan"control_mode: "fsc"min_rpm: 1000max_rpm: 4500default_target_rpm: 3000pid_gain_p: 4pid_gain_i: 2pid_gain_d: 12:
name: "Case Fan"control_mode: "pwm"default_duty_percent: 50

Load configuration:

fromemc2305.settingsimportConfigManagerconfig_mgr=ConfigManager()
config=config_mgr.load()
# Use loaded configurationfan_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 cycle
  • set_target_rpm(channel, rpm) - Set target RPM (FSC mode)
  • get_current_rpm(channel) - Read current RPM
  • get_fan_status(channel) - Get fault status
  • configure_fan(channel, config) - Apply full configuration
  • lock_configuration() - Lock settings (irreversible until reset)
  • get_product_features() - Read hardware capabilities

FanConfig

Configuration dataclass for fan channels.

Fields:

  • control_mode: PWM or FSC
  • min_rpm, max_rpm: RPM limits
  • min_drive_percent: Minimum PWM percentage
  • pid_gain_p/i/d: PID tuning parameters
  • spin_up_level_percent, spin_up_time_ms: Spin-up configuration
  • pwm_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 control
  • test_rpm_monitor.py - RPM monitoring
  • test_fsc_mode.py - Closed-loop control
  • test_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


Donate

If you find this project useful, consider supporting its development:

PayPal


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.

About

Python driver for Microchip EMC2305 5-channel PWM fan controller

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

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.

PyPI versionLicense: MITPythonPlatformCICode style: blackDownloads


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

fromemc2305.driver.i2cimportI2CBusfromemc2305.driver.emc2305importEMC2305, FanConfig# Initialize I2C bus (with cross-process locking)i2c_bus=I2CBus(bus_number=0)
# Initialize EMC2305 at default address 0x4Dfan_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/revconfig=FanConfig(edges=3) # Adjust based on your fan's tachometerfan_controller.configure_fan(channel=1, config=config)
# Set fan 1 to 75% duty cyclefan_controller.set_pwm_duty_cycle(channel=1, percent=75.0)
# Read current RPMrpm=fan_controller.get_current_rpm(channel=1)
print(f"Fan 1 speed: {rpm} RPM")

Closed-Loop RPM Control (FSC Mode)

fromemc2305.driver.emc2305importEMC2305, ControlMode, FanConfig# Configure for FSC modeconfig=FanConfig(
control_mode=ControlMode.FSC,
min_rpm=1000,
max_rpm=4000,
pid_gain_p=4, # Proportional gainpid_gain_i=2, # Integral gainpid_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

fromemc2305.driver.emc2305importFanStatus# Check fan statusstatus=fan_controller.get_fan_status(channel=1)
ifstatus==FanStatus.STALLED:
print("Fan 1 is stalled!")
elifstatus==FanStatus.DRIVE_FAILURE:
print("Fan 1 is aging (drive failure)")
elifstatus==FanStatus.OK:
print("Fan 1 is operating normally")

Alert/Interrupt Handling

# Enable alerts for fan 1fan_controller.configure_fan_alerts(channel=1, enabled=True)
# Check if any alerts are activeiffan_controller.is_alert_active():
# Get which fans have alertsalerts=fan_controller.get_alert_status()
forchannel, has_alertinalerts.items():
ifhas_alert:
print(f"Fan {channel} has an alert condition")
# Clear alert statusfan_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 TypePulses/Revolutionedges Setting
1-pole1edges=3
2-pole2edges=5 (default)
3-pole3edges=7
4-pole4edges=9

How to determine your fan's pulse count:

  1. Check the fan datasheet for "FG Signal" or "Tachometer" specification
  2. Or use trial and error: the correct setting gives RPM readings that scale linearly with PWM

Example: Configuring for Different Fan Types

fromemc2305.driver.emc2305importEMC2305, FanConfigfromemc2305.driver.i2cimportI2CBusbus=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 fanconfig_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 configurationsimporttimecontroller.set_pwm_duty_cycle(1, 100) # Set to full speedtime.sleep(2) # Wait for fan to stabilizeforedgesin [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_SELAddress
GND0x4C
VDD0x4D
SDA0x5C
SCL0x5D
Float0x5E/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.yamli2c:
bus: 0lock_enabled: trueemc2305:
address: 0x61pwm_frequency_hz: 26000fans:
1:
name: "CPU Fan"control_mode: "fsc"min_rpm: 1000max_rpm: 4500default_target_rpm: 3000pid_gain_p: 4pid_gain_i: 2pid_gain_d: 12:
name: "Case Fan"control_mode: "pwm"default_duty_percent: 50

Load configuration:

fromemc2305.settingsimportConfigManagerconfig_mgr=ConfigManager()
config=config_mgr.load()
# Use loaded configurationfan_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 cycle
  • set_target_rpm(channel, rpm) - Set target RPM (FSC mode)
  • get_current_rpm(channel) - Read current RPM
  • get_fan_status(channel) - Get fault status
  • configure_fan(channel, config) - Apply full configuration
  • lock_configuration() - Lock settings (irreversible until reset)
  • get_product_features() - Read hardware capabilities

FanConfig

Configuration dataclass for fan channels.

Fields:

  • control_mode: PWM or FSC
  • min_rpm, max_rpm: RPM limits
  • min_drive_percent: Minimum PWM percentage
  • pid_gain_p/i/d: PID tuning parameters
  • spin_up_level_percent, spin_up_time_ms: Spin-up configuration
  • pwm_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 control
  • test_rpm_monitor.py - RPM monitoring
  • test_fsc_mode.py - Closed-loop control
  • test_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


Donate

If you find this project useful, consider supporting its development:

PayPal


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.

About

Python driver for Microchip EMC2305 5-channel PWM fan controller

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

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.

PyPI versionLicense: MITPythonPlatformCICode style: blackDownloads


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

fromemc2305.driver.i2cimportI2CBusfromemc2305.driver.emc2305importEMC2305, FanConfig# Initialize I2C bus (with cross-process locking)i2c_bus=I2CBus(bus_number=0)
# Initialize EMC2305 at default address 0x4Dfan_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/revconfig=FanConfig(edges=3) # Adjust based on your fan's tachometerfan_controller.configure_fan(channel=1, config=config)
# Set fan 1 to 75% duty cyclefan_controller.set_pwm_duty_cycle(channel=1, percent=75.0)
# Read current RPMrpm=fan_controller.get_current_rpm(channel=1)
print(f"Fan 1 speed: {rpm} RPM")

Closed-Loop RPM Control (FSC Mode)

fromemc2305.driver.emc2305importEMC2305, ControlMode, FanConfig# Configure for FSC modeconfig=FanConfig(
control_mode=ControlMode.FSC,
min_rpm=1000,
max_rpm=4000,
pid_gain_p=4, # Proportional gainpid_gain_i=2, # Integral gainpid_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

fromemc2305.driver.emc2305importFanStatus# Check fan statusstatus=fan_controller.get_fan_status(channel=1)
ifstatus==FanStatus.STALLED:
print("Fan 1 is stalled!")
elifstatus==FanStatus.DRIVE_FAILURE:
print("Fan 1 is aging (drive failure)")
elifstatus==FanStatus.OK:
print("Fan 1 is operating normally")

Alert/Interrupt Handling

# Enable alerts for fan 1fan_controller.configure_fan_alerts(channel=1, enabled=True)
# Check if any alerts are activeiffan_controller.is_alert_active():
# Get which fans have alertsalerts=fan_controller.get_alert_status()
forchannel, has_alertinalerts.items():
ifhas_alert:
print(f"Fan {channel} has an alert condition")
# Clear alert statusfan_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 TypePulses/Revolutionedges Setting
1-pole1edges=3
2-pole2edges=5 (default)
3-pole3edges=7
4-pole4edges=9

How to determine your fan's pulse count:

  1. Check the fan datasheet for "FG Signal" or "Tachometer" specification
  2. Or use trial and error: the correct setting gives RPM readings that scale linearly with PWM

Example: Configuring for Different Fan Types

fromemc2305.driver.emc2305importEMC2305, FanConfigfromemc2305.driver.i2cimportI2CBusbus=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 fanconfig_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 configurationsimporttimecontroller.set_pwm_duty_cycle(1, 100) # Set to full speedtime.sleep(2) # Wait for fan to stabilizeforedgesin [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_SELAddress
GND0x4C
VDD0x4D
SDA0x5C
SCL0x5D
Float0x5E/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.yamli2c:
bus: 0lock_enabled: trueemc2305:
address: 0x61pwm_frequency_hz: 26000fans:
1:
name: "CPU Fan"control_mode: "fsc"min_rpm: 1000max_rpm: 4500default_target_rpm: 3000pid_gain_p: 4pid_gain_i: 2pid_gain_d: 12:
name: "Case Fan"control_mode: "pwm"default_duty_percent: 50

Load configuration:

fromemc2305.settingsimportConfigManagerconfig_mgr=ConfigManager()
config=config_mgr.load()
# Use loaded configurationfan_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 cycle
  • set_target_rpm(channel, rpm) - Set target RPM (FSC mode)
  • get_current_rpm(channel) - Read current RPM
  • get_fan_status(channel) - Get fault status
  • configure_fan(channel, config) - Apply full configuration
  • lock_configuration() - Lock settings (irreversible until reset)
  • get_product_features() - Read hardware capabilities

FanConfig

Configuration dataclass for fan channels.

Fields:

  • control_mode: PWM or FSC
  • min_rpm, max_rpm: RPM limits
  • min_drive_percent: Minimum PWM percentage
  • pid_gain_p/i/d: PID tuning parameters
  • spin_up_level_percent, spin_up_time_ms: Spin-up configuration
  • pwm_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 control
  • test_rpm_monitor.py - RPM monitoring
  • test_fsc_mode.py - Closed-loop control
  • test_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


Donate

If you find this project useful, consider supporting its development:

PayPal


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.

About

Python driver for Microchip EMC2305 5-channel PWM fan controller

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

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.

PyPI versionLicense: MITPythonPlatformCICode style: blackDownloads


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

fromemc2305.driver.i2cimportI2CBusfromemc2305.driver.emc2305importEMC2305, FanConfig# Initialize I2C bus (with cross-process locking)i2c_bus=I2CBus(bus_number=0)
# Initialize EMC2305 at default address 0x4Dfan_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/revconfig=FanConfig(edges=3) # Adjust based on your fan's tachometerfan_controller.configure_fan(channel=1, config=config)
# Set fan 1 to 75% duty cyclefan_controller.set_pwm_duty_cycle(channel=1, percent=75.0)
# Read current RPMrpm=fan_controller.get_current_rpm(channel=1)
print(f"Fan 1 speed: {rpm} RPM")

Closed-Loop RPM Control (FSC Mode)

fromemc2305.driver.emc2305importEMC2305, ControlMode, FanConfig# Configure for FSC modeconfig=FanConfig(
control_mode=ControlMode.FSC,
min_rpm=1000,
max_rpm=4000,
pid_gain_p=4, # Proportional gainpid_gain_i=2, # Integral gainpid_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

fromemc2305.driver.emc2305importFanStatus# Check fan statusstatus=fan_controller.get_fan_status(channel=1)
ifstatus==FanStatus.STALLED:
print("Fan 1 is stalled!")
elifstatus==FanStatus.DRIVE_FAILURE:
print("Fan 1 is aging (drive failure)")
elifstatus==FanStatus.OK:
print("Fan 1 is operating normally")

Alert/Interrupt Handling

# Enable alerts for fan 1fan_controller.configure_fan_alerts(channel=1, enabled=True)
# Check if any alerts are activeiffan_controller.is_alert_active():
# Get which fans have alertsalerts=fan_controller.get_alert_status()
forchannel, has_alertinalerts.items():
ifhas_alert:
print(f"Fan {channel} has an alert condition")
# Clear alert statusfan_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 TypePulses/Revolutionedges Setting
1-pole1edges=3
2-pole2edges=5 (default)
3-pole3edges=7
4-pole4edges=9

How to determine your fan's pulse count:

  1. Check the fan datasheet for "FG Signal" or "Tachometer" specification
  2. Or use trial and error: the correct setting gives RPM readings that scale linearly with PWM

Example: Configuring for Different Fan Types

fromemc2305.driver.emc2305importEMC2305, FanConfigfromemc2305.driver.i2cimportI2CBusbus=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 fanconfig_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 configurationsimporttimecontroller.set_pwm_duty_cycle(1, 100) # Set to full speedtime.sleep(2) # Wait for fan to stabilizeforedgesin [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_SELAddress
GND0x4C
VDD0x4D
SDA0x5C
SCL0x5D
Float0x5E/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.yamli2c:
bus: 0lock_enabled: trueemc2305:
address: 0x61pwm_frequency_hz: 26000fans:
1:
name: "CPU Fan"control_mode: "fsc"min_rpm: 1000max_rpm: 4500default_target_rpm: 3000pid_gain_p: 4pid_gain_i: 2pid_gain_d: 12:
name: "Case Fan"control_mode: "pwm"default_duty_percent: 50

Load configuration:

fromemc2305.settingsimportConfigManagerconfig_mgr=ConfigManager()
config=config_mgr.load()
# Use loaded configurationfan_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 cycle
  • set_target_rpm(channel, rpm) - Set target RPM (FSC mode)
  • get_current_rpm(channel) - Read current RPM
  • get_fan_status(channel) - Get fault status
  • configure_fan(channel, config) - Apply full configuration
  • lock_configuration() - Lock settings (irreversible until reset)
  • get_product_features() - Read hardware capabilities

FanConfig

Configuration dataclass for fan channels.

Fields:

  • control_mode: PWM or FSC
  • min_rpm, max_rpm: RPM limits
  • min_drive_percent: Minimum PWM percentage
  • pid_gain_p/i/d: PID tuning parameters
  • spin_up_level_percent, spin_up_time_ms: Spin-up configuration
  • pwm_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 control
  • test_rpm_monitor.py - RPM monitoring
  • test_fsc_mode.py - Closed-loop control
  • test_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


Donate

If you find this project useful, consider supporting its development:

PayPal


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.

About

Python driver for Microchip EMC2305 5-channel PWM fan controller

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

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.

PyPI versionLicense: MITPythonPlatformCICode style: blackDownloads


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

fromemc2305.driver.i2cimportI2CBusfromemc2305.driver.emc2305importEMC2305, FanConfig# Initialize I2C bus (with cross-process locking)i2c_bus=I2CBus(bus_number=0)
# Initialize EMC2305 at default address 0x4Dfan_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/revconfig=FanConfig(edges=3) # Adjust based on your fan's tachometerfan_controller.configure_fan(channel=1, config=config)
# Set fan 1 to 75% duty cyclefan_controller.set_pwm_duty_cycle(channel=1, percent=75.0)
# Read current RPMrpm=fan_controller.get_current_rpm(channel=1)
print(f"Fan 1 speed: {rpm} RPM")

Closed-Loop RPM Control (FSC Mode)

fromemc2305.driver.emc2305importEMC2305, ControlMode, FanConfig# Configure for FSC modeconfig=FanConfig(
control_mode=ControlMode.FSC,
min_rpm=1000,
max_rpm=4000,
pid_gain_p=4, # Proportional gainpid_gain_i=2, # Integral gainpid_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

fromemc2305.driver.emc2305importFanStatus# Check fan statusstatus=fan_controller.get_fan_status(channel=1)
ifstatus==FanStatus.STALLED:
print("Fan 1 is stalled!")
elifstatus==FanStatus.DRIVE_FAILURE:
print("Fan 1 is aging (drive failure)")
elifstatus==FanStatus.OK:
print("Fan 1 is operating normally")

Alert/Interrupt Handling

# Enable alerts for fan 1fan_controller.configure_fan_alerts(channel=1, enabled=True)
# Check if any alerts are activeiffan_controller.is_alert_active():
# Get which fans have alertsalerts=fan_controller.get_alert_status()
forchannel, has_alertinalerts.items():
ifhas_alert:
print(f"Fan {channel} has an alert condition")
# Clear alert statusfan_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 TypePulses/Revolutionedges Setting
1-pole1edges=3
2-pole2edges=5 (default)
3-pole3edges=7
4-pole4edges=9

How to determine your fan's pulse count:

  1. Check the fan datasheet for "FG Signal" or "Tachometer" specification
  2. Or use trial and error: the correct setting gives RPM readings that scale linearly with PWM

Example: Configuring for Different Fan Types

fromemc2305.driver.emc2305importEMC2305, FanConfigfromemc2305.driver.i2cimportI2CBusbus=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 fanconfig_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 configurationsimporttimecontroller.set_pwm_duty_cycle(1, 100) # Set to full speedtime.sleep(2) # Wait for fan to stabilizeforedgesin [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_SELAddress
GND0x4C
VDD0x4D
SDA0x5C
SCL0x5D
Float0x5E/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.yamli2c:
bus: 0lock_enabled: trueemc2305:
address: 0x61pwm_frequency_hz: 26000fans:
1:
name: "CPU Fan"control_mode: "fsc"min_rpm: 1000max_rpm: 4500default_target_rpm: 3000pid_gain_p: 4pid_gain_i: 2pid_gain_d: 12:
name: "Case Fan"control_mode: "pwm"default_duty_percent: 50

Load configuration:

fromemc2305.settingsimportConfigManagerconfig_mgr=ConfigManager()
config=config_mgr.load()
# Use loaded configurationfan_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 cycle
  • set_target_rpm(channel, rpm) - Set target RPM (FSC mode)
  • get_current_rpm(channel) - Read current RPM
  • get_fan_status(channel) - Get fault status
  • configure_fan(channel, config) - Apply full configuration
  • lock_configuration() - Lock settings (irreversible until reset)
  • get_product_features() - Read hardware capabilities

FanConfig

Configuration dataclass for fan channels.

Fields:

  • control_mode: PWM or FSC
  • min_rpm, max_rpm: RPM limits
  • min_drive_percent: Minimum PWM percentage
  • pid_gain_p/i/d: PID tuning parameters
  • spin_up_level_percent, spin_up_time_ms: Spin-up configuration
  • pwm_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 control
  • test_rpm_monitor.py - RPM monitoring
  • test_fsc_mode.py - Closed-loop control
  • test_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


Donate

If you find this project useful, consider supporting its development:

PayPal


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.

About

Python driver for Microchip EMC2305 5-channel PWM fan controller

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

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.

PyPI versionLicense: MITPythonPlatformCICode style: blackDownloads


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

fromemc2305.driver.i2cimportI2CBusfromemc2305.driver.emc2305importEMC2305, FanConfig# Initialize I2C bus (with cross-process locking)i2c_bus=I2CBus(bus_number=0)
# Initialize EMC2305 at default address 0x4Dfan_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/revconfig=FanConfig(edges=3) # Adjust based on your fan's tachometerfan_controller.configure_fan(channel=1, config=config)
# Set fan 1 to 75% duty cyclefan_controller.set_pwm_duty_cycle(channel=1, percent=75.0)
# Read current RPMrpm=fan_controller.get_current_rpm(channel=1)
print(f"Fan 1 speed: {rpm} RPM")

Closed-Loop RPM Control (FSC Mode)

fromemc2305.driver.emc2305importEMC2305, ControlMode, FanConfig# Configure for FSC modeconfig=FanConfig(
control_mode=ControlMode.FSC,
min_rpm=1000,
max_rpm=4000,
pid_gain_p=4, # Proportional gainpid_gain_i=2, # Integral gainpid_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

fromemc2305.driver.emc2305importFanStatus# Check fan statusstatus=fan_controller.get_fan_status(channel=1)
ifstatus==FanStatus.STALLED:
print("Fan 1 is stalled!")
elifstatus==FanStatus.DRIVE_FAILURE:
print("Fan 1 is aging (drive failure)")
elifstatus==FanStatus.OK:
print("Fan 1 is operating normally")

Alert/Interrupt Handling

# Enable alerts for fan 1fan_controller.configure_fan_alerts(channel=1, enabled=True)
# Check if any alerts are activeiffan_controller.is_alert_active():
# Get which fans have alertsalerts=fan_controller.get_alert_status()
forchannel, has_alertinalerts.items():
ifhas_alert:
print(f"Fan {channel} has an alert condition")
# Clear alert statusfan_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 TypePulses/Revolutionedges Setting
1-pole1edges=3
2-pole2edges=5 (default)
3-pole3edges=7
4-pole4edges=9

How to determine your fan's pulse count:

  1. Check the fan datasheet for "FG Signal" or "Tachometer" specification
  2. Or use trial and error: the correct setting gives RPM readings that scale linearly with PWM

Example: Configuring for Different Fan Types

fromemc2305.driver.emc2305importEMC2305, FanConfigfromemc2305.driver.i2cimportI2CBusbus=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 fanconfig_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 configurationsimporttimecontroller.set_pwm_duty_cycle(1, 100) # Set to full speedtime.sleep(2) # Wait for fan to stabilizeforedgesin [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_SELAddress
GND0x4C
VDD0x4D
SDA0x5C
SCL0x5D
Float0x5E/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.yamli2c:
bus: 0lock_enabled: trueemc2305:
address: 0x61pwm_frequency_hz: 26000fans:
1:
name: "CPU Fan"control_mode: "fsc"min_rpm: 1000max_rpm: 4500default_target_rpm: 3000pid_gain_p: 4pid_gain_i: 2pid_gain_d: 12:
name: "Case Fan"control_mode: "pwm"default_duty_percent: 50

Load configuration:

fromemc2305.settingsimportConfigManagerconfig_mgr=ConfigManager()
config=config_mgr.load()
# Use loaded configurationfan_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 cycle
  • set_target_rpm(channel, rpm) - Set target RPM (FSC mode)
  • get_current_rpm(channel) - Read current RPM
  • get_fan_status(channel) - Get fault status
  • configure_fan(channel, config) - Apply full configuration
  • lock_configuration() - Lock settings (irreversible until reset)
  • get_product_features() - Read hardware capabilities

FanConfig

Configuration dataclass for fan channels.

Fields:

  • control_mode: PWM or FSC
  • min_rpm, max_rpm: RPM limits
  • min_drive_percent: Minimum PWM percentage
  • pid_gain_p/i/d: PID tuning parameters
  • spin_up_level_percent, spin_up_time_ms: Spin-up configuration
  • pwm_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 control
  • test_rpm_monitor.py - RPM monitoring
  • test_fsc_mode.py - Closed-loop control
  • test_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


Donate

If you find this project useful, consider supporting its development:

PayPal


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.

About

Python driver for Microchip EMC2305 5-channel PWM fan controller

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

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.

PyPI versionLicense: MITPythonPlatformCICode style: blackDownloads


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

fromemc2305.driver.i2cimportI2CBusfromemc2305.driver.emc2305importEMC2305, FanConfig# Initialize I2C bus (with cross-process locking)i2c_bus=I2CBus(bus_number=0)
# Initialize EMC2305 at default address 0x4Dfan_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/revconfig=FanConfig(edges=3) # Adjust based on your fan's tachometerfan_controller.configure_fan(channel=1, config=config)
# Set fan 1 to 75% duty cyclefan_controller.set_pwm_duty_cycle(channel=1, percent=75.0)
# Read current RPMrpm=fan_controller.get_current_rpm(channel=1)
print(f"Fan 1 speed: {rpm} RPM")

Closed-Loop RPM Control (FSC Mode)

fromemc2305.driver.emc2305importEMC2305, ControlMode, FanConfig# Configure for FSC modeconfig=FanConfig(
control_mode=ControlMode.FSC,
min_rpm=1000,
max_rpm=4000,
pid_gain_p=4, # Proportional gainpid_gain_i=2, # Integral gainpid_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

fromemc2305.driver.emc2305importFanStatus# Check fan statusstatus=fan_controller.get_fan_status(channel=1)
ifstatus==FanStatus.STALLED:
print("Fan 1 is stalled!")
elifstatus==FanStatus.DRIVE_FAILURE:
print("Fan 1 is aging (drive failure)")
elifstatus==FanStatus.OK:
print("Fan 1 is operating normally")

Alert/Interrupt Handling

# Enable alerts for fan 1fan_controller.configure_fan_alerts(channel=1, enabled=True)
# Check if any alerts are activeiffan_controller.is_alert_active():
# Get which fans have alertsalerts=fan_controller.get_alert_status()
forchannel, has_alertinalerts.items():
ifhas_alert:
print(f"Fan {channel} has an alert condition")
# Clear alert statusfan_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 TypePulses/Revolutionedges Setting
1-pole1edges=3
2-pole2edges=5 (default)
3-pole3edges=7
4-pole4edges=9

How to determine your fan's pulse count:

  1. Check the fan datasheet for "FG Signal" or "Tachometer" specification
  2. Or use trial and error: the correct setting gives RPM readings that scale linearly with PWM

Example: Configuring for Different Fan Types

fromemc2305.driver.emc2305importEMC2305, FanConfigfromemc2305.driver.i2cimportI2CBusbus=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 fanconfig_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 configurationsimporttimecontroller.set_pwm_duty_cycle(1, 100) # Set to full speedtime.sleep(2) # Wait for fan to stabilizeforedgesin [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_SELAddress
GND0x4C
VDD0x4D
SDA0x5C
SCL0x5D
Float0x5E/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.yamli2c:
bus: 0lock_enabled: trueemc2305:
address: 0x61pwm_frequency_hz: 26000fans:
1:
name: "CPU Fan"control_mode: "fsc"min_rpm: 1000max_rpm: 4500default_target_rpm: 3000pid_gain_p: 4pid_gain_i: 2pid_gain_d: 12:
name: "Case Fan"control_mode: "pwm"default_duty_percent: 50

Load configuration:

fromemc2305.settingsimportConfigManagerconfig_mgr=ConfigManager()
config=config_mgr.load()
# Use loaded configurationfan_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 cycle
  • set_target_rpm(channel, rpm) - Set target RPM (FSC mode)
  • get_current_rpm(channel) - Read current RPM
  • get_fan_status(channel) - Get fault status
  • configure_fan(channel, config) - Apply full configuration
  • lock_configuration() - Lock settings (irreversible until reset)
  • get_product_features() - Read hardware capabilities

FanConfig

Configuration dataclass for fan channels.

Fields:

  • control_mode: PWM or FSC
  • min_rpm, max_rpm: RPM limits
  • min_drive_percent: Minimum PWM percentage
  • pid_gain_p/i/d: PID tuning parameters
  • spin_up_level_percent, spin_up_time_ms: Spin-up configuration
  • pwm_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 control
  • test_rpm_monitor.py - RPM monitoring
  • test_fsc_mode.py - Closed-loop control
  • test_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


Donate

If you find this project useful, consider supporting its development:

PayPal


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.

About

Python driver for Microchip EMC2305 5-channel PWM fan controller

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages