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

📄 modbus.c

📁 富士通单片机MB90F387上实现MODBUS
💻 C
📖 第 1 页 / 共 5 页
字号:
/********************************************************************
*函数的编写力求独立性!函数用到的参数只通过形参出入口传输!
*对于 Modbus, 每个功能代码都对应着对一个特定的Modbus 参考集的访问。
*因此,在Modbus 消息的地址域中不包括最高位。
*0xxxx 参考集– Modbus 线圈(支持的功能码01,05,15),参考ACS510用户手册P154:
			 前32 个线圈专门用于控制字的逐位映射(00001~00020位,00021~00032保留);
			 继电器输出从线圈00033开始顺序编号到00038;
*1xxxx 映射 – Modbus 离散输入(支持的功能码02),参考ACS510用户手册P156:	
			 前32 个输入专门用于状态字映射(10001~10032)。
			 硬件离散输入从输入33开始按位顺序编号(10033~10038)	 
*3xxxx 映射– Modbus 输入(支持的功能码04),参考ACS510用户手册P157:
			 任何由用户定义的模拟输入:30001(AI1),30002(AI2)
*4xxxx 寄存器映射.传动将它的参数和其它数据映射到4xxxx 保持寄存器(支持的功能码03/06/16/23).
			 40001 ~ 40099 映射到传动控制和实际值
			 40101 ~ 49999 映射到传动参数 0101 ~ 9999。 
			 如果寄存器的地址不对应传动参数,那么该寄存器地址无效。
			 如果试图对参数地址以外的寄存器进行读写,那么Modbus 接口会向控制器返回一个异常码			 
**********************************************************************/
#include "GLOBAL.H"
				//线圈(0xxxx):控制位、继电器输出
#define READ_COIL 01  //读取线圈状态(ABB ACS510)
#define SET_COIL 05   //对单个线圈进行强制(ABB ACS510)
#define SET_COILs 15  //对多个线圈进行强制(ABB ACS510)
				//离散输入(1xxxx):状态位、离散输入
#define READ_DI 02    //读取输入状态(ABB ACS510)
				//输入寄存器(3xxxx):模拟输入
#define READ_AI 04    //读取输入寄存器(ABB ACS510)
				//保持寄存器(4xxxx):参数、控制字/ 状态字、给定
#define READ_HLD_REGs 03      //读单个4X 寄存器(ABB ACS510)
#define SET_HLD_REG 06        //写单个4X 寄存器(ABB ACS510)
#define SET_HLD_REGs 16       //写多个4X 寄存器(ABB ACS510)
#define READ_SET_HLD_REGs 23  //读/写 4X 寄存器(ABB ACS510)
				//异常
#define PROTOCOL_ERR (-1)
#define FRM_ERR (-2)
#define CRC_ERR (-3)
#define CMD_ERR (-4) //

uchar  Rx_Ptr;  // 接收数据指针
uchar  Tx_Ptr;  // 发送数据指针

uchar  Rx_Flag; // 接收标志  1: 准备接收; 0: 接收结束
uchar  Tx_Flag; // 发送标志  1: 准备发送; 0: 发送结束
uchar  Tx_Buffer[256],Rx_Buffer[256];
uint   TxBytes; // 每帧信息中发送字节总数
uint   RxBytes; // 每帧信息中接收字节总数 

uchar  Slave_addr = 1;  //地址范围1~31,主设备默认31,从设备默认设置1
uchar  Modbus_mode = 0; //0:RTU模式;1:ASCII模式
uchar  ModbusBPS = 3;   //默认9600bps,可选波特率:1200,2400,4800,9600,19200,38400,76800
uchar  Commformat = 0; 
        /*Commformat范围0~5
        	          数据位   校验位   停止位
                    0: 8        N        1
                    1: 8        N        2
                    2: 8        E        1
                    3:  8        E        2
                    4:  8        O        1
                    5:  8        O        1   
        */
const uchar auchCRCHi[] = 
{
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
    0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
    0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81,
    0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
    0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
    0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
    0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
    0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
    0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
    0x40
} ;

const uchar auchCRCLo[] = 
{
    0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4,
    0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
    0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD,
    0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
    0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7,
    0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
    0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE,
    0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
    0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2,
    0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
    0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB,
    0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
    0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91,
    0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
    0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88,
    0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
    0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80,
    0x40
};

const uchar char_tab[128]=
{
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   //15                    
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   //31                   
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   //47                   
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0,   //63                   
    0, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0, 0, 0, 0, 0, 0, 0, 0, 0,    
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,                      
    0, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0, 0, 0, 0, 0, 0, 0, 0, 0,    
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0                        
};

