野火电子论坛
标题:
瑞萨RA6M5移植FreeModbus库实现Modbus从机
[打印本页]
作者:
xinmeng_wit
时间:
2022-11-13 21:09
标题:
瑞萨RA6M5移植FreeModbus库实现Modbus从机
一、简介
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接口接在开发板的哪个串口上,引脚号是多少
(, 下载次数: 3)
上传
点击文件名下载附件
从原理图可以看到,第一路485接在uart5上,端口号为:P501,P502,P503
因此FSP配置UART5如下:
(, 下载次数: 0)
上传
点击文件名下载附件
(, 下载次数: 1)
上传
点击文件名下载附件
(, 下载次数: 1)
上传
点击文件名下载附件
(2)、配置timer
为了能够与FreeModbus对接,驱动需要配置时间基准为50us的定时器,并开启溢出中断。
根据手册,RA6M5一共有10个通用定时器,其中0~3为32位定时器,其余为16位定时器,如下:
(, 下载次数: 0)
上传
点击文件名下载附件
我这里就选择timer3来进行配置:
(, 下载次数: 1)
上传
点击文件名下载附件
另外,可以根据需要再配置一路uart作为调试串口使用,我配置了uart4作为调试串口,配置方法与uart5基本类似。
现在,需要配置的硬件都已经配置好了(别忘了点击按钮生成代码哦),可以正式开始移植了。
三、移植FreeModbus
(1)、下载FreeModbus源码
源码下载地址:
https://www.embedded-experts.at/en/freemodbus-downloads/
(, 下载次数: 1)
上传
点击文件名下载附件
(2)、将源码加入工程
源码解压后,得到如下文件和文件夹:
(, 下载次数: 1)
上传
点击文件名下载附件
我们需要的是modbus这个文件夹,和demo->BARE下的port文件夹。
将这个两个文件夹添加到工程中:
(, 下载次数: 1)
上传
点击文件名下载附件
添加头文件路径“
(, 下载次数: 2)
上传
点击文件名下载附件
(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文件如下:
/*
* 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$
*/
#include "port.h"
#include "uart.h"
#include "hal_data.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.
*/
if (xRxEnable == TRUE)
{
uart5_rx_state = UART5_RX_ENABLE;
}
else
{
uart5_rx_state = UART5_RX_DISABLE;
}
if (xTxEnable == TRUE)
{
uart5_tx_state = UART5_TX_ENABLE;
}
else
{
uart5_tx_state = UART5_TX_DISABLE;
}
}
BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
uart5_init();
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. */
R_SCI_UART_Write(&g_uart5_ctrl, &ucByte, 1);
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.
*/
//R_SCI_UART_Read (&g_uart5_ctrl, pucByte, 1);
*pucByte = uart5_recv_data;
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( );
}
/* uart4中断回调函数 */
void uart5_callback (uart_callback_args_t * p_args)
{
/* Handle the UART event */
switch (p_args->event)
{
/* Received a character */
case UART_EVENT_RX_CHAR:
{
if (uart5_rx_state == UART5_RX_ENABLE)
{
uart5_recv_data = p_args->data;
//R_SCI_UART_Write(&g_uart4_ctrl, &uart5_recv_data, 1);
prvvUARTRxISR();
}
break;
}
/* Receive complete */
case UART_EVENT_RX_COMPLETE:
{
break;
}
/* Transmit complete */
case UART_EVENT_TX_COMPLETE:
{
//uart4_tx_complete = true;
if (uart5_tx_state == UART5_TX_ENABLE)
{
prvvUARTTxReadyISR();
}
break;
}
default:
{
}
}
}
复制代码
(4)、修改porttimer.c文件
该文件需要对接3个函数。
① BOOL xMBPortTimersInit( USHORT usTim1Timerout50us )
用于对timer进行初始化
② void vMBPortTimersEnable( )
用于使能timer,让timer开始计数
③ void vMBPortTimersDisable( )
用于失能timer,让timer停止计数
另外,timer中断的回调函数也是放在这个文件中。
对接后完整porttimer.c文件如下:
/*
* 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$
*/
/* ----------------------- Platform includes --------------------------------*/
#include "port.h"
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
#include "hal_data.h"
#include <stdio.h>
/* ----------------------- static functions ---------------------------------*/
static void prvvTIMERExpiredISR( void );
/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
/* Initializes the module. */
fsp_err_t err = R_GPT_Open(&g_timer3_ctrl, &g_timer3_cfg);
if(err != FSP_SUCCESS)
{
printf("gpt3 open error\r\n");
}
/* 设置timer的中断周期
** 100表示timer的计数时钟是100MHz
*/
err = R_GPT_PeriodSet(&g_timer3_ctrl, (uint32_t)usTim1Timerout50us * 50U * 100U);
if(err != FSP_SUCCESS)
{
printf("gpt3 periodset error\r\n");
}
return TRUE;
}
void
vMBPortTimersEnable( )
{
/* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
fsp_err_t err = R_GPT_Reset (&g_timer3_ctrl);
if(err != FSP_SUCCESS)
{
printf("gpt3 reset error\r\n");
}
/* Start the timer. */
err = R_GPT_Start(&g_timer3_ctrl);
if(err != FSP_SUCCESS)
{
printf("gpt3 start error\r\n");
}
}
void
vMBPortTimersDisable( )
{
/* Disable any pending timers. */
fsp_err_t err = R_GPT_Stop (&g_timer3_ctrl);
if(err != FSP_SUCCESS)
{
printf("gpt3 stop error\r\n");
}
}
/* 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 g_timer3_callback (timer_callback_args_t * p_args)
{
if (TIMER_EVENT_CYCLE_END == p_args->event)
{
uint8_t led_status;
R_IOPORT_PinRead (&g_ioport_ctrl, BSP_IO_PORT_04_PIN_00, (bsp_io_level_t *)&led_status);
if(led_status)
{
R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_04_PIN_00, BSP_IO_LEVEL_LOW);
}
else
{
R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_04_PIN_00, BSP_IO_LEVEL_HIGH);
}
/* Add application code to be called periodically here. */
prvvTIMERExpiredISR();
}
}
复制代码
(5)、定义Modbus寄存器并实现读写函数
我将寄存器定义和读写函数的实现放在了自己定义的modbus_read_write.c文件中,完整代码如下:
注意:这个几个函数必须实现,否则会报错
/* ----------------------- Platform includes --------------------------------*/
#include "port.h"
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
/* ----------------------- Defines ------------------------------------------*/
#define REG_INPUT_START 1000
#define REG_INPUT_NREGS 4
#define REG_HOLDING_START 1000
#define REG_HOLDING_NREGS 130
/* ----------------------- Static variables ---------------------------------*/
static USHORT usRegInputStart = REG_INPUT_START;
static USHORT usRegInputBuf[REG_INPUT_NREGS] = {1,2,3,4};
static USHORT usRegHoldingStart = REG_HOLDING_START;
static USHORT usRegHoldingBuf[REG_HOLDING_NREGS] = {10,11,12,13};
/* ----------------------- Start implementation -----------------------------*/
void free_modbus_init(eMBMode eMode, UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity)
{
eMBInit( eMode, ucSlaveAddress, ucPort, ulBaudRate, eParity );
eMBEnable();
}
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++ = ( unsigned char )( usRegInputBuf[iRegIndex] >> 8 );
*pucRegBuffer++ = ( unsigned char )( 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 )
{
/* Pass current register values to the protocol stack. */
case MB_REG_READ:
while( usNRegs > 0 )
{
*pucRegBuffer++ = ( unsigned char )( usRegHoldingBuf[iRegIndex] >> 8 );
*pucRegBuffer++ = ( unsigned char )( usRegHoldingBuf[iRegIndex] & 0xFF );
iRegIndex++;
usNRegs--;
}
break;
/* Update current register values with new values from the
* protocol stack. */
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 )
{
return MB_ENOREG;
}
eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
return MB_ENOREG;
}
复制代码
(6)、修改hal_entry.c文件
在hal_entry()函数中加入Modbus初始化相关内容和状态轮询函数,完整内容如下:
void hal_entry(void)
{
/* TODO: add your own code here */
/* 初始化GPIO端口 */
R_IOPORT_Open(&g_ioport_ctrl, &g_bsp_pin_cfg);
/* 初始化uart4,作为调试串口使S用 */
uart4_init();
//uart5_init();//初始化uart5
printf("Renesas RA6M5 Start...\r\n");
/* 初始化Modbus端口和状态 */
free_modbus_init(MB_RTU, 1, 5, 9600, MB_PAR_NONE);
while(1)
{
/* 状态机轮询 */
( void )eMBPoll();
}
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}
复制代码
(7)、修改mbrtu.c文件
修改mbrtu.c中eMBRTUSend函数,如果不修改无法触发485发送。
(, 下载次数: 1)
上传
点击文件名下载附件
修改后MBRTUSend函数完整内容如下:
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++;
usSndBufferCount--;
vMBPortSerialEnable( FALSE, TRUE );
}
else
{
eStatus = MB_EIO;
}
EXIT_CRITICAL_SECTION( );
return eStatus;
}
复制代码
(8)、修改mbconfig.h文件
取消对ASCII的支持
(, 下载次数: 1)
上传
点击文件名下载附件
(9)、去掉寄存器地址自动加一
需要修改四个文件,分别为mbfunccoils.c、mbfuncdisc.c、mbfuncholding.c、mbfuncinput.c
直接去对应的文件搜索:usRegAddress++;
然后屏蔽掉。
mbfunccoils.c里面有三处
mbfuncdisc.c里面有一处
mbfuncholding.c里面有三处
mbfuncinput.c里面有一处
如果不修改,绝对不能正常工作。
四、测试
编译下载运行,并使用PC端工具Modbus Poll模拟Modbus主机来进行测试。
如下:
(, 下载次数: 1)
上传
点击文件名下载附件
经过测试,实测开发板能正确与Modbus Poll相互通讯。
至此,FreeModbus开源库移植成功/
作者:
濮黛娥
时间:
2022-11-18 09:44
谢谢分享,学习一个
作者:
a123123
时间:
2022-12-3 20:55
好帖, 多谢分享!
作者:
西点
时间:
2022-12-6 09:09
好帖, 多谢分享!
作者:
lrz
时间:
2022-12-8 11:47
牛
作者:
zjlsmhfd
时间:
2023-4-19 11:51
楼主能否把uart 相关的几个文件也发出来学习下,调试一直不成功~~
欢迎光临 野火电子论坛 (https://www.firebbs.cn/)
Powered by Discuz! X3.4