总结两种串口与通信的方式

文章目录


前言

这个暑假参加了电赛,做的是飞机题,因此经常需要使用到串口通信,完成各个模块之间的通信。但是,在使用串口的时候却发现很多的问题,因此这里记录一下一些串口通信方式。主要参考匿名飞控的串口的通信代码。

一、STM32与Openmv之间的串口通信(通信数据长度已知)

1.STM32

首先,先给出匿名飞控之间的串口通信的协议。

 由上图可知,匿名飞控之间的串口通信都是数据长度已知的

下面开始分析代码

首先是串口的配置(STM32F103C8T6)

//USART1
void DrvUart1Init(u32 br_num)
{
    USART_InitTypeDef USART_InitStructure;
    USART_ClockInitTypeDef USART_ClockInitStruct;
    NVIC_InitTypeDef NVIC_InitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;

    USART_StructInit(&USART_InitStructure);

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);	//使能USART1,GPIOA时钟


	//Usart1 NVIC 配置
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器

//    GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_USART3);
//    GPIO_PinAFConfig(GPIOB, GPIO_PinSource11, GPIO_AF_USART3);

	//USART1_TX   GPIOA.9
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
	GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
   
	//USART1_RX	  GPIOA.10初始化
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
	GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10 


    USART_InitStructure.USART_BaudRate = br_num;                            
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;                     
    USART_InitStructure.USART_StopBits = USART_StopBits_1;                         
    USART_InitStructure.USART_Parity = USART_Parity_No;                            
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;                

    USART_ClockInitStruct.USART_Clock = USART_Clock_Disable;     
    USART_ClockInitStruct.USART_CPOL = USART_CPOL_Low;           
    USART_ClockInitStruct.USART_CPHA = USART_CPHA_2Edge;         
    USART_ClockInitStruct.USART_LastBit = USART_LastBit_Disable; 

    USART_Init(USART1, &USART_InitStructure);
    USART_ClockInit(USART1, &USART_ClockInitStruct);

    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

    USART_Cmd(USART1, ENABLE);
}


u8 Tx1Buffer[256];
u8 Tx1Counter = 0;
u8 count1 = 0;
void DrvUart1SendBuf(unsigned char *DataToSend, u8 data_num)
{
    u8 i;
    for (i = 0; i < data_num; i++)
    {
        Tx1Buffer[count1++] = *(DataToSend + i);
    }

    if (!(USART1->CR1 & USART_CR1_TXEIE))
    {
        USART_ITConfig(USART1, USART_IT_TXE, ENABLE); //打开发送中断
    }
}
u8 U1RxDataTmp[100];
u8 U1RxInCnt = 0;
u8 U1RxoutCnt = 0;
void drvU1GetByte(u8 data)
{
	U1RxDataTmp[U1RxInCnt++] = data;
	if(U1RxInCnt >= 100)
		U1RxInCnt = 0;
}
void drvU1DataCheck(void)
{
	while(U1RxInCnt!=U1RxoutCnt)
	{
		U1GetOneByte(U1RxDataTmp[U1RxoutCnt++]);
		if(U1RxoutCnt >= 100)
			U1RxoutCnt = 0;
	}
}
void Usart1_IRQ(void)
{
    u8 com_data;

    if (USART1->SR & USART_SR_ORE) //ORE中断
    {
        com_data = USART1->DR;
    }
    //接收中断
    if (USART_GetITStatus(USART1, USART_IT_RXNE))
    {
        USART_ClearITPendingBit(USART1, USART_IT_RXNE); //清除中断标志
        Res_Data = USART1->DR;
		
        drvU1GetByte(com_data);
    }
    //发送(进入移位)中断
    if (USART_GetITStatus(USART1, USART_IT_TXE))
    {
        USART1->DR = Tx1Buffer[Tx1Counter++]; //写DR清除中断标志
        if (Tx1Counter == count1)
        {
            USART1->CR1 &= ~USART_CR1_TXEIE; //关闭TXE(发送中断)中断
        }
    }
}

void USART1_IRQHandler(void)
{
	Usart1_IRQ();
}

//USART3
void DrvUart3Init(u32 br_num)
{
    USART_InitTypeDef USART_InitStructure;
    USART_ClockInitTypeDef USART_ClockInitStruct;
    NVIC_InitTypeDef NVIC_InitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;

    USART_StructInit(&USART_InitStructure);

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); 
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);


    NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

//    GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_USART3);
//    GPIO_PinAFConfig(GPIOB, GPIO_PinSource11, GPIO_AF_USART3);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);


    USART_InitStructure.USART_BaudRate = br_num;                            
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;                     
    USART_InitStructure.USART_StopBits = USART_StopBits_1;                         
    USART_InitStructure.USART_Parity = USART_Parity_No;                            
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;                

    USART_ClockInitStruct.USART_Clock = USART_Clock_Disable;     
    USART_ClockInitStruct.USART_CPOL = USART_CPOL_Low;           
    USART_ClockInitStruct.USART_CPHA = USART_CPHA_2Edge;         
    USART_ClockInitStruct.USART_LastBit = USART_LastBit_Disable; 

    USART_Init(USART3, &USART_InitStructure);
    USART_ClockInit(USART3, &USART_ClockInitStruct);

    USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);

    USART_Cmd(USART3, ENABLE);
}

u8 Tx3Buffer[256];
u8 Tx3Counter = 0;
u8 count3 = 0;
void DrvUart3SendBuf(unsigned char *DataToSend, u8 data_num)
{
    u8 i;
    for (i = 0; i < data_num; i++)
    {
        Tx3Buffer[count3++] = *(DataToSend + i);
    }
    if (!(USART3->CR1 & USART_CR1_TXEIE))
    {
        USART_ITConfig(USART3, USART_IT_TXE, ENABLE); //打开发送中断
    }
}
u8 U3RxDataTmp[100];
u8 U3RxInCnt = 0;
u8 U3RxoutCnt = 0;
void drvU3GetByte(u8 data)
{
	U3RxDataTmp[U3RxInCnt++] = data;
	if(U3RxInCnt >= 100)
		U3RxInCnt = 0;
}
void drvU3DataCheck(void)
{
	while(U3RxInCnt!=U3RxoutCnt)
	{
		U3GetOneByte(U3RxDataTmp[U3RxoutCnt++]);
		if(U3RxoutCnt >= 100)
			U3RxoutCnt = 0;
	}
}
void Usart3_IRQ(void)
{
    u8 com_data;

    if (USART3->SR & USART_SR_ORE) //ORE中断
        com_data = USART3->DR;

    //接收中断
    if (USART_GetITStatus(USART3, USART_IT_RXNE))
    {
        USART_ClearITPendingBit(USART3, USART_IT_RXNE); //清除中断标志
        com_data = USART3->DR;
		drvU3GetByte(com_data);
    }
    //发送(进入移位)中断
    if (USART_GetITStatus(USART3, USART_IT_TXE))
    {
        USART3->DR = Tx3Buffer[Tx3Counter++]; //写DR清除中断标志
        if (Tx3Counter == count3)
        {
            USART3->CR1 &= ~USART_CR1_TXEIE; //关闭TXE(发送中断)中断
        }
    }
}
void DrvUartDataCheck(void)
{
	drvU3DataCheck();
    drvU1DataCheck();
}
void USART3_IRQHandler(void)
{
	Usart3_IRQ();
}

        需要注意的事,匿名飞控的Usart3_IRQ();Usart1_IRQ();都是封装好的串口处理函数,使用时,必须把它们放到对应的中断函数处理函数void USART3_IRQHandler(void),否则程序会卡死。同时,将void DrvUartDataCheck(void)这个函数需要放入1ms的中断(建议用一个定时器作为系统的定时,1ms进入1次),这样串口收发的数据就会1ms处理一次。

        void DrvUartDataCheck(void)中调用drvU3DataCheck()就是对应串口的数据接收处理的部分。

void drvU3DataCheck(void)
{
	while(U3RxInCnt!=U3RxoutCnt)
	{
		U3GetOneByte(U3RxDataTmp[U3RxoutCnt++]);
		if(U3RxoutCnt >= 100)
			U3RxoutCnt = 0;
	}
}

        drvU3DataCheck()中调用了用户自定义的串口接收函数U3GetOneByte()U3GetOneByte可以通过宏定义修改为自定义函数名。例如下图 

        同时,这个时候自己就可以在另外的.c文件中定义自己的函数接收函数 ,下面以OPENMV_GetOneByte为例。

 

 

 

下面为代码 

/***********接受数据定义区***************/
#define BB_data0 opmv.lt.shape_flag
#define BB_data1 opmv.lt.err_X
#define BB_data2 opmv.lt.shape_cx
#define BB_data3 opmv.lt.err_Dis
#define BB_data4 opmv.lt.ultrasonic_distance
#define BB_data5 opmv.lt.fire_flag
#define BB_data6 opmv.lt.color_flag
#define BB_data7 opmv.lt.y_vel_out//openmv x方向速度
#define BB_data8 opmv.lt.x_vel_out//超声波前后 控制速度


#define CC_data0 opmv.lt.found_flag//光源寻找到的标志位
#define CC_data1 opmv.lt.red_cx
#define CC_data2 opmv.lt.red_cy
#define CC_data3 opmv.lt.green_pid_vel_v
#define CC_data4 opmv.lt.red_pid_vel_v
#define CC_data5 opmv.lt.ultrasonic_distance
#define CC_data6 opmv.lt.ultrasonic_vel_v
#define CC_data7 opmv.lt.y_vel_out//
#define CC_data8 opmv.lt.x_vel_out

#define DD_data0 opmv.lt.start_flag
#define DD_data1 opmv.lt.start_flag2
#define DD_data2 nouse
#define DD_data3 nouse
#define DD_data4 nouse
#define DD_data5 nouse
#define DD_data6 nouse
#define DD_data7 nouse
#define DD_data8 nouse






/***********接受数据处理***************/
static void OPENMV_DataAnl(uint8_t *data, uint8_t len1)
{
	u8 check_sum3 = 0, check_sum4 = 0;
	if (*(data + 3) != (len1 - 6)) //判断数据长度是否正确
		return;
	for (u8 i = 0; i < len1 - 2; i++)
	{
		check_sum3 += *(data + i);
		check_sum4 += check_sum3;
	}
	if ((check_sum3 != *(data + len1 - 2)) || (check_sum4 != *(data + len1 - 1))) //判断sum校验
		return;
	//================================================================================	

	if(*(data + 2) == 0XBB)//底面的openmv
	{		
		BB_data0 = *(data + 4)<<8|*(data + 5);//opmv.lt.shape_flag
		BB_data1 = *(data + 6)<<8|*(data + 7);//opmv.lt.color_flag
		BB_data2 = *(data + 8)<<8|*(data + 9);//opmv.lt.shape_cx
		BB_data3 = *(data + 10)<<8|*(data + 11);//opmv.lt.shape_cy
		BB_data4 = *(data + 12)<<8|*(data + 13);//Nouse	
		BB_data5 = *(data + 14)<<8|*(data + 15);//Nouse		
		BB_data6 = *(data + 16)<<8|*(data + 17);//Nouse	
		BB_data7 = *(data + 18)<<8|*(data + 19);//opmv.lt.shape_cy
		BB_data8 = *(data +20)<<8|*(data + 21);//opmv.lt.shape_cy		
		
	}
	 else if(*(data + 2) == 0XCC)//顶面的openmv
	{
		
		CC_data0 = *(data + 4)<<8|*(data + 5);//Nouse
		CC_data1 = *(data + 6)<<8|*(data + 7);//Nouse
		CC_data2 = *(data + 8)<<8|*(data + 9);//Nouse
//		CC_data3 = *(data + 10)<<8|*(data + 11);//Nouse
//		CC_data4 = *(data + 12)<<8|*(data + 13);//Nouse	
//		CC_data5 = *(data + 14)<<8|*(data + 15);//Nouse		
//		CC_data6 = *(data + 16)<<8|*(data + 17);//Nouse	
		CC_data7 = *(data + 18)<<8|*(data + 19);//Nouse
		CC_data8 = *(data +20)<<8|*(data + 21);//Nouse				
	}	
	 else if(*(data + 2) == 0XDD)//zigbee数传
	{
		
		DD_data0 = *(data + 4);//opmv.lt.start_flag
		DD_data1 = *(data + 5);//Nouse
//		DD_data2 = *(data + 8)<<8|*(data + 9);//Nouse
//		DD_data3 = *(data + 10)<<8|*(data + 11);//Nouse
//		DD_data4 = *(data + 12)<<8|*(data + 13);//Nouse	
//		DD_data5 = *(data + 14)<<8|*(data + 15);//Nouse		
//		DD_data6 = *(data + 16)<<8|*(data + 17);//Nouse	
//		DD_data7 = *(data + 18)<<8|*(data + 19);//Nouse
//		DD_data8 = *(data +20)<<8|*(data + 21);//Nouse				
	}	
}


# define HW_TYPE 0x61
/***********接受数据解析,参考匿名飞控通信协议***************/
void OPENMV_GetOneByte(uint8_t data)
{
	static u8 _data_len = 0, _data_cnt = 0;
	static u8 rxstate = 0;

	if (rxstate == 0 && data == 0xAA)
	{
		rxstate = 1;
		uart3_datatemp[0] = data;
	}
	else if (rxstate == 1 && (data == HW_TYPE || data == HW_ALL))
	{
		rxstate = 2;
		uart3_datatemp[1] = data;
	}
	else if (rxstate == 2)
	{
		rxstate = 3;
		uart3_datatemp[2] = data;
	}
	else if (rxstate == 3 && data < 250)
	{
		rxstate = 4;
		uart3_datatemp[3] = data;
		_data_len = data;
		_data_cnt = 0;
	}
	else if (rxstate == 4 && _data_len > 0)
	{
		_data_len--;
		uart3_datatemp[4 + _data_cnt++] = data;
		if (_data_len == 0)
			rxstate = 5;
	}
	else if (rxstate == 5)
	{
		rxstate = 6;
		uart3_datatemp[4 + _data_cnt++] = data;
	}
	else if (rxstate == 6)
	{
		rxstate = 0;
		uart3_datatemp[4 + _data_cnt] = data;
		//		DT_data_cnt = _data_cnt+5;
		//
		OPENMV_DataAnl(uart3_datatemp, _data_cnt + 5); //
	}
	else
	{
		rxstate = 0;
	}	
}

         void OPENMV_GetOneByte(uint8_t data)就是按照匿名协议进行数据接收,接受完一帧数据后调用OPENMV_DataAnl(uart3_datatemp, _data_cnt + 5)这个函数,进行数据解析,通过自己的宏定义与数据对应上,这里采用宏定义主要是为了与openmv那边的数据位对应上(方便与视觉队友对接),同时在1对多的通信也比较容易区分。

2.OPENMV

from pyb import UART

uart = UART(3,500000)


class Receive(object):
    uart_buf = []
    _data_len = 0
    _data_cnt = 0
    state = 0
R=Receive()


class Ctrl(object):
    Data1 = 0
    Data2 = 0
    Data3 = 0
    Data4 = 0
    Data5 = 0
    Data6 = 0
    Data7 = 0
    Data8 = 0
    Data9 = 0
    Data10 = 0
    IsDebug = 1
    T_ms = 0
Ctr=Ctrl()


def UartSendData(Data):
    if uart.write(Data)==None:
       print('send error')


def ReceiveAnl(data_buf):
    Ctr.Data1=data_buf[2]
    Ctr.Data2=data_buf[3]
    Ctr.Data3=data_buf[4]
    Ctr.Data4=data_buf[5]
    Ctr.Data5=data_buf[6]
    Ctr.Data6=data_buf[7]
    Ctr.Data7=data_buf[8]
    Ctr.Data8=data_buf[9]
    Ctr.Data9=data_buf[10]
    Ctr.Data10=data_buf[11]


def ReceivePrepare(data):
    if R.state==0:
        if data == 0xAA:
            R.uart_buf.append(data)
            R.state = 1
        else:
            R.state = 0
    elif R.state==1:
        if data == 0x32:
            R.uart_buf.append(data)
            R.state = 2
        else:
            R.state = 0
    elif R.state==2:

            R.uart_buf.append(data)
            R.state = 3
    elif R.state==3:

            R.state = 4
            R.uart_buf.append(data)
    elif R.state==4:

            R.uart_buf.append(data)
            R.state = 5
    elif R.state==5:

            R.state = 6
            R.uart_buf.append(data)
    elif R.state==6:

            R.uart_buf.append(data)
            R.state = 7
    elif R.state==7:

            R.state = 8
            R.uart_buf.append(data)
    elif R.state==8:

            R.state = 9
            R.uart_buf.append(data)
    elif R.state==9:

            R.state = 10
            R.uart_buf.append(data)
    elif R.state==10:

            R.state = 11
            R.uart_buf.append(data)
    elif R.state==11:

            R.state = 12
            R.uart_buf.append(data)
    elif R.state==12:
        if data == 0xdd:
            R.state = 0
            R.uart_buf.append(data)
            ReceiveAnl(R.uart_buf)
            R.uart_buf=[]#清空缓冲区,准备下次接收数据
        else:
            R.state = 0
    else:
        R.state = 0


def UartReadBuffer():
    i = 0
    Buffer_size = uart.any()
    while i<Buffer_size:
        ReceivePrepare(uart.readchar())
        i = i + 1



def UserDataPack(data0,data1,data2,data3,data4,data5,data6,data7,data8):
    UserData=bytearray([0xAA,0x61,0xBB,0x00
                        ,data0,data1,data2,data3,data4,data5,data6,data7,data8
                        ,0x00,0x00])
    lens = len(UserData)
    UserData[3] = lens-6;
    i = 0
    sum = 0
    sum1 = 0
    while i<(lens-2):
        sum = sum + UserData[i]
        sum1 = sum1 + sum
        i = i+1
    UserData[lens-2] = sum;
    UserData[lens-1] = sum1;
    return UserData

        这里也是参照匿名openmv的代码,这边的代码就参考上图进行修改。但是注意openmv在接受数据的时候存在丢包的现象,目前还不清楚为什么。飞控这边为了减少处理的压力,把一部分的PID运算放到了Openmv端,但是在初始化PID参数的时候发现经常会有某个PID无法传参,存在丢包现象。因此,为保险起见,飞控在传输数据到openmv时,通常是连续发个几百毫秒

 3.小结

        以上便是数据长度已知的串口通信方式,之后在运用的时候可以参照这种方式修改,调了一个暑假这种方式还是挺稳定的。

void Uart_Send_Str(unsigned char * p)
{
	while(*p)
	{
		Uart_Send_byte(*p++);
	}
}

        值得注意的事,进行串口发送的时候,采用上述代码的方式一旦数据有0便会结束,调用void DrvUart1SendBuf(unsigned char *DataToSend, u8 data_num),这个函数进行发送的时候就不会。

二、STM32与UWB之间的通信(通信数据长度未知)

UWB的通信协议

UWB配置完成后会,自动按照上述例子将数据发送过来,按照字符进行发送,数据不定长。当时比赛的时候是第一次用这个UWB,虽然协议很简单,但是为了读UWB的数据也花费了一个下午的时间。 

1.STM32

       这里也是采用匿名飞控的程序的数据接收,但是数据解析函数有些不相同。

#define UART2_BUFFER_SIZE 100 // 假设uart2_datatemp的大小为100
void UWB_GetOneByte(char data)
{
    static u8 rxstate_2 = 0;
    static u8 i = 0;

    // 增加数据长度检查,防止数据长度超出缓冲区大小
    if (i >= UART2_BUFFER_SIZE - 1)
    {
        // 缓冲区已满,进行错误处理或清空操作
        rxstate_2 = 0;
        i = 0;
        // 这里可以根据实际情况进行处理,比如错误提示、丢弃当前数据等
        return;
    }

    if (rxstate_2 == 0 && data == '[')
    {
        rxstate_2 = 1;
        uart2_datatemp[0] = data;
    }
    else if (rxstate_2 == 1)
    {
        if (data != ']')
        {
            i++;
            uart2_datatemp[i] = data;
        }
        else
        {
            i++;
            uart2_datatemp[i] = data;
            // 增加对数据长度的检查,防止传递错误的数据长度
            if (i + 2 <= UART2_BUFFER_SIZE)
            {
                UWB_Data_Anl(uart2_datatemp, i + 2);
            }
            // 处理完一组数据后,重置接收状态和缓冲区
            rxstate_2 = 0;
            i = 0;
            // 清空缓冲区
//            memset(uart2_datatemp, 0, sizeof(uart2_datatemp));
        }
    }
    else
    {
        // 处理未知状态,重置接收状态和缓冲区
        rxstate_2 = 0;
        i = 0;
        // 清空缓冲区
        //memset(uart2_datatemp, 0, sizeof(uart2_datatemp));
    }
}

          注意这这rxstate_2、i两个变量在接受完成或者数据接收错误的时候都要清0。


 三、总结

        目前这就更新到这里,以后在遇到坑再过来填!

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值