Accessing IO pins by ordinal - a good idea ?

Go To Last Post
42 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

(EDIT: The source code is available in this post.)

Let's assume that it is possible to create a scheme that allows a developer to address each IO pin by number, with no code overhead compared to direct access (using PORTx et al).

To me it looks like a good idea in more than one way.

It allows you to put the hardware dependency in one place only (wherever you define your pin numbers) rather than scattered throughout the source code.

It is less hardware dependent than direct access, as no explicit port addresses are used.

It allows great portability between AVRs, as the same source code can run on any 8-bit AVR without modification. A recompile is all it takes to move from a tiny to a mega, for instance.

It is transparent, i.e. it can be mixed with direct access if desirable.

Are there any potential problems with such an addressing scheme ?

Would you use it, if it came at no size or speed cost ?

Sid

Life... is a state of mind

Last Edited: Sun. Nov 25, 2012 - 09:46 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Arduino does this, but at great cost in speed. I'm curious to know how you propose to do this without incurring a speed penalty.

As for disadvantages, it will make accessing multiple pins [on the same port] slower, as they will now have to be done as individual discreet accesses, instead of a single access.

Writing code is like having sex.... make one little mistake, and you're supporting it for life.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

glitch wrote:
Arduino does this, but at great cost in speed. I'm curious to know how you propose to do this without incurring a speed penalty.

You can get an idea from this post. I've added quite a bit since then, but I'm planning to share my code once I get enough feedback in this thread. If anybody wants to use it, I mean.

glitch wrote:
As for disadvantages, it will make accessing multiple pins [on the same port] slower, as they will now have to be done as individual discreet accesses, instead of a single access.

That's a good point. I'll see if I can find a way to add that while maintaining portability.

The code I have so far is transparent, though, so you can still do that part of your code the way you do it now.

Another disadvantage is the need for a "mental mapping" of pin numbers to actual pins on the chip. That is going to take some getting used to. But it may be worth it considering the portability and hardware independence you get.

Sid

Life... is a state of mind

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
Arduino does this, but at great cost in speed.

I regard this as one of the "great ideas" in Arduino, but mainly for beginners. The whole "bit arithmetic" needed to deal with ports and masks is apparently a big stumbling block for new users. It IS a board-level thing, though, rather than something that you can do on a chip-by-chip basis.

The speed penalty in Arduino comes more from allowing non-constant values for "pin" and "bit value" than from treating the IOs as ordinals. (with additional penalties for handling "analogWrite()" as well.) If you're willing to only allow constants, there are multiple ways to speed up ordinal pin operations to the maximum that would be possible (usually, single instructions.)

What you gain in beginner-friendliness, you lose in ability to deal with multibit fields.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

westfw,

I'm not trying to create a new Arduino library. I discovered that not only is it possible to address ports by ordinal at no size or speed cost - but it also allows for code that is portable between all 8-bit AVRs simply by means of recompiling.

My scheme allows for non-constant addressing as well. In this case, the generated code is going to be bigger - but you face the same cost if you try to implement something similar using direct access. So this is not a penalty, it is flexibility that you can use if you want to.

Since my previous post here I have also implemented what you need to deal with multibit fields in a portable manner. There is no size or speed cost associated with this either.

Sid

Life... is a state of mind

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

what if the multi-bit span, spans a port boundary? Not an ideal situation, but your code may result in someone doing that inadvertently more easily. [not a fault in your code/concept, just curious]

Writing code is like having sex.... make one little mistake, and you're supporting it for life.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

glitch wrote:
what if the multi-bit span, spans a port boundary?

Good thinking, you understand my code very well even before I posted it. ;o)

I don't plan to address that - you would face the same problem if you did that by direct access, and you will see that the solution I chose will prevent most people from falling into that trap. I.e. if you know how to do it with direct access, you will know how to do it with this.

It's possible to mess it up with direct access, and in a similar way it is possible to mess it up with this scheme. The consequence will be identical.

My goal is to create efficient, reusable code. There will be no pampering, although anybody that wants it can seamlessly add it themselves.

Sid

Life... is a state of mind

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have two different shields that are mapped to io pins really stupidly. One was a graphics lcd that uses 2 pins on one port and another 6 in another port for the data bus. That makes EVERY pixel write about 5 or 6 instructions longer even if you inline everything. The example that came with the shield used subroutine calls to do it. The other one did the same kludge with a ram expander. I have sort of settled on writing a define for each pin hi and lo in the lo level routines CSHI() and CSLO() for example. Its fast and readable. Both important.

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I am skeptical too. In addition to multibit fields, there are also bits beyond the CBI/SBI range. (Yes, you can check for that without extra code being generated, but you would probably need two sets of functions, for use inside ISRs and outside (or a single set but with overhead for cli/sei)).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

There are things which may make giving numbers unuseful, at least for hobby users:
a) some cards do not give numbers on their extension connectors, but just PA.x (say) ex : https://www.olimex.com/Products/...
Managing with numbers would only enhance this world's entropy, with unneeded abstraction ...

b) when one wants to connect a LED, a motor, a ck signal for serial expansion, the number is much less interesting than the final function...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ChaunceyGardiner wrote:
It allows you to put the hardware dependency in one place only (wherever you define your pin numbers) rather than scattered throughout the source code.

I define also all used pins in one place only.
But I use not numbers, I use the pin names.

My exampe code:
https://www.avrfreaks.net/index.p...

I have also predefined all pins to make the defines easier (less text to write):
https://www.avrfreaks.net/index.p...

Peter

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ChaunceyGardiner wrote:
glitch wrote:
what if the multi-bit span, spans a port boundary?

Good thinking, you understand my code very well even before I posted it. ;o)

I don't plan to address that - you would face the same problem if you did that by direct access, and you will see that the solution I chose will prevent most people from falling into that trap. I.e. if you know how to do it with direct access, you will know how to do it with this.

It's possible to mess it up with direct access, and in a similar way it is possible to mess it up with this scheme. The consequence will be identical.

My goal is to create efficient, reusable code. There will be no pampering, although anybody that wants it can seamlessly add it themselves.

Fair enough, was just curious as to how/if you would address it. I just see using un-grouped ordinals like that making falling into that trap easier. But ultimately it is a design flaw, not an API one.

Writing code is like having sex.... make one little mistake, and you're supporting it for life.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

bobgardner wrote:
I have two different shields that are mapped to io pins really stupidly. One was a graphics lcd that uses 2 pins on one port and another 6 in another port for the data bus. That makes EVERY pixel write about 5 or 6 instructions longer even if you inline everything. The example that came with the shield used subroutine calls to do it. The other one did the same kludge with a ram expander. I have sort of settled on writing a define for each pin hi and lo in the lo level routines CSHI() and CSLO() for example. Its fast and readable. Both important.

And that is the side effect of too much abstraction, in the arduino platform. keeping the port groupings would have made things a whole lot better IMHO.

Just out of curiosity, which shields are the ones you have that have those problems?

Writing code is like having sex.... make one little mistake, and you're supporting it for life.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The seeedstudio 240x320 shield, and the rugged circuit megaram 512k board that plugs into the rear doublerow header on a mega.

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think that one of the big problems for me would be hardware. Yes, something like

setbit(17);

is slightly attractive, but ....

Which bloody pin is "17"?

You would need some mapping macro that converts, lets say, "PD5" to the ordinal number, and back again. When you are trying to set an I/O bit that controls a relay, you need to control THAT pin, how ever it is referenced, And, when I go to lay out the board, I need to know which pin it is, so, again, some kind of mapping macro is needed.

So, unless documentation also adopts the ordinal pin numbers, it would be a continuing annoyance to match up software pin reference to hardware pin.

Jim

 

Until Black Lives Matter, we do not have "All Lives Matter"!

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

unless documentation also adopts the ordinal pin numbers,

AFAIK every AVR datasheet has a pin diagram on page 2. Surely it's easy to look up there and see that PD5 or OC1A or SCK or however you like to refer to it is the 17th of 28 or whatever?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Learning new tricks may be annoying for old dogs. But, if you give in to it you quickly get used to it.

