news 2026/3/21 8:34:41

27.串口

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
27.串口

1.串口介绍

串口(通常指 UART,通用异步收发传输器)是一种异步串行通信接口,也是嵌入式系统中最常用的通信方式之一,它通过一根线发送数据、另一根线接收数据,实现设备间的全双工通信。

2.串口组成

1.物理层

2.协议

串口协议:

传输1个字节:

传输多个字节:

3.代码

使用的是米联客的开发板:

串口的实现思路:

状态机+计数器

top.v:

`timescale 1ns / 1ps module top ( input sys_clk_p, //系统差分输入时钟 input sys_clk_n, //系统差分输入时钟 input sys_rst_n, //系外部复位信号,低有效 //UART端口 input uart_rxd , //UART接收端口 output uart_txd , //UART发送端口 output reg [1:0] LED_o ); reg [31:0]tcnt; //***************************************************** //** main code //***************************************************** //转换差分信号 IBUFDS diff_clock ( .I (sys_clk_p), //系统差分输入时钟 .IB(sys_clk_n), //系统差分输入时钟 .O (sys_clk) //输出系统时钟 ); //例化串口模块 uart_loopback uart_looop( .sys_clk(sys_clk), //时钟信号 .sys_rst_n(sys_rst_n), //系外部复位信号,低有效 .uart_rxd (uart_rxd), //UART接收端口 .uart_txd(uart_txd) //UART发送端口 ); always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n) tcnt <= 32'd0; else if(tcnt < 32'd99_999_999) tcnt <= tcnt + 1'b1; else tcnt <= 32'd0; end wire led_en = (tcnt == 32'd99_999_999); always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n) LED_o <= 2'b1; else if(led_en) if(LED_o == 2'b10) LED_o <= 2'b1; else LED_o <= LED_o << 1; end endmodule

uart_loopback.v

`timescale 1ns / 1ps module uart_loopback( input sys_clk, //时钟信号 input sys_rst_n, //系外部复位信号,低有效 //UART端口 input uart_rxd , //UART接收端口 output uart_txd //UART发送端口 ); //parameter define parameter CLK_FREQ = 100000000; //定义系统时钟频率 parameter UART_BPS = 115200 ; //定义串口波特率 //wire define wire uart_rx_done; //UART接收完成信号 wire [7:0] uart_rx_data; //UART接收数据 wire [3:0] rx_cnt; //***************************************************** //** main code //***************************************************** //串口接收模块 uart_rx #( .CLK_FREQ (CLK_FREQ), .UART_BPS (UART_BPS) ) u_uart_rx( .clk (sys_clk ), .rst_n (sys_rst_n ), .uart_rxd (uart_rxd ), .uart_rx_done (uart_rx_done), .uart_rx_data (uart_rx_data), .rx_cnt(rx_cnt) ); //串口发送模块 uart_tx #( .CLK_FREQ (CLK_FREQ), .UART_BPS (UART_BPS) ) u_uart_tx( .clk (sys_clk ), .rst_n (sys_rst_n ), .uart_tx_en (uart_rx_done), .uart_tx_data (uart_rx_data), .uart_txd (uart_txd ), .uart_tx_busy ( ) ); ila_1 u_ila_1 ( .clk(sys_clk), // ILA的采样时钟(必须和被观测信号同时钟,此处用系统时钟100MHz) .probe0(sys_rst_n), // 探头0:复位信号(参考用) .probe1(uart_rx_done), // 探头1:UART接收信号(核心观测) .probe2(uart_rx_data[7:0]), // 探头2:UART发送信号(核心观测) .probe3(uart_rxd), // 探头3:LED输出(辅助参考) .probe4(rx_cnt) // 探头4:LED翻转使能(辅助参考) ); endmodule

uart_rx.v

`timescale 1ns / 1ps module uart_rx( input clk , //系统时钟 input rst_n , //系统复位,低有效 input uart_rxd , //UART接收端口 output reg uart_rx_done, //UART接收完成信号 output reg [7:0] uart_rx_data, //UART接收到的数据 output reg [3:0] rx_cnt ); //parameter define parameter CLK_FREQ = 100000000; //系统时钟频率 parameter UART_BPS = 115200 ; //串口波特率 localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS; //为得到指定波特率,对系统时钟计数BPS_CNT次 //reg define reg uart_rxd_d0; reg uart_rxd_d1; reg uart_rxd_d2; reg rx_flag ; //接收过程标志信号 reg [3:0 ] rx_cnt ; //接收数据计数器 reg [15:0] baud_cnt ; //波特率计数器 reg [7:0 ] rx_data_t ; //接收数据寄存器 //wire define wire start_en; //***************************************************** //** main code //***************************************************** //捕获接收端口下降沿(起始位),得到一个时钟周期的脉冲信号 assign start_en = uart_rxd_d2 & (~uart_rxd_d1) & (~rx_flag); //针对异步信号的同步处理 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin uart_rxd_d0 <= 1'b0; uart_rxd_d1 <= 1'b0; uart_rxd_d2 <= 1'b0; end else begin uart_rxd_d0 <= uart_rxd; uart_rxd_d1 <= uart_rxd_d0; uart_rxd_d2 <= uart_rxd_d1; end end //给接收标志赋值 always @(posedge clk or negedge rst_n) begin if(!rst_n) rx_flag <= 1'b0; else if(start_en) //检测到起始位 rx_flag <= 1'b1; //接收过程中,标志信号rx_flag拉高 //在停止位一半的时候,即接收过程结束,标志信号rx_flag拉低 else if((rx_cnt == 4'd9) && (baud_cnt == BAUD_CNT_MAX/2 - 1'b1)) rx_flag <= 1'b0; else rx_flag <= rx_flag; end //波特率的计数器赋值 always @(posedge clk or negedge rst_n) begin if(!rst_n) baud_cnt <= 16'd0; else if(rx_flag) begin //处于接收过程时,波特率计数器(baud_cnt)进行循环计数 if(baud_cnt < BAUD_CNT_MAX - 1'b1) baud_cnt <= baud_cnt + 16'b1; else baud_cnt <= 16'd0; //计数达到一个波特率周期后清零 end else baud_cnt <= 16'd0; //接收过程结束时计数器清零 end //对接收数据计数器(rx_cnt)进行赋值 always @(posedge clk or negedge rst_n) begin if(!rst_n) rx_cnt <= 4'd0; else if(rx_flag) begin //处于接收过程时rx_cnt才进行计数 if(baud_cnt == BAUD_CNT_MAX - 1'b1) //当波特率计数器计数到一个波特率周期时 rx_cnt <= rx_cnt + 1'b1; //接收数据计数器加1 else rx_cnt <= rx_cnt; end else rx_cnt <= 4'd0; //接收过程结束时计数器清零 end //根据rx_cnt来寄存rxd端口的数据 always @(posedge clk or negedge rst_n) begin if(!rst_n) rx_data_t <= 8'b0; else if(rx_flag) begin //系统处于接收过程时 if(baud_cnt == BAUD_CNT_MAX/2 - 1'b1) begin //判断baud_cnt是否计数到数据位的中间 case(rx_cnt) 4'd1 : rx_data_t[0] <= uart_rxd_d2; //寄存数据的最低位 4'd2 : rx_data_t[1] <= uart_rxd_d2; 4'd3 : rx_data_t[2] <= uart_rxd_d2; 4'd4 : rx_data_t[3] <= uart_rxd_d2; 4'd5 : rx_data_t[4] <= uart_rxd_d2; 4'd6 : rx_data_t[5] <= uart_rxd_d2; 4'd7 : rx_data_t[6] <= uart_rxd_d2; 4'd8 : rx_data_t[7] <= uart_rxd_d2; //寄存数据的高低位 default : ; endcase end else rx_data_t <= rx_data_t; end else rx_data_t <= 8'b0; end //给接收完成信号和接收到的数据赋值 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin uart_rx_done <= 1'b0; uart_rx_data <= 8'b0; end //当接收数据计数器计数到停止位,且baud_cnt计数到停止位的中间时 else if(rx_cnt == 4'd9 && baud_cnt == BAUD_CNT_MAX/2 - 1'b1) begin uart_rx_done <= 1'b1 ; //拉高接收完成信号 uart_rx_data <= rx_data_t; //并对UART接收到的数据进行赋值 end else begin uart_rx_done <= 1'b0; uart_rx_data <= uart_rx_data; end end endmodule

uart_tx.v

`timescale 1ns / 1ps module uart_tx( input clk , //系统时钟 input rst_n , //系统复位,低有效 input uart_tx_en , //UART的发送使能 input [7:0] uart_tx_data, //UART要发送的数据 output reg uart_txd , //UART发送端口 output reg uart_tx_busy //发送忙状态信号 ); //parameter define parameter CLK_FREQ = 100000000; //系统时钟频率 parameter UART_BPS = 115200 ; //串口波特率 localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS; //为得到指定波特率,对系统时钟计数BPS_CNT次 //reg define reg [7:0] tx_data_t; //发送数据寄存器 reg [3:0] tx_cnt ; //发送数据计数器 reg [15:0] baud_cnt ; //波特率计数器 //***************************************************** //** main code //***************************************************** //当uart_tx_en为高时,寄存输入的并行数据,并拉高BUSY信号 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin tx_data_t <= 8'b0; uart_tx_busy <= 1'b0; end //发送使能时,寄存要发送的数据,并拉高BUSY信号 else if(uart_tx_en) begin tx_data_t <= uart_tx_data; uart_tx_busy <= 1'b1; end //当计数到停止位结束时,停止发送过程 else if(tx_cnt == 4'd9 && baud_cnt == BAUD_CNT_MAX - BAUD_CNT_MAX/16) begin tx_data_t <= 8'b0; //清空发送数据寄存器 uart_tx_busy <= 1'b0; //并拉低BUSY信号 end else begin tx_data_t <= tx_data_t; uart_tx_busy <= uart_tx_busy; end end //波特率的计数器赋值 always @(posedge clk or negedge rst_n) begin if(!rst_n) baud_cnt <= 16'd0; //当处于发送过程时,波特率计数器(baud_cnt)进行循环计数 else if(uart_tx_busy) begin if(baud_cnt < BAUD_CNT_MAX - 1'b1) baud_cnt <= baud_cnt + 16'b1; else baud_cnt <= 16'd0; //计数达到一个波特率周期后清零 end else baud_cnt <= 16'd0; //发送过程结束时计数器清零 end //tx_cnt进行赋值 always @(posedge clk or negedge rst_n) begin if(!rst_n) tx_cnt <= 4'd0; else if(uart_tx_busy) begin //处于发送过程时tx_cnt才进行计数 if(baud_cnt == BAUD_CNT_MAX - 1'b1) //当波特率计数器计数到一个波特率周期时 tx_cnt <= tx_cnt + 1'b1; //发送数据计数器加1 else tx_cnt <= tx_cnt; end else tx_cnt <= 4'd0; //发送过程结束时计数器清零 end //根据tx_cnt来给uart发送端口赋值 always @(posedge clk or negedge rst_n) begin if(!rst_n) uart_txd <= 1'b1; else if(uart_tx_busy) begin case(tx_cnt) 4'd0 : uart_txd <= 1'b0 ; //起始位 4'd1 : uart_txd <= tx_data_t[0]; //数据位最低位 4'd2 : uart_txd <= tx_data_t[1]; 4'd3 : uart_txd <= tx_data_t[2]; 4'd4 : uart_txd <= tx_data_t[3]; 4'd5 : uart_txd <= tx_data_t[4]; 4'd6 : uart_txd <= tx_data_t[5]; 4'd7 : uart_txd <= tx_data_t[6]; 4'd8 : uart_txd <= tx_data_t[7]; //数据位最高位 4'd9 : uart_txd <= 1'b1 ; //停止位 default : uart_txd <= 1'b1; endcase end else uart_txd <= 1'b1; //空闲时发送端口为高电平 end endmodule

引脚约束文件:

# ============================================= # 时钟约束:差分时钟(P/N对) # ============================================= # 1. 创建主时钟:10ns(100MHz),50%占空比,时钟源P端 create_clock -period 10.000 -name sysclk -waveform {0 5} [get_ports sys_clk_p] # 2. 差分时钟引脚分配 set_property PACKAGE_PIN AE5 [get_ports sys_clk_p] # 3. 差分时钟电平标准(P/N端同时设置DIFF_SSTL12 set_property IOSTANDARD DIFF_SSTL12 [get_ports sys_clk_p] # ============================================= # 复位信号:引脚+电平约束 # ============================================= set_property PACKAGE_PIN B14 [get_ports sys_rst_n] set_property IOSTANDARD LVCMOS18 [get_ports sys_rst_n] # ============================================= # LED灯:引脚+电平约束 # ============================================= set_property PACKAGE_PIN E14 [get_ports {LED_o[1]}] set_property PACKAGE_PIN E13 [get_ports {LED_o[0]}] set_property IOSTANDARD LVCMOS18 [get_ports {LED_o[*]}] # ============================================= # PL调试串口:引脚+电平约束 # ============================================= set_property PACKAGE_PIN A15 [get_ports uart_txd] set_property PACKAGE_PIN B15 [get_ports uart_rxd] set_property IOSTANDARD LVCMOS18 [get_ports {uart_txd uart_rxd}] # ============================================= # 全局配置:开启比特流压缩 # ============================================= set_property BITSTREAM.GENERAL.COMPRESS true [current_design]

4.仿真

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/17 7:12:46

人工智能大模型技术解析:程序员和小白的必读指南

人工智能大模型是通过超大规模参数和海量训练数据构建的基础模型&#xff0c;具有强泛化能力和多任务处理能力。其发展历经萌芽期、沉淀期和爆发期三个阶段。大模型由海量数据与算法、巨量算力平台、核心能力及服务应用四部分组成&#xff0c;能够实现内容生成、知识问答、代码…

作者头像 李华
网站建设 2026/3/15 22:59:09

收藏!30+程序员零基础转行大模型,2个月拿2w+offer,别只看理论不行动

作为一名30北漂程序员&#xff0c;我用2个月时间&#xff0c;从零基础跨界大模型领域&#xff0c;成功斩获月薪2w的offer&#xff0c;彻底摆脱了十年“码农内耗”的困境。今天把我的亲身经历转行全攻略毫无保留分享出来&#xff0c;尤其适合迷茫想转行、只懂基础编程、不敢迈出…

作者头像 李华
网站建设 2026/3/15 16:40:17

教培管家第14讲:家长端——打造合规且顺畅的登录门户

目录⏳ 前情回顾&#x1f3af; 本节目标为什么要专门设计一个“登录页”&#xff1f;第一步&#xff1a;数据建模——动态协议管理第二步&#xff1a;搭建后台配置管理页第三歩&#xff1a;创建小程序第四步&#xff1a;搭建登录页4.1 创建变量4.2 搭建页面布局第五步&#xff…

作者头像 李华
网站建设 2026/3/15 16:13:05

人力资源系统革新,打造企业核心竞争力

人力资源系统革新&#xff0c;打造企业人才发展新引擎在当今竞争激烈的商业环境中&#xff0c;企业的人才发展是决定其成败的关键因素之一。然而&#xff0c;传统的人力资源管理方式往往存在诸多弊端&#xff0c;如效率低下、数据不准确、缺乏实时性等&#xff0c;这些问题严重…

作者头像 李华
网站建设 2026/3/15 16:11:35

企业四大核心场景运营提质增效最佳实践

当数字化转型进入“深水区”&#xff0c;一个根本性的挑战摆在所有大型企业面前&#xff1a;如何让前沿的AI技术跨越概念的鸿沟&#xff0c;在核心业务场景中产生可衡量、可复制的真实价值&#xff1f;对于正处于“人工智能”行动关键期的央企国企而言&#xff0c;这一问题尤为…

作者头像 李华