需要源码的请关注我的公众号: Modbus是一个非常好用的通讯协议,经常用在串口通讯中,也可以用在网口。它既简洁又规范,尤其在工业中应用非常广泛。Modbus的程序实现也比较简单,用户可以自己实现,也可以移植开源的协议代码,比如今天要介绍的FreeModbus。 硬件环境:STM32F103C8T6 软件环境:STM32CubeMXv6.1.1 HAL库:STM32CubeF1Firmware Package V1.8.3 FreeModbus版本:freemodbus-v1.6 FreeModbus文件说明 下载之后解压出来,可以看到文件夹内包含以下内容。我们需要关注的只有modbus文件夹和demo下的BARE文件夹。modbus文件夹下是协议的具体代码。Demo->BARE文件夹下是接口文件,需要用户进行移植和修改的。 STM32CubeMX配置 需要使能一个串口和一个定时器,定时器的功能是用于检测3.5个字符的空闲,以判断一帧数据的结束。这里以USART1和TIM4为例进行介绍。 定时器配置: 串口配置: 定时器和串口的参数任意即可,具体在程序中进行配置。打开定时器和串口的中断,且串口中断的优先级要高于定时器中断。 串口和定时器中断程序我们自己编写,所以这里需要取消串口和定时器中断自动生成代码的选项。如下图: FreeModbus移植 生成代码后,将下载的FreeModbus的源码复制到工程目录中,在Keil工程中新建两个Group组:FreeModbus和Port。添加modbus文件夹下的全部文件到FreeModbus组。将Demo->BARE->Port文件夹下的文件添加到Port组。同时新建一个Port.c文件也添加到Port组。 另外,别忘了在工程中添加包含路径,否则编译出错。 下面开始程序移植,首先是portserial.c文件,该文件是串口的接口文件,包含以下函数:串口使能、串口初始化、发送一个字节、接收一个字节等。我们需要自己实现完成这些函数的内容。完成后的内容如下: - void
- vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
- {
- /* If xRXEnable enable serial receive interrupts. If xTxENable enable
- * transmitter empty interrupts.
- */
- if(xRxEnable)
- {
- __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE); //使能接收中断
- }
- else
- {
- __HAL_UART_DISABLE_IT(&huart1, UART_IT_RXNE); //关闭接收中断
- }
-
- if(xTxEnable)
- {
- SET_DE; //485芯片设置为发送模式
- __HAL_UART_ENABLE_IT(&huart1, UART_IT_TXE); //使能发送中断
- }
- else
- {
- __HAL_UART_DISABLE_IT(&huart1, UART_IT_TXE); //关闭发送为空中断
- __HAL_UART_ENABLE_IT(&huart1, UART_IT_TC); //使能发送完成中断
- }
- }
-
- BOOL
- xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
- {
- huart1.Instance = USART1;
- huart1.Init.BaudRate = ulBaudRate;
- huart1.Init.StopBits = UART_STOPBITS_1;
- huart1.Init.Mode = UART_MODE_TX_RX;
- huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
- huart1.Init.OverSampling = UART_OVERSAMPLING_16;
-
- switch(eParity)
- {
- // 奇校验
- case MB_PAR_ODD:
- huart1.Init.Parity = UART_PARITY_ODD;
- huart1.Init.WordLength = UART_WORDLENGTH_9B; //带奇偶校验数据位为9bits
- break;
-
- //偶校验
- case MB_PAR_EVEN:
- huart1.Init.Parity = UART_PARITY_EVEN;
- huart1.Init.WordLength = UART_WORDLENGTH_9B; //带奇偶校验数据位为9bits
- break;
-
- //无校验
- default:
- huart1.Init.Parity = UART_PARITY_NONE;
- huart1.Init.WordLength = UART_WORDLENGTH_8B; //无奇偶校验数据位为8bits
- break;
- }
- return HAL_UART_Init(&huart1) == HAL_OK ? TRUE : FALSE;
- }
-
- BOOL
- xMBPortSerialPutByte( CHAR ucByte )
- {
- /* Put a byte in the UARTs transmit buffer. This function is called
- * by the protocol stack if pxMBFrameCBTransmitterEmpty( ) has been
- * called. */
- USART1->DR = ucByte;
- return TRUE;
- }
-
- BOOL
- xMBPortSerialGetByte( CHAR * pucByte )
- {
- /* Return the byte in the UARTs receive buffer. This function is called
- * by the protocol stack after pxMBFrameCBByteReceived( ) has been called.
- */
- *pucByte = (USART1->DR & (uint16_t)0x00FF);
- return TRUE;
- }
-
- /* Create an interrupt handler for the transmit buffer empty interrupt
- * (or an equivalent) for your target processor. This function should then
- * call pxMBFrameCBTransmitterEmpty( ) which tells the protocol stack that
- * a new character can be sent. The protocol stack will then call
- * xMBPortSerialPutByte( ) to send the character.
- */
- static void prvvUARTTxReadyISR( void )
- {
- pxMBFrameCBTransmitterEmpty( );
- }
-
- /* Create an interrupt handler for the receive interrupt for your target
- * processor. This function should then call pxMBFrameCBByteReceived( ). The
- * protocol stack will then call xMBPortSerialGetByte( ) to retrieve the
- * character.
- */
- static void prvvUARTRxISR( void )
- {
- pxMBFrameCBByteReceived( );
- }
-
- //串口中断函数
- void USART1_IRQHandler(void)
- {
- if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE)) //接收中断标
- {
- __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_RXNE); //清除中断标记
- prvvUARTRxISR(); //通知modbus有数据到达
- }
-
- if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TXE)) //发送中断标记被置位
- {
- __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_TXE); //清除中断标记
- prvvUARTTxReadyISR(); //通知modbus数据可以发松
- }
-
- if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TC)) //发送完成中断
- {
- __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_TC); //清除中断标记
- CLR_DE; //485芯片设置为接收模式
- }
- }
复制代码
然后是porttimer.c文件,该文件时定时器的接口文件。定时器的作用是用于通知modbus协议栈3.5个字符的空闲时间已经到达。我们需要实现定时器的初始化和中断相关函数,定时需要配置为50us计数一次,具体计数周期与波特率有关。完成后的内容如下: - BOOL
- xMBPortTimersInit( USHORT usTim1Timerout50us )
- {
- TIM_ClockConfigTypeDef sClockSourceConfig = {0};
- TIM_MasterConfigTypeDef sMasterConfig = {0};
-
- htim4.Instance = TIM4;
- htim4.Init.Prescaler = 3599; //50us计数一次
- htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
- htim4.Init.Period = usTim1Timerout50us - 1;
- htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
- htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
- if (HAL_TIM_Base_Init(&htim4) != HAL_OK)
- {
- Error_Handler();
- }
- sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
- if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK)
- {
- Error_Handler();
- }
- sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
- sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
- if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK)
- {
- Error_Handler();
- }
-
- __HAL_TIM_ENABLE_IT(&htim4, TIM_IT_UPDATE); //使能定时器更新中断
-
- return TRUE;
- }
-
-
- inline void
- vMBPortTimersEnable( )
- {
- /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
- __HAL_TIM_SET_COUNTER(&htim4, 0); //清空定时器
- __HAL_TIM_ENABLE(&htim4); //使能定时器
- }
-
- inline void
- vMBPortTimersDisable( )
- {
- /* Disable any pending timers. */
- __HAL_TIM_DISABLE(&htim4); //关闭定时器
- }
-
- /* Create an ISR which is called whenever the timer has expired. This function
- * must then call pxMBPortCBTimerExpired( ) to notify the protocol stack that
- * the timer has expired.
- */
- static void prvvTIMERExpiredISR( void )
- {
- ( void )pxMBPortCBTimerExpired( );
- }
-
- //定时器4中断函数
- void TIM4_IRQHandler(void)
- {
- if(__HAL_TIM_GET_FLAG(&htim4, TIM_FLAG_UPDATE)) //判断更新中断
- {
- __HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_UPDATE); //清除中断标记
- prvvTIMERExpiredISR(); //通知modbus3.5个字符等待时间到
- }
- }
复制代码
接下来需要实现的就是port.c文件,需要实现各个寄存器/线圈的读写函数,可以参考demo->BARE文件夹下的demo.c文件。完成后的内容如下: - //输入寄存器
- #define REG_INPUT_START 3000
- #define REG_INPUT_NREGS 4
-
- //保持寄存器
- #define REG_HOLD_START 4000
- #define REG_HOLD_NREGS 10
-
- //线圈
- #define REG_COILS_START 0
- #define REG_COILS_NREGS 4
-
- //开关寄存器
- #define REG_DISCRETE_START 1000
- #define REG_DISCRETE_NREGS 4
- /* ----------------------- Static variables ---------------------------------*/
- static USHORT usRegInputStart = REG_INPUT_START;
- static USHORT usRegInputBuf[REG_INPUT_NREGS];
-
-
- static USHORT usRegHoldStart = REG_HOLD_START;
- static USHORT usRegHoldBuf[REG_HOLD_NREGS];
-
- static USHORT usRegCoilsStart = REG_COILS_START;
- static uint8_t usRegCoilsBuf[REG_COILS_NREGS];
-
- static USHORT usRegDiscreteStart = REG_DISCRETE_START;
- static uint8_t usRegDiscreteBuf[REG_DISCRETE_NREGS];
- /* ----------------------- Start implementation -----------------------------*/
- //int
- //main( void )
- //{
- // eMBErrorCode eStatus;
-
- // eStatus = eMBInit( MB_RTU, 0x0A, 0, 38400, MB_PAR_EVEN );
-
- // /* Enable the Modbus Protocol Stack. */
- // eStatus = eMBEnable( );
-
- // for( ;; )
- // {
- // ( void )eMBPoll( );
-
- // /* Here we simply count the number of poll cycles. */
- // usRegInputBuf[0]++;
- // }
- //}
- /****************************************************************************
- * 名 称:eMBRegInputCB
- * 功 能:读取输入寄存器,对应功能码是 04 eMBFuncReadInputRegister
- * 入口参数:pucRegBuffer: 数据缓存区,用于响应主机
- * usAddress: 寄存器地址
- * usNRegs: 要读取的寄存器个数
- * 出口参数:
- * 注 意:上位机发来的 帧格式是: SlaveAddr(1 Byte)+FuncCode(1 Byte)
- * +StartAddrHiByte(1 Byte)+StartAddrLoByte(1 Byte)
- * +LenAddrHiByte(1 Byte)+LenAddrLoByte(1 Byte)+
- * +CRCAddrHiByte(1 Byte)+CRCAddrLoByte(1 Byte)
- * 3 区
- ****************************************************************************/
- eMBErrorCode
- eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
- {
- eMBErrorCode eStatus = MB_ENOERR;
- int iRegIndex;
- usAddress = usAddress - 1;
-
- 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--;
- }
- }
- else
- {
- eStatus = MB_ENOREG;
- }
-
- return eStatus;
- }
- /****************************************************************************
- * 名 称:eMBRegHoldingCB
- * 功 能:对应功能码有:06 写保持寄存器 eMBFuncWriteHoldingRegister
- * 16 写多个保持寄存器 eMBFuncWriteMultipleHoldingRegister
- * 03 读保持寄存器 eMBFuncReadHoldingRegister
- * 23 读写多个保持寄存器 eMBFuncReadWriteMultipleHoldingRegister
- * 入口参数:pucRegBuffer: 数据缓存区,用于响应主机
- * usAddress: 寄存器地址
- * usNRegs: 要读写的寄存器个数
- * eMode: 功能码
- * 出口参数:
- * 注 意:4 区
- ****************************************************************************/
- eMBErrorCode
- eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
- {
- eMBErrorCode eStatus = MB_ENOERR;
- int iRegIndex;
- usAddress = usAddress - 1;
-
- if((usAddress >= REG_HOLD_START) && ((usAddress+usNRegs) <= (REG_HOLD_START + REG_HOLD_NREGS)))
- {
- iRegIndex = (int)(usAddress - usRegHoldStart);
- switch(eMode)
- {
- case MB_REG_READ://读寄存器
- while(usNRegs > 0)
- {
- *pucRegBuffer++ = (uint8_t)(usRegHoldBuf[iRegIndex] >> 8);
- *pucRegBuffer++ = (uint8_t)(usRegHoldBuf[iRegIndex] & 0xFF);
- iRegIndex++;
- usNRegs--;
- }
- break;
- case MB_REG_WRITE://写寄存器
- while(usNRegs > 0)
- {
- usRegHoldBuf[iRegIndex] = *pucRegBuffer++ << 8;
- usRegHoldBuf[iRegIndex] |= *pucRegBuffer++;
- iRegIndex++;
- usNRegs--;
- }
- }
- }
- else//错误
- {
- eStatus = MB_ENOREG;
- }
-
- return eStatus;
- }
-
- /****************************************************************************
- * 名 称:eMBRegCoilsCB
- * 功 能:对应功能码有:01 读线圈 eMBFuncReadCoils
- * 05 写线圈 eMBFuncWriteCoil
- * 15 写多个线圈 eMBFuncWriteMultipleCoils
- * 入口参数:pucRegBuffer: 数据缓存区,用于响应主机
- * usAddress: 线圈地址
- * usNCoils: 要读写的线圈个数
- * eMode: 功能码
- * 出口参数:
- * 注 意:如继电器
- * 0 区
- ****************************************************************************/
- eMBErrorCode
- eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
- {
- eMBErrorCode eStatus = MB_ENOERR;
- USHORT iRegIndex;
- USHORT usCoilGroups = ((usNCoils - 1) / 8 + 1);
- UCHAR ucStatus = 0;
- UCHAR ucBits = 0;
- UCHAR ucDisp = 0;
- usAddress = usAddress - 1;
-
- if((usAddress >= REG_COILS_START) && ((usAddress + usNCoils) <= (REG_COILS_START + REG_COILS_NREGS)))
- {
- iRegIndex = (int)(usAddress - usRegCoilsStart);
- switch(eMode)
- {
- case MB_REG_READ://读线圈
- while(usCoilGroups--)
- {
- ucDisp = 0;
- ucBits = 8;
- while((usNCoils--) != 0 && (ucBits--) != 0)
- {
- ucStatus |= (usRegCoilsBuf[iRegIndex++] << (ucDisp++));
- }
- *pucRegBuffer++ = ucStatus;
- }
- break;
- case MB_REG_WRITE://写线圈
- while(usCoilGroups--)
- {
- ucStatus = *pucRegBuffer++;
- ucBits = 8;
- while((usNCoils--) != 0 && (ucBits--) != 0)
- {
- usRegCoilsBuf[iRegIndex++] = ucStatus & 0X01;
- ucStatus >>= 1;
- }
- }
- }
- }
- else//错误
- {
- eStatus = MB_ENOREG;
- }
-
- return eStatus;
- }
- /****************************************************************************
- * 名 称:eMBRegDiscreteCB
- * 功 能:读取离散寄存器,对应功能码有:02 读离散寄存器 eMBFuncReadDiscreteInputs
- * 入口参数:pucRegBuffer: 数据缓存区,用于响应主机
- * usAddress: 寄存器地址
- * usNDiscrete: 要读取的寄存器个数
- * 出口参数:
- * 注 意:1 区
- ****************************************************************************/
- eMBErrorCode
- eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
- {
- eMBErrorCode eStatus = MB_ENOERR;
- USHORT iRegIndex;
- USHORT usDiscreteGroups = ((usNDiscrete - 1) / 8 + 1);
- UCHAR ucStatus = 0;
- UCHAR ucBits = 0;
- UCHAR ucDisp = 0;
- usAddress = usAddress - 1;
-
- if((usAddress >= REG_DISCRETE_START) && ((usAddress + usNDiscrete) <= (REG_DISCRETE_START + REG_DISCRETE_NREGS)))
- {
- iRegIndex = (int)(usAddress - usRegDiscreteStart);
-
- while(usDiscreteGroups--)
- {
- ucDisp = 0;
- ucBits = 8;
- while((usNDiscrete--) != 0 && (ucBits--) != 0)
- {
- if(usRegDiscreteBuf[iRegIndex])
- {
- ucStatus |= (1 << ucDisp);
- }
- ucDisp++;
- }
- *pucRegBuffer++ = ucStatus;
- }
- }
- else//错误
- {
- eStatus = MB_ENOREG;
- }
-
- return eStatus;
- }
复制代码
最后在主程序中初始化并调用相关函数即可: - eMBInit(MB_RTU, 0x01, 0, 9600, MB_PAR_NONE); // 初始化modbus为RTU方式,地址0x01, 波特率9600,无校验
- eMBEnable(); // 使能modbus协议栈
- /* USER CODE END 2 */
-
- /* Infinite loop */
- /* USER CODE BEGIN WHILE */
- while (1)
- {
- eMBPoll();
- /* USER CODE END WHILE */
-
- /* USER CODE BEGIN 3 */
- }
复制代码
总结 FreeModbus实现了Modbus协议的全部功能,移植和使用起来也比较简单。唯一不足的是只有从机协议是开源的,而主机协议是收费的。
|