赞
踩
目录
刚开始学习FPGA开发,项目中用到了串口RS232协议进行通信,记录一下设计思路和设计过程,开发板是野火的征途pro开发板,软件采用Quartus II 13.0。(参考资料:《FPGA实战开发指南》)
RS232协议是UART的一种,只有两根数据线,分别是rx和tx,分别用来接收数据和发送数据,数据收发基于帧结构,每次按照8bit的大小来接收和发送数据。数据线空闲状态下为高电平,开始发送数据后将电平拉低一帧作为起始位,随后8帧的数据为数据内容,发送完毕后将数据线电平拉高一帧作为停止位,然后一直拉高回到空闲状态。
本次实例将编写串口接收模块以及串口发送模块,并用顶层模块实例化调用这两个模块,组成串口回环,来进行功能验证,接下来分模块介绍这几个模块的代码编写。
本次实例串口波特率采用常见的9600 bit/s,开发板系统时钟为50MHz,所以每一帧数据所需的周期为1/9600 s,因此所需开发板计数到1/9600/(1/50_000_000) ≈ 5208,采用计数和清零的方式确定每一帧的开始和结束。
采用寄存器放置读取的异步串口数据后将数据打两拍,减小亚稳态的可能性。随后在起始位的下降沿标记一个标志位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
同样的,将需要发送的数据放置在寄存器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
实例化两个模块即可:
- 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协议并验证正确,这样的话一个简单的串口通信就完成了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。