2020年6月11日 星期四

ROSA 2020 系統開發 3 ─ Arduino 的 String

http://4rdp.blogspot.com/2020/06/rosa-2020-3-arduino-string.html

最近開始動手重寫 ROSA (Robot Operating System for Arduino),原本想利用 Arduino 的字串函數來處理字串,發現它的函數庫尚未優化,會佔用很多記憶體,因此留文記錄問題,並提供 ROSA 的解決方案。

正式討論 ROSA 程式之前,先從資料結構說起,話說 String 是一串字元以零值結尾,可以表示成

String str1 = "123456789"; 它也可以是字元陣列,
char str2[10] = "123456789";   或
char str3[10] = {'1', '2', '3', '4', '5', '6', '7', '8', '9', 0};  或
char str4[10] = {0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x00};  或
char str5[10] = {49, 50, 51, 52, 53, 54, 55, 56, 57, 0}; 甚至可以是字元指標,
char* str6 = str2;   將 str6 指標指向 str2 字串的起頭。

了解上述概念後,我們寫幾個程式測試,你會更加清楚怎麼一回事。
void setup() {
  // put your setup code here, to run once:
  Serial.begin(57600);
  String str1 = "123456789";
  Serial.println(str1);
}

void loop() {
  // put your main code here, to run repeatedly:
}
這個程式編譯後,程式碼佔用 ROM 2642 bytes,RAM 使用 208 bytes

後面的程式僅更改 setup() 比較差異,
void setup() {
  // put your setup code here, to run once:
  Serial.begin(57600);
  char str2[10] = "123456789";     // ROM 1546 bytes, RAM 198 bytes
  Serial.println(str2);
}
當陣列改成 {'1', '2', '3', '4', '5', '6', '7', '8', '9', 0}、{0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x00}  或 {49, 50, 51, 52, 53, 54, 55, 56, 57, 0},都和 "123456789" 一樣。

void setup() {
  // put your setup code here, to run once:
  Serial.begin(57600);
  char str2[10] = "123456789";     // ROM 1546 bytes, RAM 198 bytes
  char* str6 = str2;
  Serial.println(str6);
}
使用指標也相同於陣列。
=====================================================

再來測試字串與數值混雜顯示的情形,
void setup() {
  // put your setup code here, to run once:
  Serial.begin(57600);
  int a = 255;
  char str2[4] = "ABCD";     // ROM 1576 bytes, RAM 196 bytes
  Serial.print(str2);
  Serial.print(a);
  Serial.println("EF");
}


void setup() {
  // put your setup code here, to run once:
  Serial.begin(57600);
  int a = 255;
  Serial.println(String("ABCD")+a+"EF");     // ROM 3132 bytes, RAM 206 bytes
}


void setup() {
  // put your setup code here, to run once:
  Serial.begin(57600);
  int a = 255;
  char str2[10];          // ROM 3020 bytes, RAM 196 bytes
  sprintf(str2, "ABCD%dEF", a);
  Serial.println(str2);
}


void setup() {
  // put your setup code here, to run once:
  Serial.begin(57600);
  int a = 255;
  Serial.println(STRING("ABCD%dEF", a));     // ROM 1728 bytes, RAM 206 bytes
}


void setup() {
  // put your setup code here, to run once:
  Serial.begin(57600);
  int a = 255;
  Serial.print(STRING("ABCD%dEF\n", a));     // ROM 1710 bytes, RAM 204 bytes
}
這個 STRING() 副程式是自己設計的,雖然無法像第一個例子 ROM size 那麼小,但是可以像 sprintf() 進行格式設定。

綜合上述比較,設計 STRING() 時,放棄使用 String 資料格式,改用 char 陣列或指標,另外使用 print() 不用 println(),現在僅提供三種整數格式轉換 %d, %nd, %0nd    n = 1~9。

#ifndef STRING_LENGTH
  #define STRING_LENGTH 20
#endif
char* STRING(char* fmt, int t) {
  char  s[STRING_LENGTH];      // 原先缺少 static 會讓函數無法正常多次執行
                                                      // 但現在有 static 會讓超音波偵測無法正常運作
  char  n[9] = "         ";
  char  a;
  char* si = s;
  byte  f = 0;   // 最後字串的起始位置
  byte  m = 0;   // 0 %d   1~9 %nd or %0nd
 
  while (bool (a = *(fmt+(f++)))) {
    if (a == '%') {
      goto found;
    }
    *(si++) = a;  // 暫存 % 之前字元  
  }
  goto end;

found: 
  while ((a = *(fmt+(f++))) != 'd') {
    m = a - '0';
    if (m == 0) {
      for (a = 8 ; a>=0 ; a--)
        n[a] = '0';
    } else if (m > 9)
      goto end;   // 無效轉換
  }
 
  if (t < 0) {
    *(si++) = '-';   // 負值
    if (m != 0)
      m--;
    t = -t;
  }
 
  a = 9;
  do {
    n[--a] = '0' + (t % 10);  // 轉換
    t /= 10;
  } while (t != 0);
  if (a > 9-m)
    a = 9-m;

  for ( ; a < 9 ; a++)
    *(si++) = n[a];           // 插入數字

  fmt += f;
  while (*(si++) = *(fmt++))  // 補 d 之後字串
    {}
  *si = 0;
  fmt = s;
end:
  return fmt;
}

STRING() 這段副程式放在 ROSA_BASE.cpp 程式中。

沒有留言:

張貼留言