专栏首页单片机爱好者STM32 移植FreeModbus详细过程

STM32 移植FreeModbus详细过程

modbus是一个非常好的串口协议(当然也能用在网口上),它简洁、规范、强大。可以满足大部分的工业、嵌入式需求。

这里详细说下如何将freemodbus移植到stm32平台。我之前下载的版本是1.5,当前官网最新的版本是1.6。两者差别不大,这里以1.5版本做演示。

1

下载

下载好之后,解压得到如下内容:

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

2

准备一个STM32的工程文件夹

在工程文件夹下新建一个文件夹:FreeModbus。将第一步获取的两个文件夹放到里面。

打开工程,添加两个group,名字分别为modbus和port。将这两个文件夹下的C文件都添加进来,tcp相关的除外。

文件包含路径,也添加这几个文件夹的位置:

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工具测试

工具配置如下:

modbus指令格式如下:

咱们这里设置如下:01 04 00 00 00 02,功能码04,起始地址0,数据长度2.校验码没有写怎么办?

这就是这个工具的便利之处!我们不用管,它会自动计算!直接点击发送即可。得到结果如下:

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

文章分享自微信公众号:
单片机爱好者

本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!

作者:MCU启航
原始发表时间:2020-03-15
如有侵权,请联系 cloudcommunity@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • 干货|10分钟教你玩转freemodbus

    FreeMODBUS是一个奥地利人写的Modbus协议。它是一个针对嵌入式应用的一个免费(自由)的通用MODBUS协议的移植。Modbus是一个工业制造环境中应...

    用户8913398
  • Modbus TCP移植二

    上次推送,给大家移植了lwip协议栈,我们这次移植modbus TCP就是基于这个网络协议栈,lwip协议栈是开源的应用非常广泛的TCP协议栈。特别是在嵌入式上...

    用户1605515
  • 看图秒懂Modbus TCP/IP

    曾经很多次的介绍过Modbus,但主要是介绍Modbus RTU的驱动和开发,Modbus还包括Modbus TCP/IP, 那么大家熟悉的RTU模式的应用数据...

    用户1605515
  • 基于STM32设计的掌上游戏机(运行NES游戏模拟器)详细开发过程

    代码风格: 寄存器风格,没有采用库函数,底层代码全部寄存器方式编写,运行效率高,注释清楚。

    DS小龙哥
  • 移植uIP开源协议栈需注意的几点

    随着物联网的发展,越来越多的设备需要加入联网的功能,Uip是专为嵌入式设计的轻型开源TCP/IP协议栈,很小的代码尺寸,因为嵌入式控制器资源的限制,所以不是全功...

    用户1605515
  • Modbus TCP/IP经典例程演示

    在工业控制中,我们除过用Modbus RTU外还经常会用Modbus TCP/IP,在公众号里,曾给大家介绍过libmodbus,如何编译和安装,今天我们就来演...

    用户1605515
  • 高手入门STM32总结+学习步骤

    每当我们在入门之前(ARM是这样,DSP也一样),总会有很多疑问,会有很多顾虑。我们渴望知道学习STM32前景如何?需要啥基础?难不难?适不适合我?但是什么时候...

    用户8811670
  • 这样学习STM32单片机,从菜鸟到牛人很简单

    本身就是一个错误的问题。假如你会使用8051,会写C语言,那么STM32本身并不需要刻意地学习。

    混说Linux
  • STM32库开发实战指南 PDF+源码

    还是哪个永恒的话题的,学习!(今天回家已经很晚了,本来不打算写东西的,不过一直读野火的书,赶紧很好,这里就分享一下)

    云深无际
  • ESA2GJK1DH1K基础篇: 测试APP使用SmartConfig绑定Wi-Fi 设备并控制设备

      该程序需要的基础知识:  https://www.cnblogs.com/yangfengwu/category/1566194.html   所有源码开源...

    杨奉武
  • STM32f407程序移植到GD32F407

    根据电路设计,外部使用8M,通过PLL到168M因此在sys_gd32f4xx.c文件中选择宏定义:

    ManInRoad
  • 【ST开发板评测】使用Python来开发STM32F411

    板子申请了也有一段时间了,也快到评测截止时间了,想着做点有意思的东西,正好前一段时间看到过可以在MCU上移植MicroPython的示例,就自己尝试一下,记录移...

    单片机点灯小能手
  • ESA2GJK1DH1K基础篇: 测试APP扫描Air202上面的二维码绑定通过MQTT控制设备(兼容SIM800)

    一,该程序需要的基础知识:  https://www.cnblogs.com/yangfengwu/category/1566194.html   所有源码开源...

    杨奉武
  • 用GD32E10x替代STM32F10x程序移植记录

    前言:本文记录一下用GD32E10x替代STM32F10x程序移植过程,两个芯片是pin to pin的,因此无须修改硬件设计,只需修改软件即可。

    ManInRoad
  • RT-Thread Nano上移植GuiLite Samples

    最近手痒痒的,想玩一下显示器,也是为了选型,制作我的小板子。目前这个板子还没画完,还想加多点动能,目前只有显示器,没有其他外设了,感觉太简陋了。不过这篇文章...

    Rice加饭
  • 基于GPRS模块(air202)AT指令TCP透传方式,MQTT通信控制升级(V1.0)

    这节演示下,基于GPRS模块(air202)AT指令TCP透传方式,MQTT通信控制升级STM32程序

    杨奉武
  • 尝尝MicroPython控制单片机

    本文档主要介绍,在python环境下,如何开发嵌入式应用程序,以STM32H43板卡为例介绍. 从系统环境搭建,到编译,到用python实现硬件控制。可作为入门...

    用户1605515

扫码关注腾讯云开发者

领取腾讯云代金券