4、硬件接口设计:SPI接口详解、I2C接口详解、并行接口详解、FSMC总线应用
做嵌入式频谱仪,说白了就是跟各种外设打交道。ADC采样数据要读进来,LCD屏幕要刷出去,中间还得挂个Flash存点波形快照。这些外设怎么连?靠的就是我们今天要聊的这几种接口。
我个人习惯,在设计硬件接口之前,先想清楚一件事:数据量有多大?实时性要求有多高? 这决定了你选SPI、I2C还是并行总线。别一上来就堆FSMC,杀鸡用牛刀,板子画出来又大又贵。
4.1 SPI接口详解
SPI,全称Serial Peripheral Interface。我最早接触它是在大学做智能车的时候,用SPI读陀螺仪数据。那时候觉得这玩意儿真简单,就四根线,一主多从,速度还快。
SPI的典型信号线就四条:
- SCLK:时钟线,由主机产生
- MOSI:主机输出,从机输入
- MISO:主机输入,从机输出
- CS:片选线,低电平有效
注意,SPI是全双工的。什么意思?就是主机发数据的同时,从机也能回数据。这一点比I2C强,I2C是半双工,同一时刻只能一个方向传。
核心要点:SPI的时钟极性和相位可以配置。CPOL决定时钟空闲电平,CPHA决定数据采样沿。主从机必须配成一样的,否则数据对不上。
我在项目中遇到过一个问题:用SPI驱动一个高速ADC,采样率10Msps。一开始怎么调都读不到正确数据。后来用示波器一抓,发现是CPHA配反了。从机在上升沿采样,我却在下降沿送数据。嗯,这种低级错误,犯过一次就记住了。
SPI的速率能跑多快?看你的MCU和外设。STM32的SPI一般能跑到36MHz甚至更高。对于频谱仪来说,如果ADC是16位、采样率1Msps,那数据量就是2MB/s。SPI跑个20MHz绰绰有余。
我的小技巧:SPI的线长尽量控制在10cm以内。线太长,信号反射严重,高速下容易出错。如果实在要长线,加个74HC244做缓冲。
4.2 I2C接口详解
I2C,Inter-Integrated Circuit。这玩意儿比SPI省引脚,就两根线:SDA(数据线)和SCL(时钟线)。但省引脚是有代价的——速度慢,而且协议复杂一点。
I2C的典型速率分三档:
| 模式 | 速率 | 适用场景 |
|---|---|---|
| 标准模式 | 100kHz | 传感器配置、EEPROM读写 |
| 快速模式 | 400kHz | 音频编解码器、小屏控制 |
| 高速模式 | 3.4MHz | 高速传感器、部分ADC |
你想想看,I2C最高才3.4MHz,而SPI随便就几十兆。所以频谱仪里,I2C一般只用来配置寄存器,不拿来传大量数据。比如配置ADC的增益、滤波器带宽,用I2C就很合适。
I2C的协议细节我就不全讲了,说几个容易踩坑的地方:
- 上拉电阻:SDA和SCL必须接上拉电阻,一般4.7kΩ。总线电容大时,电阻要小一点,比如2.2kΩ。
- 地址冲突:I2C设备有7位地址,最多挂127个设备。但很多芯片的地址引脚只有两三个,容易冲突。选型时注意。
- 时钟拉伸:有些从机处理慢,会拉低SCL让主机等。你的MCU驱动库要支持这个特性。
我曾经踩过的坑:用I2C读一个温湿度传感器,读出来的数据偶尔跳变。查了半天,发现是I2C总线上的电容太大,信号上升沿太缓。解决办法是把上拉电阻从4.7kΩ换成2.2kΩ,问题解决。
4.3 并行接口详解
并行接口,说白了就是一次传多个bit。比如8位并行,就是一次传8个bit。速度比SPI快得多,但引脚也多得多。
典型的并行接口信号:
- D0-D7:8位数据线
- WR:写使能
- RD:读使能
- CS:片选
- RS:寄存器选择(数据/命令)
在频谱仪里,并行接口最常用的地方是LCD屏幕。尤其是那些分辨率高、刷新率快的屏,比如320x240的TFT,用SPI刷一帧要几十毫秒,用8位并行只要几毫秒。
我记得有一次做手持频谱仪,客户要求屏幕刷新率不低于30fps。用SPI刷屏,算下来一帧要40ms,只能到25fps。换成8位并行接口,一帧降到12ms,轻松跑到60fps。这就是并行接口的优势。
但并行接口也有缺点:
- 引脚占用多,MCU的IO不够用
- 布线麻烦,8根数据线要等长
- 高速时信号串扰严重
我的建议:如果MCU的IO够用,且对刷新率有要求,优先用并行接口。如果IO紧张,可以考虑用SPI+DMA的方式,也能达到不错的性能。
4.4 FSMC总线应用
FSMC,Flexible Static Memory Controller。这玩意儿是STM32等高端MCU才有的外设。它可以把外部存储器或外设映射到MCU的地址空间里,像访问内部RAM一样访问外部设备。
FSMC支持三种类型的设备:
- NOR Flash:用于存代码或数据
- PSRAM:扩展RAM
- NAND Flash:大容量存储
在频谱仪里,FSMC最常见的用法是驱动大尺寸LCD。比如7寸屏,分辨率800x480,16位色。一帧数据量是800x480x2 = 768KB。如果用SPI刷,就算跑40MHz,一帧也要768KB x 8 / 40MHz ≈ 153ms,才6.5fps。用FSMC呢?FSMC的访问速度可以到几十纳秒一次,一帧只要几毫秒。
FSMC的配置要点:
- 时序参数:地址建立时间、数据保持时间、片选有效时间。这些要根据外设的datasheet来配。
- 地址映射:FSMC把外部设备映射到不同的Bank。比如Bank1的起始地址是0x60000000,你往这个地址写数据,就等于往外部设备写数据。
- 数据宽度:8位、16位可选。16位速度更快,但引脚多一倍。
实战经验:用FSMC驱动LCD时,我习惯把LCD的寄存器地址和数据地址分开。比如寄存器地址映射到0x60000000,数据地址映射到0x60020000。这样写命令和写数据就是两个不同的地址,代码写起来很直观。
FSMC的代码示例(以STM32驱动ILI9488为例):
// FSMC初始化配置
void FSMC_LCD_Init(void)
{
FSMC_NORSRAMInitTypeDef FSMC_NORSRAMInitStructure;
FSMC_NORSRAMTimingInitTypeDef p;
// 时序参数:根据ILI9488 datasheet配置
p.FSMC_AddressSetupTime = 1; // 地址建立时间
p.FSMC_AddressHoldTime = 0; // 地址保持时间
p.FSMC_DataSetupTime = 3; // 数据建立时间
p.FSMC_BusTurnAroundDuration = 0;
p.FSMC_CLKDivision = 0;
p.FSMC_DataLatency = 0;
p.FSMC_AccessMode = FSMC_AccessMode_A;
FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM1;
FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable;
FSMC_NORSRAMInitStructure.FSMC_MemoryType = FSMC_MemoryType_SRAM;
FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;
FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode = FSMC_BurstAccessMode_Disable;
FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
FSMC_NORSRAMInitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable;
FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;
FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable;
FSMC_NORSRAMInitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable;
FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable;
FSMC_NORSRAMInitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable;
FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &p;
FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &p;
FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure);
}
// 写命令
#define LCD_REG (*(volatile uint16_t *)0x60000000)
// 写数据
#define LCD_RAM (*(volatile uint16_t *)0x60020000)
void LCD_WriteCmd(uint16_t cmd)
{
LCD_REG = cmd;
}
void LCD_WriteData(uint16_t data)
{
LCD_RAM = data;
}
你看,用FSMC之后,写LCD就跟写内存一样简单。而且速度极快,一条指令就完成一次写操作。
注意:FSMC的引脚是固定的,不能随便改。比如STM32F407的FSMC,Bank1的片选是PD7,地址线A0是PF0。画PCB时一定要对照datasheet,别接错了。
最后总结一下这四种接口的选择思路:
- SPI:引脚少、速度快,适合ADC、DAC、Flash。频谱仪的主力接口。
- I2C:引脚最少,但速度慢,只适合配置寄存器、读传感器。
- 并行接口:速度快、引脚多,适合中小尺寸LCD。
- FSMC:速度最快、功能最强,适合大尺寸LCD、外部RAM。但只有高端MCU才有。
我个人习惯,频谱仪里ADC用SPI,LCD用FSMC或并行接口,传感器和配置芯片用I2C。这样各取所长,性能最优。
好了,接口设计就聊到这里。下一章我们开始讲具体的电路设计,包括电源、信号调理、抗干扰这些实战内容。