STM8L DMA 串口空闲中断 微型modbus HOLD寄存器程序

2019-12-22 13:44发布

使用STM8L DMA 串口空闲中断 使用RS485(sp485EE)的微型modbus HOLD寄存器程序
自己测试是好用的

//****************************************************************************************
/*
miniModuBusSavle 是基于ModbusRTU协议的微型slave端程序(目前只支持HOLD寄存器读写模式)
使用一个装满指针的结构体来保存数据,模拟HOLD型寄存器。
对数据的写入和读取全通过指针直接操作
利用指针可以将分散的数据集中起来管理,方便形成连续的地址,在与上位机进行数据通信时更加方便
*/
//****************************************************************************************

//**********************************************************
//构建一个由指针组成的结构体,类型均为16bit或32bit
//例如  uint16_t *pSET_V_CO2;//计算得到SET_V
//**********************************************************
struct PARA_REG{  
//构建一个由指针组成的结构体,类型均为16bit或32bit
//例如  uint16_t *pSET_V_CO2;//计算得到SET_V
};

//**********************************************************
//声明相关变量
//**********************************************************
uint8_t CRC_TYPE;//CRC16校验类型 高位在前或地位在前CRC_TYPE_HL;CRC_TYPE_LH;
uint16_t CRC; //CRC校验计算结果
uint8_t SENT_COUNT; //发送数据长度
uint8_t MODBUS_ADD = 0XAA; //设备地址

struct PARA_REG PARA_REG_STRUCT;//声明一个结构体实例
uint8_t ERRCOD;//错误数据

//**********************************************************
//程序声明
//**********************************************************
uint16_t CRC16(uint8_t *BUF,uint8_t LENGTH); //CRC16计算程序
uint8_t CHACK_CRC(uint8_t *BUF,uint8_t BUFLENGTH); //接收数据crc校验程序
uint8_t CHACK_ADDR(uint8_t *BUF); //检查设备地址是否匹配,返回ERROR_COD,具体见 .H文件定义
uint8_t Function_MODBUS(uint8_t *rec_buff,uint8_t BUFLENGTH); //modbus操作选择程序,接收数据后进入此程序进行地址校验,crc校验,正确后分配工作如,读,写等
uint8_t READ_REG(uint8_t * rec_buff); //读取寄存器
uint8_t WRITE_REG(uint8_t * rec_buf); //写寄存器
void MODBUS_POINTER_INIT(void); //数据结构体指针初始化


void MODBUS_POINTER_INIT(void)
{
//结构体内指针指向内容初始化
//  PARA_REG_STRUCT.pSET_V_CO2 = &SET_V_CO2; //计算得到SET_V_co2
}

//****************************************************
//检查设备地址是否匹配
//*BUF 接收的数组
//****************************************************
uint8_t CHACK_ADDR(uint8_t *BUF)
{
  if(BUF[0] == MODBUS_ADD)return ADDRESS_MATCHED; else return ADDRESS_UNMATCH;
}

