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!

3.1.4 Smart Fan(MCP3008)

Note

_images/mcp3008_and_adc0834.jpg

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.

_images/list2_Smart_Fan.png

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

_images/schematic_3.1.4_smart_fan_mcp3008.png

Experimental Procedures

Step 1: Build the circuit.

_images/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.

_images/image118.jpeg

For C Language Users

Step 2: Get into the folder of the code.

cd ~/davinci-kit-for-raspberry-pi/c/3.1.4-2/

Step 3: Compile.

gcc 3.1.4_SmartFan.c -o SmartFan -lwiringPi -lm

Step 4: Run the executable file above.

./SmartFan

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 4th 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.

Note

If it does not work after running, or there is an error prompt: "wiringPi.h: No such file or directory", please refer to Install and Check the WiringPi.

Code

#include <wiringPi.h>
#include <wiringPiSPI.h>
#include <stdio.h>
#include <softPwm.h>
#include <math.h>

#define SPI_CHANNEL 0
#define SPI_SPEED   1000000
#define MotorPin1   21
#define MotorPin2   22
#define MotorEnable 23
#define BtnPin      3

int read_ADC(int channel)
{
    if (channel < 0 || channel > 7) return -1;

    unsigned char buffer[3];
    buffer[0] = 1;                      // Start bit
    buffer[1] = (8 + channel) << 4;     // Single-ended mode and channel
    buffer[2] = 0;

    wiringPiSPIDataRW(SPI_CHANNEL, buffer, 3);

    int result = ((buffer[1] & 3) << 8) | buffer[2];
    return result;
}

int temperture()
{
    int analogVal = read_ADC(0);
    double Vr = 3.3 * analogVal / 1023.0;  // Use 3.3V as Vref for MCP3008
    double Rt = 10000.0 * Vr / (3.3 - Vr);
    double temp = 1 / (((log(Rt / 10000.0)) / 3950.0) + (1 / (273.15 + 25.0)));
    double cel = temp - 273.15;
    double Fah = cel * 1.8 + 32;
    printf("Celsius: %.2f C  Fahrenheit: %.2f F\n", cel, Fah);
    return (int)cel;
}

int motor(int level)
{
    if (level == 0) {
        digitalWrite(MotorEnable, LOW);
        return 0;
    }
    if (level >= 4) {
        level = 4;
    }
    digitalWrite(MotorEnable, HIGH);
    softPwmWrite(MotorPin1, level * 25);
    return level;
}

void setup()
{
    if (wiringPiSetup() == -1) {
        printf("wiringPi setup failed!\n");
        return;
    }

    if (wiringPiSPISetup(SPI_CHANNEL, SPI_SPEED) == -1) {
        printf("SPI setup failed!\n");
        return;
    }

    softPwmCreate(MotorPin1, 0, 100);
    softPwmCreate(MotorPin2, 0, 100);
    pinMode(MotorEnable, OUTPUT);
    pinMode(BtnPin, INPUT);
}

int main(void)
{
    setup();
    int currentState, lastState = 0;
    int level = 0;
    int currentTemp, markTemp = 0;

    while (1) {
        currentState = digitalRead(BtnPin);
        currentTemp = temperture();

        if (currentTemp <= 0) continue;

        if (currentState == 1 && lastState == 0) {
            level = (level + 1) % 5;
            markTemp = currentTemp;
            delay(500);
        }

        lastState = currentState;

        if (level != 0) {
            if (currentTemp - markTemp <= -2) {
                level = level - 1;
                markTemp = currentTemp;
            }
            if (currentTemp - markTemp >= 2) {
                level = level + 1;
                markTemp = currentTemp;
            }
        }

        level = motor(level);
    }

    return 0;
}

Code Explanation

int read_ADC(int channel)
{
    if (channel < 0 || channel > 7) return -1;

    unsigned char buffer[3];
    buffer[0] = 1;                      // Start bit
    buffer[1] = (8 + channel) << 4;     // Single-ended mode and channel
    buffer[2] = 0;

    wiringPiSPIDataRW(SPI_CHANNEL, buffer, 3);

    int result = ((buffer[1] & 3) << 8) | buffer[2];
    return result;
}

This function is used to read analog input from MCP3008 on the specified channel. It sends a 3-byte SPI command and returns a 10-bit digital value between 0–1023.

int temperture()
{
    int analogVal = read_ADC(0);
    double Vr = 3.3 * analogVal / 1023.0;  // Use 3.3V as Vref for MCP3008
    double Rt = 10000.0 * Vr / (3.3 - Vr);
    double temp = 1 / (((log(Rt / 10000.0)) / 3950.0) + (1 / (273.15 + 25.0)));
    double cel = temp - 273.15;
    double Fah = cel * 1.8 + 32;
    printf("Celsius: %.2f C  Fahrenheit: %.2f F\n", cel, Fah);
    return (int)cel;
}

The temperture() function reads the thermistor analog signal via MCP3008, calculates voltage, resistance, then converts to Celsius and Fahrenheit using the thermistor formula (Steinhart–Hart approximation).

int motor(int level)
{
    if (level == 0) {
        digitalWrite(MotorEnable, LOW);
        return 0;
    }
    if (level >= 4) {
        level = 4;
    }
    digitalWrite(MotorEnable, HIGH);
    softPwmWrite(MotorPin1, level * 25);
    return level;
}

This motor() function controls fan speed via PWM. Level ranges from 0–4, where 0 turns the fan off and each level increases the duty cycle by 25%.

void setup()
{
    if (wiringPiSetup() == -1) {
        printf("wiringPi setup failed!\n");
        return;
    }

    if (wiringPiSPISetup(SPI_CHANNEL, SPI_SPEED) == -1) {
        printf("SPI setup failed!\n");
        return;
    }

    softPwmCreate(MotorPin1, 0, 100);
    softPwmCreate(MotorPin2, 0, 100);
    pinMode(MotorEnable, OUTPUT);
    pinMode(BtnPin, INPUT);
}

The setup() function initializes WiringPi, sets up SPI, configures PWM and GPIO pins needed for motor control and button input.

int main(void)
{
    setup();
    int currentState, lastState = 0;
    int level = 0;
    int currentTemp, markTemp = 0;

    while (1) {
        currentState = digitalRead(BtnPin);
        currentTemp = temperture();

        if (currentTemp <= 0) continue;

        if (currentState == 1 && lastState == 0) {
            level = (level + 1) % 5;
            markTemp = currentTemp;
            delay(500);
        }

        lastState = currentState;

        if (level != 0) {
            if (currentTemp - markTemp <= -2) {
                level = level - 1;
                markTemp = currentTemp;
            }
            if (currentTemp - markTemp >= 2) {
                level = level + 1;
                markTemp = currentTemp;
            }
        }

        level = motor(level);
    }

    return 0;
}

The main() function contains the program loop:

  1. Constantly checks button state and reads current temperature.

  2. On button press, fan level increases (cycles 0–4) and saves temperature.

  3. If temperature changes by ±2°C, it auto-adjusts fan speed accordingly.

  4. Calls motor(level) to update PWM output based on current level.

For Python Language Users

Step 2: Set up the SPI interface and install the spidev library (see 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.

cd ~/davinci-kit-for-raspberry-pi/python

Step 4: Run.

sudo python3 3.1.4-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 4th 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 davinci-kit-for-raspberry-pi/python. After modifying the code, you can run it directly to see the effect.

#!/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

  1. 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.

    #!/usr/bin/env python3
    
    import RPi.GPIO as GPIO
    import spidev
    import time
    import math
    
  2. Set up GPIO pins:

    • Button on GPIO22 (with internal pull-up),

    • Motor control using GPIO5 (forward), GPIO6 (backward), and GPIO13 (PWM enable).

    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)
    
  3. Initialize SPI communication to the MCP3008 (Bus 0, Chip Enable 0) at 1 MHz.

    spi = spidev.SpiDev()
    spi.open(0, 0)
    spi.max_speed_hz = 1000000
    
  4. Define read_adc() function to read a 10-bit analog value (0–1023) from the specified MCP3008 channel (0–7).

    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
    
  5. Define temperature() function to:

    • Convert analog voltage to resistance,

    • Apply the Steinhart–Hart equation to compute temperature in Celsius.

    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
    
  6. 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%.

    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
    
  7. Define changeLevel() callback for button press to:

    • Increase the motor level cyclically (0 to 4),

    • Record the current temperature as the new baseline.

    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)
    
  8. 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.

    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)
    
  9. Run the main function and ensure proper cleanup on Ctrl+C (stop motor, cleanup GPIO, close SPI).

    try:
        main()
    except KeyboardInterrupt:
        pass
    finally:
        pwm.stop()
        GPIO.cleanup()
        spi.close()