注釈

こんにちは、SunFounder Raspberry Pi & Arduino & ESP32 愛好家コミュニティ(Facebook)へようこそ!Raspberry Pi、Arduino、ESP32 の世界を仲間と一緒にさらに深めましょう。

参加する理由

  • 専門的なサポート: 購入後の問題や技術的な課題を、コミュニティやチームの助けで解決できます。

  • 学びと共有: ヒントやチュートリアルを交換し、スキルを高められます。

  • 特別な先行公開: 新製品の発表やプレビューにいち早くアクセスできます。

  • 特別割引: 最新製品を会員限定割引で入手できます。

  • 季節のイベントやプレゼント企画: プレゼントや季節ごとのキャンペーンに参加できます。

👉 一緒に探求と創作を始めませんか?今すぐ [ここ] をクリックして参加しましょう!

3.1.4 スマートファン(MCP3008)

注釈

_images/mcp3008_and_adc0834.jpg

お持ちのキットの種類によって、ADC0834 または MCP3008 を確認し、対応するセクションに進んでください。

はじめに

このプロジェクトでは、モーター、ボタン、サーミスタを使って、手動+自動のスマートファンを作ります。風量は調整可能です。

必要な部品

このプロジェクトには以下の部品を使用します。

_images/list2_Smart_Fan.png

回路図

T-Board 名

物理

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

実験手順

手順1: 回路を組み立てます。

_images/july24_3.1.4_smart_fan_mcp3008.png

注釈

電源モジュールにはキット付属の9V電池スナップを使用して9V電池を接続できます。電源モジュールのジャンパーキャップをブレッドボードの5V電源ラインに差し込みます。

_images/image1181.jpeg

C言語ユーザー向け

手順2: コードのフォルダに移動します。

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

手順3: コンパイルします。

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

手順4: 実行ファイルを実行します。

./SmartFan

プログラム実行後、ボタンを押すとファンが動きます。押すたびに風量レベルが1段階ずつ上下し、0~4 の5段階で調整されます。レベル4の状態でさらに押すと風量0になり停止します。

温度が ±2℃ 変化すると、自動的に風量が1段階増減します。

注釈

実行してもうまく動作しない場合や、エラー「wiringPi.h: No such file or directory」が出る場合は、wiringPi のインストールと確認 を参照してください。

コード

#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;
}

この関数は、MCP3008の指定した入力から値を読むものです。3バイトのSPI信号を送り、0~1023の10bitの値を返します。

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;
}

temperture() 関数は、MCP3008を通じてサーミスタの値を読み、電圧・抵抗を計算してから、セルシウスと華氏の温度に変換します(シュタインハルト・ハート近似式)。

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;
}

motor() 関数はPWMで扇風機の回転を制御します。レベル0では停止し、レベル1~4ではそれぞれ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);
}

setup() 関数はWiringPiを初期化し、SPIを設定し、PWMとGPIOを準備します。

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;
}

main() 関数は以下を行います:

  1. 常に釦入力と温度を確認します。

  2. 釦を押すと風量が1段階上がり(0~4を循環)、その時点の温度を記録します。

  3. 温度が ±2℃ 変化したら風量を自動調整します。

  4. motor(level) を呼び出して出力を更新します。

Pythonユーザー向け

手順2: SPIインターフェースを有効化し、 spidev ライブラリをインストールします(詳細は SPI 設定 を参照)。既に完了している場合はスキップ可能です。

手順3: コードのフォルダに移動します。

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

手順4: 実行します。

sudo python3 3.1.4-2_SmartFan.py

プログラム実行後、ボタンを押すとファンが動きます。押すたびに風量レベルが1段階ずつ上下し、 0~4 の5段階で調整されます。レベル4の状態でさらに押すと風量0になり停止します。

温度が ±2℃ 変化すると、自動的に風量が1段階増減します。

コード

注釈

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()

コード解説

  1. 必要な道具を導入:

    • RPi.GPIO … GPIO制御(押し釦と回転機)

    • spidev … MCP3008との通信

    • time … 待機処理

    • math … 対数関数を用いた温度計算

    #!/usr/bin/env python3
    
    import RPi.GPIO as GPIO
    import spidev
    import time
    import math
    
  2. GPIOの端子を設定:

    • 押し釦を GPIO22 に接続(内部プルアップ有効)

    • 回転機の制御に GPIO5(正転), GPIO6(逆転), GPIO13(PWM有効)を使用

    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. MCP3008とのSPI通信を初期化(バス0, チップイネーブル0, 速度1 MHz):

    spi = spidev.SpiDev()
    spi.open(0, 0)
    spi.max_speed_hz = 1000000
    
  4. read_adc() 関数を定義(指定したMCP3008の入力端子から10bit値(0–1023)を読み取る):

    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. temperature() 関数を定義:

    • アナログ電圧を抵抗値に換算

    • シュタインハルト・ハート式を使ってセルシウス温度を計算

    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. motor_run() 関数を定義:

    • レベル0のときは回転機を停止

    • レベル1~4のときは前進回転、PWMデューティ比を25%~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. changeLevel() コールバックを定義(押し釦が押されたとき):

    • レベルを 0~4 の範囲で循環的に増加

    • 現在の温度を基準温度として記録

    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. main() ループを定義:

    • 記録した温度との変化を監視

    • 温度が2℃以上下がればレベルを1段階下げる

    • 温度が2℃以上上がればレベルを1段階上げる

    • 0.2秒ごとに回転機の速度を調整

    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. main() を実行し、Ctrl+Cで停止したときに必ず後始末を行う(回転機停止、GPIO解放、SPI終了):

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