FPGA例程(7):UART串口接收程序--状态机的编写
《FPGA经典例程及解读--基于xilinx K325T平台》系列导航本专栏主要针对与想学习FPGA的同学从基础的点灯到之后的复杂功能实战例程从入门到进阶通过这些例程的学习和了解希望可以帮助你从一个FPGA小白进阶到FPGA中级阶段能够处理工作中大多数的FPGA使用场景。本篇是该系列的第七篇内容上一篇FPGA例程6UART串口通讯协议解析_fpga串口通信程序详解-CSDN博客下一篇关注我第一时间获取更新1 概述上一篇我们介绍了UART串口的协议本篇我们以UART串口接收程序为案例说明一下状态机的写法。2 状态机的结构简述状态机的详细理论介绍大家可以查看这一篇FPGA基础知识六状态机设计实战--从概念到可靠实现的完整指南-CSDN博客2.1 状态机三要素状态不是简单的计数器而是系统当前所处的工作状态转移条件状态变化的触发事件需要明确且无歧义输出在特定状态下产生的动作响应”2.2 状态机常见的的编码方式顺序二进制编码按00-01-10-11的顺序编码资源最省但多个状态位同时变化易产生毛刺格雷码编码按00-01-11-10的顺序编码状态转化时只有一位变化可靠性高功耗低但编码没有顺序二进制的方式直观热编码按0001-0010-0100-1000的方式编码时序性能好但需要更多的资源。2.3 三段式状态机经典模板如下// 第一段状态寄存器时序逻辑 always (posedge clk or negedge rst_n) begin if (!rst_n) current_state IDLE; else current_state next_state; end // 第二段下一状态逻辑组合逻辑 always (*) begin next_state current_state; // 默认保持当前状态 case (current_state) IDLE: begin if (start_signal) next_state START; end START: begin if (ready_signal) next_state WORK; else if (timeout) next_state IDLE; end WORK: begin if (done_signal) next_state DONE; end DONE: begin next_state IDLE; end default: next_state IDLE; endcase end // 第三段输出逻辑Moore风格 - 时序逻辑 always (posedge clk or negedge rst_n) begin if (!rst_n) begin output_bus b0; control_sig 1b0; end else begin // 默认输出值 output_bus b0; control_sig 1b0; case (current_state) // Moore输出只依赖于当前状态 START: control_sig 1b1; WORK: begin output_bus work_data; control_sig 1b1; end DONE: output_bus result_data; endcase end end为什么推荐这种结构时序清晰状态注册和输出生成都在时钟沿完成避免毛刺组合逻辑只用于状态转移判断综合友好工具能够很好地进行时序分析和优化3 UART串口接收模块程序设计参考FPGA例程6UART串口通讯协议解析_fpga串口通信程序详解-CSDN博客的串口接收协议。3.1 接收模块配置说明串口接收模块是个参数可配置模块BAUD_DIV依据波特率和采样时钟计算出的分频系数例如采样时钟50MHz我们希望波特率为115200bps那BAUD_DIV 50_000000 / 115200 434D_WORD_NUM数据位我们使用的是8位那这里就是8有的时候会用到6位或者7位修改这个值即可。3.2 接收模块状态转移流程说明接收模块状态机转移流程如下IDLE是空闲状态上电后进入IDLE状态如果rx为低我们认为接收到了串口的起始位进入状态STARTSTART接收完起始位即等待一个数据位的时间后进入数据接收状态RECEIVERECEIVE接收数据位我们设定为8位等待8位都接收完成之后进入STOPSTOP停止位之后再一次进入IDLE状态等待下一包数据即可。3.3 接收状态机设计我们使用三段式状态机完成接收模块的设计下面是第一段和第二段主要控制状态机的状态转移逻辑其中第一段是时序逻辑内部使用第二段是组合逻辑内部使用这里一定要注意在同一个always块中绝对不可以混用阻塞赋值和非阻塞赋值。// // 状态机定义 localparam IDLE 4d0; localparam START 4d1; localparam RECEIVE 4d2; localparam STOP 4d3; // --------- 控制状态机 reg [3:0] current_state,next_state; always (posedge clk or negedge rstn) begin if (!rstn) begin current_state IDLE; end else begin current_state next_state; end end // ----------- 主状态机 always (*) begin next_state current_state; case(current_state) IDLE:begin if (!rx_c1) begin next_state START; end end START:begin if(baud_clk)begin next_state RECEIVE; end end RECEIVE:begin if ((bit_cnt D_WORD_NUM-1 ) (baud_cnt BAUD_DIV-1)) begin // 已接收8位 next_state STOP; end end STOP:begin next_state IDLE; end default:begin next_state IDLE; end endcase end第三段为状态输入输出逻辑一般按照我们的功能分为独立的always块例如1接收数据位计数块always块内部仅产生bit_cnt当在RECEIVE状态下每采样一次bit_cnt1// ------------ 接收数据位计数 reg [3:0] bit_cnt; // 位计数器 always (posedge clk or negedge rstn) begin if (!rstn) begin bit_cnt 4d0; end else begin if (baud_clk) begin if (current_state START) begin bit_cnt 4d0; end else if (current_state RECEIVE) begin bit_cnt bit_cnt 4d1; end else begin bit_cnt bit_cnt; end end end end例如2数据包有效的使能信号当我们接收到rx的下降沿时认为接收到有效数据当我们接收够D_WORD_NUM的数据位后认为该包数据接收完成always块中仅产生start信号。// ----------- 生成 单帧接收使能信号 reg start; // start有效时 才接收数据 always (posedge clk or negedge rstn) begin if (!rstn) begin start 1b0; end else begin if (!rx_c0 rx_c1) begin start 1b1; end else if ((baud_cnt BAUD_DIV-1) (bit_cnt D_WORD_NUM-1)) begin start 1b0; end else begin start start; end end end最终的实现代码如下timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Company: // Engineer: // // Create Date: 2026/01/16 16:29:11 // Design Name: // Module Name: uart_rx // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // ////////////////////////////////////////////////////////////////////////////////// module uart_rx # ( parameter D_WORD_NUM 4d8, parameter BAUD_DIV 16d434 )( input clk, input rstn, input uart_rx_i, output [D_WORD_NUM-1:0] uart_rx_data_o, output uart_rx_done ); // parameter D_WORD_NUM 8; // parameter BAUD_DIV 16d434; // // 状态机定义 localparam IDLE 4d0; localparam START 4d1; localparam RECEIVE 4d2; localparam STOP 4d3; // --------- 控制状态机 reg [3:0] current_state,next_state; always (posedge clk or negedge rstn) begin if (!rstn) begin current_state IDLE; end else begin current_state next_state; end end // ----------- 主状态机 always (*) begin next_state current_state; case(current_state) IDLE:begin if (!rx_c1) begin next_state START; end end START:begin if(baud_clk)begin next_state RECEIVE; end end RECEIVE:begin if ((bit_cnt D_WORD_NUM-1 ) (baud_cnt BAUD_DIV-1)) begin // 已接收8位 next_state STOP; end end STOP:begin next_state IDLE; end default:begin next_state IDLE; end endcase end // // ------------- 输入信号同步 reg rx_c0,rx_c1; always (posedge clk) begin rx_c0 uart_rx_i; rx_c1 rx_c0; end // ------------ 接收数据位计数 reg [3:0] bit_cnt; // 位计数器 always (posedge clk or negedge rstn) begin if (!rstn) begin bit_cnt 4d0; end else begin if (baud_clk) begin if (current_state START) begin bit_cnt 4d0; end else if (current_state RECEIVE) begin bit_cnt bit_cnt 4d1; end else begin bit_cnt bit_cnt; end end end end // ----------- 生成 单帧接收使能信号 reg start; // start有效时 才接收数据 always (posedge clk or negedge rstn) begin if (!rstn) begin start 1b0; end else begin if (!rx_c0 rx_c1) begin start 1b1; end else if ((baud_cnt BAUD_DIV-1) (bit_cnt D_WORD_NUM-1)) begin start 1b0; end else begin start start; end end end // ------------ 波特率时钟生成 reg [15:0] baud_cnt; // 波特率计数器 always (posedge clk or negedge rstn) begin if (!rstn) begin baud_cnt 16d0; end else begin if (start) begin if (baud_cnt BAUD_DIV - 1) begin baud_cnt 16d0; end else begin baud_cnt baud_cnt 16d1; end end else begin baud_cnt 16d0; end end end wire baud_clk; // 波特率时钟 assign baud_clk (baud_cnt BAUD_DIV - 1) ? 1b1:1b0; // ------------ 串并转换 wire data_en ; assign data_en (baud_cnt BAUD_DIV 1)?1b1:1b0; reg [ D_WORD_NUM-1:0] rx_shift; // 接收移位寄存器 always (posedge clk or negedge rstn) begin if (!rstn) begin rx_shift 8d0; end else begin if (data_en (current_state RECEIVE)) begin rx_shift {rx_c1, rx_shift[ D_WORD_NUM-1:1]}; // 右移LSB在前 end else begin rx_shift rx_shift; end end end // -------------- 数据锁存 reg [7:0] data; always (posedge clk or negedge rstn) begin if (!rstn) begin data 8d0; end else begin if (current_state STOP) begin data rx_shift; // 保存接收的数据 end else begin data data; end end end assign uart_rx_data_o data; // ----------------- 生成一帧数据接收完成的标志 reg rx_done; always (posedge clk or negedge rstn) begin if (!rstn) begin rx_done 1b0; end else begin if (current_state STOP) begin rx_done 1b1; end else begin rx_done 1b0; end end end assign uart_rx_done rx_done; // endmodule4 总结本文以串口接收程序为案例讲述了状态机的编写方式我很推荐大家进行这样编写因为这种每个always块中仅产生一个信号的编写方式能让我们更明确这个信号的时序当出现问题或者bug的时候我们也仅需要对一个always块进行分析即可。那下一篇我们继续来说一说串口发送程序。

