2025-04-30 3:12 AM - last edited on 2025-05-06 2:32 AM by KDJEM.1
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:
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!
Solved! Go to Solution.
2025-05-06 5:18 AM
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.
2025-05-06 3:51 AM
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.
2025-05-06 5:18 AM
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.
2025-05-06 6:50 AM
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.