<meter id="pryje"><nav id="pryje"><delect id="pryje"></delect></nav></meter>
          <label id="pryje"></label>

          新聞中心

          EEPW首頁(yè) > 嵌入式系統(tǒng) > 設(shè)計(jì)應(yīng)用 > 基于51單片機(jī)modbusRTU從機(jī)設(shè)計(jì)

          基于51單片機(jī)modbusRTU從機(jī)設(shè)計(jì)

          作者: 時(shí)間:2016-11-20 來源:網(wǎng)絡(luò) 收藏
          設(shè)計(jì)思想如下:

          modbus協(xié)議是以主從的方式通信的,也就是上位機(jī)發(fā)送指令,下位機(jī)應(yīng)答機(jī)制,發(fā)起通信的一直是上位機(jī),下位機(jī)只要應(yīng)答就好了。

          本文引用地址:http://www.ex-cimer.com/article/201611/318646.htm

          modbus協(xié)議被設(shè)計(jì)出來是針對(duì)PLC應(yīng)用的,這里我們可以簡(jiǎn)單的模擬PLC環(huán)境,可以在單片機(jī)里面設(shè)計(jì)一塊共享區(qū),該區(qū)域是上位機(jī)和下位機(jī)共享的,均可以讀取或?qū)懭朐搮^(qū)域的值,所有的modbus協(xié)議都是針對(duì)該快區(qū)域的操作,下位機(jī)也是根據(jù)這塊區(qū)域的值做相應(yīng)的操作。

          這塊共享區(qū)我們用結(jié)構(gòu)體來表示,這里我們只用了兩個(gè)變量:

          /*modbus 16位值的定義,起始地址0000H,每一個(gè)值為16位 int型,占兩個(gè)字節(jié) */struct MODBUS_ADD{int LED_value;//地址:0000H  LED燈的值,該值得低8位代表分表代表LED1--LED8int LED_ctrl;//地址:0001H  控制指令};
          struct MODBUS_ADD modbus_Addt;//聲明一個(gè)modbus結(jié)構(gòu)體變量struct MODBUS_ADD *modbusAdd;//結(jié)構(gòu)體指針,指向這個(gè)變量

          在主函數(shù)中,只需要查詢這塊區(qū)域的值,作出相應(yīng)的動(dòng)作就好了:

          void main(){SystemInit();init_MODBUS();modbus_Addt.LED_ctrl = COMM_PC;while(1){				//將需要交互的數(shù)據(jù)讀取到公共區(qū)/*start*/if(modbus_Addt.LED_ctrl != COMM_PC){modbus_Addt.LED_value = LED_PORT;}/*end*///同步公共區(qū)數(shù)據(jù)到實(shí)際運(yùn)行效果/*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協(xié)議具體怎么實(shí)現(xiàn)的,可以看到在主函數(shù)中是沒有參與這個(gè)協(xié)議的,也就是相當(dāng)于modbus協(xié)議的實(shí)現(xiàn)是在另外一個(gè)線程中,主函數(shù)不需要關(guān)心實(shí)現(xiàn)的細(xì)節(jié),這樣做的好處的是主函數(shù)可以近針對(duì)于自己的實(shí)現(xiàn)任務(wù),二不用考慮任務(wù)的參數(shù)從哪來的

          51單片機(jī)與上位機(jī)通信采用串口的方式,串口中斷負(fù)責(zé)接收和發(fā)送數(shù)據(jù),這里我們還用到了一個(gè)定時(shí)器,負(fù)責(zé)監(jiān)控當(dāng)前modbus的狀態(tài),判斷這一幀數(shù)據(jù)是否完成,如果判斷為一幀數(shù)據(jù)接收完成,就解析該幀數(shù)據(jù),并執(zhí)行相應(yīng)的指令。

          注意一下rec_time_out這個(gè)變量,這個(gè)變量在定時(shí)器中斷里面是不斷自加的,但在串口中斷里面就清零了,這樣做的意義是判斷一幀數(shù)據(jù)是否接收完成,如果rec_time_out這個(gè)變量值大于某個(gè)值,說明在一段時(shí)間是沒有數(shù)據(jù)接收的,可以認(rèn)為數(shù)據(jù)接收接收,當(dāng)然上位機(jī)那邊必須滿足一幀數(shù)據(jù)是連續(xù)發(fā)送的

          串口中斷程序如下,這里用到了串口中斷發(fā)送數(shù)據(jù)幀,具體解析可以參考我的另一篇博客 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;//發(fā)送未完成時(shí)禁止接收data_value = SBUF;rec_time_out = 0;//一旦接收到數(shù)據(jù),清空超時(shí)計(jì)數(shù)switch(rec_stat){case PACK_START:rec_num = 0;if(data_value == PACK_START)//默認(rèn)剛開始檢測(cè)第一個(gè)字節(jié),檢測(cè)是否為本站號(hào){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:	// 地址不符合 等待超時(shí) 幀結(jié)束break;default : break;}}if(TI == 1)	 //進(jìn)入發(fā)送完成中斷,檢測(cè)是否有需要發(fā)送的數(shù)據(jù)并進(jìn)行發(fā)送{TI = 0;send_buf.index++;if(send_buf.index >= send_buf.length){send_buf.busy_falg = 0;//發(fā)送結(jié)束return;}SBUF = send_buf.buf[send_buf.index];//繼續(xù)發(fā)送下一個(gè)	}}

          定時(shí)器實(shí)現(xiàn)函數(shù),注意超時(shí)檢測(cè)方法:

          /* 定時(shí)器中斷 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()) {//成功接收一幀數(shù)據(jù)后,處理modbus信息,同步公共區(qū)數(shù)據(jù)function_MODBUS(modbus_recv_buf);}}
          /*超時(shí)幀檢測(cè),在1ms定時(shí)器里面運(yùn)行,返回當(dāng)前狀態(tài)*/int time_out_check_MODBUS(void){	rec_time_out++;	if(rec_time_out == 9)				// 數(shù)據(jù)接收超時(shí)5ms,給程式足夠長(zhǎng)的處理時(shí)間	{		rec_stat = PACK_START;		rec_num = 0;	}	else if((rec_time_out == 4) && (rec_num > 4)) // 超時(shí)數(shù)據(jù)幀結(jié)束4ms	{		rec_stat = PACK_REC_OK;//		modbus_rtu->rec_num = 0;	}		return rec_stat;		}

          一幀數(shù)據(jù)接收成功后,執(zhí)行方法就在函數(shù)function_MODBUS中,如下,指令解析和發(fā)動(dòng)都是嚴(yán)格按照modbus協(xié)議來的,這里只是用到了協(xié)議的常用的幾個(gè)指令,大家可以自由擴(kuò)展,

          void function_MODBUS(unsigned char *rec_buff){	switch(rec_buff[1])	// 功能碼索引{case 1:	// 01功能碼:讀取線圈(輸出)狀態(tài)  讀取一組邏輯線圈的當(dāng)前狀態(tài)(ON/OFF)//read_coil();break;case 2:	 //02功能碼:讀取輸入狀態(tài)  讀取一組開關(guān)輸入的當(dāng)前狀態(tài)(ON/OFF)//read_input_bit();break;case 3:	//03功能碼:讀取保持型寄存器 在一個(gè)或多個(gè)保持寄存器中讀取當(dāng)前二進(jìn)制值read_reg(rec_buff);break;case 4:	//04功能碼:讀取輸入寄存器 在一個(gè)或多個(gè)輸入寄存器中讀取當(dāng)前二進(jìn)制值read_reg(rec_buff);break;case 5:	//05功能碼 :強(qiáng)制(寫)單線圈(輸出)狀態(tài)  強(qiáng)制(寫)一個(gè)邏輯線圈通斷狀態(tài)(ON/OFF)//force_coil_bit();break;case 6:	//06功能碼:強(qiáng)制(寫)單寄存器 把二進(jìn)制寫入一個(gè)保持寄存器force_reg(rec_buff);break;case 15://force_coil_mul();break;case 16: //16功能碼:強(qiáng)制(寫)多寄存器 把二進(jìn)制值寫入一串連續(xù)的保持寄存器force_reg(rec_buff);break;default://modbus_send_buff[1] = rec_buff[1] | 0X80;//modbus_send_buff[2] = ERR_FUN_CODE;		// 不合法功能號(hào)//send_num = 5;break;}rec_stat = PACK_START;//發(fā)送之后使緩存回到初始狀態(tài)rec_num = 0;}
          /*function:對(duì)應(yīng)modbus功能號(hào)03,04 批量讀寄存器input:rec_buf接收到的指令 send_data需要發(fā)送的指令*/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字節(jié)	data_num = rec_buff[5]*2;//需要讀取的字節(jié)數(shù)	send_num = 5 + data_num;	// 5個(gè)固定字節(jié)+數(shù)據(jù)個(gè)數(shù) addr1 + fun1 + num1 ++ crc2	rec_buff[2] = data_num;//字節(jié)數(shù)	piont = (unsigned char *)modbusAdd; //將結(jié)構(gòu)體轉(zhuǎn)換為字符數(shù)組,便于后面的循環(huán)讀取或?qū)懭?span style="white-space:pre">	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:對(duì)應(yīng)modbus功能號(hào)06和16,單個(gè)和批量寫寄存器input:rec_buf接收到的指令 send_data需要發(fā)送的指令*/void force_reg(unsigned char * rec_buf){	unsigned char fun_code,begin_add,data_num;//功能碼,開始地址,數(shù)據(jù)長(zhǎng)度	unsigned int send_num;//發(fā)送數(shù)據(jù)長(zhǎng)度	unsigned char *piont;	unsigned int send_CRC;	int i;//	send_data[0] = rec_buf[0]; //獲取站號(hào)	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;	//將結(jié)構(gòu)體轉(zhuǎn)換為字符數(shù)組,便于后面的循環(huán)讀取或?qū)懭?span style="white-space:pre">		if(fun_code == 6)//寫單個(gè)寄存器,返回指令與接收的指令完全一樣	{		piont[begin_add] = rec_buf[4];//寄存器高位寫入		piont[begin_add+1] = rec_buf[5];//寄存器低位寫入		send_num = 8;//	}	else if(fun_code == 16)//寫多個(gè)寄存器	{		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校驗(yàn)	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單片機(jī)modbus下位機(jī)設(shè)計(jì)這里就結(jié)束了,這種方法是比較靈活了,將協(xié)議的實(shí)現(xiàn)單獨(dú)放在一層,避免與主函數(shù)有太多交互


          評(píng)論


          技術(shù)專區(qū)

          關(guān)閉
          看屁屁www成人影院,亚洲人妻成人图片,亚洲精品成人午夜在线,日韩在线 欧美成人 (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })();