【FPGA学习】实例一、Cyclone IV串口通信(RS232)

目录

目录

前言

一、RS232协议

二、模块框图

 三、代码编写

1.串口接收

2.串口发送

3.顶层模块

四、仿真验证

总结


前言

刚开始学习FPGA开发,项目中用到了串口RS232协议进行通信,记录一下设计思路和设计过程,开发板是野火的征途pro开发板,软件采用Quartus II 13.0。(参考资料:《FPGA实战开发指南》)

一、RS232协议

RS232协议是UART的一种,只有两根数据线,分别是rx和tx,分别用来接收数据和发送数据,数据收发基于帧结构,每次按照8bit的大小来接收和发送数据。数据线空闲状态下为高电平,开始发送数据后将电平拉低一帧作为起始位,随后8帧的数据为数据内容,发送完毕后将数据线电平拉高一帧作为停止位,然后一直拉高回到空闲状态。

二、模块框图

 本次实例将编写串口接收模块以及串口发送模块,并用顶层模块实例化调用这两个模块,组成串口回环,来进行功能验证,接下来分模块介绍这几个模块的代码编写。

 三、代码编写

本次实例串口波特率采用常见的9600 bit/s,开发板系统时钟为50MHz,所以每一帧数据所需的周期为1/9600 s,因此所需开发板计数到1/9600/(1/50_000_000) ≈ 5208,采用计数和清零的方式确定每一帧的开始和结束。

1.串口接收

采用寄存器放置读取的异步串口数据后将数据打两拍,减小亚稳态的可能性。随后在起始位的下降沿标记一个标志位start_flag产生一个周期的高电平,通过该信号控制读取数据的使能信号打开,波特率计数器baud_cnt在使能有效时进行计数,0-5207周期循环,当计数器计数到最大值5207的一半时拉高数据采集标志位bit_flag一个时钟周期,在该标志信号下降沿进行bit_cnt计数,在计数器1-8之间并且采集标志位bit_flag拉高时,将串口数据串联到rx_data的最高位,形成一个移位寄存器,移位八次后生成一个周期的高电平,将其打一拍后与数据同步输出到下一级模块。

代码如下:

module uart_rx
#(
    parameter       UART_BPS    =   'd9600      ,//串口波特率
    parameter       CLK_FREQ    =   'd50_000_000//时钟频率
)
(
    input   wire            sys_clk     ,
    input   wire            sys_rst_n   ,
    input   wire            rx          ,//输入数据

    output  reg   [7:0]    out_data    ,//输出数据
    output  reg            out_flag    //输出标志位
);

parameter   BAUD_CNT_MAX    =   CLK_FREQ / UART_BPS;

//中间变量声明
reg          rx_reg1         ;//打一拍把异步串口信号同步读取
reg          rx_reg2         ;
reg          rx_reg3         ;//打两拍减小亚稳态影响
reg          start_flag      ;//开始标志位
reg          work_en         ;//读取数据的使能信号
reg  [15:0]  baud_cnt        ;//波特率计数器
reg          bit_flag        ;//数据读取标志位
reg  [3:0]   bit_cnt         ;//数据读取计数器
reg  [7:0]   rx_data         ;//并行已读数据
reg          rx_flag         ;//并行已读数据标志位

//同步读取信号
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rx_reg1 <= 1'b1;
    else 
        rx_reg1 <= rx;
        
//打两拍减小亚稳态影响
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rx_reg2 <= 1'b1;
    else
        rx_reg2 <= rx_reg1;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rx_reg3 <= 1'b1;
    else 
        rx_reg3 <= rx_reg2;

//第三个寄存器下降沿开始,起始标志位一个clk的高电平
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        start_flag <= 1'b0;//增加使能信号判据防止数据变化误判
    else if((rx_reg3 == 1'b1)&&(rx_reg2 == 1'b0)&&(work_en == 1'b0))
        start_flag <= 1'b1;
    else
        start_flag <= 1'b0;

//提取数据使能信号
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        work_en <= 1'b0;
    else if(start_flag == 1'b1)
        work_en <= 1'b1;//增加标志位信号严格条件
    else if((bit_cnt == 4'd8)&&(bit_flag == 1'b1))
        work_en <= 1'b0;
    else
        work_en <= work_en;

//波特率计数器
always@(posedge sys_clk or negedge sys_rst_n)
    if((sys_rst_n == 1'b0))
        baud_cnt <= 16'd0; //计数满或使能信号无的时候清零
    else if((baud_cnt == BAUD_CNT_MAX - 1'b1)||(work_en == 1'b0))
        baud_cnt <= 16'd0; 
    else
        baud_cnt <= baud_cnt + 1'b1;

//数据读取标志位
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        bit_flag <= 1'b0; 
    else if(baud_cnt == BAUD_CNT_MAX / 2 - 1)
        bit_flag <= 1'b1;
    else
        bit_flag <= 1'b0;

//数据读取计数器
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        bit_cnt <= 4'd0; //计数满或使能信号无的时候清零
    else if((bit_flag == 1'b1)&&(bit_cnt == 4'd8))
        bit_cnt <= 4'd0;
    else if(bit_flag == 1'b1)
        bit_cnt <= bit_cnt + 1'b1;

//并行已读数据标志位
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rx_flag <= 1'b0; 
    else if((bit_cnt == 4'd8)&&(bit_flag == 1'b1))
        rx_flag <= 1'b1;
    else
        rx_flag <= 1'b0; 

//并行已读数据
always@(posedge sys_clk or negedge sys_rst_n)
    if((sys_rst_n == 1'b0))
        rx_data <= 8'b0; 
    else if((bit_cnt >= 4'd1)&&(bit_cnt <= 4'd8)&&(bit_flag == 1'b1))
        rx_data <= {rx_reg3,rx_data[7:1]};//在第八个数据到来时仍需要移位

//输出数据
always@(posedge sys_clk or negedge sys_rst_n)
    if((sys_rst_n == 1'b0))
        out_data <= 8'b0;
    else if(rx_flag == 1'b1)
        out_data <= rx_data;

//输出标志位
always@(posedge sys_clk or negedge sys_rst_n)
    if((sys_rst_n == 1'b0))
        out_flag <= 1'b0; 
    else
        out_flag <= rx_flag;//与输入标志位信号同步,实际刚好打一拍

endmodule

2.串口发送

同样的,将需要发送的数据放置在寄存器in_data中,in_flag为与数据同步的标志信号,使能信号和计数器作用与串口接收模块类似,此处不再赘述。使能有效后每当bit_cnt计数到1时bit_flag拉高一个周期,并在其下降沿对数据进行按位输出(一共10位宽,包含起始低电平和停止位高电平)并同时按位计数,完成10位宽输出后拉高电平。

 代码如下:

module  uart_tx
#(
    parameter       UART_BPS    =   'd9600      ,//串口波特率
    parameter       CLK_FREQ    =   'd50_000_000//时钟频率
)
(
input        wire          sys_clk     ,
input        wire          sys_rst_n   ,
input        wire   [7:0]  in_data     ,    //wire型数据,大小在wire后面
input        wire          in_flag     ,

output       reg          tx                //always中reg型数据输出
);

parameter   BAUD_CNT_MAX    =   CLK_FREQ / UART_BPS;

//中间参数定义
reg             work_en     ;
reg   [16:0]    baud_cnt    ;
reg   [3:0]     bit_cnt     ;
reg             bit_flag    ;

//使能信号,读到输入标志位后拉高,数据读取9次后拉低
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        work_en <= 1'b0;
    else if(in_flag == 1'b1)
        work_en <= 1'b1;
    else if((bit_cnt == 4'd9)&&(bit_flag == 1'b1))
        work_en <= 1'b0;
    else
        work_en <= work_en;

//波特率计数器
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        baud_cnt <= 16'd0;
    else if((baud_cnt == BAUD_CNT_MAX - 1'b1)||(work_en == 1'b0))
        baud_cnt <= 16'd0;
    else if(work_en == 1'b1)//时序逻辑不需要列出所有,不会产生多余latch
        baud_cnt <= baud_cnt + 1'b1;

//数据提取标志位
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        bit_flag <= 1'b0;
    else if(baud_cnt == 16'd1)  //此处因为每个计数周期“1”只出现一次
        bit_flag <= 1'b1;
    else
        bit_flag <= 1'b0;

//提取数据计数器
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        bit_cnt <= 4'd0;
    else if((bit_cnt == 4'd9)&&(bit_flag == 1'b1))
        bit_cnt <= 4'd0;
    else if((bit_flag == 1'b1)&&(work_en == 1'b1))
        bit_cnt <= bit_cnt + 1'b1;
    else
        bit_cnt <= bit_cnt;

//输出数据
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        tx <= 1'b1;      //空闲状态为高电平
    else if(bit_flag == 1'b1)
        begin
            case(bit_cnt)
                0:  tx <= 1'b0;         //起始位
                1:  tx <= in_data[0];
                2:  tx <= in_data[1];
                3:  tx <= in_data[2];
                4:  tx <= in_data[3];
                5:  tx <= in_data[4];
                6:  tx <= in_data[5];
                7:  tx <= in_data[6];
                8:  tx <= in_data[7];
                9:  tx <= 1'b1;         //停止位
                default: tx = 1'b1;
            endcase
        end
endmodule

3.顶层模块

实例化两个模块即可:

module  RS232
(
    input     wire    sys_clk     ,
    input     wire    sys_rst_n   ,
    input     wire    rx          ,

    output    wire    tx
);

wire    [7:0]   rx_data     ;
wire            rx_flag     ;

uart_rx
#(
    .UART_BPS    (9600      ),//串口波特率
    .CLK_FREQ    (50_000_000)//时钟频率
)
uart_rx_inst
(
    .sys_clk    (sys_clk) ,
    .sys_rst_n  (sys_rst_n) ,
    .rx         (rx) ,//输入数据

    .out_data   (rx_data) ,//输出数据
    .out_flag   (rx_flag) //输出标志位
);

uart_tx
#(
    .UART_BPS    (9600      ),//串口波特率
    .CLK_FREQ    (50_000_000)//时钟频率
)
uart_tx_inst
(
    .sys_clk    (sys_clk) ,
    .sys_rst_n  (sys_rst_n) ,
    .in_data    (rx_data) ,    //wire型数据,大小在wire后面
    .in_flag    (rx_flag) ,

    .tx         (tx)      //always中reg型数据输出
);

endmodule

四、仿真验证

通过串口发送数据并返回可以看到功能无误,模块代码编写正确。


总结

本次实例采用波形图法,编写了常见的低速通信协议UART中的RS232协议并验证正确,这样的话一个简单的串口通信就完成了。

  • 5
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值