RS232 and RS485 Standards
Serial communication serves as a foundational protocol for hardware interfaces. RS232 utilizes dedicated transmission (TX) and reception (RX) lines for full-duplex operation. Data transfer speed is defined by the baud rate (bits per second), with standard frequencies including 9600, 38400, and 115200 bps.
RS485 offers distinct advantages over RS232:
- Electrical Characteristics: RS232 relies on single-ended signaling with negative voltage for logic '1' and positive for logic '0'. RS485 employs differential signaling across A and B lines, providing superior noise immunity and supporting multi-drop configurations.
- Distance and Speed: RS232 is limited to short distances (tens of meters) and speeds up to 115.2 kbps. RS485 can span thousands of meters and reach speeds up to 10 Mbps.
- Topology: RS232 is strictly point-to-point, whereas RS485 supports bus topologies with multiple nodes.
- Duplex Mode: RS232 operates in full-duplex. RS485 can function in either half-duplex or full-duplex modes depending on the wiring.
UART Frame Timing
The serial data frame follows a strict sequence:
- Idle State: The bus remains high.
- Start Bit: A single low bit indicates the beginning of a frame.
- Data Bits: Typically 8 bits, transmitted LSB first.
- Parity Bit: Optional error detection. Even parity ensures the total count of '1's is even; odd parity ensures it is odd. Often omitted in favor of protocol-level checksums.
- Stop Bit: One or two high bits signal the end of the frame.
A standard 8-bit frame without parity requires 10 clock cycles (1 start + 8 data + 1 stop). Including parity increases this to 11 cycles.
Verilog Implementation: Configurable UART Core
The following module implements a highly configurable UART transmitter and receiver, supporting variable baud rates, data widths, parity generation/checking, and stop bit lengths.
module uart_core #(
parameter CLK_FREQ = 100_000_000,
parameter BAUD_RATE = 115200
)(
input wire clk_i,
input wire rst_n_i,
// Configuration
input wire [1:0] cfg_data_len_i, // 00:5, 01:6, 10:7, 11:8
input wire [1:0] cfg_parity_i, // 00:None, 01:Odd, 10:Even
input wire cfg_stop_len_i, // 0:1-bit, 1:2-bit
// TX Interface
input wire [7:0] tx_data_i,
input wire tx_valid_i,
output wire tx_ready_o,
output reg tx_pin_o,
// RX Interface
output reg [7:0] rx_data_o,
output reg rx_valid_o,
input wire rx_pin_i
);
localparam DIV_FACTOR = CLK_FREQ / BAUD_RATE;
// --- TX Logic ---
localparam TX_IDLE = 3'd0, TX_START = 3'd1, TX_DATA = 3'd2,
TX_PAR = 3'd3, TX_STOP1 = 3'd4, TX_STOP2 = 3'd5;
reg [2:0] tx_state;
reg [31:0] tx_clk_cnt;
reg [3:0] tx_bit_idx;
reg [7:0] tx_shift_reg;
reg tx_parity_bit;
assign tx_ready_o = (tx_state == TX_IDLE);
always @(posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
tx_state <= TX_IDLE;
tx_pin_o <= 1'b1;
tx_clk_cnt <= 0;
tx_bit_idx <= 0;
tx_shift_reg <= 0;
end else begin
case (tx_state)
TX_IDLE: begin
tx_pin_o <= 1'b1;
tx_clk_cnt <= 0;
if (tx_valid_i && tx_ready_o) begin
tx_shift_reg <= tx_data_i;
tx_state <= TX_START;
end
end
TX_START: begin
if (tx_clk_cnt == DIV_FACTOR - 1) begin
tx_clk_cnt <= 0;
tx_pin_o <= 1'b0; // Start bit
tx_state <= TX_DATA;
tx_bit_idx <= 0;
end else tx_clk_cnt <= tx_clk_cnt + 1;
end
TX_DATA: begin
if (tx_clk_cnt == DIV_FACTOR - 1) begin
tx_clk_cnt <= 0;
tx_pin_o <= tx_shift_reg[0];
tx_shift_reg <= {1'b0, tx_shift_reg[7:1]};
tx_bit_idx <= tx_bit_idx + 1;
case (cfg_data_len_i)
2'b00: if (tx_bit_idx == 4) tx_state <= TX_PAR;
2'b01: if (tx_bit_idx == 5) tx_state <= TX_PAR;
2'b10: if (tx_bit_idx == 6) tx_state <= TX_PAR;
2'b11: if (tx_bit_idx == 7) tx_state <= TX_PAR;
default: tx_state <= TX_PAR;
endcase
end else tx_clk_cnt <= tx_clk_cnt + 1;
end
TX_PAR: begin
if (tx_clk_cnt == DIV_FACTOR - 1) begin
tx_clk_cnt <= 0;
case (cfg_data_len_i)
2'b00: tx_parity_bit = ^tx_data_i[4:0];
2'b01: tx_parity_bit = ^tx_data_i[5:0];
2'b10: tx_parity_bit = ^tx_data_i[6:0];
2'b11: tx_parity_bit = ^tx_data_i[7:0];
endcase
case (cfg_parity_i)
2'b00: tx_pin_o <= 1'b1; // None
2'b01: tx_pin_o <= ~tx_parity_bit; // Odd
2'b10: tx_pin_o <= tx_parity_bit; // Even
default: tx_pin_o <= 1'b1;
endcase
tx_state <= TX_STOP1;
end else tx_clk_cnt <= tx_clk_cnt + 1;
end
TX_STOP1: begin
if (tx_clk_cnt == DIV_FACTOR - 1) begin
tx_clk_cnt <= 0;
tx_pin_o <= 1'b1;
if (!cfg_stop_len_i) tx_state <= TX_IDLE;
else tx_state <= TX_STOP2;
end else tx_clk_cnt <= tx_clk_cnt + 1;
end
TX_STOP2: begin
if (tx_clk_cnt == DIV_FACTOR - 1) begin
tx_clk_cnt <= 0;
tx_state <= TX_IDLE;
end else tx_clk_cnt <= tx_clk_cnt + 1;
end
endcase
end
end
// --- RX Logic ---
localparam RX_IDLE = 3'd0, RX_START = 3'd1, RX_DATA = 3'd2,
RX_PAR = 3'd3, RX_STOP1 = 3'd4, RX_STOP2 = 3'd5;
reg [2:0] rx_state;
reg [31:0] rx_clk_cnt;
reg [3:0] rx_bit_idx;
reg [7:0] rx_shift_reg;
// Synchronizer
reg rx_sync_0, rx_sync_1;
always @(posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
rx_sync_0 <= 1'b1;
rx_sync_1 <= 1'b1;
end else begin
rx_sync_0 <= rx_pin_i;
rx_sync_1 <= rx_sync_0;
end
end
always @(posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
rx_state <= RX_IDLE;
rx_clk_cnt <= 0;
rx_bit_idx <= 0;
rx_shift_reg <= 0;
rx_data_o <= 0;
rx_valid_o <= 0;
end else begin
rx_valid_o <= 0;
case (rx_state)
RX_IDLE: begin
rx_clk_cnt <= 0;
rx_bit_idx <= 0;
if (rx_sync_1 == 1'b0) begin
rx_state <= RX_START;
end
end
RX_START: begin
if (rx_clk_cnt == (DIV_FACTOR/2) - 1) begin
rx_clk_cnt <= 0;
if (rx_sync_1 == 1'b0) rx_state <= RX_DATA;
else rx_state <= RX_IDLE;
end else rx_clk_cnt <= rx_clk_cnt + 1;
end
RX_DATA: begin
if (rx_clk_cnt == DIV_FACTOR - 1) begin
rx_clk_cnt <= 0;
rx_shift_reg <= {rx_sync_1, rx_shift_reg[7:1]};
rx_bit_idx <= rx_bit_idx + 1;
case (cfg_data_len_i)
2'b00: if (rx_bit_idx == 4) rx_state <= (cfg_parity_i == 2'b00) ? RX_STOP1 : RX_PAR;
2'b01: if (rx_bit_idx == 5) rx_state <= (cfg_parity_i == 2'b00) ? RX_STOP1 : RX_PAR;
2'b10: if (rx_bit_idx == 6) rx_state <= (cfg_parity_i == 2'b00) ? RX_STOP1 : RX_PAR;
2'b11: if (rx_bit_idx == 7) rx_state <= (cfg_parity_i == 2'b00) ? RX_STOP1 : RX_PAR;
default: rx_state <= RX_PAR;
endcase
end else rx_clk_cnt <= rx_clk_cnt + 1;
end
RX_PAR: begin
if (rx_clk_cnt == DIV_FACTOR - 1) begin
rx_clk_cnt <= 0;
rx_state <= RX_STOP1;
end else rx_clk_cnt <= rx_clk_cnt + 1;
end
RX_STOP1: begin
if (rx_clk_cnt == DIV_FACTOR - 1) begin
rx_clk_cnt <= 0;
if (!cfg_stop_len_i) begin
rx_data_o <= rx_shift_reg;
rx_valid_o <= 1'b1;
rx_state <= RX_IDLE;
end else rx_state <= RX_STOP2;
end else rx_clk_cnt <= rx_clk_cnt + 1;
end
RX_STOP2: begin
if (rx_clk_cnt == DIV_FACTOR - 1) begin
rx_clk_cnt <= 0;
rx_data_o <= rx_shift_reg;
rx_valid_o <= 1'b1;
rx_state <= RX_IDLE;
end else rx_clk_cnt <= rx_clk_cnt + 1;
end
endcase
end
end
endmoduleSimulation Waveform Analysis
During FPGA simulation, the transmission sequence is observed as follows:
- tx_valid_i pulses high for one clock cycle, loading the data payload (e.g., 0x12) into the core.
- tx_pin_o drives low for the start bit, followed by the LSB-first data bits (0b01001000), and finally drives high for the stop bit.
- On the receiver side, rx_valid_o asserts high once the full frame has been sampled and decoded.