请选择 进入手机版 | 继续访问电脑版
[X]关闭

[米联客-XILINX-H3_CZ08_7100] LINUX驱动篇连载-09 PS SPI环路测试实验

文档创建者:LINUX课程
浏览次数:315
最后更新:2024-09-10
文档课程分类-AMD-ZYNQ
AMD-ZYNQ: ZYNQ-SOC » 2_LINUX应用开发
本帖最后由 LINUX课程 于 2024-9-11 10:29 编辑

软件版本:vitis2021.1(vivado2021.1)
操作系统:WIN10 64bit
硬件平台:适用XILINX Z7/ZU系列FPGA
登录“米联客”FPGA社区-www.uisrc.com视频课程、答疑解惑!

1 概述
SPI总线接口简单,SPI的时钟可以到100M以上,SPI总线可以用于多种场合串行通信,比如存储器,温度传感器,压力传感器,模拟转换器,实时时钟,显示器以及任何支持串行模式的SD卡。一些ADC比如AD7606也可以用SPI接口实现通信。
image.jpg
实验目的:
  • 熟悉掌握SPI通信协议
  • 熟悉SPI控制器的硬件资源
  • 实现对SPI控制器的使用
2 系统框图
image.jpg
3 SPI总线协议介绍
技术性能:
SPI接口是Motorola 首先提出的全双工三线同步串行外围接口,采用主从模式(MasterSlave)架构;支持多slave模式应用,一般仅支持单Master。时钟由Master控制,在时钟移位脉冲下,数据按位传输,高位在前,低位在后(MSBfirst);SPI接口有2根单向数据线,为全双工通信,目前应用中的数据速率可达几Mbps的水平。总线结构如下图所示。
image.jpg
接口定义:
SPI接口共有4根信号线,分别是:设备选择线、时钟线、串行输出数据线、串行输入数据线。
(1)MOSI:主器件数据输出,从器件数据输入
(2)MISO:主器件数据输入,从器件数据输出
(3)SCLK:时钟信号,由主器件产生
(4)/SS:从器件使能信号,由主器件控制
时钟极性和时钟相位:
SPI数据的传输是在串行同步时钟信号(Serial Clock,SCK)的控制下进行的。主机的时钟发生器一方面控制主机的移位寄存器,另一方面通过从机的SCK信号线来控制从机的移位寄存器,从而保证主机与从机的数据交换是同步进行的。
SPI串行同步时钟可以设置为不同的极性(Clock Polarity ,CPOL)与相位(Clock Phase ,CPHA)。
时钟的极性(CPOL)用来决定在总线空闲时,同步时钟(SCK)信号线上的电位是高电平还是低电平。当时钟极性为0时(CPOL=0),SCK信号线在空闲时为低电平;当时钟极性为1时(CPOL=1),SCK信号线在空闲时为高电平;
时钟的相位(CPHA)用来决定何时进行信号采样。
当时钟相位为1时(CPHA=1),在SCK信号线的第二个跳变沿进行采样;这里的跳变沿究竟是上升沿还是下降沿?取决于时钟的极性。当时钟极性为0时,取下降沿;当时钟极性为1时,取上升沿;如下图:
image.jpg
CPHA=1 的SPI时序
当时钟相位为0时(CPHA=0),在SCK信号线的第一个跳变沿进行采样。跳变沿同样与时钟极性有关:当时钟极性为0时,取上升沿;当时钟极性为1时,取下降沿;如下图:
image.jpg
CPHA=0 的SPI时序
数据传输
在一个SPI时钟周期内,会完成如下操作:
1)主机通过MOSI线发送1位数据,从机通过该线读取这1位数据;
2)从机通过MISO线发送1位数据,主机通过该线读取这1位数据。
这是通过移位寄存器来实现的。如下图所示,主机和从机各有一个移位寄存器,且二者连接成环。随着时钟脉冲,数据按照从高位到低位的方式依次移出主机寄存器和从机寄存器,并且依次移入从机寄存器和主机寄存器。当寄存器中的内容全部移出时,相当于完成了两个寄存器内容的交换。
image.jpg
4 ZYNQ SPI控制器介绍
4.1 SPI控制器介绍
SPI控制器通过APB总线接入到ARM,SPI控制器部分包含了数据的SPI控制器、SPI中断、发送模块、发送FIFO、接收模块、接收FIFO,并且支持SPI Master模式,以及SPI Slave模式,对于SPI的SLAVE模式还有SLAVE 同步默默。SPI的信号接口可以定义为MIO也可以定于到FPGA的EMIO,对于MIO速度可以到50M,对于EMIO速度为25M.
image.jpg
以下介绍SPI控制器的重要功能模块。
1:SPI的SS选通信号
当SPI作为Master模式的时候,SPI的可以通过3个SS信号实现1~3个外设或者通过3-8译码可以实现1~8个外设选通。SS选通支持手动模式和自动模式,默认是自动模式,在自动模式下每次发送数据会自动设置SS。如果手动模式则需要手动取消SS的片选。我们演示demo就是自动模式。
2:SPI控制器的FIFO
SPI控制器的RxFIFO和TxFIFO深度各是128字节。
往已经满的RxFIFO中送入数据,会导致溢出标志置1,新的数据不会添加进入FIFO。可以通过软件写1清除[RX_OVERFLOW]位。
往已经满的TxFIFO写入数据,这个数据会被忽略。当TxFIFO已经写满,TX_FIFO_full标志会置1,直到数据被读出,当FIFO非满后TX_FIFO_full标志会置0。如果TxFIFO读空产生向下溢出, TX_FIFO_underflow位置1。
3:SPI控制器的时钟
SPI_REF_CLK时钟在主机模式提供控制器的工作时钟,并且是SCLK的波特率分频器的参考时钟。在主模式下, SCLK使用波特率分频器从SPI_REF_CLK分频而来。 波特率分频器支持4~256分频(4,8,16,... 256分频)。
        在从机模式下使用输入的SCL时钟驱动MISO信号和对MOSI以及SS信号采样,数据最终需要和SPI_REF_CLK时钟同步到SPI的控制器。
4:控制器的中断
下图中RXFIFO和TXFIFO的最多可以保存128个数据。可以通过设置TX FIFO或者RX FIFO的Threshold寄存器设置TX FIFO的将满中断以及RX FIFO的将空中断中FIFO的数据量。
image.jpg
以下这种图表示了中断相关寄存器的设置关系。
image.jpg
4.2 SPI控制器的寄存器
XSPIPS_CR寄存器XSPIPS_CR_OFFSET (0x00U)
Field Name
Bits
Type
Reset Value
Description
XSPIPS_CR
31:0
RW
0x0
31~18bit:保留(RO只读)
17bit:模式失败生成启动(Modefail_gen_en)
1:使能,当总线冲突导致模式设置失败
0:禁用
16bit:Master启动命令(MANSTRT)
1:使能
0:禁用
15bit:手动启动使能(Man_start_en)
1:使能
0:禁用
14bit:手动CS(Manual_CS)
1:手动模式
0:自动模式
13~10bit:外设选择(CS)
xxx0:Slave0选通
xx01:Slave1选通
x010:Slave2选通
0111:保留
1111:没有Slave选通
9bit:外设译码选择(PERI_SEL)
1:支持3-8译码
0:支持1~3个外设
8bit:参考时钟(REF_CLK)
1:不支持
0:使用SPI REFERENCE CLOCK
7~6bit:保留(RO只读)
5~3bit:分频器(REF_CLK)
    只有Master模式才支持对spi_ref_clk的分频
