AVR2012.02.11 14:32

#include <mega128.h>
#include 
<delay.h>
#include 
<stdio.h>
//
//[lcd1]       [AVR PortA]
//RS (pin4) -----  bit 0
//RD (pin 5) ----- bit 1
//EN (pin 6) ----- bit 2
//사용안함         bit 3
//DB4 (pin 11) --- bit 4
//DB5 (pin 12) --- bit 5
//DB6 (pin 13) --- bit 6
//DB7 (pin 14) --- bit 7
//
#define lcd1_RS  PORTA.0
#define lcd1_E   PORTA.2
#define lcd1_Out PORTA   
// PORTA.4~7 4bits
//
flash char str1[]= " cafe.naver.com ";
flash char str2[]= " circuitsmanual ";
//
void lcd1Data(char d){

    lcd1_RS=1;
    lcd1_Out=(d&0xF0)|1; lcd1_E=1; lcd1_E=0; delay_us(1);
    lcd1_Out=(d<<4)|1;   lcd1_E=1; lcd1_E=0; delay_us(100);
}
//
void lcd1Cmd(char c){
    lcd1_RS=0;
    lcd1_Out=c&0xF0; lcd1_E=1; lcd1_E=0; delay_us(1);
    lcd1_Out=c<<4;   lcd1_E=1; lcd1_E=0; delay_ms(5);
}
//
void lcd1_init(void){
    delay_ms(150); DDRA=0xFF
// lcd port output
    lcd1Cmd(0x28); lcd1Cmd(0x28); lcd1Cmd(0x28);
    lcd1Cmd(0x0C); lcd1Cmd(0x06); lcd1Cmd(0x01); delay_ms(30); 
}
//
void lcd1_gotoxy(char x, char y){
    if(!y)lcd1Cmd(0x80+x);
    else  lcd1Cmd(0xC0+x);
}
//
void lcd1_puts(char *str){ while(*str)lcd1Data(*str++); }
//
void lcd1_putsf(char flash *str){ while(*str)lcd1Data(*str++); }
//
void main(void){
    char buf[30], i;
    int k=1234;
    lcd1_init();
    while(1){
        lcd1_gotoxy(0,0); lcd1_putsf(str1);
        lcd1_gotoxy(0,1); lcd1_putsf(" circuitsmanual ");
        delay_ms(1000);
        for(i=0; i<16; i++)buf[i]=str2[i];
        lcd1_gotoxy(0,0); lcd1_puts(buf);
        sprintf(buf,"test %05d",k);
        lcd1_gotoxy(0,1); lcd1_puts(buf);
        delay_ms(1000);
    }
}

동작 시켜본 것은 아닙니다.

필요한 분은 테스트 해보세요

Posted by 콩알은
AVR2012.02.11 14:31
//LCD 선 연결
//[LCD]            [AVR Port]
//RS (pin4) -----  bit 0
//RD (pin 5) ----- bit 1
//EN (pin 6) ----- bit 2
//DB4 (pin 11) --- bit 4
//DB5 (pin 12) --- bit 5
//DB6 (pin 13) --- bit 6
//DB7 (pin 14) --- bit 7
//
//함수들
//unsigned char lcd_read_byte(unsigned char addr);
//     reads a byte from the LCD character generator or display RAM.
//     The high level LCD Functions are:
//
//unsigned char lcd_init(unsigned char lcd_columns)
//     initializes the LCD module, clears the display and sets the printing character position at row 0 and column 0. 
//     The numbers of columns of the LCD must be specified (e.g. 16). No cursor is displayed.
//     The function returns 1 if the LCD module is detected and 0 if it is not.
//     This is the first function that must be called before using the other high level LCD Functions.
//
//void lcd_clear(void)
//     clears the LCD and sets the printing character position at row 0 and column 0.
//
//void lcd_gotoxy(unsigned char x, unsigned char y)
//     sets the current display position at column x and row y. The row and column numbering starts from 0.
//
//void lcd_putchar(char c)
//    displays the character c at the current display position.
//
//void lcd_puts(char *str)
//     displays at the current display position the string str, located in RAM.
//
//void lcd_putsf(char flash *str)
//     displays at the current display position the string str, located in FLASH.
//
//
//==================================================================
// 코드비젼 컴파일러 사용
#pragma opt- 
//최적화금지옵션
//==================================================================
#include 
<mega128.h>
#include 
<stdio.h>
#include 
<delay.h>
//==================================================================
// Alphanumeric LCD Module functions
#asm
   .equ __lcd_port=0x1B ;PORTA
#endasm
#include 
<lcd.h>
//==================================================================
#define  SW1 PINB.0 
// 멈춤, 시작
#define  SW2 PINB.1 
// 리셋
//==================================================================

bit  CntFlg=0, KeyFlg=0;
char Min=0, Sec=0, mSec=0;
//==================================================================
//     Interrupt Service Routinue
//==================================================================
interrupt [TIM1_COMPA] void timer1_compa_isr(void){ 
//100msec 주기
//Key check
    if((SW1==0)&&(KeyFlg==0)){
        KeyFlg=1;       
//한번의 키 입력 연속으로 인식 막기위한 플래그
        CntFlg=~CntFlg; 
//SW1눌리면 플래그 반전
    }
    else KeyFlg=0;

    if(SW2==0){    
//SW2눌리면 카운터 초기
        CntFlg=0;
        Min=Sec=mSec=0;
    }
    
//time count-up
    if(CntFlg){
        mSec++;
        if(mSec>9){ mSec=0;  Sec++; }
        if(Sec>59){ Sec=0;   Min++; }
        if(Min>59)Min=0;
    }
}

//==================================================================
//     Main Routinue
//==================================================================

void main(void){
  char buf[20];
  
  PORTB=0xff
//스위치 입력 풀업저항 사용
  DDRA=0xff;

  TCCR1B=0x0C; OCR1A=6249; TIMSK=0x10
//16000000/256/(1+6249)=10Hz=0.1sec

  lcd_init(16);
  lcd_clear();
  lcd_gotoxy(00); lcd_putsf("circuitsmanual");

  #asm("sei")

  while(1){
      lcd_gotoxy(01);
      sprintf(buf,"%2d:%2d.%d",Min,Sec,mSec);
      lcd_puts(buf); 
      delay_ms(100);
  };
}
Posted by 콩알은
AVR2012.02.11 14:29

 

#include <lcd.h> 헤더 안에 있는 함수입니다.

 

