标准RDM协议详解及代码实现篇三 —— 接收端解包 & 串口DMA接收实现

目录

前言

一、串口DMA接收 & 阻塞发送

二、接收端解包函数

三、接收端组包函数

总结


前言

        RDM协议最后一篇,接收端的解包实现以及使用串口DMA减少CPU占用。


一、串口DMA接收 & 阻塞发送

        修改了DMA接收,同时区分控制端额外初始化UID链表,初始化后默认状态是接收。

void MU_DMX_Init(void)
{
    /* 开启时钟 */
    __HAL_RCC_GPIOB_CLK_ENABLE();
    __HAL_RCC_USART1_CLK_ENABLE();
    __HAL_RCC_DMA1_CLK_ENABLE();

    /* GPIO */
    GPIO_InitTypeDef GPIO_Init;
    GPIO_Init.Pin = GPIO_PIN_5;
    GPIO_Init.Mode = GPIO_MODE_OUTPUT_PP;

	HAL_GPIO_Init(GPIOB,&GPIO_Init);

    GPIO_Init.Pin = GPIO_PIN_6;
    GPIO_Init.Mode = GPIO_MODE_AF_PP;
    GPIO_Init.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_Init.Alternate = GPIO_AF2_USART1;
    HAL_GPIO_Init(GPIOB,&GPIO_Init);


    GPIO_Init.Pin = GPIO_PIN_7;
    GPIO_Init.Mode = GPIO_MODE_AF_OD;
    GPIO_Init.Alternate = GPIO_AF2_USART1;
    HAL_GPIO_Init(GPIOB,&GPIO_Init);

    /* 串口 */
    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);
    //空闲中断
    __HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);


    hdma_usart1_rx.Instance = DMA1_Channel2;
    hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_usart1_rx.Init.Mode = DMA_NORMAL;
    hdma_usart1_rx.Init.Priority = DMA_PRIORITY_LOW;
    HAL_DMA_Init(&hdma_usart1_rx);

    __HAL_LINKDMA(&huart1, hdmarx, hdma_usart1_rx);

    HAL_DMA_ChannelMap(&hdma_usart1_rx, DMA_CHANNEL_MAP_USART1_RD);

    /* 开启中断 */
    HAL_NVIC_SetPriority(USART1_IRQn,0,2);
    HAL_NVIC_EnableIRQ(USART1_IRQn);

    MU_DMX_Change_Mode(DMX_RECV_DATA);

#ifdef RDM_MODE_CTRL
    RDM_Uid_Init();
#endif
    MU_DMX_Change_Mode(DMX_RECV_DATA);
    HAL_UART_Receive_DMA(&huart1,rx_buf.buf,RX_BUF_MAX);

}

        因为RDM协议是半双工的,所以需要进行收发模式切换,在发送模式时需要关闭DMA接收和串口中断。

oid MU_DMX_Change_Mode(enum dmx_mode mode)
{
    GPIO_InitTypeDef GPIO_Init;
    //DMX发送
    if(mode == DMX_SEND_DATA)
    {
        HAL_UART_DMAPause(&huart1);
        HAL_NVIC_DisableIRQ(USART1_IRQn);
        DMX512_SEND;

        HAL_UART_DeInit(&huart1);

        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);

        HAL_UART_Init(&huart1);

    }
    //DMX接收
    else if(mode == DMX_RECV_DATA)
    {
        DMX512_RECV;
        HAL_NVIC_EnableIRQ(USART1_IRQn);
        HAL_UART_DMAResume(&huart1);
        HAL_UART_Receive_DMA(&huart1,rx_buf.buf,RX_BUF_MAX);


    }
    //DMX输出起始信号
    else if(mode == DMX_SEND_RESET)
    {
        HAL_UART_DMAPause(&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);
    }

    
}

        数据发送采用阻塞发送,因为硬件不支持发送和接收的DMA分开控制,开启发送的DMA就会一同开启接收的DMA,如果你的板子支持分别控制可以使用发送DMA提高效率。

        接收采用DMA接收。当串口空闲时读取DMA接收的数据并重新开启DMA接收

