差别

这里会显示出您选择的修订版和当前版本之间的差别。

到此差别页面的链接

两侧同时换到之前的修订记录 前一修订版
后一修订版
前一修订版
rs-232 [2017/03/26 19:25]
gongyu
rs-232 [2017/03/26 20:00] (当前版本)
gongyu [应用案例]
行 86: 行 86:
   * 一个[[http://​www.taltech.com/​datacollection/​articles/​serial_intro|关于RS232串行通信的介绍]]   * 一个[[http://​www.taltech.com/​datacollection/​articles/​serial_intro|关于RS232串行通信的介绍]]
  
-==== 波特率发生器 ==== +====== 波特率发生器 ====== 
-==== 发送器 ==== +Here we want to use the serial link at maximum speed, i.e. 115200 bauds (slower speeds would also be easy to generate). FPGAs usually run at MHz speeds, well above 115200Hz (RS-232 is pretty slow by today'​s standards). We need to find a way to generate (from the FPGA clock) a "​tick"​ as close as possible to 115200 times a second. 
-==== 接收器 ==== + 
-==== 应用案例 ====+Traditionally,​ RS-232 chips use a 1.8432MHz clock, because that makes generating the standard baud frequencies very easy... 1.8432MHz divided by 16 gives 115200Hz. 
 + 
 +------ 
 +<code verilog>​ 
 +// 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);​ 
 +</​code>​ 
 + 
 +That was easy. But what do you do if instead of 1.8432MHz, you have a 2MHz clock? To generate 115200Hz from a 2MHz clock, we need to divide the clock by "​17.361111111..."​ Not exactly a round number. The solution is to divide sometimes by 17, sometimes by 18, making sure the ratio stays "​17.361111111"​. That's actually easy to do. 
 + 
 +Look at the following "​C"​ code: 
 + 
 +<code c> 
 +while(1) // repeat forever 
 +
 +  acc += 115200; 
 +  if(acc>​=2000000) printf("​*"​);​ else printf("​ "); 
 + 
 +  acc %= 2000000; 
 +
 +</​code>​ 
 + 
 +That prints the "​*"​ in the exact ratio, once every "​17.361111111..."​ loops on average. 
 + 
 +To obtain the same thing efficiently in an FPGA, we rely on the fact that the serial interface can tolerate a few % of error in the baud frequency generator. 
 + 
 +It is desirable that the 2000000 be a power of two. Obviously 2000000 is not. So we change the ratio... Instead of "​2000000/​115200",​ let's use "​1024/​59"​ = 17.356. That's very close to our ideal ratio, and makes an efficient FPGA implementation:​ we use a 10-bit accumulator incremented by 59, with a tick marked everytime the accumulator overflows. 
 + 
 +------ 
 +<code verilog>​ 
 +// 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 result 
 + 
 +wire BaudTick = acc[10]; // so that the 11th bit is the accumulator carry-out 
 + 
 +</​code>​ 
 + 
 +Using our 2MHz clock, "​BaudTick"​ is asserted 115234 times a second, a 0.03% error from the ideal 115200. 
 + 
 +Parameterized FPGA baud generator 
 + 
 +The previous design was using a 10 bits accumulator,​ but as the clock frequency increases, more bits are required. 
 + 
 +Here's a design with a 25MHz clock and a 16 bits accumulator. The design is parameterized,​ so easy to customize. 
 + 
 +<code verilog>​ 
 +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];​ 
 + 
 +</​code>​ 
 + 
 +One last implementation issue: the "​BaudGeneratorInc"​ calculation is wrong, due to the fact that Verilog uses 32 bits intermediate results, and the calculation exceeds that. Change the line as follow for a workaround. 
 + 
 +<code verilog>​ 
 +parameter BaudGeneratorInc = ((Baud<<​(BaudGeneratorAccWidth-4))+(ClkFrequency>>​5))/​(ClkFrequency>>​4);​ 
 +</​code>​ 
 + 
 +This line has also the added advantage to round the result instead of truncating. 
 + 
 +Now that we have a precise enough Baud generator, we can go ahead with the RS-232 transmitter and receiver modules. 
 + 
 +====== 发送器 ====== 
 +We are building an "async transmitter"​ with fixed parameters: 8 data bits, 2 stop bits, no-parity. 
 +{{ :​serialtxdmodule.gif |}} 
 + 
 +It works like that: 
 + 
 +  * The transmitter takes an 8-bits data inside the FPGA and serializes it (starting when the "​TxD_start"​ signal is asserted). 
 +  * The "​busy"​ signal is asserted while a transmission occurs (the "​TxD_start"​ signal is ignored during that time). 
 + 
 +===== Serializing the data ===== 
 + 
 +To go through the start bit, the 8 data bits, and the stop bits, a state machine seems appropriate. 
 +<code verilog>​ 
 +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;​ // start 
 +  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;​ // stop1 
 +  4'​b0010:​ if(BaudTick) state <= 4'​b0000;​ // stop2 
 +  default: if(BaudTick) state <= 4'​b0000;​ 
 +endcase 
 +</​code>​ 
 + 
 +Now, we just need to generate the "​TxD"​ output. 
 + 
 +<code verilog>​ 
 +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); 
 + 
 +</​code>​ 
 + 
 +====== 接收器 ====== 
 +We are building an "async receiver":​ 
 + 
 +{{ :​serialrxdmodule.gif |}} 
 + 
 +Our implementation works like that: 
 + 
 +  * The module assembles data from the RxD line as it comes. 
 +  * As a byte is being received, it appears on the "​data"​ bus. Once a complete byte has been received, "​data_ready"​ is asserted for one clock. 
 + 
 +Note that "​data"​ is valid only when "​data_ready"​ is asserted. The rest of the time, don't use it as new data may come that shuffles it. 
 + 
 +===== Oversampling ===== 
 + 
 +An asynchronous receiver has to somehow get in-sync with the incoming signal (it normally doesn'​t have access to the clock used by the transmitter). 
 + 
 +To determine when a new data byte is coming, we look for the "​start"​ bit by oversampling the signal at a multiple of the baud rate frequency. 
 +Once the "​start"​ bit is detected, we sample the line at the known baud rate to acquire the data bits. 
 +Receivers typically oversample the incoming signal at 16 times the baud rate. We use 8 times here... For 115200 bauds, that gives a sampling rate of 921600Hz. 
 + 
 +Let's assume that we have a "​Baud8Tick"​ signal available, asserted 921600 times a second. 
 + 
 +===== The design ===== 
 + 
 +First, the incoming "​RxD"​ signal has no relationship with our clock. 
 +We use two D flip-flops to oversample it, and synchronize it to our clock domain. 
 + 
 +<code verilog>​ 
 +reg [1:0] RxD_sync; 
 +always @(posedge clk) if(Baud8Tick) RxD_sync <= {RxD_sync[0],​ RxD}; 
 +</​code>​ 
 + 
 +We filter the data, so that short spikes on the RxD line aren't mistaken with start bits. 
 +<code verilog>​ 
 +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 
 +</​code>​ 
 + 
 +A state machine allows us to go through each bit received, once a "​start"​ is detected. 
 +<code verilog>​ 
 +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 
 +</​code>​ 
 + 
 +Notice that we used a "​next_bit"​ signal, to go from bit to bit. 
 +<code verilog>​ 
 +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);​ 
 +Finally a shift register collects the data bits as they come. 
 + 
 +reg [7:0] RxD_data; 
 +always @(posedge clk) if(Baud8Tick && next_bit && state[3]) RxD_data <= {RxD_bit, RxD_data[7:​1]};​ 
 +</​code>​ 
 + 
 + 
 +====== 应用案例 ====== 
 +This design allows controlling a few FPGA pins from your PC (through your PC's serial port). 
 + 
 +  * It create 8 outputs on the FPGA (port named "​GPout"​). GPout is updated by any character that the FPGA receives. 
 +  * Also 8 inputs on the FPGA (port named "​GPin"​). GPin is transmitted every time the FPGA receives a character. 
 + 
 +The GP outputs can be used to control anything remotely from your PC, might be LEDs or a coffee machine... 
 +<code verilog>​ 
 +module serialGPIO( 
 +    input clk, 
 +    input RxD, 
 +    output TxD, 
 + 
 +    output reg [7:0] GPout, ​ // general purpose outputs 
 +    input [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 
 + 
 +</​code>​ 
 + 
 +Remember to grab the async_receiver and async_transmitter modules here, and to update the clock frequency values inside.