The numbering scheme is not rocket science. The first PORTx on your chip is pins 0-7. The second PORTx is pins 8-15.

On an ATmega48A/48PA/88A/88PA/168A/168PA/328/328P, PB1 is pin 1.

On an an ATtiny24/44/84, PB1 is pin 9.

Symbols that give you the ordinal for any given pin could easily be added, but by using them you would throw away portability. E.g. mega328 doesn't have a PORTA like a tiny84, but it sure has enough IO pins to do anything the tiny84 does.

One possible source of confusion, is that the pin numbers are not based on the layout of the chip.

I'll throw in a couple of random pieces from the documentation I have written so far:

    Sample pin mappings

    ATtiny24/44/84
    Pin ordinal   Physical pin
         0            PA0
         1            PA1
         2            PA2
         3            PA3
         4            PA4
         5            PA5
         6            PA6
         7            PA7
         8            PB0
         9            PB1
        10            PB2
        11            PB3

    ATmega48A/48PA/88A/88PA/168A/168PA/328/328P
    Pin ordinal   Physical pin
         0            PB0
         1            PB1
         2            PB2
         3            PB3
         4            PB4
         5            PB5
         6            PB6
         7            PB7
         8            PC0
         9            PC1
        10            PC2
        11            PC3
        12            PC4
        13            PC5
        14            PC6
        15            (PC7)
        16            PD0
        17            PD1
        18            PD2
        19            PD3
        20            PD4
        21            PD5
        22            PD6
        23            PD7

Example application

Assume a circuit with two switches and two LEDs.
One LED will be lit while the corresponding button is pressed.
The other LED will be lit while the corresponding button is not pressed.

On an ATtiny84 the switches are hooked up to PA1 and PA2 while the LEDs are hooked up to PB1 and PB2.
Or, on an ATmega328P the switches are hooked up to PB1 and PB2 while the LEDs are hooked up to PC1 and PC2.
Or, a similar configuration on any other AVR.

The source code remains the same:

//=========================================================

#include "NumberedPins.h"

enum
{
    Switch1Pin  = 1,
    Switch2Pin  = 2,
    RedLedPin   = 9,
    GreenLedPin = 10
};

int main(void)
{
    setPinType(Switch1Pin,  InputPin);
    setPinType(Switch2Pin,  InputPin);
    setPinType(RedLedPin,   OutputPin);
    setPinType(GreenLedPin, OutputPin);

    // Enable internal pullups
    setPinState(Switch1Pin, 1);
    setPinState(Switch2Pin, 1);

    while(1)
    {
        // Turn red LED on when switch 1 is on,
        // turn it off when switch 1 is off
        setPinState(RedLedPin, getPinState(Switch1Pin));

        // Turn green LED off when switch 2 is on,
        // turn it on when switch 2 is off
        setPinState(GreenLedPin, !getPinState(Switch2Pin));
    }
}

Sid

Life... is a state of mind

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
The first PORTx on your chip is pins 0-7. The second PORTx is pins 8-15.

Even if the first two PORTx ports only have (say) six useful external pin connections?

I always thought that the obvious mapping was to use the chip pin numbers. Except those aren't constant either.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

westfw wrote:
Quote:
The first PORTx on your chip is pins 0-7. The second PORTx is pins 8-15.

Even if the first two PORTx ports only have (say) six useful external pin connections?

Yes, just like it is with direct access. If you have a PORTx that only deals with six pins, the PORTx in question still has 8 bits. Hence, my solution is just as good as what is already there.

I don't think there is any other way to deal with this AVR design feature in a portable way at no cost.

westfw wrote:
I always thought that the obvious mapping was to use the chip pin numbers. Except those aren't constant either.

While that may initially seem obvious, it's clearly not portable. It's not even portable between different packages of the same AVR. E.g. on a mega328p PD0 is chip pin 2, 26 or 30 depending on the package.

Sid

Life... is a state of mind

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

An example of how multipin IO may be done.

Let's assume a mega328p, where PC0-PC3 will constantly be copied to PB4-PB7.

So you may do something like this:

//============================================================

#include 

#define DATA_IN_PINX   PINC
#define DATA_OUT_PORTX PORTB
#define DATA_OUT_DDRX  DDRB

