Freemodbus通信,串口接收模式改为DMA接收模式。

相信老铁们,在实际项目开发中,当使用Freemodbus从机协议栈时,会遇到一个问题,就是网上大多数对于该协议栈的移植,在数据接收这块,使用的大都是串口中断接收模式。这样做会有一个问题,如果一条Modbus总线上有若干个从机,一个个从机接收到主机的数据请求时,返回的数据长度大于1kBytes,会有一个问题,总线上其他的从机都处于串口中断接收模式,这样的话,在这段时间内,其他的从机是处于忙碌状态。可想而知对于MCU的CPU是一种浪费。那么对于现在网上大家常用的stm32,大家在实际项目中,都有可能用到Modbus通信协议,要么是自己写,要么是移植人家的。我在实际项目总,为了省事,直接移植人家的freemodbus,做从机协议栈来使用。但是对它的数据接收模式做了一些修改,具体修改看下文代码。


 posterial.c文件:该文件实现对串口的底层配置和发送实现

/*
 * FreeModbus Libary: STM32 Port
 * Copyright (C) 2013 Armink 
 *
 * 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: portserial.c,v 1.60 2013/08/13 15:07:05 Armink $
 */
 
/* ----------------------- Platform includes --------------------------------*/ 
#include "port.h"

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


/* ----------------------- static functions ---------------------------------*/
static void prvvUARTTxReadyISR(void);
//static void prvvUARTRxISR(void);

/* -------------------------- golobal function ------------------------------*/
unsigned char * exportPoint(unsigned short * length);
void  MB_DMAchannelInit (void);

extern volatile UCHAR  ucRTUBuf[7*1024];

/* ----------------------- Start implementation -----------------------------*/
void vMBPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable)
{
	
	if (xRxEnable)
	{	
		//USART_ITConfig(MODBUS_RS485_SERIAL_PORT, USART_IT_RXNE, ENABLE);
		SLAVE_RS485_RECEIVE_MODE;
	}
	else
	{
		SLAVE_RS485_SEND_MODE;
		//USART_ITConfig(MODBUS_RS485_SERIAL_PORT, USART_IT_RXNE, DISABLE);
	}
	if (xTxEnable)
	{
		USART_ITConfig(MODBUS_RS485_SERIAL_PORT, USART_IT_TXE, ENABLE);
	}
	else
	{
		USART_ITConfig(MODBUS_RS485_SERIAL_PORT, USART_IT_TXE, DISABLE);
	}
}

void vMBPortClose(void)
{
	USART_ITConfig(MODBUS_RS485_SERIAL_PORT, USART_IT_TXE | USART_IT_RXNE, DISABLE);
	USART_Cmd(MODBUS_RS485_SERIAL_PORT, DISABLE);
}

 
/* 默认一个从机 串口3 波特率可设置  奇偶检验可设置. */
BOOL 
xMBPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits,
		eMBParity eParity)
{	
    OS_CPU_SR  cpu_sr;


	GPIO_InitTypeDef 					GPIO_InitStructure;
	USART_InitTypeDef 					USART_InitStructure;
    NVIC_InitTypeDef					NVIC_InitStructure;

	
	/* -------------------时钟初始化------------------------------------. */
	//RCC_APB2PeriphClockCmd(MODBUS_RS485_CONTRL_RCC, ENABLE);
	RCC_APB2PeriphClockCmd(MODBUS_PORT_SERIAL_RCC | 
	           MODBUS_RS485_CONTRL_RCC, ENABLE);
	
												
	/* -----------------------IO初始化---------------------------------. */
	
	/* USART1_TX -----.*/
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = MODBUS_PORT_SERIAL_TX_PIN;
	GPIO_Init(MODBUS_PORT_SERIAL_TX_GPIO,&GPIO_InitStructure);
	
	/* USART1_RX------.*/
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Pin = MODBUS_PORT_SERIAL_RX_PIN;
	GPIO_Init(MODBUS_PORT_SERIAL_RX_GPIO, &GPIO_InitStructure);
	
	/* 配置485发送和接收模----------.*/
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = MODBUS_RS485_CONTRL_PIN;
	GPIO_Init(MODBUS_RS485_CONTRL_GPIO, &GPIO_InitStructure);
	 
	
	/* 串口初始化 ---------------. */
	USART_InitStructure.USART_BaudRate = ulBaudRate;

	switch (eParity)
	{
		case MB_PAR_NONE:  
		USART_InitStructure.USART_Parity = USART_Parity_No;
		USART_InitStructure.USART_WordLength = USART_WordLength_8b;
		break;
		
		case MB_PAR_ODD:  
		USART_InitStructure.USART_Parity = USART_Parity_Odd;
		USART_InitStructure.USART_WordLength = USART_WordLength_9b;
		break;
		
		case MB_PAR_EVEN:  
		USART_InitStructure.USART_Parity = USART_Parity_Even;
		USART_InitStructure.USART_WordLength = USART_WordLength_9b;
		break;
		
		default:
		return FALSE;
	}

	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_HardwareFlowControl =
	USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	
	if (ucPORT != 1)
		return FALSE;
 
	OS_ENTER_CRITICAL();

	USART_Init(MODBUS_RS485_SERIAL_PORT, &USART_InitStructure);
	//USART_ITConfig(MODBUS_RS485_SERIAL_PORT, USART_IT_RXNE, ENABLE);
	USART_Cmd(MODBUS_RS485_SERIAL_PORT, ENABLE);
    USART_ClearFlag(MODBUS_RS485_SERIAL_PORT,USART_FLAG_TC);
    USART_DMACmd(MODBUS_RS485_SERIAL_PORT,USART_DMAReq_Rx,ENABLE);   

	 
		/* ------------------中断初始化-----------------------------
	*设置NVIC优先级分组为Group2:0-3抢占式优先级,0-3的响应式优先级
	-----------------------------------------------------------. */
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	NVIC_InitStructure.NVIC_IRQChannel = MB_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	
    OS_EXIT_CRITICAL();

    MB_DMAchannelInit();
	
	return TRUE;
}

BOOL xMBPortSerialPutByte(CHAR ucByte)
{
	USART_SendData(MODBUS_RS485_SERIAL_PORT, ucByte);
	/* add by frank 2019 -3-11. */
	while(USART_GetFlagStatus(MODBUS_RS485_SERIAL_PORT, USART_FLAG_TC) == RESET){};
	return TRUE;
}

BOOL xMBPortSerialGetByte(CHAR * pucByte)
{
	*pucByte = USART_ReceiveData(MODBUS_RS485_SERIAL_PORT);
	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.
 */
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.
 */
//void prvvUARTRxISR(void)
//{
//	//pxMBFrameCBByteReceived();
//}
 

void MB_IRQHandler(void)
{
	OS_CPU_SR  cpu_sr;
 
	OS_ENTER_CRITICAL(); 
 
	/*  发送中断. */
	if (USART_GetITStatus(MODBUS_RS485_SERIAL_PORT, USART_IT_TXE) == SET)
	{
		prvvUARTTxReadyISR();
	}
	
 
	OS_EXIT_CRITICAL();
}



//函数名称:MB_DMAchannelInit
//功能描述:初始化DMA接收通道
//入口参数:无
//出口参数:无

void  MB_DMAchannelInit (void)
{	
	DMA_InitTypeDef DMA_InitStructure;	
	static unsigned char  *ptraddress;
	unsigned short length = 0;
	
	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);	 
	DMA_DeInit(DMA1_Channel5);    
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&USART1->DR);     
	DMA_InitStructure.DMA_MemoryBaseAddr     = (uint32_t)(exportPoint(&length));      

	DMA_InitStructure.DMA_DIR       = DMA_DIR_PeripheralSRC;       
	DMA_InitStructure.DMA_BufferSize   =length;   

	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;   
	DMA_InitStructure.DMA_MemoryInc  = DMA_MemoryInc_Enable;       

	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; 
	DMA_InitStructure.DMA_MemoryDataSize   = DMA_MemoryDataSize_Byte;     

	DMA_InitStructure.DMA_Mode		   = DMA_Mode_Normal;             
	DMA_InitStructure.DMA_Priority      = DMA_Priority_VeryHigh;      
	DMA_InitStructure.DMA_M2M		 = DMA_M2M_Disable;             

	DMA_Init(DMA1_Channel5, &DMA_InitStructure);   
	DMA_Cmd(DMA1_Channel5, ENABLE);  

}

