当前位置:首页 > 实时热点 > 正文内容

标准【DMX512】协议详解 & 接收与发送实现(附HAL库代码)

admin21小时前实时热点3

标准【DMX512】协议详解 & 接收与发送实现(附HAL库代码)

目录

前言

协议主要用于控制舞台设备,也可用于控制灯光设备。这里给出协议的国际标准,有兴趣的可以自行研究。本文主要了解协议的时序以及数据包的收发。

一、时序

协议规定的标准物理链路是RS485,但现在已经朝无线(蓝牙,WIFI)的方向进行发展了,毕竟客户更喜欢选择不用接线的设备。本文还是从最简单的RS485进行讲解。

首先给出的时序图,是不是很复杂,没关系,先从简单的开始讲起。

标准【DMX512】协议详解 & 接收与发送实现(附HAL库代码) 第1张

1、串口配置

本文使用串口产生时序再用硬件将TTL电平转换为RS485电平,所以协议的底层是对串口进行配置,按照协议的标准,串口应该配置如下

    huart1.Instance = USART1;
    huart1.Init.BaudRate = 250000;
    huart1.Init.WordLength = UART_WORDLENGTH_8B;
    huart1.Init.StopBits = UART_STOPBITS_2;
    huart1.Init.Parity = UART_PARITY_NONE;
    huart1.Init.Mode = UART_MODE_TX_RX;
    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    huart1.Init.OverSampling = UART_OVERSAMPLING_8;
    HAL_UART_Init(&huart1);

配置完成后,发送一位的时间是4us,一帧持续时间是44us。

2、起始信号

图中的1和2共同构成起始信号,起始信号完后面跟着的就是数据包

标准【DMX512】协议详解 & 接收与发送实现(附HAL库代码) 第2张

说白了起始信号就是先拉低后拉高,但是要产生这个起始信号可不容易,因为串口无法输出持续两帧的低电平,每一帧后面固定会跟随停止位(高电平)dmx512控制器英文说明书标准【DMX512】协议详解 & 接收与发送实现(附HAL库代码),所以要产生起始信号需要通过GPIO的输出实现,代码在后面有。

信号最少持续时间最大持续时间

2帧(88us)

1s

1位(4us)

1s

3、数据包时序

1是数据帧之间的占,用于分割每帧数据,可有可无。2就是数据帧,数据帧为一位起始位,八位数据位及两位停止位组成。按照上面配置即可

标准【DMX512】协议详解 & 接收与发送实现(附HAL库代码) 第3张

这样的时序就介绍完了,其实就是起始信号,后面跟数据包,每帧数据之间插入一个占。没有想象的那么复杂

信号最短持续时间最长持续时间

0s

1s

44us

44us

二、数据包格式

的数据包格式比较简单,由一个起始码和512个通道值组成,协议标准中规定0x00作为的固定起始码,一般不做更改,但实际生产中可以自定义,例如0x00作为舞台灯光控制,0x01作为舞台火花机控制......但是以下的起始码不能自定义。

标准【DMX512】协议详解 & 接收与发送实现(附HAL库代码) 第4张

1~512(最大是512)作为通道值,用于控制舞台设备,例如有个彩灯,彩灯有四个RGB灯珠地址是1,占据通道有16个。那么这个彩灯就处理数据包的通道1到通道16,通过这些通道的值完成相应的动作。如:当数据包的通道1为0~255时,彩灯就调自己的亮度为0~255。当通道2为0~255时就调自己的R值,通道3调G值......这样就可以使用协议进行对灯光设备的控制。一般发送数据的设备是控台,可以去淘宝搜索看看。

三、数据包发送

使用串口发送数据包,首先需要使用GPIO拉低再拉高产生起始信号,再复用为串口发送数据。这里就需要切换模式。又因为RS485是半双工的dmx512控制器英文说明书标准【DMX512】协议详解 & 接收与发送实现(附HAL库代码),所以还需要切换串口的输入/输出模式。

//改变模式
void MU_DMX_Change_Mode(enum dmx_mode mode)
{
    GPIO_InitTypeDef GPIO_Init;
    //DMX发送
    if(mode == DMX_SEND_DATA)
    {
        HAL_UART_DMAStop(&huart1);
        HAL_NVIC_DisableIRQ(USART1_IRQn);
        DMX512_SEND;
        GPIO_Init.Pin = GPIO_PIN_6;
        GPIO_Init.Mode = GPIO_MODE_AF_PP;
        GPIO_Init.Alternate = GPIO_AF2_USART1;
        HAL_GPIO_Init(GPIOB,&GPIO_Init);
    }
    //DMX输出起始信号
    else if(mode == DMX_SEND_RESET)
    {
        
        HAL_UART_DMAStop(&huart1);
        HAL_NVIC_DisableIRQ(USART1_IRQn);
        DMX512_SEND;
        GPIO_Init.Pin = GPIO_PIN_6;
        GPIO_Init.Mode = GPIO_MODE_OUTPUT_PP;
        HAL_GPIO_Init(GPIOB,&GPIO_Init);
    }
    //DMX接收数据
    else if(mode == DMX_RECV_DATA)
    {
        DMX512_RECV;
        HAL_NVIC_EnableIRQ(USART1_IRQn);
        HAL_UART_Receive_DMA(&huart1,rx_buf.buf,RX_BUF_MAX);
    }
}

