thoughts on GPIO pin abstractions? - Page 1
Forum section: electronics / general

author: ralphd
author_rank: Frequent Contributor
timestamp: June 09, 2015, 01:19:45 am
content: What are people's thoughts on pin numbering abstractions, like Wiring where digitalWrite(13,HIGH) is the same thing as sbi(PORTB, 5)?On one hand I generally dislike simple abstractions like that as it is one more level of indirection to understand how things really work. I remember having a problem with my first Pro Mini after soldering the pin headers. I wasn't seeing a pin change on my breadboard, and wanted to check the QFP lead directly. That meant first looking up the maping of the Wiring pin numbers to the port/bit of the m328, then finding the port/bit on the chip pinout.On the other hand, it is a bit more convenient when dealing with libraries to be able to define the port and bit in one go like:#define SPI_SS 10instead of:#define SPI_SS_PORT PORTB#define SPI_SS_BIT 2The other factor I hadn't though of (or really realized the scope of) when I started doing AVR development was the 'duino crowd. When I write an open-source AVR lib, if it's at all popular, without fail, someone asks if I have a duino version of the library. If I use pin abstractions like digitalWrite, it would be easier to make a Wiring/Arduino library. I'm not even sure if that is a good thing though; most of the people using my code now are smart enough to change compiler flags in a makefile. If I start writing code that can be cut & pasted into an IDE by a newbie, I'm afraid I will start getting badgered by the lazy and clueless.Anyone else struggle with this question and come up with a satisfactory answer?

author: Royce
author_rank: Contributor
timestamp: June 09, 2015, 02:36:22 am
content: Macro away. Just be honest in the docs about your intentions for support. Tell them you may tire of beginner questions. Tell them direct email and PMs will go unanswered.The Arduino crowd has a range of folks, some advanced, some not so much. You can possibly leverage the advanced folks if any of them find your library at all useful. Maybe try starting a thread over at the .cc site and talk a little bit about your library. Other advanced folks may notice beginner questions there and answer in your stead. Then you can redirect the beginner questions there.

author: ralphd
author_rank: Frequent Contributor
timestamp: June 09, 2015, 02:51:03 am
content: Good points. My most popular AVR code has already been ported to 'duino, so someone else ends up supporting the newbies. I was banned from the .cc site for blasphemy against Marchese Banzi, so I don't announce any of my code there any more. :-)I was also banned from avrfreaks for saying bullshit too many times. Since bullshit is a regular part of Dave's vocabulary I'm pretty sure I won't encounter that kind of prudish censorship here. :-)

author: Ian.M
author_rank: Super Contributor
timestamp: June 09, 2015, 02:58:04 am
content: As long as the abstraction is entirely resolved at compile time if (#defined) literal constants are used so it has no runtime performance implications, abstract away to your heart's content.Its helpful if the same #defined named pin can be used with another macro to set the pin direction and so on for other pin configuration features.

author: ralphd
author_rank: Frequent Contributor
timestamp: June 09, 2015, 03:14:14 am
content: Quote from: Ian.M on June 09, 2015, 02:58:04 amI already do that with my AVR code. In all the tiny and megas, the DDR address is always 1 less than the PORT address. So I just define the PORT, and have another macro that defines the DDR as &PORT-1.

author: kolonelkadat
author_rank: Regular Contributor
timestamp: June 09, 2015, 03:41:49 am
content: I like code like thisCode://-------------------------// Output pins//-------------------------//these values are for building bitmasks//so should be between 0 and 7 inclusive#define ANODE 3 //digital pin 3#define MODE 5 //digital pin 5#define ST_BY 6 //digital pin 6...PORTD = 0x00 | ((1<<ANODE)|(1<<MODE)|(1<<ST_BY));for me, it is clear, concise, and anyone with 2 IQ above a rock can understand its function without needing to look it up. With your example of "sbi(PORTB, 5)" I would have to look it up and check for any side effects because Im only a noob at AVR.

author: westfw
author_rank: Super Contributor
timestamp: June 09, 2015, 09:24:24 am
content: I've often thought that there should be a set of Arduino pin def files (pins_arduino.h) where the "digital pin" numbers are exactly the chip pin numbers... Yes, you'd have to do something for the assorted holes in the middle that aren't gpio capable, but that's not really any worse than going off the end of the defined pins in the current arduino scheme.

author: ralphd
author_rank: Frequent Contributor
timestamp: June 09, 2015, 12:07:27 pm
content: Quote from: westfw on June 09, 2015, 09:24:24 amThat's another good idea. Non-gpio pins could be made to generate an error in the macro.

author: free_electron
author_rank: Super Contributor
timestamp: June 09, 2015, 01:10:50 pm
content: Depends on the language and the cpu (like is the port bit adressable like ina 8051)Define led p1.1Define on 1Define off 0Define pressed 0Define switch p1.0If switch = pressed then led = on else led = offYou get it .. At compile time ths becomes like three or four instructionsLoad pin in accumulatorBranch depending on accumulator clear. (Actually skip instruction)P1.7 itself is a define for the processor pointing to the address of the pin (in this case each bit has its own address : the bits are individually adressable on an 8051)

author: bktemp
author_rank: Super Contributor
timestamp: June 09, 2015, 01:28:41 pm
content: Quote from: kolonelkadat on June 09, 2015, 03:41:49 amIt has the disadvantage of all pins must be on the same port. This can be annyoing especially if you use a lot of pins.My favourite is something like#define LED PORTB_5Then you can use it withLED=1;LED=0;This works for all microcontroller families and compilers I have seen. If not it can be added with a few lines of preprocessor magic using unions of bitfields (like for AVRs). All compilers I have used translate this directly into bit set and clear instructions like sbi/cbi.Using these defines, it is very easy to use the code on a different microcontroller with only minor changes necessary.

author: FrankBuss
author_rank: Unknown
timestamp: June 09, 2015, 01:30:16 pm
content: Usually I have an Excel sheet which lists all my physical pins and the function for it (if the IDE doesn't provide such a function). Very useful for debugging. Then I use simple names in my program.I really like how mbed works:https://developer.mbed.org/handbook/DigitalIn(and DigitalInOut) It needs some cycles, but you can use some tricks with templates to make it as fast as inline assembler, but as easy to read as the C++ version:https://developer.mbed.org/users/igorsk/notebook/fast-gpio-with-c-templates/

author: mikerj
author_rank: Super Contributor
timestamp: June 09, 2015, 01:38:52 pm
content: Many years ago I wrote some macros for pin manipulation on an old ATMega128 based project. I learnt a lot about the preprocessor when writing this, and also a lot about how overly complicated macros are usually not a good ideaThe idea was to define a symbol that contained all the following values as 'function-like' arguments. When passed as an argument to the function-like macros, the macros would extract only the fields of interest.Port (A,B,C,D etc.)Bit number (0-7)Initial DDR value (0 or 1)Initial output latch value (0 or 1)"Asserted" state (0 or 1)Code:/* Define the pins: Pin port, Pin bit, Pin DDR value, Pin output latch initial value, Pin assert stateThese pin names are always defined, and are used by the initialisation function */#define PIN_A0 A,0,1,1,1#define PIN_A1 A,1,1,0,1#define PIN_A2 A,2,1,0,1#define PIN_A3 A,3,1,1,1#define PIN_A4 A,4,0,1,0#define PIN_A5 A,5,0,1,0#define PIN_A6 A,6,0,1,0#define PIN_A7 A,7,0,1,0/* Assign useful names to the pins. These names describe the pin's functionand are used within the project along with the pin manipulation macros */#define PIN_RED_LED PIN_A0#define PIN_BLUE_LED PIN_A1#define PIN_MOTOR1 PIN_A2#define PIN_MOTOR2 PIN_A3#define PIN_BUTTON_UP PIN_A4#define PIN_BUTTON_DOWN PIN_A5#define PIN_BUTTON_LEFT PIN_A6#define PIN_BUTTON_RIGHT PIN_A7/* Extract the bit number of the pin */#define IO_BIT(x) IO_BIT_(x)#define IO_BIT_(p,b,d,l,s)(b)/* Extract the PORTx register name from the pin */#define IO_PORT(x) IO_PORT_(x)#define IO_PORT_(p,b,d,l,s) PORT ## p/* Extract the PINx register name from the pin */#define IO_PIN(x) IO_PIN_(x)#define IO_PIN_(p,b,d,l,s) (PIN ## p)/* Extract the DDRx register name from the pin */#define IO_DDR(x) IO_DDR_(x)#define IO_DDR_(p,b,d,l,s) (DDR ## p)/* Create a bitmask from the pin */#define IO_PIN_MASK(x) IO_PIN_MASK_(x)#define IO_PIN_MASK_(p,b,d,l,s) (1 << (b))/* Create a bitmask from the default DDR setting */#define IO_DDR_MASK(x) IO_DDR_MASK_(x)#define IO_DDR_MASK_(p,b,d,l,s) (d << (b))/* Create a bitmask from the default PORT setting */#define IO_LVL_MASK(x) IO_LVL_MASK_(x)#define IO_LVL_MASK_(p,b,d,l,s) (l << (b))/* Macros to extract the DDR states from all pins in a port and build a bitmask */#define IO_PORT_DDR_MASK(p) (IO_DDR_MASK(PIN_##p##0) | \IO_DDR_MASK(PIN_##p##1) | \IO_DDR_MASK(PIN_##p##2) | \IO_DDR_MASK(PIN_##p##3) | \IO_DDR_MASK(PIN_##p##4) | \IO_DDR_MASK(PIN_##p##5) | \IO_DDR_MASK(PIN_##p##6) | \IO_DDR_MASK(PIN_##p##7) )/* Macros to extract the inital logic states from all the pins in a port and build a bitmask */#define IO_PORT_LVL_MASK(p) (IO_LVL_MASK(PIN_##p##0) | \IO_LVL_MASK(PIN_##p##1) | \IO_LVL_MASK(PIN_##p##2) | \IO_LVL_MASK(PIN_##p##3) | \IO_LVL_MASK(PIN_##p##4) | \IO_LVL_MASK(PIN_##p##5) | \IO_LVL_MASK(PIN_##p##6) | \IO_LVL_MASK(PIN_##p##7) )/* Set a logic 1 level on a pin */#define SET_PORT_BIT_(p,b,d,l,s) ((PORT ## p) |= _BV(b))#define SetPin(x) SET_PORT_BIT_(x)/* Set a logic 0 level on a pin */#define CLR_PORT_BIT_(p,b,d,l,s) ((PORT ## p) &= ~_BV(b))#define ClearPin(x) CLR_PORT_BIT_(x)/* Toggle the pins output level */#define TGL_PORT_BIT_(p,b,d,l,s) ((PORT ## p) ^= _BV(b))#define TogglePin(x) TGL_PORT_BIT_(x)/* Get the current output level from a pin */#define GET_PORT_BIT_(p,b,d,l,s) (((PORT ## p) & _BV(b)) != 0)#define GetPort(x) GET_PORT_BIT_(x)/* Get the pins DDR state */#define GET_PIN_DDR_(p,b,d,l,s) (((DDR ## p) & _BV(b)) != 0)#define GetPinDdr(x) GET_PIN_DDR_(x)/* Read the pin state */#define GET_PIN_BIT_(p,b,d,l,s) (((PIN ## p) & _BV(b)) != 0)#define GetPin(x) GET_PIN_BIT_(x)/* Read the pin state and invert it */#define GET_PIN_BIT_NEG_(p,b,d,l,s) (((PIN ## p) & _BV(b)) == 0)#define GetPinNeg(x) GET_PIN_BIT_NEG_(x)/* Assert the output level on a pin, this takes into account the polarity bit */#define ASSERT_PIN_(p,b,d,l,s) if(s) { SET_PORT_BIT_(p,b,d,l,s); } else { CLR_PORT_BIT_(p,b,d,l,s); }#define AssertPin(x) ASSERT_PIN_(x)/* Deassert the output level on a pin, this takes into account the polarity bit */#define DEASSERT_PIN_(p,b,d,l,s) if(s) { CLR_PORT_BIT_(p,b,d,l,s); } else { SET_PORT_BIT_(p,b,d,l,s); }#define DeassertPin(x) DEASSERT_PIN_(x)/* Get the current assert state of a pin. This takes into account the polarity bit */#define PIN_ASSERTED_(p,b,d,l,s) (((PIN ## p) & _BV(b)) == (s<<b))#define PinAsserted(x) PIN_ASSERTED_(x)/* Tristate a pin (switch pin to an input) */#define TRIS_PIN_(p,b,d,l,s) ((DDR ## p) &= ~_BV(b))#define TristatePin(x) TRIS_PIN_(x)/* Drive the output of a bit (switch pin to an output) */#define DRIVE_PIN_(p,b,d,l,s) ((DDR ## p) |= _BV(b))#define DrivePin(x) DRIVE_PIN_(x)/* Initialisation function uses values extracted from the pin definitions */void InitIO ( void ){PORTA = IO_PORT_LVL_MASK(A);PORTB = IO_PORT_LVL_MASK(B);PORTC = IO_PORT_LVL_MASK(C);PORTD = IO_PORT_LVL_MASK(D);PORTE = IO_PORT_LVL_MASK(E);PORTF = IO_PORT_LVL_MASK(F);PORTG = IO_PORT_LVL_MASK(G);/* Set data direction registers */DDRA = IO_PORT_DDR_MASK(A);DDRB = IO_PORT_DDR_MASK(B);DDRC = IO_PORT_DDR_MASK(C);DDRD = IO_PORT_DDR_MASK(D);DDRE = IO_PORT_DDR_MASK(E);DDRF = IO_PORT_DDR_MASK(F);DDRG = IO_PORT_DDR_MASK(G);}/* Examples: */if( PinAsserted(PIN_BUTTON_UP ) /* Get the assert status of a digital input */{AssertPin( PIN_LED_BLUE ); /* Assert a digital output */}DrivePin( PIN_MOTOR1 ); /* Switch a pins DDR state to an output */TristatePin( PIN_BUTTON_LEFT ); /* Switch a pins DDR state to an input */if( GetPin( PIN_BUTTON_RIGHT ) ) /* Read the logic state of an input pin (not using assert status) */{SetPin( PIN_MOTOR1 ); /* set the logic state of an output pin */}else{ClearPin( PIN_MOTOR1 );}

author: zapta
author_rank: Super Contributor
timestamp: June 09, 2015, 02:05:49 pm
content: Quote from: FrankBuss on June 09, 2015, 01:30:16 pmThat's neat. Can you pass those objects as function parameters or do you need to define a base class?

author: ralphd
author_rank: Frequent Contributor
timestamp: June 09, 2015, 04:04:15 pm
content: Quote from: bktemp on June 09, 2015, 01:28:41 pmI like the readability of the port bit union technique, and would probably go with it if I could figure a way to avoid errors like this:Code:#define LED0 SBIT( PORTB, 1 )#define LED0_DDR SBIT( DDRD, 1 )[/code/

author: FrankBuss
author_rank: Unknown
timestamp: June 10, 2015, 06:32:04 am
content: Quote from: zapta on June 09, 2015, 02:05:49 pmYes, you can do anything with it that you can do with other C++ value objects, like passing it around as function or method parameters, or collecting it in STL vectors. Internally the class has only one gpio_t member (seethis pagefor the details), so the default C++ copy constructor works. There is no common base class, but you can use the DigitalInOut class, if you need pins which needs to change the direction at run-time.I just tested it with the mbed web IDE, creating the blinky example with a mouse click, then editing main.cpp:The system is open source, you can download the Python based build-system and library locally, too, if you don't like online IDEs.

author: FrankBuss
author_rank: Unknown
timestamp: June 10, 2015, 06:33:13 am
content: Quote from: FrankBuss on June 10, 2015, 06:32:04 amYes, you can do anything with it that you can do with other C++ value objects, like passing it around as function or method parameters, or collecting it in STL vectors. Internally the class has only one gpio_t member (seethis pagefor the details), so the default C++ copy constructor works. There is no common base class, but you can use the DigitalInOut class, if you need pins which needs to change the direction at run-time.I just tested it with the mbed web IDE, creating the blinky example with a mouse click, then editing main.cpp (of course, in this case it would be better to use a reference as the function parameter, to avoid invoking the copy-constructor) :The system is open source, you can download the Python based build-system and library locally, too, if you don't like online IDEs.

author: obiwanjacobi
author_rank: Super Contributor
timestamp: June 10, 2015, 06:57:58 am
content: I truly don't understand why people use up precious RAM for stuff that doesn't change....I have a Arduino Template Library (see signature) that I am now moving towards plain AVR level.My take on GPIO abstraction is for Arduino:Code:template<const byte BoardPinNumber>class DigitalOutputPin{public:/*The ctor sets the pinMode.*/DigitalOutputPin(){pinMode(BoardPinNumber, OUTPUT);}/*The ctor sets the pinMode and initialValue.*/DigitalOutputPin(bool initialValue){pinMode(BoardPinNumber, OUTPUT);Write(initialValue);}/*Writes the value to the BoardPinNumder.*/inline void Write(bool value) const{digitalWrite(BoardPinNumber, value);}/*Returns the BoardPinNumber template parameter.*/inline byte getPinNumber() const{return BoardPinNumber;}};And for plain AVRCode:template<const Ports PortId, const Pins PinId>class DigitalOutputPin{public:/*The ctor sets the Pin on the Port to Output.*/DigitalOutputPin(){Port<PortId>::SetDirection(PinId, Output);//Write(false);}/*The ctor sets the pinMode and initialValue.*/DigitalOutputPin(bool initialValue){Port<PortId>::SetDirection(PinId, Output);Write(initialValue);}/*Writes the value to the BoardPinNumder.*/inline void Write(bool value) const{Port<PortId>::Write(PinId, value);}/*Returns the PortId template parameter.*/inline uint8_t getPortNumber() const{return PortId;}/*Returns the PinId template parameter.*/inline uint8_t getPinNumber() const{return PinId;}};Which uses the Port classCode:enum Ports //: uint8_t{#ifdef PORTAPortA = 0,#endif //PORTA#ifdef PORTBPortB = 1,#endif //PORTB#ifdef PORTCPortC = 2,#endif //PORTC#ifdef PORTDPortD = 3,#endif //PORTC#ifdef PORTEPortE = 4,#endif //PORTE#ifdef PORTFPortF = 5,#endif //PORTF};enum PinIO //: uint8_t{Input,Output};enum Pins //: uint8_t{Pin0,Pin1,Pin2,Pin3,Pin4,Pin5,Pin6,Pin7};template<const Ports PortId>class Port{private:Port(){}public:// Enables the internal pull-up for inputs.// Returns false when pin is not an input.inline static bool EnablePullup(Pins pin, bool enable = true){uint8_t mask = PinToMask(pin);if ((PortRegDir() & mask) == Input){PortRegOut() |= mask;return true;}return false;}inline static void SetDirection(Pins pin, PinIO io){uint8_t mask = PinToMask(pin);if (io) PortRegDir() |= mask;else PortRegDir() &= ~mask;}inline static void SetDirection(PinIO io7, PinIO io6, PinIO io5, PinIO io4, PinIO io3, PinIO io2, PinIO io1, PinIO io0){PortRegDir() = io7 << 7 | io6 << 6 | io5 << 5 | io4 << 4 | io3 << 3 | io2 << 2 | io1 << 1 | io0;}inline static void SetDirection(uint8_t allPinsIO){PortRegDir() = allPinsIO;}inline static void Write(Pins pin, bool value){uint8_t mask = PinToMask(pin);if (value) PortRegOut() |= mask;else PortRegOut() &= ~mask;}inline static bool Read(Pins pin){return (PortRegIn() & PinToMask(pin)) > 0;}inline Ports getPort() const{return PortId;}private:inline static volatile uint8_t& PortRegIn(){return _SFR_IO8(((uint8_t)PortId * 3));}inline static volatile uint8_t& PortRegDir(){return _SFR_IO8((((uint8_t)PortId * 3) + 0x01));}inline static volatile uint8_t& PortRegOut(){return _SFR_IO8((((uint8_t)PortId * 3) + 0x02));}inline static uint8_t PinToMask(Pins pin){return 1 << (uint8_t)pin;}};You would use this as follows:Code://ArduinoDigitalOutputPin<13> Led1;// AVRDigitalOutputPin<PortB, Pin0> Led1;The real strength of this abstraction is that all additional code that is built on top of this, for instance LCD support, does not care if it is an Arduino or AVR implementation.My Arduino/Avr Template Library focusses on effecient code (not using up any RAM if you don't have to), extensibilty and reuse. This library was born out of the frustration of the typical Arduino Lib that just did not fit my needs/wishes (use of delay, not reusable/extensible etc).Hope it helps,Marc

author: westfw
author_rank: Super Contributor
timestamp: June 10, 2015, 08:31:37 am
content: I was going to mention that I've been impressed by some of the template-based C++ abstractions I've seen.But I worry a bit that it's one of those thing that would affect performance without anyone noticing. (an Arduino pin number gets passed around as a uint8, and it's always "slow" to access. A c++ pin object may be wonderfully optimized for "mypin = 1;", but what happens when you do something like onewire_write(mypin, data)? Now you're at least passing an object (16bits instead of, and the code in onewire_write() bloats up and slows down because it's no longer got a constant to optimize...There have been "many" implementations of "fastdigitalWrite()" for arduino that would generate optimal code for the common case of constant arguments, and one of the reasons they've never been accepted is that it might be a bad thing if the speed of digitalWrite() varies by a factor of 20-50 depending on the arguments...

author: zapta
author_rank: Super Contributor
timestamp: June 10, 2015, 03:42:18 pm
content: Quote from: FrankBuss on June 10, 2015, 06:32:04 amThis is not the fast one that uses template, right? That is, there is an extra level of indirection rather that having the port as a compile time constant.I implemented once similar classes and toyed with the idea of having a template based implementation for faster get/set.https://github.com/zapta/arm/blob/master/pro-mini/lpcxpresso/arm_pro_mini_lib/inc/io_pins.hI always pass them by reference but passing by value as you say may have faster get/set because it will eliminate one level of indirection by the reference.

author: ehughes
author_rank: Frequent Contributor
timestamp: June 10, 2015, 06:32:23 pm
content: Unless you are making a ton of money from writing Arduino wrappers, just say no!Just use C macros for the port and pins to directly hit the registers. Any abstraction beyond that is a waste of time and resources.

author: ralphd
author_rank: Frequent Contributor
timestamp: June 14, 2015, 01:41:57 am
content: Quote from: westfw on June 10, 2015, 08:31:37 amI don't trust code until I review the source and confirm it works. I find figuring out template metaprogramming a lot more complicated than someone's digitalWrite macro.QuoteLook at Wiring (wiring.org.co). Their digitalWrite implementation compiles to a single sbi/cbi for a compile-time HIGH/LOW, or 5 cycles (4 if gcc was smarter) when the pin state is not known at runtime.http://nerdralph.blogspot.ca/2014/04/a-better-digitalwrite-for-arduino.html

author: mikerj
author_rank: Super Contributor
timestamp: June 14, 2015, 09:20:47 am
content: Quote from: ralphd on June 14, 2015, 01:41:57 amThat's only the case if the pin number is a constant though, which may often be the case but not always.

author: zapta
author_rank: Super Contributor
timestamp: June 15, 2015, 04:16:28 am
content: Quote from: mikerj on June 14, 2015, 09:20:47 am... and super fast pin set/reset is not always important. (The sin of premature optimization)