Thursday, February 24, 2011

Detecting twi signals...

One of the most common ways to use an open collector bus is to allow multiple different devices to communicate with each other without a serious risk of electrically damaging each other.  Every device has physical limits of the amount of energy it can supply or drain safely - it's not a problem when one device is in control of the current but when multiple devices are active at the same time bad things can happen.  An easy way to remove this risk is to have a bus where the only source of current is limited to design by a resistor and only allow devices attached to the bus to sink the current (connect it to ground).  This way if multiple devices are sinking the current at the same time there is no risk of physical damages (the resistor limits the maximum current on the bus) and only if all of the connected devices are in high impedance mode will the bus achieve a high state (again fed current by the resistor).

The only problem here is now one of deciding in what order devices should listen to each other and what order devices should speak...  One of the most common communications protocol here I2C (there are many other variants with alterations in the protocol for different design reasons).  Two wires are used to communicate - one for a clock (SCL - serial clock line) and one for data (SDA - serial data line) and these connect to every device.  The key is that at any given time there is only one physical device that has logical control of the bus and that device supplies the clock (SCL).  If you think about it there are only 2 timing signals (positive and negative edge) on each of the SCL and SDA lines which means that there are 4 effective signals that can be transmitted over the pair.

Since we know that we need to be able to transmit a 0 and a 1 for binary data that leaves us with two other signals for controlling the logical state of the bus.  In convention we will say that if the SDA line stays consistent for the entire period that the SCL line is high (between sequential positive and negative edges) then the state of SDA during that time will represent the 0 or 1 we are transmitting.  If the SDA line changes during the period that the SCL line is high (between sequential positive and negative edges) then the direction of change in SDA (posedge for stop or negedge for start) during that time will represent the logical beginning or end of a logical set of 0's and 1's that belong together.


So, if we want to determine what signals (0,1,start,stop) exist on the bus we need to build a little detector module.  This is rather straightforward in verilog - if we keep track of the previous states of sda and scl and compare them to the current states we can easily determine which of the four possible signals have occurred.  When we see one of these conditions, we will set the appropriate output signal (rec_start, rec_stop, rec_data0, rec_data1) high.

     scl_p <= scl; // remember previous state
         sda_p <= sda; // remember previous state
        
         if (scl && !scl_p) begin
            // we have the positive edge of a pulse
            sda_f <= sda;  // remember sda state at front edge of pulse
            flag  <= 1'b1; // set flag - we saw the front edge             
         end    
         else if (!scl && scl_p && (sda_f == sda) && flag) begin              
            // both high or both low on neg edge of scl
            rec_data0 <= !sda;  
            rec_data1 <= sda;   
            flag      <= 1'b0; // reset flag       
         end
         else if (scl && scl_p) begin
            if (sda && !sda_p) begin
               rec_stop <= 1'b1;             
            end
            else if (!sda && sda_p) begin
               rec_start <= 1'b1;  
               flag      <= 1'b0;
            end
         end
Since it's important to know when the signal occurred (they only exist for a moment) we will clear the signals on the next system clock cycle.

         // clear so they last only 1 cycle
         if (rec_start) rec_start <= 1'b0;
         if (rec_stop)  rec_stop  <= 1'b0;
         if (rec_data0) rec_data0 <= 1'b0;
         if (rec_data1) rec_data1 <= 1'b0;
The complete verilog file is here and contains the initializations, inputs and outputs for the module.  Overall it's relatively simple and straightforward to implement (but simple and straightforward is often good).