전전실험 8주차 내용으로 인터럽트를 이용한 예제 두개와 과제를 포스팅하겠습니다.
개요 : Interrupt에 대한 소개
- External Interrupt(EICRx, EIMSK, EFIR)에 대한 소개
- 예제 1 : 인터럽트에 의한 LED ON/OFF
- 예제 2 : 두 개의 인터럽트가 들어왔을 때
- 과제1 : Interrupt (up/down count)
배경지식
1. 인터럽트는 하드웨어에서 발생하는 Function call이다.
(vs. Polling : 일정시간 간격으로 확인, 차례대로 검사)(7번에서 더 자세히...)
2. 인터럽트에는 우선순위가 있어 그 중 가장 큰 우선순위는 Reset이다.
3. sREG : AVR status register, 인터럽트의 enable/disable을 결정
(sei() 사용o, cli() 사용x)
EICRx : 인터럽트가 어떨 때 발생이 되는지 설정.(ex rising edge, falling edge)
EIMSK : 어떤 입력을 인터럽트가 걸리는지를 설정
EFIR : INT0~INT7 중에 “어떤 인터럽트가 트리거 되었는가?”를 표시함
4. 인터럽트 INT0~3은 EICRA , INT4~7은 EICRB이고 EICRA와 EICRB의 각각 Interrupt register는 서로 다르다.
+ 예를 들어, EICRB=0b00001010;로 설정되면, INT4와 INT5의 인터럽트가 falling edge일 때 발생)
5. volatile 키워드에 대한 조사 (+ optimization option)
- 우선 volatile의 사전적인 의미는 “휘발성”이다. 이름 그대로 volatile 변수는 외부적 요인에 의해서 언제든 값이 바뀔 수 있음을 의미합니다. 이에 따라, compiler는 volatile로 선언된 변수에 대해서는 최적화를 수행하지 않습니다. Volatile 변수는 레지스터에 로드된 값을 사용하지 않고, 메모리에서 직접 참조합니다.
- 여기서 최적화란 compiler가 속도 향상을 위해 불필요한 메모리 사용을 없애는 것인데, 하드웨어 조작에 있어서 임의로 코드가 제거/조정된다면 원하는 결과를 가져올 수 없을 것입니다. 따라서 volatile 변수로 선언하면 최적화를 수행하지 않고 메모리를 그대로 사용합니다.
- 과제1을 수행할 때 ‘cnt’를 이용해서 숫자의 증/감을 구현할 것인데, volatile이 아니라 register 변수로 선언하게 된다면 인터럽트 루틴에서 변수 내용을 바꿀 때 레지스터 값이 바뀌므로 언제 값이 없어질지 알 수 없습니다. 이런 경우 메모리 변수로 선언하기 위해 volatile을 사용합니다.
- 보통 I/O Port를 특별한 변수 이름으로 지정해놓고 계속적으로 사용하고자 할 때, volatile 키워드를 붙여서 사용합니다.
6. ISR (Interrupt Service Routine) 조사
- 다른 이름으로는 “Interrupt Handler”라고 불립니다. 하드웨어에서 발생한 인터럽트 요청을 소프트웨어적으로 처리를 일컫습니다. ISR은 진행 중인 프로세스를 interrupt하고, 요청을 제어하고 CPU에게 보냅니다. ISR이 끝난 뒤에는 다시 프로세스가 재개됩니다.
- 대표적인 예로는 키보드 입력이 있습니다. 키보드가 눌릴 때 마다 ISR은 입력을 처리합니다. 만약 키보드에서 “→”를 누른다하면 CPU는 이 정보를 활성화된 프로그램에 보내서 커서를 오른쪽으로 한 칸 옮깁니다. 우선순위(인터럽트 테이블)를 미리 정해놓는데 보통 키보드가 높은 우선순위에 있습니다.
- AVR의 경우 vector no.1은 external pin, power-on reset, brown-out reset, watchdog reset, and JTAG AVR reset이 있습니다.
7. Interrupt vs. Polling Comparison
no. |
Interrupt |
Polling |
1. |
인터럽트는 장치(device)가 CPU에게 처리를 요청합니다. |
폴링은 장치가 요청하든 말든, CPU가 지속적으로 확인을 합니다. |
2. |
프로토콜이 아닌 하드웨어적 원리로 작동됩니다. |
프로토콜(데이터 통신을 위한 통신 규약, 예를 들어 신호 송신의 순서, 데이터 표현법 등..)입니다. |
3. |
장치의 요청을 interrupt handler를 통해 처리합니다. |
CPU가 직접 장치를 처리합니다. |
4. |
폴링과 다르게 언제든(any time) 권한을 받을 수 있습니다. |
반면, CPU가 특정 인터벌에 따라 지속적으로 정합니다. |
5. |
IRQ(Interrupt Request Line)은 장치가 서비스 요청한다는 지시를 명시합니다. |
폴링에는 Command Ready Bit가 있는데, 이를 통해서 장치가 서비스 요청함을 알 수 있습니다. |
6. |
그 어떤 장치가 요청하면 간단하게 프로세서는 인터럽트를 받을 수 있습니다. |
지속적으로 확인하기 때문에 프로세서가 많은 시간을 command ready bit를 확인하는 곳에 낭비합니다. |
- 위의 표를 통해 파악할 수 있는 사실은
○ Interrupt는 폴링보다 다소 복잡하지만, 처리의 정확한 시간(timing)을 요구하는 곳에 적합하고, 지속적인 체크가 필요 없기 때문에 프로세서 시간 낭비(시스템 성능 저하)가 없습니다.
○ Polling은 구현이 상대적으로 간단하지만, 리소스(시간)를 많이 잡기 때문에 성능 저하의 큰 원인이 됩니다. 거기에 소프트웨어의 처리이기 때문에 속도가 느리다는 단점이 있습니다.
예제1. 인터럽트에 의한 LED ON/OFF
- (그림1)에선 init_port()를 사용해서 포트(LED)에 대한 초기화와 init_interrupt()를 사용해서 INT5(인터럽트)에 대한 초기화를 설정했습니다.
- DDRF = 0xF0을 통해서 PF7~4 즉, LED를 출력으로 설정하였고, DDRE = 0x00을 이용해서 모두 입력으로 받고 그 중에서 INT4,5를 사용합니다.
-EICRB = 0x08;를 이용해서 INT5를 falling edge에 사용하는 것으로 설정, EIMSK = 0x20;(0b0010_0000)를 써서 INT5를 씀을 선언합니다.
- (그림2)에서 포트/인터럽트 초기화 이후 sei()를 이용해서 인터럽트를 set해줬습니다.
- while(1) 즉, 무한루프를 돌게 만들었고 ISR(INT5_vect)를 통해서 INT5 인터럽트가 들어오게 된다면 PORTF(LED)의 여집합 상태를 갖게 만들었습니다.
결과를 확인해보면,
예제 2 : 두 개의 인터럽트가 들어왔을 때
- (그림2)실험은 인터럽트 4번 5번이 동시에 눌렸을 때 LED의 변화를 보는 실습이다. 하나를 누를 때는 동작하지 않고, 두 버튼이 모두 눌러지게 되면 인터럽트가 활성되기 전 Flag 값을 확인해 LED1,2를 ON합니다.
- 인터럽트 활성화 후에는 VECTOR TABLE에 따라 INT4번이 먼저 실행된 후 5번이 실행되는 것을 알 수 있었습니다. LED 3,4가 켜지고, INT4번은 PORTF 상태의 여집합으로 켜지는 것이었고, INT5번은 LED1,4가 켜지 는 것이었다. 인터럽트가 발생했을 때 FLAG값은 0으로 변하므로 인터럽트 해제 전에 모든 LED를 끄고(PORTF = 0x00) 인터럽트를 해제하게 됩니다.
결과를 확인해보면,
위의 그림은 INT4/5누를 시 LED가 켜지는 것 확인(●●○○ -> ○○●● -> ●○○● (4321)순서
과제1 : Interrupt (up/down count)
- (그림1)에서 지난 7_segment 수업때 사용했던 SEGNP, SEGWP, SEGPOS를 가져왔습니다. DDRA/E/G를 통해서 LED 사용에 대한 초기화를 했습니다.
- 예제에서 사용하였던 interrupt 초기화 함수를 사용하였습니다.
- 앞서 설명했던 volatile 키워드를 사용했습니다. Memory에 저장해서 인터럽트 루틴 안에서 ‘cnt’를 사용해서 ‘숫자 counting’을 할 것입니다. 다시 기능에 대한 요약을 하자면 compiler가 최적화 하지 않고 memory에서 변수를 사용하는 것입니다.
- (그림2)은 저번 7 segment 과제를 수행하면서 사용했던 display용 함수이고, 이번 과제는 정확한 시간을 계산하는 것이 아니기 때문에 delay 함수에 각각 5ms 씩 넉넉하게 시간을 부여했습니다.
- INT4를 누르면 증가, INT5를 누르면 감소하게 만드는 실험이었습니다. (그림3)의 코드를 보면 우선 포트(LED), 인터럽트에 대한 초기화를 진행하였습니다. 숫자판에 사용될 a, b, c, d도 각각 int형으로 선언하고 0으로 초기화했습니다.
- 인터럽트를 사용할 것이기 때문에 cnt를 while문 안에서 제어하지 않습니다. while문에서는 display를 위해서 time_check 함수만 계속 실행시키고, 각각 자릿수에 들어갈 숫자들만 계산을 해줍니다. 최고자리수인 1000의 자리는 1000으로 나눈 몫을 통해 구하고, 나머지는 구할 숫자의 값으로 나누고 %10(나머지 연산)을 통해 자릿수의 값을 구했습니다. 추가적으로, 9999를 넘어서면 0으로 보내주고 0에서 더 낮아지면 9999로 보내주기 위해 if문을 사용하였습니다.
- ISR(INT4_vect)를 통해서 volatile 변수로 선언된 cnt의 값을 ‘+1’해주었습니다. 마찬가지로 ISR(INT5_vect)를 통해서 cnt 값의 감소를 구현하였습니다. 이렇게 인터럽트 루틴에서 사용되는 변수는 volatile 변수로 선언하여야합니다. (이유는 앞서 설명한 메모리 저장)
결과를 확인해보면,
각각 0->9999, 9999->0 으로 넘어가는 상황
이번 실험을 요약하자면
-(예제 1)을 통해 인터럽트가 들어올 때, PORTF 상태의 여집합 상태가 되도록 만들었습니다. 또한, 보드에서 명시된 것과 다르게 schematic을 확인해보면 INT4와 INT5의 명시가 거꾸로 되어있는 것을 확인할 수 있었습니다.
-(예제 2)를 진행하였습니다. 인터럽트 활성화 후에 VECTOR TABLE에 따라 INT4가 실행된 후 5번이 실행되는 것을 알 수 있었습니다. 인터럽트가 발생했을 때 FLAG값은 0으로 변하는 것을 확인하였습니다.
-과제_interrupt up and down을 진행하였습니다. 여기서 volatile 키워드를 사용하였고 volatile 변수는 C/C++에서 최적화 등 컴파일러의 재량을 제한하는 역할을 한다는 것을 확인했습니다. 즉, volatile로 선언된 변수는 최적화에서 제외가 됩니다. 또한 OS와 연관되어 장치제어를 위해 지정한 주소를 직접 엑세스하는 방식을 설정할 수 있습니다.
Reference
- volatile 키워드
https://ko.wikipedia.org/wiki/Volatile_%EB%B3%80%EC%88%98
- ATmega128
http://www.kjit.bme.hu/images/stories/targyak/mechatronika_robot_mcu/SH_kepzes/atmel_atmega128_manual.pdf
+ schematic.pdf
- ISR 조사
https://techterms.com/definition/isr
- Interrupt vs. Polling
https://www.geeksforgeeks.org/difference-between-interrupt-and-polling/
'Electronic_Engineering > AVR' 카테고리의 다른 글
[AVR - ATmega128] Timer Counter (타이머 카운터) (0) | 2021.05.06 |
---|---|
[AVR - ATmega128] 7 segments 다루기 (0) | 2020.07.03 |
[20200627] Line Tracer 주행 성공 (0) | 2020.07.02 |
최근댓글