前言
SPI(Serial Peripheral Interface)是一种应用广泛的通信总线,通常微处理器上会集成SPI模块以支持该通信协议,输出正确的信号的时序,并保证时序间同步,实现与外部SPI设备正常通信。当需要使用微处理器上SPI模块,但发现引脚被占用时,那么可以通过SPI Bit-banging这个方法,通过GPIO端口模拟SPI接口引脚(CS、MOSI、MISO、CLK)上的时序信号来实现SPI协议,与对应SPI设备进行通信。以下为MT7688芯片的SPI读写时序图。本文主要介绍如何在嵌入式linux发行版平台上实现SPI Bit-banging功能。
设备树语法和格式
-
嵌入式linux系统引入了设备树(Device Tree)机制,采用这种数据结构将硬件信息组织成DTS(Device Tree source)文件用于描述板级设备。设备树由基本单元——节点(node)组织成树状结构,一个设备树只有一个根节点(root node),根节点中可包含若干子节点,每个子节点可以同时包含若干属性和下一级子节点,属性用于描述了节点的具体特征。
-
以下为.dts文件中最基本的树结构,“/”表示根节点,根节点下的两个子节点分别为“node1”和“node2”,“node1”和“node2”又各自包含了子节点,如“child-node1”和“child-node2”。文件中若干键-值对,为分散在设备树中的属性。
-
-
/ {
-
node1 {
-
a-
string-
property =
"A string";
-
a-
string-list-
property =
"first string",
"second string";
-
a-byte-data-
property = [
0x01
0x23
0x34
0x56];
-
child-node1 {
-
first-child-
property;
-
second-child-
property = <
1>;
-
a-
string-
property =
"Hello, world";
-
};
-
child-node2 {
-
};
-
};
-
node2 {
-
an-
empty-
property;
-
a-cell-
property = <
1
2
3
4>; /*
each number (cell)
is a uint32 */
-
child-node1 {
-
};
-
};
-
};
由于以上的.dts文件并没有描述任何硬件设备的特征,下面以openwrt发行版上的MT7628dts文件为例对设备树进行具体说明。每个节点以“<名称>[@<设备地址>]”形式命名,当该节点描述的设备存在设备地址时需在名称后加上主地址,同时节点中以reg=<地址1 长度1] [地址2 长度2] [地址3 长度3] ... >方式列出设备使用的地址范围,父节点的 #address-cells 和 #size-cells 属性声明reg中各字段的数量。节点中的compatible属性表示使用哪个设备驱动绑定到当前节点描述的设备上。
-
Mt7628an.dtsi文件部分代码:
-
/ {
-
#address-cells = <1>;
-
#size-cells = <1>;
-
compatible =
"ralink,mtk7628an-soc";
-
-
cpus {
-
cpu@
0 {
-
compatible =
"mips,mips24KEc";
-
};
-
};
-
-
chosen {
-
bootargs =
"console=ttyS0,57600";
-
};
-
......
-
palmbus: palmbus@
10000000 {
-
compatible =
"palmbus";
-
reg = <
0x10000000
0x200000>;
-
ranges = <
0x0
0x10000000
0x1FFFFF>;
-
-
#address-cells = <1>;
-
#size-cells = <1>;
-
-
sysc: sysc@
0 {
-
compatible =
"ralink,mt7620a-sysc";
-
reg = <
0x0
0x100>;
-
};
-
gpio@
600 {
-
#address-cells = <1>;
-
#size-cells = <0>;
-
-
compatible =
"mtk,mt7628-gpio",
"mtk,mt7621-gpio";
-
reg = <
0x600
0x100>;
-
-
interrupt-parent = <&intc>;
-
interrupts = <
6>;
-
-
gpio0: bank@
0 {
-
reg = <
0>;
-
compatible =
"mtk,mt7621-gpio-bank";
-
gpio-controller;
-
#gpio-cells = <2>;
-
};
-
-
gpio1: bank@
1 {
-
reg = <
1>;
-
compatible =
"mtk,mt7621-gpio-bank";
-
gpio-controller;
-
#gpio-cells = <2>;
-
};
-
-
gpio2: bank@
2 {
-
reg = <
2>;
-
compatible =
"mtk,mt7621-gpio-bank";
-
gpio-controller;
-
#gpio-cells = <2>;
-
};
-
};
-
-
spi0: spi@b00 {
-
compatible =
"ralink,mt7621-spi";
-
reg = <
0xb00
0x100>;
-
-
resets = <&rstctrl
18>;
-
reset-names =
"spi";
-
-
#address-cells = <1>;
-
#size-cells = <0>;
-
-
pinctrl-names =
"default";
-
pinctrl
-0 = <&spi_pins>;
-
-
status =
"disabled";
-
};
-
......
-
};
-
-
pinctrl: pinctrl {
-
compatible =
"ralink,rt2880-pinmux";
-
pinctrl-names =
"default";
-
pinctrl
-0 = <&state_default>;
-
-
state_default: pinctrl0 {
-
};
-
-
spi_pins: spi {
-
spi {
-
ralink,group =
"spi";
-
ralink,function =
"spi";
-
};
-
};
-
-
spi_cs1_pins: spi_cs1 {
-
spi_cs1 {
-
ralink,group =
"spi cs1";
-
ralink,function =
"spi cs1";
-
};
-
};
-
......
-
};
-
};
SPI Bit-banging实现
嵌入式Linux内核中已提供SPI Bit-banging驱动代码,只需进行对应的配置即可使用。
首先在openwrt_widora-master/target/linux/ramips/dts路径下修改Widora.dts文件,将配置代码加入根节点中,并且在pinctrl中将Spis功能的引脚申明为GPIO引脚,驱动文件为drivers/spi路径下的spi-gpio.c,函数中注册的驱动名称为spi_gpio,与 compatible属性对应。
-
dts文件部分代码:
-
gpio-spi {
-
status =
"okay";
-
compatible =
"spi-gpio";
-
#address-cells = <0x1>;
-
ranges;
-
-
gpio-sck = <&gpio0
14
1>;
-
gpio-miso = <&gpio0
15
1>;
-
gpio-mosi = <&gpio0
16
1>;
-
cs-gpios = <&gpio0
17
1>;
-
num-chipselects = <
1>;
-
}
-
-
spi-gpio.c文件部分代码:
-
#define DRIVER_NAME "spi_gpio"
-
static
struct platform_driver spi_gpio_driver = {
-
.driver = {
-
.name = DRIVER_NAME,
-
.owner = THIS_MODULE,
-
.of_match_table = of_match_ptr(spi_gpio_dt_ids),
-
},
-
.probe = spi_gpio_probe,
-
.remove = spi_gpio_remove,
-
};
-
module_platform_driver(spi_gpio_driver);
其次在OpenWrt的配置界面中选择Kernel modules–> SPI Support –>kmod-spi-gpio,选中后会自动关联 kmod-spi-bitbang模块。
最后编译内核并烧录固件。内核启动后通过lsmod命令查看已经加载到内核中的模块的状态信息,可发现内核已经加载了spi_bitbang和spi_gpio模块。
在spi-gpio.c文件的spi_gpio_probe函数加入printk语句观察可获悉到spi-gpio驱动在启动时被加载运行,并且从dts文件中获取到定义为SPI Bit-banging功能的GPIO引脚。
结论
SPI的bit-bang方式实现SPI协议虽然可以不依赖于控制器上的SPI外设模块,但是需要代码完成时序逻辑和同步要求,相对来说性能低于控制器自身的SPI模块,因此仅适用于低速要求的应用场合。