3. Microcontroller memory and peripherals.

0

This tutorial will explain how a microcontroller functions internally, how the memory registers have to be treated from our C program perspective in order to achieve the expected result in our electronic circuit.

In the microcontroller datasheet there is pin mapping, each pin has one or several names assigned:

ATtiny841 pin mapping

ATtiny841 pin mapping

Those names provide a description of the alternative functions the pins could perform. A few of the pins are dedicated to reading analog signals, other to produce PWM outputs, to establish a serial communication through a serial protocol, etc… In other words, the name of the pin sets what internal hardware would be accessible and that will condition what it should connect to the rest of the board.

In the datasheet there is a block diagram of the different hardware elements (peripherals) that gather the different features available.

 Internal hardware modules of an Attiny841

Internal hardware modules of an Attiny841

In the picture above, every rectangular box represents a piece of electronic circuitry that is embedded in the microcontroller and assembles a specific function. The modules are linked to the CPU, which is where the software instructions get executed and the interface to the outside is done through the input/output pins.

The software we write will have a section to configure the different modules, including the one that connects to the microcontroller pins. In order to find out how to configure and work with the different microcontroller modules or peripherals we will need to study how its memory is structured.

The microcontroller has three different types of memory: the program FLASH, the data SRAM and data EEPROM.

ATtiny841 memory types

ATtiny841 memory types

The flash memory is the zone where the program memory that we download into the microcontroller gets stored, it contains a set of instructions that the CPU executes one after the other, in a sequential fashion to perform a programmed task. This is a persistent memory, therefore it remains when the microcontroller isn’t powered and cannot be altered by the program execution.

The FLASH memory is contained within 4096 memory address of 16 bits long each.

FLASH memory

FLASH memory

The EEPROM memory could be read and written while the binary instructions take place to load or store numerical data. When the microcontroller is switched off, the contents remain and the stored values will be there for the next time they are needed.

The EEPROM memory is organized a long 512 memory addresses, being each a single Byte long.

EEPROM

EEPROM

Unlike the previous memories the SRAM has a volatile nature, that means that if the microcontroller isn’t powered then all the contents of the memory will vanish. The microcontroller uses this memory to store data required during the execution, as well as to configure and talk to the internal hardware modules.

ATtiny841 SRAM Memory

ATtiny841 SRAM Memory

The memory SRAM is arranged in 8 bit blocks named registers which have a fixed address assigned to them.

The top 32 register of the SRAM memory map to quick access register that the CPU makes use for the program execution.

The following 224 bytes (I/O register file) represent the configuration register to control the behaviour of the different pins, and the diverse hardware modules (peripherals: UART, ADC, timers, etc..) that are part of the internal circuitry. The configuration and behaviour of the peripherals is done by setting the appropriate bits in each relevant register.

From the address 0x0100 there are 512 data memory addresses that will act as a storage space at the microcontroller execution’s disposal, it contains variables and a memory stack.

The principal concept to bear in mind is that data gets stored in chunks of 8 bits wide (a greater size variable needs more than a single memory access). And that the hardware in the microcontroller (pins, peripherals) is access through a memory reference, similar to working with variables in our code that’s because the hardware is mapped into memory addresses.

For instance, if we wanted to work with the Port A pins (A0, A1, …, A7) and configure them as digital outputs, then we would have to alter the contents of memory addresses (registers) that are allocated to define the behaviour of those pins and set them to digital outputs.

PORTA digital output registers

PORTA digital output registers

By setting to 1 the bits of the SRAM memory at the 0x1A address, named as DDRA, then port A is configured as an output. Similarly by setting to 1 or resetting to 0 the bits at 0x1B address, known as PORTA, the corresponding output pin will be getting either a high or low voltage level state.

In our case the microcontroller will get the desired set of instructions by means of a C compiler, AVR-gcc, therefore to apply what has been described above we will need to change the content of those registers associated to the microcontroller hardware. This operation could be carried out easily by masking the register we would like to alter with the AND (&) and OR (|) operators (do not mistake them with && and ||).

Result of an OR operation:

0 | 0 = 0

0 | 1 = 1

1 | 0 = 1

1 | 1 = 1

Result of an AND operation:

0 | 0 = 0

0 | 1 = 0

1 | 0 = 0

1 | 1 = 1

The AND and OR operators work at a bit level. Given the two 8 bit registers:

Register_1 = (bit7, bit6, bit5, bit4, bit3, bit2, bit1, bit0)

Register_2 = (BIT7, BIT6, BIT5, BIT4, BIT3, BIT2, BIT1, BIT0)

And the result of the Register_1 & Register_2 operation, will be 8 bit wide and calculated as:

Result = (bit7 & BIT7, bit6 & BIT6, bit5 & BIT5, bit4 & BIT4, bit3 & BIT3, bit2 & BIT2, bit1 & BIT1, bit0 & BIT0)

The OR operator works in a similar fashion but applying an OR operation bit by bit.

For example, if we wan to set bit 5 in the DDRA register (located at 0x1A SRAM memory address) and keep the rest of the bits of the DDRA as they were, then we could do it by typing the following line of code in our C file that represents the OR operator with a mask:

DDRA = DDRA | 0b00100000;
DDRA |= 0x20; // DDRA |= equates to DDRA = DDRA | . .

The constant 0xb00100000 (in binary) or 0x20 (in hexadecimal) is known as mask. The previous line won’t change the value of the remaining DDRA bits: X | 0 = X and would set to 1 bit 5: X | 1 = 1. Being X any possibly binary state for the bit (0 or 1).

If instead of 1 we want to reset to 0 the same bit in DDRA, then we will choose the AND operator with the corresponding mask:

DDRA = DDRA & 0b11011111;
DDRA &= 0xDF; // DDRA &= equates to DDRA = DDRA & . .

The line above leaves the rest of the DDRA bits unaltered: X & 1 = X and would reset bit 5: X & 0 = 0. Being X any possible value (0 or 1).

We can handle one of more bits in a register by having a mask as an operand and & or | operators, in other words we can have individual control of any bit of interest.

Everytime there is an application to write we will include the following library #include <avr/io.h>. This line includes external libraries that have a definition with user friendly names for all the memory references (registers) to the microcontroller internal hardware, as well as the bit positions of the register bits.

If we browse the included file that has been referenced by our C program:

#include  defines all the memory positions of the microcontroller hardware

#include <avr/io.h> defines all the memory positions of the microcontroller hardware

The file explicitly shows how the microcontroller hardware registers are defined with the corresponding memory allocation in SRAM, and the assigned name for each of the bits that compose the register, hence the position within a register is defined by the naming convention.

The library assists with a more user friendly library, convenient to reuse code too, and the created masks will be simplified by its inclusion and using the &, |, “<<” left shift and “~” NOT operators.

Let’s see it with another example, if we want to set bit 5 of the DDRA register we could do it as before by typing 0b00100000 but the most readable style would probably be:

DDRA |= (1<<DDRA5); // (1<<5) = 00100000, DDRA5 has been defined by 5

The previous line is equivalent to:

DDRA = DDRA | 0b00100000;

Alternatively, if we would like to change several bits instead of a single bit of the DDRA register then we could write the mask as:

DDRA |= ((1<<DDRA5) | (1<<DDRA2) |(1<<DDRA0));

The previous line would be equivalent to:

DDRA = DDRA | 0b00100101;

This approach expands to making masks to reset bits in a register thanks to the & | and ~ operators.

For instance if we want to reset bit 5 in the DDRA register we will type:

DDRA &= ~(1 << DDRA5); //~(1 << DDRA5) = ~(00100000) = (11011111)

The previous line is equivalent to:

DDRA = DDRA & 0b11011111;

If we would like to reset several bits in a register we could write the following:

 DDRA &= ~((1 << DDRA5) | (1 << DDRA2) | (1 << DDRA0));

The line above equates to:

DDRA = DDRA & 0b11011010;

The initial perception of working with the bits in this manner may seem a little bit complicated, however after exercising it a couple of times it becomes second nature and everything boils down to the name of the register of the hardware module we are working with, and the name of the bit of that register that needs setting to either 1 or 0. All the information regarding registers and their configuration is collated in the microcontroller datasheet.

If the reader is unfamiliar with the use of the bitwise operators (&, |, ~, <<, ^) there are throrough explanations in any C language book or reference.

The subsequent tutorials will provide examples with how to use the different hardware features present in a microcontroller, therefore it might possibly make more sense when the relevant example gets addressed. The aim of this tutorial was only to understand that to control the internal hardware and the microcontroller pins we need to manipulate the bits at certain registers (memory addresses) and to do so we will compute registers with the & and | operators over a mask.

Related links: