1 参考资料
1.\marsboard\marsboard-a20-linux-sdk-v1.2\linux-sunxi\drivers\input\touchscreen\Gt818_ts.c
2.\marsboard\marsboard-a20-linux-sdk-v1.2\linux-sunxi\drivers\i2c\I2c-core.c
3.\marsboard\marsboard-a20-linux-sdk-v1.2\linux-sunxi\include\linux\I2c.h
4.\marsboard\marsboard-a20-linux-sdk-v1.2\linux-sunxi\drivers\i2c\I2c-dev.c提供应用层接口
5.\marsboard\marsboard-a20-linux-sdk-v1.2\linux-sunxi\drivers\i2c\I2c-core.c提供核心层文件
6.\marsboard\marsboard-a20-linux-sdk-v1.2\linux-sunxi\drivers\i2c\busses\I2c-sunxi.c提供底层硬件驱动
1.1 I2C框架总图
I2C框架总图2 I2C接口
2.1 函数接口
/*注册驱动*/
#define i2c_add_driver(driver) \
i2c_register_driver(THIS_MODULE, driver)
/*注销驱动*/
void i2c_del_driver(struct i2c_driver *driver)
/*承载实际的数据传输*/
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
2.2 结构体
struct i2c_board_info {
char type[I2C_NAME_SIZE];
unsigned short flags;
unsigned short addr;
void *platform_data;
struct dev_archdata *archdata;
struct device_node *of_node;
int irq;
};
struct i2c_client {
unsigned short flags; /* div., see below */
unsigned short addr; /* chip address - NOTE: 7bit */
/* addresses are stored in the */
/* _LOWER_ 7 bits */
char name[I2C_NAME_SIZE];
struct i2c_adapter *adapter; /* the adapter we sit on */
struct i2c_driver *driver; /* and our access routines */
struct device dev; /* the device structure */
int irq; /* irq issued by device */
struct list_head detected;
};
struct i2c_driver {
unsigned int class;
/* Notifies the driver that a new bus has appeared or is about to be
* removed. You should avoid using this, it will be removed in a
* near future.
*/
int (*attach_adapter)(struct i2c_adapter *) __deprecated;
int (*detach_adapter)(struct i2c_adapter *) __deprecated;
/* Standard driver model interfaces */
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
/* driver model interfaces that don't relate to enumeration */
void (*shutdown)(struct i2c_client *);
int (*suspend)(struct i2c_client *, pm_message_t mesg);
int (*resume)(struct i2c_client *);
/* Alert callback, for example for the SMBus alert protocol.
* The format and meaning of the data value depends on the protocol.
* For the SMBus alert protocol, there is a single bit of data passed
* as the alert response's low bit ("event flag").
*/
void (*alert)(struct i2c_client *, unsigned int data);
/* a ioctl like command that can be used to perform specific functions
* with the device.
*/
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver;
const struct i2c_device_id *id_table;
/* Device detection callback for automatic device creation */
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_list;
struct list_head clients;
};
struct i2c_device_id {
char name[I2C_NAME_SIZE];
kernel_ulong_t driver_data /* Data private to the driver */
__attribute__((aligned(sizeof(kernel_ulong_t))));
};
3 硬件原理图
使用风火轮出品的DVK521底板,其I2C硬件原理图如下所示:
I2C接口
找到数据手册中对应引脚描述:
I2C引脚描述
从这里可以看出,使用的是i2c1接口。
我在i2c1上外接的是FM24CL16芯片,该芯片容量为2KB。从机地址按照下图所示:
image.png
也就是如下图所示:
I2C模组图 原理图按照上图,我们将短接帽设置在0上,也就是接地。那么,访问该芯片的地址就为0x50。
这里就直接给出一个测试例程:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#define SLAVE_ADDRESS 0x50 //FM24CL16芯片地址为0x50
#define I2C_DEV "/dev/i2c-1"//i2c_dev为i2c adapter创建的别名
//读操作先发Slaveaddr_W+Regaddr_H+Regaddr_L 3个字节来告诉设备操作器件及两个byte参数
//然后发送Slaveaddr_R读数据
static int iic_read(int fd, char buff[], int addr, int count)
{
int res;
char sendbuffer1[2];
//sendbuffer1[0]=addr>>8;
//sendbuffer1[1]=addr;
sendbuffer1[0]=addr;
write(fd,sendbuffer1,1);
res=read(fd,buff,count);
//printf("read %d byte at 0x%x\n", res, addr);
return res;
}
//在写之前,在数据前加两个byte的参数,根据需要解析
static int iic_write(int fd, char buff[], int addr, int count)
{
int res;
int i,n;
char sendbuffer[2048+3];
memcpy(sendbuffer+1, buff, count);
sendbuffer[0]=addr;
res=write(fd,sendbuffer,count+1);
//printf("write %d byte at 0x%x\n", res, addr);
}
unsigned char wbuf1[2048];
unsigned char wbuf2[2048];
unsigned char wbuf3[2048];
unsigned char wbuf4[2048];
unsigned char wbuf5[2048];
unsigned char wbuf6[2048];
unsigned char wbuf7[2048];
unsigned char wbuf8[2048];
unsigned char rbuf[2048];
int main(void){
int fd;
int res;
char ch;
char buf[50];
int regaddr,i,slaveaddr;
fd = open(I2C_DEV, O_RDWR);// I2C_DEV /dev/i2c-0
if(fd < 0){
printf("####i2c test device open failed####\n");
return -1;
}
if(ioctl(fd,I2C_TENBIT,0)<0)
{
printf("---set i2c bit error---\r\n");
return -1;
}
if(ioctl(fd,I2C_SLAVE,SLAVE_ADDRESS)<0)
{
printf("--set i2c address error---\r\n");
return -1;
}
/*write data as 512Bytes once*/
for(i = 0 ; i < 256 ; i ++)
{
wbuf1[i] = i;
wbuf2[i] = 255-i;
wbuf3[i] = i%256;
wbuf4[i] = 255-i;
wbuf5[i] = i%256;
wbuf6[i] = 255-i;
wbuf7[i] = i%256;
wbuf8[i] = 255-i;
}
ioctl(fd,I2C_SLAVE,SLAVE_ADDRESS+0);
iic_write(fd,wbuf1,0,256);
ioctl(fd,I2C_SLAVE,SLAVE_ADDRESS+1);
iic_write(fd,wbuf2,0,256);
ioctl(fd,I2C_SLAVE,SLAVE_ADDRESS+2);
iic_write(fd,wbuf3,0,256);
ioctl(fd,I2C_SLAVE,SLAVE_ADDRESS+3);
iic_write(fd,wbuf4,0,256);
ioctl(fd,I2C_SLAVE,SLAVE_ADDRESS+4);
iic_write(fd,wbuf5,0,256);
ioctl(fd,I2C_SLAVE,SLAVE_ADDRESS+5);
iic_write(fd,wbuf6,0,256);
ioctl(fd,I2C_SLAVE,SLAVE_ADDRESS+6);
iic_write(fd,wbuf7,0,256);
ioctl(fd,I2C_SLAVE,SLAVE_ADDRESS+7);
iic_write(fd,wbuf8,0,256);
ioctl(fd,I2C_SLAVE,SLAVE_ADDRESS);
iic_read(fd,rbuf,0,256);
printf("read page 0:\r\n");
for(i = 0 ; i <256 ; i ++)
{
printf("0x%0x ",rbuf[i]);
}
printf("\r\n");
ioctl(fd,I2C_SLAVE,SLAVE_ADDRESS+1);
iic_read(fd,rbuf,0,256);
printf("read page 1:\r\n");
for(i = 0 ; i <256 ; i ++)
{
printf("0x%0x ",rbuf[i]);
}
printf("\r\n");
ioctl(fd,I2C_SLAVE,SLAVE_ADDRESS+2);
iic_read(fd,rbuf,0,256);
printf("read page 2:\r\n");
for(i = 0 ; i <256 ; i ++)
{
printf("0x%0x ",rbuf[i]);
}
printf("\r\n");
ioctl(fd,I2C_SLAVE,SLAVE_ADDRESS+3);
iic_read(fd,rbuf,0,256);
printf("read page 3:\r\n");
for(i = 0 ; i <256 ; i ++)
{
printf("0x%0x ",rbuf[i]);
}
printf("\r\n");
ioctl(fd,I2C_SLAVE,SLAVE_ADDRESS+4);
iic_read(fd,rbuf,0,256);
printf("read page 4:\r\n");
for(i = 0 ; i <256 ; i ++)
{
printf("0x%0x ",rbuf[i]);
}
printf("\r\n");
ioctl(fd,I2C_SLAVE,SLAVE_ADDRESS+5);
iic_read(fd,rbuf,0,256);
printf("read page 5:\r\n");
for(i = 0 ; i <256 ; i ++)
{
printf("0x%0x ",rbuf[i]);
}
printf("\r\n");
ioctl(fd,I2C_SLAVE,SLAVE_ADDRESS+6);
iic_read(fd,rbuf,0,256);
printf("read page 6:\r\n");
for(i = 0 ; i <256 ; i ++)
{
printf("0x%0x ",rbuf[i]);
}
printf("\r\n");
ioctl(fd,I2C_SLAVE,SLAVE_ADDRESS+7);
iic_read(fd,rbuf,0,256);
printf("read page 7:\r\n");
for(i = 0 ; i <256 ; i ++)
{
printf("0x%0x ",rbuf[i]);
}
printf("\r\n");
return 0;
}
在程序编写上,需要着重注意的是访问的寄存器地址。由于我们的地址是8位的,所以,访问256B后面的数据,都是要两个以上的字节了,这个时候,我们就要用上内部的访问地址了。
简单的讲,就是要变换page。
4 通过I2C驱动ZLG7290前面板
4.1 sys_config.fex文件的配置
配置内容为:
[twi4_para]
twi4_used = 1
twi4_scl = port:PI02<3><default><default><default>
twi4_sda = port:PI03<3><default><default><default>
twi4配置
4.2 修改Kconfig和Makefile文件
在linux-sunxi/drivers/input/misc/目录下,修改Kconfig内容:
config INPUT_I2C7290
tristate "I2C7290 Keyboard device"
depends on I2C
help
Say Y here if you want to support a keypad connected via I2C
with a ZLG7290(bad design by xxx).
To compile this driver as a module, choose M here: the
module will be called ZLG7290_keypad.
然后修改该目录下的Makefile文件:
obj-$(CONFIG_INPUT_I2C7290) += keyboard_i2c7290.o
然后:
$ make menuconfig
选中I2C7290
在linux-sunxi/drivers/input/misc/目录下,编写keyboard_i2c7290.c文件,内容如下:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
//#define KEYBOARD_I2C7290_DEBUG
#ifdef KEYBOARD_I2C7290_DEBUG
#define keyboard_i2c7290_debug(fmt, ...) printk(KERN_INFO "[KEYBOARD_I2C7290][BUG][%d]"fmt, __LINE__, ##__VA_ARGS__)
#else
#define keyboard_i2c7290_debug(fmt, ...)
#endif /* KEYBOARD_I2C7290_DEBUG */
#define keyboard_i2c7290_error(fmt, ...) printk(KERN_INFO "[KEYBOARD_I2C7290][ERR]"fmt"\n", ##__VA_ARGS__)
#define DRV_NAME "keyboard_i2c7290"
#define ZLG7290_SystemReg 0x00
#define ZLG7290_Key 0x01
#define ZLG7290_RepeatCnt 0x02
#define ZLG7290_FunctionKey 0x03
#define ZLG7290_CmdBuf 0x07
#define ZLG7290_CmdBuf0 0x07
#define ZLG7290_CmdBuf1 0x08
#define ZLG7290_FlashOnOff 0x0C
#define ZLG7290_ScanNum 0x0D
#define ZLG7290_DpRam 0x10
#define ZLG7290_DpRam0 0x10
#define ZLG7290_DpRam1 0x11
#define ZLG7290_DpRam2 0x12
#define ZLG7290_DpRam3 0x13
#define ZLG7290_DpRam4 0x14
#define ZLG7290_DpRam5 0x15
#define ZLG7290_DpRam6 0x16
#define ZLG7290_DpRam7 0x17
#define LED_ONOFF_CMD 0x01
#define LED_ON_MASK 0x80
#define LED_OFF_MASK 0x7F
#define LED_ON_CMD_U16(led_id) (((((u16)(led_id | LED_ON_MASK)) << 8) | LED_ONOFF_CMD))
#define LED_OFF_CMD_U16(led_id) (((((u16)(led_id & LED_OFF_MASK)) << 8) | LED_ONOFF_CMD))
#define LED_IOCTL_ON 0
#define LED_IOCTL_OFF 1
#define I2C_DELAY_MS 2
struct UserKeyDataStru {
unsigned int key_state : 8; //按键状态[1:有键按下,0:没有按键按下(或功能键,此处不用)]
unsigned int key : 8; //按键键值
unsigned int repeat : 8; //连击计数器
unsigned int reserved : 8; //预留
};
struct KeyboardI2C7290DataStru {
char *name;
struct i2c_client *client;
struct miscdevice misc;
};
//LED与按键的对应关系定义
typedef struct LedAndKeyInfoStru {
unsigned char key;
unsigned char led_id;
}LedAndKeyInfoStru_t;
struct KeyboardI2C7290DataStru kb = {.name = DRV_NAME};
const LedAndKeyInfoStru_t led_and_key[] = {
// {.key = 1, .led_id = 0},
// {.key = 2, .led_id = 8},
// {.key = 3, .led_id = 16},
// {.key = 4, .led_id = 24},
{.key = 28, .led_id = 32},
{.key = 27, .led_id = 40},
{.key = 26, .led_id = 48},
{.key = 25, .led_id = 56},
{.key = 20, .led_id = 56},
{.key = 19, .led_id = 4},
{.key = 18, .led_id = 20},
{.key = 5, .led_id = 24},
};
static int I2C_Gets(struct i2c_client *client, unsigned int SubAddr, char *dat)
{
i2c_smbus_write_byte(client, SubAddr);
*dat = (char)(0xFF & i2c_smbus_read_byte(client));
return 0;
}
static int read_system_reg(struct KeyboardI2C7290DataStru *kb)
{
char c = '\0';
I2C_Gets(kb->client, ZLG7290_SystemReg, &c);
return (int)(c & 0x01);
}
static int read_key(struct KeyboardI2C7290DataStru *kb)
{
char c = '\0';
I2C_Gets(kb->client, ZLG7290_Key, &c);
return (int)c;
}
static int read_repeat_reg(struct KeyboardI2C7290DataStru *kb)
{
char c = '\0';
I2C_Gets(kb->client, ZLG7290_RepeatCnt, &c);
return (int)c;
}
static void led_on(unsigned char key)
{
int i = 0;
u16 value = 0;
for (i = 0; i < sizeof(led_and_key) / sizeof(LedAndKeyInfoStru_t); ++i) {
if (led_and_key[i].key == key) {
value = LED_ON_CMD_U16(led_and_key[i].led_id);
i2c_smbus_write_word_data(kb.client, ZLG7290_CmdBuf, value);
keyboard_i2c7290_debug("value = 0x%04X\n", value);
}
}
}
static void led_off(unsigned char key)
{
int i = 0;
u16 value = 0;
for (i = 0; i < sizeof(led_and_key) / sizeof(LedAndKeyInfoStru_t); ++i) {
if (led_and_key[i].key == key) {
value = LED_OFF_CMD_U16(led_and_key[i].led_id);
i2c_smbus_write_word_data(kb.client, ZLG7290_CmdBuf, value);
keyboard_i2c7290_debug("value = 0x%04X\n", value);
}
}
}
static void led_off_all(void)
{
int i = 0;
u16 value = 0;
for (i = 0; i < sizeof(led_and_key) / sizeof(LedAndKeyInfoStru_t); ++i) {
value = LED_OFF_CMD_U16(led_and_key[i].led_id);
i2c_smbus_write_word_data(kb.client, ZLG7290_CmdBuf, value);
// keyboard_i2c7290_debug("value = 0x%04X\n", value);
mdelay(I2C_DELAY_MS);
}
}
static int keyboard_i2c7290_open(struct inode *inode, struct file *file)
{
u16 value = 0;
//关闭LED闪烁显示
value = 0x0070;
i2c_smbus_write_word_data(kb.client, ZLG7290_CmdBuf, value);
mdelay(I2C_DELAY_MS);
led_off_all();
return 0;
}
static int keyboard_i2c7290_close(struct inode *inode, struct file *file)
{
led_off_all();
return 0;
}
static ssize_t keyboard_i2c7290_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
{
struct UserKeyDataStru user_data = {0};
user_data.key_state = read_system_reg(&kb);
if (user_data.key_state == 0) {
//没有键按下
} else {
mdelay(I2C_DELAY_MS);
user_data.key = (unsigned char)read_key(&kb);
mdelay(I2C_DELAY_MS);
user_data.repeat = read_repeat_reg(&kb);
}
#if 0
keyboard_i2c7290_debug("SysReg:%d\n", user_data.key_state);
keyboard_i2c7290_debug("KEY: %d\n", user_data.key);
keyboard_i2c7290_debug("Repeat: %d\n", user_data.repeat);
#endif
if (copy_to_user(buf, (const void *)&user_data, sizeof(struct UserKeyDataStru)) == 0) {
//Empty
}
return sizeof(struct UserKeyDataStru);
}
static long keyboard_i2c7290_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
unsigned char key = *((unsigned char *)arg);
keyboard_i2c7290_debug("Cmd: %d, Key: %d\n", cmd, (unsigned int)key);
switch (cmd) {
case LED_IOCTL_ON:
led_on(key);
break;
case LED_IOCTL_OFF:
led_off(key);
break;
default:
break;
}
return 0;
}
static const struct file_operations keyboard_i2c7290_fops = {
.owner = THIS_MODULE,
.open = keyboard_i2c7290_open,
.release = keyboard_i2c7290_close,
.unlocked_ioctl = keyboard_i2c7290_ioctl,
.read = keyboard_i2c7290_read,
// .write = keyboard_i2c7290_write,
};
static int keyboard_i2c7290_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret = 0;
struct miscdevice *misc = NULL;
keyboard_i2c7290_debug("In %s\n", __FUNCTION__);
keyboard_i2c7290_debug("Addr:0x%X, name:%s\n", client->addr, id->name);
kb.client = client;
misc = &kb.misc;
misc->minor = MISC_DYNAMIC_MINOR;
misc->name = DRV_NAME;
misc->fops = &keyboard_i2c7290_fops;
ret = misc_register(misc);
if (ret) {
keyboard_i2c7290_error("Unable to register a misc device\n");
return -1;
}
keyboard_i2c7290_debug("Register a misc device Ok\n");
return ret;
}
static int keyboard_i2c7290_remove(struct i2c_client *client)
{
struct miscdevice *misc = NULL;
keyboard_i2c7290_debug("In %s\n", __FUNCTION__);
misc = i2c_get_clientdata(client);
free_irq(client->irq, misc);
misc_deregister(misc);
return 0;
}
static const struct i2c_device_id keyboard_i2c7290_id[] = {
{DRV_NAME, 0},
{ },
};
MODULE_DEVICE_TABLE(i2c, keyboard_i2c7290_id);
static struct i2c_driver keyboard_i2c7290_driver = {
.driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
},
.probe = keyboard_i2c7290_probe,
.remove = keyboard_i2c7290_remove,
.id_table = keyboard_i2c7290_id,
};
/*added by wit_yuan 2017-08-11*/
static struct i2c_board_info __initdata bfin_i2c_board_info4[] = {
#if defined(CONFIG_INPUT_I2C7290)
{
I2C_BOARD_INFO("keyboard_i2c7290", 0x38),
// .irq = IRQ_PE15,
},
#endif
};
static int __init keyboard_i2c7290_init(void)
{
printk("------keyboard_i2c7290_init----\r\n");
i2c_register_board_info(4, bfin_i2c_board_info4, ARRAY_SIZE(bfin_i2c_board_info4));
return 0;
}
static void __exit keyboard_i2c7290_exit(void)
{
return ;
}
module_init(keyboard_i2c7290_init);
module_exit(keyboard_i2c7290_exit);
module_i2c_driver(keyboard_i2c7290_driver);
MODULE_AUTHOR("peace <whb_xxx@sina.com>");
MODULE_DESCRIPTION("Keyboard driver for I2c7290");
MODULE_LICENSE("GPL");
在树莓派A20上查看是否有keyboard_i2c7290设备:
root@marsboard:~# ls /dev/keyboard_i2c7290
/dev/keyboard_i2c7290
到这一步,就可以写测试程序keyTest.c:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <time.h>
#include <sys/time.h>
#define KEY_DEV "/dev/keyboard_i2c7290"
struct UserKeyDataStru {
unsigned int key_state : 8; //按键状态[1:有键按下,0:没有按键按下(或功能键,此处不用)]
unsigned int key : 8; //按键键值
unsigned int repeat : 8; //连击计数器
unsigned int reserved : 8; //预留
};
//按键对应的LED状态定义
typedef struct LedAndKeyInfoStru {
unsigned int key : 8;
unsigned int led_state : 8;
}KeyAndLedStateStru_t;
static int fd = -1;
KeyAndLedStateStru_t led_and_key[] = {
{.key = 1, .led_state = 0},
{.key = 2, .led_state = 0},
{.key = 3, .led_state = 0},
{.key = 4, .led_state = 0},
{.key = 28, .led_state = 0},
{.key = 27, .led_state = 0},
{.key = 26, .led_state = 0},
{.key = 25, .led_state = 0},
{.key = 20, .led_state = 0},
{.key = 19, .led_state = 0},
{.key = 18, .led_state = 0},
};
#define LED_IOCTL_ON 0
#define LED_IOCTL_OFF 1
static void led_onoff(unsigned int onoff, unsigned int key)
{
if (fd < 0) {
return;
}
if (onoff == 0) { //ON
ioctl(fd, LED_IOCTL_ON, &key);
} else { //OFF
ioctl(fd, LED_IOCTL_OFF, &key);
}
}
#define LED_ON_FUN(key) led_onoff(0, key)
#define LED_OFF_FUN(key) led_onoff(1, key)
static void key_led_onoff(unsigned int key)
{
int i = 0;
if (fd < 0) {
return;
}
for (i = 0; i < (sizeof(led_and_key) / sizeof(KeyAndLedStateStru_t)); ++i) {
if (led_and_key[i].key == key) {
break;
}
}
if (i >= (sizeof(led_and_key) / sizeof(KeyAndLedStateStru_t))) {
return;
}
if (led_and_key[i].led_state == 1) {
LED_OFF_FUN(key);
led_and_key[i].led_state = 0;
} else { //OFF
LED_ON_FUN(key);
led_and_key[i].led_state = 1;
}
}
int main()
{
struct UserKeyDataStru user_key_data = {0};
fd = open(KEY_DEV, O_RDONLY);
if (fd < 0) {
printf("Error open %s\n\n", KEY_DEV);
return -1;
}
printf("[%d]Open %s Ok\n", fd, KEY_DEV);
while(1) {
if (read(fd, &user_key_data, sizeof(user_key_data)) < 0) {
perror("read error");
break;
}
if (user_key_data.key_state != 0) {
printf("key = %d, repeat = %d\n", user_key_data.key, user_key_data.repeat);
key_led_onoff(user_key_data.key);
}
usleep(500);
}
close(fd);
return 0;
}
在树莓派A20上做测试,效果如下:
root@marsboard:~# ./keyTest
[3]Open /dev/keyboard_i2c7290 Ok
key = 20, repeat = 1
key = 26, repeat = 1
key = 27, repeat = 1
key = 28, repeat = 1
key = 19, repeat = 1
key = 20, repeat = 1
key = 26, repeat = 1
key = 27, repeat = 1
key = 28, repeat = 1
key = 1, repeat = 1
key = 5, repeat = 1
key = 5, repeat = 1
网友评论