FreeModbus移植记录(一)野火指南者+Keil+FreeModbus 的Modbus RTU从站

在网上关于STM32F103+FreeModbus 的Modbus RTU从站移植的移植有很多,在此记录一下自己在野火的指南者开发板上基于FreeModbus的Modbus RTU从站的过程。

硬件准备

野火指南者(STM32F103VET6)

软件准备

1、freeModbus-v1.6
2、指南者开发板工程模板(随便一个工程都可以,我习惯用一个移植好库函数的空白工程模板)

工程准备工作

1、…\freemodbus-master\freemodbus-master\demo\BARE\port中的四个文件port.h、portevent.c、portserial.c、porttimer.c是移植FreeModbus主要修改的文件,需要这四个文件放到自己的工程文件中
2、将…freemodbus-master\freemodbus-master\modbus中的所有.c文件全部添加到项目中
3、在项目路径中添加所有.c、.h文件路径
这是我的项目结构图
FreeModbus移植记录(一)野火指南者+Keil+FreeModbus 的Modbus RTU从站

移植FreeModbus需要修改的文件

port.h

port.h的修改主要补充两个关于控制器开关总中断的两个宏定义

#define ENTER_CRITICAL_SECTION( )   __set_PRIMASK(1);//关总中断
#define EXIT_CRITICAL_SECTION( )    __set_PRIMASK(0);//开总中断

补充一下
__set_PRIMASK()这个函数在core_cm3.c里面定义
具体函数定义:

/**
 * @brief  Set the Priority Mask value
 *
 * @param  priMask  PriMask
 *
 * Set the priority mask bit in the priority mask register
 */
__ASM void __set_PRIMASK(uint32_t priMask)
{
  msr primask, r0
  bx lr
}

portserial.c

portserial.c文件主要是串口相关函数

//该函数用于RS485接收/发送状态的切换
void vMBPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable)
{
	/* If xRXEnable enable serial receive interrupts. If xTxENable enable
     * transmitter empty interrupts.
     */
	//接收使能
	if (xRxEnable == TRUE)
	{
		//MODBUS_RECIEVE();使用485时使用该函数将485芯片更改为接收状态
		if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET)
		{
			USART_ClearFlag(USART1, USART_FLAG_RXNE);
		}
		USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	}
	else if (xRxEnable == FALSE)
	{
		//MODBUS_SEND();使用485时使用该函数将485芯片更改为发送状态
		USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);
	}

	//发送使能
	if (xTxEnable == TRUE)
	{
		//MODBUS_SEND();使用485时使用该函数将485芯片更改为发送状态
		if (USART_GetFlagStatus(USART1, USART_FLAG_TC) == SET)
		{
			USART_ClearFlag(USART1, USART_FLAG_TC);
		}
		USART_ITConfig(USART1, USART_IT_TC, ENABLE);
	}
	else if (xTxEnable == FALSE)
	{
		//MODBUS_RECIEVE();使用485时使用该函数将485芯片更改为接收状态
		USART_ITConfig(USART1, USART_IT_TC, DISABLE);
	}
}
//该函数用于串口的初始化,要根据工程实际使用的板子进行初始化
//如果加上485的话还要在代码里面增加485芯片发送/接收使能引脚的驱动代码
BOOL xMBPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	// 串口中断优先级配置
	NVIC_InitTypeDef NVIC_InitStructure;

	// 打开串口GPIO的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

	// 打开串口外设的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

	// 将USART Tx的GPIO配置为推挽复用模式
	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);

	// 将USART Rx的GPIO配置为浮空输入模式
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

	// 配置串口的工作参数
	// 配置波特率
	USART_InitStructure.USART_BaudRate = 9600;
	// 配置 针数据字长
	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);

	/* 嵌套向量中断控制器组选择 */
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);

	/* 配置USART为中断源 */
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	/* 抢断优先级*/
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	/* 子优先级 */
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	/* 使能中断 */
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	/* 初始化配置NVIC */
	NVIC_Init(&NVIC_InitStructure);

	// 使能串口接收中断
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

	// 使能串口
	USART_Cmd(USART1, ENABLE);

	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. */
	USART_SendData(USART1, ucByte);
	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.
     */
	*pucByte = USART_ReceiveData(USART1);
	return TRUE;
}

