.. 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 [|link_sf_facebook|] and join today!
.. _4.1.10_py_mcp3008:
4.1.7 Smart Fan(MCP3008)
===========================
.. note::
.. image:: ../img/mcp3008_and_adc0834.jpg
:width: 25%
:align: left
Depending on your kit version, please identify whether you have **ADC0834** or **MCP3008** and proceed with the matching section.
Introduction
-----------------
In this project, we will use motors, buttons and thermistors to make a
manual + automatic smart fan whose wind speed is adjustable.
Required Components
------------------------------
In this project, we need the following components.
.. image:: ../img/list2_Smart_Fan.png
:width: 800
:align: center
It's definitely convenient to buy a whole kit, here's the link:
.. list-table::
:widths: 20 20 20
:header-rows: 1
* - Name
- ITEMS IN THIS KIT
- LINK
* - Raphael Kit
- 337
- |link_Raphael_kit|
You can also buy them separately from the links below.
.. list-table::
:widths: 30 20
:header-rows: 1
* - COMPONENT INTRODUCTION
- PURCHASE LINK
* - :ref:`cpn_gpio_board`
- |link_gpio_board_buy|
* - :ref:`cpn_breadboard`
- |link_breadboard_buy|
* - :ref:`cpn_wires`
- |link_wires_buy|
* - :ref:`cpn_resistor`
- |link_resistor_buy|
* - :ref:`cpn_power_module`
- \-
* - :ref:`cpn_thermistor`
- |link_thermistor_buy|
* - :ref:`cpn_l293d`
- \-
* - :ref:`cpn_mcp3008`
- \-
* - :ref:`cpn_button`
- |link_button_buy|
* - :ref:`cpn_motor`
- |link_motor_buy|
Schematic Diagram
------------------------
============ ======== ======== ===
T-Board Name physical wiringPi BCM
SPICE0 Pin 24 10 8
SPIMOSI Pin 19 12 10
SPIMISO Pin 21 13 9
SPISCLK Pin 23 14 11
GPIO22 Pin 15 3 22
GPIO5 Pin 29 21 5
GPIO6 Pin 31 22 6
GPIO13 Pin 33 23 13
============ ======== ======== ===
.. image:: ../img/schematic_3.1.4_smart_fan_mcp3008.png
:align: center
Experimental Procedures
-----------------------------
**Step 1:** Build the circuit.
.. image:: ../img/july24_3.1.4_smart_fan_mcp3008.png
.. note::
The power module can apply a 9V battery with the 9V Battery
Buckle in the kit. Insert the jumper cap of the power module into the 5V
bus strips of the breadboard.
.. image:: ../img/image118.jpeg
:align: center
**Step 2:** Set up the SPI interface and install the ``spidev`` library (see :ref:`spi_configuration` for detailed instructions). If you have already completed these steps, you can skip this.
**Step 3**: Get into the folder of the code.
.. raw:: html
.. code-block::
cd ~/raphael-kit/python
**Step 4**: Run.
.. raw:: html
.. code-block::
sudo python3 4.1.10-2_SmartFan.py
As the code runs, start the fan by pressing the button. Every time you
press, 1 speed grade is adjusted up or down. There are **5** kinds of
speed grades: **0~4**. When set to the 4\ :sup:`th` speed grade and you
press the button, the fan stops working with a **0** wind speed.
Once the temperature goes up or down for more than 2℃, the speed
automatically gets 1-grade faster or slower.
Code
--------
.. note::
You can **Modify/Reset/Copy/Run/Stop** the code below. But before that, you need to go to source code path like ``raphael-kit/python``. After modifying the code, you can run it directly to see the effect.
.. raw:: html
.. code-block:: python
#!/usr/bin/env python3
import RPi.GPIO as GPIO
import spidev
import time
import math
# Pin configuration
BTN_PIN = 22 # Button GPIO (physical pin 15)
MOTOR_IN1 = 5 # Motor forward
MOTOR_IN2 = 6 # Motor backward
MOTOR_EN = 13 # PWM enable pin
# GPIO setup
GPIO.setmode(GPIO.BCM)
GPIO.setup(BTN_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(MOTOR_IN1, GPIO.OUT)
GPIO.setup(MOTOR_IN2, GPIO.OUT)
GPIO.setup(MOTOR_EN, GPIO.OUT)
# PWM setup for motor speed control
pwm = GPIO.PWM(MOTOR_EN, 1000) # 1kHz frequency
pwm.start(0)
# Initialize SPI for MCP3008
spi = spidev.SpiDev()
spi.open(0, 0) # Bus 0, CE0
spi.max_speed_hz = 1000000 # 1 MHz
# Global variables
level = 0
currentTemp = 0
markTemp = 0
def read_adc(channel):
if channel < 0 or channel > 7:
return -1
adc = spi.xfer2([1, (8 + channel) << 4, 0])
value = ((adc[1] & 0x03) << 8) | adc[2]
return value
def temperature():
analogVal = read_adc(0)
Vr = 3.3 * analogVal / 1023.0
Rt = 10000.0 * Vr / (3.3 - Vr)
tempK = 1.0 / (((math.log(Rt / 10000.0)) / 3950.0) + (1.0 / (273.15 + 25.0)))
Cel = tempK - 273.15
return Cel
def motor_run(level):
if level == 0:
GPIO.output(MOTOR_IN1, GPIO.LOW)
GPIO.output(MOTOR_IN2, GPIO.LOW)
pwm.ChangeDutyCycle(0)
return 0
if level >= 4:
level = 4
GPIO.output(MOTOR_IN1, GPIO.HIGH)
GPIO.output(MOTOR_IN2, GPIO.LOW)
pwm.ChangeDutyCycle(level * 25) # Map level (1–4) to 25%–100%
return level
def changeLevel(channel):
global level, currentTemp, markTemp
print("Button pressed")
level = (level + 1) % 5
markTemp = currentTemp
# Add event detection for button press
GPIO.add_event_detect(BTN_PIN, GPIO.FALLING, callback=changeLevel, bouncetime=300)
def main():
global level, currentTemp, markTemp
markTemp = temperature()
while True:
currentTemp = temperature()
if level != 0:
if currentTemp - markTemp <= -2:
level -= 1
markTemp = currentTemp
elif currentTemp - markTemp >= 2:
if level < 4:
level += 1
markTemp = currentTemp
level = motor_run(level)
time.sleep(0.2)
try:
main()
except KeyboardInterrupt:
pass
finally:
pwm.stop()
GPIO.cleanup()
spi.close()
Code Explanation
---------------------
#. Import required modules:
- ``RPi.GPIO`` for GPIO control (button and motor),
- ``spidev`` for communicating with MCP3008 ADC,
- ``time`` for delays,
- ``math`` for temperature calculation using logarithmic functions.
.. code-block:: python
#!/usr/bin/env python3
import RPi.GPIO as GPIO
import spidev
import time
import math
#. Set up GPIO pins:
- Button on GPIO22 (with internal pull-up),
- Motor control using GPIO5 (forward), GPIO6 (backward), and GPIO13 (PWM enable).
.. code-block:: python
BTN_PIN = 22
MOTOR_IN1 = 5
MOTOR_IN2 = 6
MOTOR_EN = 13
GPIO.setmode(GPIO.BCM)
GPIO.setup(BTN_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(MOTOR_IN1, GPIO.OUT)
GPIO.setup(MOTOR_IN2, GPIO.OUT)
GPIO.setup(MOTOR_EN, GPIO.OUT)
pwm = GPIO.PWM(MOTOR_EN, 1000)
pwm.start(0)
#. Initialize SPI communication to the MCP3008 (Bus 0, Chip Enable 0) at 1 MHz.
.. code-block:: python
spi = spidev.SpiDev()
spi.open(0, 0)
spi.max_speed_hz = 1000000
#. Define ``read_adc()`` function to read a 10-bit analog value (0–1023) from the specified MCP3008 channel (0–7).
.. code-block:: python
def read_adc(channel):
if channel < 0 or channel > 7:
return -1
adc = spi.xfer2([1, (8 + channel) << 4, 0])
value = ((adc[1] & 0x03) << 8) | adc[2]
return value
#. Define ``temperature()`` function to:
- Convert analog voltage to resistance,
- Apply the Steinhart–Hart equation to compute temperature in Celsius.
.. code-block:: python
def temperature():
analogVal = read_adc(0)
Vr = 3.3 * analogVal / 1023.0
Rt = 10000.0 * Vr / (3.3 - Vr)
tempK = 1.0 / (((math.log(Rt / 10000.0)) / 3950.0) + (1.0 / (273.15 + 25.0)))
Cel = tempK - 273.15
return Cel
#. Define ``motor_run()`` to:
- Stop motor at level 0,
- Run motor forward at increasing speed based on level 1–4, with PWM duty cycle of 25% to 100%.
.. code-block:: python
def motor_run(level):
if level == 0:
GPIO.output(MOTOR_IN1, GPIO.LOW)
GPIO.output(MOTOR_IN2, GPIO.LOW)
pwm.ChangeDutyCycle(0)
return 0
if level >= 4:
level = 4
GPIO.output(MOTOR_IN1, GPIO.HIGH)
GPIO.output(MOTOR_IN2, GPIO.LOW)
pwm.ChangeDutyCycle(level * 25)
return level
#. Define ``changeLevel()`` callback for button press to:
- Increase the motor level cyclically (0 to 4),
- Record the current temperature as the new baseline.
.. code-block:: python
def changeLevel(channel):
global level, currentTemp, markTemp
print("Button pressed")
level = (level + 1) % 5
markTemp = currentTemp
GPIO.add_event_detect(BTN_PIN, GPIO.FALLING, callback=changeLevel, bouncetime=300)
#. Define ``main()`` loop to:
- Monitor temperature change relative to the marked temperature,
- Decrease level if temperature drops by 2°C or more,
- Increase level if it rises by 2°C or more,
- Adjust motor speed accordingly every 0.2 seconds.
.. code-block:: python
def main():
global level, currentTemp, markTemp
markTemp = temperature()
while True:
currentTemp = temperature()
if level != 0:
if currentTemp - markTemp <= -2:
level -= 1
markTemp = currentTemp
elif currentTemp - markTemp >= 2:
if level < 4:
level += 1
markTemp = currentTemp
level = motor_run(level)
time.sleep(0.2)
#. Run the main function and ensure proper cleanup on Ctrl+C (stop motor, cleanup GPIO, close SPI).
.. code-block:: python
try:
main()
except KeyboardInterrupt:
pass
finally:
pwm.stop()
GPIO.cleanup()
spi.close()