GithubHelp home page GithubHelp logo

someweisguy / esp_dmx Goto Github PK

View Code? Open in Web Editor NEW
295.0 16.0 28.0 2.85 MB

Espressif ESP32 implementation of ANSI-ESTA E1.11 DMX-512A and E1.20 RDM

License: MIT License

CMake 0.28% C 99.72%
esp-dmx dmx-devices dmx dmx512 dmx-512 dmx-512a dmx512a theatre theater lighting

esp_dmx's Introduction

esp_dmx

This library allows for transmitting and receiving ANSI-ESTA E1.11 DMX-512A and ANSI-ESTA E1.20 RDM using an Espressif ESP32. It provides control and analysis of the packet configuration and allows the user to read or write synchronously or asynchronously from the DMX bus using whichever hardware UART port that is desired. This library also includes tools for data error-checking to safely process DMX and RDM commands as well as DMX packet metadata extraction to assist with troubleshooting errors.

Contents

Library Installation

Arduino

This library requires the Arduino-ESP32 framework version 2.0.3 or newer. To install the correct framework, follow Espressif's instructions on the Arduino-ESP32 documentation page here.

This library can be installed by cloning this repository into your your Arduino/libaries folder or by searching for esp_dmx in the Arduino IDE Library Manager. Then simply include the library by adding #include "esp_dmx.h" at the top of your Arduino sketch.

ESP-IDF

This library requires ESP-IDF version 4.4.1 or newer. Clone this repository into your project's components folder. The library can be linked by writing #include "esp_dmx.h" at the top of your main.c file.

PlatformIO

This library is compatible with the PlatformIO IDE. Search for this library in the PlatformIO library registry and add it to your project. The library can be included by writing #include "esp_dmx.h" at the top of your main.c or main.cpp file.

This library includes a Kconfig file for configuring build options on the ESP32. When using the ESP-IDF framework, it is recommended to move library folders to a components folder located in your project's root directory rather than leaving PlatformIO installed libraries in their default location. This is not required but it can result in more a more performant driver. See Using Flash or Disabling Cache for more information.

Quick-Start Guide

To get started, call the following code in your setup() function if using Arduino, or app_main() in your main.c file if using ESP-IDF.

const dmx_port_t dmx_num = DMX_NUM_1;

// First, use the default DMX configuration...
dmx_config_t config = DMX_CONFIG_DEFAULT;

// ...declare the driver's DMX personalities...
const int personality_count = 1;
dmx_personality_t personalities[] = {
  {1, "Default Personality"}
};

// ...install the DMX driver...
dmx_driver_install(dmx_num, &config, personalities, personality_count);

// ...and then set the communication pins!
const int tx_pin = 17;
const int rx_pin = 16;
const int rts_pin = 21;
dmx_set_pin(dmx_num, tx_pin, rx_pin, rts_pin);

To write data to the DMX bus, two functions are provided. The function dmx_write() writes data to the DMX buffer and dmx_send() sends the data out onto the bus. The function dmx_wait_sent() is used to block the task until the DMX bus is idle.

uint8_t data[DMX_PACKET_SIZE] = {0};

while (true) {
  // Write to the packet and send it.
  dmx_write(dmx_num, data, DMX_PACKET_SIZE);
  dmx_send(dmx_num);
  
  // Do work here...

  // Block until the packet is finished sending.
  dmx_wait_sent(dmx_num, DMX_TIMEOUT_TICK);
}

To read from the DMX bus, two additional functions are provided. The function dmx_receive() waits until a new packet has been received. The function dmx_read() reads the data from the driver buffer into an array so that it can be processed. If it is desired to process RDM requests, the function rdm_send_response() may be used.

dmx_packet_t packet;
while (true) {
  int size = dmx_receive(dmx_num, &packet, DMX_TIMEOUT_TICK);
  if (size > 0) {
    dmx_read(dmx_num, data, size);

    // Optionally handle RDM requests
    if (packet.is_rdm) {
      rdm_send_response(dmx_num);
    }

    // Process data here...
  }

  // Do other work here...

}

That's it! For more detailed information on how this library works including details on RDM, keep reading.

What is DMX?

DMX is a unidirectional communication protocol used primarily in the entertainment industry to control lighting and stage equipment. DMX is transmitted as a continuous stream of packets using half-duplex RS-485 signalling with a standard UART port. DMX devices are typically connected using XLR5 in a daisy-chain configuration but other connectors such as XLR3 are common in consumer products.

Each DMX packet begins with a high-to-low transition called the break, followed by a low-to-high transition called the mark-after-break, followed by an eight-bit byte. This first byte is called the start code. The start-of-packet break, mark-after-break, and start code is called the reset sequence. After the reset sequence, a packet of up to 512 data bytes may be sent.

DMX imposes very strict timing requirements to allow for backwards compatibility with older lighting equipment. Frame rates may range from 1fps to up to approximately 830fps. A typical DMX controller transmits packets between approximately 25fps to 44fps. DMX receivers and transmitters have different timing requirements which must be adhered to carefully to ensure commands are processed.

Today, DMX often struggles to keep up with the demands of the latest hardware. Its low data rate and small packet size sees it losing market popularity over more capable protocols. However its simplicity and robustness often makes it the first choice for small scale projects.

For in-depth information on DMX, see the E1.11 standards document.

What is RDM?

RDM stands for Remote Device Management. It is an extension to the DMX protocol to allow intelligent, bidirectional communication between devices from multiple manufacturers utilizing a modified DMX data link. RDM permits a console or other controlling device to discover and then configure, monitor, and manage intermediate and end-devices connected through a DMX network.

When RDM capable devices must be configured but are inconveniently out of reach, it is said that RDM is "faster than the ladder." Instead of needing to climb a ladder to reach a device users are able to make changes to device settings from their DMX controller.

For in-depth information on RDM, see the E1.20 standards document.

DMX Basics

In a typical configuration, a DMX system consists of one DMX controller and up to 32 DMX fixtures per DMX port. A five-pin XLR cable, commonly known as a DMX cable, is connected to the DMX-out port of the DMX controller and into the the DMX-in port of the first DMX fixture. Each subsequent fixture is connected by a DMX cable between the DMX-out port of the previous fixture and the DMX-in port of the next fixture. A DMX terminator may be connected to the DMX-out port of the final fixture. This is only required when using RDM but can be helpful to ensure DMX signal stability when connecting more than 32 fixtures or for long runs of DMX cable.

Addresses and the Start Code

DMX addresses are needed for DMX controllers to communicate with fixtures. A fixture's address can be set between 1 and 512 (inclusive) by setting the fixture's DIP switches or by using the fixture's built-in display. DMX addresses correspond to DMX slots in a DMX packet. DMX address one is mapped to DMX packet slot one. Each DMX slot is an 8-bit number. A slot's minimum value is 0 and its maximum value is 255. To control a DMX dimmer set to DMX address five, DMX packet slot five must be written. To set the DMX dimmer to full intensity, slot five must be written to value 255. To set the dimmer to zero intensity, slot five must be written to value 0. Setting slot five to any value in between will dim the intensity appropriately.

Slot zero in the DMX packet is called the DMX start code. The start code informs DMX fixtures what type of packet is being sent. Standard DMX packets use a start code of 0x00, also called the null start code. DMX fixtures will not respond to DMX packets unless the packet begins with a null start code! A list of the other supported start codes can be found in the DMX start codes section.

Footprints

Many DMX fixtures support multiple controllable DMX parameters. Fixtures that support multiple parameters use multiple DMX addresses. An RGB LED fixture is a common example of a multi-parameter fixture. It uses three parameters: red, green, and blue. In this example, this fixture would therefore use three, consecutive DMX addresses. This is called the fixture's DMX footprint. The red, green, and blue parameters of an RGB LED fixture set to DMX address five can be controlled by writing to slots five, six, and seven, respectively. A fixture's DMX footprint can be found by reading its user manual. It is possible to address multiple DMX fixtures so that their DMX footprints overlap but this is uncommon.

Multi-parameter fixtures may support multiple footprints. On a fixture that supports multiple footprints, only one footprint can be active at a time. Larger footprints may be used to provide finer control of the fixture's DMX parameters. Conversely, smaller footprints may be used when finer control of a fixture is not needed. Instructions to change a fixture's active footprint may be found by consulting its user manual.

A fixture which supports multiple footprints is said to possess multiple personalities. Each DMX personality may support a different footprint.

Universes

When more than 512 DMX addresses are used, it is required to use multiple DMX ports. Each DMX port is called a DMX universe. DMX fixtures can be uniquely identified by their DMX universe and address numbers. A common way to notate this is by separating the universe and address with a /. Therefore 3/475 represents universe three, address 475. It is important to understand that DMX fixtures are not aware of the concept of a universe. A fixture set to DMX address one will respond to writes to slot one on whichever universe to which it is connected.

RDM Basics

Compared to DMX, RDM is a fairly complex protocol which utilizes different packet and data types depending on the information that is being requested. This library attempts to abstract away many of the details of the RDM implementation to facilitate code ease-of-use while still providing a powerful feature set. The following sections are intended to provide a introduction to RDM as it pertains to this library. For a more comprehensive introduction of RDM, see the E1.20 standards document.

Unique IDs

In an RDM network, there is one controller device and several responder devices. Each device in the network has a Unique ID (UID) which uniquely identifies itself against others in the network. If an RDM device has multiple DMX ports, it may possess multiple UIDs; one for each DMX port. UIDs are 48-bits long. The most-significant 16-bits are a device's manufacturer ID. Devices made by the same manufacturer have the same most-significant 16-bits. The remaining, 32 least-significant-bits are a device's device ID. Readers may draw a reasonable comparison to MAC addresses in IP networking equipment. Every IP capable device has at least one MAC address and if they have multiple network interfaces they may have multiple MAC addresses. Similarly, the most significant bits in a MAC address identify a network interface's manufacturer.

UIDs are represented in text by displaying the UID in hexadecimal and by separating the manufacturer ID from the device ID with a :. If a device has a manufacturer ID with a value of 0xabcd and device ID with a value of 0x12345678, its full UID would be displayed as abcd:12345678.

When a controller device composes an RDM request, it must be addressed using a destination UID. The recipient of a request may be a single device, by using the device's UID, or multiple devices, by using a broadcast UID. Broadcast UIDs can be addressed to every device of a specific manufacturer or to all devices on the RDM network. To send a manufacturer broadcast, the destination UID's manufacturer ID must match the manufacturer ID of the desired manufacturer, and the device ID must be ffffffff. To broadcast to every device with the manufacturer ID of 05e0, the UID must be set to 05e0:ffffffff. The UID used to broadcast to all devices on the RDM network is ffff:ffffffff.

The lowest possible UID is 0001:00000000 and the highest possible UID is ffff:fffffffe. In practice the manufacturer ID ffff is not permitted so real-world RDM devices would never possess a UID higher than 7fff:fffffffe. This library represents the maximum UID with the constant RDM_UID_MAX.

Organizations may apply for a unique manufacturer ID by contacting ESTA. The instructions to do so and a list of registered manufacturer IDs can be found here. This software library is registered and listed with the manufacturer ID of 05e0. Users of this library may use this manufacturer ID for their devices.

In this library, UIDs are represented with the rdm_uid_t type. The macro rdm_uid_broadcast_man() can be used to create a UID which broadcasts to the desired manufacturer ID and the constant RDM_UID_BROADCAST_ALL can be used to broadcast to all devices on the RDM network.

Sub-devices

Each RDM device may support up to 512 sub-devices. An example of a device that may support sub-devices is a dimmer rack which possesses multiple dimmers. Requests may be addressed to a specific dimmer in the dimmer rack by addressing the dimmer rack's UID, and specifying a sub-device number to target the appropriate dimmer.

The sub-device number which represents the root device is 0x0000. A request may also be addressed to all sub-devices of a root device by using the sub-device number 0xffff. The constants RDM_SUB_DEVICE_ROOT and RDM_SUB_DEVICE_ALL are provided to improve code readability.

A root device and its sub-devices may support different RDM parameters, but each sub-device within a root device must support the same parameters as each other.

Parameters

RDM requests must be able to fetch and update parameters. The RDM standard specifies more than 50 different Parameter IDs (PIDs) which a device may support. The standard also specifies that manufacturers may define custom PIDs for their devices.

Most PIDs can be either GET or SET if the responding device supports the requested PID. Some PIDs may support GET but do not support SET, and vice versa. Some PIDs may support both GET and SET. Three PIDs cannot be GET nor SET. These three PIDs are used for the RDM discovery algorithm. They are DISC_UNIQUE_BRANCH, DISC_MUTE, and DISC_UN_MUTE. This library provides constants for each PID. Each PID in this library is prefixed with RDM_PID_. Therefore, DISC_UNIQUE_BRANCH would become RDM_PID_DISC_UNIQUE_BRANCH. This document will refer to PIDs by their prefixed names for consistency of documentation.

RDM specifies that every device (but not its sub-devices necessarily) must support a specific set of PIDs to ensure proper communication between devices. The list of the supported and the required PIDs can be found in the appendix.

GET requests may not be sent to all sub-devices of a root device. It is therefore not permitted to send a GET request to RDM_SUB_DEVICE_ALL.

Discovery

When making RDM requests it is typically needed (but not required) to discover the UIDs of the devices on the RDM network. The discovery process begins with the controller device broadcasting an RDM_PID_DISC_UNIQUE_BRANCH command to all devices. The data included in this request consist of an address space defined by a UID lower bound and UID upper bound. Responding devices respond to RDM_PID_DISC_UNIQUE_BRANCH requests if their UID is greater-than-or-equal to the lower bound and less-than-or-equal to the upper bound. When multiple devices respond at the same time, data collisions can occur. When a data collision occurs, the controller divides the address space in two. An RDM_PID_DISC_UNIQUE_BRANCH request is sent to each new address space. This is repeated until a single device is found within an address space.

When a single device is found within an address space, that device is sent an RDM_PID_DISC_MUTE request to mute its response to future RDM_PID_DISC_UNIQUE_BRANCH requests. When responding to RDM_PID_DISC_MUTE requests, devices that have multiple RDM ports return a binding UID which represents its primary UID.

Some RDM devices act as proxy devices. A proxy device is any inline device that acts as an agent or representative for one or more devices. A proxy device shall respond to all controller messages on behalf of the devices it represents as if it is the represented device. If a device is acting as a proxy device or if it is proxied by another device, it will indicate so in its response to RDM_PID_DISC_MUTE and RDM_PID_DISC_UN_MUTE requests.

Discovery should be performed periodically as discovered devices may be removed from the RDM network or new devices may be added. Before restarting the discovery algorithm, a RDM_PID_DISC_UN_MUTE request should be broadcast to all devices in order to detect if devices were removed from the RDM network.

Responses

