Arduino and Port Manipulation
by pmdwayhk in Circuits > Arduino
58759 Views, 12 Favorites, 0 Comments
Arduino and Port Manipulation
In this article we are going to examine Arduino I/O pins in more detail by using “Port Manipulation” to control them in a much faster manner than using digitalWrite()/digitalRead().
Why? Speed! Using this method allows for much faster I/O control, and we can control or read groups of I/O pins simultaneously, not one at a time; Memory! Using this method reduces the amount of memory your sketch will use. Once again I will try and keep things as simple as possible. This article is written for Arduino boards that use the ATmega168 or ATmega328 microcontrollers (used in Arduino Duemilanove/Uno, Nano, Pro Mini, etc).
First, we’ll use the I/O as outputs. There are three port registers that we can alter to set the status of the digital and analogue I/O pins. A port register can be thought of as a special byte variable that we can change which is read by the microcontroller, therefore controlling the state of various I/O ports. We have three port registers to work with:
D – for digital pins seven to zero (bank D)B – for digital pins thirteen to eight (bank B)C – for analogue pins five to zero (bank … C!)Register C can control analogue pins seven to zero if using an Arduino with the TQFP style of ATmega328, such as the Nano or Pro Mini). For example see the image in this step.
It is very simple to do so. In void setup(), we use:
DDRy = Bxxxxxxxx
where y is the register type (B/C/D) and xxxxxxxx are eight 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. Next, to control a bank of pins, use
PORTy = Bxxxxxxxx
where y is the register type (B/C/D) and xxxxxxxx are eight status bits – 1 for HIGH, 0 for LOW. This is demonstrated in the following example:
// 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 = B11110000; // digital 4~7 HIGH, digital 3~0 LOW delay(1000); PORTD = B00001111; // digital 4~7 LOW, digital 3~0 HIGH delay(1000); }
It sets digital pins 7~0 to output in void setup(). Then it alternates turning on and off alternating halves of digital pins 0~7. At the start I mentioned that using port manipulation was a lot faster than using regular Arduino I/O functions. How fast? To test the speed of port manipulation vs. using digitalWrite(), we will use the circuit in the image.
Now to analyse the output at digital pins zero and seven using a digital storage oscilloscope. Our first test sketch turns on and off digital pins 0~7 without any delay between PORTD commands – in other words, as fast as possible. The sketch:
// 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; }
In the image, digital zero is channel one, and digital seven is channel three. Wow – check the frequency measurements – 1.1432 MHz! Interesting to note the longer duration of time when the pins are low vs. high.
[Update] Well it turns out that the extra time in LOW includes the time for the Arduino to go back to the top of void loop(). This can be demonstrated in the following sketch. We turn the pins on and off five times instead of once:
void setup() { DDRD = B11111111; // set PORTD (digital 7~0) to outputs }
void loop() { PORTD = B11111111; PORTD = B00000000; PORTD = B11111111; PORTD = B00000000; PORTD = B11111111; PORTD = B00000000; PORTD = B11111111; PORTD = B00000000; PORTD = B11111111; PORTD = B00000000; }
And the results from the MSO. You can see the duty cycle is much closer to 50% until the end of the sketch, at which point around 660 nanoseconds is the time used between the end of the last LOW period and the start of the next HIGH
Next we do it the normal way, using this sketch:
// Digital 0~7 set to 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 results in the image.
That was a lot slower – we’re down to 14.085 kHz, with a much neater square-wave output. Could some CPU time be saved by not using the for loop? We tested once more with the following sketch:
// 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); }
and the results in the image.
A small speed boost, the frequency has increased to 14.983 kHz. Hopefully you can now understand the benefits of using port manipulation. However 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!
- Always document your sketch – take pity on others who may need to review it later on and become puzzled about wchich bits are controlling or reading what!
Now to waste some electron flows by blinking LEDs. Using the circuit described earlier, the following sketch will create various effects for someone’s enjoyment:
// Fun with 8 LEDs on digital 7~0
void setup() { DDRD = B11111111; // set PORTD (digital 7~0) // to output }byte a = B11111111; byte b = B00000001; byte c = B10000000; byte e = B10101010;
void krider() { for (int k=0; k<5; k++) { for (int z=0; z<8; z++) { PORTD = b << z; delay(100); }
for (int z=0; z<8; z++) { PORTD = c >> z; delay(100); } } }
void onOff() { for (int k=0; k<10; k++) { PORTD = a; delay(100); PORTD = 0; delay(100); } }
void invBlink() { for (int z=0; z<10; z++) { PORTD = e; delay(100); PORTD = ~e; delay(100); } }
void binaryCount() { for (int z=0; z<256; z++) { PORTD = z; delay(100); } PORTD=0; }
void loop() { invBlink(); delay(500); binaryCount(); delay(500); krider(); delay(500); onOff(); }
with the results in the video.
I/O Pins As Inputs
Now to use the I/O pins as inputs. Again, it is very simple to do so. In void setup(), we use:
DDRy = Bxxxxxxxx
where y is the register type (B/C/D) and xxxxxxxx are eight bits that determine if a pin is to be an input or output. Use 0 for input. The LSB (least-significant bit [the one on the right!]) is the lowest pin number for that register. Next, to read the status of the pins we simply read the byte:
PINy
where y is the register type (B/C/D). So if you were using port B as inputs, and digital pins 8~10 were high, and 11~13 were low, PINB would be equal to B00000111. Really, that’s it!
Now for another demonstration using both inputs and outputs. We will use a push-wheel switch on our inputs (digital pins 8~11), and a seven segment LED display for output (on digtal pins 7~0 – segments dp then a~f). The following sketch reads the input from the switch, which returns 0~9 in binary-coded decimal.
This value is then used in the function void disp() to retrieve the matching byte from the array “segments”, which contains the appropriate outputs to drive the seven segment LED display unit. Here is the sketch:
// inputs and outputs
byte segments[] = { B01111110, B00110000, B01101101, B01111001, B00110011, B01011011, B01011111, B01110000, B01111111, B01111011}; // digital pins 7~0 connected to display pins dp,a~g void setup() { DDRB = B00000000; // set PORTB (digital 13~8) to inputs DDRD = B11111111; // set PORTD (digital 7~0) to outputs }void disp(int z) { PORTD = segments[z]; }
void loop() { disp(PINB); delay(100); }
with the video showing the results.
By now we hope you have an understanding of using port manipulation for your benefit. With a little effort your sketches can be more efficient in terms of speed and memory space, and also allow nifty simultaneous reading of input pins.
This post brought to you by pmdway.com – offering everything for makers and electronics enthusiasts, with free delivery worldwide.