prvvUARTTxReadyISR()和prvvUARTRxISR()两个函数不需要更改,这两个函数需要串口中断中调用。

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

最后一个是串口的中断服务函数,为了方便管理我把串口的中断服务函数写在了portserial.c这个文件里面。因为指南者的usart1连接的是TTL转USB,所以我在这个工程里面使用的是usart1

void USART1_IRQHandler(void)
{
	if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
	{
		prvvUARTRxISR();//在串口接收中断,调用FreeModbus的prvvUARTRxISR()函数
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);
	}

	if (USART_GetITStatus(USART1, USART_IT_TC) != RESET)
	{
		prvvUARTTxReadyISR();//在串口发送中断中,调用FreeModbus的prvvUARTTxReadyISR()函数
		USART_ClearITPendingBit(USART1, USART_IT_TC);
	}
}

至此portserial.c文件移植修改完毕。

porttimer.c

//该函数用于初始化定时器
//需要初始化一个定时时间为50us的定时器
//处理器设定主频为72MHz
//这里用到了F103的基本定时器TIM6
BOOL xMBPortTimersInit(USHORT usTim1Timerout50us)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	NVIC_InitTypeDef NVIC_InitStructure;

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
	// 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断   计时器计数1次为50us
	TIM_TimeBaseStructure.TIM_Period = usTim1Timerout50us;
	// 时钟预分频数为50us
	TIM_TimeBaseStructure.TIM_Prescaler = 3600 - 1;
	// 时钟分频因子 ,基本定时器没有,不用管
	//TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
	// 计数器计数模式,基本定时器只能向上计数,没有计数模式的设置
	//TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
	// 重复计数器的值,基本定时器没有,不用管
	//TIM_TimeBaseStructure.TIM_RepetitionCounter=0;
	// 初始化定时器
	TIM_TimeBaseInit(TIM6, &TIM_TimeBaseStructure);
	// 清除计数器中断标志位
	TIM_ClearFlag(TIM6, TIM_FLAG_Update);
	// 开启计数器中断
	TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE);

	// 设置中断组为0
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
	// 设置中断来源
	NVIC_InitStructure.NVIC_IRQChannel = TIM6_IRQn;
	// 设置主优先级为 0
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	// 设置抢占优先级为3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	return TRUE;
}
//定时器使能函数
//调用库函数使能TIM6
void vMBPortTimersEnable()
{
	/* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
	TIM_ClearITPendingBit(TIM6, TIM_IT_Update);
	TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE);
	TIM_SetCounter(TIM6, 0x00000000);
	TIM_Cmd(TIM6, ENABLE);
}
//定时器失能函数
//调用库函数失能TIM6
void vMBPortTimersDisable()
{
	/* Disable any pending timers. */

	TIM_ClearITPendingBit(TIM6, TIM_IT_Update);
	TIM_ITConfig(TIM6, TIM_IT_Update, DISABLE);
	TIM_SetCounter(TIM6, 0x00000000);
	TIM_Cmd(TIM6, DISABLE);
}
//prvvTIMERExpiredISR()函数不需要更改
//需要在定时器定时结束后调用prvvTIMERExpiredISR()
static void prvvTIMERExpiredISR(void)
{
	(void)pxMBPortCBTimerExpired();
}

//TIM6定时溢出中断函数
//为了方便管理把TIM6的中断服务函数放在porttimer.c函数
//定时器溢出后清除定时标志
//再调用FreeModbus的prvvTIMERExpiredISR()
void TIM6_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET)
	{
		TIM_ClearITPendingBit(TIM6, TIM_FLAG_Update);
		prvvTIMERExpiredISR();
	}
}

至此porttimer.c文件移植修改完毕。

portevent.c

本工程不需要修改

mbrtu.c

在这个工程里面,串口发送中断函数使用的时串口发送完成中断,所以在mbrtu.c文件的 eMBRTUSend() 函数里面还要加上一段函数用于手动发送第一个字节。

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;
}

到这里FreeModbus的移植(Modbus RTU)已经基本完成。
接下来是编写main函数。

最后一步 main.c文件

main函数

因为是Modbus的测试工程,板载外设只用到了一个串口和一个定时器,这两个外设都在FreeModbus的移植文件中修改,没有在main.c中体现出来。
本工程的main函数很简单,都是调用FreeModbus的函数

int main(void)
{
    eMBInit(MB_RTU, 0X01, 1, 9600, MB_PAR_NONE);//初始化FreeModbus
    eMBEnable();//FreeModbus使能

    while (1)
    {
        eMBPoll();//在while (1)循环调用eMBPoll()
    }
}

定义Modbus对象

Modbus常用的数据类型有4种:
线圈量(可读可写开关量)
离散输入量(只读开关量)
保持寄存器(可读可写模拟量)
输入寄存器(只读模拟量)
根据工程的不同我们要自己定义用来存储这些数据的数组
在FreeModbus的demo里面对这四种数据类型都预留了函数接口,并编写了保持寄存器作为范例。工程参考armink的移植补充完成其他数据类型的定义。数据类型的定义可根据工程需要进行裁剪

线圈量

#define COIL_START 0
#define COIL_NCOILS 100
static USHORT usCoilStart = COIL_START;
static UCHAR usCoilBuf[COIL_NCOILS] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10};
#if S_COIL_NCOILS % 8
UCHAR ucSCoilBuf[COIL_NCOILS / 8 + 1];
#else
UCHAR ucSCoilBuf[COIL_NCOILS / 8];
#endif

/**
 * Modbus slave coils callback function.
 *
 * @param pucRegBuffer coils buffer
 * @param usAddress coils address
 * @param usNCoils coils number
 * @param eMode read or write
 *
 * @return result
 */
eMBErrorCode
eMBRegCoilsCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNCoils,
              eMBRegisterMode eMode)
{
    eMBErrorCode eStatus = MB_ENOERR;
    USHORT iRegIndex, iRegBitIndex, iNReg;
    iNReg = usNCoils / 8 + 1;

    usAddress--;

    if ((usAddress >= usCoilStart) && (usAddress + usNCoils <= usCoilStart + COIL_NCOILS))
    {
        iRegIndex = (USHORT)(usAddress - usCoilStart) / 8;
        iRegBitIndex = (USHORT)(usAddress - usCoilStart) % 8;
        switch (eMode)
        {
        /* read current coil values from the protocol stack. */
        case MB_REG_READ:
            while (iNReg > 0)
            {
                *pucRegBuffer++ = xMBUtilGetBits(&usCoilBuf[iRegIndex++], iRegBitIndex, 8);
                iNReg--;
            }
            pucRegBuffer--;
            /* last coils */
            usNCoils = usNCoils % 8;
            /* filling zero to high bit */
            *pucRegBuffer = *pucRegBuffer << (8 - usNCoils);
            *pucRegBuffer = *pucRegBuffer >> (8 - usNCoils);
            break;

            /* write current coil values with new values from the protocol stack. */
        case MB_REG_WRITE:
            while (iNReg > 1)
            {
                xMBUtilSetBits(&usCoilBuf[iRegIndex++], iRegBitIndex, 8, *pucRegBuffer++);
                iNReg--;
            }
            /* last coils */
            usNCoils = usNCoils % 8;
            /* xMBUtilSetBits has bug when ucNBits is zero */
            if (usNCoils != 0)
            {
                xMBUtilSetBits(&usCoilBuf[iRegIndex++], iRegBitIndex, usNCoils, *pucRegBuffer++);
            }
            break;
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }
    return eStatus;
}

离散输入量

#define DISCRETE_START 0
#define DISCRETE_NDISCRETES 100
static USHORT usNDiscreteStart = DISCRETE_START;
static UCHAR usDiscreteBuf[DISCRETE_NDISCRETES] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10};
#if DISCRETE_NDISCRETES % 8
UCHAR ucSDiscInBuf[DISCRETE_NDISCRETES / 8 + 1];
#else
UCHAR ucSDiscInBuf[DISCRETE_NDISCRETES / 8];
#endif

/**
 * Modbus slave discrete callback function.
 *
 * @param pucRegBuffer discrete buffer
 * @param usAddress discrete address
 * @param usNDiscrete discrete number
 *
 * @return result
 */
eMBErrorCode
eMBRegDiscreteCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNDiscrete)
{
    eMBErrorCode eStatus = MB_ENOERR;
    USHORT iRegIndex, iRegBitIndex, iNReg;
    iNReg = usNDiscrete / 8 + 1;

    usAddress--;

    if ((usAddress >= usNDiscreteStart) && (usAddress + usNDiscrete <= usNDiscreteStart + DISCRETE_NDISCRETES))
    {
        iRegIndex = (USHORT)(usAddress - usNDiscrete) / 8;
        iRegBitIndex = (USHORT)(usAddress - usNDiscrete) % 8;

        while (iNReg > 0)
        {
            *pucRegBuffer++ = xMBUtilGetBits(&usDiscreteBuf[iRegIndex++],
                                             iRegBitIndex, 8);
            iNReg--;
        }
        pucRegBuffer--;
        /* last discrete */
        usNDiscrete = usNDiscrete % 8;
        /* filling zero to high bit */
        *pucRegBuffer = *pucRegBuffer << (8 - usNDiscrete);
        *pucRegBuffer = *pucRegBuffer >> (8 - usNDiscrete);
    }
    else
    {
        eStatus = MB_ENOREG;
    }

    return eStatus;
}

保持寄存器

//保持寄存器变量
#define REG_HOLDING_START 0
#define REG_HOLDING_NREGS 100
static USHORT usRegHoldingStart = REG_HOLDING_START;
static USHORT usRegHoldingBuf[REG_HOLDING_NREGS] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

/**
 * Modbus slave holding register callback function.
 *
 * @param pucRegBuffer holding register buffer
 * @param usAddress holding register address
 * @param usNRegs holding register number
 * @param eMode read or write
 *
 * @return result
 */
eMBErrorCode
eMBRegHoldingCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNRegs,
                eMBRegisterMode eMode)
{
    eMBErrorCode eStatus = MB_ENOERR;
    USHORT iRegIndex;

    /* it already plus one in modbus function method. */
    usAddress--;

    if ((usAddress >= usRegHoldingStart) && (usAddress + usNRegs <= usRegHoldingStart + REG_HOLDING_NREGS))
    {
        iRegIndex = usAddress - usRegHoldingStart;
        switch (eMode)
        {
        /* read current register values from the protocol stack. */
        case MB_REG_READ:
            while (usNRegs > 0)
            {
                *pucRegBuffer++ = (UCHAR)(usRegHoldingBuf[iRegIndex] >> 8);
                *pucRegBuffer++ = (UCHAR)(usRegHoldingBuf[iRegIndex] & 0xFF);
                iRegIndex++;
                usNRegs--;
            }
            break;

        /* write 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--;
            }
            break;
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }
    return eStatus;
}

输入寄存器

//输入寄存器变量
#define REG_INPUT_START 0
#define REG_INPUT_NREGS 100
static USHORT usRegInputStart = REG_INPUT_START;
static USHORT usRegInputBuf[REG_INPUT_NREGS] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

/**
 * Modbus slave input register callback function.
 *
 * @param pucRegBuffer input register buffer
 * @param usAddress input register address
 * @param usNRegs input register number
 *
 * @return result
 */
eMBErrorCode
eMBRegInputCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNRegs)
{
    eMBErrorCode eStatus = MB_ENOERR;
    int iRegIndex;

    if ((usAddress >= usRegInputStart) && (usAddress + usNRegs <= usRegInputStart + 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;
}

到这里基于FreeModbus的Modbus RTU工程已经完成移植了,接下来是测试各个功能码

测试

在电脑使用Modbus Poll模拟主站,开发板通过USB与电脑连接,功能测试正常。
FreeModbus移植记录(一)野火指南者+Keil+FreeModbus 的Modbus RTU从站
查看串口的数据流
FreeModbus移植记录(一)野火指南者+Keil+FreeModbus 的Modbus RTU从站

FreeModbus移植记录(一)野火指南者+Keil+FreeModbus 的Modbus RTU从站FreeModbus移植记录(一)野火指南者+Keil+FreeModbus 的Modbus RTU从站 你的小方同学 发布了2 篇原创文章 · 获赞 0 · 访问量 53 私信 关注
上一篇:动手做一个简单的智能小车


下一篇:STM32定时器(一):通用定时器实现定时并产生着中断