STM32与FPGA协同设计:电赛控制类题目软硬件集成实战指南
2026/8/2 2:39:48 网站建设 项目流程

最近在准备2026年电赛,特别是控制类题目,发现很多同学在“发挥部分”的软硬件协同设计上容易卡壳。本文将以一个典型的“吉林省电赛发挥部分第一问”为蓝本,系统拆解从需求分析、硬件选型、核心算法到代码实现的完整闭环方案。无论你是初次接触电赛的新手,还是想优化现有方案的进阶选手,都能从中找到清晰的实现路径和避坑指南。我们将重点围绕STM32与FPGA的协同工作模式展开,涵盖信号处理、控制算法和系统调试的全流程。

1. 背景与核心概念:电赛“发挥部分”的挑战与应对

全国大学生电子设计竞赛(电赛)的“发挥部分”历来是区分队伍水平的关键。这类题目通常不会给出明确的实现路径,而是设定一个开放性的目标,要求参赛者在基础功能之上,进行创新性设计和性能优化。其核心挑战在于系统集成度、实时性和稳定性

以常见的控制类题目(如小球平衡、循迹小车、姿态控制)为例,“发挥部分第一问”往往聚焦于一个核心性能指标的极致优化,例如:

  • 控制精度:将小球的定位误差从厘米级提升到毫米级。
  • 响应速度:缩短系统从扰动到恢复稳定的调节时间。
  • 抗干扰能力:在存在外部扰动(如风吹、敲击)时,系统仍能保持稳定。
  • 多模态任务:在完成基本跟踪后,实现轨迹绘制、速度跟随等复杂任务。

要应对这些挑战,单一单片机(如STM32)有时会显得力不从心,尤其是在需要高速并行数据采集、复杂图像处理或超高速脉冲控制的场景下。这时,STM32 + FPGA的异构架构就成为了一个强有力的解决方案。

  • STM32:作为主控制器,擅长复杂的逻辑判断、浮点运算、通信协议处理(如PID控制算法、状态机、USB/串口通信)。
  • FPGA:作为协处理器或前端接口,擅长高速、并行的硬件逻辑处理(如多路ADC/DAC控制、摄像头数据流预处理、精确PWM/脉冲生成、电机编码器计数)。

本文假设的“吉林省电赛发挥部分第一问”场景为:基于视觉的小球跟踪与平衡系统,要求将小球在平面内的定位精度提高至1mm以内,并实现快速镇定。我们将以此为例,贯穿全文进行讲解。

2. 环境准备与版本说明

在开始具体设计前,搭建一个稳定、可复现的开发环境至关重要。以下是本文示例所基于的软硬件环境,请根据你的实际板卡和需求进行调整。

硬件平台:

  • 主控制器:STM32F407ZGT6(高性能Cortex-M4内核,带FPU,适合浮点运算密集的控制算法)
  • 协处理器:Xilinx Artix-7 FPGA(XC7A35T-1FGG484C,逻辑资源适中,性价比高)
  • 图像传感器:OV5640摄像头模块(500万像素,通过DVP接口输出)
  • 执行机构:步进电机或伺服电机+丝杆滑台(用于控制平板倾斜)
  • 通信接口:STM32与FPGA之间采用高速SPI(用于命令与状态传输)或FSMC(用于大量图像数据搬运)。本文以SPI为例。
  • 调试工具:ST-Link V2仿真器、USB转串口模块、逻辑分析仪(可选,用于分析时序)。

软件开发环境:

  • STM32开发
    • IDE: Keil uVision 5 MDK(或STM32CubeIDE)
    • 固件库: HAL库(STM32CubeMX生成)
    • 编译器: ARM Compiler 6
  • FPGA开发
    • IDE: Xilinx Vivado 2019.1(或更新版本)
    • 设计语言: Verilog HDL
  • 上位机(可选,用于调试)
    • 语言: Python 3.8+
    • 库: PySerial, OpenCV, PyQt5

关键软件版本说明:

  • STM32CubeMX版本:6.6.0+,用于快速配置引脚、时钟和外设。
  • Vivado版本:建议使用2018.3至2020.1之间的稳定版本,新版本可能存在IP核兼容性问题。
  • 版本管理的核心原则:在团队内部统一开发环境版本,避免因版本差异导致的工程无法打开或编译错误。

3. 系统架构与核心原理拆解

一个典型的“STM32+FPGA+视觉”的电赛系统架构如下图所示(文字描述):

[传感器层] OV5640摄像头 -> [数据采集与预处理层] FPGA -> [核心控制层] STM32 -> [执行层] 电机驱动器 -> 电机/滑台 | | | | (图像数据流) (图像二值化、中心坐标提取) (PID计算、控制指令生成) (功率放大)

3.1 FPGA的角色与核心任务

FPGA在这里不运行软件程序,而是通过硬件逻辑电路实现以下功能,其优势是确定性和高速度。

  1. 摄像头驱动与图像采集:编写Verilog代码,模拟OV5640的SCCB(类似I2C)配置时序,并捕获DVP接口的像素数据(PCLK, VSYNC, HREF, DATA)。
  2. 图像预处理(算法硬化)
    • RGB转灰度Gray = 0.299*R + 0.587*G + 0.114*B,在FPGA中可用移位和加法近似实现。
    • 二值化:根据预设阈值,将灰度图像转为黑白图像,极大减少数据量。阈值可通过STM32实时下发。
    • 中心坐标计算:对二值化后的图像,计算所有白色像素点的X、Y坐标平均值。这是一个典型的“累加-计数-除法”流程,FPGA可以并行流水线处理,每帧图像结束时立即得出结果。
  3. 高速数据接口:将计算得到的小球中心坐标(X, Y)、帧有效标志等数据,通过SPI接口实时发送给STM32。
// 示例:FPGA端SPI发送模块的核心状态机片段 (Verilog) module spi_master ( input wire clk, input wire rst_n, input wire [15:0] data_x, // 小球X坐标 input wire [15:0] data_y, // 小球Y坐标 input wire data_valid, // 坐标有效信号 output reg spi_clk, output reg spi_mosi, output reg spi_cs_n ); reg [2:0] state; reg [4:0] bit_cnt; reg [31:0] shift_reg; // 组合发送X和Y坐标 always @(posedge clk or negedge rst_n) begin if (!rst_n) begin state <= IDLE; spi_cs_n <= 1'b1; end else begin case(state) IDLE: begin if(data_valid) begin shift_reg <= {data_x, data_y}; spi_cs_n <= 1'b0; bit_cnt <= 31; state <= SEND; end end SEND: begin spi_clk <= ~spi_clk; // 生成SPI时钟 if(spi_clk) begin // 在时钟下降沿移位数据 spi_mosi <= shift_reg[bit_cnt]; bit_cnt <= bit_cnt - 1; if(bit_cnt == 0) begin state <= IDLE; spi_cs_n <= 1'b1; end end end endcase end end endmodule

3.2 STM32的角色与核心任务

STM32作为系统大脑,负责接收FPGA处理后的信息,执行核心控制算法,并管理整个系统的任务调度。

  1. 通信与数据解析:通过SPI中断或DMA方式接收FPGA发来的坐标数据包,并解析出X, Y值。
  2. 核心控制算法
    • PID控制:这是实现高精度跟踪和快速镇定的核心。根据小球当前位置与目标位置(如画面中心)的偏差,计算电机控制量。
    • 位置式PID公式u(k) = Kp*e(k) + Ki*Σe(j) + Kd*[e(k)-e(k-1)]
    • 增量式PID(更常用,抗积分饱和)Δu(k) = Kp*[e(k)-e(k-1)] + Ki*e(k) + Kd*[e(k)-2e(k-1)+e(k-2)]
  3. 电机控制:将PID计算出的控制量(PWM占空比、脉冲数)通过定时器输出,驱动电机运动。
  4. 系统状态管理:实现一个状态机(例如QP状态机),管理“初始化”、“标定”、“自动运行”、“故障处理”等系统状态。

3.3 视觉定位精度的提升关键