const uchar tab_char[16]=
{ 
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};
/***************************************************************************************
* 作者  :冯子龙
* 日期  :200709
* 名称  :getCRC16()
* 功能  :取字的高字节
* 返回  :getCRC16校验和(字)
* 备注  :Tested;
****************************************************************************************/
uint getCRC16(uchar *puchMsg , uint usDataLen)//*puchMsg:message to calculate CRC upon;usDataLen:quantity of bytes in message
{
    uchar CRCH = 0xFF ; /* high byte of CRC initialized */
    uchar CRCL = 0xFF ; /* low byte of CRC initialized */
    uchar uIndex ;  /* will index into CRC lookup table */
    
    while (usDataLen--) /* pass through message buffer*/
    {
        uIndex = CRCL ^ *puchMsg++;/* calculate the CRC*/
        CRCL = CRCH ^ auchCRCHi [uIndex] ;
        CRCH = auchCRCLo [uIndex] ;
    }
    return (CRCH<< 8 | CRCL);//注意:Modbus传输getCRC16时要求低字节在前高字节在后
    
}
/***************************************************************************************
* 作者  :冯子龙
* 日期  :200709
* 名称  :CheckCRC()
* 功能  :对接收的数据进行CRC校验,与发送来的CRC校验值进行比较
* 返回  :如果CRC校验值与接收到的CRC校验值相等,则返回1,否则返回0
* 备注  :
****************************************************************************************/
uchar CheckCRC(uchar *crctmp,uint length)
{
		uint crc_result, crc_tmp;
    crc_tmp = *(crctmp + length-2);                  // crc 低字节
    crc_tmp = crc_tmp  + (*( crctmp+length-1))* 256; // CRC 值
    crc_result = getCRC16(crctmp, length-2);         // 计算CRC 值                    
    if ( crc_tmp != crc_result ) // 比较CRC值
      	return(0);
    return(1);
}    
/***************************************************************************************
* 作者  :冯子龙
* 日期  :200709
* 名称  :send()
* 功能  :发送命令请求
* 返回  :无
* 备注  :该函数要借助串口中断,全局变量有Tx_Flag、Tx_Ptr、Tx_Buffer[]
****************************************************************************************/
void comSend(void)
{   
    //开始发送,后续数据在中断中继续发送  
    Tx_Flag = 1;//准备发送,发送完成后清零        
    Tx_Ptr = 0; //发送指针清零(也相当于发送计数器)                
          
    SODR1 = Tx_Buffer[0]; //发送第一个数据      
    SSR1_TIE = 1;         //发送中断使能,通过串口中断发送剩余数据		       
    while (Tx_Flag);      //等待发送完成 
    SSR1_TIE = 0;         //发送完成后,关闭发送中断   		  		   	 
}
/***************************************************************************************
* 作者  :冯子龙
* 日期  :200709
* 名称  :comReceive()
* 功能  :中断接收从设备发送来的应答数据,每100ms判断是否接收完毕。
* 返回  :如果接收成功,则返回接收到的字节数RxBytes,否则返回0,认为接收到的数据无效
* 备注  :该函数利用了2.5ms定时中断以及串口接收中断;
          该函数修改了接收计数器Rx_Ptr,接收允许标志Rx_Flag,产生了接收数据总字节数;
          接收到的数据,存放在Rx_Buffer[256]缓冲区中;
          Rx_Ptr、Rx_Flag是全局变量,Rx_Buffer[256]是全局数组,都在串口中断中进行改变
****************************************************************************************/
uchar comReceive(void)
{
    uint   j,ptr_old;
    
    //中断接收数据,100ms延时查询接收计数器变化,如果无变化,则认为接收完毕(只有被查询设备才处于应答状态)
    Rx_Flag = 1;//准备接收,接收完成后清零 
		Rx_Ptr = 0; //接收指针清零,保证数据从缓冲区的首地址处开始存放   
		ptr_old = 0;//记录上次指针位置,以判断数据接收是否结束  
		j = soft_timer;       
    SSR1_RIE = 1; //接收中断使能,通过串口中断接收数据    
     
    while (Rx_Flag) //等待接收完成,以及数据溢出判断Rx_Ptr<256
    {        	      	  
        if (soft_timer - j>40) //每50ms延时判断接收是否完毕
        {
        	 j = soft_timer;
        	 if(Rx_Ptr > ptr_old)  
        	 		ptr_old = Rx_Ptr; //正在接收
        	 else 
        	 {
        	 		Rx_Flag = 0;  //接收完成标志置位
        	 		SSR1_RIE = 0; //关接收中断
        	 		Rx_Ptr = 0;   //初始化接收计数器
        	 }
        }         
    }               
    if (!Rx_Flag)//接收完毕,记录总字节数
    {        	   
        RxBytes =  ptr_old;//记录接收到的字节总数,以便命令处理用。
        return (RxBytes);  
    }
    RxBytes = 0;//超过256个字节,认为错误,清零;
    return (0); //return(0)置于该处而不是置于while循环中的if语句后,是鉴于该函数具有超时重试的功能         	
}
/*******************************************************
* Name  :init_uart1
* Func  :Initiallize uart1
* Input :uint bps,uchar DataBits,uchar ParityBits, uchar StopBits
* Output:None
* Mark  :φ=16M,div decided by CDCR1
	CS2 CS1 CS0   BAUD 				Calculation
	0	  0 	0	 		76,923 bps 	(φ/div) / (8 × 13 × 2)
	0 	0   1		 	38,461 bps 	(φ/div) / (8 × 13 × 4)
	0 	1 	0  		19,230 bps 	(φ/div) / (8 × 13 × 8)
	0	  1 	1 		9,615 bps 	(φ/div) / (8 × 13 × 16)
	1		0 	0 		500 kbps 		(φ/div) / (8 × 2 × 2)
	1 	0 	1 		250 kbps 		(φ/div) / (8 × 2 × 4)
********************************************************/
void init_uart1(uchar bps,uchar Dataform)
{
	  switch(bps)
	  {
	  		case 0://1200
	  		{
       		  //SMR0/1:MD1 MD0 CS2 CS1     CS0 BCH SCKE SOE
       	    SMR1 = 0x19;		
         	  //CDCR0/1:MD - - - 0 DIV2 DIV1 DIV0  
         	  // 使能预分频器,设置预分频器系数为4分频
         	  CDCR1 = 0x87;         	  	  				
	  				break; //实际产生1201bps的波特率
	  		}	
	  		case 1://2400
	  		{
        		 //SMR0/1:MD1 MD0 CS2 CS1     CS0 BCH SCKE SOE		
        	   SMR1 = 0x19;		
          	 //CDCR0/1:MD - - - 0 DIV2 DIV1 DIV0  
          	 // 使能预分频器,设置预分频器系数为2分频
          	 CDCR1 = 0x83;          	  	  				
	  				 break;//实际产生2403bps的波特率
	  		}	
	  		case 2://4800
	  		{
        		 //SMR0/1:MD1 MD0 CS2 CS1     CS0 BCH SCKE SOE		
        	   SMR1 = 0x19;		
          	 //CDCR0/1:MD - - - 0 DIV2 DIV1 DIV0  
          	 // 使能预分频器,设置预分频器系数为2分频
          	 CDCR1 = 0x81; //实际产生4807bps的波特率          	 		  				
	  				 break;
	  		}	
	  		case 3://9600
	  		{
        		 //SMR0/1:MD1 MD0 CS2 CS1     CS0 BCH SCKE SOE		
        	   SMR1 = 0x19;		
          	 //CDCR0/1:MD - - - 0 DIV2 DIV1 DIV0  
          	 // 使能预分频器,设置预分频器系数为1分频
          	 CDCR1 = 0x80;          		  				
	  				 break; //实际产生9615bps的波特率 
	  		}	
	  		case 4://19200
	  		{
        		 //SMR0/1:MD1 MD0 CS2 CS1     CS0 BCH SCKE SOE		
        	   SMR1 = 0x11;
          	 //CDCR0/1:MD - - - 0 DIV2 DIV1 DIV0  
          	 // 使能预分频器,设置预分频器系数为1分频
          	 CDCR1 = 0x80;          	  		  				
	  				 break;//实际产生19230bps的波特率
	  		}	
	  		case 5://38400
	  		{
        		 //SMR0/1:MD1 MD0 CS2 CS1     CS0 BCH SCKE SOE		
        	   SMR1 = 0x09;
          	 //CDCR0/1:MD - - - 0 DIV2 DIV1 DIV0  
          	 // 使能预分频器,设置预分频器系数为1分频
          	 CDCR1 = 0x80;          	 	  				
	  				 break;//实际产生38461bps的波特率 
	  		}	
	  		case 6://76800
	  		{
        		 //SMR0/1:MD1 MD0 CS2 CS1     CS0 BCH SCKE SOE		
        	   SMR1 = 0x01;
          	 //CDCR0/1:MD - - - 0 DIV2 DIV1 DIV0  
          	 // 使能预分频器,设置预分频器系数为1分频
          	 CDCR1 = 0x80;          	 	  				
	  				 break;//实际产生76,923bps的波特率 
	  		}
	  		default:
	  			   break;		  			  			  			  			  			  		
	  }  
      
    switch(Dataform)
    {
    	  //SCR0/1:   PEN P SBL CL   A/D REC RXE TXE 数据格式设置
    		case 0: //8 N 1
    		{
    				SCR1 = 0x13;//1位停止位   0001 0011
    				break;	
    		}	
    		case 1: //8 N 2
    		{
    			  SCR1 = 0x33;//2位停止位	  0011 0011 
    				break;	
    		}	    		
    		case 2: //8 E 1
    		{
    				SCR1 = 0x93;//1位停止位   1001 0011
    				break;	
    		}	
    		case 3: //8 E 2
    		{
    				SCR1 = 0xB3;//2位停止位	  1011 0011 
    				break;	
    		}	    		
    		case 4://8 O 1
    		{
    			  SCR1 = 0x93;//1位停止位   1101 0011
    				break;	
    		}	
    		case 5://8 O 2
    		{
    			  SCR1 = 0xB3;//2位停止位	  1111 0011  
    				break;	
    		}	
    		default:
    		{
    				break;	
    		}	    		    		    		    		    		   	
    }					   									
		  	     	           
  //SSR0/1:PE ORE PRE PDRF TDRE BDS RIE TIE (SIDR/SODR)      
  SSR1_TIE = 0;        	// 关闭发送中断,发送时要开中断

⌨️ 快捷键说明

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