SPI ======== 概述 -------- SPI ( Serial Peripheral Interface, 串行外围设备接口)是一种高速,全双工,同步的通信总线 常规只占用四根线,可节约芯片管脚和 PCB 的布局省空间 常见的集成这种协议的芯片有 EEPROM , FLASH , AD 转换器等 优点: - 支持全双工 - 支持高速 - 协议支持字长不限于 8bit ,可根据应用特点灵活选择消息字长 - 硬件连接简单 缺点: - 相比 IIC 多两根线 - 没有寻址机制,只能靠片选选择不同设备 - 没有从设备接受 ACK ,主设备对于发送成功与否不得而知 - SPI 传输距离较短 .. image:: ../../../_static/SPI_topologies.png :align: center .. centered:: SPI 一主多从拓扑结构 SPI 总线包含以下 4 条逻辑线: - SCK : Serial Clock 串行时钟信号,由主机发给从机 - MOSI : Master Output Slave Input 主发从收信号 - MISO : Master Input Slave Output 主收从发信号 - SS/CS : Slave Select 片选信号,由主机发送 WQ7036A/AX 内置两个 SPI master 控制器 WQ7036A/AX 作为主机,向从机发送命令或数据,从机端接收主机端发送的数据 .. image:: ../../../_static/SPI_framework.png :align: center .. centered:: SPI 架构图 功能特性 ------------------- - 单收、单发和收发并行模式 - 四线全双工和三线半双工通信 - 主机从机模式配置 - 时钟极性( CPOL )和时钟相位( CPHA )可配 - 时钟分频倍数可配 - 传输数据大小可配 - SPI DMA 接口支持使用链表收/发数据 - SPI 中断接口 工作模式 ---------- SPI 支持四种传输模式,可以通过寄存器 SPICR1 中的 CPOL 和 CPHA 位设置: CPOL , 即 Clock Polarity 时钟极性,决定在总线空闲时, SCK 信号线上的电平为高或低 - 1 : 时钟低电平时有效, SCK 信号线在空闲时为高电平 - 0 : 时钟高电平时有效, SCK 信号线在空闲时为低电平 CPHA ,即 Clock Phase 时钟相位,定义 SPI 数据传输的两种基本模式: - 1 : 在时钟偶数( 2 , 4 , 6 , ... , 16 )边沿(包括上下边沿)进行数据采样 - 0 : 在时钟奇数( 1 , 3 , 5 , ... , 15 )边沿(包括上下边沿)进行数据采样 SPI 通讯要求主从设备必须配置相同的传输模式 .. wavedrom:: { signal:[ {node: "..A", phase:-0.35}, {node: "...B", phase:0.14}, [ "时钟信号", {name: "SCK (CPOL=0)", wave: "l.nn......l."}, {name: "SCK (CPOL=1)", wave: "h.pp......h."} ], {}, [ "从机选择", {name: "SSEL", wave: "10.........1"}, ], {}, [ "CPHA=0", {name: "CYCLE (CPHA=0)", wave: "xx34567893x.", data:[1, 2, 3, 4, 5, 6, 7, 8], phase:0.14}, {name: "MOSI (CPHA=0)", wave: "xx34567893x.", data:["bit1", "bit2", "bit3", "bit4", "bit5", "bit6", "bit7", "bit8"], phase:0.14}, {name: "MISO (CPHA=0)", wave: "xx34567893x.", data:["bit1", "bit2", "bit3", "bit4", "bit5", "bit6", "bit7", "bit8"], phase:0.14}, ], [ "CPHA=1", {name: "CYCLE (CPHA=1)", wave: "xx34567893x.", data:[1, 2, 3, 4, 5, 6, 7, 8], phase: -0.35}, {name: "MOSI (CPHA=1)", wave: "xx34567893x.", data:["bit1", "bit2", "bit3", "bit4", "bit5", "bit6", "bit7", "bit8"], phase: -0.35}, {name: "MISO (CPHA=1)", wave: "xx34567893x.", data:["bit1", "bit2", "bit3", "bit4", "bit5", "bit6", "bit7", "bit8"], phase: -0.35}, ], {node: "..C", phase:-0.35}, {node: "...D", phase:0.14}, ], foot: { text:'SPI 工作模式与 CPOL 和 CPHA 设置' }, edge: [ 'A|C', 'B|D' ] } 注意事项一: 当 CPHA = 0 且 CPOL = 0 时, DW_apb_ssi 主设备在开始连续串行传输的每个新数据帧之前切换从选择输出( ss_0_n ) 而当 CPHA = 1 且 CPOL = 1 时, DW_apb_ssi 主设备在开始连续串行传输之前拉低从选择输出( ss_0_n )即可 .. wavedrom:: { signal: [ {name: "sclk_out/in 0" , wave: "nlnnnlnn"}, {name: "txd", wave: "30423042", data: ["LSB", "MSB", "", "LSB", "MSB"]}, {name: "rxd", wave: "30423042", data: ["LSB", "MSB", "", "LSB", "MSB"]}, {name: "ss_0_n/ss_in_n", wave: "l.pl..pl.", phase:0.5}, {name: "ssi_oe_n", wave: "0......."} ], gaps: ". . . s", foot: { text:'当 CPHA = 0 且 CPOL = 0 时 DW_apb_ssi SPI 连续传输' }, } | .. wavedrom:: { signal:[ {name: "sclk_out/in 1" , wave: "n........h"}, {name: "txd", wave: "x32222224x", data: ["MSB", "", "", "", "", "", "", "LSB"]}, {node: ".A.......B"}, {name: "rxd", wave: "x32222224x", data: ["MSB", "", "", "", "", "", "", "LSB"]}, {name: "ss_0_n/ss_in_n", wave: "hl........", phase: 0.3}, {name: "ssi_oe_n", wave: "hl........", phase: 0.2} ], edge:['A+B 4-16 bits'], gaps:". . . . . s", foot: { text:'当 CPHA = 1 且 CPOL = 1 时 DW_apb_ssi SPI 连续传输' }, } 故当用户需要使用 CPHA = 0 且 CPOL = 0 此模式时, 如果用户配置 CS ,交由驱动去控制,那 auto_cs 需设为 false , 如果用户为控制多个 SPI 从设备,那用户需自己手动拉低从选择输出( ss_0_n ), 来保证数据连续传输 注意事项二: - 运行核频率需适配设置的 SPI 频率(推荐运行核主频设置为大于或等于设置的 SPI 频率的四倍) - 如果运行核频率不适配于设置的 SPI 频率,则可能出现 SPI 大数据包传输时传输波形不连续甚至异常终止的问题 .. only:: bbb - SPI 的时钟是从 DCORE 主频上分出的( SPI FREQ = DCORE FREQ / DIV ) - 分频系数 DIV 须为正偶数 - 如若 DCORE 频率无法整除 SPI 频率或因分频系数限制,分频系数 DIV 默认会向上取整 - DCORE 的主频必须大于或等于设置的 SPI 频率的四倍,否则可能出现 SPI CLK 波形异常的问题 .. only:: hornet - SPI 的时钟是从 DCORE 主频上分出的( SPI FREQ = DCORE FREQ / DIV ) - 分频系数 DIV 须为非1非3的正整数 - 如若 DCORE 频率无法整除 SPI 频率或因分频系数限制,分频系数 DIV 默认会向上取整 - DCORE 的主频必须大于或等于设置的 SPI 频率的四倍,否则可能出现 SPI CLK 波形异常的问题 .. only:: scarab - SPI 的时钟是从 APB 主频上分出的( SPI FREQ = APB FREQ / DIV ) - 分频系数 DIV 须为非3的正整数 - 如若 APB 频率无法整除 SPI 频率或因分频系数限制,分频系数 DIV 默认会向上取整 - APB 的主频必须大于或等于设置的 SPI 频率的四倍,否则可能出现 SPI CLK 波形异常的问题 - 如若需要知道 SPI 真实运行频率,可调用 wq_spi_get_frequence 获取 SPI 真实频率信息 - 如若需要知道切频后 SPI 真实频率,可调用 wq_spi_register_freq_change_cb 注册回调函数,在切频时去获取相应信息 - 如若需要在切频后修改 SPI 频率,可在 wq_spi_register_freq_change_cb 注册的回调函数中调用 wq_spi_set_frequence 去设置 注意事项三: 因为存在线路延迟,所以 rx 可能需 delay 几个执行核 clk ( ssi_clk )周期。由于用户不一定知道当下执行核的 clk , 但一定知道 SPI CLK ( SCLK ),故在 spi open 配置参数中添加了 rx_sampledly 参数,即延迟 rx_sampledly/1000 个 SPI clk 周期( SCLK )。( rx_sampledly 推荐设置为 500 ) 比如 rx_sampledly 设置为 500,即延迟半个 SPI clk 周期, 如若 ssi_clk = 4 * SCLK,则驱动内会配置延迟 2 个执行核 clk 周期; 如若 ssi_clk = 8 * SCLK,则驱动内会配置延迟 4 个执行核 clk 周期; 以此类推。 .. wavedrom:: { signal: [ {node: ".....A", phase:-0.35}, {node: "..........B", phase:-0.35}, {node: "...........E", phase:-0.35}, {node: "............G", phase:-0.35}, [ "时钟信号", {name: "ssi_clk", wave: "lnn.........................", period:1} ], {}, [ "主设备信号", {name: "sclk_out", wave: "lnn...l", period:4, phase:2.5}, {name: "txd_mst", wave: "x..3...2...2...2...4...x....", data: ["MSB", "", "", "", "LSB"], phase:-0.4}, {node: ".....C", phase:-0.35}, {name: "rxd_mst", wave: "x........3...2...2...2...4..", data: ["MSB", "", "", "", "LSB"], phase:-0.4}, {node: ".........J", phase:-0.35}, ], {node: "..........D", phase:-0.35}, {node: "...........F", phase:-0.35}, {node: "............H", phase:-0.35}, [ "从设备信号", {name: "sclk_in", wave: "lnn...l", period:4, phase:0.5}, {name: "rxd_slv", wave: "x..3...2...2...2...4...x...", data: ["MSB", "", "", "","LSB"], phase:-2.4}, {name: "txd_slv", wave: "x....3...2...2...2...4...x.", data: ["MSB", "", "", "","LSB"], phase:-2.4}, {node: ".......I", phase:-0.35}, ], ], gaps:". . . . . . . . . . . . . . . . . s", foot: { text: '线路延迟对sclk_out信号的影响(示例:ssi_clk 是 sclk 的 4 倍)' }, edge: [ 'A~>C dly=0','B~>D dly=5','E~>F dly=6','G~>H dly=7','I~>J', ], skin: "narrow", } 资源依赖 --------- - SPI - DMA 用法流程 --------- - 初始化 :cpp:func:`wq_spi_init` - 配置并打开 :cpp:func:`wq_spi_open` - poll 方式先写后读 :cpp:func:`wq_spi_poll_transfer` - poll 方式双工同时读写 :cpp:func:`wq_spi_poll_duplex_transfer` - DMA 方式先写后读 :cpp:func:`wq_spi_dma_transfer` - DMA 方式双工同时读写 :cpp:func:`wq_spi_dma_duplex_transfer` - 关闭 :cpp:func:`wq_spi_close` - 注销 :cpp:func:`wq_spi_deinit` 注:如果需要 DMA 方式读写,需要在调用传输函数调用 :cpp:func:`wq_dma_init` 参考示例 --------- :: examples/spi_demo API 介绍 ----------- .. doxygenfile:: wq_spi.h