000:不支持
001:4分频
010:8分频
011:16分频
100:32分频
101:64分频
110:128分频
111:256分频
2bit:时钟相位(CPHA)
1:在SCLK第2个时钟沿采样
0:在SCLK第1个时钟沿采样
1bit:时钟极性(CPOL)
1:在SCLK上升沿采样
0:在SCLK下降沿采样
0bit:模式选择(MSTREN)
1:主机模式
0:从机模式
XSPIPS_SR寄存器XSPIPS_SR_OFFSET (0x04U)
中断状态寄存器,当相应的中断产生后,中断状态寄存器相应的位置1,写1清零
Field Name
Bits
Type
Reset Value
Description
XSPIPS_SR
31:0
WTC
0x0
31~7bit:保留(RO只读)
6bit:TX FIFO溢出(IXR_TXUF)
1:溢出
0:未溢出
5bit:RX FIFO满(IXR_RXFULL)
1:满
0:未满
4bit: RX FIFO将空(IXR_RXNEMPTY)
1:FIFO数据大于等于THRESHOLD
0:FIFO数据小于THRESHOLD
3bit:TX FIFO满(IXR_TXFULL)
1:满
0:未满
2bit:TX FIFO将满(IXR_TXOW)
1:FIFO数据小于THRESHOLD
0:FIFO数据大于等于THRESHOLD
1bit:SPI 模式错误(IXR_MODF)
表示管脚 n_ss_in上的电压与SPI模式不一致。如果 n_ss_in在主机模式(多主机竞争)下为低电平,或者n_ss_in在从机模式下传输期间变为高电平,则设置 =1。这些条件将清除 spi_enable 位并禁用SPI。该位仅在系统复位时复位,并且仅在读取该寄存器时清零。 ModeFail 中断,向该位写入1清除。
1:产生模式错误
0:没有错误
0bit: 接收溢出中断(IXR_RXOVR)
1:溢出
0:未溢出
XSPIPS_IER寄存器XSPIPS_IER_OFFSET (0x08U)
中断使能寄存器,对应于之前的中断状态寄存器,设置1为使能相关中断
Field Name
Bits
Type
Reset Value
Description
XSPIPS_IER
31:0
WO
0x0
31~7bit:保留(RO只读)
6bit:TX FIFO溢出(IXR_TXUF)
5bit:RX FIFO满(IXR_RXFULL)
4bit: RX FIFO将空(IXR_RXNEMPTY)
3bit:TX FIFO满(IXR_TXFULL)
2bit:TX FIFO将满(IXR_TXOW)
1bit:SPI 模式错误(IXR_MODF)
0bit: 接收溢出中断(IXR_RXOVR)
XSPIPS_IDR寄存器XSPIPS_IDR_OFFSET (0x0CU)
中断禁用寄存器, 对应于之前的中断状态寄存器,设置1为禁用相关中断
Field Name
Bits
Type
Reset Value
Description
XSPIPS_IDR
31:0
WO
0x0
31~7bit:保留(RO只读)
6bit:TX FIFO溢出(IXR_TXUF)
5bit:RX FIFO满(IXR_RXFULL)
4bit: RX FIFO将空(IXR_RXNEMPTY)
3bit:TX FIFO满(IXR_TXFULL)
2bit:TX FIFO将满(IXR_TXOW)
1bit:SPI 模式错误(IXR_MODF)
0bit: 接收溢出中断(IXR_RXOVR)
XSPIPS_IMR寄存器XSPIPS_IMR_OFFSET (0x10U)
中断掩码寄存器, 对应于之前的中断状态寄存器、中断使能寄存器、中断禁用寄存器,1代表相关中断禁用
Field Name
Bits
Type
Reset Value
Description
XSPIPS_IMR
31:0
RO
0x0
31~7bit:保留(RO只读)
6bit:TX FIFO溢出(IXR_TXUF)
5bit:RX FIFO满(IXR_RXFULL)
4bit: RX FIFO将空(IXR_RXNEMPTY)
3bit:TX FIFO满(IXR_TXFULL)
2bit:TX FIFO将满(IXR_TXOW)
1bit:SPI 模式错误(IXR_MODF)
0bit: 接收溢出中断(IXR_RXOVR)
XSPIPS_ER寄存器XSPIPS_ER_OFFSET (0x14U)
SPI使能寄存器
Field Name
Bits
Type
Reset Value
Description
XSPIPS_ER
31:0
RO
0x0
31~1bit:保留(RO只读)
0bit:
   1:使能SPI
   2:禁止SPI
XSPIPS_DR寄存器XSPIPS_DR_OFFSET (0x18U)
SPI的延迟寄存器用于控制选通到非选通、非选通到选通、当前WORD最后bit到下个WORD第1bit、选通到第一个Bit开始的延迟参数。
Field Name
Bits
Type
Reset Value
Description
XSPIPS_DR
31:0
RW
0x0
31~24bit:选通到取消选通延迟(d_nss)
Master模式,当cpha=0时,设置SPI REFERENCE CLOCK或ext_clk个周期的延迟。
23~16bit:取消选通到选通的延迟(BTWN)
        从取消选通到选通之间延迟SPI REFERENCE CLOCK或ext_clk个周期
15~8bit:最后1Bit到下1bit间延迟 (AFTER)
        当前WORD的最后1Bit到下个WORD的第1bit间延迟SPI REFERENCE CLOCK或ext_clk个周期
7~0bit:nss低电平到第1bit的延迟(INIT)
        低电平到第1bit间延迟SPI REFERENCE CLOCK或ext_clk个周期
XSPIPS_TXD寄存器XSPIPS_TXD_OFFSET (0x1CU)
SPI的发送数据寄存器
Field Name
Bits
Type
Reset Value
Description
XSPIPS_TXD
31:0
RW
0x0
7~0bit:数据寄存器写入这个寄存器的数据会写入FIFO
XSPIPS_RXD寄存器XSPIPS_RXD_OFFSET (0x20U)
SPI的接收数据寄存器
Field Name
Bits
Type
Reset Value
Description
XSPIPS_RXD
31:0
RW
0x0
7~0bit:从接收FIFO读出的数据
XSPIPS_SICR寄存器XSPIPS_SICR_OFFSET (0x24U)
SPI的SLAVE启动对于总线时钟监测设置
Field Name
Bits
Type
Reset Value
Description
Slave_Idle_coun
31:0
RW
0xff
31~8:保留(只读)
7~0bit:当外部主机输出的SCLK时钟稳定的输入Slave_Idle_coun个SPI参考时钟周期或者SPI被取消选择,则SPI  SLAVE模式会检测到启动
XSPIPS_TXWR寄存器XSPIPS_TXWR_OFFSET (0x28U)
XSPIPS_TXWR将满值
Field Name
Bits
Type
Reset Value
Description
Threshold_of_TX_FIFO
31:0
RW
0x1
设置TX FIFO将满值
RX_thres_reg0寄存器RX_thres_reg0_OFFSET (0x2CU)
设置RX FIFO的将空值
Field Name
Bits
Type
Reset Value
Description
Threshold_of_RX_FIFO
31:0
RW
0x1
设置RX FIFO将空值
Mod_id_reg0寄存器Mod_id_reg0 (0xFCU)
这个寄存器暂时不清楚作用
Field Name
Bits
Type
Reset Value
Description
module_ID
31:0
RW
0x90106
31~25:保留(只读)
24~0bit:模块ID号
5硬件电路分析
5.1FEP-BASE功能拓展卡
注意:MZ7035使用的FEP扩展IO默认是3.3V,所以默认选择3.3V 的BASE卡完成本实验。
为了完成SPI的环路测试,需要使用FEP-BASE-CARD,以下截图为FEP-BASE-CARD的相关pin脚原理图。这部分都是FPGA的pin脚,所以我们这里使用EMIO。
下图是FEP-BASE-CARD上的CEP扩展接口的定义
image.jpg
下图是MZ7035FA开发板FEP和CEP-BASE卡FEP接口的定义。
image.jpg       image.jpg
我们把IO分配到CEP6_P: CEP6_N  CEP5_P: CEP5_N
5.2硬件位置
将FEP-BASE-CARD插入开发板的FEP扩展接口,用短接冒,短接如图所示PIN脚,这两个脚在原理图中是CEP6P和CEP6N。MZ7035系列默认都是选3.3V的BASE CARD,选择FEP卡的时候一定不要选错。
image.jpg
6 搭建SOC系统工程
详细的搭建过程这里不再重复,对于初学读者如果还不清楚如何创建SOC工程的,请学习“01Vitis Soc开发入门”这篇文章。
6.1 SOC系统工程
image.jpg
ZYNQ IP中设置SPI0
image.jpg
设置SPI的参考时钟,以及PL 50M时钟提供给ILA使用
image.jpg
ILA设置
image.jpg
6.2 编译并导出平台文件
以下步骤简写,有不清楚的看“[米联客-XILINX-H3_CZ08_7100] LINUX基础篇连载-04 从vitis移植Ubuntu实现二次开发”
1:打开soc_prj内工程。
2:生成Bit文件。
3:导出到硬件: FileExport HardwareInclude bitstream
4:导出完成后,对应工程路径的soc_hw路径下有硬件平台文件:system_wrapper.xsa的文件。根据硬件平台文件system_wrapper.xsa来创建需要Platform平台。
image.jpg
5:打开vitis,并添加设备树模板。
6:创建工程文件,选择device tree创建,创建完成后编译工程。
7:获得设备树以及启动文件,打开虚拟机将文件拷贝到开发包的指定位置。
6.3 设备树及驱动修改
1:设备树修改
vitis生成的设备树已经生成了spi的节点,我们要做的是在节点上添加我们需要的设备。
image.jpg à image.jpg
添加的设备如下:
  1. device@0 {
  2. compatible = "milianke,spidev";
  3. reg = <0>;
  4. spi-max-frequency = <187500000>;
  5. #address-cells = <1>;
  6. #size-cells = <1>;
  7. };
