简体   繁体   中英

Implementing pin numbers in AVR microcontroller similar to Arduino

I'd like to implement something similar to Arduino's pin numbers for an Atmel ATMega32U4. I've looked at Arduino's digitalWrite command and associated source files to see how they do it, but I think this is slightly convoluted so I wanted to implement a more basic version.

The idea is to have integers 1 to n that represent each of the I/O pins on the AVR chip. I started with an array of pointers to the addresses of the DDR/PORT registers, where the index would represent the pin:

volatile uint8_t *pin_port_dir_regs[] = {
    0,                // NOT USED
    0,                // NOT USED
    0,                // NOT USED
    0,                // NOT USED
    0,                // NOT USED
    0,                // NOT USED
    0,                // NOT USED
    &DDRE,            // PIN_7
    &DDRB,            // PIN_8
    0,                // NOT USED
    0,                // NOT USED
    0,                // NOT USED
    0,                // NOT USED
    0,                // NOT USED
    0,                // NOT USED
    &DDRB,            // PIN_15
    &DDRB             // PIN_16
};

volatile uint8_t *pin_port_out_regs[] = {
    0,                 // NOT USED
    0,                 // NOT USED
    0,                 // NOT USED
    0,                 // NOT USED
    0,                 // NOT USED
    0,                 // NOT USED
    0,                 // NOT USED
    &PORTE,            // PIN_7
    &PORTB,            // PIN_8
    0,                 // NOT USED
    0,                 // NOT USED
    0,                 // NOT USED
    0,                 // NOT USED
    0,                 // NOT USED
    0,                 // NOT USED
    &PORTB,            // PIN_15
    &PORTB             // PIN_16
};

I also need to know the bit numbers in each of the DDRx/PORTx registers, so I created another array:

const uint8_t pin_bits[] = {
    _BV(0),  // NOT USED
    _BV(0),  // NOT USED
    _BV(0),  // NOT USED
    _BV(0),  // NOT USED
    _BV(0),  // NOT USED
    _BV(0),  // NOT USED
    _BV(0),  // NOT USED
    _BV(6),  // PIN_7
    _BV(4),  // PIN_8
    _BV(0),  // NOT USED
    _BV(0),  // NOT USED
    _BV(0),  // NOT USED
    _BV(0),  // NOT USED
    _BV(0),  // NOT USED
    _BV(0),  // PIN_14
    _BV(1),  // PIN_15
    _BV(3)   // PIN_16
};

To set the pin mode and write to the pins, I created the following functions:

void pin_mode(uint8_t pin, uint8_t direction) {
    // defeference the pointer to the direction register
    uint8_t port_dir_register = *(pin_port_dir_regs[pin]);

    // get pin mask
    uint8_t mask = pin_bits[pin];

    // set its mode
    if (direction == INPUT) {
        port_dir_register &= ~mask;
    } else {
        port_dir_register |= mask;
    }
}

void pin_write(uint8_t pin, uint8_t level) {
    // defeference the pointer to the output register
    uint8_t port_out_register = *(pin_port_out_regs[pin]);

    // get pin mask
    uint8_t mask = pin_bits[pin];

    // set output
    if (level == LOW) {
        port_out_register &= ~mask;
    } else {
        port_out_register |= mask;
    }
}

What's supposed to happen is that you would call eg pin_mode(7, OUTPUT) to set pin 7 as an output, and then pin_write(7, HIGH) to set the output to 1 (where OUTPUT and HIGH are predefined macros). The code compiles and uploads to the AVR successfully, but when I test the output it doesn't respond. I guess I must be writing to some memory location, but not the one corresponding to the intended registers. Does anyone see a problem with the way I am trying to do this?

As answer to the comment I gave:

what standard files do you refer to?

From avr-gcc with installed avrlibc is a standard set of files for each controller available.

If your code is something like:

#include <avr/io.h>  // automatic include correct io from device like
                     // <avr/iom32u4.h> in your case. That file defines
                     // all registers and pin descriptions and also irq
                     // vectors!

// So you can write:
int main()
{
     DDRA= 1 << PA4;  // enable output Pin 4 on Port A
     PORTA= 1 << PA4; // set output Pin4 on Port A high

     // and normally on startup all needed output pins will be 
     // set with only one instruction like:
     // To set Pin 2..4 as output
     DDRA= ( 1 << PA4) | ( 1 << PA3 ) | ( 1 << PA2 );
}

Make a simple PORTA= 1 << PA4 to a function call is a lot of waste. And defining your own register names and pin names is useless. All the pins are already defined as you can see.

Your idea to have only a logical port number which is located "somewhere" on a magic defined port looks not very natural to me. If you design a circuit and the software, you have to deal with the pins as described in the datasheet. There is no "logical pin number". This idea helps for nothing but for bigger code size :-)

In addition you have to remember which pins have also multiple functions like for uarts and all the other hardware stuff. So moving to another device will always need a review on pin / port usage. Here a logical port number is also a complicating layer between the hardware and the natural way of software.

The reason to why your code does not work is this step:

uint8_t port_out_register = *(pin_port_out_regs[pin]);

You want to write to a hardware register located at a specific address. To do this you need to:

*(pin_port_out_regs[pin]) = some_value;

Otherwise you will just modify stack memory to no gain.

Try to change to this instead:

void pin_write(uint8_t pin, uint8_t level)
{
    uint8_t *port_out_register = pin_port_out_regs[pin];
    uint8_t mask = pin_bits[pin];

    if (level == LOW) {
        *port_out_register &= ~mask;
    } else {
        *port_out_register |= mask;
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM