ModbusでNeoPixelを制御するだけのやつ

No Image

多数のLEDを制御する際にNeoPixel(WS2812B等)はスタンダードになりつつあります。
互換品はもう大量に流通していますし。リモコンで制御できるやつも売ってますよね。

フィールドネットワークでもLEDを制御したいなと思ってAliExpressを漁ったのですが1つも見つかりませんでした。
RS485でいいのですが売ってません。
照明器具を制御するDMX512(物理層はRS485)用のコントローラは見つかりましたが5,000円くらいしていました。
「さすがに高すぎる」という経緯で自作に踏み切りました。

スポンサーリンク

といっても回路は超シンプルです。
RS485のインターフェースとマイコンを用意してUARTとNeoPixelを制御するIOのを引き出すだけです。
電源はフィールドネットワーク用に対応するためにDC24Vですが、特殊なのはそこだけです。

簡単RS485-NeoPixel
RS485の保護は最小限ですが、コンパクトにするために仕方ないのです。
終端抵抗も抵抗の実装の有無で切り替えです。

マイコンはATtiny1616(megaTinyCore)で、メモリも不足せずいい感じです。
LEDの電流は1.5Aくらいまで流せるようにしました。

プログラムはあまり考えず、今回はAIで試行錯誤してもらいました。
Modbusの保持レジスタにデータを入れて、それをもとにLEDを制御するだけですけどね。
LEDのエフェクトはいくらでも考えられそうです。
EEPROMにLEDの数やModbusのアドレスを保存しています。

プログラムを一応張っておきます。
ArduinoだとAIも結構ちゃんと書いてくれますよ、ライブラリが入ってると時々怪しいことを出力しますが。
そういうときはある程度のベースを書いてあげると精度が上がる気がします。

#include <megaTinyCore.h>
#include <ModbusRTUSlave.h>
#include <tinyNeoPixel.h>
#include <EEPROM.h>

/*  エフェクト番号
    0	常灯(普通に光る)
    1	点滅
    2	フェード(ふわっと明滅)
    3 ランナー
    4	レインボー(HSVで七色に)
*/

// Modbus機能設定
#define MODBUS_BAUD    19200
#define MODBUS_CONFIG  SERIAL_8N1

#define EEPROM_ADDR_SLAVE_ID   0
#define EEPROM_ADDR_PIXEL_CNT  1

// ピン設定
const int16_t ledPin = 5;
const int16_t dePin = 10;

ModbusRTUSlave modbus(Serial, dePin);
tinyNeoPixel* pixels; // 後から初期化

// Modbus用変数
byte slave_id = 1;
const uint8_t numCoils = 1;
bool coils[numCoils];
const byte numHoldingRegisters = 10;
uint16_t holdingRegisters[numHoldingRegisters];

byte pixel_count = 16;
byte pixels_static[48];

// エフェクト用変数
unsigned long last_effect_ms = 0;
byte effect_step = 0;
bool blink_state = false;


void setup() {

  // EEPROM から Slave ID 読み込み
  byte saved_id = EEPROM.read(EEPROM_ADDR_SLAVE_ID);
  if (saved_id >= 1 && saved_id <= 247) {
    slave_id = saved_id;
  } else {
    slave_id = 1;  // 破損時のデフォルト
  }

  // EEPROM から LED個数 読み込み
  byte saved_count = EEPROM.read(EEPROM_ADDR_PIXEL_CNT);
  if (saved_count >= 1 && saved_count <= 60) {
    pixel_count = saved_count;
  } else {
    pixel_count = 16;
  }

  modbus.configureCoils(coils, numCoils);
  modbus.configureHoldingRegisters(holdingRegisters, numHoldingRegisters);

  Serial.begin(MODBUS_BAUD, MODBUS_CONFIG);
  modbus.begin(slave_id, MODBUS_BAUD, MODBUS_CONFIG);

  // 初期値
  coils[0] = true;
  holdingRegisters[0] = 0;        // Red
  holdingRegisters[1] = 0;        // Green
  holdingRegisters[2] = 0;        // Blue
  holdingRegisters[3] = 255;      // 輝度
  holdingRegisters[4] = 0;        // エフェクト番号
  holdingRegisters[5] = 500;      // エフェクト速度 ms
  holdingRegisters[8] = pixel_count; // LED個数
  holdingRegisters[9] = slave_id; // 現在のアドレス表示

  // NeoPixel 初期化 と 起動時エフェクト
  pixels = new tinyNeoPixel(pixel_count, ledPin, NEO_GRB + NEO_KHZ800);
  pixels->begin();

  for (byte i = 0; i < 3; i++) {
    for (byte j = 0; j < pixel_count; j++) {
      pixels->setPixelColor(j, pixels->Color(10, 10, 10));
    }
    pixels->show();
    delay(100);
    pixels->clear();
    pixels->show();
    delay(100);
  }
}

void loop() {
  // 監視スレッド
  modbus.poll();

  // RGB・輝度
  byte red   = holdingRegisters[0] & 0xFF;
  byte green = holdingRegisters[1] & 0xFF;
  byte blue  = holdingRegisters[2] & 0xFF;
  byte brightness = holdingRegisters[3] & 0xFF;

  // エフェクト
  byte effect = holdingRegisters[4] & 0xFF;
  uint16_t speed_ms = holdingRegisters[5];

   // LED電源がOFFのときはエフェクトの代わりに全消灯
  if (!coils[0]) {
    pixels->clear();
    pixels->show();
  } else {
    applyEffect(effect, red, green, blue, brightness, speed_ms);
  }

  // LED数変更要求確認
  byte new_pixels = holdingRegisters[8] & 0xFF;
  if (new_pixels != pixel_count && new_pixels >= 1 && new_pixels <= 255) {
    EEPROM.write(EEPROM_ADDR_PIXEL_CNT, new_pixels);
    delay(200);
    ResetViaSoftware(); // 再起動
  }

   // スレーブアドレス変更要求確認
  byte new_id = holdingRegisters[9] & 0xFF;
  if (new_id != slave_id && new_id >= 1 && new_id <= 247) {
    EEPROM.write(EEPROM_ADDR_SLAVE_ID, new_id);
    delay(200);
    ResetViaSoftware(); // 再起動
  }

  delay(1);
}


void applyEffect(byte mode, byte r, byte g, byte b, byte brightness, uint16_t speed_ms) {

  unsigned long now = millis();

  switch (mode) {

    // 0 = 常灯
    case 0: {
      pixels->setBrightness(brightness);
      for (byte i = 0; i < pixel_count; i++) {
        pixels->setPixelColor(i, pixels->Color(r, g, b));
      }
      pixels->show();
      break;
    }

    // 1 = 点滅
    case 1: {
      if (now - last_effect_ms >= speed_ms) {
        last_effect_ms = now;
        effect_step++;
      }
      pixels->setBrightness(brightness);
      if (effect_step % 2 == 0) {
        for (byte i = 0; i < pixel_count; i++) {
          pixels->setPixelColor(i, pixels->Color(r, g, b));
        }
      } else {
        pixels->clear();
      }
      pixels->show();
      break;
    }

    // 2 = フェード (明るさ 0〜brightness)
    case 2:
      if (now - last_effect_ms >= speed_ms) {
        last_effect_ms = now;
        effect_step++;
      }
      {
        byte fade = (sin(effect_step * 0.15) + 1.0) * (brightness / 2);
        pixels->setBrightness(fade);
        for (byte i = 0; i < pixel_count; i++) {
          pixels->setPixelColor(i, pixels->Color(r, g, b));
        }
        pixels->show();
      }
      break;

    // 3 = 走るLED(ランナー)
    case 3: {
      if (now - last_effect_ms >= speed_ms) {
        last_effect_ms = now;
        effect_step++;
        if (effect_step >= pixel_count) effect_step = 0;
      }

      pixels->clear();

      const byte tail_len = 4;      // テールの長さ
      const byte runner_count = 3;  // ランナーの数
      pixels->setBrightness(brightness);

      for (byte r_idx = 0; r_idx < runner_count; r_idx++) {
        // ランナーごとの開始位置を固定しておく
        int runner_pos = (r_idx * pixel_count / runner_count + effect_step) % pixel_count;

        for (int i = 0; i < tail_len; i++) {
          int pos = runner_pos - i;
          if (pos < 0) pos += pixel_count;

          byte fade = map(i, 0, tail_len - 1, 255, 20);
          byte rr = (r * fade) / 255;
          byte gg = (g * fade) / 255;
          byte bb = (b * fade) / 255;

          pixels->setPixelColor(pos, pixels->Color(rr, gg, bb));
        }
      }

      pixels->show();
      break;
    }

    // 4 = レインボー
    case 4: {
      if (now - last_effect_ms >= speed_ms) {
        last_effect_ms = now;
        effect_step++;
      }
      for (byte i = 0; i < pixel_count; i++) {
        byte hue = i * 16 + effect_step;
        pixels->setPixelColor(i, pixels->ColorHSV(hue * 256));
      }
      pixels->setBrightness(brightness);
      pixels->show();
      break;
    }

    // 該当なし:常灯と同じ
    default: {
      pixels->setBrightness(brightness);
      for (byte i = 0; i < pixel_count; i++) {
        pixels->setPixelColor(i, pixels->Color(r, g, b));
      }
      pixels->show();
      break;
    }
  }
}

 

ModbusでNeoPixelを制御するだけのやつ

スポンサーリンク

Leave a Comment