CHƯƠNG TRÌNH: tôi có phải sao chép dữ liệu từ flash sang RAM để đọc không?


8

Tôi đã có một số khó khăn trong việc hiểu quản lý bộ nhớ.

Tài liệu về Arduino cho biết, có thể giữ các hằng số như chuỗi hoặc bất cứ điều gì tôi không muốn thay đổi trong thời gian chạy trong bộ nhớ chương trình. Tôi nghĩ về nó như được nhúng ở đâu đó trong đoạn mã, điều này hoàn toàn có thể xảy ra trong một kiến ​​trúc von-Neumann. Tôi muốn sử dụng từ đó để làm cho menu UI của mình trên màn hình LCD.

Nhưng tôi hoang mang trước những hướng dẫn chỉ đọc và in dữ liệu từ bộ nhớ chương trình:

strcpy_P(buffer, (char*)pgm_read_word(&(string_table[i]))); // Necessary casts and dereferencing, just copy. 
    Serial.println( buffer );

Tại sao tôi phải sao chép nội dung chết tiệt vào RAM trước khi truy cập? Và nếu điều này là đúng, điều gì sẽ xảy ra với tất cả các mã sau đó? Có phải nó cũng được tải vào RAM trước khi thực hiện? Mã (32kiB) được xử lý như thế nào chỉ với RAM 2kiB? Những con yêu tinh nhỏ đó mang đĩa mềm ở đâu?

Và thậm chí thú vị hơn: Điều gì xảy ra với các hằng số theo nghĩa đen như trong biểu thức này:

a = 5*(10+7)

5, 10 và 7 có thực sự được sao chép vào RAM trước khi tải chúng vào thanh ghi không? Tôi không thể tin điều đó.


Một biến toàn cục được tải vào bộ nhớ và không bao giờ được giải phóng khỏi nó. Đoạn mã trên chỉ sao chép dữ liệu vào bộ nhớ khi cần và giải phóng nó khi hoàn tất. Cũng lưu ý rằng mã ở trên chỉ đọc một byte từ string_tablemảng. Mảng đó có thể là 20KB và sẽ không bao giờ vừa trong bộ nhớ (thậm chí là tạm thời). Tuy nhiên, bạn có thể tải chỉ một chỉ mục bằng phương pháp trên.
Gerben

@Gerben: Đây là một nhược điểm thực sự đối với các biến toàn cầu, tôi chưa tính đến điều này. Tôi đau đầu bây giờ. Và đoạn mã chỉ là một ví dụ từ tài liệu. Tôi kiềm chế chương trình sth. bản thân tôi trước khi làm rõ về các khái niệm. Nhưng tôi đã có một cái nhìn sâu sắc bây giờ. Cảm ơn!
Ariser - phục hồi Monica

Tôi tìm thấy tài liệu hơi khó hiểu khi lần đầu tiên đọc nó. Hãy thử nhìn vào một số ví dụ thực tế quá (ví dụ như một thư viện).
Gerben

Câu trả lời:


10

AVR là một họ kiến trúc Harvard đã được sửa đổi , vì vậy mã chỉ được lưu trữ trong flash, trong khi dữ liệu tồn tại chủ yếu trong RAM khi bị thao túng.

Với ý nghĩ đó, hãy giải quyết câu hỏi của bạn.

Tại sao tôi phải sao chép nội dung chết tiệt vào RAM trước khi truy cập?

Bạn không cần phải tìm kiếm, nhưng theo mặc định, mã giả định rằng dữ liệu nằm trong RAM trừ khi mã được sửa đổi để đặc biệt tìm trong flash cho nó (chẳng hạn như với strcpy_P()).

Và nếu điều này là đúng, điều gì sẽ xảy ra với tất cả các mã sau đó? Có phải nó cũng được tải vào RAM trước khi thực hiện?

Không. Kiến trúc Harvard. Xem trang Wikipedia để biết chi tiết đầy đủ.

Mã (32kiB) được xử lý như thế nào chỉ với RAM 2kiB?

Lời mở đầu được tạo bởi trình biên dịch sẽ sao chép dữ liệu cần được sửa đổi / sửa đổi thành SRAM trước khi chạy chương trình thực tế.

Những con yêu tinh nhỏ đó mang đĩa mềm ở đâu?

Không biết. Nhưng nếu bạn tình cờ nhìn thấy chúng thì tôi không thể làm gì để giúp đỡ.

... 5, 10 và 7 có thực sự được sao chép vào RAM trước khi tải chúng vào thanh ghi không?

