[Embedded Software] ARM Cortex-M : printf()를 만들어 보자

C언어를 배울 때 가장 먼저 사용하는 함수는 printf()일것이다.  또한 C프로그램에서 가장 많이 불리워지는 함수도 printf()일것이다.  그러나 이렇게 많이 사용하고 있는 printf() 함수가 실제로 어떻게 생겼는지 그 소스를 찾아본적은 거의 없을 것이다.

이번 글에서는 printf() 함수를 라이브러리에서 제공하는 것을 사용하지 않고 직접 만들어 보기로 하겠다.

printf() 함수의 소스 코드는 인터넷에서 검색하면 어렵지 않게 찾을 수 있을 것이다.  이 글에서는 BSD-lite 안에 들어 있는 코드를 참조하였다.

아직 C언어에 익숙하지 않은 프로그래머가 보기에는 소스코드가 다소 복잡하게 보일것이다.  따라서 이 코드를 바로 임베디드 시스템에 적용하기에 앞서 visual studio나 리눅스 환경에서 미리 검증한 후 실제 임베디드 보드에서 확인하는것이 좋다.

 

아래 코드는 visual studio에서 동작 확인을 위하여 만들어 본 코드이다.

main.c  main.c

 

직접 만들어 본 코드에 문제가 없음을 확인하였으면 실제 보드에서 동작 시켜 보도록 하겠다.

먼저 해야 할 일은 이제 더이상 컴파일러에 포함되어 있는 C라이브러리가 프로젝트 빌드시에 포함되지 않도록 하기 위하여 Makefile을 수정하여야 한다.

1
2
3
4
5
6
7
8
9
gccincdir := $(shell $(CC) -print-file-name=include)
 
CFLAGS := -g -Os -ffunction-sections -fdata-sections -Wall -std=gnu99
CFLAGS += -fno-builtin -ffreestanding
CFLAGS += -mthumb -mcpu=cortex-m3
CFLAGS += -Iinc -nostdinc -isystem $(gccincdir)
 
LDFLAGS := -L $(shell dirname `$(CC) $(CFLAGS) -print-libgcc-file-name`)
LDFLAGS += -nostdlib

6번줄에 있는 -nostdinc 옵션을 이용하여 라이브러리에서 제공하는 include 파일을 사용하지 않겠다는것을 알려 준다.  하지만, GCC 컴파일러에서 제공하는 헤더 파일은 사용하여야 하기 때문에 -isystem 옵션으로 GCC에서 제공하는 헤더파일을 include 시킨다.  GCC 헤더 파일의 위치는 1번줄에서 표현된 방법으로 그 디렉토리를 알 수 있다.

그리고, 링크시에 라이브러리에서 제공하는 코드가 포함되지 않도록 하기 위하여 9번 줄에서처럼 -nostdlib 옵션을 사용하였다.

컴파일과 링크 단계에서 -nostdinc와 -nostdlib 옵션을 주게 되면 더이상 라이브러리에서 제공하는 코드를 사용할 수 없게 되므로 프로그래머가 직접 해당 함수와 헤더 파일을 만들어야 한다.

원본 코드를 참조하여 만들어 본 printf() 함수의 코드는 다음과 같다.

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
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
#include <stdarg.h>
#include <stdint.h>
#include <stddef.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
 
#define UART_BUF_SIZE   256
 
char uart_buf[UART_BUF_SIZE];
 
 
#define to_digit(c)     ((c) - '0')
#define is_digit(c)     ((unsigned)to_digit(c) <= 9)
#define to_char(n)      ((n) + '0')
 
#define BUF             68
 
#define ALT             0X001
#define HEXPREFIX       0X002
#define LADJUST         0X004
#define LONGDBL         0X008
#define LONGINT         0X010
#define QUADINT         0X020
#define SHORTINT        0X040
#define ZEROPAD         0X080
#define FPT             0X100
 
 
 
static char *__ultoa(register uint32_t  val,
                     char               *endp,
                     int                base,
                     int                octzero,
                     char               *xdigs)
{
    register char       *cp = endp;
    register int32_t    sval;
 
    switch (base)
    {
        case 8:
            do
            {
                *--cp = to_char(val & 7);
                val >>= 3;
            } while(val);
            if (octzero && *cp != '0')
                *--cp = '0';
            break;
 
        case 10:
            if (val < 10)
            {
                *--cp = to_char(val);
                return cp;
            }
            if (val > LONG_MAX)
            {
                *--cp = to_char(val % 10);
                sval = val / 10;
            }
            else
                sval = val;
 
            do
            {
                *--cp = to_char(sval % 10);
                sval /= 10;
            } while(sval != 0);
            break;
 
        case 16:
            do
            {
                *--cp = xdigs[val & 15];
                val >>= 4;
            } while(val);
            break;
 
        default:
            break;
    }
 
    return cp;
}
 
static char *__uqtoa(register uint64_t  val,
                     char               *endp,
                     int                base,
                     int                octzero,
                     char               *xdigs)
{
    register char       *cp = endp;
    register int64_t    sval;
 
    if (val <= ULONG_MAX)
        return __ultoa((uint32_t)val, endp, base, octzero, xdigs);
 
    switch (base)
    {
        case 8:
            do
            {
                *--cp = to_char(val & 7);
                val >>= 3;
            } while(val);
            if (octzero && *cp != '0')
                *--cp = '0';
            break;
 
        case 10:
            if (val < 10)
            {
                *--cp = to_char(val % 10);
                return cp;
            }
            if (val > QUAD_MAX)
            {
                *--cp = to_char(val % 10);
                sval = val / 10;
            }
            else
                sval = val;
 
            do
            {
                *--cp = to_char(sval % 10);
                sval /= 10;
            } while(sval != 0);
            break;
 
        case 16:
            do
            {
                *--cp = xdigs[val & 15];
                val >>= 4;
            } while(val);
            break;
 
        default:
            break;
    }
 
    return cp;
}
 
