⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 uartcomm.c

📁 Modbus串口通讯协议通讯源代码
💻 C
📖 第 1 页 / 共 3 页
字号:

*******************************************************************************/

#include "includes.h"

#define WRITE_ENABLE   1        //属性可写
#define READ_ENABLE    2        //属性可读

#define RW_ENAB        (WRITE_ENABLE | READ_ENABLE)
#define R_ENAB         (READ_ENABLE)
#define W_ENAB         (WRITE_ENABLE)
#define RW_DISABLE     0

#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))


typedef struct STC_TAG_PROP
{
  INT8S cFlag;
  INT8U abyPropRW[4]; //分别为位、字节、字、双字的操作属性
                      //WRITE_ENABLE与READ_ENABLE作或运算
                      //为0时不能进行相应范围的操作 
  INT32S maxAddr[4];  //分别为位、字节、字、双字的最大允许操作地址,
} STC_TAG_PROP;

const static STC_TAG_PROP s_astcTagProp[] =       //TAG属性定义
{
  {'M', RW_ENAB, RW_ENAB,    RW_ENAB, RW_ENAB, 65535, 65535, 65534, 65532},
  {'G', RW_ENAB, RW_ENAB,    RW_ENAB, RW_ENAB, 65535, 65535, 65534, 65532},
  {'L', RW_ENAB, RW_ENAB,    RW_ENAB, RW_ENAB, 65535, 65535, 65534, 65532},
  {'S', RW_ENAB, RW_ENAB,    RW_ENAB, RW_ENAB, 65535, 65535, 65534, 65532},
  {'F', RW_ENAB, RW_ENAB,    RW_ENAB, RW_ENAB, 65535, 65535, 65534, 65532},
  {'X', R_ENAB,  R_ENAB,     R_ENAB,  R_ENAB,  65535, 65535, 65534, 65532},
  {'Y', RW_ENAB, RW_ENAB,    RW_ENAB, RW_ENAB, 65535, 65535, 65534, 65532},
  {'T', RW_ENAB, RW_DISABLE, RW_DISABLE, RW_ENAB, 65535, 65535, 65535, 65535},
  {'C', RW_ENAB, RW_DISABLE, RW_DISABLE, RW_ENAB, 65535, 65535, 65535, 65535},
  {'R', RW_ENAB, RW_ENAB,    RW_ENAB,    RW_ENAB, 65535, 65535, 65534, 65532},
  {'I', R_ENAB,  R_ENAB,     R_ENAB,  R_ENAB,  65535, 65535, 65534, 65532},
  {'Q', RW_ENAB, RW_ENAB,    RW_ENAB, RW_ENAB, 65535, 65535, 65534, 65532},
  {'P', RW_DISABLE, RW_DISABLE, RW_DISABLE, RW_ENAB, 65535, 65535, 65534, 65532}
};

INT32S g_iComNumber = DEF_COM1;               //工作过程中选取的串口值
INT32S g_iMaxFrameSize = DEF_MaxFrameSize;   //工作过程中的最大帧长
INT32S g_iMaxOutTime = DEF_MaxOutTime;       //工作过程中的等待时间

