MPU6050传感器应用指南:从基础到实战

1. 引言

随着物联网和智能设备的快速发展,传感器在各种应用中变得愈发重要。MPU6050是一款广泛使用的传感器,它集成了3轴加速度计和3轴陀螺仪,广泛应用于智能手机、无人机、机器人、运动追踪设备等领域。本文将深入介绍MPU6050的工作原理、应用以及如何使用它与微控制器进行数据通信。

2. MPU6050概述

MPU6050是由InvenSense公司推出的一款传感器模块,它结合了一个三轴加速度计和一个三轴陀螺仪。加速度计用于检测物体的线性加速度,陀螺仪则用于测量物体的角速度。这两者的结合,使得MPU6050能够实现精准的运动检测和姿态控制。

MPU6050的主要特点:

  • 加速度计:具有±2g、±4g、±8g、±16g的测量范围,能够精确测量加速度。
  • 陀螺仪:具有±250°/s、±500°/s、±1000°/s、±2000°/s的测量范围,能够测量角速度。
  • 低功耗模式:MPU6050具有多种工作模式,能够在低功耗的状态下工作,适用于嵌入式应用。
  • I2C接口:MPU6050通过I2C接口与主控设备进行通信,便于与微控制器如STM32、Arduino等进行连接。
3. MPU6050工作原理

MPU6050内部有一个三轴加速度计和一个三轴陀螺仪。它们分别通过不同的传感元件工作:

  • 加速度计:通过测量加速度的变化来获取物体的线性加速度。加速度计的原理基于微机电系统(MEMS)技术,通过测量电容变化来感应加速度的变化。
  • 陀螺仪:通过测量角速度来确定物体的旋转角度。陀螺仪内部有一个微小的转子,转子在旋转时会产生相应的电荷变化,这些变化被传感器读取,从而计算出物体的角速度。

MPU6050的数据是通过I2C协议传输的。在通信过程中,微控制器通过读取MPU6050的寄存器获取加速度计和陀螺仪的数值。

4. MPU6050的应用

MPU6050在多个领域中都有广泛的应用,尤其是在运动控制、姿态检测和传感器融合中。以下是一些典型的应用场景:

  • 智能手机:用于实现屏幕自动旋转、步态识别、手势识别等功能。
  • 无人机:在飞行控制中,MPU6050可以实时监测无人机的姿态与加速度,从而保持飞行稳定性。
  • 机器人:通过检测机器人各个部位的运动状态,MPU6050帮助实现精确的姿态控制和运动轨迹规划。
  • 运动追踪设备:用于监测人体的动作,如运动员的步伐、体态等。
5. MPU6050与微控制器的通信

为了与微控制器(如STM32、Arduino)进行通信,MPU6050通过I2C协议与主控设备连接。下面是如何通过I2C与MPU6050进行数据读取的基本步骤。

I2C 通信实现

在 STM32F103C8T6 的 I2C 通信中,我们可以通过手动控制 SCL(时钟线)和 SDA(数据线)来实现。以下代码展示了如何初始化 I2C,并进行数据读写。

I2C 初始化函数

void IIC_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_IIC_PORT, ENABLE);
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;   // 设置为输出模式
    GPIO_InitStructure.GPIO_Pin = IIC_SCL_PIN | IIC_SDA_PIN;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(IIC_SCL_PORT, &GPIO_InitStructure);
    IIC_SCL_HIGH();
    IIC_SDA_HIGH();
}

设置 SDA 为输入模式

void IIC_SDA_IN(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_SDA_PORT, ENABLE);
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;  // 设置为输入模式
    GPIO_InitStructure.GPIO_Pin = IIC_SDA_PIN;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(IIC_SDA_PORT, &GPIO_InitStructure);
    IIC_SDA_HIGH();
}

设置 SDA 为输出模式

void IIC_SDA_OUT(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_SDA_PORT, ENABLE);
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;        // 设置为开漏输出模式
    GPIO_InitStructure.GPIO_Pin = IIC_SDA_PIN;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(IIC_SDA_PORT, &GPIO_InitStructure);
    IIC_SDA_HIGH();
}