void USART1_IRQHandler(void)
{
    HAL_UART_ReceiveIdleCallback(&huart1);
	HAL_UART_IRQHandler(&huart1);
}
//串口空闲中断回调
void HAL_UART_ReceiveIdleCallback(UART_HandleTypeDef *huart)
{
    //当触发了串口空闲中断
    if((__HAL_UART_GET_FLAG(huart,UART_FLAG_IDLE) != RESET)) 
    {
		if(huart->Instance == USART1)
		{
			__HAL_UART_CLEAR_IDLEFLAG(huart);

			//读取DMA
			HAL_UART_DMAStop(huart);
            //接收个数等于接收缓冲区总大小减剩余计数
            rx_buf.index += RX_BUF_MAX - (__HAL_DMA_GET_COUNTER(&hdma_usart1_rx));

            //起始信号引发空闲中断不处理
            if(rx_buf.index == 1 && rx_buf.buf[0] == 0x00);
            else
            {
                package_buf.package_num++;
                package_buf.package_num = package_buf.package_num == 9 ? 8 : package_buf.package_num;
                memset(package_buf.package_buf[package_buf.package_num-1],0,255);
                //提取包到二级缓冲区
                memcpy((void *)package_buf.package_buf[package_buf.package_num-1],(void *)rx_buf.buf,rx_buf.index);

            }

            //继续接收包
            memset(rx_buf.buf,0,rx_buf.index);
            rx_buf.index = 0;
            HAL_UART_Receive_DMA(&huart1,rx_buf.buf,RX_BUF_MAX);

		}
    }
}

        使用了双缓冲区,rx_buf作为一级缓冲区,大小是255,能够完整的接收一个RDM包,然后将数据放到二级缓冲区package_buf。如果不使用双缓冲区有可能在解包的时候DMA更新数据导致解包出错。

#define RX_BUF_MAX 255
typedef struct rx_package_buf_t
{
    uint8_t package_buf[8][255];
    uint8_t package_num;
}rx_package_buf_t;
typedef struct rx_buffer_t 
{
    uint8_t buf[255];
    uint8_t index;
}rx_buffer_t;

二、接收端解包函数

        接收端解包函数比控制端要多两个判断,先要判断是否在哑音状态,如果在就只处理解除哑音包,再这个包的目的地UID是否是自己,如果是才处理。

