FreeModbus-移植到stm32f103

本文介绍了FreeModbus库在STM32嵌入式系统中的应用,重点讨论了如何通过串口收发数据,以及如何判断帧结束。FreeModbus利用串口发送完成中断进行数据传输,并依赖定时器来判断帧结束。同时,展示了如何初始化串口、定时器,以及实现中断服务函数。此外,还提供了读取输入、保持、线圈和离散寄存器的回调函数示例。遇到的问题包括寄存器地址理解及波特率设置,通过调整解决了问题。
摘要由CSDN通过智能技术生成

参考:
https://www.amobbs.com/thread-5491615-1-1.html
https://wenku.baidu.com/view/fdae481db52acfc789ebc982.html

简介

FreeModbus是一款适合嵌入式系统使用,只具有从机功能的开源库。

运行机制

  1. 如何通过串口收发数据

freemodbus通过串口中断的方式接收和发送数据。串口中断接收毋庸置疑,在中断服务函数中把数据保存在数组中,以便稍后处理。但是串口发送中断使用哪种形式?
串口发送中断至少有两种方式,第一种,数据寄存器空中断,只要数据寄存器为空并且中断屏蔽位置位,那么中断就会发生;第二种,发送完成中断,若数据寄存器的数据发送完成并且中断屏蔽位置位,那么中断也会发送。建议各位使用串口发送完成中断。freemodbus多使用RS485通信中,从机要么接收要么发送,多数情况下从机处于接收状态,要有数据发送时才进入发送状态。进入发送状态时,数据被一个一个字节发送出去,当最后一个字节被发送出去之后,从机再次进入接收状态。如果使用发送寄存器为空中断,还需要使用其他的方法才可以判断最后一个字节的数据是否发送完成。如果使用数据寄存器为空中断,那么将很有可能丢失最后一个字节。

  1. 如何判断帧结束

Modbus协议中没有明显的开始符和结束符,而是通过帧与帧之间的间隔时间来判断的。如果在指定的时间内(3.5个字符时间),没有接收到新的字符数据,那么就认为收到了新的帧。Modbus通过时间来判断帧是否接受完成,自然需要单片机中的定时器配合。

需改动文件

  1. portserial.c
#include "port.h"
#include "stm32f10x.h"
#include "bsp_usart.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)
    {
        //使能串口接收中断
        USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
    }
    else
    {
        USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);
    }

    if (xTxEnable)
    {
        //使能串口发送完成中断
        USART_ITConfig(USART1, USART_IT_TC, ENABLE);
    }
    else
    {
        USART_ITConfig(USART1, USART_IT_TC, DISABLE);
    }


}

BOOL
xMBPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity)//适配串口初始化
{
    USART_Config(ulBaudRate);
    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;
}

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

//串口中断服务函数
void USART1_IRQHandler(void)//适配中断服务函数
{
    if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)//接收中断
    {
        prvvUARTRxISR();
        USART_ClearITPendingBit(USART1, USART_IT_RXNE);
    }
    if (USART_GetITStatus(USART1, USART_IT_ORE) == SET) //接收溢出中断
    {
        USART_ClearITPendingBit(USART1, USART_IT_ORE);
        prvvUARTRxISR();
    }
    if (USART_GetITStatus(USART1, USART_IT_TC) == SET) //发送完成中断
    {
        prvvUARTTxReadyISR();
        USART_ClearITPendingBit(USART1, USART_IT_TC);//
    }

}

  1. porttimer.c
#include "port.h"
#include "bsp_timer2.h"
#include "stm32f10x.h"

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

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

/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit(USHORT usTim1Timerout50us)  //适配定时器初始化
{
    timer2_init(usTim1Timerout50us);
    return TRUE;
}


void
vMBPortTimersEnable()
{
    /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
    TIM_SetCounter(TIM2, 0x0000);
    TIM_Cmd(TIM2, ENABLE);
}

