野火电子论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 3424|回复: 5

瑞萨RA6M5移植FreeModbus库实现Modbus从机

[复制链接]
发表于 2022-11-13 21:09:42 | 显示全部楼层 |阅读模式
一、简介

Modbus是一种串行通信协议,是Modicon公司(现在的施耐德电气 Schneider Electric)于1979年为使用可编程逻辑控制器(PLC)通信而发表。
Modbus已经成为工业领域通信协议的业界标准(De facto),并且现在是工业电子设备之间常用的连接方式。

Modbus通信协议是一种主从的协议,在一个Modbus网络里面只能有一个主机,但是可以有多个从机,从机与从机之间通过地址进行区分,每个从机都具有唯一的设备地址。
每次通讯必须由主机进行发起,从机进行响应。

本次基于开发板的485硬件接口移植Modbus开源协议栈FreeModbus实现Modbus从机。
(FreeModbus目前只能支持从机)


二、FSP驱动配置

FreeModbus库需要用到硬件资源包括:
①485接口(开发板已经板载有TTL转485芯片,对于MCU来说就是配置UART)
②timer定时器

(1)、UART配置
首先打开原理图,确认板载的485接口接在开发板的哪个串口上,引脚号是多少

野火论坛202211131800316379..png

从原理图可以看到,第一路485接在uart5上,端口号为:P501,P502,P503

因此FSP配置UART5如下:

野火论坛202211131808394317..png

野火论坛202211131811512329..png

野火论坛202211131813287434..png


(2)、配置timer

为了能够与FreeModbus对接,驱动需要配置时间基准为50us的定时器,并开启溢出中断。

根据手册,RA6M5一共有10个通用定时器,其中0~3为32位定时器,其余为16位定时器,如下:

野火论坛202211131828428048..png

我这里就选择timer3来进行配置:

野火论坛202211131832506499..png

另外,可以根据需要再配置一路uart作为调试串口使用,我配置了uart4作为调试串口,配置方法与uart5基本类似。

现在,需要配置的硬件都已经配置好了(别忘了点击按钮生成代码哦),可以正式开始移植了。

三、移植FreeModbus

(1)、下载FreeModbus源码

源码下载地址:https://www.embedded-experts.at/en/freemodbus-downloads/
野火论坛202211131848497603..png


(2)、将源码加入工程

源码解压后,得到如下文件和文件夹:

野火论坛202211131923389823..png

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

将这个两个文件夹添加到工程中:

野火论坛202211131938475655..png

添加头文件路径“

野火论坛202211131940102534..png

(3)、修改portserial.c文件

portserial.c文件主要是对UART驱动的对接,需要对接的函数主要有4个。

① void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
用于对串口发送和接收进行使能或者失能

② BOOL xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
用于对485进行初始化

③BOOLxMBPortSerialPutByte( CHAR ucByte )
用于485发送一个字节

④BOOLxMBPortSerialGetByte( CHAR * pucByte )
用于485读取一个字节

另外,我将uart的回调函数也是放在这个文件中。
对接后完整portserial.c文件如下:

  1. /*
  2. * FreeModbus Libary: BARE Port
  3. * Copyright (C) 2006 Christian Walter <wolti@sil.at>
  4. *
  5. * This library is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU Lesser General Public
  7. * License as published by the Free Software Foundation; either
  8. * version 2.1 of the License, or (at your option) any later version.
  9. *
  10. * This library is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  13. * Lesser General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Lesser General Public
  16. * License along with this library; if not, write to the Free Software
  17. * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  18. *
  19. * File: $Id$
  20. */

  21. #include "port.h"
  22. #include "uart.h"
  23. #include "hal_data.h"

  24. /* ----------------------- Modbus includes ----------------------------------*/
  25. #include "mb.h"
  26. #include "mbport.h"

  27. /* ----------------------- static functions ---------------------------------*/
  28. static void prvvUARTTxReadyISR( void );
  29. static void prvvUARTRxISR( void );

  30. /* ----------------------- Start implementation -----------------------------*/
  31. void
  32. vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
  33. {
  34.     /* If xRXEnable enable serial receive interrupts. If xTxENable enable
  35.      * transmitter empty interrupts.
  36.      */
  37.     if (xRxEnable == TRUE)
  38.     {
  39.         uart5_rx_state = UART5_RX_ENABLE;
  40.     }
  41.     else
  42.     {
  43.         uart5_rx_state = UART5_RX_DISABLE;
  44.     }

  45.     if (xTxEnable == TRUE)
  46.     {
  47.         uart5_tx_state = UART5_TX_ENABLE;
  48.     }
  49.     else
  50.     {
  51.         uart5_tx_state = UART5_TX_DISABLE;
  52.     }   
  53. }

  54. BOOL
  55. xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
  56. {
  57.     uart5_init();
  58.     return TRUE;
  59. }

  60. BOOL
  61. xMBPortSerialPutByte( CHAR ucByte )
  62. {
  63.     /* Put a byte in the UARTs transmit buffer. This function is called
  64.      * by the protocol stack if pxMBFrameCBTransmitterEmpty( ) has been
  65.      * called. */
  66.     R_SCI_UART_Write(&g_uart5_ctrl, &ucByte, 1);
  67.     return TRUE;
  68. }

  69. BOOL
  70. xMBPortSerialGetByte( CHAR * pucByte )
  71. {
  72.     /* Return the byte in the UARTs receive buffer. This function is called
  73.      * by the protocol stack after pxMBFrameCBByteReceived( ) has been called.
  74.      */
  75.     //R_SCI_UART_Read (&g_uart5_ctrl, pucByte, 1);
  76.     *pucByte = uart5_recv_data;
  77.     return TRUE;
  78. }

  79. /* Create an interrupt handler for the transmit buffer empty interrupt
  80. * (or an equivalent) for your target processor. This function should then
  81. * call pxMBFrameCBTransmitterEmpty( ) which tells the protocol stack that
  82. * a new character can be sent. The protocol stack will then call
  83. * xMBPortSerialPutByte( ) to send the character.
  84. */
  85. static void prvvUARTTxReadyISR( void )
  86. {
  87.     pxMBFrameCBTransmitterEmpty(  );
  88. }

  89. /* Create an interrupt handler for the receive interrupt for your target
  90. * processor. This function should then call pxMBFrameCBByteReceived( ). The
  91. * protocol stack will then call xMBPortSerialGetByte( ) to retrieve the
  92. * character.
  93. */
  94. static void prvvUARTRxISR( void )
  95. {
  96.     pxMBFrameCBByteReceived(  );
  97. }

  98. /* uart4中断回调函数 */
  99. void uart5_callback (uart_callback_args_t * p_args)
  100. {
  101.     /* Handle the UART event */
  102.     switch (p_args->event)
  103.     {
  104.         /* Received a character */
  105.         case UART_EVENT_RX_CHAR:
  106.         {
  107.             if (uart5_rx_state == UART5_RX_ENABLE)
  108.             {
  109.                 uart5_recv_data = p_args->data;
  110.                 //R_SCI_UART_Write(&g_uart4_ctrl, &uart5_recv_data, 1);
  111.                 prvvUARTRxISR();               
  112.             }

  113.             break;
  114.         }
  115.         /* Receive complete */
  116.         case UART_EVENT_RX_COMPLETE:
  117.         {
  118.             break;
  119.         }
  120.         /* Transmit complete */
  121.         case UART_EVENT_TX_COMPLETE:
  122.         {
  123.             //uart4_tx_complete = true;
  124.             if (uart5_tx_state == UART5_TX_ENABLE)
  125.             {
  126.                 prvvUARTTxReadyISR();
  127.             }
  128.             break;
  129.         }
  130.         default:
  131.         {
  132.         }
  133.     }
  134. }
复制代码
(4)、修改porttimer.c文件

该文件需要对接3个函数。

① BOOL xMBPortTimersInit( USHORT usTim1Timerout50us )
用于对timer进行初始化

② void vMBPortTimersEnable(  )
用于使能timer,让timer开始计数

③ void vMBPortTimersDisable(  )
用于失能timer,让timer停止计数

另外,timer中断的回调函数也是放在这个文件中。
对接后完整porttimer.c文件如下:

  1. /*
  2. * FreeModbus Libary: BARE Port
  3. * Copyright (C) 2006 Christian Walter <wolti@sil.at>
  4. *
  5. * This library is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU Lesser General Public
  7. * License as published by the Free Software Foundation; either
  8. * version 2.1 of the License, or (at your option) any later version.
  9. *
  10. * This library is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  13. * Lesser General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Lesser General Public
  16. * License along with this library; if not, write to the Free Software
  17. * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  18. *
  19. * File: $Id$
  20. */

  21. /* ----------------------- Platform includes --------------------------------*/
  22. #include "port.h"

  23. /* ----------------------- Modbus includes ----------------------------------*/
  24. #include "mb.h"
  25. #include "mbport.h"
  26. #include "hal_data.h"
  27. #include <stdio.h>
  28. /* ----------------------- static functions ---------------------------------*/
  29. static void prvvTIMERExpiredISR( void );

  30. /* ----------------------- Start implementation -----------------------------*/
  31. BOOL
  32. xMBPortTimersInit( USHORT usTim1Timerout50us )
  33. {
  34.     /* Initializes the module. */
  35.     fsp_err_t err = R_GPT_Open(&g_timer3_ctrl, &g_timer3_cfg);
  36.     if(err != FSP_SUCCESS)
  37.     {
  38.         printf("gpt3 open error\r\n");
  39.     }  

  40.     /* 设置timer的中断周期
  41.     ** 100表示timer的计数时钟是100MHz
  42.      */
  43.     err = R_GPT_PeriodSet(&g_timer3_ctrl, (uint32_t)usTim1Timerout50us * 50U * 100U);
  44.     if(err != FSP_SUCCESS)
  45.     {
  46.         printf("gpt3 periodset error\r\n");
  47.     }  
  48.     return TRUE;
  49. }


  50. void
  51. vMBPortTimersEnable(  )
  52. {
  53.     /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */

  54.     fsp_err_t err = R_GPT_Reset (&g_timer3_ctrl);
  55.     if(err != FSP_SUCCESS)
  56.     {
  57.         printf("gpt3 reset error\r\n");
  58.     }   

  59.     /* Start the timer. */
  60.     err = R_GPT_Start(&g_timer3_ctrl);
  61.     if(err != FSP_SUCCESS)
  62.     {
  63.         printf("gpt3 start error\r\n");
  64.     }      
  65. }

  66. void
  67. vMBPortTimersDisable(  )
  68. {
  69.     /* Disable any pending timers. */
  70.     fsp_err_t err = R_GPT_Stop (&g_timer3_ctrl);
  71.     if(err != FSP_SUCCESS)
  72.     {
  73.         printf("gpt3 stop error\r\n");
  74.     }      
  75. }

  76. /* Create an ISR which is called whenever the timer has expired. This function
  77. * must then call pxMBPortCBTimerExpired( ) to notify the protocol stack that
  78. * the timer has expired.
  79. */
  80. static void prvvTIMERExpiredISR( void )
  81. {
  82.     ( void )pxMBPortCBTimerExpired(  );
  83. }


  84. /* 定时器中断回调函数. */
  85. void g_timer3_callback (timer_callback_args_t * p_args)
  86. {
  87.     if (TIMER_EVENT_CYCLE_END == p_args->event)
  88.     {
  89.         uint8_t led_status;   
  90.         R_IOPORT_PinRead (&g_ioport_ctrl, BSP_IO_PORT_04_PIN_00, (bsp_io_level_t *)&led_status);
  91.         if(led_status)
  92.         {
  93.             R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_04_PIN_00, BSP_IO_LEVEL_LOW);
  94.         }
  95.         else
  96.         {
  97.             R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_04_PIN_00, BSP_IO_LEVEL_HIGH);
  98.         }
  99.         /* Add application code to be called periodically here. */
  100.         prvvTIMERExpiredISR();
  101.     }
  102. }
复制代码
(5)、定义Modbus寄存器并实现读写函数

我将寄存器定义和读写函数的实现放在了自己定义的modbus_read_write.c文件中,完整代码如下:
注意:这个几个函数必须实现,否则会报错


  1. /* ----------------------- Platform includes --------------------------------*/
  2. #include "port.h"

  3. /* ----------------------- Modbus includes ----------------------------------*/
  4. #include "mb.h"
  5. #include "mbport.h"

  6. /* ----------------------- Defines ------------------------------------------*/
  7. #define REG_INPUT_START   1000
  8. #define REG_INPUT_NREGS   4
  9. #define REG_HOLDING_START 1000
  10. #define REG_HOLDING_NREGS 130

  11. /* ----------------------- Static variables ---------------------------------*/
  12. static USHORT   usRegInputStart = REG_INPUT_START;
  13. static USHORT   usRegInputBuf[REG_INPUT_NREGS] = {1,2,3,4};
  14. static USHORT   usRegHoldingStart = REG_HOLDING_START;
  15. static USHORT   usRegHoldingBuf[REG_HOLDING_NREGS] = {10,11,12,13};

  16. /* ----------------------- Start implementation -----------------------------*/

  17. void free_modbus_init(eMBMode eMode, UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity)
  18. {
  19.     eMBInit( eMode, ucSlaveAddress, ucPort, ulBaudRate, eParity );
  20.     eMBEnable();
  21. }

  22. eMBErrorCode
  23. eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
  24. {
  25.     eMBErrorCode    eStatus = MB_ENOERR;
  26.     int             iRegIndex;

  27.     if( ( usAddress >= REG_INPUT_START )
  28.         && ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) )
  29.     {
  30.         iRegIndex = ( int )( usAddress - usRegInputStart );
  31.         while( usNRegs > 0 )
  32.         {
  33.             *pucRegBuffer++ = ( unsigned char )( usRegInputBuf[iRegIndex] >> 8 );
  34.             *pucRegBuffer++ = ( unsigned char )( usRegInputBuf[iRegIndex] & 0xFF );
  35.             iRegIndex++;
  36.             usNRegs--;
  37.         }
  38.     }
  39.     else
  40.     {
  41.         eStatus = MB_ENOREG;
  42.     }

  43.     return eStatus;
  44. }

  45. eMBErrorCode
  46. eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
  47. {
  48.     eMBErrorCode    eStatus = MB_ENOERR;
  49.     int             iRegIndex;

  50.     if( ( usAddress >= REG_HOLDING_START ) &&
  51.         ( usAddress + usNRegs <= REG_HOLDING_START + REG_HOLDING_NREGS ) )
  52.     {
  53.         iRegIndex = ( int )( usAddress - usRegHoldingStart );
  54.         switch ( eMode )
  55.         {
  56.             /* Pass current register values to the protocol stack. */
  57.         case MB_REG_READ:
  58.             while( usNRegs > 0 )
  59.             {
  60.                 *pucRegBuffer++ = ( unsigned char )( usRegHoldingBuf[iRegIndex] >> 8 );
  61.                 *pucRegBuffer++ = ( unsigned char )( usRegHoldingBuf[iRegIndex] & 0xFF );
  62.                 iRegIndex++;
  63.                 usNRegs--;
  64.             }
  65.             break;

  66.             /* Update current register values with new values from the
  67.              * protocol stack. */
  68.         case MB_REG_WRITE:
  69.             while( usNRegs > 0 )
  70.             {
  71.                 usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
  72.                 usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
  73.                 iRegIndex++;
  74.                 usNRegs--;
  75.             }
  76.         }
  77.     }
  78.     else
  79.     {
  80.         eStatus = MB_ENOREG;
  81.     }
  82.     return eStatus;
  83. }

  84. eMBErrorCode
  85. eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
  86. {
  87.     return MB_ENOREG;
  88. }

  89. eMBErrorCode
  90. eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
  91. {
  92.     return MB_ENOREG;
  93. }
复制代码

(6)、修改hal_entry.c文件

在hal_entry()函数中加入Modbus初始化相关内容和状态轮询函数,完整内容如下:

  1. void hal_entry(void)
  2. {
  3.     /* TODO: add your own code here */
  4.     /* 初始化GPIO端口 */
  5.     R_IOPORT_Open(&g_ioport_ctrl, &g_bsp_pin_cfg);
  6.     /* 初始化uart4,作为调试串口使S用 */
  7.     uart4_init();
  8.     //uart5_init();//初始化uart5
  9.     printf("Renesas RA6M5 Start...\r\n");
  10.     /* 初始化Modbus端口和状态 */
  11.     free_modbus_init(MB_RTU, 1, 5, 9600, MB_PAR_NONE);
  12.     while(1)
  13.     {
  14.         /* 状态机轮询 */
  15.         ( void )eMBPoll();               
  16.     }
  17. #if BSP_TZ_SECURE_BUILD
  18.     /* Enter non-secure code */
  19.     R_BSP_NonSecureEnter();
  20. #endif
  21. }
复制代码

(7)、修改mbrtu.c文件

修改mbrtu.c中eMBRTUSend函数,如果不修改无法触发485发送。

野火论坛202211132056355782..png

修改后MBRTUSend函数完整内容如下:

  1. eMBErrorCode
  2. eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength )
  3. {
  4.     eMBErrorCode    eStatus = MB_ENOERR;
  5.     USHORT          usCRC16;

  6.     ENTER_CRITICAL_SECTION(  );

  7.     /* Check if the receiver is still in idle state. If not we where to
  8.      * slow with processing the received frame and the master sent another
  9.      * frame on the network. We have to abort sending the frame.
  10.      */
  11.     if( eRcvState == STATE_RX_IDLE )
  12.     {
  13.         /* First byte before the Modbus-PDU is the slave address. */
  14.         pucSndBufferCur = ( UCHAR * ) pucFrame - 1;
  15.         usSndBufferCount = 1;

  16.         /* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */
  17.         pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress;
  18.         usSndBufferCount += usLength;

  19.         /* Calculate CRC16 checksum for Modbus-Serial-Line-PDU. */
  20.         usCRC16 = usMBCRC16( ( UCHAR * ) pucSndBufferCur, usSndBufferCount );
  21.         ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 & 0xFF );
  22.         ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 >> 8 );

  23.         /* Activate the transmitter. */
  24.         eSndState = STATE_TX_XMIT;

  25.         //启动第一次发送,后面才能进完成中断
  26.         xMBPortSerialPutByte((CHAR)*pucSndBufferCur);
  27.         pucSndBufferCur++;
  28.         usSndBufferCount--;

  29.         vMBPortSerialEnable( FALSE, TRUE );
  30.     }
  31.     else
  32.     {
  33.         eStatus = MB_EIO;
  34.     }
  35.     EXIT_CRITICAL_SECTION(  );
  36.     return eStatus;
  37. }
复制代码

(8)、修改mbconfig.h文件

取消对ASCII的支持

野火论坛202211132059533254..png

(9)、去掉寄存器地址自动加一

需要修改四个文件,分别为mbfunccoils.c、mbfuncdisc.c、mbfuncholding.c、mbfuncinput.c

直接去对应的文件搜索:usRegAddress++;
然后屏蔽掉。
mbfunccoils.c里面有三处
mbfuncdisc.c里面有一处
mbfuncholding.c里面有三处
mbfuncinput.c里面有一处

如果不修改,绝对不能正常工作。

四、测试

编译下载运行,并使用PC端工具Modbus Poll模拟Modbus主机来进行测试。
如下:

2022-11-12_221533.jpg

经过测试,实测开发板能正确与Modbus Poll相互通讯。
至此,FreeModbus开源库移植成功/


回复

使用道具 举报

发表于 2022-11-18 09:44:26 | 显示全部楼层
谢谢分享,学习一个
回复 支持 反对

使用道具 举报

发表于 2022-12-3 20:55:44 | 显示全部楼层
好帖, 多谢分享!
回复 支持 反对

使用道具 举报

发表于 2022-12-6 09:09:02 | 显示全部楼层
好帖, 多谢分享!
回复 支持 反对

使用道具 举报

发表于 2023-4-19 11:51:13 | 显示全部楼层
楼主能否把uart 相关的几个文件也发出来学习下,调试一直不成功~~
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

联系站长|手机版|野火电子官网|野火淘宝店铺|野火电子论坛 ( 粤ICP备14069197号 ) 大学生ARM嵌入式2群

GMT+8, 2024-4-18 11:45 , Processed in 0.054361 second(s), 28 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表