아두이노 강좌 #28 2차원 배열 PROGMEM 매크로 사용 방법
Lucy Archive
Lucy 2023
2020. 8. 26. 01:32

PROGMEM 매트로 사용시 메모리 변화PROGMEM 매트로 사용시 메모리 변화

Arduino : PROGMEM 2D Array

지난 포스트에서는 아두이노의 SRAM 부족 방법을 해결하기 위해 PROGMEM 와 F() 매크로를 사용하여 전역 변수와 문자열을 FLASH MEMORY 에 저장하여 사용하는 방법을 소개하였습니다. 본 포스트에서는

PROGMEM을 사용하여 2차원 배열을 FLASH MEMORY 에 사용하는 법

에 대해 소개합니다.

문자열 배열에서 사용하기

아두이노에서 많은 양의 텍스트를 작업할 때 문자열을 배열로 설정하여 편리하게 사용할 수 있습니다. 문자열은 기본적으로 char* 의 배열이기 때문에, 여러 문자열을 배열로 저장하는 것은 사실상 2차원 배열입니다. 

코드

아래 코드는 아두이노 공식 사이트에서의 [각주:1]예제 코드를 약간 수정하였습니다. 수정한 내용은 주석을 삭제하고, 20번 Line 에서 string_table 배열에서 buffer 로 저장되는 코드를 수정하였습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const char string_0[] PROGMEM = "String 0";
const char string_1[] PROGMEM = "String 1";
const char string_2[] PROGMEM = "String 2";
const char string_3[] PROGMEM = "String 3";
const char string_4[] PROGMEM = "String 4";
const char string_5[] PROGMEM = "String 5";
 
const char *const string_table[] PROGMEM = {string_0, string_1, string_2, string_3, string_4, string_5};
 
char buffer[30];
 
void setup() {
  Serial.begin(9600);
  delay(2000);
  Serial.println("OK");
}
 
void loop() {
  for (int i = 0; i < 6; i++) {
    strcpy_P(buffer, pgm_read_byte_near(&(string_table[i])));
    Serial.println(buffer);
    delay(500);
  }
}
cs

코드 설명

  • 1~5번 라인은 출력할 문자열들을 저장하는 배열을 Flash Memory 에 저장하는 코드입니다.
  • 8 Line : 1~5 Line 에서 설정한 문자열을 배열로 감싸는 코드입니다. 문자열 자체가 배열이기 떄문에 string_table 은 2차원 배열이 됩니다.
  • 10 Line : buffer 는 출력할 메세지를 임시로 저장하는 버퍼로, 프로그램 실행시 SRAM 에 업로드 되어 사용됩니다.
  • 20 Line : string_table 메세지 테이블에서 buffer 로 데이터를 저장하는 코드 입니다. 기존 아두이노 사이트의 예제에서는 아래와 같은 코드를 사용했지만, pgm_read_word 는 지난 포스트의 16bit 데이터를 읽어 들이는 pgm_read_word_near 과 동일하고 최종적으로 (char *)로 캐스트하기 때문에 pgm_read_byte_near 로 사용해도 무방하다고 판단했습니다. 아두이노 에서 제공하는 예제대로 하셔도 무방합니다.

아두이노 예제 코드 : strcpy_P(buffer, (char *)pgm_read_word(&(string_table[i])));

※ 주의사항 : 위 코드와 같이 문자열을 테이블로 감싸는 배열은 const char *const 로 선언 되어야 합니다. const char *const 가 무슨 의미인지 알기 위해 몇시간을 알아보았으나 정확히 모르겠습니다. 아시는 분은 공유해주시면 감사하겠습니다.  2차원 배열을 만드는 방법도 위의 코드방식을 따라야 합니다. 아래와 같이 코드를 작성하는 경우 빌드는 되지만 정상 동작 하지 않습니다.

const char *const string_table[] PROGMEM = { "string_0" , "string_1", "string_2", "string_3", "string_4", "string_5"};


실행 결과

아래와 같이 String 0 ~ String 5 의 메세지가 순차적으로 표시되는 것을 볼 수 있습니다.

LED Matrix 코드에 적용하기

하단에 링크된 LED Matrix 코드에서 문제점은 프로그램 빌드시 SRAM 메모리가 70% 차지하여, 안정성에 문제가 생길 수 있습니다. SRAM 은 프로그램이 동작하면서 사용 메모리는 프로그램에 따라 변하기 떄문에, 메모리 부족으로 예상치 못하는 문제가 항상 발생 할 수 있습니다. 이를 해결하기 위해 본 포스트에서 소개한 PROGMEM 을 적용하여 코드를 작성하였습니다.

