Post

Memory layout trên ATmega328p

Memory layout trên ATmega328p

👋 I. Lời mở đầu

Trong bài viết này, tôi sẽ nói về bộ nhớ trên vi điều khiển ATmega328p (Arduino Uno). Qua đó có thể học được cách mà một vi điều khiển phân chia các vùng RAM để trữ các biến trong chương trình.

🌱 II. Bộ nhớ vi điều khiển

Memory của ATmega328p như sau:

  • Flash 32k byte. (Bộ nhớ chương trình)
  • SRAM 2k byte.
  • EEPROM 1k byte.

Bộ nhớ Flash là nơi chúng ta lưu trữ chương trình khi viết. Trong số đó, có 500 byte dành cho bootloader. Bộ nhớ Flash và EEPROM là các vùng lưu trữ không mất dữ liệu (non-volatile) , nghĩa là khi ta tắt chương trình thì dữ liệu vẫn còn đó.

Trong khi đó SRAM là vùng nhớ volatile và được truy cập khi chương trình chạy. SRAM chứa các biến static, global, local, heap.

Hình ảnh. https://embeddedwala.com/Blogs/embedded-c/memory-layout-of-c-program

Các phân vùng của SRAM

  • .data: int a = 1 (global), static int b = 2 (global) (local).
  • .bss: int a or int a = 0 (global), static int b or static in b = 0 (global) (local).
  • stack: int a = 6 (local).
  • heap: malloc(), free().

1. Vùng .data

Môi trường thử nghiệm: Arduino IDE và board Arduino Uno (Atmega328p).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int initialization_variable = 1;
int *p;

static int static_initialization_variable = 2;
int *p2;

void setup() {
  p = &initialization_variable;
  p2 = &static_initialization_variable; 


  Serial.begin(9600);
}

int _delay = 0;
void loop() { 
  if(_delay < 1)
  {
    Serial.print("Address of initialization variable: 0x");
    Serial.println((uint16_t)p,HEX);

    Serial.print("Address of static initialization variable: 0x");
    Serial.println((uint16_t)p2,HEX);

  }
  _delay++;
  delay(1000);
}

OUTPUT:

1
2
Address of initialization variable: 0x102
Address of static initialization variable: 0x100

Dựa vào kết quả trên, tôi có phán đoán là vùng .data của atmega328p bắt đầu từ địa chỉ: 0x100.

Câu hỏi: Tại vì sao .data lại bắt đầu từ 0x100 ?

2. Vùng .bss

Môi trường thử nghiệm: Arduino IDE và board Arduino Uno (Atmega328p).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int Uninitialized_variable;
int *p;

static int static_Uninitialized_variable;
int *p2;

void setup() {
  p = &Uninitialized_variable;
  p2 = &static_Uninitialized_variable; 


  Serial.begin(9600);
}

int _delay = 0;
void loop() { 
  if(_delay < 1)
  {
    Serial.print("Address of Uninitialized variable: 0x");
    Serial.println((uint16_t)p,HEX);

    Serial.print("Address of static Uninitialized_variable: 0x");
    Serial.println((uint16_t)p2,HEX);

  }
  _delay++;
  delay(1000);
}

OUTPUT:

1
2
Address of Uninitialized variable: 0x16A
Address of static Uninitialized_variable: 0x168

Dựa vào kết quả trên, tôi có phán đoán là vùng .bss của atmega328p bắt đầu từ địa chỉ: 0x168.

Câu hỏi: Tại vì sao .bss lại bắt đầu từ 0x168 ?

3. Vùng heap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
uint8_t *p;

void setup() {

  p = malloc(20);
  Serial.begin(9600);

}

int _delay = 0;
void loop() { 
  if(_delay < 1)
  {
    Serial.print("Start address of p: 0x");
    Serial.println((uint16_t)p,HEX);

    Serial.print("End address of p: 0x");
    Serial.println((uint16_t)(p + 20),HEX);

  }
  _delay++;
  delay(1000);
}

OUTPUT:

1
2
Start address of p: 0x1F8
End address of p: 0x20C

Qua kết quả trên, tôi có nhận định:

  • Vùng heap bắt đầu từ địa chỉ 0x1F8.
  • Khi lấy 0x20C - 0x1F8 = 20 byte -> Điều này chứng minh được cách hoạt động đúng của malloc(20) là cấp phát 20 byte.

Câu hỏi: Tại sao vùng heap lại bắt đầu tại địa chỉ 0x1F8.

4. Vùng stack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

void setup() {
  int i;      // biến local chưa khởi tạo
  int *p;

  p = &i;

  int i2 = 1; // biến local được khởi tạo
  int *p2;

  p2 = &i2;

  Serial.begin(9600);

  Serial.print("Address of i: 0x");
  Serial.println((uint16_t)p, HEX);

  Serial.print("Address of i2: 0x");
  Serial.println((uint16_t)p2, HEX);


}


void loop() { 

  delay(1000);
}

OUTPUT:

1
2
Address of i: 0x8FA
Address of i2: 0x8F8

Qua kết quả, tôi thấy được vùng stack bắt đầu tại: 0x8F8.

Câu hỏi: Tại sao vùng stack lại bắt đầu tại địa chỉ 0x8F8 ?

5. Vùng .rodata

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const char* msg = "Hello";  // Chuỗi hằng - sẽ nằm trong .rodata (flash)

void setup() {
  Serial.begin(9600);
  while (!Serial);

  Serial.print("Address of msg pointer: 0x");
  Serial.println((uintptr_t)&msg, HEX);

  Serial.print("Address msg points to: 0x");
  Serial.println((uintptr_t)msg, HEX);

  Serial.print("First character: ");
  Serial.println(*msg);  // In ra 'H'
}

void loop() {
  // Don't use
}

Output:

1
2
3
Address of msg pointer: 0x100
Address msg points to: 0x15E
First character: H
RegionSizeStartEnd
Flash32 KB0x00000x7FFF
SRAM2 KB0x01000x08FF

Xem kết quả

  • Địa chỉ con trỏ 0x100: con trỏ nằm trong vùng SRAM.
  • Địa chỉ bắt đầu của chuỗi 0x15E < 0x8000, nằm trong vùng 0x0000 -> 0x7FFF -> Chứng minh được const string nằm trong vùng Flash, cụ thể là .rodata.

TÌM HIỂU VỀ ARDUINO CLI để trả lời những câu hỏi trên.

Refrence

  • https://www.teachmemicro.com/arduino-programming-pointers/
This post is licensed under CC BY 4.0 by the author.