差别
这里会显示出您选择的修订版和当前版本之间的差别。
| 两侧同时换到之前的修订记录 前一修订版 后一修订版 | 前一修订版 | ||
|
rs-232 [2017/03/26 19:28] gongyu |
rs-232 [2017/03/26 20:00] (当前版本) gongyu [应用案例] |
||
|---|---|---|---|
| 行 91: | 行 91: | ||
| 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. | 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 | // let's assume the FPGA clock signal runs at 1.8432MHz | ||
| // we create a 4-bit counter | // we create a 4-bit counter | ||
| 行 98: | 行 100: | ||
| // and a tick signal that is asserted once every 16 clocks (so 115200 times a second) | // and a tick signal that is asserted once every 16 clocks (so 115200 times a second) | ||
| wire BaudTick = (BaudDivCnt==15); | 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. | 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: | Look at the following "C" code: | ||
| + | <code c> | ||
| while(1) // repeat forever | while(1) // repeat forever | ||
| { | { | ||
| 行 109: | 行 114: | ||
| acc %= 2000000; | acc %= 2000000; | ||
| } | } | ||
| + | </code> | ||
| + | |||
| That prints the "*" in the exact ratio, once every "17.361111111..." loops on average. | That prints the "*" in the exact ratio, once every "17.361111111..." loops on average. | ||
| 行 115: | 行 122: | ||
| 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. | 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 | // 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 | // we use a 10-bit accumulator plus an extra bit for the accumulator carry-out | ||
| 行 124: | 行 133: | ||
| wire BaudTick = acc[10]; // so that the 11th bit is the accumulator carry-out | 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. | Using our 2MHz clock, "BaudTick" is asserted 115234 times a second, a 0.03% error from the ideal 115200. | ||
| 行 132: | 行 144: | ||
| Here's a design with a 25MHz clock and a 16 bits accumulator. The design is parameterized, so easy to customize. | 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 ClkFrequency = 25000000; // 25MHz | ||
| parameter Baud = 115200; | parameter Baud = 115200; | ||
| 行 142: | 行 155: | ||
| wire BaudTick = BaudGeneratorAcc[BaudGeneratorAccWidth]; | 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. | 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); | parameter BaudGeneratorInc = ((Baud<<(BaudGeneratorAccWidth-4))+(ClkFrequency>>5))/(ClkFrequency>>4); | ||
| + | </code> | ||
| This line has also the added advantage to round the result instead of truncating. | This line has also the added advantage to round the result instead of truncating. | ||
| 行 150: | 行 168: | ||
| Now that we have a precise enough Baud generator, we can go ahead with the RS-232 transmitter and receiver modules. | 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. | ||