2. SPI控制器架构设计:顶层模块划分、AHB/APB总线接口设计、时钟域划分与同步策略、寄存器列表定义
好,咱们进入正题。这一章聊的是SPI控制器的骨架——架构设计。说白了,就是决定这个模块长什么样,各个部分怎么分工,数据怎么流动。
我个人习惯,在设计任何IP之前,先画一张顶层框图。这张图能帮你理清思路,也能让团队里的其他人一眼看懂你的设计意图。来,先看这张我画的架构图:
2.1 顶层模块划分
从图上你能看到,我把SPI控制器分成了几个核心模块。为什么这么分?其实核心原则就一条——高内聚、低耦合。每个模块只干自己那摊事,模块之间通过清晰的接口通信。
我列一下主要模块的职责:
- AHB/APB总线接口模块:负责和系统总线打交道。CPU通过它来读写寄存器。说白了,这就是个"翻译官",把总线协议翻译成寄存器读写操作。
- 寄存器模块:存放所有配置、状态和数据。包括控制寄存器、状态寄存器、数据寄存器,还有中断相关的逻辑。
- 时钟域同步模块:处理跨时钟域的数据传递。这个模块虽然不大,但要是出问题,整个芯片都可能挂掉。
- SPI核心控制模块:这是整个控制器的"大脑"。它负责状态机跳转、数据移位、时钟极性和相位的控制。
- 发送/接收FIFO:缓冲数据用的。CPU可以一次性写入多个字节,然后SPI慢慢发出去,不用CPU一直等着。
- IO Pad模块:处理芯片管脚的电平信号。包括SCLK、MOSI、MISO、片选信号CS,还有中断输出。
2.2 AHB/APB总线接口设计
总线接口这块,我建议用APB。为什么?因为SPI控制器的寄存器访问频率不高,APB协议简单,面积也小。当然,如果你的系统总线是AHB,那也可以直接挂AHB从接口。
APB接口的核心信号就这几个:
| 信号名 | 方向 | 描述 |
|---|---|---|
| PCLK | 输入 | APB时钟,通常和HCLK同源 |
| PRESETn | 输入 | 异步复位,低有效 |
| PSEL | 输入 | 从设备选择信号 |
| PENABLE | 输入 | 使能信号,高电平表示有效传输 |
| PWRITE | 输入 | 读写指示,1为写,0为读 |
| PADDR | 输入 | 地址总线,32位 |
| PWDATA | 输入 | 写数据总线,32位 |
| PRDATA | 输出 | 读数据总线,32位 |
| PREADY | 输出 | 准备好信号,表示可以完成传输 |
APB的传输时序其实很简单。PSEL拉高后,PENABLE拉高,一个周期内完成读写。我一般会在PREADY上做个小处理——如果寄存器还没准备好,就拉低PREADY插入等待周期。不过SPI控制器通常不需要等待,直接一个周期搞定。
2.3 时钟域划分与同步策略
时钟域划分,这是SPI控制器设计里最容易出问题的地方。你想想看,系统总线跑在HCLK上,SPI接口跑在SCLK上,这两个时钟频率可能完全不同,甚至SCLK是外部输入的异步时钟。
我一般把整个设计分成三个时钟域:
- HCLK域:总线接口、寄存器模块、中断逻辑。这些逻辑和系统时钟同步。
- SCLK域:SPI核心控制、移位寄存器、FIFO的读写指针。这些逻辑和SPI时钟同步。
- 异步桥接:跨时钟域的数据传递,包括控制信号和状态信号。
跨时钟域同步,我常用的方法就两种:
- 双级同步器:用于单比特控制信号。比如使能信号、中断请求。两个触发器串联,降低亚稳态概率。
- 异步FIFO:用于多比特数据。比如从HCLK域写到SCLK域的发送数据。用格雷码指针和双级同步器实现。
这里有个细节要注意——同步器的建立时间。双级同步器需要两个时钟周期才能完成同步。所以设计状态机时,一定要留出足够的等待时间。我习惯在状态机里加一个"同步等待"状态,确保信号稳定后再操作。
2.4 寄存器列表定义
寄存器定义,说白了就是给CPU一个操作SPI控制器的接口。我一般把寄存器分成三类:控制类、状态类、数据类。
下面是我常用的寄存器列表:
| 偏移地址 | 寄存器名称 | 类型 | 位宽 | 描述 |
|---|---|---|---|---|
| 0x00 | SPI_CR1 | 读写 | 32 | 控制寄存器1:使能、主从模式、时钟极性/相位、波特率分频 |
| 0x04 | SPI_CR2 | 读写 | 32 | 控制寄存器2:中断使能、FIFO复位、数据帧格式 |
| 0x08 | SPI_SR | 只读 | 32 | 状态寄存器:忙标志、发送空、接收非空、溢出错误 |
| 0x0C | SPI_DR | 读写 | 32 | 数据寄存器:发送/接收数据,共用地址 |
| 0x10 | SPI_CRCPR | 读写 | 16 | CRC多项式寄存器(可选) |
| 0x14 | SPI_RXCRCR | 只读 | 16 | 接收CRC寄存器(可选) |
| 0x18 | SPI_TXCRCR | 只读 | 16 | 发送CRC寄存器(可选) |
| 0x1C | SPI_IER | 读写 | 32 | 中断使能寄存器 |
| 0x20 | SPI_IFR | 读写 | 32 | 中断标志寄存器,写1清除 |
控制寄存器SPI_CR1的位定义,我习惯这样安排:
| 位域 | 名称 | 描述 |
|---|---|---|
| [0] | SPE | SPI使能,1使能,0关闭 |
| [1] | MSTR | 主从模式,1主模式,0从模式 |
| [3:2] | CPOL_CPHA | 时钟极性/相位:00模式0,01模式1,10模式2,11模式3 |
| [7:4] | BR | 波特率分频:0000=2分频,0001=4分频,...,1111=256分频 |
| [8] | LSBFIRST | 数据顺序:0 MSB先发,1 LSB先发 |
| [9] | SSM | 软件片选使能 |
| [10] | SSI | 软件片选值 |
| [31:11] | RESERVED | 保留位,读返回0 |
状态寄存器SPI_SR的位定义:
| 位域 | 名称 | 描述 |
|---|---|---|
| [0] | BSY | 忙标志,1表示正在传输 |
| [1] | TXE | 发送FIFO空,1为空 |
| [2] | RXNE | 接收FIFO非空,1有数据可读 |
| [3] | OVR | 溢出错误,接收FIFO满时又有新数据 |
| [4] | MODF | 模式错误,多主冲突 |
| [5] | CRCERR | CRC错误 |
| [31:6] | RESERVED | 保留位 |
嗯,寄存器定义这块,我建议你留一些保留位。为什么?因为后续可能增加功能。比如加个DMA支持、加个多片选控制。保留位就是给未来留的"后门"。我吃过这个亏——第一次设计时寄存器位全用满了,后来加功能只能改地址映射,兼容性搞得一团糟。
好了,这一章的内容就这些。架构设计是基础,基础打好了,后面的实现和验证才能顺风顺水。
可可精品资料库