DW spi寄存器简介与代码示例
以下是针对 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) | 名称 | 访问类型 | 复位值 (典型) | 功能描述 |
|---|---|---|---|---|
0x00 | CTRLR0 | R/W | 0x00000007 | 控制寄存器 0:配置 SPI 工作模式、协议、帧格式等。 |
0x04 | CTRLR1 | R/W | 0x00000000 | 控制寄存器 1:主模式下,控制接收/发送的数据帧数量。 |
0x08 | SSIENR | R/W | 0x00000000 | SSI 使能寄存器:全局使能控制器(1=使能,0=禁用)。 |
0x0C | MWCR | R/W | 0x00000000 | Microwire 控制寄存器:配置 Microwire 协议相关参数。 |
0x10 | SER | R/W | 0x00000000 | 从设备使能寄存器:主模式下,选择有效的从设备片选信号,最多支持 16 个 CS。 |
0x14 | BAUDR | R/W | 0x00000000 | 波特率选择寄存器:主模式下,控制 SCLK 输出频率。 |
0x18 | TXFTLR | R/W | 0x00000000 | 发送 FIFO 阈值寄存器:设置发送 FIFO 触发中断的深度阈值。 |
0x1C | RXFTLR | R/W | 0x00000000 | 接收 FIFO 阈值寄存器:设置接收 FIFO 触发中断的深度阈值。 |
0x20 | TXFLR | RO | 0x00000000 | 发送 FIFO 状态寄存器:指示当前发送 FIFO 中的有效数据条目数。 |
0x24 | RXFLR | RO | 0x00000000 | 接收 FIFO 状态寄存器:指示当前接收 FIFO 中的有效数据条目数。 |
0x28 | SR | RO | 0x00000006 | 状态寄存器:反映控制器当前状态(忙、FIFO 空/满、冲突等)。 |
0x2C | IMR | R/W | 0x0000003F | 中断屏蔽寄存器:屏蔽或使能各种中断源。 |
0x30 | ISR | RO | 0x00000000 | 中断状态寄存器:反映屏蔽后的中断状态。 |
0x34 | RISR | RO | 0x00000000 | 原始中断状态寄存器:反映未经屏蔽的原始中断状态。 |
0x38 | TXOICR | WO | 0x00000000 | 发送 FIFO 溢出中断清除寄存器:写任意值清除发送 FIFO 溢出中断。 |
0x3C | RXOICR | WO | 0x00000000 | 接收 FIFO 溢出中断清除寄存器:写任意值清除接收 FIFO 溢出中断。 |
0x40 | RXUICR | WO | 0x00000000 | 接收 FIFO 下溢中断清除寄存器:写任意值清除接收 FIFO 下溢中断。 |
0x44 | MSTICR | WO | 0x00000000 | 多主冲突中断清除寄存器:写任意值清除多主冲突中断。 |
0x48 | ICR | WO | 0x00000000 | 中断清除寄存器:写任意值一次性清除所有已使能的中断。 |
0x4C | DMACR | R/W | 0x00000000 | DMA 控制寄存器:使能发送/接收 DMA 请求。 |
0x50 | DMATDLR | R/W | 0x00000000 | DMA 发送数据阈值寄存器:设置触发 DMA 发送请求的 FIFO 深度。 |
0x54 | DMARDLR | R/W | 0x00000000 | DMA 接收数据阈值寄存器:设置触发 DMA 接收请求的 FIFO 深度。 |
0x58 | IDR | RO | 0x55100000 | 识别寄存器:外设的固定标识码。 |
0x5C | SSI_VERSION_ID | RO | 0x3332322A | 版本 ID 寄存器:IP 核版本信息。 |
0x60 | DR | R/W | 0x00000000 | 数据寄存器:发送/接收 FIFO 的读写缓冲器。 |
0xF0 | RX_SAMPLE_DLY | R/W | 0x00000000 | 接收采样延迟寄存器:控制 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] | BUSY | SSI 总线忙标志。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.c | dw_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 进行适配,欢迎提供更多硬件信息。
更多推荐
所有评论(0)