void _lcd_ready(void);

void _lcd_write_data(unsigned char data);

 

// write a byte to the LCD character generator or display RAM
void lcd_write_byte(unsigned char addr, unsigned char data);

 

// read a byte from the LCD character generator or display RAM
unsigned char lcd_read_byte(unsigned char addr);

 

// set the LCD display position  x=0..39 y=0..3
void lcd_gotoxy(unsigned char x, unsigned char y);

 

// clear the LCD
void lcd_clear(void);

void lcd_putchar(char c);

 

// write the string str located in SRAM to the LCD
void lcd_puts(char *str);

 

// write the string str located in FLASH to the LCD
void lcd_putsf(char flash *str);

 

// initialize the LCD controller
unsigned char lcd_init(unsigned char lcd_columns);

 

//--------------------------------------------------------------

#include <mega128.h>
#asm
   .equ __lcd_port=0x1B ;PORTA
#endasm
#include <lcd.h>

 

 

void main(void){
  lcd_init(16);

  while(1){
      lcd_gotoxy(0,0);
      lcd_putsf("HELLO");
      
      lcd_gotoxy(0,1);
      lcd_puts("Lcd Test Program");
  };
}

 

 

 

 

4x24 CLCD경우에는

위처럼 선을 연결 하고서

 

#include <mega128.h>
#include <delay.h>

#asm
   .equ __lcd_port=0x1B ;PORTA
#endasm
#include <lcd4x40.h>

 

void main(void){
  lcd_init();

  while(1){

      lcd_clear();
      lcd_gotoxy(0,0);
      lcd_puts("Lcd Test 1-Line");
      lcd_gotoxy(0,1);
      lcd_puts("Lcd Test 2-Line");
      lcd_gotoxy(0,2);
      lcd_puts("Lcd Test 3-Line");
      lcd_gotoxy(0,3);
      lcd_puts("Lcd Test 4-Line");

      delay_ms(1000);
  };
}

 

위처럼 하면 될 것 같습니다.

잘 안되면 lcd4x40.h 내용을 읽어보세요

Posted by 콩알은
AVR2012.02.11 14:28


 

E클럭의 시간이 꽤 길어서

LCD_E=1; delay_us(1); LCD_E=0;

이런 식으로 딜레이를 주는 소스를 본적이 많을 겁니다.

 

Busy 체크를 안하면 회로나 코드가 간단 해지므로

딜레이로 대신 하는 소스가 많습니다. 

 

LED 백라이트는 노란색 LED가 직렬로 두개 들어가 있으므로

순방향 전압이 4.2V입니다.

5V에 정류 다이오드를 1개 넣어서 공급 하기도 하고

10옴 정도의 저항을 넣기도 합니다.

 

 

 

10장. LCD 표시 장치의 제어