Không Trình biên dịch đánh giá biểu thức tại thời gian biên dịch. Bất cứ điều gì khác xảy ra phụ thuộc vào các dòng mã xung quanh nó.


Ok, tôi không biết AVR là máy gặt. Nhưng tôi quen thuộc với khái niệm đó. Yêu tinh sang một bên, tôi nghĩ rằng tôi biết khi nào nên sử dụng các chức năng sao chép đó. Tôi phải hạn chế sử dụng PROGMEM cho dữ liệu hiếm khi được sử dụng để lưu chu kỳ CPU.
Ariser - phục hồi Monica

Hoặc sửa đổi mã của bạn để sử dụng nó trực tiếp từ flash.
Ignacio Vazquez-Abrams

Nhưng làm thế nào mã này sẽ trông như thế nào? giả sử tôi có một số mảng uint8_t đại diện cho các chuỗi tôi muốn đặt lên màn hình LCD thông qua SPI. const uint8_t test1[5]= { 0x54, 0x65, 0x73, 0x74, 0x31 }; const uint8_t bla[9]= { 0x62, 0x6c, 0x61, 0x62, 0x6c, 0x61, 0x62, 0x6c, 0x62 }; const uint8_t Menu[4]= { 0x3d, 0x65, 0x6e, 0x75};Làm cách nào để đưa dữ liệu này vào flash và sau này vào hàm SPI.transfer (), mất một uint8_t cho mỗi cuộc gọi.
Ariser - phục hồi Monica


8

Đây là cách Print::printin từ bộ nhớ chương trình trong thư viện Arduino:

size_t Print::print(const __FlashStringHelper *ifsh)
{
  const char PROGMEM *p = (const char PROGMEM *)ifsh;
  size_t n = 0;
  while (1) {
    unsigned char c = pgm_read_byte(p++);
    if (c == 0) break;
    n += write(c);
  }
  return n;
}

__FlashStringHelper*là một lớp trống cho phép các hàm quá tải như in để phân biệt một con trỏ tới bộ nhớ chương trình từ một đến bộ nhớ thông thường, vì cả hai đều được const char*trình biên dịch xem (xem /programming/16597437/arduino-f- what-does-it-really-do )

Vì vậy, bạn có thể quá tải printchức năng cho màn hình LCD của mình để nó có một __FlashStringHelper*đối số, hãy gọi nó LCD::printvà sau đó sử dụng lcd.print(F("this is a string in progmem"));' to call it.F () `là một macro đảm bảo chuỗi nằm trong bộ nhớ chương trình.

Để xác định trước chuỗi (để tương thích với in Arduino tích hợp) tôi đã sử dụng:

const char firmware_version_s[] PROGMEM = {"1.0.2"};
__FlashStringHelper* firmware_version = (__FlashStringHelper*) firmware_version_s;
...
Serial.println(firmware_version);

Tôi nghĩ rằng một sự thay thế sẽ là một cái gì đó như

size_t LCD::print_from_flash(const char *pgms)
{
  const char PROGMEM *p = (const char PROGMEM *) pgms;
  size_t n = 0;
  while (1) {
    unsigned char c = pgm_read_byte(p++);
    if (c == 0) break;
    n += write(c);
  }
  return n;
}

mà sẽ tránh các __FlashStringHelperdiễn viên.


2

Tài liệu về Arduino cho biết, có thể giữ các hằng số như chuỗi hoặc bất cứ điều gì tôi không muốn thay đổi trong thời gian chạy trong bộ nhớ chương trình.

Tất cả các hằng số ban đầu là trong bộ nhớ chương trình. Họ sẽ ở đâu khi mất điện?

Tôi nghĩ về nó như được nhúng ở đâu đó trong đoạn mã, điều này hoàn toàn có thể xảy ra trong một kiến ​​trúc von-Neumann.

Đó thực sự là kiến trúc Harvard .

Tại sao tôi phải sao chép nội dung chết tiệt vào RAM trước khi truy cập?

Bạn không. Trong thực tế, có một lệnh phần cứng (LPM - Bộ nhớ chương trình tải) để di chuyển dữ liệu trực tiếp từ bộ nhớ chương trình vào một thanh ghi.

Tôi có một ví dụ về kỹ thuật này trong đầu ra Arduino Uno sang màn hình VGA . Trong mã đó có một phông chữ bitmap được lưu trữ trong bộ nhớ chương trình. Nó được đọc từ đó một cách nhanh chóng và được sao chép vào đầu ra như thế này:

  // blit pixel data to screen    
  while (i--)
    UDR0 = pgm_read_byte (linePtr + (* messagePtr++));

Một sự phân tách của các dòng cho thấy (một phần):

  f1a:  e4 91           lpm r30, Z+
  f1c:  e0 93 c6 00     sts 0x00C6, r30

Bạn có thể thấy rằng một byte bộ nhớ chương trình đã được sao chép vào R30, và sau đó ngay lập tức được lưu vào thanh ghi USART UDR0. Không có RAM liên quan.


Tuy nhiên có một sự phức tạp. Đối với các chuỗi thông thường, trình biên dịch sẽ tìm thấy dữ liệu trong RAM chứ không phải PROGMEM. Chúng là các không gian địa chỉ khác nhau và do đó 0x200 trong RAM là một cái gì đó khác với 0x200 trong PROGMEM. Do đó, trình biên dịch gặp rắc rối khi sao chép các hằng số (như chuỗi) vào RAM khi khởi động chương trình, do đó không phải lo lắng về việc biết sự khác biệt sau này.

Mã (32kiB) được xử lý như thế nào chỉ với RAM 2kiB?

Câu hỏi hay. Bạn sẽ không tránh khỏi việc có nhiều hơn 2 KB chuỗi liên tục, vì sẽ không có chỗ để sao chép tất cả.

Đó là lý do tại sao những người đang viết những thứ như menu và các công cụ dài dòng khác, thực hiện các bước bổ sung để cung cấp cho chuỗi thuộc tính PROGMEM, vô hiệu hóa chúng được sao chép vào RAM.

Nhưng tôi hoang mang trước những hướng dẫn chỉ đọc và in dữ liệu từ bộ nhớ chương trình:

Nếu bạn thêm thuộc tính PROGMEM, bạn phải thực hiện các bước để cho trình biên dịch biết rằng các chuỗi này nằm trong một không gian địa chỉ khác. Tạo một bản sao hoàn chỉnh (tạm thời) là một cách. Hoặc chỉ in trực tiếp từ PROGMEM, một byte mỗi lần. Một ví dụ về điều đó là:

// Print a string from Program Memory directly to save RAM 
void printProgStr (const char * str)
{
  char c;
  if (!str) 
    return;
  while ((c = pgm_read_byte(str++)))
    Serial.print (c);
} // end of printProgStr

Nếu bạn truyền hàm này một con trỏ tới một chuỗi trong PROGMEM, thì nó sẽ "đọc đặc biệt" (pgm_read_byte) để kéo dữ liệu từ PROGMEM thay vì RAM và in ra nó. Lưu ý rằng điều này cần thêm một chu kỳ xung nhịp, mỗi byte.

Và thậm chí thú vị hơn: Điều gì xảy ra với các hằng số theo nghĩa đen như trong biểu thức này a = 5*(10+7)là 5, 10 và 7 thực sự được sao chép vào RAM trước khi tải chúng vào các thanh ghi? Tôi không thể tin điều đó.

Không, bởi vì họ không phải như vậy. Điều đó sẽ biên dịch thành một lệnh "nạp chữ vào thanh ghi". Hướng dẫn đó đã có trong PROGMEM, vì vậy nghĩa đen hiện đang được xử lý. Không cần phải sao chép nó vào RAM và sau đó đọc lại.


Tôi có một mô tả dài về những điều này trên trang Đưa dữ liệu không đổi vào bộ nhớ chương trình (PROGMEM) . Điều đó có mã ví dụ để thiết lập chuỗi và mảng chuỗi, một cách hợp lý dễ dàng.

Nó cũng đề cập đến macro F (), một cách dễ dàng để in đơn giản từ PROGMEM:

Serial.println (F("Hello, world"));

Một chút phức tạp của tiền xử lý cho phép biên dịch thành hàm trợ giúp, kéo các byte trong chuỗi từ PROGMEM một byte mỗi lần. Không cần sử dụng RAM trung gian.

Thật dễ dàng để sử dụng kỹ thuật đó cho những thứ khác ngoài Nối tiếp (ví dụ: LCD của bạn) bằng cách lấy bản in LCD từ lớp In.

Ví dụ, trong một trong những thư viện LCD tôi đã viết, tôi đã làm chính xác điều đó:

class I2C_graphical_LCD_display : public Print
{
...
    size_t write(uint8_t c);
};

Điểm mấu chốt ở đây là xuất phát từ In và ghi đè chức năng "ghi". Bây giờ chức năng ghi đè của bạn làm bất cứ điều gì nó cần để xuất ra một ký tự. Vì nó có nguồn gốc từ Print, giờ bạn có thể sử dụng macro F (). ví dụ.

lcd.println (F("Hello, world"));
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.