差别
这里会显示出您选择的修订版和当前版本之间的差别。
两侧同时换到之前的修订记录 前一修订版 后一修订版 | 前一修订版 | ||
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. |