modbus是一个非常好的串口协议(当然也能用在网口上),它简洁、规范、强大。可以满足大部分的工业、嵌入式需求。
这里详细说下如何将freemodbus移植到stm32平台。我之前下载的版本是1.5,当前官网最新的版本是1.6。两者差别不大,这里以1.5版本做演示。
1、下载
下载好之后,解压得到如下内容:
eb5299e18b9f40f5accb1630fcc158f6~noop.image?_iz=58558&from=article.jpg

我们需要的是modbus这个文件夹,和demo->BARE下的port文件夹。

2、准备一个STM32的工程文件夹
在工程文件夹下新建一个文件夹:FreeModbus。将第一步获取的两个文件夹放到里面。
打开工程,添加两个group,名字分别为modbus和port。将这两个文件夹下的C文件都添加进来,tcp相关的除外。
de01d27d0c194646bde6b2802da1281b~noop.image?_iz=58558&from=article.jpg

文件包含路径,也添加这几个文件夹的位置:
8b185b1e01e241679c977f56cfcf9164~noop.image?_iz=58558&from=article.jpg

3、完善portserial.c文件
该文件就是modbus通信中用到的串口的初始化配置文件。我这里选择usart1,波特率9600.
第一次打开这个文件,内容如下:
void
  • vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
  • {
  •    
  • }
  • BOOL
  • xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
  • {
  •     return FALSE;
  • }
  • BOOL
  • xMBPortSerialPutByte( CHAR ucByte )
  • {
  •    
  •     return TRUE;
  • }
  • 复制代码
    认真看一下函数名字,你会发现这些函数分别是:串口使能、串口初始化、发送一个字节、接收一个字节等等。
    完善后代码如下:
    void
  • vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
  • {
  •    
  •   if(xRxEnable == TRUE)
  •   {
  •     USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
  •   }
  •   else
  •   {
  •     USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);
  •   }
  •   
  •   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 )
  • {
  •    
  •   
  •     USART_SendData(USART1, ucByte);
  •     return TRUE;
  • }
  • BOOL
  • xMBPortSerialGetByte( CHAR * pucByte )
  • {
  •    
  •   *pucByte = USART_ReceiveData(USART1);
  •   return TRUE;
  • }
  • static void prvvUARTTxReadyISR( void )
  • {
  •     pxMBFrameCBTransmitterEmpty(  );
  • }
  • static void prvvUARTRxISR( void )
  • {
  •     pxMBFrameCBByteReceived(  );
  • }
  • 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();是串口初始化的代码,如下:
    void USART1_Config(uint16_t buad)
  • {
  •     GPIO_InitTypeDef GPIO_InitStructure;
  •     USART_InitTypeDef USART_InitStructure;
  •    
  •    
  •     RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
  •    
  •    
  •    
  •     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);   
  •    
  •     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
  •     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  •     GPIO_Init(GPIOA, &GPIO_InitStructure);
  •       
  •    
  •     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_Init(USART1, &USART_InitStructure);
  •     USART_Cmd(USART1, ENABLE);
  • }
  • void USART_NVIC(void)
  • {
  •   NVIC_InitTypeDef NVIC_InitStructure;
  •   
  •   
  •   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);
  • }
  • 复制代码
    4、完善porttimer.c文件
    modbus工作时需要一个定时器,所以这里配置一个定时器。定时器时基是50us,周期做为参数输入。代码如下:
    BOOL
  • xMBPortTimersInit( USHORT usTim1Timerout50us )
  • {
  •   timer2_init(usTim1Timerout50us);
  •   timer2_nvic();
  •   return TRUE;
  • }
  • void vMBPortTimersEnable(  )
  • {
  •    
  •   TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
  •   TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
  •   TIM_SetCounter(TIM2,0x0000);
  •   TIM_Cmd(TIM2, ENABLE);
  • }
  • void vMBPortTimersDisable(  )
  • {
  •    
  •   TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
  •   TIM_ITConfig(TIM2, TIM_IT_Update, DISABLE);
  •   TIM_SetCounter(TIM2,0x0000);
  •   TIM_Cmd(TIM2, DISABLE);
  • }
  • 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初始化函数,内容如下:
    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);  
  • }
  • 复制代码
    5、在main.c文件中,定义各个模拟寄存器的地址和大小。
    #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
  • 复制代码
    6
    补全输入寄存器操作函数、保持寄存器操作函数
    modbus功能进行初始化,设置地址和波特率。这部分内容可以参考官方资料里的例程,也可以直接复制别人写好的。我这里放别人写好的代码:
    int main(void)
  • {
  •     usRegInputBuf[0] = 'I';
  •     usRegInputBuf[1] = ' ';
  •     usRegInputBuf[2] = 'a';
  •     usRegInputBuf[3] = 'm';
  •     usRegInputBuf[4] = ' ';
  •     usRegInputBuf[5] = 'I';
  •   
  •     RCC_Config();
  •     eMBInit(MB_RTU, 0x01, 0x01, 9600, MB_PAR_NONE);
  •     eMBEnable();   
  •     for(;;)
  •     {
  •       (void)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++ = ( UCHAR )( usRegInputBuf[iRegIndex] >> 8 );
  •             *pucRegBuffer++ = ( UCHAR )( usRegInputBuf[iRegIndex] & 0xFF );
  •             iRegIndex++;
  •             usNRegs--;
  •         }
  •     }
  •     else
  •     {
  •         eStatus = MB_ENOREG;
  •     }
  •     return eStatus;
  • }
  • 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:
  •         while(usNRegs > 0)
  •         {
  •           *pucRegBuffer++ = (u8)(usRegHoldingBuf[iRegIndex] >> 8);            
  •           *pucRegBuffer++ = (u8)(usRegHoldingBuf[iRegIndex] & 0xFF);
  •           iRegIndex++;
  •           usNRegs--;         
  •         }                           
  •         break;
  •       case MB_REG_WRITE:
  •         while(usNRegs > 0)
  •         {         
  •           usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
  •           usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
  •           iRegIndex++;
  •           usNRegs--;
  •         }        
  •       }
  •   }
  •   else
  •   {
  •     eStatus = MB_ENOREG;
  •   }  
  •   
  •   return eStatus;
  • }
  • eMBErrorCode
  • eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
  • {
  •   eMBErrorCode    eStatus = MB_ENOERR;
  •   int             iRegIndex;
  •   if((usAddress >= REG_HOLDING_START)&&\
  •     ((usAddress+usNCoils) <= (REG_HOLDING_START + REG_HOLDING_NREGS)))
  •   {
  •     iRegIndex = (int)(usAddress - usRegHoldingStart);
  •     switch(eMode)
  •     {                                       
  •       case MB_REG_READ:
  •         while(usNCoils > 0)
  •         {
  •           iRegIndex++;
  •           usNCoils--;         
  •         }                           
  •         break;
  •       case MB_REG_WRITE:
  •         while(usNCoils > 0)
  •         {         
  •           iRegIndex++;
  •           usNCoils--;
  •         }        
  •       }
  •   }
  •   else
  •   {
  •     eStatus = MB_ENOREG;
  •   }  
  •   
  •   return eStatus;
  • }
  • eMBErrorCode
  • eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
  • {
  •     ( void )pucRegBuffer;
  •     ( void )usAddress;
  •     ( void )usNDiscrete;
  •     return MB_ENOREG;
  • }
  • 复制代码
    7、修改mbrtu.c文件
    否则modbus从机收到命令后,只会返回一次数据。在函数“eMBRTUSend”中。
    eMBErrorCode
  • eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength )
  • {
  •     eMBErrorCode    eStatus = MB_ENOERR;
  •     USHORT          usCRC16;
  •     ENTER_CRITICAL_SECTION(  );
  •    
  •     if( eRcvState == STATE_RX_IDLE )
  •     {
  •         
  •         pucSndBufferCur = ( UCHAR * ) pucFrame - 1;
  •         usSndBufferCount = 1;
  •         
  •         pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress;
  •         usSndBufferCount += usLength;
  •         
  •         usCRC16 = usMBCRC16( ( UCHAR * ) pucSndBufferCur, usSndBufferCount );
  •         ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 & 0xFF );
  •         ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 >> 8 );
  •         
  •         
  •         eSndState = STATE_TX_XMIT;
  •         
  •         
  •         xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur );
  •         pucSndBufferCur++;  
  •         usSndBufferCount--;
  •         
  •         
  •         vMBPortSerialEnable( FALSE, TRUE );
  •     }
  •     else
  •     {
  •         eStatus = MB_EIO;
  •     }
  •     EXIT_CRITICAL_SECTION(  );
  •     return eStatus;
  • }
  • 复制代码
    8、修改mbconfig.h文件
    取消对ASCII的支持。
    #define MB_ASCII_ENABLED                        (  0 )
    复制代码
    9、保存,编译,下载。使用专用的modbus工具测试
    工具配置如下:
    8cea1ad150e54508b4456dfdb692007b~noop.image?_iz=58558&from=article.jpg

    modbus指令格式如下:
    87050dbc5e9f49658e507f69b33dd8b5~noop.image?_iz=58558&from=article.jpg

    咱们这里设置如下:01 04 00 00 00 02,功能码04,起始地址0,数据长度2.校验码没有写怎么办?
    这就是这个工具的便利之处!我们不用管,它会自动计算!直接点击发送即可。得到结果如下:
    3e6374af1fdc481ebdebc5823b5a3d52~noop.image?_iz=58558&from=article.jpg

    可以看到下面的框里,绿色的是我们发送的内容,最后两位是工具自动补上的。蓝色内容是单片机(也就是modbus从机)返回给我们的。