XMEGA tutorial: ports (04)

|

Port is the fundamental peripheral, which enables connection between microcontroller and other devices. In well known and appreciated processors: ATtiny & ATmega each port used to have three registers: PIN, PORT and DDR. However in XMEGA microcontrollers there are 21 registers for each port... but don't worry! Using the XMEGA ports is even easier than in ATmega!

All ports have unique name: PORTA, PORTB, PORTC... and so on until the end of alphabet! Within each port there are several registers and some of the most important are listed below:
  • DIR – this one register decides which of the pins is supposed to be an input or output. Typing 1 will configure the pin as an output and 0 as input. For example, the instruction below sets pins 3 and 6 as outputs, therefore the other pins will be inputs:
    
    PORTA.DIR = PIN3_bm | PIN6_bm;
     
  • OUT - this is the output register. Typing 1 will cause the appearance of high state of appropriate "leg" of port, while 0 means low state.
  • IN – is the input register, used for reading current pin state. Here is the example of conditional statement (branch control structure) which checks if the state on the pin E5 is high:
    
    if(PORTE.IN & PIN5_bm)
     
In ATmega and ATtiny microcontrollers, just to set or reset the status of one pin (of one port) we had to use bit masks and operators |= . For example, to set high state on pin A1 and reset pin A2 there was a need to type:
    
    PORTA |= (1<<PA1);      // set A1 to 1
    PORTA &= ~(1<<PA2);     // set A2 to 0
    
That's not really convenient and fast working, but fortunately designers of XMEGA processors invented much faster and easier access to pins. Above instructions can be replaced by using registers OUTSET and OUTCLR:
    
    PORTA.OUTSET = PIN1_bm;  // set A1 to 1
    PORTA.OUTCLR = PIN2_bm;  // set A2 to 0
    
There is also OUTTGL register used for toggling the status of the port's bits, which are indicated in this register. We have registers DIRSET, DIRCLR and DIRTGL, which sets, resets or toggles the status of bits responsible for that if the indicated pin should be recognized as input or output.

Registers PINxCTRL are also really important, because they let us to configure advanced options of specific pins. What's important, each pin has its own control register. It's possible to turn on resistors: pull-up, pull-down, keeper, just by typing appropriate values into controlling register.

By these registers we are able also to configure interrupts, slew rate and several other options. Let's connect few LEDs to X3-DIL from Leon Instruments, which will blink with the frequency depending on that if the button E5 is pushed (the same button that is used for programming by the FLIP).



Firstly, we need to configure the input/output direction of signals through the pins in registers DIR, which belong to the appropriate ports. We configure outputs A0, B0, C0, D0 and E0 to which we connect the diodes. It's possible to do it in different ways, but I recommend using the first one mentioned in this course.

Typing values in hexadecimal code is highly inadvisable, especially in case of configuring more complicated peripherals.
 
    PORTA.DIR    =    PIN0_bm;        // bit mask
    PORTB.DIR    =    (1<<PORT0);     // by name
    PORTC.DIR    =    0b00000001;     // binary
    PORTD.DIR    =    0x01;           // hexadecimal
    PORTE.DIR    =    1;              // decimal
 
Secondly, we configure the button. It's soldered to the E5 pin and shorts it to the ground (when pushed). Otherwise pin E5 should have high logic state imposed by pull-up resistor. In the first line of code we reset bit 5 in DIR register by typing the value of PIN5_bm to the DIRCLR register. Afterwards we must turn off the pull-up resistor using the PIN5CTRL register. It needs a special attention because of the difference between the way we turn on pull-up resistors in XMEGA and ATmega or ATtiny.
    
    PORTE.DIRCLR    =    PIN5_bm;
    PORTE.PIN5CTRL  =    PORT_OPC_PULLUP_gc;
 
Next one thing is the main infinite loop: while(1). Blinking diodes can be realized in several ways. Let's analyze the code:
    
    _delay_ms(500);                    // waiting 500ms (1)
    PORTA_OUT      |= (1<<PIN0_bp);    // setting bit in the old way (2a)
    PORTB.OUTSET    =  PIN0_bm;        // setting bit in the new way (3a)
        
    _delay_ms(500);                    // waiting 500ms (1)
    PORTA_OUT      &= ~(1<<PIN0_bp);   // clearing the bit in the old way (2b)
    PORTB.OUTCLR    =  PIN0_bm;        // clearing the bit in the new way (3b)
 
Instruction _delay_ms(500) means waiting for the half a second. Afterwards we set the pin A0 and B0 high. It is clear that a new way of controlling the pins described in line 3a is much more concise and readable. More importantly, it is also performed faster and requires less memory space, as we use operator = instead of |=. In the next part of code we reset pins A0 and B0 in the same way as in ATmega and another one, typical for XMEGA. There is also other way to realize diode's blinking. We can use function:
    
    toggle(&PORTC);
        
...defined as:
    
    void toggle(PORT_t *io) {     // toggle state of pin 0 of selected port 
        io->OUTTGL = PIN0_bm;
    }
 
This function takes as argument the name of the port and converts the status of the last bit to the opposite by typing the value of PIN0_bm to register OUTTGL of selected port. This function can be used in the following examples.

Let two more LEDs blinking at different frequencies, depending on whether the button is pressed or not.
    
    if(!(PORTE.IN & PIN5_bm)) {    // if button pressed
        toggle(&PORTD);
    } else {                       // if button released
        toggle(&PORTE);
    }
 
Logical instruction PORTE.IN & PIN5_bm checks, it there's high state on the PORT's E 5th pin. If so, this statement returns true. However, remember that the pin E5 has enabled pull-up resistor, so the default status is the status of the logical is one, and pushing the button shorts the pin to the ground, and thus the zero appears. Therefore, this expression has been negated with the negation operator !. Then we call the toggle function with the PORTE as an arguments, when the button is pressed or PORTD, when the button is released. (to be more precise - we use a pointer to the port register)

Here is the code of the program:
    
    #define     F_CPU    2000000UL
    #include    <avr/io.h>
    #include    <util/delay.h>

    void toggle(PORT_t *io) {            // toggle pin 0 of the selected port
        io->OUTTGL = PIN0_bm;
    }

    int main(void) {
       
       // different ways to set pin 0 as output
       PORTA.DIR    =    PIN0_bm;        // bit mask
       PORTB.DIR    =    (1<<PORT0);     // by name
       PORTC.DIR    =    0b00000001;     // binary
       PORTD.DIR    =    0x01;           // hexadecimal
       PORTE.DIR    =    1;              // decimal
       
       // E5 as an input with pull-up
       PORTE.DIRCLR     =    PIN5_bm;
       PORTE.PIN5CTRL   =    PORT_OPC_PULLUP_gc;
       
       while(1) {
        _delay_ms(500);                  // waiting 500ms
        PORTA_OUT      |=  (1<<PIN0_bp); // setting bit in the old way
        PORTB.OUTSET    =   PIN0_bm;     // setting bit in the new way
        
        _delay_ms(500);                  // waiting 500ms
        PORTA_OUT      &= ~(1<<PIN0_bp); // clearing bit in the old way
        PORTB.OUTCLR    =   PIN0_bm;     // clearing bit in the new way
        
        toggle(&PORTC);
        
        if(!(PORTE.IN & PIN5_bm)) {      // if button pressed
            toggle(&PORTD);
        } else {                         // if button released
            toggle(&PORTE);
        }
       }
    }
 
But it is not all about the ports - only the tip of the iceberg. In addition, ports in XMEGA have other features such as:
  • slew rate control slope
  • remapping pins
  • configurations pull-up, pull-down, keeper, or wired, wired and
  • interrupt reporting
  • generating events
  • virtual ports

1 comments :

Anonymous said...

this code worked perfectly for me, very easy, after many unsuccessful I/O attempts with other code examples. I'm a little confused by the toggling function:

void toggle(PORT_t *io) { // toggle pin 0 of the selected port
io->OUTTGL = PIN0_bm;
}

You pass to this function a pointer for PORTD, and it seems also to accept *io. Are you making OUTTGL somehow a member of io? I see in the manual that setting a bit mask to the OUTTGL register inverts those bits in the OUT register, that's clear. But the *io argument in the function definition, and the using OUTTGL as a member ofL are confusing to me - can you help me understand? much thanks

Post a Comment

Post a comment