PIC单片机移植FreeModbus

发布于:2022-10-23 ⋅ 阅读:(488) ⋅ 点赞:(0)

        Modbus是一项工业上经常用到的通讯协议,而freemodbus是一款开源的从机协议栈。关于它的移植网上已经有了很多的文章,但是对于移植到PIC单片机,少之又少。本文将重介绍freemodbus在PIC18F47Q84单片机的移植。

本次移植使用的是PIC18F47Q84开发板,编译环境 MPLAB X IDE v5.50

想现解PIC18F47Q84开发板更多信息,请点击下面 PIC18F47Q84开发板

PIC18F47Q84开发板

1、FreeModbus 源码的获取

freemodbus源码GitHub链接:

GitHub - cwalter-at/freemodbus: BSD licensed MODBUS RTU/ASCII and TCP slave

2、FreeModbus 移植到PIC18F47Q84

2.1 准备基础工程

我们先准备一个基础工程,所以我们选择第十五章RS485通信实验做为基础工程。

将其拷贝一份,重命名为 FreeModbus_RS485,然后,我们将下载的FreeModbus文件里的modbus这个文件夹全部复制到我们的工程路径,然后将demo文件里的BARE文件夹也复制到我们的工程路径,如下,

(1)、右击  sourse file, 选择 New Logical Folder

新建4个文件夹,分别命名为Driver,FreeModbus_app,FreeModbus_core,FreeModbus_port, 分别放置文件如下:

 

 

(2)、相同的方法,添加相应的头文件

右击 Heads Files 选择 New Logical Folder,同样的,新建4个文件夹,分别命名为Driver,FreeModbus_app,FreeModbus_core,FreeModbus_port, 分别放置和源文件对应的头文件。

 

(3)、移植后,我们编译会发现有很多问题,都是头文件没有找到。

MPLAB X IDE要添加的头文件,都是要指定相对应的路径才可以,以portserial.c为例,

其它文件同理,如mbfuncdisc.c

屏蔽掉的是原文件的写法,添加上路径是我们MPLAB X IDE要求的写法。

把头文件路径一一添加完成后,再次编译,成功!!!

2.2、修改FreeModbus 中portserial.c 文件

前面添加进来的FreeModbus 源码是没有任何串口和定时器的驱动,接下来我们需要对FreeModbus 的portserial.c 文件添加Modbus 串口发送中断和接收

中断使能函数。

portserial.c 修改后的具体代码如下:(红色标记为添加的代码

#include <xc.h>

#include "port.h"

#include "../../../Driver/device_config.h"

/* ----------------------- Modbus includes ----------------------------------*/

#include "../../include/mb.h"

#include "../../include/mbport.h"

#define F_OSC 64000000        // 64MHz   #define F_OSC1 64000000

#define UART2_BAUD_RATE          9600    

#define UART2_BAUD_CALC(UART2_BAUD_RATE,F_OSC) \

    ( ( F_OSC ) / ( ( UART2_BAUD_RATE ) * 4UL ) - 1 )

/* ----------------------- static functions ---------------------------------*/

static void prvvUARTTxReadyISR( void );

static void prvvUARTRxISR( void );

/* ----------------------- Start implementation -----------------------------*/

void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )

{

    //MCU 串口接收中断使能

    if(xRxEnable==TRUE)  

    {  

        //使能接收和接收中断 

        LATCbits.LATC1 = 0;     // RS485驱动芯片,接收使能

        PIR8bits.U2RXIF = 0;

        PIE8bits.U2RXIE = 1;    // enable receive interrupt   

    }

    else

    {

        //禁止接收和接收中断   

        LATCbits.LATC1 = 1;     //RS485驱动芯片,发送使能

        PIR8bits.U2RXIF = 0;

        PIE8bits.U2RXIE = 0;    // enable receive interrupt     

    }

   

    //MCU 串口发送中断使能

    if(xTxEnable==TRUE)  

    { 

        PIR8bits.U2TXIF = 0;

        PIE8bits.U2TXIE = 1;    //使能发送中断 

    }  

    else 

    { 

        PIR8bits.U2TXIF = 0;

        PIE8bits.U2TXIE = 0;    //禁止发送中断 

    } 

}

BOOL

xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )

{

    (void)ucPORT;       //用串口2,不修改串口

    (void)ucDataBits;   //不修改数据位长度   

  

    // 模式控制寄存器,比如 MAX , DALI, LIN

    U2P1L = 0x00;           // P1L 0;

    U2P1H = 0x00;           // P1H 0;

    U2P2L = 0x00;           // P2L 0;

    U2P2H = 0x00;           // P2H 0;

    U2P3L = 0x00;           // P3L 0;

    U2P3H = 0x00;           // P3H 0;

    U2CON0 = 0xB0;        

    U2CON1 = 0x80;       

    U2CON2 = 0x00;

    U2BRG = UART2_BAUD_CALC(ulBaudRate,F_OSC);

   

    U2FIFO = 0x00;            

    U2UIR = 0x00;         

    U2ERRIR = 0x00;       

    U2ERRIE = 0x00;        

    switch(eParity)

    {

        case MB_PAR_EVEN:       // 奇偶校验

            U2CON0bits.MODE = 0x3;

            break;

        case MB_PAR_ODD:        // 奇偶校验   

            U2CON0bits.MODE = 0x2;

            break;

        case MB_PAR_NONE:       // 无奇偶校验

            U2CON0bits.MODE = 0x0;

            break;

        default:break;   

    }

    // enable error interrupt

   PIE8bits.U2EIE = 1;

   vMBPortSerialEnable(TRUE, FALSE );

    return TRUE;

}

BOOL

xMBPortSerialPutByte( CHAR ucByte )

{

    while(!U2FIFObits.TXBE);    // 发送一个数据

    U2TXB = ucByte;

    return TRUE;

}

BOOL

xMBPortSerialGetByte( CHAR * pucByte )

{

    *pucByte = U2RXB;               // 接收一个数据

    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 )

{

    LATCbits.LATC1 = 1;        //MAX485 使能发送

    pxMBFrameCBTransmitterEmpty( );

    LATCbits.LATC1 = 0;

}

/* 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(  );

}

/******************************************************************************

Name        :   INTERRUPT_InterruptManagerHigh()

Input       :

Output      :

description :   高优先级中断

******************************************************************************/

void __interrupt() INTERRUPT_InterruptManagerHigh (void)

{

    if(PIE8bits.U2RXIE && PIR8bits.U2RXIF)

    {

        PIR8bits.U2RXIF = 0;    //清中断标志

        prvvUARTRxISR();        //接受中断 

    }

    else if(PIE8bits.U2TXIE && PIR8bits.U2TXIF)

    {

        PIR8bits.U2TXIF = 0;    //清中断标志位

        prvvUARTTxReadyISR();   //发送完成中断

    }

    else if(PIE8bits.U2EIE == 1 && PIR8bits.U2EIF == 1)

    {

        PIR8bits.U2EIF = 0;

         LATEbits.LATE0 = 1;

         U2ERRIR = 0;  // To clear the interrupt condition, all bits in the UxERRIR register must be cleared

    }

}

首先在portserial.c 文件开头添加#include <xc.h>语句,因为该头文件内已经包含了 PIC18F47Q84 对应的头文件,如果不添加的话,那么再添加串口等驱动时会提示错误。

    另外,要添加#include "../../../Driver/device_config.h" 头文件, 因为在这个头文件定义 #define _XTAL_FREQ 64000000 这个在调用__delay_ms(1);时会用到,否则编译会报错。

2.3、修改FreeModbus 中porttimer.c 文件

