Saturday 16 June 2012

INTRO to port manipulation

Someone asked me about what sometimes comes in more direct code in Arduino, when we use port manipulation to make it a bit faster or for convenience, etc...
So a likkle explanation here !


As we know, Arduino made it much easier for beginners to create code in sketches by doing away with the complexity seen before in uC's ( port registers, header files, linker files, etc etc)
With just one button , most people are blinking a led and really understanding the basics of it in 10 minutes( most of it will be installing and downloading the IDE anyway).

But time comes that we will get to hit the limits of such simplicity, as their own functions, etc do take more time. For that, AVR way of doing things is the way to go.
So, by doing it directly improves the speed quite considerably.
So in this case we will do away with digitalWrite()/digitalRead(), by doing some port manipulation directly..


ATMega328 has three port registers that  can be altered to set the digital and analogue I/O pins. A port register is a kind of byte variable that we can change on the microcontroller, in order to control the state of various I/O ports. We have three port registers to work with:

D – digital pins 7 to zero (Port D)
B – digital pins 13 to eight (Port B)
C – analogue pins 5 to zero (Port C!)



In void setup(), we will use

DDRy = Bxxxxxxxx
where y is the register type (B/C/D) and  xxxxxxxx  are 8 bits that determine if a pin is to be an input or output. Use 0 for input, and 1 for output. The LSB (least-significant bit [the one on the right!]) is the lowest pin number for that register.
So , then, to control the port pins, we will use

PORT y  = Bxxxxxxxx
where y is the register type (B/C/D) and  xxxxxxxx  are 8 status bits = 1 for HIGH, 0 for LOW. That can be seen in the following example:

// Digital 0 to 7 set to outputs, then on/off using port manipulation

void setup()
{
  DDRD = B11111111; // set PORTD (digital 7~0) to outputs
}

void loop()
{
  PORTD = B11111100; // digital 2~7 HIGH, digital 1~0 LOW
  delay(1000);
  PORTD = B00000011; // digital 2~7 LOW, digital 1~0 HIGH
  delay(1000);
}




So, we have set the digital pins 7~0 to output in void setup().
Then we alternate them on and off , pins 2 to 7 with 0 and 1 always in opposite state to the rest .

So, why would we want to do that ?!?
Well, John Boxall done some measurements and he came up with these results :



The code as a first test:


// Digital 0 t o7 set as outputs, then on/off using digitalWrite()

void setup()
{
  for (int a=0; a<8; a++)
  {
    pinMode(a, OUTPUT);
  }
}

void loop()
{
  for (int a=0; a<8; a++)
  {
    digitalWrite(a, HIGH);
  }
  for (int a=0; a<8; a++)
  {
    digitalWrite(a, LOW);
  }
}

And the frequency was  14.085 kHz.
This would be a way of doing it.
Another way would be:


// Digital 0~7 set to outputs, then on/off using individual digitalWrite() 

void setup()
{
  for (int a=0; a<8; a++)
  {
    pinMode(a, OUTPUT);
  }
}

void loop()
{
  digitalWrite(0, HIGH);
  digitalWrite(1, HIGH);
  digitalWrite(2, HIGH);
  digitalWrite(3, HIGH);
  digitalWrite(4, HIGH);
  digitalWrite(5, HIGH);
  digitalWrite(6, HIGH);
  digitalWrite(7, HIGH);
  digitalWrite(0, LOW);
  digitalWrite(1, LOW);
  digitalWrite(2, LOW);
  digitalWrite(3, LOW);
  digitalWrite(4, LOW);
  digitalWrite(5, LOW);
  digitalWrite(6, LOW);
  digitalWrite(7, LOW);
}

A small speed boost, the frequency has increased to 14.983 kHz. 

Then he used same type of port manipulation with:

// John Boxall - October 2011
// Digital 0~7 set to outputs, then on/off using port manipulation

void setup()
{
  DDRD = B11111111; // set PORTD (digital 7~0) to outputs
}

void loop()
{
  PORTD = B11111111;
  PORTD = B00000000;
}


And  the frequency measurements – 1.1432 MHz! 




However, as he notes, there are a few things to take note of:


You can’t control digital pins 0 and 1 (in bank D) and use the serial monitor/port. For example if you set pin zero to output, it can’t receive data!


Also , keep this in mind, if you are not aware of it-


Binary| Hex
0000 |  0
0001 |  1
0010 |  2
0011 |  3
0100 |  4
0101 |  5
0110 |  6
0111 |  7
1000 |  8
1001 |  9
1010 |  A
1011 |  B
1100 |  C
1101 |  D
1110 |  E
1111 |  F

(precede with 0x)


So in those terms, this code

void setup()
{
  DDRD = B11111111; // set PORTD (digital 7~0) to output
}

could be written as:

void setup()
{
  DDRD = 0xFF; // set PORTD (digital 7~0) to output
}


Same way that


void loop()
{
  PORTD = B00000000; //Digital 0~7  to LOW
}
Could be done as

void loop()
{
  PORTD = 0x00; //Digital 0~7  to LOW
}

*PS- Someone asked "...why is digitalWrite() relatively that much slower?"

Well, here is the source code for digitalWrite() – quite a lot of code to convert compared against port manipulation:
void digitalWrite(uint8_t pin, uint8_t val)
{
  uint8_t timer = digitalPinToTimer(pin);
  uint8_t bit = digitalPinToBitMask(pin);
  uint8_t port = digitalPinToPort(pin);
  volatile uint8_t *out;

  if (port == NOT_A_PIN) return;

  // If the pin that support PWM output, we need to turn it off
  // before doing a digital write.
  if (timer != NOT_ON_TIMER) turnOffPWM(timer);

  out = portOutputRegister(port);

  if (val == LOW) {
    uint8_t oldSREG = SREG;
    cli();
    *out &= ~bit;
    SREG = oldSREG;
  } 
  else {
    uint8_t oldSREG = SREG;
    cli();
    *out |= bit;
    SREG = oldSREG;
  }
}



3 comments:

  1. Is it difficult to make the higher level coding transfer to the port manipulation type? I can't figure why this isn't the default for final (right before it's turned into machine code) programming to the chip.

    ReplyDelete
  2. There are other examples posted where this method is used within the arduino code, though small changes might be needed for an avr code to work in arduino . You can go lower level and use assembly ( useful to work around the compiler ), though personally i only used assembly in PICs ( check arduino docs for details on it).

    ReplyDelete
  3. Everything gets compiled to machine code in the end before the chip can use it... but I was wondering why even the machine code was so inefficient when relating to the slow speed when using the higher level language coming from arduino. I came to find out, that it was all the checks that it makes, before it changes anything. Safety, safety, safety. Almost like we need a warning that coffee is hot, or bags are not toys for infants.
    I didn't know you could use assembly.

    ReplyDelete

Feel free to contact me with any suggestions, doubts or requests.

Bless