Use macro definitions to simplify work with I/O ports in C (MICROCHIP PIC/ATMEL AVR)

Wouldn't it be nice that instead of the following PIC  C  ( or similar AVR C ) code:

TRISAbits.TRISA2 = 1;  //make switch pin an input
WPUAbits.WPUA2 = 1;    //turn on the pull-up on switch pin
TRISBbits.TRISB5 = 0;  //make led pin an output

 

You could just right something like this, that allows you to change later on the PIN assigned for a specific function:

 

#define pinSwitch(f)       f(A,2)        //Declare Switch pin RA2
#define pinLed(f)          f(B,5)        //Declar Led pin RB5
_TRIS(pinSwitch) = 1; //make pin an input
_WPU(pinSwitch) = 1;  // turn on internal pull-up on switch pin
_TRIS(pinLed) = 0;    // make led pin an output

 

If you're interested how you can simplify your PIC/AVR C code then read on ….

—————–

I started working with MCUs using Basic Stamps 2. My first programs where written in PBASIC. I enjoyed the simplicity of this language but soon moved on to C. One particular thing that I was missing in the C enviroment was the ability to define a pin at the begining of the program and then perform various operations on it. (Make it an input or output, toggle the pin, read its level, output a high or low signal). PIC Microcontrollers make use of several registers to perform these operations, and if you change your pin you need to update all your lines of code that perform these operation.

To illustrate let's take a simple example. Let's say you have a button and a led, you'd like to create a simple application that will turn on the led when a momentary switch is pressed. The hardware setup will be very simple: the led is connected to an output pin of MCU (with a resitor in series to avoid burning the led); the switch is connected to another pin with an optional external pull-up resistor (some PIC and AVR can use an internal pull-up). Please note when the switch is pressed it will output 0 (LOW) and when it is not pressed it will output 1 (HIGH) due to the pull-up resistor.

 [VDD 5V]  
   |
  [RESISTOR 10K]
   |
P1 —-[SWITCH] — [GND]

P2 —-[RESISTOR ~ 200Ohm] —[LED]—[GND]

Now here is the PBASIC code:

pinSwitch  PIN 1
pinLed     PIN 2

INPUT  pinSwitch
OUTPUT pinLed

main:

 IF pinSwitch = 0 THEN
  HIGH pinLed
 ELSE
  LOW pinLed
 ENDIF

GOTO main

If you ever need to change the pins, let's say you want to swap the pins for led and switch all you need to change is the first two lines of code:

pinSwitch  PIN 2
pinLed     PIN 1

All the remaining PBASIC code remains the same.

Now let's look at the C18/C30 code for the PIC microcontroller. Hardware setup is similar except we're going to use the internal pull-up.

RA2—-[SWITCH] — [GND]

RB5 —-[RESISTOR ~ 200Ohm] —[LED]—[GND]

To accomplish same thing we'd have to write something like this in C:

TRISAbits.TRISA2 = 1; //make switch pin an input
WPUAbits.WPUA2 = 1; //turn on the pull-up
TRISBbits.TRISB5 = 0; //make led pin an output

while(1){
  PORTBbits.RB5 =  ! PORTAbits.RA2;
}

Now if you would need to swap the pins  you'd have to update pretty much every line of  C code. This is very inconvenient !!! This is why I came up with following macros for C18/C30 :

//———————————————————————
// MACROS FOR EASY PIN HANDLING IN PIC C18/C30
//———————————————————————
#define _TRIS(pin)            pin(_TRIS_F)
#define _TRIS_F(alpha,bit)    (TRIS ## alpha ## bits.TRIS ## alpha ## bit)
#define _PORT(pin)            pin(_PORT_F)
#define _PORT_F(alpha,bit)    (PORT ## alpha ## bits.R ## alpha ## bit)
#define _LAT(pin)            pin(_LAT_F)
#define _LAT_F(alpha,bit)    (LAT ## alpha ## bits.LAT ## alpha ## bit)
#define _WPU(pin)            pin(_WPU_F)
#define _WPU_F(alpha,bit)    (WPU ## alpha ## bits.WPU ## alpha ## bit)

//———————————————————————
// USAGE
//———————————————————————

#define pinSwitch(f)       f(A,2)        //Switch, INPUT , pin RA2
#define pinLed(f)          f(B,5)        //Led, OUTPUT , pin RB5

_TRIS(pinSwitch) = 1; //make pin an input
_WPU(pinSwitch) = 1; // turn on internal pull-up
_TRIS(pinLed) = 0; // make pin an output

while(1){ 
 _PORT(pinLed) = ! _PORT(pinSwitch)
}

This code is much better. If you need to swap the pins all you need to do is update two lines of code:

#define pinSwitch(f)       f(B,5)        //Switch, INPUT, pin RB5
#define pinLed(f)          f(A,2)        //Led, OUTPUT,  pin RA2

The rest of the code remains the same. The only confusing thing in this code might be the macro definitions. I have arrived at them through many trials and errors. Each pin is defined as a  macro function with a single parameter f   – which is another macro  function which we'd like to apply to this pin. Possible values for f are PORT_F , TRIS_F, LAT_F , WPU_F.

Here is an example how compiler interprets these statements: 

_PORT(pinLed)  =>    pinLed (PORT_F)   =>  PORT_F( B, 5)  =>  PORTBbits.RB5

 

Now moving on to AVR controllers. I am using gcc-avr compiler (Windows version). I came up with following macros that seem to work in this compiler:


// MACROS FOR EASY PIN HANDLING FOR ATMEL GCC-AVR
//these macros are used indirectly by other macros , mainly for string concatination
#define _SET(type,name,bit)            type ## name  |= _BV(bit)   
#define _CLEAR(type,name,bit)        type ## name  &= ~ _BV(bit)       
#define _TOGGLE(type,name,bit)        type ## name  ^= _BV(bit)   
#define _GET(type,name,bit)            ((type ## name >> bit) &  1)
#define _PUT(type,name,bit,value)    type ## name = ( type ## name & ( ~ _BV(bit)) ) | ( ( 1 & (unsigned char)value ) << bit )

//these macros are used by end user
#define OUTPUT(pin)            _SET(DDR,pin)   
#define INPUT(pin)            _CLEAR(DDR,pin)   
#define HIGH(pin)            _SET(PORT,pin)
#define LOW(pin)            _CLEAR(PORT,pin)   
#define TOGGLE(pin)            _TOGGLE(PORT,pin)   
#define READ(pin)            _GET(PIN,pin)

/*
    BASIC STAMPS STYLE COMMANDS FOR ATMEL GCC-AVR

    Usage Example:
    ———————————————–
    #define pinLed B,5  //define pins like this

    OUTPUT(pinLED);     //compiles as DDRB |= (1<<5);
    HIGH(pinLed);         //compiles as PORTB |= (1<<5);
    ———————————————–
*/

 

As a bonus, below are some macros for calculating MIN/MAX or translating a value to a different range, something similar to the map()  function that is already built-in into Arduino.

#define MIN(A,B)  (((A)<(B)) ? (A) : (B) )
#define MAX(A,B)  (((A)>(B)) ? (A) : (B) )
#define PUT_IN_RANGE(V,VMIN,VMAX) MAX(VMIN,MIN(VMAX,V))
#define MAP_TO_RANGE(V,VMIN0,VMAX0,VMIN1,VMAX1) ( (VMIN1) +  ( (V) – (VMIN0) ) * ( (VMAX1) – (VMIN1) ) / ( (VMAX0) – (VMIN0) ) )

Now , what do you do when you need your pin to actually be stored as a variable (it updates at run-time, or you might want to pass it as a parameter to a function). Let's say you would like to have 4 pins and perform some actions on those pins in bulk as well as define those pins are defined at run time ? The answer is pointers. Here I will illustrate with an example for C30 that you can adapt to your needs:

volatile unsigned int * tris_ptr[4];
unsigned char* pin_bit[4];

//define our pins as  RA2 , RA3 , RB5 , RB7 at run time

tris_ptr[0] = &TRISA;  pin_bit[0] = 2;  //RA2
tris_ptr[1] = &TRISA;  pin_bit[1] = 3;  //RA3

tris_ptr[3] = &TRISB;  pin_bit[2] = 5;  //RB5
tris_ptr[4] = &TRISB;  pin_bit[3] = 7;  //RB7

int i;

//perform bulk operations on pins

