| **银杏科技有限公司旗下技术文档发布平台** |||| |技术支持电话|**0379-69926675-801**||| |技术支持邮件|Gingko@vip.163.com||| ^ 版本 ^ 日期 ^ 作者 ^ 修改内容 ^ | V1.0 | 2020-9-26 | zgf | 初次建立 | ===== 实验四十五:Modbus TCP通信实验——电源监控 ===== ==== 一、 实验目的与意义 ==== - 了解Modbus TCP通讯协议; - 了解Modbus TCP与TCP协议的关系; - 掌握Modbus Poll的使用方法; - 掌握KEIL MDK 集成开发环境使用方法。 ==== 二、 实验设备及平台 ==== - iCore4T 双核心板及底板; - JLINK(或相同功能)仿真器; - 以太网通讯线缆; - Keil MDK 开发平台; - 装有WIN XP(及更高版本)系统的计算机。 ==== 三、 实验原理 ==== === 1.Modbus _TCP简介 === * Modbus通信协议由Modicon公司于1979年发明的,是全球最早用于工业现场的总线规约。它是一种主从通讯方式,采用服务器和客户机的模式记性通讯。Modbus通信协议具有多个变种,其具有支持串口(主要是RS-485总线),以太网多个版本,其中最著名的是Modbus RTU,Modbus ASCII和Modbus TCP三种。本实验主要针对Modbus TCP进行的一个数据通讯实验。 * Modbus TCP协议基于TCP协议实现,其数据帧包含于TCP数据之中,本文以数据读取及其应答为例对Modbus TCP进行读取。数据读取请求报文格式如表1所示。 * 表1 Modbus TCP数据读取请求报文 |事务处理标识 |协议标识 |报文长度 |设备标识 |功能码 |起始地址 |寄存器数量| |2byte |2byte |2byte |1byte |1byte |2 byte |2 byte| * 事务处理标识符:两个字节,一般每次通讯后将加1,从而区别不同的通讯数据报文。 * 协议标识符:两个字节,Modbus TCP标识符为0x0000。 * 数据长度:两个字节,标识后面的数据量的大小,数据长度以字节为单位。 * 设备标识:用以标识连接在串行线或者网络上的远程服务端的地址。 * 功能码:一般为读取的寄存器的类型。 * 起始地址:寄存器的起始地址。 * 寄存器数量:要读取寄存器的数量。 * 数据读取应答报文格式如表2所示。 * 表2 Modbus TCP数据读取应答报文 |事务处理标识 |协议标识 |报文长度 |设备标识 |功能码 |数据长度 |数据| |2byte |2byte |2byte |1byte |1byte |1byte |n byte| * 事务处理标识符:两个字节,与请求标识一致。 * 协议标识符:两个字节,与请求标识一致。 * 数据长度:两个字节,指示后面的数据量的大小,数据长度以字节为单位。 * 设备标识:用以标识连接在串行线或者网络上的远程服务端的地址。 * 功能码:一般为读取的寄存器的地址,与请求标识一致。 * 数据长度:传输的数据长度。 * 数据:寄存器的数据。 ==== 四、 实验程序 ==== * 本实验主要基于TCP通讯和ADC电源监控两部分实现,本文对这两个方面不做过多描述。 1.主函数 int main(void) { /* USER CODE BEGIN 1 */ int cnt; short data; /* USER CODE END 1 */ /*MCU Configuration------------------------------------*/ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ i2c.initialize(); axp152.initialize(); axp152.set_dcdc1(3500);//[ARM & FPGA BK1/2/6 &OTHER] axp152.set_dcdc2(1200);//[FPGA INT & PLL D] axp152.set_aldo1(2500);//[FPGA PLL A] axp152.set_dcdc4(3300);//[POWER_OUTPUT] axp152.set_dcdc3(3300);//[FPGA BK4][Adjustable] axp152.set_aldo2(3300);//[FPGA BK3][Adjustable] axp152.set_dldo1(3300);//[FPGA BK7][Adjustable] axp152.set_dldo2(3300);//[FPGA BK5][Adjustable] /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_ADC1_Init(); MX_ADC3_Init(); MX_ETH_Init(); /* USER CODE BEGIN 2 */ //LWIP初始化 NETMPU_Config(); lwip.initialize(); eth_tcps.initialize(); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ if((cnt ++ / 800000) % 2){ LED_RED_ON; }else{ LED_RED_OFF; } lwip.periodic_handle(); //根据定时器计数标志,每隔一秒,读取一次ADC的值 if(adc_read_flag ==1) { adc_read_flag = 0; my_adc.read(0); my_adc.read_mux(); } //将ADC采集的计算值,放大1000倍便于观察数据的正确性。计算公式请参考adc部分例程。 data = my_adc.value[0]*6000; hold_reg[0] = data >> 8; hold_reg[1] = data & 0xff; data = my_adc.value[1] * 2000; hold_reg[2] = data >> 8; hold_reg[3] = data & 0xff; data = my_adc.value[2]*2000; hold_reg[4] = data >> 8; hold_reg[5] = data & 0xff; data = my_adc.value[3]*2000; hold_reg[6] = data >> 8; hold_reg[7] = data & 0xff; data = my_adc.value[4]*2000; hold_reg[8] = data >> 8; hold_reg[9] = data & 0xff; data = my_adc.value[5]*500;·· hold_reg[10] = data >> 8; hold_reg[11] = data & 0xff; data = my_adc.value[6]*1000; hold_reg[12] = data >> 8; hold_reg[13] = data & 0xff; data = my_adc.value[7]*2000; hold_reg[14] = data >> 8; hold_reg[15] = data & 0xff; data = my_adc.value[8]*2000; hold_reg[16] = data >> 8; hold_reg[17] = data & 0xff; if(eth_tcps.receive_ok_flag == 1){ eth_tcps.receive_ok_flag = 0; modbus_tcp.process(eth_tcps.receive_buffer); } } /* USER CODE END 3 */ } 2.TCP数据发送函数 * 基于TCP中的服务器代码数据发送函数进行函数封装,设计一个发送指定数量数据的函数。 int send(struct tcp_pcb *tpcb,unsigned char *data,int len) { int ret_err; struct tcp_server_struct *es; memcpy(eth_tcps.send_buffer,data,len); es = tpcb->callback_arg; if(es != NULL){ //连接处于空闲可以发送数据 es->p = pbuf_alloc(PBUF_TRANSPORT,len,PBUF_POOL);//申请内存 pbuf_take(es->p,(char*)eth_tcps.send_buffer,len);//将eth_tcps.send_buffer[]中的数据拷贝到es->p_tx中 tcp_server_senddata(tpcb,es); //将eth_tcps.send_buffer[]里面复制给pbuf的数据发送出去 if(es->p)pbuf_free(es->p); //释放内存 ret_err=ERR_OK; }else{ tcp_abort(tpcb); //终止连接,删除pcb控制块 ret_err=ERR_ABRT; } return ret_err; } 3.Modbus TCP协议处理 * Modbus TCP的协议处理由多个子函数组成,函数代码如下。 int err; char discrete_input[32] = {0x55,0x55,0x55}; char coil[32] = {0x55,0x55,0x55}; char input_reg[20] = {0,1,0,2,0,3,0,4,0,5,0,6,0,7,0,8,0,9,0,10}; char hold_reg[512] = {0x01,0x02,0x03}; //--------------------------- Function --------------------------// static int process(unsigned char *modbus_recvbuf) { unsigned char receive_buffer_temp[100]; memcpy(receive_buffer_temp,modbus_recvbuf,12); memset(modbus_recvbuf,0,12); mb_rsq_pdu(receive_buffer_temp,12); return 0; } static int mb_rsq_pdu(unsigned char *receive_buffer_temp,int counter_temp) { if(receive_buffer_temp[0 + 6] == 0x01){ switch(receive_buffer_temp[1 + 6]){ case 1: function_1(receive_buffer_temp); break; case 2: function_2(receive_buffer_temp); break; case 3: function_3(receive_buffer_temp); break; case 4: function_4(receive_buffer_temp); break; case 5: function_5(receive_buffer_temp,counter_temp); break; case 6: function_6(receive_buffer_temp,counter_temp); break; default : mb_excep_rsq_pdu(receive_buffer_temp,1); break; } }else if(receive_buffer_temp[0 + 6] == 0){ broadcast(receive_buffer_temp); } return 0; } static int function_1(unsigned char *receive_buffer_temp) { int i; unsigned short cnt; unsigned short coil_num; unsigned short start_address; int temp = 0; start_address = (receive_buffer_temp[2 + 6] << 8) | receive_buffer_temp[3 + 6]; coil_num = receive_buffer_temp[4 + 6] << 8| receive_buffer_temp[5 + 6]; if((start_address + coil_num) > 255){ mb_excep_rsq_pdu(receive_buffer_temp,2); return 1; } receive_buffer_temp[2 + 6] = ((coil_num % 8 )? (coil_num / 8 + 1) : (coil_num / 8)); cnt = receive_buffer_temp[2 + 6] + 5 - 2; if(coil_num % 8){ if(coil_num < 8){ for(i = 0;i < coil_num;i ++)temp |= 1 << i; receive_buffer_temp[3 + 6] = ((coil[start_address / 8]) >> (start_address % 8) | (coil[start_address / 8 + 1]) << (8 - (start_address % 8))) & temp; }else { for(i = 0;i < receive_buffer_temp[2 + 6] - 1;i++)receive_buffer_temp[3 + i + 6] = (coil[i + start_address / 8]) >> (start_address % 8) | (coil[i + start_address / 8 + 1]) << (8 - (start_address % 8)); receive_buffer_temp[3 + i + 6] = (coil[i + start_address / 8] << ((8 - (coil_num % 8 - start_address % 8) % 8)) & 0xff) >> (8 - (coil_num % 8)); } }else { for(i = 0;i < receive_buffer_temp[2 + 6];i++)receive_buffer_temp[3 + i + 6] = (coil[i + start_address / 8]) >> (start_address % 8) | (coil[i + start_address / 8 + 1]) << (8 - (start_address % 8)); } receive_buffer_temp[4] = (cnt & 0xff00) >> 8; receive_buffer_temp[5] = (cnt & 0x00ff); cnt = cnt + 6; eth_tcps.send(eth_tcps.tcppcbnew,receive_buffer_temp,cnt); return 0; } static int function_2(unsigned char *receive_buffer_temp) { int i; unsigned short cnt; unsigned short discrete_num; unsigned short start_address; int temp = 0; start_address = (receive_buffer_temp[2 + 6] << 8) | receive_buffer_temp[3 + 6]; discrete_num = receive_buffer_temp[4 + 6] << 8| receive_buffer_temp[5 + 6]; if((start_address + discrete_num) > 255){ mb_excep_rsq_pdu(receive_buffer_temp,2); return 1; } receive_buffer_temp[2 + 6] = ((discrete_num % 8 )? (discrete_num / 8 + 1) : (discrete_num / 8)); cnt = receive_buffer_temp[2 + 6] + 5 - 2; if(discrete_num % 8){ if(discrete_num < 8){ for(i = 0;i < discrete_num;i ++)temp |= 1 << i; receive_buffer_temp[3 + 6] = ((discrete_input[start_address / 8]) >> (start_address % 8) | (discrete_input[start_address / 8 + 1]) << (8 - (start_address % 8))) & temp; }else { for(i = 0;i < receive_buffer_temp[2 + 6] - 1;i++)receive_buffer_temp[3 + i + 6] = (discrete_input[i + start_address / 8]) >> (start_address % 8) | (discrete_input[i + start_address / 8 + 1]) << (8 - (start_address % 8)); receive_buffer_temp[3 + i + 6] = (discrete_input[i + start_address / 8] << ((8 - (discrete_num % 8 - start_address % 8) % 8)) & 0xff) >> (8 - (discrete_num % 8)); } }else { for(i = 0;i < receive_buffer_temp[2 + 6];i++)receive_buffer_temp[3 + i + 6] = (discrete_input[i + start_address / 8]) >> (start_address % 8) | (discrete_input[i + start_address / 8 + 1]) << (8 - (start_address % 8)); } receive_buffer_temp[4] = (cnt & 0xff00) >> 8; receive_buffer_temp[5] = (cnt & 0x00ff); cnt = cnt + 6; eth_tcps.send(eth_tcps.tcppcbnew,receive_buffer_temp,cnt); return 0; } static int function_3(unsigned char *receive_buffer_temp) { int i; int cnt = 0; unsigned short int start_address; start_address = (receive_buffer_temp[2 + 6] << 8) | receive_buffer_temp[3 + 6]; receive_buffer_temp[2 + 6] = receive_buffer_temp[5 + 6] * 2; if(receive_buffer_temp[2 + 6] > 48){ mb_excep_rsq_pdu(receive_buffer_temp,2); return 1; } if((start_address * 2 + receive_buffer_temp[2]) > 512){ mb_excep_rsq_pdu(receive_buffer_temp,2); return 1; } cnt = receive_buffer_temp[2 + 6] + 5 - 2; for(i = 0;i < receive_buffer_temp[2 + 6];i++){ receive_buffer_temp[i + 3 + 6] = hold_reg[start_address * 2 + i]; } receive_buffer_temp[4] = (cnt & 0xff00) >> 8; receive_buffer_temp[5] = (cnt & 0x00ff); cnt = cnt + 6; eth_tcps.send(eth_tcps.tcppcbnew,receive_buffer_temp,cnt); return 0; } static int function_4(unsigned char *receive_buffer_temp) { int i; int cnt; unsigned short int start_address; start_address = (receive_buffer_temp[2 + 6] << 8) | receive_buffer_temp[3 + 6]; receive_buffer_temp[2 + 6] = receive_buffer_temp[5 + 6] * 2; if((start_address * 2 + receive_buffer_temp[2 + 6]) > 20){ mb_excep_rsq_pdu(receive_buffer_temp,2); return 1; } cnt = receive_buffer_temp[2 + 6] + 5 - 2; for(i = 0;i < receive_buffer_temp[2 + 6];i++)receive_buffer_temp[i + 3 + 6] = input_reg[start_address * 2 + i]; receive_buffer_temp[4] = (cnt & 0xff00) >> 8; receive_buffer_temp[5] = (cnt & 0x00ff); cnt = cnt + 6; eth_tcps.send(eth_tcps.tcppcbnew,receive_buffer_temp,cnt); return 0; } static int function_5(unsigned char *receive_buffer_temp,int counter_temp) { unsigned short start_address; start_address = (receive_buffer_temp[2 + 6] << 8) | receive_buffer_temp[3 + 6]; if(start_address > 255){ mb_excep_rsq_pdu(receive_buffer_temp,2); return 1; } if((receive_buffer_temp[4 + 6] == 0xff) && (receive_buffer_temp[5 + 6] == 0x00)){ coil[(start_address / 8)] |= 1 << start_address % 8; }else if((receive_buffer_temp[4 + 6] == 0x00) && (receive_buffer_temp[5 + 6] == 0x00)){ coil[(start_address / 8)] &= ~(1 << start_address % 8); }else { mb_excep_rsq_pdu(receive_buffer_temp,3); } receive_buffer_temp[4] = (6 & 0xff00) >> 8; receive_buffer_temp[5] = (6 & 0x00ff); eth_tcps.send(eth_tcps.tcppcbnew,receive_buffer_temp,counter_temp); return 0; } static int function_6(unsigned char *receive_buffer_temp,int counter_temp) { unsigned short start_address; start_address = (receive_buffer_temp[2 + 6] << 8) | receive_buffer_temp[3 + 6]; if(start_address > 255){ mb_excep_rsq_pdu(receive_buffer_temp,2); return 1; } hold_reg[start_address * 2] = receive_buffer_temp[4 + 6]; hold_reg[start_address * 2 + 1] = receive_buffer_temp[5 + 6]; receive_buffer_temp[4] = (6 & 0xff00) >> 8; receive_buffer_temp[5] = (6 & 0x00ff); eth_tcps.send(eth_tcps.tcppcbnew,receive_buffer_temp,counter_temp); return 0; } static int mb_excep_rsq_pdu(unsigned char *receive_buffer_temp,int error_code) { receive_buffer_temp[1 + 6] |= 0x80; switch(error_code) { case 1: receive_buffer_temp[2 + 6] = 1; break; case 2: receive_buffer_temp[2 + 6] = 2; break; case 3: receive_buffer_temp[2 + 6] = 3; break; case 4: receive_buffer_temp[2 + 6] = 4; break; default : break; } receive_buffer_temp[4] = (3 & 0xff00) >> 8; receive_buffer_temp[5] = (3 & 0x00ff); eth_tcps.send(eth_tcps.tcppcbnew,receive_buffer_temp,9); return 0; } static int broadcast(unsigned char *receive_buffer_temp) { int start_address; switch(receive_buffer_temp[1 + 6]){ case 5: start_address = (receive_buffer_temp[2 + 6] << 8) | receive_buffer_temp[3 + 6]; if(start_address > 255){ return 1; } if((receive_buffer_temp[4 + 6] == 0xff) && (receive_buffer_temp[5 + 6] == 0x00)){ coil[(start_address / 8)] |= 1 << start_address % 8; }else if((receive_buffer_temp[4 + 6] == 0x00) && (receive_buffer_temp[5 + 6] == 0x00)){ coil[(start_address / 8)] &= ~(1 << start_address % 8); } break; case 6: start_address = (receive_buffer_temp[2 + 6] << 8) | receive_buffer_temp[3 + 6]; if(start_address > 255){ return 1; } hold_reg[start_address * 2] = receive_buffer_temp[4 + 6]; hold_reg[start_address * 2 + 1] = receive_buffer_temp[5 + 6]; break; } return 0; } ==== 五、 实验步骤 ==== - 把仿真器与iCore4T的SWD调试口相连(直接相连或者通过转接器相连); - 把iCore4T通过以太网线与计算机相连,为iCore4T供电; - 设置电脑IP地址(见附录1); - 打开Keil MDK 开发环境,并打开本实验工程; - 烧写程序到iCore4T上; - 打开Modbus Poll软件建立连接(见附录2),读取hold寄存器中的数据值,观察核心板返回的数据值。 ==== 六、 实验现象 ==== * Modbus Poll软件可看到核心板电源数据的检测值。 {{ :icore4t:icore4t_arm_hal_45_1.png?direct |}} ==== 附录1:电脑IP设置 ==== * 打开控制面板->网络和 Internet->网络连接,选择对应的网卡,右键点击属性,设置IPV4的IP地址,如图所示: {{ :icore4t:icore4t_arm_hal_45_2.png?direct |}} {{ :icore4t:icore4t_arm_hal_45_3.png?direct |}} ==== 附录2:Modbus Poll软件建立连接 ==== * 打开Modbus Poll,点击Connet,设置协议类型、IP地址和端口号,点击OK开始进行连接。 {{ :icore4t:icore4t_arm_hal_45_4.png?direct |}} {{ :icore4t:icore4t_arm_hal_45_5.png?direct |}}