您的位置 首页 数字

根据51单片机modbusRTU从机规划

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

规划思维如下:

modbus协议是以主从的办法通讯的,也便是上位机发送指令,下位机应对机制,建议通讯的一直是上位机,下位机只需应对就好了。

modbus协议被规划出来是针对PLC使用的,这儿咱们能够简略的模仿PLC环境,能够在单片机晒干规划一块同享区,该区域是上位机和下位机同享的,均能够读取或写入该区域的值,一切的modbus协议都是针对该快区域的操作,下位机也是依据这块区域的值做相应的操作。

这块同享区咱们用结构体来一共,这儿咱们只用了两个变量:

/*modbus 16位值的界说,开端地址0000H,每一个值为16位 int型,占两个字节 */struct MODBUS_ADD{int LED_value;//地址:0000H  LED灯的值,该值得低8位代表分表代表LED1--LED8int 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 byteTH0 = T1MS >> 8;                //reload timer0 high byteif(PACK_REC_OK == time_out_check_MODBUS()) {//成功接纳一帧数据后,处理modbus信息,同步公共区数据function_MODBUS(modbus_recv_buf);}}
/*超时帧检测,在1ms定时器晒干运转,回来当时状况*/int time_out_check_MODBUS(void){	rec_time_out++;	if(rec_time_out == 9)				// 数据接纳超时5ms,给程式满足长的处理时刻	{		rec_stat = PACK_START;		rec_num = 0;	}	else if((rec_time_out == 4) && (rec_num > 4)) // 超时数据帧完毕4ms	{		rec_stat = PACK_REC_OK;//		modbus_rtu->rec_num = 0;	}		return rec_stat;		}

一帧数据接纳成功后,碑文办法就在函数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){	unsigned char begin_add = 0;	unsigned char data_num = 0;	unsigned char *piont;	unsigned int send_CRC;	unsigned int send_num;	int i;	begin_add = rec_buff[3]*2;//地址1字节	data_num = rec_buff[5]*2;//需求读取的字节数	send_num = 5 + data_num;	// 5个固定字节+数据个数 addr1 + fun1 + num1 ++ crc2	rec_buff[2] = data_num;//字节数	piont = (unsigned char *)modbusAdd; //将结构体转换为字符数组,便于后边的循环读取或写入	for(i=0;i	{		rec_buff[3+i] = piont[begin_add +i];	}	send_CRC = comp_crc16(rec_buff, send_num-2);	rec_buff[send_num-2] = send_CRC >> 8;	rec_buff[send_num -1] = send_CRC;	send_count = send_num;	PutNChar(rec_buff , send_count);}/*function:对应modbus功用号06和16,单个和批量写寄存器input:rec_buf接纳到的指令 send_data需求发送的指令*/void force_reg(unsigned char * rec_buf){	unsigned char fun_code,begin_add,data_num;//功用码,开端地址,数据长度	unsigned int send_num;//发送数据长度	unsigned char *piont;	unsigned int send_CRC;	int i;//	send_data[0] = rec_buf[0]; //获取站号	fun_code = rec_buf[1];	//获取功用码//	send_data[1] = fun_code;//	send_data[2] = rec_buf[2];//获取开端地址//	send_data[3] = rec_buf[3];	begin_add = rec_buf[3]*2;	piont = (unsigned char *)modbusAdd;	//将结构体转换为字符数组,便于后边的循环读取或写入		if(fun_code == 6)//写单个寄存器,回来指令与接纳的指令彻底相同	{		piont[begin_add] = rec_buf[4];//寄存器高位写入		piont[begin_add+1] = rec_buf[5];//寄存器低位写入		send_num = 8;//	}	else if(fun_code == 16)//写多个寄存器	{		data_num = rec_buf[5]*2;		send_num = 8;		for(i=0;i		{			piont[begin_add+i] = rec_buf[7+i];		}	}	send_CRC = comp_crc16(rec_buf, send_num-2);//CRC校验	rec_buf[send_num-2] = send_CRC >> 8;	rec_buf[send_num -1] = send_CRC;	send_count = send_num; PutNChar(rec_buf , send_count);}

根据51单片机modbus下位机规划这儿就完毕了,这种办法是比较灵活了,将协议的完结独自放在一层,避免与主函数有太多交互

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/zhishi/shuzi/263770.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部