int vprintf(char *strbuf, const char *format, va_list ap)
{
    register char   *fmt;
    register char   *cp;
    register char   ch;
    register int    n;
    register int    flags;
    char            sign;
    int             ret;
    int             width;
    int             prec;
    int             dprec = 0;
    int             size;
    int             fieldsz;
    int             realsz;
    int             base;
    char            *xdigs = NULL;
    uint32_t        ulval = 0;
    uint64_t        uqval = 0;
    char            buf[BUF];
    char            ox[2];
 
 
#define PADSIZE 16
    static char blanks[PADSIZE] =
     {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};
    static char zeroes[PADSIZE] =
     {'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'};
 
#define PRINT(ptr, len)                 \
    do {                                \
        int     i;                      \
        for (i=0; i<len; i++)           \
            *strbuf++ = ptr[i];         \
    }while(0)
 
#define PAD(howmany, with)              \
    do {                                \
        if ((n = (howmany)) > 0) {      \
            while (n > PADSIZE) {       \
                PRINT(with, PADSIZE);   \
                n -= PADSIZE;           \
            }                           \
            PRINT(with, n);             \
        }                               \
    }while(0)
 
#define SARG()                                          \
    (flags & LONGINT ? va_arg(ap, long) :               \
     flags & SHORTINT ? (long)(short)va_arg(ap, int) :  \
     (long)va_arg(ap, int))
#define UARG()                                                  \
    (flags & LONGINT ? va_arg(ap, uint32_t) :                   \
     flags & SHORTINT ? (uint32_t)(uint16_t)va_arg(ap, int) :   \
     (uint32_t)va_arg(ap, uint32_t))
 
 
    fmt = (char *)format;
    ret = 0;
 
    while (1)
    {
        for (cp = fmt; (ch = *fmt) != '

[Embedded Software] ARM Cortex-M : printf()를 만들어 보자

C언어를 배울 때 가장 먼저 사용하는 함수는 printf()일것이다.  또한 C프로그램에서 가장 많이 불리워지는 함수도 printf()일것이다.  그러나 이렇게 많이 사용하고 있는 printf() 함수가 실제로 어떻게 생겼는지 그 소스를 찾아본적은 거의 없을 것이다.

이번 글에서는 printf() 함수를 라이브러리에서 제공하는 것을 사용하지 않고 직접 만들어 보기로 하겠다.

printf() 함수의 소스 코드는 인터넷에서 검색하면 어렵지 않게 찾을 수 있을 것이다.  이 글에서는 BSD-lite 안에 들어 있는 코드를 참조하였다.

아직 C언어에 익숙하지 않은 프로그래머가 보기에는 소스코드가 다소 복잡하게 보일것이다.  따라서 이 코드를 바로 임베디드 시스템에 적용하기에 앞서 visual studio나 리눅스 환경에서 미리 검증한 후 실제 임베디드 보드에서 확인하는것이 좋다.

 

아래 코드는 visual studio에서 동작 확인을 위하여 만들어 본 코드이다.

main.c  main.c

 

직접 만들어 본 코드에 문제가 없음을 확인하였으면 실제 보드에서 동작 시켜 보도록 하겠다.

먼저 해야 할 일은 이제 더이상 컴파일러에 포함되어 있는 C라이브러리가 프로젝트 빌드시에 포함되지 않도록 하기 위하여 Makefile을 수정하여야 한다.

1
2
3
4
5
6
7
8
9
gccincdir := $(shell $(CC) -print-file-name=include)
 
CFLAGS := -g -Os -ffunction-sections -fdata-sections -Wall -std=gnu99
CFLAGS += -fno-builtin -ffreestanding
CFLAGS += -mthumb -mcpu=cortex-m3
CFLAGS += -Iinc -nostdinc -isystem $(gccincdir)
 
LDFLAGS := -L $(shell dirname `$(CC) $(CFLAGS) -print-libgcc-file-name`)
LDFLAGS += -nostdlib

6번줄에 있는 -nostdinc 옵션을 이용하여 라이브러리에서 제공하는 include 파일을 사용하지 않겠다는것을 알려 준다.  하지만, GCC 컴파일러에서 제공하는 헤더 파일은 사용하여야 하기 때문에 -isystem 옵션으로 GCC에서 제공하는 헤더파일을 include 시킨다.  GCC 헤더 파일의 위치는 1번줄에서 표현된 방법으로 그 디렉토리를 알 수 있다.

그리고, 링크시에 라이브러리에서 제공하는 코드가 포함되지 않도록 하기 위하여 9번 줄에서처럼 -nostdlib 옵션을 사용하였다.

컴파일과 링크 단계에서 -nostdinc와 -nostdlib 옵션을 주게 되면 더이상 라이브러리에서 제공하는 코드를 사용할 수 없게 되므로 프로그래머가 직접 해당 함수와 헤더 파일을 만들어야 한다.

원본 코드를 참조하여 만들어 본 printf() 함수의 코드는 다음과 같다.

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
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
#include <stdarg.h>
#include <stdint.h>
#include <stddef.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
 
#define UART_BUF_SIZE   256
 
char uart_buf[UART_BUF_SIZE];
 
 
#define to_digit(c)     ((c) - '0')
#define is_digit(c)     ((unsigned)to_digit(c) <= 9)
#define to_char(n)      ((n) + '0')
 
#define BUF             68
 
#define ALT             0X001
#define HEXPREFIX       0X002
#define LADJUST         0X004
#define LONGDBL         0X008
#define LONGINT         0X010
#define QUADINT         0X020
#define SHORTINT        0X040
#define ZEROPAD         0X080
#define FPT             0X100
 
 
 
static char *__ultoa(register uint32_t  val,
                     char               *endp,
                     int                base,
                     int                octzero,
                     char               *xdigs)
{
    register char       *cp = endp;
    register int32_t    sval;
 
    switch (base)
    {
        case 8:
            do
            {
                *--cp = to_char(val & 7);
                val >>= 3;
            } while(val);
            if (octzero && *cp != '0')
                *--cp = '0';
            break;
 
        case 10:
            if (val < 10)
            {
                *--cp = to_char(val);
                return cp;
            }
            if (val > LONG_MAX)
            {
                *--cp = to_char(val % 10);
                sval = val / 10;
            }
            else
                sval = val;
 
            do
            {
                *--cp = to_char(sval % 10);
                sval /= 10;
            } while(sval != 0);
            break;
 
        case 16:
            do
            {
                *--cp = xdigs[val & 15];
                val >>= 4;
            } while(val);
            break;
 
        default:
            break;
    }
 
    return cp;
}
 
static char *__uqtoa(register uint64_t  val,
                     char               *endp,
                     int                base,
                     int                octzero,
                     char               *xdigs)
{
    register char       *cp = endp;
    register int64_t    sval;
 
    if (val <= ULONG_MAX)
        return __ultoa((uint32_t)val, endp, base, octzero, xdigs);
 
    switch (base)
    {
        case 8:
            do
            {
                *--cp = to_char(val & 7);
                val >>= 3;
            } while(val);
            if (octzero && *cp != '0')
                *--cp = '0';
            break;
 
        case 10:
            if (val < 10)
            {
                *--cp = to_char(val % 10);
                return cp;
            }
            if (val > QUAD_MAX)
            {
                *--cp = to_char(val % 10);
                sval = val / 10;
            }
            else
                sval = val;
 
            do
            {
                *--cp = to_char(sval % 10);
                sval /= 10;
            } while(sval != 0);
            break;
 
        case 16:
            do
            {
                *--cp = xdigs[val & 15];
                val >>= 4;
            } while(val);
            break;
 
        default:
            break;
    }
 
    return cp;
}
 
int vprintf(char *strbuf, const char *format, va_list ap)
{
    register char   *fmt;
    register char   *cp;
    register char   ch;
    register int    n;
    register int    flags;
    char            sign;
    int             ret;
    int             width;
    int             prec;
    int             dprec = 0;
    int             size;
    int             fieldsz;
    int             realsz;
    int             base;
    char            *xdigs = NULL;
    uint32_t        ulval = 0;
    uint64_t        uqval = 0;
    char            buf[BUF];
    char            ox[2];
 
 
#define PADSIZE 16
    static char blanks[PADSIZE] =
     {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};
    static char zeroes[PADSIZE] =
     {'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'};
 
#define PRINT(ptr, len)                 \
    do {                                \
        int     i;                      \
        for (i=0; i<len; i++)           \
            *strbuf++ = ptr[i];         \
    }while(0)
 
#define PAD(howmany, with)              \
    do {                                \
        if ((n = (howmany)) > 0) {      \
            while (n > PADSIZE) {       \
                PRINT(with, PADSIZE);   \
                n -= PADSIZE;           \
            }                           \
            PRINT(with, n);             \
        }                               \
    }while(0)
 
#define SARG()                                          \
    (flags & LONGINT ? va_arg(ap, long) :               \
     flags & SHORTINT ? (long)(short)va_arg(ap, int) :  \
     (long)va_arg(ap, int))
#define UARG()                                                  \
    (flags & LONGINT ? va_arg(ap, uint32_t) :                   \
     flags & SHORTINT ? (uint32_t)(uint16_t)va_arg(ap, int) :   \
     (uint32_t)va_arg(ap, uint32_t))
 
 
    fmt = (char *)format;
    ret = 0;
 
    while (1)
    {
        for (cp = fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++);
 
        if ((n = fmt - cp) != 0)
        {
            PRINT(cp, n);
            ret += n;
        }
 
        if (ch == '\0')
            goto done;
 
        fmt++;
 
        flags = 0;
        width = 0;
        prec  = -1;
        sign  = '\0';
 
rflag:
        ch = *fmt++;
reswitch:
        switch (ch)
        {
            case ' ':
                if (!sign)
                    sign = ' ';
                goto rflag;
 
            case '#':
                flags |= ALT;
                goto rflag;
 
            case '*':
                if ((width = va_arg(ap, int)) >= 0)
                    goto rflag;
                width = -width;
 
            case '-':
                flags |= LADJUST;
                goto rflag;
 
            case '+':
                sign = '+';
                goto rflag;
 
            case '.':
                if ((ch = *fmt++) == '*')
                {
                    n = va_arg(ap, int);
                    prec = n < 0 ? -1 : n;
                    goto rflag;
                }
                n = 0;
                while (is_digit(ch))
                {
                    n = 10 * n + to_digit(ch);
                    ch = *fmt++;
                }
                prec = n < 0 ? -1 : n;
                goto reswitch;
 
            case '0':
                flags |= ZEROPAD;
                goto rflag;
 
            case '1' ... '9':
                n = 0;
                do
                {
                    n = 10 * n + to_digit(ch);
                    ch = *fmt++;
                } while(is_digit(ch));
                width = n;
                goto reswitch;
 
            case 'h':
                flags |= SHORTINT;
                goto rflag;
 
            case 'l':
                if (*fmt == 'l')
                {
                    fmt++;
                    flags |= QUADINT;
                }
                else
                    flags |= LONGINT;
                goto rflag;
 
            case 'q':
                flags |= QUADINT;
                goto rflag;
 
            case 'c':
                *(cp = buf) = va_arg(ap, int);
                size = 1;
                sign = '\0';
                break;
 
            case 'D':
                flags |= LONGINT;
            case 'd':
            case 'i':
                if (flags & QUADINT)
                {
                    uqval = va_arg(ap, int64_t);
                    if ((int64_t)uqval < 0)
                    {
                        uqval = -uqval;
                        sign = '-';
                    }
                }
                else
                {
                    ulval = SARG();
                    if ((long)ulval < 0)
                    {
                        ulval = -ulval;
                        sign = '-';
                    }
                }
                base = 10;
                goto number;
 
            case 'O':
                flags |= LONGINT;
            case 'o':
                if (flags & QUADINT)
                    uqval = va_arg(ap, uint64_t);
                else
                    ulval = UARG();
                base = 8;
                goto nosign;
 
            case 'p':
                ulval = (uint32_t)va_arg(ap, void *);
                base = 16;
                xdigs = "0123456789abcdef";
                flags = (flags & ~QUADINT) | HEXPREFIX;
                ch = 'x';
                goto nosign;
 
            case 's':
                if ((cp = va_arg(ap, char *)) == NULL)
                    cp = "(null)";
                if (prec >= 0)
                {
                    char *p = memchr(cp, 0, prec);
                    if (p != NULL)
                    {
                        size = p - cp;
                        if (size > prec)
                            size = prec;
                    }
                    else
                        size = prec;
                }
                else
                    size = strlen(cp);
                sign = '\0';
                break;
 
            case 'U':
                flags |= LONGINT;
            case 'u':
                if (flags & QUADINT)
                    uqval = va_arg(ap, uint64_t);
                else
                    ulval = UARG();
                base = 10;
                goto nosign;
 
            case 'X':
                xdigs = "0123456789ABCDEF";
                goto hex;
            case 'x':
                xdigs = "0123456789abcdef";
hex:
                if (flags & QUADINT)
                    uqval = va_arg(ap, uint64_t);
                else
                    ulval = UARG();
                base = 16;
 
                if (flags & ALT && (flags & QUADINT ? uqval != 0 : ulval != 0))
                    flags |= HEXPREFIX;
nosign:
                sign = '\0';
number:
                if ((dprec = prec) >= 0)
                    flags &= ~ZEROPAD;
                cp = buf + BUF;
                if (flags & QUADINT)
                {
                    if (uqval != 0 || prec != 0)
                        cp = __uqtoa(uqval, cp, base, flags & ALT, xdigs);
                }
                else
                {
                    if (ulval != 0 || prec != 0)
                        cp = __ultoa(ulval, cp, base, flags & ALT, xdigs);
                }
                size = buf + BUF - cp;
                break;
 
            default:
                if (ch == '\0')
                    goto done;
                cp = buf;
                *cp = ch;
                size = 1;
                sign = '\0';
                break;
        }
 
        fieldsz = size;
        if (sign)
            fieldsz++;
        else if (flags & HEXPREFIX)
            fieldsz += 2;
        realsz = dprec > fieldsz ? dprec : fieldsz;
 
        if ((flags & (LADJUST | ZEROPAD)) == 0)
            PAD(width - realsz, blanks);
 
        if (sign)
        {
            *strbuf++ = sign;
        }
        else if (flags & HEXPREFIX)
        {
            ox[0] = '0';
            ox[1] = ch;
            PRINT(ox, 2);
        }
 
        if ((flags & (LADJUST | ZEROPAD)) == ZEROPAD)
            PAD(width - realsz, zeroes);
 
        PAD(dprec - fieldsz, zeroes);
        PRINT(cp, size);
 
        if (flags & LADJUST)
            PAD(width - realsz, blanks);
 
        ret += width > realsz ? width : realsz;
    }
 
done:
    *strbuf = '\0';
    return ret;
}
 
int printf(const char *format, ...)
{
    int     rc;
    va_list ap;
 
    va_start(ap, format);
 
    rc = vprintf(uart_buf, format, ap);
 
    va_end(ap);
 
    puts(uart_buf);
 
    return rc;
}
 
 
void putc(char ch)
{
    extern void uart_putc(char ch);
 
    uart_putc(ch);
 
    if (ch == '\n')
        uart_putc('\r');
}
 
void puts(const char *str)
{
    while(*str)
        putc(*str++);
}

실제 코드를 보면 무척 복잡하다는 느낌이 들것이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int main(int argc, char *argv[])
{
    int     count = 100;
 
    sysclock_init(BD_CLKSRC, BD_PLLMUL, BD_AHBPRE, BD_APB1PRE, BD_APB2PRE, BD_ADCPRE);
    rcc_apb2enable(RCC_APB2_IOPC);
    gpio_set_outmode(GPIO_PORT_C, GPIO_PIN13, GPIO_GEN_PUSHPULL, GPIO_OUT_2M);
 
    uart_init();
 
    while (1)
    {
        delay(500);
        gpio_out_toggle(GPIO_PORT_C, GPIO_PIN13);
        printf("%10s = 0x%08X[%8d]\n", "Count", count, count);
        count--;
    }
 
    return 0;
}

main()를 위와 같이 만들어서 제대로 동작되는지 확인해 본다.  15번 줄에 보여지는 코드가 제대로 표현되는지 실행하여 그 결과를 확인한다.

Count 값이 100에서부터 1씩 감소하여 0이 되고, 그 다음에 -1이 되는것을 확인할 수 있다.

 

프로젝트에서 사용된 코드들은 아래에 첨부 하였다.  

stdio2.zip

 

소스코드를 열어보면 알겠지만 그동안 라이브러리에서 제공하여 그냥 불러 썼던 코드들이 모두 들어 있다.  이번 프로젝트에서는 물론 printf() 함수를 사용하는데 필요한 코드만 들어있다.  다른 함수들이 필요하면 그 코드들도 직접 만들어야 한다.

그렇다면 C에서 제공하는 라이브러리를 그냥 쓰면 되지 굳이 이렇게 불편하게 직접 만들 필요까지 있을까?

그 이유는 다음과 같다.

첫째, 이미 앞의 글에서도 얘기 했다시피 라이브러리코드가 포함되면 프로젝트의 실행 파일 크기가 상당히 커지게 된다.  ROM이나 RAM 크기가 충분하면 문제될게 없겠지만 MCU와 같이 ROM과 RAM 크기가 불과 몇K바이트나 몇십K바이트일뿐인 경우에는 라이브러리가 포함됨으로써 전체적으로 커지게 되는 실행 파일의 크기에 민감해 질 수밖에 없다.  어떤 경우 불과 몇십바이트 크기가 아쉬울때가 있기 때문이다.  이런 경우 라이브러리를 사용하지 않고 직접 필요한 함수만 만들어 쓰게 되면 그 크기를 대폭 줄일수 있게 된다.

둘째, 라이브러리 내부를 알 수 없으므로 원하는 동작이 수행되지 않을때 그 원인을 찾기가 쉽지 않은 경우가 있다.  때로는 보드에 맞지 않게 동작되는 라이브러리 코드가 포함될 수 있기 때문이다.  프로그래머가 직접 라이브러리를 만들어 쓰게 되면 어느 정도는 디버깅이 가능할 수 있게 된다.

세번째 이유는 프로젝트와는 직접적인 관계는 없지만 라이브러리 코드를 직접 만들어 봄으로써 프로그래밍 능력이 높아지게 된다.  좋은 글을 쓰기 위해서는 좋은 책을 많이 읽어야 하는것처럼 좋은 프로그램을 짜기 위해서는 좋은 코드들을 많이 분석해 보아야 한다.  C 라이브러리 코드들은 거의 프로그램의 신들이 만들기 때문에 거의 예술에 가까운 코드들이다.  평소에 절대 사용하지 않는 프로그래밍 기법들을 볼 수 있고, 특히 #define을 이용한 매크로 코드들을 보면 감탄하지 않을 수 없다.

단순히 취미가 아닌 프로그래머를 직업으로 삼으려하는 초보 프로그래머가 있다면 틈틈히 라이브러리 코드를 공부하기 바란다. 

 

[출처] https://embed-avr.tistory.com/120

 

 

 

' && ch != '%'; fmt++);
 
        if ((n = fmt - cp) != 0)
        {
            PRINT(cp, n);
            ret += n;
        }
 
        if (ch == '

[Embedded Software] ARM Cortex-M : printf()를 만들어 보자

C언어를 배울 때 가장 먼저 사용하는 함수는 printf()일것이다.  또한 C프로그램에서 가장 많이 불리워지는 함수도 printf()일것이다.  그러나 이렇게 많이 사용하고 있는 printf() 함수가 실제로 어떻게 생겼는지 그 소스를 찾아본적은 거의 없을 것이다.

이번 글에서는 printf() 함수를 라이브러리에서 제공하는 것을 사용하지 않고 직접 만들어 보기로 하겠다.

printf() 함수의 소스 코드는 인터넷에서 검색하면 어렵지 않게 찾을 수 있을 것이다.  이 글에서는 BSD-lite 안에 들어 있는 코드를 참조하였다.

아직 C언어에 익숙하지 않은 프로그래머가 보기에는 소스코드가 다소 복잡하게 보일것이다.  따라서 이 코드를 바로 임베디드 시스템에 적용하기에 앞서 visual studio나 리눅스 환경에서 미리 검증한 후 실제 임베디드 보드에서 확인하는것이 좋다.

 

아래 코드는 visual studio에서 동작 확인을 위하여 만들어 본 코드이다.

main.c  main.c

 

직접 만들어 본 코드에 문제가 없음을 확인하였으면 실제 보드에서 동작 시켜 보도록 하겠다.

먼저 해야 할 일은 이제 더이상 컴파일러에 포함되어 있는 C라이브러리가 프로젝트 빌드시에 포함되지 않도록 하기 위하여 Makefile을 수정하여야 한다.

1
2
3
4
5
6
7
8
9
gccincdir := $(shell $(CC) -print-file-name=include)
 
CFLAGS := -g -Os -ffunction-sections -fdata-sections -Wall -std=gnu99
CFLAGS += -fno-builtin -ffreestanding
CFLAGS += -mthumb -mcpu=cortex-m3
CFLAGS += -Iinc -nostdinc -isystem $(gccincdir)
 
LDFLAGS := -L $(shell dirname `$(CC) $(CFLAGS) -print-libgcc-file-name`)
LDFLAGS += -nostdlib

6번줄에 있는 -nostdinc 옵션을 이용하여 라이브러리에서 제공하는 include 파일을 사용하지 않겠다는것을 알려 준다.  하지만, GCC 컴파일러에서 제공하는 헤더 파일은 사용하여야 하기 때문에 -isystem 옵션으로 GCC에서 제공하는 헤더파일을 include 시킨다.  GCC 헤더 파일의 위치는 1번줄에서 표현된 방법으로 그 디렉토리를 알 수 있다.

그리고, 링크시에 라이브러리에서 제공하는 코드가 포함되지 않도록 하기 위하여 9번 줄에서처럼 -nostdlib 옵션을 사용하였다.

컴파일과 링크 단계에서 -nostdinc와 -nostdlib 옵션을 주게 되면 더이상 라이브러리에서 제공하는 코드를 사용할 수 없게 되므로 프로그래머가 직접 해당 함수와 헤더 파일을 만들어야 한다.

원본 코드를 참조하여 만들어 본 printf() 함수의 코드는 다음과 같다.

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
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
#include <stdarg.h>
#include <stdint.h>
#include <stddef.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
 
#define UART_BUF_SIZE   256
 
char uart_buf[UART_BUF_SIZE];
 
 
#define to_digit(c)     ((c) - '0')
#define is_digit(c)     ((unsigned)to_digit(c) <= 9)
#define to_char(n)      ((n) + '0')
 
#define BUF             68
 
#define ALT             0X001
#define HEXPREFIX       0X002
#define LADJUST         0X004
#define LONGDBL         0X008
#define LONGINT         0X010
#define QUADINT         0X020
#define SHORTINT        0X040
#define ZEROPAD         0X080
#define FPT             0X100
 
 
 
static char *__ultoa(register uint32_t  val,
                     char               *endp,
                     int                base,
                     int                octzero,
                     char               *xdigs)
{
    register char       *cp = endp;
    register int32_t    sval;
 
    switch (base)
    {
        case 8:
            do
            {
                *--cp = to_char(val & 7);
                val >>= 3;
            } while(val);
            if (octzero && *cp != '0')
                *--cp = '0';
            break;
 
        case 10:
            if (val < 10)
            {
                *--cp = to_char(val);
                return cp;
            }
            if (val > LONG_MAX)
            {
                *--cp = to_char(val % 10);
                sval = val / 10;
            }
            else
                sval = val;
 
            do
            {
                *--cp = to_char(sval % 10);
                sval /= 10;
            } while(sval != 0);
            break;
 
        case 16:
            do
            {
                *--cp = xdigs[val & 15];
                val >>= 4;
            } while(val);
            break;
 
        default:
            break;
    }
 
    return cp;
}
 
static char *__uqtoa(register uint64_t  val,
                     char               *endp,
                     int                base,
                     int                octzero,
                     char               *xdigs)
{
    register char       *cp = endp;
    register int64_t    sval;
 
    if (val <= ULONG_MAX)
        return __ultoa((uint32_t)val, endp, base, octzero, xdigs);
 
    switch (base)
    {
        case 8:
            do
            {
                *--cp = to_char(val & 7);
                val >>= 3;
            } while(val);
            if (octzero && *cp != '0')
                *--cp = '0';
            break;
 
        case 10:
            if (val < 10)
            {
                *--cp = to_char(val % 10);
                return cp;
            }
            if (val > QUAD_MAX)
            {
                *--cp = to_char(val % 10);
                sval = val / 10;
            }
            else
                sval = val;
 
            do
            {
                *--cp = to_char(sval % 10);
                sval /= 10;
            } while(sval != 0);
            break;
 
        case 16:
            do
            {
                *--cp = xdigs[val & 15];
                val >>= 4;
            } while(val);
            break;
 
        default:
            break;
    }
 
    return cp;
}
 
int vprintf(char *strbuf, const char *format, va_list ap)
{
    register char   *fmt;
    register char   *cp;
    register char   ch;
    register int    n;
    register int    flags;
    char            sign;
    int             ret;
    int             width;
    int             prec;
    int             dprec = 0;
    int             size;
    int             fieldsz;
    int             realsz;
    int             base;
    char            *xdigs = NULL;
    uint32_t        ulval = 0;
    uint64_t        uqval = 0;
    char            buf[BUF];
    char            ox[2];
 
 
#define PADSIZE 16
    static char blanks[PADSIZE] =
     {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};
    static char zeroes[PADSIZE] =
     {'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'};
 
#define PRINT(ptr, len)                 \
    do {                                \
        int     i;                      \
        for (i=0; i<len; i++)           \
            *strbuf++ = ptr[i];         \
    }while(0)
 
#define PAD(howmany, with)              \
    do {                                \
        if ((n = (howmany)) > 0) {      \
            while (n > PADSIZE) {       \
                PRINT(with, PADSIZE);   \
                n -= PADSIZE;           \
            }                           \
            PRINT(with, n);             \
        }                               \
    }while(0)
 
#define SARG()                                          \
    (flags & LONGINT ? va_arg(ap, long) :               \
     flags & SHORTINT ? (long)(short)va_arg(ap, int) :  \
     (long)va_arg(ap, int))
#define UARG()                                                  \
    (flags & LONGINT ? va_arg(ap, uint32_t) :                   \
     flags & SHORTINT ? (uint32_t)(uint16_t)va_arg(ap, int) :   \
     (uint32_t)va_arg(ap, uint32_t))
 
 
    fmt = (char *)format;
    ret = 0;
 
    while (1)
    {
        for (cp = fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++);
 
        if ((n = fmt - cp) != 0)
        {
            PRINT(cp, n);
            ret += n;
        }
 
        if (ch == '\0')
            goto done;
 
        fmt++;
 
        flags = 0;
        width = 0;
        prec  = -1;
        sign  = '\0';
 
rflag:
        ch = *fmt++;
reswitch:
        switch (ch)
        {
            case ' ':
                if (!sign)
                    sign = ' ';
                goto rflag;
 
            case '#':
                flags |= ALT;
                goto rflag;
 
            case '*':
                if ((width = va_arg(ap, int)) >= 0)
                    goto rflag;
                width = -width;
 
            case '-':
                flags |= LADJUST;
                goto rflag;
 
            case '+':
                sign = '+';
                goto rflag;
 
            case '.':
                if ((ch = *fmt++) == '*')
                {
                    n = va_arg(ap, int);
                    prec = n < 0 ? -1 : n;
                    goto rflag;
                }
                n = 0;
                while (is_digit(ch))
                {
                    n = 10 * n + to_digit(ch);
                    ch = *fmt++;
                }
                prec = n < 0 ? -1 : n;
                goto reswitch;
 
            case '0':
                flags |= ZEROPAD;
                goto rflag;
 
            case '1' ... '9':
                n = 0;
                do
                {
                    n = 10 * n + to_digit(ch);
                    ch = *fmt++;
                } while(is_digit(ch));
                width = n;
                goto reswitch;
 
            case 'h':
                flags |= SHORTINT;
                goto rflag;
 
            case 'l':
                if (*fmt == 'l')
                {
                    fmt++;
                    flags |= QUADINT;
                }
                else
                    flags |= LONGINT;
                goto rflag;
 
            case 'q':
                flags |= QUADINT;
                goto rflag;
 
            case 'c':
                *(cp = buf) = va_arg(ap, int);
                size = 1;
                sign = '\0';
                break;
 
            case 'D':
                flags |= LONGINT;
            case 'd':
            case 'i':
                if (flags & QUADINT)
                {
                    uqval = va_arg(ap, int64_t);
                    if ((int64_t)uqval < 0)
                    {
                        uqval = -uqval;
                        sign = '-';
                    }
                }
                else
                {
                    ulval = SARG();
                    if ((long)ulval < 0)
                    {
                        ulval = -ulval;
                        sign = '-';
                    }
                }
                base = 10;
                goto number;
 
            case 'O':
                flags |= LONGINT;
            case 'o':
                if (flags & QUADINT)
                    uqval = va_arg(ap, uint64_t);
                else
                    ulval = UARG();
                base = 8;
                goto nosign;
 
            case 'p':
                ulval = (uint32_t)va_arg(ap, void *);
                base = 16;
                xdigs = "0123456789abcdef";
                flags = (flags & ~QUADINT) | HEXPREFIX;
                ch = 'x';
                goto nosign;
 
            case 's':
                if ((cp = va_arg(ap, char *)) == NULL)
                    cp = "(null)";
                if (prec >= 0)
                {
                    char *p = memchr(cp, 0, prec);
                    if (p != NULL)
                    {
                        size = p - cp;
                        if (size > prec)
                            size = prec;
                    }
                    else
                        size = prec;
                }
                else
                    size = strlen(cp);
                sign = '\0';
                break;
 
            case 'U':
                flags |= LONGINT;
            case 'u':
                if (flags & QUADINT)
                    uqval = va_arg(ap, uint64_t);
                else
                    ulval = UARG();
                base = 10;
                goto nosign;
 
            case 'X':
                xdigs = "0123456789ABCDEF";
                goto hex;
            case 'x':
                xdigs = "0123456789abcdef";
hex:
                if (flags & QUADINT)
                    uqval = va_arg(ap, uint64_t);
                else
                    ulval = UARG();
                base = 16;
 
                if (flags & ALT && (flags & QUADINT ? uqval != 0 : ulval != 0))
                    flags |= HEXPREFIX;
nosign:
                sign = '\0';
number:
                if ((dprec = prec) >= 0)
                    flags &= ~ZEROPAD;
                cp = buf + BUF;
                if (flags & QUADINT)
                {
                    if (uqval != 0 || prec != 0)
                        cp = __uqtoa(uqval, cp, base, flags & ALT, xdigs);
                }
                else
                {
                    if (ulval != 0 || prec != 0)
                        cp = __ultoa(ulval, cp, base, flags & ALT, xdigs);
                }
                size = buf + BUF - cp;
                break;
 
            default:
                if (ch == '\0')
                    goto done;
                cp = buf;
                *cp = ch;
                size = 1;
                sign = '\0';
                break;
        }
 
        fieldsz = size;
        if (sign)
            fieldsz++;
        else if (flags & HEXPREFIX)
            fieldsz += 2;
        realsz = dprec > fieldsz ? dprec : fieldsz;
 
        if ((flags & (LADJUST | ZEROPAD)) == 0)
            PAD(width - realsz, blanks);
 
        if (sign)
        {
            *strbuf++ = sign;
        }
        else if (flags & HEXPREFIX)
        {
            ox[0] = '0';
            ox[1] = ch;
            PRINT(ox, 2);
        }
 
        if ((flags & (LADJUST | ZEROPAD)) == ZEROPAD)
            PAD(width - realsz, zeroes);
 
        PAD(dprec - fieldsz, zeroes);
        PRINT(cp, size);
 
        if (flags & LADJUST)
            PAD(width - realsz, blanks);
 
        ret += width > realsz ? width : realsz;
    }
 
done:
    *strbuf = '\0';
    return ret;
}
 
int printf(const char *format, ...)
{
    int     rc;
    va_list ap;
 
    va_start(ap, format);
 
    rc = vprintf(uart_buf, format, ap);
 
    va_end(ap);
 
    puts(uart_buf);
 
    return rc;
}
 
 
void putc(char ch)
{
    extern void uart_putc(char ch);
 
    uart_putc(ch);
 
    if (ch == '\n')
        uart_putc('\r');
}
 
void puts(const char *str)
{
    while(*str)
        putc(*str++);
}

실제 코드를 보면 무척 복잡하다는 느낌이 들것이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int main(int argc, char *argv[])
{
    int     count = 100;
 
    sysclock_init(BD_CLKSRC, BD_PLLMUL, BD_AHBPRE, BD_APB1PRE, BD_APB2PRE, BD_ADCPRE);
    rcc_apb2enable(RCC_APB2_IOPC);
    gpio_set_outmode(GPIO_PORT_C, GPIO_PIN13, GPIO_GEN_PUSHPULL, GPIO_OUT_2M);
 
    uart_init();
 
    while (1)
    {
        delay(500);
        gpio_out_toggle(GPIO_PORT_C, GPIO_PIN13);
        printf("%10s = 0x%08X[%8d]\n", "Count", count, count);
        count--;
    }
 
    return 0;
}

main()를 위와 같이 만들어서 제대로 동작되는지 확인해 본다.  15번 줄에 보여지는 코드가 제대로 표현되는지 실행하여 그 결과를 확인한다.

Count 값이 100에서부터 1씩 감소하여 0이 되고, 그 다음에 -1이 되는것을 확인할 수 있다.

 

프로젝트에서 사용된 코드들은 아래에 첨부 하였다.  

stdio2.zip

 

소스코드를 열어보면 알겠지만 그동안 라이브러리에서 제공하여 그냥 불러 썼던 코드들이 모두 들어 있다.  이번 프로젝트에서는 물론 printf() 함수를 사용하는데 필요한 코드만 들어있다.  다른 함수들이 필요하면 그 코드들도 직접 만들어야 한다.

그렇다면 C에서 제공하는 라이브러리를 그냥 쓰면 되지 굳이 이렇게 불편하게 직접 만들 필요까지 있을까?

그 이유는 다음과 같다.

첫째, 이미 앞의 글에서도 얘기 했다시피 라이브러리코드가 포함되면 프로젝트의 실행 파일 크기가 상당히 커지게 된다.  ROM이나 RAM 크기가 충분하면 문제될게 없겠지만 MCU와 같이 ROM과 RAM 크기가 불과 몇K바이트나 몇십K바이트일뿐인 경우에는 라이브러리가 포함됨으로써 전체적으로 커지게 되는 실행 파일의 크기에 민감해 질 수밖에 없다.  어떤 경우 불과 몇십바이트 크기가 아쉬울때가 있기 때문이다.  이런 경우 라이브러리를 사용하지 않고 직접 필요한 함수만 만들어 쓰게 되면 그 크기를 대폭 줄일수 있게 된다.

둘째, 라이브러리 내부를 알 수 없으므로 원하는 동작이 수행되지 않을때 그 원인을 찾기가 쉽지 않은 경우가 있다.  때로는 보드에 맞지 않게 동작되는 라이브러리 코드가 포함될 수 있기 때문이다.  프로그래머가 직접 라이브러리를 만들어 쓰게 되면 어느 정도는 디버깅이 가능할 수 있게 된다.

세번째 이유는 프로젝트와는 직접적인 관계는 없지만 라이브러리 코드를 직접 만들어 봄으로써 프로그래밍 능력이 높아지게 된다.  좋은 글을 쓰기 위해서는 좋은 책을 많이 읽어야 하는것처럼 좋은 프로그램을 짜기 위해서는 좋은 코드들을 많이 분석해 보아야 한다.  C 라이브러리 코드들은 거의 프로그램의 신들이 만들기 때문에 거의 예술에 가까운 코드들이다.  평소에 절대 사용하지 않는 프로그래밍 기법들을 볼 수 있고, 특히 #define을 이용한 매크로 코드들을 보면 감탄하지 않을 수 없다.

단순히 취미가 아닌 프로그래머를 직업으로 삼으려하는 초보 프로그래머가 있다면 틈틈히 라이브러리 코드를 공부하기 바란다. 

 

[출처] https://embed-avr.tistory.com/120

 

 

 

')
            goto done;
 
        fmt++;
 
        flags = 0;
        width = 0;
        prec  = -1;
        sign  = '

[Embedded Software] ARM Cortex-M : printf()를 만들어 보자

C언어를 배울 때 가장 먼저 사용하는 함수는 printf()일것이다.  또한 C프로그램에서 가장 많이 불리워지는 함수도 printf()일것이다.  그러나 이렇게 많이 사용하고 있는 printf() 함수가 실제로 어떻게 생겼는지 그 소스를 찾아본적은 거의 없을 것이다.

이번 글에서는 printf() 함수를 라이브러리에서 제공하는 것을 사용하지 않고 직접 만들어 보기로 하겠다.

printf() 함수의 소스 코드는 인터넷에서 검색하면 어렵지 않게 찾을 수 있을 것이다.  이 글에서는 BSD-lite 안에 들어 있는 코드를 참조하였다.

아직 C언어에 익숙하지 않은 프로그래머가 보기에는 소스코드가 다소 복잡하게 보일것이다.  따라서 이 코드를 바로 임베디드 시스템에 적용하기에 앞서 visual studio나 리눅스 환경에서 미리 검증한 후 실제 임베디드 보드에서 확인하는것이 좋다.

 

아래 코드는 visual studio에서 동작 확인을 위하여 만들어 본 코드이다.

main.c  main.c

 

직접 만들어 본 코드에 문제가 없음을 확인하였으면 실제 보드에서 동작 시켜 보도록 하겠다.

먼저 해야 할 일은 이제 더이상 컴파일러에 포함되어 있는 C라이브러리가 프로젝트 빌드시에 포함되지 않도록 하기 위하여 Makefile을 수정하여야 한다.

1
2
3
4
5
6
7
8
9
gccincdir := $(shell $(CC) -print-file-name=include)
 
CFLAGS := -g -Os -ffunction-sections -fdata-sections -Wall -std=gnu99
CFLAGS += -fno-builtin -ffreestanding
CFLAGS += -mthumb -mcpu=cortex-m3
CFLAGS += -Iinc -nostdinc -isystem $(gccincdir)
 
LDFLAGS := -L $(shell dirname `$(CC) $(CFLAGS) -print-libgcc-file-name`)
LDFLAGS += -nostdlib

6번줄에 있는 -nostdinc 옵션을 이용하여 라이브러리에서 제공하는 include 파일을 사용하지 않겠다는것을 알려 준다.  하지만, GCC 컴파일러에서 제공하는 헤더 파일은 사용하여야 하기 때문에 -isystem 옵션으로 GCC에서 제공하는 헤더파일을 include 시킨다.  GCC 헤더 파일의 위치는 1번줄에서 표현된 방법으로 그 디렉토리를 알 수 있다.

그리고, 링크시에 라이브러리에서 제공하는 코드가 포함되지 않도록 하기 위하여 9번 줄에서처럼 -nostdlib 옵션을 사용하였다.

컴파일과 링크 단계에서 -nostdinc와 -nostdlib 옵션을 주게 되면 더이상 라이브러리에서 제공하는 코드를 사용할 수 없게 되므로 프로그래머가 직접 해당 함수와 헤더 파일을 만들어야 한다.

원본 코드를 참조하여 만들어 본 printf() 함수의 코드는 다음과 같다.

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
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
#include <stdarg.h>
#include <stdint.h>
#include <stddef.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
 
#define UART_BUF_SIZE   256
 
char uart_buf[UART_BUF_SIZE];
 
 
#define to_digit(c)     ((c) - '0')
#define is_digit(c)     ((unsigned)to_digit(c) <= 9)
#define to_char(n)      ((n) + '0')
 
#define BUF             68
 
#define ALT             0X001
#define HEXPREFIX       0X002
#define LADJUST         0X004
#define LONGDBL         0X008
#define LONGINT         0X010
#define QUADINT         0X020
#define SHORTINT        0X040
#define ZEROPAD         0X080
#define FPT             0X100
 
 
 
static char *__ultoa(register uint32_t  val,
                     char               *endp,
                     int                base,
                     int                octzero,
                     char               *xdigs)
{
    register char       *cp = endp;
    register int32_t    sval;
 
    switch (base)
    {
        case 8:
            do
            {
                *--cp = to_char(val & 7);
                val >>= 3;
            } while(val);
            if (octzero && *cp != '0')
                *--cp = '0';
            break;
 
        case 10:
            if (val < 10)
            {
                *--cp = to_char(val);
                return cp;
            }
            if (val > LONG_MAX)
            {
                *--cp = to_char(val % 10);
                sval = val / 10;
            }
            else
                sval = val;
 
            do
            {
                *--cp = to_char(sval % 10);
                sval /= 10;
            } while(sval != 0);
            break;
 
        case 16:
            do
            {
                *--cp = xdigs[val & 15];
                val >>= 4;
            } while(val);
            break;
 
        default:
            break;
    }
 
    return cp;
}
 
static char *__uqtoa(register uint64_t  val,
                     char               *endp,
                     int                base,
                     int                octzero,
                     char               *xdigs)
{
    register char       *cp = endp;
    register int64_t    sval;
 
    if (val <= ULONG_MAX)
        return __ultoa((uint32_t)val, endp, base, octzero, xdigs);
 
    switch (base)
    {
        case 8:
            do
            {
                *--cp = to_char(val & 7);
                val >>= 3;
            } while(val);
            if (octzero && *cp != '0')
                *--cp = '0';
            break;
 
        case 10:
            if (val < 10)
            {
                *--cp = to_char(val % 10);
                return cp;
            }
            if (val > QUAD_MAX)
            {
                *--cp = to_char(val % 10);
                sval = val / 10;
            }
            else
                sval = val;
 
            do
            {
                *--cp = to_char(sval % 10);
                sval /= 10;
            } while(sval != 0);
            break;
 
        case 16:
            do
            {
                *--cp = xdigs[val & 15];
                val >>= 4;
            } while(val);
            break;
 
        default:
            break;
    }
 
    return cp;
}
 
int vprintf(char *strbuf, const char *format, va_list ap)
{
    register char   *fmt;
    register char   *cp;
    register char   ch;
    register int    n;
    register int    flags;
    char            sign;
    int             ret;
    int             width;
    int             prec;
    int             dprec = 0;
    int             size;
    int             fieldsz;
    int             realsz;
    int             base;
    char            *xdigs = NULL;
    uint32_t        ulval = 0;
    uint64_t        uqval = 0;
    char            buf[BUF];
    char            ox[2];
 
 
#define PADSIZE 16
    static char blanks[PADSIZE] =
     {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};
    static char zeroes[PADSIZE] =
     {'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'};
 
#define PRINT(ptr, len)                 \
    do {                                \
        int     i;                      \
        for (i=0; i<len; i++)           \
            *strbuf++ = ptr[i];         \
    }while(0)
 
#define PAD(howmany, with)              \
    do {                                \
        if ((n = (howmany)) > 0) {      \
            while (n > PADSIZE) {       \
                PRINT(with, PADSIZE);   \
                n -= PADSIZE;           \
            }                           \
            PRINT(with, n);             \
        }                               \
    }while(0)
 
#define SARG()                                          \
    (flags & LONGINT ? va_arg(ap, long) :               \
     flags & SHORTINT ? (long)(short)va_arg(ap, int) :  \
     (long)va_arg(ap, int))
#define UARG()                                                  \
    (flags & LONGINT ? va_arg(ap, uint32_t) :                   \
     flags & SHORTINT ? (uint32_t)(uint16_t)va_arg(ap, int) :   \
     (uint32_t)va_arg(ap, uint32_t))
 
 
    fmt = (char *)format;
    ret = 0;
 
    while (1)
    {
        for (cp = fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++);
 
        if ((n = fmt - cp) != 0)
        {
            PRINT(cp, n);
            ret += n;
        }
 
        if (ch == '\0')
            goto done;
 
        fmt++;
 
        flags = 0;
        width = 0;
        prec  = -1;
        sign  = '\0';
 
rflag:
        ch = *fmt++;
reswitch:
        switch (ch)
        {
            case ' ':
                if (!sign)
                    sign = ' ';
                goto rflag;
 
            case '#':
                flags |= ALT;
                goto rflag;
 
            case '*':
                if ((width = va_arg(ap, int)) >= 0)
                    goto rflag;
                width = -width;
 
            case '-':
                flags |= LADJUST;
                goto rflag;
 
            case '+':
                sign = '+';
                goto rflag;
 
            case '.':
                if ((ch = *fmt++) == '*')
                {
                    n = va_arg(ap, int);
                    prec = n < 0 ? -1 : n;
                    goto rflag;
                }
                n = 0;
                while (is_digit(ch))
                {
                    n = 10 * n + to_digit(ch);
                    ch = *fmt++;
                }
                prec = n < 0 ? -1 : n;
                goto reswitch;
 
            case '0':
                flags |= ZEROPAD;
                goto rflag;
 
            case '1' ... '9':
                n = 0;
                do
                {
                    n = 10 * n + to_digit(ch);
                    ch = *fmt++;
                } while(is_digit(ch));
                width = n;
                goto reswitch;
 
            case 'h':
                flags |= SHORTINT;
                goto rflag;
 
            case 'l':
                if (*fmt == 'l')
                {
                    fmt++;
                    flags |= QUADINT;
                }
                else
                    flags |= LONGINT;
                goto rflag;
 
            case 'q':
                flags |= QUADINT;
                goto rflag;
 
            case 'c':
                *(cp = buf) = va_arg(ap, int);
                size = 1;
                sign = '\0';
                break;
 
            case 'D':
                flags |= LONGINT;
            case 'd':
            case 'i':
                if (flags & QUADINT)
                {
                    uqval = va_arg(ap, int64_t);
                    if ((int64_t)uqval < 0)
                    {
                        uqval = -uqval;
                        sign = '-';
                    }
                }
                else
                {
                    ulval = SARG();
                    if ((long)ulval < 0)
                    {
                        ulval = -ulval;
                        sign = '-';
                    }
                }
                base = 10;
                goto number;
 
            case 'O':
                flags |= LONGINT;
            case 'o':
                if (flags & QUADINT)
                    uqval = va_arg(ap, uint64_t);
                else
                    ulval = UARG();
                base = 8;
                goto nosign;
 
            case 'p':
                ulval = (uint32_t)va_arg(ap, void *);
                base = 16;
                xdigs = "0123456789abcdef";
                flags = (flags & ~QUADINT) | HEXPREFIX;
                ch = 'x';
                goto nosign;
 
            case 's':
                if ((cp = va_arg(ap, char *)) == NULL)
                    cp = "(null)";
                if (prec >= 0)
                {
                    char *p = memchr(cp, 0, prec);
                    if (p != NULL)
                    {
                        size = p - cp;
                        if (size > prec)
                            size = prec;
                    }
                    else
                        size = prec;
                }
                else
                    size = strlen(cp);
                sign = '\0';
                break;
 
            case 'U':
                flags |= LONGINT;
            case 'u':
                if (flags & QUADINT)
                    uqval = va_arg(ap, uint64_t);
                else
                    ulval = UARG();
                base = 10;
                goto nosign;
 
            case 'X':
                xdigs = "0123456789ABCDEF";
                goto hex;
            case 'x':
                xdigs = "0123456789abcdef";
hex:
                if (flags & QUADINT)
                    uqval = va_arg(ap, uint64_t);
                else
                    ulval = UARG();
                base = 16;
 
                if (flags & ALT && (flags & QUADINT ? uqval != 0 : ulval != 0))
                    flags |= HEXPREFIX;
nosign:
                sign = '\0';
number:
                if ((dprec = prec) >= 0)
                    flags &= ~ZEROPAD;
                cp = buf + BUF;
                if (flags & QUADINT)
                {
                    if (uqval != 0 || prec != 0)
                        cp = __uqtoa(uqval, cp, base, flags & ALT, xdigs);
                }
                else
                {
                    if (ulval != 0 || prec != 0)
                        cp = __ultoa(ulval, cp, base, flags & ALT, xdigs);
                }
                size = buf + BUF - cp;
                break;
 
            default:
                if (ch == '\0')
                    goto done;
                cp = buf;
                *cp = ch;
                size = 1;
                sign = '\0';
                break;
        }
 
        fieldsz = size;
        if (sign)
            fieldsz++;
        else if (flags & HEXPREFIX)
            fieldsz += 2;
        realsz = dprec > fieldsz ? dprec : fieldsz;
 
        if ((flags & (LADJUST | ZEROPAD)) == 0)
            PAD(width - realsz, blanks);
 
        if (sign)
        {
            *strbuf++ = sign;
        }
        else if (flags & HEXPREFIX)
        {
            ox[0] = '0';
            ox[1] = ch;
            PRINT(ox, 2);
        }
 
        if ((flags & (LADJUST | ZEROPAD)) == ZEROPAD)
            PAD(width - realsz, zeroes);
 
        PAD(dprec - fieldsz, zeroes);
        PRINT(cp, size);
 
        if (flags & LADJUST)
            PAD(width - realsz, blanks);
 
        ret += width > realsz ? width : realsz;
    }
 
done:
    *strbuf = '\0';
    return ret;
}
 
int printf(const char *format, ...)
{
    int     rc;
    va_list ap;
 
    va_start(ap, format);
 
    rc = vprintf(uart_buf, format, ap);
 
    va_end(ap);
 
    puts(uart_buf);
 
    return rc;
}
 
 
void putc(char ch)
{
    extern void uart_putc(char ch);
 
    uart_putc(ch);
 
    if (ch == '\n')
        uart_putc('\r');
}
 
void puts(const char *str)
{
    while(*str)
        putc(*str++);
}

실제 코드를 보면 무척 복잡하다는 느낌이 들것이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int main(int argc, char *argv[])
{
    int     count = 100;
 
    sysclock_init(BD_CLKSRC, BD_PLLMUL, BD_AHBPRE, BD_APB1PRE, BD_APB2PRE, BD_ADCPRE);
    rcc_apb2enable(RCC_APB2_IOPC);
    gpio_set_outmode(GPIO_PORT_C, GPIO_PIN13, GPIO_GEN_PUSHPULL, GPIO_OUT_2M);
 
    uart_init();
 
    while (1)
    {
        delay(500);
        gpio_out_toggle(GPIO_PORT_C, GPIO_PIN13);
        printf("%10s = 0x%08X[%8d]\n", "Count", count, count);
        count--;
    }
 
    return 0;
}

main()를 위와 같이 만들어서 제대로 동작되는지 확인해 본다.  15번 줄에 보여지는 코드가 제대로 표현되는지 실행하여 그 결과를 확인한다.

Count 값이 100에서부터 1씩 감소하여 0이 되고, 그 다음에 -1이 되는것을 확인할 수 있다.

 

프로젝트에서 사용된 코드들은 아래에 첨부 하였다.  

stdio2.zip

 

소스코드를 열어보면 알겠지만 그동안 라이브러리에서 제공하여 그냥 불러 썼던 코드들이 모두 들어 있다.  이번 프로젝트에서는 물론 printf() 함수를 사용하는데 필요한 코드만 들어있다.  다른 함수들이 필요하면 그 코드들도 직접 만들어야 한다.

그렇다면 C에서 제공하는 라이브러리를 그냥 쓰면 되지 굳이 이렇게 불편하게 직접 만들 필요까지 있을까?

그 이유는 다음과 같다.

첫째, 이미 앞의 글에서도 얘기 했다시피 라이브러리코드가 포함되면 프로젝트의 실행 파일 크기가 상당히 커지게 된다.  ROM이나 RAM 크기가 충분하면 문제될게 없겠지만 MCU와 같이 ROM과 RAM 크기가 불과 몇K바이트나 몇십K바이트일뿐인 경우에는 라이브러리가 포함됨으로써 전체적으로 커지게 되는 실행 파일의 크기에 민감해 질 수밖에 없다.  어떤 경우 불과 몇십바이트 크기가 아쉬울때가 있기 때문이다.  이런 경우 라이브러리를 사용하지 않고 직접 필요한 함수만 만들어 쓰게 되면 그 크기를 대폭 줄일수 있게 된다.

둘째, 라이브러리 내부를 알 수 없으므로 원하는 동작이 수행되지 않을때 그 원인을 찾기가 쉽지 않은 경우가 있다.  때로는 보드에 맞지 않게 동작되는 라이브러리 코드가 포함될 수 있기 때문이다.  프로그래머가 직접 라이브러리를 만들어 쓰게 되면 어느 정도는 디버깅이 가능할 수 있게 된다.

세번째 이유는 프로젝트와는 직접적인 관계는 없지만 라이브러리 코드를 직접 만들어 봄으로써 프로그래밍 능력이 높아지게 된다.  좋은 글을 쓰기 위해서는 좋은 책을 많이 읽어야 하는것처럼 좋은 프로그램을 짜기 위해서는 좋은 코드들을 많이 분석해 보아야 한다.  C 라이브러리 코드들은 거의 프로그램의 신들이 만들기 때문에 거의 예술에 가까운 코드들이다.  평소에 절대 사용하지 않는 프로그래밍 기법들을 볼 수 있고, 특히 #define을 이용한 매크로 코드들을 보면 감탄하지 않을 수 없다.

단순히 취미가 아닌 프로그래머를 직업으로 삼으려하는 초보 프로그래머가 있다면 틈틈히 라이브러리 코드를 공부하기 바란다. 

 

[출처] https://embed-avr.tistory.com/120

 

 

 

';
 
rflag:
        ch = *fmt++;
reswitch:
        switch (ch)
        {
            case ' ':
                if (!sign)
                    sign = ' ';
                goto rflag;
 
            case '#':
                flags |= ALT;
                goto rflag;
 
            case '*':
                if ((width = va_arg(ap, int)) >= 0)
                    goto rflag;
                width = -width;
 
            case '-':
                flags |= LADJUST;
                goto rflag;
 
            case '+':
                sign = '+';
                goto rflag;
 
            case '.':
                if ((ch = *fmt++) == '*')
                {
                    n = va_arg(ap, int);
                    prec = n < 0 ? -1 : n;
                    goto rflag;
                }
                n = 0;
                while (is_digit(ch))
                {
                    n = 10 * n + to_digit(ch);
                    ch = *fmt++;
                }
                prec = n < 0 ? -1 : n;
                goto reswitch;
 
            case '0':
                flags |= ZEROPAD;
                goto rflag;
 
            case '1' ... '9':
                n = 0;
                do
                {
                    n = 10 * n + to_digit(ch);
                    ch = *fmt++;
                } while(is_digit(ch));
                width = n;
                goto reswitch;
 
            case 'h':
                flags |= SHORTINT;
                goto rflag;
 
            case 'l':
                if (*fmt == 'l')
                {
                    fmt++;
                    flags |= QUADINT;
                }
                else
                    flags |= LONGINT;
                goto rflag;
 
            case 'q':
                flags |= QUADINT;
                goto rflag;
 
            case 'c':
                *(cp = buf) = va_arg(ap, int);
                size = 1;
                sign = '

[Embedded Software] ARM Cortex-M : printf()를 만들어 보자

C언어를 배울 때 가장 먼저 사용하는 함수는 printf()일것이다.  또한 C프로그램에서 가장 많이 불리워지는 함수도 printf()일것이다.  그러나 이렇게 많이 사용하고 있는 printf() 함수가 실제로 어떻게 생겼는지 그 소스를 찾아본적은 거의 없을 것이다.

이번 글에서는 printf() 함수를 라이브러리에서 제공하는 것을 사용하지 않고 직접 만들어 보기로 하겠다.

printf() 함수의 소스 코드는 인터넷에서 검색하면 어렵지 않게 찾을 수 있을 것이다.  이 글에서는 BSD-lite 안에 들어 있는 코드를 참조하였다.

아직 C언어에 익숙하지 않은 프로그래머가 보기에는 소스코드가 다소 복잡하게 보일것이다.  따라서 이 코드를 바로 임베디드 시스템에 적용하기에 앞서 visual studio나 리눅스 환경에서 미리 검증한 후 실제 임베디드 보드에서 확인하는것이 좋다.

 

아래 코드는 visual studio에서 동작 확인을 위하여 만들어 본 코드이다.

main.c  main.c

 

직접 만들어 본 코드에 문제가 없음을 확인하였으면 실제 보드에서 동작 시켜 보도록 하겠다.

먼저 해야 할 일은 이제 더이상 컴파일러에 포함되어 있는 C라이브러리가 프로젝트 빌드시에 포함되지 않도록 하기 위하여 Makefile을 수정하여야 한다.

1
2
3
4
5
6
7
8
9
gccincdir := $(shell $(CC) -print-file-name=include)
 
CFLAGS := -g -Os -ffunction-sections -fdata-sections -Wall -std=gnu99
CFLAGS += -fno-builtin -ffreestanding
CFLAGS += -mthumb -mcpu=cortex-m3
CFLAGS += -Iinc -nostdinc -isystem $(gccincdir)
 
LDFLAGS := -L $(shell dirname `$(CC) $(CFLAGS) -print-libgcc-file-name`)
LDFLAGS += -nostdlib

6번줄에 있는 -nostdinc 옵션을 이용하여 라이브러리에서 제공하는 include 파일을 사용하지 않겠다는것을 알려 준다.  하지만, GCC 컴파일러에서 제공하는 헤더 파일은 사용하여야 하기 때문에 -isystem 옵션으로 GCC에서 제공하는 헤더파일을 include 시킨다.  GCC 헤더 파일의 위치는 1번줄에서 표현된 방법으로 그 디렉토리를 알 수 있다.

그리고, 링크시에 라이브러리에서 제공하는 코드가 포함되지 않도록 하기 위하여 9번 줄에서처럼 -nostdlib 옵션을 사용하였다.

컴파일과 링크 단계에서 -nostdinc와 -nostdlib 옵션을 주게 되면 더이상 라이브러리에서 제공하는 코드를 사용할 수 없게 되므로 프로그래머가 직접 해당 함수와 헤더 파일을 만들어야 한다.

원본 코드를 참조하여 만들어 본 printf() 함수의 코드는 다음과 같다.

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
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
#include <stdarg.h>
#include <stdint.h>
#include <stddef.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
 
#define UART_BUF_SIZE   256
 
char uart_buf[UART_BUF_SIZE];
 
 
#define to_digit(c)     ((c) - '0')
#define is_digit(c)     ((unsigned)to_digit(c) <= 9)
#define to_char(n)      ((n) + '0')
 
#define BUF             68
 
#define ALT             0X001
#define HEXPREFIX       0X002
#define LADJUST         0X004
#define LONGDBL         0X008
#define LONGINT         0X010
#define QUADINT         0X020
#define SHORTINT        0X040
#define ZEROPAD         0X080
#define FPT             0X100
 
 
 
static char *__ultoa(register uint32_t  val,
                     char               *endp,
                     int                base,
                     int                octzero,
                     char               *xdigs)
{
    register char       *cp = endp;
    register int32_t    sval;
 
    switch (base)
    {
        case 8:
            do
            {
                *--cp = to_char(val & 7);
                val >>= 3;
            } while(val);
            if (octzero && *cp != '0')
                *--cp = '0';
            break;
 
        case 10:
            if (val < 10)
            {
                *--cp = to_char(val);
                return cp;
            }
            if (val > LONG_MAX)
            {
                *--cp = to_char(val % 10);
                sval = val / 10;
            }
            else
                sval = val;
 
            do
            {
                *--cp = to_char(sval % 10);
                sval /= 10;
            } while(sval != 0);
            break;
 
        case 16:
            do
            {
                *--cp = xdigs[val & 15];
                val >>= 4;
            } while(val);
            break;
 
        default:
            break;
    }
 
    return cp;
}
 
static char *__uqtoa(register uint64_t  val,
                     char               *endp,
                     int                base,
                     int                octzero,
                     char               *xdigs)
{
    register char       *cp = endp;
    register int64_t    sval;
 
    if (val <= ULONG_MAX)
        return __ultoa((uint32_t)val, endp, base, octzero, xdigs);
 
    switch (base)
    {
        case 8:
            do
            {
                *--cp = to_char(val & 7);
                val >>= 3;
            } while(val);
            if (octzero && *cp != '0')
                *--cp = '0';
            break;
 
        case 10:
            if (val < 10)
            {
                *--cp = to_char(val % 10);
                return cp;
            }
            if (val > QUAD_MAX)
            {
                *--cp = to_char(val % 10);
                sval = val / 10;
            }
            else
                sval = val;
 
            do
            {
                *--cp = to_char(sval % 10);
                sval /= 10;
            } while(sval != 0);
            break;
 
        case 16:
            do
            {
                *--cp = xdigs[val & 15];
                val >>= 4;
            } while(val);
            break;
 
        default:
            break;
    }
 
    return cp;
}
 
int vprintf(char *strbuf, const char *format, va_list ap)
{
    register char   *fmt;
    register char   *cp;
    register char   ch;
    register int    n;
    register int    flags;
    char            sign;
    int             ret;
    int             width;
    int             prec;
    int             dprec = 0;
    int             size;
    int             fieldsz;
    int             realsz;
    int             base;
    char            *xdigs = NULL;
    uint32_t        ulval = 0;
    uint64_t        uqval = 0;
    char            buf[BUF];
    char            ox[2];
 
 
#define PADSIZE 16
    static char blanks[PADSIZE] =
     {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};
    static char zeroes[PADSIZE] =
     {'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'};
 
#define PRINT(ptr, len)                 \
    do {                                \
        int     i;                      \
        for (i=0; i<len; i++)           \
            *strbuf++ = ptr[i];         \
    }while(0)
 
#define PAD(howmany, with)              \
    do {                                \
        if ((n = (howmany)) > 0) {      \
            while (n > PADSIZE) {       \
                PRINT(with, PADSIZE);   \
                n -= PADSIZE;           \
            }                           \
            PRINT(with, n);             \
        }                               \
    }while(0)
 
#define SARG()                                          \
    (flags & LONGINT ? va_arg(ap, long) :               \
     flags & SHORTINT ? (long)(short)va_arg(ap, int) :  \
     (long)va_arg(ap, int))
#define UARG()                                                  \
    (flags & LONGINT ? va_arg(ap, uint32_t) :                   \
     flags & SHORTINT ? (uint32_t)(uint16_t)va_arg(ap, int) :   \
     (uint32_t)va_arg(ap, uint32_t))
 
 
    fmt = (char *)format;
    ret = 0;
 
    while (1)
    {
        for (cp = fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++);
 
        if ((n = fmt - cp) != 0)
        {
            PRINT(cp, n);
            ret += n;
        }
 
        if (ch == '\0')
            goto done;
 
        fmt++;
 
        flags = 0;
        width = 0;
        prec  = -1;
        sign  = '\0';
 
rflag:
        ch = *fmt++;
reswitch:
        switch (ch)
        {
            case ' ':
                if (!sign)
                    sign = ' ';
                goto rflag;
 
            case '#':
                flags |= ALT;
                goto rflag;
 
            case '*':
                if ((width = va_arg(ap, int)) >= 0)
                    goto rflag;
                width = -width;
 
            case '-':
                flags |= LADJUST;
                goto rflag;
 
            case '+':
                sign = '+';
                goto rflag;
 
            case '.':
                if ((ch = *fmt++) == '*')
                {
                    n = va_arg(ap, int);
                    prec = n < 0 ? -1 : n;
                    goto rflag;
                }
                n = 0;
                while (is_digit(ch))
                {
                    n = 10 * n + to_digit(ch);
                    ch = *fmt++;
                }
                prec = n < 0 ? -1 : n;
                goto reswitch;
 
            case '0':
                flags |= ZEROPAD;
                goto rflag;
 
            case '1' ... '9':
                n = 0;
                do
                {
                    n = 10 * n + to_digit(ch);
                    ch = *fmt++;
                } while(is_digit(ch));
                width = n;
                goto reswitch;
 
            case 'h':
                flags |= SHORTINT;
                goto rflag;
 
            case 'l':
                if (*fmt == 'l')
                {
                    fmt++;
                    flags |= QUADINT;
                }
                else
                    flags |= LONGINT;
                goto rflag;
 
            case 'q':
                flags |= QUADINT;
                goto rflag;
 
            case 'c':
                *(cp = buf) = va_arg(ap, int);
                size = 1;
                sign = '\0';
                break;
 
            case 'D':
                flags |= LONGINT;
            case 'd':
            case 'i':
                if (flags & QUADINT)
                {
                    uqval = va_arg(ap, int64_t);
                    if ((int64_t)uqval < 0)
                    {
                        uqval = -uqval;
                        sign = '-';
                    }
                }
                else
                {
                    ulval = SARG();
                    if ((long)ulval < 0)
                    {
                        ulval = -ulval;
                        sign = '-';
                    }
                }
                base = 10;
                goto number;
 
            case 'O':
                flags |= LONGINT;
            case 'o':
                if (flags & QUADINT)
                    uqval = va_arg(ap, uint64_t);
                else
                    ulval = UARG();
                base = 8;
                goto nosign;
 
            case 'p':
                ulval = (uint32_t)va_arg(ap, void *);
                base = 16;
                xdigs = "0123456789abcdef";
                flags = (flags & ~QUADINT) | HEXPREFIX;
                ch = 'x';
                goto nosign;
 
            case 's':
                if ((cp = va_arg(ap, char *)) == NULL)
                    cp = "(null)";
                if (prec >= 0)
                {
                    char *p = memchr(cp, 0, prec);
                    if (p != NULL)
                    {
                        size = p - cp;
                        if (size > prec)
                            size = prec;
                    }
                    else
                        size = prec;
                }
                else
                    size = strlen(cp);
                sign = '\0';
                break;
 
            case 'U':
                flags |= LONGINT;
            case 'u':
                if (flags & QUADINT)
                    uqval = va_arg(ap, uint64_t);
                else
                    ulval = UARG();
                base = 10;
                goto nosign;
 
            case 'X':
                xdigs = "0123456789ABCDEF";
                goto hex;
            case 'x':
                xdigs = "0123456789abcdef";
hex:
                if (flags & QUADINT)
                    uqval = va_arg(ap, uint64_t);
                else
                    ulval = UARG();
                base = 16;
 
                if (flags & ALT && (flags & QUADINT ? uqval != 0 : ulval != 0))
                    flags |= HEXPREFIX;
nosign:
                sign = '\0';
number:
                if ((dprec = prec) >= 0)
                    flags &= ~ZEROPAD;
                cp = buf + BUF;
                if (flags & QUADINT)
                {
                    if (uqval != 0 || prec != 0)
                        cp = __uqtoa(uqval, cp, base, flags & ALT, xdigs);
                }
                else
                {
                    if (ulval != 0 || prec != 0)
                        cp = __ultoa(ulval, cp, base, flags & ALT, xdigs);
                }
                size = buf + BUF - cp;
                break;
 
            default:
                if (ch == '\0')
                    goto done;
                cp = buf;
                *cp = ch;
                size = 1;
                sign = '\0';
                break;
        }
 
        fieldsz = size;
        if (sign)
            fieldsz++;
        else if (flags & HEXPREFIX)
            fieldsz += 2;
        realsz = dprec > fieldsz ? dprec : fieldsz;
 
        if ((flags & (LADJUST | ZEROPAD)) == 0)
            PAD(width - realsz, blanks);
 
        if (sign)
        {
            *strbuf++ = sign;
        }
        else if (flags & HEXPREFIX)
        {
            ox[0] = '0';
            ox[1] = ch;
            PRINT(ox, 2);
        }
 
        if ((flags & (LADJUST | ZEROPAD)) == ZEROPAD)
            PAD(width - realsz, zeroes);
 
        PAD(dprec - fieldsz, zeroes);
        PRINT(cp, size);
 
        if (flags & LADJUST)
            PAD(width - realsz, blanks);
 
        ret += width > realsz ? width : realsz;
    }
 
done:
    *strbuf = '\0';
    return ret;
}
 
int printf(const char *format, ...)
{
    int     rc;
    va_list ap;
 
    va_start(ap, format);
 
    rc = vprintf(uart_buf, format, ap);
 
    va_end(ap);
 
    puts(uart_buf);
 
    return rc;
}
 
 
void putc(char ch)
{
    extern void uart_putc(char ch);
 
    uart_putc(ch);
 
    if (ch == '\n')
        uart_putc('\r');
}
 
void puts(const char *str)
{
    while(*str)
        putc(*str++);
}

실제 코드를 보면 무척 복잡하다는 느낌이 들것이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int main(int argc, char *argv[])
{
    int     count = 100;
 
    sysclock_init(BD_CLKSRC, BD_PLLMUL, BD_AHBPRE, BD_APB1PRE, BD_APB2PRE, BD_ADCPRE);
    rcc_apb2enable(RCC_APB2_IOPC);
    gpio_set_outmode(GPIO_PORT_C, GPIO_PIN13, GPIO_GEN_PUSHPULL, GPIO_OUT_2M);
 
    uart_init();
 
    while (1)
    {
        delay(500);
        gpio_out_toggle(GPIO_PORT_C, GPIO_PIN13);
        printf("%10s = 0x%08X[%8d]\n", "Count", count, count);
        count--;
    }
 
    return 0;
}

main()를 위와 같이 만들어서 제대로 동작되는지 확인해 본다.  15번 줄에 보여지는 코드가 제대로 표현되는지 실행하여 그 결과를 확인한다.

Count 값이 100에서부터 1씩 감소하여 0이 되고, 그 다음에 -1이 되는것을 확인할 수 있다.

 

프로젝트에서 사용된 코드들은 아래에 첨부 하였다.  

stdio2.zip

 

소스코드를 열어보면 알겠지만 그동안 라이브러리에서 제공하여 그냥 불러 썼던 코드들이 모두 들어 있다.  이번 프로젝트에서는 물론 printf() 함수를 사용하는데 필요한 코드만 들어있다.  다른 함수들이 필요하면 그 코드들도 직접 만들어야 한다.

그렇다면 C에서 제공하는 라이브러리를 그냥 쓰면 되지 굳이 이렇게 불편하게 직접 만들 필요까지 있을까?

그 이유는 다음과 같다.

첫째, 이미 앞의 글에서도 얘기 했다시피 라이브러리코드가 포함되면 프로젝트의 실행 파일 크기가 상당히 커지게 된다.  ROM이나 RAM 크기가 충분하면 문제될게 없겠지만 MCU와 같이 ROM과 RAM 크기가 불과 몇K바이트나 몇십K바이트일뿐인 경우에는 라이브러리가 포함됨으로써 전체적으로 커지게 되는 실행 파일의 크기에 민감해 질 수밖에 없다.  어떤 경우 불과 몇십바이트 크기가 아쉬울때가 있기 때문이다.  이런 경우 라이브러리를 사용하지 않고 직접 필요한 함수만 만들어 쓰게 되면 그 크기를 대폭 줄일수 있게 된다.

둘째, 라이브러리 내부를 알 수 없으므로 원하는 동작이 수행되지 않을때 그 원인을 찾기가 쉽지 않은 경우가 있다.  때로는 보드에 맞지 않게 동작되는 라이브러리 코드가 포함될 수 있기 때문이다.  프로그래머가 직접 라이브러리를 만들어 쓰게 되면 어느 정도는 디버깅이 가능할 수 있게 된다.

세번째 이유는 프로젝트와는 직접적인 관계는 없지만 라이브러리 코드를 직접 만들어 봄으로써 프로그래밍 능력이 높아지게 된다.  좋은 글을 쓰기 위해서는 좋은 책을 많이 읽어야 하는것처럼 좋은 프로그램을 짜기 위해서는 좋은 코드들을 많이 분석해 보아야 한다.  C 라이브러리 코드들은 거의 프로그램의 신들이 만들기 때문에 거의 예술에 가까운 코드들이다.  평소에 절대 사용하지 않는 프로그래밍 기법들을 볼 수 있고, 특히 #define을 이용한 매크로 코드들을 보면 감탄하지 않을 수 없다.

단순히 취미가 아닌 프로그래머를 직업으로 삼으려하는 초보 프로그래머가 있다면 틈틈히 라이브러리 코드를 공부하기 바란다. 

 

[출처] https://embed-avr.tistory.com/120

 

 

 

';
                break;
 
            case 'D':
                flags |= LONGINT;
            case 'd':
            case 'i':
                if (flags & QUADINT)
                {
                    uqval = va_arg(ap, int64_t);
                    if ((int64_t)uqval < 0)
                    {
                        uqval = -uqval;
                        sign = '-';
                    }
                }
                else
                {
                    ulval = SARG();
                    if ((long)ulval < 0)
                    {
                        ulval = -ulval;
                        sign = '-';
                    }
                }
                base = 10;
                goto number;
 
            case 'O':
                flags |= LONGINT;
            case 'o':
                if (flags & QUADINT)
                    uqval = va_arg(ap, uint64_t);
                else
                    ulval = UARG();
                base = 8;
                goto nosign;
 
            case 'p':
                ulval = (uint32_t)va_arg(ap, void *);
                base = 16;
                xdigs = "0123456789abcdef";
                flags = (flags & ~QUADINT) | HEXPREFIX;
                ch = 'x';
                goto nosign;
 
            case 's':
                if ((cp = va_arg(ap, char *)) == NULL)
                    cp = "(null)";
                if (prec >= 0)
                {
                    char *p = memchr(cp, 0, prec);
                    if (p != NULL)
                    {
                        size = p - cp;
                        if (size > prec)
                            size = prec;
                    }
                    else
                        size = prec;
                }
                else
                    size = strlen(cp);
                sign = '

[Embedded Software] ARM Cortex-M : printf()를 만들어 보자

C언어를 배울 때 가장 먼저 사용하는 함수는 printf()일것이다.  또한 C프로그램에서 가장 많이 불리워지는 함수도 printf()일것이다.  그러나 이렇게 많이 사용하고 있는 printf() 함수가 실제로 어떻게 생겼는지 그 소스를 찾아본적은 거의 없을 것이다.

이번 글에서는 printf() 함수를 라이브러리에서 제공하는 것을 사용하지 않고 직접 만들어 보기로 하겠다.

printf() 함수의 소스 코드는 인터넷에서 검색하면 어렵지 않게 찾을 수 있을 것이다.  이 글에서는 BSD-lite 안에 들어 있는 코드를 참조하였다.

아직 C언어에 익숙하지 않은 프로그래머가 보기에는 소스코드가 다소 복잡하게 보일것이다.  따라서 이 코드를 바로 임베디드 시스템에 적용하기에 앞서 visual studio나 리눅스 환경에서 미리 검증한 후 실제 임베디드 보드에서 확인하는것이 좋다.

 

아래 코드는 visual studio에서 동작 확인을 위하여 만들어 본 코드이다.

main.c  main.c

 

직접 만들어 본 코드에 문제가 없음을 확인하였으면 실제 보드에서 동작 시켜 보도록 하겠다.

먼저 해야 할 일은 이제 더이상 컴파일러에 포함되어 있는 C라이브러리가 프로젝트 빌드시에 포함되지 않도록 하기 위하여 Makefile을 수정하여야 한다.

1
2
3
4
5
6
7
8
9
gccincdir := $(shell $(CC) -print-file-name=include)
 
CFLAGS := -g -Os -ffunction-sections -fdata-sections -Wall -std=gnu99
CFLAGS += -fno-builtin -ffreestanding
CFLAGS += -mthumb -mcpu=cortex-m3
CFLAGS += -Iinc -nostdinc -isystem $(gccincdir)
 
LDFLAGS := -L $(shell dirname `$(CC) $(CFLAGS) -print-libgcc-file-name`)
LDFLAGS += -nostdlib

6번줄에 있는 -nostdinc 옵션을 이용하여 라이브러리에서 제공하는 include 파일을 사용하지 않겠다는것을 알려 준다.  하지만, GCC 컴파일러에서 제공하는 헤더 파일은 사용하여야 하기 때문에 -isystem 옵션으로 GCC에서 제공하는 헤더파일을 include 시킨다.  GCC 헤더 파일의 위치는 1번줄에서 표현된 방법으로 그 디렉토리를 알 수 있다.

그리고, 링크시에 라이브러리에서 제공하는 코드가 포함되지 않도록 하기 위하여 9번 줄에서처럼 -nostdlib 옵션을 사용하였다.

컴파일과 링크 단계에서 -nostdinc와 -nostdlib 옵션을 주게 되면 더이상 라이브러리에서 제공하는 코드를 사용할 수 없게 되므로 프로그래머가 직접 해당 함수와 헤더 파일을 만들어야 한다.

원본 코드를 참조하여 만들어 본 printf() 함수의 코드는 다음과 같다.

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
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
#include <stdarg.h>
#include <stdint.h>
#include <stddef.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
 
#define UART_BUF_SIZE   256
 
char uart_buf[UART_BUF_SIZE];
 
 
#define to_digit(c)     ((c) - '0')
#define is_digit(c)     ((unsigned)to_digit(c) <= 9)
#define to_char(n)      ((n) + '0')
 
#define BUF             68
 
#define ALT             0X001
#define HEXPREFIX       0X002
#define LADJUST         0X004
#define LONGDBL         0X008
#define LONGINT         0X010
#define QUADINT         0X020
#define SHORTINT        0X040
#define ZEROPAD         0X080
#define FPT             0X100
 
 
 
static char *__ultoa(register uint32_t  val,
                     char               *endp,
                     int                base,
                     int                octzero,
                     char               *xdigs)
{
    register char       *cp = endp;
    register int32_t    sval;
 
    switch (base)
    {
        case 8:
            do
            {
                *--cp = to_char(val & 7);
                val >>= 3;
            } while(val);
            if (octzero && *cp != '0')
                *--cp = '0';
            break;
 
        case 10:
            if (val < 10)
            {
                *--cp = to_char(val);
                return cp;
            }
            if (val > LONG_MAX)
            {
                *--cp = to_char(val % 10);
                sval = val / 10;
            }
            else
                sval = val;
 
            do
            {
                *--cp = to_char(sval % 10);
                sval /= 10;
            } while(sval != 0);
            break;
 
        case 16:
            do
            {
                *--cp = xdigs[val & 15];
                val >>= 4;
            } while(val);
            break;
 
        default:
            break;
    }
 
    return cp;
}
 
static char *__uqtoa(register uint64_t  val,
                     char               *endp,
                     int                base,
                     int                octzero,
                     char               *xdigs)
{
    register char       *cp = endp;
    register int64_t    sval;
 
    if (val <= ULONG_MAX)
        return __ultoa((uint32_t)val, endp, base, octzero, xdigs);
 
    switch (base)
    {
        case 8:
            do
            {
                *--cp = to_char(val & 7);
                val >>= 3;
            } while(val);
            if (octzero && *cp != '0')
                *--cp = '0';
            break;
 
        case 10:
            if (val < 10)
            {
                *--cp = to_char(val % 10);
                return cp;
            }
            if (val > QUAD_MAX)
            {
                *--cp = to_char(val % 10);
                sval = val / 10;
            }
            else
                sval = val;
 
            do
            {
                *--cp = to_char(sval % 10);
                sval /= 10;
            } while(sval != 0);
            break;
 
        case 16:
            do
            {
                *--cp = xdigs[val & 15];
                val >>= 4;
            } while(val);
            break;
 
        default:
            break;
    }
 
    return cp;
}
 
int vprintf(char *strbuf, const char *format, va_list ap)
{
    register char   *fmt;
    register char   *cp;
    register char   ch;
    register int    n;
    register int    flags;
    char            sign;
    int             ret;
    int             width;
    int             prec;
    int             dprec = 0;
    int             size;
    int             fieldsz;
    int             realsz;
    int             base;
    char            *xdigs = NULL;
    uint32_t        ulval = 0;
    uint64_t        uqval = 0;
    char            buf[BUF];
    char            ox[2];
 
 
#define PADSIZE 16
    static char blanks[PADSIZE] =
     {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};
    static char zeroes[PADSIZE] =
     {'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'};
 
#define PRINT(ptr, len)                 \
    do {                                \
        int     i;                      \
        for (i=0; i<len; i++)           \
            *strbuf++ = ptr[i];         \
    }while(0)
 
#define PAD(howmany, with)              \
    do {                                \
        if ((n = (howmany)) > 0) {      \
            while (n > PADSIZE) {       \
                PRINT(with, PADSIZE);   \
                n -= PADSIZE;           \
            }                           \
            PRINT(with, n);             \
        }                               \
    }while(0)
 
#define SARG()                                          \
    (flags & LONGINT ? va_arg(ap, long) :               \
     flags & SHORTINT ? (long)(short)va_arg(ap, int) :  \
     (long)va_arg(ap, int))
#define UARG()                                                  \
    (flags & LONGINT ? va_arg(ap, uint32_t) :                   \
     flags & SHORTINT ? (uint32_t)(uint16_t)va_arg(ap, int) :   \
     (uint32_t)va_arg(ap, uint32_t))
 
 
    fmt = (char *)format;
    ret = 0;
 
    while (1)
    {
        for (cp = fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++);
 
        if ((n = fmt - cp) != 0)
        {
            PRINT(cp, n);
            ret += n;
        }
 
        if (ch == '\0')
            goto done;
 
        fmt++;
 
        flags = 0;
        width = 0;
        prec  = -1;
        sign  = '\0';
 
rflag:
        ch = *fmt++;
reswitch:
        switch (ch)
        {
            case ' ':
                if (!sign)
                    sign = ' ';
                goto rflag;
 
            case '#':
                flags |= ALT;
                goto rflag;
 
            case '*':
                if ((width = va_arg(ap, int)) >= 0)
                    goto rflag;
                width = -width;
 
            case '-':
                flags |= LADJUST;
                goto rflag;
 
            case '+':
                sign = '+';
                goto rflag;
 
            case '.':
                if ((ch = *fmt++) == '*')
                {
                    n = va_arg(ap, int);
                    prec = n < 0 ? -1 : n;
                    goto rflag;
                }
                n = 0;
                while (is_digit(ch))
                {
                    n = 10 * n + to_digit(ch);
                    ch = *fmt++;
                }
                prec = n < 0 ? -1 : n;
                goto reswitch;
 
            case '0':
                flags |= ZEROPAD;
                goto rflag;
 
            case '1' ... '9':
                n = 0;
                do
                {
                    n = 10 * n + to_digit(ch);
                    ch = *fmt++;
                } while(is_digit(ch));
                width = n;
                goto reswitch;
 
            case 'h':
                flags |= SHORTINT;
                goto rflag;
 
            case 'l':
                if (*fmt == 'l')
                {
                    fmt++;
                    flags |= QUADINT;
                }
                else
                    flags |= LONGINT;
                goto rflag;
 
            case 'q':
                flags |= QUADINT;
                goto rflag;
 
            case 'c':
                *(cp = buf) = va_arg(ap, int);
                size = 1;
                sign = '\0';
                break;
 
            case 'D':
                flags |= LONGINT;
            case 'd':
            case 'i':
                if (flags & QUADINT)
                {
                    uqval = va_arg(ap, int64_t);
                    if ((int64_t)uqval < 0)
                    {
                        uqval = -uqval;
                        sign = '-';
                    }
                }
                else
                {
                    ulval = SARG();
                    if ((long)ulval < 0)
                    {
                        ulval = -ulval;
                        sign = '-';
                    }
                }
                base = 10;
                goto number;
 
            case 'O':
                flags |= LONGINT;
            case 'o':
                if (flags & QUADINT)
                    uqval = va_arg(ap, uint64_t);
                else
                    ulval = UARG();
                base = 8;
                goto nosign;
 
            case 'p':
                ulval = (uint32_t)va_arg(ap, void *);
                base = 16;
                xdigs = "0123456789abcdef";
                flags = (flags & ~QUADINT) | HEXPREFIX;
                ch = 'x';
                goto nosign;
 
            case 's':
                if ((cp = va_arg(ap, char *)) == NULL)
                    cp = "(null)";
                if (prec >= 0)
                {
                    char *p = memchr(cp, 0, prec);
                    if (p != NULL)
                    {
                        size = p - cp;
                        if (size > prec)
                            size = prec;
                    }
                    else
                        size = prec;
                }
                else
                    size = strlen(cp);
                sign = '\0';
                break;
 
            case 'U':
                flags |= LONGINT;
            case 'u':
                if (flags & QUADINT)
                    uqval = va_arg(ap, uint64_t);
                else
                    ulval = UARG();
                base = 10;
                goto nosign;
 
            case 'X':
                xdigs = "0123456789ABCDEF";
                goto hex;
            case 'x':
                xdigs = "0123456789abcdef";
hex:
                if (flags & QUADINT)
                    uqval = va_arg(ap, uint64_t);
                else
                    ulval = UARG();
                base = 16;
 
                if (flags & ALT && (flags & QUADINT ? uqval != 0 : ulval != 0))
                    flags |= HEXPREFIX;
nosign:
                sign = '\0';
number:
                if ((dprec = prec) >= 0)
                    flags &= ~ZEROPAD;
                cp = buf + BUF;
                if (flags & QUADINT)
                {
                    if (uqval != 0 || prec != 0)
                        cp = __uqtoa(uqval, cp, base, flags & ALT, xdigs);
                }
                else
                {
                    if (ulval != 0 || prec != 0)
                        cp = __ultoa(ulval, cp, base, flags & ALT, xdigs);
                }
                size = buf + BUF - cp;
                break;
 
            default:
                if (ch == '\0')
                    goto done;
                cp = buf;
                *cp = ch;
                size = 1;
                sign = '\0';
                break;
        }
 
        fieldsz = size;
        if (sign)
            fieldsz++;
        else if (flags & HEXPREFIX)
            fieldsz += 2;
        realsz = dprec > fieldsz ? dprec : fieldsz;
 
        if ((flags & (LADJUST | ZEROPAD)) == 0)
            PAD(width - realsz, blanks);
 
        if (sign)
        {
            *strbuf++ = sign;
        }
        else if (flags & HEXPREFIX)
        {
            ox[0] = '0';
            ox[1] = ch;
            PRINT(ox, 2);
        }
 
        if ((flags & (LADJUST | ZEROPAD)) == ZEROPAD)
            PAD(width - realsz, zeroes);
 
        PAD(dprec - fieldsz, zeroes);
        PRINT(cp, size);
 
        if (flags & LADJUST)
            PAD(width - realsz, blanks);
 
        ret += width > realsz ? width : realsz;
    }
 
done:
    *strbuf = '\0';
    return ret;
}
 
int printf(const char *format, ...)
{
    int     rc;
    va_list ap;
 
    va_start(ap, format);
 
    rc = vprintf(uart_buf, format, ap);
 
    va_end(ap);
 
    puts(uart_buf);
 
    return rc;
}
 
 
void putc(char ch)
{
    extern void uart_putc(char ch);
 
    uart_putc(ch);
 
    if (ch == '\n')
        uart_putc('\r');
}
 
void puts(const char *str)
{
    while(*str)
        putc(*str++);
}

실제 코드를 보면 무척 복잡하다는 느낌이 들것이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int main(int argc, char *argv[])
{
    int     count = 100;
 
    sysclock_init(BD_CLKSRC, BD_PLLMUL, BD_AHBPRE, BD_APB1PRE, BD_APB2PRE, BD_ADCPRE);
    rcc_apb2enable(RCC_APB2_IOPC);
    gpio_set_outmode(GPIO_PORT_C, GPIO_PIN13, GPIO_GEN_PUSHPULL, GPIO_OUT_2M);
 
    uart_init();
 
    while (1)
    {
        delay(500);
        gpio_out_toggle(GPIO_PORT_C, GPIO_PIN13);
        printf("%10s = 0x%08X[%8d]\n", "Count", count, count);
        count--;
    }
 
    return 0;
}

main()를 위와 같이 만들어서 제대로 동작되는지 확인해 본다.  15번 줄에 보여지는 코드가 제대로 표현되는지 실행하여 그 결과를 확인한다.

Count 값이 100에서부터 1씩 감소하여 0이 되고, 그 다음에 -1이 되는것을 확인할 수 있다.

 

프로젝트에서 사용된 코드들은 아래에 첨부 하였다.  

stdio2.zip

 

소스코드를 열어보면 알겠지만 그동안 라이브러리에서 제공하여 그냥 불러 썼던 코드들이 모두 들어 있다.  이번 프로젝트에서는 물론 printf() 함수를 사용하는데 필요한 코드만 들어있다.  다른 함수들이 필요하면 그 코드들도 직접 만들어야 한다.

그렇다면 C에서 제공하는 라이브러리를 그냥 쓰면 되지 굳이 이렇게 불편하게 직접 만들 필요까지 있을까?

그 이유는 다음과 같다.

첫째, 이미 앞의 글에서도 얘기 했다시피 라이브러리코드가 포함되면 프로젝트의 실행 파일 크기가 상당히 커지게 된다.  ROM이나 RAM 크기가 충분하면 문제될게 없겠지만 MCU와 같이 ROM과 RAM 크기가 불과 몇K바이트나 몇십K바이트일뿐인 경우에는 라이브러리가 포함됨으로써 전체적으로 커지게 되는 실행 파일의 크기에 민감해 질 수밖에 없다.  어떤 경우 불과 몇십바이트 크기가 아쉬울때가 있기 때문이다.  이런 경우 라이브러리를 사용하지 않고 직접 필요한 함수만 만들어 쓰게 되면 그 크기를 대폭 줄일수 있게 된다.

둘째, 라이브러리 내부를 알 수 없으므로 원하는 동작이 수행되지 않을때 그 원인을 찾기가 쉽지 않은 경우가 있다.  때로는 보드에 맞지 않게 동작되는 라이브러리 코드가 포함될 수 있기 때문이다.  프로그래머가 직접 라이브러리를 만들어 쓰게 되면 어느 정도는 디버깅이 가능할 수 있게 된다.

세번째 이유는 프로젝트와는 직접적인 관계는 없지만 라이브러리 코드를 직접 만들어 봄으로써 프로그래밍 능력이 높아지게 된다.  좋은 글을 쓰기 위해서는 좋은 책을 많이 읽어야 하는것처럼 좋은 프로그램을 짜기 위해서는 좋은 코드들을 많이 분석해 보아야 한다.  C 라이브러리 코드들은 거의 프로그램의 신들이 만들기 때문에 거의 예술에 가까운 코드들이다.  평소에 절대 사용하지 않는 프로그래밍 기법들을 볼 수 있고, 특히 #define을 이용한 매크로 코드들을 보면 감탄하지 않을 수 없다.

단순히 취미가 아닌 프로그래머를 직업으로 삼으려하는 초보 프로그래머가 있다면 틈틈히 라이브러리 코드를 공부하기 바란다. 

 

[출처] https://embed-avr.tistory.com/120

 

 

 

';
                break;
 
            case 'U':
                flags |= LONGINT;
            case 'u':
                if (flags & QUADINT)
                    uqval = va_arg(ap, uint64_t);
                else
                    ulval = UARG();
                base = 10;
                goto nosign;
 
            case 'X':
                xdigs = "0123456789ABCDEF";
                goto hex;
            case 'x':
                xdigs = "0123456789abcdef";
hex:
                if (flags & QUADINT)
                    uqval = va_arg(ap, uint64_t);
                else
                    ulval = UARG();
                base = 16;
 
                if (flags & ALT && (flags & QUADINT ? uqval != 0 : ulval != 0))
                    flags |= HEXPREFIX;
nosign:
                sign = '

[Embedded Software] ARM Cortex-M : printf()를 만들어 보자

C언어를 배울 때 가장 먼저 사용하는 함수는 printf()일것이다.  또한 C프로그램에서 가장 많이 불리워지는 함수도 printf()일것이다.  그러나 이렇게 많이 사용하고 있는 printf() 함수가 실제로 어떻게 생겼는지 그 소스를 찾아본적은 거의 없을 것이다.

이번 글에서는 printf() 함수를 라이브러리에서 제공하는 것을 사용하지 않고 직접 만들어 보기로 하겠다.

printf() 함수의 소스 코드는 인터넷에서 검색하면 어렵지 않게 찾을 수 있을 것이다.  이 글에서는 BSD-lite 안에 들어 있는 코드를 참조하였다.

아직 C언어에 익숙하지 않은 프로그래머가 보기에는 소스코드가 다소 복잡하게 보일것이다.  따라서 이 코드를 바로 임베디드 시스템에 적용하기에 앞서 visual studio나 리눅스 환경에서 미리 검증한 후 실제 임베디드 보드에서 확인하는것이 좋다.

 

아래 코드는 visual studio에서 동작 확인을 위하여 만들어 본 코드이다.

main.c  main.c

 

직접 만들어 본 코드에 문제가 없음을 확인하였으면 실제 보드에서 동작 시켜 보도록 하겠다.

먼저 해야 할 일은 이제 더이상 컴파일러에 포함되어 있는 C라이브러리가 프로젝트 빌드시에 포함되지 않도록 하기 위하여 Makefile을 수정하여야 한다.

1
2
3
4
5
6
7
8
9
gccincdir := $(shell $(CC) -print-file-name=include)
 
CFLAGS := -g -Os -ffunction-sections -fdata-sections -Wall -std=gnu99
CFLAGS += -fno-builtin -ffreestanding
CFLAGS += -mthumb -mcpu=cortex-m3
CFLAGS += -Iinc -nostdinc -isystem $(gccincdir)
 
LDFLAGS := -L $(shell dirname `$(CC) $(CFLAGS) -print-libgcc-file-name`)
LDFLAGS += -nostdlib

6번줄에 있는 -nostdinc 옵션을 이용하여 라이브러리에서 제공하는 include 파일을 사용하지 않겠다는것을 알려 준다.  하지만, GCC 컴파일러에서 제공하는 헤더 파일은 사용하여야 하기 때문에 -isystem 옵션으로 GCC에서 제공하는 헤더파일을 include 시킨다.  GCC 헤더 파일의 위치는 1번줄에서 표현된 방법으로 그 디렉토리를 알 수 있다.

그리고, 링크시에 라이브러리에서 제공하는 코드가 포함되지 않도록 하기 위하여 9번 줄에서처럼 -nostdlib 옵션을 사용하였다.

컴파일과 링크 단계에서 -nostdinc와 -nostdlib 옵션을 주게 되면 더이상 라이브러리에서 제공하는 코드를 사용할 수 없게 되므로 프로그래머가 직접 해당 함수와 헤더 파일을 만들어야 한다.

원본 코드를 참조하여 만들어 본 printf() 함수의 코드는 다음과 같다.

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
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
#include <stdarg.h>
#include <stdint.h>
#include <stddef.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
 
#define UART_BUF_SIZE   256
 
char uart_buf[UART_BUF_SIZE];
 
 
#define to_digit(c)     ((c) - '0')
#define is_digit(c)     ((unsigned)to_digit(c) <= 9)
#define to_char(n)      ((n) + '0')
 
#define BUF             68
 
#define ALT             0X001
#define HEXPREFIX       0X002
#define LADJUST         0X004
#define LONGDBL         0X008
#define LONGINT         0X010
#define QUADINT         0X020
#define SHORTINT        0X040
#define ZEROPAD         0X080
#define FPT             0X100
 
 
 
static char *__ultoa(register uint32_t  val,
                     char               *endp,
                     int                base,
                     int                octzero,
                     char               *xdigs)
{
    register char       *cp = endp;
    register int32_t    sval;
 
    switch (base)
    {
        case 8:
            do
            {
                *--cp = to_char(val & 7);
                val >>= 3;
            } while(val);
            if (octzero && *cp != '0')
                *--cp = '0';
            break;
 
        case 10:
            if (val < 10)
            {
                *--cp = to_char(val);
                return cp;
            }
            if (val > LONG_MAX)
            {
                *--cp = to_char(val % 10);
                sval = val / 10;
            }
            else
                sval = val;
 
            do
            {
                *--cp = to_char(sval % 10);
                sval /= 10;
            } while(sval != 0);
            break;
 
        case 16:
            do
            {
                *--cp = xdigs[val & 15];
                val >>= 4;
            } while(val);
            break;
 
        default:
            break;
    }
 
    return cp;
}
 
static char *__uqtoa(register uint64_t  val,
                     char               *endp,
                     int                base,
                     int                octzero,
                     char               *xdigs)
{
    register char       *cp = endp;
    register int64_t    sval;
 
    if (val <= ULONG_MAX)
        return __ultoa((uint32_t)val, endp, base, octzero, xdigs);
 
    switch (base)
    {
        case 8:
            do
            {
                *--cp = to_char(val & 7);
                val >>= 3;
            } while(val);
            if (octzero && *cp != '0')
                *--cp = '0';
            break;
 
        case 10:
            if (val < 10)
            {
                *--cp = to_char(val % 10);
                return cp;
            }
            if (val > QUAD_MAX)
            {
                *--cp = to_char(val % 10);
                sval = val / 10;
            }
            else
                sval = val;
 
            do
            {
                *--cp = to_char(sval % 10);
                sval /= 10;
            } while(sval != 0);
            break;
 
        case 16:
            do
            {
                *--cp = xdigs[val & 15];
                val >>= 4;
            } while(val);
            break;
 
        default:
            break;
    }
 
    return cp;
}
 
int vprintf(char *strbuf, const char *format, va_list ap)
{
    register char   *fmt;
    register char   *cp;
    register char   ch;
    register int    n;
    register int    flags;
    char            sign;
    int             ret;
    int             width;
    int             prec;
    int             dprec = 0;
    int             size;
    int             fieldsz;
    int             realsz;
    int             base;
    char            *xdigs = NULL;
    uint32_t        ulval = 0;
    uint64_t        uqval = 0;
    char            buf[BUF];
    char            ox[2];
 
 
#define PADSIZE 16
    static char blanks[PADSIZE] =
     {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};
    static char zeroes[PADSIZE] =
     {'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'};
 
#define PRINT(ptr, len)                 \
    do {                                \
        int     i;                      \
        for (i=0; i<len; i++)           \
            *strbuf++ = ptr[i];         \
    }while(0)
 
#define PAD(howmany, with)              \
    do {                                \
        if ((n = (howmany)) > 0) {      \
            while (n > PADSIZE) {       \
                PRINT(with, PADSIZE);   \
                n -= PADSIZE;           \
            }                           \
            PRINT(with, n);             \
        }                               \
    }while(0)
 
#define SARG()                                          \
    (flags & LONGINT ? va_arg(ap, long) :               \
     flags & SHORTINT ? (long)(short)va_arg(ap, int) :  \
     (long)va_arg(ap, int))
#define UARG()                                                  \
    (flags & LONGINT ? va_arg(ap, uint32_t) :                   \
     flags & SHORTINT ? (uint32_t)(uint16_t)va_arg(ap, int) :   \
     (uint32_t)va_arg(ap, uint32_t))
 
 
    fmt = (char *)format;
    ret = 0;
 
    while (1)
    {
        for (cp = fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++);
 
        if ((n = fmt - cp) != 0)
        {
            PRINT(cp, n);
            ret += n;
        }
 
        if (ch == '\0')
            goto done;
 
        fmt++;
 
        flags = 0;
        width = 0;
        prec  = -1;
        sign  = '\0';
 
rflag:
        ch = *fmt++;
reswitch:
        switch (ch)
        {
            case ' ':
                if (!sign)
                    sign = ' ';
                goto rflag;
 
            case '#':
                flags |= ALT;
                goto rflag;
 
            case '*':
                if ((width = va_arg(ap, int)) >= 0)
                    goto rflag;
                width = -width;
 
            case '-':
                flags |= LADJUST;
                goto rflag;
 
            case '+':
                sign = '+';
                goto rflag;
 
            case '.':
                if ((ch = *fmt++) == '*')
                {
                    n = va_arg(ap, int);
                    prec = n < 0 ? -1 : n;
                    goto rflag;
                }
                n = 0;
                while (is_digit(ch))
                {
                    n = 10 * n + to_digit(ch);
                    ch = *fmt++;
                }
                prec = n < 0 ? -1 : n;
                goto reswitch;
 
            case '0':
                flags |= ZEROPAD;
                goto rflag;
 
            case '1' ... '9':
                n = 0;
                do
                {
                    n = 10 * n + to_digit(ch);
                    ch = *fmt++;
                } while(is_digit(ch));
                width = n;
                goto reswitch;
 
            case 'h':
                flags |= SHORTINT;
                goto rflag;
 
            case 'l':
                if (*fmt == 'l')
                {
                    fmt++;
                    flags |= QUADINT;
                }
                else
                    flags |= LONGINT;
                goto rflag;
 
            case 'q':
                flags |= QUADINT;
                goto rflag;
 
            case 'c':
                *(cp = buf) = va_arg(ap, int);
                size = 1;
                sign = '\0';
                break;
 
            case 'D':
                flags |= LONGINT;
            case 'd':
            case 'i':
                if (flags & QUADINT)
                {
                    uqval = va_arg(ap, int64_t);
                    if ((int64_t)uqval < 0)
                    {
                        uqval = -uqval;
                        sign = '-';
                    }
                }
                else
                {
                    ulval = SARG();
                    if ((long)ulval < 0)
                    {
                        ulval = -ulval;
                        sign = '-';
                    }
                }
                base = 10;
                goto number;
 
            case 'O':
                flags |= LONGINT;
            case 'o':
                if (flags & QUADINT)
                    uqval = va_arg(ap, uint64_t);
                else
                    ulval = UARG();
                base = 8;
                goto nosign;
 
            case 'p':
                ulval = (uint32_t)va_arg(ap, void *);
                base = 16;
                xdigs = "0123456789abcdef";
                flags = (flags & ~QUADINT) | HEXPREFIX;
                ch = 'x';
                goto nosign;
 
            case 's':
                if ((cp = va_arg(ap, char *)) == NULL)
                    cp = "(null)";
                if (prec >= 0)
                {
                    char *p = memchr(cp, 0, prec);
                    if (p != NULL)
                    {
                        size = p - cp;
                        if (size > prec)
                            size = prec;
                    }
                    else
                        size = prec;
                }
                else
                    size = strlen(cp);
                sign = '\0';
                break;
 
            case 'U':
                flags |= LONGINT;
            case 'u':
                if (flags & QUADINT)
                    uqval = va_arg(ap, uint64_t);
                else
                    ulval = UARG();
                base = 10;
                goto nosign;
 
            case 'X':
                xdigs = "0123456789ABCDEF";
                goto hex;
            case 'x':
                xdigs = "0123456789abcdef";
hex:
                if (flags & QUADINT)
                    uqval = va_arg(ap, uint64_t);
                else
                    ulval = UARG();
                base = 16;
 
                if (flags & ALT && (flags & QUADINT ? uqval != 0 : ulval != 0))
                    flags |= HEXPREFIX;
nosign:
                sign = '\0';
number:
                if ((dprec = prec) >= 0)
                    flags &= ~ZEROPAD;
                cp = buf + BUF;
                if (flags & QUADINT)
                {
                    if (uqval != 0 || prec != 0)
                        cp = __uqtoa(uqval, cp, base, flags & ALT, xdigs);
                }
                else
                {
                    if (ulval != 0 || prec != 0)
                        cp = __ultoa(ulval, cp, base, flags & ALT, xdigs);
                }
                size = buf + BUF - cp;
                break;
 
            default:
                if (ch == '\0')
                    goto done;
                cp = buf;
                *cp = ch;
                size = 1;
                sign = '\0';
                break;
        }
 
        fieldsz = size;
        if (sign)
            fieldsz++;
        else if (flags & HEXPREFIX)
            fieldsz += 2;
        realsz = dprec > fieldsz ? dprec : fieldsz;
 
        if ((flags & (LADJUST | ZEROPAD)) == 0)
            PAD(width - realsz, blanks);
 
        if (sign)
        {
            *strbuf++ = sign;
        }
        else if (flags & HEXPREFIX)
        {
            ox[0] = '0';
            ox[1] = ch;
            PRINT(ox, 2);
        }
 
        if ((flags & (LADJUST | ZEROPAD)) == ZEROPAD)
            PAD(width - realsz, zeroes);
 
        PAD(dprec - fieldsz, zeroes);
        PRINT(cp, size);
 
        if (flags & LADJUST)
            PAD(width - realsz, blanks);
 
        ret += width > realsz ? width : realsz;
    }
 
done:
    *strbuf = '\0';
    return ret;
}
 
int printf(const char *format, ...)
{
    int     rc;
    va_list ap;
 
    va_start(ap, format);
 
    rc = vprintf(uart_buf, format, ap);
 
    va_end(ap);
 
    puts(uart_buf);
 
    return rc;
}
 
 
void putc(char ch)
{
    extern void uart_putc(char ch);
 
    uart_putc(ch);
 
    if (ch == '\n')
        uart_putc('\r');
}
 
void puts(const char *str)
{
    while(*str)
        putc(*str++);
}

실제 코드를 보면 무척 복잡하다는 느낌이 들것이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int main(int argc, char *argv[])
{
    int     count = 100;
 
    sysclock_init(BD_CLKSRC, BD_PLLMUL, BD_AHBPRE, BD_APB1PRE, BD_APB2PRE, BD_ADCPRE);
    rcc_apb2enable(RCC_APB2_IOPC);
    gpio_set_outmode(GPIO_PORT_C, GPIO_PIN13, GPIO_GEN_PUSHPULL, GPIO_OUT_2M);
 
    uart_init();
 
    while (1)
    {
        delay(500);
        gpio_out_toggle(GPIO_PORT_C, GPIO_PIN13);
        printf("%10s = 0x%08X[%8d]\n", "Count", count, count);
        count--;
    }
 
    return 0;
}

main()를 위와 같이 만들어서 제대로 동작되는지 확인해 본다.  15번 줄에 보여지는 코드가 제대로 표현되는지 실행하여 그 결과를 확인한다.

Count 값이 100에서부터 1씩 감소하여 0이 되고, 그 다음에 -1이 되는것을 확인할 수 있다.

 

프로젝트에서 사용된 코드들은 아래에 첨부 하였다.  

stdio2.zip

 

소스코드를 열어보면 알겠지만 그동안 라이브러리에서 제공하여 그냥 불러 썼던 코드들이 모두 들어 있다.  이번 프로젝트에서는 물론 printf() 함수를 사용하는데 필요한 코드만 들어있다.  다른 함수들이 필요하면 그 코드들도 직접 만들어야 한다.

그렇다면 C에서 제공하는 라이브러리를 그냥 쓰면 되지 굳이 이렇게 불편하게 직접 만들 필요까지 있을까?

그 이유는 다음과 같다.

첫째, 이미 앞의 글에서도 얘기 했다시피 라이브러리코드가 포함되면 프로젝트의 실행 파일 크기가 상당히 커지게 된다.  ROM이나 RAM 크기가 충분하면 문제될게 없겠지만 MCU와 같이 ROM과 RAM 크기가 불과 몇K바이트나 몇십K바이트일뿐인 경우에는 라이브러리가 포함됨으로써 전체적으로 커지게 되는 실행 파일의 크기에 민감해 질 수밖에 없다.  어떤 경우 불과 몇십바이트 크기가 아쉬울때가 있기 때문이다.  이런 경우 라이브러리를 사용하지 않고 직접 필요한 함수만 만들어 쓰게 되면 그 크기를 대폭 줄일수 있게 된다.

둘째, 라이브러리 내부를 알 수 없으므로 원하는 동작이 수행되지 않을때 그 원인을 찾기가 쉽지 않은 경우가 있다.  때로는 보드에 맞지 않게 동작되는 라이브러리 코드가 포함될 수 있기 때문이다.  프로그래머가 직접 라이브러리를 만들어 쓰게 되면 어느 정도는 디버깅이 가능할 수 있게 된다.

세번째 이유는 프로젝트와는 직접적인 관계는 없지만 라이브러리 코드를 직접 만들어 봄으로써 프로그래밍 능력이 높아지게 된다.  좋은 글을 쓰기 위해서는 좋은 책을 많이 읽어야 하는것처럼 좋은 프로그램을 짜기 위해서는 좋은 코드들을 많이 분석해 보아야 한다.  C 라이브러리 코드들은 거의 프로그램의 신들이 만들기 때문에 거의 예술에 가까운 코드들이다.  평소에 절대 사용하지 않는 프로그래밍 기법들을 볼 수 있고, 특히 #define을 이용한 매크로 코드들을 보면 감탄하지 않을 수 없다.

단순히 취미가 아닌 프로그래머를 직업으로 삼으려하는 초보 프로그래머가 있다면 틈틈히 라이브러리 코드를 공부하기 바란다. 

 

[출처] https://embed-avr.tistory.com/120

 

 

 

';
number:
                if ((dprec = prec) >= 0)
                    flags &= ~ZEROPAD;
                cp = buf + BUF;
                if (flags & QUADINT)
                {
                    if (uqval != 0 || prec != 0)
                        cp = __uqtoa(uqval, cp, base, flags & ALT, xdigs);
                }
                else
                {
                    if (ulval != 0 || prec != 0)
                        cp = __ultoa(ulval, cp, base, flags & ALT, xdigs);
                }
                size = buf + BUF - cp;
                break;
 
            default:
                if (ch == '

[Embedded Software] ARM Cortex-M : printf()를 만들어 보자

C언어를 배울 때 가장 먼저 사용하는 함수는 printf()일것이다.  또한 C프로그램에서 가장 많이 불리워지는 함수도 printf()일것이다.  그러나 이렇게 많이 사용하고 있는 printf() 함수가 실제로 어떻게 생겼는지 그 소스를 찾아본적은 거의 없을 것이다.

이번 글에서는 printf() 함수를 라이브러리에서 제공하는 것을 사용하지 않고 직접 만들어 보기로 하겠다.

printf() 함수의 소스 코드는 인터넷에서 검색하면 어렵지 않게 찾을 수 있을 것이다.  이 글에서는 BSD-lite 안에 들어 있는 코드를 참조하였다.

아직 C언어에 익숙하지 않은 프로그래머가 보기에는 소스코드가 다소 복잡하게 보일것이다.  따라서 이 코드를 바로 임베디드 시스템에 적용하기에 앞서 visual studio나 리눅스 환경에서 미리 검증한 후 실제 임베디드 보드에서 확인하는것이 좋다.

 

아래 코드는 visual studio에서 동작 확인을 위하여 만들어 본 코드이다.

main.c  main.c

 

직접 만들어 본 코드에 문제가 없음을 확인하였으면 실제 보드에서 동작 시켜 보도록 하겠다.

먼저 해야 할 일은 이제 더이상 컴파일러에 포함되어 있는 C라이브러리가 프로젝트 빌드시에 포함되지 않도록 하기 위하여 Makefile을 수정하여야 한다.

1
2
3
4
5
6
7
8
9
gccincdir := $(shell $(CC) -print-file-name=include)
 
CFLAGS := -g -Os -ffunction-sections -fdata-sections -Wall -std=gnu99
CFLAGS += -fno-builtin -ffreestanding
CFLAGS += -mthumb -mcpu=cortex-m3
CFLAGS += -Iinc -nostdinc -isystem $(gccincdir)
 
LDFLAGS := -L $(shell dirname `$(CC) $(CFLAGS) -print-libgcc-file-name`)
LDFLAGS += -nostdlib

6번줄에 있는 -nostdinc 옵션을 이용하여 라이브러리에서 제공하는 include 파일을 사용하지 않겠다는것을 알려 준다.  하지만, GCC 컴파일러에서 제공하는 헤더 파일은 사용하여야 하기 때문에 -isystem 옵션으로 GCC에서 제공하는 헤더파일을 include 시킨다.  GCC 헤더 파일의 위치는 1번줄에서 표현된 방법으로 그 디렉토리를 알 수 있다.

그리고, 링크시에 라이브러리에서 제공하는 코드가 포함되지 않도록 하기 위하여 9번 줄에서처럼 -nostdlib 옵션을 사용하였다.

컴파일과 링크 단계에서 -nostdinc와 -nostdlib 옵션을 주게 되면 더이상 라이브러리에서 제공하는 코드를 사용할 수 없게 되므로 프로그래머가 직접 해당 함수와 헤더 파일을 만들어야 한다.

원본 코드를 참조하여 만들어 본 printf() 함수의 코드는 다음과 같다.

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
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
#include <stdarg.h>
#include <stdint.h>
#include <stddef.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
 
#define UART_BUF_SIZE   256
 
char uart_buf[UART_BUF_SIZE];
 
 
#define to_digit(c)     ((c) - '0')
#define is_digit(c)     ((unsigned)to_digit(c) <= 9)
#define to_char(n)      ((n) + '0')
 
#define BUF             68
 
#define ALT             0X001
#define HEXPREFIX       0X002
#define LADJUST         0X004
#define LONGDBL         0X008
#define LONGINT         0X010
#define QUADINT         0X020
#define SHORTINT        0X040
#define ZEROPAD         0X080
#define FPT             0X100
 
 
 
static char *__ultoa(register uint32_t  val,
                     char               *endp,
                     int                base,
                     int                octzero,
                     char               *xdigs)
{
    register char       *cp = endp;
    register int32_t    sval;
 
    switch (base)
    {
        case 8:
            do
            {
                *--cp = to_char(val & 7);
                val >>= 3;
            } while(val);
            if (octzero && *cp != '0')
                *--cp = '0';
            break;
 
        case 10:
            if (val < 10)
            {
                *--cp = to_char(val);
                return cp;
            }
            if (val > LONG_MAX)
            {
                *--cp = to_char(val % 10);
                sval = val / 10;
            }
            else
                sval = val;
 
            do
            {
                *--cp = to_char(sval % 10);
                sval /= 10;
            } while(sval != 0);
            break;
 
        case 16:
            do
            {
                *--cp = xdigs[val & 15];
                val >>= 4;
            } while(val);
            break;
 
        default:
            break;
    }
 
    return cp;
}
 
static char *__uqtoa(register uint64_t  val,
                     char               *endp,
                     int                base,
                     int                octzero,
                     char               *xdigs)
{
    register char       *cp = endp;
    register int64_t    sval;
 
    if (val <= ULONG_MAX)
        return __ultoa((uint32_t)val, endp, base, octzero, xdigs);
 
    switch (base)
    {
        case 8:
            do
            {
                *--cp = to_char(val & 7);
                val >>= 3;
            } while(val);
            if (octzero && *cp != '0')
                *--cp = '0';
            break;
 
        case 10:
            if (val < 10)
            {
                *--cp = to_char(val % 10);
                return cp;
            }
            if (val > QUAD_MAX)
            {
                *--cp = to_char(val % 10);
                sval = val / 10;
            }
            else
                sval = val;
 
            do
            {
                *--cp = to_char(sval % 10);
                sval /= 10;
            } while(sval != 0);
            break;
 
        case 16:
            do
            {
                *--cp = xdigs[val & 15];
                val >>= 4;
            } while(val);
            break;
 
        default:
            break;
    }
 
    return cp;
}
 
int vprintf(char *strbuf, const char *format, va_list ap)
{
    register char   *fmt;
    register char   *cp;
    register char   ch;
    register int    n;
    register int    flags;
    char            sign;
    int             ret;
    int             width;
    int             prec;
    int             dprec = 0;
    int             size;
    int             fieldsz;
    int             realsz;
    int             base;
    char            *xdigs = NULL;
    uint32_t        ulval = 0;
    uint64_t        uqval = 0;
    char            buf[BUF];
    char            ox[2];
 
 
#define PADSIZE 16
    static char blanks[PADSIZE] =
     {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};
    static char zeroes[PADSIZE] =
     {'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'};
 
#define PRINT(ptr, len)                 \
    do {                                \
        int     i;                      \
        for (i=0; i<len; i++)           \
            *strbuf++ = ptr[i];         \
    }while(0)
 
#define PAD(howmany, with)              \
    do {                                \
        if ((n = (howmany)) > 0) {      \
            while (n > PADSIZE) {       \
                PRINT(with, PADSIZE);   \
                n -= PADSIZE;           \
            }                           \
            PRINT(with, n);             \
        }                               \
    }while(0)
 
#define SARG()                                          \
    (flags & LONGINT ? va_arg(ap, long) :               \
     flags & SHORTINT ? (long)(short)va_arg(ap, int) :  \
     (long)va_arg(ap, int))
#define UARG()                                                  \
    (flags & LONGINT ? va_arg(ap, uint32_t) :                   \
     flags & SHORTINT ? (uint32_t)(uint16_t)va_arg(ap, int) :   \
     (uint32_t)va_arg(ap, uint32_t))
 
 
    fmt = (char *)format;
    ret = 0;
 
    while (1)
    {
        for (cp = fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++);
 
        if ((n = fmt - cp) != 0)
        {
            PRINT(cp, n);
            ret += n;
        }
 
        if (ch == '\0')
            goto done;
 
        fmt++;
 
        flags = 0;
        width = 0;
        prec  = -1;
        sign  = '\0';
 
rflag:
        ch = *fmt++;
reswitch:
        switch (ch)
        {
            case ' ':
                if (!sign)
                    sign = ' ';
                goto rflag;
 
            case '#':
                flags |= ALT;
                goto rflag;
 
            case '*':
                if ((width = va_arg(ap, int)) >= 0)
                    goto rflag;
                width = -width;
 
            case '-':
                flags |= LADJUST;
                goto rflag;
 
            case '+':
                sign = '+';
                goto rflag;
 
            case '.':
                if ((ch = *fmt++) == '*')
                {
                    n = va_arg(ap, int);
                    prec = n < 0 ? -1 : n;
                    goto rflag;
                }
                n = 0;
                while (is_digit(ch))
                {
                    n = 10 * n + to_digit(ch);
                    ch = *fmt++;
                }
                prec = n < 0 ? -1 : n;
                goto reswitch;
 
            case '0':
                flags |= ZEROPAD;
                goto rflag;
 
            case '1' ... '9':
                n = 0;
                do
                {
                    n = 10 * n + to_digit(ch);
                    ch = *fmt++;
                } while(is_digit(ch));
                width = n;
                goto reswitch;
 
            case 'h':
                flags |= SHORTINT;
                goto rflag;
 
            case 'l':
                if (*fmt == 'l')
                {
                    fmt++;
                    flags |= QUADINT;
                }
                else
                    flags |= LONGINT;
                goto rflag;
 
            case 'q':
                flags |= QUADINT;
                goto rflag;
 
            case 'c':
                *(cp = buf) = va_arg(ap, int);
                size = 1;
                sign = '\0';
                break;
 
            case 'D':
                flags |= LONGINT;
            case 'd':
            case 'i':
                if (flags & QUADINT)
                {
                    uqval = va_arg(ap, int64_t);
                    if ((int64_t)uqval < 0)
                    {
                        uqval = -uqval;
                        sign = '-';
                    }
                }
                else
                {
                    ulval = SARG();
                    if ((long)ulval < 0)
                    {
                        ulval = -ulval;
                        sign = '-';
                    }
                }
                base = 10;
                goto number;
 
            case 'O':
                flags |= LONGINT;
            case 'o':
                if (flags & QUADINT)
                    uqval = va_arg(ap, uint64_t);
                else
                    ulval = UARG();
                base = 8;
                goto nosign;
 
            case 'p':
                ulval = (uint32_t)va_arg(ap, void *);
                base = 16;
                xdigs = "0123456789abcdef";
                flags = (flags & ~QUADINT) | HEXPREFIX;
                ch = 'x';
                goto nosign;
 
            case 's':
                if ((cp = va_arg(ap, char *)) == NULL)
                    cp = "(null)";
                if (prec >= 0)
                {
                    char *p = memchr(cp, 0, prec);
                    if (p != NULL)
                    {
                        size = p - cp;
                        if (size > prec)
                            size = prec;
                    }
                    else
                        size = prec;
                }
                else
                    size = strlen(cp);
                sign = '\0';
                break;
 
            case 'U':
                flags |= LONGINT;
            case 'u':
                if (flags & QUADINT)
                    uqval = va_arg(ap, uint64_t);
                else
                    ulval = UARG();
                base = 10;
                goto nosign;
 
            case 'X':
                xdigs = "0123456789ABCDEF";
                goto hex;
            case 'x':
                xdigs = "0123456789abcdef";
hex:
                if (flags & QUADINT)
                    uqval = va_arg(ap, uint64_t);
                else
                    ulval = UARG();
                base = 16;
 
                if (flags & ALT && (flags & QUADINT ? uqval != 0 : ulval != 0))
                    flags |= HEXPREFIX;
nosign:
                sign = '\0';
number:
                if ((dprec = prec) >= 0)
                    flags &= ~ZEROPAD;
                cp = buf + BUF;
                if (flags & QUADINT)
                {
                    if (uqval != 0 || prec != 0)
                        cp = __uqtoa(uqval, cp, base, flags & ALT, xdigs);
                }
                else
                {
                    if (ulval != 0 || prec != 0)
                        cp = __ultoa(ulval, cp, base, flags & ALT, xdigs);
                }
                size = buf + BUF - cp;
                break;
 
            default:
                if (ch == '\0')
                    goto done;
                cp = buf;
                *cp = ch;
                size = 1;
                sign = '\0';
                break;
        }
 
        fieldsz = size;
        if (sign)
            fieldsz++;
        else if (flags & HEXPREFIX)
            fieldsz += 2;
        realsz = dprec > fieldsz ? dprec : fieldsz;
 
        if ((flags & (LADJUST | ZEROPAD)) == 0)
            PAD(width - realsz, blanks);
 
        if (sign)
        {
            *strbuf++ = sign;
        }
        else if (flags & HEXPREFIX)
        {
            ox[0] = '0';
            ox[1] = ch;
            PRINT(ox, 2);
        }
 
        if ((flags & (LADJUST | ZEROPAD)) == ZEROPAD)
            PAD(width - realsz, zeroes);
 
        PAD(dprec - fieldsz, zeroes);
        PRINT(cp, size);
 
        if (flags & LADJUST)
            PAD(width - realsz, blanks);
 
        ret += width > realsz ? width : realsz;
    }
 
done:
    *strbuf = '\0';
    return ret;
}
 
int printf(const char *format, ...)
{
    int     rc;
    va_list ap;
 
    va_start(ap, format);
 
    rc = vprintf(uart_buf, format, ap);
 
    va_end(ap);
 
    puts(uart_buf);
 
    return rc;
}
 
 
void putc(char ch)
{
    extern void uart_putc(char ch);
 
    uart_putc(ch);
 
    if (ch == '\n')
        uart_putc('\r');
}
 
void puts(const char *str)
{
    while(*str)
        putc(*str++);
}

실제 코드를 보면 무척 복잡하다는 느낌이 들것이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int main(int argc, char *argv[])
{
    int     count = 100;
 
    sysclock_init(BD_CLKSRC, BD_PLLMUL, BD_AHBPRE, BD_APB1PRE, BD_APB2PRE, BD_ADCPRE);
    rcc_apb2enable(RCC_APB2_IOPC);
    gpio_set_outmode(GPIO_PORT_C, GPIO_PIN13, GPIO_GEN_PUSHPULL, GPIO_OUT_2M);
 
    uart_init();
 
    while (1)
    {
        delay(500);
        gpio_out_toggle(GPIO_PORT_C, GPIO_PIN13);
        printf("%10s = 0x%08X[%8d]\n", "Count", count, count);
        count--;
    }
 
    return 0;
}

main()를 위와 같이 만들어서 제대로 동작되는지 확인해 본다.  15번 줄에 보여지는 코드가 제대로 표현되는지 실행하여 그 결과를 확인한다.

Count 값이 100에서부터 1씩 감소하여 0이 되고, 그 다음에 -1이 되는것을 확인할 수 있다.

 

프로젝트에서 사용된 코드들은 아래에 첨부 하였다.  

stdio2.zip

 

소스코드를 열어보면 알겠지만 그동안 라이브러리에서 제공하여 그냥 불러 썼던 코드들이 모두 들어 있다.  이번 프로젝트에서는 물론 printf() 함수를 사용하는데 필요한 코드만 들어있다.  다른 함수들이 필요하면 그 코드들도 직접 만들어야 한다.

그렇다면 C에서 제공하는 라이브러리를 그냥 쓰면 되지 굳이 이렇게 불편하게 직접 만들 필요까지 있을까?

그 이유는 다음과 같다.

첫째, 이미 앞의 글에서도 얘기 했다시피 라이브러리코드가 포함되면 프로젝트의 실행 파일 크기가 상당히 커지게 된다.  ROM이나 RAM 크기가 충분하면 문제될게 없겠지만 MCU와 같이 ROM과 RAM 크기가 불과 몇K바이트나 몇십K바이트일뿐인 경우에는 라이브러리가 포함됨으로써 전체적으로 커지게 되는 실행 파일의 크기에 민감해 질 수밖에 없다.  어떤 경우 불과 몇십바이트 크기가 아쉬울때가 있기 때문이다.  이런 경우 라이브러리를 사용하지 않고 직접 필요한 함수만 만들어 쓰게 되면 그 크기를 대폭 줄일수 있게 된다.

둘째, 라이브러리 내부를 알 수 없으므로 원하는 동작이 수행되지 않을때 그 원인을 찾기가 쉽지 않은 경우가 있다.  때로는 보드에 맞지 않게 동작되는 라이브러리 코드가 포함될 수 있기 때문이다.  프로그래머가 직접 라이브러리를 만들어 쓰게 되면 어느 정도는 디버깅이 가능할 수 있게 된다.

세번째 이유는 프로젝트와는 직접적인 관계는 없지만 라이브러리 코드를 직접 만들어 봄으로써 프로그래밍 능력이 높아지게 된다.  좋은 글을 쓰기 위해서는 좋은 책을 많이 읽어야 하는것처럼 좋은 프로그램을 짜기 위해서는 좋은 코드들을 많이 분석해 보아야 한다.  C 라이브러리 코드들은 거의 프로그램의 신들이 만들기 때문에 거의 예술에 가까운 코드들이다.  평소에 절대 사용하지 않는 프로그래밍 기법들을 볼 수 있고, 특히 #define을 이용한 매크로 코드들을 보면 감탄하지 않을 수 없다.

단순히 취미가 아닌 프로그래머를 직업으로 삼으려하는 초보 프로그래머가 있다면 틈틈히 라이브러리 코드를 공부하기 바란다. 

 

[출처] https://embed-avr.tistory.com/120

 

 

 

')
                    goto done;
                cp = buf;
                *cp = ch;
                size = 1;
                sign = '

[Embedded Software] ARM Cortex-M : printf()를 만들어 보자

C언어를 배울 때 가장 먼저 사용하는 함수는 printf()일것이다.  또한 C프로그램에서 가장 많이 불리워지는 함수도 printf()일것이다.  그러나 이렇게 많이 사용하고 있는 printf() 함수가 실제로 어떻게 생겼는지 그 소스를 찾아본적은 거의 없을 것이다.

이번 글에서는 printf() 함수를 라이브러리에서 제공하는 것을 사용하지 않고 직접 만들어 보기로 하겠다.

printf() 함수의 소스 코드는 인터넷에서 검색하면 어렵지 않게 찾을 수 있을 것이다.  이 글에서는 BSD-lite 안에 들어 있는 코드를 참조하였다.

아직 C언어에 익숙하지 않은 프로그래머가 보기에는 소스코드가 다소 복잡하게 보일것이다.  따라서 이 코드를 바로 임베디드 시스템에 적용하기에 앞서 visual studio나 리눅스 환경에서 미리 검증한 후 실제 임베디드 보드에서 확인하는것이 좋다.

 

아래 코드는 visual studio에서 동작 확인을 위하여 만들어 본 코드이다.

main.c  main.c

 

직접 만들어 본 코드에 문제가 없음을 확인하였으면 실제 보드에서 동작 시켜 보도록 하겠다.

먼저 해야 할 일은 이제 더이상 컴파일러에 포함되어 있는 C라이브러리가 프로젝트 빌드시에 포함되지 않도록 하기 위하여 Makefile을 수정하여야 한다.

1
2
3
4
5
6
7
8
9
gccincdir := $(shell $(CC) -print-file-name=include)
 
CFLAGS := -g -Os -ffunction-sections -fdata-sections -Wall -std=gnu99
CFLAGS += -fno-builtin -ffreestanding
CFLAGS += -mthumb -mcpu=cortex-m3
CFLAGS += -Iinc -nostdinc -isystem $(gccincdir)
 
LDFLAGS := -L $(shell dirname `$(CC) $(CFLAGS) -print-libgcc-file-name`)
LDFLAGS += -nostdlib

6번줄에 있는 -nostdinc 옵션을 이용하여 라이브러리에서 제공하는 include 파일을 사용하지 않겠다는것을 알려 준다.  하지만, GCC 컴파일러에서 제공하는 헤더 파일은 사용하여야 하기 때문에 -isystem 옵션으로 GCC에서 제공하는 헤더파일을 include 시킨다.  GCC 헤더 파일의 위치는 1번줄에서 표현된 방법으로 그 디렉토리를 알 수 있다.

그리고, 링크시에 라이브러리에서 제공하는 코드가 포함되지 않도록 하기 위하여 9번 줄에서처럼 -nostdlib 옵션을 사용하였다.

컴파일과 링크 단계에서 -nostdinc와 -nostdlib 옵션을 주게 되면 더이상 라이브러리에서 제공하는 코드를 사용할 수 없게 되므로 프로그래머가 직접 해당 함수와 헤더 파일을 만들어야 한다.

원본 코드를 참조하여 만들어 본 printf() 함수의 코드는 다음과 같다.

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
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
#include <stdarg.h>
#include <stdint.h>
#include <stddef.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
 
#define UART_BUF_SIZE   256
 
char uart_buf[UART_BUF_SIZE];
 
 
#define to_digit(c)     ((c) - '0')
#define is_digit(c)     ((unsigned)to_digit(c) <= 9)
#define to_char(n)      ((n) + '0')
 
#define BUF             68
 
#define ALT             0X001
#define HEXPREFIX       0X002
#define LADJUST         0X004
#define LONGDBL         0X008
#define LONGINT         0X010
#define QUADINT         0X020
#define SHORTINT        0X040
#define ZEROPAD         0X080
#define FPT             0X100
 
 
 
static char *__ultoa(register uint32_t  val,
                     char               *endp,
                     int                base,
                     int                octzero,
                     char               *xdigs)
{
    register char       *cp = endp;
    register int32_t    sval;
 
    switch (base)
    {
        case 8:
            do
            {
                *--cp = to_char(val & 7);
                val >>= 3;
            } while(val);
            if (octzero && *cp != '0')
                *--cp = '0';
            break;
 
        case 10:
            if (val < 10)
            {
                *--cp = to_char(val);
                return cp;
            }
            if (val > LONG_MAX)
            {
                *--cp = to_char(val % 10);
                sval = val / 10;
            }
            else
                sval = val;
 
            do
            {
                *--cp = to_char(sval % 10);
                sval /= 10;
            } while(sval != 0);
            break;
 
        case 16:
            do
            {
                *--cp = xdigs[val & 15];
                val >>= 4;
            } while(val);
            break;
 
        default:
            break;
    }
 
    return cp;
}
 
static char *__uqtoa(register uint64_t  val,
                     char               *endp,
                     int                base,
                     int                octzero,
                     char               *xdigs)
{
    register char       *cp = endp;
    register int64_t    sval;
 
    if (val <= ULONG_MAX)
        return __ultoa((uint32_t)val, endp, base, octzero, xdigs);
 
    switch (base)
    {
        case 8:
            do
            {
                *--cp = to_char(val & 7);
                val >>= 3;
            } while(val);
            if (octzero && *cp != '0')
                *--cp = '0';
            break;
 
        case 10:
            if (val < 10)
            {
                *--cp = to_char(val % 10);
                return cp;
            }
            if (val > QUAD_MAX)
            {
                *--cp = to_char(val % 10);
                sval = val / 10;
            }
            else
                sval = val;
 
            do
            {
                *--cp = to_char(sval % 10);
                sval /= 10;
            } while(sval != 0);
            break;
 
        case 16:
            do
            {
                *--cp = xdigs[val & 15];
                val >>= 4;
            } while(val);
            break;
 
        default:
            break;
    }
 
    return cp;
}
 
int vprintf(char *strbuf, const char *format, va_list ap)
{
    register char   *fmt;
    register char   *cp;
    register char   ch;
    register int    n;
    register int    flags;
    char            sign;
    int             ret;
    int             width;
    int             prec;
    int             dprec = 0;
    int             size;
    int             fieldsz;
    int             realsz;
    int             base;
    char            *xdigs = NULL;
    uint32_t        ulval = 0;
    uint64_t        uqval = 0;
    char            buf[BUF];
    char            ox[2];
 
 
#define PADSIZE 16
    static char blanks[PADSIZE] =
     {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};
    static char zeroes[PADSIZE] =
     {'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'};
 
#define PRINT(ptr, len)                 \
    do {                                \
        int     i;                      \
        for (i=0; i<len; i++)           \
            *strbuf++ = ptr[i];         \
    }while(0)
 
#define PAD(howmany, with)              \
    do {                                \
        if ((n = (howmany)) > 0) {      \
            while (n > PADSIZE) {       \
                PRINT(with, PADSIZE);   \
                n -= PADSIZE;           \
            }                           \
            PRINT(with, n);             \
        }                               \
    }while(0)
 
#define SARG()                                          \
    (flags & LONGINT ? va_arg(ap, long) :               \
     flags & SHORTINT ? (long)(short)va_arg(ap, int) :  \
     (long)va_arg(ap, int))
#define UARG()                                                  \
    (flags & LONGINT ? va_arg(ap, uint32_t) :                   \
     flags & SHORTINT ? (uint32_t)(uint16_t)va_arg(ap, int) :   \
     (uint32_t)va_arg(ap, uint32_t))
 
 
    fmt = (char *)format;
    ret = 0;
 
    while (1)
    {
        for (cp = fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++);
 
        if ((n = fmt - cp) != 0)
        {
            PRINT(cp, n);
            ret += n;
        }
 
        if (ch == '\0')
            goto done;
 
        fmt++;
 
        flags = 0;
        width = 0;
        prec  = -1;
        sign  = '\0';
 
rflag:
        ch = *fmt++;
reswitch:
        switch (ch)
        {
            case ' ':
                if (!sign)
                    sign = ' ';
                goto rflag;
 
            case '#':
                flags |= ALT;
                goto rflag;
 
            case '*':
                if ((width = va_arg(ap, int)) >= 0)
                    goto rflag;
                width = -width;
 
            case '-':
                flags |= LADJUST;
                goto rflag;
 
            case '+':
                sign = '+';
                goto rflag;
 
            case '.':
                if ((ch = *fmt++) == '*')
                {
                    n = va_arg(ap, int);
                    prec = n < 0 ? -1 : n;
                    goto rflag;
                }
                n = 0;
                while (is_digit(ch))
                {
                    n = 10 * n + to_digit(ch);
                    ch = *fmt++;
                }
                prec = n < 0 ? -1 : n;
                goto reswitch;
 
            case '0':
                flags |= ZEROPAD;
                goto rflag;
 
            case '1' ... '9':
                n = 0;
                do
                {
                    n = 10 * n + to_digit(ch);
                    ch = *fmt++;
                } while(is_digit(ch));
                width = n;
                goto reswitch;
 
            case 'h':
                flags |= SHORTINT;
                goto rflag;
 
            case 'l':
                if (*fmt == 'l')
                {
                    fmt++;
                    flags |= QUADINT;
                }
                else
                    flags |= LONGINT;
                goto rflag;
 
            case 'q':
                flags |= QUADINT;
                goto rflag;
 
            case 'c':
                *(cp = buf) = va_arg(ap, int);
                size = 1;
                sign = '\0';
                break;
 
            case 'D':
                flags |= LONGINT;
            case 'd':
            case 'i':
                if (flags & QUADINT)
                {
                    uqval = va_arg(ap, int64_t);
                    if ((int64_t)uqval < 0)
                    {
                        uqval = -uqval;
                        sign = '-';
                    }
                }
                else
                {
                    ulval = SARG();
                    if ((long)ulval < 0)
                    {
                        ulval = -ulval;
                        sign = '-';
                    }
                }
                base = 10;
                goto number;
 
            case 'O':
                flags |= LONGINT;
            case 'o':
                if (flags & QUADINT)
                    uqval = va_arg(ap, uint64_t);
                else
                    ulval = UARG();
                base = 8;
                goto nosign;
 
            case 'p':
                ulval = (uint32_t)va_arg(ap, void *);
                base = 16;
                xdigs = "0123456789abcdef";
                flags = (flags & ~QUADINT) | HEXPREFIX;
                ch = 'x';
                goto nosign;
 
            case 's':
                if ((cp = va_arg(ap, char *)) == NULL)
                    cp = "(null)";
                if (prec >= 0)
                {
                    char *p = memchr(cp, 0, prec);
                    if (p != NULL)
                    {
                        size = p - cp;
                        if (size > prec)
                            size = prec;
                    }
                    else
                        size = prec;
                }
                else
                    size = strlen(cp);
                sign = '\0';
                break;
 
            case 'U':
                flags |= LONGINT;
            case 'u':
                if (flags & QUADINT)
                    uqval = va_arg(ap, uint64_t);
                else
                    ulval = UARG();
                base = 10;
                goto nosign;
 
            case 'X':
                xdigs = "0123456789ABCDEF";
                goto hex;
            case 'x':
                xdigs = "0123456789abcdef";
hex:
                if (flags & QUADINT)
                    uqval = va_arg(ap, uint64_t);
                else
                    ulval = UARG();
                base = 16;
 
                if (flags & ALT && (flags & QUADINT ? uqval != 0 : ulval != 0))
                    flags |= HEXPREFIX;
nosign:
                sign = '\0';
number:
                if ((dprec = prec) >= 0)
                    flags &= ~ZEROPAD;
                cp = buf + BUF;
                if (flags & QUADINT)
                {
                    if (uqval != 0 || prec != 0)
                        cp = __uqtoa(uqval, cp, base, flags & ALT, xdigs);
                }
                else
                {
                    if (ulval != 0 || prec != 0)
                        cp = __ultoa(ulval, cp, base, flags & ALT, xdigs);
                }
                size = buf + BUF - cp;
                break;
 
            default:
                if (ch == '\0')
                    goto done;
                cp = buf;
                *cp = ch;
                size = 1;
                sign = '\0';
                break;
        }
 
        fieldsz = size;
        if (sign)
            fieldsz++;
        else if (flags & HEXPREFIX)
            fieldsz += 2;
        realsz = dprec > fieldsz ? dprec : fieldsz;
 
        if ((flags & (LADJUST | ZEROPAD)) == 0)
            PAD(width - realsz, blanks);
 
        if (sign)
        {
            *strbuf++ = sign;
        }
        else if (flags & HEXPREFIX)
        {
            ox[0] = '0';
            ox[1] = ch;
            PRINT(ox, 2);
        }
 
        if ((flags & (LADJUST | ZEROPAD)) == ZEROPAD)
            PAD(width - realsz, zeroes);
 
        PAD(dprec - fieldsz, zeroes);
        PRINT(cp, size);
 
        if (flags & LADJUST)
            PAD(width - realsz, blanks);
 
        ret += width > realsz ? width : realsz;
    }
 
done:
    *strbuf = '\0';
    return ret;
}
 
int printf(const char *format, ...)
{
    int     rc;
    va_list ap;
 
    va_start(ap, format);
 
    rc = vprintf(uart_buf, format, ap);
 
    va_end(ap);
 
    puts(uart_buf);
 
    return rc;
}
 
 
void putc(char ch)
{
    extern void uart_putc(char ch);
 
    uart_putc(ch);
 
    if (ch == '\n')
        uart_putc('\r');
}
 
void puts(const char *str)
{
    while(*str)
        putc(*str++);
}

실제 코드를 보면 무척 복잡하다는 느낌이 들것이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int main(int argc, char *argv[])
{
    int     count = 100;
 
    sysclock_init(BD_CLKSRC, BD_PLLMUL, BD_AHBPRE, BD_APB1PRE, BD_APB2PRE, BD_ADCPRE);
    rcc_apb2enable(RCC_APB2_IOPC);
    gpio_set_outmode(GPIO_PORT_C, GPIO_PIN13, GPIO_GEN_PUSHPULL, GPIO_OUT_2M);
 
    uart_init();
 
    while (1)
    {
        delay(500);
        gpio_out_toggle(GPIO_PORT_C, GPIO_PIN13);
        printf("%10s = 0x%08X[%8d]\n", "Count", count, count);
        count--;
    }
 
    return 0;
}

main()를 위와 같이 만들어서 제대로 동작되는지 확인해 본다.  15번 줄에 보여지는 코드가 제대로 표현되는지 실행하여 그 결과를 확인한다.

Count 값이 100에서부터 1씩 감소하여 0이 되고, 그 다음에 -1이 되는것을 확인할 수 있다.

 

프로젝트에서 사용된 코드들은 아래에 첨부 하였다.  

stdio2.zip

 

소스코드를 열어보면 알겠지만 그동안 라이브러리에서 제공하여 그냥 불러 썼던 코드들이 모두 들어 있다.  이번 프로젝트에서는 물론 printf() 함수를 사용하는데 필요한 코드만 들어있다.  다른 함수들이 필요하면 그 코드들도 직접 만들어야 한다.

그렇다면 C에서 제공하는 라이브러리를 그냥 쓰면 되지 굳이 이렇게 불편하게 직접 만들 필요까지 있을까?

그 이유는 다음과 같다.

첫째, 이미 앞의 글에서도 얘기 했다시피 라이브러리코드가 포함되면 프로젝트의 실행 파일 크기가 상당히 커지게 된다.  ROM이나 RAM 크기가 충분하면 문제될게 없겠지만 MCU와 같이 ROM과 RAM 크기가 불과 몇K바이트나 몇십K바이트일뿐인 경우에는 라이브러리가 포함됨으로써 전체적으로 커지게 되는 실행 파일의 크기에 민감해 질 수밖에 없다.  어떤 경우 불과 몇십바이트 크기가 아쉬울때가 있기 때문이다.  이런 경우 라이브러리를 사용하지 않고 직접 필요한 함수만 만들어 쓰게 되면 그 크기를 대폭 줄일수 있게 된다.

둘째, 라이브러리 내부를 알 수 없으므로 원하는 동작이 수행되지 않을때 그 원인을 찾기가 쉽지 않은 경우가 있다.  때로는 보드에 맞지 않게 동작되는 라이브러리 코드가 포함될 수 있기 때문이다.  프로그래머가 직접 라이브러리를 만들어 쓰게 되면 어느 정도는 디버깅이 가능할 수 있게 된다.

세번째 이유는 프로젝트와는 직접적인 관계는 없지만 라이브러리 코드를 직접 만들어 봄으로써 프로그래밍 능력이 높아지게 된다.  좋은 글을 쓰기 위해서는 좋은 책을 많이 읽어야 하는것처럼 좋은 프로그램을 짜기 위해서는 좋은 코드들을 많이 분석해 보아야 한다.  C 라이브러리 코드들은 거의 프로그램의 신들이 만들기 때문에 거의 예술에 가까운 코드들이다.  평소에 절대 사용하지 않는 프로그래밍 기법들을 볼 수 있고, 특히 #define을 이용한 매크로 코드들을 보면 감탄하지 않을 수 없다.

단순히 취미가 아닌 프로그래머를 직업으로 삼으려하는 초보 프로그래머가 있다면 틈틈히 라이브러리 코드를 공부하기 바란다. 

 

[출처] https://embed-avr.tistory.com/120

 

 

 

';
                break;
        }
 
        fieldsz = size;
        if (sign)
            fieldsz++;
        else if (flags & HEXPREFIX)
            fieldsz += 2;
        realsz = dprec > fieldsz ? dprec : fieldsz;
 
        if ((flags & (LADJUST | ZEROPAD)) == 0)
            PAD(width - realsz, blanks);
 
        if (sign)
        {
            *strbuf++ = sign;
        }
        else if (flags & HEXPREFIX)
        {
            ox[0] = '0';
            ox[1] = ch;
            PRINT(ox, 2);
        }
 
        if ((flags & (LADJUST | ZEROPAD)) == ZEROPAD)
            PAD(width - realsz, zeroes);
 
        PAD(dprec - fieldsz, zeroes);
        PRINT(cp, size);
 
        if (flags & LADJUST)
            PAD(width - realsz, blanks);
 
        ret += width > realsz ? width : realsz;
    }
 
done:
    *strbuf = '

[Embedded Software] ARM Cortex-M : printf()를 만들어 보자

C언어를 배울 때 가장 먼저 사용하는 함수는 printf()일것이다.  또한 C프로그램에서 가장 많이 불리워지는 함수도 printf()일것이다.  그러나 이렇게 많이 사용하고 있는 printf() 함수가 실제로 어떻게 생겼는지 그 소스를 찾아본적은 거의 없을 것이다.

이번 글에서는 printf() 함수를 라이브러리에서 제공하는 것을 사용하지 않고 직접 만들어 보기로 하겠다.

printf() 함수의 소스 코드는 인터넷에서 검색하면 어렵지 않게 찾을 수 있을 것이다.  이 글에서는 BSD-lite 안에 들어 있는 코드를 참조하였다.

아직 C언어에 익숙하지 않은 프로그래머가 보기에는 소스코드가 다소 복잡하게 보일것이다.  따라서 이 코드를 바로 임베디드 시스템에 적용하기에 앞서 visual studio나 리눅스 환경에서 미리 검증한 후 실제 임베디드 보드에서 확인하는것이 좋다.

 

아래 코드는 visual studio에서 동작 확인을 위하여 만들어 본 코드이다.

main.c  main.c

 

직접 만들어 본 코드에 문제가 없음을 확인하였으면 실제 보드에서 동작 시켜 보도록 하겠다.

먼저 해야 할 일은 이제 더이상 컴파일러에 포함되어 있는 C라이브러리가 프로젝트 빌드시에 포함되지 않도록 하기 위하여 Makefile을 수정하여야 한다.

1
2
3
4
5
6
7
8
9
gccincdir := $(shell $(CC) -print-file-name=include)
 
CFLAGS := -g -Os -ffunction-sections -fdata-sections -Wall -std=gnu99
CFLAGS += -fno-builtin -ffreestanding
CFLAGS += -mthumb -mcpu=cortex-m3
CFLAGS += -Iinc -nostdinc -isystem $(gccincdir)
 
LDFLAGS := -L $(shell dirname `$(CC) $(CFLAGS) -print-libgcc-file-name`)
LDFLAGS += -nostdlib

6번줄에 있는 -nostdinc 옵션을 이용하여 라이브러리에서 제공하는 include 파일을 사용하지 않겠다는것을 알려 준다.  하지만, GCC 컴파일러에서 제공하는 헤더 파일은 사용하여야 하기 때문에 -isystem 옵션으로 GCC에서 제공하는 헤더파일을 include 시킨다.  GCC 헤더 파일의 위치는 1번줄에서 표현된 방법으로 그 디렉토리를 알 수 있다.

그리고, 링크시에 라이브러리에서 제공하는 코드가 포함되지 않도록 하기 위하여 9번 줄에서처럼 -nostdlib 옵션을 사용하였다.

컴파일과 링크 단계에서 -nostdinc와 -nostdlib 옵션을 주게 되면 더이상 라이브러리에서 제공하는 코드를 사용할 수 없게 되므로 프로그래머가 직접 해당 함수와 헤더 파일을 만들어야 한다.

원본 코드를 참조하여 만들어 본 printf() 함수의 코드는 다음과 같다.

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
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
#include <stdarg.h>
#include <stdint.h>
#include <stddef.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
 
#define UART_BUF_SIZE   256
 
char uart_buf[UART_BUF_SIZE];
 
 
#define to_digit(c)     ((c) - '0')
#define is_digit(c)     ((unsigned)to_digit(c) <= 9)
#define to_char(n)      ((n) + '0')
 
#define BUF             68
 
#define ALT             0X001
#define HEXPREFIX       0X002
#define LADJUST         0X004
#define LONGDBL         0X008
#define LONGINT         0X010
#define QUADINT         0X020
#define SHORTINT        0X040
#define ZEROPAD         0X080
#define FPT             0X100
 
 
 
static char *__ultoa(register uint32_t  val,
                     char               *endp,
                     int                base,
                     int                octzero,
                     char               *xdigs)
{
    register char       *cp = endp;
    register int32_t    sval;
 
    switch (base)
    {
        case 8:
            do
            {
                *--cp = to_char(val & 7);
                val >>= 3;
            } while(val);
            if (octzero && *cp != '0')
                *--cp = '0';
            break;
 
        case 10:
            if (val < 10)
            {
                *--cp = to_char(val);
                return cp;
            }
            if (val > LONG_MAX)
            {
                *--cp = to_char(val % 10);
                sval = val / 10;
            }
            else
                sval = val;
 
            do
            {
                *--cp = to_char(sval % 10);
                sval /= 10;
            } while(sval != 0);
            break;
 
        case 16:
            do
            {
                *--cp = xdigs[val & 15];
                val >>= 4;
            } while(val);
            break;
 
        default:
            break;
    }
 
    return cp;
}
 
static char *__uqtoa(register uint64_t  val,
                     char               *endp,
                     int                base,
                     int                octzero,
                     char               *xdigs)
{
    register char       *cp = endp;
    register int64_t    sval;
 
    if (val <= ULONG_MAX)
        return __ultoa((uint32_t)val, endp, base, octzero, xdigs);
 
    switch (base)
    {
        case 8:
            do
            {
                *--cp = to_char(val & 7);
                val >>= 3;
            } while(val);
            if (octzero && *cp != '0')
                *--cp = '0';
            break;
 
        case 10:
            if (val < 10)
            {
                *--cp = to_char(val % 10);
                return cp;
            }
            if (val > QUAD_MAX)
            {
                *--cp = to_char(val % 10);
                sval = val / 10;
            }
            else
                sval = val;
 
            do
            {
                *--cp = to_char(sval % 10);
                sval /= 10;
            } while(sval != 0);
            break;
 
        case 16:
            do
            {
                *--cp = xdigs[val & 15];
                val >>= 4;
            } while(val);
            break;
 
        default:
            break;
    }
 
    return cp;
}
 
int vprintf(char *strbuf, const char *format, va_list ap)
{
    register char   *fmt;
    register char   *cp;
    register char   ch;
    register int    n;
    register int    flags;
    char            sign;
    int             ret;
    int             width;
    int             prec;
    int             dprec = 0;
    int             size;
    int             fieldsz;
    int             realsz;
    int             base;
    char            *xdigs = NULL;
    uint32_t        ulval = 0;
    uint64_t        uqval = 0;
    char            buf[BUF];
    char            ox[2];
 
 
#define PADSIZE 16
    static char blanks[PADSIZE] =
     {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};
    static char zeroes[PADSIZE] =
     {'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'};
 
#define PRINT(ptr, len)                 \
    do {                                \
        int     i;                      \
        for (i=0; i<len; i++)           \
            *strbuf++ = ptr[i];         \
    }while(0)
 
#define PAD(howmany, with)              \
    do {                                \
        if ((n = (howmany)) > 0) {      \
            while (n > PADSIZE) {       \
                PRINT(with, PADSIZE);   \
                n -= PADSIZE;           \
            }                           \
            PRINT(with, n);             \
        }                               \
    }while(0)
 
#define SARG()                                          \
    (flags & LONGINT ? va_arg(ap, long) :               \
     flags & SHORTINT ? (long)(short)va_arg(ap, int) :  \
     (long)va_arg(ap, int))
#define UARG()                                                  \
    (flags & LONGINT ? va_arg(ap, uint32_t) :                   \
     flags & SHORTINT ? (uint32_t)(uint16_t)va_arg(ap, int) :   \
     (uint32_t)va_arg(ap, uint32_t))
 
 
    fmt = (char *)format;
    ret = 0;
 
    while (1)
    {
        for (cp = fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++);
 
        if ((n = fmt - cp) != 0)
        {
            PRINT(cp, n);
            ret += n;
        }
 
        if (ch == '\0')
            goto done;
 
        fmt++;
 
        flags = 0;
        width = 0;
        prec  = -1;
        sign  = '\0';
 
rflag:
        ch = *fmt++;
reswitch:
        switch (ch)
        {
            case ' ':
                if (!sign)
                    sign = ' ';
                goto rflag;
 
            case '#':
                flags |= ALT;
                goto rflag;
 
            case '*':
                if ((width = va_arg(ap, int)) >= 0)
                    goto rflag;
                width = -width;
 
            case '-':
                flags |= LADJUST;
                goto rflag;
 
            case '+':
                sign = '+';
                goto rflag;
 
            case '.':
                if ((ch = *fmt++) == '*')
                {
                    n = va_arg(ap, int);
                    prec = n < 0 ? -1 : n;
                    goto rflag;
                }
                n = 0;
                while (is_digit(ch))
                {
                    n = 10 * n + to_digit(ch);
                    ch = *fmt++;
                }
                prec = n < 0 ? -1 : n;
                goto reswitch;
 
            case '0':
                flags |= ZEROPAD;
                goto rflag;
 
            case '1' ... '9':
                n = 0;
                do
                {
                    n = 10 * n + to_digit(ch);
                    ch = *fmt++;
                } while(is_digit(ch));
                width = n;
                goto reswitch;
 
            case 'h':
                flags |= SHORTINT;
                goto rflag;
 
            case 'l':
                if (*fmt == 'l')
                {
                    fmt++;
                    flags |= QUADINT;
                }
                else
                    flags |= LONGINT;
                goto rflag;
 
            case 'q':
                flags |= QUADINT;
                goto rflag;
 
            case 'c':
                *(cp = buf) = va_arg(ap, int);
                size = 1;
                sign = '\0';
                break;
 
            case 'D':
                flags |= LONGINT;
            case 'd':
            case 'i':
                if (flags & QUADINT)
                {
                    uqval = va_arg(ap, int64_t);
                    if ((int64_t)uqval < 0)
                    {
                        uqval = -uqval;
                        sign = '-';
                    }
                }
                else
                {
                    ulval = SARG();
                    if ((long)ulval < 0)
                    {
                        ulval = -ulval;
                        sign = '-';
                    }
                }
                base = 10;
                goto number;
 
            case 'O':
                flags |= LONGINT;
            case 'o':
                if (flags & QUADINT)
                    uqval = va_arg(ap, uint64_t);
                else
                    ulval = UARG();
                base = 8;
                goto nosign;
 
            case 'p':
                ulval = (uint32_t)va_arg(ap, void *);
                base = 16;
                xdigs = "0123456789abcdef";
                flags = (flags & ~QUADINT) | HEXPREFIX;
                ch = 'x';
                goto nosign;
 
            case 's':
                if ((cp = va_arg(ap, char *)) == NULL)
                    cp = "(null)";
                if (prec >= 0)
                {
                    char *p = memchr(cp, 0, prec);
                    if (p != NULL)
                    {
                        size = p - cp;
                        if (size > prec)
                            size = prec;
                    }
                    else
                        size = prec;
                }
                else
                    size = strlen(cp);
                sign = '\0';
                break;
 
            case 'U':
                flags |= LONGINT;
            case 'u':
                if (flags & QUADINT)
                    uqval = va_arg(ap, uint64_t);
                else
                    ulval = UARG();
                base = 10;
                goto nosign;
 
            case 'X':
                xdigs = "0123456789ABCDEF";
                goto hex;
            case 'x':
                xdigs = "0123456789abcdef";
hex:
                if (flags & QUADINT)
                    uqval = va_arg(ap, uint64_t);
                else
                    ulval = UARG();
                base = 16;
 
                if (flags & ALT && (flags & QUADINT ? uqval != 0 : ulval != 0))
                    flags |= HEXPREFIX;
nosign:
                sign = '\0';
number:
                if ((dprec = prec) >= 0)
                    flags &= ~ZEROPAD;
                cp = buf + BUF;
                if (flags & QUADINT)
                {
                    if (uqval != 0 || prec != 0)
                        cp = __uqtoa(uqval, cp, base, flags & ALT, xdigs);
                }
                else
                {
                    if (ulval != 0 || prec != 0)
                        cp = __ultoa(ulval, cp, base, flags & ALT, xdigs);
                }
                size = buf + BUF - cp;
                break;
 
            default:
                if (ch == '\0')
                    goto done;
                cp = buf;
                *cp = ch;
                size = 1;
                sign = '\0';
                break;
        }
 
        fieldsz = size;
        if (sign)
            fieldsz++;
        else if (flags & HEXPREFIX)
            fieldsz += 2;
        realsz = dprec > fieldsz ? dprec : fieldsz;
 
        if ((flags & (LADJUST | ZEROPAD)) == 0)
            PAD(width - realsz, blanks);
 
        if (sign)
        {
            *strbuf++ = sign;
        }
        else if (flags & HEXPREFIX)
        {
            ox[0] = '0';
            ox[1] = ch;
            PRINT(ox, 2);
        }
 
        if ((flags & (LADJUST | ZEROPAD)) == ZEROPAD)
            PAD(width - realsz, zeroes);
 
        PAD(dprec - fieldsz, zeroes);
        PRINT(cp, size);
 
        if (flags & LADJUST)
            PAD(width - realsz, blanks);
 
        ret += width > realsz ? width : realsz;
    }
 
done:
    *strbuf = '\0';
    return ret;
}
 
int printf(const char *format, ...)
{
    int     rc;
    va_list ap;
 
    va_start(ap, format);
 
    rc = vprintf(uart_buf, format, ap);
 
    va_end(ap);
 
    puts(uart_buf);
 
    return rc;
}
 
 
void putc(char ch)
{
    extern void uart_putc(char ch);
 
    uart_putc(ch);
 
    if (ch == '\n')
        uart_putc('\r');
}
 
void puts(const char *str)
{
    while(*str)
        putc(*str++);
}

실제 코드를 보면 무척 복잡하다는 느낌이 들것이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int main(int argc, char *argv[])
{
    int     count = 100;
 
    sysclock_init(BD_CLKSRC, BD_PLLMUL, BD_AHBPRE, BD_APB1PRE, BD_APB2PRE, BD_ADCPRE);
    rcc_apb2enable(RCC_APB2_IOPC);
    gpio_set_outmode(GPIO_PORT_C, GPIO_PIN13, GPIO_GEN_PUSHPULL, GPIO_OUT_2M);
 
    uart_init();
 
    while (1)
    {
        delay(500);
        gpio_out_toggle(GPIO_PORT_C, GPIO_PIN13);
        printf("%10s = 0x%08X[%8d]\n", "Count", count, count);
        count--;
    }
 
    return 0;
}

main()를 위와 같이 만들어서 제대로 동작되는지 확인해 본다.  15번 줄에 보여지는 코드가 제대로 표현되는지 실행하여 그 결과를 확인한다.

Count 값이 100에서부터 1씩 감소하여 0이 되고, 그 다음에 -1이 되는것을 확인할 수 있다.

 

프로젝트에서 사용된 코드들은 아래에 첨부 하였다.  

stdio2.zip

 

소스코드를 열어보면 알겠지만 그동안 라이브러리에서 제공하여 그냥 불러 썼던 코드들이 모두 들어 있다.  이번 프로젝트에서는 물론 printf() 함수를 사용하는데 필요한 코드만 들어있다.  다른 함수들이 필요하면 그 코드들도 직접 만들어야 한다.

그렇다면 C에서 제공하는 라이브러리를 그냥 쓰면 되지 굳이 이렇게 불편하게 직접 만들 필요까지 있을까?

그 이유는 다음과 같다.

첫째, 이미 앞의 글에서도 얘기 했다시피 라이브러리코드가 포함되면 프로젝트의 실행 파일 크기가 상당히 커지게 된다.  ROM이나 RAM 크기가 충분하면 문제될게 없겠지만 MCU와 같이 ROM과 RAM 크기가 불과 몇K바이트나 몇십K바이트일뿐인 경우에는 라이브러리가 포함됨으로써 전체적으로 커지게 되는 실행 파일의 크기에 민감해 질 수밖에 없다.  어떤 경우 불과 몇십바이트 크기가 아쉬울때가 있기 때문이다.  이런 경우 라이브러리를 사용하지 않고 직접 필요한 함수만 만들어 쓰게 되면 그 크기를 대폭 줄일수 있게 된다.

둘째, 라이브러리 내부를 알 수 없으므로 원하는 동작이 수행되지 않을때 그 원인을 찾기가 쉽지 않은 경우가 있다.  때로는 보드에 맞지 않게 동작되는 라이브러리 코드가 포함될 수 있기 때문이다.  프로그래머가 직접 라이브러리를 만들어 쓰게 되면 어느 정도는 디버깅이 가능할 수 있게 된다.

세번째 이유는 프로젝트와는 직접적인 관계는 없지만 라이브러리 코드를 직접 만들어 봄으로써 프로그래밍 능력이 높아지게 된다.  좋은 글을 쓰기 위해서는 좋은 책을 많이 읽어야 하는것처럼 좋은 프로그램을 짜기 위해서는 좋은 코드들을 많이 분석해 보아야 한다.  C 라이브러리 코드들은 거의 프로그램의 신들이 만들기 때문에 거의 예술에 가까운 코드들이다.  평소에 절대 사용하지 않는 프로그래밍 기법들을 볼 수 있고, 특히 #define을 이용한 매크로 코드들을 보면 감탄하지 않을 수 없다.

단순히 취미가 아닌 프로그래머를 직업으로 삼으려하는 초보 프로그래머가 있다면 틈틈히 라이브러리 코드를 공부하기 바란다. 

 

[출처] https://embed-avr.tistory.com/120

 

 

 

';
    return ret;
}
 
int printf(const char *format, ...)
{
    int     rc;
    va_list ap;
 
    va_start(ap, format);
 
    rc = vprintf(uart_buf, format, ap);
 
    va_end(ap);
 
    puts(uart_buf);
 
    return rc;
}
 
 
void putc(char ch)
{
    extern void uart_putc(char ch);
 
    uart_putc(ch);
 
    if (ch == '\n')
        uart_putc('\r');
}
 
void puts(const char *str)
{
    while(*str)
        putc(*str++);
}

실제 코드를 보면 무척 복잡하다는 느낌이 들것이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int main(int argc, char *argv[])
{
    int     count = 100;
 
    sysclock_init(BD_CLKSRC, BD_PLLMUL, BD_AHBPRE, BD_APB1PRE, BD_APB2PRE, BD_ADCPRE);
    rcc_apb2enable(RCC_APB2_IOPC);
    gpio_set_outmode(GPIO_PORT_C, GPIO_PIN13, GPIO_GEN_PUSHPULL, GPIO_OUT_2M);
 
    uart_init();
 
    while (1)
    {
        delay(500);
        gpio_out_toggle(GPIO_PORT_C, GPIO_PIN13);
        printf("%10s = 0x%08X[%8d]\n", "Count", count, count);
        count--;
    }
 
    return 0;
}

main()를 위와 같이 만들어서 제대로 동작되는지 확인해 본다.  15번 줄에 보여지는 코드가 제대로 표현되는지 실행하여 그 결과를 확인한다.

경축! 아무것도 안하여 에스천사게임즈가 새로운 모습으로 재오픈 하였습니다.
어린이용이며, 설치가 필요없는 브라우저 게임입니다.
https://s1004games.com

Count 값이 100에서부터 1씩 감소하여 0이 되고, 그 다음에 -1이 되는것을 확인할 수 있다.

 

프로젝트에서 사용된 코드들은 아래에 첨부 하였다.  

stdio2.zip

 

소스코드를 열어보면 알겠지만 그동안 라이브러리에서 제공하여 그냥 불러 썼던 코드들이 모두 들어 있다.  이번 프로젝트에서는 물론 printf() 함수를 사용하는데 필요한 코드만 들어있다.  다른 함수들이 필요하면 그 코드들도 직접 만들어야 한다.

그렇다면 C에서 제공하는 라이브러리를 그냥 쓰면 되지 굳이 이렇게 불편하게 직접 만들 필요까지 있을까?

그 이유는 다음과 같다.

첫째, 이미 앞의 글에서도 얘기 했다시피 라이브러리코드가 포함되면 프로젝트의 실행 파일 크기가 상당히 커지게 된다.  ROM이나 RAM 크기가 충분하면 문제될게 없겠지만 MCU와 같이 ROM과 RAM 크기가 불과 몇K바이트나 몇십K바이트일뿐인 경우에는 라이브러리가 포함됨으로써 전체적으로 커지게 되는 실행 파일의 크기에 민감해 질 수밖에 없다.  어떤 경우 불과 몇십바이트 크기가 아쉬울때가 있기 때문이다.  이런 경우 라이브러리를 사용하지 않고 직접 필요한 함수만 만들어 쓰게 되면 그 크기를 대폭 줄일수 있게 된다.

둘째, 라이브러리 내부를 알 수 없으므로 원하는 동작이 수행되지 않을때 그 원인을 찾기가 쉽지 않은 경우가 있다.  때로는 보드에 맞지 않게 동작되는 라이브러리 코드가 포함될 수 있기 때문이다.  프로그래머가 직접 라이브러리를 만들어 쓰게 되면 어느 정도는 디버깅이 가능할 수 있게 된다.

세번째 이유는 프로젝트와는 직접적인 관계는 없지만 라이브러리 코드를 직접 만들어 봄으로써 프로그래밍 능력이 높아지게 된다.  좋은 글을 쓰기 위해서는 좋은 책을 많이 읽어야 하는것처럼 좋은 프로그램을 짜기 위해서는 좋은 코드들을 많이 분석해 보아야 한다.  C 라이브러리 코드들은 거의 프로그램의 신들이 만들기 때문에 거의 예술에 가까운 코드들이다.  평소에 절대 사용하지 않는 프로그래밍 기법들을 볼 수 있고, 특히 #define을 이용한 매크로 코드들을 보면 감탄하지 않을 수 없다.

단순히 취미가 아닌 프로그래머를 직업으로 삼으려하는 초보 프로그래머가 있다면 틈틈히 라이브러리 코드를 공부하기 바란다. 

 

[출처] https://embed-avr.tistory.com/120

 

 

 

본 웹사이트는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.
번호 제목 글쓴이 날짜 조회 수
23 [아두이노 arduino] Run a Virtual Factory with Arduino! : 아두이노로 가상 공장을 운영해보세요! file 졸리운_곰 2024.12.28 116
22 [아두이노 arduino] Arduino – base64 encode and decode file 졸리운_곰 2024.03.11 207
21 [아두이노 arduino] Simple encryption using XOR operator 졸리운_곰 2024.03.11 214
20 [아두이노 arduino] 아두이노 json 암호화: Crypt json content file 졸리운_곰 2024.03.11 218
19 C#으로 아두이노의 LED 제어하기 - C# 프로그래밍 file 졸리운_곰 2024.02.25 269
18 Top 20 Arduino-based Health Projects file 졸리운_곰 2020.06.07 252
17 Get started with machine learning on Arduino file 졸리운_곰 2019.11.12 270
16 아두이노 운영체제 : 4 Operating Systems for the Arduino file 졸리운_곰 2019.11.04 315
15 아두이노 프로토쉴드(ProtoShield) 조립 가이드 file 졸리운_곰 2019.10.31 307
14 아두이노 우노 를 이용한 부트로더 굽기 ATMEGA328 file 졸리운_곰 2018.01.04 475
13 아두이노 부트로더 굽기(Arduino boorloader burning) file 졸리운_곰 2018.01.04 526
12 아두이노를 이용한 미니 웹 서버 만들기 - LED 제어 file 졸리운_곰 2017.08.03 494
11 아두이노를 이용한 미니 웹 서버 만들기 - 예제 분석 file 졸리운_곰 2017.08.03 596
10 아두이노를 이용한 미니 웹 서버 만들기 - 시작하기 file 졸리운_곰 2017.08.03 431
9 CSEduino : 아두이노 호환 : PCB 주문하여 납땜 조립가능 소형 보드 file 졸리운_곰 2017.08.02 441
8 회로 시뮬레이션 및 아두이노 시뮬레이터 Autodesk의 123D Circuits file 졸리운_곰 2017.06.10 794
7 브레드보드 위에 아두이노 만들기- Arduino Breadboard DIY file 졸리운_곰 2016.11.20 671
6 아두이노 UNO 회로도, Arduino UNO Schemetic file 졸리운_곰 2015.07.04 2222
5 Intel® Edison Boards, rcad* Schematic Design File file 졸리운_곰 2015.07.04 312
4 아두이노 부트로더 굽기 2 file 졸리운_곰 2015.07.04 670
대표 김성준 주소 : 경기 용인 분당수지 U타워 등록번호 : 142-07-27414
통신판매업 신고 : 제2012-용인수지-0185호 출판업 신고 : 수지구청 제 123호 개인정보보호최고책임자 : 김성준 sjkim70@stechstar.com
대표전화 : 010-4589-2193 [fax] 02-6280-1294 COPYRIGHT(C) stechstar.com ALL RIGHTS RESERVED