파일 형식: PDF/Adobe Acrobat - 빠른 보기
←2열 DDRAM 주소. ➢ DDRAM의 주소와 LCD 표시장치와의 관계 (20문자ⅹ2라인을 사용하는 경우 ..... LCD_STR(str);. // 문자열 str을 LCD 출력 while(1);. } 10.5 LCD 인터페이스와 구동 프로그램 .... “김치”라는 한글을 LCD에 표시하는 프로그램 작성 ...
121.143.152.79/down.../10장_LCD%20표시%20장치의%20제어.pdf
 
이 글에 예제 프로그램과 설명이 있습니다.
 
Posted by 콩알은
AVR2012.02.04 11:24

[AVR Essay 13] 인터럽트 이야기

 

 

지금까지 기본적인 I/O에 대해서 대부분 이야기 한 것 같다.

 

이제, ATmega 8535에 있는 조금은 더 특수한 기능을 다루게 될 것이다.

 

타이머, 카운터, PWM, USART, SPI, TWI, A/D 컨버터, 아날로그 비교기가 있다.

 

이들 중 대부분은 인터럽트를 사용한다.

 

그래서 간단하게나마 인터럽트가 무엇인지 이해하고 진행해야 할 것 같다.

 

 

인터럽트는 프로그램이 실행 중에 외부의 장치 등에서 요구가 있으면

 

실행중의 프로그램을 일시 중단하고 요구에 따라 다른 처리를 수행 한다.

 

간단히 아래 그림을 보면 된다.

 

프로그램이 잘 실행되고 있다가 인터럽트가 요청되면 나중에 되돌아올 return address를 스택에 저장 한 후

 

interrupt service routine에서 인터럽트를 처리한 후 인터럽트 발생전 위치로 돌아 오는 것이다.

 

대충 이정도 개념만 알고 넘어 가자. 이론적으로 인터럽트를 풀어 나가면 할 말이 너무 많다.

 

 

ATmega 8535에는 SREG 레지스터(Status Register)가 있다.

 

여기서 Bit 7이 지금 다루고 있는 인터럽트 관련 레지스터다.

 

그것도 시스템 전체 인터럽트를 제어한다. Global Interrupt Enable이라는 의미이고

 

인터럽트를 사용하기 위해서는 우선 이를 1로 해 설정 해야 한다.

 

이 레지스터를 직접 건들여서 제어할 수도 있지만 주로 WinAVR에서는 sei();, cli(); 명령을 사용한다.

 

전체 인터럽트를 사용하기 위해서는 sei();, 전체 인터럽트를 중지 할려면 cli(); 명령을 사용하면 된다.

 

대충 set interrupt, clear interrupt 하면 명령어 외우는데는 문제 없을 것이다.

 

(사실 이 명령은 SEI, CLI로 어셈블리에 근거 한 것이다.)

 

나머지 Bit들도 상당히 중요한 것이지만 인터럽트와는 관계 없기 때문에 생략 하겠다.

 

(데이터 시트에 설명이 아주 훌륭하게 되어 있다.)

 

 

각 장치 별로 인터럽트를 사용하기 위해서는 각 장치에 해당하는 레지스터마다

 

인터럽트 사용 유무를 결정 할 수 있는 부분이 있다.

 

이들에 대해서는 해당 장치가 나올 때마다 다룰 예정이다.

 

인터럽트가 발생 했을 때 어떤 장치가 인터럽트를 발생 했는지 판단하는 인터럽트 벡터가 있다.

 

인터럽트 벡터는 인터럽트 마스크 레지스터를 통해서 각각 개별적으로 허용여부를 설정할 수 있다.

 

ATmega 8535에는 다음과 같이 인터럽트 벡터가 정의 되어 있다.

 

(AVR에서는 인터럽트가 동시에 발생하면 아래 순서대로 우선순위가 결정 된다.)

 

여기서 각각 인터럽트는 Program Address를 통해서 접근하는데,

 

역시 사람은 숫자에 약하기 때문에 WinAVR의 avr/iom8535.h 파일에 다음과 같이 #define 되어 있다.

 

 

이하 부분은 생략 했으니 직접 파일을 열어서 어떻게 되어 있는지 확인 해 보도록 해라.

 

 

인터럽트가 발생 했을 때 interrupt service routine으로 점프 하는데 여기서 할 일을 정해 주면 된다.

 

interrupt service routine은 WinAVR에서는 함수와 유사하게 작성하면 된다.

 

 

ISR(인터럽트 벡터)

{

   ...

}

 

 

예를 들어 타이머0 오버플로우 인터럽트의 interrupt service routine을 구현 할려면 다음과 같이 하면 된다.

 

 

ISR(TIMER0_OVF_vect)

{

   // 할일

}

 

 

그리고 인터럽트를 사용하기 위해서는 avr/interrupt.h를 #include 해 줘야 한다.

 

경우에 따라 interrupt service routine을 ISR 대신 SIGNAL이나 INTERRUPT로 처리하기도 하는데,

 

WinAVR에서는 앞으로 없엘 명령이기 때문에 사용하지 않는 것이 좋다.

 

자세한 내용은 avr/interrupt.h 파일을 참조 하도록 하라.

 

 

인터럽트와 대비되서 사용되는 말은 폴링(polling)이 있다.

 

인터럽트는 프로그래머가 모르는 시점에서 발생하는 것이지만

 

폴링은 프로그래머가 원하는 시점에 처리하는 것 이다.

 

텀즈에는 폴링을 다음과 같이 설명하고 있으니 참조 하도록 하라.

 

 

통신에서, "폴링"은 한 프로그램이나 장치에서 다른 프로그램이나 장치들이 어떤 상태에 있는지를

 

지속적으로 체크하는 전송제어 방식으로서, 대체로 그들이 아직도 접속되어 있는 지와 데이터 전송을 원하는지 등을 확인한다.

 

명확히 말하면, 여러 개의 장치가 동일 회선을 공유하는 멀티드롭이나 멀티포인트 통신에서,

 

제어 장치는 각 장치에 한번에 하나씩 메시지를 보내어, 전송할 데이터가 있는지(즉, 회선을 사용하기 원하는지)를 묻는다.

 

 

일단 이 정도 하면 인터럽트가 무엇인지 감을 잡았다고 믿고 싶다.

 

구체적인 예제는 앞으로 쭉~~~ 나올 것이다.

Posted by 콩알은
AVR2012.02.04 11:23

[AVR Essay 11] 2 x 16 Character LCD

 

 

2 x 16 Character LCD는 학습용 키트에서 가장 많이 사용되는 LCD로 알고 있다.

 

내 경험 상, 이거 안 달린 키트를 못 봤고 이 내용을 다루지 않는 마이크로 프로세서 서적을 못 봤다.

 

그리고 내 방에 가장 많이 굴러다니고 있는 LCD이기도 하다.

 

요놈은 다음과 같이 생겼다.

 

 

이 놈은 제조 회사나 백라이트 유무에 따라 많은 모델이 있고

 

모듈 형태도 조금씩 다르다.

 

그리고 여기서는 2 x 16 크기만 다루지만 1 x 8 부터 4 x 40 크기까지 다양한 크기가 있다.

 

(여기서 2 x 16 이라는 것은 2줄짜리이고 한 라인에 16 글자 표현 가능하다는 의미이다.)

 

그런데, 내부 구조는 거의 동일 하다.

 

즉, 한가지 모델만 제대로 이해하면 다른 모델은 데이터 시트만 쓱 보면 잘 사용 할 수 있다는 것이다.

 

 

일반적인 Character LCD는 다음과 같은 특징을 가지고 있다.

 

1. 5V의 단일 전원으로 동작한다.

 

2. CG (Character Generator) ROM, CG RAM, DD (Display Data) RAM을 내장하고 있다

 

3. 인터페이스가 표준화 되어 있고, 8bit 데이터 버스, 4bit 데이터 버스 중 선택 해서 제어가 가능하다.

 

4. 내부 제어 명령이 거의 표준화되어 있어서 제조회사나 모델에 상관 없이 동일한 방법으로 구동 가능하다.

 

5. LCD 모듈은 일반적인 I/O 인터페이스 반도체 소자에 비해 액세스 시간이 상당히 길어 동작이 느리다.

 

6. 영문 ASCII 문자와 일본어를 표시 가능하며 8개의 사용자 정의 문자를 만들어 사용 할 수 있다.

 

7. 자동 커서 증가, 커서 이동, 커서 디스플레이 유무를 조작 할 수 있다.

 

8. 문자를 5 x 7 (혹은 5 x 10) 도트 매트릭스 방식으로 표시한다.

 

9. DD RAM, CG RAM에 데이터를 읽고 쓸 수 있다.

 

10. 백라이트 모델이 있어 어두운 곳에서도 화면을 볼 수 있고 백라이트 광원도 다양하다.

 

 

대충 이정도로 이론은 정리하고, 내부 구조 등을 상세히 알고 싶으면 데이터 시트를 참조 해라.

 

(데이터시트 구하기 생각보다 어려웠다. 그래서 이놈 데이터 시트는 이 문서에 첨부 했다.)

 

 

외부 핀은 다음과 같은 기능을 한다.

신호명

기능

1

Vss

 전원 GND

2

Vdd

 전원 +5V

3

Vo

 화면 밝기 제어 동작 전압 (4.2 < Vdd-Vo < 4.6)

4

Rs

 Register Select (1 = data, 2 = instruction)

5

R/W

 Read/Write ( 1 = CPU <- LCD, 0 = CPU -> LCD )

6

E

 Enable signal for read/write

7

DB0

 (LSB)

8

DB1

 

9

DB2

 

10

DB3

 데이터 버스

11

DB4

 (4bit 인터페이스 모드에서는

12

DB5

  DB7~DB4를 사용)

13

DB6

 

14

DB7

 (MSB)

 

모델에 따라 15, 16은 백라이트로 사용된다.

 

백라이트는 모델마다 사용법이 다를 수 있으므로 해당 모델 데이터 시트를 구해서 참조 하기를 바란다.

 

LCD 자체 전원은 다음과 같이 준다.

 

이때 VDD와 VSS의 방향이 바뀌면 LCD 뿐만 아니라 회로 전체가 손상 입을 수 있으므로 주의 해야한다.

 

(실제로 나는 전원 잘 못 줬는데 LCD는 멀쩡한데 MCU가 나가 버렸다.)

 

여기서 가변 저항은 Vo에 들어가는 전압을 조작하기 위한 용도이기 때문에

 

아무것이나 사용해도 상관 없지만 경험상 5k옴 정도가 적합한 것 같다.

 

가변 저항은 LCD에 표시되는 화면을 최적으로 조정하기 위해서 사용한다.

 

 

핀 4, 5, 6은 제어 포트이고 7에서 14는 데이터 I/O 포트이다.

 

이 내용은 아래 표에 설명 되어 있다.

 

기능

제어 신호

제어 명령

실행시간

(Max.)

RS

R/W

7

6

5

4

3

2

1

0

 Clear Display

0

0

0

0

0

0

0

0

0

1

1.52 ms

 Return Home

0

0

0

0

0

0

0

0

1

-

1.52 ms

 Entry mode set

0

0

0

0

0

0

0

1

I

S

37 us

 Display ON/OFF control

0

0

0

0

0

0

1

D

C

B

37 us

 Cursor or display shift

0

0

0

0

0

1

S

R

-

-

37 us

 Function set

0

0

0

0

1

D

N

F

-

-

37 us

 Set CG RAM address

0

0

0

1

CG RAM address

37 us

 Set DD RAM address

0

0

1

DD RAM address

37 us

 Read busy flag and address

0

0

B

Address Counter

0 us

Data write to CG RAM/ DD RAM

1

0

 

41 us

Data read from CG RAM/ DD RAM

1

1

 

41 us

 

① 화면을 모두 지우고 커서를 home으로 위치 시킨다.

 

② 커서만 home으로 위치 시킨다. (데이터 보존)

 

③ LCD를 read/write 할 때 DD RAM 어드레스 증가(I=1), 감소(I=0), 화면 시프트 할 것(S=1)인지 안 할 것(S= 0)인지 지정한다.

 

④ 화면 On/Off (D), 커서 On/Off (C), 커서를 깜박이게 할 것인지(B)를 지정한다.

 

⑤ 커서를 이동시킨다. (R = 1이면 오른쪽, R=0이면 왼쪽)

 

⑥ 데이터 비트 제어(8bit : D = 1, 4bit : D = 0), 화면 행 수(2행 : N = 1), 폰트 크기(F)를 지정한다.

 

⑦ CG RAM을 선택한다.

 

⑧ DD RAM을 선택한다.

 

⑨ LCD가 명령을 수행 중인지 확인한다.

 

 

제어 명령은 몰라도 대충 어떤 기능이 있는지는 알고 있어야 한다.

 

lcd 관련 라이브러리를 직접 만들 수도 있겠지만 범위가 방대하기 때문에

 

다른 사람이 제작한 라이브러리를 읽고 이해하는 정도면 충분 하다고 생각한다.

 

 

LCD를 사용하기 위해서는 간단한 초기화 과정이 필요 하다.

 

이는 사용 준비를 하기위한 절차이며

 

4 bit로 제어 할 것인지, 8 bit로 제어 할 것인지 설정 하기 위함이다.

 

 

8 bit 제어는 다음 절차를 거친다.

 

 

4 bit 제어 초기화는 다음과 같다.

 

만약 잘 안 보이면 첨부한 데이터시트를 참조 하도록 하라.

 

 

DD RAM은 표시할 문자들의 ASCII 코드가 저장되어 있는 내부 메모리이며 모두 80개의 번지가 있다.

 

그 중에서 2 x 16 CLCD는 32개만을 사용한다. 그리고 이 주소에 해당하는 위치가 실제 출력 위치이다.

 

 

문자 데이터는 데이터 시트에 잘 나와있고 그 중 일부를 발췌 했다.

 

아래와 같은 형태로 출력이 되고 데이터 값 자체는 ASCII 코드와 일치 하므로 편리하다.

 

 

이제 그러면 직접 ATmega 8535로 LCD를 구동해 보도록 하자.

 

다음과 같이 하드웨어를 구성한다.

 

(여기서 VDD, VSS, Vo는 생략 했다. 회로도에는 생략되었지만 반드시 구현해야한다.)


 

 

이 프로그램에 대한 소스는 다음과 같다. (delay.h와 delay.c는 생략 했다.)

 

형광색으로 칠한 주석 부분은 소스의 파일 명이다.

 

 

// main.c

#include <avr/io.h>
#include "delay.h"
#include "lcd.h"

int main(void)
{
   lcd_init() ;

   //                              "0123456789ABCDEF"
   lcd_display_string(0, 0, "          Hello!         ");
   lcd_display_string(0, 1, "ATmega8535 World");
 
   return 0;
}

 

 

// lcd.h

#ifndef _LCD_H_
#define _LCD_H_

 

#include <avr/io.h>
#include "delay.h"

 

// LCD 사용 포트를 지정한다.
#define LCD_DCTRL (DDRC)
#define LCD_DDATA (DDRD)
#define LCD_CTRL (PORTC)
#define LCD_DATA (PORTD)

#define LCD_RS   (PC5)
#define LCD_RW   (PC6)
#define LCD_E   (PC7)
#define LCD_DB0  (PD0)
#define LCD_DB1  (PD1)
#define LCD_DB2  (PD2)
#define LCD_DB3  (PD3)
#define LCD_DB4  (PD4)
#define LCD_DB5  (PD5)
#define LCD_DB6  (PD6)
#define LCD_DB7  (PD7)

 

void lcd_init(void); // LCD 초기화
void lcd_control(uint8_t data) ; // 기능 제어
void lcd_write_char(uint8_t data) ; // CG/DD RAM 데이터 쓰기
void lcd_position (uint8_t x, uint8_t y) ; // 커서 위치 지정
void lcd_display_string(uint8_t x, uint8_t y, char *string) ; // 문자열 출력

 

#endif

 

 

// lcd.c

#include "lcd.h"

void lcd_init(void) 

   LCD_DDATA = 0xFF;

   LCD_DCTRL = 0xFF;
   LCD_DATA = 0;
   LCD_CTRL = 0;

   delay_ms(1);

 

   lcd_control(0x38);  // Function Set (8bit, 2line, 5 x 7 dot 
   lcd_control(0x0C);  // Display ON, Cursor OFF
   lcd_control(0x06);  // Entry Mode Set (increment, not shift)
   lcd_control(0x01);  // Clear Display   
}

 

void lcd_control(uint8_t data) 
{
   LCD_CTRL = LCD_CTRL & ~_BV(LCD_RW);
   LCD_CTRL = LCD_CTRL & ~_BV(LCD_RS);
   LCD_DATA = data;
   LCD_CTRL = LCD_CTRL | _BV(LCD_E);
   delay_ms(2);
   LCD_CTRL = LCD_CTRL & ~_BV(LCD_E);
}


void lcd_write_char(uint8_t data) 
{
   LCD_CTRL = LCD_CTRL & ~_BV(LCD_RW);

   LCD_CTRL = LCD_CTRL | _BV(LCD_RS);
   LCD_DATA = data;
   LCD_CTRL = LCD_CTRL | _BV(LCD_E);
   delay_ms(2);
   LCD_CTRL = LCD_CTRL & ~_BV(LCD_E);
}


void lcd_position(uint8_t x, uint8_t y) 
{
   uint8_t location=0;


   if(y > 0x01) 
      y = 0x01; 
   if(x > 0x0f)

      x = 0x0f;
 

   if(y == 0)

      location = x + 0x80;

   else

      location = x + 0xC0; 
   lcd_control(location); 
}


void lcd_display_string(uint8_t x, uint8_t y, char *string) 
{
   lcd_position(x,y);

   while(*string != '\0')
   {
      lcd_write_char(*string);
      string++;
   }
}


 

