zhanan 发表于 2019-1-9 13:05:33

超简单的用状态机实现的modbus从机模式来啦

本帖最后由 zhanan 于 2019-1-9 13:14 编辑

上次写了个主机模式:https://www.amobbs.com/forum.php?mod=viewthread&tid=5705140&page=1&_dsign=c924640c

从机模式和主机模式不一样,从机模式是被动的,收到相关的信息后,处理然后回传,接着再等待。
用状态机的写法是一样的,主要是功能处理要啰嗦一些。我把常用的功能收到什么、回传什么整理好了,照着套吧。

不多说了,上图,然后上程序

zhanan 发表于 2019-1-9 13:06:07

void mb_gncl(void);// 功能处理

#define MBID 2 /* 从机站号 */
#define MBZJG 1 /* 帧间隔时间(按波特率调整) */
#define MBSIZE 14/* 缓存大小 */
uchar mmbbuf; // mod缓存
uchar mmbgs;// mod数据个数,从1开始
uchar mmbjs;// mod帧间计时(用于判断帧尾)/出错码/mod发送指针(从0开始)
uchar mmbzt;// mod状态

#define DEkz PORTD.2 /* PD2为485发送控制 */
#define Dreg UDR   /* 收发数据寄存器 */
/***/
interrupt void mb_rx(void)// 接收缓存非空中断
{
uchar nrxd=Dreg; // 读接受缓存
if(mmbzt<=2)
{
    mmbjs=0;// 复位帧间计时
    switch (mmbzt)
    {
      case 0:
      mmbgs=1;
      mmbbuf=nrxd; // 站点ID
      if(nrxd==MBID) mmbzt=1; else mmbzt=2; // 是本机站号转后续字节,否则等待帧结束
      break;
      case 1:
      if(mmbgs<(MBSIZE-1)) mmbbuf=nrxd; else mmbzt=2; // 装入后续字节,超量则等待帧结束
      break;
    }
}
}
/***/
interrupt void mb_tx(void)// TXC发送完成中断 DRE→Dreg空中断
{
同主机模式
}
/***/
void mb_zjjs(void)// 帧间计时判断
{
同主机模式
}
/***/
void mb_slave(void)
{
uint ncrc;
if(mmbzt==8) // 收到一帧数据
{
    if(mmbgs>=5) // 至少5字节
    {
      if(mb_crc16(mmbbuf,mmbgs)==0) // crc校验正确
      {
      mmbjs=0;// 出错码清0
      mb_gncl(); // 功能处理
      if(mmbjs) // 如果有错误
      {
          mmbbuf|=0x80; // 功能码最高位置1
          mmbbuf=mmbjs; // 错误码
          mmbgs=3;
      }
      ncrc=mb_crc16(mmbbuf,mmbgs); // 计算crc
      mmbbuf=ncrc;    // crc低字节
      mmbbuf=ncrc>>8; // crc高字节
      mmbjs=0; // 发送指针
      DEkz=1; Dreg=mmbbuf; // 485芯片DE高电平,启动发送
      mmbzt=6; // 置为发送状态
      return;
      }
    }
    mmbzt=0; // 回空闲状态
}
}
/***/

zhanan 发表于 2019-1-9 13:06:48

/*** 从机模式功能处理 ***/
void mb_gncl(void)// 功能处理,若出错则mmbjs为错误码
{
switch (mmbbuf)// 按功能码转
{
    case 1: case 2:// 读n个线圈、读n个位输入
      // 处理
          // buf 线圈/位首地址
          // buf 线圈/位个数
          // ......
      // 回传
      mmbbuf=1;// 回传字节数=n/8+1
      mmbbuf=mxsd; // 显示灯线圈/位7-0 ,0对应线圈/位首地址
      mmbgs=4;
      break;
    case5:// 写1个线圈
      // 处理
          // buf 线圈地址
          // buf: 0xFF 置1,0x00 置0
          // buf: 0x00
          // ......
      // 回传:原样回传
      mmbgs=6;//
      break;
    case 15:// 写n个线圈
      // 处理
          // buf 线圈首地址
          // buf 线圈个数
          // buf 数据字节数
          // buf=0x00; // 线圈位7-0 ,位0对应线圈首地址
          // buf=0x00; // 15-8
          // ......
      // 回传:前6个字节(buf-)原样回传
      mmbgs=6;//
      break;
    case3: case 4:// 读n个、读1个寄存器
      // 处理
          // buf 寄存器首地址
          // buf 寄存器个数,读一个=1
          // ......
      // 回传
      mmbbuf=2;// 回传字节数,读一个寄存器字=2
      mmbbuf=mwd>>8; // 温度值高字节
      mmbbuf=mwd;    // 温度值低字节
      mmbgs=5;
      break;
    case6:// 写1个寄存器
      // 处理
          // buf 寄存器地址
          // buf 寄存器数据
          // ......
      mwd=((uint)mmbbuf<<8)|mmbbuf;// 改写温度值
      // 回传:原样回传
      mmbgs=6;
      break;
    case 16:// 写n个寄存器
      // 处理
          // buf 寄存器首地址
          // buf 寄存器个数
          // buf 数据字节数
          // buf寄存器1数据
          // buf 寄存器2数据
          // buf寄存器3数据
          // ......
      mwd=((uint)mmbbuf<<8)|mmbbuf;// 改写温度值
      // 回传:前6个字节(buf-)原样回传
      mmbgs=6;
      break;
    default:// 无效功能
      mmbjs=1; // 功能码错误
      break;
} // endcase
}
/***/

lvxinchao-206 发表于 2020-6-28 11:01:35

这种处理方式 比较好我喜欢

jlljh 发表于 2020-9-15 15:27:55

非常不错,

z31com 发表于 2022-3-11 11:39:36

mmbzt = 8    时,是在哪里处理的。如果状态为8时,UART接收中断是否需要中止接收数据
页: [1]
查看完整版本: 超简单的用状态机实现的modbus从机模式来啦