int main()
{
    DATA_OUT_DDRX |= 0xF;
    while (1)
    {
        uint8_t data = DATA_IN_PINX & 0xF;
        DATA_OUT_PORTX = (DATA_OUT_PORTX & 0xF) | (data << 4);
    }
}

With my solution, you could do the same thing like this:

#include "NumberedPins.h"

enum
{
    DataIn  = 8,
    DataOut = 4,
};

#define DATA_IN_PINX   (*getPINx(DataIn))
#define DATA_OUT_PORTX (*getPORTx(DataOut))
#define DATA_OUT_DDRX  (*getDDRx(DataOut))

int main()
{
    DATA_OUT_DDRX |= 0xF;
    while (1)
    {
        uint8_t data = DATA_IN_PINX & 0xF;
        DATA_OUT_PORTX = (DATA_OUT_PORTX & 0xF) | (data << 4);
    }
}

As you can see, the source code is pretty much identical, the difference is in the 3 #defines and the added pin identifiers.

The generated code is identical.

Now, the difference is that using my solution, this can be moved to a different AVR just by recompiling.

For instance, on a tiny84 the code will copy from PB0-PB3 to PA4-PA7.

Sid

Life... is a state of mind

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
it's clearly not portable.

I dunno I'm not sure what "portable" means in this context...

Quote:
I don't think there is any other way to deal with this AVR design feature in a portable way at no cost.

An implementation like:

static volatile uint8_t * const pin2port[] = {&PORTB, &PORTC, &PORTD, &PORTD, &PORTD };
static const uint8_t pin2bit[] = {1, 16, 32, 1, 2};
static inline void setbit(uint8_t pin)
{
  *(pin2port[pin]) |= pin2bit[pin];
}

looks to me like it allows for fully arbitrary mapping at zero cost...