대충 어떤 것을 출력 할 것인지는 감이 올 것이다.

 

lcd.h에서 #define 된 부분에 괄호속에 값을 바꾸면 손쉽게 제어 포트를 바꿀 수 있다.

 

그리고 명령 중에 PC에서 프로그래밍 할 때 못 본 내용이 있다.

 

_BV() 인데.... 이는 sft_defs.h에

 

#define _BV(bit) (1 << (bit))

 

과 같이 정의 되어 있다.

 

나머지는 표준 C 언어로 되어 있기 때문에 생략 한다.

 

명령 코드 표과 참조해서 보면 쉽게 이해가 될 것이다.

 

또, LCD의 많은 기능 중에서 문자 출력만 구현 했는데,

 

더 원하는 기능이 있다면 함수를 추가해서 사용 할 수 있다.

 

그러나 당장 사용하지 않는 기능을 함수로 추가 하는 것은 별로 좋지 않다.

 

AVR은 성능이나 용량이 PC에 비해 형편없이 딸리기 때문에 어느 정도 최적화를 고려 해야 한다.

 

 

결과는 다음과 같다.

 

 

 

PS...

 

2007년 06월 26일 4bit 제어 라이브러리도 추가하였다.

 

Posted by 콩알은
AVR2012.02.04 11:22

[AVR Essay 10] 74LS47을 이용한 7-Segment 제어

 

 

앞에서 봤듯이 7-Segment를 제어하기 위해서는 8개의 포트가 필요하다.

 

즉, ATmega 8535에는 32개의 포트가 있기 때문에 총 4개의 7-Segment를 제어 할 수 있다.

 

그런데 이를 두배로 뻥 튀겨 주는게 있으면 어떨까?

 

이런 기능을 하는 것 중 대표적인 것이 74LS47이다.

 

유사한 칩으로는 74LS46, 74LS48, 74LS49, 74LS246, 74LS247, 74LS248, 74LS249가 있다.

 

일단 이들은 7-Segment가 어떤 타입이냐, 어떤 전압이 필요하냐에 따른 구분으로 기능은 동일하다.

 

이들 칩을 BCD to 7-Segment Decoder/Driver이라고 부른다.

 

 

기능은 BCD 값을 입력하면 그에 맞는 값을 7-Segment에 출력 한다.

 

여기서 BCD는 Binary-Coded Decimal의 약자로서 이진수와는 조금 다른 개념이다.

 

간단하게 말하면 십진수의 각 자리를 2진수로 표현 한 것이다.

 

예를 들어 보면 십진수로 261은

 

2는 2진수로 0010

 

6은 2진수로 0110

 

1은 2진수로 0001

 

이므로 0010 0110 0001 로 표현 한 것이 BCD이다.

 

잘 이해가 안 되면 검색해 보도록 한다.

 

 

 

7-Segment 하나를 제어하는데는 8개의 포트가 필요 했지만

 

BCD를 사용하면 0에서 9까지 표현하는데 있어서 4개의 포트만 있으면 된다.

 

(이때, 7-Segment의 DP는 제외다.)

 

 

핀 이름

기능

A0  A3

BCD 데이터 입력

RBI

Ripple Blanking Input (Active LOW)

LT

램프 테스트 입력 (Active LOW)

BI/RBO

Blanking Input (Active LOW) /

Ripple Blanking Output (Active LOW)

 f

7- Segment 출력 (Active LOW)

 

74LS47은 위와 같이 생겼다.

 

핀 A3, A2, A1, A0에는 BCD 코드를 입력으로 준다. 만약 1010이라는 BCD 코드를 입력 할려면

 

A3 : High

 

A2 : Low

 

A1 : High

 

A0 : Low

 

위와 같이 데이터를 주면 된다.

 

그러면 핀 a ~ g에서 출력이 나온다.

 

이는 7-Segment에 있는 핀 a ~ g와 일치하게 연결 하면 된다.

 

단 이때, 보통 출력이 5V이기 때문에 7-Segment와 74LS47사이에 330옴 저항을 달아 줘야 한다.

 

 

그리고 나머지 포트는 사실 잘 사용하지 않는다.

 

74LS47을 사용하는 대부분의 목적이 포트를 줄이는 것인데

 

나머지 포트를 사용하면 이 효과가 없기 때문이다.

 

그래도 알아둬서 나쁠 것은 없기 때문에 간단히 설명하겠다.

 

RBI 포트가 Low인 경우 0에 해당하는 BCD 코드가 입력되었을 때, 7-Segment의 모든 램프가 꺼진다.

 

만약 4개의 7-Segment를 제어 시 BCD 코드로 10을 출력 한다면 RBI를 사용하지 않는다면 출력이 “0010”이 되겠지만

 

