ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

STM32HAL库移植FreeModbus协议

2021-02-22 12:30:47  阅读:324  来源: 互联网

标签:HAL TIM STM32HAL huart1 UART usAddress FreeModbus REG 移植


需要源码的请关注我的公众号:

图片

Modbus是一个非常好用的通讯协议,经常用在串口通讯中,也可以用在网口。它既简洁又规范,尤其在工业中应用非常广泛。Modbus的程序实现也比较简单,用户可以自己实现,也可以移植开源的协议代码,比如今天要介绍的FreeModbus。

 

硬件环境:STM32F103C8T6

软件环境:STM32CubeMX v6.1.1

HAL库:STM32CubeF1 Firmware Package V1.8.3

FreeModbus版本:freemodbus-v1.6

freemodbus下载地址:https://github.com/cwalter-at/freemodbus

 

1.FreeModbus文件说明

下载之后解压出来,可以看到文件夹内包含以下内容。我们需要关注的只有modbus文件夹和demo下的BARE文件夹。modbus文件夹下是协议的具体代码。Demo->BARE文件夹下是接口文件,需要用户进行移植和修改的。

图片

2.STM32CubeMX配置

需要使能一个串口和一个定时器,定时器的功能是用于检测3.5个字符的空闲,以判断一帧数据的结束。这里以USART1和TIM4为例进行介绍。

定时器配置:

图片

串口配置:

图片

定时器和串口的参数任意即可,具体在程序中进行配置。打开定时器和串口的中断,且串口中断的优先级要高于定时器中断。

图片

串口和定时器中断程序我们自己编写,所以这里需要取消串口和定时器中断自动生成代码的选项。如下图:

图片

 

3.FreeModbus移植

生成代码后,将下载的FreeModbus的源码复制到工程目录中,在Keil工程中新建两个Group组:FreeModbus和Port。添加modbus文件夹下的全部文件到FreeModbus组。将Demo->BARE->Port文件夹下的文件添加到Port组。同时新建一个Port.c文件也添加到Port组。

另外,别忘了在工程中添加包含路径,否则编译出错。

图片

下面开始程序移植,首先是portserial.c文件,该文件是串口的接口文件,包含以下函数:串口使能、串口初始化、发送一个字节、接收一个字节等。我们需要自己实现完成这些函数的内容。完成后的内容如下:

void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
    /* If xRXEnable enable serial receive interrupts. If xTxENable enable
     * transmitter empty interrupts.
     */
		if(xRxEnable)
    {
        __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);		//使能接收中断
    }
    else
    {
        __HAL_UART_DISABLE_IT(&huart1, UART_IT_RXNE);		//关闭接收中断
    }

    if(xTxEnable)
    {
				SET_DE;	//485芯片设置为发送模式
        __HAL_UART_ENABLE_IT(&huart1, UART_IT_TXE);			//使能发送中断
    }
    else
    {
        __HAL_UART_DISABLE_IT(&huart1, UART_IT_TXE);		//关闭发送为空中断
				__HAL_UART_ENABLE_IT(&huart1, UART_IT_TC);				 //使能发送完成中断
    }
}

BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
    huart1.Instance = USART1;
    huart1.Init.BaudRate = ulBaudRate;
    huart1.Init.StopBits = UART_STOPBITS_1;
    huart1.Init.Mode = UART_MODE_TX_RX;
    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    huart1.Init.OverSampling = UART_OVERSAMPLING_16;

    switch(eParity)
    {
		// 奇校验
    case MB_PAR_ODD:
        huart1.Init.Parity = UART_PARITY_ODD;
        huart1.Init.WordLength = UART_WORDLENGTH_9B;			//带奇偶校验数据位为9bits
        break;
	
		//偶校验
    case MB_PAR_EVEN:
        huart1.Init.Parity = UART_PARITY_EVEN;
        huart1.Init.WordLength = UART_WORDLENGTH_9B;			//带奇偶校验数据位为9bits
        break;
	
		//无校验
    default:
        huart1.Init.Parity = UART_PARITY_NONE;
        huart1.Init.WordLength = UART_WORDLENGTH_8B;			//无奇偶校验数据位为8bits
        break;
    }
    return HAL_UART_Init(&huart1) == HAL_OK ? TRUE : FALSE;
}

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. */
		USART1->DR = 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 = (USART1->DR & (uint16_t)0x00FF);
    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(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE))			//接收中断标
    {
        __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_RXNE);		//清除中断标记
        prvvUARTRxISR();										//通知modbus有数据到达
    }

    if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TXE))				//发送中断标记被置位
    {
        __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_TXE);		//清除中断标记
        prvvUARTTxReadyISR();									//通知modbus数据可以发松
    }
		
		if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TC))			//发送完成中断
		{
				__HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_TC);		//清除中断标记
				CLR_DE;	//485芯片设置为接收模式
		}
}

然后是porttimer.c文件,该文件是定时器的接口文件。定时器的作用是用于通知modbus协议栈3.5个字符的空闲时间已经到达。我们需要实现定时器的初始化和中断相关函数,定时需要配置为50us计数一次,具体计数周期与波特率有关。完成后的内容如下:

BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
		TIM_ClockConfigTypeDef sClockSourceConfig = {0};
		TIM_MasterConfigTypeDef sMasterConfig = {0};

		htim4.Instance = TIM4;
		htim4.Init.Prescaler = 3599;		//50us计数一次
		htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
		htim4.Init.Period = usTim1Timerout50us - 1;  
		htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
		htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
		if (HAL_TIM_Base_Init(&htim4) != HAL_OK)
		{
			Error_Handler();
		}
		sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
		if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK)
		{
			Error_Handler();
		}
		sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
		sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
		if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK)
		{
			Error_Handler();
		}
	
		__HAL_TIM_ENABLE_IT(&htim4, TIM_IT_UPDATE);					//使能定时器更新中断
		
    return TRUE;
}


inline void
vMBPortTimersEnable(  )
{
    /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
		__HAL_TIM_SET_COUNTER(&htim4, 0);		//清空定时器
    __HAL_TIM_ENABLE(&htim4);						//使能定时器
}

inline void
vMBPortTimersDisable(  )
{
    /* Disable any pending timers. */
		__HAL_TIM_DISABLE(&htim4);				//关闭定时器
}

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

//定时器4中断函数
void TIM4_IRQHandler(void)
{
    if(__HAL_TIM_GET_FLAG(&htim4, TIM_FLAG_UPDATE))			//判断更新中断
    {
        __HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_UPDATE);		//清除中断标记
        prvvTIMERExpiredISR();								//通知modbus3.5个字符等待时间到
    }
}

接下来需要实现的就是port.c文件,需要实现各个寄存器/线圈的读写函数,可以参考demo->BARE文件夹下的demo.c文件。完成后的内容如下:

//输入寄存器
#define REG_INPUT_START  3000
#define REG_INPUT_NREGS  4

//保持寄存器
#define REG_HOLD_START   4000
#define REG_HOLD_NREGS   10

//线圈
#define REG_COILS_START  0
#define REG_COILS_NREGS  4

//开关寄存器
#define REG_DISCRETE_START 1000
#define REG_DISCRETE_NREGS 4
/* ----------------------- Static variables ---------------------------------*/
static USHORT   usRegInputStart = REG_INPUT_START;
static USHORT   usRegInputBuf[REG_INPUT_NREGS];


static USHORT   usRegHoldStart = REG_HOLD_START;
static USHORT   usRegHoldBuf[REG_HOLD_NREGS];

static USHORT   usRegCoilsStart = REG_COILS_START;
static uint8_t  usRegCoilsBuf[REG_COILS_NREGS];

static USHORT   usRegDiscreteStart = REG_DISCRETE_START;
static uint8_t  usRegDiscreteBuf[REG_DISCRETE_NREGS];
/* ----------------------- Start implementation -----------------------------*/
//int
//main( void )
//{
//    eMBErrorCode    eStatus;

//    eStatus = eMBInit( MB_RTU, 0x0A, 0, 38400, MB_PAR_EVEN );

//    /* Enable the Modbus Protocol Stack. */
//    eStatus = eMBEnable(  );

//    for( ;; )
//    {
//        ( void )eMBPoll(  );

//        /* Here we simply count the number of poll cycles. */
//        usRegInputBuf[0]++;
//    }
//}
/****************************************************************************
* 名	  称:eMBRegInputCB
* 功    能:读取输入寄存器,对应功能码是 04 eMBFuncReadInputRegister
* 入口参数:pucRegBuffer: 数据缓存区,用于响应主机
*						usAddress: 寄存器地址
*						usNRegs: 要读取的寄存器个数
* 出口参数:
* 注	  意:上位机发来的 帧格式是: SlaveAddr(1 Byte)+FuncCode(1 Byte)
*								+StartAddrHiByte(1 Byte)+StartAddrLoByte(1 Byte)
*								+LenAddrHiByte(1 Byte)+LenAddrLoByte(1 Byte)+
*								+CRCAddrHiByte(1 Byte)+CRCAddrLoByte(1 Byte)
*							3 区
****************************************************************************/
eMBErrorCode
eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    int             iRegIndex;
		usAddress = usAddress - 1;
	
    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;
}
/****************************************************************************
* 名	  称:eMBRegHoldingCB
* 功    能:对应功能码有:06 写保持寄存器 eMBFuncWriteHoldingRegister
*													16 写多个保持寄存器 eMBFuncWriteMultipleHoldingRegister
*													03 读保持寄存器 eMBFuncReadHoldingRegister
*													23 读写多个保持寄存器 eMBFuncReadWriteMultipleHoldingRegister
* 入口参数:pucRegBuffer: 数据缓存区,用于响应主机
*						usAddress: 寄存器地址
*						usNRegs: 要读写的寄存器个数
*						eMode: 功能码
* 出口参数:
* 注	  意:4 区
****************************************************************************/
eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    int             iRegIndex;
		usAddress = usAddress - 1;

    if((usAddress >= REG_HOLD_START) && ((usAddress+usNRegs) <= (REG_HOLD_START + REG_HOLD_NREGS)))
    {
        iRegIndex = (int)(usAddress - usRegHoldStart);
        switch(eMode)
        {
        case MB_REG_READ://读寄存器
            while(usNRegs > 0)
            {
                *pucRegBuffer++ = (uint8_t)(usRegHoldBuf[iRegIndex] >> 8);
                *pucRegBuffer++ = (uint8_t)(usRegHoldBuf[iRegIndex] & 0xFF);
                iRegIndex++;
                usNRegs--;
            }
            break;
        case MB_REG_WRITE://写寄存器
            while(usNRegs > 0)
            {
                usRegHoldBuf[iRegIndex] = *pucRegBuffer++ << 8;
                usRegHoldBuf[iRegIndex] |= *pucRegBuffer++;
                iRegIndex++;
                usNRegs--;
            }
        }
    }
    else//错误
    {
        eStatus = MB_ENOREG;
    }

    return eStatus;
}

/****************************************************************************
* 名	  称:eMBRegCoilsCB
* 功    能:对应功能码有:01 读线圈 eMBFuncReadCoils
*													05 写线圈 eMBFuncWriteCoil
*													15 写多个线圈 eMBFuncWriteMultipleCoils
* 入口参数:pucRegBuffer: 数据缓存区,用于响应主机
*						usAddress: 线圈地址
*						usNCoils: 要读写的线圈个数
*						eMode: 功能码
* 出口参数:
* 注	  意:如继电器
*						0 区
****************************************************************************/
eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    USHORT          iRegIndex;
    USHORT usCoilGroups = ((usNCoils - 1) / 8 + 1);
    UCHAR  ucStatus     = 0;
    UCHAR  ucBits       = 0;
    UCHAR  ucDisp       = 0;
		usAddress = usAddress - 1;
	
    if((usAddress >= REG_COILS_START) &&	((usAddress + usNCoils) <= (REG_COILS_START + REG_COILS_NREGS)))
    {
        iRegIndex = (int)(usAddress - usRegCoilsStart);
        switch(eMode)
        {
        case MB_REG_READ://读线圈
            while(usCoilGroups--)
            {
                ucDisp = 0;
                ucBits = 8;
                while((usNCoils--) != 0 && (ucBits--) != 0)
                {
                    ucStatus |= (usRegCoilsBuf[iRegIndex++] << (ucDisp++));
                }
                *pucRegBuffer++ = ucStatus;
            }
            break;
        case MB_REG_WRITE://写线圈
            while(usCoilGroups--)
            {
                ucStatus = *pucRegBuffer++;
                ucBits   = 8;
                while((usNCoils--) != 0 && (ucBits--) != 0)
                {
                    usRegCoilsBuf[iRegIndex++] = ucStatus & 0X01;
                    ucStatus >>= 1;
                }
            }
        }
    }
    else//错误
    {
        eStatus = MB_ENOREG;
    }

    return eStatus;
}
/****************************************************************************
* 名	  称:eMBRegDiscreteCB 
* 功    能:读取离散寄存器,对应功能码有:02 读离散寄存器 eMBFuncReadDiscreteInputs
* 入口参数:pucRegBuffer: 数据缓存区,用于响应主机   
*						usAddress: 寄存器地址
*						usNDiscrete: 要读取的寄存器个数
* 出口参数:
* 注	  意:1 区
****************************************************************************/
eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    USHORT          iRegIndex;
    USHORT usDiscreteGroups = ((usNDiscrete - 1) / 8 + 1);
    UCHAR  ucStatus     = 0;
    UCHAR  ucBits       = 0;
    UCHAR  ucDisp       = 0;
		usAddress = usAddress - 1;
	
    if((usAddress >= REG_DISCRETE_START) &&	((usAddress + usNDiscrete) <= (REG_DISCRETE_START + REG_DISCRETE_NREGS)))
    {
        iRegIndex = (int)(usAddress - usRegDiscreteStart);

				while(usDiscreteGroups--)
				{
						ucDisp = 0;
						ucBits = 8;
						while((usNDiscrete--) != 0 && (ucBits--) != 0)
						{
								if(usRegDiscreteBuf[iRegIndex])
								{
										ucStatus |= (1 << ucDisp);
								}
								ucDisp++;
						}
						*pucRegBuffer++ = ucStatus;
				}
    }
    else//错误
    {
        eStatus = MB_ENOREG;
    }

    return eStatus;
}

最后在主程序中初始化并调用相关函数即可:

	eMBInit(MB_RTU, 0x01, 0, 9600, MB_PAR_NONE);		// 初始化modbus为RTU方式,地址0x01, 波特率9600,无校验
	eMBEnable();									// 使能modbus协议栈
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		eMBPoll();
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

4.总结

FreeModbus实现了Modbus协议的全部功能,移植和使用起来也比较简单。唯一不足的是只有从机协议是开源的,而主机协议是收费的。

 

标签:HAL,TIM,STM32HAL,huart1,UART,usAddress,FreeModbus,REG,移植
来源: https://blog.csdn.net/zhang062061/article/details/113933993

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有