freemodbus移植进STM32(包含HAL库和标准库两种方法)

打印 上一主题 下一主题

主题 1012|帖子 1012|积分 3036

freemodbus移植

基于freemodbus1.6
使用HAL库
软件:stm32cubemx  stm32cubeide
后续会更新标准库的移植。以及rtos下的移植(尽量)
下载freemodbus1.6

这个获取方法网上到处都是,不细说了。
cubemx新建工程

新建工程只列出了与移植freemodbus相关的设置
这里我使用的是485通信,所以额外使能了一个引脚

使能一个定时器,这里我用的是tim2。并且开始定时器2中断

其他设置如下图,参数其实设什么无所谓,因为后面要改的,我们并不用系统的初始化函数。

然后使能一个串口,我这里用的串口1,参数其实设什么无所谓,因为后面要改的,

这里可以把串口1和定时器2的最前面的取消勾选,就不会生成他们的初始化函数,不勾也没有太大关系,因为我们的函数在他之后,会覆盖掉系统的设置。

另外在中断优先级设置中,将串口优先级设置高于定时器2,数字越小越高。

相关的中断处理函数也要生成。

然后就可以generate code!生成代码。
代码修改

首先我们在我们项目的根目录中新建一个freemodbus文件夹,文件夹中再建一个modbus文件夹,一个port文件夹。

把你最开始下载下来的freemodbus中modbus文件夹中的内容复制到你刚才的modbus文件夹中,

把你最开始下载下来的freemodbus中demo/bare路径下的内容全部复制到你刚才的port文件夹中

然后我们进入cubeide,右键项目->属性,配置头文件和源文件路径。

把如图六个头文件路径添加。

把如图最下面两个源文件路径添加。

先在port.h文件中补充这两个宏定义,这是HAL库的全局中断开启、关闭函数。

ok,然后我们修改portserail.c
这两个函数前面的static标志去掉。

vMBPortSerialEnable函数修改如下
  1. void
  2. vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
  3. {
  4.     /* If xRXEnable enable serial receive interrupts. If xTxENable enable
  5.      * transmitter empty interrupts.
  6.      */
  7.         if (xRxEnable)                                                //将串口收发中断和modbus联系起来,下面的串口改为自己使能的串口
  8.                                 {
  9.                                         __HAL_UART_ENABLE_IT(&huart2,UART_IT_RXNE);        //我用的是串口2,故为&huart2
  10.                                         HAL_GPIO_WritePin(EN485_GPIO_Port, EN485_Pin, GPIO_PIN_RESET);//
  11.                                 }
  12.                         else
  13.                                 {
  14.                                         __HAL_UART_DISABLE_IT(&huart2,UART_IT_RXNE);
  15.                                         HAL_GPIO_WritePin(EN485_GPIO_Port, EN485_Pin, GPIO_PIN_SET);//
  16.                                 }
  17.         if (xTxEnable)
  18.                                 {
  19.                                 HAL_GPIO_WritePin(EN485_GPIO_Port, EN485_Pin, GPIO_PIN_SET);//
  20.                                         __HAL_UART_ENABLE_IT(&huart2,UART_IT_TXE);
  21.                                 }//
  22.                         else
  23.                                 {
  24.                                 HAL_GPIO_WritePin(EN485_GPIO_Port, EN485_Pin, GPIO_PIN_RESET);//
  25.                                         __HAL_UART_DISABLE_IT(&huart2,UART_IT_TXE);
  26.                                 }
  27. }
复制代码
串口初始化函数如下
  1. BOOL
  2. xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
  3. {
  4.                         huart2.Instance = USART2;
  5.                     huart2.Init.BaudRate = ulBaudRate;
  6.                     huart2.Init.StopBits = UART_STOPBITS_1;
  7.                     huart2.Init.Mode = UART_MODE_TX_RX;
  8.                     huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  9.                     huart2.Init.OverSampling = UART_OVERSAMPLING_16;
  10.                     switch(eParity)
  11.                     {
  12.                     // 奇校验
  13.                     case MB_PAR_ODD:
  14.                         huart2.Init.Parity = UART_PARITY_ODD;
  15.                         huart2.Init.WordLength = UART_WORDLENGTH_9B;            // 带奇偶校验数据位为9bits
  16.                         break;
  17.                     // 偶校验
  18.                     case MB_PAR_EVEN:
  19.                         huart2.Init.Parity = UART_PARITY_EVEN;
  20.                         huart2.Init.WordLength = UART_WORDLENGTH_9B;            // 带奇偶校验数据位为9bits
  21.                         break;
  22.                     // 无校验
  23.                     default:
  24.                         huart2.Init.Parity = UART_PARITY_NONE;
  25.                         huart2.Init.WordLength = UART_WORDLENGTH_8B;            // 无奇偶校验数据位为8bits
  26.                         break;
  27.                     }
  28.                     return HAL_UART_Init(&huart2) == HAL_OK ? TRUE : FALSE;
  29. }
复制代码
收发字节函数如下
  1. BOOL
  2. xMBPortSerialPutByte( CHAR ucByte )
  3. {
  4.     /* Put a byte in the UARTs transmit buffer. This function is called
  5.      * by the protocol stack if pxMBFrameCBTransmitterEmpty( ) has been
  6.      * called. */
  7.         HAL_GPIO_WritePin(EN485_GPIO_Port, EN485_Pin, GPIO_PIN_SET);//
  8.             if(HAL_UART_Transmit (&huart2 ,(uint8_t *)&ucByte,1,10) != HAL_OK )
  9.                 return FALSE ;//HAL_UART_Transmit最后一位形参为最大发送时间,
  10.                                               //超出改时间退出发送,可能导致485发送失败,可稍微长一点。
  11.             else
  12.                 return TRUE;
  13. }
  14. BOOL
  15. xMBPortSerialGetByte( CHAR * pucByte )
  16. {
  17.     /* Return the byte in the UARTs receive buffer. This function is called
  18.      * by the protocol stack after pxMBFrameCBByteReceived( ) has been called.
  19.      */
  20.            HAL_GPIO_WritePin(EN485_GPIO_Port, EN485_Pin, GPIO_PIN_RESET);
  21.             if(HAL_UART_Receive (&huart2,(uint8_t *)pucByte,1,10) != HAL_OK )
  22.                 return FALSE ;
  23.             else
  24.                 return TRUE;
  25. }
复制代码
然后我们修改porttimer.c
首先依旧去掉这个函数前的static标志,方便之后调用,函数声明和函数实体前的static都要去掉

然后修改这几个函数:
定时器初始化函数
  1. BOOL
  2. xMBPortTimersInit( USHORT usTim1Timerout50us )
  3. {
  4.                         TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  5.                     TIM_MasterConfigTypeDef sMasterConfig = {0};
  6.                     htim2.Instance = TIM2;
  7.                     htim2.Init.Prescaler = 3599;                                                                // 50us记一次数
  8.                     htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  9.                     htim2.Init.Period = usTim1Timerout50us-1;                                        // usTim1Timerout50us * 50即为定时器溢出时间
  10.                     htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  11.                     htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  12.                     if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
  13.                     {
  14.                         return FALSE;
  15.                     }
  16.                     sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  17.                     if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
  18.                     {
  19.                         return FALSE;
  20.                     }
  21.                     sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
  22.                     sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  23.                     if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
  24.                     {
  25.                         return FALSE;
  26.                     }
  27.                     return TRUE;
  28. }
复制代码
然后是定时器开启、关闭、中断服务函数
  1. inline void
  2. vMBPortTimersEnable(  )
  3. {
  4.     /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
  5.                 __HAL_TIM_CLEAR_IT(&htim2,TIM_IT_UPDATE);//避免程序一上电就进入定时器中断
  6.                 __HAL_TIM_ENABLE_IT(&htim2,TIM_IT_UPDATE);
  7.                 __HAL_TIM_SET_COUNTER(&htim2, 0);                // 清空计数器
  8.             __HAL_TIM_ENABLE(&htim2);                                // 使能定时器
  9. }
  10. inline void
  11. vMBPortTimersDisable(  )
  12. {
  13.     /* Disable any pending timers. */
  14.     __HAL_TIM_DISABLE(&htim2);                                // 禁能定时器
  15.         __HAL_TIM_SET_COUNTER(&htim2,0);
  16.         __HAL_TIM_DISABLE_IT(&htim2,TIM_IT_UPDATE);
  17.         __HAL_TIM_CLEAR_IT(&htim2,TIM_IT_UPDATE);
  18. }
  19. /* Create an ISR which is called whenever the timer has expired. This function
  20. * must then call pxMBPortCBTimerExpired( ) to notify the protocol stack that
  21. * the timer has expired.
  22. */
  23. void prvvTIMERExpiredISR( void )
  24. {
  25.     ( void )pxMBPortCBTimerExpired(  );
  26. }
复制代码
最后还有两处修改,有的教程中并没有提到这两处修改,应该是与vMBPortSerialEnable中使用USART_IT_TC还是USART_IT_TXE中断标志有关,如果使用USART_IT_TC中断的话需要添加这两处修改,就我目前使用USART_IT_TXE中断标志的情况下,加上这两处修改也并无问题,待后续研究明白了再更新
  1. //启动第一次发送,进入发送完成中断
  2.            xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur );
  3.            pucSndBufferCur++;  /* next byte in sendbuffer. */
  4.            usSndBufferCount--;
  5.                    //添加代码end
复制代码
  1. //插入代码begin
  2.                 if(eStatus==MB_ENOERR)
  3.                 {
  4.                         xMBRTUTransmitFSM();  //发送一帧数据中第一个字节出发发送完成中断
  5.                 }
  6.                 //插入代码end
复制代码


在系统的中断处理.c中添加以下的函数声明,有些教程是自己写的中断处理,这里我们还是用系统自己的。

定时器中断处理函数:
  1. void TIM2_IRQHandler(void)
  2. {
  3.   /* USER CODE BEGIN TIM2_IRQn 0 */
  4.           HAL_NVIC_ClearPendingIRQ(TIM2_IRQn);
  5.   /* USER CODE END TIM2_IRQn 0 */
  6.   HAL_TIM_IRQHandler(&htim2);
  7.   /* USER CODE BEGIN TIM2_IRQn 1 */
  8.   /* USER CODE END TIM2_IRQn 1 */
  9. }
复制代码
串口中断函数:
  1. void USART2_IRQHandler(void)
  2. {
  3.   /* USER CODE BEGIN USART2_IRQn 0 */
  4.           if(__HAL_UART_GET_IT_SOURCE(&huart2, UART_IT_RXNE)!= RESET)
  5.                           {
  6.                                   prvvUARTRxISR();//接收中断
  7.                           }
  8.                   if(__HAL_UART_GET_IT_SOURCE(&huart2, UART_IT_TXE)!= RESET)
  9.                           {
  10.                                   prvvUARTTxReadyISR();//发送中断
  11.                           }
  12.             HAL_NVIC_ClearPendingIRQ(USART2_IRQn);
  13.   /* USER CODE END USART2_IRQn 0 */
  14.   HAL_UART_IRQHandler(&huart2);
  15.   /* USER CODE BEGIN USART2_IRQn 1 */
  16.   /* USER CODE END USART2_IRQn 1 */
  17. }
复制代码
在文档末尾user code 代码段添加定时器中断回调函数:
  1. /* USER CODE BEGIN 1 */
  2. void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
  3. {
  4.   /* NOTE : This function Should not be modified, when the callback is needed,
  5.             the __HAL_TIM_PeriodElapsedCallback could be implemented in the user file
  6.    */
  7.         if(htim->Instance == TIM2)
  8.         {
  9.                 prvvTIMERExpiredISR( );
  10.         }
  11. }
  12. /* USER CODE END 1 */
复制代码
回到main函数


首先包含几个头文件
在Private define段添加如下:
  1. /* Private define ------------------------------------------------------------*/
  2. /* USER CODE BEGIN PD */
  3. //输入寄存器起始地址
  4. #define REG_INPUT_START       0x0001
  5. //输入寄存器数量
  6. #define REG_INPUT_NREGS       8
  7. //保持寄存器起始地址
  8. #define REG_HOLDING_START     0x0001
  9. //保持寄存器数量
  10. #define REG_HOLDING_NREGS     8
  11. //线圈起始地址
  12. #define REG_COILS_START       0x0001
  13. //线圈数量
  14. #define REG_COILS_SIZE        16
  15. //离散寄存器起始地址
  16. #define REG_DISCRETE_START    0x0001
  17. //离散寄存器数量
  18. #define REG_DISCRETE_SIZE     16
  19. /* USER CODE END PD */
复制代码
Private variables段添加如下:
  1. /* Private variables ---------------------------------------------------------*/
  2. /* USER CODE BEGIN PV */
  3. //输入寄存器内容
  4. uint16_t usRegInputBuf[REG_INPUT_NREGS] = {0x1000,0x1001,0x1002,0x1003,0x1004,0x1005,0x1006,0x1007};
  5. //输入寄存器起始地址
  6. uint16_t usRegInputStart = REG_INPUT_START;
  7. //保持寄存器内容
  8. uint32_t usRegHoldingBuf[REG_HOLDING_NREGS] = {0x147b,0x3f8e,0x147b,0x400e,0x1eb8,0x4055,0x147b,0x408e};
  9. //保持寄存器起始地址
  10. uint16_t usRegHoldingStart = REG_HOLDING_START;
  11. //线圈状态
  12. uint8_t ucRegCoilsBuf[REG_COILS_SIZE / 8] = {0x01,0x02};
  13. //离散输入状态
  14. uint8_t usRegDiscreteBuf[REG_DISCRETE_SIZE / 8] = {0x01,0x02};
  15. uint8_t testfalg=0;
  16. extern unsigned char NUM [];
  17. /* USER CODE END PV */
复制代码
Private function prototypes段添加如下:
  1. /* USER CODE BEGIN PFP */
  2. /**
  3. * @Brief : 读输入寄存器处理函数,功能码04
  4. * @param  pucRegBuffer    保存输入寄存器值的缓存
  5. * @param  usAddress       寄存器地址
  6. * @param  usNRegs         读取个数
  7. * @return?eMBErrorCode?
  8. */
  9. eMBErrorCode
  10. eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
  11. {
  12.     eMBErrorCode    eStatus = MB_ENOERR;
  13.     int             iRegIndex;
  14.     if( ( usAddress >= REG_INPUT_START )\
  15.         && ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) )
  16.     {
  17.         iRegIndex = ( int )( usAddress - usRegInputStart );
  18.         while( usNRegs > 0 )
  19.         {
  20.             *pucRegBuffer++ = ( UCHAR )( usRegInputBuf[iRegIndex] >> 8 );
  21.             *pucRegBuffer++ = ( UCHAR )( usRegInputBuf[iRegIndex] & 0xFF );
  22.             iRegIndex++;
  23.             usNRegs--;
  24.         }
  25.     }
  26.     else
  27.     {
  28.         eStatus = MB_ENOREG;
  29.     }
  30.     return eStatus;
  31. }
  32. /**
  33. * @Brief : 读保持寄存器处理函数,功能码03
  34. * @param  pucRegBuffer
  35. * @param  usAddress
  36. * @param  usNRegs
  37. * @param  eMode
  38. * @return?eMBErrorCode?
  39. */
  40. eMBErrorCode
  41. eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
  42. {
  43.         eMBErrorCode    eStatus = MB_ENOERR;
  44.         int             iRegIndex;
  45.         if((usAddress >= REG_HOLDING_START)&&\
  46.                 ((usAddress+usNRegs) <= (REG_HOLDING_START + REG_HOLDING_NREGS)))
  47.         {
  48.                 iRegIndex = (int)(usAddress - usRegHoldingStart);
  49.                 switch(eMode)
  50.                 {
  51.                         case MB_REG_READ://�??? MB_REG_READ = 0
  52.                 while(usNRegs > 0)
  53.                                 {
  54.                                         *pucRegBuffer++ = (uint8_t)(usRegHoldingBuf[iRegIndex] >> 8);
  55.                                         *pucRegBuffer++ = (uint8_t)(usRegHoldingBuf[iRegIndex] & 0xFF);
  56.                                   iRegIndex++;
  57.                                   usNRegs--;
  58.                                 }
  59.                 break;
  60.                         case MB_REG_WRITE://�??? MB_REG_WRITE = 1
  61.                         while(usNRegs > 0)
  62.                                 {
  63.                                         usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
  64.                                   usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
  65.                                   iRegIndex++;
  66.                                   usNRegs--;
  67.                         }
  68.                 }
  69.         }
  70.         else//错误
  71.         {
  72.                 eStatus = MB_ENOREG;
  73.         }
  74.         return eStatus;
  75. }
  76. /**
  77.   *****************************************************************************
  78.   * @Name   : 操作线圈
  79.   *
  80.   * @Brief  : 对应功能�???0x01 -> eMBFuncReadCoils
  81.   *                    0x05 -> eMBFuncWriteCoil
  82.   *                    0x15 -> 写多个线�??? eMBFuncWriteMultipleCoils
  83.   *
  84.   * @Input  : *pucRegBuffer:数据缓冲区,响应主机用
  85.   *           usAddress:     寄存器地�???
  86.   *           usNRegs:       操作寄存器个�???
  87.   *           eMode:         功能�???
  88.   *
  89.   * @Output : none
  90.   *
  91.   * @Return : Modbus状�?�信�???
  92.   *****************************************************************************
  93. **/
  94. eMBErrorCode eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
  95. {
  96.         eMBErrorCode        eStatus = MB_ENOERR;
  97.         int                         iNCoils = ( int )usNCoils;
  98.         unsigned short        usBitOffset;
  99.         /* Check if we have registers mapped at this block. */
  100.         if( ( usAddress >= REG_COILS_START ) &&        ( usAddress + usNCoils <= REG_COILS_START + REG_COILS_SIZE ) )
  101.         {
  102.                 usBitOffset = ( unsigned short )( usAddress - REG_COILS_START );
  103.                 switch ( eMode )
  104.                 {
  105.                         /* Read current values and pass to protocol stack. */
  106.                         case MB_REG_READ:
  107.                         while( iNCoils > 0 )
  108.                         {
  109.                                 *pucRegBuffer++ = xMBUtilGetBits( ucRegCoilsBuf, usBitOffset,        ( unsigned char )( iNCoils > 8 ? 8 : iNCoils ) );
  110.                                 iNCoils -= 8;
  111.                                 usBitOffset += 8;
  112.                         }
  113.                         break;
  114.                                 /* Update current register values. */
  115.                         case MB_REG_WRITE:
  116.                         while( iNCoils > 0 )
  117.                         {
  118.                                 xMBUtilSetBits( ucRegCoilsBuf, usBitOffset,        ( unsigned char )( iNCoils > 8 ? 8 : iNCoils ),        *pucRegBuffer++ );
  119.                                 iNCoils -= 8;
  120.                                 usBitOffset += 8;
  121.                         }
  122.                         break;
  123.                 }
  124.         }
  125.         else
  126.         {
  127.                 eStatus = MB_ENOREG;
  128.         }
  129.         return eStatus;
  130. }
  131. /**
  132.   *****************************************************************************
  133.   * @Name   : 操作离散寄存�???
  134.   *
  135.   * @Brief  : 对应功能�???0x02 -> eMBFuncReadDiscreteInputs
  136.   *
  137.   * @Input  : *pucRegBuffer:数据缓冲区,响应主机用
  138.   *           usAddress:     寄存器地�???
  139.   *           usNRegs:       操作寄存器个�???
  140.   *
  141.   * @Output : none
  142.   *
  143.   * @Return : Modbus状�?�信�???
  144.   *****************************************************************************
  145. **/
  146. //eMBErrorCode eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
  147. //{
  148. //        pucRegBuffer = pucRegBuffer;
  149. //        return MB_ENOREG;
  150. //}
  151. eMBErrorCode
  152. eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
  153. {
  154.     eMBErrorCode    eStatus = MB_ENOERR;
  155.     short           iNDiscrete = ( short )usNDiscrete;
  156.     USHORT  usBitOffset;
  157.     /* Check if we have registers mapped at this  block. */
  158.     if( ( usAddress >= REG_DISCRETE_START ) && ( usAddress + usNDiscrete <= REG_DISCRETE_START + REG_DISCRETE_SIZE ) )
  159.     {
  160.         usBitOffset = ( USHORT )( usAddress - REG_DISCRETE_START );
  161.         while( iNDiscrete > 0 )
  162.         {
  163.             *pucRegBuffer++ =
  164.             xMBUtilGetBits( usRegDiscreteBuf, usBitOffset,( UCHAR )( iNDiscrete > 8 ? 8 : iNDiscrete ) );
  165.             iNDiscrete -= 8;
  166.             usBitOffset += 8;
  167.         }
  168.     }
  169.     else
  170.     {
  171.         eStatus = MB_ENOREG;
  172.     }
  173.     return eStatus;
  174. }
  175. /* USER CODE END PFP */
复制代码
新建变量estatus  前后说的这些添加内容必须夹在user code字段中,否则cubemx修改工程重新生成代码后,你修改的内容会消失

初始化、使能

因为前面在串口初始化中,串口选择是被我写死的,不能通过这里的第三个形参去选择使用串口几,但是通过简单修改可以实现自由配置
最后在main函数中启动轮询就可以了

最后你把想传出去的数据放在各个寄存器中,外部modbus主机就可以查询到你放入寄存器的数据了
  1. /* Private variables ---------------------------------------------------------*/
  2. /* USER CODE BEGIN PV */
  3. //输入寄存器内容
  4. uint16_t usRegInputBuf[REG_INPUT_NREGS] = {0x1000,0x1001,0x1002,0x1003,0x1004,0x1005,0x1006,0x1007};
  5. //输入寄存器起始地址
  6. uint16_t usRegInputStart = REG_INPUT_START;
  7. //保持寄存器内容
  8. uint32_t usRegHoldingBuf[REG_HOLDING_NREGS] = {0x147b,0x3f8e,0x147b,0x400e,0x1eb8,0x4055,0x147b,0x408e};
  9. //保持寄存器起始地址
  10. uint16_t usRegHoldingStart = REG_HOLDING_START;
  11. //线圈状态
  12. uint8_t ucRegCoilsBuf[REG_COILS_SIZE / 8] = {0x01,0x02};
  13. //离散输入状态
  14. uint8_t usRegDiscreteBuf[REG_DISCRETE_SIZE / 8] = {0x01,0x02};
  15. uint8_t testfalg=0;
  16. extern unsigned char NUM [];
  17. /* USER CODE END PV */
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

惊落一身雪

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表