0%

tmux使用指南

tmux快捷指令

tmux的所有指令,都包含同一个前缀,默认为Ctrl+b,但两按键相隔甚远,推荐Ctrl+a

系统指令

前缀 指令 描述
Ctrl + a ? 显示快捷键帮助文档
Ctrl + a d 断开当前会话
Ctrl + a D 选择要断开的会话
Ctrl + a Ctrl+z 挂起当前会话
Ctrl + a r 强制重载当前会话
Ctrl + a s 显示会话列表用于选择并切换
Ctrl + a : 进入命令行模式,此时可直接输入ls等命令
Ctrl + a [ 进入复制模式,按q退出
Ctrl + a ] 粘贴复制模式中复制的文本
Ctrl + a ~ 列出提示信息缓存

窗口指令(window)

前缀 指令 描述
Ctrl+a c 新建窗口
Ctrl+a & 关闭当前窗口(关闭前需输入y or n确认)
Ctrl+a 0~9 切换到指定窗口
Ctrl+a p 切换到上一窗口
Ctrl+a n 切换到下一窗口
Ctrl+a w 打开窗口列表,用于且切换窗口
Ctrl+a , 重命名当前窗口
Ctrl+a . 修改当前窗口编号(适用于窗口重新排序)
Ctrl+a f 快速定位到窗口(输入关键字匹配窗口名称)

面板指令(panel)

前缀 指令 描述
Ctrl+b 当前面板上下一分为二,下侧新建面板
Ctrl+b % 当前面板左右一分为二,右侧新建面板
Ctrl+b x 关闭当前面板(关闭前需输入y or n确认)
Ctrl+b z 最大化当前面板,再重复一次按键后恢复正常(v1.8版本新增)
Ctrl+b ! 将当前面板移动到新的窗口打开(原窗口中存在两个及以上面板有效)
Ctrl+b ; 切换到最后一次使用的面板
Ctrl+b q 显示面板编号,在编号消失前输入对应的数字可切换到相应的面板
Ctrl+b { 向前置换当前面板
Ctrl+b } 向后置换当前面板
Ctrl+b Ctrl+o 顺时针旋转当前窗口中的所有面板
Ctrl+b 方向键 移动光标切换面板
Ctrl+b o 选择下一面板
Ctrl+b 空格键 在自带的面板布局中循环切换
Ctrl+b Alt+方向键 以5个单元格为单位调整当前面板边缘
Ctrl+b Ctrl+方向键 以1个单元格为单位调整当前面板边缘(Mac下被系统快捷键覆盖)
Ctrl+b t 显示时钟

会话 session

新建会话

新建一个tmux session非常简单,语法为tmux new -s session-name,也可以简写为tmux,为了方便管理,建议指定会话名称,如下:

1
2
tmux # 新建一个无名称的会话
tmux new -s demo # 新建一个名称为demo的会话

断开当前会话

会话中操作了一段时间,我希望断开会话同时下次还能接着用,可以使用detach命令。

1
2
tmux detach # 断开当前会话
Ctrl+b + d # tmux的会话中,断开快捷键

TODO: Ctrl+b + dCtrl+b + z区别

进入之前的会话

断开会话后,想要接着上次留下的现场继续工作,就要使用到tmux的attach命令了,语法为tmux attach-session -t session-name,可简写为tmux a -t session-nametmux a。通常我们使用如下两种方式之一即可:

1
2
tmux a # 默认进入第一个会话
tmux a -t demo # 进入到名称为demo的会话

关闭会话

会话的使命完成后,一定是要关闭的。我们可以使用tmux的kill命令,kill命令有

1
2
3
4
kill-pane、kill-server、kill-session 和 kill-window共四种,其中kill-session的语法为tmux kill-session -t session-name。如下:

tmux kill-session -t demo # 关闭demo会话
tmux kill-server # 关闭服务器,所有的会话都将关闭

查看所有的会话

管理会话的第一步就是要查看所有的会话,我们可以使用如下命令:

1
2
tmux list-session # 查看所有会话
tmux ls # 查看所有会话,提倡使用简写形式

如果刚好处于会话中,可以使用对应的tmux快捷键Ctrl+b + s,此时tmux将打开一个会话列表,按上下键或者鼠标滚轮,可选中目标会话,按左右键可收起或展开会话的窗口,选中目标会话或窗口后,按回车键即可完成切换。

灵活配置

开启鼠标支持

默认情况下,tmux的多窗口之间的切换以及面板大小调整,需要输入指令才能完成,这一过程,涉及到的指令较多,而且操作麻烦,特别是面板大小调整,指令难以一步到位,这个时候开启鼠标支持就完美了。

对于tmux v2.1(2015.10.28)之前的版本,需加入如下配置:

1
2
3
4
setw -g mode-mouse on # 支持鼠标选取文本等
setw -g mouse-resize-pane on # 支持鼠标拖动调整面板的大小(通过拖动面板间的分割线)
setw -g mouse-select-pane on # 支持鼠标选中并切换面板
setw -g mouse-select-window on # 支持鼠标选中并切换窗口(通过点击状态栏窗口名称)

有的地方可能会出现set-window-option的写法,setw就是它的别名。

对于tmux v2.1及以上的版本,仅需加入如下配置:

1
set-option -g mouse on # 等同于以上4个指令的效果

需要注意的是,开启鼠标支持后,iTem2默认的鼠标选中即复制功能需要同时按下 Alt 键,才会生效。

快速面板切换

鼠标支持确实能带来很大的便捷性,特别是对于习惯了鼠标操作的tmux新手,但对于键盘爱好者而言,这不是什么好消息,对他们而言,双手不离键盘是基本素质。

虽然指令前缀加方向键可以切换面板,但方向键太远,不够快,不够Geek。没关系,我们可以将面板切换升级为熟悉的h、j、k、l键位。

1
2
3
4
5
# 绑定hjkl键为面板切换的上下左右键
bind -r k select-pane -U # 绑定k为↑
bind -r j select-pane -D # 绑定j为↓
bind -r h select-pane -L # 绑定h为←
bind -r l select-pane -R # 绑定l为→

-r表示可重复按键,大概500ms之内,重复的h、j、k、l按键都将有效,完美支持了快速切换的Geek需求。

dts←→dtb

  1. 反编译dtb

    1
    dtc -I dtb -O dts -o *.dts  *.dtb
  2. 正编译dts

    1
    dtc -I dts -O dtb -o *.dtb  *.dts
  3. dtc -help

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
Usage: dtc [options] <input file>

Options: -[qI:O:o:V:d:R:S:p:a:fb:i:H:sW:E:@AThv]
-q, --quiet
Quiet: -q suppress warnings, -qq errors, -qqq all
-I, --in-format <arg>
Input formats are:
dts - device tree source text
dtb - device tree blob
fs - /proc/device-tree style directory
-o, --out <arg>
Output file
-O, --out-format <arg>
Output formats are:
dts - device tree source text
dtb - device tree blob
asm - assembler source
-V, --out-version <arg>
Blob version to produce, defaults to 17 (for dtb and asm output)
-d, --out-dependency <arg>
Output dependency file
-R, --reserve <arg>
Make space for <number> reserve map entries (for dtb and asm output)
-S, --space <arg>
Make the blob at least <bytes> long (extra space)
-p, --pad <arg>
Add padding to the blob of <bytes> long (extra space)
-a, --align <arg>
Make the blob align to the <bytes> (extra space)
-b, --boot-cpu <arg>
Set the physical boot cpu
-f, --force
Try to produce output even if the input tree has errors
-i, --include <arg>
Add a path to search for include files
-s, --sort
Sort nodes and properties before outputting (useful for comparing trees)
-H, --phandle <arg>
Valid phandle formats are:
legacy - "linux,phandle" properties only
epapr - "phandle" properties only
both - Both "linux,phandle" and "phandle" properties
-W, --warning <arg>
Enable/disable warnings (prefix with "no-")
-E, --error <arg>
Enable/disable errors (prefix with "no-")
-@, --symbols
Enable generation of symbols
-A, --auto-alias
Enable auto-alias of labels
-T, --annotate
Annotate output .dts with input source file and line (-T -T for more details)
-h, --help
Print this help and exit
-v, --version
Print version and exit

if(p)和if(!p)含义

对于 int *p; 来说

1
2
if (!p) ==> if (p == NULL)
if (p) ==> if (p != NULL)

对于 int p; 来说

1
2
if (!p) ==> if (p == 0)
if (p) ==> if (p != 0)

在c语言中,0 被认为是假,非0 被认为是真

如果p的值为0,!p判定为真;

如果p的值不是0,!p的判定为假

如果是一个指针,指针有内容,即:p的值不是0,!p的判定为假

如果指针没有内容,NULL,即:p的值为0,!p判定为真;

win10 右键菜单添加使用 Neovim 打开方式

  1. 打开注册表编辑器,开始–>运行–>regedit

  2. 定位到:HKEY_CLASSSES_ROOT—> * —>Shell,在Shell 上右击,新建—> 项,输入: Open With Neovim(使用Neovim打开)

  3. 在 Open With Neovim 右键—>新建—>字符串值,数值名称设置为:Icon,数值数据设置为:xxx\nvim-qt.exe,0 (替换成自己的路径地址)

    1
    2
    3
    4
    /* 以 Gvim 举例,两者类似,注意应用时候替换路劲
    * 数值名称:Icon
    * 数值数据:C:\MyProgram\gvim73\gVimPortable\vim\vim73\gvim.exe,0
    */

上面的设置会带来一个小问题:当文件名的最后一个字符为空格时,使用VIM打开某个文件时会新建一个空白的文件,而不是直接打开该文件。

  • 解决方法:加引号,如下图所示,注意应用时候替换路径

    xxxx.exe “%1”

80字符提示条

1
2
3
4
5
6
" 设置编码最长80字符提示条,额外高亮显示第80列
set cc=80
" 超过长度自动折行 default ,根据屏幕长度
" Tjis option changes how text is displayed. It do not change the text
set wrap

vim 开启错误信息一闪而过,可使用命令查看

:messages

使用silent静默执行命令

如果不希望显示提示信息,那么可以使用:silent命令

1
对于*`git` executable not found. Most commands will not be available. To suppress this message, prepend `silent!` to `call plug#begin(...)`.*错误

在vim配置文件中修改为:

1
silent! call plug#begin

neovim-qt 标签栏显示有问题

neovim 还有一个配置文件,ginit.vim ,与init.vim 同一个目录下。

These options are specific to Neovim Qt. The options cannot be set from init.vim, they must be set from ginit.vim.

Recommended ginit.vim

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
" Enable Mouse
set mouse=a

" Set Editor Font
if exists(':GuiFont')
" Use GuiFont! to ignore font errors
GuiFont {font_name}:h{size}
endif

" Disable GUI Tabline
if exists(':GuiTabline')
GuiTabline 0
endif

" Disable GUI Popupmenu
if exists(':GuiPopupmenu')
GuiPopupmenu 0
endif

" Enable GUI ScrollBar
if exists(':GuiScrollBar')
GuiScrollBar 1
endif

" Right Click Context Menu (Copy-Cut-Paste)
nnoremap <silent><RightMouse> :call GuiShowContextMenu()<CR>
inoremap <silent><RightMouse> <Esc>:call GuiShowContextMenu()<CR>
vnoremap <silent><RightMouse> :call GuiShowContextMenu()<CR>gv

For more options, try :help nvim_gui_shim and scroll down to Commands

使用powerline 字体斜体导致状态栏消失

增大行距

set linespace=5

编译安装与卸载 make install 与 make uninstall

编译安装

通用过程

  1. ./configure
  2. make
  3. make install

注意:第一步./configure时,可以加--prefix的参数指定安装路径
更多参数自己参考

make uninstall 卸载安装的软件

注意 Makefile 文件里的指令
一般 install 对应 uninstall ,大部分的作者会写有卸载的部分,这时只要简单地执行

1
make unistall

就可以,如果作者懒没有写,那就只有根据make install中的步骤,看它把什么文件拷到哪去了,然后分别手动删除。

注意:编译安装完成后,不要删除源代码,不然就算作者写了unnistall目标,也没有makefile可以执行了。

交叉编译

1
./configure --target=riscv64-unknown-elf

交叉编译配置参数主要有三个

1
2
3
4
System types:
--build
--host
--target
  • build 一般与 host 相同
  • host 指运行在的 PC 端
  • target 指交叉编译的架构,不配置即可 host 保持一致

gdb 添加 python 支持

./configure --help 中并没有 --with-python 选项,但并不影响。

在配置阶段加上

1
2
3
which python

./configure --with-python=/path

添加 python 支持。

NOTE:GDB 源码编译可行,其他类型的源码编译是否可以举一反三。

linux变量的种类

按变量的生存周期来划分,Linux变量可分为两类:

  1. 永久的:需要修改配置文件,变量永久生效。
  2. 临时的:使用export命令声明即可,变量在关闭shell时失效。

设置变量有三种方法:

  1. /etc/profile文件中添加变量,对所有用户永久生效
  2. 在用户目录下对.bashrc文件进行修改,对单一用户永久生效
  3. 直接运行export命令定义变量,只是临时对当前shell有效,shell退出后变量失效

查看环境变量

  1. 可用 export 命令查看PATH值
    1
    2
    export
    //但这种方式会显示一大堆数据
  2. 单独查看PATH环境变量
    1
    2
    //应用较多
    echo $PATH
  3. 使用printenv打印环境变量
    1
    printenv
  4. 使用set查看所有本地定义的环境变量
    1
    set

临时添加环境变量

1
export PATH=/opt/STM/STLinux-2.3/devkit/sh4/bin:$PATH

PATH 设置会在终端关闭后就会消失。

永久添加环境变量,

  1. 当前用户生效
    1
    2
    3
    4
    5
    6
    7
    8
    9
    vim ~/.bashrc

    //在文档最后,添加:

    export PATH="/opt/STM/STLinux-2.3/devkit/sh4/bin:$PATH"

    //保存,退出,然后运行:

    source ~/.bashrc
  2. 所有用户生效
    1
    2
    3
    4
    5
    6
    7
    8
    9
    vim /etc/profile

    //在文档最后,添加:

    export PATH="/opt/STM/STLinux-2.3/devkit/sh4/bin:$PATH"

    //保存,退出,然后运行:

    source /etc/profile
    不报错则成功。

注意:当然$PATH是放在开头还是最后是没有影响的,要注意 :的使用

常用的环境变量

  • PATH 决定了shell将到哪些目录中寻找命令或程序
  • HOME 当前用户主目录
  • HISTSIZE 历史记录数
  • LOGNAME 当前用户的登录名
  • HOSTNAME 指主机的名称
  • SHELL 当前用户Shell类型
  • LANGUGE  语言相关的环境变量,多语言可以修改此环境变量
  • MAIL 当前用户的邮件存放目录
  • PS1 基本提示符,对于root用户是#,对于普通用户是$

其他

1、执行多次source /etc/profile之后,打印PATH的值会出现重复

在阅读一些开源代码时,常会在注释中碰到诸如:TODO、FIXME 和 XXX的单词,它们是有其特殊含义的。在编写代码的时候我们可以利用这些公认的特殊注释方式快速简介表达自己的目地。

TODO: <说明>

1
2
常出现在一些函数的上方或者内部,它表示该注释标识处,有一些代码功能还未实现,未来会实现。
<说明> 中应该简单描述下该功能。

FIXME: <说明>

1
2
如果代码中有该标识,说明标识处代码需要修正,甚至代码是错误的,有可能无法正常工作,需要修复,
<说明> 中应该简单描述下如何修复该问题。

XXX: <说明>

1
2
如果代码中有该标识,说明标识处代码虽然实现了功能,但是实现的方法有待商榷,将来可以进行一些改进优化,
<说明> 中应该简单描述下改进优化的策略。

配置git

先配置git邮箱和用户名

1
2
git config --global user.email "user@email.com"
git config --global user.name "name"

可通过以下命令查看配置

1
git config --list

生成SSH keys

生成SSH keys 密钥

1
ssh-keygen -t rsa -C "your_email@example.com"

默认会在相应路径下(/your_home_path)生成id_rsaid_rsa.pub两个文件

  • 输入passphrase(本步骤可以跳过)
  • id_rsa.pub文件,里面的信息即为SSH key

添加ssh key

  1. 在git bash 界面 cat id_rsa.pub 文件

  2. 将打印信息上传至github setting -> ADD SSH key

选择ssh链接

git clone 代码有HTTP链接和 SSH链接,两者密钥,或者认证方式不同,选择SSH方式链接

SPI驱动框架

SPI 驱动框架和 I2C 很类似 ,都分为主机控制器驱动和设备驱动。

SPI主机驱动

​ SPI 主机驱动就是 SOC 的 SPI 控制器驱动,类似 I2C 驱动里面的适配器驱动。 Linux 内核
使用结构体spi_master 表示 SPI 主机驱动 。

​ SPI 主机驱动的核心就是申请 spi_master,然后初始化 spi_master,最后向 Linux 内核注册
spi_master。

​ 和 I2C 适配器驱动一样, SPI 主机驱动一般都是半导体厂商去编写的。

SPI 设备驱动

​ Linux 内核使用 spi_driver 结构体来表示 spi 设备驱动。 SPI 设备驱动的关键就是 spi_driver ,申请,设置,向内核注册 spi_driver

1
2
3
4
5
6
7
8
9
10
static struct spi_driver chip_spi_driver = {
.probe = chip_spi_probe,
.remove = chip_spi_remove,
.driver = {
.name = "chip",
.owner = THIS_MODULE,
.of_match_table = chip_of_match,
},
.id_table = chip_id,
};

spi_driver 注册

  1. 传统方法

在驱动入口init函数中,调用spi_register_driver 来注册 spi_driver。

在驱动出口exit函数中,调用spi_unregister_driver 来注销 spi_driver

  1. 使用宏定义module_spi_driver 来直接注册spi_driver

这个宏定义将 spi_register/unregister_driver() 与 module_init 和 module_exit 封装了起来。

注册完成,匹配成功就可调用probe函数

SPI device 与 driver 匹配

