当前位置: 首页 > news >正文

手把手教你用FPGA(EP4CE10)和STM32F103实现双向UART数据转发(含完整Verilog与C代码)

FPGA与STM32双向UART通信实战:从硬件搭建到代码解析

在嵌入式系统开发中,FPGA和MCU的协同工作越来越常见。FPGA擅长并行处理和硬件加速,而STM32这类微控制器则更适合控制逻辑和协议处理。本文将带你实现一个完整的FPGA(EP4CE10)与STM32F103之间的双向UART数据转发系统,包含硬件连接、Verilog和C代码的逐行解析,以及实际调试中的关键技巧。

1. 系统架构与硬件准备

这个项目的核心目标是建立一个双向通信桥梁:上位机发送的数据可以通过STM32转发到FPGA,也可以直接发送到FPGA再转发给STM32。这种架构在工业控制、数据采集等场景中非常实用。

所需硬件清单

  • FPGA开发板:EP4CE10F17C8核心板
  • STM32开发板:STM32F103RCT6(正点原子或野火系列均可)
  • USB转TTL模块(用于连接上位机)
  • 杜邦线若干

硬件连接示意图:

上位机 <--UART--> STM32(UART2) <--UART--> FPGA(UART1) <--UART--> 上位机

关键连接细节

  • FPGA的UART1_TX连接STM32的UART1_RX(PA10)
  • FPGA的UART1_RX连接STM32的UART1_TX(PA9)
  • STM32的UART2(PA2/PA3)连接上位机
  • FPGA的UART2连接另一个上位机通道(可选)

注意:所有UART接口的电平必须匹配,通常是3.3V TTL电平。如果使用5V设备,需要电平转换电路。

2. FPGA端的Verilog实现

FPGA作为通信中间节点,需要处理两路UART的收发以及数据缓冲。我们采用FIFO作为数据中转站,确保在数据突发时不会丢失。

2.1 顶层模块设计

module test( input sys_clk, // 50MHz系统时钟 input sys_rst_n, // 低电平复位 // UART1接口 input uart1_rxd, // FPGA接收STM32数据 output uart1_txd, // FPGA发送数据到STM32 // UART2接口 input uart2_rxd, // FPGA接收上位机数据 output uart2_txd // FPGA发送数据到上位机 );

时钟分频处理:由于UART波特率通常较低(如115200bps),我们需要对50MHz系统时钟进行分频:

wire clk_1m_w; pll_clk u_pll_clk( .inclk0(sys_clk), .c0(clk_1m_w) // 生成1MHz时钟用于调试 );

2.2 双FIFO数据缓冲机制

数据流向控制是核心难点,我们采用两个独立的FIFO分别处理不同方向的数据:

  1. uart2_to_uart1 FIFO:存储从上位机(UART2)接收要转发给STM32(UART1)的数据
  2. uart1_to_uart2 FIFO:存储从STM32(UART1)接收要转发给上位机(UART2)的数据
// UART2到UART1的FIFO uart2_uart1 u_uart2_uart1( .wrclk(sys_clk), .wrreq(uart2_wrreq), .data(uart2_recv_data), .wrfull(uart2_wrfull), .rdclk(sys_clk), .rdreq(uart2_rdreq), .q(uart2_data_out), .rdempty(uart2_rdempty) ); // UART1到UART2的FIFO uart1_uart2 u_uart1_uart2( .wrclk(sys_clk), .wrreq(uart1_wrreq), .data(uart1_recv_data), .wrfull(uart1_wrfull), .rdclk(sys_clk), .rdreq(uart1_rdreq), .q(uart1_data_out), .rdempty(uart1_rdempty) );

2.3 UART收发模块关键代码

UART接收模块需要准确检测起始位并在数据位中点采样:

always @(posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) begin rxdata <= 8'd0; end else if(rx_flag) begin if (clk_cnt == BPS_CNT/2) begin // 在数据位中点采样 case (rx_cnt) 4'd1: rxdata[0] <= uart_rxd_d1; 4'd2: rxdata[1] <= uart_rxd_d1; // ... 其他数据位 4'd8: rxdata[7] <= uart_rxd_d1; default:; endcase end end end

发送模块则需要注意忙信号处理,防止数据覆盖:

always @(posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) begin uart_txd <= 1'b1; end else if (tx_flag) begin case(tx_cnt) 4'd0: uart_txd <= 1'b0; // 起始位 4'd1: uart_txd <= tx_data[0]; // ... 其他数据位 4'd9: uart_txd <= 1'b1; // 停止位 default:; endcase end end

3. STM32端的C语言实现

STM32作为系统的另一个智能节点,需要配置两个UART接口并实现数据转发逻辑。

3.1 UART初始化配置

使用STM32CubeMX可以快速生成初始化代码,但我们还是分析关键配置:

void uart1_init(u32 bound) { // GPIO端口设置 GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; // 使能USART1和GPIOA时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); // 配置USART1_TX (PA9)为复用推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); // 配置USART1_RX (PA10)为浮空输入 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); // USART参数配置 USART_InitStructure.USART_BaudRate = bound; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); // 使能USART1接收中断 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); USART_Cmd(USART1, ENABLE); }

3.2 中断服务程序实现

STM32通过中断方式接收数据,提高系统响应效率:

void USART1_IRQHandler(void) { u8 Res; if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { Res = USART_ReceiveData(USART1); if((USART1_RX_STA & 0x8000) == 0) { // 接收未完成 USART1_RX_BUF[USART1_RX_STA & 0X3FFF] = Res; USART1_RX_STA++; if(Res == 0x65) // 检测到结束标志 USART1_RX_STA |= 0x8000; else if(USART1_RX_STA > (USART1_REC_LEN-1)) USART1_RX_STA = 0; // 错误处理 } } }

3.3 主循环数据转发逻辑

主程序不断检查接收状态标志,实现数据转发:

while(1) { if(USART2_RX_STA & 0x8000) { // UART2收到完整数据 len = USART2_RX_STA & 0x3fff; for(t=0; t<len; t++) { USART1->DR = USART2_RX_BUF[t]; // 转发到UART1 while((USART1->SR & 0X40) == 0); // 等待发送完成 } USART2_RX_STA = 0; } else if(USART1_RX_STA & 0x8000) { // UART1收到完整数据 len = USART1_RX_STA & 0x3fff; for(t=0; t<len; t++) { USART2->DR = USART1_RX_BUF[t]; // 转发到UART2 while((USART2->SR & 0X40) == 0); // 等待发送完成 } USART1_RX_STA = 0; } else { delay_ms(10); // 短暂延时降低CPU占用 } }

4. 系统调试与性能优化

实际部署时,以下几个关键点需要特别注意:

4.1 波特率匹配问题

常见问题

  • FPGA和STM32的波特率设置不一致
  • 高波特率下的时钟误差累积

解决方案

  1. 双方使用相同的波特率(推荐115200或256000)
  2. 在FPGA端精确计算波特率分频系数:
parameter CLK_FREQ = 50000000; // 50MHz系统时钟 parameter UART_BPS = 115200; // 目标波特率 localparam BPS_CNT = CLK_FREQ/UART_BPS; // 分频系数
  1. 使用示波器测量实际波特率,检查起始位、停止位和数据的脉宽

4.2 FIFO缓冲区的深度优化

根据数据流量特点调整FIFO深度:

应用场景推荐FIFO深度考虑因素
低频控制指令16-32字节指令长度短,间隔时间长
中速数据采集64-128字节保证不会因处理延迟丢数据
高速数据流256字节以上应对突发数据

4.3 错误处理机制增强

在实际项目中,我们需要增加以下保护措施:

  1. 超时机制:当FIFO长时间不满时强制发送
  2. 数据校验:添加CRC校验字段
  3. 流量控制:硬件流控(RTS/CTS)或软件流控(XON/XOFF)
  4. 错误计数:统计通信错误率,超过阈值报警
// 增强型接收状态判断 if(USART_GetITStatus(USART1, USART_IT_RXNE)) { Res = USART_ReceiveData(USART1); if(USART_GetFlagStatus(USART1, USART_FLAG_PE|USART_FLAG_FE|USART_FLAG_NE)) { error_count++; // 统计错误 USART_ClearFlag(USART1, USART_FLAG_PE|USART_FLAG_FE|USART_FLAG_NE); } else { // 正常数据处理 } }

4.4 性能测试数据

在不同波特率下的实测性能对比:

波特率(bps)FPGA资源占用(LUT)最大稳定吞吐量STM32 CPU占用率
96001200.8KB/s<5%
11520015010KB/s15%
25600018022KB/s30%
92160022075KB/s65%

提示:当波特率超过500kbps时,建议使用DMA方式传输,降低CPU负载

5. 扩展应用与进阶设计

这个基础框架可以扩展出多种实际应用:

5.1 协议转换网关

在数据转发过程中添加协议转换逻辑:

  1. Modbus RTU转ASCII:工业设备互联
  2. 自定义二进制协议转JSON:物联网应用
  3. 数据加密/解密:安全通信
// 简单的异或加密示例 always @(posedge sys_clk) begin if(uart2_recv_en) begin encrypted_data <= uart2_recv_data ^ 8'hAA; // 异或加密 uart2_wrreq <= 1; end else begin uart2_wrreq <= 0; end end

5.2 多设备组网

通过UART扩展可以实现更复杂的网络拓扑:

[上位机] | [STM32] / \ [FPGA1] [FPGA2] | | [设备A] [设备B]

5.3 高速数据采集系统

结合FPGA的并行处理能力:

  1. FPGA负责高速ADC数据采集和预处理
  2. STM32负责数据打包、协议处理和网络传输
  3. 通过UART发送控制命令和接收状态信息
// STM32控制FPGA采集的指令格式 void send_acq_command(uint8_t mode, uint16_t sample_count) { uint8_t cmd[5] = {0xAA, mode, sample_count>>8, sample_count&0xFF, 0x55}; for(int i=0; i<5; i++) { USART1->DR = cmd[i]; while((USART1->SR & USART_FLAG_TXE) == 0); } }

在实现这些扩展功能时,FPGA端的Verilog代码需要相应增加状态机和数据处理逻辑,而STM32端则需要完善协议栈和错误处理机制。通过这种灵活的组合,可以构建出适应各种工业场景的可靠通信系统。

http://www.jsqmd.com/news/753999/

相关文章:

  • Vue3+java基于springboot框架的旅游网站
  • 2025届毕业生推荐的AI论文神器实测分析
  • 三月七小助手:星穹铁道玩家的终极时间管理神器
  • 如何快速免费转换TTF字体?ttf2woff工具让Web字体优化变得超简单!
  • Cowork Context Framework:构建项目级AI协作的持久化上下文系统
  • 【技术底稿 27】私有库全栈落地:闲置台式机变分站开发环境,Ubuntu22.04+Docker 私有镜像 + FTP 服务闭环落地
  • AI 未来趋势:智能体与职业教育
  • STAR-BENCH:音频4D智能评估基准详解
  • Vue3+java基于springboot框架的智慧养老云服务平台设计与开发
  • 低代码调试不是噱头——.NET 9 Roslyn注入式诊断器源码级剖析(附可落地的6类场景模板)
  • 视觉语言导航技术:SeeNav-Agent的创新与实践
  • 为什么93%的.NET开发者至今无法启用.NET 9边缘调试?3个被忽略的SDK版本锁死条件揭晓
  • 【限时开源】PHP 8.9 Fiber微服务骨架(含自动上下文传播、分布式TraceID、熔断日志埋点)
  • PartNeXt:百万级3D模型部件语义分割标注平台解析
  • 2026年4月新发布:揭秘长沙集训画室环境**榜及智博艺术培训学校的卓越之选 - 2026年企业推荐榜
  • 基于改进MPC的自动驾驶车辆轨迹跟踪粒子群算法【附代码】
  • DS4Windows终极指南:5分钟解决PS4手柄在Windows的兼容性问题
  • APKMirror应用:安卓用户的终极安全下载解决方案
  • LLM生成测试用例的价值重估与工程实践
  • 基于粒子滤波算法优化的锂离子电池荷电状态预测参数辨识【附代码】
  • MIDI文件只有几十KB?手把手教你用Python解析SMF格式,看看它到底存了些什么
  • 一个不靠谱的专利申请
  • 3步解锁老旧设备:让安卓4.x电视重获新生的终极方案
  • PACED框架:教育领域的知识蒸馏与自蒸馏技术解析
  • 暗黑破坏神2存档编辑新纪元:d2s-editor的5大革新功能深度解析
  • 完全掌握手柄映射:AntiMicroX让你的游戏操控更专业
  • ShotVerse:基于空间先验的多镜头视频生成技术解析
  • 基于多智能体与实时数据流的加密货币交易竞技场实战指南
  • Taotoken 模型广场功能助力开发者快速进行模型选型与对比
  • JoyCon手柄PC控制终极解决方案:JoyCon-Driver免费开源驱动完全指南