Dillon Nichols
Published © CC BY

High Voltage Coil (Relay/Contactor) Characterization

A self-contained, smart tester that can determine the characteristics of a coil such as a relay.

IntermediateFull instructions provided5 hours1,261
High Voltage Coil (Relay/Contactor) Characterization

Things used in this project

Hardware components

CoolMOS C7 Gold SJ MOSFET
Infineon CoolMOS C7 Gold SJ MOSFET
×1
Infineon 2EDN7424FXTMA1
×1
TinyFPGA BX
×1

Software apps and online services

Autodesk EAGLE
apio
Mentor Graphics ModelSim

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Code

Automatic Control Firmware

Verilog
This is the top level code for the automatic control version of the firmware. This code steps through the state machine described and turns on the MOSFET at full power.
`timescale 1ns / 1ps
module top (
	input CLK,    // 16MHz clock
	input RST,    // Active low reset button
  output LED,   // User/boot LED next to power LED
  input  PRESENT,   // detects presence of the coil
  output reg SWITCH_EN, // enable the gate driver
  output reg SWITCH,    // enable the relay
  input  ENABLED,   // detects that the MOSFET was enabled
  input  ENGAGED,   // detects that the relay output has switched
  input  MEASURE,   // PWM signal related to the COIL voltage
  output USBPU,  // USB pull-up resistor
	output tx  // TX UART line
);

// drive USB pull-up resistor to '0' to disable USB
assign USBPU = 0;

// blink user LED
reg [23:0] counter;
always @(posedge CLK) counter <= counter + 1;
assign LED = counter[22];

// memory array for characters
reg [7:0] char [0:15];
// pointer into char array
reg [3:0] charptr;

// set to 1 to begin sending
reg uart_start;
// low when UART transmitter is idle
wire uart_idle;
// current character to send
reg [7:0] tx_byte;

// = is blocking statement. In an always block, the line of code
//will be executed only after it's previous line has executed.

// <= is non-blocking in nature. This means that in an always block,
// every line will be executed in parallel.

// FSM based on code from http://www.asic-world.com/tidbits/verilog_fsm.html
// variables to track the state progression
parameter INIT   = 4'b0001,
          STATE0 = 4'b0010,
          STATE1 = 4'b0011,
          STATE2 = 4'b0100,
          STATE3 = 4'b0101,
          STATE4 = 4'b0110,
          LOAD_DATA = 4'b1000,
          TRANSMIT  = 4'b1001,
          NEXTCHAR  = 4'b1010,
          WAIT      = 4'b1011,
          COMPLETE  = 4'b1100;
reg [4:0] state;
// stores the current state which corresponds to a message to send over UART
reg [4:0] message;

always @ (posedge CLK or negedge RST)
// initialize values on RST
if(!RST)
	begin
		// set outputs low
		SWITCH_EN <= 0'b0;
		SWITCH <= 0'b0;

		// go to init state
		state <= INIT;
		// just a default
		message <= INIT;

		// set pointer to the beginning
		charptr = 0'b0;
		// not transmitting
		uart_start = 0'b0;
	end
else
  begin
      case(state)
        INIT:
					begin
						// start when coil is present
						if (PRESENT == 1'b1)
							begin
		            // store current state to point to the correct message
		            message = INIT;

		            state <= LOAD_DATA;
		          end
						else
							begin
								state <= INIT;
							end
					end
        STATE0:
          begin
						// enable MOSFET driver
						SWITCH_EN = 1'b1;

				    // store current state to point to the correct message
            message = STATE0;

            state <= LOAD_DATA;
          end
        STATE1:
          begin
						// enable MOSFET
						SWITCH = 1'b1;

						// store current state to point to the correct message
						message = STATE1;

						state <= LOAD_DATA;
          end
					STATE2:
						begin
							// continue when MOSFET is switched on
							if (ENABLED == 1'b1)
								begin
									// store current state to point to the correct message
									message = STATE2;

									state <= LOAD_DATA;
								end
							else
								begin
									state <= STATE2;
								end
						end
					STATE3:
						// continue when relay output is switched on
						if (ENGAGED == 1'b1)
							begin
								// store current state to point to the correct message
								message = STATE3;

								state <= LOAD_DATA;
							end
						else
							begin
								state <= STATE3;
							end
					STATE4:
						// go to INIT state if relay is disconnected
						begin
							if ((!ENABLED) | (!ENGAGED))
								begin
									// set outputs low
									SWITCH_EN <= 0'b0;
									SWITCH <= 0'b0;

									state <= INIT;
								end
							// otherwise stay here
							else
								begin
									state <= #1 STATE4;
								end
						end
					default:
						state <= #1 INIT;
        LOAD_DATA:
          begin
				    // change message depending on previous state
            case (message)
              INIT:
                begin
                  char[0]  = 7'h0A;	// \n = LF (Line Feed)
                  char[1]  = 7'h0D;	// \r = CR (Carriage Return)
                  char[2]  = "C";
                  char[3]  = "o";
                  char[4]  = "i";
                  char[5]  = "l";
                  char[6]  = " ";
                  char[7]  = "P";
                  char[8]  = "r";
                  char[9]  = "e";
                  char[10] = "s";
                  char[11] = "e";
                  char[12] = "n";
                  char[13] = "t";
                  char[14] = 7'h0A;	// \n = LF (Line Feed)
                  char[15] = 7'h0D;	// \r = CR (Carriage Return)
                end
              STATE0:
                begin
                  char[0]  = "E";
                  char[1]  = "N";
                  char[2]  = " ";
                  char[3]  = "D";
                  char[4]  = "r";
                  char[5]  = "i";
                  char[6]  = "v";
                  char[7]  = "e";
                  char[8]  = "r";
                  char[9]  = " ";
                  char[10] = " ";
                  char[11] = " ";
                  char[12] = " ";
                  char[13] = 7'h0A;	// \n = LF (Line Feed)
                  char[14] = 7'h0D;	// \r = CR (Carriage Return)
                  char[15] = "";
                  end
								STATE1:
	                begin
	                  char[0]  = "E";
	                  char[1]  = "N";
	                  char[2]  = " ";
	                  char[3]  = "M";
	                  char[4]  = "O";
	                  char[5]  = "S";
	                  char[6]  = "F";
	                  char[7]  = "E";
	                  char[8]  = "T";
	                  char[9]  = " ";
	                  char[10] = " ";
	                  char[11] = " ";
	                  char[12] = " ";
	                  char[13] = 7'h0A;	// \n = LF (Line Feed)
	                  char[14] = 7'h0D;	// \r = CR (Carriage Return)
	                  char[15] = "";
	                  end
								STATE2:
	                begin
	                  char[0]  = "M";
	                  char[1]  = "O";
	                  char[2]  = "S";
	                  char[3]  = "F";
	                  char[4]  = "E";
	                  char[5]  = "T";
	                  char[6]  = " ";
	                  char[7]  = "O";
	                  char[8]  = "n";
	                  char[9]  = " ";
	                  char[10] = " ";
	                  char[11] = " ";
	                  char[12] = " ";
	                  char[13] = 7'h0A;	// \n = LF (Line Feed)
	                  char[14] = 7'h0D;	// \r = CR (Carriage Return)
	                  char[15] = "";
	                  end
								STATE3:
	                begin
	                  char[0]  = "R";
	                  char[1]  = "e";
	                  char[2]  = "l";
	                  char[3]  = "a";
	                  char[4]  = "y";
	                  char[5]  = " ";
	                  char[6]  = "E";
	                  char[7]  = "n";
	                  char[8]  = "g";
	                  char[9]  = "a";
	                  char[10] = "g";
	                  char[11] = "e";
	                  char[12] = "d";
	                  char[13] = 7'h0A;	// \n = LF (Line Feed)
	                  char[14] = 7'h0D;	// \r = CR (Carriage Return)
	                  char[15] = "";
	                  end
            endcase

            state <= TRANSMIT;
          end
        TRANSMIT:
          begin
            // put character into UART buffer
            tx_byte <= char[charptr];
            // trigger UART to start
            uart_start <= 1'b1;

            // go to next state
            state <= NEXTCHAR;
          end
        NEXTCHAR:
          begin
            // move to next character
            charptr <= charptr + 1;
            // reset variable
            uart_start <= 1'b0;

            // go to next state
            state <= WAIT;
          end
        WAIT:
          begin
            // after character has been completely sent
            if (!uart_idle)
              begin
                // go to next state
                state <= COMPLETE;
              end
          end
        COMPLETE:
          begin
            // end at this character's index
            if(charptr == 4'b1111)
              begin
                // go to the state after the one we came from
                // (state stored in the message variable)
					      message = message + 1;
                state <= message;
              end
            // or go to next character if not done
            else
              begin
                state <= TRANSMIT;
              end
          end
        default:
					begin
          	state <= INIT;
					end
      endcase
  end

// Connect to the UART module
uart #(
  .baud_rate(9600),             // default is 9600
  .sys_clk_freq(16000000)       // default is 100000000
)
uart0(
  .clk(CLK),                    // The master clock for this module
  .rst(!RST),                    // Synchronous reset
  .rx(),                        // Incoming serial line
  .tx(tx),                      // Outgoing serial line
  .transmit(uart_start),        // Signal to transmit
  .tx_byte(tx_byte),            // Byte to transmit
  .received(),                  // Indicated that a byte has been received
  .rx_byte(),                   // Byte received
  .is_receiving(),              // Low when receive line is idle
  .is_transmitting(uart_idle),  // Low when transmit line is idle
  .recv_error()                 // Indicates error in receiving packet.
);

endmodule

PWM Control Firmware

Verilog
This is the top level code for the PWM control version of the firmware. This code steps through the state machine described but controls the MOSFET with a PWM waveform. It also prints the duty cycle of the PWM waveform once the relay is turned on.
`timescale 1ns / 1ps
module top (
	input CLK,    // 16MHz clock
	input RST,    // Active low reset button
  output LED,   // User/boot LED next to power LED
  input  PRESENT,   // detects presence of the coil
  output reg SWITCH_EN, // enable the gate driver
  output reg SWITCH,    // enable the relay
  input  ENABLED,   // detects that the MOSFET was enabled
  input  ENGAGED,   // detects that the relay output has switched
  input  MEASURE,   // PWM signal related to the COIL voltage
  output USBPU,  // USB pull-up resistor
	output tx  // TX UART line
);

// drive USB pull-up resistor to '0' to disable USB
assign USBPU = 0;

// blink user LED
reg [23:0] counter;
always @(posedge CLK) counter <= counter + 1;
assign LED = counter[22];

// memory array for characters
reg [7:0] char [0:15];
// pointer into char array
reg [3:0] charptr;

// set to 1 to begin sending
reg uart_start;
// low when UART transmitter is idle
wire uart_idle;
// current character to send
reg [7:0] tx_byte;

// = is blocking statement. In an always block, the line of code
//will be executed only after it's previous line has executed.

// <= is non-blocking in nature. This means that in an always block,
// every line will be executed in parallel.

// FSM based on code from http://www.asic-world.com/tidbits/verilog_fsm.html
// variables to track the state progression
parameter INIT   = 4'b0001,
          STATE0 = 4'b0010,
          STATE1 = 4'b0011,
					// remove STATE2
          STATE3 = 4'b0100,
          STATE4 = 4'b0101,
          STATE5 = 4'b0110,
          LOAD_DATA = 4'b1000,
          TRANSMIT  = 4'b1001,
          NEXTCHAR  = 4'b1010,
          WAIT      = 4'b1011,
          COMPLETE  = 4'b1100;
reg [4:0] state;
// stores the current state which corresponds to a message to send over UART
reg [4:0] message;

// PWM enable
reg pwm_en;
// Hold the duty cycle steady
reg dc_hold;

// PWM variables
// all 0-127 decimal
reg [6:0] period;
reg [6:0] dc;		// duty cycle
reg [6:0] pos;	// position inside period

always @ (posedge CLK or negedge RST)
// initialize values on RST
if(!RST)
	begin
		// set outputs low
		SWITCH_EN <= 0'b0;
		SWITCH <= 0'b0;

		// go to init state
		state <= INIT;
		// just a default
		message <= INIT;

		// set pointer to the beginning
		charptr = 0'b0;
		// not transmitting
		uart_start = 0'b0;

		// PWM init
		pwm_en <= 1'b0;
		dc_hold <= 1'b0;
		period	<= 7'd100;	// go to 100 to make duty cycle calc easy
		dc 			<= 7'd2;		// will never actually hit 1
		pos			<= 7'd0;
	end
else
  begin
      case(state)
        INIT:
					begin
						// start when coil is present
						if (PRESENT == 1'b1)
							begin
		            // store current state to point to the correct message
		            message = INIT;

		            state <= LOAD_DATA;
		          end
						else
							begin
								state <= INIT;
							end
					end
        STATE0:
          begin
						// enable MOSFET driver
						SWITCH_EN = 1'b1;

				    // store current state to point to the correct message
            message = STATE0;

            state <= LOAD_DATA;
          end
        STATE1:
          begin
						// turn on the PWM MOSFET driver
						pwm_en <= 1'b1;

						// store current state to point to the correct message
						message = STATE1;

						state <= LOAD_DATA;
          end
					STATE2:
						begin
							// continue when MOSFET is switched on
							if (ENABLED == 1'b1)
								begin
									// store current state to point to the correct message
									message = STATE2;

									state <= LOAD_DATA;
								end
							else
								begin
									state <= STATE2;
								end
						end
					STATE3:
						// continue when relay output is switched on
						if (ENGAGED == 1'b1)
							begin
								// hold the duty cycle steady
								dc_hold <= 1'b1;
								// store current state to point to the correct message
								message = STATE3;

								state <= LOAD_DATA;
							end
						else
							begin
								state <= STATE3;
							end
					STATE4:
						begin
							// print duty cycle
							// store current state to point to the correct message
							message = STATE4;

							state <= LOAD_DATA;
						end
					STATE5:
						// TODO: make new state that decreases duty cycle
						begin
							state <= STATE5;
						end
					default:
						state <= #1 INIT;
        LOAD_DATA:
          begin
				    // change message depending on previous state
            case (message)
              INIT:
                begin
                  char[0]  = "C";
                  char[1]  = "o";
                  char[2]  = "i";
                  char[3]  = "l";
                  char[4]  = " ";
                  char[5]  = "P";
                  char[6]  = "r";
                  char[7]  = "e";
                  char[8]  = "s";
                  char[9]  = "e";
                  char[10] = "n";
                  char[11] = "t";
                  char[12] = " ";
                  char[13] = 7'h0A;	// \n = LF (Line Feed)
                  char[14] = 7'h0D;	// \r = CR (Carriage Return)
                  char[15] = "";
                end
              STATE0:
                begin
                  char[0]  = "E";
                  char[1]  = "N";
                  char[2]  = " ";
                  char[3]  = "D";
                  char[4]  = "r";
                  char[5]  = "i";
                  char[6]  = "v";
                  char[7]  = "e";
                  char[8]  = "r";
                  char[9]  = " ";
                  char[10] = " ";
                  char[11] = " ";
                  char[12] = " ";
                  char[13] = 7'h0A;	// \n = LF (Line Feed)
                  char[14] = 7'h0D;	// \r = CR (Carriage Return)
                  char[15] = "";
                  end
								STATE1:
	                begin
	                  char[0]  = "E";
	                  char[1]  = "N";
	                  char[2]  = " ";
	                  char[3]  = "M";
	                  char[4]  = "O";
	                  char[5]  = "S";
	                  char[6]  = "F";
	                  char[7]  = "E";
	                  char[8]  = "T";
	                  char[9]  = " ";
	                  char[10] = " ";
	                  char[11] = " ";
	                  char[12] = " ";
	                  char[13] = 7'h0A;	// \n = LF (Line Feed)
	                  char[14] = 7'h0D;	// \r = CR (Carriage Return)
	                  char[15] = "";
	                  end
								STATE2:
	                begin
	                  char[0]  = "M";
	                  char[1]  = "O";
	                  char[2]  = "S";
	                  char[3]  = "F";
	                  char[4]  = "E";
	                  char[5]  = "T";
	                  char[6]  = " ";
	                  char[7]  = "O";
	                  char[8]  = "n";
	                  char[9]  = " ";
	                  char[10] = " ";
	                  char[11] = " ";
	                  char[12] = " ";
	                  char[13] = 7'h0A;	// \n = LF (Line Feed)
	                  char[14] = 7'h0D;	// \r = CR (Carriage Return)
	                  char[15] = "";
	                  end
								STATE3:
	                begin
	                  char[0]  = "R";
	                  char[1]  = "e";
	                  char[2]  = "l";
	                  char[3]  = "a";
	                  char[4]  = "y";
	                  char[5]  = " ";
	                  char[6]  = "E";
	                  char[7]  = "n";
	                  char[8]  = "g";
	                  char[9]  = "a";
	                  char[10] = "g";
	                  char[11] = "e";
	                  char[12] = "d";
	                  char[13] = 7'h0A;	// \n = LF (Line Feed)
	                  char[14] = 7'h0D;	// \r = CR (Carriage Return)
	                  char[15] = "";
	                  end
								STATE4:
	                begin
	                  char[0]  = "D";
	                  char[1]  = "C";
	                  char[2]  = " ";
	                  char[3]  = "o";
	                  char[4]  = "n";
	                  char[5]  = ":";
	                  char[6]  = " ";
	                  //char[7]  = "";
										// tens place
										if (dc >= 90) begin char[7] = "9"; end
										else if (dc >= 80) begin char[7] = "8"; end
										else if (dc >= 70) begin char[7] = "7"; end
										else if (dc >= 60) begin char[7] = "6"; end
										else if (dc >= 50) begin char[7] = "5"; end
										else if (dc >= 40) begin char[7] = "4"; end
										else if (dc >= 30) begin char[7] = "3"; end
										else if (dc >= 20) begin char[7] = "2"; end
										else if (dc >= 10) begin char[7] = "1"; end
										else begin char[7] = ""; end
	                  //char[9]  = "";
										// ones place
										if ((dc & 9) == 9) begin char[8] = "9"; end
										else if ((dc & 9) == 8) begin char[8] = "8"; end
										else if ((dc & 9) == 7) begin char[8] = "7"; end
										else if ((dc & 9) == 8) begin char[8] = "6"; end
										else if ((dc & 9) == 5) begin char[8] = "5"; end
										else if ((dc & 9) == 4) begin char[8] = "4"; end
										else if ((dc & 9) == 3) begin char[8] = "3"; end
										else if ((dc & 9) == 2) begin char[8] = "2"; end
										else if ((dc & 9) == 1) begin char[8] = "1"; end
										else begin char[8] = "0"; end
	                  char[9] = "%";
	                  char[10] = "";
	                  char[11] = "";
	                  char[12] = "";
	                  char[13] = 7'h0A;	// \n = LF (Line Feed)
	                  char[14] = 7'h0D;	// \r = CR (Carriage Return)
	                  char[15] = "";
	                  end
            endcase

            state <= TRANSMIT;
          end
        TRANSMIT:
          begin
            // put character into UART buffer
            tx_byte <= char[charptr];
            // trigger UART to start
            uart_start <= 1'b1;

            // go to next state
            state <= NEXTCHAR;
          end
        NEXTCHAR:
          begin
            // move to next character
            charptr <= charptr + 1;
            // reset variable
            uart_start <= 1'b0;

            // go to next state
            state <= WAIT;
          end
        WAIT:
          begin
            // after character has been completely sent
            if (!uart_idle)
              begin
                // go to next state
                state <= COMPLETE;
              end
          end
        COMPLETE:
          begin
            // end at this character's index
            if(charptr == 4'b1111)
              begin
                // go to the state after the one we came from
                // (state stored in the message variable)
					      message = message + 1;
                state <= message;
              end
            // or go to next character if not done
            else
              begin
                state <= TRANSMIT;
              end
          end
        default:
					begin
          	state <= INIT;
					end
      endcase

		// PWM code
		if (pwm_en)
			begin
				// increase pos
				pos <= pos + 1;

				// turn on MOSFET at the start
				if (pos == 7'd0)
					begin
						SWITCH <= 1'b1;
					end
				// turn off MOSFET when position == set duty cycle
				else if (pos == dc)
					begin
						SWITCH <= 1'b0;
					end
				// reset position at the end
				// increase duty cycle for the next iteration
				if (pos == period)
					begin
						pos <= 7'd0;
						// increase duty cycle if it's not held in place
						if (!dc_hold)
							begin
								dc <= dc + 1;
							end
					end
			end
  end

// Connect to the UART module
uart #(
  .baud_rate(9600),             // default is 9600
  .sys_clk_freq(16000000)       // default is 100000000
)
uart0(
  .clk(CLK),                    // The master clock for this module
  .rst(!RST),                    // Synchronous reset
  .rx(),                        // Incoming serial line
  .tx(tx),                      // Outgoing serial line
  .transmit(uart_start),        // Signal to transmit
  .tx_byte(tx_byte),            // Byte to transmit
  .received(),                  // Indicated that a byte has been received
  .rx_byte(),                   // Byte received
  .is_receiving(),              // Low when receive line is idle
  .is_transmitting(uart_idle),  // Low when transmit line is idle
  .recv_error()                 // Indicates error in receiving packet.
);

endmodule

UART Library Firmware

Verilog
This is the UART controller. This file is used regardless of the control method of the MOSFET.
`timescale 1ns / 1ps
// Documented Verilog UART
// Copyright (C) 2010 Timothy Goddard (tim@goddard.net.nz)
//               2013 Aaron Dahlen
// Distributed under the MIT licence.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
//** INSTANTIATION ********************************************
//
//  To instantiate this module copy this section to your main code...
//
//    uart #(
//        .baud_rate(baud_rate),            // default is 9600
//        .sys_clk_freq(sys_clk_freq)       // default is 100000000
//     )
//    instance_name(
//        .clk(clk),                        // The master clock for this module
//        .rst(rst),                        // Synchronous reset
//        .rx(rx),                          // Incoming serial line
//        .tx(tx),                          // Outgoing serial line
//        .transmit(transmit),              // Signal to transmit
//        .tx_byte(tx_byte),                // Byte to transmit
//        .received(received),              // Indicated that a byte has been received
//        .rx_byte(rx_byte),                // Byte received
//        .is_receiving(is_receiving),      // Low when receive line is idle
//        .is_transmitting(is_transmitting),// Low when transmit line is idle
//        .recv_error(recv_error)           // Indicates error in receiving packet.
//      //.recv_state(recv_state),          // for test bench
//      //.tx_state(tx_state)               // for test bench
//    );
//

 module uart(
    input clk,                  // The master clock for this module
    input rst,                  // Synchronous reset
    input rx,                   // Incoming serial line
    output tx,                  // Outgoing serial line
    input transmit,             // Assert to begin transmission
    input [7:0] tx_byte,        // Byte to transmit
    output received,            // Indicates that a byte has been received
    output [7:0] rx_byte,       // Byte received
    output wire is_receiving,   // Low when receive line is idle.
    output wire is_transmitting,// Low when transmit line is idle.
    output wire recv_error,      // Indicates error in receiving packet.
    output reg [3:0] rx_samples,
    output reg [3:0] rx_sample_countdown
);

// The clock_divider is calculated using baud_rate and sys_clk_freq.
// To modify baud rate you can modify the defaults shown below or instantiate
// the module using the template shown in the INSTANTIATION section above.
// For aditional information about instantiation please see:
// http://www.sunburst-design.com/papers/CummingsHDLCON2002_Parameters_rev1_2.pdf

    parameter baud_rate = 9600;
    parameter sys_clk_freq = 100000000;

    localparam one_baud_cnt = sys_clk_freq / (baud_rate);

//** SYMBOLIC STATE DECLARATIONS ******************************

    localparam [2:0]
        RX_IDLE             = 3'd0,
        RX_CHECK_START      = 3'd1,
        RX_SAMPLE_BITS      = 3'd2,
        RX_READ_BITS        = 3'd3,
        RX_CHECK_STOP       = 3'd4,
        RX_DELAY_RESTART    = 3'd5,
        RX_ERROR            = 3'd6,
        RX_RECEIVED         = 3'd7;

    localparam [1:0]
        TX_IDLE             = 2'd0,
        TX_SENDING          = 2'd1,
        TX_DELAY_RESTART    = 2'd2,
        TX_RECOVER          = 2'd3;


//** SIGNAL DECLARATIONS **************************************

    reg [log2(one_baud_cnt * 16)-1:0] rx_clk;
    reg [log2(one_baud_cnt)-1:0] tx_clk;

    reg [2:0] recv_state = RX_IDLE;
    reg [3:0] rx_bits_remaining;
    reg [7:0] rx_data;

    reg tx_out = 1'b1;
    reg [1:0] tx_state = TX_IDLE;
    reg [3:0] tx_bits_remaining;
    reg [7:0] tx_data;


//** ASSIGN STATEMENTS ****************************************

    assign received = recv_state == RX_RECEIVED;
    assign recv_error = recv_state == RX_ERROR;
    assign is_receiving = recv_state != RX_IDLE;
    assign rx_byte = rx_data;

    assign tx = tx_out;
    assign is_transmitting = tx_state != TX_IDLE;


//** TASKS / FUNCTIONS ****************************************

    function integer log2(input integer M);
        integer i;
    begin
        log2 = 1;
        for (i = 0; 2**i <= M; i = i + 1)
            log2 = i + 1;
    end endfunction


//** Body *****************************************************

    always @(posedge clk) begin
        if (rst) begin
            recv_state <= RX_IDLE;
            tx_state = TX_IDLE;
        end

        // Countdown timers for the receiving and transmitting
        // state machines are decremented.

        if(rx_clk) begin
            rx_clk <= rx_clk - 1'd1;
        end

        if(tx_clk) begin
            tx_clk <= tx_clk - 1'd1;
        end


//** Receive state machine ************************************

        case (recv_state)
            RX_IDLE: begin
                // A low pulse on the receive line indicates the
                // start of data.
                if (!rx) begin
                    // Wait 1/2 of the bit period
                    rx_clk <= one_baud_cnt / 2;
                    recv_state <= RX_CHECK_START;
                end
            end

            RX_CHECK_START: begin
                if (!rx_clk) begin
                    // Check the pulse is still there
                    if (!rx) begin
                        // Pulse still there - good
                        // Wait the bit period plus 3/8 of the next
                        rx_clk <= (one_baud_cnt / 2) + (one_baud_cnt * 3) / 8;
                        rx_bits_remaining <= 8;
                        recv_state <= RX_SAMPLE_BITS;
                        rx_samples <= 0;
                        rx_sample_countdown <= 5;
                    end else begin
                        // Pulse lasted less than half the period -
                        // not a valid transmission.
                        recv_state <= RX_ERROR;
                    end
                end
            end

            RX_SAMPLE_BITS: begin
                // sample the rx line multiple times
                if (!rx_clk) begin
                    if (rx) begin
                        rx_samples <=  rx_samples + 1'd1;
                    end
                    rx_clk <= one_baud_cnt / 8;
                    rx_sample_countdown <= rx_sample_countdown -1'd1;
						  // in the original code this was keying on zero
						  // but the code was using blocking assignment
						  // I (Al Williams) moved everything to nonblocking
						  // so now the test should be 1 because that means IT WILL BE
						  // zero after the previous subtraction completes
                    recv_state <= rx_sample_countdown==1 ? RX_READ_BITS : RX_SAMPLE_BITS;
                end
            end

            RX_READ_BITS: begin
                if (!rx_clk) begin
                    // Should be finished sampling the pulse here.
                    // Update and prep for next
                    if (rx_samples > 3) begin
                        rx_data <= {1'd1, rx_data[7:1]};
                    end else begin
                        rx_data <= {1'd0, rx_data[7:1]};
                    end

                    rx_clk <= (one_baud_cnt * 3) / 8;
                    rx_samples <= 0;
                    rx_sample_countdown = 5;
                    rx_bits_remaining <= rx_bits_remaining - 1'd1;

                    if(rx_bits_remaining!=1)begin
                        recv_state <= RX_SAMPLE_BITS;
                    end else begin
                        recv_state <= RX_CHECK_STOP;
                        rx_clk <= one_baud_cnt / 2;
                    end
                end
            end

            RX_CHECK_STOP: begin
                if (!rx_clk) begin
                    // Should resume half-way through the stop bit
                    // This should be high - if not, reject the
                    // transmission and signal an error.
                    recv_state <= rx ? RX_RECEIVED : RX_ERROR;
                end
            end



            RX_ERROR: begin
                // There was an error receiving.
                // Raises the recv_error flag for one clock
                // cycle while in this state and then waits
                // 2 bit periods before accepting another
                // transmission.
                rx_clk <= 8 * sys_clk_freq / (baud_rate);
                recv_state <= RX_DELAY_RESTART;
            end

    // why is this state needed?  Why not go to idle and wait for next?

            RX_DELAY_RESTART: begin
                // Waits a set number of cycles before accepting
                // another transmission.
                recv_state <= rx_clk ? RX_DELAY_RESTART : RX_IDLE;
            end


            RX_RECEIVED: begin
                // Successfully received a byte.
                // Raises the received flag for one clock
                // cycle while in this state.
                recv_state <= RX_IDLE;
            end

        endcase


//** Transmit state machine ***********************************

        case (tx_state)
            TX_IDLE: begin
                if (transmit) begin
                    // If the transmit flag is raised in the idle
                    // state, start transmitting the current content
                    // of the tx_byte input.
                    tx_data <= tx_byte;
                    // Send the initial, low pulse of 1 bit period
                    // to signal the start, followed by the data
                  //  tx_clk_divider =  clock_divide;
                    tx_clk <= one_baud_cnt;
                    tx_out <= 0;
                    tx_bits_remaining <= 8;
                    tx_state <= TX_SENDING;
                end
            end

            TX_SENDING: begin
                if (!tx_clk) begin
                    if (tx_bits_remaining) begin
                        tx_bits_remaining <= tx_bits_remaining - 1'd1;
                        tx_out <= tx_data[0];
                        tx_data <= {1'b0, tx_data[7:1]};
                        tx_clk <= one_baud_cnt;
                        tx_state <= TX_SENDING;
                    end else begin
                        // Set delay to send out 1 stop bit.
                        tx_out <= 1;
                        tx_clk <= one_baud_cnt;
                        tx_state <= TX_DELAY_RESTART;
                    end
                end
            end

            TX_DELAY_RESTART: begin
                // Wait until tx_countdown reaches the end before
                // we send another transmission. This covers the
                // "stop bit" delay.
                tx_state <= tx_clk ? TX_DELAY_RESTART : TX_RECOVER;// TX_IDLE;
            end

            TX_RECOVER: begin
                // Wait unitil the transmit line is deactivated.  This prevents repeated characters
                tx_state <= transmit ? TX_RECOVER : TX_IDLE;

            end

        endcase
    end

endmodule

Credits

Dillon Nichols

Dillon Nichols

8 projects β€’ 17 followers
Electrical engineer: hardware/firmware; tinkerer; hobbyist; amateur fabricator;
Thanks to Tim Goddard and Luke Valenty.

Comments