接下来需要修改的是porttimer.c 文件,需要补充的就是Modbus 定时器初始化函数、Modbus 定时器使能和失能函数,以及Modbus 定时器中断函数。这个也是非常简单的。具体代码如下:(红色标记为添加代码

/* ----------------------- Platform includes --------------------------------*/

#include <xc.h>

#include "port.h"

/* ----------------------- Modbus includes ----------------------------------*/

#include "../../include/mb.h"

#include "../../include/mbport.h"

static void prvvTIMERExpiredISR( void );

/* ----------------------- Defines ------------------------------------------*/

#define MB_TIMER_PRESCALER      ( 6400UL )

#define F_CPU                  64000000ul

#define MB_TIMER_TICKS          ( F_CPU / MB_TIMER_PRESCALER ) 

#define MB_50US_TICKS           ( 1000UL )  

/* ----------------------- Static variables ---------------------------------*/

static USHORT   usTimerOCRADelta;

//static USHORT   usTimerOCRBDelta;

/* ----------------------- Start implementation -----------------------------*/

BOOL

xMBPortTimersInit( USHORT usTim1Timerout50us )

{

    usTimerOCRADelta = (uint16_t)(65536ul - ( MB_TIMER_TICKS * usTim1Timerout50us ) / ( MB_50US_TICKS ));

    // 50us interrupt

    T0CON1 = 0x43;        

    T0CON0 = 0x90;        

    TMR0 = 0x00;

    T0CON0bits.T0EN = 0;    // Clear Interrupt flag before enabling the interrupt

    PIE3bits.TMR0IE = 1;   // Enabling TMR0 interrupt.

    return TRUE;

}

void vMBPortTimersEnable()

{

    /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */

    if( usTimerOCRADelta > 0 )

    {

        TMR0H = (unsigned char)(usTimerOCRADelta >> 8);

        TMR0L = (unsigned char)(usTimerOCRADelta & 0xff);

    }

    T0CON0bits.T0EN  = 1;

}

void vMBPortTimersDisable()

{

    /* Disable any pending timers. */

    T0CON0bits.T0EN  = 0;

}

static void prvvTIMERExpiredISR( void )

{

    ( void )pxMBPortCBTimerExpired(  );

}

void __interrupt(low_priority) INTERRUPT_InterruptManagerLow (void)

{

    if(PIE3bits.TMR0IE == 1 && PIR3bits.TMR0IF == 1)

    {

        //TMR0 = usTimerOCRADelta;

        TMR0H = (unsigned char)(usTimerOCRADelta >> 8);

        TMR0L = (unsigned char)(usTimerOCRADelta & 0xff);

       

        ( void )pxMBPortCBTimerExpired();

        

        PIR3bits.TMR0IF = 0;

    }

}

2.4 修改FreeModbus 中demo.c 文件

前面移植时,我们在FreeModbus_app 中添加了一个demo.c 文件,该文件是

从官方源码案列中复制过的。该文件本身就是一个Modbus 测试程序,其内含有Modbus 输入寄存器功能测试。

/* ----------------------- Modbus includes ----------------------------------*/

#include "../include/mb.h"

#include "../include/mbport.h"

//

//* ----------------------- Defines ------------------------------------------*/

#define REG_INPUT_START 0x0001                     //保持寄存器的开始地址

#define REG_INPUT_NREGS 8                      //数据字节数

#define REG_HOLDING_START  0x0001             //保持寄存器的开始地址

#define REG_HOLDING_NREGS  8                  //数据字节数

/* ----------------------- Static variables ---------------------------------*/

static USHORT   usRegInputStart = REG_INPUT_START;

static USHORT   usRegInputBuf[REG_INPUT_NREGS] =

{0x1111,0x2222,0x3333,0x4444,0x5555,0x6666,0x7777,0x8888};

static USHORT         usRegHoldingStart = REG_HOLDING_START;

static USHORT         usRegHoldingBuf[REG_HOLDING_NREGS] =

{0x1111,0x2222,0x3333,0x4444,0x5555,0x6666,0x7777,0x8888};

/* ----------------------- Start implementation -----------------------------*/

/*===================================================================

function name: eMBRegInputCB()

Description: 0x04 输入寄存器处理函数,将数据放入BUF

Input Parameters:

Returncode

====================================================================*/

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;

}

/*===================================================================

function name: eMBRegHoldingCB()

Description: 0x030x06.0x10 保持寄存器处理函数,将数据放入BUF

Input Parameters:

Returncode

====================================================================*/

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 - usRegInputStart );

             while( usNRegs > 0 )

             {

                      *pucRegBuffer++ =

                      ( unsigned char )( usRegHoldingBuf[iRegIndex] >> 8 );

                      *pucRegBuffer++ =

                      ( unsigned char )( usRegHoldingBuf[iRegIndex] & 0xFF );

                      iRegIndex++;

                      usNRegs--;

             }

    }

    else

    {

             eStatus = MB_ENOREG;

    }

    return eStatus;

}

3 ModbusPoll 安装和使用

3.1、ModbusPoll 安装

FreeModbus 只能作为从机使用,这就需要其它的工具ModbusPoll 来模拟主机调试。我们实验使用的是Modbus RTU 模式。调试工具 ModbusPoll 我们已经提供,在我们提供的资料文件  --- Modbus源码及调试工具 文件夹里。

将其解压后,选择对应的系统版本双击安装即可,安装过程这里就不在说明。

安装成功后,桌面会有一个快捷键图标,如下:

双击打开 ModbusPoll工具,提示 No connection

现在我们要将从机(也就是我们PIC18F47Q84实验板)与PC 机连接,即使用一个RS232 转RS485 模块和一条USB转RS232 线。RS232 转RS485 模块的RS485 端与开发板的485 的A、B 一一对应连接,不能交叉。RS232 转RS485 模块的RS232 端与USB 转RS232 线连接,该线另一端与电脑USB 口连接,连接示意图如下:

硬件线路连接好后,开始对ModbusPoll 软件设置,

因为软件没有破解,所以只有30天试用时间,这里我们先OK

弹出如下对话框,开始设置串口参数:

点击OK 后,画面显示无效的数据地址,这是因为我们还没有配置对应的寄存器地址和数据量。

3.2、实验现象

首先来测试下读保持寄存器03H 功能,我们点击Setup --- Read/Write Definition

因为程序中配置的O3H 保持寄存器数量为8,所以要对应修改。点击OK 后,

软件现象如下:

这些数据和我们写入寄存器的数据不一样,因为默认该软件是以10进制数显示,而我们写入的是16 进制数,所以在软件中设置下显示方式即可。选中要查看的数据,鼠标右键,选中格式HEX 即可。如下:

结果如下,和我们发送的数据一样,说明FreeModbus 移植正确。

如需测试其它功能,在此基础上,添加自己的代码就可以,操作上大同小异。