Friday, February 25, 2011

Sending twi signals...

If one wants to transmit signals on an I2C/TWI bus more things need to be considered than when one is simply detecting signals sent by another device.  The reason is that while there are only 4 possible timing conditions between SCL and SDA, it matters when these signals occur relative to each other.  In the detector we only have to pay attention to the sequential order, but in a transmitter we need to pay attention to the relationships in real time... Since physical changes (voltages, saturation, gate speed, wire speed) all take non-zero amounts of real time to occur they need to be considered when deciding the exact moment to transmit changes.

This is why we have timing diagrams, hold times, setup times that all components should follow for reliable communication.  For our case, the important thing is that since SCL is used to determine the meaning of SDA changes, we need to make certain that we control the changes in SDA with safe margins relative to SCL.  If we are controlling SCL (we are a master) then it's easy to do; however, if we are acting as a slave then we have to guess when SCL will change to decide when to change SCL.

Sometimes we can force SCL no to change when we aren't ready to send data by stretching SCL low.  If the master supplying the SCL signal releases the line (so it should float high) and a slave pulls SCL low then the master will wait until the line is released by the slave.  There is no way for the slave (or any device) to hold SCL high though...  The way around this is to count the amount of elapsed time from a change in SCL and only change SDA within an interval that isn't too early or too late to meet the timing specifications.  In this design we will simply take the clock divider for generating SCL and divide by two and four to mark an interval that easily meets the timing specs.  The exceptions to this interval occur when the bus is stretched - once a complete SCL cycle has passed and it's still in the same state (being stretched) we allow changes to occur as well. For example...


   // We are going to pay attention to the period between 1/4 and 1/2 of the delay and also when stretched...
   assign half_pulse_delay    = pulse_delay[15:1];
   assign quarter_pulse_delay = pulse_delay[15:2];
   assign valid_interval      = ((delay > quarter_pulse_delay) & (delay < half_pulse_delay) | clear_line);

The only part remaining is to make certain that exactly one logical change occurs in a given SCL clock phase.  Since the interval may be long enough that several signals could be sent (and we only weant to send one) we will include a block after a signal has been sent (SDA state has been changed logically) that won't be released until the SCL state changes.


  if (scl == scl_p) begin
            // scl remains high or low
            if (delay < pulse_delay) begin
               delay <= delay + 1;
            end
            else begin
               if ((high_block | low_block) && !block_p) begin
                  delay       <= 16'b0000000000000000;  
                  block_p     <= 1'b1;
               end
               else begin
                  // if nothing happened then it's been long enough we know
                  // that we are stretching so signal the line is clear.
                  clear_line  <= 1'b1;    
                  block_p     <= 1'b0;   
                  high_block  <= 1'b0;  // clear block     
               end            
            end   
         end    
         else begin
            // scl went high from low or low to high 
            clear_line  <= 1'b0; 
            block_p     <= 1'b0;   
            delay       <= 16'b0000000000000000; // reset counter
            if (!scl) begin
               high_block  <= 1'b0;  // clear block  
            end
            else begin
               low_block   <= 1'b0; // clear block      
            end       
         end
Now it's simply a matter of handing the interaction with other modules and completing the setup and completion of the signals we wish to transmit.  The way we are going to do this is to build a state machine that keep track of what signal we want to transmit, we are currently trying to transmit, and whether we have completed the process.  It's easier for me to connect multiple modules using an input line for the intent and a return ack to indicate the intended task was completed (when ack is low a new intention can be set, when completed an ack is returned, when the intention is released the ack is cleared) - I used this method very often.

  case (state)
           (`TF_COMPONENT_TWI_XMT_STATE_IDLE): begin
              // delay the release of sda_enable so that just in case we are sending
              // multiple signals in a quick stream, we don't induce unnecessary noise on
              // the sda line this way...
              stretch_high <= 1'b0;           
              if (slipcounter > 0) begin
                 slipcounter <= slipcounter - 1;
              end    
              else if (sda_xmt || (slipcounter == 3'b000)) begin
         // disable the transmitter if we were transmitting high sda because if a
         // slave pulls sda low too early, then we get an arbitration lost alarm.
         // This is better than just disabling sda to send a high signal because in
         // that case we would not detect an arbitration problem at all...
                 sda_enable <= 1'b0; // disable trasmitter                                             
              end
                     
              // process or reset flag back when signals are cleared
              if (!xmt_start && !xmt_stop && !xmt_data0 && !xmt_data1) begin
                 xmt_ack <= 1'b0;
              end
              else if (!xmt_ack && xmt_start) begin
                 state        <= `TF_COMPONENT_TWI_XMT_STATE_START;            
              end
              else if (!xmt_ack && xmt_stop) begin
                 state <= `TF_COMPONENT_TWI_XMT_STATE_STOP;             
              end
              else if (!xmt_ack && (xmt_data0 || xmt_data1)) begin
                 state <= `TF_COMPONENT_TWI_XMT_STATE_DATA;            
              end                    
           end
           (`TF_COMPONENT_TWI_XMT_STATE_START): begin
              if (oktosetup_start) begin
                 sda_xmt      <= 1'b0;
                 sda_enable   <= 1'b1;
                 stretch_high <= 1'b1;         
                 state        <= `TF_COMPONENT_TWI_XMT_STATE_DATA_FIN;      
              end
           end    
           (`TF_COMPONENT_TWI_XMT_STATE_STOP): begin
              if (oktosetup_stop) begin
                 sda_xmt      <= 1'b0;
                 sda_enable   <= 1'b1;
                 low_block    <= 1'b1;
                 stretch_high <= 1'b1; // extend setup time
                 state        <= `TF_COMPONENT_TWI_XMT_STATE_STOP_FIN; 
              end
           end
           (`TF_COMPONENT_TWI_XMT_STATE_STOP_FIN): begin
              if (oktofinish_stop) begin
                 sda_xmt      <= 1'b0;
                 sda_enable   <= 1'b1;   
                 xmt_ack      <= 1'b1;
                 high_block   <= 1'b1;
                 stretch_high <= 1'b1;
                 state        <= `TF_COMPONENT_TWI_XMT_STATE_IDLE; 
              end
           end    
           (`TF_COMPONENT_TWI_XMT_STATE_DATA): begin
              // setup sda to represent the data then wait until we can change sda again.
              if (oktosetup_data) begin
                 sda_xmt    <= xmt_data1; // covers both 0 and 1 xmt
                 sda_enable <= 1'b1;
                 low_block  <= 1'b1; // block forces finish to be on the next pulse             
                 state      <= `TF_COMPONENT_TWI_XMT_STATE_DATA_FIN;
              end
           end
           (`TF_COMPONENT_TWI_XMT_STATE_DATA_FIN): begin
              stretch_high <= 1'b0;
              // finish sending data, signal back data was sent and delay sda release.
              if (oktofinish_data) begin
                 // don't block here since it's ok to send more data
                 xmt_ack     <= 1'b1;
                 slipcounter <= 3'b111; // stop unneded noise if pumping fast
                 state       <= `TF_COMPONENT_TWI_XMT_STATE_IDLE;
              end
           end
         endcase // state machine logic 
This leads to a design considerably more complex than the receiver, but it not really too difficult to understand.


The whole verilog file containing initializations, input and outputs for the module is here.