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

FPGA实战:用PL端串口发送Hello world

文章目录

    • 简介
    • 串口发送
    • 生成波形
    • 顶层代码
    • 实现

简介

UART,即universal asynchronous receiver-transmitter,异步收发传输器,特点是有两路信号线,一路负责信号发送,一路负责信号接收,二者互不干扰。

UART的每帧数据共有10位,由4部分组成:

  • 起始位:当UART传输数据时,传输线会从高电平被拉到低电平并保持1个数据位,此即起始位。
  • 数据位:即数据帧实际传输的数据。
  • 奇偶校验位:统计数据中1的个数。若有奇数个1,则校验位是1,否则校验位是0。校验位可以不设置。
  • 停止位:与起始位相反,当UART传完一帧后,传输线会跃迁到高电平,并保持1个数据位,此即停止位。

UART在传输信号时,每一位的时长通过波特率来调控,波特率的单位是bps,即位/秒。

领航者开发板有一个type-c接口,实际上是USB转UART接口,在连接到电脑之后,可以通过串口调试工具查看到。

考虑到我们无法确认FPGA是否收到了上位机发送的数据,所以本文的目的,是测试FPGA的串口发送功能,具体来说,就是当按下复位键的时候,让FPGA向上位机发送“hello world"。

串口发送

在实现串口发送模块时,需要注意以下两点

  • UART协议发送的内容不全都是数据,而要包含起始位和停止位。所以,在数据发送过程中,需要以10为周期,确保起始位和截止位发送正确。
  • 串口传输过程中,FPGA和上位机需要有着同样的波特率。上位机可以直接设置波特率,而在FPGA中,需要用时钟频率来完成波特率的设置过程。设时钟频率为fff,波特率为fbf_bfb,则每次电平拉高需要经历ffb\frac{f}{f_b}fbf个时钟频率。

这两个问题,要求我们创建两个计数器,分别记录波数和信号数。其中,信号数为10,需要4位;波数实在是比较多,暂且设为16位。

此外,串口发送数据的前提是有数据,所以要在输入口提供一个数据寄存器,同时还需要一个说明数据有效的标志位。

下面即为串口发送代码

`timescale 1ns / 1psmodule uart_tx(input               clk  ,input               rst_n,input               tx_en,input     [7:0]     data ,output  reg         txd  ,);parameter CLK_FREQ = 50000000;
parameter UART_BPS = 115200  ;
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS;reg  [7:0]  tx_data_t;
reg  [3:0]  tx_cnt   ;
reg  [15:0] baud_cnt ;
reg         busy     ; always @(posedge clk or negedge rst_n) beginif(!rst_n) begintx_data_t <= 8'b0;busy <= 1'b0;endelse if(tx_en) begintx_data_t <= data;busy <= 1'b1;endelse if(tx_cnt == 4'd9 && baud_cnt == (BAUD_CNT_MAX-16'b1)) begintx_data_t <= 8'b0;busy <= 1'b0;endelse begintx_data_t <= tx_data_t;busy <= busy;end
endalways @(posedge clk or negedge rst_n) beginif(!rst_n) baud_cnt <= 16'd0;else if(tx_en)baud_cnt <= 16'd0;      else if(busy)baud_cnt <= (baud_cnt + 16'b1) % BAUD_CNT_MAX;elsebaud_cnt <= 16'd0;
endalways @(posedge clk or negedge rst_n) beginif(!rst_n) tx_cnt <= 4'd0;else if(tx_en)  tx_cnt <= 16'd0;         else if(busy) beginif(baud_cnt == (BAUD_CNT_MAX-16'b1))tx_cnt <= tx_cnt + 1'b1;elsetx_cnt <= tx_cnt;endelsetx_cnt <= 4'd0;
endalways @(posedge clk or negedge rst_n) beginif(!rst_n) txd <= 1'b1;else if(busy) beginif(tx_cnt==4'd0)txd <= 1'b0;else if(tx_cnt>=4'd1 && tx_cnt<=4'd8)txd <= tx_data_t[tx_cnt-1];elsetxd <= 1'b1;endelsetxd <= 1'b1;
endendmodule

生成波形

我们所谓的波形,其实就是Hello world的字符,但这里有个坑点,即把字符串存储为向量时,其顺序是自右向左的,为了让串口发送的内容被识别,需要调转字符串的方向。

`timescale 1ns / 1ps
module uart_gen(input            clk,input            rst_n,output reg       done,output reg [7:0] char);parameter CLK_FREQ = 50000000;
parameter UART_BPS = 115200  ;
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS;
localparam DELAY_MAX = BAUD_CNT_MAX * 10;parameter [12*8-1:0] hw = "!dlrow olleH";reg [4:0] index;
reg  [15:0] baud_cnt ;
reg send_en;always @(posedge clk or negedge rst_n) beginif(!rst_n) baud_cnt <= 16'd0;else if(send_en)baud_cnt <= (baud_cnt + 16'b1)%DELAY_MAX;elsebaud_cnt <= 16'd0;
endalways @(posedge clk or negedge rst_n) beginif (!rst_n) beginindex <= 0;send_en <= 1;char <= 0;done <= 0;endelse if ((baud_cnt % DELAY_MAX)==0) beginif (index < 12) beginchar <= hw[index*8+:8];index <= index + 1;done <= 1;end elsesend_en <= 0;endelsedone <= 0;
end
endmodule

顶层代码

顶层代码的作用,就是将【uart_gen】生成的波形传递给【uart_tx】,其架构图如下

在这里插入图片描述

代码为

`timescale 1ns / 1psmodule hello_world(input sys_clk,input sys_rst_n,output uart_txd);//parameter define
parameter CLK_FREQ = 50000000;
parameter UART_BPS = 115200  ;wire  [7:0]  char;
wire         send_en;uart_tx #(.CLK_FREQ  (CLK_FREQ),.UART_BPS  (UART_BPS))    u_uart_tx(.clk       (sys_clk     ),.rst_n     (sys_rst_n   ),.tx_en     (send_en     ),.data      (char        ),.txd       (uart_txd    ),.busy      (            ));uart_gen #(.CLK_FREQ (CLK_FREQ),.UART_BPS (UART_BPS))u_uart_gen(.clk     (sys_clk  ),.rst_n   (sys_rst_n),.done    (send_en  ),.char    (char     ));endmodule

实现

我所使用的开发板,其引脚如下

NamePackage PinI/O Std说明
clkU18LVCMOS33系统时钟50MHz
rstN16LVCMOS33复位键,低电平有效
txdJ15LVCMOS33UART发送端口
http://www.dtcms.com/a/318772.html

相关文章:

  • 【C/C++】C++引用和指针的对比
  • 29-数据仓库与Apache Hive-创建库、创建表
  • 树莓派安装OpenCV环境
  • 【CDA案例】数据分析案例拆解:解锁数据分析全流程!
  • 微服务、服务网格、Nacos架构与原理
  • mapbox进阶,mapbox-gl-draw绘图插件扩展,绘制新增、编辑模式支持点、线、面的捕捉
  • Linux系统编程--权限管理
  • 在NVIDIA Orin上用TensorRT对YOLO12进行多路加速并行推理时内存泄漏(下)
  • Redis为什么要引入多线程?
  • 如何在GPU上安装使用Docker
  • 【AI】——SpringAI通过Ollama本地部署的Deepseek模型实现一个对话机器人(二)
  • 用 tcpdump 捕获网络数据包
  • RTSP播放器技术详解:功能支持、平台覆盖与快速集成指南
  • PostgreSQL 强制索引:当重复数据让优化器“失明”时的解决方案
  • centos系统sglang单节点本地部署大模型
  • Sklearn 机器学习 数据降维PCA 自己实现PCA降维算法
  • 如何打造一支AI时代下的IT团队,为企业战略目标快速赋能
  • Java面试宝典:JVM的垃圾收集算法
  • MCU中的晶振(Crystal Oscillator)
  • 【Zephyr】02_从零教你开发芯片级ADC驱动(HAL层篇)
  • 每日五个pyecharts可视化图表-bars(6)
  • 嵌入式硬件中MOSFET基本原理与实现
  • 基于 Socket.IO 实现 WebRTC 音视频通话与实时聊天系统(Spring Boot 后端实现)
  • C语言中级_动态内存分配、指针和常量、各种指针类型、指针和数组、函数指针
  • MATLAB科研数据可视化
  • cuda编程笔记(13)--使用CUB库实现基本功能
  • 嵌入式硬件中MOSFET基本控制详解
  • 嵌入式硬件学习(十一)—— platform驱动框架
  • OpenAI 开源模型 GPT-OSS深度拆解:从1170亿参数到单卡部署,重构AI开源生态
  • 亚马逊采购风控突围:构建深度隐匿的环境安全体系