This project is a simple example of how to control a DMX light fixture using an ESP32 over bluetooth. The ESP32 will receive the DMX data over bluetooth and send it to the DMX light fixture using the RS485 module.
Introduction
I aimed to control a DMX light fixture using serial communication and React Native for a project. After a few days of research, I found out that serial communication is not ideal for React Native, so I decided to use an ESP32 to connect through Bluetooth and let the ESP32 handle the serial communication to the DMX light fixture.
The RS485 module is needed because the DMX data is transmitted using the EIA-485 protocol also known as RS-485, and the ESP32 doesn't have a RS-485 interface.
The light fixtures used in this example are four LED PARty TCL Spots from Eurolite. Below is a DMX chart illustrating the DMX channels assigned to each light fixture and their corresponding functions.
Each channel controls a specific feature of the light fixture, such as color, intensity, or movement, allowing precise adjustments for dynamic lighting effects.
ESP32
The ESP32 is a low-cost, low-power microcontroller with integrated Wi-Fi and dual-mode Bluetooth. It is a great choice for IoT projects because it has a lot of features and is easy to program. The ESP32 is programmed using the Arduino IDE, a user-friendly platform specifically designed for microcontroller projects. Its extensive library support and simple interface make it ideal for IoT applications, enabling developers to prototype and deploy projects efficiently.
esp_dmx
In this project, I use the esp_dmx library to send DMX data to the light fixture. The esp_dmx library is a simple library that allows you to send DMX data using the RS-485 protocol. The library is easy to use and has a lot of examples that show you how to use it.
Hardware
To build this project, you will need the following hardware:
- 1x an ESP32 development board (I used the ESP32 WROOM-32D) This microcontroller serves as the brain of the project, enabling Bluetooth communication and DMX signal generation.
- 1x Blue LED Used as a status indicator to show the Bluetooth connection status or data activity.
- 1x 220 Ohm Resistor Limits current to the Blue LED, preventing damage.
- 1x RS485 Module Converts the ESP32's UART signals to the DMX512 protocol, essential for controlling DMX fixtures.
- 1x Female XLR/DMX Connector Interfaces with the DMX-compatible light fixture via a standard DMX cable. -** 1x USB-C cable** Provides power to the ESP32 and allows for programming.
- Some jumper wires
- DMX compatible light fixture The output device that responds to DMX signals sent by the ESP32.
- XLR/DMX cable To connect the ESP32 to the light fixture
Software
To build this project, you will need the arduino IDE. When you have the Arduino IDE installed, you will first need to install the ESP32 core package. Open the BOARDS MANAGER from the menu on the left and search for ESP32. Install the ESP32 by Espressif Systems package.
I've had some compatibility issues with the esp_dmx library and the ESP32, so I had to downgrade the ESP32 core package to version 2.0.17.
Do the same if you encounter the following error:
esp-dmx_ble.ino:92:52: error: conversion from 'String' to non-scalar type 'std::string' {aka 'std::__cxx11::basic_string<char>'} requested
92 | std::string rxValue = pCharacteristic->getValue();
| ~~~~~~~~~~~~~~~~~~~~~~~~~^~
exit status 1
Compilation error: conversion from 'String' to non-scalar type 'std::string' {aka 'std::__cxx11::basic_string<char>'} requested
After installing the board, plug in your ESP and select the board and port in the Arduino IDE.
To install the esp_dmx library, go to the LIBRARY MANAGER and search for esp_dmx. Install the esp_dmx library by Mitch Weisbrod.
Code
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include "esp_dmx.h"
First, we include the necessary libraries. For the bluetooth part, we use the BLEDevice, BLEUtils, and BLEServer libraries. For the DMX part, we use the esp_dmx library.
// BLE Configuration
#define LED_PIN 2
#define SERVICE_UUID "afe16d0c-ce27-4ffb-8943-5c3228cffabb"
#define CHARACTERISTIC_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
// DMX Configuration
const dmx_port_t dmx_num = DMX_NUM_1;
const int TX_PIN = 16; // DI pin
const int RX_PIN = -1; // Not used since we're only transmitting
const int EN_PIN = 21; // DE & RE pins
Next, we set up the configuration for the bluetooth and DMX parts. The LED_PIN is the pin that we use to control the blue LED, this will be used to indicate the status of the bluetooth connection. The SERVICE_UUID and CHARACTERISTIC_UUID are the UUIDs that we use to identify the bluetooth service and characteristic.
The dmx_num is the DMX port that we use, in this case, we use DMX_NUM_1.
TX_PIN: The pin that we use to transmit the DMX data.
RX_PIN: The pin that we use to receive the DMX data, in this case, we don't use it since we're only transmitting.
EN_PIN: - The pin that we use to enable the the Receiver (RE) and Driver (DE) pins of the RS485 module.
// BLE variables
BLEServer *pServer = NULL;
BLEService *pService = NULL;
BLECharacteristic *pCharacteristic = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;
We declare the BLE variables that we use to create the bluetooth service and characteristic. We also declare the deviceConnected and oldDeviceConnected variables that we use to keep track of the bluetooth connection status.
// DMX variables
uint8_t dmxData[DMX_PACKET_SIZE] = {0};
const unsigned long DMX_TRANSMIT_INTERVAL = 25; // 40Hz refresh rate
unsigned long lastDmxTransmit = 0;
dmxData: The array that we use to store the DMX data that we receive over bluetooth.
DMX_TRANSMIT_INTERVAL: The interval at which we send the DMX data to the light fixture, in this case, we send the data every 25ms (40Hz refresh rate).
lastDmxTransmit: The time when we last sent the DMX data.
class MyServerCallbacks : public BLEServerCallbacks
{
void onConnect(BLEServer *pServer)
{
deviceConnected = true;
}
void onDisconnect(BLEServer *pServer)
{
deviceConnected = false;
}
};
To monitor the bluetooth connection status, we create a class called MyServerCallbacks that extends the BLEServerCallbacks class. We override the onConnect and onDisconnect methods to set the deviceConnected variable to true or false when the device connects or disconnects.
class MyCallbacks : public BLECharacteristicCallbacks
{
void onWrite(BLECharacteristic *pCharacteristic)
{
std::string rxValue = pCharacteristic->getValue();
if (rxValue.length() > 0)
{
for (size_t i = 0; i < rxValue.length() - 1; i += 2)
{
uint8_t channel = rxValue[i];
uint8_t value = rxValue[i + 1];
if (channel < DMX_PACKET_SIZE)
{
dmxData[channel] = value;
}
}
// Blink LED to indicate received data
digitalWrite(LED_PIN, HIGH);
delay(10);
digitalWrite(LED_PIN, LOW);
}
}
};
To receive the DMX data over bluetooth, we create a class called MyCallbacks that extends the BLECharacteristicCallbacks class. This way we can override the onWrite method to read the data that we receive over bluetooth and store it in the dmxData array. We also blink the LED to indicate that we received data.
void setup()
{
// Setup BLE
BLEDevice::init("DMX Controller");
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
pService = pServer->createService(SERVICE_UUID);
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE);
pCharacteristic->setCallbacks(new MyCallbacks());
pService->start();
pServer->getAdvertising()->start();
// Setup DMX
dmx_config_t config = DMX_CONFIG_DEFAULT;
dmx_driver_install(dmx_num, &config, NULL, 0);
dmx_set_pin(dmx_num, TX_PIN, RX_PIN, EN_PIN);
pinMode(LED_PIN, OUTPUT);
}
In the setup function, we initialize the BLE device and create the bluetooth service and characteristic. We also create the DMX service and set the pins that we use to transmit the DMX data. We also set the LED_PIN as an output pin.
void loop()
{
// Handle BLE connection status
if (!deviceConnected && oldDeviceConnected)
{
delay(500);
pServer->startAdvertising();
oldDeviceConnected = deviceConnected;
}
if (deviceConnected && !oldDeviceConnected)
{
oldDeviceConnected = deviceConnected;
}
// Continuously send DMX data at regular intervals
unsigned long currentMillis = millis();
if (currentMillis - lastDmxTransmit >= DMX_TRANSMIT_INTERVAL)
{
lastDmxTransmit = currentMillis;
dmx_write(dmx_num, dmxData, DMX_PACKET_SIZE);
dmx_send(dmx_num);
dmx_wait_sent(dmx_num, DMX_TIMEOUT_TICK);
}
// Blink LED when not connected
if (!deviceConnected)
{
digitalWrite(LED_PIN, HIGH);
delay(200);
digitalWrite(LED_PIN, LOW);
delay(200);
}
}
In the loop function, we handle the BLE connection status and send the DMX data to the light fixture at regular intervals. We also blink the LED when the device is not connected.
For optional debugging, you can add the following code to the project:
#define DEBUG true // Set to false to disable debug printing
void printDmxDebug(uint8_t channel, uint8_t value)
{
if (!DEBUG)
return;
Serial.print("DMX Channel ");
if (channel < 10)
Serial.print("00");
else if (channel < 100)
Serial.print("0");
Serial.print(channel);
Serial.print(" set to ");
if (value < 100)
Serial.print(" ");
if (value < 10)
Serial.print(" ");
Serial.print(value);
Serial.print(" (");
Serial.print(float(value) / 255.0 * 100.0, 1);
Serial.println("%)");
}
/* Change the MyCallbacks class to this: */
class MyCallbacks : public BLECharacteristicCallbacks
{
void onWrite(BLECharacteristic *pCharacteristic)
{
std::string rxValue = pCharacteristic->getValue();
if (rxValue.length() > 0)
{
if (DEBUG)
{
Serial.println("\n--- New DMX Values Received ---");
}
for (size_t i = 0; i < rxValue.length() - 1; i += 2)
{
uint8_t channel = rxValue[i];
uint8_t value = rxValue[i + 1];
if (channel < DMX_PACKET_SIZE)
{
dmxData[channel] = value;
printDmxDebug(channel, value);
}
}
if (DEBUG)
{
Serial.println("---------------------------\n");
}
// Blink LED to indicate received data
digitalWrite(LED_PIN, HIGH);
delay(10);
digitalWrite(LED_PIN, LOW);
}
}
};
/* Add this to the setup function: */
Serial.begin(115200);
This way you can see the DMX data that you receive over bluetooth in the serial monitor, which can be useful for debugging.
Full code
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include "esp_dmx.h"
// BLE Configuration
#define LED_PIN 2
#define SERVICE_UUID "afe16d0c-ce27-4ffb-8943-5c3228cffabb"
#define CHARACTERISTIC_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
// DMX Configuration
const dmx_port_t dmx_num = DMX_NUM_1;
const int TX_PIN = 16; // DI pinz
const int RX_PIN = -1; // Not used since we're only transmitting
const int EN_PIN = 21; // DE & RE pins
// BLE variables
BLEServer *pServer = NULL;
BLEService *pService = NULL;
BLECharacteristic *pCharacteristic = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;
// DMX variables
uint8_t dmxData[DMX_PACKET_SIZE] = {0};
const unsigned long DMX_TRANSMIT_INTERVAL = 25; // 40Hz refresh rate
unsigned long lastDmxTransmit = 0;
#define DEBUG true // Set to false to disable debug printing
void printDmxDebug(uint8_t channel, uint8_t value)
{
if (!DEBUG)
return;
Serial.print("DMX Channel ");
if (channel < 10)
Serial.print("00");
else if (channel < 100)
Serial.print("0");
Serial.print(channel);
Serial.print(" set to ");
if (value < 100)
Serial.print(" ");
if (value < 10)
Serial.print(" ");
Serial.print(value);
Serial.print(" (");
Serial.print(float(value) / 255.0 * 100.0, 1);
Serial.println("%)");
}
class MyServerCallbacks : public BLEServerCallbacks
{
void onConnect(BLEServer *pServer)
{
deviceConnected = true;
}
void onDisconnect(BLEServer *pServer)
{
deviceConnected = false;
}
};
/*
Example byte array format for sending DMX data over BLE:
For a simple RGB light starting at DMX address 1:
[1, 255, // Channel 1 (Red) = 255
2, 128, // Channel 2 (Green) = 128
3, 64] // Channel 3 (Blue) = 64
For multiple lights:
[1, 255, // Light 1: Red = 255
2, 128, // Light 1: Green = 128
3, 64, // Light 1: Blue = 64
4, 255, // Light 1: Dimmer = 255
5, 0, // Light 1: Strobe = 0
6, 255, // Light 2: Red = 255
7, 0, // Light 2: Green = 0
8, 0] // Light 2: Blue = 0
Each pair represents: [DMX_Channel, Value]
Channel: 1-512
Value: 0-255
*/
class MyCallbacks : public BLECharacteristicCallbacks
{
void onWrite(BLECharacteristic *pCharacteristic)
{
std::string rxValue = pCharacteristic->getValue();
if (rxValue.length() > 0)
{
if (DEBUG)
{
Serial.println("\n--- New DMX Values Received ---");
}
for (size_t i = 0; i < rxValue.length() - 1; i += 2)
{
uint8_t channel = rxValue[i];
uint8_t value = rxValue[i + 1];
if (channel < DMX_PACKET_SIZE)
{
dmxData[channel] = value;
printDmxDebug(channel, value);
}
}
if (DEBUG)
{
Serial.println("---------------------------\n");
}
// Blink LED to indicate received data
digitalWrite(LED_PIN, HIGH);
delay(10);
digitalWrite(LED_PIN, LOW);
}
}
};
void setup()
{
Serial.begin(115200);
// Setup BLE
BLEDevice::init("DMX Controller");
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
pService = pServer->createService(SERVICE_UUID);
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE);
pCharacteristic->setCallbacks(new MyCallbacks());
pService->start();
pServer->getAdvertising()->start();
// Setup DMX
dmx_config_t config = DMX_CONFIG_DEFAULT;
dmx_driver_install(dmx_num, &config, NULL, 0);
dmx_set_pin(dmx_num, TX_PIN, RX_PIN, EN_PIN);
pinMode(LED_PIN, OUTPUT);
}
void loop()
{
// Handle BLE connection status
if (!deviceConnected && oldDeviceConnected)
{
delay(500);
pServer->startAdvertising();
oldDeviceConnected = deviceConnected;
}
if (deviceConnected && !oldDeviceConnected)
{
oldDeviceConnected = deviceConnected;
}
// Continuously send DMX data at regular intervals
unsigned long currentMillis = millis();
if (currentMillis - lastDmxTransmit >= DMX_TRANSMIT_INTERVAL)
{
lastDmxTransmit = currentMillis;
dmx_write(dmx_num, dmxData, DMX_PACKET_SIZE);
dmx_send(dmx_num);
dmx_wait_sent(dmx_num, DMX_TIMEOUT_TICK);
}
// Blink LED when not connected
if (!deviceConnected)
{
digitalWrite(LED_PIN, HIGH);
delay(200);
digitalWrite(LED_PIN, LOW);
delay(200);
}
}
Testing
After uploading the code to the ESP32, you can connect to the ESP32 using a bluetooth debug app like nRF Connect. You can then send the DMX data to the ESP32 using the app and control the light fixture. You can also use the serial monitor in the Arduino IDE to see the DMX data that you receive over bluetooth if you added the optional debugging code.
How to send DMX data over bluetooth
To send DMX data over bluetooth, you need to send a byte array with the DMX channel and value pairs. For example, to set the red and dimmer channels to 255, you would send the following byte array: 01FF04FF
End result
For the end product I designed a custom case that holds the ESP32, RS485 module, led, and XLR connector. The case is 3D printed and held togheter with magnets for easy access to the ESP32 and RS485 module. The XLR connector is mounted on the front of the case and the USB port of the ESP is accessible from the back of the case, with a hole for the LED.
Top comments (0)