EMC2305 PWM Fan Controller Driver Documentation
Overview
The EMC2305 is a 5‑channel PWM fan controller with optional closed‑loop RPM control using tachometer feedback. Driver functions must be called while the FreeRTOS scheduler is active.
Minimal Working Example
WARNING. Don't just copy this code without reading the rest of this page. Lakshay Gupta takes all responsibility for incorrect information regarding this driver. Happy cooling :D
// Assuming I2C was initialized and interrupt callbacks overridden
// Reference
#include "EMC2305.h"
I2C_HandleTypeDef hi2c1;
EMC2305_HandleTypeDef chip;
void Init_Task(void* argument) {
// Initialize EMC2305
// Only call from ONE task!
if (EMC2305_Init(&chip, &hi2c1, 0x4D) != EMC2305_OK) {
Error_Handler();
}
// Task kills itself
vTaskDelete(NULL);
}
void EMC2305_Task_1(void* argument) {
// Allow chip to power on
vTaskDelay(pdMS_TO_TICKS(250));
// Set global config
EMC2305_Global_Config config = { 0 };
config.watchdog_enable = true;
if (EMC2305_SetGlobalConfig(&chip, &config) != EMC2305_OK) {
Error_Handler();
}
// Set config1 and config2
EMC2305_Fan_Config1 config1 = { 0 };
config1.enable_closed_loop = false; // Set this to true if using FSC (Closed Loop RPM Control). False for using PWM directly
config1.edges = EMC2305_EDG_5; // 5 edges is default for 2 pole fans
config1.range = EMC2305_RNG_2000;
EMC2305_Fan_Config2 config2 = { 0 };
config2.enable_ramp_rate_ctl = true;
config2.enable_glitch_filter = true;
config2.error_window = EMC2305_ERG_200RPM;
config2.derivative_options = EMC2305_DPT_BOTH;
if (EMC2305_SetFanConfig(&chip, EMC2305_FAN2, &config1, &config2) != EMC2305_OK) {
Error_Handler();
};
// Depends on the fan lol (should be in fan datasheet)
if (EMC2305_SetPWMBaseFrequency(&chip, EMC2305_FAN2, EMC2305_PWM_19k53) != EMC2305_OK) {
Error_Handler();
};
// Set minimum drive to 0%
if (EMC2305_WriteReg(&chip, EMC2305_FAN_REG_ADDR(EMC2305_FAN2, EMC2305_REG_FAN1_MIN_DRIVE), 0x00) != EMC2305_OK) {
Error_Handler();
};
// Set PID Gain to lowest (1x)
if (EMC2305_WriteReg(&chip, EMC2305_FAN_REG_ADDR(EMC2305_FAN2, EMC2305_REG_GAIN1), 0x00) != EMC2305_OK) {
Error_Handler();
};
// Set PWM output mode to open-drain (use false for push-pull)
if (EMC2305_SetPWMOutputMode(&chip, EMC2305_FAN2, true) != EMC2305_OK) {
Error_Handler();
};
// Control with direct PWM
// Set PWM2 duty cycle to 25%
if (EMC2305_SetFanPWM(&chip, EMC2305_FAN2, 25) != EMC2305_OK) {
Error_Handler();
};
// Control with closed-loop FSC
// Set RPM to 3000
if (EMC2305_SetFanRPM(&chip, EMC2305_FAN2, 3000) != EMC2305_OK) {
Error_Handler();
};
}
int main(void) {
// Init your HAL, System Clock, and Peripherals here
// Create tasks
xTaskCreateStatic(Init_Task,
"Init Task",
configMINIMAL_STACK_SIZE,
NULL,
tskIDLE_PRIORITY + 1,
initTaskStack,
&initTaskBuffer);
xTaskCreateStatic(EMC2305_Task_1,
"EMC2305 Task 1",
configMINIMAL_STACK_SIZE,
NULL,
tskIDLE_PRIORITY + 2,
emc2305TaskStack_1,
&emc2305TaskBuffer_1);
vTaskStartScheduler();
while (1) {
}
return 0;
}
// I2C Transmit Interrupt Callback
void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef* hi2c) {
EMC2305_I2C_MasterTxCpltCallback(hi2c);
}
// I2C Receive Interrupt Callback
void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef* hi2c) {
EMC2305_I2C_MasterTxCpltCallback(hi2c);
}
Required Hardware Setup
MCU Requirements
Your board must have:
- An STM32 MCU using STM32 HAL (PSOM, LSOM, or any of our other custom STM32 boards)
- A configured I2C peripheral (interrupt‑driven)
- FreeRTOS enabled in your project (all Embedded-Sharepoint projects use RTOS by default)
This driver relies on:
HAL_I2C_Master_Transmit_IT()HAL_I2C_Master_Receive_IT()- FreeRTOS queues, semaphores, and static task creation
Fan Electrical Requirements
Fans may be controlled via:
- Push‑pull PWM (driven to high and low logic levels)
- Open‑drain PWM (only driven low, requires an external pull‑up)
This is configured per‑fan using:
EMC2305_SetPWMOutputMode()
Initialization Process (Required Order)
Prerequisites
Before calling any EMC2305 function:
- FreeRTOS scheduler must be running
- The STM32 I2C peripheral must be initialized
- I2C interrupts must be enabled
Required HAL Callbacks
You must forward these HAL callbacks:
void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c) {
EMC2305_I2C_MasterTxCpltCallback(hi2c);
}
void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c) {
EMC2305_I2C_MasterRxCpltCallback(hi2c);
}
Without this, all I2C operations will hang.
Driver Initialization
The first thing you need to do is initialize the chip using this function. It may only be called from one RTOS task (recommended to do this in your init task). An example using I2C bus 1 and the default chip address (0x4D):
EMC2305_HandleTypeDef chip;
EMC2305_Init(&chip, &hi2c1, 0x4D);
What this does internally:
- Stores the HAL I2C handle
- Converts the 7‑bit address to HAL format (
<< 1) - Creates RTOS objects
- Spawns the I2C worker task
- Associates the chip with a specific I2C instance
-
Verifies:
-
Product ID (
PID == 0b00) - Manufacturer ID (
0x5D)
If any step fails, EMC2305_ERR is returned.
Core Configuration Functions
Global Chip Configuration
EMC2305_Global_Config cfg = {
.alert_mask = false,
.disable_smbus_timeout = true,
.watchdog_enable = false,
.drive_ext_clk = false,
.use_ext_clk = false,
};
EMC2305_SetGlobalConfig(&chip, &cfg);
Controls global chip behavior.
Software Lock (Optional)
EMC2305_EnableSWLock(&chip);
- Makes all SWL registers read‑only
- Lock persists until power cycle
- Use only after configuration is complete
- Do not use this unless you are very confident in your settings
Operating Modes
Open‑Loop PWM Mode (Simple)
When to use:
- You just want to set a duty cycle
- Fan tach feedback is unavailable or unnecessary
Required configuration:
enable_closed_loop = false
Control function:
Example to set Fan 1 PWM output to 60%:
EMC2305_SetFanPWM(&chip, EMC2305_FAN1, 60);
Closed‑Loop RPM Control (Recommended)
When to use:
- You need stable RPM across voltage/temperature changes
- You have tach feedback connected
Required configuration:
EMC2305_Fan_Config1 cfg1 = {
.enable_closed_loop = true,
.range = EMC2305_RNG_2000,
.edges = EMC2305_EDG_5,
.update_time = EMC2305_UDT_100,
};
EMC2305_Fan_Config2 cfg2 = {
.enable_ramp_rate_ctl = true,
.enable_glitch_filter = true,
.derivative_options = EMC2305_DPT_BOTH,
.error_window = EMC2305_ERG_200RPM,
};
EMC2305_SetFanConfig(&chip, EMC2305_FAN1, &cfg1, &cfg2);
Control function:
EMC2305_SetFanRPM(&chip, EMC2305_FAN1, 3000);
Fan Control API Summary
| Purpose | Function |
|---|---|
| Set PWM duty | EMC2305_SetFanPWM() |
| Set RPM target | EMC2305_SetFanRPM() |
| Read RPM | EMC2305_GetFanRPM() |
| Read PWM | EMC2305_GetFanPWM() |
| Read fault status | EMC2305_GetFanStatus() |
Common Pitfalls
Using APIs Before RTOS Starts
❌ Wrong
EMC2305_SetFanPWM(...);
before vTaskStartScheduler()
✔ Correct
Call all APIs from tasks only.
Missing HAL I2C Callbacks
Symptoms:
- Functions block forever
- Timeouts occur
Fix:
- Forward HAL callbacks exactly as shown above
Queue / Semaphore Exhaustion
If too many tasks call EMC2305 APIs simultaneously:
EMC2305_ReadReg()returnsEMC2305_ERR
Fix:
- Increase
EMC2305_QUEUE_LENGTH - Avoid excessive polling
Wrong Control Mode
Calling:
EMC2305_SetFanPWM()
while closed‑loop control is enabled will not work as expected (and vice-versa).
Debugging Guide
Chip Not Detected
Check:
- I2C address (passed as 7‑bit, not shifted)
- SDA/SCL wiring
- Pull‑ups present
RPM Always Reads UINT16_MAX
Causes:
- Invalid fan index
- I2C read failure
- Tach reading = 0
Fan Status Flags
Use:
EMC2305_Fan_Status status = EMC2305_GetFanStatus(&chip);
Flags indicate:
- Watchdog expiration
- Spin failure
- Stall detection
- Drive failure