启动信号

void IIC_Start(void)
{
    IIC_Init();
    IIC_SDA_HIGH();
    delay;
    IIC_SCL_HIGH();
    delay;
    IIC_SDA_LOW();
    delay;
    IIC_SCL_LOW();
}

停止信号

void IIC_Stop(void)
{
    IIC_Init();
    IIC_SCL_LOW();
    delay;
    IIC_SDA_LOW();
    delay;
    IIC_SCL_HIGH();
    delay;
    IIC_SDA_HIGH();
}

等待 ACK 信号

uint8_t IIC_Wait_Ack(void)
{
    uint8_t ucErrTime = 0;
    IIC_SDA_IN();
    IIC_SDA_HIGH();
    delay;
    IIC_SCL_HIGH();
    delay;

    while(GPIO_ReadInputDataBit(IIC_SDA_PORT, IIC_SDA_PIN))
    {
        ucErrTime++;
        if(ucErrTime > 250)
        {
            IIC_Stop();
            return 1;  // 超时返回错误
        }
    }
    IIC_SCL_LOW();
    return 0;  // 返回 0 表示正常接收到 ACK
}

发送 ACK

void IIC_Ack(void)
{
    IIC_Init();
    IIC_SCL_LOW();
    delay;
    IIC_SDA_HIGH();
    delay;
    IIC_SDA_LOW();
    delay;
    IIC_SCL_HIGH();
    delay;
    IIC_SCL_LOW();
    delay;
    IIC_SDA_LOW();
}

发送 NACK

void IIC_NACK(void)
{
    IIC_Init();
    IIC_SCL_LOW();  // 拉低准备数据
    delay;
    IIC_SDA_HIGH();    // 给一个高电平,表示 NACK
    delay;
    IIC_SCL_HIGH();    // 发送脉冲告诉从机读 NACK
    delay;
    IIC_SCL_LOW();     // 释放时钟
}

发送字节

void IIC_Send_Byte(uint8_t address)
{
    IIC_SDA_OUT();
    for(uint8_t i = 0; i < 8; i++)
    {
        IIC_SCL_LOW();
        delay;
        if(address & 0x80)
            IIC_SDA_HIGH();
        else
            IIC_SDA_LOW();
        delay;
        address <<= 1;
        IIC_SCL_HIGH();
        delay;
    }
    IIC_SCL_LOW();
    delay;
}

读取字节

uint8_t IIC_Read_Byte(u8 ack)
{
    uint8_t receive = 0, i;
    IIC_SDA_IN(); // 设置 SDA 为输入模式
    for (i = 0; i < 8; i++)  
    {
        IIC_SCL_HIGH(); // 拉高 SCL,让从机发送数据
        delay;
        if (GPIO_ReadInputDataBit(IIC_SDA_PORT, IIC_SDA_PIN))
            receive |= (0x80 >> i);  // 读取到 1
        IIC_SCL_LOW();
        delay;
    }
    if (!ack)
        IIC_NACK();
    else
        IIC_Ack();
    return receive;
}

MPU6050 是一款流行的六轴传感器,集成了三轴加速度计和三轴陀螺仪。它通过 I2C 或 SPI 总线进行通信,并具有许多功能寄存器用于配置、控制以及读取传感器数据。下面是一些关键的 MPU6050 寄存器、它们的地址、读写方式以及作用。

1. WHO_AM_I (0x75)

  • 地址: 0x75
  • 读取: 1字节
  • 作用: 这个寄存器用于验证 MPU6050 是否正常工作。读取时,它返回一个固定值 0x68。如果读取到的值与此不同,可能表示连接错误或者硬件故障。

2. PWR_MGMT_1 (0x6B)

  • 地址: 0x6B
  • 读取/写入: 1字节
  • 作用: 该寄存器用于控制 MPU6050 的电源管理功能。它决定了是否启用设备(比如是否关闭传感器或者将其置于休眠模式)。
    常用设置:
    • 0x00: 设备启用,使用默认时钟源。
    • 0x40: 设备进入休眠模式。

3. PWR_MGMT_2 (0x6C)

  • 地址: 0x6C
  • 读取/写入: 1字节
  • 作用: 该寄存器用于进一步配置传感器的电源管理,特别是控制陀螺仪和加速度计的电源。
    常用设置:
    • 0x00: 启用所有传感器(加速度计和陀螺仪)。
    • 0x38: 关闭陀螺仪的输出,仅启用加速度计。

4. CONFIG (0x1A)

  • 地址: 0x1A
  • 读取/写入: 1字节
  • 作用: 用于配置陀螺仪的低通滤波器。该寄存器设置滤波器的带宽,并影响陀螺仪的噪声和响应速度。
    常用设置:
    • 0x00: 不使用低通滤波器,最大带宽。
    • 0x06: 设置带宽为 5Hz,适用于较低的噪声环境。

5. GYRO_CONFIG (0x1B)

  • 地址: 0x1B
  • 读取/写入: 1字节
  • 作用: 配置陀螺仪的量程,决定测量角速度的最大范围。不同的范围选择会影响分辨率。
    常用设置:
    • 0x00: ±250°/s
    • 0x08: ±500°/s
    • 0x10: ±1000°/s
    • 0x18: ±2000°/s

6. ACCEL_CONFIG (0x1C)

  • 地址: 0x1C
  • 读取/写入: 1字节
  • 作用: 配置加速度计的量程。它决定加速度计的最大测量范围。
    常用设置:
    • 0x00: ±2g
    • 0x08: ±4g
    • 0x10: ±8g
    • 0x18: ±16g

7. ACCEL_XOUT_H (0x3B)

  • 地址: 0x3B
  • 读取: 2字节(高字节和低字节)
  • 作用: 读取加速度计在 X 轴上的数据。返回的是 16 位有符号整数,单位是“g”。
    需要注意的是,数据的高字节和低字节需要合并。

8. ACCEL_YOUT_H (0x3D)

  • 地址: 0x3D
  • 读取: 2字节
  • 作用: 读取加速度计在 Y 轴上的数据。

9. ACCEL_ZOUT_H (0x3F)

  • 地址: 0x3F
  • 读取: 2字节
  • 作用: 读取加速度计在 Z 轴上的数据。

10. GYRO_XOUT_H (0x43)

  • 地址: 0x43
  • 读取: 2字节
  • 作用: 读取陀螺仪在 X 轴上的数据。返回的是 16 位有符号整数,单位是“°/s”。

11. GYRO_YOUT_H (0x45)

  • 地址: 0x45
  • 读取: 2字节
  • 作用: 读取陀螺仪在 Y 轴上的数据。

12. GYRO_ZOUT_H (0x47)

  • 地址: 0x47
  • 读取: 2字节
  • 作用: 读取陀螺仪在 Z 轴上的数据。

13. INT_ENABLE (0x38)

  • 地址: 0x38
  • 读取/写入: 1字节
  • 作用: 配置 MPU6050 的中断功能。通过设置不同的位,可以启用或禁用不同类型的中断,例如数据准备中断、FIFO 中断等。

14. INT_STATUS (0x3A)

  • 地址: 0x3A
  • 读取: 1字节
  • 作用: 读取当前的中断状态。它指示哪些中断已被触发。通常用于处理中断和进行相应的动作。

15. FIFO_EN (0x23)

  • 地址: 0x23
  • 读取/写入: 1字节
  • 作用: 配置 MPU6050 的 FIFO 功能,允许将加速度计和陀螺仪的数据写入 FIFO 队列,以便批量读取。

16. USER_CTRL (0x6A)

  • 地址: 0x6A
  • 读取/写入: 1字节
  • 作用: 配置 MPU6050 的控制功能,启用或禁用 FIFO 和其他功能,如串行接口等。