for(i=0;i<4;i++)
  if(i % 2){
   (*
tris_ptr[i]) |= 1<<tris_bit[i];    //set bit, make odd pins INPUT 
  else
   (*
tris_ptr[i]) ^=  !(1U<<tris_bit[i]); //clear bit, make even pins OUTPUT

In the same way you could define  port_ptr[] , lat_ptr[] , wpu_ptr[] arrays and perform operations on those ports. Even more smarter would be to group all these in a structure, and then create a single array of these structures. Using macros you could simplify assignment to these arrays in one simple statement. (I leave this as a homework for you) !

I hope you'll find this article usefull. If you have any comments or suggestions do not hesitate to leave a comment below !

Happy coding !

//starlino//

5 thoughts on “Use macro definitions to simplify work with I/O ports in C (MICROCHIP PIC/ATMEL AVR)

  1. Pingback: Some (Free!) Feedback for TI MSP430 LaunchPad’s Development and Marketing Team « Starlino Electronics

  2. Filip Reply

    Very clever solution! You may sometimes need to access the bits from a completely different command, e. g. to enable the pin change interrupts on two pins (on attiny24), we may add these macros:

    #define PIN1 A,4 // starlino’s way
    #define PIN2 A,6

    #define __BVfrom(name,bit) _BV(bit) // new macros!
    #define _BVfrom(pin) __BVfrom(pin)
    PCMSK0 &= ~(_BVfrom(PIN1) | _BVfrom(PIN2)); // setting PCMSK register to two pins

    While normally, we would have to stick to

    PCMSK0 &= ~(_BV(4) | _BV(6));

    … which does not use the elegant general pin definitions.

  3. Bikotoru Reply

    Those are some nice macros but for AVR i prefer use this of my oun:

    #define TOGGLE    2
    #define CLEAR    0
    #define SET        1
    #define INPUT    0
    #define OUPUT    1
    #define LOW        0
    #define HIGH    1

    /*You can use this macro for undefined pins like this:
    *    __PORT(A, 0, HIGH); resulting in this PORTA |= (1 << 0);
    *    __PORT(A, 0, LOW); resulting in this PORTA &= ~(1 << 0);
    *    __PORT(A, 0, TOGGLE); resulting in this PORTA ^= (1 << 0);
    */
    #define    __PORT(name, bit, value)\
    {\
        switch(value)\
        {\
            case LOW:\
                PORT##name &= ~_BV(bit);\
                break;\
            case HIGH:\
                PORT##name |= _BV(bit);\
                break;\
            case TOGGLE:\
                PORT##name ^= _BV(bit);\
        }\
    }

    /*You can use this macro for undefined pins like this: __PORT(A, 0, HIGH); resulting in this PORTA |= (1 << 0);
    *    __DDR(A, 0, OUPUT); resulting in this DDRA |= (1 << 0);
    *    __PORT(A, 0, INPUT); resulting in this DDRA &= ~(1 << 0);
    *    __PORT(A, 0, TOGGLE); resulting in this DDRA ^= (1 << 0);
    */
    #define    __DDR(name, bit, value)\
    {\
        switch(value)\
        {\
            case INPUT:\
                DDR##name &= ~_BV(bit);\
                break;\
            case OUPUT:\
                DDR##name |= _BV(bit);\
                break;\
            case TOGGLE:\
                DDR##name ^= _BV(bit);\
        }\
    }

    //This macro returns the value of the pin example: if(READ(K, 5)){…}
    #define __PIN(name, bit)                (PIN##name & _BV(bit))

    /*You can use this macros for defined pins like this:
    * #define LED A,0
    * _PORT(LED, HIGH); resulting in this PORTA |= (1 << 0);
    * _PORT(LED, LOW); resulting in this PORTA &= ~(1 << 0);
    * _PORT(LED, TOGGLE); resulting in this PORTA ^= (1 << 0);
    */
    #define _PORT(pin, value) __PORT(pin, value)

    /*Same as _PORT

    #define _DDR(pin, value) __DDR(pin, value)

    //This one like this: if(READ(LED)){…}

    #define READ(pin) __PIN(pin)

    /*This macros are for general registers or variables example:
    * int i;
    * WRITE_BIT(i, 0, 1);
    * WRITE_BIT(TCCR0A, WGM00, 1);
    */
    #define WRITE_BIT(reg, bit, value)\
    {\
        switch(value)\
        {\
            case CLEAR:\
                reg &= ~_BV(bit);\
                break;\
            case SET:\
                reg |= _BV(bit);\
                break;\
            case TOGGLE:\
                reg ^= _BV(bit);\
        }\
    }

    //Same as _PIN but for general registers and variables example: if(READ_BIT(i, 0)){…}
    #define READ_BIT(reg, bit)            (reg & _BV(bit))

  4. Bikotoru Reply

    But lately for definitions I rater use this:

    #define LED(value)      __PORT(A, 0, value)

    And I use it like this:

    LED(HIGH);

    LED(LOW);

    LED(TOGGLE);

     

Leave a Reply

Your email address will not be published. Required fields are marked *