4.3.8. SPI 调试指南

4.3.8.1. SPI 概述

SPI(Serial Peripheral Interface) 通信一种全双工、同步通信方式,通常一个设备一个多个设备组成。主设备通过控制时钟信号 (SCK),数据输入信号 (MOSI),数据输出信号 (MISO) 和片选信号 (SS) 来设备进行通信。其中 MOSI 可以解释为 Master Out Slave In, MISO 可以解释为 Matser In Slave Out。

4.3.8.2. 特点

芯片支持 7 路 SPI 控制器,其中 6 路 (SPI0-SPI5) 位于 LSIO 子系统, 1 路 (SPI6) 位于 DSP 子系统。 所有 SPI 控制器支持主 / 从模式。 其中 SPI1 拥有两路片选,其他只有一路片选。

硬件说明

所有 SPI 控制器可以运行 Master 或者 Slave 模式,两种模式支持中断和 DMA 配置。 DMA 配置下 Master 及 Slave 的运行限制如下:

  • SPI-Master:最高频率 50MHz

  • SPI-Slave:最高频率 32MHz

中断配置运行限制如下:

  • 作为 SPI-Slave 端:

    • MOSI: CPU定频 在 1.5GHz 时,实测可以达到 32MHz

    • MISO: CPU定频 在 1.5GHz 时,实测可以达到 16MHz

  • 作为 SPI-Master 端:

    • 正常通讯,可以达到 32MHz。

4.3.8.3. 功能描述

典型应用

image-Topological_Connection_1

SPI 一个设备连接一个设备,使用一个片选信号。

功能原理

通信模式

SPI 采用主从模式工作,一个设备可以多个设备通信,但一次只能一个设备通信。主设备通过选线 (SS/CS) 选择通信设备

信号线说明

SPI 通信需要至少 4 条信号线,分别是: SCLK(Serial Clock):时钟信号,由主设备产生,用于同步数据传输 。 MOSI(Master Output Slave Input):主设备输出,从设备输入数据信号线。 MISO(Master Input Slave Output):主设备输入,从设备输出数据信号线。 SS/CS(Slave Select/Chip Select):从设备选择信号,用于激活特定设备。

数据传输说明

每个时钟周期内, SPI 设备发送接收一个 bit 大小数据,实现全双工通信。数据通常高位 (MSB) 在方式传输。

通信流程简述

通信开始时,主设备拉低对应设备选线,选择需要通信设备。 主设备时钟信号边缘数据发送到 MOSI 线,并同一时钟边缘接收设备响应数据。 主设备侦听设备响应数据,并通信结束释放选线。

时钟极性相位说明

SPI 有四种工作模式,这些模式时钟极性 (CPOL) 和时钟相位 (CPHA) 两个参数定义,它们控制数据采样时钟信号空闲状态。

image-Timing_CPOL0_CPHA0

CPOL = 0 , CPHA = 0 : CLK 空闲状态 = 低电平,数据上升沿采样,并下降沿移出。

给出了 CPOL = 0 , CPHA = 0 的时序图。在模式下,时钟极性为 0 ,表示时钟信号空闲状态低电平。

模式时钟相位为 0 ,表示数据上升沿采样 ( 由橙色虚线显示 ),并且数据时钟信号下降沿移出 ( 由蓝色虚线显示 )。

image-Timing_CPOL0_CPHA1

CPOL = 0 , CPHA = 1 : CLK 空闲状态 = 低电平,数据下降沿采样,并上升沿移出。

给出了 CPOL = 0 , CPHA = 1 的时序图。在模式下,时钟极性为 0 ,表示时钟信号空闲状态低电平。

模式时钟相位为 1 ,表示数据下降沿采样 ( 由橙色虚线显示 ),并且数据时钟信号上升沿移出 ( 由蓝色虚线显示 )。

image-Timing_CPOL1_CPHA1

CPOL = 1 , CPHA = 1 : CLK 空闲状态 = 高电平,数据下降沿采样,并上升沿移出。

给出了 CPOL = 1 , CPHA = 1 的时序图。在模式下,时钟极性为 1 ,表示时钟信号空闲状态高电平。

模式时钟相位为 1 ,表示数据下降沿采样 ( 由橙色虚线显示 ),并且数据时钟信号上升沿移出 ( 由蓝色虚线显示 )。

image-Timing_CPOL1_CPHA0

CPOL = 1 , CPHA = 0 : CLK 空闲状态 = 高电平,数据上升沿采样,并下降沿移出。

给出了 CPOL = 1 , CPHA = 0 的时序图。在模式下,时钟极性为 1 ,表示时钟信号空闲状态高电平。

模式时钟相位为 0 ,表示数据上升沿采样 ( 由橙色虚线显示 ),并且数据时钟信号下降沿移出 ( 由蓝色虚线显示 )。

这里归纳一下时钟极性相位说明,方便大家查阅:

CPOL CPHA 描述
0 0 时钟空闲时为低电平,数据时钟第一个边沿采样。
0 1 时钟空闲时为低电平,但数据第二个边沿采样。
1 0 时钟空闲时为高电平,数据时钟第一个边沿采样。
1 1 时钟空闲时为高电平,但数据第二个边沿采样。

工作方式

设备初始化阶段 设置时钟频率、模式 ( 时钟极性相位 )。

选择设备阶段设备目标设备的 SS 信号拉低,使处于激活状态。

数据传输阶段设备通过 MOSI 发送数据,从设备通过 MISO 返回数据。 数据每个时钟脉冲移位,同时设备设备之间交换。

通信结束阶段设备将 SS 信号拉高,结束通信。未选中设备不会响应。

4.3.8.4. 驱动代码

Linux SPI 驱动框架介绍

  • spi driver 层:主要实现对 SPI 硬件 IP 的操作,另外实现了 spi framework 定义接口。

  • spi framework 层:可以理解为 spi driver 的适配层,对下层定义一组 driver 层需要实现接口,对提供通用接口屏蔽硬件细节。

  • spi char device 层:为用户空间提供节点,方便用户空间内核空间进行数据交换。

SPI 驱动代码位置

驱动代码位于: drivers/spi 目录下,主要三个文件:

  • drivers/spi/spi-dw-core.c

  • drivers/spi/spi-dw-mmio.c

  • drivers/spi/spi-dw-dma.c

SPI DTS 配置说明

SPI 控制器设备树定义位于 BSP 源码kernel/arch/arm64/boot/dts/hobot/x5.dtsi 文件内。

备注: x5.dtsi 中节点主要声明 SoC 共有特性,和具体电路板无关,一般情况不用修改。

SPI 控制器默认关闭,请根据实际硬件情况,在对应的 DTS 文件内使相应的 SPI 控制器。 \

使能 SPI2 为例:

&spi2 {
	status = "okay";  /*启用 SPI2控制器*/
	pinctrl-names = "default";  /*使用默认引脚配置*/
	pinctrl-0 = <&pinctrl_spi2>;  /*引用默认引脚配置节点*/
};

DTS 配置 SPI 使用 DMA

如果需要使用 DMA,则需要对应的 DTS 文件绑定对应的 DMA 握手,以 SPI2 为例:

&spi2 {
	status = "okay";  /*启用 SPI2控制器*/
	pinctrl-names = "default";  /*使用默认引脚配置*/
	pinctrl-0 = <&pinctrl_spi2>;  /*引用默认引脚配置节点*/
	dma-names = "tx", "rx";  /*定义 DMA 传输和接收的名称*/
	dmas = <&axi_dmac 25>, <&axi_dmac 24>;  /*声明 DMA 握手通道*/
};

SPI6 绑定需要指定 dsp_axi_dma,如下所示:

&spi6 {
	status = "okay";  /*启用 SPI2控制器*/
	pinctrl-names = "default";  /*使用默认引脚配置*/
	pinctrl-0 = <&pinctrl_dsp_spi>;  /*引用默认引脚配置节点*/
	dma-names = "tx", "rx";  /*定义 DMA 传输和接收的名称*/
	dmas = <&dsp_axi_dma 22>, <&dsp_axi_dma 21>;  /*声明 DMA 握手通道*/
};

SPI DMA 握手列表如下:

RX TX
SPI0 20 21
SPI1 22 23
SPI2 24 25
SPI3 26 27
SPI4 28 29
SPI5 30 31
SPI6(DSP_DMAC) 21 22

4.3.8.5. 功能使用

上述文章驱动代码典型的 dts 配置进行说明,接下来换成拥有双片的 SPI1 进行详细使用说明

使用 SPI1 双片配置

首先需要在 x5.dtsi 里面确认一下 num-cs 节点是否为 2

spi1: spi@34010000 {
	compatible = "horizon,dwc-ssi-1.01a";  /*指定 SPI 设备1控制器兼容的硬件型号*/
	status = "disabled";  /*标记 SPI 设备1控制器为禁用状态*/
	......
	num-cs = <2>;  /*指定 SPI 设备1控制器的片选信号数量,这里特别说明一下,该芯片只有 SPI1有两个片选,其他的 SPI 写2无效*/
	......
};

