please dont rip this site

Using macros to simplify IO port operations in C

By Starlino

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]   
P1 —-[SWITCH] — [GND]

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

Now here is the PBASIC code:

pinSwitch  PIN 1
pinLed     PIN 2

INPUT  pinSwitch


 IF pinSwitch = 0 THEN
  HIGH pinLed 
  LOW pinLed

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.


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

  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 :

#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)


#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

 _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

{Ed; Here is a version adapted for use with HiTech C:
#define _TRIS(pin)            pin(_TRIS_F)
#define _TRIS_F(alpha,bit)    (TRIS ## alpha ## bit)
#define _PORT(pin)            pin(_PORT_F)
#define _PORT_F(alpha,bit)    (R ## alpha ## bit)
#define _LAT(pin)            pin(_LAT_F)
#define _LAT_F(alpha,bit)    (LAT ## alpha ## bit)
#define _WPU(pin)            pin(_WPU_F)
#define _WPU_F(alpha,bit)    (WPU ## alpha ## bit)

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:



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

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

//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)

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 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

 if(i % 2){
  (*tris_ptr[i]) |= 1<<tris_bit[i]; //set bit, make odd pins INPUT
  (*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 useful. If you have any comments or suggestions do not hesitate to leave a comment below ! Happy coding !


This code copyright Starlino Electronics 2011. Used by permission with link to original content here: +

file: /Techref/language/ccpp/iomacros-starlino.htm, 12KB, , updated: 2011/7/10 10:46, local time: 2024/6/21 17:09, owner: JMN-EFP-786,

 ©2024 These pages are served without commercial sponsorship. (No popup ads, etc...).Bandwidth abuse increases hosting cost forcing sponsorship or shutdown. This server aggressively defends against automated copying for any reason including offline viewing, duplication, etc... Please respect this requirement and DO NOT RIP THIS SITE. Questions?
Please DO link to this page! Digg it! / MAKE!

<A HREF=""> Using macros to simplify IO port operations in C</A>

After you find an appropriate page, you are invited to your to this massmind site! (posts will be visible only to you before review) Just type a nice message (short messages are blocked as spam) in the box and press the Post button. (HTML welcomed, but not the <A tag: Instead, use the link box to link to another page. A tutorial is available Members can login to post directly, become page editors, and be credited for their posts.

Link? Put it here: 
if you want a response, please enter your email address: 
Attn spammers: All posts are reviewed before being made visible to anyone other than the poster.
Did you find what you needed?

  PICList 2024 contributors:
o List host: MIT, Site host, Top posters @none found
- Page Editors: James Newton, David Cary, and YOU!
* Roman Black of Black Robotics donates from sales of Linistep stepper controller kits.
* Ashley Roll of Digital Nemesis donates from sales of RCL-1 RS232 to TTL converters.
* Monthly Subscribers: Gregg Rew. on-going support is MOST appreciated!
* Contributors: Richard Seriani, Sr.

Welcome to!