同样与I2C子系统非常相似,当匹配成功, probe 函数就会被调用。

常用compatible属性进行匹配。

spi_driver设置

spi设备驱动本质上任属于字符设备驱动范畴。

  • 在prob函数中

进行字符设备的注册,设备节点的创建,将file_operations结构体注册进内核,并初始化spi_device

1
2
3
4
static struct spi_device *spi; 

spi->mode = SPI_MODE_0; /*MODE0,CPOL=0,CPHA=0*/
spi_setup(spi);
  • 在remove函数

进行与prob函数中顺序相反的注销。

  • 填充file_operations结构体的open, read, write,release等函数。

注意:platform_device 中如果不提供 release 函数 ,则在调用 platform_device_unregister 时会出现警告,

如果实在无事可做,可以提供一个空的release 函数。

SPI 设备树节点

设备树添加节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
&ecspi3 { 
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi3>;
cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>; /* 软件片选 */
status = "okay";


spidev: icm20608@0{
compatible = "invensense,icm20608";
interrupt-parent = <&gpio1>;
interrupts = <1 1>;
spi-max-frequency = <8000000>;
reg = <0>;
};
};

pinctrl子系统修改

1
2
3
4
5
6
7
8
9
10
pinctrl_ecspi3: ecspi3 {              
fsl,pins = <
MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x000010B0
MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x000010B0
MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x000010B0
//MX6UL_PAD_UART2_TX_DATA__ECSPI3_SS0 0x000010B0//硬件片选
MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x000010B0 //软件片选
>;
};

片选引脚使用软件片选,即使用一个GPIO引脚进行CS引脚模拟。

SPI 通信过程

spi通信步骤

  1. 申请并初始化 spi_transfer,设置 spi_transfer 的 tx_buf 成员变量, tx_buf 为要发送的数
    据。然后设置 rx_buf 成员变量, rx_buf 保存着接收到的数据。最后设置 len 成员变量,也就是
    要进行数据通信的长度。
  2. 使用 spi_message_init 函数初始化 spi_message。
  3. 使用spi_message_add_tail函数将前面设置好的spi_transfer添加到spi_message队列中。
  4. 使用 spi_sync 函数完成 SPI 数据同步传输。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/* SPI 多字节发送 */
static int spi_send(struct spi_device *spi, u8 *buf, int len)
{
int ret;
struct spi_message message;
struct spi_transfer transfer = {
.tx_buf = buf,
.len = len,
};

spi_message_init(&message); /* 初始化 spi_message */
spi_message_add_tail(transfer, &message);/* 将 spi_transfer 添加到 spi_message 队列 */
ret = spi_sync(spi, &message); /* 同步传输 */

return ret;
}

/* SPI 多字节接收 */
static int spi_receive(struct spi_device *spi, u8 *buf, int len)
{
int ret;
struct spi_message message;
struct spi_transfer transfer = {
.rx_buf = buf,
.len = len,
};

spi_message_init(&message); /* 初始化 spi_message */
spi_message_add_tail(transfer, &message);/* 将 spi_transfer 添加到 spi_message 队列 */
ret = spi_sync(spi, &message); /* 同步传输 */

return ret;
}

​ SPI 数据传输也支持异步传输,异步传输不会阻塞地等到完成,异步传输需要设置 spi_message 中的 complete成员变量, complete 是一个回调函数,当 SPI 异步传输完成以后此函数就会被调用。

SPI 异步传输函数为 spi_async,函数原型如下:

1
int spi_async(struct spi_device *spi, struct spi_message *message)

spi通信函数封装

参考内核源码 /kernel/driver/spi/spi.c spi.h

实际还是 spi_transfer, spi_message进一步封装

1
2
3
4
5
6
7
8
9
10
11
12
static inline int spi_write(struct spi_device *spi, const void *buf, size_t len)

static inline int spi_read(struct spi_device *spi, void *buf, size_t len)

extern int spi_write_then_read(struct spi_device *spi,
const void *txbuf, unsigned n_tx,
void *rxbuf, unsigned n_rx);

static inline ssize_t spi_w8r8(struct spi_device *spi, u8 cmd)

static inline ssize_t spi_w8r16(struct spi_device *spi, u8 cmd)

I2C通信

I2C协议

I2C协议运用已经非常广泛,直接看图,一图胜前言。

image

I2C设备驱动数据处理

  • Linux内核代码:drivers\i2c\i2c-core.c

数据处理函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* i2c_transfer - execute a single or combined I2C message
* @adap: Handle to I2C bus
* @msgs: One or more messages to execute before STOP is issued to
* terminate the operation; each message begins with a START.
* @num: Number of messages to be executed.
*
* Returns negative errno, else the number of messages executed.
*
* Note that there is no requirement that each message be sent to
* the same slave address, although that is the most common model.
*/
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)

使用i2c_transfer函数发送数据之前要先构建好 i2c_msg

i2c_msg 结构体定义在文件 include/uapi/linux/i2c.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags;
#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 */
__u8 *buf; /* pointer to msg data */
};

以读取 I2C 设备寄存器数据为例:

(参考正点原子)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int ret;
struct i2c_msg msg[2];

/* msg[0],第一条写消息,发送要读取的寄存器地址 */
msg[0].addr = addr; /* I2C 设备地址 */
msg[0].flags = 0; /* 标记为发送数据 */
msg[0].buf = &reg; /* 读取的首地址 */
msg[0].len = 1; /* reg 长度 */

/* msg[1],第二条读消息,读取寄存器数据 */
msg[1].addr = addr; /* I2C 设备地址 */
msg[1].flags = I2C_M_RD; /* 标记为读取数据 */
msg[1].buf = val; /* 读取数据缓冲区 */
msg[1].len = len; /* 要读取的数据长度 */

ret = i2c_transfer(adapter, msg, 2);

向 I2C 设备寄存器写入数据:

(参考正点原子)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int ret;
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)
dev->private_data;

b[0] = reg; /* 寄存器首地址 */
memcpy(&b[1],buf,len); /* 将要发送的数据拷贝到数组 b 里面 */

msg.addr = addr; /* I2C 设备地址 */
msg.flags = 0; /* 标记为写数据 */

msg.buf = b; /* 要发送的数据缓冲区 */
msg.len = len + 1; /* 实际数据长度 + 寄存器地址长度*/

ret = i2c_transfer(client->adapter, &msg, 1);

Linux内核对i2c_transfer进一步封装,形成两个API函数分别用于I2C数据的接收和发送操作 。代码位于:drivers\i2c\i2c-core.c

  • i2c_master_recv
1
2
3
4
5
6
7
8
9
10
/**
* i2c_master_recv - issue a single I2C message in master receive mode
* @client: Handle to slave device
* @buf: Where to store data read from slave
* @count: How many bytes to read, must be less than 64k since msg.len is u16
*
* Returns negative errno, or else the number of bytes read.
*/
int i2c_master_recv(const struct i2c_client *client, char *buf, int count)

  • i2c_master_send
1
2
3
4
5
6
7
8
9
/**
* i2c_master_send - issue a single I2C message in master transmit mode
* @client: Handle to slave device
* @buf: Data that will be written to the slave
* @count: How many bytes to write, must be less than 64k since msg.len is u16
*
* Returns negative errno, or else the number of bytes written.
*/
int i2c_master_send(const struct i2c_client *client, const char *buf, int count)

kernel中i2c驱动

核心 i2c_driver 结构体

分配、设置、注册一个i2c_driver结构体

1
2
3
4
5
6
7
8
9
static struct i2c_driver i2c_example_driver = {
.driver = {
.name = "example",
.of_match_table = of_match_ids_example,
},
.probe = i2c_example_driver_probe,
.remove = i2c_example_driver_remove,
.id_table = example_ids,
};

出入口函数

1
2
3
4
5
6
7
8
9
10
11
12
/*入口函数 注册一个i2c_drvier */
static int __init i2c_driver_init(void)
{
return i2c_add_driver(&i2c_example_driver);
}
module_init(i2c_driver_init);

/**出口函数 del一个i2c_drvier */
static void __exit i2c_driver_exit(void)
{
i2c_del_driver(&i2c_example_driver);
}

i2c_driver表明能支持哪些设备

i2c_driver表明能支持哪些设备:

  • 使用of_match_table来判断

    • 设备树中,某个I2C控制器节点下可以创建I2C设备的节点
      • 如果I2C设备节点的compatible属性跟of_match_table的某项兼容,则匹配成功
    • i2c_client.name跟某个of_match_table[i].compatible值相同,则匹配成功
    1
    2
    3
    4
    5
    static const struct of_device_id of_match_ids_example[] = {
    { .compatible = "com_name,chip_name", .data = NULL},
    { /* END OF LIST */ } /*最后空一项为必须,空闲为end 判断条件*/
    };
    // of_ 开头一般与设备树关联
  • 使用id_table来判断

    • i2c_client.name跟某个id_table[i].name值相同,则匹配成功
    1
    2
    3
    4
    static const struct i2c_device_id example_ids[] = {
    { "chip_name", (kernel_ulong_t)NULL },
    { /* END OF LIST */ }
    };

