Skip to main content
Visitor II
April 30, 2025
Solved

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

  • April 30, 2025
  • 3 replies
  • 1167 views

 

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!

 

    This topic has been closed for replies.
    Best answer by Prafu_N

    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.

     

    3 replies

    ST Employee
    May 6, 2025

    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. 

    Prafu_NAuthorAnswer
    Visitor II
    May 6, 2025

    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.

     

    ST Employee
    May 6, 2025

    Thanks @Prafu_N, for sharing the solution!