1보다 위의 코드 경우 RBI를 Low로 set 한 후 출력하면 “  10”과 같이 불필요한 0을 제거 해 준다.

 

이때 RBO는 LOW가 된다. BI는 입력된 BCD 코드와 관계 없이 7-Segment의 모든 램프를 끈다.

 

데이터 조작 없이 램프를 점멸 하고자 할 때 사용한다.

 

 

 

입력한 값에 따라 아래와 같은 형태로 출력된다.

 

(위에 인덱스 값이 왜 BCD가 아닌 그냥 십진수인지는 모르겠다.)

 

그런데 10부터 15깨지는 A ~ F가 나와야 할 것 같지만 그렇지 않다.

 

나는 처음에 저 문자들이 뭔가 의미가 있은 것인 줄 알았는데...

 

사실 아무 의미가 없는 값 들이다.

 

74LS47은 0부터 9까지 출력하도록 설계 되었기 때문에 나머지는 신경을 쓰지 않았다.

 

데이터 시트를 보년 74LS47의 내부 구조가 그려져 있는데 이 회로에 따른 부산물일 뿐이다.

 

유식하게 don't care라고 부르기도 한다.

 

 

이제 직접 실험을 해 보도록 하자.

 

아래와 같이 회로를 꾸민다.

 

 

 

 

나는 여기서 74LS47과 7-Segment를 2개로 묶어서 모듈을 만들었다.

 

앞으로 디지털 시계 등을 제작할 것인데 이렇게 해 두면 상당히 편리하다. 

 

 

 


프로그램 소스는 다음과 같다.

 

 

#include <avr/io.h>
#include "delay.h"

 

int main(void)
{
   DDRA = 0xFF;
   int i = 0;

 

   while(1)
   {
      PORTA = ((i / 10) << 4) | (i % 10);
      delay_ms(200);
      i++;

      if( i == 100 ) i = 0;
   }
 
   return 0;
}

 

 

여기서 PORTA = ((i / 10) << 4) | (i % 10); 이 부분을 잘 이해 해야 한다.

 

숫자를 다룰때 자주 사용하는 기법이다.

 

잘 이해가 안 되면 연습장을 꺼내서 i값에 따라 어떤 데이터가 PORTA에 입력되는지

 

한번 손으로 계산해 보는 것도 좋은 방법이다.

 

 

실행 결과는 다음과 같다.

Posted by 콩알은
AVR2012.02.04 11:07

[AVR Essay 19] UART 통신 6 - UART 라이브러리

 

이번에는 내가 여기 저기서 주워온 소스를 적당히 수정해서 만든

 

ATmega8535용 UART 통신 라이브러리다.

 

말이 라이브러리지 내가 만들어서 그리 다양한 기능은 없다.

 

일단 이 문서에 포함되어 있는 파일의 압축을 풀면 다음과 같은 파일을 볼 수 있다.

 

 

uart.c, uart.h

 

buffer.c buffer.h

 

main.c

 

makefile

 

 

여기서 uart.c, uart.h는 실제 UART 장치 설정하는 라이브러리다. 이는 buffer에 수신된 데이터를 쌓아두기 때문에

 

별도의 버퍼가 필요한데 이 녀석이 buffer.c와 buffer.h이다.

 

즉, 인터럽트 방식과 폴링 방식의 장점을 적절히 모아 놓았다고 보면 된다.

 

소스 코드 자체가 그리 어렵지는 않고, 내가 할 수 있는한 자세하게 주석을 달아 두었으니

 

분석하고 싶다면 직접 소스코드를 열어서 분석을 해 보도록...

 

 

일단 여기서는 이 놈을 어떻게 사용하는지에 대해서만 언급하겠다.

 

간단하게 앞에서 사용했던 하드웨어를 그대로 사용한다.

 

앞에서 해서 알겠지만 먼저 makefile에 사용하고자 하는 라이브러리를 등록한다.

 

# List C source files here. (C dependencies are automatically generated.)
SRC = delay.c uart.c buffer.c

 

위 내용은 makefile의 80번째 줄 쯤에 있다.

 

 

그 다음으로는 실제 실습 소스를 토대로 이야기 해 보자.

 

실습 소스는 첨부한 파일에서 main.c 이고 프로그램에 쬐끔 크기 때문에 전체 소스는 여기에 뿌리지 않겠다.

 

참고로 이 프로그램은 세 문자를 PC에서 받아서

 

이에 해당하는 대문자는 소문자로, 소문자는 대문자로 변환해서 PC로 전송하는 프로그램이다.

 

결과를 보면 다음과 같다.

 

 

main.c를 열어 보면 일단 다음과 같은 #include를 볼 수 있다.

 

#include <stdio.h>       

#include <avr/io.h>
#include 
<avr/interrupt.h>

#include "delay.h"

#include "uart.h"

#include "buffer.h"

 

다른 녀석들은 쉽게 이해되지만 stdio.h는 새롭게 추가되었다.

 

요놈은 사실 PC상에서 C언어로 작업할 때 지겹도록 #include 해 주던 녀석이다.

 

본 라이브러리에서 stdio.h와 관련된 몇가지 기능이 필요해서 이 녀석을 #include 해 줘야 한다.

 

 

//UART 기초 설정
static FILE define_uart = FDEV_SETUP_STREAM(uart_transmit, NULL, _FDEV_SETUP_WRITE);

#define UART (FILE*)(&define_uart)

static BUFFER_STR_HANDLE uart_received_buffer;

static int8_t uart_rx_buffer[UART_SIZE_BUFFER];

 

그 다음으로 볼 수 있는게 위 문장들이다.

 

뭔가 많이 어려운 말들만 있다. 이해가 안되면 그냥 써라. 이해하고 싶다면 나름 주석은 달아 놓았으니 참고 하도록...

 

ATmega8535로 작업한다면 그냥 저 문장 그대로 쓰면 된다. 믿어라~~~

 

 

// 데이터 수신시 이 함수를 통해서 데이터를 버퍼에 넣는다.
int shell_uart( char c )
{
    buffer_putchar(&(uart_received_buffer), c);
    return c;
}

이 함수도 그냥 만들어 줘라. 인터럽트 발생시 데이터 수신 후 버퍼에 넣어주는 기능을 한다.

 

전체 소스에 이 놈 프로토타입도 있으니 나중에 필요하면 써 줘야 한다.

 

 