i2c_driver跟i2c_client匹配成功后,就调用i2c_driver.probe函数。

i2c_client

参考资料 Linux内核文档: 5.0版本内核

  • Documentation\i2c\instantiating-devices.rst
  • Documentation\i2c\writing-clients.rst

i2c_client表示一个I2C设备,创建i2c_client的方法有4种:

  • 方法1

    • 通过I2C bus number来创建
    1
    int i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len);
    • 通过设备树结点
    1
    2
    3
    4
    5
    /* 在某个I2C控制器的节点下,添加如下代码: */		
    ap3216c@1e {
    compatible = "lite-on,ap3216c";
    reg = <0x1e>;
    };
  • 方法2

    有时候无法知道该设备挂载哪个I2C bus下,无法知道它对应的I2C bus number。
    但是可以通过其他方法知道对应的i2c_adapter结构体。
    可以使用下面两个函数来创建i2c_client:

    • i2c_new_device
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    static struct i2c_client *ap3216c_client;

    static int __init i2c_client_ap3216c_init(void)
    {
    struct i2c_adapter *adapter;
    struct i2c_board_info board_info = {
    I2C_BOARD_INFO("ap3216c", 0x1e),
    };

    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    /* register I2C device */
    adapter = i2c_get_adapter(0);
    ap3216c_client = i2c_new_device(adapter, &board_info);
    i2c_put_adapter(adapter);
    return 0;
    }
    • i2c_new_probed_device
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    static struct i2c_client *ap3216c_client;
    /* 罗列出所有可能的地址,这里只有一个*/
    static const unsigned short normal_i2c[] = { 0x1e, I2C_CLIENT_END };

    static int __init i2c_client_ap3216c_init(void)
    {
    struct i2c_adapter *adapter;

    struct i2c_board_info i2c_info;

    /* 将名字信息写入i2c_info,先清0 */
    memset(&i2c_info, 0, sizeof(struct i2c_board_info));
    strscpy(i2c_info.type, "ap3216c", sizeof(i2c_info.type));

    adapter = i2c_get_adapter(0); /* 第0根i2c总线 */
    ap3216c_client = i2c_new_probed_device(adapter, &i2c_info,
    normal_i2c, NULL);

    i2c_put_adapter(adapter);
    return 0;
    }
    • 差别

      • i2c_new_device:

        会创建i2c_client,即使该设备并不存在

      • i2c_new_probed_device:

        它成功的话,会创建i2c_client,并且表示这个设备肯定存在

        I2C设备的地址可能发生变化,比如AT24C02的引脚A2A1A0电平不一样时,设备地址就不一样

        可以罗列出可能的地址

        i2c_new_probed_device使用这些地址判断设备是否存在

  • 方法3 (不推荐):由i2c_driver.detect函数来判断是否有对应的I2C设备并生成i2c_client

  • 方法4 : 通过用户空间(user-space)生成

    调试时、或者不方便通过代码明确地生成i2c_client时,可以通过用户空间来生成。

    1
    2
    3
    4
    5
    // 创建一个i2c_client, .name = "eeprom", .addr=0x50, .adapter是i2c-3
    # echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-3/new_device

    // 删除一个i2c_client
    # echo 0x50 > /sys/bus/i2c/devices/i2c-3/delete_device

SMBus协议

参考资料:

  • Linux内核文档:Documentation\i2c\smbus-protocol.rst

  • Linux内核代码:drivers\i2c\i2c-core.c

  • SMBus协议:

SMBus是I2C协议的一个子集。

SMBus: System Management Bus,系统管理总线。

SMBus最初的目的是为智能电池、充电电池、其他微控制器之间的通信链路而定义的。
SMBus也被用来连接各种设备,包括电源相关设备,系统传感器,EEPROM通讯设备等等。
SMBus 为系统和电源管理这样的任务提供了一条控制总线,使用 SMBus 的系统,设备之间发送和接收消息都是通过 SMBus,而不是使用单独的控制线,这样可以节省设备的管脚数。
SMBus是基于I2C协议的,但SMBus要求更严格。

image

如今很多设备都实现了SMBus,即使I2C控制器没有实现SMBus,软件方面也是可以使用I2C协议来模拟SMBus。
在Linux上建议优先使用SMBus

