多串口数据转发模型的改进

博客介绍了对单片机多串口数据转发模型的改进,解决了原有模型中仅限ModBus协议、固定串口设置及主从模式混淆的问题。通过改进后的代码,实现了更灵活的协议支持和主从模式切换,并统一了数据转发处理函数。同时,增加了对不同通信链路的清晰管理和中断处理。
摘要由CSDN通过智能技术生成

前言文章:

单片机多串口数据转发模型

在前面的一篇文章中,我根据实际的项目需求设计了一种基于STC8系列与PLC的PORT0间的多主机/多从机的数据交互模型。但是,这种结构在实际的使用过程中仅满足了部分使用需求,比如:

①走的仅限于ModBus协议。
②串口硬件设置为:1个起始位,8个数据位,1个停止位。
③对于单主、多主模型存在混淆。

因此本期对于上述缺陷做出改进。

需要改进的代码块

@function: **1.0.4 多串口数据轮询处理机制**
 /*设置队列读指针*/
    #define SET_RPTR(x) ((COM_UART##x).Rptr = (((COM_UART##x).Rptr + 1U) % MAX_NODE))				   
    /*设置队列写指针*/
    #define SET_WPTR(x) ((COM_UART##x).Wptr = (((COM_UART##x).Wptr + 1U) % MAX_NODE))

    /*串口一对一数据转发数据结构*/
    typedef struct
    {
        SEL_CHANNEL Source_Channel; /*数据起源通道*/
        SEL_CHANNEL Target_Channel; /*数据交付通道*/
        void (*pHandle)(void);
    } ComData_Handle;

    /*定义当前串口交换序列*/
    const ComData_Handle ComData_Array[] =
	{
		{CHANNEL_PLC, CHANNEL_RS485, Plc_To_Rs485},
		{CHANNEL_WIFI, CHANNEL_PLC, Wifi_To_Plc},
    };

    /*增加映射关系时,计算出当前关系数*/
    #define COMDATA_SIZE (sizeof(ComData_Array) / sizeof(ComData_Handle))

    /**
    * @brief	串口1对1数据转发
    * @details	
    * @param	None
    * @retval	None
    */
    void Uart_DataForward(SEL_CHANNEL Src, SEL_CHANNEL Dest)
    {
	    uint8_t i = 0;

	    for (i = 0; i < COMDATA_SIZE; i++)
	    {
		    if ((Src == ComData_Array[i].Source_Channel) && (Dest == ComData_Array[i].Target_Channel))
		    {
			ComData_Array[i].pHandle();
		    }
	    }
    }

    /**
    * @brief	串口事件处理
    * @details	
    * @param	None
    * @retval	None
    */
    void Uart_Handle(void)
    {
	    /*数据交换序列1:PLC与RS485进行数据交换*/
	    Uart_DataForward(CHANNEL_PLC, CHANNEL_RS485);
	    /*数据交换序列2:WIFI与PLC进行数据交换*/
	    Uart_DataForward(CHANNEL_WIFI, CHANNEL_PLC);
    }

    /**
    * @brief	PLC数据交付到RS485
    * @details	
    * @param	None
    * @retval	None
    */
    void Plc_To_Rs485(void)
    {
	    /*STC串口4收到PLC发出的数据*/
	    if ((COM_UART4.LNode[COM_UART4.Rptr].Frame_Flag)) //&& (COM_UART4.LNode[COM_UART4.Rptr].Rx_Length)
	    {												  
		    /*如果串口4接收到的数据帧不是EBM所需的,过滤掉*/
		    if (COM_UART4.LNode[COM_UART4.Rptr].Rx_Buffer[0] != MODBUS_SLAVEADDR)
		    {	/*标记该接收帧以进行处理*/
			    COM_UART4.LNode[COM_UART4.Rptr].Frame_Flag = false;
			    /*允许485发送*/
			    USART3_EN = 1;
			    /*数据转发给RS485时,数据长度+1,可以保证MAX3485芯片能够最后一位数据刚好不停止在串口的停止位上*/
			    Uartx_SendStr(&Uart3, COM_UART4.LNode[COM_UART4.Rptr].Rx_Buffer, COM_UART4.LNode[COM_UART4.Rptr].Rx_Length + 1U);
			    /*接收到数据长度置为0*/
			    COM_UART4.LNode[COM_UART4.Rptr].Rx_Length = 0;
			    /*发送中断结束后,清空对应接收缓冲区*/
			    memset(&COM_UART4.LNode[COM_UART4.Rptr].Rx_Buffer[0], 0, MAX_SIZE);
			    /*发送完一帧数据后拉低*/
			    USART3_EN = 0;
			    /*读指针指到下一个节点*/
			    SET_RPTR(4);
		    }

		    /*目标设备发出应答*/
		    if ((COM_UART3.LNode[COM_UART3.Rptr].Frame_Flag)) //&& (COM_UART3.LNode[COM_UART3.Rptr].Rx_Length)
		    {
			    /*标记该接收帧已经进行处理*/
			    COM_UART3.LNode[COM_UART3.Rptr].Frame_Flag = false;
			    /*数据返回给请求对象*/
			    Uartx_SendStr(&Uart4, COM_UART3.LNode[COM_UART3.Rptr].Rx_Buffer, COM_UART3.LNode[COM_UART3.Rptr].Rx_Length);
			    /*接收到数据长度置为0*/
			    COM_UART3.LNode[COM_UART3.Rptr].Rx_Length = 0;
			    /*发送中断结束后,清空对应接收缓冲区*/
			    memset(&COM_UART3.LNode[COM_UART3.Rptr].Rx_Buffer[0], 0, MAX_SIZE);
			    /*读指针指到下一个节点*/
			    SET_RPTR(3);
		    }
	    }
    }

