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学习—串口通信相关推荐

  1. (一)FPGA之串口通信(UART)

    (一)FPGA之串口通信(UART) 回到梦开始的地方,如今回过头来看串口协议,确实清晰了很多,但是奈何好记性不如烂笔头,我还是要重新记录一下学习的知识点,方便查找和学习. 波特率(Band Rate ...

  2. 嵌入式学习——串口通信小试

    嵌入式学习--串口通信小试 目录 嵌入式学习--串口通信小试 1.了解串口协议和RS-232.485标准,以及RS-232.485电平与TTL电平的区别 1.1 什么是串口协议 1.2 RS-232标 ...

  3. 基于FPGA Uart串口通信实验

    基于FPGA Uart串口通信实验 首先需要了解uart串口通信协议,根据个人专业需求不同,了解的层面可以不同. UART简介 通用异步收发传输器(Universal Asynchronous Rec ...

  4. MSP430f2619学习—串口通信

    1.初始化 USCI_A0引脚:P3.4.P3.5: UCA0CTL0:配置寄存器0,可配置奇偶校验位.数据位.通信模式等参数: UCA0CTL1:配置寄存器1,可配置时钟源等参数: 串口0初始化配置 ...

  5. 【正点原子FPGA连载】第十四章 串口通信实验 -摘自【正点原子】新起点之FPGA开发指南_V2.1

    1)实验平台:正点原子新起点V2开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=609758951113 2)全套实验源码+手册+视频下载地址:ht ...

  6. STM32串口通信学习总结

                                                                             STM32串口通信学习总结 1.概述 1.1学习目的 ...

  7. ESP32学习记录<三>串口通信

    ESP32学习记录<三>串口通信 文章目录 ESP32学习记录<三>串口通信 前言 一.通过串口打印出数据 1.初始化串口 2.打印数据 二.串口控制LED亮灭 1.接收串口发 ...

  8. STM32F105RCT6使用CubeMX初始化工程——1:初始化串口通信

    目录 1:CubeMX初始化配置 2:添加代码逻辑 3:通信测试 使用引脚:PA9,PA10 1:CubeMX初始化 在上一次基础上,打开CubeMx工程选择对应的引脚设置为串口通信.通信模式修改为异 ...

  9. 【小月电子】安路国产FPGA开发板系统学习教程-LESSON7串口通信

    串口通信例程讲解 若要观看该博客配套的视频教程,可点击此链接 根据多年工作经验,总结出的FPGA的设计流程,概括起来总共有以上12步,其中根据项目难易度可省去其中一些步骤.比如非常简单的项目,我们可以 ...

最新文章

  1. Linux下WRF Domain Wizard使用教程(PART2: 使用教程及 遇到的种种BUG)
  2. 运维监控工具之 Nagios 客户端安装(二)
  3. 网络安全04_互联网发展史_网线+网卡+协议栈_中继器_集线器_网桥_路由器_AC/AP_防火墙_流控_家庭网络_小型创业公司网络_园区网_政务网络_数据中心网络拓扑_电信网/互联网_Mac地址
  4. 深度学习(15)TensorFlow高阶操作四: 填充与复制
  5. Sigma Grid 2.4 探究 1
  6. 网络基础---IP编址
  7. Jboss 安装配置
  8. 知乎高赞的学习网站,建议收藏
  9. C Primer Plus 第十四章学习总结……2015.5.17
  10. push大法破解登录框
  11. c# DGV导出excel 使用object类型数组,解决string类型需双击后或分列才可运算的异常
  12. 阿尔萨斯监控平台普罗米修斯监控平台对服务器资源的监控
  13. 宽带无法远程连接到计算机,登录校园宽带是显示不能建立远程计算机连接,在别的电脑可以登录 是为什么?...
  14. Android ObjectAnimator类:手把手带你自定义属性动画
  15. 马化腾、张一鸣朋友圈互怼
  16. 在计算机中字节的英文名称是bit么,计算机中的字节是常用单位,它的英文名字是( )。A.bitB.byteC.boutD.baud - 试题答案网问答...
  17. 一招搞定win10网络图标显示问题-网络图标不见了
  18. 项目管理工具project软件学习(三) - 自定义日历【6天工作日】/【大小周】
  19. 禹贡(Yukon)空间数据库 QA 集锦
  20. 饥饿的小易(分枝限界法)

热门文章

  1. ubuntu中创建新用户并添加管理员权限
  2. linux 进程间通信 数据库,什么是进程间通信
  3. 【M语言编程学习笔记之一, 查找当前路径下文件】
  4. 02 C语言使用队列实现缓存模块QueueBuffer
  5. 麦语言和python区别_funcat: Funcat 将同花顺、通达信、文华财经麦语言等的公式写法移植到了 Python 中。...
  6. Git帝国之tag大臣
  7. 光伏发电最大功率点跟踪MPPT(粒子群算法)
  8. 可信、安全、稳定构建金融科技新局面
  9. Mac彻底删除mysql,重新安装mysql,修改mysql用户权限
  10. 新闻列表中,常用的文字超出后显示省略号..