完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
modbus是一个非常好的串口协议(当然也能用在网口上),它简洁、规范、强大。可以满足大部分的工业、嵌入式需求。我写的这个四个寄存器都可以用(输入寄存器、保持寄存器、线圈寄存器、离散寄存器)。不像别的代码,就只能用输入寄存器、保持寄存器。
这里详细说下如何将freemodbus移植到stm32平台。我下载的版本是1.6,我会在我的下载链接里面加上1.6的freemodbus压缩包以及我已经移植好的stm32 for freemodbus版本。我上传的移植好的绝对没问题,经过多个板子测试。这里编译器使用的是keil5.28,单片机用的是STM32F103RCT6。 移植到VET6以及ZET6的话只需要改一下选择的芯片就好,改法:点击魔法棒 - 》Device,然后找到自己用的芯片就好了,因为这三款都是大容量的,所以只需要改这里:小容量的比如stm32f103c8t6啥的请看这个教程改芯片,教程连接,单击即可跳转,我也测试编译了,但是由于手上没单片机,就没实测。 这里1.6的freemodbus压缩包也可以在官网下载,官网下载链接:freeModbus官网从机下载链接 我的压缩包里面也有,在文章的最后面可以下载。 1. 下载好之后,解压得到如下内容: 我们需要的是modbus这个文件夹,和demo-》BARE下的port文件夹。 2. 添加文件到工程 准备一个STM32的工程文件夹,带嵌入式系统ucos、freeRTOS等的工程也是一样的,在工程文件夹下新建一个文件夹:FreeModbus。将第一步获取的两个文件夹放到里面。 打开工程,添加两个Add Group,名字分别为modbus和modbus_port。将这两个文件夹下的C文件都添加进来,tcp相关的除外。 添加文件包含路径,添加这几个文件夹里面头文件的位置: 添加后如下图 添加好了之后编译有两个错误,如下图,先不管他,我们先完善文件 3. 完善portserial.c文件 该文件就是modbus通信中用到的串口的初始化配置文件。我这里选择usart1,波特率9600. 第一次打开这个文件,内容如下: * ----------------------- Start implementation -----------------------------*/ void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable ) { /* If xRXEnable enable serial receive interrupts. If xTxENable enable * transmitter empty interrupts. */ } BOOL xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity ) { return 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. */ 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. */ return TRUE; } 认真看一下函数名字,你会发现这些函数分别是:串口使能、串口初始化、发送一个字节、接收一个字节等等。 完善后代码如下,也可以直接复制整个内容替换文件内容: /* * FreeModbus Libary: BARE Port * Copyright (C) 2006 Christian Walter 《wolti@sil.at》 * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * File: $Id: portserial.c,v 1.1 2006/08/22 21:35:13 wolti Exp $ */ #include “port.h” #include “stm32f10x.h” #include “bsp_usart1.h” /* ----------------------- Modbus includes ----------------------------------*/ #include “mb.h” #include “mbport.h” /* ----------------------- static functions ---------------------------------*/ static void prvvUARTTxReadyISR( void ); static void prvvUARTRxISR( void ); /* ----------------------- Start implementation -----------------------------*/ void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable ) { /* If xRXEnable enable serial receive interrupts. If xTxENable enable * transmitter empty interrupts. */ //STM32串口接收中断使能 if(xRxEnable == TRUE) { //UART中断使能 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); } else { //禁止接收和接收中断 USART_ITConfig(USART1, USART_IT_RXNE, DISABLE); } //STM32串口发送中断使能 if(xTxEnable == TRUE) { //使能发送中断 USART_ITConfig(USART1, USART_IT_TC, ENABLE); } else { //禁止发送中断 USART_ITConfig(USART1, USART_IT_TC, DISABLE); } } BOOL xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity ) { //串口初始化 USART1_Config((uint16_t)ulBaudRate); USART_NVIC(); return TRUE; } 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. */ //串口发送函数 USART_SendData(USART1, 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 = USART_ReceiveData(USART1); 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( ); } /** * @brief This function handles usart1 Handler. * @param None * @retval None */ //串口中断函数 void USART1_IRQHandler(void) { //发生接收中断 if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET) { prvvUARTRxISR(); //串口接收中断调用函数 //清除中断标志位 USART_ClearITPendingBit(USART1, USART_IT_RXNE); } if(USART_GetITStatus(USART1, USART_IT_ORE) == SET) { USART_ClearITPendingBit(USART1, USART_IT_ORE); prvvUARTRxISR(); //串口发送中断调用函数 } //发生完成中断 if(USART_GetITStatus(USART1, USART_IT_TC) == SET) { prvvUARTTxReadyISR(); //清除中断标志 USART_ClearITPendingBit(USART1, USART_IT_TC); } } 其中USART1_Config((uint16_t)ulBaudRate);和 USART_NVIC();是串口初始化的代码,我专门弄了一个.c和.h文件来放置这个文件,这两个文件在我的工程里面的HARDWARE文件夹下,也可以直接去拷贝,具体代码如下: bsp_usart1.c #include “bsp_usart1.h” uint8_t SendBuff[SENDBUFF_SIZE]; /** * @brief USART1 GPIO 配置,工作模式配置。9600 8-N-1 * @param 无 * @retval 无 */ void USART1_Config(uint16_t buad) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; /* config USART1 clock */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); /* USART1 GPIO config */ /* Configure USART1 Tx (PA.09) as alternate function push-pull */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); /* Configure USART1 Rx (PA.10) as input floating */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); /* USART1 mode config */ USART_InitStructure.USART_BaudRate = buad; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No ; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //USART_ITConfig(USART2,USART_IT_RXNE,ENABLE); USART_Init(USART1, &USART_InitStructure); USART_Cmd(USART1, ENABLE); } /** * @brief USART1 中断 配置 * @param 无 * @retval 无 */ void USART_NVIC(void) { NVIC_InitTypeDef NVIC_InitStructure; /* Configure one bit for preemption priority */ //NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0); /* 配置中断源 */ NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } /// 重定向c库函数printf到USART1 int fputc(int ch, FILE *f) { /* 发送一个字节数据到USART1 */ USART_SendData(USART1, (uint8_t) ch); /* 等待发送完毕 */ while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); return (ch); } /// 重定向c库函数scanf到USART1 int fgetc(FILE *f) { /* 等待串口1输入数据 */ while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET); return (int)USART_ReceiveData(USART1); } /*********************************************END OF FILE**********************/ bsp_usart1.h #ifndef __USART1_H #define __USART1_H #include “stm32f10x.h” #include 《stdio.h》 #define USART1_DR_Base 0x40013804 // 0x40013800 + 0x04 = 0x40013804 #define SENDBUFF_SIZE 5000 void USART1_Config(uint16_t buad); void USART1_DMA_Config(void); void USART_NVIC(void); #endif /* __USART1_H */ |
|
|
|
4. 完善porttimer.c文件
modbus工作时需要一个定时器,所以这里配置一个定时器。定时器时基是50us,周期做为参数输入。这里注意我们这里面的inline void vMBPortTimersEnable( )以及inline void vMBPortTimersDisable( )函数需要去掉前面的inline,具体改好的代码如下: /* ----------------------- Platform includes --------------------------------*/ #include “port.h” #include “bsp_timer2.h” /* ----------------------- Modbus includes ----------------------------------*/ #include “mb.h” #include “mbport.h” /* ----------------------- static functions ---------------------------------*/ static void prvvTIMERExpiredISR( void ); /* ----------------------- Start implementation -----------------------------*/ BOOL xMBPortTimersInit( USHORT usTim1Timerout50us ) { timer2_init(usTim1Timerout50us); timer2_nvic(); return TRUE; } void vMBPortTimersEnable( ) { /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */ TIM_ClearITPendingBit(TIM2, TIM_IT_Update); TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); TIM_SetCounter(TIM2,0x0000); TIM_Cmd(TIM2, ENABLE); } void vMBPortTimersDisable( ) { /* Disable any pending timers. */ TIM_ClearITPendingBit(TIM2, TIM_IT_Update); TIM_ITConfig(TIM2, TIM_IT_Update, DISABLE); TIM_SetCounter(TIM2,0x0000); TIM_Cmd(TIM2, DISABLE); } /* 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( ); } void TIM2_IRQHandler(void) { if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { prvvTIMERExpiredISR(); TIM_ClearITPendingBit(TIM2, TIM_IT_Update); } } 其中 timer2_init(usTim1Timerout50us) 和 timer2_nvic() 是timer2初始化函数,我也同样建立了一个.c和一个.h文件保存,这两个文件在我的工程里面的HARDWARE文件夹下,也可以直接去拷贝,内容如下: bsp_timer2.c #include “bsp_timer2.h” void timer2_init(uint16_t period) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_DeInit(TIM2); TIM_TimeBaseStructure.TIM_Period = period; TIM_TimeBaseStructure.TIM_Prescaler = (1800 - 1); TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); TIM_Cmd(TIM2, ENABLE); } void timer2_nvic(void) { NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } bsp_timer2.h #ifndef __TIMER2_H #define __TIMER2_H #include “stm32f10x.h” void timer2_init(uint16_t period); void timer2_nvic(void); #endif /* __TIMER2_H */ 这样,两个文件就补充好了,接下来需要我们将bsp_usart1.h和bsp_timer2.h的文件路径加入我们的工程,如下图: 此时编译会提示我们没有加入官方库函数,我们现在导入 我的提示官方标准库里面的一些关于tim函数没找到,代表我们没加入的stm32f10x_tim.c文件,加入就好了,你们的还可能提示关于usart的文件没找到,加入stm32f10x_usart.c就好,这个我就不写教程了,不会的话建议重新学stm32,加入后如图: 接下来在编译,会发现就只报4个错误了,提示4个文件未定义,如下图: 跟我一样的情况就继续往下,不是一样的就检查检查,或者再来一遍。一样的话建议先保存一个副本,防止下面的操作出问题,然后又得重新开始。有副本的话可以继续从这里开始。 5、在main.c文件中定义各个模拟寄存器的地址和大小。 将下面的宏定义放到我们main.c 声明头文件结束之后 /* ----------------------- Defines ------------------------------------------*/ //输入寄存器起始地址 #define REG_INPUT_START 0x0000 //输入寄存器数量 #define REG_INPUT_NREGS 8 //保持寄存器起始地址 #define REG_HOLDING_START 0x0000 //保持寄存器数量 #define REG_HOLDING_NREGS 8 //线圈起始地址 #define REG_COILS_START 0x0000 //线圈数量 #define REG_COILS_SIZE 16 //开关寄存器起始地址 #define REG_DISCRETE_START 0x0000 //开关寄存器数量 #define REG_DISCRETE_SIZE 16 /* Private variables ---------------------------------------------------------*/ //输入寄存器内容 uint16_t usRegInputBuf[REG_INPUT_NREGS] = {0x1000,0x1001,0x1002,0x1003,0x1004,0x1005,0x1006,0x1007}; //输入寄存器起始地址 uint16_t usRegInputStart = REG_INPUT_START; //保持寄存器内容 uint16_t usRegHoldingBuf[REG_HOLDING_NREGS] = {0x147b,0x3f8e,0x147b,0x400e,0x1eb8,0x4055,0x147b,0x408e}; //保持寄存器起始地址 uint16_t usRegHoldingStart = REG_HOLDING_START; //线圈状态 uint8_t ucRegCoilsBuf[REG_COILS_SIZE / 8] = {0x01,0x02}; //开关输入状态 uint8_t ucRegDiscreteBuf[REG_DISCRETE_SIZE / 8] = {0x01,0x02}; 6.补全输入寄存器操作函数、保持寄存器操作函数、线圈操作函数、离散寄存器函数 在main.c文件里面更改int main()函数对modbus功能进行初始化,设置地址和波特率。这部分内容可以参考官方资料里的例程,也可以直接复制别人写好的。这是我写好的代码: /** * @brief 主函数 * @param 无 * @retval 无 */ int main(void) { //初始化 RTU模式 参数二:不用管,参数3:从机地址为1 参数4:9600 参数5:无效验 eMBInit(MB_RTU, 0x01, 0x01, 9600, MB_PAR_NONE); eMBEnable(); for(;;) { (void)eMBPoll(); } } /*********************************************END OF FILE**********************/ /**************************************************************************** * 名 称: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; if( ( usAddress 》= REG_INPUT_START ) && ( usAddress + usNRegs 《= REG_INPUT_START + REG_INPUT_NREGS ) ) { iRegIndex = ( int )( usAddress - usRegInputStart ); while( usNRegs 》 0 ) { *pucRegBuffer++ = ( UCHAR )( usRegInputBuf[iRegIndex] 》》 8 ); *pucRegBuffer++ = ( UCHAR )( 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; if((usAddress 》= REG_HOLDING_START)&& ((usAddress+usNRegs) 《= (REG_HOLDING_START + REG_HOLDING_NREGS))) { iRegIndex = (int)(usAddress - usRegHoldingStart); switch(eMode) { case MB_REG_READ://读 MB_REG_READ = 0 while(usNRegs 》 0) { *pucRegBuffer++ = (u8)(usRegHoldingBuf[iRegIndex] 》》 8); *pucRegBuffer++ = (u8)(usRegHoldingBuf[iRegIndex] & 0xFF); iRegIndex++; usNRegs--; } break; case MB_REG_WRITE://写 MB_REG_WRITE = 0 while(usNRegs 》 0) { usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ 《《 8; usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++; iRegIndex++; usNRegs--; } } } else//错误 { eStatus = MB_ENOREG; } return eStatus; } extern void xMBUtilSetBits( UCHAR * ucByteBuf, USHORT usBitOffset, UCHAR ucNBits, UCHAR ucValue ); extern UCHAR xMBUtilGetBits( UCHAR * ucByteBuf, USHORT usBitOffset, UCHAR ucNBits ); /**************************************************************************** * 名 称: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; //寄存器个数 int16_t iNCoils = ( int16_t )usNCoils; //寄存器偏移量 int16_t usBitOffset; //检查寄存器是否在指定范围内 if( ( (int16_t)usAddress 》= REG_COILS_START ) && ( usAddress + usNCoils 《= REG_COILS_START + REG_COILS_SIZE ) ) { //计算寄存器偏移量 usBitOffset = ( int16_t )( usAddress - REG_COILS_START ); switch ( eMode ) { //读操作 case MB_REG_READ: while( iNCoils 》 0 ) { *pucRegBuffer++ = xMBUtilGetBits( ucRegCoilsBuf, usBitOffset, ( uint8_t )( iNCoils 》 8 ? 8 : iNCoils ) ); iNCoils -= 8; usBitOffset += 8; } break; //写操作 case MB_REG_WRITE: while( iNCoils 》 0 ) { xMBUtilSetBits( ucRegCoilsBuf, usBitOffset, ( uint8_t )( iNCoils 》 8 ? 8 : iNCoils ), *pucRegBuffer++ ); iNCoils -= 8; } break; } } 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; //操作寄存器个数 int16_t iNDiscrete = ( int16_t )usNDiscrete; //偏移量 uint16_t usBitOffset; //判断寄存器时候再制定范围内 if( ( (int16_t)usAddress 》= REG_DISCRETE_START ) && ( usAddress + usNDiscrete 《= REG_DISCRETE_START + REG_DISCRETE_SIZE ) ) { //获得偏移量 usBitOffset = ( uint16_t )( usAddress - REG_DISCRETE_START ); while( iNDiscrete 》 0 ) { *pucRegBuffer++ = xMBUtilGetBits( ucRegDiscreteBuf, usBitOffset, ( uint8_t)( iNDiscrete 》 8 ? 8 : iNDiscrete ) ); iNDiscrete -= 8; usBitOffset += 8; } } else { eStatus = MB_ENOREG; } return eStatus; } |
|
|
|
同时这里需要在main.c文件里面加入如下两个头文件
#include “mb.h” #include “mbutils.h” 完整的main.c文件内容如下: #include “led.h” #include “delay.h” #include “sys.h” #include “mb.h” #include “mbutils.h” /* ----------------------- Defines ------------------------------------------*/ //输入寄存器起始地址 #define REG_INPUT_START 0x0000 //输入寄存器数量 #define REG_INPUT_NREGS 8 //保持寄存器起始地址 #define REG_HOLDING_START 0x0000 //保持寄存器数量 #define REG_HOLDING_NREGS 8 //线圈起始地址 #define REG_COILS_START 0x0000 //线圈数量 #define REG_COILS_SIZE 16 //开关寄存器起始地址 #define REG_DISCRETE_START 0x0000 //开关寄存器数量 #define REG_DISCRETE_SIZE 16 /* Private variables ---------------------------------------------------------*/ //输入寄存器内容 uint16_t usRegInputBuf[REG_INPUT_NREGS] = {0x1000,0x1001,0x1002,0x1003,0x1004,0x1005,0x1006,0x1007}; //输入寄存器起始地址 uint16_t usRegInputStart = REG_INPUT_START; //保持寄存器内容 uint16_t usRegHoldingBuf[REG_HOLDING_NREGS] = {0x147b,0x3f8e,0x147b,0x400e,0x1eb8,0x4055,0x147b,0x408e}; //保持寄存器起始地址 uint16_t usRegHoldingStart = REG_HOLDING_START; //线圈状态 uint8_t ucRegCoilsBuf[REG_COILS_SIZE / 8] = {0x01,0x02}; //开关输入状态 uint8_t ucRegDiscreteBuf[REG_DISCRETE_SIZE / 8] = {0x01,0x02}; /** * @brief 主函数 * @param 无 * @retval 无 */ int main(void) { //初始化 RTU模式 参数二:不用管,参数3:从机地址为1 参数4:9600 参数5:无效验 eMBInit(MB_RTU, 0x01, 0x01, 9600, MB_PAR_NONE); eMBEnable(); for(;;) { (void)eMBPoll(); } } /*********************************************END OF FILE**********************/ /**************************************************************************** * 名 称: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; if( ( usAddress 》= REG_INPUT_START ) && ( usAddress + usNRegs 《= REG_INPUT_START + REG_INPUT_NREGS ) ) { iRegIndex = ( int )( usAddress - usRegInputStart ); while( usNRegs 》 0 ) { *pucRegBuffer++ = ( UCHAR )( usRegInputBuf[iRegIndex] 》》 8 ); *pucRegBuffer++ = ( UCHAR )( 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; if((usAddress 》= REG_HOLDING_START)&& ((usAddress+usNRegs) 《= (REG_HOLDING_START + REG_HOLDING_NREGS))) { iRegIndex = (int)(usAddress - usRegHoldingStart); switch(eMode) { case MB_REG_READ://读 MB_REG_READ = 0 while(usNRegs 》 0) { *pucRegBuffer++ = (u8)(usRegHoldingBuf[iRegIndex] 》》 8); *pucRegBuffer++ = (u8)(usRegHoldingBuf[iRegIndex] & 0xFF); iRegIndex++; usNRegs--; } break; case MB_REG_WRITE://写 MB_REG_WRITE = 0 while(usNRegs 》 0) { usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ 《《 8; usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++; iRegIndex++; usNRegs--; } } } else//错误 { eStatus = MB_ENOREG; } return eStatus; } extern void xMBUtilSetBits( UCHAR * ucByteBuf, USHORT usBitOffset, UCHAR ucNBits, UCHAR ucValue ); extern UCHAR xMBUtilGetBits( UCHAR * ucByteBuf, USHORT usBitOffset, UCHAR ucNBits ); /**************************************************************************** * 名 称: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; //寄存器个数 int16_t iNCoils = ( int16_t )usNCoils; //寄存器偏移量 int16_t usBitOffset; //检查寄存器是否在指定范围内 if( ( (int16_t)usAddress 》= REG_COILS_START ) && ( usAddress + usNCoils 《= REG_COILS_START + REG_COILS_SIZE ) ) { //计算寄存器偏移量 usBitOffset = ( int16_t )( usAddress - REG_COILS_START ); switch ( eMode ) { //读操作 case MB_REG_READ: while( iNCoils 》 0 ) { *pucRegBuffer++ = xMBUtilGetBits( ucRegCoilsBuf, usBitOffset, ( uint8_t )( iNCoils 》 8 ? 8 : iNCoils ) ); iNCoils -= 8; usBitOffset += 8; } break; //写操作 case MB_REG_WRITE: while( iNCoils 》 0 ) { xMBUtilSetBits( ucRegCoilsBuf, usBitOffset, ( uint8_t )( iNCoils 》 8 ? 8 : iNCoils ), *pucRegBuffer++ ); iNCoils -= 8; } break; } } 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; //操作寄存器个数 int16_t iNDiscrete = ( int16_t )usNDiscrete; //偏移量 uint16_t usBitOffset; //判断寄存器时候再制定范围内 if( ( (int16_t)usAddress 》= REG_DISCRETE_START ) && ( usAddress + usNDiscrete 《= REG_DISCRETE_START + REG_DISCRETE_SIZE ) ) { //获得偏移量 usBitOffset = ( uint16_t )( usAddress - REG_DISCRETE_START ); while( iNDiscrete 》 0 ) { *pucRegBuffer++ = xMBUtilGetBits( ucRegDiscreteBuf, usBitOffset, ( uint8_t)( iNDiscrete 》 8 ? 8 : iNDiscrete ) ); iNDiscrete -= 8; usBitOffset += 8; } } else { eStatus = MB_ENOREG; } return eStatus; } 如果你没有这些头文件: #include “led.h” #include “delay.h” #include “sys.h” 就将其换成 #include “stm32f10x.h” 7. 修改mbrtu.c文件 否则modbus从机收到命令后,只会返回一次数据。在函数“eMBRTUSend”中,函数在mbrtu.c文件里面。修改后的代码如下: eMBErrorCode eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength ) { eMBErrorCode eStatus = MB_ENOERR; USHORT usCRC16; ENTER_CRITICAL_SECTION( ); /* Check if the receiver is still in idle state. If not we where to * slow with processing the received frame and the master sent another * frame on the network. We have to abort sending the frame. */ if( eRcvState == STATE_RX_IDLE ) { /* First byte before the Modbus-PDU is the slave address. */ pucSndBufferCur = ( UCHAR * ) pucFrame - 1; usSndBufferCount = 1; /* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */ pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress; usSndBufferCount += usLength; /* Calculate CRC16 checksum for Modbus-Serial-Line-PDU. */ usCRC16 = usMBCRC16( ( UCHAR * ) pucSndBufferCur, usSndBufferCount ); ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 & 0xFF ); ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 》》 8 ); /* Activate the transmitter. */ eSndState = STATE_TX_XMIT; //启动第一次发送,这样才可以进入发送完成中断 xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur ); pucSndBufferCur++; /* next byte in sendbuffer. */ usSndBufferCount--; //修改了从这里往下 //使能发送状态,禁止接收状态 vMBPortSerialEnable( FALSE, TRUE ); } else { eStatus = MB_EIO; } EXIT_CRITICAL_SECTION( ); return eStatus; } 8. 修改mbconfig.h文件 取消对ASCII的支持。 #define MB_ASCII_ENABLED ( 0 ) #define MB_ASCII_TIMEOUT_SEC ( 0 ) 这里再编译就会发现没有错误了,有4个警告,四个警告都是说与0比较无意义,因为我们四个寄存器的起始地址都为0x0000,所以这里不用管它,等哪天公司要求改地址啥的方好直接改宏定义就好了。 跟我一样的情况就继续往下,不是一样的就检查检查,或者再来一遍。一样的话建议先保存一个副本,防止下面的操作出问题,然后又得重新开始。有副本的话可以继续从这里开始。 |
|
|
|
9. 修改一些细节,让我们读取写入的时候地址不会自动加一,使读写的地址准确:
需要修改四个文件,分别为mbfunccoils.c、mbfuncdisc.c、mbfuncholding.c、mbfuncinput.c 直接去对应的文件搜索:usRegAddress++; 然后屏蔽掉。 mbfunccoils.c里面有三处 mbfuncdisc.c里面有一处 mbfuncholding.c里面有三处 mbfuncinput.c里面有一处 修改后就可以使读写地址不会自动加1了,如果疑问心比较强的同学也可以不屏蔽测测。 10. modbus通信格式以及寄存器功能码。 10.1 通信格式: 这个长度是根据我的代码来的。 10.2 功能码详解 modbus完整支持很多功能码,但是实际在应用的时候常用的也就那么几个。具体如下: 0x01: 读线圈寄存器 0x02: 读离散输入寄存器 0x03: 读保持寄存器 0x04: 读输入寄存器 0x05: 写单个线圈寄存器 0x06: 写单个保持寄存器 0x0f: 写多个线圈寄存器 0x10: 写多个保持寄存器 如上所示一共8种功能码。这其中有涉及到线圈、离散输入、保持、输入四种寄存器。这名字也不知道谁起的,让人看了一点不通俗易懂,搞得晕晕乎乎。实际上你要是看清他的本质就很简单了。下面分别解释一下: 线圈寄存器,实际上就可以类比为开关量,没一个bit都对应一个信号的开关状态。所以一个byte就可以同时控制8路的信号。比如控制外部8路io的高低。 线圈寄存器支持读也支持写,写在功能码里面又分为写单个线圈寄存器和写多个线圈寄存器。对应上面的功能码也就是:0x01 0x05 0x0f 离散输入寄存器,如果线圈寄存器理解了这个自然也明白了。离散输入寄存器就相当于线圈寄存器的只读模式,他也是每个bit表示一个开关量,而他的开关量只能读取输入的开关信号,是不能够写的。比如我读取外部按键的按下还是松开。所以功能码也简单就一个读的 0x02 保持寄存器,这个寄存器的单位不再是bit而是两个byte,也就是可以存放具体的数据量的,并且是可读写的。比如我我设置时间年月日,不但可以写也可以读出来现在的时间。写也分为单个写和多个写,所以功能码有对应的三个:0x03 0x06 0x10 输入寄存器,只剩下这最后一个了,这个和保持寄存器类似,但是也是只支持读而不能写。一个寄存器也是占据两个byte的空间。类比我我通过读取输入寄存器获取现在的AD采集值。对应的功能码也就一个 0x04 11. 保存,编译,下载。使用专用的modbus工具测试 工具在我上传的资源文件里面,自行下载。 工具配置如下: modbus指令格式如下: 咱们这里设置如下:01 04 00 00 00 01,功能码04,起始地址0,数据长度1.校验码没有写怎么办? 这就是这个工具的便利之处!我们不用管,它会自动计算!直接点击发送即可。得到结果如下: 可以看到下面的框里,绿色的是我们发送的内容,最后两位是工具自动补上的。蓝色内容是单片机(也就是modbus从机)返回给我们的。读取的内容是输入寄存器04里面的00 00地址的数据,数据内容为10 00 12. 广播地址回复设置 这里我们发送广播地址是没有回复的,想要有回复的话在mb.c文件里面的函数eMBPoll( void )里面屏蔽掉一个if条件,下面是我屏蔽好的。这个根据自己的需求来 eMBErrorCode eMBPoll( void ) { static UCHAR *ucMBFrame; static UCHAR ucRcvAddress; static UCHAR ucFunctionCode; static USHORT usLength; static eMBException eException; int i; eMBErrorCode eStatus = MB_ENOERR; eMBEventType eEvent; /* Check if the protocol stack is ready. */ if( eMBState != STATE_ENABLED ) { return MB_EILLSTATE; } /* Check if there is a event available. If not return control to caller. * Otherwise we will handle the event. */ if( xMBPortEventGet( &eEvent ) == TRUE ) { switch ( eEvent ) { case EV_READY: break; case EV_FRAME_RECEIVED: eStatus = peMBFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength ); if( eStatus == MB_ENOERR ) { /* Check if the frame is for us. If not ignore the frame. */ if( ( ucRcvAddress == ucMBAddress ) || ( ucRcvAddress == MB_ADDRESS_BROADCAST ) ) { ( void )xMBPortEventPost( EV_EXECUTE ); } } break; case EV_EXECUTE: ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF]; eException = MB_EX_ILLEGAL_FUNCTION; for( i = 0; i 《 MB_FUNC_HANDLERS_MAX; i++ ) { /* No more function handlers registered. Abort. */ if( xFuncHandlers[i].ucFunctionCode == 0 ) { break; } else if( xFuncHandlers[i].ucFunctionCode == ucFunctionCode ) { eException = xFuncHandlers[i].pxHandler( ucMBFrame, &usLength ); break; } } /*这里的代码含义,如果请求没有发送到广播地址我们返回一个回复。*/ /*我屏蔽了,发送广播地址也要求回复*/ // if( ucRcvAddress != MB_ADDRESS_BROADCAST ) // { if( eException != MB_EX_NONE ) { /* An exception occured. Build an error frame. */ usLength = 0; ucMBFrame[usLength++] = ( UCHAR )( ucFunctionCode | MB_FUNC_ERROR ); ucMBFrame[usLength++] = eException; } if( ( eMBCurrentMode == MB_ASCII ) && MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS ) { vMBPortTimersDelay( MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS ); } eStatus = peMBFrameSendCur( ucMBAddress, ucMBFrame, usLength ); // } break; case EV_FRAME_SENT: break; } } return MB_ENOERR; } |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1975 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1760 浏览 1 评论
1232 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
819 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1776 浏览 2 评论
2015浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
889浏览 4评论
stm32f4下spi+dma读取数据不对是什么原因导致的?
318浏览 3评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
670浏览 3评论
661浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2025-2-23 15:21 , Processed in 0.911411 second(s), Total 83, Slave 67 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191