//1读  0写
#define MPU_WHO_AM_I                0x75
#define MPU_ADDR_0  				0x68
#define MPU_ADDR_1  				0x69
#define MPU_READ                    0xD1
#define MPU_WRITE					0xD0
#define MPU_CFG_REG					0X1A	//配置寄存器
#define MPU_GYRO_CFG_REG			0X1B	//陀螺仪配置寄存器
#define MPU_ACCEL_CFG_REG			0X1C	//加速度计配置寄存器
#define MPU_PWR_MGMT1_REG			0X6B	//电源管理寄存器1
#define MPU_PWR_MGMT2_REG			0X6C	//电源管理寄存器2 
#define MPU_SAMPLE_RATE_REG			0X19	//采样频率分频器	
#define MPU_INT_EN_REG				0X38	//中断使能寄存器
#define MPU_USER_CTRL_REG			0X6A	//用户控制寄存器
#define MPU_FIFO_EN_REG				0X23	//FIFO使能寄存器
#define MPU_INTBP_CFG_REG			0X37	//中断/旁路设置寄存器

#define MPU_ACCEL_XOUTH_REG			0X3B	//加速度值,X轴高8位寄存器
#define MPU_ACCEL_XOUTL_REG			0X3C	//加速度值,X轴低8位寄存器
#define MPU_ACCEL_YOUTH_REG			0X3D	//加速度值,Y轴高8位寄存器
#define MPU_ACCEL_YOUTL_REG			0X3E	//加速度值,Y轴低8位寄存器
#define MPU_ACCEL_ZOUTH_REG			0X3F	//加速度值,Z轴高8位寄存器
#define MPU_ACCEL_ZOUTL_REG			0X40	//加速度值,Z轴低8位寄存器

#define MPU_TEMP_OUTH_REG			0X41	//温度值高八位寄存器
#define MPU_TEMP_OUTL_REG			0X42	//温度值低8位寄存器

#define MPU_GYRO_XOUTH_REG			0X43	//陀螺仪值,X轴高8位寄存器
#define MPU_GYRO_XOUTL_REG			0X44	//陀螺仪值,X轴低8位寄存器
#define MPU_GYRO_YOUTH_REG			0X45	//陀螺仪值,Y轴高8位寄存器
#define MPU_GYRO_YOUTL_REG			0X46	//陀螺仪值,Y轴低8位寄存器
#define MPU_GYRO_ZOUTH_REG			0X47	//陀螺仪值,Z轴高8位寄存器
#define MPU_GYRO_ZOUTL_REG			0X48	//陀螺仪值,Z轴低8位寄存器

要读取和写入一个字节到 MPU6050 的寄存器,我们可以通过 I2C 总线进行通信。这里我将展示如何使用之前提供的 I2C 驱动函数来进行字节读取和写入。

1. 写一个字节到 MPU6050

通过 I2C 写操作,将一个字节数据写入到指定的寄存器。

void IIC_Write_Byte(uint8_t reg, uint8_t data)
{
    IIC_Start(); // 启动信号
    IIC_Send_Byte(MPU6050_ADDR << 1); // 发送设备地址 (左移1位,写操作)
    IIC_Wait_Ack(); // 等待ACK

    IIC_Send_Byte(reg); // 发送寄存器地址
    IIC_Wait_Ack(); // 等待ACK

    IIC_Send_Byte(data); // 发送数据字节
    IIC_Wait_Ack(); // 等待ACK

    IIC_Stop(); // 停止信号
}

在这个函数中:

  • MPU6050_ADDR 是 MPU6050 的 I2C 地址 (通常是 0x680x69,取决于地址引脚的连接状态)。
  • reg 是寄存器地址,data 是要写入的数据字节。
  • IIC_Start() 用于发送 I2C 启动信号,IIC_Send_Byte() 用于发送数据,IIC_Wait_Ack() 用于等待每次数据发送后的 ACK 响应。

2. 读一个字节从 MPU6050

通过 I2C 读取一个字节的数据,从指定的寄存器中获取数据。

uint8_t IIC_Read_Byte(uint8_t reg)
{
    uint8_t data;

    IIC_Start(); // 启动信号
    IIC_Send_Byte(MPU6050_ADDR << 1); // 发送设备地址 (左移1位,写操作)
    IIC_Wait_Ack(); // 等待ACK

    IIC_Send_Byte(reg); // 发送寄存器地址
    IIC_Wait_Ack(); // 等待ACK

    IIC_Start(); // 重启信号(从设备读取数据)
    IIC_Send_Byte((MPU6050_ADDR << 1) | 0x01); // 发送设备地址 + 读取操作(右移1位)
    IIC_Wait_Ack(); // 等待ACK

    data = IIC_Read_Byte(0); // 读取数据字节,0 表示返回 NACK,结束读取
    IIC_Stop(); // 停止信号

    return data; // 返回读取的字节
}

在这个函数中:

  • MPU6050_ADDR 是 MPU6050 的 I2C 地址。
  • reg 是我们希望读取的寄存器地址。
  • 在开始读操作之前,我们发送一个启动信号,然后发送设备地址(MPU6050_ADDR << 1),并接着发送寄存器地址。
  • 为了执行读操作,我们再发送一个重启信号,并将设备地址的最低位设置为 1,表示读取操作。
  • 最后,我们使用 IIC_Read_Byte() 函数读取一个字节的数据,并返回该字节。

例子

假设我们要读取 WHO_AM_I 寄存器(地址为 0x75),并检查返回值来验证 MPU6050 是否正常工作:

uint8_t who_am_i;

who_am_i = IIC_Read_Byte(0x75); // 读取 WHO_AM_I 寄存器

if (who_am_i == 0x68) {
    // MPU6050 正常工作
    printf("MPU6050 I2C Address is correct!\n");
} else {
    // 读取错误
    printf("MPU6050 I2C Address is incorrect!\n");
}

MPU6050 的初始化主要包括配置传感器的工作模式、传感器的量程、以及其他配置寄存器。下面是一个简单的 MPU6050 初始化代码示例:

#include "mpu6050.h"

// MPU6050 I2C 地址
#define MPU6050_ADDR 0x68  // 默认地址 (AD0 = 0)

void MPU6050_Init(void)
{
    uint8_t temp;

    // 1. 确保 MPU6050 处于正常工作状态
    IIC_Write_Byte(0x6B, 0x00);  // 将 PWR_MGMT_1 寄存器清零,解除休眠模式
    delay(100);  // 等待 100 毫秒

    // 2. 配置陀螺仪和加速度计的量程
    // 设置陀螺仪的量程(FS_SEL: 0x00 - 250度/秒, 0x08 - 500度/秒, 0x10 - 1000度/秒, 0x18 - 2000度/秒)
    IIC_Write_Byte(0x1B, 0x00);  // 陀螺仪量程设置为 250度/秒
    delay(10);

    // 设置加速度计的量程(AFS_SEL: 0x00 - 2g, 0x08 - 4g, 0x10 - 8g, 0x18 - 16g)
    IIC_Write_Byte(0x1C, 0x00);  // 加速度计量程设置为 2g
    delay(10);

    // 3. 配置数字低通滤波器 (DLF) 和采样率
    IIC_Write_Byte(0x19, 0x07);  // 设置采样率为 1kHz,采样率分频因子为 8
    delay(10);

    // 4. 配置中断
    IIC_Write_Byte(0x38, 0x01);  // 开启数据就绪中断
    delay(10);

    // 5. 读取 WHO_AM_I 寄存器,确认 MPU6050 是否正常连接
    temp = IIC_Read_Byte(0x75);  // 读取 WHO_AM_I 寄存器的值
    if (temp == 0x68) {
        // MPU6050 正常连接
        printf("MPU6050 Initialized Successfully!\n");
    } else {
        // MPU6050 初始化失败
        printf("MPU6050 Initialization Failed. WHO_AM_I = 0x%02X\n", temp);
    }
}

MPU6050 数据获取

在成功初始化 MPU6050 并配置好 I2C 后,我们可以通过读取 MPU6050 的加速度计、陀螺仪和温度数据来进行进一步的应用。

MPU6050_GetData 函数

