电子类研究生的STM32小白入门 一个定时器 四个channel复用映射 实现超声波四路测距

本文介绍了如何使用STM32的TIM3定时器四个通道复用实现超声波四路测距,避免了使用多个定时器的低效问题。通过配置滴答定时器和捕获中断,实现了非阻塞型延时和超声波模块的精确触发。在捕获回波高电平时间后,计算距离并优化了超声波测距的效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

闲来无事,转眼就要开学了,学长说超声波测距用多个定时器太low了,而且还不让用定时器计时高电平时间的方法,对此学长的建议,我虚心接受,并在他的帮助教学下,完成了一个定时器四个channal复用映射 实现超声波四路测距,极大的优化了超声波测距的方法。鉴于超声波模块的测距原理网上很多,在此不再赘述,网上关于超声波测距的方法也很多,建议阅读此文章。下面进入本篇正片。

本超声波测距方式基于hal库,参考了正点原子的滴答定时器延时函数(非阻塞型延时),进行1ms一次的超声波测距发送触发trig函数(拉高拉低某一I/O电平 时间间隔为40us)

void SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */

  /* USER CODE END SysTick_IRQn 0 */
  HAL_IncTick();
  /* USER CODE BEGIN SysTick_IRQn 1 */
    hcsr04_send();
  /* USER CODE END SysTick_IRQn 1 */
}//1ms 进一次
 

void hcsr04_send()

    //GPIOB->ODR|=1<<5;      //PB.5 Êä³ö¸ß
    //GPIOB->ODR=0x00000010||GPIOB->IDR;
    HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_SET);

    delay_us(40);

    HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_RESET);
    
}

至此,单片机已经能触发超声波模块的trig口,echo口已经能发送回波,我们需要测量超声波模块echo口的回波持续高电平时间。那么我们先完成一路超声波测回波时间,首先我们对于定时器3进行初始化,选择上升沿捕获,配置通道1,开启TIM3捕获通道1,使能中断更新,设置中断优先级,开启ITM3中断通道,使能TIM3时钟,复用推挽输入此channel对应的I/O,下拉,高速模式。打开定时器3中断服务函数。后来发现此通道的定时器捕获通道被LCD屏幕占用,果断选择I/O复用功能,查询数据手册得知STM32F103ZET6     TIM3四个捕获通道可全部复用到PC6至PC9。初始化至此完成。代码为配置四路复用I/O后的。

