Android驱动模型(kernel-hal-framework-app)

读书的时候有写博客的习惯,后面就再也没写过了,发现很多知识点整理在电脑上容易丢失,也不能共享,所以今天又拿起笔开始写博客了,这篇文章的内容是基于兆芯平台的Android架构,实现了一个APP调用hal层来控制导光板的灯光效果。
Android上层平台和底层通信有两种模型: 1.NDK模型(不常用); 2.框架模型
Android驱动开发模型
Android系统使用JNI的原因有:
1、代码保护,Java程序很容易被反编译,C/C++反汇编难度大
2、可以很方便的使用开源库,大部分开源库都是C/C++编写的
3、执行效率的问题,将高执行效率的代码段用C/C++编写
4、Java在文件操作方面,找不到相应的API

1、NDK模型

由内核级别的驱动程序和APP组成,内核级别的驱动程序对外的接口是ioctl,而APP是由JAVA语言写的,JAVA语言并没有(或者并不支持)ioctl接口,所以内核级别的驱动程序和APP是不能直接连接(数据交换),那么就在中间增加了一层C/C++,这层C/C++支持ioctl接口,可以和内核级别的驱动程序连接,而APP的JAVA程序又可以调用C/C++,这样就连接起来了,JAVA通过JNI调用C/C++(JNI:java native interface是专门给java程序调用c/c++提供一种程序调用方法)。通过NDK这个工具将APP和第三方的一些库(C/C++)打包成apk安装包。存在问题:内核级别的驱动程序需要遵循GPL协议,而GPL协议需要开源,但是有些厂家的内核由于包含商业信息,故不完全开源,即一部分开源,一部分不开源,这样就有了框架模型。
Android驱动模型1-NDK驱动模型
NDK驱动程序设计步骤

2、框架模型

开源的一部分是需要遵循GPL协议的,放在内核里面,不开源的一部分就不能放在内核里面,故放在Android系统里面,即HAL,硬件抽象层,与硬件相关的代码,是不想开源的。 在硬件抽象层上面又加了一层硬件服务层,把封装好的硬件服务注册到Android系统中,那么APP通过service manager找到硬件服务层,硬件服务层找到硬件抽象层,硬件抽象层找到驱动程序。
Android驱动模型2-框架模型
框架设计模型设计总共分为4步:

  1. 第一步:是内核级的驱动开发,就是封装Linux驱动方法供hal层调用。
  2. 第二步:设计硬件抽象层HAL程序,下图是设计步骤

框架模型设计步骤

下面是具体的实现代码:
  • sn3199_hal.c (相对android源码路径:hardware/libhardware/modules/sn3199/)
#include <cutils/log.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/types.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <hardware/sn3199_hal.h>
#define LOG_TAG "sn3199_hal_default"

//全局变量
uchar s,t1,t2,flag,N;
uchar mode=2;
int i2c_fd = -1;
const unsigned char PWM64[64]=
{
        0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,
        0x08,0x09,0x0b,0x0d,0x0f,0x11,0x13,0x16,
        0x1a,0x1c,0x1d,0x1f,0x22,0x25,0x28,0x2e,
        0x34,0x38,0x3c,0x40,0x44,0x48,0x4b,0x4f,  
        0x55,0x5a,0x5f,0x64,0x69,0x6d,0x72,0x77,
        0x7d,0x80,0x88,0x8d,0x94,0x9a,0xa0,0xa7,
        0xac,0xb0,0xb9,0xbf,0xc6,0xcb,0xcf,0xd6,
        0xe1,0xe9,0xed,0xf1,0xf6,0xfa,0xfe,0xff
};
//adau1761单字节写入函数
void single_world_write(unsigned char chip_addr, int i2c_fd, unsigned short reg, unsigned char val)
{
        struct i2c_rdwr_ioctl_data light_guide_plate_data;
        light_guide_plate_data.msgs = (struct i2c_msg *)calloc(1, sizeof(struct i2c_msg));
        if(!light_guide_plate_data.msgs)
        {
                perror("light_guide_plate_data.msgs calloc error:\n");
                return;
        }
        //构造写入到adau1761单个寄存器中的消息
        light_guide_plate_data.nmsgs = 1;
        (light_guide_plate_data.msgs[0]).len = 2; //写入数据的长度为3
        (light_guide_plate_data.msgs[0]).addr = chip_addr;
        (light_guide_plate_data.msgs[0]).flags = 0;
        (light_guide_plate_data.msgs[0]).buf = (unsigned char *)calloc(1,2);
        (light_guide_plate_data.msgs[0]).buf[0] = reg;
        (light_guide_plate_data.msgs[0]).buf[1] = val;
        //使用ioctl写入数据
        ioctl(i2c_fd, I2C_RDWR, (unsigned long)&light_guide_plate_data);
        return;
}
//adau1761多字节写入函数
void burst_model_write(unsigned char chip_addr, int i2c_fd, unsigned short reg, unsigned char *data, unsigned int data_len)
{
        int i;
        struct i2c_rdwr_ioctl_data light_guide_plate_data;
        light_guide_plate_data.msgs = (struct i2c_msg *)calloc(1, sizeof(struct i2c_msg));
        if(!light_guide_plate_data.msgs)
        {
                perror("light_guide_plate_data.msgs calloc error:\n");
                exit(-1);
        }
        light_guide_plate_data.nmsgs = 1;
        (light_guide_plate_data.msgs[0]).len = data_len + 2; //写入数据的长度为3
        (light_guide_plate_data.msgs[0]).addr = chip_addr;
        (light_guide_plate_data.msgs[0]).flags = 0;
        (light_guide_plate_data.msgs[0]).buf = (unsigned char *)calloc(1,data_len + 1);
        (light_guide_plate_data.msgs[0]).buf[0] = reg;
        for(i = 0; i < data_len; i++)
        {
                (light_guide_plate_data.msgs[0]).buf[i + 1] = data[i];
        }
        //使用ioctl写入数据
        ioctl(i2c_fd, I2C_RDWR, (unsigned long)&light_guide_plate_data);
        return;
}
//adau1761单字节读出函数
unsigned char single_world_read(unsigned char chip_addr, int i2c_fd, unsigned short reg)
{

        struct i2c_rdwr_ioctl_data light_guide_plate_data;
        light_guide_plate_data.msgs = (struct i2c_msg *)calloc(2, sizeof(struct i2c_msg));
        if(!light_guide_plate_data.msgs)
        {
                perror("light_guide_plate_data.msgs calloc error:\n");
                exit(-1);
        }
        //构造从adau1761单个寄存器读数据的消息
        //先写偏移地址
        light_guide_plate_data.nmsgs = 2;
        (light_guide_plate_data.msgs[0]).len = 1;
        (light_guide_plate_data.msgs[0]).addr = chip_addr;
        (light_guide_plate_data.msgs[0]).flags = 0x0;
        (light_guide_plate_data.msgs[0]).buf = (unsigned char *)calloc(1,1);
        (light_guide_plate_data.msgs[0]).buf[0] = reg;
        (light_guide_plate_data.msgs[1]).len = 1;
        (light_guide_plate_data.msgs[1]).addr = chip_addr;
        (light_guide_plate_data.msgs[1]).flags = I2C_M_RD;
        (light_guide_plate_data.msgs[1]).buf = (unsigned char *)calloc(1,2);
        //使用ioctl读取数据
        ioctl(i2c_fd, I2C_RDWR, (unsigned long)&light_guide_plate_data);
        printf("buf[0] = 0x%x\n",(light_guide_plate_data.msgs[1]).buf[0]);
        return (light_guide_plate_data.msgs[1]).buf[0];
}
//adau1761多字节读出函数
unsigned char * burst_model_read(unsigned char chip_addr, int i2c_fd, unsigned short reg, unsigned int data_len)
{
        struct i2c_rdwr_ioctl_data light_guide_plate_data;
        light_guide_plate_data.msgs = (struct i2c_msg *)calloc(2, sizeof(struct i2c_msg));
        if(!light_guide_plate_data.msgs)
        {
                perror("light_guide_plate_data.msgs calloc error:\n");
                exit(-1);
        }
        //构造从adau1761单个寄存器读数据的消息
        //先写偏移地址
        light_guide_plate_data.nmsgs = 2;
        (light_guide_plate_data.msgs[0]).len = 2;
        (light_guide_plate_data.msgs[0]).addr = chip_addr;
        (light_guide_plate_data.msgs[0]).flags = 0x0;
        (light_guide_plate_data.msgs[0]).buf = (unsigned char *)calloc(1,1);
        (light_guide_plate_data.msgs[0]).buf[0] = reg;
        (light_guide_plate_data.msgs[1]).len = data_len;
        (light_guide_plate_data.msgs[1]).addr = chip_addr;
        (light_guide_plate_data.msgs[1]).flags = I2C_M_RD;
        (light_guide_plate_data.msgs[1]).buf = (unsigned char *)calloc(1,data_len);
        //使用ioctl读取数据
        ioctl(i2c_fd, I2C_RDWR, (unsigned long)&light_guide_plate_data);
        return (light_guide_plate_data.msgs[1]).buf;
}

int reset()
{

        single_world_write(CHIP_ADDR1, i2c_fd, 0xff,0xff);
        single_world_write(CHIP_ADDR1, i2c_fd, 0x00,0x01);         //enable sd chip1
        single_world_write(CHIP_ADDR1, i2c_fd, 0x01,0x77);
        single_world_write(CHIP_ADDR1, i2c_fd, 0x02,0x07);
        single_world_write(CHIP_ADDR1, i2c_fd, 0x04,0x00);



        single_world_write(CHIP_ADDR2, i2c_fd, 0xff,0xff);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x00,0x01);         //enable sd chip1
        single_world_write(CHIP_ADDR2, i2c_fd, 0x01,0x77);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x02,0x07);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x04,0x00);

        return 0;
}

/************************************************/
//设置LED 电流大小
//0= 20mA , 1 = 15mA, 2= 10mA, 3=5mA, 4= 40mA, 5= 35mA, 6=30mA,7=25mA
/*************************************************/
void Led_SetCurrent(unsigned char level)
{

        if ( level > 7 ) return;

        g_LedEffect |= level<<4;

        single_world_write(CHIP_ADDR1, i2c_fd, SN3216_REG_LED_EFFECT, g_LedEffect);
        single_world_write(CHIP_ADDR2, i2c_fd, SN3216_REG_LED_EFFECT, g_LedEffect);

}
/*****************正常工作(音箱本身喇叭不工作)-常亮白色********************
   *i2c_fd:i2c文件操作描述符
 *
 ***********************************************************************/
int normally_bright_white()
{
        single_world_write(CHIP_ADDR1, i2c_fd, 0x03, 0x0); //pwm mode
        single_world_write(CHIP_ADDR2, i2c_fd, 0x03, 0x0); //pwm mode
        single_world_write(CHIP_ADDR1, i2c_fd, 0x07,0xff); //紫色
        single_world_write(CHIP_ADDR1, i2c_fd, 0x08,0xff);
        single_world_write(CHIP_ADDR1, i2c_fd, 0x09,0xff);
        single_world_write(CHIP_ADDR1, i2c_fd, 0x0a,0xff);
        single_world_write(CHIP_ADDR1, i2c_fd, 0x0b,0xff);
        single_world_write(CHIP_ADDR1, i2c_fd, 0x0c,0xff);
        single_world_write(CHIP_ADDR1, i2c_fd, 0x0d,0xff);
        single_world_write(CHIP_ADDR1, i2c_fd, 0x0e,0xff);
        single_world_write(CHIP_ADDR1, i2c_fd, 0x0f,0xff);
        single_world_write(CHIP_ADDR1, i2c_fd, 0x10,0xff);

        single_world_write(CHIP_ADDR2, i2c_fd, 0x07,0xff); //紫色
        single_world_write(CHIP_ADDR2, i2c_fd, 0x08,0xff);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x09,0xff);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x0a,0xff);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x0b,0xff);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x0c,0xff);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x0d,0xff);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x0e,0xff);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x0f,0xff);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x10,0xff);
        ALOGD("normally_bright_white:i2c_cfd = %d\n", i2c_fd);
        return 0;

}
/*****************待机-白色呼吸灯效果********************
 *
 ******************************************************/
