前言文章:
在前面的一篇文章中,我根据实际的项目需求设计了一种基于
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_Rs485
和Wifi_To_Plc
这样的交互对象,虽然见名知意,但是函数体内相似代码片段高达99%。 - 映射关系导入容易,但是映射回调函数需要分别编写。
改进后
- 所有映射关系均已存放到
ComData_Array
。 - 作为主机还是从机的选择,可以通过系统变量
System_Parameter.WorkMode
进行选择。 - 通讯协议可以支持的更加广泛,典型的有
10bit
的Modbus和11bit
PPI。 - 数据转发函数共用
Public_DataHandle
进行处理,数据请求发起方psrc
和交付方pdest
由Get_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