关于和的切换逻辑可以看标准RDM协议详解及代码实现篇一 —— RDM协议详解及数据包定义的第一点和第二点,RDM和的数据链路是相同的。

标准【DMX512】协议详解 & 接收与发送实现(附HAL库代码) 第5张

接下来就是发送的函数了

uint8_t dmx_send_buf[520] = {0};
//设置起始码
static inline void DMX_Set_Start_Code(uint8_t start_code)
{
    dmx_send_buf[0] = start_code;
}
//设置通道数据
static inline void DMX_Set_Channel_Date(uint16_t dmx_addr,uint8_t* channel_data,uint16_t channel_num)
{
    dmx_addr = dmx_addr < 1 ? 1 : dmx_addr;
    for(int i = dmx_addr; i < dmx_addr+channel_num; i++)
    {
        dmx_send_buf[i] = channel_data[i-dmx_addr];
    }
}
//DMX发送 发送包越短,发送的越快,代表对设备的刷新率越高
void DMX_Send(uint16_t package_len)
{
    package_len = package_len > 513 ? 513 : package_len;
    package_len = package_len < 1 ? 1 : package_len;
    //起始信号
    MU_DMX_Change_Mode(DMX_SEND_RESET);
    HAL_GPIO_WritePin(GPIOB,GPIO_PIN_6,GPIO_PIN_RESET);
    delay_us(88*2);
    HAL_GPIO_WritePin(GPIOB,GPIO_PIN_6,GPIO_PIN_SET);
    delay_us(12*2);
    //发送包

    MU_DMX_Change_Mode(DMX_SEND_DATA);
#ifdef DMX512_ZHAN
    for(int i = 0; i < package_len; i++)
    {
        HAL_UART_Transmit(&huart1,&dmx_send_buf[i],1,HAL_MAX_DELAY);
        //延时产生占
        delay_us(DMX512_ZHAN);
    }
    //发送完切换回接收模式
    MU_DMX_Change_Mode(DMX_RECV_DATA);
#else
    HAL_UART_Transmit(&huart1,&dmx_send_buf[0],1,HAL_MAX_DELAY);
    HAL_UART_Transmit_DMA(&huart1,dmx_send_buf+1,package_len-1);
    //再DMA发送完成中断回调里切换为接收模式
#endif
}

测试了100us的占可以看到波形如下,图中低电平就是数据帧的起始位和数据0,高电平就是数据帧的停止位和占,没有问题。

标准【DMX512】协议详解 & 接收与发送实现(附HAL库代码) 第6张

四、数据包接收

按照协议的标准进行接收就需要注意不能使用空闲中断来进行接收,因为每帧之间的占就可能会进入空闲中断导致数据包接收不完整。那既然这样就会有人问为什么不一次接收512个,接收完就代表接收到一个数据包呢?

这样肯定是不行的,因为协议没有规定每个数据包的长度固定是512。如果按512接收就可能会导致两个包的数据混合到一起。那可不可以逐个接收呢?

也是不行的dmx512控制器英文说明书,因为逐个接收无法区分每个数据包,而且起始信号也会被串口接收到然后被串口识别为错误帧。想要不定长的接收每个数据包只能通过起始信号来区分每个数据包。那么怎么配置才能让串口识别的起始信号呢?

排除所有方法就只剩一种方法就是用串口BREAK中断进行接收,的起始信号会被串口识别为断开帧,具体的解释看兼容DMX及RDM协议的数据包接收方式 & 串口BREAK中断,这里给出代码

//DMX
void USART1_IRQHandler(void)
{
    //如果使能了空闲中断
    if(huart1.Instance->CR1 & USART_CR1_IDLEIE)
        HAL_UART_ReceiveIdleCallback(&huart1);
    if(huart1.Instance->CR2 & USART_CR2_LBDIE)
        HAL_UART_BreakCallback(&huart1);
    HAL_UART_IRQHandler(&huart1);
    __HAL_UART_CLEAR_FLAG(&huart1,USART_SR_LBD);
}
//DMX RX
void DMA1_Channel2_IRQHandler(void)
{
    HAL_DMA_IRQHandler(&hdma_usart1_rx);
}

