1. 项目概述
这个项目实现了一个基于FPGA+STM32架构的简易数字示波器系统。核心功能是通过AD9200模数转换芯片采集模拟信号,经由FPGA进行数据缓存和处理,再通过SPI协议与STM32通信,最终在串口屏上显示波形和测量结果。整个系统实现了从信号采集、数据处理到可视化显示的全流程功能。
作为一名电子工程师,我在实际工作中经常需要快速观察和分析各种信号波形。商用示波器虽然功能强大,但在一些简单场景下显得过于笨重。这个项目就是为了解决这个问题而设计的便携式解决方案。它特别适合用于教学演示、简单电路调试等场景。
2. 硬件架构设计
2.1 核心器件选型
AD9200模数转换芯片:
- 10位分辨率
- 最高20MSPS采样率
- 并行数据输出接口
- 单电源供电(3V或5V)
- 内置采样保持电路
选择AD9200主要考虑到其性价比和易用性。10位分辨率对于简易示波器已经足够,20MSPS的采样率可以满足音频频段信号的采集需求。并行接口虽然占用较多IO资源,但数据传输速率高,适合与FPGA直接对接。
FPGA芯片(EP3C16F484):
- Cyclone III系列
- 15408个逻辑单元
- 56个M9K存储块
- 最大用户IO:346个
- 支持多种配置方式
选用这款FPGA主要看中其丰富的逻辑资源和IO数量,能够轻松处理AD9200的并行数据接口。内置的M9K存储块可以方便地实现数据缓存功能。
STM32F407VET6微控制器:
- Cortex-M4内核
- 168MHz主频
- 512KB Flash
- 192KB SRAM
- 丰富的通信接口(SPI/I2C/USART等)
STM32F4系列强大的处理能力和丰富的外设资源使其非常适合作为系统的控制核心。内置的DSP指令集和浮点运算单元可以高效完成FFT等信号处理算法。
2.2 系统连接框图
整个硬件系统的连接关系如下:
code复制AD9200采样芯片 → FPGA(EP3C16F484) ↔ SPI ↔ STM32F407 ↔ UART ↔ 串口屏
AD9200与FPGA之间采用并行接口连接,FPGA与STM32之间通过SPI通信,STM32与串口屏之间通过UART连接。这种架构充分发挥了各器件的优势:FPGA擅长高速数据采集和缓存,STM32适合算法处理和系统控制,串口屏则提供了友好的人机界面。
3. FPGA设计与实现
3.1 AD9200接口设计
AD9200需要以下关键信号:
- CLK:采样时钟(由FPGA提供)
- OE:输出使能(常接有效电平)
- D[9:0]:10位并行数据输出
- PDWN:掉电控制(正常工作时置低)
FPGA需要为AD9200提供稳定的时钟信号。在实际设计中,我使用FPGA内部的PLL将外部晶振时钟分频/倍频到所需的采样频率。例如,如果需要10MHz采样率:
verilog复制// PLL配置示例
pll_ad9200 pll_inst(
.inclk0(clk_50m), // 输入50MHz晶振
.c0(ad_clk) // 输出10MHz采样时钟
);
3.2 数据采集状态机
FPGA内部实现了一个状态机来控制整个采集流程:
verilog复制typedef enum {
IDLE, // 等待启动采样指令
START_SAMPLE, // 启动AD采样
STORE_DATA, // 存储采样数据
TRANSFER, // 通过SPI传输数据
DONE // 传输完成
} state_t;
reg [2:0] state;
reg [8:0] data_counter; // 512点计数器
reg [15:0] data_buffer[511:0]; // 数据缓存
状态转换逻辑:
- 初始处于IDLE状态,等待STM32的启动信号
- 收到信号后进入START_SAMPLE状态,激活AD9200
- STORE_DATA状态存储512个采样点
- 存满后进入TRANSFER状态,通过SPI发送数据
- 传输完成后返回IDLE
注意:状态机的时钟域要与SPI接口时钟协调好,避免跨时钟域问题。我在实际调试中就遇到过因时钟不同步导致的数据丢失问题。
3.3 SPI从机接口实现
FPGA作为SPI从机与STM32通信。SPI接口需要实现以下功能:
- 接收STM32的采样启动命令
- 发送存储的采样数据
- 支持16位数据帧格式
SPI接口的关键信号:
- SCLK:时钟(由STM32主机提供)
- MOSI:主机输出从机输入
- MISO:主机输入从机输出
- SS:片选(低有效)
Verilog实现要点:
verilog复制// SPI接收处理
always @(posedge sclk or posedge ss) begin
if(ss) begin
bit_cnt <= 0;
rx_data <= 16'h0000;
end else begin
rx_data <= {rx_data[14:0], mosi};
bit_cnt <= bit_cnt + 1;
if(bit_cnt == 15) begin
cmd_reg <= rx_data[15:0];
end
end
end
// SPI发送处理
assign miso = tx_data[15 - bit_cnt];
4. STM32软件设计
4.1 系统初始化
STM32需要初始化多个外设:
c复制void Hardware_Init(void)
{
// 1. 初始化系统时钟
SystemClock_Config();
// 2. 初始化SPI接口(主机模式)
MX_SPI1_Init();
// 3. 初始化USART接口(与串口屏通信)
MX_USART1_UART_Init();
// 4. 初始化GPIO
MX_GPIO_Init();
// 5. 初始化DSP库
arm_rfft_init_q15(&S, 512, 0, 1);
}
4.2 SPI通信协议实现
STM32作为SPI主机,需要实现以下通信流程:
- 拉低SS信号启动通信
- 发送16位控制命令(如0xA001表示启动采样)
- 等待FPGA准备好数据(通过GPIO中断检测)
- 读取512个16位数据帧
- 拉高SS信号结束通信
关键代码实现:
c复制void StartSampling(void)
{
uint16_t cmd = 0xA001; // 启动采样命令
HAL_GPIO_WritePin(FPGA_SS_GPIO_Port, FPGA_SS_Pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, (uint8_t*)&cmd, 1, 100);
HAL_GPIO_WritePin(FPGA_SS_GPIO_Port, FPGA_SS_Pin, GPIO_PIN_SET);
}
void ReadSampleData(uint16_t *buffer)
{
uint16_t dummy = 0x0000;
HAL_GPIO_WritePin(FPGA_SS_GPIO_Port, FPGA_SS_Pin, GPIO_PIN_RESET);
for(int i=0; i<512; i++) {
HAL_SPI_TransmitReceive(&hspi1, (uint8_t*)&dummy,
(uint8_t*)&buffer[i], 1, 100);
}
HAL_GPIO_WritePin(FPGA_SS_GPIO_Port, FPGA_SS_Pin, GPIO_PIN_SET);
}
4.3 数据处理算法
4.3.1 数据格式转换
从FPGA接收到的数据是16位格式(高6位补零),需要转换为实际的10位AD值:
c复制uint16_t raw_data = spi_rx_buffer[i];
uint16_t actual_data = (raw_data >> 6) & 0x03FF; // 取低10位
然后转换为实际电压值(假设参考电压3.3V):
c复制float voltage = (float)actual_data * 3.3f / 1024.0f;
4.3.2 FFT频率分析
使用STM32的DSP库进行FFT计算:
c复制void ProcessFFT(uint16_t *adc_buffer, float *amplitude, float *frequency)
{
q15_t fft_input[512];
q15_t fft_output[512];
// 1. 转换为Q15格式
for(int i=0; i<512; i++) {
fft_input[i] = (q15_t)((adc_buffer[i] - 512) * 64); // 去直流并缩放
}
// 2. 执行FFT
arm_rfft_q15(&S, fft_input, fft_output);
// 3. 计算幅值
q15_t max_value = 0;
uint32_t max_index = 0;
arm_max_q15(fft_output+2, 256, &max_value, &max_index);
// 4. 转换为实际频率
*amplitude = (float)max_value / 32768.0f;
*frequency = (float)max_index * (SAMPLING_RATE / 512.0f);
}
实际调试中发现,AD9200的输入阻抗会影响高频信号的测量精度。建议在信号输入端加入适当的阻抗匹配电路。
5. 串口屏界面设计
5.1 通信协议
使用淘晶驰串口屏的Modbus-RTU协议格式:
code复制帧头(5A A5) + 数据长度(06) + 指令(82) + 地址(00 40) + 数据(00 01)
STM32发送波形数据的示例代码:
c复制void SendToScreen(uint16_t *wave_data)
{
uint8_t header[] = {0x5A, 0xA5};
uint8_t len = 2 + 512*2; // 2字节命令 + 512个16位数据
uint8_t cmd[] = {0x82, 0x00, 0x40}; // 写波形显示区域
HAL_UART_Transmit(&huart1, header, 2, 100);
HAL_UART_Transmit(&huart1, &len, 1, 100);
HAL_UART_Transmit(&huart1, cmd, 3, 100);
HAL_UART_Transmit(&huart1, (uint8_t*)wave_data, 512*2, 1000);
}
5.2 界面布局设计
串口屏界面主要包含以下区域:
- 波形显示区:512像素宽,显示实时波形
- 参数显示区:显示频率、幅值、波形类型
- 控制按钮区:开始/停止采样、调节时基等
使用淘晶驰的屏幕设计软件可以方便地设计界面,生成对应的配置文件下载到屏幕中。
6. 系统调试与优化
6.1 常见问题排查
-
AD采样数据不稳定
- 检查电源滤波:AD9200的电源引脚需要紧靠芯片放置0.1μF去耦电容
- 确保时钟信号质量:使用示波器检查采样时钟的抖动和幅度
- 检查模拟输入阻抗匹配:必要时增加缓冲放大器
-
SPI通信失败
- 确认时钟极性(CPOL)和相位(CPHA)设置一致
- 检查SS信号时序:确保在数据传输前已有效
- 验证时钟频率:初期可降低SPI时钟速度调试
-
FFT结果不准确
- 检查采样率设置:确保符合奈奎斯特采样定理
- 添加窗函数减少频谱泄漏:如汉宁窗
- 验证输入信号幅度:避免ADC饱和
6.2 性能优化技巧
-
提高采样率稳定性
- 使用FPGA的专用时钟管脚连接AD9200时钟
- 在PCB布局时使时钟走线尽量短
- 考虑使用差分时钟传输
-
优化SPI传输效率
- 使用STM32的DMA功能传输SPI数据
- 适当提高SPI时钟频率(经测试最高可达18MHz)
- 采用双缓冲机制:FPGA一边采集新数据,STM32一边读取上一批数据
-
改进FFT算法精度
- 增加采样点数到1024或2048
- 采用浮点FFT代替定点运算
- 添加自动增益控制(AGC)功能
7. 项目扩展方向
这个基础框架可以进一步扩展:
- 多通道支持:增加AD9200芯片实现双通道示波器
- 更高采样率:选用更高速的ADC芯片(如AD9288)
- 网络功能:通过以太网或WiFi传输波形数据
- 数据存储:添加SD卡接口保存波形数据
- 高级触发:实现边沿触发、脉宽触发等专业功能
在实际开发过程中,我发现FPGA和STM32的协同工作模式非常灵活。FPGA负责时间关键型任务,STM32处理复杂算法和系统控制,这种架构在很多嵌入式系统中都有广泛应用。通过这个项目,我深刻理解了高速数据采集系统的设计要点,特别是在信号完整性和时序控制方面的考量。
