多数のLEDを制御する際にNeoPixel(WS2812B等)はスタンダードになりつつあります。
互換品はもう大量に流通していますし。リモコンで制御できるやつも売ってますよね。
フィールドネットワークでもLEDを制御したいなと思ってAliExpressを漁ったのですが1つも見つかりませんでした。
RS485でいいのですが売ってません。
照明器具を制御するDMX512(物理層はRS485)用のコントローラは見つかりましたが5,000円くらいしていました。
「さすがに高すぎる」という経緯で自作に踏み切りました。
スポンサーリンク
といっても回路は超シンプルです。
RS485のインターフェースとマイコンを用意してUARTとNeoPixelを制御するIOのを引き出すだけです。
電源はフィールドネットワーク用に対応するためにDC24Vですが、特殊なのはそこだけです。

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;
}
}
}
スポンサーリンク
Leave a Comment