这个函数用于从 MPU6050 获取加速度计、陀螺仪和温度的数据。通过传入的指针,将获取到的数据存储到相应的变量中。

void MPU6050_GetData(int16_t *AX, int16_t *AY, int16_t *AZ, 
                     int16_t *GX, int16_t *GY, int16_t *GZ, 
                     float *Temp)
{
    // 读取加速度计数据(X轴、Y轴、Z轴)
    *AX = (int16_t)((Read_Byte(MPU_ACCEL_XOUTH_REG, 0) << 8) | Read_Byte(MPU_ACCEL_XOUTL_REG, 0));
    *AY = (int16_t)((Read_Byte(MPU_ACCEL_YOUTH_REG, 0) << 8) | Read_Byte(MPU_ACCEL_YOUTL_REG, 0));
    *AZ = (int16_t)((Read_Byte(MPU_ACCEL_ZOUTH_REG, 0) << 8) | Read_Byte(MPU_ACCEL_ZOUTL_REG, 0));

    // 读取陀螺仪数据(X轴、Y轴、Z轴)
    *GX = (int16_t)((Read_Byte(MPU_GYRO_XOUTH_REG, 0) << 8) | Read_Byte(MPU_GYRO_XOUTL_REG, 0));
    *GY = (int16_t)((Read_Byte(MPU_GYRO_YOUTH_REG, 0) << 8) | Read_Byte(MPU_GYRO_YOUTL_REG, 0));
    *GZ = (int16_t)((Read_Byte(MPU_GYRO_ZOUTH_REG, 0) << 8) | Read_Byte(MPU_GYRO_ZOUTL_REG, 0));

    // 读取温度数据
    *Temp = (int16_t)((Read_Byte(MPU_TEMP_OUTH_REG, 0) << 8) | Read_Byte(MPU_TEMP_OUTL_REG, 0));
    
    // 温度值转换公式,转换为实际的温度值(单位:摄氏度)
    *Temp = *Temp / 340.0 + 36.53;
}

完整主函数示例

在这个主函数中,我们会初始化 IIC 总线、MPU6050,并持续读取传感器数据(加速度计、陀螺仪和温度)。读取到的数据将通过串口输出。

#include "stm32f10x.h"
#include "MPU6050.h"  // 包含相关函数头文件
#include "IIC.h"
#include "usart.h"

int main(void)
{
    // 系统初始化
    SystemInit();
    
    // 初始化 IIC
    IIC_Init();

    // 使能 MPU6050
    MPU6050_Init();

    // 初始化串口
    USART_Init(9600);
    
    // 存储读取到的数据
    int16_t AX, AY, AZ, GX, GY, GZ;
    float Temp;

    // 主循环
    while (1)
    {
        // 读取 MPU6050 数据
        MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ, &Temp);
        
        // 打印加速度计数据(AX, AY, AZ)
        printf("AX: %d, AY: %d, AZ: %d\n", AX, AY, AZ);
        
        // 打印陀螺仪数据(GX, GY, GZ)
        printf("GX: %d, GY: %d, GZ: %d\n", GX, GY, GZ);
        
        // 打印温度数据
        printf("Temperature: %.2f °C\n", Temp);
        
    }
}

结尾

通过本篇文章,我们详细介绍了如何使用 STM32 微控制器与 MPU6050 传感器进行通信,并实现了加速度计、陀螺仪和温度的实时读取。您已经学会了如何初始化 IIC 总线、如何与 MPU6050 进行数据交互,以及如何将数据通过串口输出。希望这些内容对您的项目有所帮助,并为后续更多传感器应用提供了一定的基础。

在实际应用中,您可以根据项目需求扩展该功能,例如通过 LCD 屏幕显示传感器数据,或与其他外设进行通信与控制。此外,MPU6050 作为一个常用的传感器,应用非常广泛,尤其是在运动控制、姿态检测等领域。

效果图示意

为了更直观地了解数据输出的效果,我们提供了以下示意图,展示了通过串口输出的加速度计、陀螺仪和温度数据。

需要源码私信作者

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值