In this tutorial, we will Improve your Arduino programming: register management, To delve a little deeper into how to make the most of a microcontroller’s processing capacity, it is necessary to know its internal architecture and the way in which information passes through the device. To do this, look at the following diagram:
Hardware Required
Components | # | Buy From Amazon |
---|---|---|
Arduino UNO | 1 | Buy Now |
Led 5mm | 1 | Buy Now |
Resistor 1K | 1 | Buy Now |
37 in 1 Sensors kit | 1 | Buy Now |
Jumper Wires | few | Buy Now |
Breadboard | 1 | Buy Now |
9v DC Adapter (Optional) | 1 | Buy Now |
![Arduino-programming-register-management-Diagram](http://arduinocircuit.com/wp-content/uploads/2023/06/Arduino-programming-register-management-Diagram.webp)
Within our microcontroller, we can identify the above structure, which in essence shows us how data moves through the system. We can identify some important elements, such as the logical-arithmetic unit or ALU, which allows operations to be carried out, general purpose registers, and input and output modules, among others. Registers are basically memory units that we can access and read or write data.
There are also specific-purpose registers, such as timers and those that control serial communication. Specific-purpose registers, in addition to managing information, allow you to configure and manage hardware, such as analog inputs, are very important, and the more we learn from them, the better we can take advantage of them.
Let us consider, for example, how to configure the digital inputs and outputs, while controlling the registers. To do this, let’s look at the AVR manual, and see how the inputs and outputs are distributed in the integrated. As we can see, we have a color code to determine if they are digital or analog pins as well as a PBx, PCx, or PDx denominator, where P is for the port, B, C, or D is the set to which it belongs and x is the pin number.
Pinout
![ATmega-Pinout](http://arduinocircuit.com/wp-content/uploads/2023/06/ATmega-Pinout.webp)
Next, let’s find the section that describes how to handle inputs and outputs. In essence, this section indicates which registers are related to inputs/outputs and how to write to them to tell you how to configure them and how to get data from them.
Pin Configuration
- Each port pin is composed of three register bits: DDxn, PORTxn, and PINxn. These bits can be accessed at specific I/O addresses, as described in the Register Description. The DDxn bits are accessed at the DDRX I/O address, the PORTXn bits at the PORTX I/O address, and the PINXn bits at the PINX I/O address.
- The DDxn bit in the DDRX Register determines the direction of the pin. When DDxn is set to ‘1’, the corresponding pin (Pxn) is configured as an output pin. Conversely, when DDxn is set to ‘0’, Pxn is configured as an input pin.
- When a pin is configured as an input pin and PORTxn is set to ‘1’, the pull-up resistor is activated. To disable the pull-up resistor, PORTxn must be set to ‘0’, or the pin needs to be configured as an output pin. Even without active clocks, the port pins enter a tri-state when the reset condition becomes active.
- When a pin is configured as an output pin and PORTxn is set to ‘1’, the port pin is driven high. Conversely, when PORTXn is set to logic zero, the pin is driven low.
If we go to the section that describes the registers in detail, we can see how they are composed and what effect writing to them will have. For example, for Port B we have the three registers DDRB, PORTB, and PINB, which configure the address of the pin, the pull-up resistors, and the data to be read.
![Data-register-management](http://arduinocircuit.com/wp-content/uploads/2023/06/Data-register-management.webp)
For example, let’s make a small program in which we read a button and reflect the data on an LED. To do this, at the first level of abstraction, the program would look like this, with the conventional read and write functions.
Code
const int btn_pin = 2; // Define pin 2 as the constant btn_pin
const int led_pin = 5; // Define pin 5 as the constant led_pin
void setup() {
pinMode(btn_pin, INPUT_PULLUP); // Set btn_pin as input with internal pull-up resistor enabled
pinMode(led_pin, OUTPUT); // Set led_pin as output
}
void loop() {
int btn = digitalRead(btn_pin); // Read the state of the button and store it in the btn variable
if (btn == LOW) {
digitalWrite(led_pin, HIGH); // If the button is pressed (LOW state), turn on the LED
} else {
digitalWrite(led_pin, LOW); // If the button is not pressed (HIGH state), turn off the LED
}
}
But now we want to do it by directly handling the resources, so to do this we must first know which ports are related to the pins we chose to create our program. To do this we must consult the schematic diagram of, in this case, the Arduino UNO.
![Register-management-ATmega](http://arduinocircuit.com/wp-content/uploads/2023/06/Register-management-ATmega.webp)
Once the pins that we must modify have been identified, we can proceed to write the lines that relate these pins with their registers, that is, we are going to modify the DDRD, PORTD AND PIND register to handle the PD2 and PD5 pins. First let’s make the changes in the setup, where we previously defined pin 2 as an input and pin 5 as an output. To do this we must write the byte ‘00100000’ in DDRD and ‘00000100’ in PORTD.
Code
const int btn_pin = 2; // Define pin 2 as the constant btn_pin
const int led_pin = 5; // Define pin 5 as the constant led_pin
void setup() {
pinMode(btn_pin, INPUT); // Set btn_pin as an input
pinMode(led_pin, OUTPUT); // Set led_pin as an output
digitalWrite(led_pin, LOW); // Set the initial state of led_pin to LOW (off)
}
void loop() {
int btn = digitalRead(btn_pin); // Read the state of the button and store it in the btn variable
if (btn == LOW) {
digitalWrite(led_pin, HIGH); // If the button is pressed (LOW state), turn on the LED
} else {
digitalWrite(led_pin, LOW); // If the button is not pressed (HIGH state), turn off the LED
}
}
The program should work normally with these changes. Now we will try to change the loop section, where we read the input and write to the output pin. For this we will have to read and save the data that PIND has and write again in PORTD. Let’s start by changing the parts where we turn PORTD on and off. When we want to edit a bit, we only have to move the bit to the desired position and do an OR operation with the register that we want to edit, thus we do not modify the other bits of the register. Let’s see the code:
Code
const int btn_pin = 2; // Define pin 2 as the constant btn_pin
const int led_pin = 5; // Define pin 5 as the constant led_pin
void setup() {
DDRD |= B00100000; // Set pin 5 (led_pin) as an output
PORTD |= B00000100; // Set initial value for pin 2 (assuming it should be set HIGH initially)
}
void loop() {
int btn = digitalRead(btn_pin); // Read the state of the button
if (btn == LOW) {
PORTD = (1 << led_pin) | PORTD; // Set the led_pin HIGH using bitwise OR (|) operator
} else {
PORTD &= ~(1 << led_pin); // Set the led_pin LOW using bitwise AND (&) operator
}
}
To send a 1 to a particular bit, we shift it to position and OR the register; thus, we will not modify the other bits and we only change the PD5 bit. When we want to send a 0 to the bit, we use the not operation, written with a tilde ‘ ~ ‘, on the data (1 << led_bit) and ‘and’ it to the PORTD register. This will keep the other bits intact, sending a 0 to the PD5 bit.
Lastly let’s read the PD2 bit, for this, we need the PIND register and operate it with (1 << btn_pin), followed by a shift to position 0, this returns the value of the bit and stores it in btn, so we can evaluate it. after. The code will look like this at the end:
Code
const int btn_pin = 2; // Define pin 2 as the constant btn_pin
const int led_pin = 5; // Define pin 5 as the constant led_pin
void setup() {
DDRD |= B00100000; // Set pin 5 (led_pin) as an output
PORTD |= B001000100; // Set initial values for the pins (assuming the intention is to set pin 2 and pin 5 HIGH)
}
void loop() {
int btn = (PIND & (1 << btn_pin)) >> btn_pin; // Read the register and perform bitwise operations
if (btn == LOW) {
PORTD |= (1 << led_pin); // Set the led_pin HIGH using bitwise OR (|) operator
} else {
PORTD &= ~(1 << led_pin); // Set the led_pin LOW using bitwise AND (&) operator
}
}
This is probably too farfetched to just turn an LED on and off with a switch, but it’s useful for explaining how we should read and write the registers. It also allows to expose a very important matter clearly and that is to optimize the code. Notice the size of the program when we use the normal functions and when we write directly to the registers.
![Improve-your-Arduino-programming-register-management-code-compile](http://arduinocircuit.com/wp-content/uploads/2023/06/Improve-your-Arduino-programming-register-management-code-compile.webp)
In conclusion, when one wants to make a first program and make an idea work, it is useful to use code with functions and expressions that can be easily read (high-level language). When we seek to optimize the code and make it faster and more efficient, we write directly to the logs. Always remember to consult the manual of the microcontroller that you are using, since the names may vary according to the integrated that is used. this is all about Improve your Arduino programming: register management
See Also