FPGA初步学习之串口发送模块【单字节和字符串的发送】
串口相关简介
UART 在发送或接收过程中的一帧数据由4部分组成,起始位、数据位、奇偶校验位和停止位,如图所示。其中,起始位标志着一帧数据的开始,停止位标志着一帧数据的结束,数据位是一帧数据中的有效数据。

通常用的串口数据帧格式是:8位数据位,无校验位,1位停止位。
所以一帧数据有10个bit:1bit起始位,8bit数据位,1bit停止位。
关于串口波特率
串口波特率是指串口通信的速率,它表示每秒传输二进制数据的位数,单位是bps(位 /秒),常用的波特率有9600、19200、38400、57600以及115200等。
FPGA如何通过系统时钟来得到串口波特率呢?
已知串口时钟为50Mhz = 50000000hz,生成串口波特率为9600。
那么我们需要在FPGA里构建两个计数器。一个系统时钟计数器用来计数系统时钟周期数,一个串口时钟计数器,用来计数对应串口波特率的时钟周期数。
系统时钟:sys_clk,系统时钟计数值:sys_cnt。
串口时钟计数值:tx_cnt。
当sys_cnt = 50000000 / 9600时,tx_cnt计数一次,相当于串口时钟的一个周期。
数据是怎么发送出去的呢?
由上一个问题的解答已经得到了【串口时钟】这么一个东西,那么发送数据都是在该时钟下进行发送。
按照前面所提到的数据格式【1bit起始位,8bit数据位,1bit停止位】,那么就可以知道,发送一个8位的数据,需要在10个串口时钟周期下,将10bit的数据对应好时序一个个发出去。
数据是怎么接收的呢?
类似于上个问题,只需要按照时序,检测起始位,接收数据,再检测停止位即可。
那么这样是否就能够搭建好串口模块呢?
答案明显是不能的,FPGA并不知道什么时候该发数据,什么时候该收数据,什么时候进入发送状态,什么时候脱离发送状态。这时候还要添加其他信号来打辅助。比如添加发送使能信号来确定什么时候发数据,添加状态信号来表示串口是空闲还是忙碌等一系列的状态。
串口发送子模块的搭建
串口发送子模块相关参数
module uart_send(input sys_clk, //系统时钟input sys_rst_n, //系统复位,低电平有效//由其他模块输入input uart_en, //发送使能信号input [7:0] uart_din, //待发送数据//输出给其他模块output uart_tx_busy, //发送忙状态标志output reg tx_flag, //发送过程标志信号output reg [ 7:0] tx_data, //寄存发送数据output reg [ 3:0] tx_cnt, //发送数据计数器output reg uart_txd //UART发送端口,即tx引脚);//parameter defineparameter CLK_FREQ = 50000000; //系统时钟频率parameter UART_BPS = 9600; //串口波特率//为得到指定波特率,对系统时钟计数BPS_CNT次localparam BPS_CNT = CLK_FREQ/UART_BPS; //reg definereg uart_en_d0; reg uart_en_d1; reg [15:0] clk_cnt; //系统时钟计数器//wire definewire en_flag;
在该子模块中的参数可分为三个部分:
- 系统时钟和复位。
- 由其它模块发送来的使能信号和待发送的数据。
- 本模块正在执行功能时的标志信号,本模块的数据接口,相关计数器,以及UART串口TX的物理IO口。
根据这些参数,可以大致知道串口发送1个bit数据的流程:
- 首先模块通过uart_en收到发送使能信号,则首先拉高tx_flag信号,同时uart_tx_busy会接受tx_flag的值。
- 当uart_en拉高后,en_flag会获取一个脉冲,当检测到这个脉冲后,则说明串口进入发送状态。
- 进入发送过程后要做三件事:寄存待发送的数据,启动系统时钟计数器,启动发送数据计数器。
- 发送数据计数器开始计数,直到计数值等于9时会自动清零,计数器一次计数循环即意味着一个8bit的数据发送完。
- 在发送过程中,[7:0]tx_data要将8个bit的数据从低位到高位传给[1:0]uart_txd,首先发送一个低电平作为起始位,然后发送tx_data的8个bit,最后发送一个高电平表示停止位。
- 经过这么一个过程,一个8位数据就通过FPGA串口的TX引脚发送出去了。
发送子模块中的逻辑块
获取使能信号脉冲
//捕获uart_en上升沿,得到一个时钟周期的脉冲信号
assign en_flag = (~uart_en_d1) & uart_en_d0;//对发送使能信号uart_en延迟两个时钟周期
always @(posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) beginuart_en_d0 <= 1'b0; uart_en_d1 <= 1'b0;end else begin uart_en_d0 <= uart_en; uart_en_d1 <= uart_en_d0; end
end
通过两次寄存和取反与等操作,将高电平信号转化为一个脉冲信号,作为使能信号。
进入发送过程和退出发送过程
//当脉冲信号en_flag到达时,寄存待发送的数据,并进入发送过程
always @(posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) begin tx_flag <= 1'b0;tx_data <= 8'd0;end else if (en_flag) begin //检测到发送使能上升沿 tx_flag <= 1'b1; //进入发送过程,标志位tx_flag拉高tx_data <= uart_din; //寄存待发送的数据end//计数到停止位结束时,停止发送过程(并提前1/16个串口时钟)else if ((tx_cnt == 4'd9) && (clk_cnt == BPS_CNT - (BPS_CNT/16))) begin tx_flag <= 1'b0; //发送过程结束,标志位tx_flag拉低tx_data <= 8'd0;endelse begintx_flag <= tx_flag;tx_data <= tx_data;end
end
两个计数器
计数器的作用是保证数据按照特定波特率被发送出去
//进入发送过程后,启动系统时钟计数器
always @(posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) clk_cnt <= 16'd0; else if (tx_flag) begin //如果处于发送过程if (clk_cnt < BPS_CNT - 1)clk_cnt <= clk_cnt + 1'b1;elseclk_cnt <= 16'd0; //系统时钟计数一个波特率周期后清零endelseclk_cnt <= 16'd0; //发送过程结束
end//进入发送过程后,启动发送数据计数器
always @(posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) tx_cnt <= 4'd0;else if (tx_flag) begin //处于发送过程if (clk_cnt == BPS_CNT - 1) //对系统时钟计数达一个波特率周期tx_cnt <= tx_cnt + 1'b1; //此时发送数据计数器加1elsetx_cnt <= tx_cnt; endelse tx_cnt <= 4'd0; //发送过程结束
end
将8bit数据按bit拆分,并按顺序赋值给tx端口
//根据发送数据计数器来给uart发送端口赋值
always @(posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) uart_txd <= 1'b1; else if (tx_flag)case(tx_cnt)4'd0: uart_txd <= 1'b0; //起始位 4'd1: uart_txd <= tx_data[0]; //数据位最低位4'd2: uart_txd <= tx_data[1];4'd3: uart_txd <= tx_data[2];4'd4: uart_txd <= tx_data[3];4'd5: uart_txd <= tx_data[4];4'd6: uart_txd <= tx_data[5];4'd7: uart_txd <= tx_data[6];4'd8: uart_txd <= tx_data[7]; //数据位最高位4'd9: uart_txd <= 1'b1; //停止位default: ;endcaseelse uart_txd <= 1'b1; //空闲时发送端口为高电平
end
发送字符串模块
子模块写好后,可以再做一个顶层模块,用来发送字符串。
发送字符串的思路:
当满足某个条件后,使能串口发送信号,此时串口就开始发送数据了,当在串口发送数据的时候,串口就处在发送忙状态,此时我们就可以更新一次待发送的数据。为什么可以在发送的时候更新待发送数据呢?因为在进入发送过程后,数据还没发送前的时间里,待发送的数据就已经寄存在了子模块里的一个寄存器里了。这样就节约了数据更新的时间了。
相关代码如下:
module uart_loopback_top(input sys_clk, //外部50M时钟input sys_rst_n, //外部复位信号,低有效output uart_txd //UART发送端口);//parameter define
parameter CLK_FREQ = 50000000; //定义系统时钟频率
parameter UART_BPS = 115200; //定义串口波特率//wire define
reg [7:0] uart_send_data; //UART发送数据
wire uart_tx_busy; //UART发送忙状态标志
reg uart_send_en; //UART发送使能
reg [31:0] data_cnt;reg send_d0;
reg send_d1;
wire send_en;
reg string_end; //停止字符串发送reg [ 31:0] Data_Count; //字符计数器
parameter [31:0] Data_Len=32'd10; //字符串的长度
reg [7:0] arry [Data_Len-1:0]; //定义要发送的字符串//初始化字符串
initial beginarry[0] = "H";arry[1] = "e";arry[2] = "l"; arry[3] = "l"; arry[4] = "o"; arry[5] = "W"; arry[6] = "o"; arry[7] = "r"; arry[8] = "l";arry[9] = "d";
end//使能数据更新位,获得串口数据更新的脉冲
assign send_en = (~send_d1) & send_d0;//数据更新标志,寄存两次uart_send_en的数据,为构成发送使能脉冲做准备
always @(posedge sys_clk or negedge sys_rst_n) beginif (!sys_rst_n) beginsend_d0 <= 1'b0;send_d1 <= 1'b0;end else begin send_d0 <= uart_send_en;send_d1 <= send_d0; end
end//串口发送字符串
always @(posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) beginuart_send_en <= 1'd0; //初始化串口发送使能data_cnt <= 32'd0; //初始化数据计数器uart_send_data <= arry[0]; //初始化首字符string_end <= 1'd0; //控制字符串发送结束end else begin//当串口处于空闲状态,且数据计数器小于10的时候//条件:data_cnt < Data_Len 不要也可以//条件必须满足TX空闲,字符串发送结束标志位0,可在此基础上添加其它条件if((~uart_tx_busy) && (data_cnt < Data_Len) && (string_end == 1'b0)) begin //使能串口发送使能寄存器uart_send_en <= 1'b1; end//当数据计数器大于等于字符串长度时if(data_cnt == Data_Len) beginstring_end <= 1'b1;uart_send_en <= 1'b0; //失能串口发送使能,停止串口发送数据data_cnt <= 32'b0;end//如果数据发送使能脉冲到来else if((send_en) && (data_cnt < Data_Len)) begin data_cnt <= data_cnt + 32'b1; //则数据计数器加一end//如果串口有数据在发送,并且结束位没有拉高else if((uart_tx_busy) && (string_end == 1'b0)) begin//则失能串口发送使能,为下一个数据的发送做准备uart_send_en <= 1'b0; uart_send_data <= arry[data_cnt];endend
end//串口发送模块
uart_send #( .CLK_FREQ (CLK_FREQ), //设置系统时钟频率.UART_BPS (UART_BPS)) //设置串口发送波特率
u_uart_send( .sys_clk (sys_clk),.sys_rst_n (sys_rst_n),.uart_en (uart_send_en),.uart_din (uart_send_data),.uart_tx_busy (uart_tx_busy),.uart_txd (uart_txd));//例化ILA IP核
//ila_0 your_instance_name (
// .clk(sys_clk), // input wire clk
//
// .probe0(uart_send_en), // input wire [0:0] probe0
// .probe1(data_cnt), // input wire [7:0] probe1
// .probe2(uart_tx_busy), // input wire [0:0] probe2
// .probe3(send_d0),
// .probe4(send_d1),
// .probe5(send_en),
// .probe6(uart_send_data),
// .probe7(string_end)
//);endmodule
进一步的改进
如果要发送多个字符串,可以在更新字符串数据时加入状态机,进行不同字符串的转换。
相关文章:
FPGA初步学习之串口发送模块【单字节和字符串的发送】
串口相关简介 UART 在发送或接收过程中的一帧数据由4部分组成,起始位、数据位、奇偶校验位和停止位,如图所示。其中,起始位标志着一帧数据的开始,停止位标志着一帧数据的结束,数据位是一帧数据中的有效数据。 通常用…...
Kotlin重点理解安全性
目录 一 Kotlin安全性1.1 可空类型1.2 安全调用运算符1.3 Elvis 运算符1.4 非空断言运算符1.5 安全类型转换1.6 延迟初始化 一 Kotlin安全性 Kotlin 在设计时采用了一系列策略,旨在尽可能地减少空指针异常(NullPointerException)的出现。空指…...
基于Java+SpringBoot+SpringCloud+Vue的智慧养老平台设计与实现(源码+LW+部署文档等)
博主介绍: 大家好,我是一名在Java圈混迹十余年的程序员,精通Java编程语言,同时也熟练掌握微信小程序、Python和Android等技术,能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…...
Spring中的全局异常处理
在项目中通常会定义各种异常,比如断言异常、业务异常,甚至调用外部接口报的异常需要转成自己系统中的异常类型,这时就需要将各种异常类型进行统一的处理,返回统一的数据结构。在spring中提供了这样全局统一处理异常的注解Controll…...
【安全测试】Web应用安全之XSS跨站脚本攻击漏洞
目录 前言 XSS概念及分类 反射型XSS(非持久性XSS) 存储型XSS(持久型XSS) 如何测试XSS漏洞 方法一: 方法二: XSS漏洞修复 原则:不相信客户输入的数据 处理建议 资料获取方法 前言 以前都只是在各类文档中见到过XSS,也进…...
LeNet卷积神经网络-笔记
LeNet卷积神经网络-笔记 手写分析LeNet网三卷积运算和两池化加两全连接层计算分析 修正上图中H,W的计算公式为下面格式 基于paddle飞桨框架构建测试代码 #输出结果为: #[validation] accuracy/loss: 0.9530/0.1516 #这里准确率为95.3% #通过运行结果可以看出&am…...
使用XMLHttpRequest实现文件异步下载
1、问题描述 我想通过异步的方式实现下载文化,请求为post请求。一开始我打算用ajax。 $.ajax({type:post,contentType:application/json,url:http://xxx/downloadExcel,data:{data:JSON.stringify(<%oJsonResponse.JSONoutput()%>)},}).success(function(dat…...
Lombok 的安装与使用
文章目录 一、什么是 Lombok1.1 Lombok 的概念1.2 为什么使用 Lombok1.3 Lombok 的相关注解 二、Lombok 的安装2.1 引入依赖2.2 安装插件 三、Lombok 的使用案例四、Lombok 的原理 一、什么是 Lombok 1.1 Lombok 的概念 Lombok(“Project Lombok”)是一…...
springBean生命周期解析
本文基于Spring5.3.7 参考: kykangyuky Spring中bean的生命周期 阿斌Java之路 SpringBean的生命周期, 杨开振 JavaEE互联网轻量级框架整合开发 黑马程序员 JavaEE企业级应用开发教程 马士兵 Spring源码讲解 一. SpringBean生命周期流程图 二. 示例代码 …...
人工智能轨道交通行业周刊-第54期(2023.7.31-8.6)
本期关键词:BIM智能运维、铁水联运、编组站美容、鸿蒙4.0、LK-99完全悬浮 1 整理涉及公众号名单 1.1 行业类 RT轨道交通人民铁道世界轨道交通资讯网铁路信号技术交流北京铁路轨道交通网上榜铁路视点ITS World轨道交通联盟VSTR铁路与城市轨道交通RailMetro轨道世界…...
Docker Compose 使用方法
目录 前言 安装 Docker Compose Ubuntu 安装与更新 Red Hat 安装与更新 验证是否安装 Docker Compose 创建 docker-compose.yml 文件 创建一个MySQL 与 tomcat 示例 使用Docker Compose启动服务 前言 Docker Compose 是一个工具,旨在帮助定义和 共享多容器…...
HTML 初
前言 HTML的基本骨架 HTML基本骨架是构建网页的最基本的结果。 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0">…...
IPv6地址分类,EUI-64转换规则
1、可聚合的单全球单播地址Global Unique Address: Aggregate global unicast address,前3位是001,即2000::/3,目前IANA已经将一部分可聚合全球单播进行了专门使用,如:2001::/16用于IPV6互联网,…...
Nginx安装部署
什么是Nginx? Nginx(发音同engine x)是一款由俄罗斯程序员Igor Sysoev所开发轻量级的网页服务器、反向代 理服务器以及电子邮件(IMAP/POP3)代理服务器。 Nginx 因具有高并发(特別是静态资源)、 占用系统资…...
物联网|按键实验---学习I/O的输入及中断的编程|读取I/O的输入信号|中断的编程方法|轮询实现按键捕获实验-学习笔记(13)
文章目录 实验目的了解擒键的工作原理及电原理图 STM32F407中如何读取I/O的输入信号STM32F407对中断的编程方法通过轮询实现按键捕获实验如何利用已有内工程创建新工程通过轮询实现按键捕获代码实现及分析1 代码的流程分析2 代码的实现 Tips:下载错误的解决 实验目的 了解擒键…...
Hadoop-HDFS的Namenode及Datanode(参考Hadoop官网)
HDFS有什么特点,被设计做什么 Hadoop分布式文件系统(HDFS)被设计成适合运行在通用硬件(commodity hardware)上的分布式文件系统。有一下几个特点: HDFS是一个高度容错性的系统,具有高容错、高可靠性、高扩展性的特点,适合部…...
C:通过alarm发送信号
可以通过alarm定时发送SIGALRM信号: #include <unistd.h> unsigned int alarm(unsigned int seconds); alarm()函数用来在seconds秒之后安排发送一个SIGALRM信号,如果seconds为0,将取消所有已设置的闹钟请求。alarm()函数的返回值是以前…...
如何将 dubbo filter 拦截器原理运用到日志拦截器中?
业务背景 我们希望可以在使用日志拦截器时,定义属于自己的拦截器方法。 实现的方式有很多种,我们分别来看一下。 拓展阅读 java 注解结合 spring aop 实现自动输出日志 java 注解结合 spring aop 实现日志traceId唯一标识 java 注解结合 spring ao…...
【java】【maven】【基础】MAVEN安装配置介绍
目录 1 下载 2 安装-windows为例 3 配置环境变量 3.1 JAVA_HOME 3.2 MAVEN_HOME 3.3 PATH 3.4 验证 4 MAVEN基础概念 4.1 仓库概念 4.2 坐标概念 4.2.1 打开网址 4.2.2 输入搜索内容junit 4.2.3 找到对应API名称点击 4.2.4 点击对应版本 4.2.5 复制MAVEN坐标 4.3 配置…...
【C语言进阶】指针的高级应用(下)
文章目录 一、指针数组与数组指针1.1 指针数组与数组指针的表达式 二、函数指针2.1 函数指针的书写方式 三、二重指针与一重指针3.1 二重指针的本质3.2 二重指针的用法3.3 二重指针与数组指针 总结 一、指针数组与数组指针 (1)指针数组的实质是一个数组,这个数组中存…...
Python 使用 `raise` 报错抛出异常显示 Unicode 码如何解决
在 Python 开发中,我们经常使用 raise 抛出异常来处理错误情况。但有时候,异常信息中的中文或其他非 ASCII 字符会被显示为 Unicode 转义序列(如 \u6b63\u6587),而不是直接显示中文(如“正文”)…...
实战指南:基于快马平台与yolov11快速开发货架商品检测系统
今天想和大家分享一个最近用yolov11实现的零售商品检测项目,整个过程在InsCode(快马)平台上完成得特别顺利。这个系统可以自动识别超市货架上的商品,特别适合库存管理或者智能结算场景。 项目背景与需求分析 超市货架商品识别看似简单,实际会…...
收藏级|2026大模型全景解析(小白/程序员必看):技术迭代+梯队格局+产业链+落地案例
2026年,全球AI产业正式迈入“寡头固化垂直突围”的成熟发展阶段,大模型技术彻底告别此前的参数竞赛,转向核心能力深耕与商业化落地。对于刚入门大模型的小白、深耕技术的程序员而言,本文将系统梳理国内外顶尖大模型的迭代成果与梯…...
多 Agent 协作架构:Agent 之间如何通信、协调和分工
多 Agent 协作架构:Agent 之间如何通信、协调和分工 我在字节跳动 OpenViking 分析之后,有一件事一直在脑子里转:多 Agent 的协作到底难在哪里?不是难在"写代码",而是难在把一堆各自为政的 Agent 变成一个真…...
OpenClaw成本优化方案:Qwen3.5-9B-AWQ-4bit自部署省下80%Token
OpenClaw成本优化方案:Qwen3.5-9B-AWQ-4bit自部署省下80%Token 1. 为什么需要关注OpenClaw的Token消耗 第一次用OpenClaw完成图片处理任务时,我的信用卡账单给我上了深刻的一课——单月API调用费用直接突破2000元。这个数字让我意识到:如果…...
JavaSE从0到1-DAY7-内部类(i)
Java 内部类学习笔记(i) 一、为什么会有内部类? 核心作用 内部类是写在外部类里面的类,它的主要作用是: 逻辑封装:把只属于外部类的辅助功能封装起来,不暴露给外界访问权限:内部类可…...
拒绝“一眼AI”!硬核跑通Gemini去AIGC工作流:实测3组调优指令+3款工具,把99%硬生生打回10%
视角重构,打破“平铺直叙”的机械感 AI生成的最大特征是“正确但平庸的上帝视角”。要ai降ai,第一步不是改词,而是强行植入一个具有批判性的“人类观察者”视角,迫使模型重组叙事逻辑。 核心原理:通过引入“辩证法”…...
社交媒体数据采集难题?MediaCrawler让复杂任务变简单
社交媒体数据采集难题?MediaCrawler让复杂任务变简单 【免费下载链接】MediaCrawler-new 项目地址: https://gitcode.com/GitHub_Trending/me/MediaCrawler-new 在信息爆炸的数字时代,企业、研究机构和内容创作者常常需要从各大社交平台获取有价…...
终极指南:QLVideo让macOS视频预览支持200+格式,Finder管理效率提升300%
终极指南:QLVideo让macOS视频预览支持200格式,Finder管理效率提升300% 【免费下载链接】QuickLookVideo This package allows macOS Finder to display thumbnails, static QuickLook previews, cover art and metadata for most types of video files. …...
WMIC命令行高效卸载Windows软件:从入门到精通
1. 为什么选择WMIC卸载软件? 每次电脑卡顿的时候,打开C盘一看,总会被各种不明所以的软件占满空间。传统的卸载方式要经过"控制面板-程序和功能-找到目标-点击卸载"的繁琐流程,而WMIC只需要几行命令就能搞定。我在帮同事…...
