cancel
Showing results for 
Search instead for 
Did you mean: 

Implementing a DMX512 Protocol Receiver with STM32G0C1CE and Zephyr OS v4.1.0

Prafu_N
Associate II

 

Hello, So I'm tackling a DMX512 project using an STM32G0C1CE with Zephyr OS v4.1.0 and I'm running into a bit of a snag with detecting the BREAK and Mark After Break (MAB) signals to start receiving the DMX slot data via USART.

I've already tried a couple of things:

  1. Using the framing error detection, but that didn't seem to work out.
  2. Since I only have the USART3_RX pin available at PB11, I tried configuring it as a GPIO input to catch the BREAK and MAB using a GPIO interrupt. After detecting them, I switched it back to USART_RX mode. This allowed me to see the BREAK and MAB, but I couldn't reliably grab the subsequent DMX slot data within the USART interrupt.

Code:

#include <stdio.h>
#include <string.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/printk.h>
#include <zephyr/shell/shell.h>
#include <zephyr/logging/log.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys_clock.h>
#include <gpios_common/gpios_common.h>

#define LOG_MODULE_NAME USART3_MODULE
LOG_MODULE_REGISTER(LOG_MODULE_NAME, LOG_LEVEL_DBG);

// DMX Constants
#define DMX_MAX_SLOTS 513 // 1 start code + 512 channel values

#define USART3_NODE DT_ALIAS(usart3)
#define USART3_RX_PORT DT_NODELABEL(gpiob)
#define USART3_RX_PIN 11

static const struct gpio_dt_spec rx_gpio = {
    .port = DEVICE_DT_GET(USART3_RX_PORT),
    .pin = USART3_RX_PIN,
    .dt_flags = GPIO_INPUT
};

static struct gpio_callback rx_gpio_cb;

static int64_t break_start_cycles = 0;
static bool break_detected = false;
static bool mab_detected = false;

static const struct device *usart3_dev;
static uint8_t dmx_data[DMX_MAX_SLOTS];
static size_t dmx_index = 0;
static bool receiving_dmx = false;

// --- BREAK + MAB Detection using GPIO ISR ---
static void rx_gpio_isr(const struct device *dev, struct gpio_callback *cb, gpio_port_pins_t pins)
{
    int level = gpio_pin_get_dt(&rx_gpio);
    uint64_t now = k_cycle_get_64();

    if (level == 0)
    {
        // FALLING edge = BREAK start
        break_start_cycles = now;
    }
    else
    {
        // RISING edge = BREAK end
        uint64_t delta_cycles = now - break_start_cycles;
        uint64_t delta_us = (delta_cycles * 1000000) / sys_clock_hw_cycles_per_sec();

        if (delta_us >= 88)
        {
            printk("BREAK detected (duration: %llu us)\n", delta_us);
            break_detected = true;

            // Measure MAB
            uint64_t mab_start_cycles = k_cycle_get_64();
            while (gpio_pin_get_dt(&rx_gpio) == 1)
            {
                // Wait for MAB to end
            }
            uint64_t mab_end_cycles = k_cycle_get_64();
            uint64_t mab_cycles = mab_end_cycles - mab_start_cycles;
            uint64_t mab_us = (mab_cycles * 1000000) / sys_clock_hw_cycles_per_sec();

            if (mab_us >= 8)
            {
                printk("MAB detected (duration: %llu us)\n", mab_us);
                mab_detected = true;
                receiving_dmx = false; // Reset DMX reception on new BREAK
                dmx_index = 0;

                // Stop GPIO interrupts and hand over to USART
                gpio_pin_interrupt_configure_dt(&rx_gpio, GPIO_INT_DISABLE);
                gpio_remove_callback(rx_gpio.port, &rx_gpio_cb);
            }
            else
            {
                // Invalid MAB, ignore
                break_detected = false;
            }
        }
        else
        {
            // Glitch or short pulse
            return;
        }
    }
}

// --- Configure GPIO Interrupt on RX pin ---
static void configure_rx_gpio_interrupt()
{
    gpio_pin_configure_dt(&rx_gpio, GPIO_INPUT);
    gpio_init_callback(&rx_gpio_cb, rx_gpio_isr, BIT(rx_gpio.pin));
    gpio_add_callback(rx_gpio.port, &rx_gpio_cb);
    gpio_pin_interrupt_configure_dt(&rx_gpio, GPIO_INT_EDGE_BOTH);

    printk("GPIO edge detection on PB11 enabled\n");
}

