7SDevice part3

From Pinguino
Jump to: navigation, search

PART 3 - Protocol handshaking simulation

So far we have designed the board hardware and the software concepts. Before starting to code the firmware it is helpful to implement a protocol simulator that will allow us to test if the message mimics are correct. We will develop this as a Java multi-threaded application. We will transmit a couple of digits, tracking every step of progress of both the device and the host state machines.

There are several classes belonging to this project that are in a package I called SPI_simulator. The classes are:

  • DataBus.java This class implements the SPI bus
  • fifo.java This class implements a FIFO structure
  • Ansi.java This class implements some colors for writing text
  • device_rx.java This is the receiving state machine
  • host_tx.java This is the transmitting state machine
  • SPI_simulator.java This is the main program

Let start with the Data bus. This, as expected, is composed of three Boolean variables as seen from the host perspective. So clk is clock line derived by host and mosi is the Master Out Slave Input signal.

public class DataBus {   
    boolean miso;
    boolean mosi;
    boolean clk;
}

Next we will define the fifo structure we will use for storing received characters.

public class fifo {
 
    fifo first;
    fifo current;
    Object o;
    fifo next;
    
    fifo () {
        first = this;
        current = this;
        o = null;
        next = null;
    }
    
    void push (Object o) {
        fifo p;
        p = first;
        while (p.o != null) p=p.next;
        p.o = o;
        p.next = new fifo();
    }
    
    Object pop() {
        Object o;
        o = first.o;
        first = first.next;
        return o;
    }
    
    void print() {
        fifo p;
        char c;
        p=first;
        while(p.o!=null) {
            c = (char) ((int) p.o);
            System.out.print(c);
            p = p.next;
        }
        System.out.println();
    }
}

This is a dynamic structure that handles generic objects with only three methods:

  • push(Object o);
  • pop();
  • print();

The print method is specialized for printing chars. The structure however can be used for storing any kind of object.

The Ansi class is simply a set of ANSI codes made in a clean readable format. We will only use the red and green colors but should you need other colors in your projects I provided them all for you.

public class Ansi {  
public static final String RESET = "\u001B[0m";
public static final String BLACK = "\u001B[30m";
public static final String RED = "\u001B[31m";
public static final String GREEN = "\u001B[32m";
public static final String YELLOW = "\u001B[33m";
public static final String BLUE = "\u001B[34m";
public static final String PURPLE = "\u001B[35m";
public static final String CYAN = "\u001B[36m";
public static final String WHITE = "\u001B[37m";
}

Now, before starting to analyze the central parts of our simulator work (device and host) I will introduce the main flow of the program.

public class SPI_simulator {
    public static void main(String[] args) {        
     fifo rxbuf;
     rxbuf = new fifo();
     DataBus sbus;
     sbus = new DataBus();
     device_rx  device;
     device = new device_rx(sbus,rxbuf);
     device.start();
     host_tx sender;
     sender = new host_tx(sbus);
     sender.send_message("The quick brown fox jumped over the lazy dogs back");
     device.stop();
     rxbuf.print();     
   }
}

So, we simply declare and initialize an rx buffer of the fifo type. Then we declare and initialize a system bus of type DataBus. Then we declare a device of “device_rx” type that works on the system bus and writes on the receive buffer and then we start it. At this point of the code “device.start()”, the device thread starts to run on its own in parallel with the main. We then declare a sender of “host_tx” type and initialize it attaching it to the system bus. We will use the method “send_message” to invoke the sender passing it a string that it will transfer to the listening receive device.

When finished we stop the device and invoke the print method of rxbuf to show the received string that hopefully should be equal to the one we passed to the send_message method. Pretty simple, isn't it?

In this simulation the device and host are blocking, so if we don't start the device the send_message() method will hang in a loop waiting for device to be ready and the program will never terminate. The device thread, until stopped, will run forever waiting for the host transmission. In the real firmware we'll implement this as non blocking code.

Ok, let's now move on to the device state machine. We will derive it from the Thread class and we will use an enumerative type to name the machine states with easy to read labels. If you remember Part 1 where we depicted our concepts, this is where we find the states Init, WaitForClkDown,WaitForClkUp and UpdateBuffer.

In the constructor we find the definition for the bus and fifo structure passed as a parameters from the main calling method.

public class device_rx extends Thread {
    
  fifo buffer;
  int ch;
  DataBus device_bus;
  enum t_stato {Init,WaitForClkDown,WaitForClkUp,UpdateBuffer};
    
