ШИМ управление активным охлаждением на Raspberry Pi, Orange Pi, Banana Pi

Во многих случаях мини-компьютеры, такие как: Raspberry Pi, Orange Pi, Banana Pi и т.п., поставляется с небольшим вентилятором на 5 В, чтобы можно было охлаждать процессор (вернее — СнК/SoC) компьютера. Тем не менее, эти вентиляторы обычно довольно шумные, и многие подключают его к выводу на 3.3 В, чтобы уменьшить шум. Вентиляторы обычно рассчитаны на 200 мА, что довольно много для регулятора на 3.3 В на Raspberry Pi.

Этот проект научит вас, как регулировать скорость вращения вентилятора в зависимости от температуры процессора. В отличие от большинства руководств, охватывающих эту тему, мы не только включим или выключим вентилятор, но и будем контролировать его скорость с помощью ШИМ, как это делается на обычном ПК.

Что такое ШИМ?

Широтно-импульсная модуляция (ШИМ, англ. pulse-width modulation (PWM)) — процесс управления мощности методом пульсирующёго включения и выключения прибора. Различают аналоговую ШИМ и цифровую ШИМ, двоичную (двухуровневую) ШИМ и троичную (трёхуровневую) ШИМ. Основной причиной применения ШИМ является стремление к повышению КПД при построении вторичных источников питания электронной аппаратуры и в других узлах, например, ШИМ используется для регулировки яркости подсветки LCD-мониторов и дисплеев в телефонах, КПК и т.п..

Управление вентилятором с помощью биполярного NPN-транзистора

Первое что приходит на ум — поставить биполярный NPN-транзистор. Вентилятору требуется 200мА, поэтому ищем транзистор с коллекторным током более 200мА, лучше раза в 2-3. В импортных даташитах этот параметр называется Ic, в наших Iк. Подойдут транзисторы: 2N5550, 2N5551, 2N2222A и т.д.. У транзистора, в первую очередь, надо определить назначение выводов. Где у него коллектор, где база, а где эмиттер. Сделать это лучше всего по даташиту или справочнику.

Схема подключения вентилятора

Берем транзистор и подключаем его по такой схеме:

 

Схема подключения вентилятора - Принципиальная схема (Orange Pi One + 2N2222 + 1N4001)

Таким образом, при подаче «1» на вход нашей схемы ток от источника питания потечёт через резистор R1, базу и эмиттер на землю. При этом транзистор откроется и ток сможет идти через переход коллектор-эмиттер, а значит и через нагрузку (вентилятор).

Резистор R1 играет важную роль — он ограничивает ток через переход база-эмиттер. Если бы его не было, ток не был бы ничем ограничен и просто испортил бы управляющую микросхему (ведь именно она связывает линию питания с транзистором).

Кроме того, нужно помнить, что нагрузка индуктивная и нужен защитный диод D1. Дело в том, что энергия, запасённая магнитным полем, не даёт мгновенно уменьшить ток до нуля при отключении ключа. А значит, на контактах нагрузки возникнет напряжение обратной полярности, которое легко может нарушить работу контроллера или даже повредить его.

Схема подключения вентилятора (Orange Pi One + 2N2222 + 1N4001)

Управление вентилятором с помощью МОП транзистора

Вместо биполярного можно использовать полевой транзистор — MOSFET, то есть полевые транзисторы с изолированным затвором (они же МОП, они же МДП). Они удобны тем, что управляются исключительно напряжением: если напряжение на затворе больше порогового, то транзистор открывается. При этом управляющий ток через транзистор пока он открыт или закрыт не течёт. Это значительное преимущество перед биполярными транзисторами, у которых ток течёт всё время, пока открыт транзистор.

В дальнейшем мы будем использовать только n-канальные MOSFET. Это связано с тем, что n-канальные транзисторы дешевле, имеют лучшие характеристики и для управления N-канальным полевиком необходимо приложить положительное напряжение на затвор относительно истока.

Схема подключения вентилятора