TIM_HandleTypeDef TIM3_Handler;      //¶¨Ê±Æ÷¾ä±ú 
//TIM_HandleTypeDef TIM4_Handler;      //¶¨Ê±Æ÷¾ä±ú 
//ͨÓö¨Ê±Æ÷3Öжϳõʼ»¯
//arr£º×Ô¶¯ÖØ×°Öµ¡£
//psc£ºÊ±ÖÓÔ¤·ÖƵÊý
//¶¨Ê±Æ÷Òç³öʱ¼ä¼ÆËã·½·¨:Tout=((arr+1)*(psc+1))/Ft us.
//Ft=¶¨Ê±Æ÷¹¤×÷ƵÂÊ,µ¥Î»:Mhz
//ÕâÀïʹÓõÄÊǶ¨Ê±Æ÷3!
void TIM3_Init(u16 arr,u16 psc)
{  
        TIM_IC_InitTypeDef TIM3_CH1Config;  
    
    TIM3_Handler.Instance=TIM3;                          //ͨÓö¨Ê±Æ÷3
    TIM3_Handler.Init.Prescaler=psc;                     //·ÖƵϵÊý
    TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP;    //ÏòÉϼÆÊýÆ÷
    TIM3_Handler.Init.Period=arr;                        //×Ô¶¯×°ÔØÖµ
    TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;//ʱÖÓ·ÖÆµÒò×Ó
    HAL_TIM_IC_Init(&TIM3_Handler);                        //³õʼ»¯ÊäÈë²¶»ñʱ»ù²ÎÊý
    
    TIM3_CH1Config.ICPolarity=TIM_ICPOLARITY_RISING;    //ÉÏÉýÑØ²¶»ñ
    TIM3_CH1Config.ICSelection=TIM_ICSELECTION_DIRECTTI;//Ó³Éäµ½TI1ÉÏ
    TIM3_CH1Config.ICPrescaler=TIM_ICPSC_DIV1;          //ÅäÖÃÊäÈë·ÖƵ£¬²»·ÖƵ
    TIM3_CH1Config.ICFilter=0;                          //ÅäÖÃÊäÈëÂ˲¨Æ÷£¬²»Â˲¨
    HAL_TIM_IC_ConfigChannel(&TIM3_Handler,&TIM3_CH1Config,TIM_CHANNEL_1);//ÅäÖÃTIM3ͨµÀ1
      HAL_TIM_IC_ConfigChannel(&TIM3_Handler,&TIM3_CH1Config,TIM_CHANNEL_2);//ÅäÖÃTIM3ͨµÀ2
      HAL_TIM_IC_ConfigChannel(&TIM3_Handler,&TIM3_CH1Config,TIM_CHANNEL_3);//ÅäÖÃTIM3ͨµÀ3
      HAL_TIM_IC_ConfigChannel(&TIM3_Handler,&TIM3_CH1Config,TIM_CHANNEL_4);//ÅäÖÃTIM3ͨµÀ4
    
    HAL_TIM_IC_Start_IT(&TIM3_Handler,TIM_CHANNEL_1);   //¿ªÆôTIM3µÄ²¶»ñͨµÀ1£¬²¢ÇÒ¿ªÆô²¶»ñÖжÏ
    HAL_TIM_IC_Start_IT(&TIM3_Handler,TIM_CHANNEL_2);   //¿ªÆôTIM3µÄ²¶»ñͨµÀ2£¬²¢ÇÒ¿ªÆô²¶»ñÖжϠ   
    HAL_TIM_IC_Start_IT(&TIM3_Handler,TIM_CHANNEL_3);   //¿ªÆôTIM3µÄ²¶»ñͨµÀ3£¬²¢ÇÒ¿ªÆô²¶»ñÖжÏ
        HAL_TIM_IC_Start_IT(&TIM3_Handler,TIM_CHANNEL_4);   //¿ªÆôTIM3µÄ²¶»ñͨµÀ4£¬²¢ÇÒ¿ªÆô²¶»ñÖжÏ


    __HAL_TIM_ENABLE_IT(&TIM3_Handler,TIM_IT_UPDATE);   //ʹÄܸüÐÂÖжÏ
    
        HAL_NVIC_SetPriority(TIM3_IRQn,2,0);    //ÉèÖÃÖжÏÓÅÏȼ¶£¬ÇÀÕ¼ÓÅÏȼ¶2£¬×ÓÓÅÏȼ¶0
    HAL_NVIC_EnableIRQ(TIM3_IRQn);          //¿ªÆôITM3ÖжÏͨµÀ  
    __HAL_RCC_TIM3_CLK_ENABLE();            //ʹÄÜTIM3ʱÖÓ

  TIM3_Init(65535,72-1);           //¶¨Ê±Æ÷3³õʼ»¯

    HAL_TIM_IC_Start_IT(&TIM3_Handler,TIM_CHANNEL_1);   
    __HAL_TIM_ENABLE_IT(&TIM3_Handler,TIM_IT_UPDATE);   
}


//¶¨Ê±Æ3µ×²ãÇý¶¯£¬Ê±ÖÓʹÄÜ£¬Òý½ÅÅäÖÃ
//´Ëº¯Êý»á±»HAL_TIM_IC_Init()µ÷ÓÃ
//htim:¶¨Ê±Æ÷3¾ä±ú
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
    GPIO_InitTypeDef GPIO_Initure;
    __HAL_RCC_TIM3_CLK_ENABLE();            //ʹÄÜTIM5ʱÖÓ
    __HAL_AFIO_REMAP_TIM3_ENABLE(); //TIM3 ???????????
        __HAL_RCC_GPIOC_CLK_ENABLE();                  //¿ªÆôGPIOBʱÖÓ
        //__HAL_RCC_AFIO_CLK_ENABLE(); //?????? IO ??
    
    GPIO_Initure.Pin=GPIO_PIN_6 | GPIO_PIN_7| GPIO_PIN_8| GPIO_PIN_9;           //PA0
    GPIO_Initure.Mode=GPIO_MODE_AF_INPUT;     //¸´ÓÃÍÆÍìÊäÈë
    GPIO_Initure.Pull=GPIO_PULLDOWN;        //ÏÂÀ­
    GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH;//¸ßËÙ
    HAL_GPIO_Init(GPIOC,&GPIO_Initure);
    
    
    HAL_NVIC_SetPriority(TIM3_IRQn,2,0);    //ÉèÖÃÖжÏÓÅÏȼ¶£¬ÇÀÕ¼ÓÅÏȼ¶2£¬×ÓÓÅÏȼ¶0
    HAL_NVIC_EnableIRQ(TIM3_IRQn);          //¿ªÆôITM5ÖжÏͨµÀ  
}


//¶¨Ê±Æ÷3ÖжϷþÎñº¯Êý
void TIM3_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&TIM3_Handler);                //¶¨Ê±Æ÷¹²Óô¦Àíº¯Êý
}
 

定义四个变量,分别为pre_tim3_count[1],sec_tim3_count[1],flag[1],scr04_count[1],注意,此方法的核心是通过捕获上升沿与下降沿所触发的时刻计数值,记录这两个值,得到高电平时间,也就完成了测距。在初始化中,将捕获定时器的定时触发时间设置为65.5ms溢出,注意,由超声波模块的使用手册可以得知这个捕获时间也就是在trig收到高低电平时间后的时间至下一次trig收到高低电平的时间间隔,理想的一次超声波测距应该是在第一个if判断标志位里的操作,此时测量超声波模块返回高电平的时间恰好在我们定时器计数的65.5ms之内,注意,若在测量超声波模块高电平中的某一时刻定时器发生了溢出,计数清零,那么会出现这个if后的else情况此时我们将定时器捕获方式改为下降沿捕获,此时才能得到回波信号的转换为低电平的时间计数点。

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)//²¶»ñÖжϷ¢ÉúʱִÐÐ
{
    if(htim == &TIM3_Handler)
    {
            
        /*****************³¬Éù²¨Í¨µÀ1 PC6********************************************/
        
        if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
        {
            if(!flag[1])
            {
                //HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,GPIO_PIN_SET);
                pre_tim3_count[1] = __HAL_TIM_GetCounter(&TIM3_Handler);
                __HAL_TIM_SET_CAPTUREPOLARITY(&TIM3_Handler,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_FALLING);
                flag[1] = !flag[1];
            }
            else
                {
                    sec_tim3_count[1] = __HAL_TIM_GetCounter(&TIM3_Handler);
                    __HAL_TIM_SET_CAPTUREPOLARITY(&TIM3_Handler,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_RISING);
                    
                    if(pre_tim3_count[1] > pre_tim3_count[1])
                    {
                        scr04_count[1] = sec_tim3_count[1] - pre_tim3_count[1];
                    }
                    else
                    {
                        scr04_count[1] = 65535 - pre_tim3_count[1] + sec_tim3_count[1]+1;
                    }
                    flag[1] = !flag[1];
                }    
            }
        /*****************³¬Éù²¨Í¨µÀ1 PC6********************************************/        


            
            
        /*****************³¬Éù²¨Í¨µÀ2 PC7********************************************/            
        if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)
        {
            
            if(!flag[2])
            {

                pre_tim3_count[2] = __HAL_TIM_GetCounter(&TIM3_Handler);
                __HAL_TIM_SET_CAPTUREPOLARITY(&TIM3_Handler,TIM_CHANNEL_2,TIM_INPUTCHANNELPOLARITY_FALLING);
                flag[2] = !flag[2];
            }
            else
            {
                sec_tim3_count[2] = __HAL_TIM_GetCounter(&TIM3_Handler);
                __HAL_TIM_SET_CAPTUREPOLARITY(&TIM3_Handler,TIM_CHANNEL_2,TIM_INPUTCHANNELPOLARITY_RISING);
                
                if(pre_tim3_count[2] > pre_tim3_count[2])
                {
                    scr04_count[2] = sec_tim3_count[2] - pre_tim3_count[2];
                }
                else
                {
                    scr04_count[2] = 65535 - pre_tim3_count[2] + sec_tim3_count[2]+1;
                }
                flag[2] = !flag[2];
            }    
        }        
        
        
        if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_3)
        {    
            if(!flag[3])
            {
                pre_tim3_count[3] = __HAL_TIM_GetCounter(&TIM3_Handler);
                __HAL_TIM_SET_CAPTUREPOLARITY(&TIM3_Handler,TIM_CHANNEL_3,TIM_INPUTCHANNELPOLARITY_FALLING);
                flag[3] = !flag[3];
            }
            else
            {
                sec_tim3_count[3] = __HAL_TIM_GetCounter(&TIM3_Handler);
                __HAL_TIM_SET_CAPTUREPOLARITY(&TIM3_Handler,TIM_CHANNEL_3,TIM_INPUTCHANNELPOLARITY_RISING);
                
                if(pre_tim3_count[3] > pre_tim3_count[3])
                {
                    scr04_count[3] = sec_tim3_count[3] - pre_tim3_count[3];
                }
                else
                {
                    scr04_count[3] = 65535 - pre_tim3_count[3] + sec_tim3_count[3]+1;
                }
                flag[3] = !flag[3];
            }    
            }        
        
            
        if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_4)
        {    
            if(!flag[4])
            {
                pre_tim3_count[4] = __HAL_TIM_GetCounter(&TIM3_Handler);
                __HAL_TIM_SET_CAPTUREPOLARITY(&TIM3_Handler,TIM_CHANNEL_4,TIM_INPUTCHANNELPOLARITY_FALLING);
                flag[4] = !flag[4];
            }
            else
            {
                sec_tim3_count[4] = __HAL_TIM_GetCounter(&TIM3_Handler);
                __HAL_TIM_SET_CAPTUREPOLARITY(&TIM3_Handler,TIM_CHANNEL_4,TIM_INPUTCHANNELPOLARITY_RISING);
                
                if(pre_tim3_count[4] > pre_tim3_count[4])
                {
                    scr04_count[4] = sec_tim3_count[4] - pre_tim3_count[4];
                }
                else
                {
                    scr04_count[4] = 65535 - pre_tim3_count[4] + sec_tim3_count[4]+1;
                }
                flag[4] = !flag[4];
            }    
            }        
        }
    }

READ一下就完成啦。

void hcsr04_read()
{
//    if(TIM3CH1_CAPTURE_STA & 0x80)    //
//    {
//        time = TIM3CH1_CAPTURE_STA & 0x3F;    //
//        time *= 65536;        //
//        time += TIM3CH1_CAPTURE_VAL;    //
//        distance = time * 342.62*100/2000000;
//        LCD_ShowxNum(20, 250, distance, 20, 16, 0);
//        TIM3CH1_CAPTURE_STA = 0;
//    }
    
    distance[1] = (uint16_t)((scr04_count[1] * 342.62 * 100.0)/2000000.0);
//    LCD_ShowString(0, 235, 20, 20, 16, "ONE");
//    LCD_ShowxNum(40, 235, distance[1], 6, 16, 0);
    
    distance[2] = (uint16_t)((scr04_count[2] * 342.62 * 100.0)/2000000.0);
//    LCD_ShowString(0, 255, 20, 20, 16, "TWO");
//    LCD_ShowxNum(40, 255, distance[2], 6, 16, 0);    
    
    distance[3] = (uint16_t)((scr04_count[3] * 342.62 * 100.0)/2000000.0);
//    LCD_ShowString(0, 275, 20, 20, 16, "TRE");
//    LCD_ShowxNum(40, 275, distance[3], 6, 16, 0);    
    
    distance[4] = (uint16_t)((scr04_count[4] * 342.62 * 100.0)/2000000.0);
//    LCD_ShowString(0, 295, 20, 20, 16, "FOU");
//    LCD_ShowxNum(40, 295, distance[4], 6, 16, 0);    
}

<think>嗯,用户之前已经问过关于制作单片机小车所需的各个模块和材料,现在他们希望按照之前的要求新增关于STM32单片机的详细介绍。首先,我需要回顾一下之前的回答,看看在核心控制模块部分已经提到了STM32F103C8T6作为进阶方案,但可能不够详细。用户现在想要更深入的信息,可能是在选型、开发环境、硬件设计、编程示例等方面。 接下来,我需要考虑用户可能的背景。他们可能已经有一定的Arduino经验,现在想升级到STM32,可能是因为需要更高的性能或更多的功能。所以需要比较STM32和Arduino的区别,突出STM32的优势,比如更高的主频、更多的外设、更低的功耗等,同时也要提到可能的学习曲线,比如需要更复杂的开发环境和编程知识。 用户的需求可能包括具体的硬件连接示例、开发工具的选择(如STM32CubeIDE或Keil),以及如何将原有的Arduino代码迁移到STM32平台。此外,他们可能关心如何利用STM32的高级功能,比如PWM控制、中断处理、DMA等,来优化小车性能。 在回答时,需要分模块详细说明,比如核心模块选型、开发环境配置、硬件设计要点、编程示例,以及注意事项。同时要提醒用户STM32相比Arduino的优缺点,帮助他们做出明智的选择。可能还需要提供一些实际代码片段,展示如何在STM32实现避障或循迹功能,并强调与Arduino的不同之处,比如寄存器的直接操作或HAL库的使用。 此外,用户可能对电源管理、外设扩展(如编码器、蓝牙/WiFi模块)以及调试工具(如ST-Link)有疑问,这些都需要涵盖。还要注意指出常见的开发难点,比如时钟配置、中断优先级设置,以及如何避免外设冲突,确保系统的稳定性。 最后,总结STM32的优势和适用场景,帮助用户判断是否适合他们的项目需求。可能需要建议如果项目复杂度高,需要实时性或多任务处理,STM32是更好的选择,而简单项目则Arduino更合适。同时,推荐一些学习资源和社区支持,帮助用户顺利过渡到STM32开发。</think>我将针对STM32单片机在智能小车中的应用进行专项补充说明,重点突出其与Arduino方案的差异及优化方向: 一、STM32核心模块选型推荐 1. 基础型号:STM32F103C8T6(蓝色药丸开发板) • Cortex-M3内核@72MHz • 64KB Flash / 20KB SRAM • 3个USART、2个SPI、2个I2C • 16通道12位ADC(1μs转换时间) • 7通道DMA控制器 2. 进阶型号:STM32F407VET6 • Cortex-M4F@168MHz(带FPU) • 512KB Flash / 192KB SRAM • 定时器支持6路PWM互补输出 • 带硬件编码器接口 • 双CAN总线支持 二、专用开发环境配置 1. 开发工具链: • IDE选择:STM32CubeIDE(免费)或 Keil MDK(商业版) • 编程接口:ST-Link V2调试器 • 关键库文件:HAL库/CubeMX配置工具 2. 典型外设配置流程: ``` 1. 使用CubeMX初始化时钟树(设置PLL至72MHz) 2. 配置GPIO模式: - 电机PWM引脚设为TIMx_CHy复用功能 - 传感器输入设为浮空输入模式 3. 启用DMA用于超声波测距数据采集 4. 生成工程代码框架 ``` 三、硬件设计要点 1. 最小系统电路: • 外部晶振:8MHz主频+32.768kHz RTC • 复位电路:10kΩ上拉+100nF电容 • 启动模式:BOOT0接10kΩ下拉电阻 • SWD接口:SWDIO/SWCLK引出 2. 电机驱动优化: ```c // 使用高级定时器产生互补PWM TIM_OC_InitTypeDef sConfigOC; htim1.Init.Prescaler = 71; // 72MHz/(71+1)=1MHz htim1.Init.CounterMode = TIM_COUNTERMODE_UP; htim1.Init.Period = 999; // 1MHz/1000=1kHz PWM sConfigOC.OCMode = TIM_OCMODE_PWM1; sConfigOC.Pulse = 500; // 50%占空比 HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1); ``` 四、关键外设编程示例 1. 超声波驱动优化(使用输入捕获): ```c void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if(htim->Instance == TIM2) { static uint32_t rising = 0; if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) { rising = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING); } else { uint32_t falling = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); distance_cm = (falling - rising) * 0.034 / 2; } } } ``` 2. 多传感器数据融合: ```c typedef struct { uint16_t ultrasonic; // 单位:cm uint8_t infrared[5]; // 0-1状态 int16_t motor_rpm; // 编码器数据 } SensorData_t; void sensorFusionTask(void *argument) { SensorData_t data; while(1) { data.ultrasonic = getUltrasonic(); readInfraredArray(data.infrared); data.motor_rpm = readEncoder(); osMessageQueuePut(sensorQueue, &data, 0, osWaitForever); osDelay(10); // 100Hz采样率 } } ``` 五、系统优化建议 1. 实时操作系统选择: • FreeRTOS内存占用:约6KB RAM • 典型任务划分: - 运动控制任务(优先级最高) - 环境感知任务 - 通信处理任务 - 状态显示任务 2. 硬件加速应用: • 使用DMA传输ADC多通道采样数据 • 定时器编码器模式读取电机转速 • CRC校验无线通信数据包 六、注意事项 1. 开发难点: • 时钟树配置需确保各总线频率不超限 • 中断优先级分组设置(建议使用4位抢占优先级) • GPIO复用功能映射需查手册确认 2. 常见问题解决方案: • PWM输出异常:检查TIMx重映射寄存器(AFIO) • ADC采样值波动:添加RC滤波电路(10kΩ+0.1μF) • 程序卡死:启用看门狗(IWDG),设置1秒超时 七、扩展能力对比 | Arduino UNO | STM32F103C8T6 -----------|-------------|--------------- 循迹响应 | 10-20ms | <1ms PWM精度 | 490Hz/8bit | 20kHz/16bit 多任务处理 | 轮询调度 | 支持抢占式RTOS 通信扩展 | UART×1 | CAN/USB/UART×3 八、推荐学习路径 1. 阶段一:GPIO控制(LED/按键) 2. 阶段二:定时器应用(PWM/输入捕获) 3. 阶段三:ADC多通道采样 4. 阶段四:RTOS多任务开发 5. 阶段五:硬件加速功能开发 升级到STM32后的优势体现: - 可实现毫米级精度的速度闭环控制 - 支持同时处理蓝牙遥控+超声波避障+视觉识别 - 可扩展CAN总线实现多车协同 - 通过DMA降低CPU占用率(典型应用可降至30%以下) 项目成本增量: • STM32核心板:¥15-30 • ST-Link调试器:¥10-20 • 进阶传感器:增加¥50-100 • 开发时间:需额外20小时学习周期
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值