/*******************************************************************************
函数名: IsTagValid
描  述: 检查tag是否为有效
输  入: pstcTag   需要检查的TAG
        byTestRW   对读写属性进行检查,为下列四者之一
                   RW_ENAB   : 必须包含读写属性
                   R_ENAB    : 必须包含只读属性
                   W_ENAB    : 必须包含可写属性
                   RW_DISABLE: 包含只读或只写属性
输  出: 无
返回值: FALSE  TAG无效
        TRUE   TAG有效
*******************************************************************************/
static INT8U IsTagValid(const STC_TAG *pstcTag, INT8U byTestRW)
{
  INT8U byIndex;
  INT8U byRangeIdx;

  if (pstcTag->enmType > ENUM_OPER_DWORD)
  {
    return FALSE;
  }

  for (byIndex = 0; byIndex < ARRAY_SIZE(s_astcTagProp); byIndex++)
  {
    if (pstcTag->cFlag == s_astcTagProp[byIndex].cFlag)
    {
      break;
    }
  }

  if (byIndex >= ARRAY_SIZE(s_astcTagProp))
  {
    return FALSE;
  }

  byRangeIdx = (INT8U)pstcTag->enmType;
  if (s_astcTagProp[byIndex].abyPropRW[byRangeIdx] == RW_DISABLE)
  { //对应范围操作不允许
    return FALSE;
  }
  if ((s_astcTagProp[byIndex].abyPropRW[byRangeIdx] & byTestRW) != byTestRW)
  { //不包含对应范围操作数
    return FALSE;
  }

  if (pstcTag->addr >= s_astcTagProp[byIndex].maxAddr[byRangeIdx]
     || pstcTag->addr < 0)
  {
    return FALSE;
  }
  else if (byRangeIdx == ENUM_OPER_BIT && (pstcTag->bit < 0 || pstcTag->bit > 7))
  { //位操作数但位溢出
    return FALSE;
  }

  return TRUE;
}

/*******************************************************************************
函数名: IsDecodeSuccess
描  述: 解码十进制格式的tag,并判断tag是否为有效
输  入: pszDecTag  需要解码的Tag名, 为十进制格式
        byTestRW   对TAG读写属性进行检查,为下列四者之一
                   RW_ENAB   : 必须包含读写属性
                   R_ENAB    : 必须包含只读属性
                   W_ENAB    : 必须包含可写属性
                   RW_DISABLE: 包含只读或只写属性
输  出: pstcTag    存放解码后的TAG,pstcTag为NULL时不输出
返回值: 0  解码失败
        1  解码成功
其他  :
十进制方式位Tag定义             (引导符 + 偏移地址 + '.' + 位)
十进制方式字节、字、双字Tag定义 (引导符 + 范围符 + 偏移地址) 范围符有'B' 'W' 'D'

  有效的引导符         该引导符操作范围            读写操作
  M: 辅助继存器       (位、字节、字、双字)         读写
  G: 全局继存器       (位、字节、字、双字)         读写
  L: 局部继存器       (位、字节、字、双字)         读写
  S: 静态继存器       (位、字节、字、双字)         读写
  F: 特殊功能继存器   (位、字节、字、双字)         读写
  X: 输入继存器       (位、字节、字、双字)         只读
  Y: 输出继存器       (位、字节、字、双字)         读写
  T: 定时器           (位、双字)                   读写
  C: 计数器           (位、双字)                   读写
  R: 只读继存器       (位、双字、字、双字)         只读
  I: 外部输入继存器   (位、双字、字、双字)         只读
  Q: 外部输出继存器   (位、双字、字、双字)         只写
  P: 程序继存器       (双字)                       读写

以下为有效十进制位TAG:
  M100.0、G10.7、L20.2、S11.2、 F0.0、 X0.0、 Y0.0、T1.2、  C2.3、 R0.1、I0.2、 Q1.1
对应十六进制位TAG(固定为6个字节):
  M00064  G7000A L20014 S2000B F00000 X00000 Y00000 T20001 C30002 R10000 I20000 Q10001

以下为有效十进制字节TAG
  MB100、GB100、LB100、 SB11、 FB0、  XB0、  YB0、  RB0、  IB0、  QB0
对应十六进制字节TAG(固定为6个字节):
  MB0064 GB0064 LB0064 SB000B FB0000 XB0000 YB0000 RB0000 EB0000 QB0000

以下为有效十进制字TAG
  MW100、GW100、LW100、  SW11、FW0、  XW0、  YW0、  RW0、  IW0、  QW0
对应十六进制字TAG(固定为6个字节):
  MW0064 GW0064 LW0064 SW000B FW0000 XW0000 YW0000 RW0000 IW0000 QW0000

以下为有效十进制双字TAG
  MD100、GD100、LD100、  SD11、FD0、  XD0、  YD0、  TD0、  CD0、  RD0、  ID0、  QD0
对应十六进制双字TAG(固定为6个字节):
  MD0064 GD0064 LD0064 SD000D FD0000 XD0000 YD0000 TD0000 CD0000 RD0000 ID0000 QD0000
*******************************************************************************/
INT8U IsDecodeSuccess(const char *pszDecTag, STC_TAG *pstcTag, INT8U byTestRW)
{
  EOperType enmType;
  STC_TAG   stcTag;
  INT32S addr      = -1;
  INT32S bit       = -1;
  INT8S  cFlag     = toupper(pszDecTag[0]);
  INT8S  cRange    = toupper(pszDecTag[1]);

  if (!isupper(cFlag))
  {
    return FALSE;                                 //若不是合法引导符,返回false
  }

  if (isdigit(cRange))                            //表示为位TAG操作
  {
    sscanf(pszDecTag + 1, "%d.%d", &addr,&bit);
    stcTag.cFlag      = cFlag;
    stcTag.enmType    = ENUM_OPER_BIT;
    stcTag.addr       = addr;                     //偏移地址
    stcTag.bit        = bit;                      //位
    if (pstcTag != NULL)
    {
      *pstcTag = stcTag;
    }
    return IsTagValid(&stcTag, byTestRW);
  }
  else if (cRange == 'B' || cRange == 'W' || cRange == 'D')
  {
    sscanf(pszDecTag + 2, "%d", &addr);           //偏移地址
    if (cRange == 'B')
    {
      enmType = ENUM_OPER_BYTE;
    }
    else if (cRange == 'W')
    {
      enmType = ENUM_OPER_WORD;
    }
    else
    {
      enmType = ENUM_OPER_DWORD;
    }

    stcTag.cFlag      = cFlag;
    stcTag.enmType    = enmType;
    stcTag.addr       = addr;
    stcTag.bit        = -1;                       //位无效

    if (pstcTag != NULL)
    {
      *pstcTag = stcTag;
    }
    return IsTagValid(&stcTag, byTestRW);
  }
  else
  {
    return FALSE;
  }
}

/*******************************************************************************
函数名: CaculateLRC
描  述: 计算字符串msg的校验码
输  入: msg       需校验的信息字符串
        msgLength  信息字符串的长度
输  出: 无
返回值: INT8U 类型,信息串的校验码
*******************************************************************************/
static INT8U CaculateLRC(const INT8U *msg, INT16U msgLength)
{
  INT8U uchLRC = 0;
  while (msgLength--)
  {
    uchLRC += *msg++;
  }
  return (INT8U)(-(INT8S)uchLRC);
}

/*******************************************************************************
函数名: SendReadBitCommand
描  述: 发送读位信息,供读位函数调用
输  入: byAddr    选用的下位机地址,如1,2等
        pstcTagCmd 读取位的信息TAG,详见通讯协议的方式
输  出: 无
返回值: INT32S 类型,其值表示函数的执行结果,操作成功返回发送的数据长度,否则返回
        错误代码,调用GetErrMsg可获得详细的错误信息
*******************************************************************************/
INT32S SendReadBitCommand(const STC_TAG_COMMAND *pstcTagCmd, INT8U byAddr)
{
    INT32S len, result;
    INT8S* buf = (INT8S *)malloc(g_iMaxFrameSize + 1);  //用来存储发送的数据

    /*将buf中的数据格式化为通讯协议所要求的格式*/
    len = sprintf(buf, ":%02X%02X%C%C%04X%02X", byAddr, COMMAND_BIT_RD,
                  pstcTagCmd->stcTag.cFlag, pstcTagCmd->stcTag.bit + 0x30,
                  pstcTagCmd->stcTag.addr, pstcTagCmd->byRange);
    /*LRC校验和结束符*/
    len += sprintf(&buf[len], "%02X\x0d\x0a", CaculateLRC(buf + 1, len - 1));

    result = sio_write(g_iComNumber, buf, len);    //发送到串口发送缓冲区

    free(buf);                                     //释放buf指针

    #ifndef  FORTESTONLY                           //测试编译开关
      return result;
    #else
      return len;                                  //for test only
    #endif  
}

/*******************************************************************************
函数名: SendReadByteCommand

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -