差别
这里会显示出您选择的修订版和当前版本之间的差别。
两侧同时换到之前的修订记录 前一修订版 后一修订版 | 前一修订版 上一修订版 两侧同时换到之后的修订记录 | ||
uart串口模块 [2017/06/15 18:39] anran [硬件说明] |
uart串口模块 [2020/07/24 12:03] gongyu |
||
---|---|---|---|
行 1: | 行 1: | ||
- | ======基于STEP FPGA的矩阵按键驱动====== | + | ======基于STEP FPGA的UART串口通信模块驱动====== |
- | 本节将和大家一起使用FPGA驱动底板上的UART接口通信。 | + | 本节将和大家一起使用[[FPGA]]驱动底板上的[[UART]]接口通信。 |
====硬件说明==== | ====硬件说明==== | ||
+ | |||
------- | ------- | ||
- | UART通信 | + | 通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),通常称作[[UART]],是一种通用串行数据总线,用于异步通信。该总线双向通信,可以实现全双工传输和接收。 |
- | + | ||
- | + | ||
- | 在键盘中按键数量较多时,为了减少I/O口的占用,通常将按键排列成矩阵形式,使用行线和列线分别连接到按键开关的两端,这样我们就可以通过4根行线和4根列线(共8个I/O口)连接16个按键,而且按键数量越多优势越明显。 | + | |
- | + | ||
- | FPGA驱动矩阵按键模块,首先我们来了解矩阵按键的硬件连接: | + | |
\\ | \\ | ||
- | {{ :矩阵按键.jpg?800 |}} | + | 异步通信以一个字符为传输单位,通信中两个字符间的时间间隔多少是不固定的,然而在同一个字符中的两个相邻位间的时间间隔是固定的。两个相邻位间的时间间隔与UART通信的波特率有关,波特率用来表征[[UART]]通信中数据传输的速率,即每秒钟传送的二进制位数。例如数据传送速率为120字符/秒,而每一个字符为10位(1个起始位,7个数据位,1个校验位,1个结束位),则其传送的波特率为10×120=1200字符/秒=1200波特。 |
\\ | \\ | ||
- | 上图为4x4矩阵按键的硬件电路图,可以看到4根行线(ROW1、ROW2、ROW3、ROW4)和4根列线(COL1、COL2、COL3、COL4),同时列线通过上拉电阻连接到VCC电压(3.3V),对于矩阵按键来讲: | + | {{ :uart时序.jpg?800 |}} |
- | - 4根行线是输入的,是由FPGA控制拉高或拉低, | + | |
- | - 4根列线数输出的,是由4根行线的输入及按键的状态决定,输出给FPGA | + | |
- | 当某一时刻,FPGA控制4根行线分别为ROW1=0、ROW2=1、ROW3=1、ROW4=1时, | + | |
- | * 对于K1、K2、K3、K4按键:按下时对应4根列线输出COL1=0、COL2=0、COL3=0、COL4=0,不按时对应4根列线输出COL1=1、COL2=1、COL3=1、COL4=1, | + | |
- | * 对于K5~~~K16之间的按键:无论按下与否,对应4根列线输出COL1=1、COL2=1、COL3=1、COL4=1, | + | |
- | 通过上面的描述:在这一时刻只有K1、K2、K3、K4按键被按下,才会导致4根列线输出COL1=0、COL2=0、COL3=0、COL4=0,否则COL1=1、COL2=1、COL3=1、COL4=1,反之当FPGA检测到列线(COL1、COL2、COL3、COL4)中有低电平信号时,对应的K1、K2、K3、K4按键应该是被按下了。 | + | |
- | + | ||
- | 按照扫描的方式,一共分为4个时刻,分别对应4根行线中的一根拉低,4个时刻依次循环,这样就完成了矩阵按键的全部扫描检测,我们在程序中以这4个时刻对应状态机的4个状态。 | + | |
- | 至于循环的周期,根据我们基础教程里可知,按键抖动的不稳定时间在10ms以内,所以对同一个按键采样的周期大于10ms,这同样取20ms时间。20ms时间对应4个状态,每5分钟进行一次状态转换。 | + | |
\\ | \\ | ||
- | {{ :矩阵按键程序框图.jpg?800 |}} | + | * 起始位:先发出一个逻辑”0”信号,表示传输字符的开始。 |
+ | * 数据位:可以是5~8位逻辑”0”或”1”。如ASCII码(7位),扩展BCD码(8位)。小端传输 | ||
+ | * 校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验) | ||
+ | * 停止位:它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。 | ||
+ | * 空闲位:处于逻辑“1”状态,表示当前线路上没有资料传送。 | ||
\\ | \\ | ||
- | {{ :矩阵按键扫描法原理.jpg |}} | + | 我们这里使用的时序为去掉校验位的时序 |
+ | {{ :uart4.png?600 |}} | ||
\\ | \\ | ||
+ | 本设计共有四个模块,一个top模块,一个baud模块,一个接收模块和一个发送模块,大家可以根据自己的需求进行调整。 | ||
+ | |||
====Verilog代码==== | ====Verilog代码==== | ||
+ | |||
------ | ------ | ||
<code verilog> | <code verilog> | ||
- | |||
// -------------------------------------------------------------------- | // -------------------------------------------------------------------- | ||
// >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<< | // >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<< | ||
// -------------------------------------------------------------------- | // -------------------------------------------------------------------- | ||
- | // Module: Array_KeyBoard | + | // Module: Uart_bus |
// | // | ||
// Author: Step | // Author: Step | ||
// | // | ||
- | // Description: Array_KeyBoard | + | // Description: The module for uart communication |
// | // | ||
// Web: www.stepfapga.com | // Web: www.stepfapga.com | ||
- | // | + | // |
// -------------------------------------------------------------------- | // -------------------------------------------------------------------- | ||
// Code Revision History : | // Code Revision History : | ||
// -------------------------------------------------------------------- | // -------------------------------------------------------------------- | ||
// Version: |Mod. Date: |Changes Made: | // Version: |Mod. Date: |Changes Made: | ||
- | // V1.0 |2015/11/11 |Initial ver | + | // V1.0 |2016/04/20 |Initial ver |
// -------------------------------------------------------------------- | // -------------------------------------------------------------------- | ||
- | module Array_KeyBoard # | + | module Uart_Bus # |
( | ( | ||
- | parameter NUM_FOR_200HZ = 60000 //定义计数器cnt的计数范围,例化时可更改 | + | parameter BPS_PARA = 1250 //当使用12MHz时钟时波特率参数选择1250对应9600的波特率 |
) | ) | ||
( | ( | ||
- | input clk_in, //系统时钟 | + | input clk_in, //系统时钟 |
- | input rst_n_in, //系统复位,低有效 | + | input rst_n_in, //系统复位,低有效 |
- | input [3:0] col, //矩阵按键列接口 | + | input rs232_rx, //FPGA中UART接收端,分配给UART模块中的发送端TXD |
- | output reg [3:0] row, //矩阵按键行接口 | + | output rs232_tx //FPGA中UART发送端,分配给UART模块中的接收端RXD |
- | output reg [15:0] key_out //消抖后的信号 | + | ); |
+ | |||
+ | /////////////////////////////////UART接收功能模块例化//////////////////////////////////// | ||
+ | wire bps_en_rx,bps_clk_rx; | ||
+ | wire [7:0] rx_data; | ||
+ | |||
+ | //UART接收波特率时钟控制模块 例化 | ||
+ | Baud # | ||
+ | ( | ||
+ | .BPS_PARA (BPS_PARA ) | ||
+ | ) | ||
+ | Baud_rx | ||
+ | ( | ||
+ | .clk_in (clk_in ), //系统时钟 | ||
+ | .rst_n_in (rst_n_in ), //系统复位,低有效 | ||
+ | .bps_en (bps_en_rx ), //接收时钟使能 | ||
+ | .bps_clk (bps_clk_rx ) //接收时钟输出 | ||
); | ); | ||
- | /* | ||
- | 因使用4x4矩阵按键,通过扫描方法实现,所以这里使用状态机实现,共分为4种状态 | ||
- | 在其中的某一状态时间里,对应的4个按键相当于独立按键,可按独立按键的周期采样法采样 | ||
- | 周期采样时每隔20ms采样一次,对应这里状态机每隔20ms循环一次,每个状态对应5ms时间 | ||
- | 对矩阵按键实现原理不明白的,请去了解矩阵按键实现原理 | ||
- | */ | ||
- | localparam STATE0 = 2'b00; | ||
- | localparam STATE1 = 2'b01; | ||
- | localparam STATE2 = 2'b10; | ||
- | localparam STATE3 = 2'b11; | ||
- | //计数器计数分频实现5ms周期信号clk_200hz | + | //UART接收数据模块 例化 |
- | reg [15:0] cnt; | + | Uart_Rx Uart_Rx_uut |
- | reg clk_200hz; | + | ( |
- | always@(posedge clk_in or negedge rst_n_in) begin | + | .clk_in (clk_in ), //系统时钟 |
- | if(!rst_n_in) begin //复位时计数器cnt清零,clk_200hz信号起始电平为低电平 | + | .rst_n_in (rst_n_in ), //系统复位,低有效 |
- | cnt <= 16'd0; | + | .bps_en (bps_en_rx ), //接收时钟使能 |
- | clk_200hz <= 1'b0; | + | .bps_clk (bps_clk_rx ), //接收时钟输入 |
- | end else begin | + | .rs232_rx (rs232_rx ), //UART接收输入 |
- | if(cnt >= ((NUM_FOR_200HZ>>1) - 1)) begin //数字逻辑中右移1位相当于除2 | + | .rx_data (rx_data ) //接收到的数据 |
- | cnt <= 16'd0; | + | ); |
- | clk_200hz <= ~clk_200hz; //clk_200hz信号取反 | + | |
- | end else begin | + | |
- | cnt <= cnt + 1'b1; | + | /////////////////////////////////UART发送功能模块例化//////////////////////////////////// |
- | clk_200hz <= clk_200hz; | + | wire bps_en_tx,bps_clk_tx; |
- | end | + | |
- | end | + | //UART发送波特率时钟控制模块 例化 |
+ | Baud # | ||
+ | ( | ||
+ | .BPS_PARA (BPS_PARA ) | ||
+ | ) | ||
+ | Baud_tx | ||
+ | ( | ||
+ | .clk_in (clk_in ), //系统时钟 | ||
+ | .rst_n_in (rst_n_in ), //系统复位,低有效 | ||
+ | .bps_en (bps_en_tx ), //发送时钟使能 | ||
+ | .bps_clk (bps_clk_tx ) //发送时钟输出 | ||
+ | ); | ||
+ | |||
+ | //UART发送数据模块 例化 | ||
+ | Uart_Tx Uart_Tx_uut | ||
+ | ( | ||
+ | .clk_in (clk_in ), //系统时钟 | ||
+ | .rst_n_in (rst_n_in ), //系统复位,低有效 | ||
+ | .bps_en (bps_en_tx ), //发送时钟使能 | ||
+ | .bps_clk (bps_clk_tx ), //发送时钟输入 | ||
+ | .rx_bps_en (bps_en_rx ), //因需要自收自发,使用接收时钟使能判定:接收到新的数据,需要发送 | ||
+ | .tx_data (rx_data ), //需要发出的数据 | ||
+ | .rs232_tx (rs232_tx ) //UART发送输出 | ||
+ | ); | ||
+ | |||
+ | endmodule | ||
+ | </code> | ||
+ | \\ | ||
+ | <code verilog> | ||
+ | // -------------------------------------------------------------------- | ||
+ | // >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<< | ||
+ | // -------------------------------------------------------------------- | ||
+ | // Module: Baud | ||
+ | // | ||
+ | // Author: Step | ||
+ | // | ||
+ | // Description: Beat for uart transfer and receive baud rate | ||
+ | // | ||
+ | // Web: www.stepfapga.com | ||
+ | // | ||
+ | // -------------------------------------------------------------------- | ||
+ | // Code Revision History : | ||
+ | // -------------------------------------------------------------------- | ||
+ | // Version: |Mod. Date: |Changes Made: | ||
+ | // V1.0 |2016/04/20 |Initial ver | ||
+ | // -------------------------------------------------------------------- | ||
+ | module Baud # | ||
+ | ( | ||
+ | parameter BPS_PARA = 1250 //当使用12MHz时钟时波特率参数选择1250对应9600的波特率 | ||
+ | ) | ||
+ | ( | ||
+ | input clk_in, //系统时钟 | ||
+ | input rst_n_in, //系统复位,低有效 | ||
+ | input bps_en, //接收或发送时钟使能 | ||
+ | output reg bps_clk //接收或发送时钟输出 | ||
+ | ); | ||
+ | |||
+ | reg [12:0] cnt; | ||
+ | //计数器计数满足波特率时钟要求 | ||
+ | always @ (posedge clk_in or negedge rst_n_in) begin | ||
+ | if(!rst_n_in) | ||
+ | cnt <= 1'b0; | ||
+ | else if((cnt >= BPS_PARA-1)||(!bps_en)) //当时钟信号不使能(bps_en为低电平)时,计数器清零并停止计数 | ||
+ | cnt <= 1'b0; //当时钟信号使能时,计数器对系统时钟计数,周期为BPS_PARA个系统时钟周期 | ||
+ | else | ||
+ | cnt <= cnt + 1'b1; | ||
+ | end | ||
+ | |||
+ | //产生相应波特率的时钟节拍,接收模块将以此节拍进行UART数据接收 | ||
+ | always @ (posedge clk_in or negedge rst_n_in) | ||
+ | begin | ||
+ | if(!rst_n_in) | ||
+ | bps_clk <= 1'b0; | ||
+ | else if(cnt == (BPS_PARA>>1)) //BPS_PARA右移一位等于除2,因计数器终值BPS_PARA为数据更替时间点,所以计数器中值时为数据最稳定时间点 | ||
+ | bps_clk <= 1'b1; | ||
+ | else | ||
+ | bps_clk <= 1'b0; | ||
end | end | ||
- | reg [1:0] c_state; | + | endmodule |
- | //状态机根据clk_200hz信号在4个状态间循环,每个状态对矩阵按键的行接口单行有效 | + | </code> |
- | always@(posedge clk_200hz or negedge rst_n_in) begin | + | \\ |
- | if(!rst_n_in) begin | + | <code verilog> |
- | c_state <= STATE0; | + | // -------------------------------------------------------------------- |
- | row <= 4'b1110; | + | // >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<< |
- | end else begin | + | // -------------------------------------------------------------------- |
- | case(c_state) | + | // Module: Uart_Rx |
- | STATE0: begin c_state <= STATE1; row <= 4'b1101; end //状态c_state跳转及对应状态下矩阵按键的row输出 | + | // |
- | STATE1: begin c_state <= STATE2; row <= 4'b1011; end | + | // Author: Step |
- | STATE2: begin c_state <= STATE3; row <= 4'b0111; end | + | // |
- | STATE3: begin c_state <= STATE0; row <= 4'b1110; end | + | // Description: The receive module of uart interface |
- | default:begin c_state <= STATE0; row <= 4'b1110; end | + | // |
- | endcase | + | // Web: www.stepfapga.com |
- | end | + | // |
+ | // -------------------------------------------------------------------- | ||
+ | // Code Revision History : | ||
+ | // -------------------------------------------------------------------- | ||
+ | // Version: |Mod. Date: |Changes Made: | ||
+ | // V1.0 |2016/04/20 |Initial ver | ||
+ | // -------------------------------------------------------------------- | ||
+ | module Uart_Rx | ||
+ | ( | ||
+ | input clk_in, //系统时钟 | ||
+ | input rst_n_in, //系统复位,低有效 | ||
+ | |||
+ | output reg bps_en, //接收时钟使能 | ||
+ | input bps_clk, //接收时钟输入 | ||
+ | |||
+ | input rs232_rx, //UART接收输入 | ||
+ | output reg [7:0] rx_data //接收到的数据 | ||
+ | ); | ||
+ | |||
+ | reg rs232_rx0,rs232_rx1,rs232_rx2; | ||
+ | //多级延时锁存去除亚稳态 | ||
+ | always @ (posedge clk_in or negedge rst_n_in) begin | ||
+ | if(!rst_n_in) begin | ||
+ | rs232_rx0 <= 1'b0; | ||
+ | rs232_rx1 <= 1'b0; | ||
+ | rs232_rx2 <= 1'b0; | ||
+ | end else begin | ||
+ | rs232_rx0 <= rs232_rx; | ||
+ | rs232_rx1 <= rs232_rx0; | ||
+ | rs232_rx2 <= rs232_rx1; | ||
end | end | ||
- | + | end | |
- | //因为每个状态中单行有效,通过对列接口的电平状态采样得到对应4个按键的状态,依次循环 | + | |
- | always@(negedge clk_200hz or negedge rst_n_in) begin | + | //检测UART接收输入信号的下降沿 |
- | if(!rst_n_in) begin | + | wire neg_rs232_rx = rs232_rx2 & rs232_rx1 & (~rs232_rx0) & (~rs232_rx); |
- | key_out <= 16'hffff; | + | |
- | end else begin | + | reg [3:0] num; |
- | case(c_state) | + | //接收时钟使能信号的控制 |
- | STATE0:key_out[3:0] <= col; //采集当前状态的列数据赋值给对应的寄存器位 | + | always @ (posedge clk_in or negedge rst_n_in) begin |
- | STATE1:key_out[7:4] <= col; | + | if(!rst_n_in) |
- | STATE2:key_out[11:8] <= col; | + | bps_en <= 1'b0; |
- | STATE3:key_out[15:12] <= col; | + | else if(neg_rs232_rx && (!bps_en)) //当空闲状态(bps_en为低电平)时检测到UART接收信号下降沿,进入工作状态(bps_en为高电平),控制时钟模块产生接收时钟 |
- | default:key_out <= 16'hffff; | + | bps_en <= 1'b1; |
- | endcase | + | else if(num==4'd9) //当完成一次UART接收操作后,退出工作状态,恢复空闲状态 |
+ | bps_en <= 1'b0; | ||
+ | end | ||
+ | |||
+ | reg [7:0] rx_data_r; | ||
+ | //当处于工作状态中时,按照接收时钟的节拍获取数据 | ||
+ | always @ (posedge clk_in or negedge rst_n_in) begin | ||
+ | if(!rst_n_in) begin | ||
+ | num <= 4'd0; | ||
+ | rx_data <= 8'd0; | ||
+ | rx_data_r <= 8'd0; | ||
+ | end else if(bps_en) begin | ||
+ | if(bps_clk) begin | ||
+ | num <= num+1'b1; | ||
+ | if(num<=4'd8) | ||
+ | rx_data_r[num-1]<=rs232_rx; //先接受低位再接收高位,8位有效数据 | ||
+ | end else if(num == 4'd9) begin //完成一次UART接收操作后,将获取的数据输出 | ||
+ | num <= 4'd0; | ||
+ | rx_data <= rx_data_r; | ||
end | end | ||
end | end | ||
+ | end | ||
endmodule | endmodule | ||
</code> | </code> | ||
+ | \\ | ||
+ | <code verilog> | ||
+ | // -------------------------------------------------------------------- | ||
+ | // >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<< | ||
+ | // -------------------------------------------------------------------- | ||
+ | // Module: Uart_Tx | ||
+ | // | ||
+ | // Author: Step | ||
+ | // | ||
+ | // Description: The transfer module of uart interface | ||
+ | // | ||
+ | // Web: www.stepfapga.com | ||
+ | // | ||
+ | // -------------------------------------------------------------------- | ||
+ | // Code Revision History : | ||
+ | // -------------------------------------------------------------------- | ||
+ | // Version: |Mod. Date: |Changes Made: | ||
+ | // V1.0 |2016/04/20 |Initial ver | ||
+ | // -------------------------------------------------------------------- | ||
+ | module Uart_Tx | ||
+ | ( | ||
+ | input clk_in, //系统时钟 | ||
+ | input rst_n_in, //系统复位,低有效 | ||
+ | output reg bps_en, //发送时钟使能 | ||
+ | input bps_clk, //发送时钟输入 | ||
+ | input rx_bps_en, //因需要自收自发,使用接收时钟使能判定:接收到新的数据,需要发送 | ||
+ | input [7:0] tx_data, //需要发出的数据 | ||
+ | output reg rs232_tx //UART发送输出 | ||
+ | ); | ||
- | \\ | + | reg rx_bps_en_r; |
- | \\ | + | //延时锁存接收时钟使能信号 |
+ | always @ (posedge clk_in or negedge rst_n_in) begin | ||
+ | if(!rst_n_in) rx_bps_en_r <= 1'b0; | ||
+ | else rx_bps_en_r <= rx_bps_en; | ||
+ | end | ||
+ | |||
+ | //检测接收时钟使能信号的下降沿,因为下降沿代表接收数据的完成,以此作为发送信号的激励 | ||
+ | wire neg_rx_bps_en = rx_bps_en_r & (~rx_bps_en); | ||
+ | |||
+ | reg [3:0] num; | ||
+ | reg [9:0] tx_data_r; | ||
+ | //根据接收数据的完成,驱动发送数据操作 | ||
+ | always @ (posedge clk_in or negedge rst_n_in) begin | ||
+ | if(!rst_n_in) begin | ||
+ | bps_en <= 1'b0; | ||
+ | tx_data_r <= 8'd0; | ||
+ | end else if(neg_rx_bps_en)begin | ||
+ | bps_en <= 1'b1; //当检测到接收时钟使能信号的下降沿,表明接收完成,需要发送数据,使能发送时钟使能信号 | ||
+ | tx_data_r <= {1'b1,tx_data,1'b0}; | ||
+ | end else if(num==4'd10) begin | ||
+ | bps_en <= 1'b0; //一次UART发送需要10个时钟信号,然后结束 | ||
+ | end | ||
+ | end | ||
+ | //当处于工作状态中时,按照发送时钟的节拍发送数据 | ||
+ | always @ (posedge clk_in or negedge rst_n_in) begin | ||
+ | if(!rst_n_in) begin | ||
+ | num <= 1'b0; | ||
+ | rs232_tx <= 1'b1; | ||
+ | end else if(bps_en) begin | ||
+ | if(bps_clk) begin | ||
+ | num <= num + 1'b1; | ||
+ | rs232_tx <= tx_data_r[num]; | ||
+ | end else if(num>=4'd10) | ||
+ | num <= 4'd0; | ||
+ | end | ||
+ | end | ||
+ | |||
+ | endmodule | ||
+ | |||
+ | </code> | ||
====小结==== | ====小结==== | ||
+ | |||
------ | ------ | ||
- | 本节主要为大家讲解了矩阵按键的工作原理及软件设计,需要大家掌握的同时自己创建工程,通过整个设计流程,生成FPGA配置文件加载测试。 | + | 本节主要为大家讲解了[[UART]]通信的原理及软件设计,需要大家掌握的同时自己创建工程,通过整个设计流程,生成[[FPGA]]配置文件加载测试。 |
\\ | \\ | ||
如果你对Diamond软件的使用不了解,请参考这里:[[lattice_diamond的使用|Diamond的使用]]。 | 如果你对Diamond软件的使用不了解,请参考这里:[[lattice_diamond的使用|Diamond的使用]]。 | ||
====相关资料==== | ====相关资料==== | ||
+ | |||
------ | ------ | ||
\\ | \\ | ||
- | 使用[[STEP-MXO2第二代]]的矩阵按键程序: 后续会有下载连接 待更新 | + | 使用[[STEP-MXO2第二代]]的UART通信程序: 后续会有下载连接 待更新 |
\\ | \\ | ||
- | 使用[[STEP-MAX10]]的矩阵按键程序: 后续会有下载连接 待更新 | + | 使用[[STEP-MAX10]]的UART通信程序: 后续会有下载连接 待更新 |
\\ | \\ |