要达到1mm的定位精度,仅靠软件算法不够,需要软硬件协同:

  • 硬件:选择光学畸变小的镜头,保证摄像头安装平整,减少机械误差。
  • FPGA处理:采用亚像素定位算法。简单实现如“重心法”,在二值化边界附近利用灰度值进行加权计算,可将定位精度从像素级提升到0.1像素级别。
  • STM32算法:结合卡尔曼滤波互补滤波,融合当前视觉坐标和历史坐标/惯性数据,预测小球下一时刻位置,有效滤除图像噪声和抖动,使控制更加平滑。

4. 完整实战案例:从零搭建小球跟踪控制系统

4.1 硬件连接与STM32基础工程创建

  1. 硬件连接清单

    • OV5640 DVP接口 -> FPGA GPIO Bank (PCLK, VSYNC, HREF, D[9:0])
    • FPGA SPI接口 (MISO, MOSI, SCLK, CS) -> STM32对应的SPI引脚 (PA5, PA6, PA7, PA4)
    • STM32 电机控制PWM引脚 (TIM1_CH1, TIM1_CH2) -> 电机驱动器PWM输入
    • STM32 USART1 (PA9-TX, PA10-RX) -> USB转串口模块,用于打印调试信息。
  2. 使用STM32CubeMX创建工程

    • 选择MCU:STM32F407ZGTx。
    • 配置时钟树:HSE外部晶振8MHz,主频PLL到168MHz。
    • 配置外设:
      • SPI1:全双工主模式,时钟分频到10.5MHz(根据FPGA速度调整),数据大小16位。
      • TIM1:通道1和2配置为PWM Generation模式,频率1kHz,用于电机控制。
      • USART1:异步模式,波特率115200。
    • 生成代码(IDE选择MDK-ARM V5)。

4.2 FPGA图像采集与处理核心代码

在Vivado中创建工程,主要模块如下:

// top_module.v - 顶层模块 module top_module( input wire sys_clk, // 系统时钟,如50MHz input wire rst_n, // 复位 // OV5640接口 input wire cam_pclk, input wire cam_vsync, input wire cam_href, input wire [7:0] cam_data, // SPI接口 output wire spi_clk, output wire spi_mosi, input wire spi_miso, output wire spi_cs_n, // 阈值输入(可连接至拨码开关或由STM32配置) input wire [7:0] thresh ); wire [15:0] pixel_x, pixel_y; // 当前像素坐标 wire [7:0] gray_data; // 灰度数据 wire coord_valid; // 坐标有效信号 wire [15:0] center_x, center_y; // 计算出的中心坐标 // 实例化摄像头驱动与坐标生成器 cam_driver u_cam_driver( .pclk(cam_pclk), .vsync(cam_vsync), .href(cam_href), .data(cam_data), .pixel_x(pixel_x), .pixel_y(pixel_y), .gray_data(gray_data) ); // 实例化图像处理与中心计算模块 img_processor u_img_processor( .clk(cam_pclk), // 使用像素时钟同步 .rst_n(rst_n), .pixel_x(pixel_x), .pixel_y(pixel_y), .gray_data(gray_data), .thresh(thresh), .center_x(center_x), .center_y(center_y), .data_valid(coord_valid) ); // 实例化SPI主控制器,发送坐标 spi_master u_spi_master( .clk(sys_clk), .rst_n(rst_n), .data_x(center_x), .data_y(center_y), .data_valid(coord_valid), .spi_clk(spi_clk), .spi_mosi(spi_mosi), .spi_cs_n(spi_cs_n) ); endmodule
// img_processor.v - 图像处理模块核心逻辑 module img_processor( input wire clk, input wire rst_n, input wire [15:0] pixel_x, input wire [15:0] pixel_y, input wire [7:0] gray_data, input wire [7:0] thresh, output reg [15:0] center_x, output reg [15:0] center_y, output reg data_valid ); reg [31:0] sum_x, sum_y; // 累加器,宽度要足够大 reg [31:0] pixel_count; reg frame_active; // 帧有效标志 always @(posedge clk or negedge rst_n) begin if (!rst_n) begin sum_x <= 0; sum_y <= 0; pixel_count <= 0; frame_active <= 0; data_valid <= 0; end else begin // 检测帧开始(VSYNC后第一个HREF) // 此处简化,实际需根据摄像头时序设计 if (pixel_y == 0 && pixel_x == 0) begin frame_active <= 1; sum_x <= 0; sum_y <= 0; pixel_count <= 0; data_valid <= 0; end if (frame_active) begin // 二值化判断:灰度值大于阈值视为目标 if (gray_data > thresh) begin sum_x <= sum_x + pixel_x; sum_y <= sum_y + pixel_y; pixel_count <= pixel_count + 1; end // 检测帧结束(假设一帧为640*480) if (pixel_y == 479 && pixel_x == 639) begin frame_active <= 0; if (pixel_count != 0) begin // 计算中心坐标(除法在FPGA中消耗资源,可考虑在STM32做) // 此处为简化,先发送累加和与计数,由STM32做除法 center_x <= sum_x[15:0]; // 实际可发送部分高位 center_y <= sum_y[15:0]; data_valid <= 1; end else begin data_valid <= 0; // 未检测到目标 end end else begin data_valid <= 0; end end end end endmodule

