UART Controlled Stopwatch Using an FPGA


Being able to interface an FPGA project with a device that has a serial port allows for a basic means of sending and receiving data between the FPGA and a PC. For this we need to implement a Universal Asynchronous Receiver Transmitter (UART) circuit within the FPGA system. You can then use a PC with an RS232 Serial port (shown above) to interface the FPGA with a PC, assuming your development board has the port and voltage conversion circuitry.

Because RS232 is a dated protocol that sometimes isn’t available on modern PC’s, another option is to use a USB-Serial converter chip, which connects to a USB port.

In this post I will detail a UART controlled stopwatch, designed for the Basys 2, using the same stopwatch implementation as in my previous post. To communicate with the PC through serial  we will implement a UART receiver and transmitter circuit, as well as a master control circuit that routes all the pieces together. We will then be able to control the stopwatch’s start, stop, and clear functions by sending ascii characters from the PC to the FPGA’s UART receiver circuit, and also send the current stopwatch time from the FPGA’s Transmitter circuit to the PC for viewing.

The UART circuit has an input called rx (receive) and an output called tx (transmit).  The rx from the FPGA UART connects to the tx of the USB-Serial converter, and the tx from the FPGA UART connects to the rx of the USB-Serial converter.

The signal protocol that will be sent or received is shown above. The line is normally held high when idling. The beginning of packet is a start bit, which holds the line low for one time interval. Next comes the 8 bits in the byte, LSB first. Finally a stop bit, which is the line being held high for one time interval. This UART configuration has one start bit, 8 data bits, and 1 stop bit. The time interval for each bit is set by what is called the buad rate. Serial communication doesn’t have a clock line shared between the two systems to synchronize communication. Instead, both systems must agree to communicate with each other at the same baud rate which is given in bits per second. For our implementation we will use 19200 baud.

I will detail a receiver and transmitter algorithm that is similar to what is taught in Pong Chu’s FPGA Prototyping by Verilog Examples.

For the FPGA receiver, instead of sampling the rx input line on each tick from a 19200 baud counter/tick generator, we will have a counter/tick generator that ticks 16 times as fast (307200 ticks per second), and thus will count 16 ticks for every data bit.

When the rx line goes from idle (high) to the start bit (low), the counter will begin counting ticks. A bit for a 19200 baud rate lasts around 52 μs, so if we want that divided into 16 smaller ticks, each will be separated by 3.25 μs. From the beginning of the start bit, the counter will count up every 3.25 μs until tick 8, upon which we will be in the middle of the start bit. The counter will then be reset to 0, and will then continue to tick. When the counter ticks up to 8 again, the first data bit will be conveyed across the line. When the counter reaches 16, we will be in the middle of the first data bit, and we will then sample the data and reset the counter. We then count up to 16 again and sample the next data bit, and so on for the remaining data bits.

By counting up to 16, we ensure that we’re always no more than 1/16 from the middle of the bit when we sample the rx line, regardless of when the asynchronous start bit begins. This scheme is called oversampling, and counting to 16 is slightly arbitrary. Keep in mind that if we needed a higher baud rate, counting to 16 could be limited by the system clock being too slow.

The receiver algorithm is as follows:

  1. When idling and the rx line goes low, the start bit has arrived. Begin counting the oversampling 16*baudrate ticks.
  2. Once 8 ticks are counted, reset the counter.
  3. Once we have counted 16 ticks, we are in the middle of the first data bit. Sample data by shifting it into a register for received data.
  4. Repeat above step 7 more times for remaining data bits.
  5. Finally, rx line is high for stop bit. Count 16 more ticks, then go back to idling.

Let’s look at the code:

module uart_rx
	(
		input wire clk, reset,
		input wire rx, baud_tick,   // input rx line, and 16*baudrate tick 
		output reg rx_done_tick,    // receiver done tick
		output wire [7:0] rx_data   // received data
	);
	
	// symbolic state declarations for FSMD
	localparam [1:0]
		idle  = 2'b00,	// idle state, waiting for rx to go low (start bit)
		start = 2'b01,	// start state, count for 8 baud_ticks
		data  = 2'b10,  // data state, shift in 8 data bits
		stop  = 2'b11;  // stop state, count 16 baud_ticks, assert done_tick
		
	// internal signal declarations
	reg [1:0] state_reg, state_next;   // FSMD state register
	reg [4:0] baud_reg, baud_next;     // register to count baud_ticks
	reg [3:0] n_reg, n_next;           // register to count number of data bits shifted in
	reg [7:0] d_reg, d_next;           // register to hold received data
	
	// FSMD state, baud counter, data counter, and data registers
	always @(posedge clk, posedge reset)
		if (reset)
		    begin
                    state_reg <= idle;
                    baud_reg  <= 0;
                    n_reg     <= 0;
                    d_reg     <= 0;
		    end
		else
		    begin
                    state_reg <= state_next;
                    baud_reg  <= baud_next;
                    n_reg     <= n_next;
                    d_reg     <= d_next;
		    end
			
	// FSMD next state logic
	always @*
		begin
		
		// defaults
		state_next   = state_reg;
		rx_done_tick = 1'b0;
		baud_next    = baud_reg;
		n_next       = n_reg;
		d_next       = d_reg;
		
		case (state_reg)
			idle:                                           // idle, wait for rx to go low (start bit)
				if (~rx)                                // when rx goes low...
					begin
					state_next = start;             // go to start state
					baud_next  = 0;                 // set baud_reg to 0
					end
					
			start:                                          // start, count 8 baud_ticks
				begin 
				if(baud_tick)
					baud_next = baud_reg + 1;       // increment baud_reg every tick 
					
				else if (baud_reg == 8)                 // when baud_reg has counted to 8
					begin
					state_next = data;              // go to data state
					baud_next  = 0;                 // set baud_reg and
					n_next     = 0;                 // data bit count reg to 0
					end 
				end
				
			data:                                           // data, shift in 8 data bits to data reg
				begin
				if(baud_tick)
					baud_next = baud_reg + 1;       // increment baud_reg every tick  
					
				else if(baud_reg == 16)                 // when baud_reg counted 16 ticks...
					begin
					d_next    = {rx, d_reg[7:1]};   // left shift in rx data to received data reg
					n_next    = n_reg + 1;          // increment data bit count
					baud_next = 0;                  // reset baud tick counter to 0
					end
					
				else if(n_reg == 8)                     // once 8 data bits have been shifted in...
					state_next = stop ;             // move to stop state
				end
				
			stop:                                           // stop, rx line is high for 16 baud_ticks (stop bit)
				begin
				if(baud_tick)
					baud_next = baud_reg + 1;       // increment baud_reg every tick 
					
				else if (baud_reg == 16)                // once 16 baud_ticks have been counted
					begin
					state_next   = idle;            // go to idle state
					rx_done_tick = 1'b1;            // assert receive done tick
					end
				end        
		endcase
		end
	
	// output received data
 	assign rx_data = d_reg; 
endmodule

 

The circuit takes an input for the rx line, baud_tick from an external tick generator, and has outputs for the receive done_tick and received data. A finite state machine with data path (FSMD) is implemented to achieve the receiver algorithm that we specified.

Next we need to create a transmitter circuit that can transmit a packet across the FPGA UART tx line to the PC for the stopwatch time.

The transmitter circuit will have a similar algorithm as the receiver, except we now need to transmit a proper packet according to the agreement of having the FPGA tx line to the rx line of PC UART held high when idle, go low for a start bit, convey 8 data bits, and a final stop bit, all at the agreed 19200 baud rate. We will use the same oversampling baud_tick generator to schedule the state of the tx line.

The algorithm is as follows:

  1. Until we need to transmit a data packet, idle and hold tx high.
  2. Once tx_start input is asserted telling us to send data, pull tx low and count 16 of the baud_ticks.
  3. Shift in the data to be transmitted to a data register.
  4. Set tx to the state of the LSB of the data register and count for 16 baud_ticks.
  5. Right shift the data register by one to shift the next data bit to the LSB.
  6. Repeat steps 4 and 5 seven more times for the remaining data bits to be transferred.
  7. Hold tx high for 16 baud_ticks to transmit the stop bit, and go back to idle.

Let’s look at the code:

module uart_tx
	(
		input wire clk, reset,
		input wire tx_start, baud_tick,   // input to start transmitter, and 16*baudrate tick
		input wire [7:0] tx_data,         // data to transmit
		output reg tx_done_tick,          // transmit done tick
		output wire tx                    // output tx line
	);
	
	// symbolic state declarations
	localparam [1:0]
		idle  = 2'b00,	// idle state, waiting for tx_start to be asserted, load tx_data to output shift register
		start = 2'b01,	// start state, hold tx low for 16 baud ticks
		data  = 2'b10,	// transmit 8 data bits 
		stop  = 2'b11;  // stop bit, hold line high for 16 baud ticks
		
	// internal signal declarations
	reg [1:0] state_reg, state_next; // FSMD state register
	reg [4:0] baud_reg, baud_next;   // register to count baud_ticks
	reg [4:0] n_reg, n_next;         // register to count number of data bits shifted out
	reg [7:0] d_reg, d_next;         // register holds data to transmit
	reg tx_reg, tx_next;             // register to hold current state of tx line
	
	// FSMD state, baud counter, data counter, and data registers
	always @(posedge clk, posedge reset)
		if (reset)
			begin
			state_reg <= idle;
                        baud_reg  <= 0;
                        n_reg     <= 0;
                        d_reg     <= 0;
                        tx_reg    <= 1'b1;
			end
		else
			begin
                        state_reg <= state_next;
                        baud_reg  <= baud_next;
                        n_reg     <= n_next;
                        d_reg     <= d_next;
                        tx_reg    <= tx_next;
			end
	
	// FSMD next state logic
	always @*
		begin
		
		// defaults
		state_next   = state_reg;
		tx_done_tick = 1'b0;
		baud_next    = baud_reg;
		n_next       = n_reg;
		d_next       = d_reg;
		tx_next      = tx_reg;
		
		case (state_reg)
		
			idle:                                      // idle, hold tx line high, until tx_start asserted...
				begin
				tx_next = 1'b1;
				if (tx_start)                      // when tx_start input asserted
					begin
					state_next = start;        // go to start state
					baud_next  = 0;            // reset baud reg to 0
					d_next     = tx_data;      // load data to transit to data register
				        end
                                end
			
			start:                                     // start, hold line low for 16 baud_ticks 
				begin
				tx_next = 1'b0;                    // hold tx low
				
				if(baud_tick)
					baud_next = baud_reg + 1;  // increment baud_reg every tick  
					
				else if(baud_reg == 16)            // once 16 baud_ticks counted...
					begin
					state_next = data;         // go to data state
					baud_next  = 0;            // reset baud counter 
					n_next     = 0;            // and data bit counter to 0
					end
				end
			
			data:
				begin
				tx_next = d_reg[0];                // transmit LSB of data reg
				
				if(baud_tick)
					baud_next = baud_reg + 1;  // increment baud_reg every tick 
				
				else if(baud_reg == 16)            // once 16 baud_ticks counted...
					begin
					d_next    = d_reg >> 1;    // right shift data reg by 1, LSB is next bit to transfer out
					baud_next = 0;             // reset baud_tick counter
					n_next    = n_reg + 1;     // increment data bit counter
					end
				
				else if(n_reg == 8)                // once 8 data bits have been shifted out and transfered...
					state_next = stop ;        // move to stop state
				end
				
			stop:                                      // stop, hold line high for 16 baud_ticks
				begin
				tx_next = 1'b1;                    // hold tx high
				
				if(baud_tick)
					baud_next = baud_reg + 1;  // increment baud_reg every tick
					
				else if(baud_reg == 16)            // once 16 baud_ticks counted
					begin
					state_next = idle;         // go to idle state
					tx_done_tick = 1'b1;       // assert transfer done tick
					end
				end
		endcase
		end
	
	// output
	assign tx = tx_reg;
endmodule

 

The circuit takes an input to start transmitting data, a baud tick from an external baud tick generator, as well as the 8 bits to be transmitted. The circuit has an output tx line, and a line for the transmission done tick. A FSMD is again implemented to achieve the transmitter algorithm that we specified.

Next we will implement the complete UART controlled stopwatch circuit. We will use the stopwatch circuit from the previous post, as well as the seven-segment multiplexing circuit.

The master circuit will also implement a FSMD, with an idle state that processes received ASCII characters from the FPGA UART receiver circuit. A c/C will clear the minSecTimer, a g/G will start the timer, a s/S will stop it, and an r/R will move to the next FSM state to start transmitting the time through the FPGA UART transmitter circuit to the PC UART receiver. Five states follow the idle state, two for the minute values to be transmitted, one for a ‘.’ to be transmitted, and two for the second values to be transmitted.

It will also have the 16*19200 baud rate tick generator for the receiver and transmitter circuits. Since our system clock runs at 50Mhz, to tick 16*19200 = 307200 times a second, we will need to count 50E6/307200 = 163 system clock cycles before outputting a tick.

Let’s look at the code:

// uart controlled stopwatch
module uart_stopwatch

	( 
	  input  wire clk, reset,        // clock reset input lines
	  input  wire rx,	         // receive
	  output wire tx,		 // transmit
	  output wire [3:0] an,          // enable for 4 displays
	  output wire [7:0] sseg         // led segments

	);
	
	// internal signal declarations
	wire [3:0] d3, d2, d1, d0;              // routing connections from BCD values of minSecTimer to display and for tx to PC
	reg  [7:0] r_reg;                       // baud rate generator register
	wire [7:0] r_next;                      // baud rate generator next state logic
	wire tick;                              // baud tick for uart_rx & uart_tx
	wire [7:0] rx_data_out;                 // routing path for received ascii data 
	reg tx_start, go_sw, stop_sw, clear_sw; // registers to control minSecTimer and start tx to PC
	reg [2:0] state_reg, state_next;        // FSM state register and next state logic
	wire rx_done_tick, tx_done_tick;        // done ticks for rx and tx circuits
	reg [7:0] tx_d_next, tx_d_reg;          // register for tx output to PC

	// tx_d register for tx data to PC
	always @(posedge reset, posedge clk)
		if(reset)
			tx_d_reg <= 8'b00000000;
		else 
			tx_d_reg <= tx_d_next;
	
	// FSM states
	localparam [2:0] idle         = 3'b000,
			 transmit_d3  = 3'b001,
		         transmit_d2  = 3'b010,
		         transmit_dot = 3'b011,
			 transmit_d1  = 3'b100,
			 transmit_d0  = 3'b101;
					 
	// FSM register
	always @(posedge reset, posedge clk)
		if(reset)
			state_reg <= idle;
		else 
			state_reg <= state_next;
	
	// FSM next state logic
	always @*
		begin
		
		// defaults
		state_next = state_reg;
		tx_d_next  = 8'b00000000;
		tx_start   = 1'b0;
		clear_sw   = 1'b0;
		go_sw      = 1'b0;
		stop_sw    = 1'b0;
		
		case(state_reg)
			idle: 
				begin
				if(rx_done_tick)  // when rx data received and ready, check value
					begin
					if(rx_data_out == 8'b01000011 || rx_data_out == 8'b01100011)       // if 'c' or 'C', enable clear_sw register to minSecTimer
						clear_sw = 1'b1;
					else if(rx_data_out == 8'b01000111 || rx_data_out == 8'b01100111)  // if 'g' or 'G', enable go_sw register to minSecTimer
						go_sw = 1'b1;
					else if(rx_data_out == 8'b01010011 || rx_data_out == 8'b01110011)  // if 's' or 'S', enable stop_sw register to minSecTimer
						stop_sw = 1'b1;
					else if(rx_data_out == 8'b01010010 || rx_data_out == 8'b01110010)  // if 'r' or 'R', load tx_d next state, go to next FSM state
						begin
						tx_d_next  = {4'b0011, d3};
						state_next = transmit_d3;
						end
				        end
				end
				
			transmit_d3:
				begin
				tx_start = 1'b1;                    // begin sending data across tx
				if(tx_done_tick)                    // when done
					begin
					tx_d_next  = {4'b0011, d2}; // load next data
					state_next = transmit_d2;   // go to next state
					end
				end
				
			transmit_d2:
				begin
				tx_start = 1'b1;
				if(tx_done_tick)
					begin
					tx_d_next  = 8'b00101110;
					state_next = transmit_dot;
					end
				end
			
			transmit_dot:
				begin
				tx_start = 1'b1;
				if(tx_done_tick)
					begin
					tx_d_next  = {4'b0011, d1};
					state_next = transmit_d1;
					end
				end
				
			transmit_d1:
				begin
				tx_start = 1'b1;
				if(tx_done_tick)
					begin
					tx_d_next  = {4'b0011, d0};
					state_next = transmit_d0;
					end
				end
				
			transmit_d0:
				begin
				tx_start = 1'b1;       
				if(tx_done_tick)               // when final tx data is sent
					state_next = idle;     // go back to idle
				end
			endcase
			end					
	
	// register for oversampling baud rate generator
	always @(posedge clk, posedge reset)
		if(reset)
			r_reg <= 0;
		else
			r_reg <= r_next;
	
	// next state logic, mod 163 counter
	assign r_next = r_reg == 163 ? 0 : r_reg + 1;
	
	// tick high once every 163 clock cycles, for 19200 baud
	assign tick = r_reg == 163 ? 1 : 0;
	
	
	// instantiate uart rx ciruit
	uart_rx uart_rx_unit (.clk(clk), .reset(reset), .rx(rx), .baud_tick(tick), .rx_done_tick(rx_done_tick), .rx_data(rx_data_out));
	
	// instantiate uart tx circuit
	uart_tx uart_tx_unit (.clk(clk), .reset(reset), .tx_start(tx_start), .baud_tick(tick), .tx_data(tx_d_reg), .tx_done_tick(tx_done_tick), .tx(tx));
	
	// instantiate stopwatch timer circuit
	minSecTimer timer (.clk(clk), .go(go_sw), .stop(stop_sw), .clr(clear_sw), .d3(d3), .d2(d2), .d1(d1), .d0(d0));
	
	// instantiate seven segment display circuit
	displayMuxBasys display_unit (.clk(clk), .hex3(d3), .hex2(d2), .hex1(d1), .hex0(d0), .dp_in(4'b1011), .an(an), .sseg(sseg));
	
endmodule 

 

The tx and rx lines are routed to GPIO on the FPGA board that are then connected with wires to the corresponding rx and tx inputs on the USB-Serial converter, which we then connect to a PC with a USB cable. The FPGA ground is also connected to the ground of the USB-Serial converter with a wire.

We then need a serial terminal such as Bray’s Terminal to send and receive data on the PC side. We have to be sure to set the baud rate to 19200, data bits to 8, no parity bit, and 1 stop bit. To make the experience a little nicer, I wrote a short Python program to communicate with the FPGA using pyserial.

# Serial interface for FPGA implementation of UART controlled stopwatch

# setup serial port
import serial                   
PORT = 'COM8' 
BAUD = 19200
s = serial.Serial(PORT, BAUD)
s.flush()

# variables to hold received user input and received timestamp from FPGA
c = ''
r = ''

# welcome message
print("Stopwatch Control Interface:\nEnter g or G to go\nEnter s or S to stop\nEnter c or C to clear\nEnter r or R to receive time\nEnter e or E to exit\n")

# event loop
while(True):
    # get user input
    c = input()

    # user wants to exit
    if(c == 'e' or c == 'E'):
        s.close()
        
        print("goodbye!\n")
        break

    # start stopwatch
    elif(c == 'g' or c == 'G'):
        print("start\n")
        # write encoded input over UART
        s.write(c.encode())
        
    # stop stopwatch
    elif(c == 's' or c == 'S'):
        print("stop\n")
        # write encoded input over UART
        s.write(c.encode())

    # clear stopwatch
    elif(c =='c' or c == 'C'):
        print("clear\n")
        # write encoded input over UART
        s.write(c.encode())

    # received timestamp
    elif(c == 'r' or c == 'R'):
        # write encoded input over UART
        s.write(c.encode())
        print("receive\n")
        
        #reset received timestamp
        r = ''
        # read in 5 bytes, decode to char and append to r
        for i in range(5):
            r += (s.read(1)).decode()
        print(r)

    # invalid input
    else:
        print("invalid input\n")
        
    

 

The pyserial module needs to be installed for Python 3, which can be done from the command line with the command: pip install pyserial .

Above is a demonstration of the system being controlled with the python program.

Here is a link to the full source code on GitHub along with the UCF for the Basys 2 that was used.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s