Saturday, February 27, 2016

First Abstractions

In our last post, we discovered how to manually program hardware pins.  Our final code to continuously toggle a pin is:


    #include <asf.h>

    int main(void) {
        DDRB |= 1<<PORTB7;    //set output
        PORTB |= 1<<PORTB7;    //Set High
      
        while (1)
        {
            PORTB |= (1<<PORTB7);
            PORTB &= ~(1<<PORTB7);
        }
    }


...


Quick aside: By probing hardware pin PB7, I've concluded that '|=' is a single-cycle operator, while '&=' takes two clock cycles.  For example, the signal is high for 2 us, and low for 4 us.  Google didn't quickly answer why, so I'll look more into it later.


...


The first problem with the code above is we have no idea what "PORTB7" does in our system.  We could easily abstract the generic pin name to whatever it does in our system as follows:

    #define PhaseUL PORTB7


...which simply directs the preprocessor to substitute 'PhaseUL' with 'PORTB7'.  This helps us understand what pin we're toggling, but it doesn't make the code any simpler:


    #include <asf.h>

    #define PhaseUL PORTB7

    int main(void) {
        DDRB |= 1<<PhaseUL;    //set output
        PORTB |= 1<<PORTB7;    //Set low
      
        while (1)
        {
            PORTB |= (1<<PhaseUL);
            PORTB &= ~(1<<PhaseUL);
        }
    }

Keep in mind all we're trying to do is set a pin high and low.  The code above still requires us to know which port 'PhaseUL' is on... we could #define it, too:



    #define PhaseUL_Port PORTB

Which would make our code look like this:


    #include <asf.h>

    #define PhaseUL PORTB7
    #define PhaseUL_Port PORTB

    int main(void) {
        DDRB |= 1<<PhaseUL;    //set output
        PhaseUL_Port |= 1<<PORTB7;    //Set low
      
        while (1)
        {
            PhaseUL_Port |= (1<<PhaseUL);
            PhaseUL_Port &= ~(1<<PhaseUL);
        }
    }

In this same fashion, we could  #define anything else we wanted to make it easier to understand what signal we're changing, but it certainly doesn't make the code any easier.  Note that the above method would require two #defines per output pin!  Out of control!!!  Yuck.  There's got to be a better way.


...


Looking at the 64M1 gpio header files, it seems possible to use:

    gpio_set_pin_high(io_id)

 However, Atmel never explains what 'io_id' values to use, and if you follow the rabbit hole, the function calls into a black box.  No luck there, so I turned to the intertubes.  I didn't find an actual answer because the 8th reply to this discussion forum caught my eye: "gpio.h libraries are deprecated, and should be replaced with IOPORT.h".  Unsurprisingly, Atmel doesn't mention this in any documentation I've found, but hey, I'd previously wondered what 'IOPORT' was, so now I know.  My optimism tells me the glass is 1% full.


The IOPORT.h documentation has a quickstart guide and looks pretty straightforward.  After a few failed attempts, I arrived at the working code below:

    #include <asf.h>
    #define PhaseUL IOPORT_CREATE_PIN(PORTB,PORTB7)

    int main(void) {
        ioport_init();

        ioport_set_pin_dir(PhaseUL , IOPORT_DIR_OUTPUT);
        ioport_set_pin_low(PhaseUL);   

        while (1)
        {
            ioport_set_pin_high(PhaseUL);
            ioport_set_pin_low(PhaseUL);
        }
    }


This code is equivalent to the code further above*, but now we've used the ioport functions to abstract the actual pinout.  So why is this useful?  Now you don't need to know anything about the pin you want to toggle, except for the actual pin name.  We previously overcame this by #define(ing) three different variables to abstract each pin (pin name, pin port, pin direction).  Now we just use the descriptive ioport functions with our human-readable pin names. That's huge!

So what do we do now?  We need to define every pin we're using in the 'board_conf.h' file, so that it's not cluttering up our main loop.  Then we can just use the various ioport functions to manipulate our gpio pins. Finally, an ASF codebase that I've figured out how to use!

...

* (from above).  The code is equivalent, except that the pin is now high for 4 us (was 2 us), and low for 2 us (was 4 us).  In other words, the signal is inverted.  I suspect the ioport functions are writing to a different hardware register than PORTxn, but I'll look into it later.