코드

수정된 코드를 일부만 넣을지, 고민을 하다가 전체 코드를 넣기로 했습니다. 

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
#define BUADRATE 115200
#define BUFFERSIZE 40
#define SCROLLTIME 50
 
// Coloumn Pin 할당
#define C1 9
#define C2 12
#define C3 7
#define C4 13
#define C5 A2
#define C6 6
#define C7 3
#define C8 A5
// Row Pin 할당
#define R1 2
#define R2 11
#define R3 10
#define R4 A4
#define R5 8
#define R6 A3
#define R7 4
#define R8 5
 
// Bit 추출 메크로 : data 에서 loc 번째 비트 부터 area 영역 만큼 추출
// ex) extract_bits( 0b00000010, 1, 2) 결과 1 -> 0b00000010 데이터의 2번째 비트 1크기 만큼 추출  
#define extract_bits(data, area, loc)   (((data) >> (loc)) & (area))
 
int colPin[8= {C1, C2, C3, C4, C5, C6, C7, C8};
int rowPin[8= {R1, R2, R3, R4, R5, R6, R7, R8};
 
const byte font[128][8] PROGMEM = {
    { 0x000x000x000x000x000x000x000x00},   // U+0000 (nul)
    { 0x000x000x000x000x000x000x000x00},   // U+0001
    { 0x000x000x000x000x000x000x000x00},   // U+0002
    { 0x000x000x000x000x000x000x000x00},   // U+0003
    { 0x000x000x000x000x000x000x000x00},   // U+0004
    { 0x000x000x000x000x000x000x000x00},   // U+0005
    { 0x000x000x000x000x000x000x000x00},   // U+0006
    { 0x000x000x000x000x000x000x000x00},   // U+0007
    { 0x000x000x000x000x000x000x000x00},   // U+0008
    { 0x000x000x000x000x000x000x000x00},   // U+0009
    { 0x000x000x000x000x000x000x000x00},   // U+000A
    { 0x000x000x000x000x000x000x000x00},   // U+000B
    { 0x000x000x000x000x000x000x000x00},   // U+000C
    { 0x000x000x000x000x000x000x000x00},   // U+000D
    { 0x000x000x000x000x000x000x000x00},   // U+000E
    { 0x000x000x000x000x000x000x000x00},   // U+000F
    { 0x000x000x000x000x000x000x000x00},   // U+0010
    { 0x000x000x000x000x000x000x000x00},   // U+0011
    { 0x000x000x000x000x000x000x000x00},   // U+0012
    { 0x000x000x000x000x000x000x000x00},   // U+0013
    { 0x000x000x000x000x000x000x000x00},   // U+0014
    { 0x000x000x000x000x000x000x000x00},   // U+0015
    { 0x000x000x000x000x000x000x000x00},   // U+0016
    { 0x000x000x000x000x000x000x000x00},   // U+0017
    { 0x000x000x000x000x000x000x000x00},   // U+0018
    { 0x000x000x000x000x000x000x000x00},   // U+0019
    { 0x000x000x000x000x000x000x000x00},   // U+001A
    { 0x000x000x000x000x000x000x000x00},   // U+001B
    { 0x000x000x000x000x000x000x000x00},   // U+001C
    { 0x000x000x000x000x000x000x000x00},   // U+001D
    { 0x000x000x000x000x000x000x000x00},   // U+001E
    { 0x000x000x000x000x000x000x000x00},   // U+001F
    { 0x000x000x000x000x000x000x000x00},   // U+0020 (space)
    { 0x180x3C0x3C0x180x180x000x180x00},   // U+0021 (!)
    { 0x360x360x000x000x000x000x000x00},   // U+0022 (")
    { 0x360x360x7F0x360x7F0x360x360x00},   // U+0023 (#)
    { 0x0C0x3E0x030x1E0x300x1F0x0C0x00},   // U+0024 ($)
    { 0x000x630x330x180x0C0x660x630x00},   // U+0025 (%)
    { 0x1C0x360x1C0x6E0x3B0x330x6E0x00},   // U+0026 (&)
    { 0x060x060x030x000x000x000x000x00},   // U+0027 (')
    { 0x180x0C0x060x060x060x0C0x180x00},   // U+0028 (()
    { 0x060x0C0x180x180x180x0C0x060x00},   // U+0029 ())
    { 0x000x660x3C0xFF0x3C0x660x000x00},   // U+002A (*)
    { 0x000x0C0x0C0x3F0x0C0x0C0x000x00},   // U+002B (+)
    { 0x000x000x000x000x000x0C0x0C0x06},   // U+002C (,)
    { 0x000x000x000x3F0x000x000x000x00},   // U+002D (-)
    { 0x000x000x000x000x000x0C0x0C0x00},   // U+002E (.)
    { 0x600x300x180x0C0x060x030x010x00},   // U+002F (/)
    { 0x3E0x630x730x7B0x6F0x670x3E0x00},   // U+0030 (0)
    { 0x0C0x0E0x0C0x0C0x0C0x0C0x3F0x00},   // U+0031 (1)
    { 0x1E0x330x300x1C0x060x330x3F0x00},   // U+0032 (2)
    { 0x1E0x330x300x1C0x300x330x1E0x00},   // U+0033 (3)
    { 0x380x3C0x360x330x7F0x300x780x00},   // U+0034 (4)
    { 0x3F0x030x1F0x300x300x330x1E0x00},   // U+0035 (5)
    { 0x1C0x060x030x1F0x330x330x1E0x00},   // U+0036 (6)
    { 0x3F0x330x300x180x0C0x0C0x0C0x00},   // U+0037 (7)
    { 0x1E0x330x330x1E0x330x330x1E0x00},   // U+0038 (8)
    { 0x1E0x330x330x3E0x300x180x0E0x00},   // U+0039 (9)
    { 0x000x0C0x0C0x000x000x0C0x0C0x00},   // U+003A (:)
    { 0x000x0C0x0C0x000x000x0C0x0C0x06},   // U+003B (//)
    { 0x180x0C0x060x030x060x0C0x180x00},   // U+003C (<)
    { 0x000x000x3F0x000x000x3F0x000x00},   // U+003D (=)
    { 0x060x0C0x180x300x180x0C0x060x00},   // U+003E (>)
    { 0x1E0x330x300x180x0C0x000x0C0x00},   // U+003F (?)
    { 0x3E0x630x7B0x7B0x7B0x030x1E0x00},   // U+0040 (@)
    { 0x0C0x1E0x330x330x3F0x330x330x00},   // U+0041 (A)
    { 0x3F0x660x660x3E0x660x660x3F0x00},   // U+0042 (B)
    { 0x3C0x660x030x030x030x660x3C0x00},   // U+0043 (C)
    { 0x1F0x360x660x660x660x360x1F0x00},   // U+0044 (D)
    { 0x7F0x460x160x1E0x160x460x7F0x00},   // U+0045 (E)
    { 0x7F0x460x160x1E0x160x060x0F0x00},   // U+0046 (F)
    { 0x3C0x660x030x030x730x660x7C0x00},   // U+0047 (G)
    { 0x330x330x330x3F0x330x330x330x00},   // U+0048 (H)
    { 0x1E0x0C0x0C0x0C0x0C0x0C0x1E0x00},   // U+0049 (I)
    { 0x780x300x300x300x330x330x1E0x00},   // U+004A (J)
    { 0x670x660x360x1E0x360x660x670x00},   // U+004B (K)
    { 0x0F0x060x060x060x460x660x7F0x00},   // U+004C (L)
    { 0x630x770x7F0x7F0x6B0x630x630x00},   // U+004D (M)
    { 0x630x670x6F0x7B0x730x630x630x00},   // U+004E (N)
    { 0x1C0x360x630x630x630x360x1C0x00},   // U+004F (O)
    { 0x3F0x660x660x3E0x060x060x0F0x00},   // U+0050 (P)
    { 0x1E0x330x330x330x3B0x1E0x380x00},   // U+0051 (Q)
    { 0x3F0x660x660x3E0x360x660x670x00},   // U+0052 (R)
    { 0x1E0x330x070x0E0x380x330x1E0x00},   // U+0053 (S)
    { 0x3F0x2D0x0C0x0C0x0C0x0C0x1E0x00},   // U+0054 (T)
    { 0x330x330x330x330x330x330x3F0x00},   // U+0055 (U)
    { 0x330x330x330x330x330x1E0x0C0x00},   // U+0056 (V)
    { 0x630x630x630x6B0x7F0x770x630x00},   // U+0057 (W)
    { 0x630x630x360x1C0x1C0x360x630x00},   // U+0058 (X)
    { 0x330x330x330x1E0x0C0x0C0x1E0x00},   // U+0059 (Y)
    { 0x7F0x630x310x180x4C0x660x7F0x00},   // U+005A (Z)
    { 0x1E0x060x060x060x060x060x1E0x00},   // U+005B ([)
    { 0x030x060x0C0x180x300x600x400x00},   // U+005C (\)
    { 0x1E0x180x180x180x180x180x1E0x00},   // U+005D (])
    { 0x080x1C0x360x630x000x000x000x00},   // U+005E (^)
    { 0x000x000x000x000x000x000x000xFF},   // U+005F (_)
    { 0x0C0x0C0x180x000x000x000x000x00},   // U+0060 (`)
    { 0x000x000x1E0x300x3E0x330x6E0x00},   // U+0061 (a)
    { 0x070x060x060x3E0x660x660x3B0x00},   // U+0062 (b)
    { 0x000x000x1E0x330x030x330x1E0x00},   // U+0063 (c)
    { 0x380x300x300x3e0x330x330x6E0x00},   // U+0064 (d)
    { 0x000x000x1E0x330x3f0x030x1E0x00},   // U+0065 (e)
    { 0x1C0x360x060x0f0x060x060x0F0x00},   // U+0066 (f)
    { 0x000x000x6E0x330x330x3E0x300x1F},   // U+0067 (g)
    { 0x070x060x360x6E0x660x660x670x00},   // U+0068 (h)
    { 0x0C0x000x0E0x0C0x0C0x0C0x1E0x00},   // U+0069 (i)
    { 0x300x000x300x300x300x330x330x1E},   // U+006A (j)
    { 0x070x060x660x360x1E0x360x670x00},   // U+006B (k)
    { 0x0E0x0C0x0C0x0C0x0C0x0C0x1E0x00},   // U+006C (l)
    { 0x000x000x330x7F0x7F0x6B0x630x00},   // U+006D (m)
    { 0x000x000x1F0x330x330x330x330x00},   // U+006E (n)
    { 0x000x000x1E0x330x330x330x1E0x00},   // U+006F (o)
    { 0x000x000x3B0x660x660x3E0x060x0F},   // U+0070 (p)
    { 0x000x000x6E0x330x330x3E0x300x78},   // U+0071 (q)
    { 0x000x000x3B0x6E0x660x060x0F0x00},   // U+0072 (r)
    { 0x000x000x3E0x030x1E0x300x1F0x00},   // U+0073 (s)
    { 0x080x0C0x3E0x0C0x0C0x2C0x180x00},   // U+0074 (t)
    { 0x000x000x330x330x330x330x6E0x00},   // U+0075 (u)
    { 0x000x000x330x330x330x1E0x0C0x00},   // U+0076 (v)
    { 0x000x000x630x6B0x7F0x7F0x360x00},   // U+0077 (w)
    { 0x000x000x630x360x1C0x360x630x00},   // U+0078 (x)
    { 0x000x000x330x330x330x3E0x300x1F},   // U+0079 (y)
    { 0x000x000x3F0x190x0C0x260x3F0x00},   // U+007A (z)
    { 0x380x0C0x0C0x070x0C0x0C0x380x00},   // U+007B ({)
    { 0x180x180x180x000x180x180x180x00},   // U+007C (|)
    { 0x070x0C0x0C0x380x0C0x0C0x070x00},   // U+007D (})
    { 0x6E0x3B0x000x000x000x000x000x00},   // U+007E (~)
    { 0x000x000x000x000x000x000x000x00}    // U+007F
};
 
byte displayData[8]; // 최종 8x8 출력 할 데이터
byte stringBuffer[BUFFERSIZE][8]; // 스크롤 할 데이터를 저장할 버퍼
 
int iTextLenth;
int scrollTime = SCROLLTIME; // scrollTime 만큼 한 글자씩 시프트
 
String myString; 
 
bool recFlag = false// 시리얼로 문자열 입력 받은 경우 활성화 되는 Flag
bool changeFlag = false// LED Matrix 에 출력할 데이터가 변경되어야 하는 경우 활성화 되는 Flag
 
void setup() {
 
    String initialString = "INPUT STRING";
    iTextLenth = initialString.length();
    
    for(int i = 0 ; i < 8 ; i++)
    {
        pinMode(colPin[i], OUTPUT);
        digitalWrite(colPin[i], LOW); // Column 핀은 LOW 로 초기화
        pinMode(rowPin[i], OUTPUT);
        digitalWrite(rowPin[i], HIGH); // Row 핀은 HIGH 로 초기화
    }
    
    for(int i = 1 ; i < iTextLenth+1 ; i++){
        for(int j = 0 ; j < 8 ; j++){
            stringBuffer[i][j] = pgm_read_byte(&(font[initialString.charAt(i-1)][j]));
        }
    }
    
    Serial.begin(BUADRATE);
    while(!Serial
    {
        ;
    }
    Serial.println(F("8x8 LED Dot Matrix Program Start.."));
    delay(500);
    Serial.print(F("Input String >> "));
}
 
void loop() {
 
    /*
        시리얼 통신으로 문자열을 입력 받은 경우 실행되는 if문
    */
    if(recFlag == true)
    {
        recFlag = false;
        iTextLenth = myString.length();
 
        for(int i = 0 ; i < BUFFERSIZE ; i++){
            for(int j = 0; j < 8; j++){
                stringBuffer[i][j] = 0;
            }
        }
        for(int i = 1 ; i < iTextLenth+1 ; i++){
            for(int j = 0 ; j < 8 ; j++){
                stringBuffer[i][j] = pgm_read_byte(&(font[myString.charAt(i-1)][j]));
            }
        }
 
        Serial.println(F("String Output Completed!"));
        Serial.print(F("Input String >> "));
        myString = "";
        changeFlag = true;
    }
 
    unsigned long now = millis();
    static unsigned long pastTime;
 
    /*
        scrollTime 시간 마다 실행되는 if문
        scrollTime 마다 최종 출력 데이터 displayData 를 한칸씩 시프트합니다.
    */
    if(now - pastTime >= scrollTime){
        pastTime = now;
        static int count;
        static int index;
        if(changeFlag == 1){
            changeFlag = 0;
            count = 0;
            index = 0;
        }
        for(int i = 0 ; i < 8 ; i++){
            displayData[i] = (stringBuffer[index][i] >> count) | (stringBuffer[index+1][i] << 8 - count);
        }
        count++;
        if(count == 8) {
            count = 0;
            index++;
            if(index == iTextLenth + 1) index = 0;
        }
    }
 
    for(int i = 0 ; i < 8 ; i++)
    {
        for(int j = 0 ; j < 8 ; j++)
        {
            digitalWrite(colPin[j], extract_bits(displayData[i],0x01,j)); // Coloumn 핀에 데이터 쓰기
        }
        digitalWrite(rowPin[i], LOW); // i 행 활성화
        delayMicroseconds(100);
        digitalWrite(rowPin[i], HIGH); // i 행 비활성화
    }
}
 
void serialEvent() // 엔터가 입력되기 까지의 문자를 myString 에 저장하고 recFlag 를 초기화
{
    char myChar = (char)Serial.read();
    myString += myChar;
    if(myChar == '\n')
    {
        recFlag = true;
    }
}
cs

코드 설명

  • 31 Line : Font Data 를 Flash Memory 로 저장하는 코드 입니다.
  • 188, 219 Line : Flash 메모리에 저장된 Font 데이터를 LED Matrix 출력버퍼에 저장하는 코드 입니다.
  • 기타 : Serial.print 와 같은 시리얼 출력 코드에 사용되는 문자열은 F() 메크로를 적용하였습니다.

결과 비교

프로그램 실행시 동작은 동일하기 때문에 생략하고, 메모리 사용율 결과만 안내드립니다. 기존 코드를 사용시 SRAM 사용율이 70% 로 여유 공간이 없었으나, PROGMEMF() 메크로 사용시 28% 로 사용 공간이 줄어 들었습니다. SRAM 사용 공간이 줄어든 만큼 다른 장치 제어 또는 코드 작성이 가능해집니다. 

PROGMEM 매트로 사용시 메모리 변화PROGMEM 매트로 사용시 메모리 변화


마무리

본 포스트에서 동적 메모리 용량 부족 문제를 해결하기 위해

2차원 배열을 PROGMEM 매크로로 FLASH에 저장하는 방법을 소개하고, 지난 포스트의 LED Matrix 코드에 적용하여 SRAM 사용율이 줄어드는 것을 확인

하였습니다.

끝까지 읽어 주셔서 감사합니다.😊

관련포스트

👉 아두이노 SRAM 용량 부족 문제 관련글 목록 보기

👉 아두이노 LED Matrix 관련글 목록 보기

👉 아두이노 관련글 전체 목록 보기

  1. https://www.arduino.cc/reference/en/language/variables/utilities/progmem/ [본문으로]