Code Monkey home page Code Monkey logo

stdpins's Introduction

stdpins.h

When working on a microcontroller project in C, we often capture the mapping of a signal to a physical pin with preprocessor macros, like so

// button is connected to PB5
#define BUTTON_PORT  PINB
#define BUTTON_BIT   5

and we use those definitions later in our code

// test if button is pressed
if (BUTTON_PORT & (1 << BUTTON_BIT)) { do_something(); }

If we later decide that the PCB layout is easier if we connect the button to PC3 instead of PB5, we just change the two lines above.

But what if we need to change the logic, so the button input is low when pressed? What if we want to use a pin change interrupt for that signal? The simple #defines won't do it anymore.

That's where the <stdpins.h> header comes in: it allows you to define port name, pin number and polarity in one place, and then refer to that signal definition throughout your code, for basic I/O, and for pin change interrupts.

It can be used with "classic" AVR projects as well as with Arduino projects.

For more details, see requirements

Introduction

In embedded projects with ATmega or ATtiny microcontrollers (and maybe others), assignment of signals to port pins may change multiple times, e.g. when you start working on your PCB layout and learn that it would really be easier if the LED was connected to PC2 instead of PB5 ...

<stdpins.h> allows you to map a logical signal to a port name and bit number, and define polarity (active high or active low), in one place, and then refer to that definition throughout your code.

Say we have an LED connected to PB5, and the light is on if PB5=Low

#define led B,5,ACTIVE_LOW 

Somewhere else in your code, you configure the pin as output

AS_OUTPUT(led);

turn the LED on for a while

ASSERT(led); 
_delay(100); 
NEGATE(led); 

If the schematics change and LED is now connected to PC2, and turns on if PC2=Hi, just change the definition to

#define led C,2,ACTIVE_HIGH

Everything else remains the same.

Defining a signal

The general format is

#define signalname port,bit,polarity

where port is one of A,B,C,etc., bit is in the range 0-7, and polarity is ACTIVE_LOW or ACTIVE_HIGH.

Open-collector outputs

You can also specity a "polarity" of ACTIVE_LOW_OC for an active-low open-collector output.

AVR controllers don't really have pins that can be programmed as open-collector outputs, like the more modern STM controllers. But we can simulate this: to set an output active (low), program the pin as output and low-level, and to set an output inactive (high), program it as an input withput a pull-up)

Configuring a pin

Use these macros to configure a pin as output or input (with or without pull-up)

Macro Direction pull-up
AS_INPUT(pin) in undefined
AS_INPUT_PU(pin) in enabled
AS_INPUT_FLOAT(pin) in disabled
AS_OUTPUT(pin) out n/a

For example, this would configure pin PC2 as input and enable the pull-up resistor:

#define BUTTON C,2,ACTIVE_LOW
AS_INPUT_PU(BUTTON);

Pre-defined pins

Several pins used by AVR peripherals are "pre-defined", depending on the microcontroller model. So, for an ATmega328, the symbol _I2C_SDA is defined as

#define _I2C_SDA	C,4,ACTIVE_HIGH

for an ATtiny2313, it is defined as

#define _I2C_SDA	B,5,ACTIVE_HIGH

They can be used in your code as if you had defined them

// pulse low the I²C clock line
SET_LOW(_I2C_SCL);
_delay(100);
SET_HIGH(_I2C_SCL);

When defining your logical signals, you can refer to the controller-specific pin assignment, and just specify a polarity:

#define led _OC0A(ACTIVE_LOW)
#define button _INT0(ACTIVE_LOW)
AS_OUTPUT(led);
AS_INPUT_PU(button);

See header for more details.

Testing input pins

Use these macros to test an input pin:

  • ÌS_TRUE(pin) returns true if the pin is active, i.e. high for an ACTIVE_HIGH pin or low for an ACTIVE_LOW pin
  • ÌS_FALSE(pin) returns true if the pin is not active, i.e. low for an ACTIVE_HIGH pin or high for an ACTIVE_LOW pin
  • ÌS_HIGH(pin) returns true if the pin is high, irrespective of polarity
  • ÌS_LOW(pin) returns true if the pin is low, irrespective of polarity

For example, if BUTTON is defined as

#define BUTTON C,2,ACTIVE_LOW

