EPPL  0.1 alpha
 All Files Typedefs Groups Pages
EPPL concept

Genesis (Pins problems in embedded systems)

Parallel input-output ports in embedded systems are one of the most important ways of communication between a software and the external world.

There are two problems with using the microcontroller ports.

First problem

The first problem is a device specific one, in some devices it could be more and in some less annoying. To fully support a port pin, in a standard AVR8 family microcontrollers,, we have to take care of 3 registers:

  • PORT - for writing port value
  • DDR - for configuring port direction
  • PIN - for reading physical state of the port

There is no guarantee that these registers are put in any order in a register map. Cursory reading of some avr8 documentations may lead us to notice, that most of them are put one after another. But there are some exceptions that we could not ignore. In ATmega128 microcontroller:

  • Address of PORTA is 0x3B
  • Address of DDRA is 0x3A
  • Address of PINA is 0x39
  • Address of PORTB is 0x38
  • Address of DDRB is 0x37
  • Address of PINB is 0x36
  • ...

So far so good, but:

  • Address of PORTF is 0x62
  • Address of DDRF is 0x61
  • Address of PINF is 0x20 !

If there were not such exceptions, we could define the whole port as a structure:

typedef avr8_port_s struct __attribute__((packed)) {
uint8_t pin, ddr, port;
}avr8_port_t;

We could easily access all port registers using structure above.

This kind of access was introduced with the AVR XMEGA family. In old AVR8, we cannot trust it, and we should use specific registers names directly in our code.

Why is the problem annoying?

There is not a problem with using the port registers direct in code:

PORTA = 0;
DDRA = 0;

But in a real program, we rather do not use port pins numbers or port registers in the code directly. We would try to extract hardware pins definitions from functional code.

In ideal world, there would be everything, that is hardware dependent in one file, and everything that is hardware independent in other file.

So let's try to define some simple system:

// Button
#define BTN_DDR DDRC
#define BTN_PORT PORTC
#define BTN_PIN PINC
#define BTN 0
// LCD
#define LCD_DDR DDRB
#define LCD_PORT PORTB
#define LCD_PIN PINB
#define LCD_RS 0
#define LCD_RW 1
#define LCD_ENA 2
#define LCD_D4 4
#define LCD_D5 5
#define LCD_D6 6
#define LCD_D7 7

In the code above we have to provide information about our ports 3 times. The definition like the one above is quite error-prone, and it is just not nice. It would be much better if we could just inform compiller that we are going to use port C to our button. We know that port C uses registers DDRC, PORTC and PINC.

There is also the second problem with our definitions. We have to provide 7 bits in one port for our LCD. It is not always possible. Sometimes we will have just 3 pins available in one, 2 available in other and 2 in other one port available. To define such a possibility in the way as in the code above, we have to use 4 lines to define every single pin. It begins to be really messy.

Simple solution to the first problem

In August of 2005, I presented simple solution for the first described problem. The solution was presented in "Electronics for Everybody" Polish magazine.

These were just 6 simple macros (out of which 3 for usage and 3 internals):

// *** Port
#define PORT(x) XPORT(x)
#define XPORT(x) (PORT##x)
// *** Pin
#define PIN(x) XPIN(x)
#define XPIN(x) (PIN##x)
// *** DDR
#define DDR(x) XDDR(x)
#define XDDR(x) (DDR##x)

It is an extremely simple code, that works just by preprocessor possibilities. Using these macros makes hardware definitions much easier and more elegant. And most important of all: using these macros do not have any disadvantages! It does not slow down (noticeably) the compilation time. And most important of all: it does not generate any more machine code than needed.

Using these macros, we can define our hardware in the following way:

// Button
#define BTN_P C
#define BTN 0
// LCD
#define LCD_P B
#define LCD_RS 0
#define LCD_RW 1
#define LCD_ENA 2
#define LCD_D4 4
#define LCD_D5 5
#define LCD_D6 6
#define LCD_D7 7

And now how we access our ports registers:

int main(void)
{
PORT(BTN_P) |= 1<<BTN;
if(!(PIN(BTN_P) & 1<<BTN))
{
Button pressed...
}
}

So one problem could be solved simply. But this is just a simple and machine dependent problem. We have to process it once again if we change machine type.

Producers are trying to make it simpler for us. In new Cortex-M3 concept we would get libraries from producer, that would help us with ports usage. They are not always optimal, but help a lot.

But there is another, more general problem described in the next section...

Second problem

Second problem is a general problem.

In a typical microcontroller we use some pins not only as a part of General Input Output Ports. Some of pins have special functionality, e.g. USART port, PWM outputs, Interrupt input... That makes pins selection not fully independent. We have to consider the hardware limitations of our microcontroller.

It is rather a problem in middle scale systems. In relatively small systems we do not have many different elements connected to ports. So in this situation pins selection is quite simple.

On the other hand, in relatively big systems, we usually have a microcontroller that is big enough to think about pins in port groups. We can have separated port for buttons, separated for display driver and so on...