rdm_package_prase_t RDM_Contorl_Package_Prase(uint8_t package_num)
{
    rdm_package_prase_t package_prase;
    
    //用于确定包长
    int i = 0;
    //定位包头
    for(i = 0; i < 255; i++)
    {
        if(package_buf.package_buf[package_num][i] == 0xCC && package_buf.package_buf[package_num][i+1] == 0x01)
            break;
    }
    //包没接收完整
    if(i == 255)
    {
		package_prase.rdm_package = NULL_PACKAGE;
    }
    //如果在哑音状态,只处理解除哑音包
    else if(RDM_MUTE)
    {
        package_prase.rdm_package = IRR_PACKAGE;
    }
    else if(RDM_UID_TRUE)
    {
        switch(package_buf.package_buf[package_num][i+20])
        {
            //寻设备包
            case 0x10:
                if(package_buf.package_buf[package_num][i+21] == 0x00 && package_buf.package_buf[package_num][i+22] == 0x01)
                {
                    //判断UID是否在低UID和高UID之间
                    uint64_t low_uid = 0;
                    uint64_t high_uid = 0;
                    low_uid = package_buf.package_buf[package_num][i+24]; low_uid <<= 8;
                    low_uid |= package_buf.package_buf[package_num][i+25]; low_uid <<= 8;
                    low_uid |= package_buf.package_buf[package_num][i+26]; low_uid <<= 8;
                    low_uid |= package_buf.package_buf[package_num][i+27]; low_uid <<= 8;
                    low_uid |= package_buf.package_buf[package_num][i+28]; low_uid <<= 8;
                    low_uid |= package_buf.package_buf[package_num][i+29];
                    high_uid = package_buf.package_buf[package_num][i+30]; high_uid <<= 8;
                    high_uid |= package_buf.package_buf[package_num][i+31]; high_uid <<= 8;
                    high_uid |= package_buf.package_buf[package_num][i+32]; high_uid <<= 8;
                    high_uid |= package_buf.package_buf[package_num][i+33]; high_uid <<= 8;
                    high_uid |= package_buf.package_buf[package_num][i+34]; high_uid <<= 8;
                    high_uid |= package_buf.package_buf[package_num][i+35];
                    if(device_info.uid >= low_uid && device_info.uid <= high_uid)
                    {
                        package_prase.rdm_package = DISC_UNIQUE;
                    }
                    else
                    {
                        //不用回应
                        package_prase.rdm_package = IRR_PACKAGE;
                    }
                }
                else if(package_buf.package_buf[package_num][i+21] == 0x00 && package_buf.package_buf[package_num][i+22] == 0x02)
                {
                    package_prase.rdm_package = DISC_MUTE;
                }
                else if(package_buf.package_buf[package_num][i+21] == 0x00 && package_buf.package_buf[package_num][i+22] == 0x03)
                {
                    package_prase.rdm_package = DISC_UN_MUTE;
                }
            break;
            //读信息包
            case 0x20:
                if(package_buf.package_buf[package_num][i+21] == 0x10 && package_buf.package_buf[package_num][i+22] == 0x00)
                {
                    package_prase.rdm_package = GET_DRIVER_FLAG;
                }
                else if(package_buf.package_buf[package_num][i+21] == 0x00 && package_buf.package_buf[package_num][i+22] == 0xF0)
                {
                    package_prase.rdm_package = GET_DRIVER_DMX_ADDR;
                }
                else if(package_buf.package_buf[package_num][i+21] == 0x00 && package_buf.package_buf[package_num][i+22] == 0xC0)
                {
                    package_prase.rdm_package = GET_DRIVER_VERSION;
                }
                else if(package_buf.package_buf[package_num][i+21] == 0x00 && package_buf.package_buf[package_num][i+22] == 0x60)
                {
                    package_prase.rdm_package = GET_DRIVER_INFO;
                }
            break;
            //设置包
            case 0x30:
                if(package_buf.package_buf[package_num][i+21] == 0x10 && package_buf.package_buf[package_num][i+22] == 0x00)
                {
                    package_prase.rdm_package = SET_DRIVER_FLAG;
                }
                else if(package_buf.package_buf[package_num][i+21] == 0x00 && package_buf.package_buf[package_num][i+22] == 0xF0)
                {
                    package_prase.rdm_package = SET_DRIVER_DMX_ADDR;
                }   
            break;
        }
        //读取数据
        for(int j = 0; j < package_buf.package_buf[package_num][i+23]; j++)
        {
            package_prase.data[j] = package_buf.package_buf[package_num][i+24+j];
        }
        //读取源UID
        package_prase.source_uid  = package_buf.package_buf[package_num][i+9];  package_prase.source_uid <<= 8;
        package_prase.source_uid |= package_buf.package_buf[package_num][i+10]; package_prase.source_uid <<= 8;
        package_prase.source_uid |= package_buf.package_buf[package_num][i+11]; package_prase.source_uid <<= 8;
        package_prase.source_uid |= package_buf.package_buf[package_num][i+12]; package_prase.source_uid <<= 8;
        package_prase.source_uid |= package_buf.package_buf[package_num][i+13]; package_prase.source_uid <<= 8;
        package_prase.source_uid |= package_buf.package_buf[package_num][i+14];
        //检测校验位
        uint32_t sum = 0;
        for(int j = i; j < i+24+package_buf.package_buf[package_num][i+23]; j++)
        {
            sum += package_buf.package_buf[package_num][j];
        }
        i += 24+package_buf.package_buf[package_num][i+23];
        //!!! & 优先级小于 ==
        if((((sum >> 8) & 0xFF) == package_buf.package_buf[package_num][i]) && ((sum & 0xFF) == package_buf.package_buf[package_num][i+1]));
        else
            package_prase.rdm_package = ERROR_PACKAGE;
        i += 2;
    }
    //不需要响应的包
    else
    {
        package_prase.rdm_package = IRR_PACKAGE;
    }
    
    return package_prase;

}

        判断条件太长所以用了宏定义

