fpga串口_串口fpga - CSDN
  • FPGA:实现串行接口 RS232串行接口(RS-232)串行接口是连接FPGA和PC机的一种简单方式。这个项目向大家展示了如果使用FPGA来创建RS-232收发器。整个项目包括5个部分1. RS232是怎样工作的2. 如何产生需要的波特率3....

    FPGA:实现串行接口 RS232
    串行接口(RS-232)
    串行接口是连接FPGAPC机的一种简单方式。这个项目向大家展示了如果使用FPGA来创建RS-232收发器。

    整个项目包括5个部分
    3.     发送模块
    4.     接收模块
    5.     应用实例
    RS-232接口是怎样工作的
    作为标准设备,大多数的计算机都有12RS-232串口。

    特性
    RS-232有下列特性:
    ·        使用9针的"DB-9"插头(旧式计算机使用25针的"DB-25"插头).
    ·        允许全双工的双向通讯(也就是说计算机可以在接收数据的同时发送数据).
    ·        最大可支持的传输速率为10KBytes/s.
    DB-9插头
    你可能已经在你的计算机背后见到过这种插头

    它一共有9个引脚,但是最重要的3个引脚是:
    ·        引脚2: RxD (接收数据).
    ·        引脚3: TxD (发送数据).
    ·        引脚5: GND ().
    仅使用3跟电缆,你就可以发送和接收数据.

    串行通讯
    数据以每次一位的方式传输;每条线用来传输一个方向的数据。由于计算机通常至少需要若干位数据,因此数据在发送之前先串行化。通常是以8位数据为1组的。。先发送最低有效位,最后发送最高有效位。

    异步通讯
    RS-232使用异步通讯协议。也就是说数据的传输没有时钟信号。接收端必须有某种方式,使之与接收数据同步。
    对于RS-232来说,是这样处理的:
    1.     串行线缆的两端事先约定好串行传输的参数(传输速度、传输格式等)
    2.     当没有数据传输的时候,发送端向数据线上发送"1"
    3.     每传输一个字节之前,发送端先发送一个"0"来表示传输已经开始。这样接收端便可以知道有数据到来了。
    4.     开始传输后,数据以约定的速度和格式传输,所以接收端可以与之同步
    5.     每次传输完成一个字节之后,都在其后发送一个停止位("1")
    让我们来看看0x55是如何传输的:01010101

    0x55的二进制表示为:01010101
    但是由于先发送的是最低有效位,所以发送序列是这样的: 1-0-1-0-1-0-1-0.
    下面是另外一个例子 :

    传输的数据为0xC4,你能看出来吗?11000100
    从图中很难看出来所传输的数据,这也说明了事先知道传输的速率对于接收端有多么重要。(串口发送的开始位为0,结束位为1,空闲line是为高

    数据传输可以多快?
    数据的传输速度是用波特来描述的,亦即每秒钟传输的数据位,例如1000波特表示每秒钟传输100比特的数据,或者说每个数据位持续1毫秒(1/1000) = 1ms
    波特率不是随意的,必须服从一定的标准,如果希望设计123456波特的RS-232接口,对不起,你很不幸运,这是不行的。常用的串行传输速率值包括以下几种:
    ·        1200 波特.
    ·        9600 波特.
    ·        38400 波特.
    ·        115200 波特 (通常情况下是你可以使用的最高速度).
    115200波特传输速度下,每位数据持续 (1/115200) = 8.7μs.如果传输8位数据,共持续 8 x 8.7μs = 69μs。但是每个字节的传输又要求额外的开始位停止位”,所以实际上需要花费10 x 8.7μs = 87μs的时间。最大的有效数据传输率只能达到 11.5KBytes每秒。
    115200波特传输速度下,一些使用了不好的芯片的计算机要求一个长的停止位(1.52位数据的长度),这使得最大传输速度降到大约10.5KBytes每秒

    物理层
    电缆上的信号使用正负电压的机制:
    ·        "1" -10V的电压表示(或者在 -5V -15V之间的电压).
    ·        "0" +10V的电压表示(或者在 5V 15V之间的电压).
    所以没有数据传输的电缆上的电压应该为-10V-5-10之间的某个电压。
     
    波特率发生器
    这里我们使用串行连接的最大速度115200波特,其他较慢的波特也很容易由此产生。
    FPGA通常运行在远高于115200Hz的时钟频率上(对于今天的标准的来说RS-232真是太慢了),这就意味着我们需要用一个较高的时钟来分频产生尽量接近于115200Hz的时钟信号。
    1.8432MHz的时钟产生
    通常RS-232芯片使用1.8432MHz的时钟,以为这个时钟很容易产生标准的波特率,所以我们假设已经拥有了一个这样的时钟源。
    只需要将 1.8432MHz 16分频便可得到 115200Hz的时钟,多方便啊!
    reg [3:0] BaudDivCnt;1843200/16=115200
    always @(posedge clk) BaudDivCnt <= BaudDivCnt + 1;
    wire BaudTick = (BaudDivCnt==15);
    所以 "BaudTick"16个时钟周期需要置位一次,从而从1.8432MHz的时钟得到115200Hz的时钟。

    从任意频率产生
    早期的发生器假设使用1.8432MHz的时钟。但如果我们使用2MHz的时钟怎么办呢?要从2MHz的时钟得到 115200Hz,需要将时钟 "17.361111111..."分频,并不是一个整数。我的解决办法是有时候17分频,有时候18分频,使得整体的分频比保持在 "17.361111111"。这是很容易做到的。
    下面是实现这个想法的C语言代码:
    while(1) //死循环
    {
    acc += 115200;        
    if(acc >=2000000) printf("*"); else printf(" ");
    acc %= 2000000;
    }
    这段代码会精确的以平均每 "17.361111111..."个时钟间隔打印出一个"*"
    为了从FPGA得到同样的效果,考虑到串行接口可以容忍一定的波特率误差,所以即使我们使用17.3或者17.4这样的分频比也是没有关系的。

    FPGA波特率发生器
    我们希望20000002的整数幂,但很可惜,它不是。所以我们改变分频比,"2000000/115200"约等于 "1024/59" = 17.356.这跟我们要求的分频比很接近,并且使得在FPGA上实现起来相当有效。
    //10位的累加器 ([9:0]), 1位进位输出 ([10])
    reg [10:0] acc; //一共11!
    always @(posedge clk)
    acc <= acc[9:0] + 59; //我们使用上一次结果的低10位,但是保留11位结果
    wire BaudTick = acc[10]; //11位作为进位输出,这里的方法用的非常好,可以作为分频始终来用
    使用 2MHz时钟, "BaudTick" 115234波特,跟理想的115200波特存在 0.03%的误差。

    参数化的FPGA波特率发生器
    前面的设计我们使用的是10位的累加器,如果时钟频率提高的话,需要更多的位数。
    下面是一个使用 25MHz时钟和 16位累加器的设计,该设计是参数化的,所以很容易根据具体情况修改。
    parameter ClkFrequency = 25000000; // 25MHz  FPGA的工作频率
    parameter Baud = 115200;
    parameter BaudGeneratorAccWidth = 16;
    parameter BaudGeneratorInc = (Baud<<BaudGeneratorAccWidth)/ClkFrequency;//左移的话意味着乘以2的几次方
    reg [BaudGeneratorAccWidth:0] BaudGeneratorAcc; //留出一位用于分频进位用的
    always @(posedge clk)
    BaudGeneratorAcc <= BaudGeneratorAcc[BaudGeneratorAccWidth-1:0] + BaudGeneratorInc;
    wire BaudTick = BaudGeneratorAcc[BaudGeneratorAccWidth];//这个式子就是我们分频的结果,达到了由25MHZ分频到115200HZ的效果
    上面的设计中存在一个错误: "BaudGeneratorInc"的计算是错误的,因为 Verilog使用 32位的默认结果,但实际计算过程中的某些数据超过了32位,所以改变一种计算方法。
    parameter BaudGeneratorInc = ((Baud<<(BaudGeneratorAccWidth-4))+(ClkFrequency>>5))/(ClkFrequency>>4);
    这行程序也使得结果成为整数,从而避免截断。
    这就是整个的设计方法了。
    现在我们已经得到了足够精确的波特率,可以继续设计串行接收和发送模块了。
     
    RS-232发送模块
    下面是我们所想要实现的:

    它应该能像这样工作:
    ·        发送器接收8位的数据,并将其串行输出。("TxD_start"置位后开始传输).
    ·        当有数传输的时候,使"busy"信号有效,此时“TxD_start”信号被忽略.
    RS-232模块的参数是固定的: 8位数据, 2个停止位,无奇偶校验.

    数据串行化
    假设我们已经有了一个115200波特的"BaudTick"信号.
    我们需要产生开始位、8位数据以及停止位。
    用状态机来实现看起来比较合适。
    reg [3:0] state;
    always @(posedge clk)
    case(state)
    4'b0000: if(TxD_start) state <= 4'b0100;
    4'b0100: if(BaudTick) state <= 4'b1000; //开始位
    4'b1000: if(BaudTick) state <= 4'b1001; // bit 0
    4'b1001: if(BaudTick) state <= 4'b1010; // bit 1
    4'b1010: if(BaudTick) state <= 4'b1011; // bit 2
    4'b1011: if(BaudTick) state <= 4'b1100; // bit 3
    4'b1100: if(BaudTick) state <= 4'b1101; // bit 4
    4'b1101: if(BaudTick) state <= 4'b1110; // bit 5
    4'b1110: if(BaudTick) state <= 4'b1111; // bit 6
    4'b1111: if(BaudTick) state <= 4'b0001; // bit 7
    4'b0001: if(BaudTick) state <= 4'b0010; //停止位1
    4'b0010: if(BaudTick) state <= 4'b0000; //停止位2
    default: if(BaudTick) state <= 4'b0000;
    endcase
    注意看这个状态机是怎样实现当"TxD_start"有效就开始,但只在"BaudTick"有效的时候才转换状态的。.
    现在,我们只需要产生"TxD"输出即可.
    reg muxbit;
    always @(state[2:0])
    case(state[2:0])
    0: muxbit <= TxD_data[0];
    1: muxbit <= TxD_data[1];
    2: muxbit <= TxD_data[2];
    3: muxbit <= TxD_data[3];
    4: muxbit <= TxD_data[4];
    5: muxbit <= TxD_data[5];
    6: muxbit <= TxD_data[6];
    7: muxbit <= TxD_data[7];
    endcase
    //将开始位、数据以及停止位结合起来
    assign TxD = (state<4) | (state[3] & muxbit);
     
    RS232接收模块
    下面是我们想要实现的模块:

    我们的设计目的是这样的:
         1.RxD线上有数据时,接收模块负责识别RxD线上的数据
         2.当收到一个字节的数据时,锁存接收到的数据到"data"总线,并使"data_ready"有效一个周期。
    注意:只有当"data_ready"有效时,"data"总线的数据才有效,其他的时间里不要使用"data"总线上的数据,因为新的数据可能已经改变了其中的部分数据。
    过采样
    异步接收机必须通过一定的机制与接收到的输入信号同步(接收端没有办法得到发送断的时钟)。这里采用如下办法。
         1.为了确定新数据的到来,即检测开始位,我们使用几倍于波特率的采样时钟对接收到的信号进行采样。
         2.一旦检测到"开始位",再将采样时钟频率降为已知的发送端的波特率。
    典型的过采样时钟频率为接收到的信号的波特率的16倍,这里我们使用8倍的采样时钟。当波特率为115200时,采样时钟为921600Hz。(115200*8=921600
    假设我们已经有了一个8倍于波特率的时钟信号 "Baud8Tick",其频率为 921600Hz

    具体设计
    首先,接受到的"RxD"信号与我们的时钟没有任何关系,所以采用两个D触发器对其进行过采样,并且使之我我们的时钟同步。
    reg [1:0] RxD_sync;
    always @(posedge clk) if(Baud8Tick) RxD_sync <= {RxD_sync[0], RxD};
    首先我们对接收到的数据进行滤波,这样可以防止毛刺信号被误认为是开始信号。
    reg [1:0] RxD_cnt;
    reg RxD_bit;
    always @(posedge clk)
    if(Baud8Tick)
    begin
    if(RxD_sync[1] && RxD_cnt!=2'b11) RxD_cnt <= RxD_cnt + 1;
    else
    if(~RxD_sync[1] && RxD_cnt!=2'b00) RxD_cnt <= RxD_cnt - 1;
    if(RxD_cnt==2'b00) RxD_bit <= 0;
    else
    if(RxD_cnt==2'b11) RxD_bit <= 1;
    end
    一旦检测到"开始位",使用如下的状态机可以检测出接收到每一位数据。
    reg [3:0] state;
    always @(posedge clk)
    if(Baud8Tick)
    case(state)
    4'b0000: if(~RxD_bit) state <= 4'b1000; // start bit found?
    4'b1000: if(next_bit) state <= 4'b1001; // bit 0
    4'b1001: if(next_bit) state <= 4'b1010; // bit 1
    4'b1010: if(next_bit) state <= 4'b1011; // bit 2
    4'b1011: if(next_bit) state <= 4'b1100; // bit 3
    4'b1100: if(next_bit) state <= 4'b1101; // bit 4
    4'b1101: if(next_bit) state <= 4'b1110; // bit 5
    4'b1110: if(next_bit) state <= 4'b1111; // bit 6
    4'b1111: if(next_bit) state <= 4'b0001; // bit 7
    4'b0001: if(next_bit) state <= 4'b0000; // stop bit
    default: state <= 4'b0000;
    endcase
    注意,我们使用了"next_bit"来遍历所有数据位。
    reg [2:0] bit_spacing;
    always @(posedge clk)
    if(state==0)
    bit_spacing <= 0;
    else
    if(Baud8Tick)
    bit_spacing <= bit_spacing + 1;
    wire next_bit = (bit_spacing==7);
    最后我们使用一个移位寄存器来存储接受到的数据。
    reg [7:0] RxD_data;
    always @(posedge clk) if(Baud8Tick && next_bit && state[3]) RxD_data <= {RxD_bit, RxD_data[7:1]};
     
     
    怎样使用发送和接收模块
    这个设计似的我们可以通过计算机的串行口来控制FPGA的几个引脚。
    具体来说,该设计完成以下功能。
       1.FPGA8个引脚作为输出(称为通用输出)。 FPGA收到任何数据时都会更新这8GPout的值。
       2.FPGA8个引脚作为输入(称为通用输入)。FPGA收到任何数据后,都会将GPin上的数值通过串行口发送出去。
    通用输出可以用来通过计算机远程控制任何东西,例如FPGA板上的LED,甚至可以再添加一个继电器来控制咖啡机。
    module serialfun(clk, RxD, TxD, GPout, GPin);
    input clk;
    input RxD;
    output TxD;
    output [7:0] GPout;
    input [7:0] GPin;
    ///////////////////////////////////////////////////
    wire RxD_data_ready;
    wire [7:0] RxD_data;
    async_receiver deserializer(.clk(clk), .RxD(RxD), .RxD_data_ready(RxD_data_ready), .RxD_data(RxD_data));
    reg [7:0] GPout;
    always @(posedge clk) if(RxD_data_ready) GPout <= RxD_data;
    ///////////////////////////////////////////////////
    async_transmitter serializer(.clk(clk), .TxD(TxD), .TxD_start(RxD_data_ready), .TxD_data(GPin));
    endmodule
    记得包含异步发送和接收模块的设计文件,并更新里面的时钟频率。

    展开全文
  • FPGA串口发送16位数

    2019-05-08 20:49:17
    近期调试FPGA,用到串口模块,之前没写过串口发送16位数的实验,最近调试通了,上来分享一下。 主要思想其实和正常的串口发送模块差不多,就是多定义几个状态变量,设置好时序就行。本模块实现的功能是:时钟来之后...
    近期调试FPGA,用到串口模块,之前没写过串口发送16位数的实验,最近调试通了,上来分享一下。
    主要思想其实和正常的串口发送模块差不多,就是多定义几个状态变量,设置好时序就行。本模块实现的功能是:时钟来之后开始计数,计到一定值之后启动串口发送模块,将数发送出去。就是很简单的串口发送模块,主要提供给初学者参考。
    
    module UART_Tx(
    	input       clk,
    	input       rst_n,
    	
    	output reg  txd
    );
    
    parameter S_IDLE = 5'd0;             //空闲状态
    parameter S_START_L = 5'd1;          //发送低八位起始位
    parameter S_BIT0_L = 5'd2;           //发送低八位数据位
    parameter S_BIT1_L = 5'd3;
    parameter S_BIT2_L = 5'd4;
    parameter S_BIT3_L = 5'd5;
    parameter S_BIT4_L = 5'd6;
    parameter S_BIT5_L = 5'd7;
    parameter S_BIT6_L = 5'd8;
    parameter S_BIT7_L = 5'd9;
    parameter S_STOP_L = 5'd10;          //发送低八位停止位
    
    parameter S_WAIT = 5'd11;            //等待一个串口时钟周期
    
    parameter S_START_H = 5'd12;          //发送高八位起始位
    parameter S_BIT0_H = 5'd13;           //发送高八位数据位
    parameter S_BIT1_H = 5'd14;
    parameter S_BIT2_H = 5'd15;
    parameter S_BIT3_H = 5'd16;
    parameter S_BIT4_H = 5'd17;
    parameter S_BIT5_H = 5'd18;
    parameter S_BIT6_H = 5'd19;
    parameter S_BIT7_H = 5'd20;
    parameter S_STOP_H = 5'd21;          //发送高八位停止位
    
    parameter CLK_F = 125000000;   //system clock frequency
    parameter UART_BPS = 9600;    //串口波特率
    localparam BPS_CNT = CLK_F/UART_BPS;  //相应波特率分频计数器
    
    parameter cnt=60000;                    //计数器记到cnt值后串口输出该值
    
    reg [4:0] state;
    reg[15:0] bit_timer;           //用于控制波特率的计数器如果波特率是115200,每个数据位50000000/115200个时钟周期
    reg[15:0] tx_data;             //串口发送接口
    reg[7:0]  tx_data_h;           //串口发送的高八位数据
    reg[7:0]  tx_data_l;           //串口发送的低八位数据
    
    
    always@(posedge clk or negedge rst_n)begin
    	if(!rst_n)begin
    		state <= S_IDLE;
    		bit_timer <= 16'd0;
    		txd <= 1'b1;
    		tx_data <= 16'd0;
    	end
    	else begin
    		case(state)
    			S_IDLE:begin
    				txd <= 1'b1;
    				bit_timer <= 16'd0;
    				tx_data <= tx_data + 16'd1;
    				if(tx_data == cnt)begin
    					tx_data_h <= tx_data[15:8];
    					tx_data_l <= tx_data[7:0];
    					//tx_data <= 0;
    					state <= S_START_L;
    				end
    				else
    					state <= state;
    			end
    			
    			S_START_L:begin
    				txd <= 1'b0;
    				if(bit_timer==BPS_CNT)begin
    					state <= S_BIT0_L;
    					bit_timer <= 16'd0;
    				end
    				else begin
    					state <= state;
    					bit_timer <= bit_timer + 16'd1;
    				end
    			end
    			
    			S_BIT0_L:begin
    				//txd <= tx_data[0];
    				txd <= tx_data_l[0];
    				if(bit_timer==BPS_CNT)begin
    					state <= S_BIT1_L;
    					bit_timer <= 16'd0;
    				end
    				else begin
    					state <= state;
    					bit_timer <= bit_timer + 16'd1;
    				end
    			end
    			
    			S_BIT1_L:begin
    				//txd <= tx_data[1];
    				txd <= tx_data_l[1];
    				if(bit_timer==BPS_CNT)begin
    					state <= S_BIT2_L;
    					bit_timer <= 16'd0;
    				end
    				else begin
    					state <= state;
    					bit_timer <= bit_timer + 16'd1;
    				end
    			end
    			
    			S_BIT2_L:begin
    				//txd <= tx_data[2];
    				txd <= tx_data_l[2];
    				if(bit_timer==BPS_CNT)begin
    					state <= S_BIT3_L;
    					bit_timer <= 16'd0;
    				end
    				else begin
    					state <= state;
    					bit_timer <= bit_timer + 16'd1;
    				end
    			end
    			
    			S_BIT3_L:begin
    				//txd <= tx_data[3];
    				txd <= tx_data_l[3];
    				if(bit_timer==BPS_CNT)begin
    					state <= S_BIT4_L;
    					bit_timer <= 16'd0;
    				end
    				else begin
    					state <= state;
    					bit_timer <= bit_timer + 16'd1;
    				end
    			end
    			
    			S_BIT4_L:begin
    				//txd <= tx_data[4];
    				txd <= tx_data_l[4];
    				if(bit_timer==BPS_CNT)begin
    					state <= S_BIT5_L;
    					bit_timer <= 16'd0;
    				end
    				else begin
    					state <= state;
    					bit_timer <= bit_timer + 16'd1;
    				end
    			end
    			
    			S_BIT5_L:begin
    				//txd <= tx_data[5];
    				txd <= tx_data_l[5];
    				if(bit_timer==BPS_CNT)begin
    					state <= S_BIT6_L;
    					bit_timer <= 16'd0;
    				end
    				else begin
    					state <= state;
    					bit_timer <= bit_timer + 16'd1;
    				end
    			end
    			
    			S_BIT6_L:begin
    				//txd <= tx_data[6];
    				txd <= tx_data_l[6];
    				if(bit_timer==BPS_CNT)begin
    					state <= S_BIT7_L;
    					bit_timer <= 16'd0;
    				end
    				else begin
    					state <= state;
    					bit_timer <= bit_timer + 16'd1;
    				end
    			end
    			
    			S_BIT7_L:begin
    				//txd <= tx_data[7];
    				txd <= tx_data_l[7];
    				if(bit_timer==BPS_CNT)begin
    					state <= S_STOP_L;
    					bit_timer <= 16'd0;
    				end
    				else begin
    					state <= state;
    					bit_timer <= bit_timer + 16'd1;
    				end
    			end
    			
    			S_STOP_L:begin
    				txd <= 1'b1;
    				if(bit_timer==BPS_CNT)begin
    					state <= S_START_H;
    					bit_timer <= 16'd0;
    				end
    				else begin
    					state <= state;
    					bit_timer <= bit_timer + 16'd1;
    				end
    			end
    			
    //			S_WAIT:begin
    //				txd <= 1'b1;
    //				if(bit_timer==BPS_CNT)begin
    //					state <= S_START_H;
    //					bit_timer <= 16'd0;
    //				end
    //				else begin
    //					state <= state;
    //					bit_timer <= bit_timer + 16'd1;
    //				end
    //			end
    			
    			S_START_H:begin
    				txd <= 1'b0;
    				if(bit_timer==BPS_CNT)begin
    					state <= S_BIT0_H;
    					bit_timer <= 16'd0;
    				end
    				else begin
    					state <= state;
    					bit_timer <= bit_timer + 16'd1;
    				end
    			end
    			
    			S_BIT0_H:begin
    				//txd <= tx_data[8];
    				txd <= tx_data_h[0];
    				if(bit_timer==BPS_CNT)begin
    					state <= S_BIT1_H;
    					bit_timer <= 16'd0;
    				end
    				else begin
    					state <= state;
    					bit_timer <= bit_timer + 16'd1;
    				end
    			end
    			
    			S_BIT1_H:begin
    				//txd <= tx_data[9];
    				txd <= tx_data_h[1];
    				if(bit_timer==BPS_CNT)begin
    					state <= S_BIT2_H;
    					bit_timer <= 16'd0;
    				end
    				else begin
    					state <= state;
    					bit_timer <= bit_timer + 16'd1;
    				end
    			end
    			
    			S_BIT2_H:begin
    				//txd <= tx_data[10];
    				txd <= tx_data_h[2];
    				if(bit_timer==BPS_CNT)begin
    					state <= S_BIT3_H;
    					bit_timer <= 16'd0;
    				end
    				else begin
    					state <= state;
    					bit_timer <= bit_timer + 16'd1;
    				end
    			end
    			
    			S_BIT3_H:begin
    				//txd <= tx_data[11];
    				txd <= tx_data_h[3];
    				if(bit_timer==BPS_CNT)begin
    					state <= S_BIT4_H;
    					bit_timer <= 16'd0;
    				end
    				else begin
    					state <= state;
    					bit_timer <= bit_timer + 16'd1;
    				end
    			end
    			
    			S_BIT4_H:begin
    				//txd <= tx_data[12];
    				txd <= tx_data_h[4];
    				if(bit_timer==BPS_CNT)begin
    					state <= S_BIT5_H;
    					bit_timer <= 16'd0;
    				end
    				else begin
    					state <= state;
    					bit_timer <= bit_timer + 16'd1;
    				end
    			end
    			
    			S_BIT5_H:begin
    				//txd <= tx_data[13];
    				txd <= tx_data_h[5];
    				if(bit_timer==BPS_CNT)begin
    					state <= S_BIT6_H;
    					bit_timer <= 16'd0;
    				end
    				else begin
    					state <= state;
    					bit_timer <= bit_timer + 16'd1;
    				end
    			end
    			
    			S_BIT6_H:begin
    				//txd <= tx_data[14];
    				txd <= tx_data_h[6];
    				if(bit_timer==BPS_CNT)begin
    					state <= S_BIT7_H;
    					bit_timer <= 16'd0;
    				end
    				else begin
    					state <= state;
    					bit_timer <= bit_timer + 16'd1;
    				end
    			end
    			
    			S_BIT7_H:begin
    				//txd <= tx_data[15];
    				txd <= tx_data_h[7];
    				if(bit_timer==BPS_CNT)begin
    					state <= S_STOP_H;
    					bit_timer <= 16'd0;
    				end
    				else begin
    					state <= state;
    					bit_timer <= bit_timer + 16'd1;
    				end
    			end
    			
    			S_STOP_H:begin
    				txd <= 1'b1;
    				if(bit_timer==BPS_CNT)begin
    					state <= S_IDLE;
    					bit_timer <= 16'd0;
    				end
    				else begin
    					state <= state;
    					bit_timer <= bit_timer + 16'd1;
    				end
    			end
    			
    			default: state <= S_IDLE;
    		endcase
    		end
    	
    	end
    
    endmodule
    
    
    展开全文
  • FPGA串口实现

    2020-07-30 23:32:06
    FPGA进行串口通信的代码编写,可以在modelsim上进行仿真
  • FPGA串口设计

    2020-07-28 23:32:03
    简单的Verilog实现串口通信,本科生一名,写得比较差,欢迎大家一起学习。
  • UART作为异步串口通信协议的一种,工作原理是将传输数据的每个字符一位接一位地传输。 其中每一位(Bit)的意义如下: 起始位:先发出一个逻辑”0”的信号,表示传输字符的开始。 数据位:紧接着起始位之后。数据位的...

    协议简介:
    UART作为异步串口通信协议的一种,工作原理是将传输数据的每个字符一位接一位地传输。
    其中每一位(Bit)的意义如下:
    起始位:先发出一个逻辑”0”的信号,表示传输字符的开始。
    数据位:紧接着起始位之后。数据位的个数可以是4、5、6、7、8等,构成一个字符。通常采用ASCII码。从最低位开始传送,靠时钟定位。
    奇偶校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性。
    停止位:它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。
    空闲位:处于逻辑“1”状态,表示当前线路上没有数据传送。
    UART工作原理
    发送数据过程:空闲状态,线路处于高电位;当收到发送数据指令后,拉低线路一个数据位的时间T,接着数据按低位到高位依次发送,数据发送完毕后,接着发送奇偶校验位和停止位(停止位为高电位),一帧数据发送结束。
    接收数据过程:空闲状态,线路处于高电位;当检测到线路的下降沿(线路电位由高电位变为低电位)时说明线路有数据传输,按照约定的波特率从低位到高位接收数据,数据接收完毕后,接着接收并比较奇偶校验位是否正确,如果正确则通知后续设备准备接收数据或存入缓存。
    由于UART是异步传输,没有传输同步时钟。为了能保证数据传输的正确性,UART采用16倍数据波特率的时钟进行采样。每个数据有16个时钟采样,取中间的采样值,以保证采样不会滑码或误码。一般UART一帧的数据位数为8,这样数据即使有一个时钟的误差,接收端也能正确地采到数据。
    UART传输时序
    本文采用的比特率是9600bps,也就是1s传输9600bit的数据,也可以不采用16倍数据波特率的时钟进行采样,因为在此设计的是用50M系统时钟产生的时序驱动,所以采样率远比16倍数据波特率高。没有必要再去分时钟去计数16倍频。
    计算传输一次的计数时间:
    计数时间 = 1000000000ns/9600 = 104166.7ns
    50MHz的时钟周期为20ns,所以计数传输一个比特的次数为104166.7 / 20 = 5208
    UART_RX:(接收部分程序)

    `timescale 1ns / 1ps
    module uart_rx(
    
                   input               clk          ,		
                   input               rst_n        ,	
                   input               din          ,
                   output  reg[7:0]    dout         ,		
                   output  reg         dout_vld     
                   );
    
    parameter    	   BPS	  =	5208;	//9600波特率
    reg   [14:0]        cnt0         ;
    wire                add_cnt0     ;
    wire                end_cnt0     ;
    
    reg   [ 3:0]        cnt1         ;
    wire                add_cnt1     ;
    wire                end_cnt1     ;
    
    reg                 rx0          ;	
    reg                 rx1          ;	
    reg                 rx2          ;	
    wire                rx_en        ;
    
    reg                 flag_add     ;
    
    //对数据的跨时钟处理,防止出现亚稳态
    always @ (posedge clk or negedge rst_n) begin
    	if(!rst_n) begin
    		rx0 <= 1'b1;
            rx1 <= 1'b1;
            rx2 <= 1'b1;
    	end
    	else begin
    		rx0 <= din;
            rx1 <= rx0;
            rx2 <= rx1;
    	end
    end
    
    assign rx_en = rx2 & ~rx1;	
    //检测到下降沿,即空闲位从1置为0,数据传输开始
    
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            cnt0 <= 0;
        end
        else if(add_cnt0)begin
            if(end_cnt0)
                cnt0 <= 0;
            else
                cnt0 <= cnt0 + 1;
        end
    end
    
    assign add_cnt0 = flag_add;
    assign end_cnt0 = add_cnt0 && cnt0==BPS-1 ;
    
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            cnt1 <= 0;
        end
        else if(add_cnt1)begin
            if(end_cnt1)
                cnt1 <= 0;
            else
                cnt1 <= cnt1 + 1;
        end
    end
    
    assign add_cnt1 = end_cnt0;
    assign end_cnt1 = add_cnt1 && cnt1==9-1 ; //由于是接收程序,此处也不设校验位,所以只需要接收数据就可以,后面的第10位必然位停止位,可以不理,节省资源
    
    always @ (posedge clk or negedge rst_n)begin
    	if(!rst_n) begin
    		flag_add <= 1'b0;
    	end
    	else if(rx_en) begin		
    		flag_add <= 1'b1;	
    	end
        else if(end_cnt1) begin		
    		flag_add <= 1'b0;	
    	end
    end
    
    always @ (posedge clk or negedge rst_n)begin
    	if(!rst_n) begin
    		dout <= 8'd0;
    	end
    	else if(add_cnt0 && cnt0==BPS/2-1 && cnt1!=0) begin		//在中间时刻采样,此时的数据比较稳定,从低位到高位依次采样
    	    dout[cnt1-1]<= rx2 ;
    	end
    end
    
    
    //传输完数据信号
    always @ (posedge clk or negedge rst_n)begin
    	if(!rst_n) begin
    		dout_vld <= 1'b0;
    	end
        else if(end_cnt1) begin		
    		dout_vld <= 1'b1;	
    	end
    	else begin	
            dout_vld <= 1'b0;			
    	end
    end
    
    endmodule
    

    UART_TX(发送)

    //删掉din_vld 和rdy信号之后,只需要把计数器cnt0计数条件改为add_cnt0 = 1,就可以了
    `timescale 1ns / 1ps
    	
    module uart_tx(
                     input             clk    ,			
                     input             rst_n  ,		
                     input [7:0]       din    ,
                     input             din_vld,	 //开始发送信号,如果你要一直发送可以删掉此信号,本人是从模块中剪下来的代码,懒得删改了
                     output reg        rdy    ,  //类似接收的完成信号,表示完成了一个字节的传输,同din_vld信号一样,不要可以删掉
                     output reg        dout   
                 );
    
    parameter         BPS    = 5208;
    
    reg   [7:0]       tx_data_tmp;	
    
    reg               flag_add   ;
    reg   [14:0]      cnt0       ;
    wire              add_cnt0   ;
    wire              end_cnt0   ;
    
    reg   [ 3:0]      cnt1       ;
    wire              add_cnt1   ;
    wire              end_cnt1   ;
    
    wire  [ 9:0]      data       ;
    
    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            flag_add <= 0;
        end
        else if(flag_add==0 && din_vld)begin
            flag_add <= 1;
        end
        else if(end_cnt1)begin
            flag_add <= 0;
        end
    end
    
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            cnt0 <= 0;
        end
        else if(add_cnt0)begin
            if(end_cnt0)
                cnt0 <= 0;
            else
                cnt0 <= cnt0 + 1;
        end
    end
    
    assign add_cnt0 = flag_add;
    assign end_cnt0 = add_cnt0 && cnt0==BPS-1 ;
    
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            cnt1 <= 0;
        end
        else if(add_cnt1)begin
            if(end_cnt1)
                cnt1 <= 0;
            else
                cnt1 <= cnt1 + 1;
        end
    end
    
    assign add_cnt1 = end_cnt0;
    assign end_cnt1 = add_cnt1 && cnt1==10-1 ;
    
    
    always @ (posedge clk or negedge rst_n) begin
    	if(!rst_n) begin
    		tx_data_tmp <=8'd0;
    	end
    	else if(flag_add==1'b0 && din_vld) begin	
    		tx_data_tmp <= din;	
    	end
    end
    
    always @ (posedge clk or negedge rst_n) begin
    	if(!rst_n) begin
    		dout <= 1'b1;
    	end
    	else if(add_cnt0 && cnt0==1-1)begin
            dout<= data[cnt1];
        end 
    end
    
    assign  data = {1'b1,tx_data_tmp,1'b0}; //传输时是从低到高 data = {停止位,数据[7],数据[6] ~ 数据[0],起始位};
    
    always  @(*)begin
        if(din_vld || flag_add)
            rdy = 1'b0;
        else
            rdy = 1'b1;
    end
    
    endmodule
    
    展开全文
  • FPGA串口实战篇

    2019-08-21 12:21:30
    前些天为了支援兄弟单位的朋友,为其调试了一款激光测距仪,而该测距仪与FPGA的数据通信方式为串口,波特率为115200。该朋友的基本需求是这样的:FPGA首先向激光测距仪发送几个命令,使其以用户需要的模式开始工作,而测距...

    前些天为了支援兄弟单位的朋友,为其调试了一款激光测距仪,而该测距仪与FPGA的数据通信方式为串口,波特率为115200。该朋友的基本需求是这样的:FPGA首先向激光测距仪发送几个命令,使其以用户需要的模式开始工作,而测距仪开始工作之后就不断的以115200b/s的速度向FPGA发送距离数据。

    1. 串口协议介绍

    在业界,串口又称为通用异步收发器(Universal Asynchronous Receiver/Transmitter,简称UART),它的通信方式遵循一套串口协议:UART通信首先将接收到的并行数据换成串行数据来传输。数据帧从起始位开始,后面是7个或8个数据位,一个可用的奇偶校验位和一个或几个高位停止位。在接收过程中,UART从数据帧中去掉起始位和结束位,并对收到的有效数据字节进行奇偶校验,最后将数据字节从串行转换成并行。另外,需要注意的是奇偶校验位可以不需要,有些实现为了简单起见,在协议实现时就没有设置奇偶校验位。UART传输时序如下图所示:

     

    1. 串口实现介绍

    下面介绍串口的verilog实现技巧:

    首先是需要产生串行数据发送和接收时钟,为了避免异步通信时发生滑码,确保串行数据被准确接收,一般我们采取的办法是串行时钟频率为波特率的16倍。以我调试的这款测距仪为例,它工作的波特率为115200,而我FPGA板上的系统时钟为100MHz,所以我需要对100MHz进行分频,分频系数N可以计算如下:

    用verilog实现起来也很简单,如下图所示:

    其次是串口发送模块:由于测距仪的串口协议没有奇偶校验位,因此我在FPGA实现端也没有加此功能,也就是说我只需要发送1位起始位、8位数据位以及1位停止位,共10位,而前面提到了发送1位帧数据需要16个时钟周期,所以我们可以知道:只需160个时钟周期我们就可以把10位帧数据发送完毕。

    所以就有了如下代码:

    其中,wrsigrise为数据使能信号,每当需要发送新数据时,该信号就拉高一个时钟周期。idle为发送模块空闲标志,idle为低电平时表示发送线路为空闲,允许新数据发送,idle为高电平时表示目前的数据还没有发送结束,线路正忙。当发送进程启动160个时钟周期后,10位帧数据就已经发送完毕。

    我们再看看发送模块进程,其实也很简单:就是每16个周期发送一位数据,从低到高发送。如下图所示限于篇幅仅给出了一头一尾,中间部分是一样的:

    这样10位帧数据就发送完成了,idle拉低表明线路开始空闲了。

    然后是串口接收模块了,其实和发送模块差不多,唯一需要注意的地方就是:如何判断数据的到来。我们看协议知道,接收线路上没有数据时会一直保持高电平,当有数据时线路会出现一个由高到低的电平跳变,我们检测到这个跳变就知道数据要来了,咱们就准备接收。如下图所示为接收控制进程:

    rxfall即为接收下降沿标志,只有当该信号出现且接收线路为空闲时,接收模块才开始接收线路上的串行数据,并且160个时钟周期之后,10位的帧数据肯定已经接收完毕。

    具体的接收模块和发送模块类似,但需要注意一点,我们在数据的中间点取值,这样接收到的数据最稳定,也就是说,第一个起始位是第0~第15时钟周期,我们在第8个时钟周期取数,第一个数据在第16~第31时钟周期出现,我们则在第24个时钟周期取数,以此类推~,如下图所示:

    1. 测试结果

    我们看看激光测距仪的数据手册,其正常工作时发送5个字节的数据:第0个字节是数据帧头,固定的0x81;第1个字节是测距的低八位数据;第2个字节是测距的高八位数据;第3个字节是测距仪曝光等级低八位;第4个字节是测距仪曝光等级低八位。如下图所示:

    如果FPGA的串口代码没问题,就应该是按这个格式收到数据。启动chipscope,我们观察数据,发现数据完全正确,就此完成调试~

     

    展开全文
  • FPGA串口

    2018-09-12 21:54:04
    前面说到了UART,也就是串口发送模块,串口发送模块两个主要组件之一即为波特率时钟生成模块,这里需要 计算出系统时钟计数值与波特率之间的关系: FPGA主板频率是50Mhz,T=20ns    9600波特率指的是9600bps,...
  • 1.根据功能需要设计模块,自上而下不断细化,确定端口、子模块、连线,最好就是画图出来,这里是设计的是把收到的串口数据重新发送出去 2.根据自己画的图,转换成verilo代码,并描述出来 //-----------------...
  • FPGA串口实现

    2020-07-19 23:31:08
    用vhdl实现串口
  • FPGA串口发送例程

    2020-07-30 23:31:51
    FPGA串口发送例程,
  • FPGA 串口接收不准确,有误码FPGA 的Uart接收时,出现接收不正确的分析波特率计数值= 时钟频率/波特率 FPGA 的Uart接收时,出现接收不正确的分析 波特率计数值= 时钟频率/波特率 协议:1位起始位,8位数据位,1位...
  • 基于spartan-3 的fpga 串口代码
  • 基于 fpga 串口通信代码,verilog代码编写,实现数据收发
  • FPGA串口通信要想应用在实际的工业现场,需要一整套完整的协议,来确保数据传输的可靠性和系统的稳定性。基于协议,进行串口指令解析是控制的关键,对于串口指令解析,有两种方式:逻辑解析和软硬核(我用的Altera的...
  • FPGA串口通信程序

    2020-07-30 23:32:00
    实现FPGA与电脑串口的通信程序,Quartus II 13.0 上运行无误,所用FPGA芯片型号为Altera Cyclone IV E ,EP4CE15F23C8。烧写进FPGA开发板后,从串口助手向FPGA板发数据,可从串口助手收到发送数据加一后的结果。
  • FPGA串口收发(一):串口发送 - 源代码与仿真测试 串口发送的数据:0 -> A (1010) -> C (1100) ->0 时钟40MHz,波特率115200 1、源文件 uart_tx.v `timescale 1ns / 1ps ////////////////////////////////...
  • FPGA串口收发(四):接收数据并转发,间隔时间发送 // Description: 串口收发:串口接收数据,内部生成数据,串口间隔特定时间发送数据 // 串口接收数据:串行信号线 1101_1000 ,转为并行数据,取反截取低4位 传递...
  • 最近在做低频数字式相位测量仪,很多人都是卡在stm32和fpga串口通信上,还有相位差的测量,下面讲讲通信的解决办法!fpga部分用了某位大神的串口通信模块进行参考(module tx_module,module tx_control_module,...
  • FPGA串口收发字符串之串口接收模块,有需要的同学可以下载!
  • FPGA串口接收仿真测试

    2020-07-30 23:32:19
    串口接收仿真测试,用verilog代码写的,有串口代码和testbench
1 2 3 4 5 ... 20
收藏数 7,864
精华内容 3,145
热门标签
关键字:

fpga串口