以下是针对 Synopsys DesignWare APB SSI (DW_apb_ssi) 控制器寄存器的详尽技术说明,并附带完整的裸机驱动代码示例。本模块通常作为 SPI 主/从控制器集成在 SoC 中,是嵌入式领域非常常见的串行外设。


一、模块概述

DW_apb_ssi 是一个可编程的同步串行接口(SSI)外设,兼容 AMBA 2.0 APB 总线规范。其核心特性包括:

  • 全双工同步传输:支持 SPI、Microwire 等多种串行协议。

  • 可编程数据帧长度:支持从 4 位到 128 位的可变帧长度传输。

  • 内置发送/接收 FIFO:支持可配置深度的 FIFO,有效减少 CPU 干预。

  • 支持 DMA 接口:可与 DMA 控制器配合,实现高效数据传输。

  • MSB/LSB 优先可配置:灵活支持不同设备的位序要求。

所有寄存器均为 32 位宽,且必须按 32 位边界对齐访问。当控制器处于 使能(SSI_EN=1) 状态时,大部分控制寄存器的写入操作将被禁止。


二、寄存器详细列表(偏移地址表)

以下是 DW_apb_ssi 控制器完整的寄存器映射表。基地址(Base Address)由 SoC 厂商定义,寄存器偏移(Offset)相对于该基地址计算。

偏移 (Offset)名称访问类型复位值 (典型)功能描述
0x00CTRLR0R/W0x00000007控制寄存器 0:配置 SPI 工作模式、协议、帧格式等。
0x04CTRLR1R/W0x00000000控制寄存器 1:主模式下,控制接收/发送的数据帧数量。
0x08SSIENRR/W0x00000000SSI 使能寄存器:全局使能控制器(1=使能,0=禁用)。
0x0CMWCRR/W0x00000000Microwire 控制寄存器:配置 Microwire 协议相关参数。
0x10SERR/W0x00000000从设备使能寄存器:主模式下,选择有效的从设备片选信号,最多支持 16 个 CS。
0x14BAUDRR/W0x00000000波特率选择寄存器:主模式下,控制 SCLK 输出频率。
0x18TXFTLRR/W0x00000000发送 FIFO 阈值寄存器:设置发送 FIFO 触发中断的深度阈值。
0x1CRXFTLRR/W0x00000000接收 FIFO 阈值寄存器:设置接收 FIFO 触发中断的深度阈值。
0x20TXFLRRO0x00000000发送 FIFO 状态寄存器:指示当前发送 FIFO 中的有效数据条目数。
0x24RXFLRRO0x00000000接收 FIFO 状态寄存器:指示当前接收 FIFO 中的有效数据条目数。
0x28SRRO0x00000006状态寄存器:反映控制器当前状态(忙、FIFO 空/满、冲突等)。
0x2CIMRR/W0x0000003F中断屏蔽寄存器:屏蔽或使能各种中断源。
0x30ISRRO0x00000000中断状态寄存器:反映屏蔽后的中断状态。
0x34RISRRO0x00000000原始中断状态寄存器:反映未经屏蔽的原始中断状态。
0x38TXOICRWO0x00000000发送 FIFO 溢出中断清除寄存器:写任意值清除发送 FIFO 溢出中断。
0x3CRXOICRWO0x00000000接收 FIFO 溢出中断清除寄存器:写任意值清除接收 FIFO 溢出中断。
0x40RXUICRWO0x00000000接收 FIFO 下溢中断清除寄存器:写任意值清除接收 FIFO 下溢中断。
0x44MSTICRWO0x00000000多主冲突中断清除寄存器:写任意值清除多主冲突中断。
0x48ICRWO0x00000000中断清除寄存器:写任意值一次性清除所有已使能的中断。
0x4CDMACRR/W0x00000000DMA 控制寄存器:使能发送/接收 DMA 请求。
0x50DMATDLRR/W0x00000000DMA 发送数据阈值寄存器:设置触发 DMA 发送请求的 FIFO 深度。
0x54DMARDLRR/W0x00000000DMA 接收数据阈值寄存器:设置触发 DMA 接收请求的 FIFO 深度。
0x58IDRRO0x55100000识别寄存器:外设的固定标识码。
0x5CSSI_VERSION_IDRO0x3332322A版本 ID 寄存器:IP 核版本信息。
0x60DRR/W0x00000000数据寄存器:发送/接收 FIFO 的读写缓冲器。
0xF0RX_SAMPLE_DLYR/W0x00000000接收采样延迟寄存器:控制 RXD 输入的采样点延迟。

编程提示:操作包含保留位(RSVD)的寄存器时,务必使用读-修改-写(RMW) 流程,避免破坏保留位状态。


三、关键寄存器位域详解

1. 控制寄存器 0 (CTRLR0)

CTRLR0 是配置 SPI 传输的核心寄存器。注意:根据 IP 核的配置参数,其位域布局可能有两种版本:16 位数据帧版本和 32 位数据帧版本(当 SSI_MAX_XFER_SIZE=32 时)。

  • 数据帧长度字段位置变化:

    • 16 位版本:位于 bits[3:0]

    • 32 位版本:位于 bits[20:16]

位域 (16-bit)位域 (32-bit)名称描述
[3:0][20:16]DFS (Data Frame Size)定义每个数据帧的位数(n-1 编码)。0x00 表示 128 位。
[5:4][5:4]FRF (Frame Format)帧格式选择:0x0=Motorola SPI,0x1=TI SSP,0x2=National Microwire
[7:6][7:6]SCPH (Serial Clock Phase)SPI 时钟相位:0=上升沿采样,1=下降沿采样
[9:8][9:8]TMOD (Transfer Mode)传输模式:0x0=收+发,0x1=只发,0x2=只收,0x3=EEPROM 读
[10][10]SSTE (Slave Select Toggle Enable)主模式下,CS 在帧间是否自动撤销
[11][11]SPOL (Serial Clock Polarity)SPI 时钟极性:0=空闲低电平,1=空闲高电平
[12][12]SRL (Shift Register Loop)内部回环模式使能
[22:21][22:21]SPI_FRF (Enhanced SPI Frame Format)增强型 SPI 帧格式,用于 Dual/Quad SPI 模式
[23][23]SRF (Slave Read FIFO)从机模式下,读 FIFO 的控制
[31:24][31:24]保留-

2. 控制寄存器 1 (CTRLR1)

仅在主模式下有效,定义了在一次传输中需要发送/接收的数据帧数量。

  • 位域 [15:0]:NDF (Number of Data Frames)

    • 写入值 +1 决定了传输的数据帧总数。

    • 例:写入 0x09 表示传输 10 帧数据。

    • 注意:当 TMOD=0x00(全双工)或 TMOD=0x01(只发)时,此寄存器决定发送帧数;当 TMOD=0x02(只收)或 TMOD=0x03(EEPROM 读)时,此寄存器决定接收帧数。

3. SSI 使能寄存器 (SSIENR)

  • 位 [0]:SSI_EN

    • 0:禁用控制器。所有串行传输立即停止,发送/接收 FIFO 被清空。

    • 1:使能控制器。编程安全建议:任何对 CTRLR0、CTRLR1、BAUDR 等控制寄存器的修改,都应先通过写 0 禁用控制器,完成配置后再写 1 重新使能。

4. 波特率寄存器 (BAUDR)

  • 位域 [15:0]:SCKDV

    • 定义 SCLK 输出的分频系数。

    • F_sclk_out = F_ssi_clk / SCKDV,其中 SCKDV 必须是偶数且范围在 2 ~ 65534 之间。LSB 位硬件强制为 0。

    • 例:F_ssi_clk = 100 MHz,若设置 SCKDV = 200,则 F_sclk_out = 100 MHz / 200 = 500 kHz。

    • SCKDV = 0 将禁用 SCLK 输出。

5. 从设备使能寄存器 (SER)

仅在主模式下有效。每个位对应一个硬件片选(CS)信号线。

  • 位域 [15:0]:SER

    • 写 1 到对应的位,将使能该片选信号并启动传输。在传输完成或发生错误前,控制器不会自动清除该位。

    • 注意:在写 SER 寄存器前,必须确保控制器未处于忙(SR.BUSY=0)状态。

6. 状态寄存器 (SR)

位名称功能描述
[0]BUSYSSI 总线忙标志。1=正在传输或发送 FIFO 非空。
[1]TFNF发送 FIFO 未满。1=至少有一个空位。
[2]TFE发送 FIFO 空。1=完全为空。
[3]RFNE接收 FIFO 非空。1=至少有一笔数据。
[4]RFF接收 FIFO 满。1=完全填满。
[6]DCOL数据冲突错误。仅主模式有效,多主冲突时置位,读清除。

7. 中断相关寄存器

DW_apb_ssi 支持多种中断源,通过 IMR 进行独立屏蔽。

  • IMR 位域:

    • [0]:TXEIM - 发送 FIFO 空中断屏蔽

    • [1]:TXOIM - 发送 FIFO 溢出中断屏蔽

    • [2]:RXUIM - 接收 FIFO 下溢中断屏蔽

    • [3]:RXOIM - 接收 FIFO 溢出中断屏蔽

    • [4]:RXFIM - 接收 FIFO 满中断屏蔽

    • [5]:MSTIM - 多主冲突中断屏蔽(仅主模式)


四、完整裸机驱动代码示例

1. 头文件与寄存器定义


#include <stdint.h>
#include <stdbool.h>

/* 寄存器偏移量定义 */
#define DW_SSI_CTRLR0           0x00
#define DW_SSI_CTRLR1           0x04
#define DW_SSI_SSIENR           0x08
#define DW_SSI_MWCR             0x0C
#define DW_SSI_SER              0x10
#define DW_SSI_BAUDR            0x14
#define DW_SSI_TXFTLR           0x18
#define DW_SSI_RXFTLR           0x1C
#define DW_SSI_TXFLR            0x20
#define DW_SSI_RXFLR            0x24
#define DW_SSI_SR               0x28
#define DW_SSI_IMR              0x2C
#define DW_SSI_ISR              0x30
#define DW_SSI_RISR             0x34
#define DW_SSI_TXOICR           0x38
#define DW_SSI_RXOICR           0x3C
#define DW_SSI_RXUICR           0x40
#define DW_SSI_MSTICR           0x44
#define DW_SSI_ICR              0x48
#define DW_SSI_DMACR            0x4C
#define DW_SSI_DMATDLR          0x50
#define DW_SSI_DMARDLR          0x54
#define DW_SSI_IDR              0x58
#define DW_SSI_SSI_VERSION_ID   0x5C
#define DW_SSI_DR               0x60
#define DW_SSI_RX_SAMPLE_DLY    0xF0

/* CTRLR0 位域定义 (16-bit 数据帧版本) */
#define CTRLR0_DFS_MASK         (0xF)   // Data Frame Size
#define CTRLR0_FRF_MOTOROLA     (0x0 << 4)
#define CTRLR0_FRF_TI           (0x1 << 4)
#define CTRLR0_FRF_MICROWIRE    (0x2 << 4)
#define CTRLR0_SCPH_NORMAL      (0x0 << 6) // 上升沿采样
#define CTRLR0_SCPH_INVERTED    (0x1 << 6) // 下降沿采样
#define CTRLR0_SCPOL_LOW        (0x0 << 7) // 空闲低电平
#define CTRLR0_SCPOL_HIGH       (0x1 << 7) // 空闲高电平
#define CTRLR0_TMOD_TX_RX       (0x0 << 8) // 发送+接收
#define CTRLR0_TMOD_TX_ONLY     (0x1 << 8) // 仅发送
#define CTRLR0_TMOD_RX_ONLY     (0x2 << 8) // 仅接收
#define CTRLR0_TMOD_EEPROM      (0x3 << 8) // EEPROM读模式

/* 状态寄存器 (SR) 位域 */
#define SR_BUSY                 (1U << 0)
#define SR_TFNF                 (1U << 1)
#define SR_TFE                  (1U << 2)
#define SR_RFNE                 (1U << 3)
#define SR_RFF                  (1U << 4)
#define SR_DCOL                 (1U << 6)

/* 驱动结构体 */
typedef struct {
    uint32_t base_addr;         // SSI 寄存器基地址
    uint32_t ssi_clk_hz;        // 输入时钟频率 (Hz)
} dw_ssi_t;

2. 底层寄存器操作封装


static inline uint32_t dw_ssi_read(dw_ssi_t *ssi, uint32_t reg) {
    return *(volatile uint32_t *)(ssi->base_addr + reg);
}

static inline void dw_ssi_write(dw_ssi_t *ssi, uint32_t reg, uint32_t val) {
    *(volatile uint32_t *)(ssi->base_addr + reg) = val;
}

3. 初始化配置函数


