FPGA学习—串口通信
FPGA学习系列
接下来介绍串口通信控制,本次就不用串口回环了,像正点原子,野火等fpga教程都会教串口回环,所以本文我将介绍如何串口控制去执行一些操作。之后也将拓展FPGA控制串口屏。
目录
- FPGA学习系列
- 介绍
- 协议
- 波特率
- 发送模块
- 接收模块
- 串口控制模块
- 按键消抖模块
- 按键控制模块
- 顶层模块
- 获取信号的上升沿或者下降沿
- 注意
- 总结
介绍
串口通信一般有异步通信,同步通信。又有全双工通信、半双工通信、单工通信。
按接口又有RS422、RS232、RS485。其区别我就不多赘述。
协议
UART串口通信协议需要两根线,一根RX用于接收数据,一根TX用于发送数据。一般来说一帧有10位。其中第一位为起始位:数据线拉低开始即低电平有效,中间8位数据位,最后加上停止位:数据线拉高 即高电平停止。如下图所示。
发送时序流程图
每个时钟周期发送一位,第一位为启动位即TX线拉低表示开始,中间8位数据位最后一位作校验位。之后再有一位停止位即TX线拉高。所有一共10位即一帧10个bit。
波特率
波特率就是每秒传输多少个bit,常见的有9600,115200.这里我以115200为例子。115200就是每秒传输115200个bit。所以传输一个bit需要1s/115200≈8.68us。而我的板子系统时钟是50MHZ也就是1秒有50000000个时钟周期。现在只要其中8.68us去发送一个bit。也就是 50 * 8.68=434(这里是50_000_000*8.68us 我把里0约掉了)。所以只要定义一个计数器去计数到434然后产生一次上升沿就可以产生115200的波特率了。
tip:为什么我用50 * 8.68呢,因为只要8.68us而1秒有50_000_000个周期所以只要算出8.68us占了几个周期就行也就是8.68 * 50.
发送模块
对于FPGA的串口发送就不用像stm32初始化结构体,清除中断标志等操作。只需按照时序编写即可实现串口发送。我都会添加详细注释。我程序中是直接50000000/115200 结果也是434.通用公式也就是频率除以波特率
module uart_send(input sys_clk, //系统时钟input sys_rst_n, //系统复位,低电平有效input uart_en, //发送使能信号input [7:0] uart_din, //传入要发送的数据output uart_tx_busy, //发送忙状态标志 output reg uart_txd //UART发送端口);parameter CLK_FREQ = 50000000; //系统时钟频率
parameter UART_BPS = 115200; //串口波特率
localparam BPS_CNT = CLK_FREQ/UART_BPS; //为得到指定波特率,对系统时钟计数BPS_CNT次 方便修改reg uart_en_d0;
reg uart_en_d1;
reg [15:0] clk_cnt; //系统时钟计数器
reg [ 3:0] tx_cnt; //发送数据计数器
reg tx_flag; //发送过程标志信号
reg [ 7:0] tx_data; //寄存发送数据wire en_flag; //使能信号//*****************************************************
//** 主函数
//*****************************************************
//在串口发送过程中给出忙状态标志
assign uart_tx_busy = tx_flag;//捕获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//计数到停止位结束时,停止发送过程 计数从0-9也就是10位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; //对系统时钟计数达一个波特率周期后清零endelse clk_cnt <= 16'd0; //发送过程结束
end//进入发送过程后,启动发送数据计数器 统计此时发送了几个bit
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//根据发送数据计数器来给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) //利用case语句发送一帧的数据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; //空闲时发送端口为高电平
endendmodule
接收模块
module uart_recv
(input wire sys_clk, //系统时钟input wire sys_rst_n, //系统复位,低电平有效input uart_rxd, //UART接收端口output reg uart_done, //接收一帧数据完成标志output reg rx_flag, //接收过程标志信号output reg [ 3: 0] rx_cnt, //接收数据计数器output reg [ 7: 0] uart_data //接收的数据 然后去判断);parameter CLK_FREQ = 50000000; //系统时钟频率
parameter UART_BPS = 115200; //串口波特率
localparam BPS_CNT = CLK_FREQ/UART_BPS; //为得到指定波特率 需要对系统时钟计数BPS_CNT次reg uart_rxd_d0;
reg uart_rxd_d1;
reg [15:0] clk_cnt; //系统时钟计数器
reg [7:0] rxdata; //接收数据缓存 wire start_flag;//*****************************************************
//** 主函数
//*****************************************************
//捕获接收端口下降沿(起始位),得到一个时钟周期的脉冲信号
assign start_flag = uart_rxd_d1 & (~uart_rxd_d0); //对UART接收端口的数据延迟两个时钟周期
always @(posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) begin uart_rxd_d0 <= 1'b0;uart_rxd_d1 <= 1'b0; endelse beginuart_rxd_d0 <= uart_rxd; uart_rxd_d1 <= uart_rxd_d0;end
end//当脉冲信号start_flag到达时,进入接收过程
always @(posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) rx_flag <= 1'b0;else beginif(start_flag) //检测到起始位rx_flag <= 1'b1; //进入接收过程,标志位rx_flag拉高//计数到停止位中间时,停止接收过程 如果计数到结束的时候容易出现错误 中间采集最稳定else if((rx_cnt == 4'd9) && (clk_cnt == BPS_CNT/2))rx_flag <= 1'b0; //接收过程结束,标志位rx_flag拉低elserx_flag <= rx_flag;end
end//进入接收过程后,启动系统时钟计数器
always @(posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) clk_cnt <= 16'd0; else if ( rx_flag ) begin //处于接收过程if (clk_cnt < BPS_CNT - 1)clk_cnt <= clk_cnt + 1'b1;elseclk_cnt <= 16'd0; //对系统时钟计数达一个波特率周期后清零endelse clk_cnt <= 16'd0; //接收过程结束,计数器清零
end//进入接收过程后,启动接收数据计数器
always @(posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) rx_cnt <= 4'd0;else if ( rx_flag ) begin //处于接收过程if (clk_cnt == BPS_CNT - 1) //对系统时钟计数达一个波特率周期rx_cnt <= rx_cnt + 1'b1; //此时接收数据计数器加1elserx_cnt <= rx_cnt; endelserx_cnt <= 4'd0; //接收过程结束,计数器清零
end//根据接收数据计数器来寄存uart接收端口数据
always @(posedge sys_clk or negedge sys_rst_n) begin if ( !sys_rst_n) rxdata <= 8'd0; else if(rx_flag) //系统处于接收过程if (clk_cnt == BPS_CNT/2) begin //判断系统时钟计数器计数到数据位中间case ( rx_cnt )4'd1 : rxdata[0] <= uart_rxd_d1; //寄存数据位最低位4'd2 : rxdata[1] <= uart_rxd_d1;4'd3 : rxdata[2] <= uart_rxd_d1;4'd4 : rxdata[3] <= uart_rxd_d1;4'd5 : rxdata[4] <= uart_rxd_d1;4'd6 : rxdata[5] <= uart_rxd_d1;4'd7 : rxdata[6] <= uart_rxd_d1;4'd8 : rxdata[7] <= uart_rxd_d1; //寄存数据位最高位default:; endcaseendelse rxdata <= rxdata;elserxdata <= 8'd0;
end//数据接收完毕后给出标志信号并寄存输出接收到的数据
always @(posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) beginuart_data <= 8'd0; uart_done <= 1'b0;endelse if(rx_cnt == 4'd9) begin //接收数据计数器计数到停止位时 uart_data <= rxdata; //寄存输出接收到的数据uart_done <= 1'b1; //并将接收完成标志位拉高endelse beginuart_data <= 8'd0; //其余时刻一直为0 uart_done <= 1'b0; end
end
endmodule
串口控制模块
module led_crtl
(input wire sys_clk ,input wire sys_rst_n ,input wire [7:0] rx_data , //接收到的数据input wire led_en , //led控制使能output reg [3:0] led ,output reg beep
);
reg led_en_d0;//用来打拍
reg led_en_d1;//用来打拍
parameter CNT_MAX=24'd10_000_000;
//流水灯
reg [23:0] cnt;
reg [1:0] led_crtl_water;
reg led_water;//流水灯使能信号wire start_en;
assign start_en = led_en_d0 & ~led_en_d1;//获取上升沿//流水灯延时计数0.2s
always@(posedge sys_clk or negedge sys_rst_n) beginif(!sys_rst_n)cnt <= 24'd0;else if(cnt == CNT_MAX - 1'b1)cnt <= 24'd0;else if(led_water)cnt <= cnt + 1'b1;elsecnt <= 24'd0;
end
//流水灯
always@(posedge sys_clk or negedge sys_rst_n) beginif(!sys_rst_n)led_crtl_water <= 2'd0;else if(cnt == CNT_MAX - 1'b1&&led_water)led_crtl_water <= led_crtl_water + 1'b1;elseled_crtl_water <= led_crtl_water;
end
//控制信号获取
always@(posedge sys_clk or negedge sys_rst_n) beginif(!sys_rst_n) beginled_en_d0 <= 1'b0;led_en_d1 <= 1'b0;endelse beginled_en_d0 <= led_en ;led_en_d1 <= led_en_d0;end
endalways@(posedge sys_clk or negedge sys_rst_n) beginif(!sys_rst_n)led <= 4'd0;else if(start_en)case(rx_data) //根据接收到的数据进行判断8'h01 : led<=4'b0001;8'h02 : led<=4'b0010;8'h03 : led<=4'b0100;8'h04 : led<=4'b1000;8'h05 : led_water<=1'b1; //开启流水灯8'h06 : led_water <= 1'b0; //关闭流水灯8'hbe : beep<=1'b1;8'hbf : beep<=1'b0;8'haa : led<=4'b1111; //a8'ha0 : led<=4'b0000; //endcaseelse if(led_water)case(led_crtl_water)2'b00 : led <= 4'b0001;2'b01 : led <= 4'b0010;2'b10 : led <= 4'b0100;2'b11 : led <= 4'b1000;default : led<=4'b0000;endcase
end
endmodule
按键消抖模块
//按键消抖
module key_filter
#(parameter CNT_MAX = 20'd999_999
)
(input wire sys_clk ,input wire sys_rst_n ,input wire key_in ,output reg key_flag //1表示按下 0表示没有按下
);
reg [19:0] cnt_20ms; //计数器always@(posedge sys_clk or negedge sys_rst_n) beginif(sys_rst_n == 1'b0)cnt_20ms <= 20'b0;else if(key_in == 1'b1)cnt_20ms <= 20'b0;else if(cnt_20ms == CNT_MAX && key_in == 1'b0)cnt_20ms <= cnt_20ms;elsecnt_20ms <= cnt_20ms + 1'b1;
end//key_flag:当计数满20ms后产生按键有效标志位
//且key_flag在999_999时拉高,维持一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)key_flag <= 1'b0;else if(cnt_20ms == CNT_MAX - 1'b1)key_flag <= 1'b1;elsekey_flag <= 1'b0;endmodule
按键控制模块只用于发送数据
按键控制模块
module key_data
( input wire sys_clk ,input wire sys_rst_n ,input wire [3:0] key ,output reg [7:0] key_data_out ,output reg key_done
);wire K1 ; //四个按键
wire K2 ; //四个按键
wire K3 ; //四个按键
wire K4 ; //四个按键always@(posedge sys_clk or negedge sys_rst_n) beginif(!sys_rst_n) key_data_out <= 8'd0; else if(K1==1'b1) key_data_out <= 8'h01;else if(K2==1'b1) key_data_out <= 8'h02;else if(K3==1'b1) key_data_out <= 8'hbe;else if(K4==1'b1)key_data_out <= 8'hbf;
end
always@(posedge sys_clk or negedge sys_rst_n) beginif(!sys_rst_n) beginkey_done <= 1'b0;endelse if(K1||K2||K3||K4) key_done <= 1'b1; //按键按下标志elsekey_done <= 1'b0;
end
//按键例化
key_filter
#(.CNT_MAX (20'd999_999)
)
key_1
(.sys_clk (sys_clk) ,.sys_rst_n (sys_rst_n) ,.key_in (key[0]) ,.key_flag (K1) //1表示按下 0表示没有按下
);key_filter
#(.CNT_MAX (20'd999_999)
)
key_2
(.sys_clk (sys_clk) ,.sys_rst_n (sys_rst_n) ,.key_in (key[1]) ,.key_flag (K2) //1表示按下 0表示没有按下
);
key_filter
#(.CNT_MAX (20'd999_999)
)
key_3
(.sys_clk (sys_clk) ,.sys_rst_n (sys_rst_n) ,.key_in (key[2]) ,.key_flag (K3) //1表示按下 0表示没有按下
);
key_filter
#(.CNT_MAX (20'd999_999)
)
key_4
(.sys_clk (sys_clk) ,.sys_rst_n (sys_rst_n) ,.key_in (key[3]) ,.key_flag (K4) //1表示按下 0表示没有按下
);endmodule
顶层模块
module top_uart_led
(input wire sys_clk ,input wire sys_rst_n ,input wire uart_rxd ,input wire [3:0] key ,output wire [3:0] led , output wire beep ,output wire uart_txd
);
parameter CLK_FREQ = 50_000_000;
parameter UART_BPS = 115200;//连接
wire [7:0] uart_data;
wire uart_done;
wire [7:0] key_data_out;
wire uart_tx_busy;
wire key_done;led_crtl led_crtl_inst
(.sys_clk (sys_clk) ,.sys_rst_n (sys_rst_n),.rx_data (uart_data), //接收到的数据.led_en (uart_done), //led控制使能.led (led) ,.beep (beep)
);
//按键发送数据
key_data key_data_inst
( .sys_clk (sys_clk) ,.sys_rst_n (sys_rst_n) ,.key (key) ,.key_data_out (key_data_out) ,.key_done (key_done)
);//发送数据
uart_send uart_send_inst
(.sys_clk (sys_clk) , //系统时钟.sys_rst_n (sys_rst_n) , //系统复位,低电平有效.uart_en (key_done) , //发送使能信号.uart_din (key_data_out) , //待发送数据.uart_tx_busy (uart_tx_busy) , //发送忙状态标志 .uart_txd (uart_txd) //UART发送端口);uart_recv uart_recv_inst
(.sys_clk (sys_clk ) , //系统时钟.sys_rst_n (sys_rst_n) , //系统复位,低电平有效.uart_rxd (uart_txd) , //UART接收端口.uart_done (uart_done) , //接收一帧数据完成标志信号.uart_data (uart_data) //接收的数据
);
endmodule
获取信号的上升沿或者下降沿
这里有很多种方法,我介绍一下最常用的方法。比如在获取串口发送开始信号的时候.
start_flag = uart_rxd_d1 & (~uart_rxd_d0);就是获取串口发送端开始发送数据的时候TX拉低的时候的下降沿,表示开始.原理如下图所示.分别定义了两个rxd去对uart_rxd信号打拍两次.
在红线部分,当uart_rxd拉低时,下个时钟周期,取反的uart_rxd_d0信号与uart_rxd_d1信号相与就会得到一个高电平,其余全都是低电平,也就是获取他的下降沿会延迟一个时钟周期.
注意
因为串口发送的时候时ascii码形式,所以要不用16进制发送,要不要看ascii码对应的16进制是否和接收判断那部分一致.比如串口直接发送1(字符串形式)那么对应16进制应该是16’h31.
总结
后续将给出模块的仿真文件以及对应vivado与quartusii工程文件.
vivado工程 使用的是2018.3
链接:https://pan.baidu.com/s/1fexI13ajY5heW9oqcWhhaw
提取码:z4qd
FPGA学习—串口通信相关推荐
- (一)FPGA之串口通信(UART)
(一)FPGA之串口通信(UART) 回到梦开始的地方,如今回过头来看串口协议,确实清晰了很多,但是奈何好记性不如烂笔头,我还是要重新记录一下学习的知识点,方便查找和学习. 波特率(Band Rate ...
- 嵌入式学习——串口通信小试
嵌入式学习--串口通信小试 目录 嵌入式学习--串口通信小试 1.了解串口协议和RS-232.485标准,以及RS-232.485电平与TTL电平的区别 1.1 什么是串口协议 1.2 RS-232标 ...
- 基于FPGA Uart串口通信实验
基于FPGA Uart串口通信实验 首先需要了解uart串口通信协议,根据个人专业需求不同,了解的层面可以不同. UART简介 通用异步收发传输器(Universal Asynchronous Rec ...
- MSP430f2619学习—串口通信
1.初始化 USCI_A0引脚:P3.4.P3.5: UCA0CTL0:配置寄存器0,可配置奇偶校验位.数据位.通信模式等参数: UCA0CTL1:配置寄存器1,可配置时钟源等参数: 串口0初始化配置 ...
- 【正点原子FPGA连载】第十四章 串口通信实验 -摘自【正点原子】新起点之FPGA开发指南_V2.1
1)实验平台:正点原子新起点V2开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=609758951113 2)全套实验源码+手册+视频下载地址:ht ...
- STM32串口通信学习总结
STM32串口通信学习总结 1.概述 1.1学习目的 ...
- ESP32学习记录<三>串口通信
ESP32学习记录<三>串口通信 文章目录 ESP32学习记录<三>串口通信 前言 一.通过串口打印出数据 1.初始化串口 2.打印数据 二.串口控制LED亮灭 1.接收串口发 ...
- STM32F105RCT6使用CubeMX初始化工程——1:初始化串口通信
目录 1:CubeMX初始化配置 2:添加代码逻辑 3:通信测试 使用引脚:PA9,PA10 1:CubeMX初始化 在上一次基础上,打开CubeMx工程选择对应的引脚设置为串口通信.通信模式修改为异 ...
- 【小月电子】安路国产FPGA开发板系统学习教程-LESSON7串口通信
串口通信例程讲解 若要观看该博客配套的视频教程,可点击此链接 根据多年工作经验,总结出的FPGA的设计流程,概括起来总共有以上12步,其中根据项目难易度可省去其中一些步骤.比如非常简单的项目,我们可以 ...
最新文章
- Linux下WRF Domain Wizard使用教程(PART2: 使用教程及 遇到的种种BUG)
- 运维监控工具之 Nagios 客户端安装(二)
- 网络安全04_互联网发展史_网线+网卡+协议栈_中继器_集线器_网桥_路由器_AC/AP_防火墙_流控_家庭网络_小型创业公司网络_园区网_政务网络_数据中心网络拓扑_电信网/互联网_Mac地址
- 深度学习(15)TensorFlow高阶操作四: 填充与复制
- Sigma Grid 2.4 探究 1
- 网络基础---IP编址
- Jboss 安装配置
- 知乎高赞的学习网站,建议收藏
- C Primer Plus 第十四章学习总结……2015.5.17
- push大法破解登录框
- c# DGV导出excel 使用object类型数组,解决string类型需双击后或分列才可运算的异常
- 阿尔萨斯监控平台普罗米修斯监控平台对服务器资源的监控
- 宽带无法远程连接到计算机,登录校园宽带是显示不能建立远程计算机连接,在别的电脑可以登录 是为什么?...
- Android ObjectAnimator类:手把手带你自定义属性动画
- 马化腾、张一鸣朋友圈互怼
- 在计算机中字节的英文名称是bit么,计算机中的字节是常用单位,它的英文名字是( )。A.bitB.byteC.boutD.baud - 试题答案网问答...
- 一招搞定win10网络图标显示问题-网络图标不见了
- 项目管理工具project软件学习(三) - 自定义日历【6天工作日】/【大小周】
- 禹贡(Yukon)空间数据库 QA 集锦
- 饥饿的小易(分枝限界法)
热门文章
- ubuntu中创建新用户并添加管理员权限
- linux 进程间通信 数据库,什么是进程间通信
- 【M语言编程学习笔记之一, 查找当前路径下文件】
- 02 C语言使用队列实现缓存模块QueueBuffer
- 麦语言和python区别_funcat: Funcat 将同花顺、通达信、文华财经麦语言等的公式写法移植到了 Python 中。...
- Git帝国之tag大臣
- 光伏发电最大功率点跟踪MPPT(粒子群算法)
- 可信、安全、稳定构建金融科技新局面
- Mac彻底删除mysql,重新安装mysql,修改mysql用户权限
- 新闻列表中,常用的文字超出后显示省略号..