改进后的代码块如下:

@function: **1.0.4 改进后的多串口数据轮询处理机制**
#include "report.h"
#include "usart.h"

void Public_DataHandle(SEL_CHANNEL src, SEL_CHANNEL dest);
void Uart_DataForward(SEL_CHANNEL Src, SEL_CHANNEL Dest);

/*定义当前串口交换序列*/
const ComData_Handle code ComData_Array[] =
	{	/*作为从机时情况*/
		{CHANNEL_WIFI, CHANNEL_PLC, Public_DataHandle},
		{CHANNEL_LAN, CHANNEL_PLC, Public_DataHandle},
		{CHANNEL_RS485, CHANNEL_PLC, Public_DataHandle},
		/*作为主机时情况*/
		{CHANNEL_PLC, CHANNEL_WIFI, Public_DataHandle},
		{CHANNEL_PLC, CHANNEL_LAN, Public_DataHandle},
		{CHANNEL_PLC, CHANNEL_RS485, Public_DataHandle}
	};

#define COMDATA_SIZE (sizeof(ComData_Array) / sizeof(ComData_Handle))

/*创建4条链队*/
Uart_List xdata Uart_LinkList[MAX_LQUEUE] = {0};

/**
 * @brief	初始化静态链队
 * @details
 * @param	*List:当前链队指针
 * @retval	None
 */
void Init_ListQueue(Uart_List *List)
{
	uint8_t j = 0;

	for (j = 0; j < MAX_NODE; j++)
	{
		/*初始化队列数据域*/
		List->LNode[j].Source_Channel = CHANNEL_IDLE;
		List->LNode[j].Target_Channel = CHANNEL_IDLE;
		List->LNode[j].Frame_Flag = false;
		List->LNode[j].Timer_Flag = false;
		List->LNode[j].Rx_Length = 0;
		List->LNode[j].OverTime = MAX_SILENCE;
		memset(&List->LNode[j].Rx_Buffer[0], 0, MAX_SIZE);
	}
	/*初始化链表节点读写指针*/
	List->Wptr = List->Rptr = 0;
}

/**
 * @brief	串口事件处理
 * @details
 * @param	None
 * @retval	None
 */
void Uart_Handle(void)
{
	static uint8_t i = 0;
	
	if (System_Parameter.WorkMode == SLAVE)
	{
		for (i = 0; i < COMDATA_SIZE - 3U; i++)
		{	
			ComData_Array[i].pHandle(ComData_Array[i].Source_Channel, ComData_Array[i].Target_Channel);
		}
	}
	else
	{
		switch (System_Parameter.CurrentSlave)
		{
			case ETHERNET_ID: 
			{
				ComData_Array[3].pHandle(ComData_Array[3].Source_Channel, ComData_Array[3].Target_Channel);
			}break;
			case WIFI_ID:
			{
				ComData_Array[4].pHandle(ComData_Array[4].Source_Channel, ComData_Array[4].Target_Channel);
			}break;
			case RS485_ID:
			{
				ComData_Array[5].pHandle(ComData_Array[5].Source_Channel, ComData_Array[5].Target_Channel);
			}break;
			default : break;
		}
	}
}

/*禁止编译器优化该模块*/
#pragma OPTIMIZE(2)
/**
 * @brief	获得目标通道链表地址
 * @details
 * @param	channel 通道号
 * @retval	p 指向链表的指针
 */
static Uart_List *Get_Target_Channel(channel)
{
	Uart_List *p = NULL;
	switch (channel)
	{
	case CHANNEL_LAN:
		p = &COM_UART1;
		break;
	case CHANNEL_WIFI:
		p = &COM_UART2;
		break;
	case CHANNEL_RS485:
		p = &COM_UART3;
		break;
	case CHANNEL_PLC:
		p = &COM_UART4;
		break;
	default:
		break;
	}
	return p;
}

/**
 * @brief	获得目标通道对应串口
 * @details
 * @param	channel 通道号
 * @retval	p 指向链表的指针
 */
static Uart_HandleTypeDef *Get_Target_Uart(channel)
{
	Uart_HandleTypeDef *p = NULL;
	switch (channel)
	{
	case CHANNEL_LAN:
		p = &Uart1;
		break;
	case CHANNEL_WIFI:
		p = &Uart2;
		break;
	case CHANNEL_RS485:
		p = &Uart3;
		break;
	case CHANNEL_PLC:
		p = &Uart4;
		break;
	default:
		break;
	}
	return p;
}


/**
 * @brief	数据帧公共转发
 * @details
 * @param	src 数据源请求通道
 * @param   dest 数据交付通道
 * @retval	None
 */
void Public_DataHandle(SEL_CHANNEL src, SEL_CHANNEL dest) 
{
	Uart_List * const psrc = Get_Target_Channel(src);
	Uart_List * const pdest = Get_Target_Channel(dest);
	Uart_HandleTypeDef * const psuart = Get_Target_Uart(src);
	Uart_HandleTypeDef * const peuart = Get_Target_Uart(dest);
	static uint8_t i = 0;

	if ((psrc == NULL) || (pdest == NULL) || (psuart == NULL) || (peuart == NULL))
	{
		return;
	}
	/*做主机时plc目标通道无法确定*/
	if (System_Parameter.WorkMode == MASTER)
	{
		psrc->LNode[psrc->Rptr].Target_Channel = dest;
	}
	/*检查当前节点源地址和目标地址是否与正在执行上目标相同*/
	if ((psrc->LNode[psrc->Rptr].Source_Channel == src) && (psrc->LNode[psrc->Rptr].Target_Channel == dest))
	{
		/*检测发起请求接收缓冲区*/
		if (psrc->LNode[psrc->Rptr].Frame_Flag)
		{
#if (USE_PRINTF_DEBUG)
			printf("1->>source %bd, target %bd, pdest %d, ptr 0x%X\r\n",
			psrc->LNode[psrc->Rptr].Source_Channel, psrc->LNode[psrc->Rptr].Target_Channel, psrc, psrc->Rptr);
#endif
			/*标记该接收帧以进行处理*/
			psrc->LNode[psrc->Rptr].Frame_Flag = false;
			if (dest == CHANNEL_RS485)
			{
				/*允许485发送*/
				USART3_EN = 1;
				/*数据转发给RS485时,数据长度+1,可以保证MAX3485芯片能够最后一位数据刚好不停止在串口的停止位上*/
				psrc->LNode[psrc->Rptr].Rx_Length += 1U;
			}
			/*启动对应端硬件数据转发*/
			Uartx_SendStr(peuart, psrc->LNode[psrc->Rptr].Rx_Buffer, psrc->LNode[psrc->Rptr].Rx_Length);
			/*接收到数据长度置为0*/
			psrc->LNode[psrc->Rptr].Rx_Length = 0;
			/*发送中断结束后,清空对应接收缓冲区*/
			memset(&psrc->LNode[psrc->Rptr].Rx_Buffer[0], 0, MAX_SIZE);
			if (dest == CHANNEL_RS485)
			{
				/*发送完一帧数据后拉低*/
				USART3_EN = 0;
			}
			/*读指针指到下一个节点*/
			psrc->Rptr = ((psrc->Rptr + 1U) % MAX_NODE);
		}
	}

	for (i = 0; i < MAX_NODE; i++)
	{
		if ((pdest->LNode[i].Source_Channel != dest) || (pdest->LNode[i].Target_Channel != src))
		{
			continue;
		}
		/*目标设备发出应答*/
		if (pdest->LNode[i].Frame_Flag)
		{
#if (USE_PRINTF_DEBUG)
			printf("2->>source %bd, target %bd, pdest %d, ptr 0x%X\r\n\r\n",
			pdest->LNode[i].Source_Channel, pdest->LNode[i].Target_Channel, pdest, i);
#endif		
			/*标记该接收帧已经进行处理*/
			pdest->LNode[i].Frame_Flag = false;
			if (src == CHANNEL_RS485)
			{
				/*允许485发送*/
				USART3_EN = 1;
				/*数据转发给RS485时,数据长度+1,可以保证MAX3485芯片能够最后一位数据刚好不停止在串口的停止位上*/
				pdest->LNode[i].Rx_Length += 1U;
			}
			/*数据返回给请求对象*/
			Uartx_SendStr(psuart, pdest->LNode[i].Rx_Buffer, pdest->LNode[i].Rx_Length);
			/*接收到数据长度置为0*/
			pdest->LNode[i].Rx_Length = 0;
			/*发送中断结束后,清空对应接收缓冲区*/
			memset(&pdest->LNode[i].Rx_Buffer[0], 0, MAX_SIZE);
			if (src == CHANNEL_RS485)
			{
				/*发送完一帧数据后拉低*/
				USART3_EN = 0;
			}
		}
	}
}