//****************************************************
//CRC16计算函数
//*BUF 要计算的数组 LENGTH 数据长度
//****************************************************
uint16_t CRC16(uint8_t *BUF,uint8_t LENGTH)
{
  uint16_t i,j,tmp,CRC;
  CRC=0XFFFF;
  for(i=0;i<LENGTH;i++)
  {
    CRC = BUF[i]^CRC;
    for(j=0;j<8;j++)
    {
      tmp=CRC&0x0001;
      CRC=CRC>>1;
      if(tmp) CRC=CRC^0xA001;
    }
  }
  return CRC;
}
//****************************************************
//接收数据crc校验程序
//*BUF 接收的缓冲数组   BUFLENGTH接收的数据长度
//****************************************************
uint8_t CHACK_CRC(uint8_t *BUF,uint8_t BUFLENGTH)
{
  //由于市面上有两种CRC校验方法,是高低位互换的,所以进行两种校准,并判断上位机使用的是哪种顺序
  uint16_t RX_CRC_A,RX_CRC_B;
  CRC = CRC16(BUF,BUFLENGTH-2);
  RX_CRC_A = (BUF[BUFLENGTH-2])<<8 | BUF[BUFLENGTH-1];
  RX_CRC_B = BUF[BUFLENGTH-1]*256 + BUF[BUFLENGTH-2];
  if(CRC == RX_CRC_A)
  {
    CRC_TYPE = CRC_TYPE_HL;
    return 1;
  }
  else if(CRC == RX_CRC_B)
  {
    CRC_TYPE = CRC_TYPE_LH;
    return 1;
  }
  else
  {
    return CRC_UNMATCH;
  }
}
//*****************************************************************************************
//modbus操作选择程序,接收数据后进入此程序进行地址校验,crc校验,正确后分配工作如,读,写等
//*rec_buff 缓冲数组  BUFLENGTH接收到的数据长度
//*****************************************************************************************
uint8_t Function_MODBUS(uint8_t *rec_buff,uint8_t BUFLENGTH)
{

  ERRCOD = CHACK_ADDR(rec_buff);
  if(ERRCOD == ADDRESS_UNMATCH) return ERRCOD;
  ERRCOD = CHACK_CRC(rec_buff,BUFLENGTH);
  if(ERRCOD == CRC_UNMATCH) return ERRCOD;
  switch(rec_buff[1])        // 功能码索引
  {
  case 1:        // 01功能码:读取线圈(输出)状态  读取一组逻辑线圈的当前状态(ON/OFF)
    //read_coil();
    ERRCOD = ERRCOD_UNSUPPORT_COMD;
    break;
   
  case 2:         //02功能码:读取输入状态  读取一组开关输入的当前状态(ON/OFF)
    //read_input_bit();
    ERRCOD = ERRCOD_UNSUPPORT_COMD;
    return ERRCOD;
    break;
   
  case 3:        //03功能码:读取保持型寄存器 在一个或多个保持寄存器中读取当前二进制值
    ERRCOD =READ_REG(rec_buff);
    break;
   
  case 4:        //04功能码:读取输入寄存器 在一个或多个输入寄存器中读取当前二进制值
    ERRCOD =READ_REG(rec_buff);
    break;
   
  case 5:        //05功能码 :强制(写)单线圈(输出)状态  强制(写)一个逻辑线圈通断状态(ON/OFF)
    //force_coil_bit();
    ERRCOD = ERRCOD_UNSUPPORT_COMD;
    break;
   
  case 6:        //06功能码:强制(写)单寄存器 把二进制写入一个保持寄存器
    ERRCOD = WRITE_REG(rec_buff);
    break;
   
  case 15:
    //force_coil_mul();
    ERRCOD = ERRCOD_UNSUPPORT_COMD;
    break;
   
  case 16: //16功能码:强制(写)多寄存器 把二进制值写入一串连续的保持寄存器
    ERRCOD = WRITE_REG(rec_buff);
    break;
   
  default:
    ERRCOD = ERRCOD_UNSUPPORT_COMD;
    break;
  }
  if(ERRCOD != NOERROR)
  {
    rec_buff[1] = rec_buff[1] | 0X80;
    rec_buff[2] = ERRCOD;                //有错误

    DMA_USART_SEND(rec_buff,5);
  }
  return ERRCOD;
}

//******************************************************
//对应modbus功能号03,04 批量读寄存器
//rec_buf接收到的指令 send_data需要发送的指令
//******************************************************
uint8_t READ_REG(uint8_t * rec_buff)
{
  uint16_t begin_add = 0;
  uint16_t data_num = 0;
  uint16_t **piont;
  uint16_t send_CRC;
  uint8_t send_num;
  int16_t i;
  uint8_t ERRCOD = NOERROR;
  //本程序中寄存器都是 int或uint型,占用2个字节,但是在modbus中地址一位是一个word也就是2个字节。
  
  begin_add = rec_buff[3];//地址1字节
  data_num = rec_buff[5];//需要读取的字节数
  
  if((begin_add+data_num) >REG_SIZE)return ERRCOD_REG_ADDRESS_ERROR; //REG_ADDRESS ERROR
  
  send_num = 5 + data_num*2; // 5个固定字节+数据个数 addr1 + fun1 + num1 +【data】+ crc2?
  rec_buff[2] = data_num*2;//字节数
  piont = (uint16_t **)&PARA_REG_STRUCT; //将结构体转换为字符数组,便于后面的循环读取或写入
  for(i=0;i<data_num;i++)
  {
    rec_buff[3+i*2] = (uint8_t)(**(piont + begin_add +i)>>8);
    rec_buff[3+i*2+1] = (uint8_t)(**(piont + begin_add +i));
  }
  
  send_CRC = CRC16(rec_buff, send_num-2);
  
  if(CRC_TYPE == CRC_TYPE_HL)
  {
    rec_buff[send_num-2] = (uint8_t)(send_CRC >> 8);
    rec_buff[send_num -1] = (uint8_t)send_CRC;
  }
  else
  {
    rec_buff[send_num-1] = send_CRC >> 8;
    rec_buff[send_num -2] = send_CRC;
  }
  SENT_COUNT =  send_num;
  DMA_USART_SEND(rec_buff,SENT_COUNT);
  return ERRCOD;
}


//*************************************************************
//对应modbus功能号06和16,单个和批量写寄存器
//rec_buf接收到的指令 send_data需要发送的指令
//*************************************************************
uint8_t WRITE_REG(uint8_t * rec_buf)
{
  uint8_t fun_code,begin_add,data_num;//功能码,开始地址,数据长度
  uint16_t send_num;//发送数据长度
  uint16_t **piont;
  uint16_t send_CRC;
  int16_t i;
  uint8_t ERRCOD = NOERROR;
  
  fun_code = rec_buf[1]; //获取功能码
  begin_add = rec_buf[3];
  piont = (uint16_t **)&PARA_REG_STRUCT; //将结构体转换为字符数组,便于后面的循环读取或写入
  
  if(fun_code == 6)//写单个寄存器,返回指令与接收的指令完全一样
  {
    if((begin_add)>=REG_SIZE) return ERRCOD_REG_ADDRESS_ERROR;
    **(piont+begin_add) = rec_buf[4]<<8 + rec_buf[5];//寄存器高位+寄存器低位写入;
  }
  else if(fun_code == 16)//写多个寄存器
  {
    data_num = rec_buf[5];
    if((begin_add+data_num)>REG_SIZE) return ERRCOD_REG_ADDRESS_ERROR;
    for(i=0;i<data_num;i++)
    {
      **(piont+begin_add+i) = rec_buf[7+i*2]*256 + rec_buf[7+i*2+1];
    }
  }
  send_num = 8;//写单个寄存器的响应数据个数是固定的为 8;
  
  send_CRC = CRC16(rec_buf, send_num-2);//计算CRC 并自动匹配两种CRC16 高地位配置
  if(CRC_TYPE_HL)
  {
    rec_buf[send_num-2] = (uint8_t)(send_CRC >> 8);
    rec_buf[send_num -1] = (uint8_t)send_CRC;
  }
  else
  {
    rec_buf[send_num-1] = send_CRC >> 8;
    rec_buf[send_num -2] = send_CRC;
  }
  SENT_COUNT = send_num;
  DMA_USART_SEND(rec_buf,SENT_COUNT);
  return   ERRCOD;
}

//**************************************************
//软启动modbus 过程
//**************************************************
void SOFT_START_MODBUS_PROCESS(uint8_t *rec_buff,uint8_t BUFLENGTH)
{
if(TX_DMA_SOFTSTART_FLG && TX_DMA_READY_FLG)//如果在串口IDLE中断中DMA处于忙状态则进入主循环中配制DMA
    {
        TX_DMA_READY_FLG = FALSE;
        TX_DMA_SOFTSTART_FLG = FALSE;
        Function_MODBUS(uint8_t *rec_buff,uint8_t BUFLENGTH);
        DMA_START_RX();//重新开启串口DMA接收模式
    }
}
友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
4条回答
lovepig200
2019-12-23 01:06
Ray______ 发表于 2017-10-11 22:35
话说485通信用空闲中断断帧,怕数据容易断开吗

stm8L的定义是1个字符空闲约10个数据位,modbus定义是1.5个字符约15个数据位左右会认为数据不可用。
如果线路正常的话不会出现超过10个数据位的断开。
modbus协议规定 两组数据至少间隔 3.5个字符的时间。

所以如果间隔大于1.5小于3.5会抛弃,大于3.5是下一组数据。这样有影响的时间只是 1个字符与1.5个字符的差异,但是对于正常线路这种情况不会发生。
再就是一般主机都有重发机制。

一周热门 更多>