兼容DMX及RDM协议的数据包接收方式 & 串口BREAK中断

目录

一、理论实现

1、问题

2、解决

二、代码实现

总结


一、理论实现

1、问题

        RDM协议是由DMX协议扩展而来,起始信号与DMX协议相同,最开始实现RDM协议包的接收采用DMA+空闲中断实现,但在兼容DMX协议时出了问题,DMX协议的字段字节会有占,即下图的序号九,这个标记可以是0到1s的时间,有可能会引发空闲中断而导致提前提取数据包。

2、解决

        根据包头将RDM包和DMX包分开处理,如果包头是0x00或其他DMX包规定的起始字节则在下一次起始信号到达时结包(DMX数据包最高1s发送44次,不必担心延迟问题)。如果包头是0xCC则在产生空闲中断时结包(RDM包需要及时响应,如果按DMX结包方式会出错)。具体实现如下。

二、代码实现

        BREAK中断即检测断开帧,如果检测到超过10或11位低电平,则会在高电平来时产生中断请求,完美适配DMX协议的起始信号检测。

        中断配置如下

    //先关闭空闲中断
    __HAL_UART_DISABLE_IT(&huart1,UART_IT_IDLE);
    //开启BREAK中断
    USART1->CR2 |= UART_IT_LBD;
    //开启LIN模式
    USART1->CR2 |= USART_CR2_LINEN;
    //检测11位低电平为断开
    USART1->CR2 |= UART_LINBREAKDETECTLENGTH_11B;
    /* 开启中断 */
    HAL_NVIC_SetPriority(USART1_IRQn,0,2);
    HAL_NVIC_EnableIRQ(USART1_IRQn);

        中断服务函数

void USART1_IRQHandler(void)
{
    //如果使能了空闲中断
    if(huart1.Instance->CR1 & USART_CR1_IDLEIE)
        HAL_UART_ReceiveIdleCallback(&huart1);
    //如果使能了BREAK中断
    if(huart1.Instance->CR2 & UART_IT_LBD)
        HAL_UART_BreakCallback(&huart1);
 
    HAL_UART_IRQHandler(&huart1);
}

        BREAK中断回调函数,初始化完后会开启DMA接收,如果接收到断开帧就会进入此中断,如果之前有接收到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);
        }
    }
}

        串口接收完成中断,接收完包头判断是不是RDM包,如果是就开启空闲中断,在空闲中断中结RDM包,如果不是就默认是DMX包。在下一个包到来时结包,需要注意的是RX_BUF_MAX > DMX最大包长,否则会反复进入接收完成中断覆盖之前接收的数据

//串口接收完成中断
//配置成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);
        }

        //开始接收包
        HAL_UART_Receive_DMA(&huart1,rx_buf.buf,RX_BUF_MAX);
    }
}

        串口空闲中断,用于结RDM包,这里结束了DMA接收却没有再开启,那么下一个BREAK中断要怎么进入呢?需要注意RDM协议是半双工的,接收完数据包需要处理并回应,回应时不能开启DMA接收,回应结束后再开启DMA接收,所以我把开启DMA接收的函数放在了发送完成的函数结尾。

void HAL_UART_ReceiveIdleCallback(UART_HandleTypeDef *huart)
{
    //当触发了串口空闲中断
    if((__HAL_UART_GET_FLAG(huart,UART_FLAG_IDLE) != RESET)) 
    {
		if(huart->Instance == USART1)
		{
            //无法用 __HAL_UART_CLEAR_FLAG 清除
            __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)
            {
                //提取包到二级缓冲区
                memcpy((void *)rdm_package_buf,(void *)rx_buf.buf,rx_buf.index);
                rdm_package_ne = 1;
            }

            //结包,接收新包
            rx_buf.index = 0;

		}
    }
}

        串口初始化完整代码

//DMX+RDM+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);

    /* 串口 */
    //需要先开启DMA再开启串口
    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);

    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_LINKDMA(&huart1, hdmarx, hdma_usart1_rx);

    HAL_DMA_ChannelMap(&hdma_usart1_rx, DMA_CHANNEL_MAP_USART1_RD);

    //先关闭空闲中断
    __HAL_UART_DISABLE_IT(&huart1,UART_IT_IDLE);
    //开启BREAK中断
    USART1->CR2 |= UART_IT_LBD;
    //开启LIN模式
    USART1->CR2 |= USART_CR2_LINEN;
    //检测11位低电平为断开
    USART1->CR2 |= UART_LINBREAKDETECTLENGTH_11B;
    /* 开启中断 */
    HAL_NVIC_SetPriority(USART1_IRQn,0,2);
    HAL_NVIC_EnableIRQ(USART1_IRQn);


    // //调用 HAL_UART_Receive_DMA 会自动中断
    // HAL_NVIC_SetPriority(DMA1_Channel2_IRQn,0,2);
    // HAL_NVIC_EnableIRQ(DMA1_Channel2_IRQn);

#ifdef RDM_MODE_CTRL
    RDM_Uid_Init();
#endif
    MU_DMX_Change_Mode(DMX_RECV_DATA);
    

}

        改变模式函数

//改变模式
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);
    }
}

总结

        原以为DMX协议的起始信号检测非常反人类,需要切换GPIO为输入模式,或者当成错误帧处理。后来了解到了BREAK中断才发现实现如此简单,进行驱动编写的时候还是要多看看芯片的用户手册,许多功能都是HAL库没有封装好的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值