其次我们设备树引用时候可以参考如下配置

&spi1 {
	status = "okay";  /*启用 SPI 设备1控制器*/
	pinctrl-names = "default";  /*使用默认引脚配置*/
	pinctrl-0 = <&pinctrl_spi1 &pinctrl_spi1_ssn1>;  /*引用默认引脚控制配置节点,选定主设备和片选信号*/
	dma-names = "tx", "rx";  /*定义 DMA 传输和接收的名称*/
	dmas = <&axi_dmac 23>, <&axi_dmac 22>;  /*声明 DMA 握手通道*/

	spidev@0 {
		compatible = "dr,x5-spidev";  /*指定 spidev0的兼容信息*/
		spi-max-frequency = <32000000>;  /*设置 spidev0的最大频率*/
		reg = <0>;  /*指定 spidev0的寄存器索引*/
	};

	spidev@1 {
		compatible = "dr,x5-spidev";  /*指定 spidev1的兼容信息*/
		spi-max-frequency = <32000000>;  /*设置 spidev1的最大频率*/
		reg = <1>;  /*指定 spidev 的寄存器索引*/
	};
};

两个片选分别对应 spidev@0 和 spidev@1 ,系统正常启动之后,我们可以找到 /dev/spidev1.0/dev/spidev1.1 两个节点。

时钟极性相位设置

我们使用时候可能遇到对 SPI 时钟极性相位要求设备,比如某些设备要求 CPOL=1 , CPHA=1 。 那么我们基于上述设备树设置,可以增加 spi-cpha;,spi-cpol; 两个节点。

&spi1 {
	status = "okay";  /*启用 SPI 设备1控制器*/
	pinctrl-names = "default";  /*使用默认引脚配置*/
	pinctrl-0 = <&pinctrl_spi1 &pinctrl_spi1_ssn1>;  /*引用默认引脚控制配置节点,选定主设备和片选信号*/
	dma-names = "tx", "rx";  /*定义 DMA 传输和接收的名称*/
	dmas = <&axi_dmac 23>, <&axi_dmac 22>;  /*声明 DMA 握手通道*/

	spidev@0 {
		compatible = "dr,x5-spidev";  /*指定 spidev0的兼容信息*/
		spi-max-frequency = <32000000>;  /*设置 spidev0的最大频率*/
		reg = <0>;  /*指定 spidev0的寄存器索引*/
		spi-cpha;  /*配置 spidev0时钟相位,声明即为1*/
		spi-cpol;  /*配置 spidev0时钟极性,声明即为1*/
	};

	spidev@1 {
		compatible = "dr,x5-spidev";
		spi-max-frequency = <32000000>;
		reg = <1>;
	};
};

增加spi-cpha;,spi-cpol; 两个节点,我们可以kernel\drivers\spi\spi.c找到解析,这里不过阐述。

极性相位可以参考上文 功能描述 时钟极性相位说明 章节

SPI 多片功能使用说明

X5 各 SPI 控制器硬件片选能力如下:

SPI 控制器 硬件片选数量
SPI1 2
SPI0、SPI2~SPI6 1

设备数量超过硬件片选数量时,可通过 GPIO 模拟片选 扩展。配置需注意:

  • num-cs: SPI 控制器硬件片选信号数量,仅 SPI1 使用硬件片选时配置为 2,其它情况使用默认配置即可。

  • cs-gpios:按顺序列出每路片配置,第 N 项对应 reg = <N>设备。

    • <0> 表示该路使用 硬件片选,需在对应 pinctrl配置 SSN 引脚。

    • <&gpio ... GPIO_ACTIVE_LOW> 表示该路使用 GPIO 片选

  • reg:从设备片选编号,与 cs-gpios 一一对应。

  • 用户节点:每个设备对应 /dev/spidev{bus}.{reg},例如 reg = <1> 对应 /dev/spidev2.1

备注:下文示例的 GPIO 编号(如 ls_gpio0_porta 3)仅作演示,实际项目选用板级空闲 GPIO,并保证其他外设占用。

场景一:一路硬件片选 + 多路 GPIO 片选

以 SPI6 为例:一路硬件片选连接 spidev@0,两路 GPIO 片选分别连接 spidev@1spidev@2

