[导读] 干过单片机的盆友或许都拿IO口对着时序模拟过SPI主控制器,在做嵌入式Linux设备开发时,发现SPI对应的脚都被用了,或者被当成别的用途了,这时候咋办?你或许会说我翻IO口写个字符驱动不就完了么?当然你可以这么做,然而并没有必要。自己写也挺麻烦且未必稳。憋慌!且看本文分解~SPI什么鬼? SPI(Serial Peripheral Interface) 是一种嵌入式系统中应用广泛的同步串行通信、主从架构式总线接口。80年代由摩托罗拉开发,已成为事实标准。 要理解啥是SPI,先上图,一图胜千言: 常见的SPI接口有这样几个引脚: - SCLK: 串行时钟,总是主端负责输出(Master)。总是由主端控制该信号,从端为输入采样。
- MOSI:主出从入(Master Output Slave Input)。总是由主端控制该信号,从端为输入采样。
- MISO:主入从出(Master Input Slave Output)。总是由从端控制该信号,主端为输入采样。
- :从选择信号(Slave Select)。总是由主端控制该信号,从端为输入采样。
5 h) t, p. |; u! T( ^+ D4 g
常见的两种总线连接方式, 方式一:独立片选9 T4 i3 W, [7 x" e4 J3 C
3 d ~, S/ o5 {4 C
每个从设备都有独立的片选引脚,主机同一时间段内,与一个从设备进行通信,也即选中一个从设备,其他线并接共用
e, g z& U# ]9 c$ c$ C/ x* {2 N$ ]方式二:菊花链+ ^: A, W: ^$ W0 h- ~- L
SCK/CS共用,数据线入出环形链接,bit流在SCK时钟作用下击鼓传花,逐次移位传递。
9 ?) W v# I4 `8 t, p+ k: J m Linux下SPI框架一图胜千言,看看我画的图吧: - spi.c 实现了SPI core的公共抽象,SPI core负责抽象所有控制器具有的公共功能代码,并同时为spi protocol驱动程序提供接口,例如spi_message,spi_transfer,spi_async等。
- SPI controller:SPI控制器层实现不同芯片SPI控制器的差异性实现,完成数据的bit级别的收发控制实现,并最终控制其对应的总线。
- SPI devie:实现设备驱动,负责从控制器收发数据,并实现应用协议的解析等。
- SPI外设控制:逻辑电路层,负责底层的IO电路级控制。
- SPI从设备:挂载在具体的SPI总线上的芯片,比如传感器、FLASH存储设备等。
# q. K5 K2 ?3 |$ b+ J) X/ ]' a2 q GPIO SPI Master?前面说自己拿GPIO对着SPI收发时序波形去实现SPI字符设备有点重新造砖的嫌疑,且造出的砖兼容性、稳定性往往都差强人意。辣么,Linux下不这么干,可以怎么干呢? 首先用下面命令打开内核配置界面,来瞅瞅: make menuconfig
* ]! d) n# V \ F' Z2 v S- ]稍等那么一会儿,熟悉的界面就出来了: 进入Device Drivers: 进入到SPI support,按Y选择: - GPIO-based bitbanging SPI Master,这便是GPIO SPI主控制器的配置项
- User mode SPI device driver support,这便是spidev设备驱动的配置项2 N: q& D( q6 {
完事之后,一顿退出保存配置,记得保存配置,别配置了半天没存哈。' u+ \8 T- J* z3 s
设备树配置前面配置好了GPIO主控制器以及spidev设备驱动,然而如果直接编译,将内核部署到目标板上运行你发现啥也没有。那么还要做什么呢?你需要根据你的板子的情况配置哪些GPIO为SPI主控制对应的引脚!比如我用的ZYNQ,我这样配: spi {
: a5 |+ B# O# h# p8 ~ compatible = "spi-gpio";
1 s6 E; D$ j2 I( q6 D- V #address-cells = <0x1>;
: t$ E9 b% _' R7 N+ s* f ranges;/ _/ z9 A" G! a& v
@6 m1 F. ?* e. y% O: u- M4 [ sck-gpios = <&gpio0 7 0>;4 Z% r' E/ h! g5 {6 W: b X
miso-gpios = <&gpio0 11 0>;2 N" ?/ g, O# \& t) c. z
mosi-gpios = <&gpio0 10 0>;
% V. q7 n) P7 H9 r4 Z* D cs-gpios = <&gpio0 12 0>;) s. V* y# G8 K3 j
num-chipselects = <1>;# g& i4 A% l% L6 Q2 D) e$ X
/* 从设备clients */
& m) p9 ^( r6 L4 T3 L device_0@0 {
$ t6 y+ p% r$ Q6 g compatible = "spidev";
U* T' o. W4 {) Y) o: v$ H reg = <0>;
7 [* n6 \' O: T: h! k: d" O1 u spi-max-frequency = <500000>;
2 N/ l1 ^+ ?0 p$ v& l- ] #address-cells = <1>;7 B# Z# L$ J' k
#size-cells = <1>;& K6 J! \) f& ?+ u
spi-rx-bus-width = <1>;
8 E2 b4 g( k+ z8 H6 q- F0 c7 U4 c( L spi-tx-bus-width = <1>;
4 z* M* v0 _, f$ G* w- ~ bits-per-word = <8>;- i6 Y5 _, m' ^ @2 ?. o+ j+ y
};/ X- ~4 X8 f' u* ~
}; + r' q: K. s; i7 U# s
主控制器设备树: - compatible = "spi-gpio",这里兼容性表示使用GPIO的主控制器。编译时会自动编译spi-gpio.c
- sck-gpios/miso-gpios/mosi-gpios/cs-gpios:为SPI对应的4个脚的配置,后面的则根据目标板进行设置。
- num-chipselects:设置有几个片选6 ^) D$ w2 D& `5 R- p
从设备设备树: - compatible = "spidev",这里设置spidev对应spidev.c的实现,当然你完全可以自己写个设备驱动,但是对于大部分应用而言没有必要,直接在用户空间实现应用协议不香么?
- reg = <0>,括号里如果有多个从设备则依次增加
- spi-max-frequency,设定spi频率,这里假定设置为500k
- spi-rx-bus-width/spi-tx-bus-width,这里可选,如果是quad等设备则需要设置
- bits-per-word:表示一个字节多少位
- 另外spi-cpol/spi-cpha,则根据设备的SPI模式进行相应配置
- spi-3wire:如果对应的设备是3线SPI设备,则可将该属性加上。0 }) Y% ~2 r$ S- g" n( ?- N
部署测试设备树配置好后,编译加载到目标板上,此时在/dev下就会出现相应的设备 ,比如这样: 我配了好几个spidev,所以出现这么几个设备。 至于怎么从用户空间测试这些驱动,就是基本的文件open/read/write操作了,这里就不赘述了。 总结一下本文梳理了一下在SPI受限的情况下,Linux中GPIO用做SPI控制器的方法及实现,不用敲一行代码就妥了。把该驱的芯片就驱起来了。这里一个小的tips: 当面临要写驱动的时候,先查查是否已经有现成的实现,鼓捣鼓捣就能用岂不是妙!
Linux发展至今,为啥能如此广泛应用,从这里也可见一斑。你大部分想到的需求,都基本给你造好了。拿来就用,一用就对!他不香么?
% m/ ~. L3 J& |1 j4 C* l" [ |