Responding devices shall respond to requests only if the request was a non-broadcast request. Responding devices may respond to requests with the following response types:

  • RDM_RESPONSE_TYPE_ACK indicates that the responder has correctly received the controller message and is acting upon the request.
  • RDM_RESPONSE_TYPE_ACK_OVERFLOW indicates that the responder has correctly received the controller message and is acting upon the request, but there is more response data available than will fit in a single response packet. To receive the remaining information, controllers are able to send repeated requests to the same PID until the remaining information can fit in a single message.
  • RDM_RESPONSE_TYPE_ACK_TIMER indicates that the responder is unable to supply the requested GET information or SET confirmation within the required response time. When sending this response, responding devices include an estimated response time that must elapse before the responder can provide the required information.
  • RDM_RESPONSE_TYPE_NACK_REASON indicates that the responder is unable to reply with the requested GET information or unable to process the specified SET command. Responding devices must include a NACK reason code in their response. NACK reason codes are enumerated in the appendix.

Two additional response types are defined for this library. These response types are included to assist users with processing RDM data.

  • RDM_RESPONSE_TYPE_NONE indicates that no response was received.
  • RDM_RESPONSE_TYPE_INVALID indicates that a response was received, but the response was invalid. This can occur for several reasons including an invalid checksum, or an invalid packet format.

Responders must respond to every non-broadcast RDM request as well as every broadcast RDM_PID_DISC_UNIQUE_BRANCH request if their RDM discovery is un-muted and if their UID falls within the request's address space. When responding to RDM_PID_DISC_UNIQUE_BRANCH requests, responders shall not send a DMX break and mark-after-break in order to improve discovery times and shall encode their response to reduce data loss during data collisions. The omission of the DMX break and mark-after-break is handled automatically by the DMX driver. Responders may only respond to RDM_PID_DISC_UNIQUE_BRANCH, RDM_PID_DISC_MUTE, and RDM_PID_DISC_UN_MUTE requests with RDM_RESPONSE_TYPE_ACK.

Configuring the DMX Port

The DMX driver’s functions identify each of the UART controllers using dmx_port_t. This identification is needed for all the following function calls.

Installing the Driver

Before any DMX functions may be called, the DMX driver must be installed. Install the driver by calling dmx_driver_install(). This function will allocate the necessary resources for the DMX driver. It instantiates the driver to default DMX timing. The following parameters are passed to this function:

  • The DMX port to use.
  • The DMX configuration to use. The macro DMX_CONFIG_DEFAULT can be used to declare a struct with the default configuration.
  • The DMX personalities that the device will use. This is an array of dmx_personality_t. If the device does not use any DMX slots this value can be NULL.
  • The personality count or 0 if the device does not use any DMX slots. The maximum number of personalities allowed is 255.
dmx_config_t config = DMX_CONFIG_DEFAULT;
dmx_personality_t personalities[] = {
  {1, "Single-channel Mode"},  // Single-address DMX personality
  {3, "RGB"},                  // Three-address RGB mode
  {4, "RGBW"},                 // Four-address RGBW personality
  {7, "RGBW with Macros"}      // RGBW with three additional macro parameters
};
const int personality_count = 4;
dmx_driver_install(DMX_NUM_1, &config, personalities, personality_count);

The dmx_config_t sets permanent configuration values within the DMX driver. These values are used to configure the DMX device and for the RDM responder. The fields in the dmx_config_t include:

  • interrupt_flags The interrupt allocation flags to use. The default value is DMX_INTR_FLAGS_DEFAULT.
  • root_device_parameter_count The number of parameters that the root device supports. This is the number of parameters that may be registered on the root device. The default value is 32.
  • sub_device_parameter_count The number of parameters that the sub-devices support. This is the number of parameters that may be registered per sub-device. The default value is 0.
  • model_id This field identifies the device model ID of the root device. This is an arbitrary value set by the user to uniquely identify different models of RDM devices made by a single manufacturer from one another. The default value is 0.
  • product_category Devices shall report a product category based on the product's primary function. The product categories are enumerated in product_category_t. The default value is RDM_PRODUCT_CATEGORY_FIXTURE.
  • software_version_id This field indicates the software version ID for the device. The software version ID is a 32-bit value determined by the manufacturer. The default value is based on the current version of esp_dmx.
  • software_version_label This RDM parameter is used to get a descriptive ASCII text label for the device's operating software version. The descriptive text returned by this parameter is intended for display to the user. The default value is a string based on the current version of esp_dmx.
  • queue_size_max The maximum size of the RDM queue. Setting this value to 0 disables the RDM queue. The default value is 32.

The dmx_personality_t type is a struct which contains two fields: footprint and description. The footprint field is the DMX footprint of the personality. This is the number of DMX slots which this footprint uses. The description field is a string which describes the purpose of the DMX personality. This field is used for RDM responses and may be up to 33 characters long including a null-terminator.

dmx_config_t config = {
  .interrupt_flags = DMX_INTR_FLAGS_DEFAULT,
  .root_device_parameter_count = 32,
  .sub_device_parameter_count = 0,
  .model_id = 0,
  .product_category = RDM_PRODUCT_CATEGORY_FIXTURE,
  .software_version_id = ESP_DMX_VERSION_ID,
  .software_version_label = ESP_DMX_VERSION_LABEL,
  .queue_size_max = 32
};
dmx_driver_install(DMX_NUM_1, &config, personalities, personality_count);

Setting Communication Pins

After the DMX driver is installed, users can configure the physical GPIO pins to which the DMX port will be connected. To do this, call the function dmx_set_pin() and specify which GPIO should be connected to the TX, RX, and RTS signals. If you want to keep a currently allocated pin to a specific signal, pass the macro DMX_PIN_NO_CHANGE. This macro should also be used if a pin isn't used.

// Set TX: GPIO16 (port 2 default), RX: GPIO17 (port 2 default), RTS: GPIO21.
dmx_set_pin(DMX_NUM_1, DMX_PIN_NO_CHANGE, DMX_PIN_NO_CHANGE, 21);

Timing Configuration

In most situations it is not necessary to adjust the default timing of the DMX driver. Nevertheless, this library allows for individual configuration of the DMX baud rate, break, and mark-after-break for the DMX controller. These functions have no effect when receiving DMX; they only effect the baud rate, break, and mark-after-break when sending DMX or RDM. After the DMX driver has been installed, the following functions may be called.

dmx_set_baud_rate(DMX_NUM_1, DMX_BAUD_RATE);     // Set DMX baud rate.
dmx_set_break_len(DMX_NUM_1, DMX_BREAK_LEN_US);  // Set DMX break length.
dmx_set_mab_len(DMX_NUM_1, DMX_MAB_LEN_US);      // Set DMX MAB length.

If timing values that are not within the DMX specification are passed to these functions, the values will be clamped so that they are within DMX specification. Note that it is possible to set driver timing to be within DMX specification but not within RDM specification. Care must be used when using these functions to ensure that RDM capabilities are maintained.

The above functions each have _get_ counterparts to retrieve the currently set DMX timing parameters.

Reading and Writing DMX

DMX is a unidirectional protocol. This means that on the DMX bus only one device can transmit commands and many devices listen for commands. Therefore, this library permits either reading or writing to the bus but not both at once. If sending and receiving data concurrently is desired, users can use two UART ports and install a driver on each port.

Reading DMX

Reading may be performed synchronously or asynchronously from the DMX bus. It is typically desired to perform reads synchronously. This means that reads are only performed when a new DMX packet is received. This is ideal because it is not commonly desired to perform reads on the same data multiple times.

To read synchronously from the DMX bus the DMX driver must wait for a new packet. The blocking function dmx_receive() can be used for this purpose.

dmx_packet_t packet;
// Wait for a packet. Returns the size of the received packet or 0 on timeout.
int packet_size = dmx_receive(DMX_NUM_1, &packet, DMX_TIMEOUT_TICK);

The function dmx_receive() takes three arguments. The first argument is the dmx_port_t which identifies which DMX port to use. The second argument is a pointer to a dmx_packet_t struct. Data about the received packet is copied into the dmx_packet_t struct when a packet is received. This data includes:

  • err reports any errors that occurred while receiving the packet (see: Error Handling).
  • sc is the start code of the packet.
  • size is the size of the packet in bytes, including the DMX start code. This value will never be higher than DMX_PACKET_SIZE.
  • is_rdm evaluates to true if the packet is an RDM packet and if the RDM checksum is valid.

Using the dmx_packet_t struct is optional. If processing DMX or RDM packet data is not desired, users can pass NULL in place of a pointer to a dmx_packet_t struct.

The dmx_receive() function only returns a non-zero value when new data is received. Data is considered "new" when a DMX break is received. DMX data may also be considered "new" when an RDM_PID_DISC_UNIQUE_BRANCH response is received since these RDM responses are not sent with a DMX break.

The final argument to dmx_receive() is the amount of FreeRTOS ticks to block until the function times out. This library defines a constant, DMX_TIMEOUT_TICK, which is the length of time that must be waited until the DMX signal is considered lost according to DMX specification. According to DMX specification this constant is equivalent to 1250 milliseconds. If non-blocking behavior is desired, users should set this value to 0.

After a packet is received, dmx_read() can be called to read the packet into a user buffer. It is recommended to check for DMX errors before reading data but it is not required.

uint8_t data[DMX_PACKET_SIZE];

dmx_packet_t packet;
if (dmx_receive(DMX_NUM_1, &packet, DMX_TIMEOUT_TICK)) {

  // Check that no errors occurred.
  if (packet.err == DMX_OK) {
    dmx_read(DMX_NUM_1, data, packet.size);
  } else {
    printf("An error occurred receiving DMX!");
  }

} else {
  printf("Timed out waiting for DMX.");
}

The function dmx_receive_num() is provided to receive a specified number of DMX slots before returning. This function is identical to dmx_receive() except that it provides an additional argument which sets the number of slots to receive. This value is ignored when receiving RDM packets so that dmx_receive() and dmx_receive_num() will always receive full RDM packets.

dmx_packet_t packet;
int num_slots_to_receive = 96;
dmx_receive_num(DMX_NUM_1, &packet, num_slots_to_receive, DMX_TIMEOUT_TICK);

The function dmx_receive() can be viewed as a wrapper for dmx_receive_num() where the number of slots to receive is equal to the packet size of the last DMX packet received. When the desired number of slots to receive is greater than the actual number of slots received (e.g. when waiting to receive 513 slots, but only 128 are received) the function will unblock upon receiving the DMX break for the subsequent packet and the packet.err will be set to DMX_ERR_NOT_ENOUGH_SLOTS.

There are two variations to the dmx_read() function. The function dmx_read_offset() is similar to dmx_read() but allows a small footprint of the entire DMX packet to be read.

const int size = 12;   // The size of this device's DMX footprint.
const int offset = 5;  // The start address of this device.
uint8_t data[size];

// Read slots 5 through 17. Returns the number of slots that were read.
int num_slots_read = dmx_read_offset(DMX_NUM_1, offset, data, size);

Lastly, dmx_read_slot() can be used to read a single slot of DMX data.

const int slot_num = 0;  // The slot to read. Slot 0 is the DMX start code!

// Read slot 0. Returns the value of the desired slot or -1 on error.
int value = dmx_read_slot(DMX_NUM_1, slot_num);

DMX Sniffer

This library offers an option to measure DMX break and mark-after-break timings of received data packets. The sniffer is much more resource intensive than the default DMX driver, so it must be explicitly enabled by calling dmx_sniffer_enable().

The DMX sniffer installs an edge-triggered interrupt on the specified GPIO pin. This library uses the ESP-IDF provided GPIO ISR which allows the use of individual interrupt handlers for specific GPIO interrupts. The interrupt handler works by iterating through each GPIO to determine if it triggered an interrupt and if so, it calls the appropriate handler.

A quirk of the default ESP-IDF GPIO ISR is that lower GPIO numbers are processed earlier than higher GPIO numbers. It is recommended that the DMX read pin be shorted to a lower GPIO number in order to ensure that the DMX sniffer can run with low latency.

It is important to note that the sniffer requires a fast clock speed in order to maintain low latency. In order to guarantee accuracy of the sniffer, the ESP32 must be set to a CPU clock speed of at least 160MHz. This setting can be configured in Kconfig if the ESP-IDF is used.

Before enabling the sniffer tool, gpio_install_isr_service() must be called with the required DMX sniffer interrupt flags. The macro DMX_SNIFFER_INTR_FLAGS_DEFAULT can be used to provide the proper interrupt flags.

gpio_install_isr_service(DMX_SNIFFER_INTR_FLAGS_DEFAULT);

const int sniffer_pin = 4; // Lowest exposed pin on the Feather breakout board.
dmx_sniffer_enable(DMX_NUM_1, sniffer_pin);

Break and mark-after-break timings are reported to the DMX sniffer when it is enabled. To read data from the DMX sniffer call dmx_sniffer_get_data() after a DMX packet is received to copy data into a dmx_metadata_t struct. If data is copied, the function will return true.

dmx_packet_t packet;
if (dmx_receive(DMX_NUM_1, &packet, DMX_TIMEOUT_TICK)) {
  dmx_metadata_t metadata;
  if (dmx_sniffer_get_data(DMX_NUM_1, &metadata, DMX_TIMEOUT_TICK)) {
    printf("The DMX break length was: %i\n", metadata.break_len);
    printf("The DMX mark-after-break length was: %i\n", metadata.mab_len);
  }
}

Writing DMX

To write to the DMX bus, dmx_write() can be called. This writes data to the DMX driver but it does not transmit a packet onto the bus. In order to transmit the data that was written, dmx_send() must be called.

uint8_t data[DMX_PACKET_SIZE] = { 0, 1, 2, 3 };

// Write the packet and send it out on the DMX bus.
const int num_bytes_to_write = DMX_PACKET_SIZE;
dmx_write(DMX_NUM_1, data, num_bytes_to_write);
dmx_send(DMX_NUM_1,);

It takes a typical DMX packet approximately 22 milliseconds to send. During this time, it is possible to write new data to the DMX driver with dmx_write() if non-RDM data is being sent. To do so would result in an asynchronous write which may not be desired. To write data synchronously it is required to wait until the DMX packet is finished being sent. The function dmx_wait_sent() is used for this purpose.

uint8_t data[DMX_PACKET_SIZE] = { 0, 1, 2, 3 };

while (true) {
  // Send the DMX packet.
  dmx_send(DMX_NUM_1);

  // Process the next DMX packet (while the previous is being sent) here.
  for (int i = 1; i < DMX_PACKET_SIZE; i++) {
    data[i]++;  // Increment the value of each slot, excluding the start code.
  }

  // Wait until the packet is finished being sent before proceeding.
  dmx_wait_sent(DMX_NUM_1, DMX_TIMEOUT_TICK);

  // Now write the packet synchronously!
  dmx_write(DMX_NUM_1, data, DMX_PACKET_SIZE);
}

When sending DMX, the dmx_send() function sends the maximum number of slots allowed by the DMX standard. When an RDM packet is sent using dmx_send(), the DMX driver will automatically send only the slots which make up the RDM packet.

To send a specific number of DMX slots, the function dmx_send_num() may be used. The number of slots to send is ignored when sending RDM data.

const int num_bytes_to_send = 96;
dmx_send_num(DMX_NUM_1, num_bytes_to_send);

An offset of DMX slots can be written using dmx_write_offset() and individual DMX slots can be written using dmx_write_slot(). This behavior is similar to reading an offset of DMX slots or reading a single DMX slot using dmx_read_offset() and dmx_read_slot(), respectively.

uint8_t data[DMX_PACKET_SIZE] = { 0, 1, 2, 3 };