复制代码
可以看到这个设备的名称是我们自定义的,在之后匹配驱动时用。另外规定了地址为0,还有最大频率。
2:修改驱动
由于上一步设置了自己的驱动匹配名称,所以我们需要前往内核源码的文件夹内修改源码。我们是想使用spi的字符驱动,所以我们在spi源码内找到spi的字符驱动实现,位置如下:
1725931800868.jpg
在672行按照规则添加一条设备名称,效果如下:
image.jpg
这样我们的设备树就能和spi字符驱动达成匹配了。
3:打开内核选项
修改了设备树,修改了驱动,还要打开这个驱动的驱动开关才行。首先source好指定文件。
image.jpg
然后使用make_kernel_menuconfig.sh命令载入内核选项。根据左上角路径找到对应的位置,如下图所示:
1725931872803.jpg
选中如图所示的驱动选项。保存并退出。
4:编译系统
编译uboot,编译kernel,制作镜像并烧录系统。具体步骤参考“[米联客-XILINX-H3_CZ08_7100] LINUX基础篇连载-04 从vitis移植Ubuntu实现二次开发”
5:拷贝程序
将对应的demo拷贝至sd卡上:
image.jpg
7 程序分析
7.1 应用程序分析
  1. #include <stdint.h>
  2. #include <unistd.h>
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <getopt.h>
  6. #include <fcntl.h>
  7. #include <sys/ioctl.h>
  8. #include <linux/types.h>
  9. #include <linux/spi/spidev.h>

  10. #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))

  11. static const char *device = "/dev/spidev1.0";
  12. static uint8_t mode;
  13. static uint8_t bits = 8;
  14. static uint32_t speed = 500000;
  15. static uint16_t delay;

  16. static void transfer(int fd)
  17. {
  18.     int ret;
  19.     uint8_t tx[] = {
  20.         0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
  21.         0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
  22.         0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11,
  23.         0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
  24.         0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D,
  25.         0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23,
  26.         0x24, 0x25,
  27.     };
  28.     uint8_t rx[ARRAY_SIZE(tx)] = {0, };
  29.     struct spi_ioc_transfer tr = {
  30.         .tx_buf = (unsigned long)tx,
  31.         .rx_buf = (unsigned long)rx,
  32.         .len = ARRAY_SIZE(tx),
  33.         .delay_usecs = delay,
  34.         .speed_hz = speed,
  35.         .bits_per_word = bits,
  36.     };
  37.     ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
  38.     if (ret < 1)
  39.         printf("can't send spi message\n");

  40.     for (ret = 0; ret < ARRAY_SIZE(tx); ret++) {
  41.         if (!(ret % 6))
  42.             puts("");
  43.         printf("%.2X ", rx[ret]);
  44.     }
  45.     puts("");
  46. }

  47. int main(int argc, char *argv[])
  48. {
  49.     int ret = 0;
  50.     int fd;

  51.     fd = open(device, O_RDWR);
  52.     if (fd < 0)
  53.         printf("can't open device\n");
  54.     ret = ioctl(fd, SPI_IOC_WR_MODE, &mode);
  55.     if (ret == -1)
  56.         printf("can't set spi mode\n");

  57.     ret = ioctl(fd, SPI_IOC_RD_MODE, &mode);
  58.     if (ret == -1)
  59.         printf("can't get spi mode\n");

  60.     ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
  61.     if (ret == -1)
  62.         printf("can't set bits per word\n");

  63.     ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits);
  64.     if (ret == -1)
  65.         printf("can't get bits per word\n");

  66.     ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
  67.     if (ret == -1)
  68.         printf("can't set max speed hz\n");

  69.     ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);
  70.     if (ret == -1)
  71.         printf("can't get max speed hz\n");

  72.     transfer(fd);

  73.     close(fd);
  74.     return ret;
  75. }
