相信老铁们,在实际项目开发中,当使用Freemodbus从机协议栈时,会遇到一个问题,就是网上大多数对于该协议栈的移植,在数据接收这块,使用的大都是串口中断接收模式。这样做会有一个问题,如果一条Modbus总线上有若干个从机,一个个从机接收到主机的数据请求时,返回的数据长度大于1kBytes,会有一个问题,总线上其他的从机都处于串口中断接收模式,这样的话,在这段时间内,其他的从机是处于忙碌状态。可想而知对于MCU的CPU是一种浪费。那么对于现在网上大家常用的stm32,大家在实际项目中,都有可能用到Modbus通信协议,要么是自己写,要么是移植人家的。我在实际项目总,为了省事,直接移植人家的freemodbus,做从机协议栈来使用。但是对它的数据接收模式做了一些修改,具体修改看下文代码。
posterial.c文件:该文件实现对串口的底层配置和发送实现
/*
* FreeModbus Libary: STM32 Port
* Copyright (C) 2013 Armink <armink.ztl@gmail.com>
*
* 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 <armink.ztl@gmail.com>
*
* 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 <cwalter@embedded-solutions.at>
* 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 <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$ */ #ifndef _PORT_H #define _PORT_H #include <assert.h> #include <inttypes.h> #include "includes.h" #include <stdio.h> #include <stdint.h> /* ---------------------------------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控制功能 *具体实现思想,参考<<CM3权威指南>>第五章(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];
- 人家实现状态机的切换,其实就是在定时器中完成的,且对发送和接收冲突通过标志位,做了一下处理。
- 代码给力的话,给我加分哦。老铁门。
- 如有转载请备注出处