STM32
直播中

杨秀英

7年用户 1411经验值
私信 关注
[问答]

基于STM32HAL库移植FreeModbus怎么实现?

如何利用STM32CubeMX新建工程?
基于STM32HAL库移植FreeModbus怎么实现?

回帖(1)

李丽华

2021-12-10 14:06:37
  一、利用STM32CubeMX新建工程

  1、配置时钟

  时钟源选择外部晶振。

  

  

  配置时钟树。

  

  

  2·、配置定时器

  暂时先勾选internal clock就行,在modbus移植过程中还会对定时器重新初始化。

  

  

  3、配置串口

  随便配置就行,在modbus移植过程中还会对串口重新初始化。

  

  

  4、中断配置

  这里注意,串口的优先级是要比定时器优先高的。

  

  

  取消掉自动生成中断服务程序,在移植过程中我们要自己编写串口和定时器的中断服务程序。

  

  

  5、配置GPIO

  如果使用了485模式,还需要一个发送接收控制端,该IO配置为推挽输出模式。也可以为该引脚设置User Label。

  

  

  6、创建工程

  
  

  

  二、下载freemodbus源码并且添加到刚才创建的工程

  1、复制freemodbus源码到工程文件夹

  
  

  

  2、在demo目录下的BARE文件夹里面新建port.c文件

  3、在keil中添加freemodbus源码

  将modbus和demo->BARE目录下的所有C文件添加进来。

  

  


  

  

  4、编辑头文件包含路径

  
  

  

  
三、编写代码
1、物理接口文件的修改
在物理层,用户只需完成串行口及超时定时器的配置即可。具体应修改接口文件portserial.c及porttimer.c。
1)portserial.c中函数的修改


void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
        if(xRxEnable)
    {
        __HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE);                 //使能接收寄存器非空中断
        HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_RESET); //MAX485操作 低电平为接收模式
    }
    else
    {
        __HAL_UART_DISABLE_IT(&huart2, UART_IT_RXNE);                //禁能接收寄存器非空中断               
        HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_SET); //MAX485操作 高电平为发送模式
    }
    if (TRUE == xTxEnable)
    {
        HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_SET); //MAX485操作 高电平为发送模式
        __HAL_UART_ENABLE_IT(&huart2, UART_IT_TC);      //使能发送完成中断
    }
    else
    {
        __HAL_UART_DISABLE_IT(&huart2, UART_IT_TC);   //禁能发送完成中断
    }
}


BOOL xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
    huart2.Instance = USART2;
    huart2.Init.BaudRate = ulBaudRate;
    huart2.Init.StopBits = UART_STOPBITS_1;
    huart2.Init.Mode = UART_MODE_TX_RX;
    huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    huart2.Init.OverSampling = UART_OVERSAMPLING_16;
    HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_RESET); //MAX485操作 低电平为接收模式
    switch(eParity)
    {
        // 奇校验
    case MB_PAR_ODD:
        huart2.Init.Parity = UART_PARITY_ODD;
        huart2.Init.WordLength = UART_WORDLENGTH_9B;                        // 带奇偶校验数据位为9bits
        break;
        // 偶校验
    case MB_PAR_EVEN:
        huart2.Init.Parity = UART_PARITY_EVEN;
        huart2.Init.WordLength = UART_WORDLENGTH_9B;                        // 带奇偶校验数据位为9bits
        break;
        // 无校验
    default:
        huart2.Init.Parity = UART_PARITY_NONE;
        huart2.Init.WordLength = UART_WORDLENGTH_8B;                        // 无奇偶校验数据位为8bits
        break;
    }
    return HAL_UART_Init(&huart2) == HAL_OK ? TRUE : FALSE;
}


BOOL xMBPortSerialPutByte( CHAR ucByte )
{
    HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_SET); //MAX485操作 高电平为发送模式
    USART2->DR = ucByte;
    return TRUE;
}


BOOL xMBPortSerialGetByte( CHAR * pucByte )
{
         HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_RESET); //MAX485操作 低电平为接收模式
   *pucByte = (USART2->DR & (uint16_t)0x00FF);
        return TRUE;
}


static void prvvUARTTxReadyISR( void )
{
    pxMBFrameCBTransmitterEmpty(  );
}


static void prvvUARTRxISR( void )
{
    pxMBFrameCBByteReceived(  );
}


void USART2_IRQHandler(void)
{
    if(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_RXNE))                        // 接收非空中断标记被置位
    {
        __HAL_UART_CLEAR_FLAG(&huart2, UART_FLAG_RXNE);                        // 清除中断标记
        prvvUARTRxISR();                                                               
    }
    if(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_TC))                                // 发送完成中断标记被置位
    {
        __HAL_UART_CLEAR_FLAG(&huart2, UART_FLAG_TC);                        // 清除中断标记
        prvvUARTTxReadyISR();                                                               
    }
}


2)porttimer.c中函数的修改


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;//usTim1Timerout50us*50即为定时器溢出时间
    htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
    if (HAL_TIM_Base_Init(&htim4) != HAL_OK)
    {
        return FALSE;
    }
    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK)
    {
        return FALSE;
    }
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK)
    {
        return FALSE;
    }
    __HAL_TIM_ENABLE_IT(&htim4, TIM_IT_UPDATE);        // 使能定时器更新中断
    return TRUE;
}


inline void vMBPortTimersEnable(  )
{
        __HAL_TIM_SET_COUNTER(&htim4, 0);        // 清空计数器
  __HAL_TIM_ENABLE(&htim4);     // 使能定时器
}


inline void
vMBPortTimersDisable(  )
{
        __HAL_TIM_DISABLE(&htim4);        // 禁能定时器
}


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个字符等待时间到
    }
}


2、应用层回函数的修改
在port中,定义所需要使用的寄存器,并修改对应的回函数。以下是操作保持寄存器的示例代码。


#include "port.h"
#include "mb.h"
//保持寄存器起始地址
#define REG_HOLDING_START     0x0001
//保持寄存器数量
#define REG_HOLDING_NREGS     10
//保持寄存器内容
uint16_t usRegHoldingBuf[REG_HOLDING_NREGS]
= {0x147b,0x3f8e,0x147b,0x400e,0x1eb8,0x4055,0x147b,0x408e,0x1111,0x1111};
/**
* @brief 保持寄存器处理函数,保持寄存器可读,可读可写
* @param pucRegBuffer 读操作时--返回数据指针,写操作时--输入数据指针
* usAddress 寄存器起始地址
* usNRegs 寄存器长度
* eMode 操作方式,读或者写
* @retval eStatus 寄存器状态
*/
eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs,
eMBRegisterMode eMode )
{
        //错误状态
        eMBErrorCode eStatus = MB_ENOERR;
        //偏移量
        int16_t iRegIndex;
        //判断寄存器是不是在范围内
        test = usAddress;
        if ((usAddress >= (USHORT)REG_HOLDING_START)
    && (usAddress + usNRegs <= (USHORT)(REG_HOLDING_START + REG_HOLDING_NREGS)) )
        {
         //计算偏移量
         iRegIndex = ( int16_t )( usAddress - REG_HOLDING_START);switch ( eMode ){
         //读处理函数
         case MB_REG_READ:
         while( usNRegs > 0 )
          {
              *pucRegBuffer++ = ( uint8_t )( usRegHoldingBuf[iRegIndex] >> 8 );
               *pucRegBuffer++ = ( uint8_t )( usRegHoldingBuf[iRegIndex] & 0xFF );
               iRegIndex++;
                   usNRegs--;
                }
           break;
                //写处理函数
                case MB_REG_WRITE:
                while( usNRegs > 0 )
                {       
                        usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
                        usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
                        iRegIndex++;
                        usNRegs--;
                }
                break;
      }
        }
        else
        {
                //返回错误状态
                eStatus = MB_ENOREG;
        }
        return eStatus;
}


/// 未使用
eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
         return MB_ENOREG;
}
/// 未使用
eMBErrorCode eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
{
    return MB_ENOREG;
}


/// 未使用
eMBErrorCode eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
    return MB_ENOREG;
}


3、主函数的编写
int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  //初始化 RTU模式 从机地址为1 USART1 9600 无校验
  eMBInit(MB_RTU, 0x01, 0x02, 9600, MB_PAR_NONE);
  //启动FreeModbus
  eMBEnable();
  while (1)
  {
      //FreeMODBUS不断查询
      eMBPoll();
      HAL_Delay(50);
  }
}


四、特别注意
这里使能了串口的“发送完成中断”,而不是官方代码建议的“发送为空中断”。由于单片机复位后,发送完成标志位TC置0,或者由于上一次发送最后一个字节时,发送中断中清除了TC标志位,所以无法进入USART2_IRQHandler()函数中启动发送。因此,在eMBPoll()函数中插入如下代码,发送一帧数据中的第一个字节,触发发送完成中断,随后就会自动完成一帧数据的发送。


eMBErrorCode
eMBPoll( void )
{
    static UCHAR   *ucMBFrame;
    static UCHAR    ucRcvAddress;
    static UCHAR    ucFunctionCode;
    static USHORT   usLength;
    static eMBException eException;
    int             i;
    eMBErrorCode    eStatus = MB_ENOERR;
    eMBEventType    eEvent;
    /* Check if the protocol stack is ready. */
    if( eMBState != STATE_ENABLED )
    {
        return MB_EILLSTATE;
    }
    /* Check if there is a event available. If not return control to caller.
     * Otherwise we will handle the event. */
    if( xMBPortEventGet( &eEvent ) == TRUE )
    {
        switch ( eEvent )
        {
        case EV_READY:
            break;
        case EV_FRAME_RECEIVED:
            eStatus = peMBFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength );
            if( eStatus == MB_ENOERR )
            {
                /* Check if the frame is for us. If not ignore the frame. */
                if( ( ucRcvAddress == ucMBAddress ) || ( ucRcvAddress == MB_ADDRESS_BROADCAST ) )
                {
                    ( void )xMBPortEventPost( EV_EXECUTE );
                }
            }
            break;
        case EV_EXECUTE:
            ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF];
            eException = MB_EX_ILLEGAL_FUNCTION;
            for( i = 0; i < MB_FUNC_HANDLERS_MAX; i++ )
            {
                /* No more function handlers registered. Abort. */
                if( xFuncHandlers.ucFunctionCode == 0 )
                {
                    break;
                }
                else if( xFuncHandlers.ucFunctionCode == ucFunctionCode )
                {
                    eException = xFuncHandlers.pxHandler( ucMBFrame, &usLength );
                    break;
                }
            }
            /* If the request was not sent to the broadcast address we
             * return a reply. */
            if( ucRcvAddress != MB_ADDRESS_BROADCAST )
            {
                if( eException != MB_EX_NONE )
                {
                    /* An exception occured. Build an error frame. */
                    usLength = 0;
                    ucMBFrame[usLength++] = ( UCHAR )( ucFunctionCode | MB_FUNC_ERROR );
                    ucMBFrame[usLength++] = eException;
                }
                if( ( eMBCurrentMode == MB_ASCII ) && MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS )
                {
                    vMBPortTimersDelay( MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS );
                }               
                eStatus = peMBFrameSendCur( ucMBAddress, ucMBFrame, usLength );
                //插入代码 begin
                if( eStatus == MB_ENOERR ) //no error
                 {
                   xMBRTUTransmitFSM();  //发送一帧数据中的第一个字节,触发发送完成中断
                 }
                                //插入代码 end
            }
            break;
        case EV_FRAME_SENT:
            break;
        }
    }
    return MB_ENOERR;
}
举报

更多回帖

×
20
完善资料,
赚取积分