SMBus与一般I2C协议的差别

  • VDD的极限值不一样

    • I2C协议:范围很广,甚至讨论了高达12V的情况
    • SMBus:1.8V~5V
  • 最小时钟频率、最大的Clock Stretching

    • Clock Stretching含义:某个设备需要更多时间进行内部的处理时,它可以把SCL拉低占住I2C总线

    • I2C协议:时钟频率最小值无限制,Clock Stretching时长也没有限制

    • SMBus:时钟频率最小值是10KHz,Clock Stretching的最大时间值也有限制

  • 地址回应(Address Acknowledge)

    • I2C协议:没有强制要求必须发出回应信号
    • SMBus:强制要求必须发出回应信号,这样对方才知道该设备的状态:busy,failed,或是被移除了
  • SMBus协议明确了数据的传输格式

    • I2C协议:它只定义了怎么传输数据,但是并没有定义数据的格式,这完全由设备来定义
    • SMBus:定义了几种数据格式
  • REPEATED START Condition(重复发出S信号)

    • 比如读EEPROM时,涉及2个操作:
      • 把存储地址发给设备
      • 读数据
    • 在写、读之间,可以不发出P信号,而是直接发出S信号:这个S信号就是REPEATED START
    • 如下图所示
      image
  • SMBus Low Power Version

    • SMBus也有低功耗的版本

SMBus通信

Linux内核上集成了SMBus,可以认为是对I2C的进一步封装,在与外设使用I2C子系统进行数据传输时,建议使用SMBus协议。

以下是常用的SMBus函数,更多资料参考内核源码:drivers\i2c\i2c-core.c

Receive Byte

image

读取一个字节,主机接收到一个字节后不需要回应(上图中N表示不回应)

1
2
3
4
5
6
7
8
/**
* i2c_smbus_read_byte - SMBus "receive byte" protocol
* @client: Handle to slave device
*
* This executes the SMBus "receive byte" protocol, returning negative errno
* else the byte received from the device.
*/
s32 i2c_smbus_read_byte(const struct i2c_client *client)

Send Byte

image

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* i2c_smbus_write_byte - SMBus "send byte" protocol
* @client: Handle to slave device
* @value: Byte to be sent
*
* This executes the SMBus "send byte" protocol, returning negative errno
* else zero on success.
*/
s32 i2c_smbus_write_byte(const struct i2c_client *client, u8 value)
{
return i2c_smbus_xfer(client->adapter, client->addr, client->flags,
I2C_SMBUS_WRITE, value, I2C_SMBUS_BYTE, NULL);
}

Read Byte

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* i2c_smbus_read_byte_data - SMBus "read byte" protocol
* @client: Handle to slave device
* @command: Byte interpreted by slave
*
* This executes the SMBus "read byte" protocol, returning negative errno
* else a data byte received from the device.
*/
s32 i2c_smbus_read_byte_data(const struct i2c_client *client, u8 command)
{
union i2c_smbus_data data;
int status;

status = i2c_smbus_xfer(client->adapter, client->addr, client->flags,
I2C_SMBUS_READ, command,
I2C_SMBUS_BYTE_DATA, &data);
return (status < 0) ? status : data.byte;
}

Read Word

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* i2c_smbus_read_word_data - SMBus "read word" protocol
* @client: Handle to slave device
* @command: Byte interpreted by slave
*
* This executes the SMBus "read word" protocol, returning negative errno
* else a 16-bit unsigned "word" received from the device.
*/
s32 i2c_smbus_read_word_data(const struct i2c_client *client, u8 command)
{
union i2c_smbus_data data;
int status;

status = i2c_smbus_xfer(client->adapter, client->addr, client->flags,
I2C_SMBUS_READ, command,
I2C_SMBUS_WORD_DATA, &data);
return (status < 0) ? status : data.word;
}

Write Byte

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* i2c_smbus_write_byte_data - SMBus "write byte" protocol
* @client: Handle to slave device
* @command: Byte interpreted by slave
* @value: Byte being written
*
* This executes the SMBus "write byte" protocol, returning negative errno
* else zero on success.
*/
s32 i2c_smbus_write_byte_data(const struct i2c_client *client, u8 command,
u8 value)
{
union i2c_smbus_data data;
data.byte = value;
return i2c_smbus_xfer(client->adapter, client->addr, client->flags,
I2C_SMBUS_WRITE, command,
I2C_SMBUS_BYTE_DATA, &data);
}

Write Word

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* i2c_smbus_write_word_data - SMBus "write word" protocol
* @client: Handle to slave device
* @command: Byte interpreted by slave
* @value: 16-bit "word" being written
*
* This executes the SMBus "write word" protocol, returning negative errno
* else zero on success.
*/
s32 i2c_smbus_write_word_data(const struct i2c_client *client, u8 command,
u16 value)
{
union i2c_smbus_data data;
data.word = value;
return i2c_smbus_xfer(client->adapter, client->addr, client->flags,
I2C_SMBUS_WRITE, command,
I2C_SMBUS_WORD_DATA, &data);
}