pinctrl_dsp_spi: dsp_spigrp {
    horizon,pins = <
        DSP_SPI6_MISO  DSP_PINMUX_1  BIT_OFFSET2  MUX_ALT0 &pconf_drv_pu_ds2_1v8
        DSP_SPI6_MOSI  DSP_PINMUX_1  BIT_OFFSET4  MUX_ALT0 &pconf_drv_pu_ds2_1v8
        DSP_SPI6_SCLK  DSP_PINMUX_1  BIT_OFFSET6  MUX_ALT0 &pconf_drv_pu_ds2_1v8
        DSP_SPI6_SSN  DSP_PINMUX_1  BIT_OFFSET8  MUX_ALT0 &pconf_drv_pu_ds2_1v8 /* 硬件片选 SSN,reg=0 */
    >;
};

&spi6 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_dsp_spi>;
    /* GPIO chip selects */
    cs-gpios = <0>,  /* SPI6 使用多片选时必须配置该项,表示 SSN 用于硬件片选*/
        <&dsp_gpio_porta 4 GPIO_ACTIVE_LOW>, /* reg=1:GPIO 片选 */
        <&ls_gpio0_porta 3 GPIO_ACTIVE_LOW>;   /* reg=2:GPIO 片选 */

    dma-names = "tx", "rx";
    dmas = <&dsp_axi_dma 22>, <&dsp_axi_dma 21>;

    spidev@0 {  /* 硬件片选从设备 */
        compatible = "dr,x5-spidev";
        reg = <0>;
        spi-max-frequency = <5000000>;
        status = "okay";
    };

    spidev@1 {  /* GPIO 片选从设备 */
        compatible = "dr,x5-spidev";
        reg = <1>;
        spi-max-frequency = <5000000>;
        status = "okay";
    };

    spidev@2 {  /* GPIO 片选从设备 */
        compatible = "dr,x5-spidev";
        reg = <2>;
        spi-max-frequency = <5000000>;
        status = "okay";
    };
};

启动生成 /dev/spidev2.0/dev/spidev2.1/dev/spidev2.2

注意:SPI6 不支持纯 GPIO 片选模式,在使用多片选时, SSN 必须默认硬件片选配置。

场景二:两路硬件片选 + 多路 GPIO 片选

以 SPI1 为例:两路硬件片选分别对应 SSNSSN_1,另用两路 GPIO 扩展 spidev@2spidev@3

pinctrl_spi1: spi1grp {
    horizon,pins = <
        LSIO_SPI1_SCLK LSIO_PINMUX_0 BIT_OFFSET2  MUX_ALT0 &pconf_drv_pu_ds2_1v8
        LSIO_SPI1_SSN  LSIO_PINMUX_0 BIT_OFFSET4  MUX_ALT0 &pconf_drv_pu_ds2_1v8  /* 硬件片选 SSN,reg=0 */
        LSIO_SPI1_MISO LSIO_PINMUX_0 BIT_OFFSET6  MUX_ALT0 &pconf_drv_pu_ds2_1v8
        LSIO_SPI1_MOSI LSIO_PINMUX_0 BIT_OFFSET8  MUX_ALT0 &pconf_drv_pu_ds2_1v8
    >;
};

pinctrl_spi1_ssn1: spi1_ssn1grp {
    horizon,pins = <
        LSIO_SPI1_SSN_1  LSIO_PINMUX_0 BIT_OFFSET0  MUX_ALT0 &pconf_drv_pu_ds2_1v8  /* 硬件片选 SSN_1,reg=1 */
    >;
};

&spi1 {
    status = "okay";
    num-cs = <2>; /*指定 SPI1 控制器的片选信号数量,这里特别说明一下,该 soc 只有 SPI1 有两个片选,其他的 SPI 写 2 无效*/
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_spi1 &pinctrl_spi1_ssn1>;
    cs-gpios = <0>,  /*表示这一路不用 GPIO,使用硬件片选(pinctrl_spi1 中配置片选信号) reg=0:SSN*/
        <0>,   /*表示这一路不用 GPIO,使用硬件片选(pinctrl_spi1_ssn1 中配置片选信号) reg=1:SSN_1*/
        <&ls_gpio1_porta 13 GPIO_ACTIVE_LOW>,  /*reg=2:GPIO 片选*/
        <&ls_gpio1_porta 15 GPIO_ACTIVE_LOW>;  /*reg=3:GPIO 片选*/

    spidev@0 {  /* 硬件片选从设备 */
        compatible = "dr,x5-spidev";
        reg = <0>;
        spi-max-frequency = <5000000>;
        status = "okay";
    };

    spidev@1 {  /* 硬件片选从设备 */
        compatible = "dr,x5-spidev";
        reg = <1>;
        spi-max-frequency = <5000000>;
        status = "okay";
       };

    spidev@2 {  /* GPIO 片选从设备 */
        compatible = "dr,x5-spidev";
        reg = <2>;
        spi-max-frequency = <5000000>;
        status = "okay";
    };

    spidev@3 {  /* GPIO 片选从设备 */
        compatible = "dr,x5-spidev";
        reg = <3>;
        spi-max-frequency = <5000000>;
        status = "okay";
    };
 };

启动生成 /dev/spidev1.0/dev/spidev1.3

场景三:纯 GPIO 片选

以 SPI2 为例:全部设备使用 GPIO 片选时,注释 pinctrl 中硬件 SSN 引脚,避免与 GPIO 冲突。

pinctrl_spi2: spi2grp {
    horizon,pins = <
        LSIO_SPI2_SCLK LSIO_PINMUX_1 BIT_OFFSET16  MUX_ALT0 &pconf_drv_pu_ds2_3v3
        /* LSIO_SPI2_SSN 必须注释,否则占用硬件片选引脚 */
        LSIO_SPI2_MISO LSIO_PINMUX_1 BIT_OFFSET20  MUX_ALT0 &pconf_drv_pu_ds2_3v3
        LSIO_SPI2_MOSI LSIO_PINMUX_1 BIT_OFFSET22  MUX_ALT0 &pconf_drv_pu_ds2_3v3
    >;
};

&spi2 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_spi2>;
    /* GPIO chip selects */
    cs-gpios = <&ls_gpio0_porta 21 GPIO_ACTIVE_LOW>   /* reg=0: GPIO 片选 */
        <&ls_gpio0_porta 12 GPIO_ACTIVE_LOW>,  /* reg=1:GPIO 片选 */
        <&ls_gpio0_porta 13 GPIO_ACTIVE_LOW>;   /* reg=2:GPIO 片选 */

    spidev@0 {  /* GPIO 片选从设备 */
        compatible = "dr,x5-spidev";
        reg = <0>;
        spi-max-frequency = <5000000>;
        status = "okay";
    };

    spidev@1 {  /* GPIO 片选从设备 */
        compatible = "dr,x5-spidev";
        reg = <1>;
        spi-max-frequency = <5000000>;
        status = "okay";
    };

    spidev@2 {  /* GPIO 片选从设备 */
        compatible = "dr,x5-spidev";
        reg = <2>;
        spi-max-frequency = <5000000>;
        status = "okay";
    };
};

启动生成 /dev/spidev2.0/dev/spidev2.1/dev/spidev2.2

用户阶段使用

使用一个代码案例介绍 SPI 的功能验证,主要包括环境配置,测试命令执行测试代码等。

确认内核的 CONFIG_SPI_SPIDEV 为使状态 :

/* arch/arm64/configs/hobot_x5_soc_defconfig */
...
CONFIG_SPI_SPIDEV=m
...

确认当前硬件的 DTS 内需要测试的 SPI 控制器节点创建一个 dummy 从设备:

&spi2 {
	spidev@0 {
		compatible = "dr,x5-spidev";
		spi-max-frequency = <32000000>;
		reg = <0>;
	};
}

SPI 内部回环测试

SPI 内部回环测试仅 SPI Master 支持,其原理是 SPI 硬件 IP 的 tx fifo 将数据发给 rx fifo 从而形成回环。

我们使用 /app/platform_samples/chip_base_test/05_spi_test 目录的 spidev_tc 代码程序进行测试 测试命令结果参考如下 ( 以 SPI2 为 Slave, SPI4 为 Master):

root@buildroot:/app/platform_samples/chip_base_test/05_spi_test# ./spidev_tc -D /dev/spidev2.0 -v -s 1000000 -e 3 -l -S 256 -I 1
spi mode: 0x20
bits per word: 8
max speed: 1000000 Hz (1000 kHz)
Userspace spi read and write test, test_len=256 iterations=1
TX | 67 C6 69 73 51 FF 4A EC 29 CD BA AB F2 FB E3 46 7C C2 54 F8 1B E8 E7 8D 76 5A 2E 63 33 9F C9 9A  |g.isQ.J.)......F|.T.....vZ.c3...|
TX | 66 32 0D B7 31 58 A3 5A 25 5D 05 17 58 E9 5E D4 AB B2 CD C6 9B B4 54 11 0E 82 74 41 21 3D DC 87  |f2..1X.Z%]..X. ^ .......T...tA!=..|
TX | 70 E9 3E A1 41 E1 FC 67 3E 01 7E 97 EA DC 6B 96 8F 38 5C 2A EC B0 3B FB 32 AF 3C 54 EC 18 DB 5C  |p.>.A..g>.~...k..8\*..;.2.<T...\|
TX | 02 1A FE 43 FB FA AA 3A FB 29 D1 E6 05 3C 7C 94 75 D8 BE 61 89 F9 5C BB A8 99 0F 95 B1 EB F1 B3  |...C...:.)...<|.u..a..\.........|
TX | 05 EF F7 00 E9 A1 3A E5 CA 0B CB D0 48 47 64 BD 1F 23 1E A8 1C 7B 64 C5 14 73 5A C5 5E 4B 79 63  |......:.....HGd..#...{d..sZ. ^ Kyc|
TX | 3B 70 64 24 11 9E 09 DC AA D4 AC F2 1B 10 AF 3B 33 CD E3 50 48 47 15 5C BB 6F 22 19 BA 9B 7D F5  |;pd$...........;3..PHG.\.o"...}.|
TX | 0B E1 1A 1C 7F 23 F8 29 F8 A4 1B 13 B5 CA 4E E8 98 32 38 E0 79 4D 3D 34 BC 5F 4E 77 FA CB 6C 05  |.....#.)......N..28.yM=4._Nw..l.|
TX | AC 86 21 2B AA 1A 55 A2 BE 70 B5 73 3B 04 5C D3 36 94 B3 AF E2 F0 E4 9E 4F 32 15 49 FD 82 4E A9  |..!+..U..p.s;.\.6.......O2.I..N.|
RX | 67 C6 69 73 51 FF 4A EC 29 CD BA AB F2 FB E3 46 7C C2 54 F8 1B E8 E7 8D 76 5A 2E 63 33 9F C9 9A  |g.isQ.J.)......F|.T.....vZ.c3...|
RX | 66 32 0D B7 31 58 A3 5A 25 5D 05 17 58 E9 5E D4 AB B2 CD C6 9B B4 54 11 0E 82 74 41 21 3D DC 87  |f2..1X.Z%]..X. ^ .......T...tA!=..|
RX | 70 E9 3E A1 41 E1 FC 67 3E 01 7E 97 EA DC 6B 96 8F 38 5C 2A EC B0 3B FB 32 AF 3C 54 EC 18 DB 5C  |p.>.A..g>.~...k..8\*..;.2.<T...\|
RX | 02 1A FE 43 FB FA AA 3A FB 29 D1 E6 05 3C 7C 94 75 D8 BE 61 89 F9 5C BB A8 99 0F 95 B1 EB F1 B3  |...C...:.)...<|.u..a..\.........|
RX | 05 EF F7 00 E9 A1 3A E5 CA 0B CB D0 48 47 64 BD 1F 23 1E A8 1C 7B 64 C5 14 73 5A C5 5E 4B 79 63  |......:.....HGd..#...{d..sZ. ^ Kyc|
RX | 3B 70 64 24 11 9E 09 DC AA D4 AC F2 1B 10 AF 3B 33 CD E3 50 48 47 15 5C BB 6F 22 19 BA 9B 7D F5  |;pd$...........;3..PHG.\.o"...}.|
RX | 0B E1 1A 1C 7F 23 F8 29 F8 A4 1B 13 B5 CA 4E E8 98 32 38 E0 79 4D 3D 34 BC 5F 4E 77 FA CB 6C 05  |.....#.)......N..28.yM=4._Nw..l.|
RX | AC 86 21 2B AA 1A 55 A2 BE 70 B5 73 3B 04 5C D3 36 94 B3 AF E2 F0 E4 9E 4F 32 15 49 FD 82 4E A9  |..!+..U..p.s;.\.6.......O2.I..N.|
Test times: 0 Data verification Successful
root@buildroot:/app/platform_samples/chip_base_test/05_spi_test#

SPI 外部回环测试

SPI 外部回环测试指定一个 SPI Slave,一个 SPI Master,对应线连接进行测试。 EVB 的板子 40pin 上只有 SPI2 , 所以在 EVB 上外部回环测试时候需要注意硬件通路。

我们首先找到 40pin 的 SPI2 引脚,以及原理图的 SPI1 通路,可以看到我们的 SPI1 实际上和 JTAG 口有复用关系,所以想要进行 SPI1 和 SPI2 的回环测试,就需要

将 JTAG 口的 SPI1 引脚和 40pin 引脚连接起来。

