Improve your Arduino programming: register management

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 UNO1Buy Now
Led 5mm1Buy Now
Resistor 1K1Buy Now
37 in 1 Sensors kit1Buy Now
Jumper WiresfewBuy Now
Breadboard1Buy Now
9v DC Adapter (Optional)1Buy Now
Arduino-programming-register-management-Diagram
The architecture of the AVR ATMEGA328P, for the Arduino UNO for example. to Improve your Arduino programming: register management

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

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

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
We can see that Pin 2 and 5 are connected to PD2 and PD5, respectively.

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
Program size with operations on registers. Note that the program is practically reduced by half.

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

Improve your Arduino programming: UART communication

Leave a Comment


error: