Linux驱动学习—I2C总线

1、应用层实现I2C通信

1.1 I2C简介

I2C是很常见的一种总线协议,I2C是NXP公司设计的,I2C使用两条线在主控制器和从机之间进行数据通信。一条是SCL(串行时钟线),另外一条是SDA(串行数据线),因为I2C这两条数据线是开漏输出的,所以需要接上拉电阻,总线空闲的时候SCL和SDA处于高电平。I2C总线标准模式下速度可以达到100Kb/s,快速模式下可以达到400kb/s。如果大家玩过51单片机,肯定对模拟I2C时序这个操作并不陌生,但是在Linux上,还需要我们继续来模拟I2C的时序吗,答案是不需要的,cpu会自带I2C控制器,有了这个I2C控制器后,就不需要模拟时序了,只需要关心怎么把数据写到寄存器和怎么从寄存器读数据即可,具体的时序都是由I2C控制器来帮我们自动完成。

1.2 如何查看板子系统上有几个i2c?

Linux把I2C控制器抽象成一个i2c_adapter,我们只要来分配这个i2c_adapter,就可以得到一个I2C控制器。我们可以先来体验一下,在Linux上操作I2C是多模任意,先来看一下系统里面都有哪些I2C的节点,在开发板串口输入:

ls /dev/i2c-*

查看i2c节点:

Linux有一个非常重要的概念是一切皆文件,那么我们能不能在应用层通过open这些节点来操作I2C来跟外设I2C通信的芯片进行数据交互呢?当然是可以的,我们来以前看一下,这里我们以7寸RGB屏幕上的触摸芯片FT5X06为例。

通过原理图来确定FT5X06使用的是哪个I2C,通过下面的截图我们可以得到在开发板上,触摸芯片FT5X06使用的是I2C2,对应的节点是dev下面的i2c-1。那么跟触摸芯片FT5X06进行通信,是不是操作dev下的i2c-1这个节点就可以了?

1.3 数据包的结构体是i2c_rdwr_ioctl_data及i2c_msg

怎么在应用层操作I2C呢,应用层操作I2C是以数据包进行交流的,所有我们在应用层就要进行封包的操作。数据包对应的结构体是i2c_rdwr_ioctl_data,这个结构体在include\uapi\linux\i2c-dev.h下面,定义如下:

struct i2c_rdwr_ioctl_data {struct i2c_msg __user *msgs;    /* pointers to i2c_msgs 要发送的数据包的指针*/__u32 nmsgs;            /* number of i2c_msgs发送数据包的个数 */
};

再来看一下i2c_msg结构体的定义,这个结构体是定义在include\uapi\linux\i2c.h下面,定义如下:

struct i2c_msg {__u16 addr; /* slave addres 从机地址*/__u16 flags ;/*读写标志位,为1表示为读,反之为0,则为写*/
#define I2C_M_RD        0x0001  /* read data, from slave to master *//* I2C_M_RD is guaranteed to be 0x0001! */
#define I2C_M_TEN       0x0010  /* this is a ten bit chip address */
#define I2C_M_RECV_LEN      0x0400  /* length will be first received byte */
#define I2C_M_NO_RD_ACK     0x0800  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK    0x1000  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR  0x2000  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART       0x4000  /* if I2C_FUNC_NOSTART */
#define I2C_M_STOP      0x8000  /* if I2C_FUNC_PROTOCOL_MANGLING */__u16 len;      /* msg length 为buf的大小,单位是字节*/__u8 *buf;      /* pointer to msg data  当flags为1是,buf是要接受的数据,当flags为0,就是要发送的数据*/
};

1.4 应用程序编写

那么怎么设计程序呢,首先要看一下触摸芯片的数据手册:

了解相关的寄存器后,就可以开始写程序了

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/inpuit.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#incldue <sys/ioctl.h>
​
int fd;
​
int i2c_read_data(unsigned int slave_addr, unsigned char reg_addr)
{unsigned char data;struct i2c_rdwr_ioctl_data i2c_read_lcd;struct i2c_msg msg[2] = {[0] = {//第一个数据包先写要操作的寄存器的地址.addr = slave_addr,.flags = 0,.buf = &reg_addr,.len = sizeof(reg_addr)},[1] = {//第二个数据包再读这个寄存器的数据.addr = slave_addr,.flags = 1,.buf = &data,.len = sizeof(data)},};i2c_read_lcd.msgs = msg;i2c_read_lcd.nmsgs = 2;ret = ioctl(fd, I2C_RDWR, &i2c_read_lcd);if (ret < 0) {perror("ioctl errror is:");return ret;}return data;
}
​
int main(int argc, char *argv[])
{int TD_STATUS;fd = open("/dev/i2c-1", O_RDWR);if (fd < 0) {perror("open error");return fd;}while (1) {TD_STATUS = i2c_read_data(0x38,0x02);printf("TD_STATUS value is %d\n", TD_STATUS);sleep(1);}return 0;
}

编译并在开发板上运行这个app:

2、I2C总线实现clien设备

2.1 Linux I2C驱动框架简介

Linux中的I2C也是按照平台总线模型设计的,既然也是按照平台总线模型设计的,是不是也分为一个device和一个driver呢?但是I2C这里的device不叫device,也叫client。platform是虚拟出来的一条总线,目的是未来实现总线、设备、驱动框架。对于I2C而言,不需要虚拟出一条总线,直接使用I2C总线即可。

同样,这里先从非设备树开始,先看一下再没有设备树之前怎么实现的I2C的device部分,也就是client部分。然后再学习有了设备树之后,我们的client是怎么编写的,按照Linux的发展路径来学习。

在没有使用设备树之前,我们使用的是i2c_board_info这个结构体来描述一个I2C设备的, i2c_board_info 这个结构体如下,在include/linux/i2c.h:

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;struct fwnode_handle *fwnode;int     irq;
};

在上面的这个结构体中,type和addr这两个成员变量是必须要设置的,一个是I2C设备的名字,这个名字就是用来进行匹配用的,一个是I2C设备的器件地址。也可以使用宏,在include/linux/i2c.h::

#define I2C_BOARD_INFO(dev_type, dev_addr) \.type = dev_type, .addr = (dev_addr)

可以看出,I2C_BOARD_INFO宏其实就是设置i2c_board_info的 type 和 addr这两个成员变量。

2.2 I2C核心提供的具体硬件无关的API函数

I2C设备和驱动的匹配过程是由I2C核心来完成的,在Linux源码的drivers/i2c/i2c-core.c就是I2C的核心部分,I2C核心提供了一些与具体硬件无关的函数,如下:

2.2.1 i2c_get_adapter函数

作用:获得一个I2C适配器。

struct i2c_adapter *i2c_get_adapter(int nr);
参数:
nr:要获得的哪个I2C适配器的编号。
返回值:失败返回NULL。
2.2.2 i2c_put_adapter函数

作用:释放I2C适配器。

void i2c_put_adapter(struct i2c_adapter *adap);
参数:
adap:要释放I2C适配器。
返回值:失败返回NULL。
2.2.3 i2c_new_device函数

作用:把I2C适配器和I2C器件关联起来。

struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info);
参数:
adap:I2C适配器。
info:i2c_board_info的指针。
返回值:失败返回NULL。
2.2.4 i2c_unregister_device函数

作用:注销一个client。

void i2c_unregister_device(struct i2c_client *client);
client:i2c_client的指针。

2.3 如果使用设备树要怎么描述硬件信息呢?

在使用设备树以后,就不用这么复杂了,使用给设备树的时候只要在对应的I2C节点下创建相应设备的节点即可,比如我想添加一个触摸芯片FT5X06的设备,我就可以在对应的I2C的节点下这样写:

注意:这里使用的是10.1寸的触摸芯片gt911,4.3寸触摸芯片是tsc2007.其他都是ft5426。

查看对应设备树节点:

注意:我们使用的是I2C2,上图设备树节点看是1-0038,为啥是1呢,因为平台上的是I2C2,是从1开始计数的,而这里是从0开始计数的。

2.4 不用设备树的方法怎么描述硬件信息

下面演示不用设备树的方法进行实验,

<1> 首先先要去掉设备树上的节点:

<2> 再make menuconfig把相关i2c驱动注释掉:

注释掉之后编译内核源码,并烧写至系统,会发现看不到设备节点了

现在运行编译脚本就不会把这个设备驱动编译进去了

<3> 最后编写实现client驱动源码,并加载到系统上,会发现可以重新看到有对应的设备节点了
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
​
//分配一个I2C适配器指针
struct i2c_adapter *i2c_ada;
​
//分配一个i2c_client指针
struct i2c_client *i2c_client;
​
//支持的I2C的设备列表
struct ic_board_info ft5x06_info[] = {//每一项都代表一个I2C设备,这句话的意思就是说这个设备的名字代表ft5x06_test,器件地址是0x38{I2C_BOARD_INFO("ft5x06_test", 0x38)},
};
​
static int ft5x06_client_init(void)
{//调用 i2c_get_adapter ,获得一个I2C总线,因为ft5x06是挂载动力I2C2上,所以这个参数就是1,所以这句代码的意思就是把这个触摸触摸芯片挂载到i2c2上.i2c_ada = i2c_get_adapter(1);//把I2C适配器和I2C器件关联起来 i2c_new_device(i2c_ada, ft5x06_info);//释放I2C控制器i2c_put_adapter(i2c_ada);printk("This is ft5x06_client_init\n");return0;
}
​
static void ft5x06_client_exit(void)
{i2c_unsigned_device(i2c_client);printk("This is ft5x06_client_exit\n");
}
​
module_init(ft5x06_client_init);
module_exit(ft5x06_client_exit);
MODULE_LICENSE("GPL");

挂载驱动,可以看到有对应的设备节点了:

3、I2C总线实现driver驱动

上面实现了client部分,然后我们再来看driver部分。不管是使用设备树还是非设备树,driver部分就比较复杂了。和注册一个杂项设备或者是字符设备的套路一样,也是要先顶一个i2c_driver的结构体,然后再对他进行初始化,下面先看一下这个结构体的定义,如下图所示:

struct i2c_driver {unsigned int class;
​/* Notifies the driver that a new bus has appeared. You should avoid* using this, it will be removed in a near future.*/int (*attach_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 *);
​/* 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").* For the SMBus Host Notify protocol, the data corresponds to the* 16-bit payload data reported by the slave device acting as master.*/void (*alert)(struct i2c_client *, enum i2c_alert_protocol protocol,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;
};

初始化完成以后就是把i2c_driver注册进内核,注册进内核我们使用的是i2c_add_driver。

<1>i2c_add_driver函数宏

作用:注册一个i2c驱动。

#define i2c_add_driver(driver) \i2c_register_driver(THIS_MODULE, driver)
参数:
driver: struct i2c_driver的指针。
返回值:失败返回负值。
<2>i2c_del_driver函数

作用:删除一个i2c驱动。

extern void i2c_del_driver(struct i2c_driver *);
参数:driver: struct i2c_driver的指针。
返回值:失败返回负值。
<3>驱动代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
​
static const struct i2c_device_id ft5x06_id_ts[] = {{"xxx",0},
};
​
static const struct of_device_id ft5x06_id[]  = {{.compatible = "edt,edt-ft5306", 0},{.compatible = "edt,edt-ft5x06", 0},{.compatible = "edt,edt-ft5406", 0},
};
​
int ft5x06_probe(struct i2c_client *i2c_client, const struct i2c_device_id *id)
{printk("This is ft5x06_probe\n");//注册一个杂项设备 或 注册一个字符设备return 0;
}
​
int ft5x06_remove(struct i2c_client *i2c_client)
{return 0;
}
static struct i2c_driver ft5x06_driver = {.driver = {.owner = YHIS_MODULE,.name = "ft5x06_test",.of_match_table = ft5x06_id,},.probe = ft5x06_probe,.remove = ft5x06_remove,.id_table = ft5x06_pid_ts
};
​
static int ft5x06_driver_init(void)
{int ret;ret = i2c_add_driver(&ft5x06_driver);if (ret < 0) {printk("i2c_add_driver is error\n");return ret;}printk("This is ft5x06_driver_init\n");return 0;
}
​
static void ft5x06_driver_exit(void)
{i2c_del_driver(&ft5x06_driver);printk("This is ft5x06_driver_exit\n");
}
module_init(ft5x06_driver_init);
module_exit(ft5x06_driver_exit);
MODULE_LICENSE("GPL");

makefile:

编译驱动,加载驱动前需要取消设备树相关内容注释并烧写到板子上:

4、驱动程序实现I2C通信

在上面程序的基础上修改:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
​
static const struct i2c_device_id ft5x06_id_ts[] = {{"xxx",0},
};
​
static const struct of_device_id ft5x06_id[]  = {{.compatible = "edt,edt-ft5306", 0},{.compatible = "edt,edt-ft5x06", 0},{.compatible = "edt,edt-ft5406", 0},
};
​
static struct i2c_client *ft5x06_client;
static int ft5x06_read_reg(u8 reg_addr);
static void ft5x06_write_reg(u8 reg_addr, u8 data, u8 len);
​
static void ft5x06_read_reg(u8 reg_addr)
{u8 data;struct i2c_msg msgs[] {//第一个数据包,写[0] = {.addr  = ft5x06_client->addr,.flags = 0,.len = sizeof(reg_addr),.buf = &reg_addr,},//第二个数据包,读[1] = {.addr  = ft5x06_client->addr,.flags = 0,.len =  sizeof(data),.buf = &data,},};i2c_transfer(ft5x06_client->adapter, msgs, 2);return data;
}
​
static void ft5x06_write_reg(u8 reg_addr, u8 data, u8 len)
{u8 buff[256];struct i2c_msg msgs[] = {[0] = {.addr  = ft5x06_client->addr,.flags = 0,.len = len+1,.buf = buff,}};buff[0] = reg_addr;memcpy(&buff[1], &data, len);i2c_transfer(ft5x06_client->adapter, msgs, 1);
}
​
int ft5x06_probe(struct i2c_client *i2c_client, const struct i2c_device_id *id)
{printk("This is ft5x06_probe\n");ft5x06_client = client;//因为我们再别的函数里面用到这个client,所以我们要把他复制出来。//往地址为0x80的寄存器里面写入0x4bft5x06_write_reg(0x80, 0x4b, 1);//读取寄存器地址为0x80的数据ret = ft5x06_read_reg(0x80);printk("ret is %#x\n",ret);return 0;
}
​
int ft5x06_remove(struct i2c_client *i2c_client)
{return 0;
}
static struct i2c_driver ft5x06_driver = {.driver = {.owner = YHIS_MODULE,.name = "ft5x06_test",.of_match_table = ft5x06_id,},.probe = ft5x06_probe,.remove = ft5x06_remove,.id_table = ft5x06_pid_ts
};
​
static int ft5x06_driver_init(void)
{int ret;ret = i2c_add_driver(&ft5x06_driver);if (ret < 0) {printk("i2c_add_driver is error\n");return ret;}printk("This is ft5x06_driver_init\n");return 0;
}
​
static void ft5x06_driver_exit(void)
{i2c_del_driver(&ft5x06_driver);printk("This is ft5x06_driver_exit\n");
}
module_init(ft5x06_driver_init);
module_exit(ft5x06_driver_exit);
MODULE_LICENSE("GPL");

编译加载驱动:

改成0x02:

重新编译加载驱动:这个是没有放手指头的

放一个手指头在屏幕上,重新加载驱动:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/620302.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Windows Server 2019配置多用户远程桌面登录服务器

正文共&#xff1a;1234 字 21 图&#xff0c;预估阅读时间&#xff1a;2 分钟 很久之前&#xff0c;在介绍显卡直通的时候我们简单介绍过RDP&#xff08;Remote Desktop Protocol&#xff0c;远程桌面协议&#xff09;&#xff08;前人栽树&#xff1a;失败的服务器显卡操作&a…

第 3 场 蓝桥杯小白入门赛 解题报告 | 珂学家 | 单调队列优化的DP + 三指针滑窗

前言 整体评价 T5, T6有点意思&#xff0c;这场小白入门场&#xff0c;好像没真正意义上的签到&#xff0c;整体感觉是这样。 A. 召唤神坤 思路: 前后缀拆解 #include <iostream> #include <algorithm> #include <vector> using namespace std;int main()…

7个Linux搜索和过滤命令

1. grep 命令 – 使用条件匹配搜索文本 Grep是处理文本文件的最强大的工具之一。 语法: grep [options] pattern [files]一些有用的 grep 选项: i – 忽略条件中的大小写区别R – 递归搜索子目录c – 只打印匹配行数v – 反转匹配&#xff0c;打印不匹配的行 它搜索与正则表…

Android平台RTMP推送|轻量级RTSP服务|GB28181设备接入模块之实时快照保存JPG还是PNG?

JPG还是PNG&#xff1f; JPG和PNG是两种常见的图片文件格式&#xff0c;在压缩方式、图像质量、透明效果和可编辑性等方面存在显著差异。 压缩方式&#xff1a;JPG是一种有损压缩格式&#xff0c;通过丢弃图像数据来减小文件大小&#xff0c;因此可能会损失一些图像细节和质量…

【AIGC】IP-Adapter:文本兼容图像提示适配器,用于文本到图像扩散模型

前言 IPAdapter能够通过图像给Stable Diffusion模型以内容提示&#xff0c;让其生成参考该图像画风&#xff0c;可以免去Lora的训练&#xff0c;达到参考画风人物的生成效果。 摘要 通过文本提示词生成的图像&#xff0c;往往需要设置复杂的提示词&#xff0c;通常设计提示词变…

系列十一、Spring Security登录接口兼容JSON格式登录

一、Spring Security登录接口兼容JSON格式登录 1.1、概述 前后端分离中&#xff0c;前端和后端的数据交互通常是JSON格式&#xff0c;而Spring Security的登录接口默认支持的是form-data或者x-www-form-urlencoded的&#xff0c;如下所示&#xff1a; 那么如何让Spring Securi…

Centos常用命令整理,常用的比较全了

目 录 1、更改文件拥有者 2、修改权限 3、修改⽂件⽇期 4、链接⽂件 5、⽇期操作 6、显⽰⽇历 7、显⽰⽂件头部 8、显⽰⽂件尾部 9、显⽰⽤户标识 10、查看当前登录的⽤户 11、显⽰都谁登录到机器上 12、显⽰当前终端上的⽤户名 13、寻找⽂件…

Open3D 反算点云缩放系数(21)

Open3D 反算点云缩放系数(21) 一、算法介绍二、算法实现1.方法12.方法2(通用)一、算法介绍 上一章按照指定的系数,对点云进行了等比例缩放,这里输入缩放后的两块点云,反算二者之间的缩放系数。 二、算法实现 已知使用的俩点云是1/2的缩放关系,用于验证计算结果是否…

redis五大基础数据类型的操作指令及示例

前言 近期回顾了Redis方面的技术&#xff0c;本文就redis的5大基础数据类型的指令做了一个总结并附上示例 一、Redis是什么&#xff1f; Redis是一种开源的内存数据库&#xff0c;它被用作缓存、消息代理和键值存储。它支持多种数据结构&#xff0c;如字符串、哈希、列表、集…

【数据结构】串,数组,广义表 | 笔记整理 | C/C++实现

文章目录 前言一、串1.1、串的定义1.2、案例引入1.3、串的类型定义和存储结构1.4、串的模式匹配算法1.4.1、BF算法1.4.2、KMP算法 二、数组2.1、数组的定义2.2、数组的抽象数据类型定义2.3、数组的顺序存储2.4、特殊矩阵的压缩存储 三、广义表四、病毒案例 前言 参考视频&…

Spring Security实现详解

一、WebSecurityConfigurerAdapter 总配置类&#xff1a; 1、介绍&#xff1a;配置类 2、主要方法&#xff1a; &#xff08;1&#xff09;configure&#xff08;HttpSecurity http&#xff09; protected void configure(HttpSecurity http) throws Exception {this.logge…

【C++】wxWidgets库实现窗体程序

一、安装wxWidgets库 在Debian系统上使用wxWidgets库来创建一个基本的窗体程序&#xff0c;首先需要确保已经安装了wxWidgets相关的库和开发工具。下面是安装wxWidgets的步骤&#xff1a; 打开终端&#xff0c;使用下述命令安装wxWidgets库及其开发文件&#xff1a; sudo ap…

etcd官方docker镜像及dockerfile问题处理

解决下我之前etcd使用docker镜像启动的坑 1、问题镜像docker-file&#xff1a; 这个dockerfile看着看不出来问题&#xff0c;但如果有人真的执行我之前两篇文章的文件&#xff0c;就会有问题&#xff0c;什么问题呢&#xff0c;无法连接到etcd&#xff0c;由于我是刚装上dock…

11k+star 开源笔记应用真香 centos部署教程

leanote binary installation on Mac and Linux (En) life edited this page on Jul 21, 2017 10 revisions Pages 26 Home How to develop leanote 如何开发leanote How to install leanote on Ubuntu? How to Upgrade Leanote Install Mongodb leanote api leanote …

js页面输出的方式

JavaScript 可以通过以下方式在页面中输出内容&#xff1a; 使用 document.write() 方法&#xff0c;将文本字符串直接写入 HTML 文档中。 document.write("Hello World!");使用 innerHTML 属性&#xff0c;向元素的内部插入 HTML 代码。 document.getElementById(&…

开源监控服务一瞥:Prometheus、Grafana、Zabbix、Nagios、Icinga和Open-Falcon

前言 随着信息技术的发展&#xff0c;监控服务在维护系统稳定性和性能方面变得越来越重要。本文将比较一些流行的开源监控服务&#xff0c;以帮助你选择适合你需求的解决方案。 监控服务对比 监控服务特点优势不足性能扩展性安全性Prometheus- 多维度数据模型- 监控容器化环…

MySQL之导入、导出远程备份

一、Navicat工具导入、导出 1.1 导入 第一步&#xff1a; 右键&#xff0c;点击运行SQL文件 第二步&#xff1a; 选择要运行的SQL&#xff0c;点击开始 第三步&#xff1a; 关闭即可 1.2 导出 第一步&#xff1a; 右键选择&#xff0c;导出向导 第二步&#xff1a; 选择SQL脚…

1.3MATLAB变量及其操作

变量 变量是内存单元的一个抽象&#xff0c;在MATLAB中&#xff0c;变量以字母开头&#xff0c;后接数字下划线构成&#xff0c;MATLAB中变量名最多占据 63 个字符。变量区分大小写标准函数及命令一般使用小写字母 赋值语句 变量 表达式(;)表达式(;)总结&#xff1a;加分号&…

C++ 实现游戏(例如MC)键位显示

效果&#xff1a; 是不是有那味儿了&#xff1f; 显示AWSD&#xff0c;空格&#xff0c;Shift和左右键的按键情况以及左右键的CPS。 彩虹色轮廓&#xff0c;黑白填充。具有任务栏图标&#xff0c;可以随时关闭字体是Minecraft AE Pixel&#xff0c;如果你没有装&#xff08;大…

约瑟夫环问题解决

链表 struct List {int data;struct List* next; }创建链表 单链表 实现 struct List* listCreate() {int data;struct List* head NULL;struct List* pre NULL;struct List* current NULL;while(scanf("%d",&data) && data ! -1){current (stru…