2025-04-30 3:12 AM
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!