龙芯杯个人赛串口——做一个 UART串口——RS-232

文章目录

      • Async transmitter
      • Async receiver
        • 1. RS-232 串行接口的工作原理
          • DB-9 connector
          • Asynchronous communication
          • How fast can we send data?
        • 2.波特率时钟生成器
          • Parameterized FPGA baud generator
        • 3.RS-232 transmitter
          • 数据序列化
          • 完整代码:
        • 4.RS-232 receiver
          • Oversampling
          • The design
          • 完整代码
        • 5.How to use the RS-232 transmitter and receiver
      • 龙芯杯个人赛串口信号连接

串行接口是将 FPGA 连接到 PC 的简单方法。我们只需要一个发射器和接收器模块。

Async transmitter

它通过对要传输的数据进行序列化,创建一个信号 “TxD”。
在这里插入图片描述

Async receiver

它从 FPGA 外部获取信号 “RxD”,并将其 “去序列化”,以便在 FPGA 内部使用。

在这里插入图片描述
本项目包括五个部分

    1. RS-232 串行接口的工作原理
    1. 波特发生器
    1. 发射器
    1. 接收器
    1. 使用示例
1. RS-232 串行接口的工作原理

RS-232 接口具有以下特点:
- 使用 9 针连接器 “DB-9”(老式 PC 使用 25 针连接器 “DB-25”)。
- 允许双向全双工通信(个人电脑可同时发送和接收数据)。
- 最大通信速度约为 10KB/s。

DB-9 connector

可能在电脑背面看到过这个连接器。
在这里插入图片描述
它有 9 个引脚,但重要的有 3 个:

第 2 针:RxD(接收数据)。
第 3 针:TxD(发送数据)。
第 5 针:GND(接地)。

只需 3 根导线,就能发送和接收数据。

数据通常以 8 位为单位发送(我们称之为字节),并进行 “序列化”:先发送 LSB(数据位 0),然后是位 1,…最后是 MSB(位 7)。

Asynchronous communication

该接口使用异步协议。这意味着没有时钟信号与数据一起传输。接收器必须有办法根据接收到的数据位 “计时”。

在 RS-232 的情况下,可以这样做:

电缆两端事先就通信参数(速度、格式......)达成一致。这需要在通信开始前手动完成。
只要线路处于空闲状态,发送器就会发送 "空闲"(="1")。
发送器在传输每个字节前都会发送 "开始"(="0"),以便接收器知道有字节到来。
发送字节数据的 8 位。
发送器在每个字节后发送 "停止"(="1")。

让我们看看 0x55 字节在传输时的样子:
在这里插入图片描述
字节 0x55 的二进制形式是 01010101。
但由于它是先传输 LSB(第 0 位),因此该行会像这样切换: 1-0-1-0-1-0-1-0.

下面是另一个例子:
在这里插入图片描述

How fast can we send data?

速度以波特为单位,即每秒可发送多少比特。例如,1000 波特表示每秒 1000 比特,或每个比特持续一毫秒。

RS-232 接口的常见实现(如 PC 中使用的接口)不允许使用任何速度。如果你想使用 123456 波特,那就没戏了。你必须采用某种 "标准 "速度。常见的值有

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.5KB。

在 115200 波特的情况下,一些带有错误芯片的 PC 需要一个 "长 "停止位(1.5 或 2 位长…),这使得最高速度降至每秒 10.5KB 左右。
物理层

电线上的信号使用正/负电压方案。

1 "使用 -10V(或 -5V 至 -15V)电压发送。
0 "使用 +10V(或 5V 至 15V 之间)发送。

因此,空闲线路的电压类似于 -10V。

2.波特率时钟生成器

在这里,我们希望以最大速度使用串行链路,即 115200 波特(较慢的速度也很容易生成)。FPGA 通常以 MHz 速度运行,远高于 115200Hz(按当今标准,RS-232 的速度相当慢)。我们需要找到一种方法,(通过 FPGA 时钟)产生尽可能接近每秒 115200 次的 "滴答 "声。

传统上,RS-232 芯片使用 1.8432MHz 时钟,因为这样可以很容易地产生标准波特频率… 1.8432MHz 除以 16 得到 115200Hz。

// let's assume the FPGA clock signal runs at 1.8432MHz
// we create a 4-bit counter
reg [3:0] BaudDivCnt;
always @(posedge clk) BaudDivCnt <= BaudDivCnt + 1; // count forever from 0 to 15// and a tick signal that is asserted once every 16 clocks (so 115200 times a second)
wire BaudTick = (BaudDivCnt==15); 

这很简单。但如果时钟频率不是 1.8432MHz,而是 2MHz,该怎么办呢?要从 2MHz 时钟产生 115200Hz 的频率,我们需要将时钟除以 “17.361111111…”。这可不是一个整数。解决办法是有时除以 17,有时除以 18,确保比率保持 “17.361111111”。这其实很容易做到。

请看下面的 "C "代码

while(1) // repeat forever
{acc += 115200;if(acc>=2000000) printf("*"); else printf(" ");acc %= 2000000;
}

这样,平均每 "17.361111111… "循环一次,就能以精确的比例打印出 “*”。

要在 FPGA 中高效地实现同样的功能,我们需要依靠串行接口能够容忍波特频率发生器中百分之几的误差。

我们希望 2000000 是 2 的幂。显然,2000000 不是。因此,我们要改变比率… 用 “1024/59” = 17.356 代替 “2000000/115200”。这非常接近我们的理想比率,而且可以高效地在 FPGA 上实现:我们使用一个 10 位累加器,以 59 为增量,每当累加器溢出时打一个勾。

// let's assume the FPGA clock signal runs at 2.0000MHz
// we use a 10-bit accumulator plus an extra bit for the accumulator carry-out
reg [10:0] acc;   // 11 bits total!// add 59 to the accumulator at each clock
always @(posedge clk)acc <= acc[9:0] + 59; // use 10 bits from the previous accumulator result, but save the full 11 bits resultwire BaudTick = acc[10]; // so that the 11th bit is the accumulator carry-out 

使用我们的 2MHz 时钟,"BaudTick "每秒断言 115234 次,与理想的 115200 误差为 0.03%。

Parameterized FPGA baud generator

之前的设计使用的是 10 位累加器,但随着时钟频率的增加,需要更多的位数。
下面是一个使用 25MHz 时钟和 16 位累加器的设计。该设计是参数化的,因此很容易定制。

parameter ClkFrequency = 25000000; // 25MHz
parameter Baud = 115200;
parameter BaudGeneratorAccWidth = 16;
parameter BaudGeneratorInc = (Baud<<BaudGeneratorAccWidth)/ClkFrequency;reg [BaudGeneratorAccWidth:0] BaudGeneratorAcc;
always @(posedge clk)BaudGeneratorAcc <= BaudGeneratorAcc[BaudGeneratorAccWidth-1:0] + BaudGeneratorInc;wire BaudTick = BaudGeneratorAcc[BaudGeneratorAccWidth]; 

最后一个实现问题:"BaudGeneratorInc "的计算是错误的,这是因为 Verilog 使用 32 位中间结果,而计算结果超过了 32 位。为解决这个问题,请修改如下内容。

parameter BaudGeneratorInc = ((Baud<<(BaudGeneratorAccWidth-4))+(ClkFrequency>>5))/(ClkFrequency>>4); 

这条线的另一个优点是将结果四舍五入,而不是截断。

现在我们有了足够精确的波特发生器,就可以继续使用 RS-232 发送器和接收器模块了。

  1. 代码实现
module BaudTickGen(input  wire clk, enable,output wire tick  // generate a tick at the specified baud rate * oversampling
);
parameter ClkFrequency = 25000000;
parameter Baud = 115200;
parameter Oversampling = 1;function integer log2(input integer v); begin log2=0; while(v>>log2) log2=log2+1; end endfunction
localparam AccWidth = log2(ClkFrequency/Baud)+8;  // +/- 2% max timing error over a byte
reg [AccWidth:0] Acc = 0;
localparam ShiftLimiter = log2(Baud*Oversampling >> (31-AccWidth));  // this makes sure Inc calculation doesn't overflow
localparam Inc = ((Baud*Oversampling << (AccWidth-ShiftLimiter))+(ClkFrequency>>(ShiftLimiter+1)))/(ClkFrequency>>ShiftLimiter);
always @(posedge clk) if(enable) Acc <= Acc[AccWidth-1:0] + Inc[AccWidth:0]; else Acc <= Inc[AccWidth:0];
assign tick = Acc[AccWidth];
  1. 解释
    这段Verilog代码看起来是在实现一个串口通信的波特率时钟生成器。我们逐段解释一下代码:
  • 模块定义:

    module BaudTickGen(input  wire clk, enable,output wire tick  // 根据指定的波特率 * 过采样率生成时钟脉冲
    );
    

    这个模块有三个端口:clkenable 作为输入,tick 作为输出。它旨在根据指定的波特率和过采样率生成一个时钟脉冲信号。

  • 参数声明:

    parameter ClkFrequency = 25000000;
    parameter Baud = 115200;
    parameter Oversampling = 1;
    

    这些参数定义了时钟频率 (ClkFrequency)、所需波特率 (Baud) 以及过采样因子 (Oversampling)。您可以根据需要自定义这些值。

  • Log2 函数:

    function integer log2(input integer v);beginlog2 = 0;while (v >> log2)log2 = log2 + 1;end
    endfunction
    

    这是一个简单的函数,用于计算输入整数 v 的以2为底的对数。

  • 局部参数:

    localparam AccWidth = log2(ClkFrequency/Baud) + 8;
    

    AccWidth 是根据所需的位数来表示累加值 (Acc),以实现在一个字节内最大的时序误差不超过 +/- 2%。

    localparam ShiftLimiter = log2(Baud*Oversampling >> (31-AccWidth));
    

    ShiftLimiter 用于限制左移操作在计算 Inc 时以防止溢出。

    localparam Inc = ((Baud*Oversampling << (AccWidth-ShiftLimiter)) + (ClkFrequency>>(ShiftLimiter+1))) / (ClkFrequency>>ShiftLimiter);
    

    Inc 是每个时钟周期添加到累加器的增量值,以实现所需的波特率。

    always @(posedge clk) if(enable) Acc <= Acc[AccWidth-1:0] + Inc[AccWidth:0];else Acc <= Inc[AccWidth:0];
    

    这个总是块在时钟的上升沿触发 (posedge clk)。如果 enable 为真,则将 Inc 的值累加到累加器 Acc 中;否则,将 Acc 设置为 Inc 的初始值。

  • 赋值语句:

    assign tick = Acc[AccWidth];
    

    这将累加器的最高位赋给 tick 输出,这就是生成的时钟脉冲信号。

3.RS-232 transmitter

我们正在构建一个参数固定的 “异步发射机”:8 个数据位、2 个停止位、无奇偶校验。
在这里插入图片描述
工作原理是这样的:

发送器在 FPGA 内获取 8 位数据并将其序列化(从 "TxD_start "信号断定时开始)。
在传输过程中,"忙 "信号被断开(在此期间 "TxD_start "信号被忽略)。
数据序列化

要完成起始位、8 个数据位和停止位,似乎应该使用状态机。

reg [3:0] state;// the state machine starts when "TxD_start" is asserted, but advances when "BaudTick" is asserted (115200 times a second)
always @(posedge clk)
case(state)4'b0000: if(TxD_start) state <= 4'b0100;4'b0100: if(BaudTick) state <= 4'b1000; // start4'b1000: if(BaudTick) state <= 4'b1001; // bit 04'b1001: if(BaudTick) state <= 4'b1010; // bit 14'b1010: if(BaudTick) state <= 4'b1011; // bit 24'b1011: if(BaudTick) state <= 4'b1100; // bit 34'b1100: if(BaudTick) state <= 4'b1101; // bit 44'b1101: if(BaudTick) state <= 4'b1110; // bit 54'b1110: if(BaudTick) state <= 4'b1111; // bit 64'b1111: if(BaudTick) state <= 4'b0001; // bit 74'b0001: if(BaudTick) state <= 4'b0010; // stop14'b0010: if(BaudTick) state <= 4'b0000; // stop2default: if(BaudTick) state <= 4'b0000;
endcase

现在,我们只需生成 "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// combine start, data, and stop bits together
assign TxD = (state<4) | (state[3] & muxbit); 
完整代码:
module async_transmitter(input wire clk,input wire TxD_start,input wire [7:0] TxD_data,output wire TxD,output wire TxD_busy
);// Assert TxD_start for (at least) one clock cycle to start transmission of TxD_data
// TxD_data is latched so that it doesn't have to stay valid while it is being sentparameter ClkFrequency = 25000000;	// 25MHz
parameter Baud = 115200;// generate
// 	if(ClkFrequency<Baud*8 && (ClkFrequency % Baud!=0)) ASSERTION_ERROR PARAMETER_OUT_OF_RANGE("Frequency incompatible with requested Baud rate");
// endgenerate`ifdef SIMULATION
wire BitTick = 1'b1;  // output one bit per clock cycle
`else
wire BitTick;
BaudTickGen #(ClkFrequency, Baud) tickgen(.clk(clk), .enable(TxD_busy), .tick(BitTick));
`endifreg [3:0] TxD_state = 0;
wire TxD_ready = (TxD_state==0);
assign TxD_busy = ~TxD_ready;reg [7:0] TxD_shift = 0;
always @(posedge clk)
beginif(TxD_ready & TxD_start)TxD_shift <= TxD_data;elseif(TxD_state[3] & BitTick)TxD_shift <= (TxD_shift >> 1);case(TxD_state)4'b0000: if(TxD_start) TxD_state <= 4'b0100;4'b0100: if(BitTick) TxD_state <= 4'b1000;  // start bit4'b1000: if(BitTick) TxD_state <= 4'b1001;  // bit 04'b1001: if(BitTick) TxD_state <= 4'b1010;  // bit 14'b1010: if(BitTick) TxD_state <= 4'b1011;  // bit 24'b1011: if(BitTick) TxD_state <= 4'b1100;  // bit 34'b1100: if(BitTick) TxD_state <= 4'b1101;  // bit 44'b1101: if(BitTick) TxD_state <= 4'b1110;  // bit 54'b1110: if(BitTick) TxD_state <= 4'b1111;  // bit 64'b1111: if(BitTick) TxD_state <= 4'b0010;  // bit 74'b0010: if(BitTick) TxD_state <= 4'b0000;  // stop1//4'b0011: if(BitTick) TxD_state <= 4'b0000;  // stop2default: if(BitTick) TxD_state <= 4'b0000;endcase
endassign TxD = (TxD_state<4) | (TxD_state[3] & TxD_shift[0]);  // put together the start, data and stop bits
endmodule
4.RS-232 receiver

我们正在构建一个 “异步接收器”:
在这里插入图片描述
我们的实施工作就是这样:

模块从 RxD 线路中收集数据。
当接收到一个字节时,它就会出现在 "数据 "总线上。一旦接收到一个完整的字节,"data_ready "就会断言一个时钟。

请注意,"数据 "只有在 "数据就绪 "断言时才有效。其余时间不要使用它,因为可能会有新的数据出现,从而对它进行洗牌。

Oversampling

异步接收器必须以某种方式与接收到的信号同步(它通常无法访问发送器使用的时钟)。

为了确定新数据字节何时到来,我们以波特率频率的倍数对信号进行过采样,寻找 "起始 "位。
一旦检测到 "起始 "位,我们就以已知的波特率对线路进行采样,以获取数据位。

接收器通常以 16 倍波特率对输入信号进行过采样。这里我们使用 8 倍… 对于 115200 波特,采样率为 921600Hz。

假设我们有一个 "Baud8Tick "信号,每秒发出 921600 次。

The design

首先,输入的 "RxD "信号与我们的时钟无关。我们使用两个 D 触发器对其进行过采样,并使其与我们的时钟域同步。

reg [1:0] RxD_sync;
always @(posedge clk) if(Baud8Tick) RxD_sync <= {RxD_sync[0], RxD}; 

我们对数据进行过滤,以免将 RxD 线路上的短尖峰误认为是起始位。

reg [1:0] RxD_cnt;
reg RxD_bit;always @(posedge clk)
if(Baud8Tick)
beginif(RxD_sync[1] && RxD_cnt!=2'b11) RxD_cnt <= RxD_cnt + 1;elseif(~RxD_sync[1] && RxD_cnt!=2'b00) RxD_cnt <= RxD_cnt - 1;if(RxD_cnt==2'b00) RxD_bit <= 0;elseif(RxD_cnt==2'b11) RxD_bit <= 1;
end

一旦检测到 “start”,状态机就会对接收到的每个比特进行处理。

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 04'b1001: if(next_bit) state <= 4'b1010; // bit 14'b1010: if(next_bit) state <= 4'b1011; // bit 24'b1011: if(next_bit) state <= 4'b1100; // bit 34'b1100: if(next_bit) state <= 4'b1101; // bit 44'b1101: if(next_bit) state <= 4'b1110; // bit 54'b1110: if(next_bit) state <= 4'b1111; // bit 64'b1111: if(next_bit) state <= 4'b0001; // bit 74'b0001: if(next_bit) state <= 4'b0000; // stop bitdefault: 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]}; 
完整代码
module async_receiver(input wire clk,input wire RxD,output reg RxD_data_ready,input wire RxD_clear,output reg [7:0] RxD_data  // data received, valid only (for one clock cycle) when RxD_data_ready is asserted
);parameter ClkFrequency = 25000000; // 25MHz
parameter Baud = 115200;parameter Oversampling = 8;  // needs to be a power of 2
// we oversample the RxD line at a fixed rate to capture each RxD data bit at the "right" time
// 8 times oversampling by default, use 16 for higher quality reception// generate
// 	if(ClkFrequency<Baud*Oversampling) ASSERTION_ERROR PARAMETER_OUT_OF_RANGE("Frequency too low for current Baud rate and oversampling");
// 	if(Oversampling<8 || ((Oversampling & (Oversampling-1))!=0)) ASSERTION_ERROR PARAMETER_OUT_OF_RANGE("Invalid oversampling value");
// endgenerate// We also detect if a gap occurs in the received stream of characters
// That can be useful if multiple characters are sent in burst
//  so that multiple characters can be treated as a "packet"
wire RxD_idle;  // asserted when no data has been received for a while
reg RxD_endofpacket; // asserted for one clock cycle when a packet has been detected (i.e. RxD_idle is going high)reg [3:0] RxD_state = 0;`ifdef SIMULATION
wire RxD_bit = RxD;
wire sampleNow = 1'b1;  // receive one bit per clock cycle`else
wire OversamplingTick;
BaudTickGen #(ClkFrequency, Baud, Oversampling) tickgen(.clk(clk), .enable(1'b1), .tick(OversamplingTick));// synchronize RxD to our clk domain
reg [1:0] RxD_sync = 2'b11;
always @(posedge clk) if(OversamplingTick) RxD_sync <= {RxD_sync[0], RxD};// and filter it
reg [1:0] Filter_cnt = 2'b11;
reg RxD_bit = 1'b1;always @(posedge clk)
if(OversamplingTick)
beginif(RxD_sync[1]==1'b1 && Filter_cnt!=2'b11) Filter_cnt <= Filter_cnt + 1'd1;else if(RxD_sync[1]==1'b0 && Filter_cnt!=2'b00) Filter_cnt <= Filter_cnt - 1'd1;if(Filter_cnt==2'b11) RxD_bit <= 1'b1;elseif(Filter_cnt==2'b00) RxD_bit <= 1'b0;
end// and decide when is the good time to sample the RxD line
function integer log2(input integer v); begin log2=0; while(v>>log2) log2=log2+1; end endfunction
localparam l2o = log2(Oversampling);
reg [l2o-2:0] OversamplingCnt = 0;
always @(posedge clk) if(OversamplingTick) OversamplingCnt <= (RxD_state==0) ? 1'd0 : OversamplingCnt + 1'd1;
wire sampleNow = OversamplingTick && (OversamplingCnt==Oversampling/2-1);
`endif// now we can accumulate the RxD bits in a shift-register
always @(posedge clk)
case(RxD_state)4'b0000: if(~RxD_bit) RxD_state <= `ifdef SIMULATION 4'b1000 `else 4'b0001 `endif;  // start bit found?4'b0001: if(sampleNow) RxD_state <= 4'b1000;  // sync start bit to sampleNow4'b1000: if(sampleNow) RxD_state <= 4'b1001;  // bit 04'b1001: if(sampleNow) RxD_state <= 4'b1010;  // bit 14'b1010: if(sampleNow) RxD_state <= 4'b1011;  // bit 24'b1011: if(sampleNow) RxD_state <= 4'b1100;  // bit 34'b1100: if(sampleNow) RxD_state <= 4'b1101;  // bit 44'b1101: if(sampleNow) RxD_state <= 4'b1110;  // bit 54'b1110: if(sampleNow) RxD_state <= 4'b1111;  // bit 64'b1111: if(sampleNow) RxD_state <= 4'b0010;  // bit 74'b0010: if(sampleNow) RxD_state <= 4'b0000;  // stop bitdefault: RxD_state <= 4'b0000;
endcasealways @(posedge clk)
if(sampleNow && RxD_state[3]) RxD_data <= {RxD_bit, RxD_data[7:1]};//reg RxD_data_error = 0;
always @(posedge clk)
beginif(RxD_clear)RxD_data_ready <= 0;elseRxD_data_ready <= RxD_data_ready | (sampleNow && RxD_state==4'b0010 && RxD_bit);  // make sure a stop bit is received//RxD_data_error <= (sampleNow && RxD_state==4'b0010 && ~RxD_bit);  // error if a stop bit is not received
end`ifdef SIMULATION
assign RxD_idle = 0;
`else
reg [l2o+1:0] GapCnt = 0;
always @(posedge clk) if (RxD_state!=0) GapCnt<=0; else if(OversamplingTick & ~GapCnt[log2(Oversampling)+1]) GapCnt <= GapCnt + 1'h1;
assign RxD_idle = GapCnt[l2o+1];
always @(posedge clk) RxD_endofpacket <= OversamplingTick & ~GapCnt[l2o+1] & &GapCnt[l2o:0];
`endifendmodule
5.How to use the RS-232 transmitter and receiver

该设计允许通过 PC 控制几个 FPGA 引脚(通过 PC 的串行端口)。

它在 FPGA 上创建了 8 个输出(端口名为 "GPout")。FPGA 接收到的任何字符都会更新 GPout。
同时在 FPGA 上创建 8 个输入端(名为 "GPin "的端口)。每当 FPGA 接收到一个字符,GPin 就会传输一次。

GP 输出可用于从电脑远程控制任何东西,可能是 LED 或咖啡机…

module serialGPIO(input clk,input RxD,output TxD,output reg [7:0] GPout,  // general purpose outputsinput [7:0] GPin  // general purpose inputs
);wire RxD_data_ready;
wire [7:0] RxD_data;
async_receiver RX(.clk(clk), .RxD(RxD), .RxD_data_ready(RxD_data_ready), .RxD_data(RxD_data));
always @(posedge clk) if(RxD_data_ready) GPout <= RxD_data;async_transmitter TX(.clk(clk), .TxD(TxD), .TxD_start(RxD_data_ready), .TxD_data(GPin));
endmodule

龙芯杯个人赛串口信号连接

// | 0xBFD003F8 | [7:0] | 串口数据,读、写地址分别表示串口接收、发送一个字节 |
// | 0xBFD003FC | [0]   | 只读,为1时表示串口空闲,可发送数据                |
// | 0xBFD003FC | [1]   | 只读,为1时表示串口收到数据                        |
module uart(input wire clk,input wire resetn,// read and write from cpu// input   wire          conf_en,     input   wire          conf_re,conf_we,// input   wire   [3 :0] conf_wen,      input   wire   [31:0] conf_addr,    input   wire   [31:0] conf_wdata,   output  wire   [31:0] conf_rdata,// read and write to device on board//直连串口信号output wire txd,  //直连串口发送端input  wire rxd  //直连串口接收端// output reg [15:0] led,// input wire [7:0] switch
);
wire read_flag;
wire write_uart;
wire read_uart;
assign read_flag = ((conf_addr == 32'hbfd003fc) && (conf_re)) ? 1'b1:1'b0;
assign read_uart = ((conf_addr == 32'hbfd003f8) && (conf_re)) ? 1'b1:1'b0;
assign write_uart = ((conf_addr == 32'hbfd003f8) && (conf_we)) ? 1'b1:1'b0;wire [7:0] ext_uart_wdata;// write data// buffers
reg [7:0] ext_uart_rbuffer;// read buffer
reg [1:0] ext_uart_flag; // uart flag for read and write at addr 0xbfd003fc
assign conf_rdata = read_flag?{30'h0,ext_uart_flag}:read_uart?{24'h0,ext_uart_rbuffer}:32'h0;wire [7:0] ext_uart_rx;
wire ext_uart_ready,ext_uart_clear;wire ext_uart_busy; // transmitter busy flag
reg ext_uart_start; // transmitter start work signal
always @(posedge clk) beginif(!resetn)ext_uart_flag <= 2'h1;else beginif(ext_uart_ready)ext_uart_flag[1] <= 1;else if(read_uart)  ext_uart_flag[1] <= 0;// write flag// if(write_uart)//     ext_uart_flag[0] <= 0;// else if(!ext_uart_busy)//     ext_uart_flag[0] <= 1;if (!ext_uart_busy)ext_uart_flag[0] <= 1;else if(write_uart)ext_uart_flag[0] <= 0;endend//uart reciever
async_receiver #(.ClkFrequency(60000000),.Baud(9600)) ext_uart_r(.clk(clk),.RxD(rxd),.RxD_data_ready(ext_uart_ready),.RxD_clear(ext_uart_clear),.RxD_data(ext_uart_rx));
// store RxD_data to read buffer and clear RxD_data after store
assign ext_uart_clear = ext_uart_ready;
// assign ext_uart_rdata = ext_uart_rx;
always @(posedge clk) beginif(ext_uart_ready)ext_uart_rbuffer <= ext_uart_rx;
end
// assign ext_uart_rbuffer = ext_uart_rx;always @(posedge clk) beginif(!ext_uart_busy && write_uart)ext_uart_start <= 1'b1;else ext_uart_start <= 1'b0;
endwire [7:0] ext_uart_tx;// write data
reg [7:0] last_data;
always @(posedge clk) beginlast_data <= ext_uart_tx[7:0];
end
assign ext_uart_tx = write_uart?conf_wdata[7:0]:last_data;
// assign ext_uart_tx = write_uart?conf_wdata[7:0]:8'h0;async_transmitter #(.ClkFrequency(6000_0000),.Baud(9600)) ext_uart_t(.clk(clk),.TxD(txd),.TxD_busy(ext_uart_busy),.TxD_start(ext_uart_start),.TxD_data(ext_uart_tx) // transmit the data in buffer to txd);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/577195.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CEC2013(python):六种算法(RFO、PSO、CSO、WOA、DBO、ABC)求解CEC2013

一、六种算法简介 1、红狐优化算法RFO 2、粒子群优化算法PSO 3、鸡群优化算法CSO 4、鲸鱼优化算法WOA 5、蜣螂优化算法DBO 6、人工蜂群算法 &#xff08;Artificial Bee Colony Algorithm, ABC&#xff09; 二、6种算法求解CEC2013 &#xff08;1&#xff09;CEC2013简…

一篇讲透:箭头函数、普通函数有什么区别

前言 &#x1f4eb; 大家好&#xff0c;我是南木元元&#xff0c;热衷分享有趣实用的文章&#xff0c;希望大家多多支持&#xff0c;一起进步&#xff01; &#x1f345; 个人主页&#xff1a;南木元元 目录 什么是箭头函数 箭头函数和普通函数的区别 更简洁的语法 箭头函数…

Rancher小白学习之路

官网&#xff1a;http://docs.rancher.cn/docs/rancher1/rancher-service/load-balancer/_indexhttp://docs.rancher.cn/docs/rancher1/rancher-service/load-balancer/_indexRancher2.5集群搭建&K3S生产环境搭建手册 - 知乎 【rancher教程】十年运维大佬两小时带你搞定ran…

Order Delivery for WooCommerce电商商城订单一体化交付解决方案

Order Delivery for WooCommerce电商商城订单一体化交付解决方案 Order Delivery for WooCommerce电商商城订单一体化交付解决方案使客户可以在结账过程中轻松选择订单的交付日期和时间。 Order Delivery for WooCommerce电商商城订单一体化交付解决方案 让您的客户以流畅且…

分割时间方法

输入开始时间和结束时间以及时间间隔&#xff0c; 并以数组形式输出其中的时间段 const generateTimeSlots (startHour: number, endHour: number, intervalMinutes: number) > {const slots [];let currentTime new Date().setHours(startHour, 0, 0, 0);const endTime…

redis—String字符串

目录 前言 1.字符串数据类型 2.常见命令 3.典型应用场景 前言 字符串类型是Redis最基础的数据类型&#xff0c;关于字符串需要特别注意: 1)首先Redis中所有的键的类型都是字符串类型&#xff0c;而且其他几种数据结构也都是在字符串类似基础.上构建的&#xff0c;例如列表…

【2024 行人重识别最新进展】ReID3D:首个关注激光雷达行人 ReID 的工作!

【2024 行人重识别最新进展】ReID3D&#xff1a;首个关注激光雷达行人 ReID 的工作&#xff01; 摘要&#xff1a;数据集&#xff1a;方法模型&#xff1a;多任务预训练&#xff1a;ReID Network&#xff1a; 实验结果&#xff1a;结论&#xff1a; 来源&#xff1a;Arxiv 2023…

drf知识--05

两个视图基类 # APIView&#xff1a;之前一直在用---》drf提供的最顶层的父类---》以后所有视图类&#xff0c;都继承自它 # GenericAPIView&#xff1a;继承自APIView--》封装 继承APIView序列化类Response写接口 # urls.py--总路由 from django.contrib import admin from dj…

ElasticSearch 使用映射定义索引结构

动态映射 dynamic 可选值解释true默认值&#xff0c;启用动态映射&#xff0c;新增的字段会添加到映射中runtime查询时动态添加到映射中false禁用动态映射&#xff0c;忽略未知字段strict发现未知字段&#xff0c;抛出异常 显示映射 创建映射 PUT user {"mappings&qu…

Elasticsearch可视化平台Kibana [ES系列] - 第498篇

历史文章&#xff08;文章累计490&#xff09; 《国内最全的Spring Boot系列之一》 《国内最全的Spring Boot系列之二》 《国内最全的Spring Boot系列之三》 《国内最全的Spring Boot系列之四》 《国内最全的Spring Boot系列之五》 《国内最全的Spring Boot系列之六》 全…

three.js后处理(发光描边OutlinePass描边样式

效果&#xff1a; <template><div><el-container><el-main><div class"box-card-left"><div id"threejs" style"border: 1px solid red"></div><div class"box-right"></div>&…

圆通速递查询,圆通速递单号查询,对需要的单号记录进行标记

随着网购的普及&#xff0c;快递服务已经深入到我们生活的方方面面,我们每天都需要处理大量的快递信息,为了更高效地管理这些信息&#xff0c;【快递批量查询高手】应运而生。 所需工具&#xff1a; 一个【快递批量查询高手】软件 圆通速递单号若干 操作步骤&#xff1a; …

不同参数规模大语言模型在不同微调方法下所需要的显存总结

原文来自DataLearnerAI官方网站&#xff1a; 不同参数规模大语言模型在不同微调方法下所需要的显存总结 | 数据学习者官方网站(Datalearner)https://www.datalearner.com/blog/1051703254378255 大模型的微调是当前很多人都在做的事情。微调可以让大语言模型适应特定领域的任…

IntelliJ IDEA快捷键及调试

文章目录 一、IntelliJ IDEA 常用快捷键一览表1-IDEA的日常快捷键第1组&#xff1a;通用型第2组&#xff1a;提高编写速度&#xff08;上&#xff09;第3组&#xff1a;提高编写速度&#xff08;下&#xff09;第4组&#xff1a;类结构、查找和查看源码第5组&#xff1a;查找、…

05|提示工程(下):用思维链和思维树提升模型思考质量 ## 什么是 Chain of Thought

05&#xff5c;提示工程&#xff08;下&#xff09;&#xff1a;用思维链和思维树提升模型思考质量 什么是 Chain of Thought CoT 这个概念来源于学术界&#xff0c;是谷歌大脑的 Jason Wei 等人于 2022 年在论文《Chain-of-Thought Prompting Elicits Reasoning in Large La…

使用代码生成工具快速开发应用-结合后端Web API提供接口和前端页面快速生成,实现通用的业务编码规则管理

1、通用的业务编码规则的管理功能 在前面随笔我们介绍了一个通用的业务编码规则的管理功能&#xff0c;通过代码生成工具Database2Sharp一步步的生成相关的后端和Winform、WPF的界面&#xff0c;进行了整合&#xff0c;通过利用代码生成工具Database2sharp生成节省了常规功能的…

DreamTuner :通过单张图片实现主题驱动的图像生成

该项目由字节跳动开发&#xff0c;你只需要提供一张图片&#xff0c;DreamTuner就能帮你生成与这张图片在主题和风格上一致的新图像。比如你有一张可乐照片&#xff0c;它可以根据你的要求将可乐放在任何场景中或添加其他元素形成一张完美海报&#xff01; 这个工具特别适用于需…

PyAV 使用浅谈

背景&#xff1a; PyAV是一个用于音频和视频处理的Python库&#xff0c;它提供了一个简单而强大的接口&#xff0c;用于解码、编码、处理和分析各种音频和视频格式。PyAV基于FFmpeg多媒体框架&#xff0c;它本质上是FFmpeg 的Python绑定&#xff0c;因此可以利用FFmpeg的功能来…

【MySQL】数据库之事务

目录 一、什么是事务 二、事务的ACID是什么&#xff1f; 三、有哪些典型的不一致性问题&#xff1f; 第一种&#xff1a;脏读 第二种&#xff1a;不可重复读 第三种&#xff1a;幻读 第四种&#xff1a;丢失更新 四、隔离级别有哪些&#xff1f; &#xff08;1&#xf…

Docker介绍、常用命令与操作

Docker介绍、常用命令与操作 学习前言为什么要学习DockerDocker里的必要基础概念常用命令与操作1、基础操作a、查看docker相关信息b、启动或者关闭docker 2、容器操作a、启动一个镜像i、后台运行ii、前台运行 b、容器运行情况查看c、日志查看d、容器删除 3、镜像操作a、镜像拉取…