基于51单片机modbusRTU从机设计

12 篇文章 0 订阅
9 篇文章 0 订阅

在了解modbus协议后就是基于该协议的设计了,下面先说一下基于航太电子HTM52单片机的从机设计。

设计思想如下:

modbus协议是以主从的方式通信的,也就是上位机发送指令,下位机应答机制,发起通信的一直是上位机,下位机只要应答就好了。

modbus协议被设计出来是针对PLC应用的,这里我们可以简单的模拟PLC环境,可以在单片机里面设计一块共享区,该区域是上位机和下位机共享的,均可以读取或写入该区域的值,所有的modbus协议都是针对该快区域的操作,下位机也是根据这块区域的值做相应的操作。

这块共享区我们用结构体来表示,这里我们只用了两个变量:

/*modbus 16位值的定义,起始地址0000H,每一个值为16位 int型,占两个字节 */
struct MODBUS_ADD{
int LED_value;//地址:0000H  LED灯的值,该值得低8位代表分表代表LED1--LED8
int LED_ctrl;//地址:0001H  控制指令
};
struct MODBUS_ADD modbus_Addt;//声明一个modbus结构体变量
struct MODBUS_ADD *modbusAdd;//结构体指针,指向这个变量

在主函数中,只需要查询这块区域的值,作出相应的动作就好了:

void main()
{
	 SystemInit();
	 init_MODBUS();
	 modbus_Addt.LED_ctrl = COMM_PC;
	while(1)
	{				
				//将需要交互的数据读取到公共区
			  /*start*/
		if(modbus_Addt.LED_ctrl != COMM_PC)
		{
			 modbus_Addt.LED_value = LED_PORT;
		}
			  /*end*/
			 
		 	//同步公共区数据到实际运行效果
			  /*start*/
			 switch(modbus_Addt.LED_ctrl)
			 {
				 case COMM_PC: 
					 LED_PORT = ~(uchar)(modbus_Addt.LED_value & 0x00ff);
				 break;
				 case COMM_FLOW:
					 LedFlow();
				 break;
				 default:
					 LED_PORT = ~(uchar)(modbus_Addt.LED_value & 0x00ff);
				 break;
			 }
			  /*end*/
	}
}

接下来看modbus协议具体怎么实现的,可以看到在主函数中是没有参与这个协议的,也就是相当于modbus协议的实现是在另外一个线程中,主函数不需要关心实现的细节,这样做的好处的是主函数可以近针对于自己的实现任务,二不用考虑任务的参数从哪来的

51单片机与上位机通信采用串口的方式,串口中断负责接收和发送数据,这里我们还用到了一个定时器,负责监控当前modbus的状态,判断这一帧数据是否完成,如果判断为一帧数据接收完成,就解析该帧数据,并执行相应的指令。

注意一下rec_time_out这个变量,这个变量在定时器中断里面是不断自加的,但在串口中断里面就清零了,这样做的意义是判断一帧数据是否接收完成,如果rec_time_out这个变量值大于某个值,说明在一段时间是没有数据接收的,可以认为数据接收接收,当然上位机那边必须满足一帧数据是连续发送的

串口中断程序如下,这里用到了串口中断发送数据帧,具体解析可以参考我的另一篇博客 http://blog.csdn.net/liucheng5037/article/details/48831993

//串口中断
void SerISR() interrupt 4 using 2
{
	if(RI == 1)
	{
		unsigned char data_value;
		RI=0;
		if(send_buf.busy_falg == 1) return;//发送未完成时禁止接收
		data_value = SBUF;
		rec_time_out = 0;//一旦接收到数据,清空超时计数
		switch(rec_stat)
		{
			case PACK_START:
				rec_num = 0;
				if(data_value == PACK_START)//默认刚开始检测第一个字节,检测是否为本站号
				{
					modbus_recv_buf[rec_num++] = data_value;
					rec_stat = PACK_REC_ING;
				}
				else
				{
					rec_stat = PACK_ADDR_ERR;
				}
				break;
	
			case PACK_REC_ING:	// 正常接收
	
				modbus_recv_buf[rec_num++] = data_value;
				break;
	
			case PACK_ADDR_ERR:	// 地址不符合 等待超时 帧结束
				break;
			default : break;
		}
		
	}
	if(TI == 1)	 //进入发送完成中断,检测是否有需要发送的数据并进行发送
	{
		TI = 0;
		send_buf.index++;
		if(send_buf.index >= send_buf.length)
		{
			send_buf.busy_falg = 0;//发送结束
			return;
		}
		SBUF = send_buf.buf[send_buf.index];//继续发送下一个	
	}
}

定时器实现函数,注意超时检测方法:

/* 定时器中断 1ms*/
void Time0ISR() interrupt 1 using 1
{
    TL0 = T1MS;                     //reload timer0 low byte
    TH0 = T1MS >> 8;                //reload timer0 high byte
	if(PACK_REC_OK == time_out_check_MODBUS()) 
	{
		//成功接收一帧数据后,处理modbus信息,同步公共区数据
		function_MODBUS(modbus_recv_buf);
	}

}
/*超时帧检测,在1ms定时器里面运行,返回当前状态*/
int time_out_check_MODBUS(void)
{
<span style="white-space:pre">	</span>rec_time_out++;
<span style="white-space:pre">	</span>if(rec_time_out == 9)<span style="white-space:pre">				</span>// 数据接收超时5ms,给程式足够长的处理时间
<span style="white-space:pre">	</span>{
<span style="white-space:pre">		</span>rec_stat = PACK_START;
<span style="white-space:pre">		</span>rec_num = 0;
<span style="white-space:pre">	</span>}
<span style="white-space:pre">	</span>else if((rec_time_out == 4) && (rec_num > 4)) // 超时数据帧结束4ms
<span style="white-space:pre">	</span>{
<span style="white-space:pre">		</span>rec_stat = PACK_REC_OK;
//<span style="white-space:pre">		</span>modbus_rtu->rec_num = 0;
<span style="white-space:pre">	</span>}
<span style="white-space:pre">		</span>return rec_stat;<span style="white-space:pre">		</span>
}

一帧数据接收成功后,执行方法就在函数 function_MODBUS中,如下,指令解析和发动都是严格按照modbus协议来的,这里只是用到了协议的常用的几个指令,大家可以自由扩展,

void function_MODBUS(unsigned char *rec_buff)
{	
	switch(rec_buff[1])	// 功能码索引
	{
		case 1:	// 01功能码:读取线圈(输出)状态  读取一组逻辑线圈的当前状态(ON/OFF)
			//read_coil();
			break;

		case 2:	 //02功能码:读取输入状态  读取一组开关输入的当前状态(ON/OFF)
			//read_input_bit();
			break;

		case 3:	//03功能码:读取保持型寄存器 在一个或多个保持寄存器中读取当前二进制值
			read_reg(rec_buff);
			break;

		case 4:	//04功能码:读取输入寄存器 在一个或多个输入寄存器中读取当前二进制值
			read_reg(rec_buff);
			break;

		case 5:	//05功能码 :强制(写)单线圈(输出)状态  强制(写)一个逻辑线圈通断状态(ON/OFF)
			//force_coil_bit();
			break;

		case 6:	//06功能码:强制(写)单寄存器 把二进制写入一个保持寄存器
			force_reg(rec_buff);
			break;

		case 15:
			//force_coil_mul();
			break;

		case 16: //16功能码:强制(写)多寄存器 把二进制值写入一串连续的保持寄存器
			force_reg(rec_buff);
			break;

		default:

			//modbus_send_buff[1] = rec_buff[1] | 0X80;
			//modbus_send_buff[2] = ERR_FUN_CODE;		// 不合法功能号
			//send_num = 5;
			break;
	}
	rec_stat = PACK_START;//发送之后使缓存回到初始状态
	rec_num = 0;
}
/*
function:对应modbus功能号03,04 批量读寄存器
input:rec_buf接收到的指令 send_data需要发送的指令
*/
void read_reg(unsigned char * rec_buff)
{
<span style="white-space:pre">	</span>unsigned char begin_add = 0;
<span style="white-space:pre">	</span>unsigned char data_num = 0;
<span style="white-space:pre">	</span>unsigned char *piont;
<span style="white-space:pre">	</span>unsigned int send_CRC;
<span style="white-space:pre">	</span>unsigned int send_num;
<span style="white-space:pre">	</span>int i;


<span style="white-space:pre">	</span>begin_add = rec_buff[3]*2;//地址1字节
<span style="white-space:pre">	</span>data_num = rec_buff[5]*2;//需要读取的字节数
<span style="white-space:pre">	</span>send_num = 5 + data_num;<span style="white-space:pre">	</span>// 5个固定字节+数据个数 addr1 + fun1 + num1 +【data】+ crc2 
<span style="white-space:pre">	</span>rec_buff[2] = data_num;//字节数
<span style="white-space:pre">	</span>piont = (unsigned char *)modbusAdd;  //将结构体转换为字符数组,便于后面的循环读取或写入
<span style="white-space:pre">	</span>for(i=0;i<data_num;i++)
<span style="white-space:pre">	</span>{
<span style="white-space:pre">		</span>rec_buff[3+i] = piont[begin_add +i];
<span style="white-space:pre">	</span>}
<span style="white-space:pre">	</span>send_CRC = comp_crc16(rec_buff, send_num-2);
<span style="white-space:pre">	</span>rec_buff[send_num-2] = send_CRC >> 8;
<span style="white-space:pre">	</span>rec_buff[send_num -1] = send_CRC;
<span style="white-space:pre">	</span>send_count = send_num;
<span style="white-space:pre">	</span>PutNChar(rec_buff , send_count);
}


/*
function:对应modbus功能号06和16,单个和批量写寄存器
input:rec_buf接收到的指令 send_data需要发送的指令
*/
void force_reg(unsigned char * rec_buf)
{
<span style="white-space:pre">	</span>unsigned char fun_code,begin_add,data_num;//功能码,开始地址,数据长度
<span style="white-space:pre">	</span>unsigned int send_num;//发送数据长度
<span style="white-space:pre">	</span>unsigned char *piont;
<span style="white-space:pre">	</span>unsigned int send_CRC;
<span style="white-space:pre">	</span>int i;


//<span style="white-space:pre">	</span>send_data[0] = rec_buf[0]; //获取站号


<span style="white-space:pre">	</span>fun_code = rec_buf[1];<span style="white-space:pre">	</span>//获取功能码
//<span style="white-space:pre">	</span>send_data[1] = fun_code;


//<span style="white-space:pre">	</span>send_data[2] = rec_buf[2];//获取起始地址
//<span style="white-space:pre">	</span>send_data[3] = rec_buf[3];


<span style="white-space:pre">	</span>begin_add = rec_buf[3]*2;
<span style="white-space:pre">	</span>piont = (unsigned char *)modbusAdd;<span style="white-space:pre">	</span>//将结构体转换为字符数组,便于后面的循环读取或写入
<span style="white-space:pre">	</span>
<span style="white-space:pre">	</span>if(fun_code == 6)//写单个寄存器,返回指令与接收的指令完全一样
<span style="white-space:pre">	</span>{
<span style="white-space:pre">		</span>piont[begin_add] = rec_buf[4];//寄存器高位写入
<span style="white-space:pre">		</span>piont[begin_add+1] = rec_buf[5];//寄存器低位写入
<span style="white-space:pre">		</span>send_num = 8;//
<span style="white-space:pre">	</span>}
<span style="white-space:pre">	</span>else if(fun_code == 16)//写多个寄存器
<span style="white-space:pre">	</span>{
<span style="white-space:pre">		</span>data_num = rec_buf[5]*2;
<span style="white-space:pre">		</span>send_num = 8;
<span style="white-space:pre">		</span>for(i=0;i<data_num;i++)
<span style="white-space:pre">		</span>{
<span style="white-space:pre">			</span>piont[begin_add+i] = rec_buf[7+i];
<span style="white-space:pre">		</span>}
<span style="white-space:pre">	</span>}
<span style="white-space:pre">	</span>send_CRC = comp_crc16(rec_buf, send_num-2);//CRC校验
<span style="white-space:pre">	</span>rec_buf[send_num-2] = send_CRC >> 8;
<span style="white-space:pre">	</span>rec_buf[send_num -1] = send_CRC;
<span style="white-space:pre">	</span>send_count = send_num;
  PutNChar(rec_buf , send_count);
}

基于51单片机modbus下位机设计这里就结束了,这种方法是比较灵活了,将协议的实现单独放在一层,避免与主函数有太多交互,该工程的位置请关注我的git地址:

https://github.com/zhui-ying/PC_MCU51Project/

里面有多个工程,但设计方法是一致的。

  • 24
    点赞
  • 126
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值