驱动文件移植解析
Read time: 19 minute(s)
本节描述了 GT911 触摸屏驱动的初始化和配置示例,包括 I2C 通信、GPIO 引脚配置以及触摸事件的处理。
主要功能模块简介
-
I2C 通信初始化:
gt911_client.bus
获取 I2C 总线设备。- 打开 I2C 总线设备,并设置从机地址。
-
GPIO 引脚配置:
- 使用
drv_pin_get
函数获取复位引脚和中断引脚。 -
配置复位引脚和中断引脚为输入模式,并设置中断模式:
static int rt_gt911_gpio_cfg() { unsigned int g, p; long pin; pin = drv_pin_get(AIC_TOUCH_PANEL_RST_PIN); g = GPIO_GROUP(pin); p = GPIO_GROUP_PIN(pin); hal_gpio_direction_input(g, p); pin = drv_pin_get(AIC_TOUCH_PANEL_INT_PIN); g = GPIO_GROUP(pin); p = GPIO_GROUP_PIN(pin); hal_gpio_direction_input(g, p); hal_gpio_set_irq_mode(g, p, 0); return 0; }
- 使用
-
触摸事件处理:
- gt911_read_point()
读取触摸点信息:
static rt_err_t gt911_read_point(struct rt_touch_device *touch, void *buf, rt_size_t read_num)
- gt911_control() 控制 TP 设备,如获取
ID、校准等:
static rt_err_t gt911_control(struct rt_touch_device *touch, int cmd, void *arg)
- 注册了
gt911_touch_ops
结构体,将上述两个函数指针赋值给相应的操作函数。
- gt911_read_point()
读取触摸点信息:
-
注册触摸设备:
-
在
rt_hw_gt911_init
函数中,创建并初始化rt_touch_device
结构体。分配内存并初始化触摸设备结构体,然后注册该设备:static int rt_hw_gt911_init(const char *name, struct rt_touch_config *cfg) { touch_device = (struct rt_touch_device *)rt_malloc(sizeof(struct rt_touch_device)); if (touch_device == RT_NULL) { LOG_E("touch device malloc fail"); return -RT_ERROR; } rt_memset((void *)touch_device, 0, sizeof(struct rt_touch_device)); touch_device->info.type = RT_TOUCH_TYPE_CAPACITANCE; touch_device->info.vendor = RT_TOUCH_VENDOR_GT; rt_memcpy(&touch_device->config, cfg, sizeof(struct rt_touch_config)); touch_device->ops = >911_touch_ops; if (RT_EOK != rt_hw_touch_register(touch_device, name, RT_DEVICE_FLAG_INT_RX, RT_NULL)) { LOG_E("touch device gt911 init failed !!!"); return -RT_ERROR; } LOG_I("touch device gt911 init success"); return RT_EOK; }
- 调用
rt_hw_touch_register
函数注册触摸设备。
-
-
硬件初始化:
在
rt_hw_gt911_port
函数中,调用rt_hw_gt911_init
完成设备的初始化和注册。
完整代码示例
#include <rtthread.h> #include <rtdevice.h> #include <string.h> #define DBG_TAG "gt911" #define DBG_LVL DBG_INFO #include <rtdbg.h> #include "gt911.h" //dev 结构体定义,其他 TP 也需要定义这个变量 static struct rt_i2c_client gt911_client; //这个是 gt911 特有的数组用于更改固件的,没这需求的可以不定义 static rt_uint8_t GT911_CFG_TBL[] = { 0x6b, 0x00, 0x04, 0x58, 0x02, 0x05, 0x0d, 0x00, 0x01, 0x0f, 0x28, 0x0f, 0x50, 0x32, 0x03, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x2a, 0x0c, 0x45, 0x47, 0x0c, 0x08, 0x00, 0x00, 0x00, 0x40, 0x03, 0x2c, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x64, 0x32, 0x00, 0x00, 0x00, 0x28, 0x64, 0x94, 0xd5, 0x02, 0x07, 0x00, 0x00, 0x04, 0x95, 0x2c, 0x00, 0x8b, 0x34, 0x00, 0x82, 0x3f, 0x00, 0x7d, 0x4c, 0x00, 0x7a, 0x5b, 0x00, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x16, 0x14, 0x12, 0x10, 0x0e, 0x0c, 0x0a, 0x08, 0x06, 0x04, 0x02, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x18, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x24, 0x13, 0x12, 0x10, 0x0f, 0x0a, 0x08, 0x06, 0x04, 0x02, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0x01, }; /* * I2C 写函数,主要传参有三个:dev,要写入的数据,写入数据的长度 * 这个函数适用于所有的 TP,编写的时候只需要更换函数名字,跟 TP 对应上即可 */ static rt_err_t gt911_write_reg(struct rt_i2c_client *dev, rt_uint8_t *data, rt_uint8_t len) { struct rt_i2c_msg msgs; //从机的设备地址,在初始化的时候会进行赋值 msgs.addr = dev->client_addr; //I2C 读写标志,这里是写标志 msgs.flags = RT_I2C_WR; //要发送的数据 msgs.buf = data; //数据长度 msgs.len = len; if (rt_i2c_transfer(dev->bus, &msgs, 1) == 1) { return RT_EOK; } else { return -RT_ERROR; } } /* * I2C 读函数,主要传参有四个:dev,要读取的寄存器地址,接受数据的 buf,读取的长度 * 这个函数适用于所有的 TP,编写的时候只需要更换函数名字,跟 TP 对应上即可 */ static rt_err_t gt911_read_regs(struct rt_i2c_client *dev, rt_uint8_t *reg, rt_uint8_t *data, rt_uint8_t len) { struct rt_i2c_msg msgs[2]; msgs[0].addr = dev->client_addr; msgs[0].flags = RT_I2C_WR; /* * 要读取得寄存器地址,目前市面上有两种常见得 TP,一种是 16 位寄存器地址的,一种是 8 位的 * 16 位的寄存器地址需要先发送高 8 位,再发送低 8 位,分成两个 buf * 8 位的直接发送即可 */ msgs[0].buf = reg; // 这里的长度对应上面的 16 位或者 8 位,16 位长度为 2,8 位长度为 1 msgs[0].len = GT911_REGITER_LEN; msgs[1].addr = dev->client_addr; msgs[1].flags = RT_I2C_RD; msgs[1].buf = data; msgs[1].len = len; if (rt_i2c_transfer(dev->bus, msgs, 2) == 2) { return RT_EOK; } else { return -RT_ERROR; } } /* * 这个函数主要用于获取 TP 的 ID,有些 TP 并没有具体获取 ID 的寄存器公开,可以不添加 */ static rt_err_t gt911_get_product_id(struct rt_i2c_client *dev, rt_uint8_t *data, rt_uint8_t len) { rt_uint8_t reg[2]; // 这里就是将 16 位的寄存器地址拆开高 8 位低 8 位两个 buf 进行传输 reg[0] = (rt_uint8_t)(GT911_PRODUCT_ID >> 8); reg[1] = (rt_uint8_t)(GT911_PRODUCT_ID & 0xff); if (gt911_read_regs(dev, reg, data, len) != RT_EOK) { LOG_E("read id failed"); return -RT_ERROR; } return RT_EOK; } /* * 这个函数主要用于获取 TP 的参数,包括:xy 的坐标范围最大值,触摸点的个数 */ static rt_err_t gt911_get_info(struct rt_i2c_client *dev, struct rt_touch_info *info) { rt_uint8_t reg[2]; rt_uint8_t out_info[7]; rt_uint8_t out_len = 7; reg[0] = (rt_uint8_t)(GT911_CONFIG_REG >> 8); reg[1] = (rt_uint8_t)(GT911_CONFIG_REG & 0xFF); if (gt911_read_regs(dev, reg, out_info, out_len) != RT_EOK) { LOG_E("read info failed"); return -RT_ERROR; } /* * 这里是对获取到的数据进行组合得到参数信息 * 有些 TP 对这些寄存器并不开放,因此我们也可以通过直接赋值的方法设置参数 * 设置的参数要跟 TP 自带的固件对应上才能正常使用,具体操作如下 */ info->range_x = (out_info[2] << 8) | out_info[1]; //info->range_x = AIC_TOUCH_X_COORDINATE_RANGE; //这里的 AIC_TOUCH_X_COORDINATE_RANGE 就是公用参数那里可以配置的,所以不同 TP 都可通用 info->range_y = (out_info[4] << 8) | out_info[3]; //info->range_y = AIC_TOUCH_Y_COORDINATE_RANGE; //这里的 AIC_TOUCH_Y_COORDINATE_RANGE 也是公用参数那里可以配置的,所以不同 TP 都可通用 info->point_num = out_info[5] & 0x0f; //info->point_num = 5; if (info->point_num > GT911_MAX_TOUCH) { info->point_num = GT911_MAX_TOUCH; rt_kprintf("Warning,tp support more than 5 points, limited to 5 points\n"); } return RT_EOK; } /* * 这里的静态变量主要是为了存储坐标点的数据,然后作逻辑判断 * GT911_MAX_TOUCH 是最大的触摸点个数也就是上面 info->point_num 定义的 5,所以数组赋值了 5 个-1 作为初始值 * 不同的触摸个数这个数组大小不一样 */ static int16_t pre_x[GT911_MAX_TOUCH] = { -1, -1, -1, -1, -1 }; static int16_t pre_y[GT911_MAX_TOUCH] = { -1, -1, -1, -1, -1 }; //这个 pre_w 是触摸面积的定义,有些 TP 并没有相关描述,所以不是必须的,但 xy 是必须要有 static int16_t pre_w[GT911_MAX_TOUCH] = { -1, -1, -1, -1, -1 }; static rt_uint8_t s_tp_dowm[GT911_MAX_TOUCH] = {0}; static struct rt_touch_data *read_data = RT_NULL; /* * 这个是抬起事件上报函数,修改函数名即可使用 * 如果定义上面的静态数组的时候没有定义 pre_w 则下面代码关于 pre_w 的要删除 */ static void gt911_touch_up(void *buf, int8_t id) { read_data = (struct rt_touch_data *)buf; if (s_tp_dowm[id] == 1) { s_tp_dowm[id] = 0; read_data[id].event = RT_TOUCH_EVENT_UP; } else { read_data[id].event = RT_TOUCH_EVENT_NONE; } read_data[id].timestamp = rt_touch_get_ts(); //下面的这行可以删除 read_data[id].width = pre_w[id]; read_data[id].x_coordinate = pre_x[id]; read_data[id].y_coordinate = pre_y[id]; read_data[id].track_id = id; pre_x[id] = -1; /* last point is none */ pre_y[id] = -1; //下面的这行可以删除 pre_w[id] = -1; } /* * 这个是移动和按下事件函数,修改函数名即可使用 * 如果定义上面的静态数组的时候没有定义 pre_w 则下面代码关于 pre_w 的要删除 * 包括函数传参里面的 int16_t w 也要删除 */ static void gt911_touch_down(void *buf, int8_t id, int16_t x, int16_t y, int16_t w) { read_data = (struct rt_touch_data *)buf; if (s_tp_dowm[id] == 1) { read_data[id].event = RT_TOUCH_EVENT_MOVE; } else { read_data[id].event = RT_TOUCH_EVENT_DOWN; s_tp_dowm[id] = 1; } read_data[id].timestamp = rt_touch_get_ts(); //下面的这行可以删除 read_data[id].width = w; read_data[id].x_coordinate = x; read_data[id].y_coordinate = y; read_data[id].track_id = id; pre_x[id] = x; /* save last point */ pre_y[id] = y; //下面的这行可以删除 pre_w[id] = w; } /* * 这个读取坐标点数据的函数,必须实现 */ static rt_size_t gt911_read_point(struct rt_touch_device *touch, void *buf, rt_size_t read_num) { rt_uint8_t point_status = 0; rt_uint8_t touch_num = 0; rt_uint8_t write_buf[3]; rt_uint8_t cmd[2]; //要读取的数据的长度,一般是读取坐标数量*每一个坐标信息的 buf 长度+一些其他信息 rt_uint8_t read_buf[8 * GT911_MAX_TOUCH] = { 0 }; rt_uint8_t read_index; int8_t read_id = 0; int16_t input_x = 0; int16_t input_y = 0; int16_t input_w = 0; //存储上一次触摸点个数 static rt_uint8_t pre_touch = 0; //存储上一次坐标 ID 的数组 static int8_t pre_id[GT911_MAX_TOUCH] = { 0 }; //这里是对传参过来的 buf 清零,避免 buf 里面已有数据对坐标读取产生影响 rt_memset(buf, 0, sizeof(struct rt_touch_data) * read_num); /* point status register */ cmd[0] = (rt_uint8_t)((GT911_READ_STATUS >> 8) & 0xFF); cmd[1] = (rt_uint8_t)(GT911_READ_STATUS & 0xFF); if (gt911_read_regs(>911_client, cmd, &point_status, 1) != RT_EOK) { LOG_D("read point failed\n"); read_num = 0; goto exit_; } if (point_status == 0) /* no data */ { read_num = 0; goto exit_; } if ((point_status & 0x80) == 0) /* data is not ready */ { read_num = 0; goto exit_; } //触摸点个数赋值 touch_num = point_status & 0x0f; /* get point num */ if (touch_num > GT911_MAX_TOUCH) /* point num is not correct */ { read_num = 0; goto exit_; } cmd[0] = (rt_uint8_t)((GT911_POINT1_REG >> 8) & 0xFF); cmd[1] = (rt_uint8_t)(GT911_POINT1_REG & 0xFF); //读取坐标数据 if (gt911_read_regs(>911_client, cmd, read_buf, read_num * GT911_POINT_INFO_NUM) != RT_EOK) { LOG_D("read point failed\n"); read_num = 0; goto exit_; } /* * 下面是对坐标事件的处理,一般可以直接照抄,更换一下信息即可 */ if (pre_touch > touch_num) /* point up */ { for (read_index = 0; read_index < pre_touch; read_index++) { rt_uint8_t j; for (j = 0; j < touch_num; j++) /* this time touch num */ { //坐标 ID 这里是需要更换的,要按照规格书来赋值 read_id = read_buf[j * 8] & 0x0F; if (pre_id[read_index] == read_id) /* this id is not free */ break; if (j >= touch_num - 1) { rt_uint8_t up_id; up_id = pre_id[read_index]; gt911_touch_up(buf, up_id); } } } } if (touch_num) /* point down */ { rt_uint8_t off_set; for (read_index = 0; read_index < touch_num; read_index++) { //这个 offset 是一个坐标信息的偏移,8 代表一个坐标要读取 8 个 byte,具体是多少按照规格书来 off_set = read_index * 8; //坐标 ID 这里是需要更换的,要按照规格书来赋值 read_id = read_buf[off_set] & 0x0f; pre_id[read_index] = read_id; //下面的 xyw 也是需要按照规格书重新组合 buf input_x = read_buf[off_set + 1] | (read_buf[off_set + 2] << 8); /* x */ input_y = read_buf[off_set + 3] | (read_buf[off_set + 4] << 8); /* y */ input_w = read_buf[off_set + 5] | (read_buf[off_set + 6] << 8); /* size */ gt911_touch_down(buf, read_id, input_x, input_y, input_w); } } else if (pre_touch) { for (read_index = 0; read_index < pre_touch; read_index++) { gt911_touch_up(buf, pre_id[read_index]); } } //将当前的触摸点个数赋值给 pre_touch pre_touch = touch_num; exit_: //这里是 gt911 特有的每次读取完数据要发送 0x00 完成同步,没有这个要求的不需要实现 write_buf[0] = (rt_uint8_t)((GT911_READ_STATUS >> 8) & 0xFF); write_buf[1] = (rt_uint8_t)(GT911_READ_STATUS & 0xFF); write_buf[2] = 0x00; gt911_write_reg(>911_client, write_buf, 3); //返回触摸点个数 return read_num; } /* * TP 的控制函数,一般来说只需要实现 RT_TOUCH_CTRL_GET_INFO,其他的可以不管 */ static rt_err_t gt911_control(struct rt_touch_device *touch, int cmd, void *arg) { if (cmd == RT_TOUCH_CTRL_GET_ID) { return gt911_get_product_id(>911_client, arg, 6); } if (cmd == RT_TOUCH_CTRL_GET_INFO) { return gt911_get_info(>911_client, arg); } rt_uint8_t buf[4]; rt_uint8_t i = 0; rt_uint8_t *config; config = (rt_uint8_t *)rt_calloc(1, sizeof(GT911_CFG_TBL) + GT911_REGITER_LEN); if (config == RT_NULL) { LOG_D("malloc config memory failed\n"); return -RT_ERROR; } config[0] = (rt_uint8_t)((GT911_CONFIG_REG >> 8) & 0xFF); config[1] = (rt_uint8_t)(GT911_CONFIG_REG & 0xFF); memcpy(&config[2], GT911_CFG_TBL, sizeof(GT911_CFG_TBL)); switch (cmd) { case RT_TOUCH_CTRL_SET_X_RANGE: { rt_uint16_t x_range; x_range = *(rt_uint16_t *)arg; config[4] = (rt_uint8_t)(x_range >> 8); config[3] = (rt_uint8_t)(x_range & 0xff); GT911_CFG_TBL[2] = config[4]; GT911_CFG_TBL[1] = config[3]; break; } case RT_TOUCH_CTRL_SET_Y_RANGE: { rt_uint16_t y_range; y_range = *(rt_uint16_t *)arg; config[6] = (rt_uint8_t)(y_range >> 8); config[5] = (rt_uint8_t)(y_range & 0xff); GT911_CFG_TBL[4] = config[6]; GT911_CFG_TBL[3] = config[5]; break; } case RT_TOUCH_CTRL_SET_X_TO_Y: { config[8] ^= (1 << 3); break; } case RT_TOUCH_CTRL_SET_MODE: { rt_uint16_t trig_type; trig_type = *(rt_uint16_t *)arg; switch (trig_type) { case RT_DEVICE_FLAG_INT_RX: config[8] &= 0xFC; break; case RT_DEVICE_FLAG_RDONLY: config[8] &= 0xFC; config[8] |= 0x02; break; default: break; } break; } default: { break; } } if (gt911_write_reg(>911_client, config, sizeof(GT911_CFG_TBL) + GT911_ADDR_LEN) != RT_EOK) { LOG_D("send config failed"); return -1; } buf[0] = (rt_uint8_t)((GT911_CHECK_SUM >> 8) & 0xFF); buf[1] = (rt_uint8_t)(GT911_CHECK_SUM & 0xFF); buf[2] = 0; for (i = GT911_ADDR_LEN; i < sizeof(GT911_CFG_TBL) + GT911_ADDR_LEN; i++) { buf[GT911_ADDR_LEN] += config[i]; } buf[2] = (~buf[2]) + 1; buf[3] = 1; gt911_write_reg(>911_client, buf, 4); rt_free(config); return RT_EOK; } /* * rt_touch_ops,主要是完成对读坐标和控制 TP 的函数注册 */ static struct rt_touch_ops gt911_touch_ops = { .touch_readpoint = gt911_read_point, .touch_control = gt911_control, }; /* * 这个函数主要实现对 TP 的初始操作 */ static int rt_hw_gt911_init(const char *name, struct rt_touch_config *cfg) { struct rt_touch_device *touch_device = RT_NULL; touch_device = (struct rt_touch_device *)rt_malloc(sizeof(struct rt_touch_device)); if (touch_device == RT_NULL) { LOG_E("touch device malloc fail"); return -RT_ERROR; } rt_memset((void *)touch_device, 0, sizeof(struct rt_touch_device)); //这里是对 TP 一些上下电的操作,具体根据规格书来 /* hw init*/ // rst output 0 rt_pin_mode(*(rt_uint8_t *)cfg->user_data, PIN_MODE_OUTPUT); rt_pin_write(*(rt_uint8_t *)cfg->user_data, PIN_LOW); rt_thread_delay(10); // irq output 0 rt_pin_mode(cfg->irq_pin.pin, PIN_MODE_OUTPUT); rt_pin_write(cfg->irq_pin.pin, PIN_LOW); rt_thread_delay(2); // rst output 1 rt_pin_mode(*(rt_uint8_t *)cfg->user_data, PIN_MODE_OUTPUT); rt_pin_write(*(rt_uint8_t *)cfg->user_data, PIN_HIGH); rt_thread_delay(5); // rst input rt_pin_mode(*(rt_uint8_t *)cfg->user_data, PIN_MODE_INPUT); //irq output 0 rt_pin_mode(cfg->irq_pin.pin, PIN_MODE_OUTPUT); rt_pin_write(cfg->irq_pin.pin, PIN_LOW); rt_thread_delay(50); //最后必须要对 IRQ 引脚设置回输入模式,不然会影响中断信号的获取 rt_pin_mode(cfg->irq_pin.pin, PIN_MODE_INPUT); gt911_client.bus = (struct rt_i2c_bus_device *)rt_device_find(cfg->dev_name); if (gt911_client.bus == RT_NULL) { LOG_E("Can't find %s device", cfg->dev_name); return -RT_ERROR; } //打开设备中断模式用 RT_DEVICE_FLAG_RDWR if (rt_device_open((rt_device_t)gt911_client.bus, RT_DEVICE_FLAG_RDWR) != RT_EOK) { LOG_E("open %s device failed", cfg->dev_name); return -RT_ERROR; } //从机设备地址赋值 gt911_client.client_addr = GT911_ADDRESS_HIGH; /* register touch device */ //赋值芯片的型号和类型 touch_device->info.type = RT_TOUCH_TYPE_CAPACITANCE; touch_device->info.vendor = RT_TOUCH_VENDOR_GT; rt_memcpy(&touch_device->config, cfg, sizeof(struct rt_touch_config)); touch_device->ops = >911_touch_ops; if (RT_EOK != rt_hw_touch_register(touch_device, name, RT_DEVICE_FLAG_INT_RX, RT_NULL)) { LOG_E("touch device gt911 init failed !!!"); return -RT_ERROR; } LOG_I("touch device gt911 init success"); return RT_EOK; } /* * 这个函数主要实现对 GPIO 的初始化,这个函数只需修改函数名即可复用 */ static int rt_gt911_gpio_cfg() { unsigned int g, p; long pin; // AIC_TOUCH_PANEL_RST_PIN 是公用参数里面的复位引脚 pin = drv_pin_get(AIC_TOUCH_PANEL_RST_PIN); g = GPIO_GROUP(pin); p = GPIO_GROUP_PIN(pin); hal_gpio_direction_input(g, p); // AIC_TOUCH_PANEL_INT_PIN 是公用参数里面的中断引脚 pin = drv_pin_get(AIC_TOUCH_PANEL_INT_PIN); g = GPIO_GROUP(pin); p = GPIO_GROUP_PIN(pin); hal_gpio_direction_input(g, p); hal_gpio_set_irq_mode(g, p, 0); return 0; } /* * 这个函数主要注册整个设备,改个名字即可复用 */ static int rt_hw_gt911_port(void) { struct rt_touch_config cfg; rt_uint8_t rst_pin; rt_gt911_gpio_cfg(); rst_pin = drv_pin_get(AIC_TOUCH_PANEL_RST_PIN); cfg.dev_name = AIC_TOUCH_PANEL_I2C_CHAN; cfg.irq_pin.pin = drv_pin_get(AIC_TOUCH_PANEL_INT_PIN); cfg.irq_pin.mode = PIN_MODE_INPUT; cfg.user_data = &rst_pin; #ifdef AIC_PM_DEMO rt_pm_set_pin_wakeup_source(cfg.irq_pin.pin); #endif // AIC_TOUCH_PANEL_NAME 这个是公用参数里面设备名字,也可以直接复用 rt_hw_gt911_init(AIC_TOUCH_PANEL_NAME, &cfg); return 0; } //通过宏 ``INIT_DEVICE_EXPORT``,驱动在系统启动时自动加载,即可轻松适配不同型号的触控芯片 INIT_DEVICE_EXPORT(rt_hw_gt911_port);