添加文件
- 获取原始free modbus library(官网)
- 将...\freemodbus-v1.5.0\demo\BARE中的所有文件复制到...\freemodbus-v1.5.0\modbus中,修改demo.c文件名为user_mb_app.c
- 将...\freemodbus-v1.5.0\modbus中的所有.c文件全部添加到项目中
- 在项目路径中添加所有.c、.h文件路径
添加完成后项目结构图:
移植修改
需要修改的文件:
- port.h:补全开关总中断的宏定义、宏定义串口和定时器
#define ENTER_CRITICAL_SECTION( ) __set_PRIMASK(1); //关闭中断 #define EXIT_CRITICAL_SECTION( ) __set_PRIMASK(0); //开启中断
- portserial.c:补全串口相关函数(串口中断使能选择、串口初始化、发送1字节、接收1字节、串口中断服务函数)
- 注意事项:
- 使能中断:发送中断应使用TC而非TXE,否则可能会出现最后一个字节不能成功发送的情况。此外由于是用485发送,所以在使能中断时,应同时转换485收发转换引脚。使能中断前,应判断对应的标志位是否为1,为1则清除该标志位。
- 串口初始化:初始化前用USART_DeInit重置寄存器;引脚初始化(GPIO)->串口初始化(USART)->中断初始化(NVIC);参数中的ucPORT和eParity都应该忽略。
- 发送1字节:不用循环等待发送完成,因为已经有发送完成中断了
- 串口中断服务函数:在stm32f0xx_it.c中添加USART3_4_IRQHandler函数,跳转到本文件中的prvvModbusUARTISR函数。
-
1 /* 2 * FreeModbus Libary: BARE Port 3 * Copyright (C) 2006 Christian Walter
- 注意事项:
- porttimer.c:补全定时器相关函数(定时器初始化、定时器使能、定时器关闭使能、定时器中断服务函数)
- 注意事项:
- 初始化:初始化前应用TIM_DeInit函数重置寄存器值;初始化后要清标志位
- 定时器使能:要先关中断、关定时器,然后清标志位、重置计数器,最后开中断、开定时器
- 中断服务函数:要先判断中断标志位,再清标志位、进入操作部分。
-
1 /* 2 * FreeModbus Libary: BARE Port 3 * Copyright (C) 2006 Christian Walter
- 注意事项:
- user_mb_app.h:定义各模拟寄存器地址
- 注意事项:
- 每个寄存器固定长度16位(2字节)
- 寄存器地址:从机内部定义的寄存器地址,要对寻址用的寄存器地址+1【Modbus标准协议规定】。即:从机内部定义的寄存器地址,必须大于1;对于寄存器1-16,寻址时通过0-15来寻址(如:查询寄存器1的值时,指令中的寄存器地址为00 00)。
- 换句话说,就是:从机程序中定义寄存器地址为1-16时,文档中的寄存器地址要写成0-15。
- 注意事项:
- user_mb_app.c:补全输入寄存器操作函数、保持寄存器操作函数(操作方式可参见...\freemodbus-v1.5.0\demo\ATSAM3S\demo.c),将main分解为modbus初始化函数和modbus进程函数,添加结构体与模拟寄存器之间的数据交互函数。
- 注意事项:
- eMBInit初始化:仅需更改地址和波特率。
- user_mb_app函数中不要用循环(本身就会被应用到主程序中的while(1)中)
-
1 /* ----------------------- Modbus includes ----------------------------------*/ 2 #include "mb.h" 3 #include "mbport.h" 4 #include "user_mb_app.h" 5 6 /* ----------------------- Defines ------------------------------------------*/ 7 #define REG_INPUT_START ((uint16_t)0x0000) 8 #define REG_INPUT_NREGS 25 9 #define REG_HOLD_START ((uint16_t)0x0000) 10 #define REG_HOLD_NREGS 30 11 12 /* ----------------------- Static variables ---------------------------------*/ 13 static USHORT usRegInputStart = REG_INPUT_START; 14 static USHORT usRegInputBuf[REG_INPUT_NREGS]; 15 static USHORT usRegHoldStart = REG_HOLD_START; 16 static USHORT usRegHoldBuf[REG_HOLD_NREGS]; 17 extern uint8_t g_Meter_Data[]; 18 extern uint8_t g_Check_Data[]; 19 /* ----------------------- Start implementation -----------------------------*/ 20 /****************************************************************************** 21 ** 函数名称: mb_Modbus_Init 22 ** 功能描述: modbus初始化 23 ** 入口参数: 无 24 ** 返 回 值: 无 25 ** 26 ** 作 者: Cage 27 ** 日 期: 2018年3月9日 28 **----------------------------------------------------------------------------- 29 ******************************************************************************/ 30 void mb_Modbus_Init(void) { 31 32 uint8_t address = dio_Get_DIP_Value(); //获取拨码开关信息,获得本机地址 33 ( void )eMBInit( MB_RTU, address, 0, 9600, MB_PAR_NONE ); 34 35 /* Enable the Modbus Protocol Stack. */ 36 ( void )eMBEnable( ); 37 } 38 39 40 /****************************************************************************** 41 ** 函数名称: user_mb_app 42 ** 功能描述: modbus进程函数 43 ** 入口参数: 无 44 ** 返 回 值: 无 45 ** 46 ** 作 者: Cage 47 ** 日 期: 2018年3月13日 48 **----------------------------------------------------------------------------- 49 ******************************************************************************/ 50 void user_mb_app( void ) 51 { 52 53 ( void )eMBPoll( ); 54 55 } 56 57 58 /****************************************************************************** 59 ** 函数名称: _mb_Fresh_Input_Reg 60 ** 功能描述: 将计量数据结构体中的数据刷新到Modbus模拟寄存器中 61 ** 入口参数: 无 62 ** 返 回 值: 无 63 ** 64 ** 作 者: Cage 65 ** 日 期: 2018年3月13日 66 **----------------------------------------------------------------------------- 67 ******************************************************************************/ 68 static void _mb_Fresh_Input_Reg(void) { 69 uint8_t i; 70 USHORT *pmeter_data = (USHORT*)g_Meter_Data; 71 for(i = 0; i
) { 72 usRegInputBuf[i] = *pmeter_data++; 73 } 74 } 75 76 77 /****************************************************************************** 78 ** 函数名称: _mb_Fresh_Check_Struct 79 ** 功能描述: 将Modbus模拟寄存器中的数据刷新到校表数据结构体 80 ** 入口参数: 无 81 ** 返 回 值: 无 82 ** 83 ** 作 者: Cage 84 ** 日 期: 2018年3月13日 85 **----------------------------------------------------------------------------- 86 ******************************************************************************/ 87 static void _mb_Fresh_Check_Struct(void) { 88 uint8_t i; 89 USHORT *pcheck_data = (USHORT*)g_Check_Data; 90 for(i = 0; i ) { 91 *pcheck_data++ = usRegHoldBuf[i]; 92 } 93 } 94 95 96 //读输入寄存器 97 eMBErrorCode 98 eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) 99 { 100 eMBErrorCode eStatus = MB_ENOERR; 101 int iRegIndex; 102 103 if( ( usAddress >= REG_INPUT_START ) 104 && ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) ) 105 { 106 _mb_Fresh_Input_Reg(); //将计量数据结构体中的数据刷新到Modbus模拟寄存器中 107 iRegIndex = ( int )( usAddress - usRegInputStart ); 108 while( usNRegs > 0 ) 109 { 110 *pucRegBuffer++ = 111 ( unsigned char )( usRegInputBuf[iRegIndex] >> 8 ); 112 *pucRegBuffer++ = 113 ( unsigned char )( usRegInputBuf[iRegIndex] & 0xFF ); 114 iRegIndex++; 115 usNRegs--; 116 } 117 } 118 else 119 { 120 eStatus = MB_ENOREG; 121 } 122 123 return eStatus; 124 } 125 126 //写保持寄存器--未定义 127 eMBErrorCode 128 eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, 129 eMBRegisterMode eMode ) 130 { 131 eMBErrorCode eStatus = MB_ENOERR; 132 int iRegIndex; 133 134 if( ( usAddress >= REG_HOLD_START ) 135 && ( usAddress + usNRegs <= REG_HOLD_START + REG_HOLD_NREGS ) ) 136 { 137 iRegIndex = ( int )( usAddress - usRegHoldStart ); 138 if(eMode == MB_REG_WRITE) { 139 while( usNRegs > 0 ) 140 { 141 usRegHoldBuf[iRegIndex] = *pucRegBuffer++ << 8; 142 usRegHoldBuf[iRegIndex] |= *pucRegBuffer++; 143 iRegIndex++; 144 usNRegs--; 145 } 146 } 147 _mb_Fresh_Check_Struct(); //将Modbus模拟寄存器中的数据刷新到校表数据结构体 148 } 149 else 150 { 151 eStatus = MB_ENOREG; 152 } 153 154 return eStatus; 155 } 156 157 /*********************************不使用的功能*********************************/ 158 eMBErrorCode 159 eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, 160 eMBRegisterMode eMode ) 161 { 162 return MB_ENOREG; 163 } 164 165 eMBErrorCode 166 eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete ) 167 { 168 return MB_ENOREG; 169 }
-
1 case EV_EXECUTE: 2 ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF]; 3 eException = MB_EX_ILLEGAL_FUNCTION; 4 for( i = 0; i < MB_FUNC_HANDLERS_MAX; i++ ) 5 { 6 /* No more function handlers registered. Abort. */ 7 if( xFuncHandlers[i].ucFunctionCode == 0 ) 8 { 9 break; 10 } 11 else if( xFuncHandlers[i].ucFunctionCode == ucFunctionCode ) 12 { 13 eException = xFuncHandlers[i].pxHandler( ucMBFrame, &usLength ); 14 break; 15 } 16 } 17 18 /* If the request was not sent to the broadcast address we 19 * return a reply. */ 20 if( ucRcvAddress != MB_ADDRESS_BROADCAST ) 21 { 22 if( eException != MB_EX_NONE ) 23 { 24 /* An exception occured. Build an error frame. */ 25 usLength = 0; 26 ucMBFrame[usLength++] = ( UCHAR )( ucFunctionCode | MB_FUNC_ERROR ); 27 ucMBFrame[usLength++] = eException; 28 } 29 if( ( eMBCurrentMode == MB_ASCII ) && MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS ) 30 { 31 vMBPortTimersDelay( MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS ); 32 } 33 eStatus = peMBFrameSendCur( ucMBAddress, ucMBFrame, usLength ); 34 /*发送数组准备完毕,串口切换到了发送状态,但TC标志位为0,不能自动开始发送*/ 35 if( eStatus == MB_ENOERR ) 36 { 37 xMBRTUTransmitFSM(); //发送第一个字节,启动发送 38 } 39 } 40 break;
调试
- 串口收到数据后,无限进入中断
- 现象:仿真时,一直进入中断服务函数,不作任何处理后跳出;如此反复进入中断服务函数。
- 判断:ORE标志位未清除。RXNEIE中断使能包括RXNE标志位和ORE标志位,中断服务函数中只判断、处理了RXNE标志位。
- 处理:在portserial.c的中断服务函数prvvModbusUARTISR中,加入USART_IT_ORE标志位的判断与处理【注:stm32f072库函数中的USART_IT_ORE标志位定义错误,需要修改】
- 结果:问题解决
- Modbus收到数据后不响应
- 现象:跟踪发现,Modbus协议一直进行到“串口发送”之前都是正常的,可是之后却没有发送
- 原因:上一次发送最后一个字节时,发送中断中清除了TC标志位;切换到发送状态、使能TCIE后,TC标志位为0,无法启动发送。
- 解决方案:
- TXE+TC发送:
- 中断使能函数:切换为发送状态时,使能TXE中断而非TC中断(由TXE启动发送,TXE永不手动清零)
- 中断服务函数:TXE启动发送后,将中断使能由TXE改为TC,之后由TC判断发送完成、清TC标志位
- 手动启动:切换为发送状态后,手动启动发送(发送第一个字节)
- TXE+TC发送:
- 处理:选择了方案2,问题解决。
仿真跟踪--Modbus数据处理流程(RTU)
- modbus poll主进程(eMBPoll)获取消息状态,执行指令
- 数据接收:串口中断接收数据(数据存入ucRTUBuf数组)->超过3.5us没有收到数据->判断一串数据接收完毕->将“接收完毕”消息添加到消息队列
- 数据处理:
- eMBPoll获取到“接收完毕”消息,令ucMBFrame指针指向ucRTUBuf数组的第1位(命令字),获取地址位和数据长度(不含地址位和校验位的长度),将“执行”消息添加到消息队列
- eMBPoll获取到“执行”消息,通过ucMBFrame中的命令字,进行对应的操作(比如:04--读输入寄存器),将要发回的数据存入ucMBFrame(不含地址位和校验位)
- 将ucMBFrame中的数据加上地址位后计算校验位,将地址位和校验位存入ucRTUBuf数组,将发送状态标志由空闲转为发送,串口状态转为发送
- --至此发送数组(ucRTUBuf)和串口状态都已经准备完毕,但没有发送指令
- 【添加】手动发送发送数组的第一个字节,启动发送
- 数据发送:通过串口将发送数组逐字节发送。