/*
* #키워드
* #Clock #PORT #Timer/Counter
*
* 1장 CPU 클럭 설정과 인터럽트 Driven 방식
*
* 기본적으로 임베디드 시스템은 폴링 방식이 아닌 인터럽트 방식으로 코드를 작성해야 한다.
* 폴링 방식과 인터럽트 방식이란 무엇일까?
* 폴링 방식이란 어떤 동작을 수행할 때 10초의 시간이 필요하면 10초 동안 기다렸다가 (CPU가 멈춤) 동작하는 방식이다.
* 하지만 이런 폴링 방식은 한가지 동작 중에 CPU를 멈추기 때문에 다른 동작에 영향을 준다.
* 따라서 Atmega4809의 CPU의 쓰레드는 하나지만 여러개로 사용하는 것 처럼 인터럽트 방식을 사용한다.
* 인터럽트 방식이란 어떤 동작을 수행할 때 10초의 시간이 필요하면 10초 동안 다른 일을 하고 인터럽트에 의해 동작하는 방식이다.
* 이제 기본적인 CPU의 클럭 설정과 인터럽트를 발생시키는 Timer/Counter 를 사용해 보겠다.
*
* next) 제일 먼저 CLK_Init()을 보자.
*/
/*
* #define F_CPU 5000000UL
* Clock configuration 에서 설정한 Pulse Division 에 따라 설정을 진행한다.
* PDIV_4X 으로 20000000hz / 4 = 5000000 이 되었다.
*
* 이제 첫번째 단계인 clock 설정을 마쳤다.
* Clock 이 올바르게 동작하는지 확인하기 위해 Timer/Counter 를 통해 1초에 한번 인터럽트를 발생 시켜 LED를 켜는 연습을 할 것이다.
*
* next) TCB0_Init()을 보자
*/
#define F_CPU 5000000UL
/*
* 각종 함수를 불러와 사용할 수 있게 해주는 전처리기 이다.
* 하지만 이러한 include 를 난발할 경우 사용하지 않는 함수들도 불러오기 때문에 적절하게 사용할 것만 부르고 필요없는 것은 사용하지 않는 것이 좋다.
* 따라서 우리는 bool 을 사용 할때 include 를 하지 않고 직접 만들어서 사용할 것이다.
*
* next) typedef enum {}bool; 을 보자
*/
#include <avr/io.h>
#include <avr/interrupt.h>
/*
* LED를 쉽고 빠르게 키고 끄기 위해서 설정을 간단하게 해주었다.
* 전처리기의 경우 컴파일시 대체가 되기 때문에 동작에 영향을 주지 않는다.
*
* next) main()의 while(1)을 보자.
*/
#define USER_LED_ON (PORTF.OUTCLR = PIN5_bm)
#define USER_LED_OFF (PORTF.OUTSET = PIN5_bm)
#define USER_LED_TOGLE (PORTF.OUTTGL = PIN5_bm)
/*
* 여기에 "함수의 원형"을 선언 함으로 어떤 함수들이 있는지 확인 할 수 있다.
*/
void CLK_Init(void);
void GPIO_Init(void);
void TCB0_Init(void);
/*
* typedef, enum, struct 를 얼마나 유연하게 사용할 줄 아는가에 프로그래머의 실력이 달려있다.
* enum 은 열거형으로 0,1,2,... 순으로 값이 들어가게 된다. 따라서 false = 0, true = 1이 되어 bool 의 역할을 할 수 있다.
*
* 본론으로 돌아가 sei() 설정 후 해야 하는 것들을 알아볼 것이다.
* next) ISR(TCB0_INT_vect)을 보자.
*/
typedef enum {false,true}bool;
volatile bool ledFlag = false;
int main(void)
{
/*
* 실질적으로 설정한 것을 한번만 실행 시켜주는 부분이다.
* 따라서 하드웨어적 initialization 과 소프트웨어적 initialization 이 존재한다.
*/
CLK_Init();
GPIO_Init();
TCB0_Init();
/*
* main문 위에서 CLK_Init(), ... 으로 설정을 진행하였다.
* sei()를 통해 TCB0에서 인터럽트 발생할 시기를 결정해 준다.
* set interrupt 를 사용하면 인터럽트가 시작되게 된다.
* sei()를 사용하기 위해서는 상단에 #include <avr/interrupt.h> 를 해야 한다.
*
* next) #include <avr/interrupt.h>을 보자
*/
sei();
/*
* Super Loop 방식으로 계속해서 동작을 하는 부분이다.
* 여기서 ledFlag를 확인하여 true 일 경우 false 로 만들어 주고 LED를 Toggle 한다.
* 앞서 USER_LED_TOGLE를 전처리기를 통해 간단하게 해주었다.
* 이제 모든 동작을 구현하였다.
* 코드를 실행해보면 1초에 한번 LED가 깜빡이는 것을 볼 수 있을 것이다.
*/
while (1)
{
if(ledFlag) { ledFlag = false; USER_LED_TOGLE; }
}
}
/*
* CCP = CCP_IOREG_gc;
* Configuration Change Protection 으로 CPU 내부 설정을 변경할 때 보호장치를 off 해야 내부 IO Register Protection 에 써넣을 수 있다.
*
* CLKCTRL.MCLKCTRLB = CLKCTRL_PDIV_4X_gc | CLKCTRL_ENABLE_bm;
* dataSheet 를 보면 4 cycle 안에 변경을 진행해야 하므로 |= 을 사용하지 않고 = 으로 바로 써 넣어준다.
* CLKCTRL.MCLKCTRLB = ~CLKCTRL_ENABLE_bm;
* #define F_CPU 20000000UL을 사용할 수 있다.
* 하지만 고주파수의 경우 에너지를 더 많이 쓰므로 적절한 주파수 설정을 해주어야 한다.
* 설정이 다 완료 되었으면 #define F_CPU 5000000UL을 한다.
*
* next) 맨 윗줄에 #define F_CPU 을 보자.
*/
void CLK_Init(void)
{
CCP = CCP_IOREG_gc;
CLKCTRL.MCLKCTRLB = CLKCTRL_PDIV_4X_gc | CLKCTRL_ENABLE_bm;
}
/*
* 데이터 시트를 보고 MUX 에 따라 Input mode 와 Output mode 의 Direction 을 설정해주어야 한다.
* ATmega4809의 경우 normal input mode 이기 떄문에 설정을 진행한다.
* PORTF.DIRSET = PIN5_bm;
* PORTF.OUTSET = PIN5_bm;
* Direction 설정, output High 설정을 함으로 LED를 꺼진 상태를 유지한다.
*
* GPIO 설정을 완료를 했다.
* 간단한 동작 구현을 위해 상단의 #define USER_LED_TOGGLE 을 확인하자
*
* next) #define USER_LED_TOGGLE 을 보자.
*/
void GPIO_Init(void)
{
PORTF.DIRSET = PIN5_bm;
PORTF.OUTSET = PIN5_bm;
}
/*
* TCB0.CCMP = 5000;
* 5000000Hz의 클럭이 존재하기 때문에 Count는 클럭이 한번 발생할 때마다 증가하여 5000000번 증가하면 인터럽트를 한번 발생하게 된다.
* 따라서 Counter를 비교해 인터럽트를 일정치 이상일 경우 발생 시키게 동작 해준다. (Compare or Capture)
* 그 값이 5000번의 클럭이 동작 했을 때 1번의 인터럽트가 발생하게 된다. 따라서 1초에 5000000번 클럭이 동작하는 CPU에서
* 5000에 한번 인터럽트가 발생하므로 인터럽트는 1초동안 1000번 발생하게 된다.
* 따라서 인터럽트 발생 주기는 0.001초에 한번 발생하게 된다.
*
* TCB0.CTRLA |= TCB_ENABLE_bm;
* TCB0.INTCTRL |= TCB_CAPT_bm;
* 위에서 CCMP를 통해 Compare or Capture만 행하였을 뿐 인터럽트를 발생시키겠다는 설정을 하지 않았다.
* TCB에서 interrupt를 발생시키겠다는 설정을 하는 부분이다.
* 이 둘은 보통 같이 붙어 다니니 잘 외워두면 좋다.
* INTCTRL을 작성해야 하는데 INTFLAG를 작성하는 경우가 많으니까 유의하자.
*
* 인터럽트를 설정만 했을 뿐 실질적으로 동작을 시키려면 set interrupt 명령어를 주어야 한다.
* next) main()에 sei()를 보자.
*/
void TCB0_Init(void)
{
TCB0.CCMP = 5000;
TCB0.CTRLA |= TCB_ENABLE_bm;
TCB0.INTCTRL |= TCB_CAPT_bm;
}
/*
* Timer/Counter 가 0.001초 마다 인터럽트를 발생시켜 Interrupt Service Routine 으로 들어온다.
*
* TCB0.INTFLAGS |= TCB_CAPT_bm;
* ISR()을 만들면 가장먼저 ISR을 꺼주는 기능을 활성화 해야한다.
*
* 인터럽트를 꺼주는 기능을 활성화 했다면 인터럽트가 발생하면 인터럽트 내부에서 어떤 동작을 할지 설정을 해줘야한다.
* 이때 유의할 점은 인터럽트 내부에서 동작하는 시간을 많이 가져서는 안된다.
* 인터럽트는 잠깐 들렀다 바로 다시 main() 으로 빠져나가야한다.
*
* static uint16_t Cnt1000Hz = 0;
* Cnt1000Hz++;
* 0.001초 마다 발생하는 인터럽트를 몇번 발생했는지 기억을 할 필요가 있기 때문에 static 을 사용한다.
* local 에서 static 을 선언하면 변수의 값을 기억하지만 외부 함수에서는 사용할 수 없게 된다.
* 외부에서도 사용하고 싶다면 static 을 사용할 필요없이 global 영역에 변수를 선언해주기만 하면 된다.
*
* 0.001초는 1ms 이기 때문에 5ms마다 다른 동작을 하고 싶다면 다음과 같이 설정하면 된다.
* uint8_t Cnt200Hz = (uint8_t)(Cnt1000Hz % 5);
*
* if(Cnt1Hz == 0) ledFlag = true;
* 1초에 한번 flag 를 true 로 설정한다.
*
* 인터럽트를 설정을 완료하였다. 이제 어느 LED를 동작 시킬지 설정을 해보자.
*
* next) GPIO_Init()을 보자.
*/
ISR(TCB0_INT_vect)
{
static uint16_t Cnt1000Hz = 0;
uint8_t Cnt200Hz = (uint8_t)(Cnt1000Hz % 5);
uint8_t Cnt1Hz = (uint8_t)(Cnt1000Hz % 1000);
Cnt1000Hz++;
if(Cnt1Hz == 0) ledFlag = true;
TCB0.INTFLAGS |= TCB_CAPT_bm;
}