Note
Hello, welcome to the SunFounder Raspberry Pi & Arduino & ESP32 Enthusiasts Community on Facebook! Dive deeper into Raspberry Pi, Arduino, and ESP32 with fellow enthusiasts.
Why Join?
Expert Support: Solve post-sale issues and technical challenges with help from our community and team.
Learn & Share: Exchange tips and tutorials to enhance your skills.
Exclusive Previews: Get early access to new product announcements and sneak peeks.
Special Discounts: Enjoy exclusive discounts on our newest products.
Festive Promotions and Giveaways: Take part in giveaways and holiday promotions.
👉 Ready to explore and create with us? Click [here] and join today!
4.7 Smart Fan
Introduction
In this project, you’ll build a “smart fan” that operates in both manual and automatic modes. By combining motors, buttons, and a thermistor, the fan can have adjustable wind speeds and respond to changes in temperature. This makes it a perfect experiment for learning about motor control, temperature sensing, and GPIO usage.
What You’ll Need
Here are the components you’ll need for this project:
COMPONENT INTRODUCTION |
PURCHASE LINK |
|---|---|
- |
|
Raspberry Pi |
- |
Circuit Diagram
The circuit diagram below illustrates how to connect the thermistor, button, motor driver, and motor:
Wiring Diagram
Refer to the following image for the breadboard layout and wiring connections:
Running the Example
All example code used in this tutorial is available in the ai-lab-kit directory.
Follow these steps to run the example:
cd ~/ai-lab-kit/python/
sudo python3 4.7_SmartFan.py
Code
Here’s the Python script for this project:
#!/usr/bin/env python3
from fusion_hat.motor import Motor
from fusion_hat.pin import Pin, Mode, Pull
from fusion_hat.adc import ADC
from time import sleep, time
import math
BtnPin = Pin(22, mode=Mode.IN, pull=Pull.DOWN)
motor = Motor("M0")
thermistor = ADC("A3")
level = 0
markTemp = None
last_button_state = 0
last_press_time = 0
_last_print = 0.0
DEBOUNCE_TIME = 0.3
PRINT_INTERVAL = 1.0
MANUAL_HOLD_TIME = 2.0
TEMP_THRESHOLD = 5.0
def temperature(samples=5, delay=0.01):
"""Read thermistor temperature and return average Celsius value."""
vals = []
for _ in range(samples):
analogVal = thermistor.read()
Vr = 3.3 * float(analogVal) / 4095.0
if Vr <= 0 or (3.3 - Vr) <= 0.1:
sleep(delay)
continue
Rt = 10000.0 * Vr / (3.3 - Vr)
if Rt <= 0:
sleep(delay)
continue
tempK = 1.0 / (
((math.log(Rt / 10000.0)) / 3950.0)
+ (1.0 / (273.15 + 25.0))
)
vals.append(tempK - 273.15)
sleep(delay)
if not vals:
return None
return sum(vals) / len(vals)
def motor_run(lv):
"""Set motor power according to level."""
lv = max(0, min(4, lv))
power_table = [0, 25, 50, 75, 100]
motor.power(power_table[lv])
return lv
def read_button():
"""Read current button state."""
try:
return BtnPin.value()
except TypeError:
return BtnPin.value
def main():
"""Main loop for button control and temperature auto adjustment."""
global level, markTemp, last_button_state, last_press_time, _last_print
while markTemp is None:
markTemp = temperature()
sleep(0.1)
while True:
now = time()
button_state = read_button()
if button_state == 1 and last_button_state == 0:
if now - last_press_time >= DEBOUNCE_TIME:
old = level
level = (level + 1) % 5
last_press_time = now
currentTemp = temperature()
if currentTemp is not None:
markTemp = currentTemp
level = motor_run(level)
print(
f"[Button] {old} -> {level} | "
f"Power: {0 if level == 0 else level * 25}%"
)
last_button_state = button_state
currentTemp = temperature()
if currentTemp is None:
print("Sensor read failed.")
sleep(0.5)
continue
auto_allowed = (now - last_press_time) >= MANUAL_HOLD_TIME
if auto_allowed and level != 0:
diff = currentTemp - markTemp
if diff >= TEMP_THRESHOLD:
level = min(4, level + 1)
markTemp = currentTemp
level = motor_run(level)
print(f"[Auto] Temp up -> Level {level}")
elif diff <= -TEMP_THRESHOLD:
level = max(0, level - 1)
markTemp = currentTemp
level = motor_run(level)
print(f"[Auto] Temp down -> Level {level}")
if now - _last_print >= PRINT_INTERVAL:
print(
f"Temp: {currentTemp:.2f} C | "
f"Mark: {markTemp:.2f} C | "
f"Level: {level}"
)
_last_print = now
level = motor_run(level)
sleep(0.05)
try:
main()
except KeyboardInterrupt:
print("\nExiting...")
finally:
motor.stop()
sleep(0.1)
This Python script integrates a motor, button, and temperature sensor to create a temperature-controlled fan system with adjustable speed. When executed:
Temperature Sensing: Reads the current temperature in Celsius using the thermistor. Multiple samples are averaged for accuracy, and invalid readings are skipped rather than causing immediate failure.
Manual Speed Adjustment:
A button connected to GPIO 22 allows the user to cycle through five speed levels (0 to 4).
Pressing the button increases the speed level, and the motor runs at the corresponding power (0%, 25%, 50%, 75%, or 100%). Speed level 0 stops the motor.
Button input includes debounce protection (0.3 seconds) to prevent false triggers from contact bounce.
Manual Priority Window: After a button press, automatic speed adjustment is suppressed for 2 seconds (
MANUAL_HOLD_TIME). This ensures the user’s manual setting is not immediately overridden by an automatic adjustment.Automatic Speed Control: After the manual priority window expires (and when the fan is not off), the system adjusts the motor speed automatically based on temperature changes:
If the temperature increases by 5°C or more, the speed level increases (up to level 4).
If the temperature decreases by 5°C or more, the speed level decreases (down to level 0).
Continuous Monitoring: The system continuously monitors the temperature and button state with a fast 0.05-second loop interval, ensuring responsive adjustments.
Graceful Exit: On
Ctrl+C, the motor stops, and the script exits cleanly.
Understanding the Code
Temperature Calculation:
def temperature(samples=5, delay=0.01): """Read thermistor temperature and return average Celsius value.""" vals = [] for _ in range(samples): analogVal = thermistor.read() Vr = 3.3 * float(analogVal) / 4095.0 if Vr <= 0 or (3.3 - Vr) <= 0.1: sleep(delay) continue Rt = 10000.0 * Vr / (3.3 - Vr) if Rt <= 0: sleep(delay) continue tempK = 1.0 / ( ((math.log(Rt / 10000.0)) / 3950.0) + (1.0 / (273.15 + 25.0)) ) vals.append(tempK - 273.15) sleep(delay) if not vals: return None return sum(vals) / len(vals)
The
temperature()function takes multiple samples from the thermistor, converts each analog reading into a resistance value, and calculates the temperature in Celsius using the Steinhart-Hart equation. Individual bad readings (voltage too low or resistance zero) are skipped viacontinuerather than causing the entire measurement to fail. Only if all samples are invalid does the function returnNone. The final result is the arithmetic mean of all valid samples, giving a stable and noise-resistant reading.Motor Speed Control:
def motor_run(lv): """Set motor power according to level.""" lv = max(0, min(4, lv)) power_table = [0, 25, 50, 75, 100] motor.power(power_table[lv]) return lv
The
motor_run()function maps the numeric level (0–4) to a percentage of motor power using a lookup table. Level 0 sends 0% power (off), while levels 1 through 4 correspond to 25%, 50%, 75%, and 100% power respectively. The level is first clamped to the valid range withmax(0, min(4, lv)).Button Reading with Debounce:
def read_button(): """Read current button state.""" try: return BtnPin.value() except TypeError: return BtnPin.value # In main(): button_state = read_button() if button_state == 1 and last_button_state == 0: if now - last_press_time >= DEBOUNCE_TIME: old = level level = (level + 1) % 5 last_press_time = now currentTemp = temperature() if currentTemp is not None: markTemp = currentTemp level = motor_run(level) print( f"[Button] {old} -> {level} | " f"Power: {0 if level == 0 else level * 25}%" ) last_button_state = button_state
Instead of using an interrupt callback, the code polls the button state in the main loop. A button press is detected by a rising edge: the button state transitions from
0to1(last_button_state == 0→button_state == 1). A debounce timer (DEBOUNCE_TIME = 0.3seconds) prevents contact bounce from triggering multiple presses. When a valid press is detected, the level cycles through 0→1→2→3→4→0, the reference temperature (markTemp) is updated, and the motor speed is applied immediately.Main Loop and Automatic Adjustment:
def main(): """Main loop for button control and temperature auto adjustment.""" global level, markTemp, last_button_state, last_press_time, _last_print while markTemp is None: markTemp = temperature() sleep(0.1) while True: now = time() # ... button polling (see above) ... currentTemp = temperature() if currentTemp is None: print("Sensor read failed.") sleep(0.5) continue auto_allowed = (now - last_press_time) >= MANUAL_HOLD_TIME if auto_allowed and level != 0: diff = currentTemp - markTemp if diff >= TEMP_THRESHOLD: level = min(4, level + 1) markTemp = currentTemp level = motor_run(level) print(f"[Auto] Temp up -> Level {level}") elif diff <= -TEMP_THRESHOLD: level = max(0, level - 1) markTemp = currentTemp level = motor_run(level) print(f"[Auto] Temp down -> Level {level}") # ... periodic print ... sleep(0.05)
The
main()function runs a fast control loop (0.05-second interval). Key behaviors:Initialization: It repeatedly reads the temperature until a valid
markTempis obtained, ensuring the system starts with a known reference.Manual Priority: After a button press, automatic adjustments are blocked for
MANUAL_HOLD_TIME(2 seconds). Thisauto_allowedgate prevents the system from immediately counteracting the user’s manual change.Automatic Adjustment: When auto is allowed and the fan is not at level 0, the code checks whether the current temperature has deviated by
TEMP_THRESHOLD(±5°C) from the reference. If so, the level is stepped up or down by one, and the reference is reset to the current temperature — this creates a latching behavior that prevents oscillation around the threshold.Periodic Output: Every
PRINT_INTERVAL(1 second), the current temperature, reference temperature, and level are printed for monitoring.
Troubleshooting
Motor Does Not Run:
Cause: Incorrect wiring or insufficient power supply.
Solution:
Verify the motor is connected to M0.
Ensure the motor’s power supply matches its voltage requirements.
Temperature Reading is Incorrect:
Cause: Faulty thermistor.
Solution:
Check the thermistor wiring and ensure it is within the specified range.
Button Press Not Detected:
Cause: Incorrect button wiring or GPIO configuration.
Solution:
Verify the button is connected to GPIO 22 and ground.
Test the button independently to confirm it closes the circuit when pressed.
Speed Level Does Not Change Automatically:
Cause: Incorrect temperature difference calculation.
Solution: Ensure the
currentTempandmarkTempvalues update correctly in themain()function.
Extendable Ideas
Overheat Alert: Add a buzzer or LED to alert the user when the temperature exceeds a critical threshold.
if currentTemp > 50: buzzer.on()
Smart Button Functions: Long-press the button to reset the speed level to 0 or toggle automatic/manual modes.
Conclusion
The Smart Fan project demonstrates how to combine manual and automatic control in a single system. It’s a practical example of integrating sensors, motors, and user interaction into a functional and efficient design. Try enhancing it with additional features to create your personalized climate control solution!