porttimer.c:该文件实现对定时器的相关配置和增加定时器中断函数//发送还是使用空中断触发方式

/*
 * FreeModbus Libary: STM32 Port
 * Copyright (C) 2013 Armink 
 *
 * 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: porttimer.c,v 1.60 2013/08/13 15:07:05 Armink $
 */

/* ----------------------- Osal includes ------------------------------------*/
#include "includes.h"

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

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

/* ----------------------- static functions ---------------------------------*/
static void prvvTIMERExpiredISR(void);

/* ----------------------- Start implementation -----------------------------*/
BOOL xMBPortTimersInit(USHORT usTim1Timerout50us)
{

	uint16_t PrescalerValue = 0;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	//====================================时钟初始化===========================
	//使能定时器3时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	//====================================定时器初始化===========================
	//定时器时间基配置说明
	//HCLK为72MHz,APB1经过2分频为36MHz
	//TIM3的时钟倍频后为72MHz(硬件自动倍频,达到最大)
	//TIM3的分频系数为3599,时间基频率为72 / (1 + Prescaler) = 20KHz,基准为50us
	//TIM最大计数值为usTim1Timerout50u
	
	PrescalerValue = (uint16_t) (SystemCoreClock / 20000) - 1;
	//定时器1初始化
	TIM_TimeBaseStructure.TIM_Period = (uint16_t) usTim1Timerout50us;
	TIM_TimeBaseStructure.TIM_Prescaler = PrescalerValue;
	TIM_TimeBaseStructure.TIM_ClockDivision = 0;
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
	//预装载使能
	TIM_ARRPreloadConfig(TIM3, ENABLE);
	//====================================中断初始化=============================
	//设置NVIC优先级分组为Group2:0-3抢占式优先级,0-3的响应式优先级
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	//清除溢出中断标志位
	TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
	//定时器3溢出中断关闭
	TIM_ITConfig(TIM3, TIM_IT_Update, DISABLE);
	//定时器3禁能
	TIM_Cmd(TIM3, DISABLE);
	return TRUE;
	//((1+TIM_Prescaler )/72M)*(1+TIM_Period )
	
}

void vMBPortTimersEnable()
{
	TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
	TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
	TIM_SetCounter(TIM3, 0);
	TIM_Cmd(TIM3, ENABLE);
}

void vMBPortTimersDisable()
{
	TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
	TIM_ITConfig(TIM3, TIM_IT_Update, DISABLE);
	TIM_SetCounter(TIM3, 0);
	TIM_Cmd(TIM3, DISABLE);
}

void prvvTIMERExpiredISR(void)
{
	(void) pxMBPortCBTimerExpired();
}

void TIM3_IRQHandler(void)
{
	OS_CPU_SR	cpu_sr;
	
//	ENTER_CRITICAL_SECTION();
	OS_ENTER_CRITICAL(	);
	if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
	{
		
		TIM_ClearFlag(TIM3, TIM_FLAG_Update);	     //清中断标记
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update);	 //清除定时器T3溢出中断标志位
		prvvTIMERExpiredISR();
	}
//	EXIT_CRITICAL_SECTION(	);
	OS_EXIT_CRITICAL(	);
}

mbrtu.c:实现数据的接收解析和数据发送

/* 
 * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU.
 * Copyright (c) 2006-2018 Christian Walter 
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

/* ----------------------- Osal includes ------------------------------------*/
#include "includes.h"
/* ----------------------- System includes ----------------------------------*/
#include "stdlib.h"
#include "string.h"

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

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbrtu.h"
#include "mbframe.h"

#include "mbcrc.h"
#include "mbport.h"


/* ----------------------- task includes -------------------------------------*/
#include "app_task.h"