复制代码
先来看main里的代码,主要设置了spi的参数:
行57,打开spi字符驱动,具体字符驱动名称使用ls /dev | grep spi可查看。
行60~66,设置spi的写模式和读模式。
行68~74,设置spi的读写多少bit每字节。
行76~82,设置spi的读写速率。
行84,调用函数传输数据。
接下来转到transfer函数中:
行22,为一个发送缓存区,里面已经设置好了需要发送的数据。
行31,接收缓存区,初始化为0。
行32~39,为spi传输结构体,里面包括了发送缓存区地址,接收缓存区地址,传输长度,延时,速率,每字节比特数。
行40,进行一次传输,括号内1的意思为传输一个数据包。
行44~49,打印传输回来的数据。
7.2 驱动IOCTL分析
  1. SPI_IOC_RD_MODE                                //读 模式
  2. SPI_IOC_RD_LSB_FIRST                //读 LSB
  3. SPI_IOC_RD_BITS_PER_WORD        //读 每字多少位
  4. SPI_IOC_RD_MAX_SPEED_HZ        //读 最大速率
  5. SPI_IOC_WR_MODE                                //写 模式
  6. SPI_IOC_WR_LSB_FIRST                //写 LSB
  7. SPI_IOC_WR_BITS_PER_WORD        //写 每字多少位
  8. SPI_IOC_WR_MAX_SPEED_HZ        //写 最大速率
  9. SPI_IOC_MESSAGE(n)                        //传输n个数据包
复制代码
外设的写操作和读操作是同步完成的,如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。所以代码使用了ioctl控制,而非write/read函数。这是因为write的数据无法和read同时进行(或者说很难同时进行),所以write完再read永远读不到数据,而ioctl的数据交换是同时的,不存在如上问题。
8 演示结果
SD2.0 启动 01 而模式开关为 ON OFF(7100 需要先将系统烧录进qspi,然后才能从qspi启动sd卡,“[米联客-XILINX-H3_CZ08_7100] LINUX基础篇连载-04 从vitis移植Ubuntu实现二次开发”)
2f5038eb9880afd532753935815b079.jpg
将 PS 端串口线连接电脑,如果要使用 ssh 登录,将网口线同样连接至电脑,最后给开发板通电。每次重新上电,需要重新插拔 PS 串口,否则会登录失败。
image.jpg
登录板卡后,cd至demo的位置:
image.jpg
运行sudo ./spiloop查看结果,root的密码为root:
image.jpg
可以拔掉跳线帽再试,数据就不对了,装上跳线帽数据恢复正常。
若想自己编译,可使用gcc命令:
image.jpg
可以看到编译完多了一个文件,gcc编译的默认输出名称为a.out。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则