void dw_ssi_init(dw_ssi_t *ssi, uint32_t ssi_clk_hz, uint32_t spi_freq_hz) {
    ssi->ssi_clk_hz = ssi_clk_hz;
    
    // 1. 禁用 SSI 控制器
    dw_ssi_write(ssi, DW_SSI_SSIENR, 0);
    
    // 2. 计算波特率分频系数 (必须为偶数)
    uint32_t div = ssi_clk_hz / spi_freq_hz;
    if (div < 2) div = 2;
    if (div & 0x1) div++;  // 确保偶数
    if (div > 65534) div = 65534;
    dw_ssi_write(ssi, DW_SSI_BAUDR, div);
    
    // 3. 配置 CTRLR0 - 以 Motorola SPI 模式、全双工为例
    //    假设数据帧长度为 8 位 (DFS = 7)
    uint32_t ctrlr0 = (7 & CTRLR0_DFS_MASK) |          // 8 位数据帧
                      CTRLR0_FRF_MOTOROLA |            // SPI 模式
                      CTRLR0_SCPH_NORMAL |             // 上升沿采样
                      CTRLR0_SCPOL_LOW |               // 空闲低电平
                      CTRLR0_TMOD_TX_RX;               // 全双工
    dw_ssi_write(ssi, DW_SSI_CTRLR0, ctrlr0);
    
    // 4. 设置 FIFO 阈值
    dw_ssi_write(ssi, DW_SSI_TXFTLR, 0);   // 发送 FIFO 空时触发中断
    dw_ssi_write(ssi, DW_SSI_RXFTLR, 0);   // 接收到 1 帧时触发中断
    
    // 5. 使能控制器
    dw_ssi_write(ssi, DW_SSI_SSIENR, 1);
}

4. 片选使能函数


void dw_ssi_select_slave(dw_ssi_t *ssi, int cs_pin, bool enable) {
    // 等待控制器空闲
    while (dw_ssi_read(ssi, DW_SSI_SR) & SR_BUSY);
    
    uint32_t ser = dw_ssi_read(ssi, DW_SSI_SER);
    if (enable)
        ser |= (1U << cs_pin);
    else
        ser &= ~(1U << cs_pin);
    dw_ssi_write(ssi, DW_SSI_SER, ser);
}

5. 发送/接收数据(轮询方式)


void dw_ssi_write_tx_fifo(dw_ssi_t *ssi, uint16_t data) {
    // 等待发送 FIFO 未满
    while (!(dw_ssi_read(ssi, DW_SSI_SR) & SR_TFNF));
    dw_ssi_write(ssi, DW_SSI_DR, data);
}

uint16_t dw_ssi_read_rx_fifo(dw_ssi_t *ssi) {
    // 等待接收 FIFO 非空
    while (!(dw_ssi_read(ssi, DW_SSI_SR) & SR_RFNE));
    return (uint16_t)dw_ssi_read(ssi, DW_SSI_DR);
}

int dw_ssi_transfer_full_duplex(dw_ssi_t *ssi, int cs_pin, 
                                 const uint8_t *tx_data, uint8_t *rx_data, int len) {
    if (len <= 0) return -1;
    
    // 使能片选
    dw_ssi_select_slave(ssi, cs_pin, true);
    
    // 逐个发送并接收数据
    for (int i = 0; i < len; i++) {
        dw_ssi_write_tx_fifo(ssi, tx_data ? tx_data[i] : 0x00);
        uint16_t rx = dw_ssi_read_rx_fifo(ssi);
        if (rx_data) rx_data[i] = (uint8_t)rx;
    }
    
    // 等待所有数据发送完成
    while (dw_ssi_read(ssi, DW_SSI_SR) & SR_BUSY);
    
    // 禁用片选
    dw_ssi_select_slave(ssi, cs_pin, false);
    
    return 0;
}

6. 中断处理框架


void dw_ssi_enable_interrupts(dw_ssi_t *ssi, uint32_t mask) {
    dw_ssi_write(ssi, DW_SSI_IMR, mask);
}

void dw_ssi_disable_interrupts(dw_ssi_t *ssi, uint32_t mask) {
    uint32_t imr = dw_ssi_read(ssi, DW_SSI_IMR);
    dw_ssi_write(ssi, DW_SSI_IMR, imr & ~mask);
}

void dw_ssi_isr_handler(dw_ssi_t *ssi) {
    uint32_t isr = dw_ssi_read(ssi, DW_SSI_ISR);
    uint32_t risr = dw_ssi_read(ssi, DW_SSI_RISR);
    
    if (isr & (1U << 0)) {  // 发送 FIFO 空中断
        // 填充更多发送数据到 TX FIFO
        // ... 处理发送逻辑 ...
        // 可选:如果没有更多数据,可通过 IMR 屏蔽此中断
    }
    
    if (isr & (1U << 4)) {  // 接收 FIFO 满中断
        // 从 RX FIFO 读取数据
        while (dw_ssi_read(ssi, DW_SSI_SR) & SR_RFNE) {
            uint16_t rx_data = dw_ssi_read(ssi, DW_SSI_DR);
            // ... 存储接收数据 ...
        }
    }
    
    if (risr & (1U << 1)) {  // 发送 FIFO 溢出
        dw_ssi_write(ssi, DW_SSI_TXOICR, 0);  // 写任意值清除
    }
    
    if (risr & (1U << 3)) {  // 接收 FIFO 溢出
        dw_ssi_write(ssi, DW_SSI_RXOICR, 0);
    }
    
    // 其他中断源类似处理...
}

五、Linux 内核驱动示例

Linux 内核已原生支持 DW_apb_ssi 控制器,驱动代码位于 drivers/spi/spi-dw-core.c。

1. 设备树配置 (Device Tree)

spi0: spi@ffda4000 {
    compatible = "snps,dw-apb-ssi";
    reg = <0xffda4000 0x1000>;
    interrupts = <0 15 4>;
    #address-cells = <1>;
    #size-cells = <0>;
    num-cs = <4>;
    clocks = <&ssi_clk>;
    clock-names = "ssi_clk";
    
    /* 片选信号极性可选配置 */
    cs-gpios = <&gpio0 1 GPIO_ACTIVE_LOW>;
    
    /* SPI 从设备子节点示例 */
    spi_flash: flash@0 {
        compatible = "jedec,spi-nor";
        reg = <0>;  // 使用 CS0
        spi-max-frequency = <50000000>;
    };
};

2. Linux 用户空间 SPI 访问示例


#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>

int main(int argc, char **argv) {
    const char *device = "/dev/spidev0.0";
    uint8_t tx_buf[4] = {0x9F, 0x00, 0x00, 0x00};  // Read JEDEC ID 命令
    uint8_t rx_buf[4] = {0};
    struct spi_ioc_transfer tr = {
        .tx_buf = (unsigned long)tx_buf,
        .rx_buf = (unsigned long)rx_buf,
        .len = 4,
        .speed_hz = 5000000,   // 5 MHz SPI 时钟
        .bits_per_word = 8,
        .delay_usecs = 0,
        .cs_change = 0,
    };
    
    int fd = open(device, O_RDWR);
    if (fd < 0) {
        perror("open");
        return 1;
    }
    
    if (ioctl(fd, SPI_IOC_MESSAGE(1), &tr) < 0) {
        perror("SPI_IOC_MESSAGE");
        close(fd);
        return 1;
    }
    
    printf("Received JEDEC ID: 0x%02X 0x%02X 0x%02X\n", 
           rx_buf[1], rx_buf[2], rx_buf[3]);
    
    close(fd);
    return 0;
}

3. 内核驱动关键函数调用路径

驱动层函数描述
spi-dw-core.cdw_spi_probe()驱动入口,解析设备树并初始化硬件
dw_spi_init_hw()复位 FIFO、配置 CTRLR0、设置波特率
dw_spi_transfer_one()执行单次 SPI 传输
dw_reader() / dw_writer()FIFO 读写操作函数
dw_spi_irq()中断处理函数

六、总结

DW_apb_ssi 控制器的寄存器设计逻辑清晰、功能完备。核心编程模型遵循 “禁用 -> 配置 -> 使能” 三步法,所有关键参数(帧格式、时钟相位/极性、波特率等)均通过 CTRLR0 和 BAUDR 两个核心寄存器控制。

  • CTRLR0:决定了帧格式、传输模式、数据帧长度等 SPI 核心属性。

  • BAUDR:通过偶数分频系数生成精确的 SCLK 时钟。

  • SER:主模式下通过独立位域控制最多 16 个片选信号。

  • FIFO:通过 TXFTLR/RXFTLR 调节中断触发阈值,配合 DMA 实现高性能传输。

理解上述寄存器之间的关联,是驱动开发的基础。在实际项目中,建议优先使用 Linux 内核原生驱动,或在 RTOS 中参照本文的裸机代码进行移植。如需针对特定 SoC 进行适配,欢迎提供更多硬件信息。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