/* ----------------------- Defines ------------------------------------------*/
#define MB_SER_PDU_SIZE_MIN     4       /*!< Minimum size of a Modbus RTU frame. */
#define MB_SER_PDU_SIZE_MAX    (7 *1024)     /*!< Maximum size of a Modbus RTU frame. */
#define MB_SER_PDU_SIZE_CRC     2       /*!< Size of CRC field in PDU. */
#define MB_SER_PDU_ADDR_OFF     0       /*!< Offset of slave address in Ser-PDU. */
#define MB_SER_PDU_PDU_OFF      1       /*!< Offset of Modbus-PDU in Ser-PDU. */

/* ----------------------- Type definitions ---------------------------------*/
typedef enum
{
    STATE_RX_INIT,              /*!< Receiver is in initial state. */
    STATE_RX_IDLE,              /*!< Receiver is in idle state. */
    STATE_RX_RCV,               /*!< Frame is beeing received. */
    STATE_RX_ERROR              /*!< If the frame is invalid. */
} eMBRcvState;

typedef enum
{
    STATE_TX_IDLE,              /*!< Transmitter is in idle state. */
    STATE_TX_XMIT               /*!< Transmitter is in transfer state. */
} eMBSndState;

/* ----------------------- Static variables ---------------------------------*/
static volatile eMBSndState eSndState;
static volatile eMBRcvState eRcvState;

/* ------------------------USART SEND BUFFER ---------------------------------*/
volatile UCHAR  ucRTUBuf[MB_SER_PDU_SIZE_MAX];

static volatile UCHAR *pucSndBufferCur;
static volatile USHORT usSndBufferCount;

static volatile USHORT usRcvBufferPos;



/*--------------------------- Golobal functions ------------------------------*/
void  MB_DMAchannelInit (void);

/* ----------------------- Start implementation -----------------------------*/
eMBErrorCode
eMBRTUInit( UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    ULONG           usTimerT35_50us;
	OS_CPU_SR  		cpu_sr;
    ( void )ucSlaveAddress;
   // ENTER_CRITICAL_SECTION(  );
	OS_ENTER_CRITICAL(	);	 

	/* Modbus RTU uses 8 Databits. */
	if( xMBPortSerialInit( ucPort, ulBaudRate, 8, eParity ) != TRUE )
    {
        eStatus = MB_EPORTERR;
    }
    else
    {
        /* - 修改为固定100  则定时5ms一次中断. */
        usTimerT35_50us = 100;
        if( xMBPortTimersInit( ( USHORT ) usTimerT35_50us ) != TRUE )
        {
            eStatus = MB_EPORTERR;
        }
    }
 //   EXIT_CRITICAL_SECTION(  );
	OS_EXIT_CRITICAL(	);
    return eStatus;
}

void
eMBRTUStart( void )
{
   OS_CPU_SR  		cpu_sr;
   // ENTER_CRITICAL_SECTION(  );
	OS_ENTER_CRITICAL(	);	 
	
    /* Initially the receiver is in the state STATE_RX_INIT. we start
     * the timer and if no character is received within t3.5 we change
     * to STATE_RX_IDLE. This makes sure that we delay startup of the
     * modbus protocol stack until the bus is free.
     */
    eRcvState = STATE_RX_INIT;
    vMBPortSerialEnable( TRUE, FALSE );
    vMBPortTimersEnable(  );
	
	OS_EXIT_CRITICAL(	);

  // EXIT_CRITICAL_SECTION(  );
}

void
eMBRTUStop( void )
{   
	OS_CPU_SR  		cpu_sr;
    //ENTER_CRITICAL_SECTION(  );
	OS_ENTER_CRITICAL(	);	 	
    vMBPortSerialEnable( FALSE, FALSE );
    vMBPortTimersDisable(  );
	OS_EXIT_CRITICAL(	);
    //EXIT_CRITICAL_SECTION(  );
}