下图是 SPI1 与 JTAG 复用原理图部分截图,可以看到 SPI1 通过电平转换芯片接到了 JTAG 口。

spi_external_loopback_2

spi_external_loopback_1

下图是 EVB 板上的 JTAG 口实物图,仅供参考。

spi_external_loopback_3

我们硬件通路搭建之后,我们进入一步

这里以 SPI2 作为 Slave , EVB 板上原 JTAG 引脚配置为 SPI1 , 作为 Master( 使用双片选中的 SPI1.1 ), 修改 SPI2 DTS 以支持 Slave 功能:

&spi2 {
	status = "okay";
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_spi2>;
	spi-slave;

	slave@0 {
		compatible = "dr,x5-spidev";
		spi-max-frequency = <32000000>;
		reg = <0>;
	};
};

修改 SPI1 DTS 以支持 Master 功能:(SPI1 具有两个片选,所以这里我们定义两个设备节点,系统正常启动之后,体现文件系统中,就两个设备,/dev/spi1.0 和 /dev/spi1.1 ), 注意,num-cs = <2>; 这个属性,上文我们提到使用双片时候需要添加到 dts 里。

&spi1 {
	num-cs = <2>;
	status = "okay";
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_spi1 &pinctrl_spi1_ssn1>;

	spidev@0 {
		compatible = "dr,x5-spidev";
		spi-max-frequency = <32000000>;
		reg = <0>;
	};

	spidev@1 {
		compatible = "dr,x5-spidev";
		spi-max-frequency = <32000000>;
		reg = <1>;
	};
};

测试命令结果参考如下 ( 以 SPI2 为 Slave, SPI1.1 为 Master):


1 、打开一个终端,操作 SPI 从设备:

root@buildroot:~# /app/platform_samples/chip_base_test/05_spi_test/spidev_tc -D /dev/spidev2.0 -e 1 -v -S 64 -I 1
spi mode: 0x0
bits per word: 8
max speed: 500000 Hz (500 kHz)
Userspace spi read test, test_len=64 iterations=1

( 说明:上述命令执行之后,程序会一直等待,直到接收到从 SPI Master 发送的数据。)



2 、打开另一个终端,操作 SPI 主设备:
root@buildroot:~# /app/platform_samples/chip_base_test/05_spi_test/spidev_tc -D /dev/spidev1.1 -e 2 -v -S 64 -I 1
spi mode: 0x0
bits per word: 8
max speed: 500000 Hz (500 kHz)
Userspace spi write test, test_len=64 iterations=1
TX | 67 C6 69 73 51 FF 4A EC 29 CD BA AB F2 FB E3 46 7C C2 54 F8 1B E8 E7 8D 76 5A 2E 63 33 9F C9 9A  |g.isQ.J.)......F|.T.....vZ.c3...|
TX | 66 32 0D B7 31 58 A3 5A 25 5D 05 17 58 E9 5E D4 AB B2 CD C6 9B B4 54 11 0E 82 74 41 21 3D DC 87  |f2..1X.Z%]..X. ^ .......T...tA!=..|
Test times: 0
root@buildroot:~#

( 说明:上述命令执行之后, SPI 主设备就直接发送数据出去了 )



3 、这个时候可以观察到 SPI 从设备的终端会显示接收到的数据,整体状态形如下述结果:

root@buildroot:~# /app/platform_samples/chip_base_test/05_spi_test/spidev_tc -D /dev/spidev2.0 -e 1 -v -S 64 -I 1
spi mode: 0x0
bits per word: 8
max speed: 500000 Hz (500 kHz)
Userspace spi read test, test_len=64 iterations=1
RX | 67 C6 69 73 51 FF 4A EC 29 CD BA AB F2 FB E3 46 7C C2 54 F8 1B E8 E7 8D 76 5A 2E 63 33 9F C9 9A  |g.isQ.J.)......F|.T.....vZ.c3...|
RX | 66 32 0D B7 31 58 A3 5A 25 5D 05 17 58 E9 5E D4 AB B2 CD C6 9B B4 54 11 0E 82 74 41 21 3D DC 87  |f2..1X.Z%]..X. ^ .......T...tA!=..|
rate: tx 0.1kbps, rx 0.1kbps
Test times: 0
root@buildroot:~#

注:在进行外部回环测试时,需要执行 SPI Slave 程序,再执行 SPI Master 程序。假如执行 SPI Master 程序,后执行 SPI Slave 程序,可能由于 Master 与 Slave 不同步导致 SPI 接收数据出现丢失。如果进行多次测试,可以脚本多次执行测试程序,来保证 Master 与 Slave 之间同步。

SPI 通信案例

这边结合 spidev_tc 和一款 IMU 通信,给大家提供一些其他通信参考。

保证硬件正常情况下,驱动正常加载之后,我们根据手册要求,要向 0x15 寄存器写入 0x80,

然后我们才能从 IMU 读出相应值,示例如下:

root@buildroot:/app# /app/platform_samples/chip_base_test/05_spi_test/spidev_tc -D /dev/spidev1.0 -s 5000000 -e 2 -S 2 -v -p "\x15\x00"
spi mode: 0x0
bits per word: 8
max speed: 5000000 Hz (5000 kHz)
TX | 15 00 __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __  |..|
root@buildroot:/app# /app/platform_samples/chip_base_test/05_spi_test/spidev_tc -D /dev/spidev1.0 -s 5000000 -e 1 -S 2 -v -p "\x80\x00"
spi mode: 0x0
bits per word: 8
max speed: 5000000 Hz (5000 kHz)
RX | FF 0F __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __  |..|
root@buildroot:/app#

我们可以观察 IMU 的 ID: 0x0F 能正常读取,证明 IMU 和 芯片 SPI 功通讯成功。

如果不想使用默认的 SPI 节点配置,可以根据 IMU 实际情况驱动适配,这里提供一些参考。

设备树配置如下:

&spi1 {
	status = "okay";
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_spi1 &pinctrl_spi1_ssn1>;
	dma-names = "tx", "rx";
	dmas = <&axi_dmac 23>, <&axi_dmac 22>;

	bmi08g@0 {
		compatible = "bmi088_gyro";  /*指定该 IMU 的兼容性标识*/
		reg = <0>;  /*指定设备的寄存器索引*/
		spi-max-frequency = <5000000>;
		interrupt-parent = <&ls_gpio1_porta>;  /*指定中断的父设备*/
		interrupts = <6 IRQ_TYPE_EDGE_RISING>;  /*定义中断号和触发类型*/
	};

	bmi08a@1 {
		compatible = "bmi08a";  /*指定该 IMU 的兼容性标识*/
		reg = <1>;  /*指定设备的寄存器索引*/
		spi-max-frequency = <5000000>;
		interrupt-parent = <&ls_gpio1_porta>;  /*指定中断的父设备*/
		interrupts = <4 IRQ_TYPE_EDGE_RISING>;  /*定义中断号和触发类型*/
	};
};

驱动文件根据情况可以放到 kernel\drivers\iio\imu 中,相应修改 kernel\drivers\iioKconfigMakefie,以及对 deconfig 文件进行修改。

测试用例源码: spidev_tc.c

SDK 中该用例源码目录位置

{platform_source_code}/app/samples/platform_samples/chip_base_test/05_spi_test/spidev_tc.c

这里赘述了,编译完成的 spidev 在系统镜像如下位置

/app/platform_samples/chip_base_test/05_spi_test/spidev_tc

详细使用说明可以参考 SPI 压力测试章节 对 spidev_tc 源码参数解释

测试用例源码编译方法:

代码放到具备交叉编译环境下,使用:/opt/arm-gnu-toolchain-11.3.rel1-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc -o spidev_tc spidev_tc.c -lpthread 编译,得到 spidev_tc 可执行文件。将可执行文件拷贝到目标板上,参考上文内容使用。

4.3.8.6. 常见的 SPI 通信错误排查方法

实际的 SPI 通信过程中,常常遇到一些问题,下面介绍一些常见的 SPI 通信错误以及相应排查方法。

时钟极性相位设置错误导致问题

当 SPI 设备时钟极性 (CPOL) 或相位 (CPHA) 设置错误时,会导致通信失败数据传输错误。在调试时,需要确保设备设备时钟极性相位设置保持一致,否则通信无法正常进行。可通过检查软件配置寄存器设置确认时钟极性相位设置是否正确。

数据传输稳定常见原因

数据传输稳定通常由于信号干扰、电源噪声、线路长度因素引起的。为了解决这些问题,可以采取一些措施,如增加数据线路阻抗匹配、加入隔离器件、优化供电设计等,以提高数据传输稳定性。

如何通过调试工具解决 SPI 通信问题

调试 SPI 通信问题时,可以借助一些调试工具辅助分析。例如使用逻辑分析仪可以观察时钟信号、数据信号波形,以判断通信是否正常。此外,还可以通过串口调试助手工具实时监测数据传输过程状态,有助于快速定位问题进行调试处理。