ШИМ управление - Принципиальная схема (Orange Pi One + ZVN4306A + 1N4001)

Нагрузка подключена к стоку («сверху»). Если подключить её «снизу», то схема работать не будет. Дело в том, что транзистор открывается, если напряжение между затвором и истоком превышает пороговое. При подключении «снизу» нагрузка будет давать дополнительное падение напряжения, и транзистор может не открыться или открыться не полностью.

Резистор R1 на 100 Ом ограничивает ток заряда-разряда, а резистор R2 на 10 кОм — это стягивающий резистор, что в неопределенном состоянии «стягивает» потенциал к земле.

Кроме того, нужно помнить, что нагрузка индуктивная и нужен защитный диод D1.

ШИМ управление - Схема подключения вентилятора (Orange Pi One + ZVN4306A + 1N4001)

N-канальные MOSFET с логическим уровнем управления

Один из минусов МОП транзисторов — это высокое пороговое напряжение затвора, больше 3.3 В. Тем не менее, существуют N-канальные транзисторы с логическим уровнем управления, например: IRL2505, FDN337N, ZVN4306A, 2N7000, PMV16XNR, NTZD3155C, IRLZ24NPBF, IRL520NPBF и т.п.

Как получить температуру процессора

Существует несколько способов получить текущую температуру процессора.

Если вы используйте Armbian, вы можете использовать команду:

armbianmonitor -m

эта команда будет давать вам время работы, частоту и текущую температуру процессора каждые 6 секунд.

Есть еще одна команда, которая просто возвращает температуру процессора:

cat /sys/devices/virtual/thermal/thermal_zone1/temp

или

cat /sys/devices/virtual/thermal/thermal_zone0/temp

или

cat /sys/class/thermal/thermal_zone0/temp

Внутри SoC Allwinner есть два датчика, вы можете взять оба значения, чтобы получить среднюю температуру. На Raspberry Pi температура измеряется в миллиградусах, для перевода в градусы Цельсия нужно разделить полученное число на 1000:

В Raspberry Pi можно узнать температуру процессора при помощи ввода консольной команды:

vcgencmd measure_temp

Включение вентилятора при заданной температуре

Ниже приведённые примеры используют WiringPi и, если библиотека у вас не установлена, установите её, инструкции тут: WiringPiWiringOP или BPI-WiringPi.

Самый простой пример управления вентилятором — это его включение, при достижении критичной температуры, и выключение, если температура ниже. Если шум вентилятора не мешает, тогда можно использовать такой вариант.

#include <iostream>
#include <fstream>
#include <wiringPi.h>
#include <unistd.h>

#define PIN                  7

#define TEMPERATURE_MIN      55

using namespace std;

static int getTemperature() {
  static fstream myfile;
  int temperature = 0;
  myfile.open("/sys/devices/virtual/thermal/thermal_zone0/temp", ios_base::in);
  myfile >> temperature;
  myfile.close();
  return temperature;
}

int main() {
  int temperature;
  int pinState = 0;
  try {
    if (wiringPiSetup() == 0) {
      pinMode(PIN, OUTPUT);

      while (1) {
        temperature = getTemperature();

        if (temperature >= TEMPERATURE_MIN && pinState == 0) {
          digitalWrite(PIN, HIGH);
          pinState = 1;
        } else if (temperature < (TEMPERATURE_MIN - 10) && pinState == 1) {
          digitalWrite(PIN, LOW);
          pinState = 0;
        }

        usleep(1000 * 1000);
      }
    }
  } catch (exception& e) {
    cerr << e.what() << endl;
  }
  return 0;
}

Программа работает следующим образом — при достижении заданной температуры (более 55°С) вентилятор включается, и отключается только когда температура снизится более чем на 10°С от максимального порога (менее 45°С).

Температуру получаем с помощью функции static int getTemperature(). На Raspberry Pi температура измеряется в миллиградусах, для перевода в градусы Цельсия нужно разделить полученное число на 1000 и необходимо будет внести несколько изменений,