// --- USART3 RX ISR to receive DMX frame ---
static void uart_interrupt_callback(const struct device *dev, void *user_data)
{
    printk("%s\n", __func__);
    uart_irq_update(dev);

    if (uart_irq_rx_ready(dev))
    {
        uint8_t byte;
        while (uart_poll_in(dev, &byte) == 0)
        {
            // printk("Received byte: 0x%02X\n", byte);                         // Print received byte
            // printk("DMX index: %zu\n", dmx_index);                           // Print current DMX index
            // printk("Receiving DMX: %s\n", receiving_dmx ? "true" : "false"); // Print receiving status
            if (!receiving_dmx && byte == 0x00)
            {
                // Start code detected
                receiving_dmx = true;
                dmx_index = 0;
            }

            // printk("DMX index: %zu\n", dmx_index);                           // Print current DMX index
            // printk("Receiving DMX: %s\n", receiving_dmx ? "true" : "false"); // Print receiving status
            if (dmx_index < DMX_MAX_SLOTS)
            {
                printk("Received byte: 0x%02X\n", byte);                         // Print received byte
                dmx_data[dmx_index++] = byte;
            }
            // printk("DMX index: %zu\n", dmx_index);                           // Print current DMX index
            if (dmx_index == DMX_MAX_SLOTS)
            {
                receiving_dmx = false;

                // Full DMX frame received
                printk("DMX Frame received:\nStart Code: 0x%02X, Channel 1: 0x%02X\n",
                       dmx_data[0], dmx_data[1]);

                // Optional: print first 10 channels
                printk("First 10 slots: ");
                for (int i = 1; i <= 10 && i < DMX_MAX_SLOTS; i++)
                {
                    printk("%02X ", dmx_data[i]);
                }
                printk("\n");
            }
        }
    }
}

// --- Enable USART3 RX after BREAK detection ---
static void enable_usart3_rx()
{
    usart3_dev = DEVICE_DT_GET(USART3_NODE);
    if (!usart3_dev)
    {
        LOG_ERR("USART3: Device driver not found.\n");
        return;
    }

    if (!device_is_ready(usart3_dev))
    {
        LOG_ERR("Device %s is not ready.\n", usart3_dev->name);
        return;
    }
    printk("USART3 Driver Init Successful\n");

    uart_irq_callback_user_data_set(usart3_dev, uart_interrupt_callback, NULL);
    uart_irq_rx_enable(usart3_dev);
    printk("USART3 RX enabled (after BREAK + MAB)\n");
    k_busy_wait(100); // wait 100 microseconds
}

// --- Init Function ---
void usart3_init()
{
    printk("DMX Receiver: GPIO BREAK + MAB detection → USART3 RX\n");

    if (!device_is_ready(rx_gpio.port))
    {
        LOG_ERR("PB11 GPIO port not ready");
        return;
    }

    configure_rx_gpio_interrupt();

    // Wait for BREAK + MAB
    while (!break_detected || !mab_detected)
    {
        k_sleep(K_MSEC(10));
    }

    // Switch to USART3 RX
    enable_usart3_rx();
    rs485_set_receive_mode(); // Switch RS-485 to receive direction

    // Main thread can sleep forever — UART ISR handles reception
    while (1)
    {
        k_sleep(K_MSEC(10));
    }
}

Would you happen to know of any code examples or have any suggestions on how to properly detect the BREAK and MAB signals and then seamlessly transition to receiving the complete DMX slot data over USART in this setup? Any guidance would be a huge help! Thanks!

 

1 ACCEPTED SOLUTION

Accepted Solutions
Prafu_N
Associate II

Hello @Sarra.S ,

Thanks for the inputs.

I further modified the hardware by establishing a direct electrical connection between pins PB11 and PB1. Following this change, PB1 was reconfigured to handle both BREAK and MAB signaling, while PB11 was assigned as the dedicated USART3 receive (RX) line.

These hardware adjustments—specifically the direct link between PB11 and PB1—enabled PB1 to manage dual signaling functions and allowed PB11 to serve as the USART3_RX input. As a result, the STM32G0C1CE microcontroller successfully received the correct DMX512 frame.

 

View solution in original post

3 REPLIES 3
Sarra.S
ST Employee

Hello @Prafu_N

Looking to the tests you did, instead of polling for MAB, consider using a timer to measure the duration of the high signal after the BREAK. This can help avoid busy-waiting!

After detecting the BREAK and MAB, try to add a small delay to ensure the USART is ready to receive data. 

To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.

Prafu_N
Associate II

Hello @Sarra.S ,

Thanks for the inputs.

I further modified the hardware by establishing a direct electrical connection between pins PB11 and PB1. Following this change, PB1 was reconfigured to handle both BREAK and MAB signaling, while PB11 was assigned as the dedicated USART3 receive (RX) line.

These hardware adjustments—specifically the direct link between PB11 and PB1—enabled PB1 to manage dual signaling functions and allowed PB11 to serve as the USART3_RX input. As a result, the STM32G0C1CE microcontroller successfully received the correct DMX512 frame.

 

Sarra.S
ST Employee

Thanks @Prafu_N, for sharing the solution! 

To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.