Terms of service
Brokking
 
 
.net
Let's keep it simple
 

 
   Home
   Projects
 

   Articles
 

   Donation
         
 
 
 
 

 
Brokking.net - Video: Improve your Arduino programming skills - Using the ATmega328P registers.

Improve your Arduino programming skills - Using the ATmega328P registers.

This page contains the full script that I used for making this video.

This is the ATMega328P. It's the microcontroller that is used for the Arduino Uno and some other Arduino boards.

And this document describes all the little details about how it works. It's the free to download datasheet that comes with the ATMega328P. I'll put a link to this document in the description.

And yes, it looks very intimidating. But don't worry, it's not that difficult to understand.

When opening the PDF version of the datasheet there is an index. Every specific feature has its own chapter. Like the System clock, the in- and output ports, the USART or serial device, analog inputs, etc.

During this video I would like to take the USART or serial communication device as an example. This device is used when you use the Serial.print command in the Arduino IDE.

The chapter for the USART device holds 26 pages and it starts with its specific features and a brief overview.

In short, the USART is a standalone device that can be used to send serial data. It uses configuration and data registers that we can use to communicate with this device.

All the available registers are explained in detail in the last paragraph.

But what is a register and how do we use it? Simplified it's something like this. Yes, I used Lego again.

This register is named LEGO and has 8 individual spaces where I can put a marble. Now every space symbolizes a bit. When the marble is in place the bit is set to 1. An empty space represents a 0. These 8 bits together form a byte. So, in simple terms a register of the ATMega328P is just a byte with a unique name.

Each space has a specific function as I'm showing here. This is just an example but the principle is the same.

With the LEGO register we can set parity

Stop bits

Communication mode

Number of data bits

And the last bit in this example is reserved. This is pretty common and means that this bit should not be changed.

After a power up or reset all registers are set to a specific initial value. The specific value for each register can be found in the datasheet.

The initial value of the Lego register example is all zero's. I'm planning to use the device in asynchronous mode without parity, a stop bit and I have 8 data bits to send.

So, parity stays empty, stop bit gets a marble, communication mode only gets a marble at the right bit, and the number of data bits is set to zero, one, one. And we leave the last bit of the register untouched so it stays empty.

In short the LEGO register is set to 01010110 in binary form. This is the same as 86 in decimal form. This is because the least significant bit represents a decimal 1, the second bit a decimal 2, the third bit a decimal 4, the forth bit 8, the fifth bit 16 and so on. By adding the numbers with a marble we get 86.

Ok it's time for a practical example. Let's say that I want to check the status of 8 digital inputs, convert them to bits and send the result as a single byte via the serial output every second.

First the serial port is configured at 57.6 kilo baud.

Inputs 2 till 9 are set as inputs with the internal pullup resistor activated. This means that the input is connected to the +5V with an internal resistor as you can see here. This makes it easier to attach switches because you only have to pull the input port low by switching it to the ground of the Arduino UNO.

The only variable that is used in this program is inputs. And it is declared as a byte. So it only holds 8 bits and is set to zero every loop. so all the eight bits are set to 0.

In the next lines the inputs are checked. If they are high, so the switch is not closed and they are not connected to ground, this value is added to the variable inputs. It represents the decimal value of the bit that needs to be set.

When all the inputs are checked the total value is send via the serial port as a byte.

A delay of one second is added in this line.

If I output the binary form to the serial output you can see that the program works as expected.

And it's even prettier on the Oscilloscope. This is the start bit. Now look what happens if the inputs are pulled low to ground one by one. We can actually see the bits change. Isn't that awesome!

Ok, back to the program because this video is about improving your programming skills. Let's check the size of the code. It's a whopping 2632 bytes and uses 8.5% of the total flash memory of the ATMega328P.

Let's see if we can shrink the size of this code with the help of the datasheet and the knowledge about registers.

Let's have a look in the USART chapter off the datasheet. While reading this chapter you will find calculations for setting the baud rate, code examples, how to send data, baud rate setting examples and finally a detailed explanation of the registers.

The first register is the data register. This is the register where we can write the data to that we want to send. The n represents the USART number that is used. In our case it's USART0 because the ATMega328P only has one serial device.

Because we only have to send one byte we can replace Serial.write with UDR0 = inputs;

In the send example in the datasheet there is a check to see if the previous byte is send before the next byte can be loaded into the data register. This is not necessary with our program because the next byte is send 1 second later. If you want to send a stream of bytes you certainly have to check if the last bit is send before sending the new one.