//串口BREAK中断
//DMX包处理
void HAL_UART_BreakCallback(UART_HandleTypeDef *huart)
{
    if(huart->Instance == USART1)
    {
        if(huart->Instance->SR & USART_SR_LBD)
        {
            //清除标志位
            __HAL_UART_CLEAR_FLAG(&huart1,USART_SR_LBD);
            //关闭空闲中断
            __HAL_UART_DISABLE_IT(&huart1,UART_IT_IDLE);
            //读取DMA
            HAL_UART_DMAStop(huart);
            rx_buf.index += RX_BUF_MAX - (__HAL_DMA_GET_COUNTER(&hdma_usart1_rx));
            //如果是DMX包则结包
            if(rx_buf.buf[0] == 0x00 && rx_buf.index > 1)
            {
                //提取包到二级缓冲区
                memcpy((void *)dmx_package_buf,(void *)&rx_buf.buf,rx_buf.index);
                dmx_package_ne = 1;
            }
            //开启接收下一个包的包头
            //接收起始位
            rx_buf.index = 0;
            HAL_UART_Receive_IT(&huart1,rx_buf.buf,1);
        }
    }
}
//串口接收完成中断
//配置成DMA接收则是DMA接收完成中断
//只有接收包头会进入中断,其他情况不会让DMA接收完成
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if(huart->Instance == USART1)
    {
        //如果是RDM包
        if(rx_buf.buf[0] == 0xCC)
        {
            //开启空闲中断
            __HAL_UART_CLEAR_IDLEFLAG(huart);
            __HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);
        }
        //升级包
        else if(rx_buf.buf[0] == 0x91 || rx_buf.buf[0] == 0x92 || rx_buf.buf[0] == 0xAA || rx_buf.buf[0] == 0xFF)
        {
            //复位进入BootLoader处理
            //软件复位
            __HAL_RCC_CLEAR_RESET_FLAGS();
            HAL_NVIC_SystemReset();
        }
        else if(rx_buf.buf[0] == 0x00)
        {
            //...
        }
        //开始接收包
        HAL_UART_Receive_DMA(&huart1,rx_buf.buf,RX_BUF_MAX);
    }
}

五、数据包解析

上面的接收代码会将一个完整的DMX包接收到二级缓冲区 中,并置位 ,接下来就可以对数据包进行解析

uint8_t dmx_package_buf[520] = {0};
uint8_t dmx_package_ne = 0;

这里直接给出数据包解析函数,主要就是根据本设备的DMX基地址和占据通道数取出包中对自己有用的数据。

typedef struct dmx_package_prase_t
{
    uint8_t* channel;     //通道数据

    uint8_t  channel_num; //通道数
}dmx_package_prase_t;

//DMX解包
static dmx_package_prase_t DMX_Package_Prase(void)
{
    dmx_package_prase_t package_prase;
    
    //用于确定包长
    int i = 0;
    //定位包头,可有可无,加上代码会更健壮一点
    for(i = 0; i < 520; i++)
    {
        if(dmx_package_buf[i] == 0x00)
            break;
    }
    if(i == 520)
    {
        package_prase.channel = NULL;
        return package_prase;
    }
    package_prase.channel = (uint8_t *)malloc(device_info.info.dmx_channel);
    //基地址
    uint16_t index = i+device_info.info.dmx_start_addr;
    for(int j = 0; j < device_info.info.dmx_channel; j++)
    {
       package_prase.channel[j] = dmx_package_buf[index+j];
    }
    package_prase.channel_num = device_info.info.dmx_channel;
    //需要在外部释放空间
    return package_prase;
}

然后得到有用的数据之后就使用这些数据,使用完后把数据占用的空间释放掉即可,这里给出示例代码,是我正在开发的一个电脑灯的产品。

uint8_t DMX_Unpack_And_Set(void)
{
    if(dmx_package_ne == 0)
        return 0;
    dmx_package_prase_t package;
    package = DMX_Package_Prase();
    if(package.channel != NULL)
    {
        //X 
        run_parameters.manual_value[0]  = package.channel[0];
        //Y
        run_parameters.manual_value[1] = package.channel[1];
        //X细调
        run_parameters.manual_value[11] = package.channel[2];
        //Y细调
        run_parameters.manual_value[12] = package.channel[3];
        //XY速度
        run_parameters.manual_value[13] = package.channel[4];
        //雾镜
        run_parameters.manual_value[5] = package.channel[5];
        //频闪
        run_parameters.manual_value[8]  = package.channel[6];
        //调光
        run_parameters.manual_value[2]  = package.channel[7];
#ifdef DIMMING
        MU_Set_Lamp(package.channel[7]);
#else
        if(package.channel[7] >= 5)
            Lamp_ON();
        else
            Lamp_OFF();
#endif
        //色盘 = package.channel[7]
        run_parameters.manual_value[2]  = package.channel[8];
        //色盘控制方式
        run_parameters.adv_set[2] = package.channel[9]/86+1;
        //图案
        run_parameters.manual_value[3] = package.channel[10];
        //图案控制方式
        run_parameters.adv_set[3] = package.channel[11]/86+1;
        //调焦
        run_parameters.manual_value[4] = package.channel[12];
        //棱镜1
        run_parameters.manual_value[9] = package.channel[13];
        //棱镜1自转
        run_parameters.manual_value[14] = package.channel[14];
        //棱镜2
        run_parameters.manual_value[10] = package.channel[15];
        //棱镜2旋转
        run_parameters.manual_value[15] = package.channel[16];
        //复位
        if(package.channel[17] >= 250)
        {
            InnerCmd_Reset();
            //复位执行
            APP_Reset();
        }
        //补充剩下的通道
        if(package.channel_num == 20)
        {
            //...
        }
        //发送数据
        InnerCmd_Run_Paremeters();
        free(package.channel);
        //解析完DMX包
        dmx_package_ne = 0;
        return 1;
    }
    return 0;
}

