마이크로프로세서 및 실험2 과목 기말고사 대비 내용 정리
xvii페이지의 C-언어 관련 설명 정리
1. 프로그램 언어에서 메모리에 데이터 쓰기/읽기
x=y //y값을 x에 넣는다.
if(PINA == 1) // PINA 레지스터를 읽어서 1과 같으면 ... (조건)
x = x + 2 //x += 2 와 동일하다.
DDRA = 0xFF; //포트 A를 모두 출력 포트로 설정
PORTA = 0x5D; // 0101 1110 해당하는 포트만 HIGH로 설정
2. while()문의 문법
while(expr1) //expr1이 참값이면(0이 아니면, 1도 되고 2도 되고 0만 아니면 다 됨.)
expr2; //expr2을 수행한다. expr1의 값은 현재 1이며 앞으로도 바뀌지 않으므로 expr2를 무한 실행한다.
3. while, for, do-while 루프 구성
for(i=0;i<10;i++)
expr1; //i=0부터 1씩 증가하여 expr1을 실행하며, i=10이 되면 중지한다. 0부터 9까지 총 10번 실행한다.
위 for 루프문은 다음 while 루프문과 동일하다.
i=0; //루프의 초기화
while(i<10) //루프의 판단
{
expr1; //루프의 몸체
i++; //루프의 처음으로 가기 전 수행문
}
■ while문과 do-while문의 비교
while(expr1) | do{
expr2; | expr2;
| }while(expr1);
while문은 expr1을 먼저 평가하여 루프를 판단하지만 do-while문은 우선 expr2를 수행한 다음 expr1로 루프를 판단한다.
4. C-라이브러리 활용
Library : 함수를 작성하여 모아놓은 함수의 집합.
도서관에서 책을 대여하는 것처럼, 라이브러리에 있는 함수를 사용하려면 빌드 과정에서 라이브러리를 링크하여야 한다.
기본적으로 제공하는 libc.a 라는 라이브러리는 링크할 필요가 없다.(printf()같은 함수)
WinAVR에서 제공하는 라이브러리를 통틀어 avr-libc라 한다.
5. 데이터 마스크(MASK)
■ 특정 비트를 0으로 리셋(리셋 : 0으로 만듦. 세트 : 1로 만듦.)하고 나머지는 원상태를 보존하기 위한 방법.
리셋하고 싶은 수를 0과 AND시키면 된다. 예를 들어 bit #0, #2, #6을 리셋하고 싶다면,
7 6 5 4 3 2 1 0 //bit 0~7
X X X X X X X X //어떤 수든 상관없음. (X : don‘t care. 상관 없다는 뜻.)
1 0 1 1 1 0 1 0 //#0, #2, #6에만 0을 넣는다.
--------------- //AND시킴
X 0 X X X 0 X 0 //X는 그대로 출력.(1이었다면 그대로1, 0이었다면 그대로 0이 보존됨)
수식으로 표현하려면 다음과 같은 방법을 따른다.
1. #0, #2, #6만 1인 16진수를 구한다. 0100 0101 : 0x45
2. 이를 반전 시킨다. ~0x45 : 1011 1010 (반전시켜야 위의 수식처럼 AND시킬 준비가 완료된다.)
3. 마스크 기능을 통해 리셋하고 싶은 변수와 AND 시킨다. : My_variable & ~0x45
(AND란 곱하기이다. 1과 1을 AND하면 1, 1과 0을 AND하면 0, 0과 0을 AND하면 0이 나온다.)
4. 물론 그 결과를 저장할 변수도 필요하다. : My_changed_variable = My_variable & ~0x45
5. 마스크 하기 전의 변수 내용이 더 이상 필요 없다면 이렇게 해도 상관은 없다 : My_variable = My_variable & ~0x45
■ 이번에는 특정 비트를 1로 세트하는 방법에 대해 설명한다.
이를 위해서 세트하고 싶은 비트만 1이고 나머지는 0이 되는 수와 OR시키면 된다.
예를 들어 bit #3, #6, #7만 세트하고 나머지를 보존하고 싶으면
7 6 5 4 3 2 1 0 //bit 0~7
X X X X X X X X //어떤 수든 상관없음. (X : don‘t care. 상관 없다는 뜻.)
1 1 0 0 1 0 0 0 //#3, #6, #7에만 1을 넣는다.
--------------- //OR시킴
1 1 X X 1 X X X //X는 그대로 출력.(1이었다면 그대로1, 0이었다면 그대로 0이 보존됨)
수식으로 표현하려면 다음과 같은 방법을 따른다.
1. #3, #6, #7만 1인 16진수를 구한다. 1100 1000 : 0xC8
2. 마스크 기능을 통해 세트하고 싶은 변수와 OR 시킨다. : My_variable | 0xC8
(OR은 더하기이다. 1과 1을 OR하면 1, 1과 0을 OR하면 1, 0과 0을 OR하면 0이 나온다.)
4. 물론 그 결과를 저장할 변수도 필요하다. : My_changed_variable = My_variable | 0xC8
5. 마스크 하기 전의 변수 내용이 더 이상 필요 없다면 이렇게 해도 상관은 없다 : My_variable = My_variable | 0xC8
위와 같이 필요한 비트만 선별적으로 뽑아내는 것을 “마스크한다”라고 한다. 마치 가면의 구멍을 통해 제한된 영역만을 외부에 알려주는 것과 같다.
6. C-언어에서 매크로의 사용
매크로는 선행처리기 #define 키워드를 사용하여 정의한다.
이것을 사용함으로써 문장의 의미를 더욱 확실하게 전달할 수 있다. 예를 들면
#define DELAY_TIME 40 |
_delay_ms(DELAY_TIME); | _delay_ms(40);
에서 오른쪽 문장은 40의 의미를 알 수 없지만 왼쪽 문장에서는 그 의미를 파악하기가 쉽다.
또한 소스 전체의 DELAY_TIME을 50으로 바꾸고 싶을 때, 왼쪽의 방법을 쓴다면
#define DELAY_TIME 40 ==> #define DELAY_TIME 50
으로 간편하지만,
오른쪽의 문장을 사용한다면 소스의 해당하는 부분에서 일일이 40을 50으로 바꾸어주어야 한다.
이처럼 하나의 상수를 여러 군데 사용할 때는 매크로를 사용하는 것이 편리하다.
일반적으로, 매크로를 일반 변수와 구분하기 위하여 대문자를 사용하여 정의한다.
7. I/O 레지스터의 비트 사용
설명을 위해 EIMSK 레지스터를 고려하자.(이 레지스터가 무엇인지 여기서 굳이 알 필요는 없다.)
#7 | #6 | #5 | #4 | #3 | #2 | #1 | #0 |
INT7 | INT6 | INT5 | INT4 | INT3 | INT2 | INT1 | INT0 |
R/W | R/W | R/W | R/W | R/W | R/W | R/W | R/W |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
만일 INT3, 즉 bit #3을 세트(1로 만듦)하고 싶다면
0000 1000 ==> 0x08 이므로
EIMSK = 0x08;
라고 할 수 있다.
그러나 이는 직관적이지 않고(한 눈에 파악하기 힘들다는 뜻), 다시 다른 비트도 세트하려고 한다면 또 다시 계산을 해 주어야 한다. 예를 들어 현재 #3이 세트되어 있는 상태에서 #2도 세트하고 싶다면
0000 1100 ==> 0x0C 이므로
EIMSK = 0x0C;
라는 과정을 거쳐야 한다.
이에 왼쪽 Shift 연산자 << 를 사용한다면 편리하다.
EIMSK 레지스터에 대해 각 비트명은 다음 매크로로 정의되어 있다.
#define INT0 0
#define INT1 1
#define INT2 2
...
따라서 1<<INT2 <==> 1<<2 <==> 0b00000100 (0b는 2진수라는 뜻이다.)
1 : 0000 0001
1<<2 : 0000 0100
즉 EIMSK = 0x0C; 대신
EIMSK = (1<<INT3);
EIMSK = (1<<INT2);
을 이용하면 편리하고 보기에도 직관적이다.
위 두 문장을 하나로 합치면
EIMSK = (1<<INT3) | (1<<INT2);
이다. 그러나 이 문장은 bit #2, #3을 제외한 나머지 비트를 0으로 리셋한다. (원래 값이 사라진다.)
이를 방지하기 위한 방법으로 5. 데이터 마스크(MASK)에서 배웠던 OR를 사용한다.
EIMSK = EIMSK | ((1<<INT3) | (1<<INT2)));
혹은 EIMSK |= ((1<<INT3) | (1<<INT2))); 도 같은 문장이다.
마찬가지로 bit #2, #3를 0으로 리셋하고 나머지 비트를 보존하려면 다음과 같이 작성하면 된다.
EIMSK &= ~((1<<INT3) | (1<<INT2)));
8. 변수의 범위★★★★★
■ 지역변수 : 지정된 영역에서 쓰이는 변수.
중괄호{}내에서 선언된 변수는 모두 지역 변수. 이들 변수는 변수가 선언된 중괄호 {} 내에서만 통용된다.
file1.c
int e_data; static int se_data;
void func1(int arg) { int l_data; static int ls_data; while(1) { int ll_data; } }
int func2(void) { int l_data; static int ls_data; } |
file2.c
extern int e_data; |
위의 변수들 중 func1의 l_data, ls_data, ll_data, arg, func2의 l_data, ls_data는 모두 지역 변수이다.
굵게 표시한 func1의 변수 l_data와 func2의 변수 l_data는 변수명이 서로 같지만, 통용되는 범위가 각각 func1와 func2의 내이므로 전혀 다른 변수이다. 서로 아무런 영향을 미치지 않는다.
함수 func1의 변수 ll_data는 func1의 안에 있지만 선언된 부분이 while(){}의 내부이므로 while(){}안에서만 통용되고 func1의 다른 부분에서는 존재를 알지 못한다.
func1의 arg 변수와 같이 함수의 인자로 넘어오는 변수는 함수의 지역변수로 간주된다.
■ 외부 변수
중괄호{} 밖에서 선언된 변수. 두 가지로 나뉜다.
1. 전역 변수 : 전체 영역에서 쓰이는 변수. 프로그램의 모든 영역에서 통용되는 변수이다.
위의 변수 중 file1.c의 e_data가 해당한다.
만일 이 e_data를 file2.c에서도 사용하려면 위와 같이 extern 키워드를 사용하여 참조하여야 한다.
2. 정적 변수 : 정지 상태에 있는 변수. 변수를 선언할 때 static 키워드를 사용한다. 통용 범위는 같은 파일 내에서 변수가 선언된 아래 부분 모두이다. 위의 변수 중 se_data가 해당한다. file1.c에서 선언되었으므로 file2.c에서는 사용할 수 없다.
9. 변수의 수명
변수가 static으로 선언되어 있으면 프로그램이 끝날 때까지 소멸하지 않는다.
static으로 선언되지 않은 변수는 변수가 선언된 중괄호{}내에서만 존재하며, 중괄호를 닫는 부분 }을 만나면 해당변수는 소멸된다.
■ 지역 변수
func1의 l_data, arg : 함수 func1에 들어올 때마다 새로 생성된다.
func1의 ll_data : while를 실행할 때마다 새로 생성된다.
func1의 ls_data : 함수 func1에 처음 들어올 때 한 번 생성되고, 소멸되지 않는다. func1에 다시 들어올 때 변수를 새로 생성하지 않고 이미 생성된 변수 ls_data를 다시 사용한다.
■ 외부 변수
한 번 생성되면 소멸하지 않는다. 프로그램이 시작될 때 한 번만 생성된다.
file1.c의 e_data, se_data.
※변수의 범위와 수명은 항상 일치하는 것은 아니다. ls_data의 범위는 함수 func1이지만 이 함수를 벗어난다고 해도 소멸하지 않는다.
file2.c의 extern int e_data; 는 변수 선언을 하는 것이 아니라 변수 e_data가 외부파일에 선언되어 있으므로 참조하라는 뜻이다. 새로 변수가 생성되지는 않는다.
가능하면 전역 변수는 사용하지 않는 것이 바람직하다.
10. C-언어의 함수
ftype name(type1 v1, type2 v2, ...)
ftype : 함수의 타입. 함수의 반환값의 데이터 형에 따라 명시(int, char 등) 반환값이 없는 경우 void로 명시. 생략 시에는 int로 간주.
name : 함수의 이름.
v1, v2 : 인자. 없을 수도 있고 여러 개의 인자가 있을 수도 있음. 인자 앞에는 인자의 데이터 형(int, char 등)을 지정.
{}안에 함수 기능 구현. 반환값은 하나를 가지며, return문으로 반환값 명시( ex)return x; ) 반환값이 없는 경우 return 생략.
11. C-언어에서 문자열
문자 : ‘ ’를 사용. ‘A’, ‘1’ 등
문자열 : “ ”를 사용. “MyCom” 등. “1”도 가능.
문자열의 끝은 NULL문자(\0)여야 한다. 따라서 MyCom이라는 문자열을 저장하기 위해선 최소한 6개의 배열이 필요하다.
char ch;
char *pch;
ch = ‘A’; //이 때 ch를 출력하면 A가 나온다.
pch = “MyCom”;
ch = pch[0]; //이 때 ch를 출력하면 M이 나온다.
ch = pch[1]; //이 때 ch를 출력하면 y가 나온다.
ch = “A”; //이는 에러이다. ch는 char형이므로 배열을 저장할 수 없다.
12. switch문
switch(제어표현식)
{
case label1: //예를 들어 제어표현식이 1이고 label1=1이면 이 문장을 실행한다.
문장1;
break; //break를 만나면 switch문을 벗어난다.
case label2:
문장2;
break;
default: //제어표현식에 해당하는 label이 없을 경우 이 default를 실행한다.
문장3;
}
13. 헤더파일에서 선행처리기 #ifndef 사용
선행처리기 : 컴파일러가 프로그램을 컴파일하기 전에 처리하는 문장. 문법은 다음과 같다.
#ifndef 매크로명
...
#endif
“매크로명”이 매크로로 정의되어 있지 않으면 #ifndef-#endif 짝 내의 문장을 포함하여 컴파일하고, 정의되어 있으면 포함하지 않고 컴파일을 수행한다.
button.h
#ifndef _BUTTON_H_ #define _BUTTON_H_ #define BTN_SW0 0x01 #endif |
outfile.h
#include “button.h” |
헤더파일 outfile.h 속에서 헤더파일 button.h을 첨부하고 있다. 프로그램을 다음과 같이 작성하면 문장 1, 2에 걸쳐 button.h가 두 번 첨부된다.
1. #include “outfile.h”
2. #include “button.h”
1에서 button.h가 첨부될 때 매크로 _BUTTON_H_가 정의되지 않았으므로 매크로 _BUTTON_H_와 BTN_SW0을 정의한다.
2에서 button.h가 첨부될 때 1에서 매크로 _BUTTON_H_가 이미 정의된 상태이므로 #ifndef-#endif 짝 내의 문장을 포함시키지 않는다. 따라서 매크로 BTN_SW0이 중복해서 정의되지 않는다.
button.h에서 #ifndef-#endif을 사용하지 않으면 2에서 매크로 BTN_SW0이 중복 정의되어 에러가 발생하게 된다.
14. 함수의 인자를 사용한 데이터 출력
int main()
{
int x, result;
x = 2;
square(x, result);
}
void square(int a, int k)
{
k = a * a;
}
위 예제에서 k는 4이다. 그렇다면 result는 4의 값을 갖는가? 그렇지 않다. 그 이유는 함수로 보낸 인자들은 복사된 값이기 때문이다. 원본을 복사한 종이에 아무리 낙서해도 원본은 변하지 않는다.
그렇기 때문에 다음과 같이 포인터를 사용한다.
int main()
{
int x, result;
x = 2;
square(x, &result);
}
void square(int a, int *k)
{
*k = a * a;
}
위와 같이 포인터를 사용할 경우, 원본을 변경하는 것이기 때문에 result는 올바른 결과값인 4를 갖는다.(잘 모르겠다면 포인터를 공부할 것)
15. 정적함수
정적 변수와 유사하게 함수가 정의된 파일에서만 함수가 통용되고 외부에는 함수의 존재를 숨길 때 사용할 수 있다. 함수의 타입 앞에 static을 쓰면 정적함수가 된다.
static int func(int x, int y)
{ ... }
16. C-언어에서 형 변경
char(1byte)형 변수 A의 데이터를 short(2byte)형 변수 B에 저장하는 경우를 고려해보자. 서로 형이 다르므로
B = (short)A; //A=0x23일 때 B=0x0023
와 같이 형을 변경하여 저장하여야 한다. 반대의 경우
A = (char)B; //B=0x1223일 때 A=0x23
와 같이 상위 바이트의 데이터를 손실하게 된다.
형의 변경을 명시하지 않으면 컴파일러가 자동적으로 왼쪽 변수의 형으로 변경하여 데이터를 저장한다.
B = A; //A값을 short형으로 변경 후 B에 저장.
서로 다른 형의 데이터를 연산하면 바이트 수를 크게 차지하는 변수의 형으로 변경하여 연산되고, 결과는 바이트 수를 가장 크게 차지하는 변수의 형이 된다. 즉 A+B의 연산의 결과는 A값을 short형으로 변경하여 계산되고, 그 결과 역시 short형이 된다.
17. 서식이 있는 출력 함수 (printf, sprintf)사용하기
함수의 끝 글자 f는 서식(formatted)을 뜻한다. 두 함수 모두 동일한 서식에 따라 인쇄를 한다. 단 인쇄를 하는 장치가 다를 뿐이다.
printf() - 디폴트 장치에 출력한다. PC에서 디폴트 장치는 화면을 말한다.
sprintf() - 주어진 문자열에 인쇄를 한다. 문자열은 인쇄에 필요한 충분한 메모리를 가져야 한다.
char buf[100];
sprintf(buf, “ABC”); //buf에 문자열 “ABC”를 저장한다. 화면상에는 아무런 변화가 없다.
printf(“%s”, buf); //buf 문자열을 출력한다. 이 과정에서 화면에 출력된다.
18. 배열과 포인터
C언어에서 배열을 선언하면 정의된 배열 개수만큼의 메모리를 할당하고 배열 이름에 할당된 메모리의 첫 주소를 저장한다.
short array[4]
array -> array[0] <=> *(array+0)
array[1] <=> *(array+1)
array[2] <=> *(array+2)
array[3] <=> *(array+3)
배열명 array는 첫 번째 short형 변수를 가리킨다. 배열의 이름은 첫 주소를 저장하므로 포인터이다.
array + 1 : 주소 array로부터 1-번째 있는 short형 변수의 주소
*(array+1) : 해당 위치를 역참조. 만일 array[] = “abc”라면 ‘b’ 출력. = array[1]
배열과 포인터는 동일하므로 함수에서 배열을 인자로 선언할 때는 두 가지를 쓸 수 있다.
void func(short *ary)
void func(short ary[])
배열은 첫 주소만 알면 각 배열 요소를 알 수 있으므로 함수에 배열을 넘기고자 할 때는 배열의 첫 주소, 즉 배열의 이름만 넘긴다.
19. 정수 계산에서 코딩에 따른 계산 정확도
20. 조건 연산자 exp1 ? exp2 : exp3
exp1가 참이면 exp2를 수행하고 거짓이면 exp3을 수행한다. 예를 들면 x=1, y=2 인 상황에서
k = (x>y) ? x : y
(x>y)는 거짓이므로 y를 수행한다. 따라서 k=y 즉 k=2가 된다.
21. 매크로 함수
매크로를 문장을 대체하는 용도로만 사용하지 않고, 함수와 유사하게 사용할 수 있다.
예를 들어 부호가 있는 변수의 절대값을 계산하는 문장을 고려해보자. 이는 조건 연산자를 사용하여 다음과 같이 표시할 수 있다.
y = a>=0 ? a : -a ;
이를 매크로 함수로 정의하면
#define ABS(x) x >= 0 ? x : -x
이다. 따라서 y = a>=0 ? a : -a ; 대신 y = ABS(a); 로 간편하게 표시할 수 있다.
그러나 위의 매크로 함수는 문제점이 있다. 만일 y = ABS(a+1); 라는 문장을 실행시킨다면
y = a+1 >= 0 ? a+1 : -a+1 ; 과 같이 실행된다. 이러한 문제점을 해결하기 위하여 다음과 같이 정의해야 한다.
#define ABS(x) (x) >= 0 ? (x) : -(x)
그러면 y = ABS(a+1); 는 y = (a+1) >= 0 ? (a+1) : -(a+1) ; 로 실행되어 올바른 계산을 수행한다.
22. typedef 키워드
새로운 데이터형을 정의할 때 사용하는 키워드.
typedef <기존 데이터형> <새 데이터형>;
예를 들어 unsigned long int 라는 데이터형은 너무 길어서 불편하다. 따라서
typedef unsigned long int ulong; 과 같이 줄여서 정의할 수 있다.
23. 스택(stack)
데이터를 임시로 저장하는 메모리 공간.
자동 변수는 함수가 호출되면 생겼다가 종료되면 사라짐. 따라서 함수가 수행되는 동안만 임시적으로 존재. 컴파일러는 자동 변수를 모두 스택에 할당한다. 프로그램에서 함수의 호출은 빈번히 일어나므로 스택은 속도가 빠른 내부 메모리 영역에 위치시키는 것이 일반적임.
교재 2장, 3장 1절, 21장 1절, 22장 2절, 27장 1절
2장 미리 알아두어야 할 일반적 사항
2.1 이진수/16진수의 계산. 십진수에서 이진수와 16진수로의 변환. 이진수와 16진수의 덧셈과 뺄셈.(인터넷 검색 이용)
2.2 데이터의 단위.
Bit : 이진수(Binary Digits)의 약자로서 0 또는 1을 가진다.
Nibble : 4비트. 개의 수를 표시할 수 있으므로 1자리의 16진수를 표현할 수 있다.
Byte : 8비트. 데이터의 크기를 표시하는 가장 기본적 단위.
Word : 2Byte. / Paragraph : 16Byte. / Page : 256Byte.
Kbyte : 일반적으로 Kilo는 배를 뜻하나, 컴퓨터에서 Kilo는
배를 가리킨다.
따라서 1Kbyte = 1024byte.
Mbyte : 일반적으로 Mega는 배를 뜻하나, 컴퓨터에서 Mega는
배를 가리킨다.
따라서 1Mbyte = 1024Kbyte.
Gbyte : 1Gbyte = 1024Mbyte.
2.3 데이터의 표시
최상위비트(MSB)
↓
bit #7 | bit #6 | bit #5 | bit #4 | bit #3 | bit #2 | bit #1 | bit #0 |
↑
최하위 비트(LSB)
이진수로 표시하는 것이 바람직하나 숫자를 표시할 때 자리수가 매우 커져 사용하기에 불편하다. 컴퓨터에서는 16진수를 많이 사용한다. 한 바이트로 표시된 수는 여덟자리 이진수이므로 이를 16진수로 표시할 때 니블(4비트) 단위로 끊어서 표시하면 된다.
예를 들어 이진수 는
1 | 1 | 1 | 0 | 1 | 0 | 0 | 1 |
E | 9
이므로 이다. (E : 1110, 9 : 1001)
2.4 보수를 사용한 음수의 표시
컴퓨터에서 신호는 1과 0만 사용하기 때문에 양수만 표현한다. 음수를 표현하기 위해서는 보수를 이용한다.