// UART 초기화
uart_init(9600, shell_uart);    
// UART 장치 초기화
uart_received_buffer.depth = UART_SIZE_BUFFER;    
// 버퍼 크기 지정
uart_received_buffer.data = uart_rx_buffer; 
// 실제 버퍼 저장 공간과 연결
buffer_init(&(uart_received_buffer));            
// 버퍼 초기화

sei();    
// 전체 인터럽트 허용

main() 함수에 위 구문만 넣어주면 UART 장치 쓸 준비가 다 되었다.

 

9600  대신 다른 숫자를 넣으면 해당 숫자로 통신 속도가 바뀐다.

 

마지막 sei(); 는 전체 인터럽트를 허용하기 때문에 안 해주면 데이터 수신이 전혀 이루어 지지 않으므로 주의하자.

 

 

이제 본격적으로 데이터를 전송해 보자.

 

fprintf(UART, "\r\nHello UART World!\r\n");

fprintf(UART, "\r\n-> %s\r\n", temp_buffer);    // 데이터를 전송한다.

위 두 문장을 보면 딱 느낌이 올 것이다. 바로 printf() 함수 이다. 이 녀석과 동일하게 써 주면 된다.

 

아마 PC 상에서 파일 입/출력을 해 봤다면 쉽게 이해가 될 것이다.

 

 

그렇다면 데이터 수신은 어떨까? 대략 다음과 같다.

 

// 버퍼에서 수신된 데이터를 확인한다.
// 인터럽트에 의해 수신된 데이터는 버퍼에 쌓인다.
while(buffer_count(&(uart_received_buffer)) != 0)
{
     buffer_getchar(&(uart_received_buffer), &temp_char);

     ...

     ...

}

 

우선 while()문에서 버퍼에 데이터가 있는지 확인한 후 데이터가 있는 동안 내부 구문을 일행한다.

buffer_gerchar() 명령으로 버퍼에 가장 마지막에 수신되어 남아있는 데이터를 temp_char라는 변수에 넣는다.

이는 자료구조에서 Queue와 같다고 생각하면 될 것이다.

그 후 수신한 데이터를 샤바샤바 처리해 주면 된다.

주로 패킷 단위로 데이터가 오고갈때 이를 이용하면 편리하게 처리 할 수 있다.

 

 

이 정도 했으면 아마 소스코드 보고 주석 적당히 참조하면 본 라이브러리를 쓰는데는 지장 없을 것이다.

 

참고로 이 라이브러리를 쓰면 4KB 정도 프로그램이 커진다.

 

ATmega8535가 8KB이기 때문에 상당히 많이 차지 하는 편이다.

 

가장 큰 원인은 stdio.h를 #include 한 것인데... 나도 모르겠다...ㅋㅋㅋ

 

(원래 이 라이브러리는 ATmega128에서 사용하던 것을 ATmega8535용으로 수정한 것이다.)

 

 

이로써 UART에 관해서는 마치도록 하겠다.

Posted by 콩알은
AVR2012.02.04 11:06

[AVR Essay 18] UART 통신 5

 

이번에는 예고 했듯이 인터럽트 방식의 UART 통신이다.

 

실험에 사용할 회로는 앞과 동일하지만 그래도 또 올려 본다.

 

다음은 실습에 사용할 프로그램이다.

 

// TX는 보낼 데이터가 있을때 알아서...
// RX는 인터럽트 발생시...
// 알파벳 진행하다가 알파벳 입력 받으면 거기서 부터 다시 시작...

// Baud Rate = 9600
// Flow Control In = None
// Flow Control Out = None
// Data Bits = 8
// Stop Bits = 1
// Parity = None

#include 
<avr/io.h>
#include 
<avr/interrupt.h>

#include "delay.h"

char rx_data, tx_data;    
// 수신, 송신 데이터를 각각 임시 저장


// 데이터 수신시 인터럽트 발생
SIGNAL(SIG_UART_RECV)
{
    cli();            
// 전체 인터럽트 중지

    rx_data = UDR;    
// 수신 받은 데이터를 임시 변수에 저장

    UCSRA &= 0x7f;    
// clear RXC flag

    sei();            
// 전체 인터럽트 허용
}


int main(void)
{
    
// 포트 초기화    
    DDRD = 0xFE;    
// RxD/PD<0> = 입력, TxD/PD<1> = 출력

    
// RS232 초기화
    UBRRL = 51;        
// 8MHz에서 9600 bps
    UCSRB = 0x18;    
// RXEN = 1, TXEN = 1
    UCSRB |= 0x80;    
// RXC interrupt 허용
    
    sei();            
// 전체 인터럽트 허용

    rx_data = 'a';    
// 초기값 설정

    while(1)
    {
        tx_data = rx_data;    
// 전송할 데이터 설정

        
// 다음 알파벳 생성, z면 a로 다시 시작
        if(rx_data != 'z')
        {
            rx_data++;
        }
        else
        {
            rx_data = 'a';
        }

        while((UCSRA & 0x20) == 0x00);    
// UDE가 비었으면 송신 가능

        UDR = tx_data;    
// 데이터를 보낸다.

        delay_ms(1000);    
// 1초 지연
    }

    return 0;
}

 

요놈을 컴파일 한 후 ATmega8535에 전송 한 후 PC에서 해당 포트를 열면

 

a 부터 z까지 약 1초 간격으로 출력됨을 확인해 볼 수 있다.

 

이때, a에서 z 중 아무 값이나 입력하면 입력한 값 부터 시작해서 알파벳에 다시 출력됨을 확인하자.

위에서 빨간색은 AVR에서 수신한 데이터이고 파란색은 PC에서 보낸 데이터이다.

 

결과와 비교해서 그리 어렵지 않으니 직접 소스코드를 분석해 보는 것이 좋다.

 

참고로 여기서는 AVR에서 데이터 수신시 SIGNAL(SIG_UART_RECV) {...} 부분이

 

인터럽트로 실행된다는 것만 유의하면 될 것이다.

 

인터럽트에 대한 기본적인 내용은 앞의 인터럽트 부분에서 언급했으니 그쪽을 참고하면 될 것이다.

