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 0That'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 0There 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; // decimalSecondly, 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 :
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