  device_rx(DataBus bus, fifo buf){
     device_bus = bus;
     buffer = buf;
     ch = 0;
  }
  .......

now, since the class is a subclass of Thread we have to implement a run method that will be executed when start() is invoked.

public void run () {
        
     t_stato stato;
     stato = t_stato.Init;
     int bit=0;
     char c;
       
     while(true) {
       switch(stato) {
         case Init:
             ch = 0;
             System.out.println(Ansi.RED+"\treceive init"+Ansi.RESET);
             bit = 0;
             device_bus.miso = true;
             System.out.println(Ansi.RED+"\treceive assert miso up"+Ansi.RESET);
             stato = t_stato.WaitForClkDown;
             break;
        case WaitForClkDown :
             System.out.println(Ansi.RED+"\treceive WaitForClockDown"+
                                Ansi.RESET);            
             while (device_bus.clk != false);
             System.out.println(Ansi.RED+"\treceive got clock down"+Ansi.RESET);
             device_bus.miso = false;
             System.out.println(Ansi.RED+"\treceive assert miso down"+
                                Ansi.RESET);
             stato = t_stato.WaitForClkUp;
             break;
         case WaitForClkUp :
             System.out.println(Ansi.RED+"\treceive WaitForClockUp"+Ansi.RESET);
             while (device_bus.clk != true);
             System.out.println(Ansi.RED+"\treceive got clock up"+Ansi.RESET);
             if (device_bus.mosi == true) ch |= 0x01;
             else ch &= 0xFE;      
             System.out.println(Ansi.RED+"\treceive Got bit : "+
                                bit+"  ch = "+ch+Ansi.RESET);              
             bit++;              
             if (bit == 8) stato = t_stato.UpdateBuffer;
             else {
                 ch = ch << 1;
                 stato = t_stato.WaitForClkDown;
             }
             device_bus.miso = true;
             System.out.println(Ansi.RED+"\treceive assert miso up"+Ansi.RESET);
             break;             
         case UpdateBuffer:
             c = (char) ch;
             System.out.println(Ansi.RED+"\treceive UpdateBuffer"+Ansi.RESET);
             buffer.push(ch);
             System.out.println(Ansi.GREEN+"\treceive Got char  "+
                                ch+" ASCII : "+c+Ansi.RESET);
             device_bus.miso = true;
             System.out.println(Ansi.RED+"\treceive assert miso up"+Ansi.RESET);
             stato = t_stato.Init;
         }
       }
    }
    
}

To make a long story short, this implements the mimic of the receiving state machine. The advantage of the simulator is to use many printouts to understand what happens at every step of the algorithm. Now you see the final version so this works as we expect. But when you are in the design stage, it is very useful to have a simulator that allows you to change the mimic and see what happens on the fly.

Each bit is read and then the register ch is updated (making bit-wise OR with 0x01 or bit-wise AND with 0xFE) then ch is shifted left. When 8 bit are read the received character is pushed in the FIFO buffer. The device asserts MISO down when a bit is received successfully so the host will read a 0 byte after a successful transfer.

Look at this instruction:

while (device_bus.clk != true);

This is the blocking code snippet because until the clock goes up (high), the thread will stop there. Other blocking parts are similar. When we design the actual C device pic routine we will avoid it and I will show you a technique to accomplish that task.

Note that in each printed message is written a tab and the word “receive” so when we execute the code, it will be easy to know which thread is writing on the screen, since they are running in parallel.


OK, let's move on to see the host class. As in the device class we find the machine states belonging to an enumerative type have easy to remember names. The constructor only needs to have the data bus to attach to.

public class host_tx {
    
    DataBus host_bus;
    enum t_stato {Init,WaitForDeviceReady,WaitForDeviceAck,WaitForReceiveOK,eot}
    
    host_tx(DataBus system_bus) {
        host_bus = system_bus;
    }
    ........

This class is not subclassed from the Thread one because it operates inside the static main thread. Here the state names are device centric, and look forward to the device behavior.

So, we finally have to implement the send_message method. Note that in this simulator I will pass a long string to the sender, but in the actual host pic C routine we will be only sending two characters to the device since the hardware we built has only two digits. However the simulation is carried out in the general case.

Here is the send_message method:

    void send_message(String s) {
        t_stato stato;      
        int i;
        int bit=0;
        int ch;
        int aux;
        
        for (i=0;i<s.length();i++) {
            System.out.println(Ansi.BLUE+"***** Sending char at s["+i+"] = "+
                    s.charAt(i)+Ansi.RESET);
            stato = t_stato.Init;
            bit = 0;
            while (stato != t_stato.eot) {
               ch = (int) s.charAt(i); 
                switch (stato) {
                    case Init :
                        System.out.println("send - Init - Sending ch="+ch);
                        host_bus.clk = true;
                        System.out.println("send - Assert clock up");
                        while (host_bus.miso != true);
                        stato = t_stato.WaitForDeviceReady;
                        break;
                    case WaitForDeviceReady :
                        System.out.println("send - WaitForDeviceReady");
                        host_bus.clk = false;
                        System.out.println("send - Assert clock down");
                        while (host_bus.miso != false);
                        aux = (ch << bit) & 0x80;
                        if (aux == 0x80) host_bus.mosi = true;
                        else host_bus.mosi = false;
                        System.out.println("send - Sent bit "+bit+" aux= "+aux);
                        bit = bit+1;
                        host_bus.clk = true;
                        System.out.println("send - Assert clock up");
                        stato = t_stato.WaitForDeviceAck;
                        break;
                    case WaitForDeviceAck :
                        System.out.println("send - WaitForDeviceAck");
                        while (host_bus.miso != true);
                        if (bit == 8) stato = t_stato.eot;
                        else stato = t_stato.WaitForDeviceReady;
                        break;
                    case eot :
                        System.out.println("send - eot");
                        break;
            }
               
        }  
    }
} // end class

Ok, so far so good. It's time to see the output of our simulation and compare it to what we've seen in Part 1. I will print the output only for the first letter of the passed string “The quick brown fox jumped over the lazy dogs back”.

run:
***** Sending char at s[0] = T
	receive init
send - Init - Sending ch=84
send - Assert clock up
	receive assert miso up
	receive WaitForClockDown
send - WaitForDeviceReady
send - Assert clock down
	receive got clock down
	receive assert miso down
	receive WaitForClockUp
send - Sent bit 0 aux= 0
send - Assert clock up
send - WaitForDeviceAck
	receive got clock up
	receive Got bit : 0  ch = 0
	receive assert miso up
	receive WaitForClockDown
send - WaitForDeviceReady
send - Assert clock down
	receive got clock down
	receive assert miso down
	receive WaitForClockUp
send - Sent bit 1 aux= 128
send - Assert clock up
send - WaitForDeviceAck
	receive got clock up
	receive Got bit : 1  ch = 1
	receive assert miso up
	receive WaitForClockDown
send - WaitForDeviceReady
send - Assert clock down
	receive got clock down
	receive assert miso down
	receive WaitForClockUp
send - Sent bit 2 aux= 0
send - Assert clock up
send - WaitForDeviceAck
	receive got clock up
	receive Got bit : 2  ch = 2
	receive assert miso up
	receive WaitForClockDown
send - WaitForDeviceReady
send - Assert clock down
	receive got clock down
	receive assert miso down
	receive WaitForClockUp
send - Sent bit 3 aux= 128
send - Assert clock up
send - WaitForDeviceAck
	receive got clock up
	receive Got bit : 3  ch = 5
	receive assert miso up
	receive WaitForClockDown
send - WaitForDeviceReady
send - Assert clock down
	receive got clock down
	receive assert miso down
	receive WaitForClockUp
send - Sent bit 4 aux= 0
send - Assert clock up
send - WaitForDeviceAck
	receive got clock up
	receive Got bit : 4  ch = 10
	receive assert miso up
	receive WaitForClockDown
send - WaitForDeviceReady
send - Assert clock down
	receive got clock down
	receive assert miso down
	receive WaitForClockUp
send - Sent bit 5 aux= 128
send - Assert clock up
send - WaitForDeviceAck
	receive got clock up
	receive Got bit : 5  ch = 21
	receive assert miso up
	receive WaitForClockDown
send - WaitForDeviceReady
send - Assert clock down
	receive got clock down
	receive assert miso down
	receive WaitForClockUp
send - Sent bit 6 aux= 0
send - Assert clock up
send - WaitForDeviceAck
	receive got clock up
	receive Got bit : 6  ch = 42
	receive assert miso up
	receive WaitForClockDown
send - WaitForDeviceReady
send - Assert clock down
	receive got clock down
	receive assert miso down
	receive WaitForClockUp
send - Sent bit 7 aux= 0
send - Assert clock up
send - WaitForDeviceAck
	receive got clock up
	receive Got bit : 7  ch = 84
	receive assert miso up
	receive UpdateBuffer
	receive Got char  84 ASCII : T

Let's analyze a few rows just to see what happened. Device thread is just created and the receive state machine prints the “Init” state and then Assert Miso up (high) signaling to the host that it is ready.

In the meantime, the host thread prints “Init” and begins sending the “T” character. The Host asserts clock up (high) and passes in the state Waiting for Device Ready. Since the device just asserted the MISO line up (high) the sender sees it and passes in the state assert clock down (low), and suddenly the receive thread responds with “Got clock down” and asserts MISO down (low) signaling that it is ready to receive a bit.

The device waits for the clock to be up (high) again. The host puts the bit 0 on the MOSI line and then asserts clock up (high) and passes in the WaitForDeviceAck state. The receive thread immediately sees the clock up (high) and reads the bit, then asserts MISO up (high) and passes in the WaitForClockDown to receive the next bit. This process repeats eight times. When all bits are transferred the receiver passes in the UpdateBuffer state where it pushes the character into the receive buffer.

As you can see from the machines' state flow everything goes as we expected and we can finally say that our algorithm will work. The character to be transmitted is printed not as a character but as its ASCII code. The send method identifies itself with the send keyword so you can clearly distinguish between the host and the device messages that have the receive keyword and are indented.

At this point most of our work is completed since we only have left the C coding of the device and host routines and the device interrupt implementation. This is what we will see in Part 4

All parts of this article