(http://blog.naver.com/ds5pnz/140033279651)

 

 

간단한 통신 프로그램일 경우에는 폴링 방식이나 인터럽트 방식 중 하나만 적절히 이용하면 된다.

 

그런데 시스템이 커질 경우에는 이를 둘 다 사용할 필요가 있다.

 

즉, 데이터 수신은 일일이 인터럽트로 받아 (버퍼가 2문자로 제한이 있다.) 이를 적절한 공간에 저장 한다.

 

그 후 필요에 따라 이 공간에서 데이터를 받아 오는 방식(폴링)이다.

 

경우에 따라서는 일정 형태의 패킷을 수신할 필요가 있기 때문에 완성된 패킷일때만 수신할 수도 있다.

 

이를 라이브러리로 만들어 사용하면 편리하다.

 

다음 에세에에서는 내가 여기 저기서 주워다 조합한 라이브러리를 소개하는 것으로 UART 통신을 마치도록 하겠다.

Posted by 콩알은
AVR2012.02.03 15:38

[AVR Essay 17] UART 통신 4

 

본격적으로 UART 통신 실습을 해 보자.

 

지금 다룰 것은 폴링 방법이다. 이와 대비되는 의미는 인터럽트이고...

 

폴링... 원래 단어는 polling이다.

 

사전적 의미로는 투표라는 의미인데 사실 감이 안 온다.

 

김동근의 텀즈 (terms.co.kr)에 따르면 다음과 같은 뜻을 가지고 있다.

 

통신에서, "폴링"은 한 프로그램이나 장치에서 다른 프로그램이나 장치들이 어떤 상태에 있는지를 지속적으로 체크하는 전송제어 방식으로서, 대체로 그들이 아직도 접속되어 있는 지와 데이터 전송을 원하는지 등을 확인한다.  명확히 말하면, 여러 개의 장치가 동일 회선을 공유하는 멀티드롭이나 멀티포인트 통신에서, 제어 장치는 각 장치에 한번에 하나씩 메시지를 보내어, 전송할 데이터가 있는지(즉, 회선을 사용하기 원하는지)를 묻는다. 
 
"장치들이 어떤 상태에 있는지를 지속적으로 체크"가 아마 핵심인 것 같다.

 

설명할려면 손 아프고 보는 사람은 눈 아프니

 

일단 폴링 방식에 의한 UART 통신을 일단 해 보고 결과만 가지고 이야기 하도록 하자.

 

아래와 같이 회로를 꾸민다. 참고로 이 회로는 바로 뒤에 다룰 인터럽트 방식에도 그대로 쓰인다.

 

이 회로의 완성품은 다음과 같다. 폰카로 찍어서 화질이 안 좋으니 이해 바란다.

위 사진에서 우측에 세가닥 선은 PC와 연결되는 모습니다.

 

실험에 사용할 소스는 다음과 같다.

 

// 전송 받은 알파벳이 소문자면 대문자로, 대문자면 소문자로 변환
// 알파벳이 아닌 경우에는 "?"를 출력

// Baud Rate = 9600
// Flow Control In = None
// Flow Control Out = None
// Data Bits = 8
// Stop Bits = 1
// Parity = None

#include 
<avr/io.h>

int main(void)
{
    
// 포트 초기화
    DDRD = 0xFE;    
// RxD/PD<0> = 입력, TxD/PD<1> = 출력

    
// USART 초기화
    UBRRL = 51;        
// 8MHz에서 9600 bps
    UCSRB = 0x18;    
// RXEN = 1, TXEN = 1

    char rx_data, tx_data;    
// 수신, 송신 데이터를 각각 임시 저장

    while(1)
    {
        while((UCSRA & 0x80) == 0x00);    
// RXC = 1 이면 수신 끝
        
        rx_data = UDR;        
// 받은 데이터를 임시 변수에 저장

        
// 알파벳 소문자가 입력 되었을 경우
        if( (rx_data >= 'a') && (rx_data <= 'z') )
        {
            tx_data = rx_data - 'a' + 'A';    
// 소문자를 대문자로 변환
        }
        
        
// 알파벳 대문자가 입력 되었을 경우
        else if( (rx_data >= 'A') && (rx_data <= 'Z') )
        {
            tx_data = rx_data - 'A' + 'a';    
// 대문자를 소문자로 변환
        }

        
// 알파벳이 아닌경우 "?" 출력
        else
        {
            tx_data = '?';
        }

        while((UCSRA & 0x20) == 0x00);    
// UDE가 비었으면 송신 가능

        UDR = tx_data;    
// 데이터를 보낸다.
    }

    return 0;
}

 

소스를 대충 보면 이해되겠지만,

 

피씨 측에서 소문자를 날리면 AVR은 이에 해당하는 대문자를 리턴하고,

 

피씨 측에서 대문자를 날리면 AVR은 이에 해당하는 소문자를 리턴하며,

 

알파벳이 아닌 경우에는 ?를 리턴하는 프로그램이다.

 

(어렵지 않은 소스이니 반드시 이해하자.)

 

이 프로그램을 컴파일 후 ATmega8535에 다운로드 한 후

 

앞서 소개한 시리얼 웍스를 실행 한 후 ATmega8535와 연결된 포트를 설정에 맞추어 연 후 알파벳을 입력해 보자.

 

다음과 같은 결과를 얻을 수 있을 것이다.

여기서 파란색은 PC에서 입력한 값이고 빨간색은 AVR에서 받아온 값이다.

 

 

결과적으로 폴링 방식에 대해서 다시 살펴 보자.

 

while(1)

{

          scanf("%d", &a);

          printf("%d", a);

}

 

위 프로그램의 경우 입력 값을 받기 이전에는 프로그램은 아무런 행동도 하지 않는다.

 

즉, 입력 값을 받은 이후에야 다음 작업을 수행 할 수 있다.

 

폴링 방식은 이와 유사하다고 볼 수 있다.

 

위에서 짠 AVR 프로그램에서 rx_data = UDR; 는 바로 위의 scanf("%d", &a);와 동일하다고 보면 된다.

 

폴링 방식은 일일이 수신된 데이터가 있는지 확인하고 데이터를 수신 할때까지 기다리던지,

 

일정 시간동안 수신 상태를 유지하다가 다음 작업을 진행하는 방법 등이다.

 

이 경우 프로그래머는 언제 날아 올지 모르는 데이터에 대해서 항상 신경써야 하기 때문에 어렵다.

 

이를 보다 편리하게 극복한 방법이 인터럽트이다.

 

다음에 인터럽트 방식의 UART 통신에 대해 다루겠지만 수신 데이터가 인터럽트로 처리되기 때문에

 

프로그래머는 데이터가 언제 날아올지 신경을 쓰지 않아도 된다는 것만 알아 두자.

 

 

이번장은 여기까지...

Posted by 콩알은