But in middle scale systems, we often have a situation that we lack pins for general usage. We have enough pins for all our peripherals, but some of them have special hardware functionality and we have to use them.

As a result, when we need to drive an LCD, we could find out that we have even more free pins that we need, but they are distributed between ports.

And last but not least - what about code recycling? We are often creating some code blocks, some libraries, that we would like to reuse in next design. In one project we could afford to give our LCD library full port. But maybe in another project we would have to split LCD connections.

Wouldn't it be great to have a library that would take care of it? Wouldn't it be great if this library would generate quite optimal code? And in the end of the day... wouldn't it be great, if your newly created code, that uses an I/O ports a lot, could be easily moved to an extremely different machine? Let's say, your newly created LCD support library, written for AVR8, could be moved to ARM7 without extreme recoding?

As for me - It all would be great and these are the reasons for the creation of this library.

If you are still not convinced, read these section to the end...

Visualization of the second problem

Take a look at the first image:

eq_system1_s.jpg
Base system

What we have here are some basic microprocessor system. This is a synthetic (not a real) design. It uses an ATmega8 microcontroller. Let's say it is some temperature logger with an alphanumeric LCD, real time clock, simple keyboard, and SD card.

On the image above we are able to divide functionality simply into ports.

  • PORTB:
    • Hardware SPI is used for SD card
    • Special hardware pins are used for connecting the oscillator
  • PORTC:
    • Hardware I2C pins are used for RTC communication
    • Basically sensor ports:
      • One analog input used for temperature sensor
      • One digital input used for reading the button state
  • PORTD:
    • Whole port is used for LCD driving

It is a simple and logical system. We are able to divide all functionality by using codes analogous to listings that were presented earlier.

Now imagine that the project is going to be upgraded.

  1. First of all, we are going to add some PC communication, using UART port
  2. We are going to add some light sensor - TSL238. It transforms light intensity to the frequency. We would use a Timer1 to measure that frequency. It means we need to use a special input, connected with the Timer1 inside the microcontroller.
  3. We would like to use some Power Save features. To do so, we would sleep the processor when there is nothing to do. We would wake it up when a user presses the button or when there is the RTC event. To do so, we have to use the hardware interrupt inputs.

The image below, shows our, modified design:

eq_system2_s.jpg
System after modification

I would like you to focus on what happens with our LCD connections. We do not even have 4 free pins together on one port. If we had it, we could divide our LCD support logically to data and control ports. But now we have it totally distributed between the ports and there is not any logic in that.

If you write your library bearing in mind that LCD is connected to one port, after project upgrade, you have to totally rebuild it.

I used to have more or less similar problems, so I decided to create this library. It makes it possible to write a program totally independent from pins mapping. If you map your pins simply to one port, generated code would be short and fast. If you map pins in a totally random way, generated code will be a little bit more complicated - but it will still work!

Port initialization

One of the first thing we have to do when our program starts is to initialize input-output ports. Basically, there are two ways to do it (or some modifications and/or combinations of these two methods):

  1. Make initialization pin by pin.
  2. Just write pins configurations directly to right registers.

Before continuing, I will put here again the image of our modified system:

eq_system2_s.jpg
System after modification

Using the conception, described in the section Simple solution to the first problem, we could write our hardware definition file:

/* Button */
#define BTN_P D
#define BTN 3
/* LCD */
#define LCD_RS_P B
#define LCD_RS 0
#define LCD_RW_P B
#define LCD_RW 1
#define LCD_ENA_P D
#define LCD_ENA 6
#define LCD_D4_P D
#define LCD_D4 7
#define LCD_D5_P C
#define LCD_D5 0
#define LCD_D6_P C
#define LCD_D6 1
#define LCD_D7_P C
#define LCD_D7 2
/* RTC */
#define I2C_SDA_P C
#define I2C_SDA 4
#define I2C_SCL_P C
#define I2C_SCL 5
#define RTC_IRQ_P D
#define RTC_IRQ 2
/* Light to frequency sensor */
#define TSL238_OUT_P D
#define TSL238_OUT 5
/* UART */
#define UART_TX_P D
#define UART_TX 1
#define UART_RX_P D
#define UART_RX 0
/* SD/MMC Card */
#define MMC_CS_P B
#define MMC_CS 2
#define MMC_DI_P B
#define MMC_DI 3
#define MMC_DO_P B
#define MMC_DO 4
#define MMC_SCK_P B
#define MMC_SCK 5
/* Thermistor */
#define T_VOUT_P C
#define T_VOUT 3

Notice, that every single pin has two lines of definition there. One is for the port and the other one is for the pin definition.

We would use this code and the image above in initialization examples in following subsections.

Initialization pin by pin

  • Pros:
    1. This method is most "user-friendly" and simplifies code refactoring.
    2. Pins could be initialized inside our libraries which makes refactoring even more simple.
  • Cons:
    1. We always modify the actual pin configuration. We rely on what was written to the registers before. To make some default pins configuration we have to write it to all registers before the reconfiguration starts.
    2. The generated code is not optimal - there would be a lot of read-modify-write instructions.

Below is an example of this method of port initialization:

int main(void)
{
/* Button Pull Up */
PORT(BTN_P) |= 1<<BTN;
/* LCD – all outputs, state 0 */
DDR(LCD_RS_P) |= 1<<LCD_RS;
DDR(LCD_RW_P) |= 1<<LCD_RW;
DDR(LCD_ENA_P) |= 1<<LCD_ENA;
DDR(LCD_D4_P) |= 1<<LCD_D4;
DDR(LCD_D5_P) |= 1<<LCD_D5;
DDR(LCD_D6_P) |= 1<<LCD_D6;
DDR(LCD_D7_P) |= 1<<LCD_D7;
/* RTC – inputs with Pull Up */
PORT(I2C_SDA_P) |= 1<< I2C_SDA;
PORT(I2C_SCK_P) |= 1<< I2C_SCK;
PORT(RTC_IRQ_P) |= 1<< RTC_IRQ;
/* UART, TX – output, RX – input with Pull Up */
DDR(UART_TX_P) |= 1<<UART_TX;
PORT(UART_RX_P) |= 1<<UART_RX;
...
}

In this example we assumed that all ports registers are initialized with 0. We only have to change the bits in the registers when we would like them to be set.

In most cases it is true that we have 0 in all port register bits before initialization. But it could be risky assumption and it could make our code refactoring harder in the future.

Moreover - notice that it is still not a complete code. It leaks in a card pins initialization. But even this part of initialization would generate 13 sbi instructions. And if some registers are outside from a bit addressable region, there have to be 3 instructions generated for every bit set!

Write pins configurations directly to right registers

  • Pros:
    1. Fast, small and reliable generated code.
    2. The code size is always constant and it only depends on the number of registers.
    3. Values written to registers do not rely on values that were there before. It is easy to make some default configuration. The result is more reliable as well.
  • Cons:
    1. It is hard to read and modify such a code.
    2. If we change hardware pins mapping, we also have to change initialization part of our code.

Below is an example of this method of port initialization:

int main(void)
{
PORTB = 0xff & ~(1<<MMC_SCK);
DDRB = 1<<LCD_RS | 1<<LCD_RW |
1<<MMC_CS | 1<<MMC_DI | 1<<MMC_SCK;
PORTC = 0xff & ~(1<<TSL238_OUT);
DDRC = 1<<UART_TX | 1<<LCD_ENA | 1<<LCD_D4;
PORTD = 0xff & ~(1<<TVOUT);
DDRD = 1<<LCD_D5 | 1<<LCD_D6 | 1<<LCD_D7;
}

I prefer this method. It is harder to read but it is more reliable and shorter in the generated code. In the worst-case scenario, it would generate 12 instructions (ldi and out).

Notice that we have some default configuration defined. All pins that are not initialized would have following settings: DDR=0, PORT=1. It means they would be set to Pull Up inputs.

The golden mean

The EPPL library gives us a possibility to define port configuration pin by pin, much like in the first solution. But it would automagically convert it to simple direct writing bytes to the right registers just like in the second solution.

In the EPPL library we would define our hardware configuration like below:

/* Button */
#define BTN _PD(3)
/* LCD */
#define LCD_RS _PB(0)
#define LCD_RW _PB(1)
#define LCD_ENA _PD(6)
#define LCD_D4 _PD(7)
#define LCD_D5 _PC(0)
#define LCD_D6 _PC(1)
#define LCD_D7 _PC(2)
/* RTC */
#define I2C_SDA _PC(4)
#define I2C_SCL _PC(5)
#define RTC_IRQ _PD(2)
/* Light to frequency sensor */
#define TSL238_OUT _PD(5)
/* UART */
#define UART_TX _PD(1)
#define UART_RX _PD(0)
/* SD/MMC Card */
#define MMC_CS _PB(2)
#define MMC_DI _PB(3)
#define MMC_DO _PB(4)
#define MMC_SCK _PB(5)
/* Thermistor */
#define T_VOUT _PC(3)

Then we can use this hardware declaration to initialize our pins:

int main(void)
{
eppl_init(EPPL_mode_inPU, /* Default */
/* Button - input with Pull Up */
/* LCD – all outputs, state 0 */
/* RTC – OC outputs with Pull Ups */
/* RTC interrupt – input with Pull Up */
/* UART, TX – output, RX – input with Pull Up */
/* TSL sensor - HiZ input */
EPPL_SC(TSL238_OUT, EPPL_mode_inHZ),
/* Temperature sensor - HiZ input */
/* Card pins configuration */
);
}

Now the code refactoring is extremely simple. Generated code is optimal.

Notice how the EPPL library makes it simple to define our hardware configuration - we are using one line for a pin. This one line already contains information about port letter and pin number in the port. This way of port definition is more "human friendly" than the method where we used two lines for every pin.