## Modbus https://blog.csdn.net/ASWaterbenben/article/details/105549750 https://blog.51cto.com/u_15830484/5767899 https://www.amobbs.com/thread-5491615-1-1.html https://blog.csdn.net/qq153471503/article/details/104840279 https://blog.csdn.net/childbor/article/details/123803534 野火 30001为输入寄存器,需要使用04指令访问,而40001为保持寄存器,可以使用03、06和16指令访问 在modbus中,需要判断一帧什么时候发送结束,我们采用定时器进行判断。 当定时器时间大于我们设定的时间时,发生定时器中断,告诉系统,一帧数据发送完成。 首先选择TIM7下的Parameter Settings,将PSC设置为7199,Counter Mode设置为UP,Counter Period 设置为39,Auto-reload preload设置为Enable。 拷贝mobdus文件加 拷贝demo/bare 到 port文件夹, demo.c 改modbus_port.c mbconfig.h 配置启用哪些模块 对于元文件需要做修改的,加 .port 后缀,放到code/port文件夹 #### 移植思路 ##### 串口定时器初始化放到 Modbus里 暂无 ##### 串口定时器单独初始化 ***portserial.c*** extern UART_HandleTypeDef huart2; // 引入串口 vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable ) 依据参考开启相关中断 UART_IT_RXNE 接收非空 UART_IT_TXE 发送完成 xMBPortSerialPutByte( CHAR ucByte ) // 处理发送字节 ```C ``` xMBPortSerialGetByte( CHAR * pucByte ) //处理接收字节 void USART2_IRQHandler(void) prvvUARTRxISR();//接收中断 prvvUARTTxReadyISR();//发送中断 void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);//发送完成回调函数 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);//接收完成回调函数 ***porttimer.c*** 定时器启用,停用。 请中断等操作 定时器是中断回调 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) prvvTIMERExpiredISR( ); //定时器中断 ***modbus_port.c*** 定义Input Holding 寄存器操作 中断建立联系 -- 如何无缝,不改中断函数代码,回调函数 ***前端初始化*** eMBInit( MB_RTU, 0x01, 2, 115200, MB_PAR_NONE);//初始化modbus,走modbusRTU,从站地址为0x01,端口为1。 eMBEnable( );//使能modbus while ( void )eMBPoll( );//启动modbus侦听 ### 源码结构 定义结构体接收数据 usart.h ```C typedef struct { uint8_t *rx_buf; //接收缓冲数组 uint16_t rx_buf_cnt; //接收缓冲计数值 uint16_t rx_size; //接收数据大小 uint8_t rx_flag; //接收完成标志位 uint8_t *tx_buf; //发送缓冲数组 uint16_t tx_buf_cnt; //发送缓冲计数值 uint16_t tx_size; //实际发送数据大小 }UART_BUF; //串口结构体 extern UART_BUF uart_buf_2; //预留一个定义的buf ``` usart.c ```C /* USER CODE BEGIN 0 */ #include "string.h" #include "tim.h" #define UART4_RXSIZE 1024 //一帧接收数据的最大值 #define UART4_TXSIZE 1024 //一帧发送数据的最大值 uint8_t uart4_rx_buf[UART4_RXSIZE]; //发送数据缓冲数组 uint8_t uart4_tx_buf[UART4_TXSIZE]; //接收数据缓冲数据 UART_BUF uart4; //串口结构体实体 uint8_t RxBuffer; //接收数据中间变量 /* USER CODE END 0 */ ``` 重写回调 ```C /*****************************重写回调函数,实现串口数据接收**********************/ void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance == UART4) { if(uart4.rx_buf_cnt >= UART4_RXSIZE-1) //接收数据量超限,错误 { uart4.rx_buf_cnt = 0; memset(uart4.rx_buf, 0x00, sizeof(uart4.rx_buf)); HAL_UART_Transmit(huart, (uint8_t *)"数据溢出", 10, 0xFFFF); } else //接收正常 { uart4.rx_buf[uart4.rx_buf_cnt++] = RxBuffer; //接收数据存储到rx_buf HAL_TIM_Base_Stop_IT(&htim7); __HAL_TIM_SET_COUNTER(&htim7, 0); HAL_TIM_Base_Start_IT(&htim7); //将定时器7的计数值清零后重新计数 } HAL_UART_Receive_IT(huart, (uint8_t *)&RxBuffer, 1); } } ``` port.h #define ENTER_CRITICAL_SECTION( ) __set_PRIMASK(1);//关总中断 #define EXIT_CRITICAL_SECTION( ) __set_PRIMASK(0);//开总中断 如何改DMA ? tim 初始化及回调 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) ### main buf 重置 ---写函数实现供调用 HAL_UART_Receive_IT(huart, uart4.rx_buf, 1); //开启接收中断 tim清理标记 -- 写函数实现供调用 开启中断 ### modbus slave 将已经编写好的modbus.c和modbus.h分别复制到Src和Inc文件夹内 #### freemodbus 解压freemodbus文件后打开, modbus 目录组合,重新分Src Inc 我们需要demo目录下的BARE,该目录下的代码是空的,STM32移植工作基本就是修改:portserial.c、porttimer.c、port.h这三个文件。 参考: https://github.com/eziya/STM32_HAL_FREEMODBUS_RTU/tree/master/Middlewares/Third_Party/modbus bare/port 目录下的Makefile 可以直接编译 modbus 目录拷贝到 Middlewares/Third_Party/modbus/ bare/port 目录下的Makefile 可以直接编译, demo 包含main() functions include 是基础目录 rtu 需要引用 rtu目录 #### porttimer.c 将定时器设置为每50us的时长记一个数,传入的usTim1Timerout50us变量给自动装载即可 prvvTIMERExpiredISR函数需要在定时器中断服务函数中调用,它的作用是用于通知modbus协议栈3.5个字符的等待时间已经到达 注意一些标志清除 定时器中断 TimX_IRQHandler 通知通知modbus3.5个字符等待时间到 prvvTIMERExpiredISR(); // 通知modbus3.5个字符等待时间到 如果使用了485芯片的话,那么同一时刻只能接收或者发送,可以将函数vMBPortSerialEnable修改成这样 拉高拉低,并增加延时。 #### 定义port.c供前端调用 ***数据定义*** ```C // 十路输入寄存器 #define REG_INPUT_SIZE 10 uint16_t REG_INPUT_BUF[REG_INPUT_SIZE]; // 十路保持寄存器 #define REG_HOLD_SIZE 10 uint16_t REG_HOLD_BUF[REG_HOLD_SIZE]; // 十路线圈 #define REG_COILS_SIZE 10 uint8_t REG_COILS_BUF[REG_COILS_SIZE] = {1, 1, 1, 1, 0, 0, 0, 0, 1, 1}; // 十路离散量 // 十路离散量 #define REG_DISC_SIZE 10 uint8_t REG_DISC_BUF[REG_DISC_SIZE] = {1,1,1,1,0,0,0,0,1,1}; ``` 对应功能函数 ```C /* 功能码 4*/ eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) { return eStatus; } /* 功能码 6 3 16*/ eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode ) { return MB_ENOREG; } /* 功能码 1 5 15*/ eMBErrorCode eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode ) { return MB_ENOREG; } /* 功能码 2*/ eMBErrorCode eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete ) { return MB_ENOREG; } ``` 完整port.c ```C #include "mb.h" #include "mbport.h" // 十路输入寄存器 #define REG_INPUT_SIZE 10 uint16_t REG_INPUT_BUF[REG_INPUT_SIZE]; // 十路保持寄存器 #define REG_HOLD_SIZE 10 uint16_t REG_HOLD_BUF[REG_HOLD_SIZE]; // 十路线圈 #define REG_COILS_SIZE 10 uint8_t REG_COILS_BUF[REG_COILS_SIZE] = {1, 1, 1, 1, 0, 0, 0, 0, 1, 1}; // 十路离散量 #define REG_DISC_SIZE 10 uint8_t REG_DISC_BUF[REG_DISC_SIZE] = {1,1,1,1,0,0,0,0,1,1}; /// CMD4命令处理回调函数 eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) { USHORT usRegIndex = usAddress - 1; // 非法检测 if((usRegIndex + usNRegs) > REG_INPUT_SIZE) { return MB_ENOREG; } // 循环读取 while( usNRegs > 0 ) { *pucRegBuffer++ = ( unsigned char )( REG_INPUT_BUF[usRegIndex] >> 8 ); *pucRegBuffer++ = ( unsigned char )( REG_INPUT_BUF[usRegIndex] & 0xFF ); usRegIndex++; usNRegs--; } // 模拟输入寄存器被改变 for(usRegIndex = 0; usRegIndex < REG_INPUT_SIZE; usRegIndex++) { REG_INPUT_BUF[usRegIndex]++; } return MB_ENOERR; } /// CMD6、3、16命令处理回调函数 eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode ) { USHORT usRegIndex = usAddress - 1; // 非法检测 if((usRegIndex + usNRegs) > REG_HOLD_SIZE) { return MB_ENOREG; } // 写寄存器 if(eMode == MB_REG_WRITE) { while( usNRegs > 0 ) { REG_HOLD_BUF[usRegIndex] = (pucRegBuffer[0] << 8) | pucRegBuffer[1]; pucRegBuffer += 2; usRegIndex++; usNRegs--; } } // 读寄存器 else { while( usNRegs > 0 ) { *pucRegBuffer++ = ( unsigned char )( REG_HOLD_BUF[usRegIndex] >> 8 ); *pucRegBuffer++ = ( unsigned char )( REG_HOLD_BUF[usRegIndex] & 0xFF ); usRegIndex++; usNRegs--; } } return MB_ENOERR; } /// CMD1、5、15命令处理回调函数 eMBErrorCode eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode ) { USHORT usRegIndex = usAddress - 1; UCHAR ucBits = 0; UCHAR ucState = 0; UCHAR ucLoops = 0; // 非法检测 if((usRegIndex + usNCoils) > REG_COILS_SIZE) { return MB_ENOREG; } if(eMode == MB_REG_WRITE) { ucLoops = (usNCoils - 1) / 8 + 1; while(ucLoops != 0) { ucState = *pucRegBuffer++; ucBits = 0; while(usNCoils != 0 && ucBits < 8) { REG_COILS_BUF[usRegIndex++] = (ucState >> ucBits) & 0X01; usNCoils--; ucBits++; } ucLoops--; } } else { ucLoops = (usNCoils - 1) / 8 + 1; while(ucLoops != 0) { ucState = 0; ucBits = 0; while(usNCoils != 0 && ucBits < 8) { if(REG_COILS_BUF[usRegIndex]) { ucState |= (1 << ucBits); } usNCoils--; usRegIndex++; ucBits++; } *pucRegBuffer++ = ucState; ucLoops--; } } return MB_ENOERR; } /// CMD2命令处理回调函数 eMBErrorCode eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete ) { USHORT usRegIndex = usAddress - 1; UCHAR ucBits = 0; UCHAR ucState = 0; UCHAR ucLoops = 0; // 非法检测 if((usRegIndex + usNDiscrete) > REG_DISC_SIZE) { return MB_ENOREG; } ucLoops = (usNDiscrete - 1) / 8 + 1; while(ucLoops != 0) { ucState = 0; ucBits = 0; while(usNDiscrete != 0 && ucBits < 8) { if(REG_DISC_BUF[usRegIndex]) { ucState |= (1 << ucBits); } usNDiscrete--; usRegIndex++; ucBits++; } *pucRegBuffer++ = ucState; ucLoops--; } // 模拟离散量输入被改变 for(usRegIndex = 0; usRegIndex < REG_DISC_SIZE; usRegIndex++) { REG_DISC_BUF[usRegIndex] = !REG_DISC_BUF[usRegIndex]; } return MB_ENOERR; } ``` #### main.c 将串口定时器的初始化都整到 Modbus 部分,这里只需要调用modbus初始化就可以 ```C int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); eMBInit(MB_RTU, 0x01, 0, 9600, MB_PAR_ODD); // 初始化modbus为RTU方式,波特率9600,奇校验 eMBEnable(); // 使能modbus协议栈 for( ;; ) { eMBPoll(); // 轮训查询 } } ``` 我们需要demo目录下的BARE,该目录下的代码是空的,STM32移植工作基本就是修改:portserial.c 串口的中断服务程序, prvvUARTTxReadyISR和prvvUARTRxISR函数需要填写进中断服务程序, 前者得到作用为通知modbus协议栈串口已经空闲可以发送数据了 后者的作用为通知modbus串口1有数据到达 #### 调用 ```C #include "stm32f4xx_hal.h" #include "cmsis_os.h" #include "mb.h" #include "mbport.h" #define REG_INPUT_START 1000 #define REG_INPUT_NREGS 8 static USHORT usRegInputStart = REG_INPUT_START; static USHORT usRegInputBuf[REG_INPUT_NREGS]; void ModbusRTUTask(void const * argument) { /* ABCDEF */ usRegInputBuf[0] = 11; usRegInputBuf[1] = 22; usRegInputBuf[2] = 33; usRegInputBuf[3] = 44; usRegInputBuf[4] = 55; usRegInputBuf[5] = 66; usRegInputBuf[6] = 77; usRegInputBuf[7] = 88; eMBErrorCode eStatus = eMBInit( MB_RTU, 1, 3, 19200, MB_PAR_NONE ); eStatus = eMBEnable(); while(1) { eMBPoll(); } } eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ) { eMBErrorCode eStatus = MB_ENOERR; int iRegIndex; if( ( usAddress >= REG_INPUT_START ) && ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) ) { iRegIndex = ( int )( usAddress - usRegInputStart ); while( usNRegs > 0 ) { *pucRegBuffer++ = ( unsigned char )( usRegInputBuf[iRegIndex] >> 8 ); *pucRegBuffer++ = ( unsigned char )( usRegInputBuf[iRegIndex] & 0xFF ); iRegIndex++; usNRegs--; } HAL_GPIO_TogglePin(LD4_GPIO_Port, LD4_Pin); } else { HAL_GPIO_TogglePin(LD5_GPIO_Port, LD5_Pin); eStatus = MB_ENOREG; } return eStatus; } eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode ) { return MB_ENOREG; } eMBErrorCode eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode ) { return MB_ENOREG; } eMBErrorCode eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete ) { return MB_ENOREG; } ```