Additional material for lesson 9

Changing the variable through its adress: Pointers

Create a pointer to memory address 0x002b:

uint8_t *port_d = (uint8_t*)0x002b;

Write a 1 to bit number 4 (note the asterisk * in front of the variable name):

*port_d = 0b00001000;

Without the asterisk we are changing the contents of the variable, rather than the memory location to which it points:

port_d = 0b00001000; // Attempts to set the variable to point to memory address 0x0008. Don't do this!

The following example illustrates how to blink a LED connected to pin 3 on the Arduino UNO:

 1#include <Arduino.h>
 2
 3uint8_t *port_d = (uint8_t*)0x002b;
 4uint8_t *ddr_d = (uint8_t*)0x002a;
 5uint8_t *pin_d = (uint8_t*)0x0029;
 6
 7void setup() {
 8
 9  // Configure pin 4 as output
10  *ddr_d = 0b00001000;
11}
12
13void loop() {
14
15  // Set bit 4 (pin 3) high, all other pins low
16  *port_d = 0b00001000;
17
18  delay(500);
19
20  // Set all pins (including bit 4 / pin 3) low
21  *port_d = 0b00000000;
22
23  delay(500);
24}

Note that the register accesses modify all the bits in the register. E.g. if pin 5 was high before, it will be written low even though we are only interested in modifying pin 3. This can be solved by first reading the register and using some logic operations before writing back to the register.

Reading and manipulating single bits

In order to manipulate single bits of a register we have to read the register, make changes using logic operations, and finally write back the result.

A very useful operator for this purpose is the left bit shift (<<) operator. It allows us to shift a binary value to the left (a right shift operator >> is also available).

uint8_t b = (1 << 3); // The value of 'b' is 1 shifted 3 places to the left, i.e. 0b1000, or 8 in decimal.

Additionally the bitwise and (&), the bitwise or (|), the bitwise xor (^), and the bitwise not (~) can be used in clever ways to set, clear, or flip the state of single bits while leaving the other bits untouched.

 1#include <Arduino.h>
 2
 3uint8_t *port_d = (uint8_t*)0x002b;
 4uint8_t *ddr_d = (uint8_t*)0x002a;
 5uint8_t *pin_d = (uint8_t*)0x0029;
 6
 7void setup() {
 8
 9  // Configure pin 4 as output
10  *ddr_d |= (1 << 3);
11
12  // Configure pin 5 as input
13  *ddr_d |= (1 << 4);
14}
15
16void loop() {
17
18  // Set bit 3 high, while not touching any other bits
19  *port_d |= (1 << 3);
20
21  // Read the state of bit 4
22  if(((*pin_d >> 4) & 0x01) == 1){
23    delay(800);
24  }
25  else{
26    delay(200);
27  }
28
29  // Set bit 3 low, while not touching any other bits
30  *port_d &= ~(1 << 3);
31
32  delay(200);
33}

Exercise: analyze the above example

  1. Verify the operation of the above code listing by testing it on a Arduino UNO. Connect a push button with pull-down resistor to pin 4, and a LED to pin 3.

  2. Use your knowledge about digital electronics to analyze the above code listing. Especially the lines which read or manipulate the registers. Consider the case that PORD = 0b11000000, PIND = 0, and DDRD = 0, before the program starts. Write down your explanation for each statement.

Exercise: read a digital input on pin A5

The analog input pins can be reconfigured to work as digital input or output pins. In this exercise you are going to use the pin A5 as a digital input by means of direct manipulation of the memory registers.

  1. Use the pinout diagram, and the datasheet of the Atmega328p to figure out the memory addresses of the registers which controls the bank of pins of which pin A5 is a member.

  2. Configure pin A5 as an input.

  3. Read the state of the A5 pin, and blink a LED if A5 is high. If A5 is low the blinking should stop and the LED should be low. You may use the normal digitalWrite() function for the LED control.

Determine RAM memory usage

In many applications it is not possible to determine the RAM usage during compile time. It might depend on the amount of external input that the micocontroller receives.

The compiler defines some symbols that may be used to compute the RAM usage at run time.

__heap_start __brkval

extern unsigned int __bss_end;
extern unsigned int __heap_start;
extern void *__brkval;
int freeMemory() {
  int free_memory;

  if((int)__brkval == 0)
    free_memory = ((int)&free_memory) - ((int)&__bss_end);
  else
    free_memory = ((int)&free_memory) - ((int)__brkval);

  return free_memory;
}

Exercise: determine stack usage

There is a macro RAMEND somewhere in the libraries that are included as part of your Atmega328p project. This macro is defined as the hexadecimal value of the highest address in the internal RAM (which according to the memory map in the datasheet of the Atmega328p page 18, is 0x8FF). By allocating a new variable, and obtaining the address of this variable by means of the & operator, it is possible to calculate the number of bytes on the stack.

uint8_t variable_at_top_of_stack = 1;
uint16_t stack_size = RAMEND - (uint16_t)&variable_at_top_of_stack + 1;
//uint16_t stack_size = RAMEND - SP;
  1. Use the above code lines in a program to determine the number of bytes on the stack.

  2. Print the value to the UART.

  3. Add some new variables to the stack, and observe how the stack size value changes. You add variables to the stack simply by declaring them inside a function. Due to compiler optimization you have to either use the variables for something, or declare them as volatile. Otherwise the compiler will optimize them away, and your stack size will not change.

  4. The compiler may choose to organize the data in the stack in another order than it is declared in the code. Thus it might not always yield correct results to use the address of a variable. The third (commented) line in the above example uses the macor SP (stack pointer) to obtain the address to the top of the stack. This is probably a better method, but both are provided for

Using the EEPROM

Manage the memory stucture for complex projects

NAND Flash

NOR Flash

Using external memory

External EEPROM

SD-card

Special memory types

NVRAM

NVSRAM

NVSRAM is a special type of hybrid non-volatile random-access memory (NVRAM). One portion is a normal volatile SRAM, while the other is some form of EEPROM which has lower read and write speeds, but allows storing the memory contents in a non volatile manner.

EERAM is similar in concept to NVSRAM. Whenever it detects a power failure it will transfer the contents of the SRAM to a non volatile storage.

OTP EPROM

Historical memory types

In this section we will briefly discuss some of the memory technologies that are not used anymore.

Random access memory (RAM)