eMBErrorCode
eMBRTUReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength )
{
    OS_CPU_SR  		cpu_sr;
    BOOL            xFrameReceived = FALSE;
    eMBErrorCode    eStatus = MB_ENOERR;
	
	OS_ENTER_CRITICAL(	);	 
  //  ENTER_CRITICAL_SECTION(  );
    assert( usRcvBufferPos < MB_SER_PDU_SIZE_MAX );

    /* Length and CRC check */
    if( ( usRcvBufferPos >= MB_SER_PDU_SIZE_MIN )
        && ( usMBCRC16( ( UCHAR * ) ucRTUBuf, usRcvBufferPos ) == 0 ) )
    {
        /* Save the address field. All frames are passed to the upper layed
         * and the decision if a frame is used is done there.
         */
        *pucRcvAddress = ucRTUBuf[MB_SER_PDU_ADDR_OFF];

        /* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus
         * size of address field and CRC checksum.
         */
        *pusLength = ( USHORT )( usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC );

        /* Return the start of the Modbus PDU to the caller. */
        *pucFrame = ( UCHAR * ) & ucRTUBuf[MB_SER_PDU_PDU_OFF];
        xFrameReceived = TRUE;
    }
    else
    {
        eStatus = MB_EIO;
    }
	OS_EXIT_CRITICAL(	);
 //   EXIT_CRITICAL_SECTION(  );
    return eStatus;
}

eMBErrorCode
eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    USHORT          usCRC16;
    OS_CPU_SR  		cpu_sr;

  // ENTER_CRITICAL_SECTION(  );
	OS_ENTER_CRITICAL(	);	 

    /* 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;
        vMBPortSerialEnable( FALSE, TRUE );
    }
    else
    {
        eStatus = MB_EIO;
    }
   // EXIT_CRITICAL_SECTION(  );
   OS_EXIT_CRITICAL(	);	 

    return eStatus;
}

BOOL
xMBRTUReceiveFSM( void )
{
    BOOL            xTaskNeedSwitch = FALSE;
    UCHAR           ucByte;

    assert( eSndState == STATE_TX_IDLE );

    /* Always read the character. */
    ( void )xMBPortSerialGetByte( ( CHAR * ) & ucByte );

    switch ( eRcvState )
    {
        /* If we have received a character in the init state we have to
         * wait until the frame is finished.
         */
    case STATE_RX_INIT:
        vMBPortTimersEnable(  );
        break;

        /* In the error state we wait until all characters in the
         * damaged frame are transmitted.
         */
    case STATE_RX_ERROR:
        vMBPortTimersEnable(  );
        break;

        /* In the idle state we wait for a new character. If a character
         * is received the t1.5 and t3.5 timers are started and the
         * receiver is in the state STATE_RX_RECEIVCE.
         */
    case STATE_RX_IDLE:
        usRcvBufferPos = 0;
        ucRTUBuf[usRcvBufferPos++] = ucByte;
        eRcvState = STATE_RX_RCV;

        /* Enable t3.5 timers. */
        vMBPortTimersEnable(  );
        break;

        /* We are currently receiving a frame. Reset the timer after
         * every character received. If more than the maximum possible
         * number of bytes in a modbus frame is received the frame is
         * ignored.
         */
    case STATE_RX_RCV:
        if( usRcvBufferPos < MB_SER_PDU_SIZE_MAX )
        {
            ucRTUBuf[usRcvBufferPos++] = ucByte;
        }
        else
        {
            eRcvState = STATE_RX_ERROR;
        }
        vMBPortTimersEnable(  );
        break;
    }
    return xTaskNeedSwitch;
}

BOOL
xMBRTUTransmitFSM( void )
{
    BOOL            xNeedPoll = FALSE;

    assert( eRcvState == STATE_RX_IDLE );

    switch ( eSndState )
    {
        /* We should not get a transmitter event if the transmitter is in
         * idle state.  */
    case STATE_TX_IDLE:
        /* enable receiver/disable transmitter. */
        vMBPortSerialEnable( TRUE, FALSE );
        break;

    case STATE_TX_XMIT:
        /* check if we are finished. */
        if( usSndBufferCount != 0 )
        {
            xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur );
            pucSndBufferCur++;  /* next byte in sendbuffer. */
            usSndBufferCount--;
        }
        else
        {
            xNeedPoll = xMBPortEventPost( EV_FRAME_SENT );
            /* Disable transmitter. This prevents another transmit buffer
             * empty interrupt. */
			
            vMBPortSerialEnable( TRUE, FALSE );
            eSndState = STATE_TX_IDLE;
        }
        break;
    }

    return xNeedPoll;
}

BOOL
xMBRTUTimerT35Expired( void )
{
    BOOL                   xNeedPoll = FALSE;
	static unsigned char   xtimeoutCount = 0;
	static unsigned short  xPreviousLength = 0;
	       unsigned short  xNowLength = 0;
	
	/* 超时计数. */
	xtimeoutCount ++;

	/* 获取dma通道剩余长度. */
	xNowLength = DMA_GetCurrDataCounter(DMA1_Channel5);

	/*- 如果DMA通道仍然在接收数据,则清空超时计数. */
	if (xNowLength != xPreviousLength)  
	{
		xtimeoutCount   = 0;
		xPreviousLength = xNowLength;
	}
	else
	{
		/*- 10ms内没有接收到数据,表示通道处于空闲状态. */
		if (xtimeoutCount > 2)
		{
			if (xNowLength < MB_SER_PDU_SIZE_MAX )
			{
				usRcvBufferPos = MB_SER_PDU_SIZE_MAX - xNowLength;
				DMA_ClearFlag(DMA1_FLAG_TC5);
				DMA_Cmd(DMA1_Channel5,DISABLE);  
                MB_DMAchannelInit();
				
				/*- 修改状态机的状态,表示接收完毕. */
			    xNeedPoll = xMBPortEventPost( EV_FRAME_RECEIVED );
 			    eRcvState = STATE_RX_IDLE;
			}
		   xtimeoutCount = 0;
		}
	
	}
    return xNeedPoll;
}


//导出指针
unsigned char * exportPoint(unsigned short * length)
{
    *length = MB_SER_PDU_SIZE_MAX;
	return (unsigned char *)&ucRTUBuf[0];
}

 

port.h:串口的相关配置宏


  • /*
     * FreeModbus Libary: BARE Port
     * Copyright (C) 2006 Christian Walter 
     *
     * 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$
     */
    
    #ifndef _PORT_H
    #define _PORT_H
    
    #include 
    #include 
    #include "includes.h"
    #include 
    #include 
    
    /* ---------------------------------STM32 includes ----------------------------*/
    #include "stm32f10x.h"
     
    
    #define	INLINE                      inline
    #define PR_BEGIN_EXTERN_C           extern "C" {
    #define	PR_END_EXTERN_C             }
    
    
    
    /* --------------------------  Modbus macros -----------------------------------*/
    #define MODBUS_PORT_SERIAL_RCC       RCC_APB2Periph_USART1
    #define MODBUS_PORT_SERIAL_TX_GPIO   GPIOA
    #define MODBUS_PORT_SERIAL_RX_GPIO   GPIOA
    #define MODBUS_PORT_SERIAL_TX_PIN    GPIO_Pin_9
    #define MODBUS_PORT_SERIAL_RX_PIN    GPIO_Pin_10
    
    #define MODBUS_RS485_CONTRL_GPIO     GPIOA     
    #define MODBUS_RS485_CONTRL_PIN      GPIO_Pin_11
    #define MODBUS_RS485_CONTRL_RCC      RCC_APB2Periph_GPIOA
    #define MODBUS_RS485_SERIAL_PORT     USART1
    
    #define MB_IRQn                      USART1_IRQn
    #define MB_IRQHandler                USART1_IRQHandler
     
     
    //TODO  暂时先写B2引脚,等组网测试时再确认
    #define SLAVE_RS485_SEND_MODE       GPIO_SetBits(MODBUS_RS485_CONTRL_GPIO,MODBUS_RS485_CONTRL_PIN)
    #define SLAVE_RS485_RECEIVE_MODE    GPIO_ResetBits(MODBUS_RS485_CONTRL_GPIO,MODBUS_RS485_CONTRL_PIN)
    
    #define ENTER_CRITICAL_SECTION( )          	 
    #define EXIT_CRITICAL_SECTION( )        
    
    
    /*位带操作,实现51类似的GPIO控制功能
     *具体实现思想,参考<>第五章(87页~92页).IO口操作宏定义*/
     
    #define BITBAND(addr, bitnum) ((addr & 0xF0000000)+ 0x2000000 \
    							+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
    							
    #define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
    	
    #define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum)) 
    
    /* ------------------------IO口地址映射 ----------------------------------------*/
    #define GPIOA_ODR_Addr    (GPIOA_BASE+12) /*!> 0x4001080C */ 
    #define GPIOB_ODR_Addr    (GPIOB_BASE+12) /*!> 0x40010C0C */
    #define GPIOC_ODR_Addr    (GPIOC_BASE+12) /*!> 0x4001100C */
    #define GPIOD_ODR_Addr    (GPIOD_BASE+12) /*!> 0x4001140C */
    #define GPIOE_ODR_Addr    (GPIOE_BASE+12) /*!> 0x4001180C */
    #define GPIOF_ODR_Addr    (GPIOF_BASE+12) /*!> 0x40011A0C */  
    #define GPIOG_ODR_Addr    (GPIOG_BASE+12) /*!> 0x40011E0C */   
    #define GPIOA_IDR_Addr    (GPIOA_BASE+8)  /*!> 0x40010808 */
    #define GPIOB_IDR_Addr    (GPIOB_BASE+8)  /*!> 0x40010C08 */
    #define GPIOC_IDR_Addr    (GPIOC_BASE+8)  /*!> 0x40011008 */
    #define GPIOD_IDR_Addr    (GPIOD_BASE+8)  /*!> 0x40011408 */
    #define GPIOE_IDR_Addr    (GPIOE_BASE+8)  /*!> 0x40011808 */
    #define GPIOF_IDR_Addr    (GPIOF_BASE+8)  /*!> 0x40011A08 */ 
    #define GPIOG_IDR_Addr    (GPIOG_BASE+8)  /*!> 0x40011E08 */
     
    /* ----------------------- IO口操作,只对单一的IO口 ----------------------------*/
    
    #define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  
    #define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)   
    
    #define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  
    #define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  
    
    #define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n) 
    #define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)   
    
    #define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)  
    #define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)   
    
    #define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)  
    #define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n)  
    
    #define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n)  
    #define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n) 
    
    #define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n)  
    #define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)  
    
    
    
    
    #define LED1    PCout(6)
    
    #define GREEN_LED_BLINK() do \
    	                      {\
    									LED1 = ~LED1 ;\
    						  }while(0)
    	
    typedef uint8_t BOOL;
    
    typedef unsigned char UCHAR;
    typedef char CHAR;
    
    typedef uint16_t USHORT;
    typedef int16_t SHORT;
    
    typedef uint32_t ULONG;
    typedef int32_t LONG;
    
    #ifndef TRUE
    #define TRUE            1
    #endif
    
    #ifndef FALSE
    #define FALSE           0
    #endif
    
    
     
     
    
    
    
    #endif
    

     

  • 在这里简单介绍一下,实现方式。上面三个文件起始已经把实现代码展示出来了,主要想说的是,第一步在串口配置中,开启串口DMA接收功能。
  • 将原有初始化串口的函数中,将定时器时间修改为5ms中断一次
  • 在定时器中断函数中,做超时接收判断,接收完成,则修改状态机的状态。
  • 注意啊,老铁们,原作者发送和接收缓冲Buff使用的都是volatile UCHAR  ucRTUBuf[MB_SER_PDU_SIZE_MAX];
  • 人家实现状态机的切换,其实就是在定时器中完成的,且对发送和接收冲突通过标志位,做了一下处理。
  • 代码给力的话,给我加分哦。老铁门。
  • 如有转载请备注出处

你可能感兴趣的:(Freemodbus通信,串口接收模式改为DMA接收模式。)