- Control bus 제어버스, Address bus 주소버스, Data bus 데이터버스 등 존재
- MCU가 제어 명령, 주소, 데이터 등을 전송시키는 시간을 조절하여 데이터간 충돌을 회피
2. 메모리
- 기억 장치. 휘발성 비휘발성에따라 RAM과 ROM으로 구분
- 휘발성 : 전원이 없을때 데이터가 날아감 -> RAM
- 비휘발성 : 전원이 없을때 데이터 보존 -> ROM
2.1 ROM Read Only Memory의 종류
- PROM Programmable ROM - 프로그래밍 가능한 메모리. 한번만 쓰기가능
- EPROM Erasable Programmable ROM - 자외선으로 쓰고 지우는게 가능한 PROM
- EEPROM Electric EPROM : 전기적 충격으로 쓰기 읽기가 가능한 ROM
=> 대표적인 EEPROM으로 Flash Memory가 존재
2.2 RAM Random Access Memory
- SRAM, DRAM, SDRAM이 존재
- SRAM Static Random Access Moery
-> 트랜지스터만으로 만든 메모리, 고속, 고가 소형화 힘듬
- DRAM Dynamic RAM
-> 캐퍼스터와 트랜지스터로 구성된 메모리
-> 캐퍼시터를 사용한 만큼 데이터 손실을 막기위해 충전하는 Refresh Time 필요. 저가
- SDRAM Synchronous DRAM
-> 동기식 메모리. 외부에서 공급해주는 클럭에 따라 동기화
-> 클럭 신호선이 존재하여 데이터 처리 시 지연시간 latency 필요
- 아래의 그림은 SRAM의 소자
3. 메모리맵
- 대부분의 MCU의 데이터 시트에서 제공하는 정보로 할당된 각 메모리의 주소와 용량을 알 수 있음.
- 아래은 하드웨어 구성을 위한 메모리맵으로 SRAM 영역이 0x2000 0000 ~ 0x3FFF FFFF임을 알 수 있음
- 아래의 그림은 소프트웨어적으로 분리된 메모리 맵
스택 영역 stack : 지역변수, 함수 포인터
힙 영역 heap : 동적 할당된 메모리 공간
코드 영역 code : 명령어 등이 저장됨
데이터 영역 data : 전역 변수들이 저장
4. 레지스터 register
- MCU 내부에 존재하는 가장 빠른 메모리. 비싸 페리페럴이나 CPU 내부에 작은 공간을 가짐
- 프로그램 카운터 PC Program Counter, 스택 포인터 SP Stack Pointer, 명령 레지스터 IR Instruction Register, 데이터 레지스터 DR Data Register, AR Address Register, 범용 레지스터 General Register 등 존재
4.1 레지스터 종류
- 프로그램 카운터 : 다음에 실행할 명령어의 주소를 저장하는 레지스터
* 아래의 그림은 프로그램의 실행과정으로 여기서 프로그램 카운터가 다음에 실행할 명령어 주소를 가지고 있음
=> 프로그램 카운터의 값이 실행할 명령어 위치가 되어 찾아감
다음 프로그램 카운터값은 명령어 크기만큼 +됨.
32비트 컴퓨터 즉, 4바이트 크기라면 0x0000 -> 0x0004 -> 0x0008
- 스택 포인터
-> 함수 호출과 관련된 정보들을 스택 자료구조로 저장하는 공간.
-> 함수가 호출되고 종료후 돌아갈 지점과 데이터들을 관리
- 명령어 레지스터 : 명령어들을 담는 레지스터
- 데이터 레지스터 : 데이터들을 담는 레지스터
- 범용 레지스터 : 프로그램 카운터, 스택 포인터, 명령/데이터 레지스터 제외한 범용 목적의 레지스터
5. 메모리 관리 유닛 MMU Memory Management Unit
MMU
- 메모리 공간에 프로그램을 로드하여 사용중에 충돌이나 문제를 막기위한 HW.
* 동일한 문제를 막기위한 SW로 OS가 있음.
MMU 기능과 구성 요소
- 기능 : 가상 메모리 Virtual Memory와 실제 Physical memory 사이 변환 및 메모리 보호
- 가상 메모리 : 가상의 메모리로 물리적인 메모리보다 큼
- 물리 메모리 : 실제 존재 존재하는 메모리
가상 메모리, MMU를 사용하는 이유
- 소프트웨어는 자신이 사용할 공간을 자기가 고르지 못하고 메모리 관리를 위해 운영체제가 배정해줌
- 프로그램들은 가상 메모리의 0x0000 0000에서 시작하지만 실제로 MMU가 물리 메모리의 주소로 변환해줌
- 여러 프로그램들이 초기 지점인 0x0000 0000에서 시작하고, 물리 메모리 상에서 겹쳐지지 않게 하기 위함
6. 캐시 메모리
캐시 메모리
- CPU는 매우 빠르게 동작하지만, 메모리는 CPU에 비해서 상대적으로 느림
- 중간에 데이터를 미리 모아놓기 위한 공간으로 캐시메모리 사용.
- 캐시메모리는 레지스터보다 크나 메모리에 보다 매우 작음
- 아래의 그림은 CPU와 캐시 메모리, 메모리 사이의 구조를 보여줌
7. 페리페럴 Peripheral
페리페럴
- MCU 내부에 존재하는 주변장치들, 주변장치들을 사용하기 위한 단자
- 대표적으로 통신 페리페럴로 UART, SPI, I2C 등이 존재
- 아래의 그림은 페리페럴들의 예시
UART Universal Asychronous Receive Transmiter
- 범용 비동기 수신 발신기로 송신용 Tx와 수신용 Rx 신호선 2개로 구성됨
- 비동기 인 만큼 클럭에 상관없이 통신은 가능
- 아래의 그림은 UART 통신 시 연결
- 아래의 그림은 UART 통신시 데이터 프레임(형태)
RS-232
- 비동기 통신 중 길이와 속도를 맞추기 위해 RS-232라는 표준이 제정됨
=> RS-232로 특정 규칙에 따라 UART 통신이 안정적으로 수행됨
- 아래의 그림은 RS-232 핀아웃 별 의미
- 아래의 그림은 RS-232 통신 시 데이터 프레임
명령어 실행과정
- 명령어 실행 사이클이라도 함
- 메모리에서 명령어를 가져오고 fetch, 명령어를 해독 후 decode, 실행 excution하는 과정
파이프라인
파이프라인 구조
- 명령어 실행 과정으로, 패치 -> 해석 -> 실행 -> 저장 으로 정리할 수 있음
- 이를 고속으로 하기 위한 구조를 파이프라인.
파이프라인과 싱글라인의 차이
- 기존의 싱글 라인 구조는 하나의 명령어가 종료될때까지 다음 명령어가 대기
- 파이프라인 구조에서는 첫 명령어가 다음 단계로 넘어가면 새로운 명령어가 들어와 여러 명령어가 동시 수행
- 아래의 그림은 파이프라인 구조 예시
인터럽트
인터럽트란
- 프로그램 수행시 중간에 처리해야하는 상황과 동작 -> 예외처리/인터럽트라 부름
- 인터럽트 발생시 인터럽트 종류에 따라(인터럽트 벡터 테이블을 참고하여) 정해진 명령으로 PC의 값이 변경됨
- 아래의 그림은 인터럽트 벡터 테이블로 인터럽트 종류에 따라 수행해야할 동작들의 주소를 알려줌
인터럽트 관련 용어
- 인터럽트 벡터 :인터럽트를 처리하기위한 PC값으로 벡터 테이블에서 프로그램 카운터 값을 가져옴
- 인터럽트 핸들러 ISR Interrupt Service Routine : 인터럽트 발생시 처리해야할 콜백 함수
OS의 개발이 32 Bit 단계에 다다르면 사용할 수 없는 BIOS 함수들이기는 하지만, 부트 섹터나 OS 개발의 초반까지는 다음 BIOS 함수를 많이 사용하게 된다.
INT(0x10) : 비디오 관련
비디오 모드 설정
AH = 0x00
AL = 모드(자주 사용되는 화면 모드만 설명)
0x03:16색 텍스트, 80x25
0x12:VGA 그래픽스, 640 x480x4bit 칼라
0x13:VGA 그래픽스, 320 x200x8bit 칼라, Packed Pixel
0x6a:확장 VGA 그래픽스, 800 x600x4bit 칼라
반환값:없음
커서 모양 설정
AH = 0x01
CH = 시작 라인
CL = 종료 라인
CH < CL라면 1개의 부분으로부터 되는 보통 커서
CH > CL라면 2개의 부분으로부터 되는 커서
CH == 0x20이면 커서는 표시되지 않는다
반환값:없음
커서 위치 지정
AH = 0x02
BH = 0(페이지 번호)
DL = x 좌표
DH = y 좌표
반환값:없음
점 출력
굳이 이 함수를 이용하지 않고 바로 Video 메모리를 이용할 수도 있다.
AH = 0x0c
AL = 색상 코드(0 ~ 15)
CX = x좌표
DX = y좌표
반환값:없음
한 문자 출력
AH = 0x0e
AL = 문자 코드
BH = 0(페이지 번호)
BL = 문자의 색
반환값:없음
주의) beep(0x07), 백 스페이스(0x08), CR(0x0d), LF(0x0a)는 제어 코드로서 인식된다
색상 코드를 대응되는 팔레트에 저장한다.
16색 모드일 때만 사용가능하다.
AX = 0x1000
BL = 색상 코드(0 ~ 15)
BH = 팔레트 코드(0 ~ 63)
주의) EGA 그래픽 카드와의 호환성을 유지하기 위해서 사용됩니다. 잘못 사용하면 상당히 복잡해지기 때문에 기본값 그대로 두고 사용하는 것이 좋습니다.
팔레트 설정
AX = 0x1010
BX = 팔레트 번호(0 ~ 255)
DH = Red(0 ~ 63)
CH = Green(0 ~ 63)
CL = Blue(0 ~ 63)
반환값:없음
문자열 출력
AH = 0x13
AL = 옵션
0x00:문자열의 속성을 BL 레지스터로 지정하고 커서는 이동시키지 않는다.
0x01:문자열의 속성을 BL 레지스터로 지정하고 커서를 이동시킨다.
0x02:문자열을 출력하고 커서는 이동시키지 않는다.
0x03:문자열을 출력하고 커서를 이동시킨다.
실제 데이터는 메모리에 [문자 코드] [칼라 코드] [문자 코드] [칼라 코드]와 같이 저장된다고 보면된다.
BH = 0(페이지 번호)
BL = 칼라 코드(AL 레지스터의 값이 0x01, 0x02일 경우에만 적용)
CX = 문자열의 길이
DL = x좌표
DH = y좌표
ES:BP = 출력할 문자열이 있는 곳의 주소
반환값:없음
제일 간단하게 사용할 수 있는 화면모드인 0x13의 사용법
0x13번 화면모드는 그다지 해상도가 좋지는 않지만 Packed Pixel 모드이기 때문에 프로그래밍 하기가 편합니다. 우선 화면 모드를 변경하고 팔레트를 설정합니다.
이 모드는 Video Ram의 0xa0000 ~ 0xafff의 64KB에 위치하게 됩니다. 정확히 말하면 320 x 200 = 64000이 되므로 62.5 KB라고 해야겠지만, VRAM는 0xa0000 ~ 0xaffff의 64 KB입니다.엄밀하게 말하면(자), 320 x200=64000이므로, 62.5 KB가 됩니다. 이 모드에서는 점 하나가 1바이트에 해당되기때문에 읽고 쓰기도 아주 간단합니다.
void io_hlt(void);
void io_cli(void);
void io_out8(int port, int data);
int io_load_eflags(void);
void io_store_eflags(int eflags);
void init_palette(void);
void set_palette(int start, int end, unsigned char *rgb);
void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1);
#define COL8_000000 0
#define COL8_FF0000 1
#define COL8_00FF00 2
#define COL8_FFFF00 3
#define COL8_0000FF 4
#define COL8_FF00FF 5
#define COL8_00FFFF 6
#define COL8_FFFFFF 7
#define COL8_C6C6C6 8
#define COL8_840000 9
#define COL8_008400 10
#define COL8_848400 11
#define COL8_000084 12
#define COL8_840084 13
#define COL8_008484 14
#define COL8_848484 15
void HariMain(void)
{
char *p;
init_palette();
p = (char *) 0xa0000;
boxfill8(p, 320, COL8_FF0000, 20, 20, 120, 120);
boxfill8(p, 320, COL8_00FF00, 70, 50, 170, 150);
boxfill8(p, 320, COL8_0000FF, 120, 80, 220, 180);
for (;;) {
io_hlt();
}
}
void init_palette(void)
{
static unsigned char table_rgb[16 * 3] = {
0x00, 0x00, 0x00, /* 0:검은색 */
0xff, 0x00, 0x00, /* 1:밝은 적색 */
0x00, 0xff, 0x00, /* 2:밝은 녹색 */
0xff, 0xff, 0x00, /* 3:밝은 노란색 */
0x00, 0x00, 0xff, /* 4:밝은 청색 */
0xff, 0x00, 0xff, /* 5:밝은 보라색 */
0x00, 0xff, 0xff, /* 6:밝은 청색*/
0xff, 0xff, 0xff, /* 7:흰색 */
0xc6, 0xc6, 0xc6, /* 8:밝은 회색 */
0x84, 0x00, 0x00, /* 9:어두운 적색 */
0x00, 0x84, 0x00, /* 10:어두운 녹색 */
0x84, 0x84, 0x00, /* 11:어두운 노란색 */
0x00, 0x00, 0x84, /* 12:군청색 */
0x84, 0x00, 0x84, /* 13:어두운 보라색 */
0x00, 0x84, 0x84, /* 14:어두운 청색 */
0x84, 0x84, 0x84 /* 15:어두운 회색 */
};
set_palette(0, 15, table_rgb);
return;
/* static char 명령은 (주소가 아니라)데이터밖에 쓰지 못하지만, DB명령과 동일함 */
}
void set_palette(int start, int end, unsigned char *rgb)
{
int i, eflags;
eflags = io_load_eflags(); /* 인터럽트 허가 플래그 값을 기록 */
io_cli(); /* 허가 플래그를 0으로 하여 인터럽트 금지 */
io_out8(0x03c8, start);
for (i = start; i <= end; i++) {
io_out8(0x03c9, rgb[0] / 4);
io_out8(0x03c9, rgb[1] / 4);
io_out8(0x03c9, rgb[2] / 4);
rgb += 3;
}
io_store_eflags(eflags); /* 인터럽트 허가 플래그를 본래 값으로 되돌린다. */
return;
}
void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1)
{
int x, y;
for (y = y0; y <= y1; y++) {
for (x = x0; x <= x1; x++)
vram[y * xsize + x] = c;
}
return;
}
현재 그래픽 모드
- 320 x 200 ( =64,000)개 화소 존재
- (y,x) 픽셀의 VRAM 번지 : 0xa000 + x + y * 320 번지
*
- 해당 메모리에 올바른 색 번호를 저장하면 화면상 그위치에 지정한 색이 나오게 됨.
boxfill8 함수
- 위와 같은 방식으로 시작점 (x0, y0), 끝점 (x1, y1)사이에 색 번호를 채워넣는 함수
void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1)
{
int x, y;
for (y = y0; y <= y1; y++) {
for (x = x0; x <= x1; x++)
vram[y * xsize + x] = c;
}
return;
}
상자 3개 그리기
- HariMain에서 x의 크기가 320인 화면 모드에서 빨강색을 (20, 20) ~ (120, 120),
; naskfunc
; TAB=4
[FORMAT "WCOFF"] ; 오브젝트 파일 만드는 모드
[INSTRSET "i486p"] ; 이 프로그램이 486 아키텍처 용 프로그램임을 nask에 알림
[BITS 32] ; 32비트 모드용 기계어 만듬
[FILE "naskfunc.nas"] ; 소스 파일명 정보
GLOBAL _io_hlt, _io_cli, _io_sti, _io_stihlt
GLOBAL _io_in8, _io_in16, _io_in32
GLOBAL _io_out8, _io_out16, _io_out32
GLOBAL _io_load_eflags, _io_store_eflags
[SECTION .text]
_io_hlt: ; void io_hlt(void);
HLT
RET
_io_cli: ; void io_cli(void);
CLI
RET
_io_sti: ; void io_sti(void);
STI
RET
_io_stihlt: ; void io_stihlt(void);
STI
HLT
RET
_io_in8: ; int io_in8(int port);
MOV EDX,[ESP+4] ; port
MOV EAX,0
IN AL,DX
RET
_io_in16: ; int io_in16(int port);
MOV EDX,[ESP+4] ; port
MOV EAX,0
IN AX,DX
RET
_io_in32: ; int io_in32(int port);
MOV EDX,[ESP+4] ; port
IN EAX,DX
RET
_io_out8: ; void io_out8(int port, int data);
MOV EDX,[ESP+4] ; port
MOV AL,[ESP+8] ; data
OUT DX,AL
RET
_io_out16: ; void io_out16(int port, int data);
MOV EDX,[ESP+4] ; port
MOV EAX,[ESP+8] ; data
OUT DX,AX
RET
_io_out32: ; void io_out32(int port, int data);
MOV EDX,[ESP+4] ; port
MOV EAX,[ESP+8] ; data
OUT DX,EAX
RET
_io_load_eflags: ; int io_load_eflags(void);
PUSHFD ; PUSH EFLAGS 라는 뜻
POP EAX
RET
_io_store_eflags: ; void io_store_eflags(int eflags);
MOV EAX,[ESP+4]
PUSH EAX
POPFD ; POP EFLAGS 라는 뜻
RET