//如果进入哑音状态只处理解除哑音包
#define RDM_MUTE device_info.rdm_stop == 1 &&                           \
                (package_buf.package_buf[package_num][i+20] != 0x10 ||  \
                 package_buf.package_buf[package_num][i+21] != 0x00 ||  \
                 package_buf.package_buf[package_num][i+22] != 0x03)
//如果UID是本设备UID 或者 UID是0xFFFFFFFFFFFF
#define RDM_UID_TRUE (package_buf.package_buf[package_num][i+3] == ((device_info.uid >> 5*8) & 0xFF) &&         \
                      package_buf.package_buf[package_num][i+4] == ((device_info.uid >> 4*8) & 0xFF) &&         \
                      package_buf.package_buf[package_num][i+5] == ((device_info.uid >> 3*8) & 0xFF) &&         \
                      package_buf.package_buf[package_num][i+6] == ((device_info.uid >> 2*8) & 0xFF) &&         \
                      package_buf.package_buf[package_num][i+7] == ((device_info.uid >> 1*8) & 0xFF) &&         \
                      package_buf.package_buf[package_num][i+8] == ((device_info.uid >> 0*8) & 0xFF)) ||        \
                     (package_buf.package_buf[package_num][i+3] == 0xFF &&                                      \
                      package_buf.package_buf[package_num][i+4] == package_buf.package_buf[package_num][i+3] && \
                      package_buf.package_buf[package_num][i+5] == package_buf.package_buf[package_num][i+4] && \
                      package_buf.package_buf[package_num][i+6] == package_buf.package_buf[package_num][i+5] && \
                      package_buf.package_buf[package_num][i+7] == package_buf.package_buf[package_num][i+6] && \
                      package_buf.package_buf[package_num][i+8] == package_buf.package_buf[package_num][i+7])   

        因为是接收端,不知道RDM包会何时收到,所以需要在主循环中不断判断是否有数据包接收。如果有操作系统可以开个任务进行解包。

        对外提供的解包接口如下

//定时解包并回应函数,放在主循环或者任务中调用,要保证响应时间不大于100ms
void RDM_Unpack_And_Execute(void)
{
    //按栈的思想处理包
    while(package_buf.package_num != 0)
    {
        rdm_package_prase_t package;
        package = RDM_Contorl_Package_Prase(--package_buf.package_num);
        if(package.rdm_package == NULL_PACKAGE)
            return;
        switch(package.rdm_package)
        {
            case DISC_UNIQUE:
                RDM_Disc_Driver_Respone();
            break;
            case DISC_MUTE:
                RDM_Disc_Mute_Respone(package.source_uid);
                device_info.rdm_stop = 1;
            break;
            case DISC_UN_MUTE:
                RDM_Disc_Un_Mute_Respone(package.source_uid);
                device_info.rdm_stop = 0;
            break;
            case GET_DRIVER_FLAG:
                RDM_Get_Flag_Respone(package.source_uid,device_info.flag);
            break;
            case GET_DRIVER_DMX_ADDR:
                RDM_Get_DMX_Addr_Respone(package.source_uid,device_info.addr);
            break;
            case GET_DRIVER_VERSION:
                RDM_Get_Version_Respone(package.source_uid,device_info.version,strlen((const char *)device_info.version));
            break;
            case GET_DRIVER_INFO:
                RDM_Get_Info_Respone(package.source_uid,device_info.info,strlen((const char *)device_info.info));
            break;
            case SET_DRIVER_DMX_ADDR:
                RDM_Set_Flag_Respone(package.source_uid);
                device_info.addr = package.data[0]; device_info.addr <<= 8;
                device_info.addr |= package.data[1];
            break;
            case SET_DRIVER_FLAG:
                RDM_Set_DMX_Addr_Respone(package.source_uid);
                device_info.flag = package.data[0];
            break;
            default:
                //不用回应...
            break;
        }
    }
}

        设备信息结构体

typedef struct device_info_t
{
    //是否哑音
    uint8_t rdm_stop;
    uint64_t uid;
    uint16_t addr;
    uint8_t flag;
    uint8_t version[231];
    uint8_t info[231];
}device_info_t;

三、接收端组包函数

        组包使用的接口在上一篇有介绍,这里只给出广播包回应及一个使用示例

        唯一特殊包:广播回应包

void RDM_Disc_Driver_Respone(void)
{
    uint8_t buf[24];
    for(int i = 0; i < 7; i++)
        buf[i] = 0xFE;
    buf[7] = 0xAA;

    buf[8]  = ((device_info.uid >> 8*5) & 0xFF) | 0xAA;
    buf[9]  = ((device_info.uid >> 8*5) & 0xFF) | 0x55;
    buf[10] = ((device_info.uid >> 8*4) & 0xFF) | 0xAA;
    buf[11] = ((device_info.uid >> 8*4) & 0xFF) | 0x55;
    buf[12] = ((device_info.uid >> 8*3) & 0xFF) | 0xAA;
    buf[13] = ((device_info.uid >> 8*3) & 0xFF) | 0x55;
    buf[14] = ((device_info.uid >> 8*2) & 0xFF) | 0xAA;
    buf[15] = ((device_info.uid >> 8*2) & 0xFF) | 0x55;
    buf[16] = ((device_info.uid >> 8*1) & 0xFF) | 0xAA;
    buf[17] = ((device_info.uid >> 8*1) & 0xFF) | 0x55;
    buf[18] = ((device_info.uid >> 8*0) & 0xFF) | 0xAA;
    buf[19] = ((device_info.uid >> 8*0) & 0xFF) | 0x55;

    uint32_t check_sum = 0;
    for(int i = 8; i < 20; i++)
    {
        check_sum += buf[i];
    }

    buf[20] = ((check_sum >> 8*1) & 0xFF) | 0xAA;
    buf[21] = ((check_sum >> 8*1) & 0xFF) | 0x55;
    buf[22] = ((check_sum >> 8*0) & 0xFF) | 0xAA;
    buf[23] = ((check_sum >> 8*0) & 0xFF) | 0x55;

    //发送包
    MU_DMX_Change_Mode(DMX_SEND_DATA);
    HAL_UART_Transmit(&huart1,buf,24,HAL_MAX_DELAY);
    MU_DMX_Change_Mode(DMX_RECV_DATA);
    

}

        以获取版本信息回应包为例

static void RDM_Get_Version_Respone(uint64_t uid,uint8_t str[],uint8_t str_len)
{
    rdm_package_t package = {0};
    
    RDM_Package_Init(&package);

    RDM_Package_Set_Uid(&package,uid);

    RDM_Package_Set_Cmd(&package,0x21,0x00C0);

    RDM_Package_Set_Data(&package,str_len+1,str);

    RDM_Package_Send(&package);
}

        最终实现效果,成功设置设备DMX地址并读取设备信息


总结

        RDM协议一经发布便没再更新。到如今还是1.0版本,虽然协议定义的比较简单,也没有逻辑错误,但是在实际开发过程中还是会遇到许多问题。

        比如起始信号的产生和识别,需要关闭GPIO口的复用,使用推挽输出产生起始信号,再切换成串口才能发送数据。识别起始信号需要检测拉低到拉高的上升沿,再马上复用成串口输入,对芯片的频率要求很高,频率低的只能被迫不切换,识别错误帧后再接收正常数据。

        再有就是对识别错误的帧不做任何回应,在调试过程是不可能一遍通的,不回应就无法确定是自己组建的包有误,还是时序有误,还是驱动能力不够,还是接收方设计有问题。导致寻找BUG让人十分头大。与其坐等官方更新不如自己努力更新一下升级RDM协议。

        在这里提出一种设想,将RDM协议的起始信号改为串口发送的数据,至少共2帧0x00表示有数据要传输,然后停止传输至少2帧使接收方进入空闲中断,接收端接收到两帧0x00就进入接收状态接收并处理数据,这样就不需要频繁切换GPIO口复用。

        同时加入符合RDM协议的包结构的错误包,数据是 包缺失错误,校验错误,包结构错误等,收到错误包不需要回应以防网络风暴。如果发送的包连错误包都收不到就需要考虑底层驱动是否错误了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值