Skip to contentSkip to main navigation Skip to footer

RGB контроллер ленты/матрицы

Задача


Изначально проект собран на печатной плате, подробности на странице проекта. Здесь же разбирается сборка макета на Arduino и брэдборде.
  • Разработать контроллер RGB светодиода/ленты/COB матрицы
  • Управление с энкодера
  • Индикация – 7 сегментный дисплей
  • Режимы работы: выбор цвета и цветовой теплоты + яркость
  • Запоминать настройки в EEPROM
  • Опционально: управление 4-пин вентилятором по датчику температуры (термистор)

Базовые уроки


Подключение


Вариант с тремя транзисторами и RGB лентой или матрицей, а также термистором и выходом на вентилятор:

Второй вариант схемы – без вентилятора, управляется RGB светодиод. Эту схему можно собрать в рамках набора:

Библиотеки


Для дисплея используется непубличная библиотека SevSeg.h, которую нужно разместить рядом со скетчем

SevSeg.h
#ifndef _SevSeg_h
#define _SevSeg_h

#define SS_CATHODE 0
#define SS_ANODE 1
#define SS_PRD 1000

static const uint8_t _digs[] = {0x7e, 0x30, 0x6d, 0x79, 0x33, 0x5b, 0x5f, 0x70, 0x7f, 0x7b};

template < uint8_t _DIG_AM, bool _TYPE = SS_CATHODE>
class SevSeg {
  public:
    SevSeg(const uint8_t* dig, const uint8_t* seg) : _dig(dig), _seg(seg) {
      for (int i = 0; i < _DIG_AM; i++) pinMode(_dig[i], OUTPUT);
      for (int i = 0; i < 7; i++) pinMode(_seg[i], OUTPUT);
    }

    void tick() {
      if (micros() - tmr >= SS_PRD) {
        tmr += SS_PRD;
        digitalWrite(_dig[_count], !_TYPE);
        if (++_count >= _DIG_AM) _count = 0;
        display(_buf[_count]);
        digitalWrite(_dig[_count], _TYPE);
      }
    }

    void setInt(int data) {
      int count = 0;
      bool minus = data < 0;
      data = abs(data);
      do {
        _buf[count] = _digs[data % 10];
        data /= 10;
        count++;
        if (count == _DIG_AM) return;
      } while (data);
      if (minus) _buf[count] = 0x01;
    }

    void clear() {
      for (int i = 0; i < _DIG_AM; i++) _buf[i] = 0;
    }

    void setOneByte(uint8_t pos, uint8_t data) {
      _buf[pos] = data;
    }

  private:
    void display(uint8_t data) {
      if (_TYPE) data = ~data;
      int i = 7;
      while (i--) {
        digitalWrite(_seg[i], data & 1);
        data >>= 1;
      }
    }

    const uint8_t *_dig, *_seg;
    uint8_t _buf[_DIG_AM];
    uint8_t _count = 0;
    uint32_t tmr = 0;
};
#endif

Программа


Полный код проекта есть на GitHub

Первым делом обозначим пины

// ИК, вентилятор, термистор
#define P_IR    2
#define P_FAN   11
#define P_NTC   6

// энкодер
#define P_ENC_A 4
#define P_ENC_B 5
#define P_BTN   6

// светодиод
#define P_LED_R 3
#define P_LED_G 9
#define P_LED_B 10

// сегменты
#define P_SSA   7
#define P_SSB   A0
#define P_SSC   A1
#define P_SSD   A2
#define P_SSE   A3
#define P_SSF   A4
#define P_SSG   A5

// DIGи
#define P_G1    0
#define P_G2    1
#define P_G3    8
#define P_G4    12

Подключаем библиотеки и всё настраиваем

// энкодер
#include <EncButton.h>
EncButton<EB_TICK, P_ENC_A, P_ENC_B, P_BTN> enc(INPUT);

// термистор
#include <GyverNTC.h>
GyverNTC therm(P_NTC, 10000, 3950);

// дисплей
#include "SevSeg.h"
const uint8_t digs[] = {P_G4, P_G3, P_G2, P_G1};
const uint8_t segs[] = {P_SSA, P_SSB, P_SSC, P_SSD, P_SSE, P_SSF, P_SSG};
SevSeg<4, SS_CATHODE> disp(digs, segs);

// светодиод
#include <GRGB.h>
GRGB led(COMMON_CATHODE, P_LED_R, P_LED_G, P_LED_B);

// настройки для eeprom 
struct Settings {
  uint8_t mode = 0;   // 0 hue, 1 kelvin
  uint8_t hue = 0;    // цвет
  uint8_t hueB = 0;   // яркость при цвете
  uint8_t temp = 0;   // температура
  uint8_t tempB = 0;  // яркость при температуре
};
Settings settings;

// менеджер eeprom
#include <EEManager.h>
EEManager memory(settings, 10000);  // 10 сек

В блоке setup() запускаем менеджер EEPROM, настраиваем пины и обновляем дисплей и светодиод:

void setup() {
  pinMode(P_FAN, OUTPUT);

  // запуск епром с адреса 0, ключ 2 (любой 0-255)
  // https://github.com/GyverLibs/EEManager
  memory.begin(0, 2);

  // принудительно обновляем
  updateLED();
  updateDisp();
}

Функции обновления дисплея и светодиода содержат следующий код:

void updateDisp() {
  // буквы http://www.uize.com/examples/seven-segment-display.html
  disp.clear();
  if (!settings.mode) {   // hue
    if (dispMode) {
      disp.setOneByte(3, 0x1f);
      disp.setInt(settings.hueB);
    } else {
      disp.setOneByte(3, 0x17);
      disp.setInt(settings.hue);
    }
  } else {                // temp
    if (dispMode) {
      disp.setOneByte(3, 0x1f);
      disp.setInt(settings.tempB);
    } else {
      disp.setOneByte(3, 0x0f);
      disp.setInt(settings.temp);
    }
  }
}
void updateLED() {
  // setWheel принимает 0-1530, у нас 8 бит - умножаем на 6 (1530 == 255 * 6)
  if (!settings.mode) led.setWheel(settings.hue * 6, settings.hueB);
  else led.setKelvin(settings.temp * 100, settings.tempB);
}

Для схемы с вентилятором у нас также есть опрос датчика и регулирование по простому линейному закону

void coolingTick() {
  // таймер на 1 сек
  static uint32_t tmr;
  if (millis() - tmr >= 1000) {
    tmr = millis();
    static float temp = 25;   // фильтрованная температура
    temp += (therm.getTemp() - temp) * 0.2;   // фильтр
    // линейное регулирование
    int duty = map(int(temp), 30, 45, 10, 255);
    duty = constrain(duty, 10, 255);
    analogWrite(P_FAN, duty);
  }
}

Также понадобится глобальная переменная byte dispMode;, которая задаёт текущий режим отображения и настройки: 0 цвет, 1 яркость

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

void loop() {
  memory.tick();    // менеджер епром
  disp.tick();      // динамо дисплея
  coolingTick();    // регулирование вентилятора

  if (enc.tick()) {   // опрос, если было событие
    // разбор действий энкодера
  }
}

И соответственно разбираем действия с энкодера, в конце обновляем память и дисплей

if (enc.tick()) {   // опрос, если было событие
  // по клику меняем режим вывода
  if (enc.click()) dispMode = !dispMode;

  // по удержанию меняем режим настройки величина/яркость
  if (enc.held()) {
    settings.mode = !settings.mode;
    dispMode = 0;
  }

  // поворот - меняем величину
  if (enc.turn()) {
    int val = enc.getDir();     // направление поворота
    if (enc.fast()) val *= 5;   // быстрый поворот - в 5 раз быстрее

    if (dispMode) {
      if (!settings.mode) settings.hueB += val;
      else settings.tempB += val;
    } else {
      if (!settings.mode) settings.hue += val;
      else settings.temp += val;
    }
  }

  // обновляем
  updateLED();
  updateDisp();
  enc.resetState();   // сбрасываем флаги (очищаем остальные события)
  memory.update();    // откладываем обновление епром
}

Полный код программы можно посмотреть под спойлером:

Полный код
// ИК, вентилятор, термистор
#define P_IR    2
#define P_FAN   11
#define P_NTC   6

// энкодер
#define P_ENC_A 4
#define P_ENC_B 5
#define P_BTN   6

// светодиод
#define P_LED_R 3
#define P_LED_G 9
#define P_LED_B 10

// сегменты
#define P_SSA   7
#define P_SSB   A0
#define P_SSC   A1
#define P_SSD   A2
#define P_SSE   A3
#define P_SSF   A4
#define P_SSG   A5

// DIGи
#define P_G1    0
#define P_G2    1
#define P_G3    8
#define P_G4    12

// энкодер
#include <EncButton.h>
EncButton< EB_TICK, P_ENC_A, P_ENC_B, P_BTN> enc(INPUT);

// термистор
#include <GyverNTC.h>
GyverNTC therm(P_NTC, 10000, 3950);

// дисплей
#include "SevSeg.h"
const uint8_t digs[] = {P_G4, P_G3, P_G2, P_G1};
const uint8_t segs[] = {P_SSA, P_SSB, P_SSC, P_SSD, P_SSE, P_SSF, P_SSG};
SevSeg< 4, SS_CATHODE> disp(digs, segs);

// светодиод
#include <GRGB.h>
GRGB led(COMMON_CATHODE, P_LED_R, P_LED_G, P_LED_B);

// настройки для eeprom 
struct Settings {
  uint8_t mode = 0;   // 0 hue, 1 kelvin
  uint8_t hue = 0;    // цвет
  uint8_t hueB = 0;   // яркость при цвете
  uint8_t temp = 0;   // температура
  uint8_t tempB = 0;  // яркость при температуре
};
Settings settings;

// менеджер eeprom
#include <EEManager.h>
EEManager memory(settings, 10000);  // 10 сек

void setup() {
  pinMode(P_FAN, OUTPUT);

  // запуск епром с адреса 0, ключ 2 (любой 0-255)
  // https://github.com/GyverLibs/EEManager
  memory.begin(0, 2);

  // принудительно обновляем
  updateLED();
  updateDisp();
}

// режим настройки
byte dispMode = 0;  // 0 color, 1 bright

void loop() {
  memory.tick();    // менеджер епром
  disp.tick();      // динамо дисплея
  coolingTick();    // регулирование вентилятора

  if (enc.tick()) {   // опрос, если было событие
    // по клику меняем режим вывода
    if (enc.click()) dispMode = !dispMode;

    // по удержанию меняем режим настройки величина/яркость
    if (enc.held()) {
      settings.mode = !settings.mode;
      dispMode = 0;
    }

    // поворот - меняем величину
    if (enc.turn()) {
      int val = enc.getDir();     // направление поворота
      if (enc.fast()) val *= 5;   // быстрый поворот - в 5 раз быстрее

      if (dispMode) {
        if (!settings.mode) settings.hueB += val;
        else settings.tempB += val;
      } else {
        if (!settings.mode) settings.hue += val;
        else settings.temp += val;
      }
    }

    // обновляем
    updateLED();
    updateDisp();
    enc.resetState();   // сбрасываем флаги (очищаем остальные события)
    memory.update();    // откладываем обновление епром
  }
}

void coolingTick() {
  // таймер на 1 сек
  static uint32_t tmr;
  if (millis() - tmr >= 1000) {
    tmr = millis();
    static float temp = 25;   // фильтрованная температура
    temp += (therm.getTemp() - temp) * 0.2;   // фильтр
    // линейное регулирование
    int duty = map(int(temp), 30, 45, 10, 255);
    duty = constrain(duty, 10, 255);
    analogWrite(P_FAN, duty);
  }
}

void updateDisp() {
  // буквы http://www.uize.com/examples/seven-segment-display.html
  disp.clear();
  if (!settings.mode) {   // hue
    if (dispMode) {
      disp.setOneByte(3, 0x1f);
      disp.setInt(settings.hueB);
    } else {
      disp.setOneByte(3, 0x17);
      disp.setInt(settings.hue);
    }
  } else {                // temp
    if (dispMode) {
      disp.setOneByte(3, 0x1f);
      disp.setInt(settings.tempB);
    } else {
      disp.setOneByte(3, 0x0f);
      disp.setInt(settings.temp);
    }
  }
}

void updateLED() {
  // setWheel принимает 0-1530, у нас 8 бит - умножаем на 6 (1530 == 255 * 6)
  if (!settings.mode) led.setWheel(settings.hue * 6, settings.hueB);
  else led.setKelvin(settings.temp * 100, settings.tempB);
}

Возможные доработки


  • Использовать 7 сегментный дисплей со встроенным контроллером TM1637 (есть в наборе)

Видео


Полезный пример?

0 Комментариев

Нет комментариев

Оставить комментарий

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