void
vMBPortTimersDisable()
{
    /* Disable any pending timers. */
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    TIM_ITConfig(TIM2, TIM_IT_Update, DISABLE);
    TIM_SetCounter(TIM2, 0x0000);
    TIM_Cmd(TIM2, DISABLE);
}

/* 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 TIM2_IRQHandler(void)//适配定时器中断服务函数
{
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
    {
        prvvTIMERExpiredISR();
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    }
}
  1. mbrtu.c
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++;  /* next byte in sendbuffer. */
        usSndBufferCount--;
/**************************************************************/
        vMBPortSerialEnable( FALSE, TRUE );
    }
    else
    {
        eStatus = MB_EIO;
    }
    EXIT_CRITICAL_SECTION(  );
    return eStatus;
}

自定义函数

#include "stm32f10x.h"
#include "mb.h"
#include "mbutils.h"

// PCLK2=HCLK=SYSCLK=PLLCLK=72M  PCLK1= HCLK/2 = 36M

//输入寄存器起始地址
#define REG_INPUT_START       0x0000
//输入寄存器数量
#define REG_INPUT_NREGS       8
//保持寄存器起始地址
#define REG_HOLDING_START     0x0000
//保持寄存器数量
#define REG_HOLDING_NREGS     8

//线圈起始地址
#define REG_COILS_START       0x0000
//线圈数量
#define REG_COILS_SIZE        16

//开关寄存器起始地址
#define REG_DISCRETE_START    0x0000
//开关寄存器数量
#define REG_DISCRETE_SIZE     16

//输入寄存器内容
uint16_t usRegInputBuf[REG_INPUT_NREGS] = {0x1000,0x1001,0x1002,0x1003,0x1004,0x1005,0x1006,0x1007};
//输入寄存器起始地址
uint16_t usRegInputStart = REG_INPUT_START;

//保持寄存器内容
uint16_t usRegHoldingBuf[REG_HOLDING_NREGS] = {0x147b,0x3f8e,0x147b,0x400e,0x1eb8,0x4055,0x147b,0x408e};
//保持寄存器起始地址
uint16_t usRegHoldingStart = REG_HOLDING_START;

//线圈状态
uint8_t ucRegCoilsBuf[REG_COILS_SIZE / 8] = {0x01,0x02};
//开关输入状态
uint8_t ucRegDiscreteBuf[REG_DISCRETE_SIZE / 8] = {0x01,0x02};

/**
 * @Brief : 读输入寄存器处理函数,功能码04
 * @param  pucRegBuffer    数据缓存区   大端模式,高字节在前
 * @param  usAddress       寄存器地址 
 * @param  usNRegs         读取个数
 * @return eMBErrorCode 
 */
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++ = ( UCHAR )( usRegInputBuf[iRegIndex] >> 8 );
            *pucRegBuffer++ = ( UCHAR )( usRegInputBuf[iRegIndex] & 0xFF );
            iRegIndex++;
            usNRegs--;
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }

    return eStatus;
}

/**
 * @Brief : 读保持寄存器处理函数,功能码03
 * @param  pucRegBuffer     
 * @param  usAddress        
 * @param  usNRegs          
 * @param  eMode            
 * @return eMBErrorCode 
 */
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)
		{                                       
			case MB_REG_READ://读 MB_REG_READ = 0
        	while(usNRegs > 0)
				{
					*pucRegBuffer++ = (u8)(usRegHoldingBuf[iRegIndex] >> 8);            
					*pucRegBuffer++ = (u8)(usRegHoldingBuf[iRegIndex] & 0xFF); 
          			iRegIndex++;
          			usNRegs--;					
				}                            
        	break;
			case MB_REG_WRITE://写 MB_REG_WRITE = 1
			while(usNRegs > 0)
				{
					usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
          			usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
          			iRegIndex++;
          			usNRegs--;
        		}				
		}
	}
	else//错误
	{
		eStatus = MB_ENOREG;
	}	
	
	return eStatus;
}

/**
 * @Brief : 读线圈寄存器处理函数,功能码01
 * @param  pucRegBuffer     
 * @param  usAddress        
 * @param  usNCoils         
 * @param  eMode            
 * @return eMBErrorCode 
 */
eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils,
              eMBRegisterMode eMode )
{
  //错误状态
  eMBErrorCode eStatus = MB_ENOERR;
  //寄存器个数
  int16_t iNCoils = ( int16_t )usNCoils;
  //寄存器偏移量
  int16_t usBitOffset;
  
  //检查寄存器是否在指定范围内
  if( ( (int16_t)usAddress >= REG_COILS_START ) &&
     ( usAddress + usNCoils <= REG_COILS_START + REG_COILS_SIZE ) )
  {
    //计算寄存器偏移量
    usBitOffset = ( int16_t )( usAddress - REG_COILS_START );
    switch ( eMode )
    {
      //读操作
    case MB_REG_READ:
      while( iNCoils > 0 )
      {
        *pucRegBuffer++ = xMBUtilGetBits( ucRegCoilsBuf, usBitOffset,\
                                         ( uint8_t )( iNCoils > 8 ? 8 : iNCoils ) );
        iNCoils -= 8;
        usBitOffset += 8;
      }
      break;
      
      //写操作
    case MB_REG_WRITE:
      while( iNCoils > 0 )
      {
        xMBUtilSetBits( ucRegCoilsBuf, usBitOffset,\
                       ( uint8_t )( iNCoils > 8 ? 8 : iNCoils ),
                       *pucRegBuffer++ );
        iNCoils -= 8;
      }
      break;
    }
    
  }
  else
  {
    eStatus = MB_ENOREG;
  }
  return eStatus;
}

/**
 * @Brief : 读离散量寄存器处理函数,功能码02
 * @param  pucRegBuffer     
 * @param  usAddress        
 * @param  usNDiscrete      
 * @return eMBErrorCode 
 */
eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
  //错误状态
  eMBErrorCode eStatus = MB_ENOERR;
  //操作寄存器个数
  int16_t iNDiscrete = ( int16_t )usNDiscrete;
  //偏移量
  uint16_t usBitOffset;
  
  //判断寄存器时候再制定范围内
  if( ( (int16_t)usAddress >= REG_DISCRETE_START ) &&
     ( usAddress + usNDiscrete <= REG_DISCRETE_START + REG_DISCRETE_SIZE ) )
  {
    //获得偏移量
    usBitOffset = ( uint16_t )( usAddress - REG_DISCRETE_START );
    
    while( iNDiscrete > 0 )
    {
      *pucRegBuffer++ = xMBUtilGetBits( ucRegDiscreteBuf, usBitOffset,
                                       ( uint8_t)( iNDiscrete > 8 ? 8 : iNDiscrete ) );
      iNDiscrete -= 8;
      usBitOffset += 8;
    }
    
  }
  else
  {
    eStatus = MB_ENOREG;
  }
  return eStatus;
}

int main(void)
{	
	
	eMBInit(MB_RTU, 0x01, 0x01, 9600, MB_PAR_NONE);
	eMBEnable();
	while (1)
	{
		eMBPoll();
	}
}

所遇bug

  1. 查询寄存器地址01,返回02的数据。

寄存器地址:从机内部定义的寄存器地址,要对寻址用的寄存器地址+1【Modbus标准协议规定】。即:从机内部定义的寄存器地址,必须大于1;对于寄存器1-16,寻址时通过0-15来寻址(如:查询寄存器1的值时,指令中的寄存器地址为00 00)。
所以,这是正常的,协议里就是这么规定的。

  1. 波特率设置为115200时无返回
    将定时器节拍设为50us后就正常了,之前分频错了。

程序源码:https://gitee.com/ll0_0ll/FreeModbus_STM32F103
ModbusRTU协议描述:https://download.csdn.net/download/lblmlms/15437417

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tangYi0_0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值