I will also remove the Serial.begin function because we will initialize the serial port manually.

The next register: USART Control and Status Register A. This register can be left untouched. The initial values will work fine.

In the register: USART Control and Status Register B we need to set bit 3 - Transmitter Enable to enable the USART transmitter.

To set this value we can use the code examples in the datasheet. Because this code only needs to be executed once we need to write it in the setup section.

This line will write a 1 on the location of the transmit enable bit in the USART Control and Status Register B.

The advantage is that the code is still recognizable due to the names.

USART Control and Status Register C can be left untouched because the correct number of data bits are already set by the initial value.

The next and last registers are the USART Baud Rate Registers. With these registers the baud rate of the serial port is set. On page 193 we can find examples of various settings at various clock speeds. The Arduino Uno has a 16MHz clock and we want to set the baud rate to 57.6kb. So the UBRR0 needs to be set to 16.

As we can see here, the first 8 bit of the UBRR0 are in the lower register. The upper 4 bits are in the higher register.

This is the binary representation of 16. So it's not necessary to change the UBRRH register. Only UBRRL needs to be set to 16. And this is done in this line.

And that's it. The Serial port is configured and every second the inputs byte is send. Let's check the screen of the oscilloscope to see if everything is working. And it is!

By the way, have you seen the difference in code size? It was 2632 bytes. It's now 1204 bytes.

Let's take it one step further. Registers are not only meant to set, they are also readable.

In chapter 14 of the datasheet, the input / output pins of the ATMega328P are explained. Again we have the general information, code examples, different pin functions and in the last part of this chapter the registers are explained.

On the schematic of the Arduino Uno we can see that input 2 till 7 are connected to port D2 till D7 of the ATMega328P. In the datasheet we can see that there are three registers used for setting and reading PortD.

The Port D Data Register is used to set the output high or low when the pin is configured as an output. When configured as an input this register activates the internal pullup resistor.

The Port D Data Direction Register is used to select if the port is used as an input or output. The initial value is zero, meaning that all the ports are used as an input.

Finally, the Port D Input Pins Address holds the current status of the pins. So, if the input is high it holds a 1 for that specific input.

With that being said we can replace these input_pullup lines with this line to activate the pullup resistors.

And because we can read the current input state from the PIND register we can copy this register value to the inputs variable like this.

But there is a problem. If we look at the schematic of the Arduino Uno we can see that the serial transmit and receiver ports are located in the lower two bits of the PIND register.

If we copy this register to the inputs variable we get this. But what it needs to be is this. I have to shift all the bits 2 positions to the right.

Now there is a great feature that is called bit shift. The function is very simple, shift all the bits to the left or right as I just did.

With this line all the bits in the PIND register are shifted two positions to the right and the value is copied to the inputs variable.

And because the last two inputs are connected to B0 and B1 we need to do everything again with portB.

Set the pullup resistors and add the lowest two bits of the PINB register to the upper two bits of the inputs variable by shifting them to the left.

And this is the final result. Let's upload the code and check if it works with the oscilloscope. And it does!

Not have a look at the code size: 682 bytes instead of the whopping 2632 bytes.

I hope that you are already wondering if we can do better than the 682 bytes?

Let's take this to the next level and copy the code into Atmel studio. I will create a new project and copy the code from the Arduino IDE.

Now Atmel studio does not use the setup and loop function. So the setup is done directly after the program start and the loop part is executed constantly by using a while loop.

Let's try to compile this code.

Ok, we have two errors. That is a good sign because we can fix it. The first error involves the declaration of a byte.

Atmel studio has no idea what a byte is. And if we take a look in the Arduino.h file we can see that it's made up to get the Arduino code more human readable. With type definition, byte is converted into a 8 bit unsigned int.

So let's change byte with the 8 bit unsigned int to solve the first error.

The next error involves the delay function. Atmel Studio has its own function for creating a delay. So we have to replace the Arduino delay function with the delay function of Atmel Studio.

This includes defining the clock frequency and including the delay library.

The only thing left to do is to build the project and upload it to the Arduino Uno via the bootloader.

In case you haven't seen my previous video. I already made a video about uploading a Atmel studio program via the bootloader. I'll put a link to this video in the description.

Now look at this code size! It's only 194 bytes. That is only 7.5% of the initial program size of 2632 bytes.

And does it work? Yes it does.

And that wraps it up for this video. As always, leave your thoughts in the comments below and give this video a thumbs up if you learned something.