// Write slots 10 through 17 (inclusive)
const int offset = 10;
const size_t size = 7;
dmx_write_offset(DMX_NUM_1, offset, data, size);

// Set slot number 5 to value 127.
const int slot_num = 5;
const uint8_t value = 127;
dmx_write_slot(DMX_NUM_1, slot_num, value);

// Don't forget to call dmx_send()!

DMX Parameters

Upon installing the DMX driver, some parameter values are set which may be get or set by the user. These parameters include the current DMX personality, the personality count, the footprint of a specified personality, the description of a personality, and the DMX start address.

Getting or setting the DMX start address can be done using dmx_get_start_address() and dmx_set_start_address(). When RDM is enabled, these functions behave similarly to rdm_get_dmx_start_address() and rdm_set_dmx_start_address().

// Get the DMX start address and increment it by one
uint16_t dmx_start_address = dmx_get_start_address(DMX_NUM_1);
dmx_start_address++;
if (dmx_start_address >= DMX_PACKET_SIZE_MAX) {
  dmx_start_address = 1;  // Ensure DMX start address is within bounds
}
dmx_set_start_address(DMX_NUM_1, dmx_start_address);

Personalities, the personality count, personality descriptions, and footprint sizes may be accessed with dmx_get_current_personality(), dmx_set_current_personality(), dmx_get_personality_count(), dmx_get_personality_description(), and dmx_get_footprint(). Personalities are indexed starting at one. There is no personality zero.

const uint8_t personality_count = dmx_get_personality_count(DMX_NUM_1);
uint8_t current_personality = dmx_get_current_personality(DMX_NUM_1);
if (current_personality < personality_count) {
  // Increment the personality.
  current_personality++;
  /* It is ok if current_personality == personality_count because personalities
    start at 1, not 0! */

  // Get and print the new personality description and footprint.
  const char *desc = dmx_get_personality_description(DMX_NUM_1, 
                                                     current_personality);
  uint16_t footprint = dmx_get_footprint(DMX_NUM_1, current_personality);
  printf("Setting the current personality to %i: '%s'\n", current_personality,
         desc);
  printf("Personality %i has a footprint of %i\n", current_personality,
         footprint);
  
  dmx_set_current_personality(DMX_NUM_1, current_personality);
}

Reading and Writing RDM

Using only the functions listed above it is possible to send and receive RDM packets. When an RDM packet is written using dmx_write() the DMX driver will respond accordingly and ensure that RDM timing requirements are met. For example, calls to dmx_send() and dmx_send_num() typically send a DMX break and mark-after-break when sending a DMX packet with a null start code. When sending an RDM discovery response packet the DMX driver automatically removes the DMX break and mark-after-break which is required per the RDM standard. Sending RDM responses with dmx_send() or dmx_send_num() may also fail when the DMX driver has detected that the RDM response timeout has already elapsed. This is done to reduce the number of data collisions on the RDM bus and keeps the RDM bus operating properly.

// This is a hard-coded discovery response packet.
const uint8_t discovery_response[] = {
  0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xaa, 0xaf, 0x55, 0xea, 0xf5, 0xba, 
  0x57, 0xbb, 0xdd, 0xbf, 0x55, 0xba, 0xdf, 0xaa, 0x5d, 0xbb, 0x7d 
};
dmx_write(DMX_NUM_1, discovery_response, sizeof(discovery_response));

// This function will not send a DMX break or mark-after-break 
dmx_send(DMX_NUM_1);

Likewise, the dmx_receive() functions behave contextually when receiving DMX or RDM packets. When receiving DMX, calls to dmx_receive() and dmx_receive_num() will timeout according to the timeout value provided, such as DMX_TIMEOUT_TICK. When receiving RDM packets, the DMX driver may timeout much more quickly than the provided timeout value as the RDM bus turnaround times are much shorter than DMX.

// This is a hard-coded GET DEVICE_INFO request.
const uint8_t get_device_info[] = {
  0xcc, 0x01, 0x18, 0x3b, 0x10, 0x44, 0xc0, 0x6f, 0xbf, 0x05, 0xe0, 0x12, 0x99,
  0x15, 0x9a, 0x14, 0x03, 0x00, 0x00, 0x00, 0x20, 0x00, 0x60, 0x00, 0x06, 0x38
};
dmx_write(DMX_NUM_1, get_device_info, sizeof(get_device_info));
dmx_send(DMX_NUM_1, sizeof(get_device_info));

dmx_packet_t packet;

// This function will unblock early because it is expecting a reply!
dmx_receive(DMX_NUM_1, &packet, DMX_TIMEOUT_TICK);  // Unblocks in 3ms

Because writing RDM requests and responses in this way can be cumbersome, this library provides functions for sending RDM requests and responses. They can be included by adding #include "rdm/controller.h" for requests and #include "rdm/responder.h" for responses.

RDM Requests

This library supports the required PIDs specified in the RDM standard. Request functions in this library are named using the prefix rdm_send_, whether the request is a GET or a SET, and the parameter name. To GET the RDM_PID_DEVICE_INFO of a responder device, users can call rdm_send_get_device_info(). To SET a device's RDM_PID_DMX_START_ADDRESS, users can call rdm_send_set_dmx_start_address(). All GET request functions return true if an RDM_RESPONSE_TYPE_ACK was received or false if an RDM_RESPONSE_TYPE_ACK was not received. All SET request functions return the number of bytes received in the RDM parameter data if an RDM_RESPONSE_TYPE_ACK was received or 0 otherwise. For example rdm_send_get_software_version_label() will return the number of characters in the software version label that was received in the RDM response packet.

In addition to the DMX port number most RDM request functions use at least two arguments to determine where the RDM request should be directed. These arguments are dest_uid, the destination UID and sub_device the RDM sub-device which should receive the request.

When printing UIDs to the terminal, the macros UIDSTR and UID2STR() can be used in printf-like functions.

rdm_uid_t dest_uid = {0x05e0, 0x44c06fbf};  // The destination UID
rdm_sub_device_t sub_device = RDM_SUB_DEVICE_ROOT;
rdm_ack_t ack;  // Stores response information

rdm_device_info_t device_info;  // Stores the response parameter data.
if (rdm_send_get_device_info(DMX_NUM_1, &dest_uid, sub_device, &device_info,
                             &ack)) {
  printf("Successfully received device info from " UIDSTR "!\n",
          UID2STR(ack.src_uid));
}

const uint16_t new_address = 123;  // The new RDM_PID_DMX_START_ADDRESS to send.
if (rdm_send_set_dmx_start_address(DMX_NUM_1, &dest_uid, sub_device, 
                                   new_address, &ack)) {
  printf("Device " UIDSTR " has been set to DMX address %i.\n",
          UID2STR(dest_uid), new_address);
}

Response information from requests is read into a rdm_ack_t pointer which is provided by the user. Users can use this type to ensure that requests were successful and, if they are not successful, handle errors. The rdm_ack_t type contains the following fields:

  • err is set to a non-zero error value if an error occurred reading DMX data. This field only indicates if an error occurred reading raw DMX data. It does not indicate if an invalid RDM packet was received. More information on error handling can be found in the Error Handling section.
  • size is the size of the received packet, including start code, RDM sub-start code, and checksum.
  • src_uid is the UID of the device originating the response packet.
  • pid is the PID of the response packet. This is typically the same as the PID which was sent in the request, but may differ for some requests.
  • type is the type of the RDM response received. It can be any of the RDM response types enumerated in Response Types.
  • message_count is used by an RDM responder to indicate that additional data is now available for collection by a controller.

The remaining field is a union which should be read depending on the value in type.

  • pdl should be read if type evaluates to RDM_RESPONSE_TYPE_ACK. It describes the size of the RDM parameter data that was received.
  • timer should be read if type evaluates to RDM_RESPONSE_TYPE_TIMER. It describes the number of FreeRTOS ticks that must elapse before the RDM responder will be ready to process the request.
  • nack_reason should be read if type evaluates to RDM_RESPONSE_TYPE_NACK_REASON. It describes the NACK reason code that was received from the RDM responder.

Discovering Devices

This library provides two functions for performing full RDM discovery. The function rdm_discover_devices_simple() is provided as a simple implementation of the discovery algorithm which takes a pointer to an array of UIDs to store discovered UIDs and returns the number of UIDs found.

const int array_size = 10;
rdm_uid_t uids[array_size];

// This function blocks and may take some time to complete!
int num_uids = rdm_discover_devices_simple(DMX_NUM_1, uids, array_size);

printf("Discovery found %i UIDs!\n", num_uids);

Discovery can take several seconds to complete. Users may want to perform an action, such as update a progress bar, whenever a new UID is found. When this is desired, the function rdm_discover_with_callback() may be used to specify a callback function which is called when a new UID is discovered.

RDM_PID_DISC_UNIQUE_BRANCH requests support neither GET nor SET. This PID request can be accessed with the function rdm_send_disc_unique_branch(). RDM_PID_DISC_UNIQUE_BRANCH requests may only be sent to the root device, and may only be addressed to all devices on the RDM network. Therefore, the dest_uid and sub_device arguments are not provided for this function.

rdm_ack_t ack;

// Define the address space within which devices will be discovered.
const rdm_disc_unique_branch_t branch = {
  .upper_bound = RDM_UID_MAX,
  .lower_bound = 0  // Set to 0000:00000000
};

rdm_send_disc_unique_branch(DMX_NUM_1, &branch, &ack);
if (ack.size > 0) {
  // Got a response!
  if (ack.type == RDM_RESPONSE_TYPE_ACK) {
    // Only one device was found - print its UID.
    printf("Found the UID " UIDSTR ".\n", UID2STR(ack.src_uid));
  } else if (ack.type == RDM_RESPONSE_TYPE_INVALID) {
    // The checksum was invalid indicating a data collision occurred.
    printf("Multiple devices detected within this address space!\n");

    // Branch the address space here...

  }
} else {
  // No response was received - stop searching this address space.
  printf("No RDM devices were discovered in this address space.\n");
}

RDM_PID_DISC_MUTE and RDM_PID_DISC_UN_MUTE similarly do not support GET nor SET. Devices may be muted and un-muted by using the functions rdm_send_disc_mute() and rdm_send_disc_un_mute(). These requests may be sent to any destination UID but may only be sent to the root device. The dest_uid argument is provided, but sub_device is not. RDM_PID_DISC_MUTE and RDM_PID_DISC_UN_MUTE requests receive the same response data from responders. Therefore rdm_disc_mute_t can be used to store parameter data from responder devices for both requests.

rdm_uid_t dest_uid = RDM_UID_BROADCAST_ALL;
rdm_ack_t ack;

rdm_disc_mute_t mute;  // Stores the response parameter data.

rdm_send_disc_un_mute(DMX_NUM_1, &dest_uid, &mute, &ack);
if (ack.size > 0) {
  /* This code will never run because the RDM controller does not receive a
    response from RDM responders when the destination UID is a broadcast UID.
    Therefore its return value can be ignored and the function can be passed
    NULL instead of an rdm_ack_t pointer or an rdm_disc_mute_t pointer. */
}

RDM Responder

An RDM responder must respond to every non-discovery, non-broadcast packet addressed to it. When a responder receives a RDM_PID_DISC_UNIQUE_BRANCH packet, it must respond to the packet if the responder's UID falls within the request's address space and if the responder is un-muted.

The DMX driver will parse RDM requests and send responses within the rdm_send_response() function. It is therefore required for all RDM responders to receive RDM requests with dmx_receive() or dmx_receive_num() and for responses to be sent with rdm_send_response(). If rdm_send_response() is not called, an RDM response will not be sent. If it is not desired for devices to respond to RDM requests the rdm_send_response() function may be omitted. To ensure responder devices are RDM compliant, users should call rdm_send_response() after receiving every RDM request.

dmx_packet_t packet;
if (dmx_receive(DMX_NUM_1, &packet, DMX_TIMEOUT_TICK)) {
  if (packet.is_rdm) {
    rdm_send_response(DMX_NUM_1);  // Only sends responses to relevant requests
  }
}

RDM imposes strict timing requirements on RDM responders. Responders must typically respond to RDM requests within approximately 3 milliseconds. It is important to call rdm_send_response() quickly after receiving new RDM data. Users are discouraged from calling lengthy functions (such as printing to the terminal) between calls to dmx_receive() and rdm_send_response().

dmx_packet_t packet;
if (dmx_receive(DMX_NUM_1, &packet, DMX_TIMEOUT_TICK)) {

  // Caution! Printing log messages may take too long!
  printf("A DMX packet has been received!");

  if (packet.is_rdm) {
    rdm_send_response(DMX_NUM_1);  // Only sends responses to relevant requests
  }
}

RDM parameters can be registered with the DMX driver using functions prefixed with rdm_register_. The parameter RDM_PID_DMX_START_ADDRESS may therefore be registered with rdm_register_dmx_start_address(). Parameter data is owned and initialized by the DMX driver, but users may set the initial value for some parameters using the arguments to the rdm_register_ functions.

RDM parameters which support GET but do not support SET generally allow users to set the parameter's initial value as the second argument of the rdm_register_ function. The initial value is set the first time the rdm_register_ function is called and then the initial value argument is subsequently ignored and may be left NULL. RDM parameters which support GET and SET will generally be set to a predefined initial value upon registration and must be manually changed using their corresponding rdm_set_ function.

The rdm_register_ functions allow allow users to attach callback functions to PIDs. When a valid request for a parameter is received, the DMX driver will call the callback function after a request is processed. When a callback is called it does not necessarily mean that a response packet has been sent.

void custom_callback(dmx_port_t dmx_num, rdm_header_t *request,
                     rdm_header_t *response, void *context) {
  if (request->pid == RDM_PID_SOFTWARE_VERSION_LABEL) {
    printf("A RDM_PID_SOFTWARE_VERSION_LABEL request was received!\n");
  }
}

The arguments in the callback function reflect the RDM header received in the RDM request and the RDM header sent in the response. The DMX port number and a user context is also provided.

void *context = NULL;  // Context not needed for the above callback 
const char *new_software_label = "My Custom Software";
if (rdm_register_software_version_label(DMX_NUM_1, new_software_label, 
                                        custom_callback, context)) {
  printf("A new software version label has been registered!\n");
}

If a request for a PID that is not registered is received, the DMX driver will automatically respond with an RDM_RESPONSE_NACK_REASON response citing RDM_NR_UNKNOWN_PID. Registering a parameters which is already defined will overwrite the previously registered callback, but not the initial parameter value. Parameters which are registered cannot be unregistered.

The RDM standard defines several parameter responses that are required by all RDM compliant responders. These functions are automatically registered when the DMX driver is installed. This is needed to ensure that RDM responders created with this library are compliant with the RDM specification. The following parameters are required per the RDM specification and are therefore automatically registered when installing the DMX driver:

  • RDM_PID_DISC_UNIQUE_BRANCH
  • RDM_PID_DISC_MUTE
  • RDM_PID_DISC_UN_MUTE
  • RDM_PID_DEVICE_INFO
  • RDM_PID_SOFTWARE_VERSION_LABEL
  • RDM_PID_IDENTIFY_DEVICE
  • RDM_PID_DMX_START_ADDRESS if the device uses a DMX slot.
  • RDM_PID_SUPPORTED_PARAMETERS if supporting parameters beyond the minimum required set.
  • RDM_PID_PARAMETER_DESCRIPTION if supporting manufacturer-specific parameters.

The following parameters are not required by the RDM specification but are automatically registered when installing the DMX driver. Parameters are registered in the following order, if there is parameter space available on the DMX driver:

  • RDM_PID_QUEUED_MESSAGE if specified in the dmx_config_t.
  • RDM_PID_MANUFACTURER_LABEL
  • RDM_PID_DMX_PERSONALITY if the device uses a DMX slot.
  • RDM_PID_DMX_PERSONALITY_DESCRIPTION if the device uses a DMX slot.
  • RDM_PID_DEVICE_LABEL

Parameters which are registered may be get or set using getter and setter functions. Parameters which support the RDM_CC_GET_COMMAND command class have a getter function prefixed prefixed with rdm_get_ and parameters which support RDM_CC_SET_COMMAND have a setter function prefixed with rdm_set_. Setter functions return true if the value was successfully set. Getter functions return the size of the parameter data in bytes or zero on failure.

Some parameters, such as RDM_PID_DMX_START_ADDRESS are copied to non-volatile storage to ensure the values are saved after the ESP32 is power-cycled. The values are copied to non-volatile storage when set using the parameter's rdm_set_ function or after receiving a valid SET request.

uint16_t dmx_start_address;
if (rdm_get_dmx_start_address(DMX_NUM_1, &dmx_start_address) == 0) {
  printf("An error occurred getting the DMX start address.\n");
}

dmx_start_address = 123;
if (!rdm_set_dmx_start_address(DMX_NUM_1, dmx_start_address)) {
  printf("An error occurred setting the DMX start address.\n");
}

Error Handling

On rare occasions, DMX packets can become corrupted. Errors are typically detected upon initially connecting to an active DMX bus but are resolved on receiving the next packet. Errors can be checked by reading the error code from the dmx_packet_t struct. The error types are as follows:

  • DMX_OK indicates data was read successfully.
  • DMX_ERR_TIMEOUT indicates that the driver timed out waiting for a packet.
  • DMX_ERR_IMPROPER_SLOT occurs when the DMX driver detects missing stop bits. If this condition occurs, the driver shall discard the improperly framed slot data and all following slots in the packet. When this error is reported the dmx_packet_t size can be read to determine at which slot the error occurred.
  • DMX_ERR_UART_OVERFLOW occurs when the ESP32 hardware overflows resulting in loss of data.
  • DMX_ERR_NOT_ENOUGH_SLOTS occurs when the number of slots received is less than the number desired in the call to dmx_receive_num().
uint8_t data[DMX_PACKET_SIZE];

int num_slots = DMX_PACKET_SIZE;
dmx_packet_t packet;
while (true) {
  if (dmx_receive_num(DMX_NUM_1, &packet, num_slots, DMX_TIMEOUT_TICK)) {
    switch (packet.err) {
      case DMX_OK:
        printf("Received packet with start code: %02X and size: %i.\n",
          packet.sc, packet.size);
        // Data is OK. Now read the packet into the buffer.
        dmx_read(DMX_NUM_1, data, packet.size);
        break;
      
      case DMX_ERR_TIMEOUT:
        printf("The driver timed out waiting for the packet.\n");
        /* If the provided timeout was less than DMX_TIMEOUT_TICK, it may be
          worthwhile to call dmx_receive() again to see if the packet could be
          received. */
        break;

      case DMX_ERR_IMPROPER_SLOT:
        printf("Received malformed byte at slot %i.\n", packet.size);
        /* A slot in the packet is malformed. Data can be recovered up until 
          packet.size. */
        break;

      case DMX_ERR_UART_OVERFLOW:
        printf("The DMX port overflowed.\n");
        /* The ESP32 UART overflowed. This could occur if the DMX ISR is being
          constantly preempted. */
        break;
      
      case DMX_ERR_NOT_ENOUGH_SLOTS:
        printf("DMX packet size is too small. %i expected, %i received.\n",
               num_slots, packet.size);
        /* The packet was smaller than expected. This only occurs when receiving
          DMX data. This error will not occur when receiving RDM packets.*/
        num_slots = packet.size;  // Update expected packet size
        break;
    }
  } else {
    printf("Lost DMX signal.\n");
    // A packet hasn't been received in DMX_TIMEOUT_TICK ticks.

    // Handle packet timeout here...
  }
}

When reading RDM packets, the packet.err field is copied into the rdm_ack_t type. It should be noted that RDM packet errors are not reported as errors. The err field only reports errors in the processing of raw DMX data. If an invalid RDM packet is received, it will be reported in the type field of rdm_ack_t. Invalid RDM packets will be reported as RDM_RESPONSE_TYPE_INVALID.

Timing Macros

It should be noted that this library does not automatically check for DMX timing errors. This library does provide macros to assist with timing error checking, but it is left to the user to implement such measures. DMX and RDM each have their own timing requirements so macros for checking DMX and RDM are both provided. The following macros can be used to assist with timing error checking.

  • dmx_baud_rate_is_valid() evaluates to true if the baud rate is valid for DMX.
  • dmx_break_len_is_valid() evaluates to true if the DMX break duration is valid.
  • dmx_mab_len_is_valid() evaluates to true if the DMX mark-after-break duration is valid.
  • rdm_baud_rate_is_valid() evaluates to true if the baud rate is valid for RDM.
  • rdm_break_len_is_valid() evaluates to true if the RDM break duration is valid.
  • rdm_mab_len_is_valid() evaluates to true if the RDM mark-after-break duration is valid.

DMX and RDM specify different timing requirements for receivers and transmitters. This library attempts to simplify error checking by combining timing requirements for receiving and transmitting. Therefore there are only the above six timing error checking macros instead of six macros each for receiving and transmitting.

DMX Start Codes

This library offers the following macro constants for use as DMX start codes. More information about each start code can be found in the DMX standards document or in dmx/include/types.h.

  • DMX_SC is the standard DMX null start code.
  • RDM_SC is the standard Remote Device Management start code.
  • DMX_TEXT_SC is the ASCII text start code.
  • DMX_TEST_SC is the test packet start code.
  • DMX_UTF8_SC is the UTF-8 text packet start code.
  • DMX_ORG_ID_SC is the organization/manufacturer ID start code.
  • DMX_SIP_SC is the System Information Packet start code.

Additional macro constants include the following:

  • RDM_SUB_SC is the sub-start code for Remote Device Management. It is the first byte received after the RDM start code.
  • RDM_PREAMBLE is not considered a start code but is often the first byte received in an RDM discovery response packet.
  • RDM_DELIMITER is not considered a start code but is the delimiter byte received at the end of an RDM discovery response preamble.

Some start codes are considered invalid and should not be used in a DMX packet. The validity of the start code can be checked using the macro dmx_start_code_is_valid(). If the start code is valid, this macro will evaluate to true. This library does not automatically check for valid start codes. Such error checking is left to the user to implement.

Additional Considerations

Using Flash or Disabling Cache

When calling functions that read from or write to flash memory on the ESP32, cache is momentarily disabled and certain interrupts are prevented from firing. This can result in data corruption if the DMX driver is configured improperly.

The included Kconfig file in this library instructs the ESP32's build system to place the DMX driver and some of its functions into IRAM. This and other configuration options can be disabled using the ESP32's menuconfig. The use of menuconfig and Kconfig files is not supported when using the Arduino framework.

The DMX driver can be placed in either IRAM or flash memory. The DMX driver and its associated functions are automatically placed in IRAM to reduce the penalty associated with loading code from flash. Placing the DMX driver in flash is acceptable although less performant. When using the Arduino framework, the DMX driver may be placed in flash.

When the driver is not placed in IRAM, functions which disable the cache will also temporarily disable the DMX driver. To prevent data corruption, it is required to gracefully disable the DMX driver before cache is disabled. This can be done with dmx_driver_disable(). The driver can be reenabled with dmx_driver_enable(). The function dmx_driver_is_enabled() can be used to check the status of the DMX driver.

// Disable the DMX driver if it isn't already
if (dmx_driver_is_enabled(DMX_NUM_1)) {
  dmx_driver_disable(DMX_NUM_1);
}

// Read from or write to flash memory (or otherwise disable the cache) here...

dmx_driver_enable(DMX_NUM_1);

Disabling and reenabling the DMX driver before disabling the cache is not required if the DMX driver is placed in IRAM.

Wiring an RS-485 Circuit

DMX is transmitted over RS-485. RS-485 uses twisted-pair, half-duplex, differential signalling to ensure that data packets can be transmitted over large distances. DMX starts as a UART signal which is then driven using an RS-485 transceiver. Because the ESP32 does not have a built-in RS-485 transceiver, it is required for the ESP32 to be wired to a transceiver in most cases.

RS-485 transceivers typically have four data input pins: RO, DI, DE, and /RE. RO is receiver output. It is the pin that the UART RX pin is connected to so that data may be read from other devices to the ESP32. DI is driver input. It is connected to the UART TX pin so that data may be written to other devices from the ESP32. DE is driver input enable. Bringing this pin high enables the input on the DI pin. /RE is receiver output enable. The overline on this pin name indicates that it is active when driven low, and inactive when driven high. Driving this pin low enables the input on the DI pin.

Because DE and /RE enable writing and reading respectively, and because DE is active high and /RE is active low, these pins are often shorted together. In this example, these pins are wired together and are controlled with one pin on the ESP32. This pin is called the enable pin. It can also be referred to as the RTS pin. The example schematic can be seen below.

An example RS-485 circuit

In this example circuit, R1 and R3 are 680 ohms each. Many RS-485 breakout boards set these resistor values to 20k ohm or higher. Such high resistance values are acceptable and should still allow DMX to be written and read.

R2, the 120 ohm resistor, is a terminating resistor. It is required only when using RDM. Including this resistor in schematics can also ensure system stability when connecting long lines of DMX consisting of multiple devices. If it is decided not to include this resistor, DMX-A and DMX-B should not be shorted together.

Many RS-485 chips, such as the Maxim MAX485 are 3.3v tolerant. This means that it can be controlled with the ESP32 without any additional electrical components. Other RS-485 chips may require 5v data to transmit DMX. In this case, it is required to convert the output of the ESP32 to 5v using a logic level converter.

Hardware Specifications

ANSI-ESTA E1.11 DMX512-A specifies that DMX devices be electrically isolated from other devices on the DMX bus. In the event of a power surge, the likely worse-case scenario would mean the failure of the RS-485 circuitry and not the entire DMX device. Some DMX devices may function without isolation, but using non-isolated equipment is not recommended.

To Do

For a list of planned features, see the esp_dmx GitHub Projects page.

Appendix

Command Classes

The command class specifies the action of the RDM message. Responders shall always generate a response to RDM_CC_GET_COMMAND and RDM_CC_SET_COMMAND messages except when the destination UID of the message is a broadcast address. Responders shall not respond to commands sent using broadcast addressing, in order to prevent collisions.

  • RDM_CC_DISC_COMMAND The packet is an RDM discovery command.
  • RDM_CC_DISC_COMMAND_RESPONSE The packet is a response to an RDM discovery command.
  • RDM_CC_GET_COMMAND The packet is an RDM GET request.
  • RDM_CC_GET_COMMAND_RESPONSE The packet is a response to an RDM GET request.
  • RDM_CC_SET_COMMAND The packet is an RDM SET request.
  • RDM_CC_SET_COMMAND_RESPONSE The packet is a response to an RDM SET request.

NACK Reason Codes

The NACK reason defines the reason that the responder is unable to comply with the request.

  • RDM_NR_UNKNOWN_PID The responder cannot comply with the request because the message is not implemented in the responder.
  • RDM_NR_FORMAT_ERROR The responder cannot interpret the request as the controller data was not formatted correctly.
  • RDM_NR_HARDWARE_FAULT The responder cannot comply due to an internal hardware fault.
  • RDM_NR_PROXY_REJECT Proxy is not the RDM line master and cannot comply with the message.
  • RDM_NR_WRITE_PROTECT Set command normally allowed but being blocked currently.
  • RDM_NR_UNSUPPORTED_COMMAND_CLASS Not valid for command class attempted. May be used where get allowed but set is not supported.
  • RDM_NR_DATA_OUT_OF_RANGE Value for given parameter out of allowable range or not supported.
  • RDM_NR_BUFFER_FULL Buffer or queue space currently has no free space to store data.
  • RDM_NR_PACKET_SIZE_UNSUPPORTED Incoming message exceeds buffer capacity.
  • RDM_NR_SUB_DEVICE_OUT_OF_RANGE Sub-device is out of range or unknown.
  • RDM_NR_PROXY_BUFFER_FULL The proxy buffer is full and cannot store any more queued message or status message responses.

Parameter IDs

The table below lists the Parameter IDs specified by the RDM standard. Parameters which support GET or SET are indicated accordingly. Required parameters are automatically registered by the DMX driver if there is enough parameter space on the DMX driver. PIDs which are currently supported by this library are indicated in the "supported" column by the earliest version of this library which supports the PID.

Parameter GET SET Supported Notes
RDM_PID_DISC_UNIQUE_BRANCH v3.1.0 Must be broadcast to all devices. Must be sent to the root sub-device.
RDM_PID_DISC_MUTE v3.1.0 Must be sent to the root sub-device.
RDM_PID_DISC_UN_MUTE v3.1.0 Must be sent to the root sub-device.
RDM_PID_PROXIED_DEVICES ✔️ Must be sent to the root sub-device.
RDM_PID_PROXIED_DEVICE_COUNT ✔️ Must be sent to the root sub-device.
RDM_PID_COMMS_STATUS ✔️ ✔️ Must be sent to the root sub-device.
RDM_PID_QUEUED_MESSAGE ✔️ v4.0.0 Must be sent to the root sub-device.
RDM_PID_STATUS_MESSAGE ✔️ Must be sent to the root sub-device.
RDM_PID_STATUS_ID_DESCRIPTION ✔️ Must be sent to the root sub-device.
RDM_PID_CLEAR_STATUS_ID ✔️
RDM_PID_SUB_DEVICE_STATUS_REPORT_THRESHOLD ✔️ ✔️ Must not be sent to the root sub-device.
RDM_PID_SUPPORTED_PARAMETERS ✔️ v4.0.0 Support required only if supporting parameters beyond the minimum required set.
RDM_PID_PARAMETER_DESCRIPTION ✔️ v4.0.0 Must be sent to the root sub-device. Support required for manufacturer-specific PIDs exposed in RDM_PID_SUPPORTED_PARAMETERS.
RDM_PID_DEVICE_INFO ✔️ v3.1.0
RDM_PID_PRODUCT_DETAIL_ID_LIST ✔️
RDM_PID_DEVICE_MODEL_DESCRIPTION ✔️ v4.1.0
RDM_PID_MANUFACTURER_LABEL ✔️ v4.0.0
RDM_PID_DEVICE_LABEL ✔️ ✔️ v3.1.0
RDM_PID_FACTORY_DEFAULTS ✔️ ✔️
RDM_PID_LANGUAGE_CAPABILITIES ✔️
RDM_PID_LANGUAGE ✔️ ✔️ v4.1.0
RDM_PID_SOFTWARE_VERSION_LABEL ✔️ v3.1.0
RDM_PID_BOOT_SOFTWARE_VERSION_ID ✔️
RDM_PID_BOOT_SOFTWARE_VERSION_LABEL ✔️
RDM_PID_DMX_PERSONALITY ✔️ ✔️
RDM_PID_DMX_PERSONALITY_DESCRIPTION ✔️
RDM_PID_DMX_START_ADDRESS ✔️ ✔️ v3.1.0 Support required if device uses a DMX slot.
RDM_PID_SLOT_INFO ✔️
RDM_PID_SLOT_DESCRIPTION ✔️
RDM_PID_DEFAULT_SLOT_VALUE ✔️
RDM_PID_SENSOR_DEFINITION ✔️ v4.1.0
RDM_PID_SENSOR_VALUE ✔️ ✔️ v4.0.0
RDM_PID_RECORD_SENSORS ✔️ v4.0.0
RDM_PID_DEVICE_HOURS ✔️ ✔️ v4.1.0 Some devices may not support RDM SET requests. Support for SET may be disabled using the ESP-IDF Kconfig.
RDM_PID_LAMP_HOURS ✔️ ✔️ v4.1.0
RDM_PID_LAMP_STRIKES ✔️ ✔️
RDM_PID_LAMP_STATE ✔️ ✔️
RDM_PID_LAMP_ON_MODE ✔️ ✔️
RDM_PID_DEVICE_POWER_CYCLES ✔️ ✔️
RDM_PID_DISPLAY_INVERT ✔️ ✔️
RDM_PID_DISPLAY_LEVEL ✔️ ✔️
RDM_PID_PAN_INVERT ✔️ ✔️
RDM_PID_TILT_INVERT ✔️ ✔️
RDM_PID_PAN_TILT_SWAP ✔️ ✔️
RDM_PID_REAL_TIME_CLOCK ✔️ ✔️
RDM_PID_IDENTIFY_DEVICE ✔️ ✔️ v3.1.0
RDM_PID_RESET_DEVICE ✔️ v4.1.0
RDM_PID_POWER_STATE ✔️ ✔️
RDM_PID_PERFORM_SELFTEST ✔️ ✔️
RDM_PID_SELF_TEST_DESCRIPTION ✔️
RDM_PID_CAPTURE_PRESET ✔️
RDM_PID_PRESET_PLAYBACK ✔️ ✔️

Product Categories

Devices shall report a product category based on the product's primary function.

  • RDM_PRODUCT_CATEGORY_NOT_DECLARED The product category is not declared.
  • RDM_PRODUCT_CATEGORY_FIXTURE The product is a fixture intended to create illumination.
  • RDM_PRODUCT_CATEGORY_FIXTURE_ACCESSORY The product is an add-on to a fixture or projector.
  • RDM_PRODUCT_CATEGORY_PROJECTORThe product is a light source capable of producing realistic images from another media.
  • RDM_PRODUCT_CATEGORY_ATMOSPHERIC The product creates atmospheric effects such as haze, fog, or pyrotechnics.
  • RDM_PRODUCT_CATEGORY_DIMMER The product is for intensity control, specifically dimming equipment.
  • RDM_PRODUCT_CATEGORY_POWER The product is for power control, other than dimming equipment.
  • RDM_PRODUCT_CATEGORY_SCENIC The product is a scenic device unrelated to lighting equipment.
  • RDM_PRODUCT_CATEGORY_DATA The product is a DMX converter, interface, or otherwise part of DMX infrastructure.
  • RDM_PRODUCT_CATEGORY_AV The product is audio-visual equipment.
  • RDM_PRODUCT_CATEGORY_MONITOR The product is monitoring equipment.
  • RDM_PRODUCT_CATEGORY_CONTROL The product is a controller or backup device.
  • RDM_PRODUCT_CATEGORY_TEST The product is test equipment.
  • RDM_PRODUCT_CATEGORY_OTHER The product isn't described by any of the other product categories.

Response Types

Responding devices shall respond to requests only if the request was a non-broadcast request. Responding devices may respond to requests with the following response types:

  • RDM_RESPONSE_TYPE_ACK indicates that the responder has correctly received the controller message and is acting upon the request.
  • RDM_RESPONSE_TYPE_ACK_OVERFLOW indicates that the responder has correctly received the controller message and is acting upon the request, but there is more response data available than will fit in a single response packet. To receive the remaining information, controllers are able to send repeated requests to the same PID until the remaining information can fit in a single message.
  • RDM_RESPONSE_TYPE_ACK_TIMER indicates that the responder is unable to supply the requested GET information or SET confirmation within the required response time. When sending this response, responding devices include an estimated response time that must elapse before the responder can provide the required information.
  • RDM_RESPONSE_TYPE_NACK_REASON indicates that the responder is unable to reply with the requested GET information or unable to process the specified SET command. Responding devices must include a NACK reason code in their response. NACK reason codes are enumerated in the appendix.
  • RDM_RESPONSE_TYPE_NONE indicates that no response was received.
  • RDM_RESPONSE_TYPE_INVALID indicates that a response was received, but the response was invalid. This can occur for several reasons including an invalid checksum, or an invalid packet format.

esp_dmx's People

Contributors

arneboe avatar chaosloth avatar someweisguy avatar userbogd avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

esp_dmx's Issues

ESP32-WROOM-32 together with MAX485 module

Hi, first of all, this seems to be an up to date libary which is very good, I couldn't another one.
The Problem is that I don't know how to wire and program it, because I am very new in that scene, so I would ask politely if someone could help me with that.
Thanks!

arduino compiler error - type uart_sclk_t not found

Using latest ardunio 1.8.15 and esp1.0.6 code
compiler error, type uart_sclk_t not found

readded your prevoius verson code to dmx_types.h file.

Now compiles, hopefully will be testing DMX output to uart2 soon.


// TODO: is this code defined somewhere in Arduino?
#ifdef ARDUINO
typedef enum {
UART_SCLK_APB = 0x0, // UART source clock from APB
UART_SCLK_RTC = 0x1, // UART source clock from RTC
UART_SCLK_XTAL = 0x2, // UART source clock from XTAL
UART_SCLK_REF_TICK = 0x3, // UART source clock from REF_TICK
} uart_sclk_t;
#endif

two driver

Thanks for this library.

It works well with two drivers also (for TRANSMITTING and RECIEVING).
I just made below adjustment for initialization.
byte data[DMX_PACKET_SIZE] = {}; (add "= {}"; from reference code.)

By the way, is this two driver "unusual case" ? and better not to do as suggested on README ?
It is pretty nice if two driver or more can be used.

I read through README, I found below wording.

If transmitting and receiving data simultaneously is desired, the user can install two drivers on two UART ports.
It should be noted that this is an unusual use case. This library is not meant to act as a DMX optoisolator or splitter.

Random read input

Hello,

first of all thank you for that nice library :) and i hope you can maybe help me with my problem.

I am trying to read the dmx packets from a dmxcontroller (eurolight LED Color Chief)

My current setup is shown below

dmx_input_bb

I am using the DMXRead example and modified it a little bit. The good thing is that there is some input coming to the esp32 :) the bad thing is that it is random.

My setup function looks like this

void setup()
{
  /* Start the serial connection back to the computer so that we can log
    messages to the Serial Monitor. Lets set the baud rate to 115200. */
  Serial.begin(115200);
  Serial.println("Start Setup");

  readDMXTimer.setInterval(1000); // every second
  readDMXTimer.setCallback(checkDMXInput);

  /* Configure the DMX hardware to the default DMX settings and tell the DMX
    driver which hardware pins we are using. */
  dmx_config_t dmxConfig = DMX_DEFAULT_CONFIG;
  // const dmx_config_t dmxConfig = {
  //     .baud_rate = 250000, // 250000 typical baud rate
  //     .break_num = 45,     // 90, //45,     // 180us
  //     .idle_num = 5        // 5 //10         // 20us
  // };

  dmx_param_config(dmxPort, &dmxConfig);
  dmx_set_pin(dmxPort, transmitPin, receivePin, enablePin);

  /* Now we can install the DMX driver! We'll tell it which DMX port to use and
    how big our DMX packet is expected to be. We'll also pass to it our queue
    handle, some information about how many events we should store in our queue,
    and some interrupt priority information. Both the queue size and interrupt
    priority can be set to 1. */
  int queueSize = 10;        // 1;
  int interruptPriority = 1;
  dmx_driver_install(dmxPort, DMX_MAX_PACKET_SIZE, queueSize, &queue,
                     interruptPriority);

  Serial.println("End Setup");
}

I changed the queue size to 10 and i tried to use a custom dmx_config but switched it back to the default one. Also i use a timer to call the read function every second.

The implementation of the read function is

void checkDMXInput()
{
  /* We need a place to store information about the DMX packet we receive. We
    will use a dmx_event_t to store that packet information.  */
  dmx_event_t event;

  /* And now we wait! The DMX standard defines the amount of time until DMX
    officially times out. That amount of time is converted into ESP32 clock ticks
    using the constant `DMX_RX_PACKET_TOUT_TICK`. If it takes longer than that
    amount of time to receive data, this if statement will evaluate to false. */
  if (xQueueReceive(queue, &packet, DMX_RX_PACKET_TOUT_TICK))
  {

    /* If this code gets called, it means we've recieved DMX data! */
    /* We should check to make sure that there weren't any DMX errors. */
    switch (event.status)
    {
    case DMX_OK:
         printf("Received packet with start code: %02X and size: %i\n",
                event.start_code, event.size);
         dmx_read_packet(DMX_NUM_2, data, event.size);
      break;
    case DMX_ERR_IMPROPER_SLOT:
      printf("Received malformed byte at slot %i\n", event.size);
      // a slot in the packet is malformed - possibly a glitch due to the
      //  XLR connector? will need some more investigation
      // data can be recovered up until event.size
      break;

    case DMX_ERR_PACKET_SIZE:
      printf("Packet size %i is invalid\n", event.size);
      // the host DMX device is sending a bigger packet than it should
      // data may be recoverable but something went very wrong to get here
      break;

    case DMX_ERR_BUFFER_SIZE:
      printf("User DMX buffer is too small - received %i slots\n",
             event.size);
      // whoops - our buffer isn't big enough
      // this code will not run if buffer size is set to DMX_MAX_PACKET_SIZE
      break;
    case DMX_ERR_DATA_OVERFLOW:
      printf("Data could not be processed in time\n");
      // the UART FIFO overflowed
      // this could occur if the interrupt mask is misconfigured or if the
      //  DMX ISR is constantly preempted
      break;
    }
  }
  else if (dmxIsConnected)
  {
    Serial.println("DMX timed out !!!");
    Serial.println(event.status);
  }
  
  yield();
}

My output is

Start Setup
End Setup
Received packet with start code: FC and size: 513
Received packet with start code: C0 and size: 2
Received packet with start code: FC and size: 513
Received packet with start code: 00 and size: 2
Received packet with start code: FC and size: 513
Received packet with start code: 00 and size: 2
Received packet with start code: FC and size: 513
Received packet with start code: 00 and size: 1
Packet size 0 is invalid
Received packet with start code: FC and size: 513
Received packet with start code: 00 and size: 1
Received packet with start code: FC and size: 513
Received packet with start code: FE and size: 513
Packet size 4658 is invalid
Packet size 4652 is invalid
Packet size 1033 is invalid
Packet size 1033 is invalid
Packet size 1033 is invalid
Packet size 1033 is invalid
Received packet with start code: FC and size: 513
Received packet with start code: FE and size: 513
Received packet with start code: 00 and size: 513
Received packet with start code: 00 and size: 8
Packet size 1032 is invalid
Received packet with start code: FC and size: 513
Received packet with start code: FC and size: 513
Received packet with start code: FC and size: 513
Received packet with start code: FC and size: 513
Received packet with start code: FC and size: 513

Do you have maybe an idea what I am doing wrong?

Thank you in advance

VSCode and Platformio

Great work Mitch, thank you.
This is not an issue with the current code and IDE, but related to another IDE. I tried to run this project from VSCode with Platformio and I get some errors for missing datatypes (like uart_sclk_t). In your instructions you are clear about using the latest framework. Because I am using the arduino framework I cannot use the latest espidf framework which is certainly causing the missing datatype errors. Have you used VSCode with Platformio successfully with your project?

Single Core Issues?

Hey!

For context:
The board I am using: ES32-C3-WROOM-02 (Single core rather than dual-core)
IDE: Arduino

After loading the DMXWrite Example, and adapting the pins to TX=20, RX=21, Enable=0 (I believe this those are the ports for UART port 1 on this board?) -- and uploading, the ESP32 is consistently reboots, and gives this serial message:

ESP-ROM:esp32c3-api1-20210207 Build:Feb 7 2021 rst:0x3 (RTC_SW_SYS_RST),boot:0xc (SPI_FAST_FLASH_BOOT) Saved PC:0x40381a00 SPIWP:0xee mode:DIO, clock div:1 load:0x3fcd5810,len:0x438 load:0x403cc710,len:0x91c load:0x403ce710,len:0x25b0 entry 0x403cc710

Other code/different library examples upload and run fine

Does the library (in the default configuration) require timers/interrupts that aren't within this board/only in 2 core varieties? Or am I missing something obvious?

Any ideas/info would be great!
Thanks!

ESP32 core 2.0.1-RC1, UART0 does not work

I have tested your DMX library on a ESP32 with core version 2.0.1-RC1 and it seems to work sending DMX packets on UART2 using your example code, expect UART0 stops working. Below is the simplest code that fails. If you comment out the #include ,esp_dmx.h> the below example will send "hello", if the library esp_dmx.h is included the serial port stops working. I do get the initial ESP32 core bootup info and then nothing after that.

#include <esp_dmx.h>
void setup() {
Serial.begin(115200);
Serial.println("setup serial");
}

void loop() {
Serial.println("hello");
delay(1000);
}

Compilation problem in platformIO

Hi, your library look fine !
I am curently testing it, no problem in the arduino IDE but in platformIO I have this error messages :
.pio/libdeps/esp32doit-devkit-v1/esp_dmx/src/esp_dmx.h:12:28: fatal error: hal/gpio_types.h: No such file or directory
.pio/libdeps/esp32doit-devkit-v1/esp_dmx/src/dmx_types.h:19:3: error: unknown type name 'uart_sclk_t'

Supporting esp-idf 5.x+

The current version does not build on esp-idf v5.0 (and 5.1). Release v5.0 is out of beta and is currently the most recent supported version for all chips.

I've made some small changes to release/2.0 to get it successfully building over at stonegray@e340115 and assume many of these changes can be ported forward to the master version. This isn't PR-ready as I don't think it will build on IDF v4.4, which is currently the default.

I'm not sure I've implemented sclk_freq correctly, and if this can properly use the reference clock instead of the default. Some abstraction to ensure its not possible to read or write the baud etc. with the wrong frequency might be a good idea.

Espressif has a migration guide for moving from v4.4 to v5 (possibly incomplete, I didn't see the changes to uart_hal_set_baudrate and friends there)

adding ESP8266 support

Hi quick question what would it take to add ESP8266 support?
Would love to use this on a project to use an ESP8266 as DMX reciever

Unable to build

I cloned this into the components folder of my project and attempted to build. I am getting an error
../components/esp_dmx/include/dmx_caps.h:7:10: fatal error: soc/uart_caps.h: No such file or directory

I am unable to find a file soc/uart_caps.h either on my local install of ESP-IDF or on Espressifs Github. Does this library require a specific version of ESP-IDF? Am I missing a setup step? Do I need to create this file?

For context:
I am attempting to get DMX up and running on an existing project with other functionalities. I was able to build and flash before attempting to add this library. I am using ESP-IDF version v4.4-dev-1404-gc13afea63 on Linux.

"unknown type name 'uart_sclk_t' " using EPS32 develop branch

Hallo using the development branch of ESP32

platform = https://github.com/platformio/platform-espressif32.git#develop

I get these errors:

[{
	"resource": "/c:/Pcloud/Projects/DMX2Visca/DMX2Visca/.pio/libdeps/esp32-poe-iso/esp_dmx/src/dmx/hal.h",
	"owner": "cpp",
	"severity": 8,
	"message": "unknown type name 'uart_sclk_t'",
	"startLineNumber": 224,
	"startColumn": 58,
	"endLineNumber": 224,
	"endColumn": 58
},{
	"resource": "/c:/Pcloud/Projects/DMX2Visca/DMX2Visca/.pio/libdeps/esp32-poe-iso/esp_dmx/src/dmx/hal.h",
	"owner": "C/C++",
	"code": "20",
	"severity": 8,
	"message": "identifier \"uart_sclk_t\" is undefined",
	"source": "C/C++",
	"startLineNumber": 224,
	"startColumn": 58,
	"endLineNumber": 224,
	"endColumn": 69
},{
	"resource": "/c:/Pcloud/Projects/DMX2Visca/DMX2Visca/.pio/libdeps/esp32-poe-iso/esp_dmx/src/dmx/hal.h",
	"owner": "C/C++",
	"code": "20",
	"severity": 8,
	"message": "identifier \"UART_SCLK_APB\" is undefined",
	"source": "C/C++",
	"startLineNumber": 225,
	"startColumn": 39,
	"endLineNumber": 225,
	"endColumn": 52
},{
	"resource": "/c:/Pcloud/Projects/DMX2Visca/DMX2Visca/.pio/libdeps/esp32-poe-iso/esp_dmx/src/dmx/hal.h",
	"owner": "cpp",
	"severity": 8,
	"message": "unknown type name 'uart_sclk_t'",
	"startLineNumber": 268,
	"startColumn": 15,
	"endLineNumber": 268,
	"endColumn": 15
},{
	"resource": "/c:/Pcloud/Projects/DMX2Visca/DMX2Visca/.pio/libdeps/esp32-poe-iso/esp_dmx/src/dmx/hal.h",
	"owner": "C/C++",
	"code": "20",
	"severity": 8,
	"message": "identifier \"uart_sclk_t\" is undefined",
	"source": "C/C++",
	"startLineNumber": 268,
	"startColumn": 15,
	"endLineNumber": 268,
	"endColumn": 26
},{
	"resource": "/c:/Pcloud/Projects/DMX2Visca/DMX2Visca/.pio/libdeps/esp32-poe-iso/esp_dmx/src/dmx/hal.h",
	"owner": "cpp",
	"severity": 8,
	"message": "'UART_SCLK_APB' undeclared (first use in this function)",
	"startLineNumber": 269,
	"startColumn": 42,
	"endLineNumber": 269,
	"endColumn": 42
},{
	"resource": "/c:/Pcloud/Projects/DMX2Visca/DMX2Visca/.pio/libdeps/esp32-poe-iso/esp_dmx/src/dmx/hal.h",
	"owner": "C/C++",
	"code": "20",
	"severity": 8,
	"message": "identifier \"UART_SCLK_APB\" is undefined",
	"source": "C/C++",
	"startLineNumber": 269,
	"startColumn": 42,
	"endLineNumber": 269,
	"endColumn": 55
},{
	"resource": "/c:/Pcloud/Projects/DMX2Visca/DMX2Visca/.pio/libdeps/esp32-poe-iso/esp_dmx/src/dmx/hal.h",
	"owner": "cpp",
	"severity": 8,
	"message": "'UART_SCLK_REF_TICK' undeclared (first use in this function)",
	"startLineNumber": 269,
	"startColumn": 58,
	"endLineNumber": 269,
	"endColumn": 58
},{
	"resource": "/c:/Pcloud/Projects/DMX2Visca/DMX2Visca/.pio/libdeps/esp32-poe-iso/esp_dmx/src/dmx/hal.h",
	"owner": "C/C++",
	"code": "20",
	"severity": 8,
	"message": "identifier \"UART_SCLK_REF_TICK\" is undefined",
	"source": "C/C++",
	"startLineNumber": 269,
	"startColumn": 58,
	"endLineNumber": 269,
	"endColumn": 76
},{
	"resource": "/c:/Pcloud/Projects/DMX2Visca/DMX2Visca/.pio/libdeps/esp32-poe-iso/esp_dmx/src/dmx/hal.h",
	"owner": "cpp",
	"severity": 4,
	"message": "\"REG_UART_BASE\" redefined",
	"startLineNumber": 11,
	"startColumn": 1,
	"endLineNumber": 11,
	"endColumn": 1
},{
	"resource": "/c:/Pcloud/Projects/DMX2Visca/DMX2Visca/.pio/libdeps/esp32-poe-iso/esp_dmx/src/dmx/hal.h",
	"owner": "cpp",
	"severity": 4,
	"message": "\"REG_UART_AHB_BASE\" redefined",
	"startLineNumber": 12,
	"startColumn": 1,
	"endLineNumber": 12,
	"endColumn": 1
},{
	"resource": "/c:/Pcloud/Projects/DMX2Visca/DMX2Visca/.pio/libdeps/esp32-poe-iso/esp_dmx/src/dmx/hal.h",
	"owner": "cpp",
	"severity": 4,
	"message": "control reaches end of non-void function [-Wreturn-type]",
	"startLineNumber": 270,
	"startColumn": 1,
	"endLineNumber": 270,
	"endColumn": 1
}]

Does not recieve data with less than 512 channels

Hello again. I've managed to upload the library but stumbed upon a new complication. It seems that my esp32 can't see dmx data with less than 512 channels. I tried 54Ch and 16Ch DMX Controllers, esp32 read nothing on both. But my DIY 512Ch controller works fine and esp32 properly reads 513 bytes. I checked the 54Ch serial on a oscilloscope, it has 44us frame, about 60us break, 20us MAB and almost 8ms between packets.

The code:

#include "esp_dmx.h"
#include "esp_log.h"
#include "esp_system.h"
#include "freertos/task.h"

#define RX_PIN 18
#define TX_PIN 17
#define EN_PIN 38

uint8_t dmx_data[DMX_PACKET_SIZE] = {};

void app_main() { 
	const dmx_port_t dmx_num = DMX_NUM_2;
	ESP_ERROR_CHECK(dmx_set_pin(dmx_num, TX_PIN, RX_PIN, EN_PIN));
	ESP_ERROR_CHECK(dmx_driver_install(dmx_num, DMX_DEFAULT_INTR_FLAGS));
	dmx_packet_t packet;
	TickType_t last_update = xTaskGetTickCount();
	while(true) {
		if(dmx_receive(dmx_num, &packet, DMX_TIMEOUT_TICK)) {
			const TickType_t now = xTaskGetTickCount();	
			if (now - last_update >= pdMS_TO_TICKS(1000)) {
				dmx_read(dmx_num, dmx_data, DMX_PACKET_SIZE);
				ESP_LOGI("DMX", "Start code: %02x, Size: %i", packet.sc, packet.size);
				ESP_LOG_BUFFER_HEX("DMX", dmx_data, 16);  // Log first 16 bytes
				last_update = now;
			}
		}
	}
}

I'm using:
ESP32-S3
PlatformIO 3.0.0
ESP-IDF 5.0.0
esp_dmx 3.0.2-beta

Very weird issue not always sending to max

hello and thank you for your library

i am struggling 3 days now to make dmx work with esp32

i succeded with esp32 1.0.6 - but not with 2.0

my problem is that it send dmx for 1 second and then stops
if i send another packet on the same bus - then it is correct and all channels work for a secod

i do not know where to search from - i tried almost every ibrary on dmx on the web

very frustrating

Wont Work

im trying to compile even the example proyects, CONFIG_DMX_MAX_PERSONALITIES It's not declared anywhere so it wont compile

Using Arduino IDE on Fedora Linux.

Send basic data

Hi, the library is amazing, great job! How can I send the value 255 through channel 2? Thank you!

How to use with Platform.IO / Arduino Framework

Hi, I am trying for hours to get this to work with Platform.IO and the Arduino Framework.
Do you know how I could mix this esp-idf component withing my existing Arduino project or do you know if there is a platform.io lib for this repo?

dmx_receive blocking

If you are trying to write code that optionally expects DMX data, how can you prevent the code from being blocked while waiting for DMX timeout?

Most DMX libraries I've worked with before allow you simple

void loop() {
Dmx.loop();
if (Dmx.newFrame()) {
}
}

Incompatibility with LittleFS (Cache disabled but cached memory region accessed)

I find that if I try to write a file with LitteFS whilst received DMX I get a Guru Meditation Error.

The backtrace is corrupted so I can't fully see exactly where this is happening :

Guru Meditation Error: Core  1 panic'ed (Cache disabled but cached memory region accessed). 

Core  1 register dump:
PC      : 0x400e692c  PS      : 0x00060035  A0      : 0x40086abc  A1      : 0x3ffbf22c  
A2      : 0x00000078  A3      : 0x3ffbdcc0  A4      : 0x00000020  A5      : 0x3ffbdcbc  
A6      : 0x3ffbc2f8  A7      : 0x00000001  A8      : 0x8008165c  A9      : 0x00000078  
A10     : 0x3ffbdcbc  A11     : 0x3ffbc45c  A12     : 0x3ffbf244  A13     : 0x3ffbdcbc  
A14     : 0x3ffc4608  A15     : 0x84022044  SAR     : 0x00000017  EXCCAUSE: 0x00000007  
EXCVADDR: 0x00000000  LBEG    : 0x400865a9  LEND    : 0x400865b1  LCOUNT  : 0x00000027  


Backtrace:0x400e6929:0x3ffbf22c |<-CORRUPTED
  #0  0x400e6929:0x3ffbf22c in uart_ll_write_txfifo at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/hal/esp32/include/hal/uart_ll.h:235
      (inlined by) uart_hal_write_txfifo at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/hal/uart_hal_iram.c:40




ELF file SHA256: 0000000000000000

Rebooting...

I am using a ESP WROVER-E and have the DMX being received on UART1 (But I have also tried it on UART2 and there is no difference).

I'm using the Arduino Framework which reports to be using ESP-IDF v4.4.1-1-gb8050b365e

FORCE_INLINE_ATTR does not name a type

Getting this error when trying to use use with WLED as I'm trying to revamp the DMX support to use your more advanced library rather than sparkfun/SparkFunDMX

Crash when WIFI enabled but not connected and DMX messages coming in

Issue

Random but consistent crash when processing DMX messages when not connected to WIFI.

When there are no incoming DMX messages then no crash occurs and the ESP continues to attempt to connect in the background.

Similarly, if WIFI is connected and DMX messages do or do not arrive then no crash occurs.

Likewise if WIFI is connected and DMX message are being received, and then WIFI disconnects then the crash does occur.

I've tried multiple hardware (various ESP boards) along with different incoming DMX message framerates, I can get this to occur in as low as 2 fps. Although it occurs more frequently at high frame rates - I suspect this is a function of the ISR being called.

Steps to reproduce:

  1. Using esp_dmx v1.1.3
  2. Configure WIFI to connect to an AP (i.e. ESP has credentials stored)
  3. AP is offline or not in range
  4. Stream incoming DMX messages (UART 1 in my case)
  5. After between 0 - 40 seconds CPU will crash

Example code

As of writing the current master is the same as this tagged release (which replicates the issue) https://github.com/chaosloth/Connotron_DMX_Gateway/releases/tag/v0.0.2-beta

Notes

I've seen discussion on the intewebs regarding Cache disabled but cached memory region accessed exceptions with the proposed fix as marking ISR functions with IRAM_ATTR. I see that the ISR functions are indeed annotated and that the function called out in the exception below is an inline function which should also inherit this.

Exception - Example 1

Decoded

ESP exception decoder points to dmx_hal_write_txinfo function, however I have also seen the exception point to dmx_hal_get_rxfifo_len too. The above decodes as follows:

PC: 0x400d9836: dmx_hal_write_txfifo at /Users/cc/Documents/Arduino/libraries/esp_dmx-1.1.3/src/dmx/hal.h line 369
EXCVADDR: 0x00000000

Decoding stack results
0x400d9833: dmx_hal_write_txfifo at /Users/cc/Documents/Arduino/libraries/esp_dmx-1.1.3/src/dmx/hal.h line 367

Dump

23:40:39.489 -> 
23:40:39.489 -> Core  1 register dump:
23:40:39.489 -> PC      : 0x400d9836  PS      : 0x00060035  A0      : 0x80081322  A1      : 0x3ffbf16c  
23:40:39.575 -> A2      : 0x00000000  A3      : 0x3ffb2938  A4      : 0x3ff50000  A5      : 0x3ffbf1b4  
23:40:39.575 -> A6      : 0x3ffc4e68  A7      : 0x84000254  A8      : 0x0000001c  A9      : 0x00000078  
23:40:39.575 -> A10     : 0x6002e000  A11     : 0x00000000  A12     : 0x8008ed14  A13     : 0x3ffbaad0  
23:40:39.575 -> A14     : 0x00000003  A15     : 0x00060023  SAR     : 0x00000000  EXCCAUSE: 0x00000007  
23:40:39.575 -> EXCVADDR: 0x00000000  LBEG    : 0x40084749  LEND    : 0x40084751  LCOUNT  : 0x00000026  
23:40:39.575 -> 
23:40:39.575 -> 
23:40:39.575 -> Backtrace:0x400d9833:0x3ffbf16c |<-CORRUPTED
23:40:39.575 -> 
23:40:39.575 -> 
23:40:39.575 -> 
23:40:39.575 -> 
23:40:39.575 -> ELF file SHA256: 0000000000000000
23:40:39.575 -> 
23:40:39.575 -> Rebooting...
23:40:39.575 -> ets Jun  8 2016 00:22:57
23:40:39.575 -> 
23:40:39.575 -> rst:0x3 (SW_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
23:40:39.575 -> configsip: 0, SPIWP:0xee
23:40:39.575 -> clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
23:40:39.575 -> mode:DIO, clock div:1
23:40:39.575 -> load:0x3fff0030,len:1324
23:40:39.575 -> ho 0 tail 12 room 4
23:40:39.575 -> load:0x40078000,len:13508
23:40:39.575 -> load:0x40080400,len:3604
23:40:39.575 -> entry 0x400805f0
23:40:39.822 -> [     3][E][WiFiGeneric.cpp:586] wifiLow⸮f⸮⸮⸮%⸮⸮ѡ): esp_wifi_init 4353
23:40:39.861 -> [     5][D][esp32-hal-cpu.c:211] setCpuFrequencyMhz(): PLL: 480 / 2 = 240 Mhz, APB: 80000000 Hz

Exception - Example 2

Decoded

This exception occured after reboot, pointing to a slightly different part of the code but similar in that it occures in the HAL code.

PC: 0x4014ffe4: dmx_hal_get_rxfifo_len at /Users/cc/Documents/Arduino/libraries/esp_dmx-1.1.3/src/dmx/hal.h line 64
EXCVADDR: 0x00000000

Decoding stack results
0x4014ffe1: WiFiUDP::remotePort() at /Users/cc/Library/Arduino15/packages/esp32/hardware/esp32/2.0.2/libraries/WiFi/src/WiFiUdp.cpp line 280

Dump

00:22:24.988 -> rst:0x3 (SW_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
00:22:24.988 -> configsip: 0, SPIWP:0xee
00:22:24.988 -> clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
00:22:24.988 -> mode:DIO, clock div:1
00:22:24.988 -> load:0x3fff0030,len:1324
00:22:24.988 -> ho 0 tail 12 room 4
00:22:24.988 -> load:0x40078000,len:13508
00:22:24.988 -> load:0x40080400,len:3604
00:22:24.988 -> entry 0x400805f0
00:22:25.204 -> [     3][E][WiFiGeneric.cpp:586] wifiLow⸮ff⸮⸮%⸮⸮ѡ): esp_wifi_init 4353
00:22:25.242 -> [     5][D][esp32-hal-cpu.c:211] setCpuFrequencyMhz(): PLL: 480 / 2 = 240 Mhz, APB: 80000000 Hz
00:22:25.427 -> 
00:22:25.427 -> Starting ConnoDMX Gateway on ESP32_DEV WIFI Manager: ESPAsync_WiFiManager v1.12.0
00:22:25.469 -> Normal mode. Entering WIFI_STA mode
00:22:25.469 -> [   247][D][WiFiGeneric.cpp:831] _eventCallback(): Arduino Event: 0 - WIFI_READY
00:22:25.537 -> [   338][D][WiFiGeneric.cpp:831] _eventCallback(): Arduino Event: 2 - STA_START
00:22:25.540 -> Guru Meditation Error: Core  1 panic'ed (Cache disabled but cached memory region accessed). 
00:22:25.586 -> 
00:22:25.586 -> Core  1 register dump:
00:22:25.586 -> PC      : 0x4014ffe4  PS      : 0x00060035  A0      : 0x40084c5c  A1      : 0x3ffbf18c  
00:22:25.586 -> A2      : 0x3ffb90bc  A3      : 0x3ffbdcc8  A4      : 0x00000000  A5      : 0x3ffbdcc4  
00:22:25.586 -> A6      : 0x00000001  A7      : 0x3ffbdcc4  A8      : 0x800813c8  A9      : 0x3ffbf16c  
00:22:25.586 -> A10     : 0x3ff50000  A11     : 0x0001819d  A12     : 0x800840d0  A13     : 0x3ffbaae0  
00:22:25.586 -> A14     : 0x3ffc4e68  A15     : 0x84000254  SAR     : 0x00000000  EXCCAUSE: 0x00000007  
00:22:25.659 -> EXCVADDR: 0x00000000  LBEG    : 0x40084749  LEND    : 0x40084751  LCOUNT  : 0x00000027  
00:22:25.659 -> 
00:22:25.659 -> 
00:22:25.659 -> Backtrace:0x4014ffe1:0x3ffbf18c |<-CORRUPTED

Exception - Example 3

Decoded

PC: 0x4015004c: dmx_hal_get_rxfifo_len at /Users/cc/Documents/Arduino/libraries/esp_dmx-1.1.3/src/dmx/hal.h line 74
EXCVADDR: 0x00000000

Decoding stack results
0x40150049: dmx_hal_get_rxfifo_len at /Users/cc/Documents/Arduino/libraries/esp_dmx-1.1.3/src/dmx/hal.h line 74

Dump

00:23:05.840 -> Guru Meditation Error: Core  1 panic'ed (Cache disabled but cached memory region accessed). 
00:23:05.840 -> 
00:23:05.840 -> Core  1 register dump:
00:23:05.840 -> PC      : 0x4015004c  PS      : 0x00060035  A0      : 0x800813f5  A1      : 0x3ffbf15c  
00:23:05.840 -> A2      : 0x000002bb  A3      : 0x00000078  A4      : 0x00000078  A5      : 0x00000201  
00:23:05.840 -> A6      : 0x3ffb91a0  A7      : 0x84000254  A8      : 0x00000243  A9      : 0x00000080  
00:23:05.878 -> A10     : 0x01002b8f  A11     : 0x00000000  A12     : 0x8008ed14  A13     : 0x3ffbaad0  
00:23:05.878 -> A14     : 0x00000003  A15     : 0x00060223  SAR     : 0x00000000  EXCCAUSE: 0x00000007  
00:23:05.878 -> EXCVADDR: 0x00000000  LBEG    : 0x40084749  LEND    : 0x40084751  LCOUNT  : 0x00000027  
00:23:05.878 -> 
00:23:05.878 -> 
00:23:05.878 -> Backtrace:0x40150049:0x3ffbf15c |<-CORRUPTED
00:23:05.878 -> 
00:23:05.878 -> 
00:23:05.878 -> 
00:23:05.878 -> 
00:23:05.878 -> ELF file SHA256: 0000000000000000
00:23:05.912 -> 
00:23:05.912 -> Rebooting...
00:23:05.912 -> ets Jun  8 2016 00:22:57
00:23:05.912 -> 
00:23:05.912 -> rst:0x3 (SW_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
00:23:05.912 -> configsip: 0, SPIWP:0xee
00:23:05.912 -> clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
00:23:05.912 -> mode:DIO, clock div:1
00:23:05.912 -> load:0x3fff0030,len:1324
00:23:05.912 -> ho 0 tail 12 room 4
00:23:05.912 -> load:0x40078000,len:13508
00:23:05.912 -> load:0x40080400,len:3604
00:23:05.912 -> entry 0x400805f0

Serial Monitor output is only the character "null"

Hi.

First of all many thanks for this great library you provided. It looks really awesome!

I tried to run your "DMXWrite" example on my machine but unfortuantely the serial monitor output is just garbage. I use VSCodium and PlatformIO and your instructions in the README were very helpful.
I checked that I use the right monitor speed (115200) but still I only get the "null" character (U+2400) back.
I traced it back to line 43 "dmx_param_config(dmxPort, &dmxConfig);" as if I remove it (and all of the depending code) the problem does not appear anymore. Not sure if this is helpful as I'm also overwhelmed with the internal clockworks of your library.

Any help would be very appreciated.

Thanks, quirsh

Using esp_dmx for reading and writing at the same time

I tried to set up a device that patches DMX data.
So i tried receiving and sending at the same time (with two different RS485 converters, one for rx and one for tx).
But it does not work very well in parallel. DMX read errors come up frequently.
I combined the read and write examples in some way.
But what is the recommended method to receive data on one DMX input and send it (with changes) to another DMX output?

very slow execution of the rest of the code

Hi, sorry to disturb you again !
is it possible to bypass this verification :
if (xQueueReceive(queue, &packet, DMX_PACKET_TIMEOUT_TICK)
"The macro DMX_PACKET_TIMEOUT_TICK can be used to block the task until a packet is received or a DMX timeout occurs." So I don't want to block the execution of the rest of my code !

does not (seem to) recieve data

I can't seem t recieve DMX data. I'm using a DOIT EPS32 DEVKIT V1 and a RS485 to TTL module (https://www.elecrow.com/uart-ttl-to-rs485-twoway-converter-p-1545.html)
The module RX pin is connected to GPIO 16 and the RX LED is flickering when a DMX signal is applied.
The DMX signal is provided by a simple DMX controller (Elation SDC-16).
What I've tried:

  • both the 3.0 and 2.0.2 release of the library with the standard DMXread sketch. Both don't get any data. Only get an error when disconnecting and connecting the DMX signal.
  • both uart port 1 and 2, without succes.
  • connecting two RS485 modules A and B together with one module on the RX pin and the other on the TX pin and using the arduino multiserial example to send and recieve data. This worked fine even with the baudrate set to 2500000.
  • swapping RS485 modules, without succes

haven't tried:

  • looking at the DMX signal on a scope
  • using other GPIO pins for the RX TX pins
  • diving deeper into the library to see what is going on

any ideas?

pin assignment for ESP32 & 485

Thanks for nice library.
I tried using example as it is DMXRead

using Arduino ide & compile is OK.

but cannot detect DMX signal and found cannot go through both following if statement ...

  • if (xQueueReceive(queue, &packet, DMX_RX_PACKET_TOUT_TICK)) {
  • } else if (dmxIsConnected) {

pin diagram is followed by here
https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/uart.html#circuit-a-collision-detection-circuit

but, is my wire connection wrong ?
esp32 - max485 dmx wiring

or is there something I should amend on example code ?

Won't compile for esp32-C3

Attempted to build for the esp32-C3 and it fails with a compiler error as was suspected in the FIXME: comment on line 17 in the driver.h file. The eps32-C3 only has two UARTS which is why it fails. I adjusted the #define but it still won't compile due to a different uart_struct.h file (in the esp-idf/components/esp32c3/include/soc dir) being used.

I attempted to make appropriate changes to the data structure members and was able to get it to compile. However, I have not yet succeeded in getting it to work. It does run and install the driver, but no activity on the io pins (yet). Most of the data structure member differences had similar equivalent member names between the two different uart_struct.h files. However, there did not seem to be an equivalent member for tick_ref_always_on. I made a wild guess on this one and I plan to revisit my changes in the areas where I changed tick_ref_always_on to another member (mem_clk_en).

dmx_receive returns too early and data size is wrong

I am experimenting with the v3.0 release branch and am seeing some strange behavior.

  • The dmx_receive method returns too early. In my experiment it is returning at ~150hz (Max hz of dmx is ~44hz).
  • The size that is reported by dmx_receive is random. I.e. after a re-boot it will return a different size. The size never changes.

From my understanding of the documentation dmx_receive should block until a new dmx frame is returned but somehow this is not the case.

Am I missing something? This feels like I am not initializing something correctly.
The received data is correct, i.e. when I move the fader on the dmx sender I can see the correct value in the buffer.

The code below produces the following output:

...
E (15660) main: 15.675053: hz: 154.513034, ret: 255, data: 7
...
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_dmx.h"
#include "esp_log.h"
#include "esp_system.h"

#define TX_PIN 17 // the pin we are using to TX with
#define RX_PIN 16 // the pin we are using to RX with
#define EN_PIN 21 // the pin we are using to enable TX on the DMX transceiver

static const char *TAG = "main";
static uint8_t data[DMX_MAX_PACKET_SIZE] = {};

void dmxTask(void *unused)
{
    ESP_ERROR_CHECK(dmx_set_pin(DMX_NUM_2, TX_PIN, RX_PIN, EN_PIN));
    ESP_ERROR_CHECK(dmx_driver_install(DMX_NUM_2, DMX_DEFAULT_INTR_FLAGS));
    int count = 0;

    while (true)
    {
        dmx_event_t event;
        size_t ret = 0;
        ret = dmx_receive(DMX_NUM_2, &event, DMX_TIMEOUT_TICK);
        if (ret)
        {
            // Check that no errors occurred.
            if (event.err == ESP_OK)
            {
                count++;

                const double currentTimS = esp_timer_get_time() / 1000000.0;
                ESP_LOGE(TAG, "%f: hz: %f, ret: %d", currentTimS, (count / currentTimS), ret);
                dmx_read(DMX_NUM_2, data, event.size);
            }
            else
            {
                ESP_LOGE(TAG, "dmx error: %s", esp_err_to_name(event.err));
            }
        }
    }
}

void app_main()
{
    TaskHandle_t dmxTaskHandle = NULL;
    // TODO determine real stack usage and reduce later
    xTaskCreatePinnedToCore(dmxTask, "DMX_TASK", 10240, NULL, 2, &dmxTaskHandle, 1);
    if (!dmxTaskHandle)
    {
        ESP_LOGE(TAG, "Failed to create dmx task");
    }
}

Questions about Mark Before Break (MBB)

Thank you for the wonderful library.
Is it possible to control the Mark Before Break (MBB) 0S-1S High level (between frames) time in DMX transmission mode of this library?
I want to add Mark Before Break of 〇〇〇usec.

Sending broken - single frame only

If I try and send data, I'm seeing only the first frame sent then stops. Confirmed the behavior with your example DMXRwite

10:52:08.131 -> Sending DMX 0x01

Then no further output, unless I comment out dmx_send, but then I'm not sending any frames ;)

DMX RDM

Hi has anyone managed to implement RDM with this great library

Receiving malformed bytes after the first 1/5 of a fader

I am using this libary with an esp32 and a custom board to recieve dmx from an analog light desk and have stumbeled over some strange behavior.
In the first 1/5 of a dmx channel I am recieving data but if the fader is at ~1/5 the esp already detects around 0 and if I try to receive dmx values higher then this I get "Received malformed byte at slot 2" (In this case I am sending of channel 1). If I go higher with the fader between 5/10 and 7/10 the esp is receiving dmx again and above that again the same errors.
I have the same behavior on different dmx channels.

I fiddled with the dmx config (baudrate, break_num, idle_num...) but that didn't seem to change anything.
Its definitly possible I made errors in building my dmx -> uart board (I am new to board design) but the fact that I can recieve data makes it feels like something is wrong in the software.

I used the "DMXRead" example in ArduinoIDE.
Any soft of help or hint into the right direction would be appreciated.

dmx_write_slot does not work

In function dmx_write_slot, the value is not written to the buffer. Instead, the index is written to the buffer.

dmx_port when not using SparkFun shield

I am trying to figure out what to do with the dmx_port_t dmxPort = 2; section when I am not using a Sparkfun Shield but a RS-485 MODULE.
If I remove it I get errors.

[question] Not Receiving data, wrong pinout, or incompatible module?

Hi,

I seem to be having a similar issue as #40.

I have an M5Stick-C with a M5 RS485 Hat. The M5 Arduino example show me data, and I can see the DMX channel values, but with your library, it doesn’t seem to receive anything.

I suspect it is because this module doesn’t have a RTS pin, and manages that automatically, so I was hoping your equivalent modules arrived, and you could help me out with this.

I am trying to receive only, and the data is being sent from an ENTTEC usb DMX sender, and QLCPlus.

Diagram

Thanks!

dmx recieve buffer shifts after saving data to FAT

Hello. I found another issue. I remember reading an issue about data shifting in dmx recieve buffer but I couldn't find a closed issue so I decided to start a new one.
I'm making a dmx recorder, using two RS-485 chips, one for recieve and one for transmit. It reads dmx input from the rx line and instantly sends it unchanged to the tx line. If a user wants to record, he or she presses a button and the device copies dmx recieve buffer each 60ms to a large buffer which allocated in a PSRAM. When a user finished recording, the device saves the large buffer to spi flash through FAT filesystem with ESP wear leveling library. The process is long, it takes several seconds to save data to FAT and it blocks the code.

The problem is, after that save the dmx recieve buffer is shifted. The first channel could be on 4th or 35th channel now. The program works as intended before the save and when the device is playing recorded data (opening FAT file doesn't shift data). The size of saved data doesn't matter, the buffer shifts anyway.
I'm also using NVS to save some meta information variables along with FAT save, so it can be the issue too.

To clarify, this is my dmx code:

uint8_t dmx_data[DMX_PACKET_SIZE] = {};
uint8_t dmx_data_out[DMX_PACKET_SIZE] = {};
if(dmx_receive(RX_DMX, &packet, 0)) {
	dmx_read(RX_DMX, dmx_data, DMX_PACKET_SIZE);
}
dmx_write(TX_DMX, dmx_data_out, DMX_PACKET_SIZE);
dmx_send(TX_DMX, DMX_PACKET_SIZE);
dmx_wait_sent(TX_DMX, DMX_TIMEOUT_TICK);

dmx_data is my recieve buffer and dmx_data_out is my transmit buffer.

I'll do some tests tomorrow to find whether FAT or NVS is the issue, provide more information and a full shortened program code if it helps.

compile example DMXwrite.ino failed

compiling the DMXwrite example failed.
Win10 / arduino IDE 2.10

....
C:\Users\Hagen\AppData\Local\Arduino15\packages\esp32\tools\xtensa-esp32-elf-gcc\esp-2021r2-patch5-8.4.0/bin/xtensa-
C:\Users\Hagen\Documents\Arduino\esp_dmx-release-v3.0\examples\DMXWrite\DMXWrite.ino: In function 'void setup()':
C:\Users\Hagen\Documents\Arduino\esp_dmx-release-v3.0\examples\DMXWrite\DMXWrite.ino:57:3: error: 'dmx_config_t' was not declared in this scope
dmx_config_t config = DMX_CONFIG_DEFAULT;
^~~~~~~~~~~~
C:\Users\Hagen\Documents\Arduino\esp_dmx-release-v3.0\examples\DMXWrite\DMXWrite.ino:57:3: note: suggested alternative: 'gpio_config_t'
dmx_config_t config = DMX_CONFIG_DEFAULT;
^~~~~~~~~~~~
gpio_config_t
C:\Users\Hagen\Documents\Arduino\esp_dmx-release-v3.0\examples\DMXWrite\DMXWrite.ino:58:32: error: 'config' was not declared in this scope
dmx_driver_install(dmxPort, &config, DMX_INTR_FLAGS_DEFAULT);
^~~~~~
C:\Users\Hagen\Documents\Arduino\esp_dmx-release-v3.0\examples\DMXWrite\DMXWrite.ino:58:32: note: suggested alternative: 'confstr'
dmx_driver_install(dmxPort, &config, DMX_INTR_FLAGS_DEFAULT);
^~~~~~
confstr
C:\Users\Hagen\Documents\Arduino\esp_dmx-release-v3.0\examples\DMXWrite\DMXWrite.ino:58:40: error: 'DMX_INTR_FLAGS_DEFAULT' was not declared in this scope
dmx_driver_install(dmxPort, &config, DMX_INTR_FLAGS_DEFAULT);
^~~~~~~~~~~~~~~~~~~~~~
C:\Users\Hagen\Documents\Arduino\esp_dmx-release-v3.0\examples\DMXWrite\DMXWrite.ino:58:40: note: suggested alternative: 'ESP_INTR_FLAG_LEVEL4'
dmx_driver_install(dmxPort, &config, DMX_INTR_FLAGS_DEFAULT);
^~~~~~~~~~~~~~~~~~~~~~
ESP_INTR_FLAG_LEVEL4

Using library esp_dmx at version 3.0.3-beta in folder: C:\Users\Hagen\Documents\Arduino\libraries\esp_dmx
exit status 1

Compilation error: 'dmx_config_t' was not declared in this scope

Error on compiling

I get the following error:
invalid conversion from 'int' to 'gpio_num_t' [-fpermissive]
on the line of:
dmx_set_pin(dmxPort, transmitPin, receivePin, enablePin);:

when I try to compile the DMXWrite example.

Cant receive DMX : timer is not running (esp-idf v5.0.2)

I have upgraded my project from esp-idf v4.4.4 upgraded to v5.0.2 so that I could use some new networking featrues I require. IN doing so I have changed from esp_dmx V2.0.2 to V3.0.3-beta as I needed compatability with esp-idf V5 and above.

However when I run a loop to recieve DMX I get repeated errors about the timer not running:

They look to be running about DMX_TIMEOUT_TICK interval. If I actually connect a DMX source then I get the same error message repeated for what looks like every byte received. This is quickly followed by the watch dog timer error and the ESP halts.

I'm building with PlatformIO under VSCode, its my own custom PCB with an ESP-32-WROVER

[env:esp-wrover-kit]
platform = espressif32
board = esp-wrover-kit
framework = espidf

build_type = debug
board_upload.flash_size = 16MB
board_upload.maximum_ram_size = 532480
board_upload.maximum_size = 16777216

board_build.partitions = partitions.csv

monitor_filters = esp32_exception_decoder
monitor_speed = 115200
monitor_rts = 0
monitor_dtr = 0
PACKAGES: 
 - framework-espidf @ 3.50002.230601 (5.0.2) 
 - tool-cmake @ 3.16.4 
 - tool-esptoolpy @ 1.40501.0 (4.5.1) 
 - tool-mkfatfs @ 2.0.1 
 - tool-mklittlefs @ 1.203.210628 (2.3) 
 - tool-mkspiffs @ 2.230.0 (2.30) 
 - tool-ninja @ 1.9.0 
 - toolchain-esp32ulp @ 1.23500.220830 (2.35.0) 
 - toolchain-xtensa-esp32 @ 11.2.0+2022r1

I have built a small test project to make sure there is nothing else in my main project code thats causing an issue. Pretty much a direct copy of the sample code from the documentation.

#include "esp_dmx.h"
#include "stddef.h"
#include "log.h"

#define DMX_RX_UART DMX_NUM_1
#define DMX_IN_TX 5
#define DMX_IN_RX 19
#define DMX_IN_RTS 18


extern "C" void app_main()
{
 LOG_I("Starting Main");
 
 uint8_t rx_buffer[DMX_PACKET_SIZE];

 dmx_set_pin(DMX_RX_UART, DMX_IN_TX, DMX_IN_RX, DMX_IN_RTS);
 dmx_driver_install(DMX_RX_UART, DMX_DEFAULT_INTR_FLAGS);

 dmx_packet_t packet;

 LOG_I("Installed Driver");

 while(1) {
  if (dmx_receive(DMX_RX_UART, &packet, DMX_TIMEOUT_TICK)) {

   // Check that no errors occurred.
   if (packet.err == ESP_OK) {
    dmx_read(DMX_RX_UART, rx_buffer, packet.size);
   } else {
    LOG_I("An error occurred receiving DMX!");
   }

  } else {
   LOG_I("Timed out waiting for DMX.");
  }

 }
}
I (29) boot: ESP-IDF 5.0.2 2nd stage bootloader
I (29) boot: compile time 07:55:20
I (29) boot: chip revision: v3.0
I (32) boot.esp32: SPI Speed      : 40MHz
I (37) boot.esp32: SPI Mode       : DIO
I (41) boot.esp32: SPI Flash Size : 16MB
I (46) boot: Enabling RNG early entropy source...
I (51) boot: Partition Table:
I (55) boot: ## Label            Usage          Type ST Offset   Length
I (62) boot:  0 nvs              WiFi data        01 02 00009000 00015000
I (70) boot:  1 otadata          OTA data         01 00 0001e000 00002000
I (77) boot:  2 ota_0            OTA app          00 10 00020000 005f0000
I (85) boot:  3 ota_1            OTA app          00 11 00610000 005f0000
I (92) boot:  4 spiffs           Unknown data     01 82 00c00000 00400000
I (100) boot: End of partition table
I (104) esp_image: segment 0: paddr=00020020 vaddr=3f400020 size=0a244h ( 41540) map
I (127) esp_image: segment 1: paddr=0002a26c vaddr=3ffb0000 size=01efch (  7932) load
I (131) esp_image: segment 2: paddr=0002c170 vaddr=40080000 size=03ea8h ( 16040) load
I (140) esp_image: segment 3: paddr=00030020 vaddr=400d0020 size=18eb4h (102068) map
I (179) esp_image: segment 4: paddr=00048edc vaddr=40083ea8 size=08ac8h ( 35528) load
I (200) boot: Loaded app from partition at offset 0x20000
I (200) boot: Disabling RNG early entropy source...
I (211) cpu_start: Pro cpu up.
I (211) cpu_start: Starting app cpu, entry point is 0x40081c60
I (0) cpu_start: App cpu up.
I (226) cpu_start: Pro cpu start user code
I (226) cpu_start: cpu freq: 160000000 Hz
I (226) cpu_start: Application information:
I (230) cpu_start: Project name:     test
I (235) cpu_start: App version:      1
I (240) cpu_start: Compile time:     Jun 16 2023 07:54:49
I (246) cpu_start: ELF file SHA256:  299e75ec48a7c4c6...
I (252) cpu_start: ESP-IDF:          5.0.2
I (256) cpu_start: Min chip rev:     v0.0
I (261) cpu_start: Max chip rev:     v3.99 
I (266) cpu_start: Chip rev:         v3.0
I (271) heap_init: Initializing. RAM available for dynamic allocation:
I (278) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM
I (284) heap_init: At 3FFB2808 len 0002D7F8 (181 KiB): DRAM
I (290) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
I (297) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (303) heap_init: At 4008C970 len 00013690 (77 KiB): IRAM
I (311) spi_flash: detected chip: generic
I (314) spi_flash: flash io: dio
I (319) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
I (329) main.cpp: Line 13: app_main(): Starting Main
I (339) main.cpp: Line 22: app_main(): Installed Driver
E (1589) gptimer: gptimer_stop(389): timer is not running
I (1589) main.cpp: Line 35: app_main(): Timed out waiting for DMX.
E (2839) gptimer: gptimer_stop(389): timer is not running
I (2839) main.cpp: Line 35: app_main(): Timed out waiting for DMX.
E (4089) gptimer: gptimer_stop(389): timer is not running
I (4089) main.cpp: Line 35: app_main(): Timed out waiting for DMX.
E (5339) gptimer: gptimer_stop(389): timer is not running
I (5339) main.cpp: Line 35: app_main(): Timed out waiting for DMX.
E (6589) gptimer: gptimer_stop(389): timer is not running
I (6589) main.cpp: Line 35: app_main(): Timed out waiting for DMX.
E (7839) gptimer: gptimer_stop(389): timer is not running

DMX512

How can I set which DMX port (0 to 512) should be listened to. the DMX port in line 32 of the example script is probably not the right one which I assumed and therefore got an error. to make it short I can only read port 1 all other ports don't work.

Yours sincerely,

Johannes Fürst

missing dependencies?

Good Morning somewiseguy,
I tried too use yore library on an Espresso 32, using the Arduino IDE on a Mac and included your library via the library manager of Arduino IDE, but I get an error for a missing file:

Arduino: 1.8.19 (Mac OS X), Board: "ESP32 Dev Module, Disabled, Default 4MB with spiffs (1.2MB APP/1.5MB SPIFFS), 240MHz (WiFi/BT), QIO, 80MHz, 4MB (32Mb), 921600, None"

In file included from /Users/Coden/Documents/Arduino/test/test.ino:4:0:
/Users/Coden/Documents/Arduino/libraries/esp_dmx/src/esp_dmx.h:12:28: fatal error: hal/gpio_types.h: No such file or directory
compilation terminated.
exit status 1
Fehler beim Kompilieren für das Board ESP32 Dev Module.

any idea where I can get this file from?

Problem with library installation for PlatformIO

Hello. I'm trying to install this library for PlatformIO ESP-IDF project. Sorry if my problem is not exactly related to this library, but I don't know what to do now. I did a clean reinstall of VSCode, PlatformIO and started a new ESP-IDF project to ensure there are no legacy code conflicts.

  1. Installation by PlatformIO Library Register gives me this error:

Error: Could not find the package with 'someweisguy/esp_dmx @ ^3.0.0-beta' requirements for your system 'windows_amd64'

Deleting "lib_deps = someweisguy/esp_dmx@^3.0.0-beta" from platformio.ini allows me to build the project, but I get several errors in dmx_hal.h:

too few arguments to function 'uart_ll_set_baudrate'
too few arguments to function 'uart_ll_get_baudrate'
control reaches end of non-void function [-Werror=return-type]
too few arguments to function 'uart_ll_set_baudrate'
'uart_dev_t' has no member named 'uart_conf0_reg_t'
'uart_dev_t' has no member named 'uart_conf0_reg_t'
control reaches end of non-void function [-Werror=return-type]
'uart_dev_t' has no member named 'uart_status_reg_t'
control reaches end of non-void function [-Werror=return-type]

And one in esp_dmx.h:

implicit declaration of function 'esp_timer_get_time' [-Werror=implicit-function-declaration]

  1. Cloning "esp_dmx-release-v3.0" folder into /components folder of my project causes #include "esp_dmx.h" error.

  2. Cloning "esp_dmx-release-v3.0" folder into /lib folder causes last five dmx_hal.h errors from 1. but no other errors.

Are there any other ways to install a library to PlatformIO ESP-IDF project?

esp dump flash message showing on serial monitor output

when I upload DMXRead example of esp_dmx library on my esp32 module and open serial monitor for getting the status of DMX. I am getting this output on serial monitor

ets Jun 8 2016 00:22:57

rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0030,len:1184
load:0x40078000,len:13132
load:0x40080400,len:3036
entry 0x400805e4
E (131) esp_core_dump_flas����J����ɕ�сsize of core dump image: 151653755

what does it means??.

AVR Port?

Hello,

First off, thanks for this amazing library. This is the only library on the entire internet that I can find that appears to have implemented RDM for an Arduino ecosystem. I've been searching for one for a long time, and trying to write my own, but with minimal success.

I have some DMX strip tape (similar to the WS2812B strip tape that's super popular) but the only way to address each segment of the strip is via RDM. Additionally, I've also been testing with some other larger fixtures that come from china which are also RDM addressable. I'm trying to figure out a way to implement an RDM controller on Arduino so I can address these fixtures.

That said, as I write this I am attempting to test this library on an ESP8266 that I have lying around, but I would prefer to use an Atmega processor instead, such as the Atmega2560. (I'm working on eventually implementing this on custom hardware, and the Atemga chips are what I had planned on using for a variety of reasons.) So my question is, will there ever be an AVR port of this library? If so, I would be happy to help (as best I could -- I'm not super experienced with writing custom Arduino libraries, but I have been working with the Arduino ecosystem for years, so I'm pretty experienced with the ecosystem in general.)

Thanks,
Drew

e1.11 devices compatibility with e1.31 data source

As you mention that your library allows for receiving of e1.11 DMX 512 , will it will be able to also receive and decode the e1.31 DMX data because some software transmit e1.31 or SACN data.
or e1.311 is protocol of with e1.11 standard . please help me as I don't have too much knowledge about it.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.