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

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;

在该子模块中的参数可分为三个部分:

  1. 系统时钟和复位。
  2. 由其它模块发送来的使能信号和待发送的数据。
  3. 本模块正在执行功能时的标志信号,本模块的数据接口,相关计数器,以及UART串口TX的物理IO口。

根据这些参数,可以大致知道串口发送1个bit数据的流程:

  1. 首先模块通过uart_en收到发送使能信号,则首先拉高tx_flag信号,同时uart_tx_busy会接受tx_flag的值。
  2. 当uart_en拉高后,en_flag会获取一个脉冲,当检测到这个脉冲后,则说明串口进入发送状态。
  3. 进入发送过程后要做三件事:寄存待发送的数据,启动系统时钟计数器,启动发送数据计数器。
  4. 发送数据计数器开始计数,直到计数值等于9时会自动清零,计数器一次计数循环即意味着一个8bit的数据发送完。
  5. 在发送过程中,[7:0]tx_data要将8个bit的数据从低位到高位传给[1:0]uart_txd,首先发送一个低电平作为起始位,然后发送tx_data的8个bit,最后发送一个高电平表示停止位。
  6. 经过这么一个过程,一个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部分组成&#xff0c;起始位、数据位、奇偶校验位和停止位&#xff0c;如图所示。其中&#xff0c;起始位标志着一帧数据的开始&#xff0c;停止位标志着一帧数据的结束&#xff0c;数据位是一帧数据中的有效数据。 通常用…...

Kotlin重点理解安全性

目录 一 Kotlin安全性1.1 可空类型1.2 安全调用运算符1.3 Elvis 运算符1.4 非空断言运算符1.5 安全类型转换1.6 延迟初始化 一 Kotlin安全性 Kotlin 在设计时采用了一系列策略&#xff0c;旨在尽可能地减少空指针异常&#xff08;NullPointerException&#xff09;的出现。空指…...

基于Java+SpringBoot+SpringCloud+Vue的智慧养老平台设计与实现(源码+LW+部署文档等)

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…...

Spring中的全局异常处理

在项目中通常会定义各种异常&#xff0c;比如断言异常、业务异常&#xff0c;甚至调用外部接口报的异常需要转成自己系统中的异常类型&#xff0c;这时就需要将各种异常类型进行统一的处理&#xff0c;返回统一的数据结构。在spring中提供了这样全局统一处理异常的注解Controll…...

【安全测试】Web应用安全之XSS跨站脚本攻击漏洞

目录 前言 XSS概念及分类 反射型XSS(非持久性XSS) 存储型XSS(持久型XSS) 如何测试XSS漏洞 方法一&#xff1a; 方法二&#xff1a; XSS漏洞修复 原则&#xff1a;不相信客户输入的数据 处理建议 资料获取方法 前言 以前都只是在各类文档中见到过XSS&#xff0c;也进…...

LeNet卷积神经网络-笔记

LeNet卷积神经网络-笔记 手写分析LeNet网三卷积运算和两池化加两全连接层计算分析 修正上图中H,W的计算公式为下面格式 基于paddle飞桨框架构建测试代码 #输出结果为&#xff1a; #[validation] accuracy/loss: 0.9530/0.1516 #这里准确率为95.3% #通过运行结果可以看出&am…...

使用XMLHttpRequest实现文件异步下载

1、问题描述 我想通过异步的方式实现下载文化&#xff0c;请求为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&#xff08;“Project Lombok”&#xff09;是一…...

springBean生命周期解析

本文基于Spring5.3.7 参考&#xff1a; kykangyuky Spring中bean的生命周期 阿斌Java之路 SpringBean的生命周期&#xff0c; 杨开振 JavaEE互联网轻量级框架整合开发 黑马程序员 JavaEE企业级应用开发教程 马士兵 Spring源码讲解 一. SpringBean生命周期流程图 二. 示例代码 …...

人工智能轨道交通行业周刊-第54期(2023.7.31-8.6)

本期关键词&#xff1a;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 是一个工具&#xff0c;旨在帮助定义和 共享多容器…...

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&#xff1a; Aggregate global unicast address&#xff0c;前3位是001&#xff0c;即2000::/3&#xff0c;目前IANA已经将一部分可聚合全球单播进行了专门使用&#xff0c;如&#xff1a;2001::/16用于IPV6互联网&#xff0c;…...

Nginx安装部署

什么是Nginx? Nginx&#xff08;发音同engine x&#xff09;是一款由俄罗斯程序员Igor Sysoev所开发轻量级的网页服务器、反向代 理服务器以及电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器。 Nginx 因具有高并发&#xff08;特別是静态资源&#xff09;、 占用系统资…...

物联网|按键实验---学习I/O的输入及中断的编程|读取I/O的输入信号|中断的编程方法|轮询实现按键捕获实验-学习笔记(13)

文章目录 实验目的了解擒键的工作原理及电原理图 STM32F407中如何读取I/O的输入信号STM32F407对中断的编程方法通过轮询实现按键捕获实验如何利用已有内工程创建新工程通过轮询实现按键捕获代码实现及分析1 代码的流程分析2 代码的实现 Tips:下载错误的解决 实验目的 了解擒键…...

Hadoop-HDFS的Namenode及Datanode(参考Hadoop官网)

HDFS有什么特点&#xff0c;被设计做什么 Hadoop分布式文件系统(HDFS)被设计成适合运行在通用硬件(commodity hardware)上的分布式文件系统。有一下几个特点&#xff1a; HDFS是一个高度容错性的系统&#xff0c;具有高容错、高可靠性、高扩展性的特点&#xff0c;适合部…...

C:通过alarm发送信号

可以通过alarm定时发送SIGALRM信号&#xff1a; #include <unistd.h> unsigned int alarm(unsigned int seconds); alarm()函数用来在seconds秒之后安排发送一个SIGALRM信号&#xff0c;如果seconds为0&#xff0c;将取消所有已设置的闹钟请求。alarm()函数的返回值是以前…...

如何将 dubbo filter 拦截器原理运用到日志拦截器中?

业务背景 我们希望可以在使用日志拦截器时&#xff0c;定义属于自己的拦截器方法。 实现的方式有很多种&#xff0c;我们分别来看一下。 拓展阅读 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)指针数组的实质是一个数组&#xff0c;这个数组中存…...

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...

生成xcframework

打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式&#xff0c;可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...

[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解

突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 ​安全措施依赖问题​ GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

OpenLayers 可视化之热力图

注&#xff1a;当前使用的是 ol 5.3.0 版本&#xff0c;天地图使用的key请到天地图官网申请&#xff0c;并替换为自己的key 热力图&#xff08;Heatmap&#xff09;又叫热点图&#xff0c;是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...

TDengine 快速体验(Docker 镜像方式)

简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能&#xff0c;本节首先介绍如何通过 Docker 快速体验 TDengine&#xff0c;然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker&#xff0c;请使用 安装包的方式快…...

Unity3D中Gfx.WaitForPresent优化方案

前言 在Unity中&#xff0c;Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染&#xff08;即CPU被阻塞&#xff09;&#xff0c;这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案&#xff1a; 对惹&#xff0c;这里有一个游戏开发交流小组&…...

cf2117E

原题链接&#xff1a;https://codeforces.com/contest/2117/problem/E 题目背景&#xff1a; 给定两个数组a,b&#xff0c;可以执行多次以下操作&#xff1a;选择 i (1 < i < n - 1)&#xff0c;并设置 或&#xff0c;也可以在执行上述操作前执行一次删除任意 和 。求…...

在Ubuntu中设置开机自动运行(sudo)指令的指南

在Ubuntu系统中&#xff0c;有时需要在系统启动时自动执行某些命令&#xff0c;特别是需要 sudo权限的指令。为了实现这一功能&#xff0c;可以使用多种方法&#xff0c;包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法&#xff0c;并提供…...

关键领域软件测试的突围之路:如何破解安全与效率的平衡难题

在数字化浪潮席卷全球的今天&#xff0c;软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件&#xff0c;这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下&#xff0c;实现高效测试与快速迭代&#xff1f;这一命题正考验着…...

沙箱虚拟化技术虚拟机容器之间的关系详解

问题 沙箱、虚拟化、容器三者分开一一介绍的话我知道他们各自都是什么东西&#xff0c;但是如果把三者放在一起&#xff0c;它们之间到底什么关系&#xff1f;又有什么联系呢&#xff1f;我不是很明白&#xff01;&#xff01;&#xff01; 就比如说&#xff1a; 沙箱&#…...