Some of the Arduino people (or rather, the non-arduino people who have been writing code for arduinos) have been doing interesting things with C++ templates, too. (http://arduino.cc/forum/index.ph... )

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

westfw wrote:
Quote:
it's clearly not portable.

I dunno I'm not sure what "portable" means in this context...

What I meant was something like "source compatible across all 8-bit AVRs" to the extent that that is possible, and easy to migrate from one 8-bit AVR to another in cases where source compatibility is not possible.

An example of the latter would be that you use pin 15 and want to migrate to an AVR with only 12 IO pins. In that case, no cost source compatibilty is not possible unless you use the preprocessor in your code.

"Portable" may be the wrong word - what word/phrase do you suggest ?

westfw wrote:
Quote:
I don't think there is any other way to deal with this AVR design feature in a portable way at no cost.

An implementation like:

static volatile uint8_t * const pin2port[] = {&PORTB, &PORTC, &PORTD, &PORTD, &PORTD };
static const uint8_t pin2bit[] = {1, 16, 32, 1, 2};
static inline void setbit(uint8_t pin)
{
  *(pin2port[pin]) |= pin2bit[pin];
}

looks to me like it allows for fully arbitrary mapping at zero cost...


Yes, it probably would be, but it would take some work to make it source compatible between all 8-bit AVRs.

westfw wrote:
Some of the Arduino people (or rather, the non-arduino people who have been writing code for arduinos) have been doing interesting things with C++ templates, too. (http://arduino.cc/forum/index.ph... )

I haven't looked at what they are doing (I'll take a look now) - but I have up until now used a template library for this stuff myself.

I will probably change the way I do things in that template library due to the discoveries I have mentioned here - my existing template library has no cost either, but the addressing scheme is tied to whatever chip I'm coding for. (Each pin is defined by which PORT and bit index is used.)

Sid

Life... is a state of mind

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The pin numbering differs not only on every AVR type.
It differs also on every package.
So using pin names instead pin numbers sounds the better approach for me.

Peter

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

danni wrote:
The pin numbering differs not only on every AVR type.
It differs also on every package.
So using pin names instead pin numbers sounds the better approach for me.

You're missing the point. This is not based on the chip pin numbering.

And pin names are hardly portable across AVRs. Ordinals are.

For example, adressing pins by ordinal allows you to use the same source code for up to twelve pins on ATmega48A/48PA/88A/88PA/168A/168PA/328/328P and ATtiny24/44/84.

Pin names only allow you to use 4 pins across those two families.

Sid

Life... is a state of mind

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ChaunceyGardiner wrote:
Let's assume that it is possible to create a scheme that allows a developer to address each IO pin by number, with no code overhead compared to direct access (using PORTx et al).

[...]

It allows great portability between AVRs, as the same source code can run on any 8-bit AVR without modification. A recompile is all it takes to move from a tiny to a mega, for instance.

Well, the source code does not "run". You'll have to recompile it if the port layout changes. This you cannot use it in a library and define the port associations outside the library. I know you know it, I just mention it for completeness.

Quote:
It is transparent, i.e. it can be mixed with direct access if desirable.

Are there any potential problems with such an addressing scheme ?

Would you use it, if it came at no size or speed cost ?


Yes I am using exactly that scheme. For example, the projects have a "ports.h" file thal lookslike that
#ifndef PORTS_H
#define PORTS_H

#include 
#include "avr-port-macros.h"
#include "avr-port-enum.h"

enum
{
    PORT_LED   = PORTC_3,
    PORT_LED2  = PORTB_1,
    
    PORT_SPEAK = PORTD_4,
	
    // 74*595
    PORT_SS   = PORTB_2,	
    PORT_MOSI = PORTB_3,
    PORT_MISO = PORTB_4,
    PORT_SCK  = PORTB_5, // SCK
	
    PORT_KEY0 = PORTB_1,
    PORT_KEY1 = PORTD_7,
    PORT_KEY2 = PORTD_6,
    PORT_KEY3 = PORTB_0,

    PORT_TxD = PORTD_1,
    PORT_RxD = PORTD_0,
    
    PORT_LED_DCF = PORTB_1,

    PORT_NIL
};

#endif /* PORTS_H */

This could use #ifdef to parameterize different devices or board variants.

Advantages

  • The C code that ises this is easy going
  • *One* Place in the suorce that maps the ports
  • Ports can be virtial, for example I can define ports in the 74*595 port expanders that are taken care of by the SPI output routines. C code that uses these ports will be the same, it just treats a bit in the output buffer instead of in I/O space.
  • This can factor out toggle which is PORTx ^= mask together with atomic protection on old devices, and PINx = mask on new devices.
Disadvantages
  • Rather complex macros in the avr-port-macros.h support header that expant to complex preprocessed C.
  • The compiler folds all that stuff away to the minimum number of instructions. (I don't remenber what -O0 does because code with -O0 does not fit into the devices, anyways.
Notice I don't use bitfields or volatile bitfields because that might lead to considerable problems (the Linux people don't use bitfields for that reason).

avrfreaks does not support Opera. Profile inactive.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

That's one way to do it, but you suffer from having to change your source code when moving to other AVRs. Or having to provide different source code variants for each AVR family and use the preprocessor to distinguish between them.

What I have created allows you to compile for different AVRs using the same source code. The differences between AVR families are taken care of in the reusable code, i.e. code you don't even have to look at. (I know you will look at it, but that's beside the point.)

I'll attach the source code to this post. It's just one header file. The functions are described at the top of the file.

This is version 1 - please be gentle.

(EDIT: version 2.00 is available in this post.)

Sid

Life... is a state of mind

Last Edited: Sun. Nov 25, 2012 - 09:51 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ChaunceyGardiner wrote:
And pin names are hardly portable across AVRs.

But why this should be a problem :?:

E.g. you want to switch on a LED anywhere inside your program, then simple write:

LED0 = 1;

And in only one file (e.g. "hardware.h") you made the assignment, where the LED was connected, e.g:

#include "sbit.h"

#define LED0 PORT_B2

Then if you change the AVR type or the schematic, you must only edit the "hardware.h".

I use this method, because I like it to be flexible.
Then I can assign the pins in this order, which was optimal for the pcb routing.
Thus it happens often, that I change the "hardware.h" after making the pcb layout.

Peter

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

danni wrote:
ChaunceyGardiner wrote:
And pin names are hardly portable across AVRs.

But why this should be a problem :?:

E.g. you want to switch on a LED anywhere inside your program, then simple write:

LED0 = 1;

And in only one file (e.g. "hardware.h") you made the assignment, where the LED was connected, e.g:

#include "sbit.h"

#define LED0 PORT_B2


You have to do more than that. Pins are set up for input by default.

danni wrote:
Then if you change the AVR type or the schematic, you must only edit the "hardware.h".

What I'm suggesting allows you to change the AVR type without editing any source files. And if you "change the schematic", there will only be one symbol to change with my approach. You will have to change at least two.

danni wrote:
I use this method, because I like it to be flexible.

What I'm suggesting is more flexible.

Compare this to your (complete) LED turner onner source code:

#include "NumberedPins.h" 

#define LED0 2 

int main(void) 
{ 
    setPinType(LED0, OutputPin);
    setPinState(LED0, 1);
    while (1)
    {
    }
}

Is that simple enough for you ? It will run on a number of different AVRs, all you have to do is recompile it.

Sid

Life... is a state of mind

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ChaunceyGardiner wrote:
That's one way to do it, but you suffer from having to change your source code when moving to other AVRs.
No. If the function is still wired to Port B1 for example, nothing will change.

If the same function will we wired to, say Port D3, then there is o way around changing the source, namely the ports.h in my case. The port numbers are assigned statically: whatever AVR it is, Port B1 will get the same value.

avrfreaks does not support Opera. Profile inactive.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

There are many ways to skin a cat.

I prefer assignments instead subroutine calls (easier reading, less writing).
I prefer so see the right pin on the schematic without needing a translation table.
But the drawback was, I must do up to 3 definitions per pin on my way.

So no way was the best.

Peter

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

danni wrote:
There are many ways to skin a cat ... But the drawback was, I must do up to 3 definitions per pin on my way.
Completely agree about the cat and that there is no "best" way. I changed the way I do things several times already. The latest incarnation I think addresses your "up to 3 definitions". What I do lately is define the pins in a text file, one line per pin. Something like:
PA0 output led
PA1 input button

Then I convert the text file to c/h using some utility. That utility of course allows me generate any number of "definitions" per pin (masks, pin numbers, port indices, etc).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

SprinterSB wrote:
ChaunceyGardiner wrote:
That's one way to do it, but you suffer from having to change your source code when moving to other AVRs.
No. If the function is still wired to Port B1 for example, nothing will change.

If the same function will we wired to, say Port D3, then there is o way around changing the source, namely the ports.h in my case. The port numbers are assigned statically: whatever AVR it is, Port B1 will get the same value.

ChaunceyGardiner wrote:
pin names are hardly portable across AVRs. Ordinals are.

For example, adressing pins by ordinal allows you to use the same source code for up to twelve pins on ATmega48A/48PA/88A/88PA/168A/168PA/328/328P and ATtiny24/44/84.

Pin names only allow you to use 4 pins across those two families.

Sid

Life... is a state of mind

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Would you be more specific and give an example?

avrfreaks does not support Opera. Profile inactive.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

SprinterSB wrote:
Would you be more specific and give an example?

An example of what ?

The example in the header file - which is also posted above, in this post - illustrates how to use this.

Granted, it only uses four pins but Shirley you can figure out how to add a fifth one ?

Sid

Life... is a state of mind

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

An example when my approch is more restrictive than your approch. As you said, my approch does not work without recompiling whereas your approch does.

avrfreaks does not support Opera. Profile inactive.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

SprinterSB wrote:
An example when my approch is more restrictive than your approch. As you said, my approch does not work without recompiling whereas your approch does.

I don't know where you think I said that. Of course you have to recompile.

What i said was that if you rely on pin names, you will only be able to use the same source code for four pins if you want it to work on ATmega48A/48PA/88A/88PA/168A/168PA/328/328P as well as ATtiny24/44/84. If you use my approach you will be able to use all twelve pins without changing your source code.

Sid

Life... is a state of mind

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

SprinterSB's code will work across more AVR's than you say [without editing], it will work on ANY AVR that has the same named ports for the pins used.

