STM32F7xx —— CAN通信程序

本文详细介绍了STM32F7xx的CAN通信实现,包括CAN基础概念、重要函数、结构体配置、过滤器设置、工作模式、帧类型以及中断处理。还展示了如何设计类似串口的中断接收接口,通过CAN设备结构体封装各路CAN,并统一处理中断。此外,提供了初始化、发送和接收的接口示例。
摘要由CSDN通过智能技术生成

STM32F7xx —— CAN通信,通过前人的经验,本人进行了代码的注释,望高人指点。

一、CAN基础

差分信号:显性电平对应逻辑0,CAN_H和CAN_L差为2.5V;隐形电平对应逻辑1,CAN_H和CAN_L差为0V。

CAN总线的开始和结束都有一个120Ω的终端电阻。

数据帧:标准帧11位, 扩展帧29位。

其他的一些理论知识就不再赘述了,可以参考维基百科对于CAN的描述。

STM32F7xx的bxCAN主要特点:支持CAN2.0A和CAN2.0B,波特率高达1Mbps,支持时间触发,具有3个发送邮箱,2个接收邮箱,可变的过滤器组等。

二、几个重要的CAN函数

HAL_StatusTypeDef HAL_CAN_Init(CAN_HandleTypeDef* hcan); // CAN初始化
 
HAL_StatusTypeDef HAL_CAN_Transmit(CAN_HandleTypeDef *hcan, uint32_t Timeout); // CAN发送
 
HAL_StatusTypeDef HAL_CAN_Receive(CAN_HandleTypeDef *hcan, uint8_t FIFONumber, uint32_t Timeout); // CAN接收

三、几个重要的结构

CAN操作句柄

// CAN操作句柄 包含CAN基地址(CAN1/CAN2/CAN3) 初始化结构 发送接收结构体 其余三个是过程变量
typedef struct
{
  CAN_TypeDef                 *Instance;  /*!< Register base address          */
 
  CAN_InitTypeDef             Init;       /*!< CAN required parameters        */
 
  CanTxMsgTypeDef*            pTxMsg;     /*!< Pointer to transmit structure  */
 
  CanRxMsgTypeDef*            pRxMsg;     /*!< Pointer to reception structure */
 
  __IO HAL_CAN_StateTypeDef   State;      /*!< CAN communication state        */
 
  HAL_LockTypeDef             Lock;       /*!< CAN locking object             */
 
  __IO uint32_t               ErrorCode;  /*!< CAN Error code                 */
 
}CAN_HandleTypeDef;  //CAN句柄类型定义

CAN配置结构体

// CAN配置结构体 
// 前5个参数来设置 CAN_BTR —— 波特率
// 后6个参数用来设置 CAN_MCR —— 通信相关的控制位
typedef struct
{
  uint32_t Prescaler;  /*!< Specifies the length of a time quantum.
                            This parameter must be a number between Min_Data = 1 and Max_Data = 1024 */
 
  uint32_t Mode;       /*!< Specifies the CAN operating mode.
                            This parameter can be a value of @ref CAN_operating_mode */
 
  uint32_t SJW;        /*!< Specifies the maximum number of time quanta
                            the CAN hardware is allowed to lengthen or
                            shorten a bit to perform resynchronization.
                            This parameter can be a value of @ref CAN_synchronisation_jump_width */
 
  uint32_t BS1;        /*!< Specifies the number of time quanta in Bit Segment 1.
                            This parameter can be a value of @ref CAN_time_quantum_in_bit_segment_1 */
 
  uint32_t BS2;        /*!< Specifies the number of time quanta in Bit Segment 2.
                            This parameter can be a value of @ref CAN_time_quantum_in_bit_segment_2 */


/*********************************************************************************************************************************/

 
  uint32_t TTCM;       /*!< Enable or disable the time triggered communication mode.  事件触发模式
                            This parameter can be set to ENABLE or DISABLE. */
 
  uint32_t ABOM;       /*!< Enable or disable the automatic bus-off management.  自动Bus-Off管理
                            This parameter can be set to ENABLE or DISABLE */
 
  uint32_t AWUM;       /*!< Enable or disable the automatic wake-up mode.   自动唤醒模式
                            This parameter can be set to ENABLE or DISABLE */
 
  uint32_t NART;       /*!< Enable or disable the non-automatic retransmission mode.   自动重传
                            This parameter can be set to ENABLE or DISABLE */
 
  uint32_t RFLM;       /*!< Enable or disable the receive FIFO Locked mode.   接收Fifo锁定模式
                            This parameter can be set to ENABLE or DISABLE */
 
  uint32_t TXFP;       /*!< Enable or disable the transmit FIFO priority.    传输Fifo优先级
                            This parameter can be set to ENABLE or DISABLE */
}CAN_InitTypeDef;

过滤器设置

// 过滤器设置
typedef struct
{
  uint32_t FilterIdHigh;          /*!< Specifies the filter identification number (MSBs for a 32-bit   32位ID,高16位
                                       configuration, first one for a 16-bit configuration).
                                       This parameter must be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF */
 
  uint32_t FilterIdLow;           /*!< Specifies the filter identification number (LSBs for a 32-bit   32位ID,低16位
                                       configuration, second one for a 16-bit configuration).
                                       This parameter must be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF */
 
  uint32_t FilterMaskIdHigh;      /*!< Specifies the filter mask number or identification number,    32位MASK,高16位
                                       according to the mode (MSBs for a 32-bit configuration,
                                       first one for a 16-bit configuration).
                                       This parameter must be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF */
 
  uint32_t FilterMaskIdLow;       /*!< Specifies the filter mask number or identification number,    32位MASK,低16位
                                       according to the mode (LSBs for a 32-bit configuration,
                                       second one for a 16-bit configuration).
                                       This parameter must be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF */
 
  uint32_t FilterFIFOAssignment;  /*!< Specifies the FIFO (0 or 1) which will be assigned to the filter.   激活过滤器 指定将分配给过滤器的FIFO(0或1)
                                       This parameter can be a value of @ref CAN_filter_FIFO */
 
  uint32_t FilterNumber;          /*!< Specifies the filter which will be initialized.  指定初始化的过滤器   28个
                                       This parameter must be a number between Min_Data = 0 and Max_Data = 27 */
 
  uint32_t FilterMode;            /*!< Specifies the filter mode to be initialized.     过滤器模式   列表模式和掩码模式
                                       This parameter can be a value of @ref CAN_filter_mode */
 
  uint32_t FilterScale;           /*!< Specifies the filter scale.         过滤器位宽
                                       This parameter can be a value of @ref CAN_filter_scale */
 
  uint32_t FilterActivation;      /*!< Enable or disable the filter.     启用或禁用过滤器
                                       This parameter can be set to ENABLE or DISABLE. */
 
  uint32_t BankNumber;            /*!< Select the start slave bank filter.   选择启动从组过滤器
                                       This parameter must be a number between Min_Data = 0 and Max_Data = 28 */
 
}CAN_FilterConfTypeDef;

CAN工作模式

// 模式  我们使用普通模式
#define CAN_MODE_NORMAL             ((uint32_t)0x00000000U)                     /*!< Normal mode   */    正常
#define CAN_MODE_LOOPBACK           ((uint32_t)CAN_BTR_LBKM)                   /*!< Loopback mode */     环回
#define CAN_MODE_SILENT             ((uint32_t)CAN_BTR_SILM)                   /*!< Silent mode   */     静默
#define CAN_MODE_SILENT_LOOPBACK    ((uint32_t)(CAN_BTR_LBKM | CAN_BTR_SILM))  /*!< Loopback combined with silent mode */   环回静默

CAN标准帧 扩展帧 ID

// 标准帧 扩展帧
#define CAN_ID_STD             ((uint32_t)0x00000000U)  /*!< Standard Id */
#define CAN_ID_EXT             ((uint32_t)0x00000004U)  /*!< Extended Id */

CAN数据帧 远程帧 RTR

// 数据帧 远程帧
#define CAN_RTR_DATA                ((uint32_t)0x00000000U)  /*!< Data frame */
#define CAN_RTR_REMOTE              ((uint32_t)0x00000002U)  /*!< Remote frame */

CAN中断使能

// CAN中断使能
__HAL_CAN_ENABLE_IT(__HANDLE__, __INTERRUPT__) 

接收中断

// 接收中断
#define CAN_IT_FMP0                 ((uint32_t)CAN_IER_FMPIE0)  /*!< FIFO 0 message pending interrupt   FIFO 0消息等待中断 */
#define CAN_IT_FF0                  ((uint32_t)CAN_IER_FFIE0)   /*!< FIFO 0 full interrupt              FIFO 0全中断    */
#define CAN_IT_FOV0                 ((uint32_t)CAN_IER_FOVIE0)  /*!< FIFO 0 overrun interrupt           FIFO 0超限中断  */
#define CAN_IT_FMP1                 ((uint32_t)CAN_IER_FMPIE1)  /*!< FIFO 1 message pending interrupt */
#define CAN_IT_FF1                  ((uint32_t)CAN_IER_FFIE1)   /*!< FIFO 1 full interrupt            */
#define CAN_IT_FOV1                 ((uint32_t)CAN_IER_FOVIE1)  /*!< FIFO 1 overrun interrupt         */

四、接口设计

与串口类似,使用中断接收

与串口类似,使用中断接收。先封装单路CAN需要的几个小接口,再顶一个列表,最后使用统一的接口扫描这个列表。

typedef enum
{
  CAN_CHANNEL_NONE,
  CAN_CHANNEL_1,
  CAN_CHANNEL_2,
  CAN_CHANNEL_NUM
} can_channel_t;
 
#define CAN1_CHANNEL              CAN1
#define CAN1_PREEMPT_PRIO         CAN1_RX_PRIORITY
#define CAN1_RX_IRQ               CAN1_RX0_IRQn
#define CAN1_RX_IRQ_FUNC          CAN1_RX0_IRQHandler
#define CAN1_CLK_ENABLE()         __HAL_RCC_CAN1_CLK_ENABLE()
#define CAN1_TX_PORT              GPIOA
#define CAN1_TX_PIN               GPIO_PIN_12
#define CAN1_TX_AF                GPIO_AF9_CAN1
#define CAN1_TX_CONFIG()          GPIOConfigExt(CAN1_TX_PORT, CAN1_TX_PIN, GPIO_MODE_AF_PP, GPIO_PULLUP, CAN1_TX_AF)
#define CAN1_RX_PORT              GPIOA
#define CAN1_RX_PIN               GPIO_PIN_11
#define CAN1_RX_AF                GPIO_AF9_CAN1
#define CAN1_RX_CONFIG()          GPIOConfigExt(CAN1_RX_PORT, CAN1_RX_PIN, GPIO_MODE_AF_PP, GPIO_PULLUP, CAN1_RX_AF)
 
#define CAN2_CHANNEL              CAN2
#define CAN2_PREEMPT_PRIO         CAN2_RX_PRIORITY
#define CAN2_RX_IRQ               CAN2_RX0_IRQn
#define CAN2_RX_IRQ_FUNC          CAN2_RX0_IRQHandler
#define CAN2_CLK_ENABLE()         __HAL_RCC_CAN2_CLK_ENABLE()
#define CAN2_TX_PORT              GPIOB
#define CAN2_TX_PIN               GPIO_PIN_6
#define CAN2_TX_AF                GPIO_AF9_CAN2
#define CAN2_TX_CONFIG()          GPIOConfigExt(CAN2_TX_PORT, CAN2_TX_PIN, GPIO_MODE_AF_PP, GPIO_PULLUP, CAN2_TX_AF)
#define CAN2_RX_PORT              GPIOB
#define CAN2_RX_PIN               GPIO_PIN_5
#define CAN2_RX_AF                GPIO_AF9_CAN2
#define CAN2_RX_CONFIG()          GPIOConfigExt(CAN2_RX_PORT, CAN2_RX_PIN, GPIO_MODE_AF_PP, GPIO_PULLUP, CAN2_RX_AF)

抽象出一个CAN设备结构体

// 抽象出一个CAN设备结构体
typedef struct
{
  CAN_HandleTypeDef handle; // CAN操作句柄
 
  CanTxMsgTypeDef tx;       // CAN发送
 
  CanRxMsgTypeDef rx;       // CAN接收
 
  can_queue_t recv;         // 接收队列
 
} can_dev_t;

将每路CAN封装成几个小函数

// 将每路CAN封装成几个小函数
static can_dev_t can1_dev, can2_dev;
 
static void can1_var_init(void)   // 静态无返回值函数can1_var_init 无类型
{
  can1_dev.recv = can1_queue_recv;
 
  CanQueueInit(&can1_dev.recv);
}
 
static void can1_gpio_init(void)
{
  CAN1_TX_CONFIG();
  CAN1_RX_CONFIG();
}
 
// 波特率 = Fpclk1 / ((ts1+ts2+3) * brp)  Fpclk1 = 54M

/*#波特率计算公式:

波特率 = 时钟(总线时钟/外设时钟) / (波特率分频器值)*(时间段1 + 时间段2 + 1)
*/

static void can1_mode_init(void)
{
  CAN1_CLK_ENABLE();
 
  can1_dev.handle.Instance = CAN1_CHANNEL;
  can1_dev.handle.pTxMsg = &can1_dev.tx;
  can1_dev.handle.pRxMsg = &can1_dev.rx;
  can1_dev.handle.Init.Prescaler = 6;     // 预分压器
  can1_dev.handle.Init.Mode = CAN_MODE_NORMAL;
  can1_dev.handle.Init.SJW = CAN_SJW_1TQ; // 重新同步跳跃宽度(Tsjw)为tsjw+1个时间单位 CAN_SJW_1TQ~CAN_SJW_4TQ
  can1_dev.handle.Init.BS1 = CAN_BS1_11TQ;// tbs1范围CAN_BS1_1TQ~CAN_BS1_16TQ
  can1_dev.handle.Init.BS2 = CAN_BS2_6TQ; // tbs2范围CAN_BS2_1TQ~CAN_BS2_8TQ
  can1_dev.handle.Init.TTCM = DISABLE;    // 非时间触发通信模式
  can1_dev.handle.Init.ABOM = ENABLE;     // 软件自动离线管理
  can1_dev.handle.Init.AWUM = DISABLE;    // 睡眠模式通过软件唤醒(清除CAN->MCR的SLEEP位)
  can1_dev.handle.Init.NART = ENABLE;     // 禁止报文自动传送
  can1_dev.handle.Init.RFLM = DISABLE;    // 报文不锁定,新的覆盖旧的
  can1_dev.handle.Init.TXFP = DISABLE;    // 优先级由报文标识符决定
  HAL_CAN_Init(&can1_dev.handle);
}
 
static void can1_filter_init(void)
{
  CAN_FilterConfTypeDef  filter;
 
  filter.FilterNumber     = 0; // 过滤器0
  filter.FilterMode       = CAN_FILTERMODE_IDMASK;
  filter.FilterScale      = CAN_FILTERSCALE_32BIT;
 
  filter.FilterIdHigh     = 0;
  filter.FilterIdLow      = 0;
  filter.FilterMaskIdHigh = 0;
  filter.FilterMaskIdLow  = 0;
 
  filter.FilterFIFOAssignment = CAN_FILTER_FIFO0; // 过滤器0关联到FIFO0
  filter.FilterActivation = ENABLE; //激活滤波器0
  //filter.BankNumber=14;
 
  HAL_CAN_ConfigFilter(&can1_dev.handle, &filter);
}
 
static void can1_nvic_init(void)    // can1中断初始化
{
  __HAL_CAN_ENABLE_IT(&can1_dev.handle, CAN_IT_FMP0); //FIFO0消息挂号中断允许.
  HAL_NVIC_SetPriority(CAN1_RX_IRQ, CAN1_RX_PRIORITY, 0);
  HAL_NVIC_EnableIRQ(CAN1_RX_IRQ);
}
 
/**************************************************************************************************************************/ 
 
static void can2_var_init(void)
{
  can2_dev.recv = can2_queue_recv;
 
  CanQueueInit(&can2_dev.recv);
}
 
static void can2_gpio_init(void)
{
  CAN2_TX_CONFIG();
  CAN2_RX_CONFIG();
}
 
// 波特率 = Fpclk1 / ((ts1+ts2+3) * brp)  Fpclk1 = 54M
static void can2_mode_init(void)
{
  CAN2_CLK_ENABLE();
 
  can2_dev.handle.Instance = CAN2_CHANNEL;
  can2_dev.handle.Init.Prescaler = 6;
  can2_dev.handle.Init.Mode = CAN_MODE_LOOPBACK;
  can2_dev.handle.Init.SJW = CAN_SJW_1TQ; // 重新同步跳跃宽度(Tsjw)为tsjw+1个时间单位 CAN_SJW_1TQ~CAN_SJW_4TQ
  can2_dev.handle.Init.BS1 = CAN_BS1_11TQ;// tbs1范围CAN_BS1_1TQ~CAN_BS1_16TQ
  can2_dev.handle.Init.BS2 = CAN_BS2_6TQ; // tbs2范围CAN_BS2_1TQ~CAN_BS2_8TQ
  can2_dev.handle.Init.TTCM = DISABLE;    // 非时间触发通信模式
  can2_dev.handle.Init.ABOM = ENABLE;     // 软件自动离线管理
  can2_dev.handle.Init.AWUM = DISABLE;    // 睡眠模式通过软件唤醒(清除CAN->MCR的SLEEP位)
  can2_dev.handle.Init.NART = ENABLE;     // 禁止报文自动传送
  can2_dev.handle.Init.RFLM = DISABLE;    // 报文不锁定,新的覆盖旧的
  can2_dev.handle.Init.TXFP = DISABLE;    // 优先级由报文标识符决定
  HAL_CAN_Init(&can2_dev.handle);
}
 
static void can2_filter_init(void)
{
  CAN_FilterConfTypeDef  CAN_FilterInitStructure;
 
  CAN_FilterInitStructure.FilterNumber     = 14;
  CAN_FilterInitStructure.FilterMode       = CAN_FILTERMODE_IDMASK;
  CAN_FilterInitStructure.FilterScale      = CAN_FILTERSCALE_32BIT;
 
  CAN_FilterInitStructure.FilterIdHigh     = 0;
  CAN_FilterInitStructure.FilterIdLow      = 0;
  CAN_FilterInitStructure.FilterMaskIdHigh = 0;
  CAN_FilterInitStructure.FilterMaskIdLow  = 0;
 
  CAN_FilterInitStructure.FilterFIFOAssignment = CAN_FILTER_FIFO0; // 过滤器0关联到FIFO0
  CAN_FilterInitStructure.FilterActivation = ENABLE; //激活滤波器0
  //CAN_FilterInitStructure.BankNumber=14;
 
  HAL_CAN_ConfigFilter(&can2_dev.handle, &CAN_FilterInitStructure);
}
 
static void can2_nvic_init(void)
{
  __HAL_CAN_ENABLE_IT(&can2_dev.handle, CAN_IT_FMP0); //FIFO0消息挂号中断允许1
  HAL_NVIC_SetPriority(CAN2_RX_IRQ, CAN2_RX_PRIORITY, 0);
  HAL_NVIC_EnableIRQ(CAN2_RX_IRQ);
}

每路CAN都有的几个操作,使用列表包含,然后统一扫描处理

// 每路CAN都有的几个操作,使用列表包含,然后统一扫描处理
typedef struct
{
  uint8_t channel;
  can_dev_t *dev;
  void (* var_init_cb)(void);
  void (* gpio_init_cb)(void);
  void (* mode_init_cb)(void);
  void (* filter_init_cb)(void);
  void (* nvic_init_cb)(void);
} can_config_t;
 
static const can_config_t can_configs[] =
{
  {CAN_CHANNEL_1, &can1_dev, can1_var_init, can1_gpio_init, can1_mode_init, can1_filter_init, can1_nvic_init},
  {CAN_CHANNEL_2, &can2_dev, can2_var_init, can2_gpio_init, can2_mode_init, can2_filter_init, can2_nvic_init},
};
 
static can_dev_t *can_dev_get(uint8_t channel)
{
  uint8_t i;
 
  for(i = 0; i < ARRAY_SIZE(can_configs); ++i)
  {
    if(channel == can_configs[i].channel)
    {
      return can_configs[i].dev;
    }
  }
 
  return 0;
}
 
static void can_var_init(uint8_t channel)
{
  uint8_t i;
 
  for(i = 0; i < ARRAY_SIZE(can_configs); ++i)
  {
    if(channel == can_configs[i].channel)
    {
      can_configs[i].var_init_cb();
      return;
    }
  }
}
 
static void can_gpio_init(uint8_t channel)
{
  uint8_t i;
 
  for(i = 0; i < ARRAY_SIZE(can_configs); ++i)
  {
    if(channel == can_configs[i].channel)
    {
      can_configs[i].gpio_init_cb();
      return;
    }
  }
}
 
static void can_mode_init(uint8_t channel)
{
  uint8_t i;
 
  for(i = 0; i < ARRAY_SIZE(can_configs); ++i)
  {
    if(channel == can_configs[i].channel)
    {
      can_configs[i].mode_init_cb();
      return;
    }
  }
}
 
static void can_filter_init(uint8_t channel)
{
  uint8_t i;
 
  for(i = 0; i < ARRAY_SIZE(can_configs); ++i)
  {
    if(channel == can_configs[i].channel)
    {
      can_configs[i].filter_init_cb();
      return;
    }
  }
}
 
static void can_nvic_init(uint8_t channel)
{
  uint8_t i;
 
  for(i = 0; i < ARRAY_SIZE(can_configs); ++i)
  {
    if(channel == can_configs[i].channel)
    {
      can_configs[i].nvic_init_cb();
      return;
    }
  }
}

对外的接口 初始化 发送和接收

// 对外的接口 初始化 发送和接收
void CanInit(uint8_t channel)
{
  can_var_init(channel);
  can_gpio_init(channel);
  can_mode_init(channel);
  can_filter_init(channel);
  can_nvic_init(channel);
}
 
void CanSend(uint8_t channel, can_frame_t *frame)
{
  can_dev_t *dev = can_dev_get(channel);
 
  if(dev == 0)
  {
    return;
  }
 
  dev->handle.pTxMsg->StdId = frame->StdId;
  dev->handle.pTxMsg->IDE   = frame->IDE;
  dev->handle.pTxMsg->RTR   = frame->RTR;
  dev->handle.pTxMsg->DLC   = frame->DLC;
  memcpy(dev->handle.pTxMsg->Data, frame->Data, frame->DLC);
 
  HAL_CAN_Transmit(&dev->handle, 10);
}
 
uint8_t CanRecv(uint8_t channel, can_frame_t *frame)
{
  can_dev_t *dev = can_dev_get(channel);
 
  if(dev == 0)
  {
    return 0;
  }
 
  return CanQueueRead(&can1_dev.recv, frame);
}

CAN中断操作

// CAN中断
void CAN1_RX_IRQ_FUNC(void)
{
  HAL_CAN_IRQHandler(&can1_dev.handle);
}
 
void CAN2_RX_IRQ_FUNC(void)
{
  HAL_CAN_IRQHandler(&can2_dev.handle);
}
 
void HAL_CAN_RxCpltCallback(CAN_HandleTypeDef *hcan)
{
  if(hcan == (&can1_dev.handle))
  {
    //CAN_Receive_IT()函数会关闭FIFO0消息挂号中断,因此我们需要重新打开
    __HAL_CAN_ENABLE_IT(&can1_dev.handle, CAN_IT_FMP0);//重新开启FIF00消息挂号中断
    CanQueueWrite(&can1_dev.recv, can1_dev.handle.pRxMsg);
  }
  else if(hcan == (&can2_dev.handle))
  {
    //CAN_Receive_IT()函数会关闭FIFO0消息挂号中断,因此我们需要重新打开
    __HAL_CAN_ENABLE_IT(&can2_dev.handle, CAN_IT_FMP0);//重新开启FIF00消息挂号中断
    CanQueueWrite(&can2_dev.recv, can2_dev.handle.pRxMsg);
  }
}

之所以使用列表的形式,是为了方便增加和删除CAN通道和使对外的接口更统一,不会出现CAN1Init() CAN2Init(),个人习惯问题。

CAN队列可参考:CAN队列

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值