4.3 STM32核心控制代码实现

在Keil工程中,我们主要编写以下逻辑:

// main.c - 主循环与初始化 #include "main.h" #include "spi.h" #include "tim.h" #include "usart.h" #include "pid.h" #include "state_machine.h" extern SPI_HandleTypeDef hspi1; extern TIM_HandleTypeDef htim1; // 全局变量 volatile int16_t ball_center_x = 0, ball_center_y = 0; volatile uint8_t vision_data_ready = 0; PID_TypeDef pid_x, pid_y; // X, Y轴PID控制器 SystemState_t sys_state = STATE_INIT; int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_SPI1_Init(); MX_TIM1_Init(); MX_USART1_UART_Init(); // 初始化PID控制器参数 PID_Init(&pid_x, 2.5, 0.1, 0.8, 100, -100); // Kp, Ki, Kd, 输出限幅 PID_Init(&pid_y, 2.5, 0.1, 0.8, 100, -100); // 启动SPI DMA接收(用于接收FPGA数据) uint8_t rx_buf[4]; HAL_SPI_Receive_DMA(&hspi1, rx_buf, 4); // 假设接收4字节数据 // 启动PWM输出 HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2); // 启动系统状态机 StateMachine_Init(); while (1) { // 状态机调度 StateMachine_Run(&sys_state); // 在自动运行状态下执行控制循环 if (sys_state == STATE_AUTO_RUN && vision_data_ready) { Control_Task(); vision_data_ready = 0; // 清除标志 } // 空闲时处理其他任务或进入低功耗 HAL_Delay(1); } } // SPI接收完成回调函数(DMA方式) void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi->Instance == SPI1) { // 解析从FPGA接收到的数据,假设前两字节为X,后两字节为Y uint8_t *buf = (uint8_t*)hspi->pRxBuffPtr; ball_center_x = (buf[0] << 8) | buf[1]; ball_center_y = (buf[2] << 8) | buf[3]; vision_data_ready = 1; // 设置数据就绪标志 // 重新启动DMA接收,准备下一帧数据 HAL_SPI_Receive_DMA(&hspi1, buf, 4); } }
// pid.c - 增量式PID实现 #include "pid.h" void PID_Init(PID_TypeDef *pid, float kp, float ki, float kd, float out_max, float out_min) { pid->Kp = kp; pid->Ki = ki; pid->Kd = kd; pid->OutMax = out_max; pid->OutMin = out_min; pid->Integral = 0; pid->PrevError = 0; pid->PrevError2 = 0; } float PID_Calculate(PID_TypeDef *pid, float setpoint, float measured) { float error = setpoint - measured; float p_out = pid->Kp * (error - pid->PrevError); float i_out = pid->Ki * error; float d_out = pid->Kd * (error - 2 * pid->PrevError + pid->PrevError2); float increment = p_out + i_out + d_out; // 积分抗饱和处理(简单版) float output = pid->Integral + increment; if (output > pid->OutMax) { output = pid->OutMax; } else if (output < pid->OutMin) { output = pid->OutMin; } else { pid->Integral = output; // 未饱和则更新积分 } // 更新误差历史 pid->PrevError2 = pid->PrevError; pid->PrevError = error; return output; } // control_task.c - 控制任务 void Control_Task(void) { // 设定点:屏幕中心 (假设分辨率320x240) const int16_t setpoint_x = 160; const int16_t setpoint_y = 120; // 计算PID输出 float control_x = PID_Calculate(&pid_x, setpoint_x, ball_center_x); float control_y = PID_Calculate(&pid_y, setpoint_y, ball_center_y); // 将控制量转换为PWM占空比 (0-1000对应0-100%) uint16_t pwm_x = (uint16_t)(500 + control_x); // 假设500为中心占空比 uint16_t pwm_y = (uint16_t)(500 + control_y); // 限制PWM范围 pwm_x = (pwm_x > 1000) ? 1000 : ((pwm_x < 0) ? 0 : pwm_x); pwm_y = (pwm_y > 1000) ? 1000 : ((pwm_y < 0) ? 0 : pwm_y); // 更新PWM输出 __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, pwm_x); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, pwm_y); // 调试信息输出(通过串口) printf("X:%d,Y:%d,PWM_X:%d,PWM_Y:%d\r\n", ball_center_x, ball_center_y, pwm_x, pwm_y); }

4.4 系统联调与验证

  1. 分模块调试

    • FPGA单独调试:使用Vivado的ILA(集成逻辑分析仪)抓取摄像头数据流和计算出的坐标信号,确保图像采集和处理逻辑正确。
    • STM32单独调试:通过串口打印模拟的坐标数据,验证PID算法输出和PWM信号是否正常。
    • 通信调试:用逻辑分析仪连接SPI线路,检查FPGA发送和STM32接收的数据格式、时序是否正确。
  2. 闭环调试

    • 先让系统在“开环”下运行,手动移动小球,观察串口打印的坐标变化是否平滑、准确。
    • 然后进入“闭环”,从小幅度扰动开始,逐步调整PID参数(Kp, Ki, Kd)。调参口诀:先比例(P)、再积分(I)、后微分(D)。先让系统有反应(P),消除静差(I),最后抑制超调和振荡(D)。
  3. 性能测试

    • 精度测试:用高精度标尺测量小球实际位置与系统报告位置,计算误差。
    • 响应速度测试:给系统一个阶跃扰动(如快速拨动小球),用手机慢动作拍摄,测量从扰动到重新稳定的时间。
    • 稳定性测试:长时间运行,观察系统是否会发散或产生累积误差。

5. 常见问题与排查思路

在电赛这类软硬件结合的项目中,90%的时间可能花在调试上。下表汇总了典型问题及解决方法:

问题现象可能原因排查思路与解决方案
摄像头无图像/FPGA采集不到数据1. 摄像头电源或时钟不正常。
2. SCCB配置失败。
3. DVP时序不匹配。
1. 用万用表测量摄像头供电电压(3.3V)。
2. 用逻辑分析仪抓取SCCB(I2C)波形,确认寄存器配置成功。
3. 检查FPGA代码中的VSYNC、HREF、PCLK采样边沿是否与摄像头手册一致。
小球坐标跳动剧烈(噪声大)1. 二值化阈值设置不当。
2. 环境光干扰。
3. 镜头抖动或对焦不准。
1. 实现动态阈值调整算法,或通过串口实时调整阈值。
2. 增加遮光罩,或改用全局快门摄像头。
3. 加固摄像头支架,使用焦距合适的镜头。
系统响应迟钝(滞后大)1. 图像处理帧率低。
2. PID参数不合适(积分项太强)。
3. 电机响应速度慢。
1. 优化FPGA图像处理流水线,减少处理延迟。降低图像分辨率。
2. 减小积分系数Ki,或加入积分分离、抗饱和算法。
3. 检查电机驱动器的电流和细分设置,确保其能快速响应PWM变化。
小球到达目标点后持续振荡微分项(D)太弱或太强,系统阻尼不足。适当增加微分系数Kd。如果振荡频率高,可能是机械传动有空程,需要机械调整或加入死区补偿。
STM32与FPGA通信数据错误1. SPI时钟相位(CPHA)和极性(CPOL)不匹配。
2. 数据位序(MSB/LSB)不匹配。
3. 未处理好跨时钟域问题。
1. 确保STM32 SPI配置与FPGA SPI从机模式配置完全一致(模式0/1/2/3)。
2. 在代码中统一数据字节序。
3. 在FPGA端对STM32发送来的信号进行同步处理(打两拍)。
使用STM32 CubeMX生成的代码,SPI DMA接收不进回调函数1. DMA或SPI外设未使能中断。
2. 回调函数名错误或未重写。
3. 缓冲区访问冲突。
1. 在CubeMX中勾选SPI和DMA的全局中断。
2. 确保正确重写了HAL_SPI_RxCpltCallback
3. 确保DMA接收缓冲区是全局变量或静态变量,且内存对齐。
想用VSCode开发STM32,但环境搭建复杂需要配置ARM GCC工具链、OpenOCD调试、IntelliSense等。对于电赛等时间紧的任务,建议优先使用Keil或CubeIDE。赛后可以再研究VSCode+EIDE/PlatformIO的配置,作为技能提升。

6. 最佳实践与工程建议

要将一个电赛作品从“能跑”提升到“跑得稳、跑得精”,需要关注以下工程细节:

  1. 电源与接地

    • 为数字部分(STM32、FPGA)和模拟部分(摄像头、电机驱动器)使用独立的LDO供电,并在电源入口处加磁珠隔离。
    • PCB上铺大面积地平面,关键信号线(如时钟、SPI)走线尽量短,并包地处理。
    • 电机电源与逻辑电源务必隔离,电机驱动部分的地线要粗。
  2. 代码架构与可维护性

    • 模块化:将摄像头驱动、图像处理、PID控制、状态机、通信协议等写成独立的.c/.h文件或Verilog模块。
    • 配置文件:将PID参数、阈值、屏幕分辨率等易变参数集中在头文件(如config.h)中,方便调试时修改。
    • 版本控制:务必使用Git管理代码,每次重大修改前提交,便于回溯和团队协作。
  3. 控制算法进阶

    • PID变种:电赛场景中,串级PID(外环位置、内环速度)对于有惯性负载的系统效果更好。模糊PID可以应对非线性较强的系统。
    • 前馈控制:如果扰动可测量(如平台被人为推动),加入前馈控制能极大提升抗扰性能。
    • 设定点平滑:不要让目标位置突变,采用斜坡函数或S曲线规划设定点,可以减少超调。
  4. 调试技巧

    • 串口打印分级:定义不同的调试信息级别(如ERROR, WARN, INFO, DEBUG),通过宏控制输出,避免调试信息淹没核心数据。
    • 利用FPGA资源:将关键内部信号(如坐标值、状态机状态)引出到FPGA空闲IO上,用逻辑分析仪观察,比软件打印更实时、更底层。
    • 录制与回放:将传感器数据(坐标)和控制输出(PWM)实时记录到STM32的SD卡或串口发送给上位机保存。事后可以分析数据,复现问题,离线优化参数。
  5. 比赛现场策略

    • 准备备用方案:准备一套参数保守但绝对稳定的“保底”程序。在最后关头如果新算法不稳定,立刻切换。
    • 封装易损接口:将摄像头、电机等易松动接口用热熔胶或扎带固定。
    • 文档与注释:代码关键部分写好注释,硬件连线图保存好。万一队友需要接手,能快速理解。

攻克电赛的“发挥部分”,本质上是完成一个完整的嵌入式系统项目。它考验的不仅是编程和焊板子,更是系统思维、调试能力和工程素养。从明确需求开始,到硬件选型、模块划分、算法实现、系统集成、调试优化,每一步都需要耐心和严谨。本文提供的STM32+FPGA协同方案,是一个经过验证的高性能架构,希望能为你提供一个清晰的实现范本。最重要的是动手去做,在调试中积累经验,你收获的将远不止一个比赛名次。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询