330 likes | 689 Views
MODBUS RTU 主从通讯实验. 1 .实验目的 学会使用 ZLG/Modbus RTU 软件包开发 Modbus 主机设备及从机设备。. 2 .实验设备. 硬件: PC 机 2 台 MagicARM2410 教学实验开发平台 2 台 RS485 通讯连接线 1 条 LA1032 逻辑分析仪 1 套 软件: Windows98/XP/2000 操作系统 ADS1.2 集成开发环境 μC/OS-II 操作系统( V2.52 ) ZLG/Modbus RTU 软件包. 3 .实验内容.
E N D
MODBUS RTU主从通讯实验 1.实验目的 学会使用ZLG/Modbus RTU软件包开发Modbus主机设备及从机设备。
2.实验设备 • 硬件: PC机 2台 • MagicARM2410教学实验开发平台 2台 • RS485通讯连接线 1条 • LA1032逻辑分析仪 1套 • 软件:Windows98/XP/2000操作系统 • ADS1.2集成开发环境 • μC/OS-II操作系统(V2.52) • ZLG/Modbus RTU软件包
3.实验内容 (1) 编与ZLG/Modbus RTU软件包的底层接口函数。 (2) Modbus从机使用蜂鸣器B1模拟为1个线圈量,地址为0。 (3) Modbus主机通过检测KEY1按键控制从机蜂鸣器B1蜂鸣。
4.实验预习要求 (1) 仔细阅读产品配套光盘上该实验例程的目录中ZLG/Modbus RTU使用说明文档。 (2) 仔细阅参考文献[2]关于S3C2410A的UART模块和定时器模块的说明。
5.实验原理 本实验中使用到的软件包的API函数及资源占用的情况如下: ZLG/Modbus RTU软件包 API函数:主机/从机初始化函数、主机/从机 服务函数、主机功能代码执行函数。 外设资源占用:UART1和一个GPIO引脚GPE13 ,定 时器2、定时器3和定时器4。 μC/OS-II资源占用:Modbus主机使用3个事件,从 机使用1个事件;Modbus主机/从机服务任 务堆栈推荐使用64。
5.实验原理 在从机中,设定从机地址为1,并且使用蜂鸣器B1模拟为1个线圈量,其地址为0,主机通过写线圈操作改变蜂鸣器B1状态。当接收到写线圈地址0的值为1时,蜂鸣器B1 蜂鸣,反之蜂鸣器B1不响。 主机通过中使用1个任务,检测KEY1的状态,当KEY1按下时,发送写地址为0的从机0地址线圈的值为1,当KEY1松开时,发送写地址为0的从机线圈0地址的值为0。
6.实验步骤 主机部分: (1) 选用ARM Executable Image for DeviceARM2410 (μCOSII)工程模板创建MBMaster工程,并将μC/OS-II相关代码文件、Modbus主机相关文件复制到相关工程目录,然后分别添加MBCommon.c、MB_RTU.c、MBMaster.c、MB_MFunction.c和OSFUNfile.c到工程。 (2) 打开cofig.h文件,添加ZLG/Modbus主机相关的头文件MBMaster.h,由于在μC/OS-II操作系统中使用,所以还需要定义UCOSII宏,如程序清单 1.1所示。
6.实验步骤 程序清单 1.1 添加头文件 /********************************** ** ZLG/Modbus TRU相关头文件与配置 ***********************************/ #define UCOSII // 在uC/OS-II下使用本必须定义该宏 #include "MBMaster.h" // Modbus主机栈头文件 void IniUART1(uint32 bps); // Modbus使用的串行口 void UART1_Exception(void); // Modbus使用串行口的中断服务函数 void TimersInit(void); // Modbus使用定时器初始化 void T15_Exception(void); // Modbus T15定时器中断服务函数 void T35_Exception(void); // Modbus T35定时器中断服务函数 void T10ms_Exception(void); // Modbus T10MS定时器中断服务函数
6.实验步骤 (3) 编写ZLG/Modbus RTU主机软件包接口文件SYSHAL.C。主要是编写:void SendResponse(uint8 *buff,uint16 len):发送应答帧函数;void StartCountT15(void):T1.5开始计时函数;void StartCountT35(void):T3.5开始计时函数;void Start10mS(void):10mS开始计时函数。 此外还有与系统相关的UART1初始化、定时器2、定时器3和定时器4初始化以及相应中断的异常处理函数。(注意:定时器4需要自动重加载定时值。)
6.实验步骤 (4) 删除模板的默认任务,UART0初始化和LCD初始化的相关代码。 (5) 在target.c文件void VICInit(void)函数中添加定时器2、定时器3、定时器4及UART1中断向量初始化代码。如程序清单 1.2所示。 程序清单 1.2 Modbus使用的中断配置 VICVectAddr[12] = (uint32) T15_Exception; rINTMSK &= ~(1<<12); // 打开TIMER2中断允许 VICVectAddr[13] = (uint32) T35_Exception; rINTMSK &= ~(1<<13); // 打开TIMER3中断允许 VICVectAddr[14] = (uint32) T10ms_Exception; rINTMSK &= ~(1<<14); // 打开TIMER4中断允许 VICVectAddr[23] = (uint32) UART1_Exception; rSUBSRCPND =(BIT_SUB_RXD1|BIT_SUB_ERR1); rINTSUBMSK &=~(BIT_SUB_RXD1|BIT_SUB_ERR1); ClearPending(BIT_UART1); rINTMSK &=~(BIT_UART1);
6.实验步骤 同时也在target.c文件中void TargetInit(void)中初始化定时器2、定时器3、定时器4和UART1的波特率为115200及初始化HostMassLib,如程序清单 1.3所示。 程序清单 1.3 初始化UART0波特率 void TargetInit(void) { uint32 temp; OS_ENTER_CRITICAL(); srand((uint32) TargetInit); VICInit(); Timer0Init(); IniUART1(115200); // Modbus驱动使用的波特率 TimersInit(); // 定时器初始化 Start10mS(); // 起动10mS定时计数 MBMasterIni(); // Modbus主机初始化函数 ... }
6.实验步骤 (6) 在main.c文件中编写Modbus主机实验程序(如程序清单 1.4所示),并选择Debug生成目标,然后编译连接工程。 注意:由于Modbus主机软件包使用了3个事件,所以需要在os_cfg.h文件中修改适当的事件数。 程序清单 1.4 Modbus主机实验程序 #define Task0StkLengh 64 // 定义用户任务0的堆栈长度 OS_STK Task0Stk [Task0StkLengh]; // 定义用户任务0的堆栈 void Task0(void *pdata); // Task0 任务0 #define OSModbusServeStkLengh 64 OS_STK OSModbusServeStk [OSModbusServeStkLengh]; int main (void) { OSInit (); OSTaskCreate ( Task0,(void *)0, &Task0Stk[Task0StkLengh - 1], 2 ); OSTaskCreate ( OSModbusServe,(void *)0, &OSModbusServeStk[OSModbusServeStkLengh - 1], 3 ); OSStart (); return 0; }
6.实验步骤 #define KEY1 (1<<4) // rGPFCON[9:8] = 00b,设置GPF4为GPIO输入模式 #define key1_ini() rGPFCON = (rGPFCON & (~(0x03<<8))); #define key1_get() (rGPFDAT & KEY1) #define key2_ini() PINSEL1 &= ~(3<<(KEY2-16)*2); IO0DIR &= ~(1<<KEY2) #define key2_get() (IO0PIN &(1<<KEY2)) void Task0 (void *pdata) { pdata = pdata; TargetInit (); key1_ini(); while (1) { if( 0 == key1_get() ) // 有按键按下 { // 写设备地址为1的线圈地址为1的值为1 OSWriteSingleCoil( 0x01,1,COIL_ON ); } else { // 写设备地址为1的线圈地址为1的值为0 OSWriteSingleCoil( 0x01,1,COIL_OFF ); } } }
6.实验步骤 从机部分: (7) 选用ARM Executable Image for DeviceARM2410 (μCOSII)工程模板创建MBSlave工程,并将μC/OS-II相关代码文件、Modbus从机相关文件包拷贝到相关工程目录,然后分别添加MBCommon.c、MB_RTU.c、MBSlave.c和MB_Pfunction.c文件到工程。 (8) 打开config.h文件,添加ZLG/Modbus主机相关的头文件MBMaster.h,由于在μC/OS-II操作系统中使用,所以还需要定义UCOSII宏,如程序清单 1.5所示。 程序清单 1.5 修改config.h文件 #define UCOSII // 在uC/OS-II下使用本必须定义该宏 #include "MBSlave.h" // Modbus从机栈头文件 void IniUART1(uint32 bps); // Modbus使用的串行口 void UART1_Exception(void); // Modbus使用串行口的中断服务函数 void TimersInit(void); // Modbus使用定时器初始化 void T15_Exception(void); // Modbus T15定时器中断服务函数 void T35_Exception(void); // Modbus T35定时器中断服务函数
6.实验步骤 (9) 编写ZLG/Modbus RTU主机软件包接口文件SYSHAL.C,与主机使用相同的代码。 (10) 在IRQ.s文件中添加定时器1及UART0中断句柄,参见实验步骤(4)。 (11) 在target.c文件void VICInit(void)函数中添加定时器1及UART0中断向量初始化代码,参见实验步骤(5)。 (12) 在MBSlave.h文件中配置使用写单线圈Modbus功能代码,并配置线圈的个数为1,如程序清单 1.6所示。
6.实验步骤 程序清单 1.6 设置Modbus从机功能代码 /*************************************************** ** 使能Modbus功能代码 ****************************************************/ #define READ_COILS_EN 0 // 读线圈 #define READ_DIS_INPUT_EN 0 // 读离散量输入 #define READ_HOLD_REG_EN 0 // 读保持寄存器 #define READ_INPUT_REG_EN 0 // 读输入寄存器 #define WRITE_SING_COIL_EN 1 // 写单个线圈 #define WRITE_SING_REG_EN 0 // 写单个寄存器 #define WRITE_MULT_COIL_EN 0 // 写多个线圈 #define WRITE_MULT_REG_EN 0 // 写多个寄存器 #define MASK_WRITE_REG_EN 0 // 处理屏蔽寄存器指令 #define READ_WRITE_REG_EN 0 // 读写多个寄存器 /*************************************************** ** 配置线圈的参数。定义线圈的数量 ****************************************************/ #define END_COILS_ADDR 1
6.实验步骤 (13) 编写与Modbus从机功能代码相关的接口函数,当写线圈地址0的值为1时,蜂鸣器蜂鸣,写线圈地址0的值为0时,蜂鸣器熄灭,如程序清单 1.7所示。 程序清单 1.7 从机功能代码相关的接口函数 #define BEEP (1<<10) // 定义蜂鸣器控制口 #define BEEP_MASK (~BEEP) // 蜂鸣器B1 GPIO初始化 #define IniBUZZ() rGPHCON = (rGPHCON & (~(0x03<<20))) | (0x01<<20); #define SetBUZZ() rGPHDAT = rGPHDAT | BEEP // 蜂鸣器蜂鸣 #define ClrBUZZ() rGPHDAT = rGPHDAT & BEEP_MASK // 蜂鸣器关闭 // 输入参数:Address,线圈地址; CoilValue,线圈值(0\1) // 输出参数:返回寄存器值 // 功能描述:设置线圈值函数,访函数由用户编写 uint8 MB_SetCoil(uint16 Address,uint8 CoilValue) { if(Address==0) // 地址为1 { if( CoilValue == 1 ) ClrBUZZ(); // 蜂鸣器蜂鸣 else SetBUZZ(); // 蜂鸣器关闭 } return TRUE; }
6.实验步骤 (14) 在main.c文件中编写Modbus从机实验程序(如程序清单 1.8所示),并选择Debug生成目标,然后编译连接工程。 注意:由于Modbus主机软件包使用了1个事件,所以需要在os_cfg.h文件中修改适当的事件数。 程序清单 1.8 Modbus从机实验程序 #define Task0StkLengh 64 // 定义用户任务0的堆栈长度 OS_STK Task0Stk [Task0StkLengh]; // 定义用户任务0的堆栈 void Task0(void *pdata); // Task0 任务0 #define TaskModBUSStkLengh 64 OS_STK TaskModBUSStk [TaskModBUSStkLengh]; int main (void) { OSInit (); OSTaskCreate ( Task0,(void *)0, &Task0Stk[Task0StkLengh - 1], 2); OSTaskCreate ( OSModbusServe,(void *)0, &TaskModBUSStk[TaskModBUSStkLengh - 1], 4); OSStart (); return 0; }
6.实验步骤 /***************************************************************************/ /* 设定Modbus从机信息 */ /***************************************************************************/ uint8 ADUBuff[ MAX_ADU_LENGTH ]; // ADU数据缓冲区 SLAVE_INFORMATION SlaveDevice = { 0x01, // 该Modbus从机地址 0x00, // 链路层协议 1152, // 波特率 = BaudRate * 100 0, // 奇偶校验 2, // 停止位 ADUBuff // 主机请求从帧指针 }; void Task0 (void *pdata) { pdata = pdata; TargetInit (); IniBUZZ(); while (1) { OSTimeDly(OS_TICKS_PER_SEC); } }
6.实验步骤 联机通讯部分: (15) 使用短路器分别短接主机和从机的JP2跳线器选择485R,并且短接Modbus从机的JP9。 (16) 使用电缆分别将主机J3的485A、485B和从机J3的485A、485B连接。 (17) 启动AXD分别全速运行Modbus主机和从机实验程序。 (18) 按下Modbus主机的KEY1按键时,Modbus从机的蜂鸣器蜂鸣,松开按键时,Modbus从机的蜂鸣器熄灭。 (19) 使用LA1032逻辑分析仪的Modbus插件观察Modbus主机发送的请求包和Modbus主机从机的应答包。主机的KEY1按下时,Modbus从机接收到请求帧如图 1.1所示,Modbus从机回应的应答帧如图 1.2所示。
6.实验步骤 联机通讯部分: (15) 使用短路器分别短接主机和从机的JP2跳线器选择485R,并且短接Modbus从机的JP9。 (16) 使用电缆分别将主机J3的485A、485B和从机J3的485A、485B连接。 (17) 启动AXD分别全速运行Modbus主机和从机实验程序。 (18) 按下Modbus主机的KEY1按键时,Modbus从机的蜂鸣器蜂鸣,松开按键时,Modbus从机的蜂鸣器熄灭。 (19) 使用LA1032逻辑分析仪的Modbus插件观察Modbus主机发送的请求包和Modbus主机从机的应答包。主机的KEY1按下时,Modbus从机接收到请求帧如图 1.1所示,Modbus从机回应的应答帧如图 1.2所示。
6.实验步骤 图 1.1 按下按键时Modbus从机接收到请求帧 图 1.2 按下按键时Modbus从机回应的应答帧
6.实验步骤 松开按键时,Modbus从机接收到请求帧如图 1.3所示,Modbus从机回应的应答帧如图 1.4所示。 图 1.3 按下按键时Modbus从机接收到请求帧 图 1.4 按下按键时Modbus从机回应的应答帧
7.实验参考程序 发送应答帧函数SendResponse,如程序清单1.9所示。 程序清单1.9 SendResponse void SendResponse(uint8 *buff,uint16 len) { uint16 i,k; RS_485_S(); // RS485发送使能 for(k=0;k<len;k++) { while(!(rUTRSTAT1 & 0x02)); // 等待发送器THR为空 for(i=0; i<10; i++); rUTXH1 = buff[k]; // 发送数据 while(!(rUTRSTAT1 & 0x02)); // 等待发送器THR为空 } while(!(rUTRSTAT1 & 0x04)); // 等待发送器THR为空 RS_485_R(); // RS485接收使能 }
7.实验参考程序 T1.5开始计时函数StartCountT15,如程序清单1.10所示。 程序清单1.10 StartCountT15 void StartCountT15(void) { uint32 temp = rTCON& TIMERS_UP_MAK; rTCNTB2 = 75; // 定时750 us rTCON = temp|(1<<13); // 更新定时器数据 rTCON = temp|(1<<12); // 启动定时器 } T3.5开始计时函数StartCountT35,如程序清单1.11所示。 程序清单1.11 StartCountT35 void StartCountT35(void) { uint32 temp = rTCON & TIMERS_UP_MAK; rTCNTB3 = 750; // 定时1750 us rTCON = temp|(1<<17); // 更新定时器数据 rTCON = temp|(1<<16); // 启动定时器 }
7.实验参考程序 10mS开始计时函数Start10mS,如程序清单1.12所示。 程序清单1.12 Start10mS void Start10mS(void) { uint32 temp = rTCON& TIMERS_UP_MAK; rTCNTB4 = 1000; // 定时10000 us rTCON = temp|(1<<21); // 更新定时器数据 rTCON = temp|(1<<20)|(1<<22); // 启动定时器 } UART1初始化函数IniUART1,如程序清单1.13所示。 程序清单1.13 UART1初始化函数 #define RS_485_S_R 13 // 485发送与接收控制引脚,初始化GPIO #define RS_485_INI() rGPECON = (rGPECON & (~(0x03<<26))) | (0x01<<26); #define RS_485_S() rGPEDAT |= (1<<RS_485_S_R); // 485发送使能 #define RS_485_R() rGPEDAT &= ~(1<<RS_485_S_R); // 485接收使能
7.实验参考程序 void IniUART1(uint32 bps) { RS_485_INI(); // IO口设置 (GPH5,GPH4) rGPHUP = rGPHUP | (0x03<<4); rGPHCON = (rGPHCON & (~0x00000F00)) | (0x00000A00); // 串口模式设置 rUFCON1 = 0x00; // 禁止FIFO功能 rUMCON1 = 0x00; // AFC(流控制)禁能 rULCON1 = 0x07; // 禁止IRDA,无奇偶校验,2位停止位,8位数据位 rUCON1 = 0x105; // 使用PCLK来生成波特率,发送中断为电平触发模 // 式,接收中断为边沿触发模式,禁止接收超时中 // 断,使能接收错误中断,正常工作模式, // 中断或查询方式(非DMA) rUBRDIV1=(int)(PCLK/16.0/bps + 0.5) -1; // 串口波特率设置 }
7.实验参考程序 定时器2、定时器3和定时器4初始化函数TimersInit,如程序清单1.14所示。 程序清单1.14 TimersInit void TimersInit(void) { // Fclk=200MHz,Pclk=50MHz。 定时器的记数单位为1微秒 rTCFG0 &=~(0xff<<8); rTCFG0 |= 250<<8; // 预分频器0设置为250,取得200KHz rTCFG1 &= ~(0xf<<8); // TIMER2再取1/2分频,取得100KHz rTCFG1 &= ~(0xf<<12); // TIMER3再取1/2分频,取得100KHz rTCFG1 &= ~(0xf<<16); // TIMER4再取1/2分频,取得100KHz rTCON &= ~(0x7ff<<12); // 初始化定时器2、3、4的控制位,全为零 }
7.实验参考程序 UART1中断处理函数UART1_Exception,如程序清单1.15所示。 程序清单1.15 UART1接收中断 void UART1_Exception(void) { rINTSUBMSK|=(BIT_SUB_RXD1|BIT_SUB_ERR1); if(rUTRSTAT1 & 0x01) { ReceOneChar(rURXH1); // 调用Modbus接收字符函数 } ClearPending(BIT_UART1); rSUBSRCPND =(BIT_SUB_RXD1|BIT_SUB_ERR1); rINTSUBMSK &=~(BIT_SUB_RXD1|BIT_SUB_ERR1); }
7.实验参考程序 定时器2中断处理函数T15_Exception,如程序清单1.16所示。 程序清单1.16 T15_Exception void T15_Exception(void) { rSRCPND = BIT_TIMER2; rINTPND = BIT_TIMER2; rINTPND; T15EndHandle(); rTCON &= ~(1<<12); // 关定时器 } 定时器3中断处理函数T35_Exception,如程序清单 1.17所示。 程序清单 1.17 T35_Exception void T35_Exception(void) { rSRCPND = BIT_TIMER3; rINTPND = BIT_TIMER3; rINTPND; T35EndHandle(); rTCON &= ~(1<<16); // 关定时器 }
7.实验参考程序 定时器4中断处理函数T10ms_Exception,如程序清单 1.18所示。 程序清单 1.18 T10ms_Exception void T10ms_Exception(void) { rSRCPND = BIT_TIMER4; rINTPND = BIT_TIMER4; rINTPND; Time10mSHandle(); }
8.思考题 请用户思考一下,如何使用Modbus RTU软件包设计一个设备与现有的Modbus设备通讯?