对于改进前后说明

改进前

  • Uart_Handle 函数作用不明确,主从混淆不清。
  • 例如Plc_To_Rs485Wifi_To_Plc 这样的交互对象,虽然见名知意,但是函数体内相似代码片段高达99%。
  • 映射关系导入容易,但是映射回调函数需要分别编写。

改进后

  • 所有映射关系均已存放到ComData_Array
  • 作为主机还是从机的选择,可以通过系统变量System_Parameter.WorkMode进行选择。
  • 通讯协议可以支持的更加广泛,典型的有10bit的Modbus和11bitPPI。
  • 数据转发函数共用Public_DataHandle进行处理,数据请求发起方psrc和交付方pdestGet_Target_Channel自动获取。

最后在 uart.c 中加入 src 和 dest 即可:

@function: **1.0.5 串口中断数据帧处理例举**

/*********************************************************
 * 函数名:void Uart2_ISR() interrupt 8 using 2
 * 功能:  串口2中断函数
 * 参数:
 * 作者:  LHC
 * note:
 *		使用的是定时器2作为波特率发生器,4G、wifi用
 **********************************************************/
void Uart2_ISR() interrupt 8 using 2
{   /*发送中断*/
    if (S2CON & S2TI)
    {
        S2CON &= ~S2TI;
        /*发送完成,清除占用*/
        Uart2.Uartx_busy = false;
    }
    /*接收中断*/
    if (S2CON & S2RI)
    {
        S2CON &= ~S2RI;

        COM_UART2.LNode[COM_UART2.Wptr].Timer_Flag = true;
        if (!COM_UART2.LNode[COM_UART2.Wptr].Frame_Flag)
        {
            COM_UART2.LNode[COM_UART2.Wptr].OverTime = MAX_SILENCE;
            if (COM_UART2.LNode[COM_UART2.Wptr].Rx_Length < MAX_SIZE)
            {
                COM_UART2.LNode[COM_UART2.Wptr].Rx_Buffer[COM_UART2.LNode[COM_UART2.Wptr].Rx_Length++] = S2BUF;
                COM_UART2.LNode[COM_UART2.Wptr].Source_Channel = CHANNEL_WIFI;
                COM_UART2.LNode[COM_UART2.Wptr].Target_Channel = CHANNEL_PLC;
            }
        }
        /*设置当前请求通道*/
        current_request_channel = CHANNEL_WIFI;
    }
}

如果需要源码请访问:

GIthub链接地址:Wireless_PLC

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值