I don't see any advantage to using ordinals, as you won't use code "as-is" like that between such vastly varying AVR's as a Mega, and a Tiny. When making such a move you are more than likely going to have things mapped quite differently, so more than a mere re-compile is going to be needed anyway. The only time I can possibly see an advantage to ordinals is on a development board like the Arduino, where the ordinals themselves are used on the PCB to call out the I/O's, rather than their native names. But then you run into the port/pin grouping errors I outlined earlier much more likely, as the user no longer sees the logical groupings.

Honestly all I see this additional abstraction adding is more confusion and frustration, as now you have to look at the datasheet [or schematic], and the library documentation to figure out which ordinal maps to the pin you just connected some function to, whereas when you use native names all you need is the datasheet/schematic. While your method may save typing a few characters, it requires looking at several documents, so in the end I don't think you're really saving anything. Also I believe there is a greater chance for error in the user looking-up/translating the port/pin to ordinal. [we already see this using native names, your method just adds a 2nd chance for that to happen]

Writing code is like having sex.... make one little mistake, and you're supporting it for life.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

glitch wrote:
SprinterSB's code will work across more AVR's than you say [without editing], it will work on ANY AVR that has the same named ports for the pins used.

I just used those two chip families as examples as those are the ones I have repeatedly referred to in here. But Shirley there must be other AVRs that have PORTA as well ?

glitch wrote:
I don't see any advantage to using ordinals, as you won't use code "as-is" like that between such vastly varying AVR's as a Mega, and a Tiny.

Well, I do. I use mega328p for development, while my design is in progress. I sometimes find that I can actually do the same thing with a tiny, and thus save quite a bit if stuff ever goes to mass production.

That being said, if it can run on a tiny there is hardly ever going to be a need to maintain compatibility with a mega328. But source code compatbilty between different chips would be good in cases where a certain part is unavailable or the price takes off without warning.

glitch wrote:
then you run into the port/pin grouping errors I outlined earlier much more likely, as the user no longer sees the logical groupings.

I did show you how you can maintain source code compatibility with groups of pins when you asked for it earlier.

glitch wrote:
Honestly all I see this additional abstraction adding is more confusion and frustration, as now you have to look at the datasheet [or schematic]

This isn't rocket science. Any skilled developer that is not asleep should be able to quickly figure out the mapping for any given pin without looking anything up.

Do your ports start with PORTA or PORTB ? Don't tell me that you have to look that up.
If they start with PORTA, PA0 is pin 0, PB0 is pin 8 and so on.
If they start with PORTB, PB0 is pin 0, PC0 is pin 8 and so on. How hard can it be ?

I understand that there are many ways to go about this, and I have gone about it differently myself until now.

What I like about this approach, in addition to source compatibility between chips, is that it allows me to create flexible reusable code that doesn't rely on any complicated code or dirty tricks.

My next step is probably to start using my modified C++ template library. I thought about sharing that library too, but it seems that nobody is really interested so I guess I'll just keep it for myself.

I did ask if this was a good idea, hoping that I would get good answers to why it may not be such a good idea. I got some good responses, and some concerns that I didn't think about before. So far, I haven't seen anything convincing me to give it up, though.

Sid

Life... is a state of mind

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Having thought some more about it I have decided that you are right - I will ditch the current pin numbering and do this in a way that is more tied to the chip at hand.

There will still be a numbering scheme behind the scenes, but the user will not have to deal with it if he doesn't want to. I will post a new version when I get it ready.

Sid

Life... is a state of mind

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ok, this is the new version. It allows you to use chip-dependent port/pin symbols to select the pins.

Example application - for any 8-bit AVR that has PORTB and PORTC

Assume a circuit with two switches and two LEDs.
One LED will be lit while the corresponding button is pressed.
The other LED will be lit while the corresponding button is not pressed.

The switches are hooked up to PB1 and PB2 while the LEDs are hooked up to PC1 and PC2.

#include "NumberedPins.h"

enum
{
    Switch1Pin  = PinB1,
    Switch2Pin  = PinB2,
    RedLedPin   = PinC1,
    GreenLedPin = PinC2
};

int main(void)
{
    setPinType(Switch1Pin,  InputPin);
    setPinType(Switch2Pin,  InputPin);
    setPinType(RedLedPin,   OutputPin);
    setPinType(GreenLedPin, OutputPin);

    // Enable internal pullups
    setPinState(Switch1Pin, 1);
    setPinState(Switch2Pin, 1);

    while(1)
    {
        // Turn red LED on when switch 1 is on,
        // turn it off when switch 1 is off
        setPinState(RedLedPin, getPinState(Switch1Pin));

        // Turn green LED off when switch 2 is on,
        // turn it on when switch 2 is off
        setPinState(GreenLedPin, !getPinState(Switch2Pin));
    }
}

While doing this, I found that I could just add those symbols to the existing code, so the examples I posted earlier will still work the same way as before. In other words, if anybody wants to do ports by number they still have that option.

(EDIT: version 2.00 is available in this post.)

Sid

Life... is a state of mind

Last Edited: Sun. Nov 25, 2012 - 09:50 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ChaunceyGardiner wrote:
glitch wrote:
SprinterSB's code will work across more AVR's than you say [without editing], it will work on ANY AVR that has the same named ports for the pins used.

I just used those two chip families as examples as those are the ones I have repeatedly referred to in here. But Shirley there must be other AVRs that have PORTA as well ?

Surely you're aware of the mega128 and the like, and don't call me Shirley ;)

ChaunceyGardiner wrote:
glitch wrote:
[The only time I can possibly see an advantage to ordinals is on a development board like the Arduino, where the ordinals themselves are used on the PCB to call out the I/O's, rather than their native names. But] then you run into the port/pin grouping errors I outlined earlier much more likely, as the user no longer sees the logical groupings.

I did show you how you can maintain source code compatibility with groups of pins when you asked for it earlier.

In this case I was specifically referring to using the ordinals in both the software and hardware documentation [like the Arduino] - I added the context back into the quote for clarity.

ChaunceyGardiner wrote:

I did ask if this was a good idea, hoping that I would get good answers to why it may not be such a good idea. I got some good responses, and some concerns that I didn't think about before. So far, I haven't seen anything convincing me to give it up, though.

I think it has been a good discussion for the most part. I took some time away from the thread to let the idea soak in my head, before coming back and posting my additional thoughts. You still haven't swayed me, I am still skeptical of any advantages... but still open to the idea.

Writing code is like having sex.... make one little mistake, and you're supporting it for life.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks, glitch

Maybe it will become more obvious with the C++ template library, attached below.

I probably won't bother with documentation unless someone says they want to use it.

The methods in the DigitalPin class are:
- constructor, see code example below
- default constructor for delayed init
- init() function for use with the default constructor.
- clear(), sets the pin low
- set(), sets the pin high
- set(bool on), sets the pin high or low
- toggle()
- isClear()
- isSet()
- get(), same as isSet()
- enablePullup()
- disablePullup()
- setType(), set type to input or output

There is also an assignment operator and an operator bool, used in the example below.

So now, a C++ user may do our earlier two-switch two-LED example like this:

#include "DigitalPin.h"

using namespace net::avrfreaks::sid::digitalpin;

int main(void)
{
    DigitalPin switch1(InputPin, EnablePullup);
    DigitalPin switch2(InputPin, EnablePullup);
    DigitalPin redLed(OutputPin);
    DigitalPin greenLed(OutputPin);

    while(1)
    {
        // Turn red LED on when switch 1 is on,
        // turn it off when switch 1 is off
        redLed = switch1;

        // Turn green LED off when switch 2 is on,
        // turn it on when switch 2 is off
        greenLed = !switch2;
    }
}

It should be pretty obvious that this allows for nice short source code, and this becomes even more obvious when you add more functionality. And there are no port/pin definitions floating around all over the place.

The generated code is just as small and fast as the equivalent direct access code.

The previously posted functionality may still be used in both C and C++, it is included in the same header file.

I have changed the name of the file to better reflect what's in it and to remove the focus from pin numbers.

Let me know what you think.

Attachment(s): 

Sid

Life... is a state of mind