| **银杏科技有限公司旗下技术文档发布平台** ||||
|技术支持电话|**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 |}}