目录
前言
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协议的包结构的错误包,数据是 包缺失错误,校验错误,包结构错误等,收到错误包不需要回应以防网络风暴。如果发送的包连错误包都收不到就需要考虑底层驱动是否错误了。