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