差别
这里会显示出您选择的修订版和当前版本之间的差别。
| 两侧同时换到之前的修订记录 前一修订版 后一修订版 | 前一修订版 | ||
|
rs-232 [2017/03/26 19:34] gongyu |
rs-232 [2017/03/26 20:00] (当前版本) gongyu [应用案例] |
||
|---|---|---|---|
| 行 160: | 行 160: | ||
| 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. | ||
| 行 166: | 行 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. | ||