and the voltage at pin PC2 is currently 0 Volt, then

  • IS_TRUE(BUTTON) returns true
  • IS_FALSE(BUTTON) returns false
  • IS_HIGH(BUTTON) returns false
  • IS_LOW(BUTTON) returns true

Setting output signals

Use these macros to set the state of an output signal:

  • ASSERT(pin) or SET_TRUE(pin) sets the output to active, i.e. high for an ACTIVE_HIGH pin or low for an ACTIVE_LOW pin
  • NEGATE(pin) or SET_FALSE(pin) sets the output to not active, i.e. low for an ACTIVE_HIGH pin or high for an ACTIVE_LOW pin
  • SET_HIGH(pin) sets the output to high, irrespective of polarity
  • SET_LOW(pin) sets the output to low, irrespective of polarity
  • TOGGLE(pin) flips the output
  • SET_PA(pin,value) sets the output based on expression value, i.e. sets it to active if value!=0 or to not active if value==0

Direct access to signal definition

Sometimes, you will need to convert your signal definition to a "classic" pin name like PC4, or you need direct access to the port register or data direction register. Use these macros to get that access:

Macro Description Example
PORT Name of port output register PORTC
PIN Name of port input register PINC
DDR Name of data direction reg. DDRC
BV Bit mask for pin 0x08
portNAME Letter of port C
portBIT Bit number 3

The example is what you get if the signal has been defined as

#define mypin C,3,ACTIVE_HIGH

Converting stdpins.h signal definitions to Arduino pin numbers

When you need to specify a pin to an Arduino library function, use the ARDUINO_PIN macro:

#define led B,5,ACTIVE_HIGH
int pin = ARDUINO_PIN(led);  // will set pin=13 on an ATmega328
someArduinoFunction(pin);

Currently, this only works for ATmega168/328 based Arduino boards, and for ATmega324/644/1284 using the MightyCore Arduino core with the "Standard" pinout.

Pin change interrrupts

These signal definitions can also be used in conjunction with pin change interrupts (in controllers that support this feature).

Say you have connected a button to input PC2 of your ATmega328:

#define button C,2,ACTIVE_LOW
AS_INPUT_PU(button);

To get pin change interrupts from this button, you need to enable the bank of pin change interrupts for the port, and then enable the individual pin change interrupt

PCI_ENABLE(button)
PCIEx_ENABLE(button)

since button is currently defined as PC2, this translates to

PCICR |= 1 << PCIE1
PCIMSK1 |= 1 << 2

If, later in your project, you decide to re-assign the button to PD7

#define button D,7,ACTIVE_LOW

The PCI_ENABLEand PCIEx_ENABLE macros above are now automatically expanded to

PCICR |= 1 << PCIE2
PCIMSK1 |= 1 << 7

Your interrupt service routine could look like this

PCI_ISR(button) {
    // ... do something
}

There are also matching PCI_DISABLE(pin)and PCIEx_DISABLE(pin) macros.

All of this works well if you use only one pin change interrupt per port. If you want to use more than one pin change interrupt on the same port, the refer to the ATmega datasheet to understand how these interrupts are enabled and disabled in groups.

Performance

As a nice side effect of using the macros in this package, your program will consume less memory, and will be faster than the standard Arduino I/O functions. I tested this on an Arduino Nano clone at 16 MHz. A simple loop that toggles an output pin using the stdpins macros looks like this

    #define LED B,5,ACTIVE_HIGH
    for (;;) {
        NEGATE(LED);
        ASSERT(LED);
    }

If you use the Arduino functions, it would look like this

    #define pinLED 13
    for(;;) {
        digitalWrite( pinLED, LOW );
        digitalWrite( pinLED, HIGH );
    }

A minimal program with this loop used 438 bytes flash memory with stdpins, and 722 bytes with the Arduino functions.

One cycle through the loop takes 0.375µs with stdpins, and 6.7µs with Arduino ... if the pin value is known at compile time. If the pin value is in a variable, so you use SET_PA(LED;var) or digitalWrite(pinLED,var), then the times are 1.6µs vs 7.0µs.

Acknowledgements

Inspired by http://www.starlino.com/port_macro.html

stdpins's People

Contributors

requireiot avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.