AVR (ATmega2560) 보고서 2013 년 6 월 14 일 스마트컨트롤러 2013 조유진
1. 기본설정 목차 1-1. 설치해야할프로그램및파일 1-2. 프로그램올리기 1-3. MAKEFILE 2. 캐릭터 LCD(PORT) 3-1. 개요 3-2. 사용하는레지스터 3-3. Source Code 3-4. 실습사진 3. 타이머카운터및초음파센서활용 (PORT, TIMER, EXT-INT) 5-1. 개요 5-2. 사용하는레지스터 5-3. Source Code 5-4. 실습사진 4. 시리얼통신및센서이용, EEPROM(UART, ADC, EEPROM) 6-1. 개요 6-2. 사용하는레지스터 6-3. Source Code 6-4. 실습사진
1. 기본설정 환경 준비물 Windows 컴퓨터, ATmega2560, 케이블, 개념 1-1. 설치해야하는프로그램및파일 이름 Cygwin WinAVR 설명윈도우에서리눅스환경을제공해주는에뮬레이터프로그램. GCC를사용가능하게해주며, make명령어로컴파일할수있다. AVR을위한 GCC컴파일러를윈도에서실행할수있게해주는프로그램이다. 이름을클릭하여 URL이동 프로그램을설치하는방법은생략하도록한다. 여기서추가로 AVRStudio 등의프로그램을 설치하여도된다. 1-2. 프로그램올리기 ARM에서는프로그래밍모드에들어가는일련의과정을거쳤으나 ATmega2560에서는단순히스위치를켜고, 컴퓨터와케이블로연결해 AVR업로드유틸리티를이용해미리 make 한 HEX파일을바로올릴수있다. 1-3. MAKEFILE makefile은리눅스유틸리티중하나인 make를쓰는옵션에대해기술한파일이다. WinAVR등을이용한다면 makefile이미리쳐져있는것을알수있다. 여기서우리는단순히 SRC부분을찾아 c 파일만추가하는식으로 ( 띄어쓰기로구분 ) 사용하면된다.
3. 캐릭터 LCD(PORT) 사용기능 조작물품 PIOA, 모듈활용 캐릭터 LCD 3-1. 개요 캐릭터 LCD는글자만을출력하기위한 LCD 장치이다. 우리는모듈을이용해조작한다. 모듈을조작하려면우선어떻게신호를주면어떤동작을하는지확인해야하므로타이밍차트를분석해코드를구상해야한다. 캐릭터 LCD 타이밍차트 Write(LCD_CmdWrite, 커맨드명령에쓰임 ) Read(LCD_DataWrite, 데이터입력시쓰임 ) Read 시에는 Write 때와다름없지만 R/W 핀이특정시점에서무조건 H 가되어야한다. 기본적으로작동하는방법
PORT에서조작할핀네개를미리선언해둔다.(LCD_RS, LCD_RW, LCD_EN, LCD_BS) 여기서우선우리는 PORTC에조작용핀을, PORTD에데이터핀을사용하기로하였다. 그리고 Write 타이밍도를참고하여 LCD_CmdWrite함수를제작하고, 이를이용해 LCD를어떤방식으로쓸지세팅해주기위해 LCD_Init함수를만들어초기화함수도만들어준다. 초기화는모듈에서명령을제공해주며이는데이터시트를참조하도록한다. 명령어및설명 이름 Clear display Return home Entry mode set Display on/off control Cursor or display shift Function set Set CG RAM address Set DD RAM address Write data to CG or DD RAM Read data from CG of DD RAM 설명화면상의모든글자를없앰원시작점으로커서를되돌림커서가움직이는방향과쉬프트되는방법을정함디스플레이 / 커서 / 깜빡이를설정커서와디스플레이를옮김데이터길이 / 표시할줄수 / 캐릭터폰트를설정 CG RAM 주소를정해줌 DD RAM주소를정해줌데이터를 DD RAM이나 CG RAM에씀데이터를 DD RAM이나 CG RAM으로부터읽음 여기서우리는 Function set, Entry mode set, Cursor or display shift, Display, Clear display, Return Home 순으로명령을넣어주는것으로초기화를끝낼수있다. 이후 LCD_DataWrite 함수를이용하여글자를출력시켜확인할수있다. ASCII 코드 ( a' 등 ) 로입력하면, 메모리에서해당하는숫자의번지속폰트를화면에띄워주는방식이다. 커맨드라이트함수를이용해커서를움직인후, 글자를띄워마치글자가한자한자움직이는것처럼보여주는것도가능하다. 사용자제작폰트추가하기 현재우리가사용하는모델은일어 / 영어 / 숫자 / 일부기호만출력이가능하고한글출력이나다른기호의출력이불가능하다. 그래서모듈자체에있는폰트메모리에사용자가추가할수있는영역이존재하는데, 이곳에폰트의모양을숫자로변환하여추가할수있다. 폰트를추가하려면직접메모리에접근해야하는데, 이를제공하는명령어가 Set CG RAM address(0x40) 이다. 명령어를실행한직후 LCD_DataWrite함수를이용해쓸내용을전부입력해준다. 하지만폰트의구조에대해유의해야할점이있는데, 한글자입력할시한줄한줄씩만입력하여 8줄, 즉 8*5크기로쓴다는점이다. 위의그림과같이생긴폰트를추가하고싶다면, 0x04, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x04, 0x00, 0x04 순으로등록하면그림과같은폰트를화면상에서볼수있다. 그리고이를읽어 올때는 LCD_DataWrite(0x00) 식으로사용해폰트메모리속의해당번지글자를띄워준다.
3-2. 사용하는레지스터 이름 주소 페이지 설명 PORTn 스펙참조 스펙참조 PORT Register DDRn 스펙참조 스펙참조 Data Direction Register 3-3. Source code #ifndef LCD_H #define LCD_H lcd.h #define PORTD ( *( ( volatile unsigned char * )0x2B ) ) #define DDRD ( *( ( volatile unsigned char * )0x2A ) ) #define PIND ( *( ( volatile unsigned char * )0x29 ) ) #define PORTC ( *( ( volatile unsigned char * )0x28 ) ) #define DDRC ( *( ( volatile unsigned char * )0x27 ) ) #define PINC ( *( ( volatile unsigned char * )0x26 ) ) //Control LCD #define LCD_RS ( 1 << 7 ) //0x01000000 #define LCD_RW ( 1 << 6 ) //0x02000000 #define LCD_EN ( 1 << 5 ) //0x04000000 #define LCD_BS ( 0xFF ) //0x00ff0000 //LCD Instruction( 13p ) #define LCD_CLEAR 0x01 //0000 0001 #define LCD_HOME 0x02 //0000 001* #define LCD_ENTRY 0x06 //0000 01IS ( Moves cursor to Right ) #define LCD_DISPLAY_ON 0x0C //0000 1DCB ( Display ON, Cursor OFF, NONE Blink ) #define LCD_DISPLAY_OFF 0x08 //0000 1DCB ( Display OFF, Cursor OFF, NONE Blink ) #define LCD_CURSOR 0x1C //0001 SR** ( Display shift, Move to Right ) #define LCD_FUNC 0x38 //001DL NF** ( Data Length(bits), Lines, Font size ) #define LCD_SHIFT_RIGHT 0x1C //000111** ( Shift to Right ) #define LCD_DELAY 2500 #define LCD_DELAY1 (0) #define LCD_DELAY2 (0) #define LCD_DELAY3 (150) #define LCD_DELAY4 (50) //Functions void LCD_Init( void ); void LCD_InitUserFont( unsigned int _fontnum, unsigned int _typenum ); void LCD_CmdWrite( unsigned char ucdata );
void LCD_DataWrite( unsigned char ucdata ); void LCD_String( void * vp ); void LCD_Number( unsigned int _x, unsigned int _y, unsigned int _usnum ); void LCD_PrintUserFont(); void LCD_ShiftToRight( void ); unsigned int LCD_GetUserFontNumber( void ); #endif #include "lcd.h" lcd.c unsigned char g_userfont[][32] = 0x1F, 0x04, 0x0A, 0x11, 0x04, 0x04, 0x04, 0x1F, 0x0E, 0x11, 0x11, 0x0E, 0x00, 0x1F, 0xA, 0x0A, 0x1D, 0x09, 0x15, 0x15, 0x01, 0x08, 0x08, 0x0F, 0x00, 0x0A, 0x15, 0x00, 0x1F, 0x1F, 0x0E, 0x00, 0x1F, 0x05, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x18, 0x08, 0x1F, 0x01, 0x1F, 0x10, 0x1F, 0x1F, 0x05, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x18, 0x08, 0x1F, 0x01, 0x1F, 0x10, 0x1F ; unsigned int LCD_GetUserFontNumber( void ) return( sizeof( g_userfont )/32 ); void LCD_Init( void ) DDRC = LCD_RS LCD_RW LCD_EN; DDRD = 0xFF; LCD_CmdWrite( LCD_FUNC ); LCD_CmdWrite( LCD_ENTRY ); LCD_CmdWrite( LCD_CURSOR ); LCD_CmdWrite( LCD_DISPLAY_ON ); LCD_CmdWrite( LCD_CLEAR ); LCD_CmdWrite( LCD_HOME ); return; void LCD_InitUserFont( unsigned int _fontnum, unsigned int _typenum ) volatile int i;
LCD_CmdWrite( 0x40 ); for( i=0; i<32; i++ ) if( _typenum == 1 ) LCD_DataWrite( g_userfont[_fontnum][i] ^ 0xFF ); else LCD_DataWrite( g_userfont[_fontnum][i] ); void LCD_PrintUserFont( unsigned int _x, unsigned int _y ) LCD_CmdWrite( LCD_CLEAR ); LCD_CmdWrite( 0x80+(0x40*_y)+_x ); //LCD_CmdWrite( 0x80 ); LCD_DataWrite( 0x00 ); LCD_DataWrite( 0x01 ); LCD_DataWrite( 0x02 ); LCD_DataWrite( 0x03 ); void LCD_CmdWrite( unsigned char ucdata ) volatile unsigned int icnt; // MUST READ TIMING DIAGRAM PORTC = 0x00; HIGH for( icnt=0; icnt<lcd_delay - LCD_DELAY1; icnt++ ); //Need delay before LCD_EN be PORTC = PORTC LCD_EN; //Enable LCD. Bus. for( icnt=0; icnt<lcd_delay-lcd_delay2; icnt++ ); //Delay before controling Data //PORTD = 0x00; //Clear all Data Buses PORTD = ucdata; //Set data in data buses buses. for( icnt=0; icnt<lcd_delay-lcd_delay3; icnt++ ); //Keeping signal of data PORTC = PORTC & ( ~LCD_EN );
buses. for( icnt=0; icnt<lcd_delay-lcd_delay4; icnt++ ); //Keeping signal of data void LCD_DataWrite( unsigned char ucdata ) volatile unsigned int icnt; // MUST READ TIMING DIAGRAM PORTC = LCD_RS; HIGH for( icnt=0; icnt<lcd_delay-lcd_delay1; icnt++ ); //Need delay before LCD_EN be PORTC = PORTC LCD_EN; //Enable LCD. Bus. for( icnt=0; icnt<lcd_delay-lcd_delay2; icnt++ ); //Delay before controling Data //PORTD = 0x00; //Clear all Data Buses PORTD = ucdata; //Set data in data buses buses. for( icnt=0; icnt<lcd_delay-lcd_delay3; icnt++ ); //Keeping signal of data PORTC = PORTC & (~LCD_EN); buses. for( icnt=0; icnt<lcd_delay-lcd_delay4; icnt++ ); //Keeping signal of data void LCD_String( void * vp ) while( 1 ) if( *( ( unsigned char * )vp )!= '\0' ) LCD_DataWrite( *( ( unsigned char * )vp ) ); vp = ( unsigned char * )vp + 1; else
void LCD_Number( unsigned int _x, unsigned int _y, unsigned int _usnum ) volatile int num; volatile int i; unsigned char m_ucstring[] = "00000"; for( num=10000, i=0; num>=1, i<5; num=num/10, i++ ) m_ucstring[i] = '0' + _usnum/num; _usnum = _usnum % num; LCD_CmdWrite( 0x80+(0x40*_y)+_x ); LCD_String( m_ucstring ); void LCD_ShiftToRight( void ) volatile int i; volatile int j; for( i=0; i<2; i++ ) for( j=0;j<16; j++ ) LCD_PrintUserFont( j, i ); MS_Delay(500); return; 3-4. 실습사진
4. 타이머카운터및초음파센서활용 (PORT, TIMER, EXT-INT) 사용기능 조작물품 외부인터럽트, 타이머카운터, 포트 캐릭터 LCD, 초음파센서 (SRF05) 5-1. 개요 초음파는인간의가청영역을벗어난고음파인데, 이를여러방향으로쏘아되돌아오는시간을측정하여거리를재는것이초음파센서이다. 초음파센서는액체에서높은효율을발생하여의료기기나돌고래등이사용한다. 일반적으로는기기의한계로인해 2~3m정도만측정이가능하다. 같은센서종류임에도불구하고조도센서나온도센서와는동작하는원리에차이가있다. 조도센서 / 온도센서는축차비교를하여센서값을읽어오는반면에초음파센서는메아리핀의상승에지가얼마나오래 HIGH상태로있는지를체크하고, 이를계산하여거리를측정한다. 음파는 1초당 340m씩갈수있으므로이를나누어보면약 0.00029초당 1cm 나아감을알수있다. 그리고초음파는왕복해야하므로, 이를이용해타이머카운터로약 0.00058 초마다카운터를올려거리를잴수있다. 이원리는어떤모듈이든똑같이적용된다. SRF05 초음파센서타이밍도 작동하는방법 초음파센서는사용자가트리거펄스를주는부분과, 에코펄스가들어오기전설정해야할것, 인터럽트, 타이머카운터등설정해주어야할것들이다소많이있다. 우선타이머를설정 (ULT_TimerInit) 해준다. TCCRnA/B를이용해분주비를 8로만들어준다. 이후트리거핀을설정 (TRG_Init) 한다. 해당핀의출력을활성화시켜준후트리거핀을 LOW상태로만들어준다. 그리고에코핀의인터럽트를설정 (Echo_Init) 해준다. 에코핀의 Pulse는모듈이만들어주므로외부인터럽트이다. Run함수에서상승에지를찾은후시간을재다하강에지를발견했을때인터럽트가걸려재는것을멈추어야하므로, 하강에지에반응하게한다. 에코핀은 6 번핀 (INT4) 에연결되어있으므로 INT4의인터럽트를사용하게한다. 실제로초음파센서를동작시킨다 (ULT_Run). 우선기기를작동시켜야하므로트리거핀이
시작신호를보내는 TRG_Pulse 함수를호출한다. 해당함수는일정 Cycle 만큼핀을 High상태로만들어준다. 그리고에코핀의입력을활성화해주고, 해당핀이 High상태가될때까지대기한다. 그리고외부인터럽트마스크레지스터 (EIMSK) 를이용하여타이머를 Overflow 방식 ( 최댓값은항상정해져있고, 해당값에도달하면인터럽트가걸린다 ) 으로설정한다. 그리고앞에서사용한 sei함수혹은 SREG를이용하여글로벌인터럽트를켜주도록한다. 그러면그순간부터타이머카운터가오버플로우될때마다인터럽트가걸려해당함수 ( vector_23) 이실행되고, 거리를재기시작할것이다. 그리고에코핀이하강에지를만나면그에맞는인터럽트함수 ( vector_5) 가실행되어재는것을멈추고그때까지세었던값을저장한다. 계속값을세면안되므로다시초기화하고, 전체인터럽트를비활성화해준다. 이후저장한값을문자열로변환시켜반환하여 LCD나하이퍼터미널에출력할수있다. 5-2. 사용하는레지스터 이름 주소 페이지 설명 EICRA 0x69 78 External Interrupt Control Register A EICRB 0x6A 79 External Interrupt Control Register B EIMSK 0x3D 79 External Interrupt Mask Register EIFR 0x3C 80 External Interrupt Flag Register TCCR0A 0x44 130 Timer/Counter Control Register A TCCR0B 0x45 133 Timer/Counter Control Register B TCNT0 0x46 134 Timer/Counter Register OCR0A 0x47 134 Output Compare Register TIMSK0 0x6E 135 Timer/Counter Interrupt Mask Register PORTn 스펙참조 스펙참조 PORT Register DDRn 스펙참조 스펙참조 Data Direction Register PINn 스펙참조 스펙참조 PORT Input Register 해당레지스터에관한상세정보는표에적힌페이지를데이터시트에서참고하기바람. #ifndef ULTRA_H #define ULTRA_H 5-3. Source code ultra.h #define MCK 16000000 #define PRESCALE 64 #define TICK_START ( 255 - ( MCK / PRESCALE / 1000 ) ) #define SREG ( *( ( volatile unsigned char * )0x5F ) ) #define TCCR ( ( ( volatile unsigned int * )0x44 ) ) #define TCCR0A ( *( ( volatile unsigned char * )0x44 ) ) #define TCCR0B ( *( ( volatile unsigned char * )0x45 ) ) #define TCNT0 ( *( ( volatile unsigned char * )0x46 ) ) #define TIMSK0 ( *( ( volatile unsigned char * )0x6E ) ) #define WGM00 0 #define WGM01 1 #define COM0B0 4
#define COM0B1 5 #define COM0A0 6 #define COM0A1 7 #define WGM20 0 #define WGM21 1 #define COM2B0 4 #define COM2B1 5 #define COM2A0 6 #define COM2A1 7 #define CS00 0 #define CS01 1 #define CS02 2 #define WGM02 3 #define FOC0B 6 #define FOC0A 7 #define TOIE0 0 #define OCIE0A 1 #define OCIE0B 2 #define I 7 #define T 6 #define H 5 #define S 4 #define V 3 #define N 2 #define Z 1 #define C 0 #define EICRA ( *( ( volatile unsigned int * )0x69 ) ) // 외부인터럽트제 어레지스터. #define ISC00 0 #define ISC01 1 #define ISC10 2 #define ISC11 3 #define ISC20 4 #define ISC21 5 #define ISC30 6 #define ISC31 7 #define EICRB ( *( ( volatile unsigned int * )0x6A ) ) #define ISC40 0 #define ISC41 1 #define ISC50 2 #define ISC51 3 #define ISC60 4
#define ISC61 5 #define ISC70 6 #define ISC71 7 #define EIMSK ( *( ( volatile unsigned int * )0x3D ) ) #define INT0 0 #define INT1 1 #define INT2 2 #define INT3 3 #define INT4 4 #define INT5 5 #define INT6 6 #define INT7 7 #define EIFR ( *( ( volatile unsigned int * )0x3C ) ) #define IINTF0 0 #define IINTF1 1 #define IINTF2 2 #define IINTF3 3 #define IINTF4 4 #define IINTF5 5 #define IINTF6 6 #define IINTF7 7 #define PORTA ( *( ( volatile unsigned char * )0x22 ) ) #define DDRA ( *( ( volatile unsigned char * )0x21 ) ) #define PINA ( *( ( volatile unsigned char * )0x20 ) ) #define PORTE ( *( ( volatile unsigned char * )0x2E ) ) #define DDRE ( *( ( volatile unsigned char * )0x2D ) ) #define PINE ( *( ( volatile unsigned char * )0x2C ) ) #define OCR0A ( *( ( volatile unsigned char * )0x47 ) ) #define TRG_PIN 1 #define ECHO_PIN 4 void vector_5( void ) attribute ( ( signal, used, externally_visible )); void vector_23( void ) attribute ( ( signal, used, externally_visible )); #define sei() asm volatile ("sei" ::) // C언어내부에강제로어셈블리코드 를삽입하는것. #define cli() asm volatile ("cli" ::) // C언어내부에강제로어셈블리코드 를삽입하는것. #define sleep() asm volatile ( "sleep" "\n\t" :: ) // CPU를최소한의작동만시 키는것. 슬립모드. 대기전력을줄인다 // 인라인어셈블리. 전처리나컴파일과정에서무시한다. GCC어셈블리문법이다. sei는아트메 가에만존재하는니모닉. unsigned char * ULT_Run();
void ULT_Init(); void Echo_Init(); void ULT_TimerInit(); void TRG_Init(); void TRG_Pulse(); #endif #include "ultra.h" ultra.c static volatile unsigned int uitick; static volatile unsigned int uidistance; static volatile unsigned int uistate; // 1Tick(375Clock) // 계산된거리 // 초음파거리를재기위한상태변수 unsigned char * ULT_Run() static unsigned char ucdistance[] = "---cm"; // 초기값 uistate = 1; uidistance = 0; uitick = 0; TRG_Pulse(); // 초기화 // 트리거펄스신호를주어작동시작 DDRE = 0x00; while( 0 == ( (PINE) & ( 1 << ECHO_PIN )) ); TIMSK0 = ( 0 << OCIE0B ) ( 0 << OCIE0A ) ( 1 << TOIE0 ); sei(); // 전체인터럽트를설정해준다. 므로, // ECHO_PIN 은초음파가보내진순간부터상승되어되돌아올때까지그상태가유지되 다. // 상승에지가발견되었으면, 타이머클록을켜고소요시간을재기위해타이머를켠 // Check NEGATIVE_EDGE in Echo pulse. uistate = 1; while( 1 == uistate ); ULT_TimerInit(); Echo_Init(); // Stop Interrupt // 인터럽트설정 cli(); // 숫자를그대로띄울수없으므로, 글자로변환한다. ucdistance[0] = '0' + (uidistance %1000)/100; ucdistance[1] = '0' + (uidistance %100)/10; ucdistance[2] = '0' + (uidistance %10)/1;
//LCD_Number( 0,0, uidistance ); return ucdistance; void ULT_Init() ULT_TimerInit(); TRG_Init(); Echo_Init(); return; void Echo_Init() EICRB = EICRB ( 0 << ISC40 ) ( 1 << ISC41 ); // 외부인터럽트는설정시 2 개의비트를쓰는데장치개수가레지스터 1개로는모지라므로 A와 B로구분한다. // 여기서우리는 4번장치를이용할것이므로 B로설정하며, 0/0이면하강에지를반응한다. EIMSK = EIMSK ( 1 << INT4 ); // INT4의인터럽트를켜준다. return; void vector_5( void ) // 인터럽트가발생했을때불러와지는함수. uidistance = uitick; // 현재 Tick을거리에저장한다.( 1 Tick = 1 cm ) uistate = 0; return; void ULT_TimerInit() TCCR0A = ( 0 << WGM00 ) ( 0 << WGM01 ) ( 0 << COM0B0 ) ( 0 << COM0B1 ) ( 0 << COM0A0 ) ( 0 << COM0A1 ); TCCR0B = ( 0 << WGM20 ) ( 0 << WGM21 ) ( 0 << COM2B0 ) ( 0 << CS02 ) ( 1 << CS01) ( 0 << CS00 ); // Count from 5 //TCNT0 = TICK_START; return; void vector_23( void )
++uitick; // 1 Centimeter per 375 clock //DBGU_SendString( "Counting!\r\n" ); return; void TRG_Init() DDRA = 0xFF; // Trigger output enable PORTA = PORTA & ~( 1 << TRG_PIN ); // 사용하기전 LOW 상태여야한다. return; void TRG_Pulse() // 트리거펄스를생성한다. volatile unsigned int uicount; PORTA = PORTA ( 1 << TRG_PIN ); for( uicount=0; uicount<10000; uicount++ ); // 48Cycle 이상대기 PORTA = PORTA & ~( 1 << TRG_PIN );
5-4. 실습사진
6. 시리얼통신및센서와 EEPROM 이용 (UART, ADC, EEPROM) 사용기능 조작물품 USART, ADC, EEPROM 조도센서, 온도센서, 하이퍼터미널 6-1-1. USART 개요 직렬 (Serial) 통신은컴퓨터와보드를 USB로연결하고하이퍼터미널등으로통신하는것등을말한다. ATmega2560에는 4개의 USART 직렬통신포트를갖고있다. 우리는이중 0번을이용해통신을해볼것이다. 작동하는방법 제일먼저통신을초기화및설정부터해보도록하겠다. 우선전송속도 (Baud Rate) 를계 산해 UBRR0H/L 에대입한다. H 와 L 로레지스터가나뉘어져있는이유는값의크기가레지 스터한개보다클수있기때문이다. 전송속도의계산공식은 이고, MCK 는 16000000(16Mhz), BAUD 는 115200 이다. 단이공식을이용해전송속도를구할때, 간 혹소수점계산의이상으로 8 에근접한값이나오더라도소수점은버리기때문에 7 이나와 결과가이상한경우가있다. 이를대비하기위해값을정확히체크하는것이좋을것이다. USART 의제어및상태레지스터 (UCSR) 도 UBRR 레지스터처럼 H 와 L 와유사한 A/B/C 로나 뉘어져있다. 우선우리는송수신동작제어및상태저장을담당하는 A 에서는모든기능을 끌것이므로 0 을입력해준다. 그리고송수신동작제어나 9 비트에관련된값을설정하는 레지스터인 B 에서는송 / 수신허용비트와, 필요에따라송 / 수신인터럽트를허용해주도록 한다. 마지막으로송수신동작을제어하는 C 에선 8 비트전송, 짝수패리티코드사용, 비동 기모드로설정으로맞춰주도록한다. 그리고글로벌인터럽트레지스터 (SREG) 에서인터럽 트를활성화시켜장치가인터럽트를사용할수있도록한다. 그런데 ATmega2560 에서는 이동작만을수행하는어셈블리명령어 sei 가있는데, 이를인라인매크로함수를이용하여 사용하거나 SREG 를조작하는방법을이용한다.( 반대로끄는것은 난다. cli) 이로써초기화는끝 그리고글자를한번띄워보도록하자.(USART_SendChar) 해당함수는인자로받은글자값 을송신한다. 내용은매우심플한데, 상태를저장하는 UCSR0A 의비트중송신준비완료 비트의값을체크하여준비가완료될때까지대기하다가 USART 데이터레지스터에값을 집어넣으면송신이된다. 이를응용하면문자열도출력이가능하다. 수신은인터럽트와폴링두가지방식을이용할수있다. 우선폴링방식 (USART_RecvChar) 은송신때처럼 UCSR0A 에서수신준비비트가 1 로세팅될때까지대기한후, UDR0 의 값을처리하는방식을취하면된다. 단, UDR 레지스터는값을읽으면내용이사라지므로유 의. 그리고인터럽트방식은기본적으로 ATmega2560 의특성상 ARM 과달리함수가이미 만들어져있고번호만맞추면알아서실행이된다. 벡터의번호는스펙에나와있다 (1 씩밀 려적혀있으므로참고하기바란다 ) USART0 의경우벡터번호가 25 이므로, vector_25 라는 함수를만들면인터럽트시자동으로실행이된다. 인터럽트방식은이미데이터가들어온 상태이기때문에굳이 while 문을이용해대기할필요가없이바로 UDR0 의값을처리하면 된다.
6-1-2. A/D Converter 개요 A/D Converter는아날로그값을디지털로변환시키는장치이다. 분해능만큼전압을쪼개어조금씩값을올려비교하는것 ( 축차비교법 ) 으로, 주로센서의값을읽어오기위해쓰인다. 이방법은어느정도값의손실을유발하나, 아날로그값을디지털로처리할수있다는장점이있다. 작동하는방법 ARM과달리초기화없이바로변환하면된다. 우선우리는기준전압은보드에서나온전압을이용할것이므로외부의 AVCC 단자로입력된전압을사용하도록설정해준다. 그리고같은레지스터에서사용할채널을설정한다. 그러나 ADMUX레지스터는 0~4까지만설정이가능하므로 5번채널은 ADCSRB에있다. 5를선택했을경우이곳에값이들어가도록해준다. 이후 A/D컨버터제어및상태레지스터 A를이용해기능활성화및분주비를 64로만들어준다. 그리고해당레지스터를이용해변환을시작토록한다. 변환을하는데에는어느정도시간이소모되므로변환완료비트가 1이될때까지대기하도록한다. 변환이끝났으면 ADCH/L을반환하여해당값을처리하도록한다.( 둘다합하여 16비트정수형이므로통째로 int로선언하여사용해도된다 ) 여기서우리는조도센서를 0번채널에, 온도센서를 1번채널을사용하도록하였다. 6-1-3. EEPROM 개요 EEPROM은전자적으로수정이가능한 ROM을말한다. ROM의한종류이므로비휘발성을띄며, 전원을껐다켜도값이그대로유지되어있다. ATmega2560에서는총 4KB 용량의 EEPROM이제공되며, 게임기의유저정보저장장치등에활용된다. 작동하는방법 ATmega2560은하버드구조를이용하고있다. 때문에 EEPROM은 SRAM과완전히분리되어있어직접적으로접근할방법은없다 ( 프로그래머는포인터로레지스터를접근해기기를동작시키는데, 애초에 SRAM에없으므로 ). 때문에간접적인방법을활용하는데, 접근하고자하는 EEPROM의번지 (0x0000~0x0FFF) 를입력한후 EEPROM 데이터레지스터를접근하여마치해당번지의 EEPROM을직접제어하는듯이사용한다. 우선글자한자를입력해보도록하자.(EEPROM_WriteChar) 우선이함수는반복적으로사용될것이므로앞의작업이다처리될때까지대기한다. 그러므로제어레지스터 (EECR) 의 EEPE레지스터가 1이될때까지기다리고, EEPROM 주소레지스터 (EEAR) 에접근번지를입력하여이동시키고, EEPROM 데이터레지스터 (EEDR) 에값을넣어해당번지에원하는값이입력된다. 라이트과정에서인터럽트가걸리면오류가날수있으므로글로벌인터럽트를꺼주도록하고, EEMPE를 1로만들어라이트가가능하도록한다. 그리고 EEPE는 4 사이클이내에 1이되어야제대로 EEPROM에들어온값을라이트하게된다. 라이트동작이수행된이후에는 EEPE가자동으로 0이된다. 읽어오는경우 (EEPROM_ReadChar) 도쓸때와똑같이앞의동작이끝날때까지대기하고, EEPROM 주소레지스터 (EEAR) 에접근을원하는번지를입력한후, 제어레지스터 (EECR) 에 Read한다는신호 (EERE) 를주고 EEPROM 데이터레지스터의값을처리하는것으로읽어오는루틴이끝난다. 6-2-1. 사용하는레지스터 (USART)
이름 주소 페이지 설명 PINA 0x20 115 Port A Input Pins Address DDRA 0x21 115 Port A Data Direction Register PORTA 0x22 115 Port A Data Register UCSR0A 0xC0 227 USART MSPIM Control and Status Register 0 A UCSR0B 0xC1 243 USART MSPIM Control and Status Register 0 B UCSR0C 0xC2 229 USART MSPIM Control and Status Register 1 C UBRR0L 0xC4 231 USART Baud Rate Registers L UBRR0H 0xC5 231 USART Baud Rate Registers H UDR0 0xC6 242 USART MSPIM I/O Data Register 해당레지스터에관한상세정보는표에적힌페이지를데이터시트에서참고하기바람. 6-2-2. 사용하는레지스터 (A/D Converter) 이름 주소 페이지 설명 ADCSRA 0x7A 297 ADC Control and Status Register A ADCSRB 0x7B 299 ADC Control and Status Register B ADMUX 0x7C 294 ADC Multiplexer Selection Register ADC 0x78 298 ADC Data Register DIDR0 0x7E 300 Digital Input Disable Register 0 DIDR2 0x7D 300 Digital Input Disable Register 2 해당레지스터에관한상세정보는표에적힌페이지를데이터시트에서참고하기바람. 6-2-3. 사용하는레지스터 (EEPROM) 이름 주소 페이지 설명 EEAR 0x41 32 EEPROM Address Register EEDR 0x40 32 EEPROM Data Register EECR 0x3F 32 EEPROM Control Register SPMCSR 0x57 340 Store Program Memory Control and Status Register SREG 0x5F 12 AVR Status Register 해당레지스터에관한상세정보는표에적힌페이지를데이터시트에서참고하기바람. #ifndef USART_H #define USART_H 6-3. Source code usart.h #define sei() asm volatile ("sei" ::) // C언어내부에강제로어셈블리코드 를삽입하는것. #define sleep() asm volatile ( "sleep" "\n\t" :: ) // CPU를최소한의작동만시 키는것. 슬립모드. 대기전력을줄인다 // 인라인어셈블리. 전처리나컴파일과정에서무시한다. GCC어셈블리문법이다. sei는아트메 가에만존재하는니모닉. #define MCK (16L * 1000 * 1000 ) // 메인클럭
#define BAUD ( 1152L * 100 ) #define UBRR ( ( MCK / ( BAUD * 16L ) ) - 1 ) // 전송속도공식 // UBRR 은 H 와 L 로두개의레지스터를사용한다.( 크기가크므로 ) #define UBRRH ( UBRR >> 8 ) #define UBRRL ( UBRR & 0xFF ) #define UDR0 ( *( ( volatile unsigned char * )0xC6 ) ) #define UCSR0A ( *( ( volatile unsigned char * )0xC0 ) ) #define UCSR0B ( *( ( volatile unsigned char * )0xC1 ) ) #define UCSR0C ( *( ( volatile unsigned char * )0xC2 ) ) #define UBRR0H ( *( ( volatile unsigned char * )0xC5 ) ) #define UBRR0L ( *( ( volatile unsigned char * )0xC4 ) ) #define PORTA ( *( ( volatile unsigned char * )0x22 ) ) #define DDRA ( *( ( volatile unsigned char * )0x21 ) ) #define PINA ( *( ( volatile unsigned char * )0x20 ) ) // UCSR0A #define MPCM 0 #define U2X 1 #define UPE 2 #define DOR 3 #define FE 4 #define UDRE 5 #define TXC 6 #define RXC 7 // UCSR0B #define TXB8 0 #define RXB8 1 #define UCSZ2 2 #define TXEN 3 #define RXEN 4 #define UDRIE 5 #define TXCIE 6 #define RXCIE 7 // UCSR0C #define UCPOL 0 #define UCSZ0 1 #define UCSZ1 2 #define USBS 3 #define UPM0 4 #define UPM1 5 #define UMSEL0 6 #define UMSEL1 7 void USART_Init(void); void vector_25( void ) attribute ( ( signal, used, externally_visible ));
void LED_Init( void ); void USART_String( void *_vp ); void USART_SendChar( unsigned char _ucchar ); unsigned char USART_RecvChar( void ); #endif #include "usart.h" usart.c void USART_Init( void ) LED_Init(); // BAUD Rate를 UBBR0레지스터에넣는다.( 크기가크므로 H와 L로분리한다.) UBRR0H = 0;//UBRRH; UBRR0L = 8;//UBRRL; // 소수점문제로인해속도가뒤틀림. // 모든기능을끈다. UCSR0A = ( 0 << MPCM ) ( 0 << U2X ) ( 0 << UPE ) ( 0 << DOR ) ( 0 << FE ) ( 0 << UDRE ) ( 0 << TXC ) ( 0 << RXC ); // 수신허용, 송신허용, 수신완료인터럽트허용 UCSR0B = ( 0 << TXB8 ) ( 0 << RXB8 ) ( 0 << UCSZ2 ) ( 1 << TXEN ) ( 1 << RXEN ) ( 0 << UDRIE ) ( 0 << TXCIE ) ( 0 << RXCIE ); // 8bit 전송, 짝수패리티, 비동기방식 UCSR0C = ( 0 << UCPOL ) ( 1 << UCSZ0 ) ( 1 << UCSZ1 ) ( 0 << USBS ) ( 0 << UPM0 ) ( 1 << UPM1 ) ( 0 << UMSEL0 ) ( 0 << UMSEL1 ); sei(); // 인라인어셈블리를이용한매크로함수, 글로벌인터럽트켜기.(SREG=(SREG) (1<<7)) 와같다. UDR0 = 0; return; // Timer0 Handler void vector_25( void ) //void vector23( void ) attribute ( ( signal, used, externally_visible )) //while( 0 == ( UCSR0A & ( 1 << RXC ) ) ); // UCSR0A의 RXC비트는읽혀지지않은문자가있을시 1이된다. // 어차피인터럽트가걸리려면무언가입력되어있어야하므로이함수내에서는효력이없다. 하지만인터럽트방법이아니라면입력이되었는지알아야하므로이부분이사용된다. static unsigned char ucdata = 0; ucdata = UDR0; //ucdata = ucdata - 64; //LCD_DataWrite( ucdata );
//while( 1 ); switch( ucdata ) // UDR0에있는데이터의내용을검사한다. case '1': PORTA = 0xFE; // 1111 1110 case '2': PORTA = 0xFC; // 1111 1100 case '3': PORTA = 0xF8; // 1111 1000 case '4': PORTA = 0xF0; // 1111 0000 case '5': PORTA = 0xE0; // 1110 0000 case '6': PORTA = 0xC0; // 1100 000 case '7': PORTA = 0x80; // 1000 0000 case '8': PORTA = 0x00; // 0000 0000 default: PORTA = 0xFF; // 1111 1111 return; unsigned char USART_RecvChar( void ) static unsigned char ucselect; while( 0 == ( UCSR0A & ( 1 << RXC ) ) ); // UCSR0A의 RXC비트는읽혀지지않은문자가있을시 1이된다. // 어차피인터럽트가걸리려면무언가입력되어있어야하므로이함수내에서는효력이없다. ucselect = UDR0; return( ucselect ); void USART_SendChar( unsigned char _ucchar )
while( (UCSR0A & ( 1 << UDRE )) == 0 ); 까지대기한다. UDR0 = _ucchar; 력한다. // 받을준비가되어있을때 // 값을입 void USART_String( void *_vp ) // 한글자씩전송한다. while( 1 ) if( (*( unsigned char * )_vp) == 0 ) USART_SendChar( *(unsigned char * )_vp ); (unsigned char * )_vp++; void LED_Init( void ) DDRA = 0xFF; PORTA = 0xFF; return; #ifndef ADC_H #define ADC_H adc.h #define ADCSRA ( *( ( volatile unsigned char * )0x7A ) ) #define ADPS0 0 #define ADPS1 1 #define ADPS2 2 #define ADIE 3 #define ADIF 4 #define ADATE 5 #define ADSC 6 #define ADEN 7 #define ADCSRB ( *( ( volatile unsigned char * )0x7B ) ) #define ADTS0 0 #define ADTS1 1 #define ADTS2 2 #define MUX5 3 #define ACME 6 #define ADMUX ( *( ( volatile unsigned char * )0x7C ) ) #define MUX0 0
#define MUX1 1 #define MUX2 2 #define MUX3 3 #define MUX4 4 #define ADLAR 5 #define REFS0 6 #define REFS1 7 #define ADCH ( *( ( volatile unsigned char * )0x79 ) ) #define ADCL ( *( ( volatile unsigned char * )0x78 ) ) #define ADC ( *( ( volatile unsigned int * )0x78 ) ) // ATmega2560에서 int는 16비트이므로, 각각 8비트인 H/L레지스터를한번에쓸수있게된다. // 단, Little Endian을쓴다는전제하에작동가능.( 거꾸로저장되므로 ) #define DIDR0 ( *( ( volatile unsigned char * )0x7E ) ) #define ADC0D 0 #define ADC1D 1 #define ADC2D 2 #define ADC3D 3 #define ADC4D 4 #define ADC5D 5 #define ADC6D 6 #define ADC7D 7 #define DIDR2 ( *( ( volatile unsigned char * )0x7D ) ) #define ADC8D 0 #define ADC9D 1 #define ADC10D 2 #define ADC11D 3 #define ADC12D 4 #define ADC13D 5 #define ADC14D 6 #define ADC15D 7 static unsigned int ADC_Convert( unsigned int uich ); unsigned int SENSOR_Light( void ); unsigned int SENSOR_Temp( void ); #endif #include "adc.h" adc.c static unsigned int ADC_Convert( unsigned int uich ) ADMUX = ( 0 << ADLAR ) ( 1 << REFS0 ) ( 0 << REFS1 ) ( uich & 0x1F );// 기준전압 ADCSRB = ( ( uich & 0x20 ) >> 5 ) << MUX5; // MUX5만사용한다. // MUX는총 0~5번까지있는데, 0~4까지는 ADMUX에있고, 5는 ADCSRB에있으므로같이처리한다.
// 채널설정, ADMUX 레지스터사용 ADCSRA = ( 1 << ADEN ) ( 1 << ADPS2 ) ( 1 << ADPS1 ) ( 0 << ADPS0 ); // ADC 기능활성화, ADC 분주비 64 사용, ADCSRA 레지스터사용 ADCSRA = ( ADCSRA ) ( 1 << ADSC ); // ADC Start Conversion : ADCSRA 레지스터사용 while( 0 == ( ADCSRA & ( 1 << ADIF ) ) ); //Conversion 완료까지대기, while 문사용, ADCSRA 레지스터사용 return ADC; // ADCH, ADCL 를한번에리턴할수있는 ADC 를 define 해놓을것!! unsigned int SENSOR_Light( void ) return( ADC_Convert( 0 ) ); unsigned int SENSOR_Temp( void ) return( ADC_Convert( 1 ) ); #ifndef EEPROM_H #define EEPROM_H eeprom.h #define EEAR ( *( ( volatile unsigned int * )0x41 ) ) //Little Endian //#define EEARH ( *( ( volatile unsigned char * )0x42 ) ) //#define EEARL ( *( ( volatile unsigned char * )0x41 ) ) #define EEDR ( *( ( volatile unsigned char * )0x40 ) ) // EEPROM Data Register #define EECR ( *( ( volatile unsigned char * )0x3F ) ) // EEPROM Control Register #define EERE 0 #define EEPE 1 #define EEMPE 2 #define EERIE 3 #define EEPM0 4 #define EEPM1 5 #define SPMCSR ( *( ( volatile unsigned char * )0x57 ) ) #define SPMEN 0 #define SREG ( *( ( volatile unsigned char * )0x5F ) ) void EEPROM_WriteChar( unsigned int uiaddress, unsigned char ucdata ); unsigned char EEPROM_ReadChar( unsigned int uiaddress );
void EEPROM_WriteString( void * _string ); char *EEPROM_ReadString( void ); #endif #include "eeprom.h" eeprom.c void EEPROM_WriteChar( unsigned int uiaddress, unsigned char ucdata ) while( 0!= EECR & ( 1 << EEPE ) ); // 앞의동작이다끝났을때까지기다린다. while( 0!= SPMCSR & ( 1 << SPMEN ) ); EEAR = uiaddress; // 접근할 EEPROM 번지를등록 EEDR = ucdata; // 데이터레지스터에넣을값을입력 SREG = SREG & ~( 1 << 7 ); // 인터럽트가활성화되어있으면작동에문제가생길수있다. EECR = EECR (( 1 << EEMPE ) ( 0 << EEPE )); // 라이트가가능하도록허용. EECR = EECR ( 1 << EEPE ); // 4 사이클내에 1로설정해야한다. return; unsigned char EEPROM_ReadChar( unsigned int uiaddress ) while( 0!= EECR & ( 1 << EEPE ) ); // 앞의동작이다끝났을때까지기다린다. EEAR = uiaddress; // 접근할 EEPROM 번지를등록 EECR = EECR ( 1 << EERE ); // 리드하기위한신호를준다 return( EEDR ); // EEPROM을접근하여해당데이터를반환한다. void EEPROM_WriteString( void * _string ) volatile int i; while( 0!= EECR & ( 1 << EEPE ) ); // 앞의동작이다끝났을때까지기다린다. while( 0!= SPMCSR & ( 1 << SPMEN ) ); for( i=0x00; i<0xfff; i=i+0x08 ) EEAR = i; if( i<sizeof(_string)-1 ) EEDR = (unsigned char*)_string+i; EEDR = 0x00; EECR = EECR (( 1 << EEMPE ) ( 0 << EEPE )); EECR = EECR ( 1 << EEPE ); // 4 서아쿨내에 1 로설정해야한다.
char *EEPROM_ReadString( void ) volatile int i; char m_string[20]; for( i=0x00; i<0xfff; i=i+0x08 ) while( 0!= EECR & ( 1 << EEPE ) ); // 앞의동작이다끝났을때까지기다린다. EEAR = i; EECR = EECR ( 1 << EERE ); if( EEDR!= 0 ) m_string[i] = EEDR; LCD_DataWrite(m_string[i]); else return m_string; while(1) main.c 일부 USART_String("\f \n\r"); 통신프로그램 조유진 USART_String(" \n\r"); 1 [EEPROM]0xFFF Write 2 [EEPROM]0xFFF Read 3 [ADC] 조도센서 4 [ADC] 온도센서 USART_String(" \n\r" ); switch( USART_RecvChar() ) case '1': /* for( i=0; i<lcd_getuserfontnumber(); i++ ) for( j=0; j<2;j++ )
\n\r"); \n\r"); 다. 다. \n\r" ); LCD_InitUserFont(i,j); LCD_ShiftToRight(); */ USART_String("\f 통신프로그램 조유진 USART_String(" EEPROM의 0xFFF번지에데이터를입력합니 데이터를한자입력해주시기바랍니 USART_String(" EEPROM_WriteChar( 0xFFF, USART_RecvChar() ); \n\r"); \n\r"); 다. case '2': USART_String("\f 통신프로그램 조유진 USART_String(" EEPROM의 0xFFF번지의데이터를읽어옵니
\n\r" ); 읽어온값은,"); USART_SendChar( EEPROM_ReadChar( 0xFFF ) ); USART_String( " 입니다. USART_String(" \n\r"); \n\r"); \n\r" ); case '3': USART_String("\f 통신프로그램 조유진 USART_String(" 현재의조도값을읽어옵니다. 읽어온값은,"); USART_SendString( ConvertToString( SENSOR_Light() ) ); USART_String( " 입니다. USART_String(" case '4':
\n\r"); \n\r"); \n\r" ); USART_String("\f 통신프로그램 조유진 USART_String(" 현재의온도값을읽어옵니다. 읽어온값은,"); USART_SendChar( ConvertToString( SENSOR_Temp() ) ); USART_String( " 입니다. USART_String(" default: USART_String( "Error!" ); USART_RecvChar();
6-4. 실습사진