Skip to main content

Simple GPIO API

Practically with every project, I buy a new device - with which I need to familiarize myself. Most of the time those are devices using already very well known buses like I2C, OneWire or SPI. But from time to time it's a parallel device with a very specific interface. There's a need for the driver then. There are two ways: the easy one - get the driver from the internet and use it - now don't get me wrong that's a perfectly good way to solve the problem - your device is up and running pretty fast and you do not have to worry about it, since most of the problems have been already taken care for you.

The hard way is to write the driver yourself. Now a question arise why to do it in the first place ? The are a couple of reasons for that. First of all - you want to learn something - most obviously you will, since you will have to learn about the device thoroughly. Second - you want to make it smaller/faster - that's more pragmatic reason, since the downloaded driver may not represent the "state of the art" or simply maybe bloated with features which are not desired or simply not needed. Another good reason is that the available drivers are simply not compatible with our already existing program and making both fit one to each other may take more time and effort than simply creating a new driver from scratch.

OK, but what does this all have in common with GPIO lines ? Devices use GPIOs, in case we need to "bit bang" some sort of communication standards will operate on GPIOs

Why not use the PORT{B,C,D} directly though ? Because it will be pretty hard to change the code when you decide to move your device to different pins. In that case most people use preprocessor like that:

#define PORT PORTD
#define DDR DDRD
#define INP PIND
#define PIN 6

... and in the code

DDR |= _BV(PIN);
PORT &= ~_BV(PIN);
PORT |= _BV(PIN);

... and so on. For a simple case that's pretty good approach, but if we have a lot of lines that may become a problem since the pin declaration becomes very complicated and extensive. It can be done a lot slicker and elegant. In order to have complete control over a port we need 4 values: the port's data register (PORTX), the port's direction register (DDRX), the port's input register (PINX) and the pin number. OK, but going through the datasheet a nice dependency occurs. The PORT addresses are consecutive:

PORTB = 0x05
DDRB = 0x04
PINB = 0x03

So, having only the PORTX address, we are able to determine every other needed memory location. Let's make some macros for that:

/**
 * @brief get pointer to DDR register from PORT register
 *
 * @param __val pointer to PORT register 
 *
 * @return pointer to DDR register
 */
#define GET_DDRX_FROM_PORTX(__portx) \
(__portx - 1)


/**
 * @brief get pointer to PIN register from PORT register
 *
 * @param __val pointer to PORT register 
 *
 * @return pointer to PIN register
 */
#define GET_PINX_FROM_PORTX(__portx) \
(__portx - 2)

It's pretty simple to use them:


volatile uint8_t ddr = GET_DDRX_FROM_PORTX(&PORTB);


Now, we need only the PORTX address and the pin number. We can make it even more flexible and universal. Let's desribe a single gpio:


typedef struct _gpio_pin {
volatile uint8_t *port;
uint8_t pin;
} gpio_pin;


Now, we can declare a single pin like that:

gpio_pin x;

x.port = &PORTB;
x.pin = 3;

At the moment it's not very convenient to use this type, since just to change the value we need to:


*x.port &= ~_BV(x.pin);


But we can create some macros for that as well:


#define GPIO_CONFIGURE_AS_OUTPUT(__gpio) \
*(GET_DDRX_FROM_PORTX((__gpio)->port)) |= _BV((__gpio)->pin)

#define GPIO_CONFIGURE_AS_INPUT(__gpio) \
*(GET_DDRX_FROM_PORTX((__gpio)->port)) &= ~_BV((__gpio)->pin)


#define GPIO_GET(__gpio) \
(*(GET_PINX_FROM_PORTX((__gpio)->port)) & _BV((__gpio)->pin))


#define GPIO_SET_LOW(__gpio) \
(*(__gpio)->port) &= ~_BV((__gpio)->pin)


#define GPIO_SET_HIGH(__gpio) \
(*(__gpio)->port) |= _BV((__gpio)->pin)


#define GPIO_TOGGLE(__gpio) \
*(GET_PINX_FROM_PORTX((__gpio)->port)) = _BV((__gpio)->pin)


OK. Now we've everything we need. Let's have a look at a full blinking LED example using this API. The example presents two options. First using the TOGGLE call, second using the LOW/HIGH calls. In order to test the second one, you should comment out the first one.


This all may look unnecessarily  over complicated but it's very useful when trying to write a flexible driver. Just imagine that you wrote a great driver for an LCD display. You used in your device on let's say on a couple of pins of PORTB, then you want to use it again in a completely different device on a completely different set of pins - there's not a problem at all - since thanks to this abstraction you just need to redefine the pin numbers. The driver uses the GPIO API and is completely hardware independent ! That's a great advantage !

I hope that this tip was useful. I'm currently working on the software for one of my standalone devices - Clock with Thermometer - want to polish it as much as possible and implement some cool ideas so, it takes pretty long. Can't wait to publish some details regarding this project


Comments

Popular posts from this blog

RTC without RTC ? How good is my crystal ?

Clock - I think that it's the most popular idea for a project with the MCU . It's not that hard from both software and hardware side to realize but not that trivial as well, not mentioning the fact that once realized it sits somewhere in the house, constantly reminding about itself and brings a kind of satisfaction :). There are a lot of options really to implement, a buzzer alarm, perhaps a thermometer, display and buttons servicing, perhaps maybe even serial port usage for NTP synchronization. But ... The most challenging thing - like in every clock is the time reference source. There are a couple of options: well calibrated frequency source acting as an interrupt source Real Time Clock module with external crystal ( < 20 ppm ) internal clock These days RTC chips are so cheap and widely available that they are really the only reasonable choice, but if we're only planning to play around a bit with the software without paying much attention to accura...

Arduino R-2R ladder Audio DAC

There is a lot of projects out there which use  R-2R ladder and an Arduino to recreate sounds from either SD card or short audio clips programmed directly to MCU's flash memory. Although using SD card is fairly reasonable and provides a lot of flexibility it's not very challenging (from the software point of view) and it requires some additional hardware. Believe it or not we already have all the needed hardware in the Arduino itself. Assumptions In this project I'll play mp3 or any other multimedia files from the PC using Arduino. Bellow are the details: Play PCM 8kHz 8bit Audio with Arduino Audio samples will be transfered via USART from the PC in binary format using SLIP Audio files will be decoded on the PC side and only the RAW data will be send to Arduino Timing Arduino is a powerful machine, powerful enough that it's possible to play audio with even higher sampling rates and bit-resolutions than assumed above, the bottleneck in this...

Simple Serial Port Command Line Interface (CLI)

It's often very useful to have some sort of interface through which a basic management can be done of our application. Whether you're designing a giant Marquee with a LED display or a plotter or you simply need to get some diagnostics from your device occasionally, a simple CLI system can come very handy.  Of course, designing something similar to "bash" or any other unix shell is completely out of scope since those applications are huge and are simply an overkill for our needs. It's pretty simple though to create some basic, yet flexible & easilly extensible CLI system. First thing needed is a command type definition. This will bind a keyword with an actual underlying routine executed for that keyword typed in the CLI . typedef struct _t_cmd { const char *cmd; void (*fh)(void*); } t_cmd; The command type is pretty simple. There's the CLI command/keyword pointer, it holds a pointer to the execution function and that's it. OK, so...