int standby_mode() //RGB单色呼吸
{

        single_world_write(CHIP_ADDR1, i2c_fd, 0x03,0x70); //One shot mode
        single_world_write(CHIP_ADDR1, i2c_fd, 0x06,0x10); //INTB  OUT1
        single_world_write(CHIP_ADDR1, i2c_fd, 0x04,0x80);  //从模式
        single_world_write(CHIP_ADDR1, i2c_fd, 0x1a,0x81); //T1~T3;  T3=2T1=2*260*2ms,T2=260
        single_world_write(CHIP_ADDR1, i2c_fd, 0x1b,0x81);
        single_world_write(CHIP_ADDR1, i2c_fd, 0x1c,0x81);

        single_world_write(CHIP_ADDR1, i2c_fd, 0x1d,0x01); //T4=2*260ms
        single_world_write(CHIP_ADDR1, i2c_fd, 0x1e,0x01);
        single_world_write(CHIP_ADDR1, i2c_fd, 0x1f,0x01);

        single_world_write(CHIP_ADDR1, i2c_fd, 0x20,0x01);
        single_world_write(CHIP_ADDR1, i2c_fd, 0x21,0x01);
        single_world_write(CHIP_ADDR1, i2c_fd, 0x22,0x01);

        single_world_write(CHIP_ADDR1, i2c_fd, 0x23,0x01);
        single_world_write(CHIP_ADDR1, i2c_fd, 0x24,0x01);
        single_world_write(CHIP_ADDR1, i2c_fd, 0x25,0x01);
        single_world_write(CHIP_ADDR1, i2c_fd, 0x26,0xff);
        single_world_write(CHIP_ADDR1, i2c_fd, 0x10,0xff);

 
        single_world_write(CHIP_ADDR1, i2c_fd, 0x07,0xff);     //雪白
        single_world_write(CHIP_ADDR1, i2c_fd, 0x08,0xfa);
        single_world_write(CHIP_ADDR1, i2c_fd, 0x09,0xfa);

        single_world_write(CHIP_ADDR1, i2c_fd, 0x0a,0xff);
        single_world_write(CHIP_ADDR1, i2c_fd, 0x0b,0xfa);
        single_world_write(CHIP_ADDR1, i2c_fd, 0x0c,0xfa);

        single_world_write(CHIP_ADDR1, i2c_fd, 0x0d,0xff);
        single_world_write(CHIP_ADDR1, i2c_fd, 0x0e,0xfa);
        single_world_write(CHIP_ADDR1, i2c_fd, 0x0f,0xfa);
        single_world_write(CHIP_ADDR1, i2c_fd, 0x10,0xff);





        single_world_write(CHIP_ADDR2, i2c_fd, 0x03,0x70); //One shot mode
        single_world_write(CHIP_ADDR2, i2c_fd, 0x06,0x0); //INTB  OUT1
        single_world_write(CHIP_ADDR2, i2c_fd, 0x04,0x0);  //主模式
        single_world_write(CHIP_ADDR2, i2c_fd, 0x1a,0x81); //T1~T3;  T3=2T1=2*260*2ms,T2=260
        single_world_write(CHIP_ADDR2, i2c_fd, 0x1b,0x81);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x1c,0x81);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x1d,0x01); //T4=2*260ms
        single_world_write(CHIP_ADDR2, i2c_fd, 0x1e,0x01);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x1f,0x01);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x20,0x01);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x21,0x01);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x22,0x01);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x23,0x01);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x24,0x01);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x25,0x01);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x26,0xff);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x10,0xff);

        single_world_write(CHIP_ADDR2, i2c_fd, 0x07,0xff);         //雪白
        single_world_write(CHIP_ADDR2, i2c_fd, 0x08,0xfa);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x09,0xfa);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x0a,0xff);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x0b,0xfa);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x0c,0xfa);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x0d,0xff);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x0e,0xfa);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x0f,0xfa);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x10,0xff);
        ALOGD("standby_mode:i2c_cfd = %d\n", i2c_fd);
        return 0;
}
/*****************正在开机-蓝色呼吸灯********************
 * 
 *
 ****************************************************/
 int boot_system_state() //RGB单色呼吸
 {
 
         single_world_write(CHIP_ADDR1, i2c_fd, 0x03,0x70); //One shot mode
         single_world_write(CHIP_ADDR1, i2c_fd, 0x06,0x10); //INTB  OUT1
         single_world_write(CHIP_ADDR1, i2c_fd, 0x04,0x80);  //从模式
         single_world_write(CHIP_ADDR1, i2c_fd, 0x1a,0x81); //T1~T3;  T3=2T1=2*260*2ms,T2=260
         single_world_write(CHIP_ADDR1, i2c_fd, 0x1b,0x81);
         single_world_write(CHIP_ADDR1, i2c_fd, 0x1c,0x81);
 
         single_world_write(CHIP_ADDR1, i2c_fd, 0x1d,0x01); //T4=2*260ms
         single_world_write(CHIP_ADDR1, i2c_fd, 0x1e,0x01);
         single_world_write(CHIP_ADDR1, i2c_fd, 0x1f,0x01);
 
         single_world_write(CHIP_ADDR1, i2c_fd, 0x20,0x01);
         single_world_write(CHIP_ADDR1, i2c_fd, 0x21,0x01);
         single_world_write(CHIP_ADDR1, i2c_fd, 0x22,0x01);
 
         single_world_write(CHIP_ADDR1, i2c_fd, 0x23,0x01);
         single_world_write(CHIP_ADDR1, i2c_fd, 0x24,0x01);
         single_world_write(CHIP_ADDR1, i2c_fd, 0x25,0x01);
         single_world_write(CHIP_ADDR1, i2c_fd, 0x26,0xff);
         single_world_write(CHIP_ADDR1, i2c_fd, 0x10,0xff);
 
  
         single_world_write(CHIP_ADDR1, i2c_fd, 0x07,0x0);     //雪白
         single_world_write(CHIP_ADDR1, i2c_fd, 0x08,0x0);
         single_world_write(CHIP_ADDR1, i2c_fd, 0x09,0xff);
 
         single_world_write(CHIP_ADDR1, i2c_fd, 0x0a,0x0);
         single_world_write(CHIP_ADDR1, i2c_fd, 0x0b,0x0);
         single_world_write(CHIP_ADDR1, i2c_fd, 0x0c,0xff);
 
         single_world_write(CHIP_ADDR1, i2c_fd, 0x0d,0x0);
         single_world_write(CHIP_ADDR1, i2c_fd, 0x0e,0x0);
         single_world_write(CHIP_ADDR1, i2c_fd, 0x0f,0xff);
         single_world_write(CHIP_ADDR1, i2c_fd, 0x10,0xff);
 
 
 
 
 
         single_world_write(CHIP_ADDR2, i2c_fd, 0x03,0x70); //One shot mode
         single_world_write(CHIP_ADDR2, i2c_fd, 0x06,0x0); //INTB  OUT1
         single_world_write(CHIP_ADDR2, i2c_fd, 0x04,0x0);  //主模式
         single_world_write(CHIP_ADDR2, i2c_fd, 0x1a,0x81); //T1~T3;  T3=2T1=2*260*2ms,T2=260
         single_world_write(CHIP_ADDR2, i2c_fd, 0x1b,0x81);
         single_world_write(CHIP_ADDR2, i2c_fd, 0x1c,0x81);
         single_world_write(CHIP_ADDR2, i2c_fd, 0x1d,0x01); //T4=2*260ms
         single_world_write(CHIP_ADDR2, i2c_fd, 0x1e,0x01);
         single_world_write(CHIP_ADDR2, i2c_fd, 0x1f,0x01);
         single_world_write(CHIP_ADDR2, i2c_fd, 0x20,0x01);
         single_world_write(CHIP_ADDR2, i2c_fd, 0x21,0x01);
         single_world_write(CHIP_ADDR2, i2c_fd, 0x22,0x01);
         single_world_write(CHIP_ADDR2, i2c_fd, 0x23,0x01);
         single_world_write(CHIP_ADDR2, i2c_fd, 0x24,0x01);
         single_world_write(CHIP_ADDR2, i2c_fd, 0x25,0x01);
         single_world_write(CHIP_ADDR2, i2c_fd, 0x26,0xff);
         single_world_write(CHIP_ADDR2, i2c_fd, 0x10,0xff);
 
 
         single_world_write(CHIP_ADDR2, i2c_fd, 0x07,0x0);         //雪白
         single_world_write(CHIP_ADDR2, i2c_fd, 0x08,0x0);
         single_world_write(CHIP_ADDR2, i2c_fd, 0x09,0xff);
         single_world_write(CHIP_ADDR2, i2c_fd, 0x0a,0x0);
         single_world_write(CHIP_ADDR2, i2c_fd, 0x0b,0x0);
         single_world_write(CHIP_ADDR2, i2c_fd, 0x0c,0xff);
         single_world_write(CHIP_ADDR2, i2c_fd, 0x0d,0x0);
         single_world_write(CHIP_ADDR2, i2c_fd, 0x0e,0x0);
         single_world_write(CHIP_ADDR2, i2c_fd, 0x0f,0xff);
         single_world_write(CHIP_ADDR2, i2c_fd, 0x10,0xff);
         ALOGD("boot_system_state:i2c_cfd = %d\n", i2c_fd);
         return 0;
 }