相关新闻

手把手教你写开机启动脚本,让Armbian设备一开机就亮灯

手把手教你写开机启动脚本,让Armbian设备一开机就亮灯

2026/7/10 9:57:17 阅读更多 →
Z-Image-Turbo优化指南:显存占用还能再降?

Z-Image-Turbo优化指南:显存占用还能再降?

2026/7/10 12:30:30 阅读更多 →
Qwen-Image-2512-ComfyUI使用全记录:小白也能变高手

Qwen-Image-2512-ComfyUI使用全记录:小白也能变高手

2026/7/3 19:29:42 阅读更多 →

最新新闻

Spark RDD 数据去重避坑指南:5个常见错误与高效编码模式

Spark RDD 数据去重避坑指南:5个常见错误与高效编码模式

Spark RDD 数据去重避坑指南:5个常见错误与高效编码模式在分布式计算领域,数据去重是一项基础但至关重要的操作。作为Spark核心抽象,RDD(弹性分布式数据集)提供了多种去重方法,但在实际应用中,开…

2026/7/13 5:25:37 阅读更多 →
Pandas多维聚合实战:银行级工业数据处理指南

Pandas多维聚合实战:银行级工业数据处理指南

1. 项目概述:为什么多维聚合不是“加个groupby”就能搞定的事我在银行风控部门做过三年数据管道开发,后来跳槽到一家头部支付机构做BI平台架构。这期间最常被业务方拍着桌子问的一句话是:“上个月华东区餐饮类商户的交易金额中位数、手续费波…

2026/7/13 5:23:35 阅读更多 →
Anaconda 2024.06 环境修复:Spyder 依赖冲突的 2 种诊断与降级方案

Anaconda 2024.06 环境修复:Spyder 依赖冲突的 2 种诊断与降级方案

Anaconda 2024.06 环境修复:Spyder 依赖冲突的深度诊断与多维度解决方案引言:当科学计算环境遭遇依赖地狱在数据科学和Python开发领域,Anaconda以其强大的包管理能力和开箱即用的科学计算环境而广受欢迎。然而,随着软件生态的快速…

2026/7/13 5:21:35 阅读更多 →
C语言学习环境搭建与核心技能深度解析:从指针到项目实战

C语言学习环境搭建与核心技能深度解析:从指针到项目实战

1. 项目概述:一份“古早”开发资源的现代解读最近在整理旧硬盘时,翻到了一个名为“C语言编程合集(内含VC2010注册密钥码)”的压缩包。这个标题对于很多老C/C程序员来说,瞬间就能勾起一段回忆——那是一个集成开发环境&…

2026/7/13 5:21:35 阅读更多 →
C++智能指针深度解析:从RAII原理到现代工程实践

C++智能指针深度解析:从RAII原理到现代工程实践

1. 项目概述:为什么我们需要智能指针?在C的世界里,指针是通往内存世界的钥匙,它赋予了我们直接操作内存的强大能力,但同时也是一把双刃剑。任何一个有经验的C开发者,都或多或少经历过因指针管理不当而引发的…

2026/7/13 5:21:35 阅读更多 →
DeepSeek输入格式兼容性危机(2024最新v3.2协议深度拆解)

DeepSeek输入格式兼容性危机(2024最新v3.2协议深度拆解)

更多请点击: https://codechina.net 第一章:DeepSeek输入格式兼容性危机的全景透视 DeepSeek系列大模型(尤其是DeepSeek-V2及后续版本)在实际部署中频繁遭遇输入格式适配失败问题,其根源并非模型架构缺陷,…

2026/7/13 5:19:35 阅读更多 →

日新闻

Palworld存档编辑完全掌握:从零开始实现游戏数据可视化修改

Palworld存档编辑完全掌握:从零开始实现游戏数据可视化修改

Palworld存档编辑完全掌握:从零开始实现游戏数据可视化修改 【免费下载链接】palworld-save-tools Tools for converting Palworld .sav files to JSON and back 项目地址: https://gitcode.com/gh_mirrors/pa/palworld-save-tools 你是否曾经想要调整Palwor…

2026/7/13 0:01:19 阅读更多 →
浦东旧模块回收哪家强?专业评测带你一探究竟

浦东旧模块回收哪家强?专业评测带你一探究竟

于科技迅猛飞速迭代的当下此刻, 旧模块的回收处置, 不但关联着资源的再度利用, 而且更牵扯到数据安全以及环保合规事宜。你是不是也正为那堆积得如同山峦般的旧模块而发愁? 是不是不清楚该怎样安全且高效地去处理它们? 别忧心烦恼, 就在今日, 我会以具备权威影响力的自媒体博…

2026/7/13 0:01:19 阅读更多 →
卡梅德生物技术快报|重组蛋白的表达和纯化:IMAC 金属螯合色谱全流程工艺手册|基质 - 配基 - 金属离子匹配与蛋白质分离纯化参数优化

卡梅德生物技术快报|重组蛋白的表达和纯化:IMAC 金属螯合色谱全流程工艺手册|基质 - 配基 - 金属离子匹配与蛋白质分离纯化参数优化

1 研究背景与现存技术痛点(提出问题)基因工程、蛋白质组学、生物制药研发流程中,蛋白质分离纯化是决定下游实验成败的关键环节。当前实验室常规蛋白质分离纯化工艺存在三类难以标准化的技术瓶颈:传统离子交换、分子筛层析无特异性…

2026/7/13 0:05:20 阅读更多 →

周新闻

互联网大厂 Java 求职面试:燕双非的搞笑回答与技术探讨

互联网大厂 Java 求职面试:燕双非的搞笑回答与技术探讨

互联网大厂 Java 求职面试:燕双非的搞笑回答与技术探讨 在一个阳光明媚的上午,互联网大厂的面试官坐在桌前,准备迎接他的面试候选人——燕双非,一个以搞笑和幽默著称的程序员。第一轮提问 面试官:燕双非,作…

2026/7/13 4:38:36 阅读更多 →
车载以太网PMA测试设备选型:示波器、VNA、信号源3类仪器关键参数与预算评估

车载以太网PMA测试设备选型:示波器、VNA、信号源3类仪器关键参数与预算评估

车载以太网PMA测试设备选型:示波器、VNA、信号源3类仪器关键参数与预算评估在智能驾驶和车联网技术快速发展的今天,车载以太网作为新一代车载网络的核心传输技术,其物理层性能直接决定了数据传输的可靠性和稳定性。1000BASE-T1作为当前主流的…

2026/7/13 4:38:38 阅读更多 →
VSCode EIDE 插件 2.0:APM32/STM32 项目迁移实战,5步完成Keil工程转换

VSCode EIDE 插件 2.0:APM32/STM32 项目迁移实战,5步完成Keil工程转换

VSCode EIDE 插件 2.0:APM32/STM32 项目迁移实战指南嵌入式开发领域正经历一场工具链的静默革命。当传统Keil用户首次打开VSCode的扩展市场搜索EIDE时,往往会惊讶于这个看似简单的插件竟能重构十余年的开发习惯。本文将揭示如何用五个精准步骤&#xff0…

2026/7/13 4:38:40 阅读更多 →

月新闻