总结

到此为止,协议的基本用法,和发送接收函数已经介绍完了。是不是看完蒙蒙的,没事,上手练练就会发现协议也不过如此。

加入微信交流群:************ ,请猛戳这里→点击入群

扫描二维码推送至手机访问。

版权声明:本文由智汇百科网发布,如需转载请注明出处。

本文链接:https://www.zhihuibkw.com/post/6252.html

分享给朋友:

“标准【DMX512】协议详解 & 接收与发送实现(附HAL库代码)” 的相关文章

2025 世界花样滑冰锦标赛今日在美国波士顿开幕,看点有哪些?

2025 世界花样滑冰锦标赛今日在美国波士顿开幕,看点有哪些?

2025 年世界花样滑冰锦标赛在美国波士顿的盛大开幕,无疑将成为全球花样滑冰爱好者瞩目的焦点。这场冰雪盛宴汇聚了世界顶尖的花样滑冰选手,他们将在冰面上展现出令人惊叹的技巧、艺术和激情,为观众带来一场场视觉盛宴。那么,这场锦标赛的看点究竟有哪些呢?顶尖选手的对决将是本次锦标赛的最大看点之一。世界花样滑...

梅西踢球经历

梅西踢球经历

在足球的浩瀚星空中,梅西无疑是那颗最为璀璨的巨星。他的每一个动作、每一场比赛都仿佛是一场视觉盛宴,牵动着全球无数球迷的心。而近日,梅西再度创造了足球生涯的新纪录,这一消息如同一颗重磅炸弹,在球迷群体中引发了前所未有的热议。梅西,这位阿根廷足球传奇,以其无与伦比的技术、敏捷的身手和超凡的球商,在足球领...

电视剧的狗血剧情发生在我身边

电视剧的狗血剧情发生在我身边

近期,某部电视剧在各大平台热播,一时间成为了观众们茶余饭后的热门话题。与之相伴的却是铺天盖地的吐槽声,其剧情的狗血程度令人咋舌,引发了广泛的讨论与反思。从剧情的发展来看,这部剧可谓是将各种狗血元素集齐于一身。人物设定的刻板与不合理让人忍俊不禁。主角们往往被赋予了过于极端的性格特点,不是单纯的善良到毫...

电竞产业的发展趋势

电竞产业的发展趋势

在当今的时代潮流中,电竞产业犹如一颗璀璨的新星,正在以惊人的速度飞速发展,迅速成为备受瞩目的新体育热点。电竞,这个曾经被视为小众娱乐的领域,如今已焕发出勃勃生机。从早期的单机游戏对战,到如今的全球性电竞赛事,电竞的发展历程堪称一部传奇。随着互联网的普及和技术的不断进步,电竞的受众群体日益庞大,不再局...

中国跳水队奥运成绩

中国跳水队奥运成绩

在体育的浩瀚星空中,中国跳水队宛如一颗璀璨的星辰,始终散发着耀眼的光芒。巴黎奥运的赛场上,他们再度书写辉煌,以包揽全部金牌的壮举,将无敌神话延续到了新的高度。跳水,作为一项对技巧、勇气和稳定性要求极高的运动,中国跳水队一直以来都是当之无愧的王者。从早年的高敏、伏明霞到如今的全红婵、陈芋汐,一代又一代...

选秀节目扎堆现象堪忧

选秀节目扎堆现象堪忧

在如今这个娱乐至上的时代,选秀节目如雨后春笋般涌现,成为了无数年轻人追逐梦想的舞台。近年来,选秀节目黑幕的频繁曝光,让人们开始对这个看似光鲜亮丽的行业产生了深深的质疑:公平何在?选秀节目,原本应该是一个公平竞争的平台,让有才华的人能够脱颖而出,实现自己的梦想。现实却往往与理想背道而驰。一些选秀节目被...