вместо:

static int getTemperature() {
  static fstream myfile;
  int temperature = 0;
  myfile.open("/sys/devices/virtual/thermal/thermal_zone0/temp", ios_base::in);
  myfile >> temperature;
  myfile.close();
  return temperature;
}

использовать:

static int getTemperature() {
  static fstream myfile;
  int temperature = 0;
  myfile.open("/sys/devices/virtual/thermal/thermal_zone0/temp", ios_base::in);
  myfile >> temperature;
  myfile.close();
  return temperature / 1000;
}

Компиляция, сборка и запуск программы

Сознаём файл FanPiOnOff.cpp и вставляем вышеприведённый код:

nano FanPiOnOff.cpp

Компилируем и собираем программу:

g++ -Ofast -Wall FanPiOnOff.cpp -lwiringPi -lpthread -o FanPiOnOff

Запускаем:

./FanPiOnOff

если хотим запустить программу в фоновом режиме:

nohup ./FanPiOnOff &

ШИМ управление: Пример 1

Логика программы такова — при достижении заданных температур вентилятор включается с определённым коэффициентом заполнения, а отключается только тогда, когда температура ниже минимального порога:

  • 45 °C -> 35 %
  • 50 °C -> 50 %
  • 60 °C -> 75 %
  • 75 °C -> 100 %
#include <iostream>
#include <fstream>
#include <wiringPi.h>
#include <softPwm.h>
#include <unistd.h>

#define PIN               7
#define RANGE             100

#define PWM_VALUE1         35
#define PWM_VALUE2         50
#define PWM_VALUE3         75
#define PWM_VALUE4         100

#define TEMPERATURE_1      45
#define TEMPERATURE_2      50
#define TEMPERATURE_3      60
#define TEMPERATURE_4      70

using namespace std;

static int getTemperature() {
  static fstream myfile;
  int temperature = 0;
  myfile.open("/sys/devices/virtual/thermal/thermal_zone0/temp", ios_base::in);
  myfile >> temperature;
  myfile.close();
  return temperature;
}

int main() {
  int temperature;
  bool pwmStopped = true;

  try {
    if (wiringPiSetup() == 0) {
      while (1) {
        temperature = getTemperature();

        if (temperature > TEMPERATURE_4) {
          if (pwmStopped) {
            softPwmCreate(PIN, ((PWM_VALUE4 * RANGE) / 100), RANGE);
            pwmStopped = false;
          } else {
            softPwmWrite(PIN, ((PWM_VALUE4 * RANGE) / 100));
          }
        } else if (temperature > TEMPERATURE_3) {
          if (pwmStopped) {
            softPwmCreate(PIN, ((PWM_VALUE3 * RANGE) / 100), RANGE);
            pwmStopped = false;
          } else {
            softPwmWrite(PIN, ((PWM_VALUE3 * RANGE) / 100));
          }
        } else if (temperature > TEMPERATURE_2) {
          if (pwmStopped) {
            softPwmCreate(PIN, ((PWM_VALUE2 * RANGE) / 100), RANGE);
            pwmStopped = false;
          } else {
            softPwmWrite(PIN, ((PWM_VALUE2 * RANGE) / 100));
          }
        } else if (temperature > TEMPERATURE_1) {
          if (pwmStopped) {
            softPwmCreate(PIN, ((PWM_VALUE1 * RANGE) / 100), RANGE);
            pwmStopped = false;
          } else {
            softPwmWrite(PIN, ((PWM_VALUE1 * RANGE) / 100));
          }
        } else {
          softPwmStop(PIN);
          pwmStopped = true;
        }

        usleep(1000 * 1000);
      }
    }
  } catch (exception& e) {
    cerr << e.what() << endl;
  }
  return 0;
}

Компиляция, сборка и запуск программы

Сознаём файл FanPiPWM.cpp и вставляем вышеприведённый код:

nano FanPiPWM.cpp

Компилируем и собираем программу:

g++ -Ofast -Wall FanPiPWM.cpp -lwiringPi -lpthread -o FanPiPWM

Запускаем:

./FanPiPWM

если хотим запустить программу в фоновом режиме:

nohup ./FanPiPWM &

ШИМ управление: Пример 2

Также можно менять коэффициент заполнения линейно. В таком случае программе нужно задать минимальную температуру срабатывания и минимальный коэффициент заполнения, к примеру 45°C и 35% соответственно, а на максимальную температуру (к примеру 70°C) — коэффициент в 100%. Рассчитывается коэффициент заполнения с помощью функции static int map(int x, int inMin, int inMax, int outMin, int outMax);.

#include <iostream>
#include <fstream>
#include <wiringPi.h>
#include <softPwm.h>
#include <unistd.h>

#define PIN        7U

#define RANGE_MAX           100
#define RANGE_MIN            35

#define TEMPERATURE_MAX      70
#define TEMPERATURE_MIN      45

using namespace std;

static int getTemperature() {
  static fstream myfile;
  int temperature = 0;
  myfile.open("/sys/devices/virtual/thermal/thermal_zone0/temp", ios_base::in);
  myfile >> temperature;
  myfile.close();
  return temperature;
}

static int map(int x, int inMin, int inMax, int outMin, int outMax) {
  if (x < inMin) {
    return outMin;
  } else if (x > inMax) {
    return outMax;
  }
  return (x - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
}

int main() {
  int temperature;
  int pwmValue;
  bool pwmStopped = true;

  try {
    if (wiringPiSetup() == 0) {
      while (1) {
        temperature = getTemperature();
        pwmValue = map(temperature, TEMPERATURE_MIN, TEMPERATURE_MAX, RANGE_MIN, RANGE_MAX);

        if (temperature >= TEMPERATURE_MIN && pwmStopped) {
          softPwmCreate(PIN, pwmValue, RANGE_MAX);
          pwmStopped = false;
        } else if (temperature >= (TEMPERATURE_MIN - 5) && !pwmStopped) {
          softPwmWrite(PIN, pwmValue);
        } else {
          if (!pwmStopped) {
            softPwmStop(PIN);
          }
          pwmStopped = true;
        }

        usleep(1000 * 1000);
      }
    }
  } catch (exception& e) {
    cerr << e.what() << endl;
  }
  return 0;
}

Компиляция, сборка и запуск программы

Сознаём файл FanPiPwmLinear.cpp и вставляем вышеприведённый код:

nano FanPiPwmLinear.cpp

Компилируем и собираем программу:

g++ -Ofast -Wall FanPiPwmLinear.cpp -lwiringPi -lpthread -o FanPiPwmLinear

Запускаем:

./FanPiPwmLinear

если хотим запустить программу в фоновом режиме:

nohup ./FanPiPwmLinear &

Как поместить скрипт в автозагрузку

Остается сделать так, чтобы скрипт, контролирующий работу вентилятора охлаждения, автоматически запускался каждый раз при загрузке системы.

Для этого, в конец файла /etc/rc.local:

sudo nano /etc/rc.local

Нужно поместить команду запуска скрипта перед строкой exit 0:

sudo ./home/pi/FanPiPwmLinear &

После перезагрузки скрипт будет автоматически запущен и вентилятор будет включаться при наступлении заданных условий.

Чтобы проверить факт запуска, после перезагрузки, нужно переключиться в терминал и проверить наличие процесса:

ps aux | grep -i FanPiPwmLinear

Как проверить?

Для проверки работоспособности скрипта нужно «разогреть» процессор до нужной температуры и посмотреть, как на это будет реагировать система охлаждения. «Нагревать» процессор можно с помощью утилит sysbench или stress. Вы можете установить эти утилиты с aptitude:

sudo apt-get install sysbench

или

sudo apt-get install stress

Запуск утилиты с задействованием 4 ядер:

sysbench --num-threads=4 --test=cpu --cpu-max-prime=20000 --validate run

или

sudo stress --cpu 4 --timeout 30s

Температура процессора сразу начнет повышаться.

Принудительно завершить, как выполнение теста утилитой sysbench, так и stress можно сочетанием клавиш Ctrl+C.

Удаление скрипта из автозагрузки

Для удаления скрипта из автозагрузки нужно просто удалить строку с запуском скрипта из файла /etc/rc.local.

Материалы

Управление мощной нагрузкой постоянного тока. Часть 1
Управление мощной нагрузкой постоянного тока. Часть 3.
Raspberry Pi 3: GPIO (#3) — умное управление собственным активным охлаждением
Управление мощной нагрузкой
Широтно-импульсная модуляция

Похожие записи

Комментарии 9

  • Здравствуйте.
    У меня BPI M2Ultra с чипом A40i. Установил Armbian последний с оф.сайта.
    К сожалению температура процессора не доступна, ни одно приложение не находит сенсоров. Возможно потому, что A40i начали недавно устанавливать в эту плату.
    Есть желание подключить DS18B20 и используя ваши примеры, организовать мониторинг и smart-охлаждение.

  • Есть ли возможность заменить транзистор и резисторы одним транзистором типа MOSFET K3919?

  • Здравствуйте! Спасибо за статью! Управление вентилятором работает (с помощью биполярного NPN-транзистора) но вот столкнулся я такими нюансами.

    1) Если использовать простой пример управления, то при понижении температуры «TEMPERATURE_MIN» на 1 градус (а мож и меньше), то вентилятор сразу включается, т.е. получается что идёт включение / выключение чуть ли не каждую секунду. Я программист ещё тот, так знаю как «Hello World» вывести, попробовал исправить ситуацию через do … while..

    int main() {
    int temperature;
    int pinState = 0;
    try {
    if (wiringPiSetup() == 0) {
    pinMode(PIN, OUTPUT);

    while (1) {
    temperature = getTemperature();

    if (temperature >= TEMPERATURE_MIN && pinState == 0) {
    digitalWrite(PIN, HIGH);

    do{
    // digitalWrite(PIN, HIGH);
    pinState = 1;
    temperature = getTemperature();
    usleep(1000 * 5000);
    }
    while (temperature > TEMPERATURE_END);
    }
    else if (temperature < TEMPERATURE_END && pinState == 1) {
    digitalWrite(PIN, LOW);
    pinState = 0;
    }
    usleep(1000 * 5000);
    }
    }
    } catch (exception& e) {
    cerr << e.what() << endl;
    }
    return 0;
    }

    2) С управлением ШИМ непонятности — при понижении коэффициента вентилятор начинает жутко звучать, ещё громче чем при полных оборотах. Это так и должно быть? Или какой-то спецвентилятор надо?

  • просто поадавать ШИМ сигнал на вентилятор — плохая идея. Учитывая то что многие вентиляторы в наше время чуть сложнее устроены, чем обычный мотор с катушками.

    Кроме защитного диода, нужно ставить еще и конденсатор:
    — даст заряд на тот момент когда транзистор находиться в отключенном состоянии
    — снизит уровень излучаемых пульсаций (ну хоть немного)

    Огромным минусом вашего подхода заключается то, что для пуска вентилятора на низзких оборотах нужен больший ток чем вы обеспечиваете шимом. В итоге будет ситуация, когда вентиятор уже вроде как бы и должен крутиться на малых оборотах, а по факту просто подергивается а запуститься не может. Решить это можно программно ()сделать режим пуска с дальнейшим понижением оборотов к заданному), но без обратной связи от вентилятра — сложно.

    есть альтернативный способ управления вентилятором — это использовать какой нибудь линейный регулятор типа LM317 и управлять его выходным напряжением меня потенциал на ноге управления. Плюсы:
    — нет огромной шумовой помехи
    — не надо задумываться о пуске
    Минус — больше рассеиваемая мощность на линейном регуляторе на малых оборотах.

  • Можно ли как-то использовать аппаратный ШИМ? У чипа H3 он вроде бы есть.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *