完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
相关推荐
2个回答
|
|
Modbus是一个非常好用的通讯协议,经常用在串口通讯中,也可以用在网口。它既简洁又规范,尤其在工业中应用非常广泛。Modbus的程序实现也比较简单,用户可以自己实现,也可以移植开源的协议代码,比如今天要介绍的FreeModbus。
硬件环境:STM32F103C8T6 软件环境:STM32CubeMX v6.1.1 HAL库:STM32CubeF1 Firmware Package V1.8.3 FreeModbus版本:freemodbus-v1.6 1.FreeModbus文件说明 下载之后解压出来,可以看到文件夹内包含以下内容。我们需要关注的只有modbus文件夹和demo下的BARE文件夹。modbus文件夹下是协议的具体代码。Demo->BARE文件夹下是接口文件,需要用户进行移植和修改的。 2.STM32CubeMX配置 需要使能一个串口和一个定时器,定时器的功能是用于检测3.5个字符的空闲,以判断一帧数据的结束。这里以USART1和TIM4为例进行介绍。 定时器配置: 串口配置: 定时器和串口的参数任意即可,具体在程序中进行配置。打开定时器和串口的中断,且串口中断的优先级要高于定时器中断。 串口和定时器中断程序我们自己编写,所以这里需要取消串口和定时器中断自动生成代码的选项。如下图: 3.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) { __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 ) { USART1->DR = ucByte; return TRUE; } BOOL xMBPortSerialGetByte( CHAR * pucByte ) { *pucByte = (USART1->DR & (uint16_t)0x00FF); return TRUE; } static void prvvUARTTxReadyISR( void ) { pxMBFrameCBTransmitterEmpty( ); } 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); //关闭定时器 } 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]; |
|
|
|
/****************************************************************************
* 名 称: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 */ } 4.总结 FreeModbus实现了Modbus协议的全部功能,移植和使用起来也比较简单。唯一不足的是只有从机协议是开源的,而主机协议是收费的。 |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1606 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1538 浏览 1 评论
969 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
681 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1584 浏览 2 评论
1861浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
642浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
514浏览 3评论
528浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
501浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-20 02:27 , Processed in 0.766396 second(s), Total 77, Slave 61 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号