Arduino is a great platform for embedded system projects. You can quickly turn your ideas into actual prototypes. There are massive communities and support online, where you can get help easily. You can even use C++ and not be limited to using C.
As awesome as the Arduino platform is there is an area that I find lacking and that is the operating speed of the code for time-sensitive projects. There are several occasions in which I find myself limited by how fast the microcontroller operates with the digitalRead and digitalWrite function part of the Arduino platform. I also found the delay function to have a negative impact on my projects because it makes things seem slow or laggy. This leads to the reason for this post, which is for me to share with you some ways to speed up your time-sensitive Arduino code.
Use Port Manipulation
First off, if you are using Arduino 1.8x and a genuine Arduino development board, the digitalRead and digitalWrite functions are already running at a faster rate using port manipulation. If that is not your situation, then you are going to have to swap out all the digitalRead and digitalWrite for its port manipulation equivalent. I recommend you get familiar with bitwise operators before you begin writing any port manipulation code.
Brief Intro
Port manipulation is when you are communicating to the port registers in your code. Port registers allow for lower level and faster manipulation of the I/O pins on the microcontroller. Each port is controlled by three registers, which are also defined variables in Arduino. The three registers are DDR, PORT, and PIN.
Register | Function |
---|---|
DDR | Determines whether the pin is INPUT (0) or OUTPUT (1) |
PORT | Controls whether the pin is HIGH (1) or LOW (0) |
PIN | Reads the state of INPUT pins |
Finding the Port Manipulation Equivalent
To find the port manipulation version of a digitalRead or digitalWrite call you must find out what physical pin the digital pin value is mapped to on the microcontroller. Generally, the process consists of three main steps:
- Find which physical pin is mapped to the digital pin value in Arduino. You can find the mapping in the documentation of your Arduino board.
- Figure out which port and pin number of that port the physical pin belongs to. Some documentation has the digital and physical pin with their port information in one place. While others might have only the physical pin number. If that is the case, you will need to find the documentation of the microcontroller itself.
- Write the port manipulation code to the specific pin or port.
Example: Convert digitalRead to Port Manipulation
For this example, I will be using the common Arduino Uno, which has an ATmega328P microcontroller. The line of code to convert to port manipulation is the following:
digitalRead(13);
Step 1
Find the pin mapping for the ATmega328P on the Arduino Uno. Here is a diagram of the pin mapping of the ATmega328P.
Note: ATmega328P and ATmega168 have the same pin mapping.
Step 2
From the diagram, you can tell that the digital pin 13 is mapped to physical pin 19, which is PB5 (the sixth pin of Port B).
Step 3
Writing the port manipulation code will give you something like the following:
byte pin_val = PINB & (0x01 << 5);
Caution
While using port manipulation can speed up I/O communication dramatically, be aware that it’s not portable. If you decide to change to a different microcontroller, your code will not work. The only exception is that the new microcontroller has the exact same pin mapping as the old one.
The code is also much more difficult to read, maintain, and debug. It can take hours to figure out why something is not working. I say this coming from my own experience.
In addition, you have to be extra careful when using port manipulation or you can break things like causing the serial port to suddenly not work. Certain pins on the microcontroller have a special function(s) and when you modify them through port manipulation it can lead to unexpected behaviors. Be cautious when using port manipulation on ports that have pins with special functions.
Stop Using Delay
The delay function might be simple to use, but if you want responsiveness you should move away from using delays in your code. When you use delay, it pauses the program for the specified period. During this period, you can’t read from sensors, do mathematical calculations, or most I/O pin manipulations.
An alternative to delay is to use the millis function in Arduino. The millis function returns the time (milliseconds) the current program has been running. This means you can take a timestamp of when an event has occurred. Then next time you can check the time and take the difference between the two to determine if enough time has passed for an action to occur. Notice that the program is always running and is not paused at any point in time.
The millis function is very useful when you want to multi-task. By using millis your program will no longer pause and you are able to read sensor values, do mathematical calculations, or respond to user input via I/O pins.
Example
For this example, a LED blinks every one second. There is a second LED that turns on when the user presses a button. Here are the finite-state machines (FSM) for the situation:
You can already tell if you use delay it will not work because the button presses can get ignore. So, in this situation, you will use millis to get the desired functionality. Here is a way to represent the two FSMs in code:
int led1_pin = 0; int led2_pin = 1; int button_pin = 2; unsigned int button_blink_period = 1000; // 1 second unsigned long time_elapsed = button_blink_period; unsigned long current_time = 0; unsigned long previous_time = 0; void setup(){ pinMode(led1_pin, OUTPUT); pinMode(led2_pin, OUTPUT); pinMode(button_pin, INPUT); } void loop(){ current_time = millis(); // turn on or off the led every 1 second if (time_elapsed >= button_blink_period) { if (digitalRead(led1_pin) == HIGH) { digitalWrite(led1_pin, LOW); } else { digitalWrite(led1_pin, HIGH); } time_elapsed = 0; } // check for user input; turn on led when pressed // note: the button is active low (0 when the button is pressed down) if (digitalRead(button_pin) == LOW) { digitalWrite(led2_pin, HIGH); } else { digitalWrite(led2_pin, LOW); } // update time elapsed since the last action time_elapsed += (current_time - previous_time); previous_time = current_time; }
Since this is a simple example, the two FSM are directly coded into the loop function. However, for more complex behaviors I strongly recommend representing each task as an object and run each one based on a scheduler. To find out more about FSM and task management visit my post about embedded systems.
I hope you found this post helpful. If you found this post helpful, share it with others so they can benefit too.
To stay in touch, follow me on Twitter, leave a comment, or send me an email at steven@brightdevelopers.com.