/*****************网络异常状态-常亮红色********************
 * 
 *
 ****************************************************/
 int network_anomaly_state()
 {
        single_world_write(CHIP_ADDR1, i2c_fd, 0x03, 0x0); //pwm mode
        single_world_write(CHIP_ADDR2, i2c_fd, 0x03, 0x0); //pwm mode

        single_world_write(CHIP_ADDR1, i2c_fd, 0x07,0xff); //红色
        single_world_write(CHIP_ADDR1, i2c_fd, 0x08,0x0);
        single_world_write(CHIP_ADDR1, i2c_fd, 0x09,0x0);
        single_world_write(CHIP_ADDR1, i2c_fd, 0xa,0xff); //红色
        single_world_write(CHIP_ADDR1, i2c_fd, 0xb,0x0);
        single_world_write(CHIP_ADDR1, i2c_fd, 0xc,0x0);
        single_world_write(CHIP_ADDR1, i2c_fd, 0xd,0xff); //红色
        single_world_write(CHIP_ADDR1, i2c_fd, 0xe,0x0);
        single_world_write(CHIP_ADDR1, i2c_fd, 0xf,0x0);
        single_world_write(CHIP_ADDR1, i2c_fd, 0x10,0xff); //更新数据到各PWM寄存器里面

        single_world_write(CHIP_ADDR2, i2c_fd, 0x07,0xff);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x08,0x0);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x09,0x0);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x0a,0xff);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x0b,0x0);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x0c,0x0);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x0d,0xff);     //
        single_world_write(CHIP_ADDR2, i2c_fd, 0x0e,0x0);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x0f,0x0);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x10,0xff); //更新数据到各PWM寄存器里面  
        ALOGD("network_anomaly_state:i2c_cfd = %d\n", i2c_fd);
        return 0; 
 }
/*****************程序系统异常状态-红色闪烁********************
 *  seconds:红色闪烁状态间隔时间
 *
 ****************************************************/
int abnormal_state(unsigned int seconds)
{
        single_world_write(CHIP_ADDR1, i2c_fd, 0x03, 0x0); //pwm mode
        single_world_write(CHIP_ADDR2, i2c_fd, 0x03, 0x0); //pwm mode
        while(1)
        {
                single_world_write(CHIP_ADDR1, i2c_fd, 0x07,0xff); //红色
                single_world_write(CHIP_ADDR1, i2c_fd, 0x08,0x0);
                single_world_write(CHIP_ADDR1, i2c_fd, 0x09,0x0);
                single_world_write(CHIP_ADDR1, i2c_fd, 0xa,0xff); //红色
                single_world_write(CHIP_ADDR1, i2c_fd, 0xb,0x0);
                single_world_write(CHIP_ADDR1, i2c_fd, 0xc,0x0);
                single_world_write(CHIP_ADDR1, i2c_fd, 0xd,0xff); //红色
                single_world_write(CHIP_ADDR1, i2c_fd, 0xe,0x0);
                single_world_write(CHIP_ADDR1, i2c_fd, 0xf,0x0);
                single_world_write(CHIP_ADDR1, i2c_fd, 0x10,0xff); //更新数据到各PWM寄存器里面

                single_world_write(CHIP_ADDR2, i2c_fd, 0x07,0xff);     //红色
                single_world_write(CHIP_ADDR2, i2c_fd, 0x08,0x0);
                single_world_write(CHIP_ADDR2, i2c_fd, 0x09,0x0);
                single_world_write(CHIP_ADDR2, i2c_fd, 0x0a,0xff);     //红色
                single_world_write(CHIP_ADDR2, i2c_fd, 0x0b,0x0);
                single_world_write(CHIP_ADDR2, i2c_fd, 0x0c,0x0);
                single_world_write(CHIP_ADDR2, i2c_fd, 0x0d,0xff);     //红色
                single_world_write(CHIP_ADDR2, i2c_fd, 0x0e,0x0);
                single_world_write(CHIP_ADDR2, i2c_fd, 0x0f,0x0);
                single_world_write(CHIP_ADDR2, i2c_fd, 0x10,0xff); //更新数据到各PWM寄存器里面

                sleep(seconds);

                single_world_write(CHIP_ADDR1, i2c_fd, 0x07,0x0); //黑色
                single_world_write(CHIP_ADDR1, i2c_fd, 0x08,0x0);
                single_world_write(CHIP_ADDR1, i2c_fd, 0x09,0x0);
                single_world_write(CHIP_ADDR1, i2c_fd, 0xa,0x0); //黑色
                single_world_write(CHIP_ADDR1, i2c_fd, 0xb,0x0);
                single_world_write(CHIP_ADDR1, i2c_fd, 0xc,0x0);
                single_world_write(CHIP_ADDR1, i2c_fd, 0xd,0x0); //黑色
                single_world_write(CHIP_ADDR1, i2c_fd, 0xe,0x0);
                single_world_write(CHIP_ADDR1, i2c_fd, 0xf,0x0);
                single_world_write(CHIP_ADDR1, i2c_fd, 0x10,0xff); //更新数据到各PWM寄存器里面

                single_world_write(CHIP_ADDR2, i2c_fd, 0x07,0x0);
                single_world_write(CHIP_ADDR2, i2c_fd, 0x08,0x0);
                single_world_write(CHIP_ADDR2, i2c_fd, 0x09,0x0);
                single_world_write(CHIP_ADDR2, i2c_fd, 0x0a,0x0);
                single_world_write(CHIP_ADDR2, i2c_fd, 0x0b,0x0);
                single_world_write(CHIP_ADDR2, i2c_fd, 0x0c,0x0);
                single_world_write(CHIP_ADDR2, i2c_fd, 0x0d,0x0);     //
                single_world_write(CHIP_ADDR2, i2c_fd, 0x0e,0x0);
                single_world_write(CHIP_ADDR2, i2c_fd, 0x0f,0x0);
                single_world_write(CHIP_ADDR2, i2c_fd, 0x10,0xff); //更新数据到各PWM寄存器里面

                sleep(seconds);
        }
        abnormal_state_thread = -1;
        pthread_exit(0);
        ALOGD("abnormal_state:i2c_cfd = %d\n", i2c_fd);

        return 0;
}
/***************跟随音乐跳动状态**********************
 *
 *
 ****************************************************/
int audio_mode() //Audio mode
{
        single_world_write(CHIP_ADDR1, i2c_fd, 0x03, 0x04); //PWM mode; AE AGC
        single_world_write(CHIP_ADDR1, i2c_fd, 0x04, 0x6); //5mA, 18dB
        single_world_write(CHIP_ADDR2, i2c_fd, 0x03, 0x04); //PWM mode; AE AGC
        single_world_write(CHIP_ADDR2, i2c_fd, 0x04, 0x6); //5mA, 18dB

        single_world_write(CHIP_ADDR1, i2c_fd, 0x07,0xff);
        single_world_write(CHIP_ADDR1, i2c_fd, 0x08,0x0);
        single_world_write(CHIP_ADDR1, i2c_fd, 0x09,0x0);
        single_world_write(CHIP_ADDR1, i2c_fd, 0xa,0xff);
        single_world_write(CHIP_ADDR1, i2c_fd, 0xb,0xff);
        single_world_write(CHIP_ADDR1, i2c_fd, 0xc,0xff);
        single_world_write(CHIP_ADDR1, i2c_fd, 0xd,0xff);
        single_world_write(CHIP_ADDR1, i2c_fd, 0xe,0xff);
        single_world_write(CHIP_ADDR1, i2c_fd, 0xf,0x0);

        single_world_write(CHIP_ADDR1, i2c_fd, 0x10,0xff); //更新数据到各PWM寄存器里面

        single_world_write(CHIP_ADDR2, i2c_fd, 0x07,0x0);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x08,0xff);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x09,0x0);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x0a,0x0);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x0b,0x0);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x0c,0xff);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x0d,0x0);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x0e,0xff);
        single_world_write(CHIP_ADDR2, i2c_fd, 0x0f,0xff);

        single_world_write(CHIP_ADDR2, i2c_fd, 0x10,0xff); //更新数据到各PWM寄存器里面
        ALOGD("audio_mode:i2c_cfd = %d\n", i2c_fd);
        return 0;
}
int sn3199_init()
{
        //打开通用设备驱动文件
        i2c_fd = open("/dev/i2c-1", O_RDWR);
        if(i2c_fd < 0)
        {
                ALOGD("Open /dev/i2c-1 error:\n");
                return -1;
        }
        ALOGD("sn3199_init:i2c_cfd = %d\n", i2c_fd);
        return 0;
}
static struct sn3199_device_t sn3199_dev = 
{
        .sn3199_device = 
        {
                .tag = HARDWARE_DEVICE_TAG,
                .close = sn3199_close,
        },
        .set_light_state = set_light_state,
};
//logcat -s "SN3199_HAL"
//实现open函数
static int open_sn3199_hal(const struct hw_module_t* module, char const* id, struct hw_device_t** device)
{
    ALOGD("open_sn3199 exec.....");
    sn3199_init();   //获取i2c设备符
   /* struct sn3199_device_t *dev = calloc(1, sizeof(struct sn3199_device_t));
    dev->sn3199_device.tag = HARDWARE_DEVICE_TAG;
    dev->sn3199_device.close = (int (*)(struct hw_device_t *))sn3199_close;
    dev->set_light_state = set_light_state;
    *device = (struct hw_device_t*)dev;*/
    *device = &sn3199_dev;
    return 0;
}
int set_light_state(struct sn3199_device_t *dev, int state)
{

    ALOGD("set_light_state exec....");
    g_Flag = 0;
    reset();
    switch (state) {
    case 1:
            ALOGD("standby_mode....");
            standby_mode();
            break;
    case 2:
            ALOGD("normally_bright_white....");
            normally_bright_white();
            break;
    case 3:
            ALOGD("abnormal_state");
            pthread_create(&abnormal_state_thread, NULL, (void *)&abnormal_state, (void *)1);
            g_Flag = 1;
            //pthread_join(abnormal_state_thread, NULL);
            break;
    case 4:
            ALOGD("audio_mode");
            audio_mode();
            break;
    case 5:
            ALOGD("network_anomaly_state");
            network_anomaly_state();
            break;

    case 6:
            ALOGD(" boot_system_state");
            boot_system_state();
            break;
    default:
            break;
    }
    return 0;
}
static int sn3199_close(struct sn3199_device_t *dev)
{
    if(i2c_fd > 0)
    {
        close(i2c_fd);
        i2c_cfd = -1;   
    } 
    if(dev)
        free(dev);
    
    g_Flag = -1;
    return 0;
}

//定义HAL_MODULE_INFO_SYM,并对其进行初始化
static hw_module_methods_t sn3199_methods =
{
    .open = open_sn3199_hal,
};

struct hw_module_t HAL_MODULE_INFO_SYM =
{
    .tag = HARDWARE_MODULE_TAG,
    .id = **SN3199_HARDWARE_MODULE_ID**,     //该id起到了承上启下的作用,硬件服务层会匹配该id,一般这里使用宏定义(VIBRATOR_HARDWARE_MODULE_ID),不然可能会出错
    .author = "yjd",     
    .methods = &sn3199_methods,
};


  • sn3199_hal.h(相对android源码路径:hardware/libhardware/include/hardware/)
#ifndef _SN3199_HAL_H_
#define _SN3199_HAL_H_

#include <hardware/hardware.h>
#include <pthread.h>
#define	SN3216_REG_LED_EFFECT 		0x04

void single_world_write(unsigned char chip_addr, int i2c_fd, unsigned short reg, unsigned char val);
void burst_model_write(unsigned char chip_addr, int i2c_fd, unsigned short reg, unsigned char *data, unsigned int data_len);
unsigned char single_world_read(unsigned char chip_addr, int i2c_fd, unsigned short reg);
unsigned char * burst_model_read(unsigned char chip_addr, int i2c_fd, unsigned short reg, unsigned int data_len);
int reset();
int normally_bright_white();
int standby_mode();
int boot_system_state(); //RGB单色呼吸
int network_anomaly_state();
int abnormal_state(unsigned int seconds);
int audio_mode(); //Audio mode
static unsigned char g_Ledctrl1=0,g_Ledctrl2=0,g_LedEffect = 0;
static unsigned char g_Flag = 0;
pthread_t abnormal_state_thread = -1;
#define CHIP_ADDR1 0x64
#define CHIP_ADDR2 0x67
#define uchar unsigned char
#define uint unsigned int
#define SN3199_HARDWARE_MODULE_ID "sn3199"
//自定义一个对外的结构体
struct sn3199_device_t
{
    struct hw_device_t sn3199_device;
    int (*set_light_state)(struct sn3199_device_t *dev, int state);
};
static int sn3199_close(struct sn3199_device_t *dev);
int set_light_state(struct sn3199_device_t *dev, int state);

#endif

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := sn3199.default    #sn3199_hal_default.so

# HAL module implementation stored in
# hw/<VIBRATOR_HARDWARE_MODULE_ID>.default.so
LOCAL_MODULE_RELATIVE_PATH := hw    #/system/lib/hw/
LOCAL_C_INCLUDES := hardware/libhardware
LOCAL_SRC_FILES := sn3199_hal.c
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_MODULE_TAGS := optional

include $(BUILD_SHARED_LIBRARY)

以上就是hal的代码,通过mm命令最后生成sn3199.default.so

第三步:硬件访问服务程序设计(解决上层访问硬件冲突)
硬件服务流程
下面是具体的代码实现

  • ISn3199Service.aidl(Android源码目录的相对路径:frameworks/base/core/java/android/os/)
package android.os;

/** {@hide} */
interface ISn3199Service
{
    int sn3199Open();    //打开sn3199
    int setLightState(int state);  //设置sn3199灯光状态
}

该文件最后会生成实现Stub类方法的文件ISn3199Service.java(生成文件相对Android源码路径:out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/os)

  • Android.mk(相对Android源码相对路径:frameworks/base)
		.
		.
		.
		core/java/android/os/IVibratorService.aidl \
       + core/java/android/os/ISn3199Service.aidl
       .
       .
       .

Sn3199Service.java(相对Android路径:frameworks/base/services/core/java/com/android/server/)

package com.android.server;
import android.util.Slog;
import android.os.ISn3199Service;
public class Sn3199Service extends ISn3199Service.Stub {
    private static final String TAG = "Sn3199Service";
    public Sn3199Service()
    {
        Slog.d(TAG, "Sn3199Service");
    }
    //打开sn3199
    public int sn3199Open() throws android.os.RemoteException    
    {
        return nativeSn3199Open();
    }
    //设置sn3199灯光状态
    public int setLightState(int state) throws android.os.RemoteException 
    {
        return nativeSetLightState(state);
    }

    //声明本地函数
    public static native int nativeSn3199Open();
    public static native int nativeSetLightState(int state);
}

服务层调用hal层通过JNI
这里写图片描述

  • 将硬件服务注册到ServiceManager中去
    SystemServer.java(相对Android源码路径:frameworks/base/services/java/com/android/server/)
 ......
 VibratorService vibrator = null;
 +Sn3199Service sn3199 = null;
 IAlarmManager alarm = null;
 .......


  Slog.i(TAG, "Vibrator Service");
  vibrator = new VibratorService(context);
  ServiceManager.addService("vibrator", vibrator);

  +Slog.i(TAG, "Sn3199 Service");
  +sn3199 = new Sn3199Service();   //我们的构造函数是无参数构造函数,所以这里为空
  +ServiceManager.addService("sn3199", sn3199);

实现jni调用

  • com_android_server_Sn3199Service.cpp(相对Android源码目录:frameworks/base/services/core/jni/)
#define LOG_TAG "Sn3199Service"

#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"

#include <utils/misc.h>
#include <utils/Log.h>
#include <hardware/sn3199_hal.h>

#include <stdio.h>
struct sn3199_device_t *sn3199_dev;
namespace android
{
//JNI调用
static jint sn3199Open(JNIEnv *env, jobject clazz)
{
    hw_module_t* module;
    hw_device_t* device;
    jint err;
    err = hw_get_module(SN3199_HARDWARE_MODULE_ID , (hw_module_t const**)&module);     //获取到对应的hal中的modules对象,第一个参数为hal层中的id
    if(err == 0)
    {
        ALOGD("module->methods->open");
        module->methods->open(module, NULL, &device); //获取到hal中的device信息
        sn3199_dev = (struct sn3199_device_t* )device;
        return 0;
    }
    else
    {
        ALOGD("hw_get_module exec error");
    }
    return -1;
}
static jint setLightState(JNIEnv *env, jobject clazz, int state)   //前面了两个参数是系统带的一定要有的,后面的是用户自定参数
{
    sn3199_dev->set_light_state(sn3199_dev, state);
    return 0;
}
static JNINativeMethod method_table[] = {
    { "nativeSn3199Open", "()I", (void*)sn3199Open },    //第二个参数()参数个数,I-返回值类型
    { "nativeSetLightState", "(I)I", (void*)setLightState }
};

int register_android_server_Sn3199Service(JNIEnv *env)
{
    return jniRegisterNativeMethods(env, "com/android/server/Sn3199Service",
            method_table, NELEM(method_table));
}

};

加载cpp文件到Android.mk中(相对Android源码目录:frameworks/base/services/core/jni)

 $(LOCAL_REL_DIR)/com_android_server_VibratorService.cpp \
 +$(LOCAL_REL_DIR)/com_android_server_Sn3199Service.cpp \

调用register_android_server_Sn3199Service函数

  • onload.cpp(相对Android源码目录:frameworks/base/services/core/jni/)
.......
int register_android_server_VibratorService(JNIEnv* env);
//and by yjd
 1. int register_android_server_Sn3199Service(JNIEnv *env);   //定义
//end
.....
register_android_server_VibratorService(env);
 //and by yjd
 2. register_android_server_Sn3199Service(env);   //调用
 //end
register_android_server_SystemServer(env);
...

hal层问题编译总结:第一个是,自己编写的hal层源码怎么添加进Android的默认编译中去,也就是说在根目录执行make时,自己编写hal源码也会被编译。第二个问题是,编译完成后还要自动安装在指定的系统目录lib/hw下。刚开始时不太了解,总是需要使用mmm来进行编译安装,但是打包之后再编译这是不现实的。为此需要添加到默认中
解决方法:
在写好hardware/libhardware/modules/sn3199工程文件时,要将sn3199目录加到modules的Android.mk中,hardware_modules变量中添加sn3199文件夹的名字即可,这样在默认中就会自动编译这个sn3199的文件,这样虽然可以编译了,但是不会自动安装,如果在源码根目录下编译安装sn3199,还需修改device/s3graphics/zx2000/zx2000.mk这个文件中的PRODUCT_PACKAGES变量,在文件中添加PRODUCT_PACKAGES += sn3199.default代码即可

这样上图中的stub、Java、JNI都完成了(已经凌晨1点了,挺住写完再睡)
在framework/base/目录下执行mm命令进行编译

接下来就是编写APP进行测试了,测试之前要将刚编译框架层中的classes.jar(相对Android源码路径:out/target/common/obj/JAVA_LIBRARIES/framework_intermediates)拷贝出来供APP使用,直接上代码,APP只是测试用,所以就随便写了

  • MainActivity.java
package com.example.yjd.sn3199interfacetest;

import android.os.Bundle;
import android.os.ISn3199Service;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private ISn3199Service iSn3199Service = null;
    private Button[] btnState = new Button[6];
    Toast toast;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        iSn3199Service = ISn3199Service.Stub.asInterface(ServiceManager.getService("sn3199"));
        if(null != iSn3199Service) {
            try {
                iSn3199Service.sn3199Open();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        btnState[0] = (Button) findViewById(R.id.state1);
        btnState[1] = (Button) findViewById(R.id.state2);
        btnState[2] = (Button) findViewById(R.id.state3);
        btnState[3] = (Button) findViewById(R.id.state4);
        btnState[4] = (Button) findViewById(R.id.state5);
        btnState[5] = (Button) findViewById(R.id.state6);
        for (int i = 0; i < 6; i++) {
           btnState[i].setOnClickListener(this);
        }
    }
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.state1:    //待机-白色呼吸灯效果
                try{
                    iSn3199Service.setLightState(1);
                }catch(RemoteException e) {
                    e.printStackTrace();
                }
                toast = Toast.makeText(this, "待机-白色呼吸灯效果设置成功", Toast.LENGTH_SHORT);
                toast.show();
                break;
            case R.id.state2:   //正常工作(音箱本身喇叭不工作)-常亮白色
                try{
                    iSn3199Service.setLightState(2);
                }catch(RemoteException e)
                {
                    e.printStackTrace();
                }
                toast = Toast.makeText(this, "待机-白色呼吸灯效果设置成功", Toast.LENGTH_SHORT);
                toast.show();
                break;
            case R.id.state3:   //程序系统异常状态-红色闪烁
                try{
                    iSn3199Service.setLightState(3);
                }catch(RemoteException e)
                {
                    e.printStackTrace();
                }
                toast = Toast.makeText(this, "程序系统异常状态-红色闪烁设置成功", Toast.LENGTH_SHORT);
                toast.show();
                break;
            case R.id.state4:    //跟随音乐跳动状态
                try{
                    iSn3199Service.setLightState(4);
                }catch(RemoteException e)
                {
                    e.printStackTrace();
                }
                toast = Toast.makeText(this, "跟随音乐跳动状态设置成功", Toast.LENGTH_SHORT);
                toast.show();
                break;
            case R.id.state5:   //网络异常状态-常亮红色
                try{
                    iSn3199Service.setLightState(5);
                }catch(RemoteException e)
                {
                    e.printStackTrace();
                }
                toast = Toast.makeText(this, "网络异常状态-常亮红色设置成功", Toast.LENGTH_SHORT);
                toast.show();
                break;
            case R.id.state6:     //正在开机-蓝色呼吸灯
                try{
                    iSn3199Service.setLightState(6);
                }catch(RemoteException e)
                {
                    e.printStackTrace();
                }
                toast = Toast.makeText(this, "正在开机-蓝色呼吸灯设置成功", Toast.LENGTH_SHORT);
                toast.show();
                break;
            default:
                break;
        }
    }
}

  • activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.yjd.sn3199interfacetest.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="State1"
            android:id="@+id/state1"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="State2"
            android:id="@+id/state2"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="State3"
            android:id="@+id/state3"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="State4"
            android:id="@+id/state4"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="State5"
            android:id="@+id/state5"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="State6"
            android:id="@+id/state6"/>

    </LinearLayout>

</android.support.constraint.ConstraintLayout>

编译的时候可能会遇到方法超过64K的错误,这里添加两行代码完美解决
这里写图片描述
这里写图片描述
OK,终于写完了,至于怎么添加classes.jar包到Android项目中应该就很简单了,还是贴张图吧
这里写图片描述
这里添加依赖选第3个选模块依赖:ModuleDependency
这里写图片描述

在此特别注意的时候,在调试底层代码的时候,多看main log, 用logcat -v time -b main 命令

如果有什么疑问可以加QQ:1308418494一起讨论学习,欢迎大神指点

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值