April 29, 2024

디바이스마트 미디어:

[66호] 원하는 색상으로 제어가 가능한 아두이노 IoT 스마트 무드등 키트 -

2021-06-25

★2021 ICT 융합 프로젝트 공모전 결과 발표! -

2021-05-12

디바이스마트 국내 온라인 유통사 유일 벨로다인 라이다 공급! -

2021-02-16

★총 상금 500만원 /2021 ICT 융합 프로젝트 공모전★ -

2021-01-18

디바이스마트 온라인 매거진 전자책(PDF)이 무료! -

2020-09-29

[61호]음성으로 제어하는 간접등 만들기 -

2020-08-26

디바이스마트 자체제작 코딩키트 ‘코딩 도담도담’ 출시 -

2020-08-10

GGM AC모터 대량등록! -

2020-07-10

[60호]초소형 레이더 MDR, 어떻게 제어하고 활용하나 -

2020-06-30

[60호]NANO 33 IoT보드를 활용한 블루투스 수평계 만들기 -

2020-06-30

라즈베리파이3가 드디어 출시!!! (Now Raspberry Pi 3 is Coming!!) -

2016-02-29

MoonWalker Actuator 판매개시!! -

2015-08-27

디바이스마트 레이저가공, 밀링, 선반, 라우터 등 커스텀서비스 견적요청 방법 설명동영상 입니다. -

2015-06-09

디바이스마트와 인텔®이 함께하는 IoT 경진대회! -

2015-05-19

드디어 adafruit도 디바이스마트에서 쉽고 저렴하게 !! -

2015-03-25

[29호] Intel Edison Review -

2015-03-10

Pololu 공식 Distributor 디바이스마트, Pololu 상품 판매 개시!! -

2015-03-09

[칩센]블루투스 전 제품 10%가격할인!! -

2015-02-02

[Arduino]Uno(R3) 구입시 37종 센서키트 할인이벤트!! -

2015-02-02

[M.A.I]Ahram_ISP_V1.5 60개 한정수량 할인이벤트!! -

2015-02-02

[20호]아날로그필터와 OPAMP Op Amp. 사용설명서 PART 1

아날로그필터와 Op Amp.
아날로그필터와 Op Amp. 아날로그필터와 OPAMP

Op Amp. 사용설명서 PART 1 

 

글 | (주)싱크웍스 백종철

 

본 글은 “아날로그필터와OPAMP, ㈜싱크웍스 출판사, 백종철” 책자에서 자칫 많은 개발자들이 그 중요성을 간과 하기 쉽지만 실전에서 꼭 필요한 OPAMP에 대한 내용을 디바이스마트 매거진 독자들을 위해 발췌하여 수록하였습니다.

목차

1. Op Amp. 실제 부품의 이해와 핀 설명
2. Op Amp.의 전원 공급과 전류의 흐름
3. Rail to Rail Input Output(RRIO) 방식의 장점
4. CMRR과 PSRR
5. Loading Effect (부하의 영향)
6. Instrumentation Amplifier
(정밀 기기용, 계측용 증폭기)
7. Op Amp.의 Sense 핀과 Ref. 핀의 사용법
8. 단전원(Single Power Supply) 조건에서
Op Amp. 구동
9. Fully Differential Op Amp. (FDA)

 

이 글에서는 능동 필터의 핵심 소자로 사용되는 Op Amp.에 대한 내용을 다룬다. 쓰다 보니 양이 너무 많아져서, 취사 선택을 해야만 했다. 첫번째 기준으로는 FilterPro 사용에 있어서 부족함이 없도록. 둘째는, 현장에서 흔히 놓치는 Op Amp.의 중요한 성질들을 확실히 다루자 라는 것이었다. 아울러 실제 부품을 사용했을 때 발생 가능한 현상들을 TINA를 통해서 확인하는 과정을 곳곳에 배치했기에, 현 업무에 직접적으로 도움이 될 것이다. 마지막으로는, 최근 들어 주목받고 있는 FDA(Fully Differential op Amp.)를 다뤄봤다. 막상 별다를 바가 없다는 것을 이해하실 수 있을 것이다.

1. Op Amp. 실제 부품의 이해와 핀 설명

그림 1-1은 TINA에서 사용되고 있는 이상적인 Op Amp. (연산 증폭기)와 실제 Op Amp.의 핀을 보여 주고 있는데 일반적으로 이렇게들 사용하고 있다.

20feasync0

그림 1-1. TINA에서의 Op Amp. 라이브러리 좌. 이상적인 Op Amp. 우. 실제 부품

Ideal Op Amp의 경우, DC 전원 관련 핀을 생략하고 있기에 통상 세 개의 핀으로 Op Amp.를 구성한다. 우측의 TLC2274는 저전압용 Op Amp.로 만만하게 쓰이는 제품이며 Texas Instruments 사에서 공급한다. 전원 공급핀이 추가되어 핀이 총 다섯개다. 일반적인 경우다. 어떤 Op Amp. 들은 단일 Op Amp. 이면서도 7핀이나 9핀까지도 가지고 있는 것들도 있다. 이런 것들은 차츰 알아보기로 하고 우선은 5핀 표준 부품을 가지고 설명을 시작해보겠다. 먼저 Op Amp.를 이용할 때 가장 많이 쓰는 증폭 회로를 살펴보면서 핀에 담겨 있는 심오한 내용을 이해해 보도록 하자.

20feasync0 (1)

그림 1-2. TLC2274로 꾸민 반전증폭기

그림 1-2의 회로는 TLC2274 Op Amp.로 입력신호를 10배 증폭하면서, 출력신호의 위상이 입력신호와 180도 차이나는 반전 증폭기를 만든 것이다. 10배를 증폭한다면, Peak To Peak 전압, 즉, Vpp가 100V 인 정현파를 인가하면, 1,000Vpp 정현파가 만들어진다. 맞나? 일단은 너무 큰 것 같다. 앞서 TLC2274는 저전압용 Op Amp.라고 했는데, 100Vpp와 1000Vpp는 너무 큰 것 같다. 눈치 빠르신 분은 그림 1-2 회로에서 TLC2274에 공급된 전원이 +/-5V 이기에 출력 전압이 이 범위를 벗어날 수 없음을 잘 아실 것이다. 하지만, Op Amp.를 낯설어 하시는 분들도 계실 것이다. 그래서, TLC2274의 주요 규격부터 살펴보았다. TLC2274의 제품 설명서를 구하기 위해서 www.ti.com 을 방문 후 검색했다. 해당 제품 페이지를 찾아, 상단부를 캡쳐하여 그림 1-3에 옮겼다.

20feasync0 (2)

그림 1-3. TLC2274 제품 웹 페이지

TLC2274 라는 부품명이 선두에 보이고, 바로 아래줄에 ‘(Active)’라는 말로 현재 생산중임을 알려주고 있다. 이어서 ‘Quad Low-Noise Rail-to-Rail Operational Amplifier’ 라는 문구가 보이는데 제품의 주요특징을 압축해서 잘 알려주고 있다. Quad라는 말은 한 패키지에 Op Amp.가 4개 들어있다는 얘기다. 그렇다면, 이 패키지의 총 핀 수는? Op Amp.당 5개가 필요하니, 총 20개가 될 터인데, 전원 핀을 공유한다면 14핀이면 해결 가능하다. 실제 14핀 SOIC, 14핀 TSSOP, 14핀 PDIP, 14핀 SO 패키지등으로 공급되고 있다. ‘Low Noise’ 라는 말은 받아들이기는 쉬우나, 정말로 얼마만큼 Low Noise인지는 판단하기가 무척 어렵고 그 안에 녹아든 기술 또한 이해하기가 만만찮다. 이 절에서는 부품을 사용하는 입장에서 Low Noise 여부를 가늠할 수 있는 지표들을 설명하도록 하겠다. 이어서 ‘Rail-to-Rail’ 이라는 용어가 나왔는데, 중요한 용어다. 일단은 저전압용 시스템에 적절한 전원 회로를 탑재하여, 낮은 전압에서 최대한 출력 신호 진동을 보장한 방식이라고만 이해해두자. 3 절에서 정밀하게 다루고 있으니, 조금만 기다려 주시기 바란다.

이어서, 고객들 평점과 평, 그리고 몇몇 아이콘들이 있고, 바로 밑에 Datasheet, 즉, 제품 설명서가 보이면서, 받아 보실 수 있도록 링크를 걸어 놓았다. 제품 설명서 이름은 ‘TLC227x, TLC227xA : Advanced LinCMOS Rail-to-Rail Op Amps (Rev.G)’ 라고 되어있다. 함축적 설명이다. 풀이하자면, TLC227x에서 x는 계열을 의미하는 것으로 이 계열에는 TLC2272와 TLC2274가 있다. 2272는 한 패키지에 Op Amp.가 2개 있는 것을 말한다. 이 패키지의 핀은 몇 개 일까? 그렇다. 전원을 공유하다 보니 8개이다. ‘Advanced LinCMOS’는 Texas Instruments사가 자랑하는 아날로그 반도체 제조 공정 중에 하나를 의미한다.

이어서 Description 항목이 나온다. 여기에서는, 주요 특징을 몇몇 문단에 걸쳐 설명하는데 사실 온갖 자랑이다. 여기에 혹해서는 안되고, 정말로 제대로 된 자랑인지는 제품 설명서를 봐야 하는데, 주목해야 할 부분은 ‘CMOS’ 라는 용어다. CMOS는 Complementary Metal Oxide Semiconductor의 앞머리 글자로 주로 상보성 금속 산화물 반도체 라고 옮기는데 필자도 이해가 어렵다. 갸우뚱! 무엇보다도 한자로 옮긴 말에는 중요한 정보 하나가 누락되어 있다. CMOS FET(Field Effect Transistor)로 그냥 옮겨 쓰는 것이 더 나을 듯 하다. 즉, 이 Op Amp.는 N형 MOS FET 와 P 형 MOS FET로 구성된 FET형 Op Amp.다. Op Amp는 BJT로 구성되기도 하고, FET로 구성되기도 하고, BiCMOS (BJT+CMOS)로 만들어지기도 한다. 초기의 Op Amp.는 BJT로 만들어 졌는데, 최근 들어서 CMOS로 된 Op Amp.가 널리 보급되고 있다. 각각의 장단점이 있는데, 범용으로 쓰기에는 어떤 것을 써도 큰 차이 없다. 반도체 회사들이 알아서 잘 만들고 있다. 다만, CMOS 공정이 BJT 공정보다 상당히 간단한 편이고, 대부분의 디지털 회로들이 CMOS 공정으로 만들어지기에 Op Amp. 도 CMOS로 만들어가는 추세다. 일반적으로는, BJT로 구현된 Op Amp.가 FET로 구현된 Op Amp보다 전반적인 성능이 우수하다고 평가되지만, FET로 구현된 Op Amp.가 우세한 부분도 더러 있다. 바로 입력 임피던스 항목이 그 중 하나인데, 왜냐면, FET의 경우, 입력 단자로 쓰이는 Gate단과 전류가 흐르는 Source-Drain간의 채널 사이가 절연이기 때문이다. 실제로 TLC2274의 입력 임피던스는 10^12 옴 = 100만메가 옴 = 1천기가 옴 = 1테라 옴이다. 무지하게 크다. 참고로 Op Amp.의 산 조상격인 uA741 Op Amp.의 입력 임피던스는 2M옴에 불과하지만, 작다고 할 수는 없는 편이다. 따라서, 특별한 경우가 아니라면, CMOS건 BJT건 구분없이 사용해도 된다. 중요한 것은 CMOS냐 BJT냐 하는 것보다는 세부 규격이다. 단, 5절에서 설명하는 Loading Effect 때문에 큰 입력 임피던스가 필요하다면 당연히 CMOS로 구현된 Op Amp.가 적합하다. 이어서 ‘Feature’ 부분이 보인다. Op Amp. 선택 시 먼저 이 부분을 자세히 살펴보고 궁합을 따져본 후 맞겠다 싶으면 제품 설명서를 탐독해야 한다.

20feasync0 (3)

그림 1-4. TLC2274의 주요 특징

먼저, Output Swing Includes Both Supply Rails 라는 항목이 나오는데, 앞서 언급한 Rail-to-Rail 특성을 말한다. 4.3절까지 보류 해 두자. 이어서, Low Noise 항목이 나온다. 9nV/ Hz라고 수치로 명시하고 있는데, 감이 잘 안 온다. 일단, 9nV라고 하니 무지하게 작은 것 같다. 단위가 좀 특이하다. 9nV/ Hz라니? 주로 전자 부품 자체에서 발생하는 Noise를 알려주는 지표이다. 다시보니, 1KHz에서 9nV Hz 라고 명시하고 있다. 이를 총칭해서 1/f 노이즈라고도 한다. 엄밀히 말하면, 1/fα 노이즈라고 한다. TLC2274 특징에서는 α=0.5인 경우인데, 이게 정수가 아니면, Fractal 현상을 보이기에 Fractal Noise 라고도 한다. α=1이면 Pink Noise, 혹은 Flicker Noise 라고도 한다. 741 Op Amp.는 이 값이 어느 정도될까 궁금하여 찾아보니 없다. 인터넷을 여기저기 뒤져보니 100nV Hz수준인 듯한데, 확실한 비교를 위해서 LM2902를 찾아봤다. 35nV Hz이다. 확실히 TLC2274가 작다. Low Noise다. 하지만, TLC2274가 LM2902보다 5배 넘게 비싸다는 것은 함정.

다음으로, Low Input Bias Current가 통상 1pA 라고 말한다. 이는 CMOS로 구현되었기에 가능한 수치다. 입력 임피던스가 1테라 옴이라서 가능한 수치다. 이어지는 두 항목은 Rail-to-Rail을 설명할 때 다루기로 하자. High Gain Bandwidth가 통상 2.2MHz 라고 한다. High Gain-Bandwidth라, 이런 용어는 없다. 아마 뒤에 말이 생략된 듯 하다. 복구하자면, High Gain-Bandwidth Product일 것이다. 실제 DataSheet를 살펴보면, Gain-Bandwidth Product 항목으로 +/-5V 전원으로 동작할 경우, 2.25MHz 라는 값이 나온다. 증폭기를 다룰 때 있어서 대역폭(Bandwidth)는 어느 주파수 신호까지 증폭할 수 있는지를 알려주는 매우 중요한 지표로, Gain-Bandwidth Product 항목은 증폭비 1배를 기준으로 할 때, 2.25MHz까지는 1배 증폭이 가능하다는 얘기다. 2배 증폭을 원한다면, 대역폭이 반으로 줄어든다. 10배 증폭을 원한다면, 225KHz 까지로 제한된다. 이게 그 유명한 ‘Gain-Bandwidth Product Rule’ 이다. 모든 증폭기는 이 규칙에 지배된다. 즉, 이득과 대역폭의 곱은 일정하다라는 규칙이다. 앞서 말한, 2.25MHz를 단일 이득 대역폭 주파수 (Unity gain bandwidth frequency)라고 말한다. 그런데 이 수치가 공급 전압에 따라 변하고, 온도에 따라 변한다는 점을 명심해야 된다. 그래서 제품 설명서에는 반드시 다음과 같은 그래프가 있으니 두 눈 부릅뜨고 유심히 읽어야 한다.

20feasync0 (4)

그림 1-5. TLC2274의 공급전압과 온도에 따른 Gain-Bandwidth Product 그래프

그림 1-5의 좌측 그래프는 공급 전압에 따른 그래프다. 그 차이가 그렇게 크지는 않지만, 공급 전압이 낮을수록, Gain-Bandwidth Product 수치는 낮아진다는 점에 주목해주시길 바란다. 우측은 온도에 따른 수치다. 2.25MHz 수치는 대략 0도에서 측정된 수치인 것이다. 주위 온도가 50도 정도 된다면 2MHz 이하로 떨어진다. 광대역 신호를 다루는 경우에는 정말로 주의해야 한다. 75도에서는 1.8MHz 에 불과하다는 점. 많이들 놓치는 부분이다. 그래서, 이 책에 특별히 실었다.

다음은, Slew Rate 항. 이 항은 Gain-Bandwidth Product 항과 밀접한 관계가 있다. Gain-Bandwidth Product 수치가 높을수록 Slew Rate는 높다. 단위가 의미하듯이, 1usec 동안에 출력이 통상 3.6V 상승할 수 있는 능력을 갖췄다는 얘기인데, 요즘은 수십V/usec의 Slew Rate을 가진 Op Amp.도 수두룩하다.

다음 항목으로 Low Input Offset Voltage 라는 항목이 나와 있는데, 25도 상온에서 950uV 라고 한다. 과연 적은 값일까? 단위를 달리 하면, 0.95mV다. 크게 느껴진다. Low 라니. 사기… 일단 Input Offset Voltage가 뭔지부터 알아야겠다. Op Amp.의 두 입력단, 즉, +/- 입력단을 저항이 0인 도선으로 연결했다면, 출력은 0 이어야 한다. 그런데 0V가 아니다. 심각하다. 출력을 0으로 만들기 위해서 별도의 DC 전압을 인가해보니 출력이 0이 되더라고 한다면, 추가한 전압을 Input Offset Voltage 라고 한다. 주된 원인은 Op Amp. 입력단 차동 증폭기에 쓰인 트랜지스터가 기하학적으로나, 도핑 농도적으로나 동일하지 않기 때문인데, 요즘은 정밀 제조 기술 덕에 이 값이 많이 줄어들었다. 따라서, 950uV는 결코 작은 값이 아니다. Texas Instruments사는 자사 제품군들 중에 Input Offset Voltage가 500uV이하인 것들을 Low Input Offset Voltage 제품군으로 구분하고 있다. Texas Instruments사의 OPA4727이라는 제품을 보면, TLC2274처럼 CMOS로 구현된 4개의 OP Amp.를 내장하고 있다. 가격은 TLC2274에 비해 1.8배쯤 되지만, Input Offset Voltage가 통상 15uV에 불과하다. 놀랍지 아니한가? 이 정도는 되어야 Low Offset Input Voltage라고 말할 수 있겠다. OPA 계열이 한 성능한다. 최근에 나온 제품일수록 Input Offset Voltage 값이 작다. 왜냐면 정밀생산 기술이 발전하고 있기 때문이다. OPA4727, 기억해둘 만한 부품이다. 적당한 가격에, Gain-Bandwidth Product가 20MHz나 되며, 입력 임피던스가 0.1테라 옴이며, 저전력이기 때문이다. HiFi 오디오에 더러 쓰이는 뛰어난 제품이기도 하다. 이어서 Macro-model Included라는 말은, TINA에서 사용할 수 있는 Spice 라이브러리가 제공되고 있다는 것을 의미한다. 다음 그림처럼 제품 페이지 우측상단에 링크되어 있다.

20feasync0 (5)

그림 1-6. TLC2274 의 개발자 지원 항목

TINA와 같은 시뮬레이션 프로그램용 라이브러리가 제공되고 있으며, 필터 디자인 소프트웨어도 지원하며, 이 Op Amp. 의 성능을 직접 평가해볼 수 있는 키트류도 제공하고 있다는 것을 알려주고 있다.

이어지는 항목은 ‘TS27x나 TLC27x 보다 개량된 제품이기에 이것들 말고, TLC227x를 사용하세요.’ 라는 말을 전하고 있다. 새로 나온 제품이 대체로 성능도 좋고, 가격도 싸다. 따라서, 신제품을 열심히 찾고 골라야 한다. 단, 일부 회사 제품의 경우, 제품 설명서에 나와있는 규격과 동떨어진 성능을 보이는 경우도 있기에 주의해야 한다. 반드시 평가를 해보고 사용해야 한다. 필자가 경험한 제품과 해당 회사들을 소상히 밝히고 싶기도 한데, 그 사이에 개선을 했었을 수도 있으니 참겠다. 몇 번의 평가 실험을 했는지 모른다. 분하다. 참고로 Texas Instruments사 제품에는 이런 경우가 없었다. 경험상으로는, 제품 설명서에 오실로스코프와 같은 계측기 측정 화면을 직접 캡쳐해서 제품 규격을 설명하는 경우, 좋은 결과를 보여주는 듯 하였다. 그만큼 자신있다는 의미인 듯. 참고하시길…
마지막은 여러가지 변형 제품들이 있음을 소개하는 문구들이다. 전장품 온도 규격(Q Temp.)을 만족하는 제품, Op Amp. 전원을 차단하는 핀이 있는 제품등이 있음을 알려주고 있다.

2. Op Amp.의 전원 공급과 전류의 흐름

다시 한번 짚고 넘어가자. 그림 1-1처럼, 실제 Op Amp. 부품은 다섯개 이상의 핀으로 구성되어 있다는 것을 기억하자. 그런데 몇 개의 핀이 있다고 한 들, 아무리 찾아봐도 접지 핀은 없다. 이게 무슨 말이냐 하면, 다섯개의 핀을 가지는 Op Amp. 라고 할 때, 다섯개 핀들 중에서 접지와 연결하는 핀은 존재하지 않는다 라는 것이다. 그림 1-2를 다시 가져와서 보자.

20feasync0 (1)

그림 2-1. 다시 보는 그림 1-2

그림 2-1 회로에서 Op Amp.를 잘 보시라. + 입력단 핀, – 입력단 핀, 출력단 핀, + 전원단 핀, 그리고, – 전원단 핀, 이렇게 다섯개 핀만 있다. Op Amp. 본체에는 접지와 연결하는 핀이 없다. 주의. 그림 2-1에서 + 입력단에 접지를 연결한 것은 + 입력단에 접지를 입력시킨 것. 즉, 0V를 + 입력단에 입력시킨 것임을 이해해야 한다. 이렇게 얘기할 수는 있다. Op Amp.를 단전원(Single Power Supply)용으로 사용할 경우, 예를 들어, +5V 만을 단독으로 사용할 경우, 그림 2-1 회로에서 Op Amp.의 V- 핀은 접지에 연결된다. 하지만, 이거는 V- 핀이 0V에 연결된 것이고, Op Amp. 자체에는 접지와 연결하는 핀이 없다라는 점을 명확히 이해해야 한다.

Op Amp.는 접지를 필요로 하지 않는가? 예를 들어, 그림 2-1 회로에 +/-5V를 공급했다고 하자. 접지는? 접지는 어디에? 일반적으로 대칭형 전원을 공급하게 되면, Op Amp. 내부에 0V가 생긴다. 이것을 가상(Virtual) 접지라 한다. 0V가 생성되는 이유는 Op Amp. 내부 회로가 대칭형으로 설계되었기 때문이다. 이 대칭성에 문제가 생기면 Input Offset Voltage가 생길 수도 있는 것이다. 하지만, 대칭 전원이 아니더라도 가상 접지 생성에는 문제가 없으니 안심하셔도 된다. 만약, V+ 가 3.3V에 연결되고, V-가 -5V에 연결된다면 어떤 일이 벌어지는지 실험을 통해서 살펴보자. 100mVp, 1KHz 정현파 (DC offset은 0V)와 200mVp, 1KHz 정현파를 각각 인가하여 실험을 해보자.

20feasync0 (7)

그림 2-2. V+에는 3.3V, V-에는 -5V를 인가한 Op Amp.의 출력 파형

그림 2-2는 TINA의 오실로스코프 화면을 캡쳐하여 옮긴 것으로 DC를 차단하지 않고 있는 그대로 Y축 스케일만 조정하여 화면에 잘 나오도록 하였다. 각각의 그림에서 큰 진폭의 정현파가 출력 파형이다. 전원이 비대칭으로 공급되었음에도 불구하고, 그림 2-2의 좌측 그래프를 보면 출력신호가 0V를 기준으로 아래 위로 아름답게 진동하고 있다. 우측 그래프를 보면 역시 0V를 기준으로 진동을 하는데, 상반구 쪽에서 잘리는 현상이 관찰된다. 비대칭 전원을 공급해도 기준점 0V에는 변화가 없고, 출력 신호의 진동폭에서만 차이가 난다. 이 이유는 Op Amp.를 구성하는 회로를 살펴보면 쉽게 이해할 수 있기에 다음절로 좀 미루자. 다음 절에서 트랜지스터 레벨의 설명을 간단 명료히 다루고 있으니, 위 실험 결과를 토대로 증폭기의 AC 특성을 해석하는 데 있어서 DC 전원단은 전혀 고려하지 않아도 되는구나 하고 넘어가자. Ideal Op Amp.에 DC 전원 공급 회로가 없는 이유이기도 하다. 하지만, 그림 2-2의 우측 그림을 보면, 상반구에서 정현파가 잘린 것을 확인할 수 있다. V+가 3.3V이기에 3.3V 이상을 표현하지 못하기 때문이다. 그렇다면, 0.5Vp, 1KHz 정현파를 입력한 후 출력에 어떤 변화가 있는지 살펴보자. 그림 2-1의 회로는 반전 증폭기로 증폭비는 10배이다. 따라서, 출력으로는 5Vp, 1KHz 정현파가 나와야 한다. 회로에 대한 자세한 설명은 다음절에서 시작한다.

20feasync0 (8)

그림 2-3. 0.5Vp, 1KHz 정현파를 입력한 경우, 입력과 출력파형

그림 2-3을 보라. 상반구 뿐만이 아니라 하반구도 좀 잘렸다. 잘린 부위의 전압을 읽어보면, 각각 3.19V와 -4.83V 정도로 측정된다. 공급전압은 각각 +3.3V와 -5V인데, 출력전압은 이에 못 미친다. Op Amp. 세계의 어휘로는 출력 Swing 폭이 공급 전압에 못 미친다. 라고 표현하는데, 이 정도면 사실 굉장히 우수한 편이다. TLC2274가 Rail-to-Rail Input Output(RRIO) 구조이기에 가능한 결과다. RRIO가 뭔지는 3절에서 확인하도록 하고 먼저 그 특성을 확인해보자. RRIO 방식을 채택하지 않은 Op Amp.와 출력을 서로 비교해보면 차이를 확인해볼 수 있을 것이다. 널리 쓰이는 LM2902를 비교대상으로 선정했다. 각각 그림 2-1 회로처럼 꾸며놓고, +/-5V 전원을 공급했다. 이어서, 0.5Vp, 1KHz 정현파를 인가한 후 출력파형을 측정하였다. 그림 2-4를 살펴봐주시기 바란다. 좌측이 LM2902의 출력이고, 우측이 TLC2274의 출력이다.

20feasync0 (9)

그림 2-4. LM2902와 TLC2274의 출력 진동폭 비교

그림 2-4를 보니, LM2902의 경우 상반구에서 좀 더 심하게 잘렸다는 것을 확인할 수 있다. 각각의 상한치와 하한치를 측정해보니, LM2902는 4.01V와 -4.92V로 측정된다. TLC2274는 +/-4.82V로 측정된다. LM2902의 스윙(Swing)폭은 혹은 진동폭은 8.93V이고 비대칭이며, TLC2274의 스윙폭은 9.64V이며 대칭이다. 일단 스윙폭은 TLC2274가 더 넓다. 만약 하반구쪽 신호만 있어도 충분하다면, LM2902가 더 우수하다고 할 수 있다. Envelope Detector(포락선 검출기)처럼, 하반구나 상반구, 반쪽만 있어도 되는 그런 분야도 있지만, 일반적으로 상/하반구 모두 온전한 출력을 필요로 하는 경우가 대부분이다. 이때, RRIO방식을 채택한TLC2274는 잘림 정도가 대칭이고, 스윙폭이 더 크기에 보다 적절하다고 하겠다. 세부적으로는 RRI(Rail-to-Rail Input) 방식이 있고, RRO(Rail-to-Rail Output) 방식도 있으며, RRIO도 있다. RRIO를 쉽게 설명하기 위해서는 회로 레벨까지 내려가야 하는데, 이렇게 되면 트랜지스터 원리까지 설명해야 해서 걱정이 제법 된다. 트랜지스터가 싫어서 Op Amp.를 사용하시는 분들도 계실텐데, 트랜지스터 없이 RRIO를 설명하자니, 내공이 딸려서, 부득이 트랜지스터를 조금 빌려서 설명을 했다. 트랜지스터에 대한 언급을 최대한 자제한 채, 회로만 가져다 놓고 설명을 했으니, 조금만 참고 다음 절을 읽어봐 주시기 바란다. 개념만 이해하면 된다. Op Amp.를 선택하는데 있어서 꽤나 유용하게 쓰이니 꼭 다음 절을 읽어봐 주시기 바라며, 이제는 Op Amp. 내부에서 이뤄지는 전류 흐름에 관해 얘기해보자.

그림 2-1 회로로 돌아가서 전류가 어떻게 흐르는지 고찰해보자. Op Amp. 의 궤환(Feedback)을 담당하는 R2 100K 옴 저항에서의 전류 흐름을 한번 생각해보자. 출력단에 부하는 안 걸려있다. 지금 이 순간 R2에서의 전류 방향은 오른쪽이다 라고 가정하자. 그렇다면, 이 전류의 최종 종착점은 어디인가? 다음 순간에 R2에서의 전류 방향이 왼쪽으로 바뀌었다면, 어디로부터 전류가 나와서 R2에다가 왼쪽 방향 전류를 만들어 내는가? 만들어 낼 곳이 Op Amp. 밖에 더 있는가? Op Amp.는 DC 전원단으로부터 에너지를 공급받아 출력 신호를 생성해낸다. 이 부분이 커패시터나 인덕터와 다른 부분이다. 그래서, 능동(Active) 소자라고 한다. 발산의 위험도 있다. 그림 2-5를 보자.

20feasync0 (10)

그림 2-5. Op Amp.에서 전류의 흐름

그림 2-5회로를 보면, Op Amp. 가 전력을 소비하는 과정을 떠올릴 수 있을 것이다. V+ 전원 핀에서는 전류를 공급하고, V- 전원 핀에서는 빨아들인다. 이 과정에서 전력이 소비되는 것이다. 전원 핀이 생략된 이상적인 Op Amp. 만을 놓고, 그림 2-5와 같은 전류의 흐름을 생각하기는 솔직히 쉽지 않다. 초보 엔지니어에게 R2에 흐르는 전류는 어디로 가는가 하고 물어보면 대부분 머뭇머뭇 한다. 이러한 내용을 충분히 이해하고 있다는 가정하에 이상적인 Op Amp.를 만들어 놓은 것인데, 가정이 제대로 성립되지 못한 엔지니어들한테 Op Amp.에 관한 내용을 주입했다가는 사상누각 사태가 벌어질 수도 있는 것이다. 이번 기회에 기본을 확실히 해두도록 하자.

3. Rail to Rail Input Output(RRIO) 방식의 장점

최근 들어, 저 전압용으로 개발되는 Op Amp.는 거의 다 RRIO 방식을 채택하고 있는데, 이 RRIO 방식을 단순히 출력 진동폭을 최대화 할 수 있는 구조 정도로 받아들이고 넘어가기에는 좀 찝찝해서 이 절을 준비했다. 왜 그런지 이해하기 위해서는 트랜지스터 수준에서의 회로 설명이 필요한데, 트랜지스터에 관한 설명은 최대한 억제한 채, 최대한 이해하기 쉽도록 각고의 노력을 기울여서 설명하였으니, 찬찬히 읽어봐 주시기 바란다. 일단, LM2902처럼 RRIO 방식이 아닌 일반적인 Op Amp.의 입력단 회로를 간략히 그려서 그림 3-1에 올려두었다. 모든 Op Amp. 에는 Voltage Rail 이 있다. 보이는가? 그림 상단에 +DC 전원 공급단 +Vs가 보이고, 하단부에는 -DC 전원 공급단 -Vs가 보인다. 이게 Op Amp. 전체 회로에 공급되는 전원이기에 전체 회로도를 그리면 마치 기차 레일처럼 길게 늘어진다. 그래서 Rail이다. 그렇다면, Rail-to-Rail은 무슨 말인가? LM2902에도 Voltage Rail이 있는데, RRIO 방식이 아니라고? 궁금해요? 궁금하시면, 계속 읽어 나가보자.

상단부 전원단 바로 아래에 전류원 Is가 보이고, 하단부에는 전류원이 없고 부하저항 RL1과 RL2만 보인다. 예를 들어, RL1이나 RL2에 전류가 거의 흐르지 않는다면, RL1과 RL2에서 전압 강하가 거의 없기에, Vo1이나 Vo2는 -Vs가 된다. 즉, Vo1이나 Vo2가 down to the bottom voltage rail 이 된다. 반대로 전류가 많이 흐르는 경우를 고려해보자. 전류가 많이 흐르면, RL1이나 RL2에 전압강하가 많이 발생한다. 흐르는 전류를 각각 I1과 I2라고 한다면, Vo1은 -Vs + I1RL1 이 된다. 이 값이 아무리 커진다고 한들 +Vs에 미치질 못한다. 왜냐면 Q1과 Q2에서 전압 강하가 발생하고, 전류원 Is에서도 전압강하가 발생하기 때문이다. 이런 구조로 인해서 그림 2-4의 좌측에서 보듯이 Op Amp. 출력 신호 상반구에서 심한 왜곡을 보이는 것이다. 비 RRIO 구조의 출력 특성이다.

20feasync0 (11)

그림 3-1. LM2902의 입력단 구조

다음은 RRI(Rail-to-Rail Input) 구조이다. 구조가 조금 복잡하지만 전부 다 이해할 필요는 없다. RRI 방식을 이해하는데 도움이 될만한 부분이 있어서 그려본 것 뿐이다.

그림 3-2에서 Q1 ~ Q4는 트랜지스터다. 트랜지스터 그림에서 화살표가 그려진 단자를 에미터(Emitter)라고 한다. Q3와 Q4 에미터에는 RE3와 RE4저항이 달려있다. 그런데, 전류원 Is의 상부측이 RE3 및RE4의 상부측과 똑같이 +Vs에 묶여 있기에 트랜지스터의 에미터 전압이 +Vs까지 상승할 수 있다. 오호라, 전류원에 의한 전압 상승 제한폭이 사라진 구조구나 하는 것을 알 수 있다.

20feasync0 (12)

그림 3-2. RRI 구조

RC1과 RC2가 달려있는 트랜지스터의 단자는 컬렉터(Collector)라 한다. 나머지 단자를 베이스(Base)라고 하는데, 베이스에 입력된 전류가 증폭이 되어서, 에미터와 컬렉터 사이에 흐른다. 이 구조가 좌우로도 대칭이지만 아래 위로도 대칭이다. 왠지, TLC2274의 출력 신호가 대칭인 이유가 여기에 있는 것도 같다. (사실은 출력 회로까지 상하로 대칭이어야 하는데, 어쨌든 냄새가 좀 난다.) 그림 3-2회로 역시 출력 신호는 컬렉터에서 취하고 있다. 출력 신호가 위쪽으로 커질때면, 오로지 트랜지스터에서 발생하는 전압 강하(에미터와 컬렉터 사이의 전압)에 의해서만 제한을 받는다. Is에 의한 영향은 사라졌기에, 진동(Swing)폭이 좀 더 늘었다. 이는 RRI(Rail-to-Rail Input) 방식의 장점으로 비 RRI 방식에 비해 입력 신호를 좀 더 크게 받아들일 수 있다. 다시 말해, Op Amp.의 입력 신호를 작게 만들 필요가 줄어든다는 얘기다.
출력의 경우는 입력과 상황이 조금 다르다. 트랜지스터에 의한 전압 강하 때문에 진정한 RRO 방식을 만들기 어렵기에, RRO 방식이든 아니든 최대한 진동폭을 키우기 위해 나름대로 많은 애를 쓰고 있다. 이중에서 널리 쓰이는 두 가지 방식을 비교해보도록 하겠다.

20feasync0 (13)

그림 3-3. Op Amp. 출력단 비교

그림 3-3의 좌측 회로는 아주 널리 쓰이는 Class AB 급 증폭기, 즉, Push-Pull 방식 증폭기로 Op Amp. 출력단에 널리 쓰이는 대표적인 회로이다. HiFi 오디오에도 많이 채용되는 방식이며, LM2902도 채택한 방식이다. 이 방식의 장점은 출력 임피던스를 매우 작게 만들 수 있다는 점이다. 단점은 출력 신호의 진동 폭이 트랜지스터(Q1, Q2) 와 저항(R1, R2)에 의해 제한되어 그다지 넓지 못하다라는 점이다. 이걸 극복한 것이 우측 회로인데, 트랜지스터의 극성이 좌측과 다르다 라는 점에 주의해야 한다. 이 방식은 출력 스윙폭을 극대화할 수 있지만 출력 임피던스(BJT의 경우 )가 그다지 적지 않다는 단점이 있다. 또 다른 단점으로는, 출력 전류가 점점 높아지면 트랜지스터의 컬렉터와 에미터 사이 전압도 점점 커져서 출력 신호 진동의 궁극 목표인 Rail-to-Rail, 즉, V+에서 V-까지의 스윙에서 멀어진다. TLC2274는 FET로 구현된 Op Amp.이기에 출력단 역시 FET로 구현되어 있다. 그림 3-3 우측 회로에서 상단 트랜지스터가 PMOS(Positive Metal Oxide) 트랜지스터이고 하단 트랜지스터가 NMOS(Negative) 트랜지스터로 구현되어 있다. 이 경우가 좀 더 RR스럽다. 왜냐면, MOS 트랜지스터의 채널 저항을 BJT의 출력 저항보다 훨씬 작게 만들 수 있기 때문이다. 하지만, 이것 역시 출력 전류가 커지면 채널간 전압 강하도 커지기에 Rail-to-Rail의 목표에서 벗어난다. 현재까지는 RRIO 방식이 최대의 출력 신호 진동폭을 보장하기에, 특히, 저전압 시스템에서는 RRIO 방식을 채택한 Op Amp.가 사실상 필수 선택이다. 그렇지 않을 경우, 신호의 진동폭이 제한되어 버려서 비싼 돈주고 고른 ADC등에서 분해능 손실을 톡톡히 치르게 된다. RRIO, 꼭 명심하자.

마지막으로 다음 그림을 보고, RRIO 방식 이해를 마무리 짓도록 하자. 그림 2-1의 회로와 동일한 회로를 꾸며놓고, V+에는 3.3V를 걸고, V-에는 0V를 걸었다. – 입력단에는 0.165Vp, 1KHz 정현파를 인가하였다. + 입력단에는 적절한 DC 오프셋(Offset) 전압을 인가하여, 출력 신호가 1.65V를 기준으로 진동하도록 만들었다. DC 오프셋을 인가하는 방법은 4.8절에서 자세히 다루니 지금은 단순히 잘 인가했구나 라고 생각하자. 그림 2-1 회로의 증폭비는 10배이다. 이상적인 Op Amp.라면, 1.65V를 중심으로 1.65Vp의 정현파가 출력 되어야 한다. 다음 그림을 잘 보자. 좌측이 LM2902 출력이고, 우측이 TLC2274의 출력이다.

20feasync0 (14)

그림 3-4. LM2902와 TLC2274의 출력 비교

먼저, 그림 3-4의 좌측 파형은 LM2902에서 채취한 것인데, 최대 및 최소치가 각각2.31V와 0.07V 이다. 중심이 1.65V이다보니, 상향으로는1V를 상승하지 못하는 부실한 출력을 보여주지만, 하향으로는 0.07V까지 내려올 수 있기에 좋은 결과를 보여준다. 하반구 신호만 사용해도 충분한 시스템이라면 LM2902가 상당히 좋은 성능을 유도할 수 있겠다. 그림 3-4의 우측은 TLC2274의 출력으로, 비록 아래위가 좀 뭉그러졌지만, 1.65V 기준으로 아래위 대칭적으로 잘 진동하는 것을 볼 수 있으며, 좌측과 확연히 비교가 된다. 최대 및 최소치는 각각 3.13V와 0.17V다. 1.65V를 중심으로 거의 +/-1.5V를 진동할 수 있기에, 3.3V 단일 전원 시스템에서는 거의 최고 수준의 진동폭을 보여준다고 할 수 있겠다. LM2902의 진동폭은 2.24V(=2.31-0.07)이고, TLC2274의 진동폭은 2.96V이다. 만약, 0~3.3V의 입력범위를 가지는 16비트 ADC를 LM2902로 구동한다면, 입력 범위의 67.9%밖에 사용하질 못한다. 비싼 돈 주고 산 ADC가 제 역할을 하지 못하는 경우다. TLC2274의 경우는 약 89.7%를 사용한다. 그나마 값도 좀 저렴하고 해서 3.3V 저전압 단일 전원용으로 TLC2274가 많이 쓰이고 있는 것이다. 그림 3-4는 RRIO 방식과 비 RRIO 방식을 비교해주는 적절한 사례라고 하겠다.

4. CMRR과 PSRR

먼저 CMRR부터 얘기해보자. CMRR은 Common Mode Rejection Ratio의 앞머리글자다. 한문으로 옮기자면 동상신호제거비율 정도라고 할 수 있겠다. Op Amp.는 기본적으로 차동 증폭기이다. 차동이라함은 Op Amp.의 두 입력단에 인가되는 신호의 차이를 말한다. 이 차이를 증폭함과 동시에 그 차이가 없는 동상 신호, 즉, 두 입력단에 공통된-Common Mode- 신호는 감쇠시킨다. 그 감쇠비율이 CMRR이다. CMRR이 높을수록 좋은 차동증폭기가 된다. 예를 들어, CMRR이 매우 높다면, 두 입력단에 동일하게 유입되는 외부 노이즈는 별도의 필터없이 높은 비율로 걸러 내어진다. 멋지지 아니한가? 혹은 두 입력단에 동일하게 유입된 DC 전압 이라던지, 혹은 60Hz 전원에 의해 발생한 노이즈 등은 별도의 필터링 과정없이 Op Amp. 특유의 성질인 CMR(Common Mode Rejection) 기능에 의해 제거 된다는 것인데 대단하지 아니한가? 그림 4-1은 TLC2274제품 설명서에서 CMRR그래프를 옮긴 것이다.

20feasync0 (15)

그림 4-1. TLC2274의 CMRR 그래프

제품 설명서에서는 CMRR이 통상 75dB라고 밝히고 있다. 그래프를 보면, +/-5V, 즉, 양전원을 이용했을 때의 CMRR그래프가, +5V 단일전원을 사용했을 경우보다 약 5dB 가량 높다는 것을 확인할 수 있다. LM2902의 CMRR은 80dB로 나와있다. LM2902가 5dB 가량 우수하다고 할 수 있을텐데, 과연 그럴까? 5dB라 함은 LM2902가 동상 신호를 TLC2274에 비해서 약 1.78배 더 억제 시킨다 라는 것이기에 이는 상당한 수치다. 하지만, 여기에 혹하면 안된다. 그래서 이 책과, 이 절이 준비된 것이다. CMRR은 주파수에 대한 함수로 나타난다는 점을 명심해야 한다. 그림 4-1을 보라. CMRR의 코너 주파수는 대략 10KHz로 보이고, 감쇠비율은 대략 -20dB/decade 로 보이는데 실제로 그러하다. LM2902의 CMRR 그래프는 제품 설명서에 없어서 못 옮겼는데, 대개 비슷한 패턴을 보인다. 다만, 코너 주파수가 다르기에 코너 주파수가 높을수록 좋은 CMRR 특성을 보이는구나 라고 판단 해야 한다.

CMR 기능을 정확하고 손쉽게 이해하기 위해서는 트랜지스터 레벨에서 차동 증폭기를 살펴봐야 하는데, 트랜지스터에 대한 기초적인 부분까지 설명하려니 양이 너무 많아지는 것 같고, 책 주제를 벗어나는 것도 같아 갈등을 많이 했다. 그렇다고, 트랜지스터의 도움없이 설명을 하자니 능력이 모자라서 진도가 나가지 않기에 부득이 트랜지스터의 힘을 조금 빌렸다. 이 점 깊은 양해를 구한다. 트랜지스터에 대해서는 최대한 간단하고 단순하게 다루고, CMR에 초점을 맞추어서 설명하고자 하니 페이지 넘기지 마시고 계속해서 읽어 나가봐 주시길 바란다. 동상 신호가 제거되는 원리만 살펴 본 후, 다시 Op Amp.로 즉시 넘어오니 채널고정이 아닌 책 고정 해주시면 감사하겠다.

Op Amp. 는 대게 30 ~ 40개의 트랜지스터로 구현된다. Op Amp의 첫번째단은 입력단으로 차동 증폭기(Differential Amplifier)로 구현되어 있다. 그러기에 입력 핀이 +/- 2개이다. 그 다음 증폭단도 차동 증폭기로 구현되어 있는 것이 대부분이며, 마지막 단은 단동(Single Ended) 증폭기로 구성되는 것이 일반적이다. 마지막 출력단까지 차동 증폭기로 이뤄져 있으면서 차동으로 출력되는 증폭기를 Fully Differential Amplifier라고 하는데, 이는 9절에서 자세히 다룬다. 각 단에 설계된 차동 증폭기가 CMR 기능을 제공하고 있는데, 트랜지스터로 구성된 차동 증폭기를 그림 4-2에서 보도록 하자.

20feasync0 (16)

그림4-2. FET로 구현된 차동 증폭기

그림4-2의 회로는 현재 동작이 잘 된다라고 가정을 하자. 이를 Bias가 제대로 잡혔다 라고 말한다. Q1과 Q2는 Field Effect Transistor, 줄여서 FET라고 한다. Q1과 Q2 의 심볼을 보면 화살표가 점선을 가르키고 있는 것을 볼 수 있다. 점선은 몸통을 표시하는데, 이게 점선인 이유는 특정 조건이 되면 이 점선이 이어져서 전류가 흐르는 채널이 형성되기에 그렇다. 화살표는 반도체 극성을 말한다. 정식으로 쓰자면, Enhanced Metal Oxide P-type Semiconductor Field Effect Transistor 인데, 이 정도까지 알고 계신다면 이미 훌륭한 상태이신 것이고 모른신다고 한들 전혀 상관없다. Vo1과 Vo2가 인출된 트랜지스터의 다리를 드레인(Drain)이라고 하는데 이것도 몰라도 된다. VG1과 VG2가 인가된 핀을 게이트(Gate)라고 하고, I 라는 전류원이 연결된 핀을 소스(Source)라고 하는데 전혀 몰라도 된다. 그저, FET라는 트랜지스터는 Gate 에 인가되는 전압에 의해서 드레인-소스간에 흐르는 전류량이 제어되는 구나 라고만 알아두면 더 이상도 필요없을 만큼 적당한 수준이지만, 이것마저도 몰라도 된다. 머리속에서 회로를 한번 구동 시켜보자. VG1이 변하면, Q1의 드레인-소스간 전류가 변한다. 즉, R1에 흐르는 전류가 변한다. R1을 통해서 Id1 이라는 전류가 흐른다고 하자. 그렇다면 Vo1 전압은 다음과 같다.

20sync001
수식에서 보듯이, Q1이 어떻고 드레인이 어떻고, 게이트가 어떻다는 말이 전혀없다. 참고로, MOSFET에서 Id1은 VG1과 2차 함수 관계가 있고, 기하학적 구조, 즉, 트랜지스터 면적에 비례하고 채널 길이에 반비례 관계가 있다. 이것까지 알면, FET 가 순한 양으로 바뀔 것인데, 우리는 Op Amp를 사용하기에 이런 내용이 전혀 필요 없다. 동일한 방식으로 Vo2를 구하면,

20sync002

이다. 일단, VDD를 공통으로 사용하고 있는데, 반도체 정밀 공정 덕에 Q1과 Q2의 면적 등이 모두 동일하고, 도핑 농도까지 모두 동일하며, R1과 R2가 동일하다면 (실제 CMOS 공정에서는 R1, R2대신에 트랜지스터로 부하를 만들어서 더 높은 이득을 얻고 있다.), 정확히 V01==V02이다. 의심의 여지는 전혀 없다. 그렇다면, V02-V01는 얼마인가? Q1과 Q2의 양단에서 출력의 차이를 취하다 보니, Id1==Id2라면, 즉, 트랜지스터의 동일한 입력 신호는 상쇄되어버리는 결과를 얻게 된다. 즉, 동상 제거 능력이 생기는 것이다. 그런데, 어떻게 Q1과 Q2가 똑같겠는가? 뭐가 달라도 조금씩 다르지 않겠는가? 게다가 반도체 속성상 어쩔 수 없이 발생하는 기생 커패시턴스 성분과 증폭기의 주파수 보상 문제 때문에 어쩔 수 없이 장착해야 하는 커패시터 때문에 CMRR이 주파수 함수로 나타난다. 어쨌든, 차동 증폭기를 입력단으로 사용하는 모든 Op Amp.는 동상신호제거능력을 가지고 있다. 이제 Op Amp.로 넘어와서 동상신호 제거능력을 관찰해보도록 하자.

그림 4-3의 Op Amp. 회로를 보도록 하자. Op Amp.로 구현한 Difference Amplifier 회로이다. 번역하자면 차분증폭기다. 차동 증폭기가 아니라, 차분 증폭기. 말장난같지만, 둘은 엄연히 다르다라고 주장하시는 분들이 많지만, 그다지… 입출력관계를 구해보면 두 입력단의 차가 증폭되기에 Difference라는 말이 붙었는데, 차동 증폭기와 동작원리는 동일하니 이름에 현혹되지 마시길 바란다. 굳이 이름을 달리한 기준을 말하자면, 입력 값이 중앙값을 기준으로 동일한 수준의 차이를 보이는 경우를 차동 증폭기라고 한다. 단순히, 그냥 차이를 증폭하면 차분증폭기라고 하는데, 뭐 어떠랴 같다고 보자. 회로도 사실상 같다.

20feasync0 (17)

그림 4-3. 차분 증폭기

Vo를 구해보실 수 있겠는가? Op Amp. 회로에서 입력신호원이 2개 이상일 때에는 중첩의 원리를 적용하면 정말로 쉽게 풀린다라는 점 떠올리셨는가? 한번 풀어보자. V1에 의한 출력을 구하려고 한다면, V2의 영향을 없애야 한다. 전압원이니 단락(Short)시키면 된다. 따라서, 다음의 회로로 바뀐다.

20feasync0 (19)

그림 4-4. V2 영향을 배제한 회로

그렇다면, Vo는? 쉽게 구할 수 있다. 왜냐면, Op Amp.의 입력 전류는 0 이기에 R3와 R4의 영향은 없다. 그냥 접지와 바로 연결된 것과 같다. 흔히 보는 반전 증폭기 회로가 된다. 따라서, Vo는,

20sync003
이다. 이제 V2에 대한 Vo를 구해보자. 회로는 다음과 같이 변한다.

20feasync0 (20)

그림 4-5. V1 영향을 배제한 회로

흔히 보는 비반전 증폭기다. 사실 비반전 증폭기라는 말도 웃긴다. 그냥 증폭기라고 하면 될 것을 말이다. 비반전 증폭기라는 말을 쓰면, 그냥 ‘증폭기’는 뭔가? 그냥 증폭기와는 다른 증폭기인가? 하고 오해를 부를 수 있기 때문이다. 혹시나 헷갈릴까봐 이런 용어를 쓰신 것 같다. 증폭기는 반전 증폭기와 비반전 증폭기로 이뤄져 있다고. 이제부터 그냥 증폭기라고 하면 입력과 출력이 동위상인 일반적인 증폭기, 즉, 비반전증폭기를 의미하는 것으로 이 책에서는 규정하겠다. 전달함수를 구해보자. 3장에서 열심히 구했었는데, 기억이 나실런지 모르겠다. 이러한 형태의 증폭기 전달함수를 구하는 쉬운 방법은 Op Amp.의 또 다른 중요한 성질 중에 하나인 두 입력단의 전압은 같다 라는 성질을 떠올리시면 좋겠다. 즉, 두 입력단의 전위차는 늘 0 이다 라는 점을 이용하는 것이다. 즉, 그림에서 Vn 과 Vp 는 같다라고 관계를 설정하고 풀면 된다.

 

20sync004
중첩의 원리를 이용해서 수식 (4-1)과 합치면,

20sync008
이 된다. 이 수식이 V0=A(V2-V1)형태의 차분(혹은, 차동) 증폭기가 될려면, R4=R2, R3=R1로 설정하면 된다. 이렇게 설정하면, 수식 4-1은 다음과 같이 이쁘게 변신한다.

20sync006
즉, V2와 V1의 차이를 증폭하는 회로가 되는 것이다. 그런데, V2와 V1는 다음과 같이 동상전압과 이상전압의 합성된 형태로 표현할 수 있다.

20sync007
위 수식에서 VCM은 공통 성분, VDM은 서로 다른 성분을 말한다. 이 수식을 토대로 그림 4-3 회로를 그림 4-6처럼 바꿔 볼 수 있겠는가?

20feasync0 (21)

그림 4-6. 입력을 동상 신호와 이상 신호로 구분한 회로

V1에 쪽에 달린 신호원에 음의 기호를 할당한 것은, V1 과 VDM/2의 관계가 반전이기 때문에 그런 것이다. 그림 4-6에서 Vx와 Vy의 전압이 VCM만큼은 서로 동일하기에 한데 묶을 수가 있다. 그렇다면, 그림 4-7과 같이 된다. 등가 회로의 원리를 잘 떠올리시면 된다.

20feasync0 (22)

그림 4-7. 그림 4-6 등가 회로 : VCM을 공통으로 처리

그림 4-7처럼 신호원을 쪼개서 그린 이유는, 동상 신호와 이상 신호의 관계를 쉽게 파악할 수 있기 때문이다. 먼저, ±VDM/2=0이라고 해보자. VCM이 바뀐들 VO에 무슨 영향이 있을까? 즉, 동상 신호는 Op Amp.의 기본 속성에 의해서 아주 손쉽게 걸러 내어진다. 이것이 CMR(Common Mode Rejection)이다. CMR 기능이 이해되시는가? 그런데 주의할 점이 하나 있다. 실전에서는, Op Amp. 뿐만이 아니라 저항에도 오차가 존재한다는 점이다. Op Amp.도 이상적이지 못해서 속상한데 저항까지 말썽이다. 사실은 저항이 더 큰 영향을 미치기에 다음 내용이 준비된 것이니 계속 읽어나가 주시길 바란다. 실제로는, Op Amp. 자체의 CMRR과 외부 결선까지 포함한 CMRR 중에 낮은 CMRR이 전체 CMRR을 결정한다. 따라서, 이미 Op Amp.를 선정했다면, 외부 결선으로 인한, 즉, 저항 등의 오차로 인한 CMRR 저하는 결단코 피해야 한다. 그림 4-8 회로는 저항 오차를 감안하여 꾸민 회로이다. 오차가 존재하는 저항으로 차분 증폭기를 구현했을 경우인데, 계산 편의상 오차가 한 저항에서만 발생했다고 가정하자. 그 영향을 이해한 후에 모든 저항에 오차가 발생하면 어떻게 될까 하는 것을 고찰하는 것으로 충분하다.

20feasync0 (21)

그림 4-8. 저항 오차로 인한 CMR 영향

그림 4-8은 Op Amp.의 궤환(Feedback) 경로에 위치한 R2 저항에 오차가 -εR2만큼 발생했다고 가정한 것이다. (TINA에서는 ε가 표시가 안되어서 e로 표시했음을 양해바란다.) 입출력 관계식을 구해보자. 수식 4-2를 이용하면 편리하겠다. 수식 4-2는 다음과 같다.

20sync008
이 수식에서, R4=R2, R3=R1이고, R2가 R2(1-ε)임을 주의하시기 바란다. 그리고, V1=VCM-VDM/2이고 V2=VCM+VDM/2이다. 위 수식에 각각 대입하면,

20sync009
가 된다. CMRR(Common Mode Rejection Ratio)를 구하기 위해서, 그림 4-8의 전달함수를 다음과 같은 식으로도 구해보자.

20sync010
중첩의 원리를 약간 다르게 적용한 수식이다. 즉, 입력 신호중에 동상신호는 동상신호 증폭비율만큼 증폭되고, 이상신호는 이상신호 증폭비율만큼 증폭될 것이니, 각각을 구하여 중첩하면 원하는 결과가 나온다 라는 것을 반영한 수식이다. 수식 4-3을 수식 4-4처럼 재구성해보자. 이렇게 하면, CMRR 구하기가 쉽다. 왜냐면, CMRR의 정의가 다음과 같기 때문이다.

20sync011
(4-4-2) 수식을 정리하여, ACM과 ADM을 구하면,

20sync012
이다. 전혀 어렵지 않게 유도할 수 있다. 수식이 너저분하다고, 전개과정마저 너저분하겠지 하는 것은 오판이다. 매우 깔끔한 편이니 꼭 해보시길 바란다. 수식에 대한 트라우마로 인하여 책을 덮어버리는 불상사는 없기 바란다. 전개과정이 무척 쉽다. 위 수식에서 ACM과 ADM은 각각 다음과 같다.

20sync013

20sync014
CMRR의 정의에 대입하여 CMRR을 구해보면,

20sync015
이다. 이 수식은 그다지 아름다워 보이지 않는다. 죄송하다. 수식도 복잡하고 마음도 복잡하다. 계산기에 입력해두고 사용하면 된다고 할지라도, 입력하는데 상당한 시간이 걸리고, 오입력의 경우도 즐비하겠다. 그래서 위 수식을 아름답게 만들어 보자. 적당한 오차를 허용하여 간략화시키는 것이다. 엔지니어링은 이런 것이 맛이 아닌가? 먼저 ADM을 살펴보자.

20sync014
ε가 1% 이하 수준이라면20feasync02 이라고 할 수 있다. 아름답다. 오일러 할아버지가 아름답다고 한 숫자 중에 으뜸이 1이다. 이걸 적용하여 다시 CMRR을 구해보자.

20sync017
어떻게 좀 간략해졌는가? 이 수식이 의미 하는 바는 무엇인가? 저항간의 오차가 작을수록 CMRR은 커진다. 저항의 오차가 없으면 CMRR은 무한대인가? Op Amp. 자체의 CMRR보다는 커질 수 없음을 잘 아셔야 한다. 또한, 차분 증폭기의 증폭 비율이 커지면 커질수록 CMRR은 커진다. 지당한 얘기이겠다. 예를 들자면, 오디오의 볼륨을 높일수록 동상 노이즈 제거는 더 잘된다는 의미이다. 하지만, 1/f 노이즈라든지 다른 노이즈가 커진다는 점은 함정이니, 귀로 잘 듣고 이퀄라이져도 동원하여 본인 자신이 듣기 좋은 상태로 조정하셔야 한다.

내용이 많이 길어졌지만, CMRR에 대해서는 아무리 강조해도 지나치질 않는다. 그래서, 좀 더 CMRR에 대해서 공부할 필요가 있다. CMRR은 Op Amp.가 제공해주는 축복이다. 이걸 잘 이해하고 있으면 노이즈 억제 회로 설계에 큰 도움을 얻을 수 있다. 간단한 실험 두 가지를 해 보자. 첫번째 실험은 CMRR을 구하는 실험이다. 이상적인 Op Amp.에 이상적인 저항을 연결하여 그림 4-9와 같은 차분증폭기를 꾸미고, 입력 양단에 동일전압 DC 1V를 인가한 후 출력을 확인하여 CMRR을 구하는 것이다.

20feasync0 (24)

그림 4-9. CMRR 테스트 회로

그림 4-9처럼 이상적인 Op Amp.로 회로를 꾸민 후 TINA에서 제공하는 멀티미터 기능으로 출력단 전압을 측정해보자. -1.11E-16V가 측정 된다. 작아도 엄청 작다. 의심하시는 분이 계실까봐, 멀티미터 측정 화면을 그림 4-10에 옮겼다.

20feasync0 (25)

그림 4-10. 그림 4-9회로에서 DC1V 동상 입력시 출력 전압

따라서, ACM=-1.11E-16V이다. 이제 V1에 DC1V 그리고 V2에 DC2V를 인가해서 ADM을 구해보자. 출력이 정확히 2V 나온다. 따라서 ADM=2이다. 정의에 따라, CMRR을 구해보자. *주의. 오차나 증폭비를 대입할 때에는 절대값을 넣어야 한다.

20sync018
와우! 굉장하다. 구할 때 사실 이미 답이 나왔다. 동상 DC1V 를 인가하니, 출력이 0.111 fV (Femto V) 이다. 펨토(Femto)는 10의 -15승이다. 시뮬레이션이니까 측정가능한 수치이므로, 사실상 0다.

이상적인 Op Amp. 말고 TLC2274와 LM2902를 사용하면 어떨까? CMRR을 동일한 방식으로 각각 구해보자. TLC2274에서는 63.6dB가 나오고 LM2902는 50.0dB가 나온다. 앞서 언급한 제품 설명서의 내용과 많이 다르다. 그 이유는 이 실험이 Op Amp. 자체가 아닌, Op Amp.를 이용한 차분 증폭기를 꾸몄기 때문이다. 하지만, 의외의 결과가 나왔다. 제품 설명서상의 CMRR은 분명 LM2902가 더 컸는데도 불구하고, 차분 증폭기의 CMRR은 TLC2274가 무려 4배이상 (13.6dB) 이상 좋게 나왔다. 왜 이럴까? TINA 오류건 제품 설명서의 오류건 둘 중 하나일텐데, 우리는 Spice 엔진을 사용하는 TINA 결과를 믿고, 즐거운 엔지니어 생활을 영위하도록 하자.

이제 두번째 실험이다. 그림 4-9에서 R2 저항이 1% 오차를 보일 때와 5% 오차를 보일 때, CMRR이 각각 어떻게 변하는지 관찰해보자. TLC2274 선수가 이번 실험에도 수고를 해줬다. 입력 조건은 이전 실험과 동일하다. 1% 오차 실험을 위해R2를 19.8K옴으로 설정했다. 측정결과, CMRR은 51.4dB이다. 많이 떨어졌다. 수식 4-4로 구한 CMRR은 49.5dB이다. 잘 맞아 떨어진다. 이제는 저항 오차를 5%로 설정하고 구해보자. R2를 19K옴으로 정했다. CMRR이 35.8dB 수준으로 훅 떨어졌다. 수식4-4로 구한 값은 35.6dB다. 역시 잘 맞아 떨어진다. 비싼 Op Amp. 사다 놓고, 1원 안되는 돈 아낄려고 오차가 큰 저항을 사용했다가는 Op Amp.의 복음이 널리 전파되는 놀라운 경험을 놓치게 된다. 저항에 돈 좀 쓰자. 그러나 저러나, 심각한 문제가 하나 생겼다. 부품 중에서 가장 단순한 저항을 고르는 작업이 이제는 더 이상 단순한 작업이 아니다. 큰 일이 될 수도 있는 심각한 순간을 여러분은 맞이하고 있는 것이다. 심각하다. 저항을 아무렇게나 가져다가 쓸 일이 전혀 아니다. 어쩌나? 이럴 때, 우리 선배님들께서, 동료 분들께서, 혹은 후배님들께서 나서서 잘 풀어주셨다. 돈만 조금 더 쓰시면 된다. 간혹 R4 위치에 가변저항 (Potentionmeter) 을 달아서 문제를 해결하려 드는 경우도 있는데 필자는 극구 반대한다. 가변저항도 돈이다. 정밀 가변 저항은 더 돈이다. 게다가, 대량 생산하는 품목이라면 어떻게 일일이 조정할 것인가? 게다가 시간에 따라서 그 값도 변하고, 진동 등에 의해서도 값이 바뀌는데 그때마다 조정할 것인가? 정밀 가변 저항 살 돈으로 조금 다른 Op Amp.를 사용해보자. 대부분의 아날로그 반도체 회사들은 이런 문제에 대해 적극적으로 대응하고 있다. Texas Instrumentd 사의 홈페이지인 ti.com에 가보면 ‘Amplifier and Linear’ 라는 제품 카테고리를 발견할 수 있다. 하위 카테고리를 보면, 그림 4-11처럼 ‘Difference Amplifier’항을 발견할 수 있을 것이다.

 

20opamp027 20opamp028
그림 4-11. ti.com에서 찾은
Difference Amplifier 메뉴
그림 4-12. INA2132의 내부 구조

글 쓰는 현재, 총 25종이 Difference Amplifier 범주에 포함되어 있다. 저렴한 제품으로 INA2132가 검색된다. TINA에는 INA2132E라는 라이브러리가 마침 있다. 실험이 가능하니 이 제품을 좀 더 자세히 보도록 해보자. 제품 설명서를 보면, 그림 4-12같은 구조도를 처음 페이지에서 확인할 수 있다.
그림 4-12를 보자니, Op Amp. 2개가 보이고, 40K옴 저항이 내장되어 있음을 확인할 수 있다. 그렇다. 정밀/정확 저항을 정밀/정확 반도체 제조 공정에서 구현한 것이다. 이 부분이 다른 Op Amp.와 다른 부분이다. 그림 4-12에서 Sense 핀을 Out 핀에 물리고, Ref 핀을 접지로 만들면 이득이 1배인 차분 증폭기가 만들어 진다. (이 핀들을 왜 만들었는지는 잠시 후 살펴 보기로 하고 지금은 CMRR에 초점을 맞춰서 보자.) 따라서, Sense핀을 출력핀에 연결하고 Ref 핀을 접지에 연결한다고만 우선 받아들이자. 제품 설명서에는 CMRR이 90dB로 나온다. 이 말만 믿으면 안된다. CMRR은 주파수 함수이기에 평탄 대역이 어느 정도되는지 그래프로 살펴봐야 한다. 제품 설명서에서 CMRR 그래프를 발췌했다.

20opamp029

그림 4-13. INA2132의 CMRR 그래프

안타깝게도 10KHz 부근에서는 70dB이하로 나온다. TINA로 앞서 행한 첫번째 실험과 동일한 실험을 해보았다. 동상 DC1V를 인가하니 출력이 27.85uV로 측정된다. CMRR은 91.1dB로 측정된다. 이득이 1배에 불과하지만, CMRR이 무려 91.1dB이다. 대략 낮은 수백 Hz까지의 신호에 대해서는 이런 성능이 유지된다. 대역폭이 아쉽다. 하지만, 이 정도의 CMRR 대역폭은 우수한 편에 속한다.

비싼 제품을 한번 골라보자. INA330이 US$2.6 으로 꽤나 비싸다. 그것도 Op Amp.가 하나 들어 있는데 말이다. 그런데 TINA용 라이브러리가 없다. TINA용 라이브러리가 있으면서 가장 비싼 제품으로는 INA157이라는 제품이 검색된다. 살펴보자. CMRR이 86dB에 그친다. 그런데 왜 비싼걸까? 속도가 매우 빠르고, 기타 다른 지표들이 좀 더 우수한 듯하고, 나온지가 제법 되어서 비싼 듯도 하다. 다음은 내부 구조도다.

20opamp030

그림 4-14. INA157 내부구조

이득을 결정하는 저항이 12K와 6K이다. Sense 핀에다가 V1을 입력하고, -In을 Output에 연결하고, +In 핀을 접지에 연결한 후, Ref에다가 V2를 입력하면 이득 2배짜리가 된다. 반대로 하면 1/2배가 된다. 재미지구나. 이득을 2배로 설정하고 TINA를 이용해 CMRR을 구해보면 91.2dB를 얻을 수 있다. 비록 제품 설명서 상으로는 INA2132가 우세했지만 실제 실험에서는 INA157이 이득을 2배로 설정할 수 있기에, 좋은 결과가 나온 것도 같다. 참고로 TINA에서 Difference Amplifier 모델을 추가하려면 다음의 아이콘을 눌러야 한다.

20opamp031

그림 4-15. TINA에서 차분증폭기 모델 추가하기

그런데, INA157이 왜 비쌀까? 비싸면, CMRR도 좋아야 할 것 아닌가? 그렇다. CMRR 그래프를 보도록 하자. 주파수에 신경을 바짝 세워서 그래프를 주시해주시기 바란다.

20opamp032

그림 4-16. INA157의 CMRR 그래프

거의 70 KHz까지 CMRR이 유지된다. 이런 Op Amp. 잘 없다. 따라서, 값싼 INA2132를 사용할 것인가, 비싼 INA157을 사용할 것인가 하는 문제는 다루는 신호의 대역폭에 따라 결정되는 것이다. 만약 HiFi 오디오를 설계한다면, 당연 INA157이다. 회로에 외부에서 유입되는 노이즈가 많다면 당연히 INA157이다. 그런데, 상대적으로 너무 비싸다. 형편에 맞게 잘 선정하시길 바란다.

실제 부품을 예로 들어가면서까지 CMR 기능에 대해서 살펴보았다. 무엇보다도, Common Mode Rejection 기능을 잘 사용하는 것이 Op Amp.를 제대로 사용하는 것이라고 말할 수 있다. 그래서 이 절이 이토록 긴 것이다. CMR 기능을 잘 사용하려면, 입력을 차동으로 받아들여야 한다. 입력이 단동(Single Ended) 일 경우에는 CMR 기능을 활용하기가 어렵다. 차동이 아니면 차분이라도 하는 것이 좋다. 그래서, Op Amp.의 두 입력단으로 연결되는 각각의 선을 최대한 가까이 붙이고 동일한 길이와 동일한 임피던스 성분으로 구성한다면 신호 전송선에 유입되는 외부 노이즈는 동상일 가능성이 높기에 걸러낼 수 있다. 단순히 동일한 두 신호선만으로 동상 성격의 노이즈를 걸러 내는 행위는 고성능 아날로그 회로를 설계하는데 있어서 기본 중의 기본이다. 그림 4-16을 다시보자. 200KHz에서 CMRR이 약 80dB이다. 별 다른 필터를 사용하지 않아도, 입력 양단에 유입되는1V 크기의 노이즈는 0.1mV로 쪼그라들어 버린다. 오로지 Op Amp.를 제대로만 사용했다면 기대할 수 있는 대단한 노이즈 억제 능력인 것이다. 좋지 아니한가? 비싼 돈 주고 산 Op Amp.를 놀릴 것인가? 어렵게 생각할 필요없다. 좋은 저항으로 대칭을 잘 이뤄서 증폭기를 설계하면 높은 CMRR을 기대할 수 있다. 그래서, Op Amp.가 더더욱 좋은 것이고, 디지털 회로 세상에서도 Op Amp.의 위상은 더욱 더 공고해지고 있는 것이다.

이 정도로 해서 CMRR을 마무리 짓고, 다음 주제인 PSRR로 넘어가자. 끝 글자들이 RR로 끝나기에 라임이 잘 맞아 떨어져서 힙합가사라도 쓰나 하시겠지만, 같다고도 볼 수 있고 다르다라고도 볼 수 있는 RR이다. PSRR는 Power Supply Ripple Rejection Ratio 이다. 이렇게도 쓴다. Power Supply Rejection Ratio. 전자가 더 명확하고, 따라서, PSRRR이 맞지만 CMRR과 운율이 안 맞기에 탈락시킨듯 하다. 운율맞게 우리도PSRR 이라고 하자.

PSRR을 측정하기 위해서, Op Amp. 전원핀에 60Hz, 혹은 120Hz 정현파를 공급 전압에 섞어서 공급한다. 출력단에서 60Hz 혹은 120Hz 정현파 성분을 측정한 후 dB로 표현한 것이 PSRR이다. 60Hz나 120Hz는 교류 전원을 의미한다. 간단한 실험을 한번 해보자. TLC2274로 2배짜리 차분 증폭기를 그림 4-9처럼 꾸민 후, V+ 단자에 5V를 공급한다. Ripple을 구현하기 위해서, 5V DC 전원에 + 1Vpp, 60Hz 정현파를 더해서 공급한다. 양 입력단에는 DC1V를 인가한다. 그리고 오실로스코프로 출력을 관찰한다. 출력 파형에서 관찰되는 리플의 Vpp를 구하여 60Hz, 1Vpp 정현파와 비를 구한 후, 20log를 취하면 PSRR 이 얻어진다. TINA로 시뮬레이션을 해보니, 출력으로 약 140uVpp, 60Hz 정현파가 관찰된다. 따라서, PSRR은 77.1dB 이다. 상당히 우수하다. 따라서, Op Amp.를 사용하신다면, 60Hz 전원 노이즈로 인한 걱정은 접어두셔도 되겠다. 하지만, Op Amp. 앞 단에 저항이나 커패시터가 접지나 전원단에 직접 연결된 회로가 존재한다면 이것은 또 다른 문제다. Op Amp.의 입력 신호에 전원 노이즈가 섞여서 들어온다면, 5장에서 배울 Notch 필터로 제거해야 한다. TLC2274의 제품 설명서에 명시된 PSRR은 통상 95dB 수준이고 최저 80dB 수준이다. 제품 설명서에는 측정 환경에 대한 언급은 없다. TINA 실험은 일반적인 사용 예를 가지고 행한 것이기에 실질적으로는 TINA로 행한 실험이 적용될 것이다. 제품 설명서에서 명시한 수치에 비해 모자라지만, 77dB라는 수치는 결코 나쁜 수치가 아니다. 참고로, 이득이 높아지면, 출력에 나오는 전원 노이즈 성분도 커지기에 PSRR 수치는 낮아진다는 점을 유의해야 한다. 그렇다면, 전원단에 광대역 노이즈가 유입되고 있다면 어떨까? PSRR에 관한 그래프도 제품 설명서에 나와있으니 유심히 보시기 바란다. 역시 주파수에 대한 함수이다. 다음은 TLC2274의 PSRR 그래프이다.

20opamp033

그림 4-17. TLC2274의 PSRR

안타깝게도, 전원노이즈 제거 대역폭이 그다지 넓지 않다. 특히, 음의 전원 쪽 PSRR의 대역폭이 많이 좁다. 이를 극복하는 별다른 방법은 없다. 구현하고자 하는 시스템의 전원단을 잘 설계해야 할 것이며, Op Amp. 전원 핀에 바이패쓰(Bypass) 커패시터를 잘 달아주는 것이 거의 최선의 방법이라 하겠다.

다음 시간에는 5. Loading Effect (부하의 영향)
6. Instrumentation Amplifier (정밀 기기용, 계측용 증폭기)
7. Op Amp.의 Sense 핀과 Ref. 핀의 사용법에 대하여 살펴보도록 하겠습니다. 

 

[20호]MSP430용 절연 JTAG 에뮬레이터(MSP-SDS100i) 출시

싱크웍스 01

싱크웍스 01

(주)싱크웍스에서 MSP430 전용 절연JTAG 에뮬레이터(MSP -SDS100i)를 출시했다.
MSP-SDS100i는 Texas Instruments사의 초저전력 MCU인 MSP430 프로세서용 하드웨어 개발장비이다. PC에 설치된 소프트웨어 개발 툴, Code Composer Studio(CCS)와 MSP430 프로세서를 연결하여, 사용자가 프로그램을 프로세서의 메모리 공간에 기록(Load)하거나 프로세서의 메모리/레지스터 영역의 데이터를 관찰/디버깅할 수 있다. 특히, MSP430 전용 절연 JTAG 에뮬레이터는 표준 14핀 JTAG 인터페이스를 절연 설계하여, PC와 프로세서 사이를 전기적으로 분리시켜 주며, 보다 안전하고 신뢰성 높은 개발 환경을 구성할 수 있다. 미려한 케이스로 제작되어 사용 및 보관이 용이하며, 1년간 무상 A/S 및 대체품 서비스를 지원한다. 보다 자세한 사항은 디바이스마트 홈페이지를 통해서 확인할 수 있다.

■ 제품 주요 특징 ■

· 절연 JTAG 인터페이스 설계로 PC와 프로세서를 전기적으로 분리 (전원단 절연 1,000Vrms, 신호단 절연 ,500Vrms(Galvanic Isolation)
· 별도의 드라이버 설치없이 PC에서 자동으로 인식 (단, CCSv5가 우선 설치된 환경에 한 함)
· USB 버스 전원 사용 (별도 전원 불필요)
· USB High-speed (480Mbits/s) 지원
· CCSv5 무료 이용가능 (코드 사이즈16KB 이하 일 경우)
· 타겟 보드에 별도의 전원을 공급하지 않더라도, MSP-FET430S에서 1.8 ~ 3.6V의 전원 공급 가능
· MSP-FET430S의 펌웨어는 CCS에서 자동으로 업데이트
· JTAG Security Fuse Blow 기능으로 사용자 코드 보호 가능
· 4-wire / 2-wire 방식 모두 지원
· 에뮬레이터 동작 표시용 LED 탑재 (USB 연결 / 본체 전원 / 타겟)

제품구매하기

TEL. 031-781-2812
www.tms320.co.kr

 

[20호]JK전자와 함께하는 ARM 완전정복(6)-1

jk전자 썸네일20-1
jk전자 JK전자와 함/께/하/는 ARM 완전 정복 

Ⅳ. Cortex-M3 Applications

 

글 | JK전자

ARM Architecture에서 많은 내용을 다루어서 Cortex-M3의 이론적인 부분은 그나마 짧게 끝난 것 같습니다. 이번 파트에서는 STM32F103VC Dragon 개발보드 + 3.2 터치 LCD 가 부착된 실제 개발보드를 가지고 이론으로만 공부했던 내용을 실제 예제를 통해서 하나씩 배워나가보도록 하겠습니다. 예제들은 주로 Cortex-M3 Core 부분에 해당하는 부분을 다룰 것입니다. 처음에는 LCD, MP3 등의 디바이스 제어 예제도 진행하려고 했으나 이 부분들은 Cortex-M3의 공통된 부분도 아니고 디바이스에 따라서 많은 내용이 달라지기 때문에 STM32F10x 시리즈의 자체 기능과 Dragon 개발보드에 연결된 기본 예제들만 다루도록 하겠습니다. 추후 모든 디바이스들에 대한 예제들은 시간이 허락한다면 STM32 응용에 대한 강좌로 진행해 보도록 하겠습니다.

강의 전체 로드맵

I. ARM Architecture | 임베디드 시스템 개론에 대한 설명과 ARM7, ARM9 의 구조에 대해서 설명합니다.
II. ARM Applications | 삼성의 S3C2440(ARM9) 개발보드(S3C2440 Mini 개발보드)를 이용해서 어셈블리어와 UART, GPIO 등을 실습합니다.
III. Cortex-M3 Architecture | Cortex-M3의 특징과 구조에 대해서 설명합니다.
IV. Cortex-M3 Applications | STM32F103VCT6 Dragon 개발보드를 이용해서 GPIO, LCD, SPI, UART, MP3, SDIO, I2C 등을 실습합니다.

이 강의 자료에 대한 모든 질의사항은 http://cafe.naver.com/avrstudio의 ARM Architecture Q&A게시판에 글을 남겨주시거나 jk@deviceshop.net로 메일을 보내주시기 바랍니다. 가급적이면 여러 사람이 질문에 대한 답변을 공유할 수 있도록 네이버 카페 게시판을 이용해주셨으면 합니다. 감사합니다.

Ⅳ. Cortex-M3 Applications 목차

1. STM32F10x Overview
1.1 STM32F10x Block Diagram
1.2 STM32F10x Memory Map
1.3 STM32F10x Boot Modes
1.4 STM32F10x GPIO
2. STM32F103VC Dragon개발보드 소개
2.1 Features
3. Examples
3.1 GPIO Output without SDK
3.2 GPIO Output with SDK
3.3 GPIO Output with BitBand
3.4 GPIO Input – Polling
3.5 GPIO Input – Interrupt
3.6 General Purpose Timer
3.7 Systick – Delay
3.8 Systick – Interrupt
3.9 USART – Polling
3.10 USART – Interrupt
3.11 USART – Name Card
3.12 Interrupt Priority1
3.13 Interrupt Priority2
3.14 Power Management – Sleep
3.15 Power Management – Stop
3.16 Power Management – StandBy
3.17 Mode Privilege
3.18 USART Monitor Program

1. STM32F10x Overview

1.1 STM32F10x Block Diagram

Cortex-M3 Core를 Based로 하여 ICode, DCode, System Bus와 연결이 되어 있는 것을 볼 수 있습니다. Cortex-M3 Core는 ARM사에서 라이센싱한 것이고 아래의 그림에서 Vendor Defined Specfic 부분은 ST Microeclectonics사에서 구현한 것입니다. Bus Matrix를 통해서 AHB system bus와 Cortex-M3 Core가 연결되어 있고 APB1, APB2 bus가 AHB bus에 연결되어 있습니다.

20feajk 49

1.2 STM32F10x Memory Map

20feajk 50
Cortex-M3의 메모리 맵은 Architecture Defined 되어 있고, 4GB의 메모리 공간을 Access 할 수 있습니다. Architecture 차원에서 Memory Map이 정의되어 있기 때문에 같은 Cortex-M3를 기반으로한 CPU들 사이에서는 S/W 개발과 포팅을 쉽게 할 수 있습니다. 그리고 Memory Map에서 유심히 봐야할 부분은 Bit-Band region이 존재하고 있는 영역입니다.

-STM32F103VC CPU의 Memory layout
0×0800.0000 ~ 0×0801.FFFF (FLASH)
0×2000.0000 ~ 0×2000.BFFF (SRAM)
0×4000.0000 ~ 0×4002.3FFF
(Peripheral Memory Map)
0xE000.0000 ~ 0xE00F.FFFF
(Cortex-M3 Internal Peri.)

1.3 STM32F10x Boot Modes

STM32F 시리즈의 CPU는 3가지 모드로 부팅할 수 있습니다.

20feajk (7)
- User Flash memory : 일반적인 Normal booting입니다. STM32F CPU의 Internal Flash(0×0 or 0×8000000)에서 부팅을 하는 것입니다.
- System memory : STM32F CPU의 Internal System memory를 이용해서 부팅을 합니다. 이 모드로 부팅을 하면 CPU에 있는 내부 부트로더에 의해서 USART0가 초기화되고 ST 사에서 제공하는 ST Flashloader 프로그램을 이용해서 bin(or hex) 실행 이미지를 User Flash memory 영역에 다운로드 할 수 있습니다. JTAG 장비가 없을 경우 USART0을 이용해서 프로그램을 퓨징할 때 유용하게 사용할 수 있습니다.
- Embedded SRAM : Internal RAM에서 부팅을 시작하게 합니다. 이렇게 하려면 Startup 코드에서 Vector Table offset 위치를 RARM에 위치하도록 Register 설정을 해주어야 합니다.

20feajk (8)
위의 그림은 우리가 실습에 사용할 Dragon 개발보드의 BOOT 모드 회로도입니다.

 2. STM32F103VC Dragon 개발보드 소개

2.1 Features

CPU Module – STM32 Rabbit CPU 모듈 사용
CPU STM32F103VCT6 Cortex-M3 Core(LQFP-100)
SRAM 32KB internal SRAM
Flash 128KB internal Flash memory
ISP 1 x ISP 모드 전환 점퍼
UART 1 x UART( UART0)
LEDs 1 x User LED, 1 x Power LED
Reset Button 1 x Reset Button
JTAG 20Pin JTAG interface
Size(WxH) 68.4mm x 69.1mm(W x H)
확장포트 STM32F103VCT6의 모든 포트가 2 x 2.54mm으로 나와 있습니다.
Bottom Board – Hardware Features
전원 5V DC 전원(외경:5.5mm, 내경:2mm), USB, USB to Serial 포트를 통해서 전원공급 가능
RS232 1 x USB2Serial 포트 내장
1 x DB9 시리얼 포트 ( MAX232 )
USB  x USB 2.0 Full speed
LCD 1 x 2.4, 4.3, 7.0 inch TFT 터치 LCD 인터페이스
1 x 1602 Char LCD 인터페이스
1 x 12864 그래픽 LCD 인터페이스
MP3 1 x VS1003B SPI Interface
RF 1 x nRF24L01 SPI Interface
AHRS 1 x AHRS Sensor Interface
LEDs 3 x User LED, 1 x Power LED
Buttons 5 x User Button, 1 x Wakeup Button
CAN 1 x CAN ( VP230 )
RS485 1 x RS485 ( SP3485 )
SD Memory 1 x 4bit SD memory card interface
Adjustable Resiter 1 x ADC test
EEPROM 1 x IIC EEPROM
Backup Battery 1 x Backup battery holder
Buzzer 1 x Buzzer
ADC 2 x ADC Input( ADC12 In8, In9 )
DAC 1 x DAC
Size(WxH) 155 x 110 mm

 

3. 프로그램 다운로드 방법

3.1 JTAG을 이용한 다운로드 방법

3.1.1 ST-Link/V2

(1) ST-Link USB Driver Download and install
- http://www.st.com/web/en/catalog/tools/PF258167

20feajk (2)

(2) ST-Link Utility Download and install
- http://www.st.com/web/en/catalog/tools/PF258168

20feajk (4)

(3) ST-Link/V2 Utility Program 설치
- STM32 ST-LINK Utility_v2.5.0.exe

20feajk (5)
20feajk (6)
20feajk (7)
20feajk (8)

(4) ST-Link/V2 USB 드라이버 설치

- ST-Link_v2_usbdriver.exe

20feajk (9)
 ▼
20feajk (10)
 ▼
20feajk (11)
 ▼
20feajk (12)
 ▼
 14

- Windows7 의 경우 장치 드라이버가 자동으로 설치되지 않을 경우에 드라이버 소프트웨어 업데이트를 수행합니다.

20feajk (13)
20feajk (14)
20feajk (15)
20feajk (16)
20feajk (17)

 

(4) ST-Link/V2 Firmware upgrade

20feajk (51)

(5) Program Fusing with ST-Link/V2
ST-Link/V2의 20핀 JTAG Cable을 Dragon 개발보드의 CPU 모듈에 있는 20핀 JTAG 박스 헤더에 연결하고 ST-Link Utility에서 “Mode Setting” 메뉴에서 “SWD” 모드로 설정합니다. 일반적으로는 STM32F 시리즈에서 “JTAG” 모드로 설정해도 되지만 Dragon 개발보드에서는 JTAG 핀의 일부를 LCD 제어시 사용하고 있기 때문에 반드시 “SWD” 모드로 설정해야 합니다. 그리고 Connect Target 을 실행합니다.

20feajk (52)
다운로드 할 bin 파일을 선택합니다.

20feajk (53)
- 참고로 IAR 컴파일러 환경에서는 bin 파일을 생성하기 위해서는 Ouput Converter에서 아래 그림과 같이 Binary파일 생성 옵션을 설정해 주어야 bin 파일이 생성됩니다.

20feajk (18)
- Download & Verify를 실행합니다.

- IAR, KEIL등의 개발 환경과 직접 연동을 하지 않을 경우에는 ST-Link Utility를 이용해서 실행 binary를 다운로드 받을 수 있습니다.

20feajk (54)

(6) EWARM Debug Environment – ST-Link/V2
- Debugger에서 Driver를 ST-Link를 선택합니다.

20feajk (18)
- ST-Link 설정에서 “SWD” 모드로 설정을 합니다.
- ST-Link/V2 제품은 IAR에서 사용할 경우 6.20 이상부터 연동을 할 수 있습니다.

20feajk (49

3.1.2 ARM-JTAG
(1) ARM-JTAG Utility 설치

20feajk (59)
20feajk (60)
20feajk (61)
20feajk (62)
20feajk (63)
20feajk (64)

(2) JICE Server 프로그램 실행
- ARM-JTAG의 20핀 JTAG Cable을 Dragon 개발보드의 CPU 모듈에 있는 20핀 JTAG 박스 헤더에 연결 하고 JICE Server 프로그램을 실행합니다.
20feajk (65)

연결이 정상적이라면 Core ID를 읽어옵니다.

20feajk (66)

Core ID가 읽어지지 않는다면 아래와 같이 JICE Server를 설정해 보시기 바랍니다.

20feajk (67)

(3) JICE Configuration
- Debug Port Configuration

20feajk (68)

- Tap Configuration

20feajk (67)

(4) JICE Commander

20feajk (70)

(5) Easy Flashloader 설정
STM32F103VC(STM32F10xxC) or STM32F103ZE(STM32F10xxE) 중에서 선택합니다.

20feajk (71)
- 다운로드할 Binary 설정

20feajk (72)
- Start Download 를 실행하면 먼저 Flash를 Erase하고 나서 선택한 바이너리 파일을 다운로드합니다.

20feajk (73)

 

20feajk (74)

(6) EWARM Debug Environment – ARM-JTAG
- Debugger에서 Driver를 RDI로 선택합니다.

20feajk (75)
- RDI 드라이버를 설정합니다.

20feajk (76)
- Easy Flashloader에서 “Flash Download Before Debugging” 을 체크합니다.

20feajk (74)
- IAR 개발환경에서 “Download and Debugging”을 시작하면 “Check Flash Download State!” 실행창이 나오게 되고 JICE Commander에서 다운로드가 정상적으로 이루어 졌다면 “PASS” 버튼을 클릭합니다.

20feajk (77)
- IAR 개발환경에서 Debugging 화면

20feajk (78)
ARM-JTAG의 사용에 경우 Windows7 환경에서 보다 자세한 환경 설정은 아래 URL을 참조하시기 바랍니다.

http://www.jkelec.co.kr/img/jtag/arm/armjtag/arm_jtag_manual.html

3.2 STM32F 시리즈의 internal ISP(UART0)를 이용한 방법

STM32F 시리즈의 CPU들은 별도의 JTAG 장비없이 내장 ISP 기능을 이용해서 bin 파일을 다운로드 할 수 있습니다.

(1) ISP 모드로 진입
다운로드 모드로 진입하기 위해서는 아래 그림과 같이 Boot0 점퍼를 ISP라고 되어 있는 위치에 세팅하고 PC와 연결합니다.

20feajk (3)
Dragon Bottom 보드가 있을 경우에는 Bottom 보드의 UART 포트에 연결을 하면 됩니다. Dragon Bottom 보드에는 PL2303 USB to Serial 포트가 내장되어 있습니다.

(2) PL2303 USB to Serial 드라이버 설치
- PL2303_Prolific_DriverInstaller_v1210.exe

20feajk (1)
20feajk (55)
20feajk (56)
20feajk (57)
20feajk (58)
20feajk (79)

(3) “STMicroelectronics flash loader.exe” 프로그램을 설치하고 실행합니다.
- Parity : “Even” , Baud Rate : “115200″ 으로 설정

20feajk (80)
20feajk (81)
20feajk (83)

- Binary or Hex 파일을 선택하고 보통은 “Erase necessary pages” 를 선택하면 됩니다.

20feajk (82)
20feajk (84)
20feajk (85)

 

이어서 계속 됩니다. >>>

 

 

[20호]JK전자와 함께하는 ARM 완전정복(6)-2

jk전자 썸네일20-2
jk전자 JK전자와 함/께/하/는 ARM 완전 정복

Ⅳ. Cortex-M3 Applications

 

글 | JK전자

이어서 계속 됩니다. >>>

4. Examples

4.1 GPIO Output without SDK

STM32F 시리즈에는 ST사에서 제공하는 CPU Datasheet를 보지 않더라도 함수 이름만 보고서도 따라할 수 있을 정도의 아주 훌륭한 Library가 있습니다. 하지만 우리는 처음에는 STM32F CPU의 레지스터들을 더 잘 이해하기 위해서 SDK 라이브러리를 사용하지 않고 직접 SFR 레지스터드를 설정하는 방법으로 해보도록 하겠습니다.
첫번째 예제로 Dragon 개발보드의 Bottom 보드에 있는 LED들 중에서 LED2, LED4는 On, LED3는 Off 시키는 예제를 해보도록 하겠습니다.

(1) Dragon 개발보드의 LED 회로

20feajk (9)

회로를 보면 LED를 켜기 위해서는 GPIO포트를 High로 하면 LED가 켜지도록 되어 있습니다.

(2) Peripheral Bus

20feajk (10)
GPIOE는 APB2 버스에 연결되어 있으므로 APB2 버스의 GPIOE 포트의 Clock을 Enable시켜주어야 합니다.

(3) STM32 Peripheral Boundary Address : 0×4002 1000 (STM32F103 Reference Guide Page 50)

20feajk (11)

(4) RCC_APB2ENR Boundary Address : 0×4002 1000 + 0×18 (STM32F103 Reference Guide Page 119)

20feajk (12)

(5) RCC_APB2ENR Boundary Address : 0×4002 1018 (STM32F103 Reference Guide Page 142)
- GPIOE Port Enable : (*(volatile unsigned *)0×4002 1018) |= 0×01 << 6

20feajk (13)

(6) GPIOE Port Boundary address : 0×4001 1800 ( STM32F103 Reference Guide Page 51 )

20feajk (14)

(7) GPIO and AFIO register maps (STM32F103 Reference Guide Page 188)

20feajk (15)

(8) GPIOE_CRL Register Boundary address : 0×4001 1800
- GPIOE Port2, 3, 4를 Output push-pull로 설정

20feajk (16)

(9) GPIOE_BSRR(Set/Reset) Register Boundary address : 0×4001 1810

20feajk (17)
BSRR 레지스터는 GPIO Set or Reset 전용 레지스터입니다. 0 ~ 15비트는 Set 전용이고 16 ~ 31비트는 Reset 레지스터입니다.
LED2(GPIOE2), LED4(GPIOE4) 포트를 Set하기 위해서는 BSRR 레지스터의 2, 4번 비트를 1로 만들면 됩니다. “0″을 Write했을때 “No action” 이라고 되어 있는 부분은 일반적으로 레지스터의 특정 비트를 Set or Clear하기 위해서는 LDR, Bit 조작, STR의 3단계가 필요한데, 이러한 Feature가 지원이 되면 Bit 조작 다음에 바로 STR 명령어에 의해서 레지스터 비트 조작이 가능하게 됩니다.

(10) GPIOE_BRR(Reset) Register Boundary address : 0×4001 1814

20feajk (18)
GPIO Reset 전용 레지스터를 이용해서 GPIOE3을 Reset 합니다.
(*(volatile unsigned *) 0×40011814) = (0×1 << 3); // PE3 Off

예제 전체 코드

void led_test_wo_sdk(void)
{
// APB2 Clock enable
// Reference Page 47, 50, 119, 142
// APB2 peripheral GPIOE clock enable register (RCC_APB2ENR)
(*(volatile unsigned *)0×40021018) |= 0×01 << 6;
// General Purpose output push-pull
// Reference Page 51, 188, 166
// GPIOE_CRL
(*(volatile unsigned *)0×40011800) &= ~(0xf << 8);
(*(volatile unsigned *)0×40011800) &= ~(0xf << 12);
(*(volatile unsigned *)0×40011800) &= ~(0xf << 16);

// PE2, 3, 4 Ouput mode configuration
// Reference Page 51, 188, 166
// GPIOE_CRL
(*(volatile unsigned *)0×40011800) |= (0×3 << 8); // PE2 Ouput Mode 50MHz
(*(volatile unsigned *)0×40011800) |= (0×3 << 12); // PE3 Ouput Mode 50MHz
(*(volatile unsigned *)0×40011800) |= (0×3 << 16); // PE4 Ouput Mode 50MHz
// Reference Page 168
// GPIOE_BSRR
(*(volatile unsigned *)0×40011810) = (0×1 << 2); // PE2 On
// GPIOE_BSRR
(*(volatile unsigned *)0×40011810) = (0×1 << 4); // PE4 On
// GPIOE_BRR
(*(volatile unsigned *)0×40011814) = (0×1 << 3); // PE3 Off

}

4.2 GPIO Output with SDK

(1) 첫번째 예제와 동일한 동작을 하는데 이번에는 ST사에서 제공한 SDK 라이브러리를 이용해서 해보도록 하겠습니다.

(2) 프로젝트와 stm32f10x_conf.h 파일 수정

73
GPIO 라이브러리를 사용하기 위해서는 프로젝트에 stm32f10x_gpio.c 파일을 추가 하고 stm32f10x_conf.h 에서 stm32f10x_gpio.h 를 include 해야 합니다.

예제 전체 코드

void led_test_wt_sdk(void)
{

// Define GPIO Init structure
GPIO_InitTypeDef GPIO_InitStructure;
// APB2 Clock enable
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);

/* Configure the GPIOE ports for output*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOE, &GPIO_InitStructure);

GPIO_SetBits(GPIOE, GPIO_Pin_2); // LED2 On
GPIO_ResetBits(GPIOE, GPIO_Pin_3); // LED3 Off
GPIO_SetBits(GPIOE, GPIO_Pin_4); // LED4 On

}

별도의 설명을 하지 않아도 함수의 이름만 봐도 쉽게 코드를 이해할 수 있습니다.

4.3 GPIO Output with BitBand

(1) 예제 2번과 동일한 작업을 하는데, 이번에는 Bitband를 이용해서 해보도록 하겠습니다.

20feajk (19)
Peripheral 영역의 Bitband 영역을 이용해야하기 때문에 Bitband base 영역은 0×40000000이 되고 Bitword base는 0×42000000 이 됩니다.
Bitband 영역의 계산식을 다시 한번 상기하면서 소스코드를 분석해 보시기 바랍니다. GPIOE의 ODR 레지스터를 이용해서 구현하였습니다.
bit_word_offset = (byte_offset x 32) + (bit_number × 4)
bit_word_addr = bit_band_base + bit_word_offset

// GPIOE_ODR : 0x4001180C
// 2,3,4 Bit : PE2, PE3, PE4

예제 전체 코드

#define PERI_BASE 0×40000000
#define PERI_BB_BASE 0×42000000

void led_test_wt_bitband(void)
{

// Define GPIO Init structure
GPIO_InitTypeDef GPIO_InitStructure;

// APB2 Clock enable
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);

/* Configure the GPIOE ports */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOE, &GPIO_InitStructure);

(*(volatile unsigned *)(PERI_BB_BASE + (0x4001180C-PERI_BASE)*32 + 2*4)) = 0×1; // PE2 On
(*(volatile unsigned *)(PERI_BB_BASE + (0x4001180C-PERI_BASE)*32 + 3*4)) = 0×0; // PE3 On
(*(volatile unsigned *)(PERI_BB_BASE + (0x4001180C-PERI_BASE)*32 + 4*4)) = 0×1; // PE4 On

}

4.4 GPIO Input – Polling
Dragon 개발보드에 있는 버튼 입력을 Polling 방식으로 처리해 보도록 하겠습니다.

(1) BTN2가 눌리면 LED2를 ON 시키고, BTN2가 눌리지 않은 상태면 LED2를 Off 합니다. 동일한 방법으로 BTN3, BTN4 에도 적용 합니다. 그리고 BTN1을 누르면 테스트를 종료 합니다.

20feajk (87)

BTN 회로를 보면 버튼이 눌리지 않았을때는 VCC에 연결이 되어 High 상태이고 버튼이 눌리면 GND에 연결이 되면서 Low 상태가 됩니다.

20feajk (21)

(2) GPIOC 그룹을 사용하기 위해서는 APB2 버스의 GPIOC 그룹의 Clock을 Enable 해야 합니다.

(3) GPIOC1, 2, 3을 Input floating으로 설정합니다. 

예제 전체 코드

void key_input_test_polling_wt_sdk(void)
{

// Define GPIO Init structure
GPIO_InitTypeDef GPIO_InitStructure;

// APB2 Clock enable for LED
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);

// APB2 Clock enable for KEY
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

/* Configure the GPIOE ports for output*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOE, &GPIO_InitStructure);

/* Configure the GPIOC ports for input */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOC, &GPIO_InitStructure);

while(1)
{

// BTN1
if( (!GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_0 )) )
{
break;
}

// BTN2
if( (!GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_1 )) )
{
GPIO_SetBits(GPIOE, GPIO_Pin_2); // LED2 On
}
else
{
GPIO_ResetBits(GPIOE, GPIO_Pin_2); // LED2 Off
}

// BTN3
if( (!GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_2 )) )
{
GPIO_SetBits(GPIOE, GPIO_Pin_3); // LED3 On
}
else
{
GPIO_ResetBits(GPIOE, GPIO_Pin_3); // LED3 Off
}

// BTN4
if( (!GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_3 )) )
{
GPIO_SetBits(GPIOE, GPIO_Pin_4); // LED4 On
}
else
{
GPIO_ResetBits(GPIOE, GPIO_Pin_4); // LED4 Off
}
}

}

4.5 GPIO Input – Interrupt
이번에는 BTN 입력을 폴링 방식이 아닌 인터럽트 방식으로 구현해 보도록 하겠습니다.

(1) BTN2를 한번 누르면 LED2를 On 시키고, 다시 BTN2를 누르면 LED2를 Off 합니다.

(2) BTN 입력을 Falling Edge에서 검출되도록 설정합니다.
Falling Edge에서 검출한다는 것은 KEY Down에서 인터럽트가 발생하도록 한다는 것이죠. KEY Up을 검출하기 위해서는 Rising Edge 에서 검출되도록 설정하면 됩니다.

(3) PC1 포트의 인터럽트 입력을 받으려면 외부 인터럽트 1번에 연결해야 합니다. 당연히 PC2는 외부 인터럽트 2번에 연결해야 하겠죠.

20feajk (22)

(4) 인터럽트 레지스터 설정

20feajk (23)

(5) SDK 라이브러리 추가

20feajk (89)

(6) 인터럽트 우선순위
STM32에서 인터럽트 우선순위 비트는 8비트중에서 상위 4비트만 사용합니다. 4비트 중에서 이번 예제에서는 Priority group 2를 사용해서 Group 우선순위 2비트 Sub 우선순위 2비트를 사용하도록 설정합니다.

20feajk (90)

(7) 인터럽트 서비스 흐름도
외부 인터럽트가 발생해서 인터럽트 핸들러 함수로 분기하기까지의 과정을 도식화 해보았습니다.

20feajk (91)

(8) 예제 코드 작성 순서
□ APB2 Clock enable for LED
□ APB2 Clock enable for KEY
□ Configure the GPIOE ports for output
□ Configure the GPIOC ports for input
□ Configure EXTI to generate an interrupt on falling edge
□ EXTIPR : pending 여부및 pending clear(1:pending clear)
□ ICPR : pending clear Cortex-M3

예제 전체 코드

void key_input_test_interrupt(void)
{
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;

// Define GPIO Init structure
GPIO_InitTypeDef GPIO_InitStructure;

// APB2 Clock enable for LED
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);
// APB2 Clock enable for KEY
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

// for Interrupt
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
/* Configure the GPIOE ports for output*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOE, &GPIO_InitStructure);

/* Configure the GPIOC ports for input */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOC, &GPIO_InitStructure);

/* Connect EXTI */
// External Interrupt configuration register1 (AFIO_EXTICR1)
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource1);

/* Configure EXTI1 to generate an interrupt on falling edge */
EXTI_InitStructure.EXTI_Line = EXTI_Line1;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
// 2 bit for pre-emption priority, 2 bits for subpriority
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

/* Clear EXTI Line Pending Bit */
// STM32F10x Pending Register : 0×40010400 + 0×14
EXTI_ClearITPendingBit(EXTI_Line1);

/* Enable the Key EXTI line Interrupt */
// Cortex-M3 Interrupt Clear-Pending Register : 0xE000E280-0xE000E29C
NVIC_ClearPendingIRQ(EXTI1_IRQn);
}
void EXTI1_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line1) != RESET)
{
EXTI_ClearITPendingBit(EXTI_Line1);

// Blink
GPIOE->ODR ^= (0×1 << 2);
}
}

4.6 General Purpose Timer

임베디드 시스템에서 Timer는 가장 중요하고 필수적인 요소로 OS에서 Task스케줄링을 위해서 사용되기도 하고, 전자액자, 차량용 블랙박스 등의 응용 어플리케이션에서도 특정 시간 이후에 인터럽트를 발생시켜 정해진 일을 수행하는 경우 등 응용분야는 무수히 많습니다.

■ 참고 ■
CPU의 클럭 속도가 1Hz 라는 것은 무엇을 의미하는 것일까요?
이것은 1초에 1번의 Tick이 발생한다는 것임. STM32가 72MHz 로 동작한다면 1초에 7천 2백만번의 Tick이 발생하는 것

이번 예제에서는 STM32 CPU에 내장된 Timer2를 이용해서 1초에 한번씩 Timer 인터럽트를 발생시켜 LDE2, LED3를 Toggle(On/Off 를 반복하는것)하는 실험을 해보도록 하겠습니다.

■ 참고 ■
1sec = 1,000ms = 1,000,000us = 1,000,000,000ns
1Hz = 1KHz = 1MHz = 1GHz

20feajk (26)

(1) STM32F CPU의 타이머 종류

20feajk (27)

(2) Timer Clock
Timer2는 APB1 버스에 연결되어 있습니다. APB1 버스의 최대 동작 속도는 36MHz이지만 위의 그림을 잘 보면 APB1의 Prescaler가 1이 아니면 PB1 Clock x2를 하고 있습니다. 그러므로 Timer2가 APB1에 연결되어 있지만 Timer2에 공급되는 Clock은 72MHz가 됩니다. 주의해서 보지 않으면 Timer2에 공급되는 Clock이 36MHz라고 착각할 수 있습니다.

(3) 프로젝트와 stm32f10x_conf.h 파일 수정

20feajk (93)

(4) 예제 코드 작성 순서
□ APB1 Clock enable for TIM2
□ GPIO Init for LED
□ Configure the TIM2 interrupt
□ Time base configuration
□ TIM2 Enable
□ TIM2 interrupt enable

예제 전체 코드

void gpio_init_led(void)
{
// Define GPIO Init structure
GPIO_InitTypeDef GPIO_InitStructure;

// APB2 Clock enable
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);

/* Configure the GPIOE ports for output*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOE, &GPIO_InitStructure);
}
void gpio_init_key(void)
{
// Define GPIO Init structure
GPIO_InitTypeDef GPIO_InitStructure;

// APB2 Clock enable for KEY
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

// for Interrupt
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

/* Configure the GPIOC ports for input */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOC, &GPIO_InitStructure);
}

void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET )
{
// Also cleared the wrong interrupt flag in the ISR
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // Clear the interrupt flag

// Blink
GPIOE->ODR ^= (0×1 << 2);
GPIOE->ODR ^= (0×1 << 3);
}
}
void timer2_test(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

/* TIM2 clock enable */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

gpio_init_led();

/* Enable the TIM2 global Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

// TIM2CLK = 72 MHz( APB1은 원래 36MHz 인데 분주를 해서 사용하므로 36*2 = 72MHz 가 된다. )
// 시간의 기본단위 :S(초)–>nS.uS.mS.S.
// 72000000/60000=1200 즉 1초에 1200번 클럭이 발생하므로
// ARR 레지스터를 1199+1 번에 한번 인터럽트가
// 발생하도록 설정하면 1초에 한번 인터럽트가 발생된다.
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Period = 1200-1; // ARR(Auto reload register)
TIM_TimeBaseStructure.TIM_Prescaler = 60000-1;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

TIM_ARRPreloadConfig(TIM2, ENABLE);
TIM_Cmd(TIM2, ENABLE);

/* TIM IT enable */
TIM_ITConfig(TIM2, TIM_IT_Update , ENABLE);
}

자주 사용하는 LED, KEY포트를 초기화하는 함수 gpio_init_led, gpio_init_key를 만들어서 호출하였습니다.

4.7 Systick – Delay

SysTick Timer를 이용해서 1us Delay 함수를 구현하자. delay_ms 함수를 이용하여 LED2, LED3을 1초 간격으로 Toggle(On/Off) 해 봅시다.

(1) System Control Space

20feajk (28)
System timer인 SysTick은 System Control Space 영역에 위치하고 있습니다.

(2) SysTick Control and Status Register

20feajk (29)
이번에는 SysTick 인터럽트는 사용하지 않을 예정이며, CLKSOURCE는 core clock(72MHz)을 그대로 사용할 것입니다.

(3) Systick Reload Register

20feajk (30)
Systick Current Value Register를 보면 Systick Timer는 24비트 타이머라는것을 알 수 있습니다.

예제 전체 코드

// 1us delay 함수
void delay_us (const uint32_t usec)
{
RCC_ClocksTypeDef RCC_Clocks;

/* Configure HCLK clock as SysTick clock source */
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);
RCC_GetClocksFreq(&RCC_Clocks);
// Set SysTick Reload(1us) register and Enable
// usec * (RCC_Clocks.HCLK_Frequency / 1000000) < 0xFFFFFFUL — because of 24bit timer
// RCC_Clocks.HCLK_Frequency = 72000000
// Systick Reload Value Register = 72
// 72 / 72000000 = 1us
SysTick_Config(usec * (RCC_Clocks.HCLK_Frequency / 1000000));

// SysTick Interrupt Disable
SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk ;

// Until Tick count is 0
while (!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk));
}
// 1ms delay 함수
void delay_ms (const uint32_t msec)
{
delay_us(1000 * msec);
}
void systick_test_delay(void)
{
gpio_init_led();
gpio_init_key();

while(1)
{
// BTN1
if( (!GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_0 )) )
{
break;
}

delay_ms(100);
delay_ms(100);
delay_ms(100);
delay_ms(100);
delay_ms(100);
delay_ms(100);
delay_ms(100);
delay_ms(100);
delay_ms(100);
delay_ms(100);

// Blink
GPIOE->ODR ^= (0×1 << 2);
GPIOE->ODR ^= (0×1 << 3);
}
}

72MHz 클럭을 가지는 24Bit SysTick 타이머로는 1초 Delay를 만들어 낼 수 없기 때문에 100msec delay 함수를 10번 호출하여 구현하였습니다.

4.8 Systick – Interrupt

SysTick Timer를 이용해서 100msec 간격으로 SysTick_Handler 인터럽트를 발생시키고 LED2, LED3을 Toggle(On/Off) 해 봅시다.

예제 전체 코드

void SysTick_Handler(void)
{
// Blink
GPIOE->ODR ^= (0×1 << 2);
GPIOE->ODR ^= (0×1 << 3);
}
void systick_test_interrupt(void)
{
RCC_ClocksTypeDef RCC_Clocks;

gpio_init_led();

/* Configure HCLK clock as SysTick clock source */
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);
RCC_GetClocksFreq(&RCC_Clocks);

/* Setup SysTick Timer for 100 msec interrupts */
if (SysTick_Config(RCC_Clocks.HCLK_Frequency / 10))
{
/* Capture error */
while (1);
}
}

SysTick_Config 함수 안에서 기본으로 SysTick 인터럽트를 Enable 하고 있기 때문에 다른 설정을 하지 않아도 SysTick_Handler 인터럽트 핸들러로 진입합니다.
이전 SysTick Delay 예제에서는 인터럽트가 발생하지 않도록 하기 위해서,
SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk ;
위와 같은 코드가 삽입되어 있었습니다.

4.9 USART – Polling

UART1를 PC와 115200bps Baudrate로 통신(RX, TX)을 하는 Echo server로 만들어 봅시다.
RX, TX 통신을 폴링 방식으로 처리합니다. 터미널에서 ‘x’ 가 입력되면 폴링을 종료합니다.

(1) Dragon 개발보드의 UART 회로

20feajk (31)
Dragon 개발보드에는 COM포트가 없는 노트북, 데스크탑에서 편리하게 사용하게 하기 위해서 USB to Serial 포트가 내장되어 있습니다.
회로도를 보면 PA10이 RX, PA9가 TX 포트입니다. 이번 예제 테스트를 위해서 단순히 USB 미니케이블을 이용해서 Dragon Bottom 보드에 있는 UART 0번과 PCB의 USB 포트에 연결하면 됩니다. 이때 아직 USB to Serial USB 드라이버를 설치하지 않았다면 이전 강좌에서 설명한 “PL2303 USB to Serial 드라이버 설치” 부분을 참조하시기 바랍니다.

(2) Peripheral Bus

20feajk (32)
APB2 버스에 연결되어 있는 USART1과 GPA9, 10번 포트도 사용되고 있기 때문에 2개의 Peripheral Clock을 모두 Enable해 주어야 합니다.

(3) UART1 사용을 위한 GPIO 포트 초기화

20feajk (41)

(4) 프로젝트와 stm32f10x_conf.h 파일 수정

20feajk (95)

(5) USART Status register

20feajk (42)

 

예제 전체 코드

void usart1_test_polling(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;

char receive_data;
// APB2 Clock enable for USART(GPIOA9, A10)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
/* USART1 clock enable */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

/* Configure the GPIO ports( USART1 Transmit and Receive Lines) */
/* Configure the USART1_Tx as Alternate function Push-Pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);

/* Configure the USART1_Rx as input floating */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);

/* Configure the USART1 */
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);

/* Enable the USART1 */
USART_Cmd(USART1, ENABLE);

while(1)
{
// Rx not empty 가 될때까지 Polling
while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET );
// Rx not empty가 되면 USART Data register 에서 data를 읽어옴
receive_data = USART_ReceiveData(USART1) & 0xFF;

// Tx data 전송
USART_SendData(USART1, receive_data);
// Tx empty가 상태가 될때까지 Polling
// Tx 전송시 이 코드를 생략하면 빠른 Data 전송시 중간에 Tx Data 가 유실될수 있음
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET );

if( receive_data == ‘x’ )
break;
}
}

 

4.10 USART – Interrupt

UART1를 PC와 115200bps Baudrate로 통신(RX, TX)을 하는 Echo server로 만들어 봅시다. TX는 폴링 방식으로 처리하고 RX는 Interrupt 방식으로 처리합니다.

(1) USART1 Control Register

20feajk (43)

 

예제 전체 코드

void USART1_IRQHandler(void)
{
char receive_data;

if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
receive_data = USART_ReceiveData(USART1) & 0xFF;

USART_SendData(USART1, receive_data);
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET );

USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
void usart1_test_interrupt(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;

// APB2 Clock enable for USART(GPIOA9, A10)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

/* USART1 clock enable */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

/* Enable the USART1 Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

/* Configure the GPIO ports( USART1 Transmit and Receive Lines) */
/* Configure the USART1_Tx as Alternate function Push-Pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);

/* Configure the USART1_Rx as input floating */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);

/* Configure the USART1 */
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
// Rx Not empty interrupt enable
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

/* Enable the USART1 */
USART_Cmd(USART1, ENABLE);
}

4.11 USART – Name Card

PC의 터미널에 다음과 같이 명함을 출력해 보세요.

*************************************
* Name : Kyung Yeon Kim *
* Company : JK Electronics *
* No : 010-XXXX-XXXX *
*************************************

참고1. 터미널의 행 개행 문자는 “\r\n”
참고2. 터미널 문자열 출력을 하는데 문자열 출력 함수를 만들어 사용하세요.
void usart1_send_string(char *data);
참고3. usart1 초기(폴링방식)화 함수를 작성하세요.
void usart1_init(void);

예제 전체 코드

void usart1_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;

// APB2 Clock enable for USART(GPIOA9, A10)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

/* USART1 clock enable */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

/* Configure the GPIO ports( USART1 Transmit and Receive Lines) */
/* Configure the USART1_Tx as Alternate function Push-Pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);

/* Configure the USART1_Rx as input floating */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Configure the USART1 */
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);

/* Enable the USART1 */
USART_Cmd(USART1, ENABLE);
}
void usart1_send_string(char* data)
{
while(*data != ”)
{
USART_SendData(USART1, *(unsigned char *)data);
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET );
data++;
}
}
void usart1_test_namecard(void)
{
usart1_init();

usart1_send_string(“\r\n”);
usart1_send_string(“****************************************\r\n”);
usart1_send_string(“* Name : Kyung Yeon Kim *\r\n”);
usart1_send_string(“* Company : JK Electronics *\r\n”);
usart1_send_string(“* No : 010-XXXX-XXXX *\r\n”);
usart1_send_string(“****************************************\r\n”);
usart1_send_string(“\r\n”);
}

4.12 Interrupt Priority1

BTN3를 누르면 LED3를 무한 반복을 하면서 On을 시키고 BTN4를 누르면 LED4를 무한 반복을 하면서 On을 시킨다.

(1) Group, Sub Priority bit를 각각 2Bit씩 설정

(2) BTN3의 Group Priority를 2, Sub Priority를 0으로 설정

(3) BTN4의 Group Priority를 1, Sub Priority를 0으로 설정 후 테스트

예제 전체 코드

void EXTI2_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line2) != RESET)
{
EXTI_ClearITPendingBit(EXTI_Line2);

while(1)
{
GPIO_SetBits(GPIOE, GPIO_Pin_3); // LED3 On
}
}
}

void EXTI3_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line3) != RESET)
{
EXTI_ClearITPendingBit(EXTI_Line3);

while(1)
{
GPIO_SetBits(GPIOE, GPIO_Pin_4); // LED4 On
}
}
}
void interrupt_priority1_test(void)
{
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;

gpio_init_led();
gpio_init_key();

/* Connect EXTI */
// External Interrupt configuration register1 (AFIO_EXTICR1)
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource2);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource3);

/* Configure EXTI2 to generate an interrupt on falling edge */
EXTI_InitStructure.EXTI_Line = EXTI_Line2;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);

/* Configure EXTI3 to generate an interrupt on falling edge */
EXTI_InitStructure.EXTI_Line = EXTI_Line3;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);

// 2 bit for pre-emption priority, 2 bits for subpriority
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

/* Clear EXTI Line Pending Bit */
// STM32F10x Pending Register : 0×40010400 + 0×14
EXTI_ClearITPendingBit(EXTI_Line2);
EXTI_ClearITPendingBit(EXTI_Line3);

/* Enable the Key EXTI line Interrupt */
// Cortex-M3 Interrupt Clear-Pending Register : 0xE000E280-0xE000E29C
NVIC_ClearPendingIRQ(EXTI2_IRQn);
NVIC_ClearPendingIRQ(EXTI3_IRQn);
}

예제의 실행결과를 알 수 있겠죠. BTN3의 Group Priority를 2로 BTN4보다 높기 때문에 BTN3을 먼저 누르면 BTN4의 인터럽트가 실행되지 못합니다. 반대로 BTN4의 인터럽트 실행 중에 BTN3을 누르면 즉시 BTN3의 인터럽트 서비스 루틴이 실행됩니다.

4.13 Interrupt Priority2

BTN3를 누르면 LED3를 무한 반복을 하면서 On을 시키고 BTN4를 누르면 LED4를 무한 반복을 하면서 On을 시킨다.

(1) Group, Sub Priority bit를 각각 2Bit씩 설정

(2) BTN3의 Group Priority를 2, Sub Priority를 0으로 설정

(3) BTN4의 Group Priority를 2, Sub Priority를 1로 설정 후 테스트

예제 전체 코드

void EXTI2_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line2) != RESET)
{
EXTI_ClearITPendingBit(EXTI_Line2);

while(1)
{
GPIO_SetBits(GPIOE, GPIO_Pin_3); // LED3 On
}
}
}

void EXTI3_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line3) != RESET)
{
EXTI_ClearITPendingBit(EXTI_Line3);

while(1)
{
GPIO_SetBits(GPIOE, GPIO_Pin_4); // LED4 On
}
}
}
void interrupt_priority1_test(void)
{
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;

gpio_init_led();
gpio_init_key();

/* Connect EXTI */
// External Interrupt configuration register1 (AFIO_EXTICR1)
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource2);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource3);

/* Configure EXTI2 to generate an interrupt on falling edge */
EXTI_InitStructure.EXTI_Line = EXTI_Line2;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);

/* Configure EXTI3 to generate an interrupt on falling edge */
EXTI_InitStructure.EXTI_Line = EXTI_Line3;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);

// 2 bit for pre-emption priority, 2 bits for subpriority
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

/* Clear EXTI Line Pending Bit */
// STM32F10x Pending Register : 0×40010400 + 0×14
EXTI_ClearITPendingBit(EXTI_Line2);
EXTI_ClearITPendingBit(EXTI_Line3);

/* Enable the Key EXTI line Interrupt */
// Cortex-M3 Interrupt Clear-Pending Register : 0xE000E280-0xE000E29C
NVIC_ClearPendingIRQ(EXTI2_IRQn);
NVIC_ClearPendingIRQ(EXTI3_IRQn);
}

이번 예제에서는 BTN3과 4의 Group 우선순위가 같기 때문에 서로 인터럽트 수행 중에는 선점을 하지 못합니다.

4.14 Power Management – Sleep

Cortex-M3에서 Power Management 기능은 NVIC에 포함되어 있습니다.

20feajk (44)

(1) Sleep 모드 테스트
터미널 창에 아래와 같이 표시되도록 합니다.
Entered Sleep mode.
__WFI(); // Sleep mode 로 진입
Exit Sleep mode.

(2) BTN2를 인터럽트 모드로 입력 받아서 Sleep 모드를 빠져 나오도록 합니다. 추가로 BTN2를 누르면 LED2를 On 시키고 BTN2를 떼면 LED2를 Off 합니다.

(3) STM32F Low Power Mode

20feajk (45)
STM32에서 Sleep 모드로 진입하기 위해서는 WFI(Wait for Interrupt) or WFE(Wait for Event) 어셈블리어에 의해서 진입할 수 있습니다. 이번 예제에서는 WFI를 이용해서 Sleep 모드로 진입하도록 하겠습니다. WFI에 의해서 진입한 Sleep 모드에서 깨어나기 위해서는 어떠한 인터럽트 발생에 의해서라도 깨어날 수 있습니다.

예제 전체 코드

void power_management_sleep_test(void)
{
key_input_test_interrupt();

usart1_send_string(“\r\nEnter Sleep mode.\r\n”);

__WFI();

usart1_send_string(“\r\nExit Sleep mode.\r\n”);

}

Sleep모드의 장점은 Cortex-M3 Core의 Clock만 멈추어 있는 상태여서 인터럽트에 의해서 즉시 깨어날 수가 있어서 Sleep 모드에 진입해있는지 조차 알 수가 없다는 것이고 단점은 Stop, StandBy 모드에 비해서 소모전류가 많다는 것입니다.

20feajk (46)
Sleep 모드별 Wakeup 방식과 Clock Management 방법입니다.

4.15 Power Management – Stop

Stop, StandBy 모드는 모두 Cortex-M3 Deep sleep 모드에 해당 합니다.

(1) Stop 모드 실험
□ Timer2 인터럽트를 1초 간격으로 발생시켜 LED2, LED3를 Toggle
□ PWR_EnterSTOPMode() 함수를 호출하여 Stop 모드로 진입 합니다.
□ BTN2를 인터럽트 모드로 입력 받아서 Stop 모드를 빠져 나오도록 합니다. 추가로 BTN2를 누르면 LED2를 On 시키고 BTN2를 떼면 LED2를 Off 합니다.
□ STOP 모드를 빠져나온 이후에 LED2, LED3이 1초 간격으로 Toggle
□ PWR_EnterSTOPMode를 호출하면 진입

■ SRAM, Register 상태는 유지
■ 1.8V Domain에 있는 모든 클럭이 Stop
■ PLL, HIS RC, HSE crystal oscillator 모두 disable
■ Voltage regulator는 normal or low power mode

□ Wakeup

■ EXTI line 중의 하나를 받아야 함
■ 16개의 EXTI line or PVD output, RTC alarm, USB wakeup

(2) Peripheral Bus – PWR

20feajk (47)
STM32F의 Power 블럭은 APB1 버스에 연결되어 있습니다.

(3) 프로젝트와 stm32f10x_conf.h 파일 수정

20feajk (96)

(4) STM32 PWR 블럭도

20feajk 51

(5) Cortex-M3 System Control Register

20feajk (33)
Stop 모드로 진입시키기 위해서는 2번 비트의 SLEEPDEEP을 Set해야 합니다.

20feajk (34)

(6) STM32 Power Control Register
PDDS를 o으로, LPDS를 0으로 할 수도 있고, 1로 할 수도 있으나 이번 예제에서는 1로 설정하여 Vdd Domain에 있는 Voltage Regulator를 low-power 모드로 설정합니다.

20feajk 52

 

예제 전체 코드

// STOP 모드에 진입하면 HSE Clock 등이 Disale 되기 때문에 Wakeup시에 다시 설정을 해주어야 합니다.
void system_clock_config_stop(void)
{
RCC_HSEConfig(RCC_HSE_ON); // Enable HSE

if( RCC_WaitForHSEStartUp() == SUCCESS)
{
RCC_PLLCmd(ENABLE); // Enable PLL

// Wait until PLL is ready
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) {}

// Select PLL as system clock source
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);

// wait until PLL is used as system clock source
while(RCC_GetSYSCLKSource() != 0×08) {}
}
}
void power_management_stop_test(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);

timer2_test();
key_input_test_interrupt();

usart1_send_string(“\r\nEnter Stop mode.\r\n”);

// STOP 모드로 진입하고 외부 인터럽트에 의해서 깨어남
PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI);

system_clock_config_stop();

usart1_send_string(“\r\nExit Stop mode.\r\n”);
}

 

4.16 Power Management – StandBy

(1) PWR_WakeUpPinCmd(ENABLE)를 호출, PA0를 Wakeup 핀으로 설정

(2) BTN2를 인터럽트 모드로 입력 받아서 Stop 모드를 빠져나오도록 설정

(3) PWR_EnterSTANDBYMode()를 호출, StandBy 모드로 진입
StandBy모드로 진입하는 방법은 STOP모드로 진입하는 방법에서 STM32의 PWR_CR 레지스터의 PDDS를 1로 설정하는것을 제외하면 동일합니다. 단지 StandBy모드에서는 Wakeup 하는 방법에서 차이가 있으면 StandBy모드에서 Wakeup을 한다는 것은 CPU가 처음부터 다시 부팅하는 절차와 동일합니다. 하지만 StandBy모드에서 Wakeup이 되는 경우에는 PWR_CSR 레지스터의 Standby Flag가 H/W 적으로 Set이 되어 있습니다.

(4) Power Control Register

20feajk (36)

(5) System Control Register

20feajk (37)

(6) Enable Wakeup PIN

20feajk (38)
StandBy모드에서 깨어나기 위해서 Wakeup핀을 Enable 합니다. 반드시 PA0 핀이 이용됩니다.

(7) Power control/status register

20feajk (39)

※ StandbyMode에서 깨어났을 경우에 Hardware적으로 1로 설정됨

예제 전체 코드

void power_management_standby_test(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);

// Enable Wakeup Pin(should be PA0)
PWR_WakeUpPinCmd(ENABLE);

usart1_send_string(“\r\nWakeup pin enabled.\r\n”);

usart1_send_string(“Enter Standby mode.\r\n”);

// Standby Mode
PWR_EnterSTANDBYMode();

// 이 코드는 실행될수 없음
usart1_send_string(“\r\nExit Standby mode.\r\n”);
}

 

4.17 Mode Privilege

(1) Reset 이후의 Mode(Privilege or Unprivilege)와 어떤 Stack(Main Stack, Process Stack)을 사용하는지를 PC의 터미널에 표시합니다.

(2) Mode를 Unprivilege 모드로 전환합니다.

(3) 모든 전환 이후에 Mode(Privilege or Unprivilege)와 어떤 Stack(Main Stack, Process Stack)을 사용하는지를 PC의 터미널에 표시합니다.

(4) SVC 명령어 “__ASM(“svc #1″)” 를 사용해서 SVC_Handler Exception을 발생시키고 SVC_Handler 핸들러 진입 여부를 터미널에 표시합니다.

(5) SVC 핸들러내에서 Mode를 Privilege 모드로 전환합니다.

(6) 모든 전환 이후에 Mode(Privilege or Unprivilege)와 어떤 Stack(Main Stack, Process Stack)을 사용하는지를 PC의 터미널에 표시합니다.

20feajk (40)

예제 전체 코드

void SVC_Handler(void)
{
usart1_send_string(“\r\nSVC_Handler\r\n”);
usart1_send_string(“Mode change to privilege\r\n”);
__set_CONTROL(0×0);
}

void mode_privilege_test(void)
{
usart1_send_string(“\r\n\r\n—- mode privilege test start —-\r\n\r\n”);

// Mode
if( (__get_CONTROL() & 0×1) == 0×1 )
usart1_send_string(“Mode = Unprivilege\r\n”);
else if( (__get_CONTROL() & 0×1) == 0×0 )
usart1_send_string(“Mode = Privilege Mode\r\n”);

// Stack
if( (__get_CONTROL() & 0×2) == 0×0 )
usart1_send_string(“Stack = MSP Stack\r\n”);
else if( (__get_CONTROL() & 0×2) == 0×1 )
usart1_send_string(“Stack = PSP Stack\r\n”);

// mode change to unprivilege
usart1_send_string(“Mode change to unprivilege\r\n”);
__set_CONTROL(0×1);

// Mode
if( (__get_CONTROL() & 0×1) == 0×1 )
usart1_send_string(“Mode = Unprivilege\r\n”);
else if( (__get_CONTROL() & 0×1) == 0×0 )
usart1_send_string(“Mode = Privilege Mode\r\n”);

// Stack
if( (__get_CONTROL() & 0×2) == 0×0 )
usart1_send_string(“Stack = MSP Stack\r\n”);
else if( (__get_CONTROL() & 0×2) == 0×1 )
usart1_send_string(“Stack = PSP Stack\r\n”);

__ASM(“svc #1″);

// Mode
if( (__get_CONTROL() & 0×1) == 0×1 )
usart1_send_string(“Mode = Unprivilege\r\n”);
else if( (__get_CONTROL() & 0×1) == 0×0 )
usart1_send_string(“Mode = Privilege Mode\r\n”);

// Stack
if( (__get_CONTROL() & 0×2) == 0×0 )
usart1_send_string(“Stack = MSP Stack\r\n”);
else if( (__get_CONTROL() & 0×2) == 0×1 )
usart1_send_string(“Stack = PSP Stack\r\n”);

usart1_send_string(“—- mode privilege test end —-\r\n\r\n”);
}

여기까지 전통적인 ARM 프로세서인 ARM9의 구조를 시작으로 ARM9 Application을 거쳐 Cortex-M3 Architecture, Cortex-M3 Application의 모든 과정이 마무리되었습니다. 머리 속에 있는 내용을 글로 옮긴다는 것이 쉽지는 않았던 것 같습니다. Cortex-M3 Application part에서 기본적인 예제만 다룬 것이 아쉬움이 남네요. 기회가 된다면 STM32 Dragon 개발보드에 있는 모든 디바이스들을 제어해보는 연재를 다시 시작해볼까 합니다. 그리고 개발 환경도 GCC와 이클립스 환경으로 바꾸어 좀 더 사이즈가 크고 전문적인 S/W개발 방법에 대해서 공부해보도록 하겠습니다. 특히 SD메모리와 LCD를 이용해서 예쁜 GUI를 구성하는 부분에 대해서 집중적으로 준비해보도록 하겠습니다.

수고하셨습니다.

 

JK전자와 함께하는 ARM 완전정복 시즌1이 모두 마무리 되었습니다.
약 6개월 뒤쯤 새롭게 시작될 시즌 2도 기대해주시기 바랍니다.
아울러 컨텐츠를 제공해주신 JK전자 관계자분들께 다시 한번 감사의 말씀을 드립니다.

 

 

[20호]DIY 프로젝트 공모전 입선작 – EVALARM 물체회피 기능을 가진 알람카

20diy평지
20Feadiym

작품설명

제작 동기

293260

일반시계와 알람시계의 가장 큰 차이점은 알람기능 수행 여부의 차이입니다. 그 중 알람기능은 정해진 시간에 맞추어 사용자가 일어날 수 있도록 도와주는 것이 가장 큰 핵심이라고 할 수 있습니다. 하지만 기존의 알람시계는 소리를 잘 듣기위해 곁에 두고 자게 되고, 이로 인해 사용자가 무의식중에 알람을 꺼버려서 지각을 하거나 그 기능을 상실해 버리는 경우가 대부분입니다. 그렇기 때문에 저희 Evalam은 소리가 잘 들리게 끔 곁에 두지만 일어나지 않으면 꺼지지 않게 끔 설계되어 있어 가장 중요하고 큰 기능이 상실되는 것을 막고 이를 재미와 접목시켜 상쾌한 아침을 맞을 수 있도록 만들어졌습니다.

유사 작품 | 클로키로보틱 알람시계

20diy클로키로보틱
유동성을 가진다는 측면에서 본 아이디어와 유사한 장점을 가졌지만 가격이 너무 비싸고 장애물들을 피할 수 없기 때문에, 자칫 장롱 밑 등의 폐쇄된 장소에 들어갈 시 분실의 위험이 따른다는 큰 단점이 있습니다.
아이디어는 클로키로보틱 알람시계와 비슷하지만 클로키로보틱 알람시계의 단점인 장애물을 피할 수 없다는 점과 폐쇄된 장소에 들어갈 시 분실의 위험이 따른 점을 개선하도록 설계되어 있습니다. 적외선 센서을 통한 물체 회피와 리모컨 센서를 통한 RC카 제어 등으로 실용성을 높이는 것과 더불어 재미를 위해 제작하게 되었습니다.

작품 기능 및 설명

20Feadiym0 (6)
동작은 RC카와 시계의 기능을 합쳐 놓은 작품입니다.
RC카는 물체 회피(모터), 조정기(수신부), 움직임(거리센서)으로 구성되어 있고, 시계에서는 일반적인 알람과 시계로 구성되어 있습니다. 서로 다른 두 작품을 합쳐 알람이 울릴 경우에 알람을 꺼버리려는 행동을 방지할 수 있도록 설계했습니다.
또한 4개의 센서를 통해 벽이나 물건을 회피하여 작품과 물건을 보호하도록 설계했습니다. 이에 대한 자세한 설명은 후에 하겠습니다.

 

주요 동작 및 특징

알람이 울릴 시 전체 시나리오

20Feadiym0

4개의 거리 측정 센서로 물체에 감지하는 상황은 총 7가지 경우로 생각해 보았습니다. 그리고 그 상황에 따라 동작은 5가지로 구성했습니다. 먼저 주요 동작 조건을 사진으로 확인해 보겠습니다.

20diy평지 1. 평지에서 사진 : 단순히 알람이 시끄럽게 울리도록 설계했습니다.
20diy앞에서 알람잡 2. 앞에서 Evalarm을 잡으려고 할 때 : 앞에서 Evalarm을 잡으려고 할 때 뒤로 움직이도록 설계했습니다.
20diy뒤에서 알람잡 3. 뒤에서 Evalarm을 잡으려고 할 때 : 앞으로 움직이도록 설계했습니다.
20diy앞뒤에서 알람잡 4. 앞뒤에서만 물체가 감지 될 때 : 좌측으로 회전하도록 설계했습니다.
네방향 5. 네 방향에서 물체가 감지 될 때 : 빠져나갈 수 있는 틈새를 찾도록 설계했습니다.
앞뒤랑 좌측 6. 앞뒤 그리고 좌측에서 물체가 감지 될 때 :우측으로 회전하도록 설계했습니다.
앞뒤 우측 7. 앞뒤 그리고 우측에서 물체가 감지 될 때 : 좌측으로 회전하도록 설계했습니다.

설정 시 전체 시나리오 : 시간 수정, 알람 설정, 알람 시간

20Feadiym0 (1)
위와 같은 표처럼 알람 시간과 알람 설정 그리고 시간을 4개의 스위치로 설정할 수 있도록 설계되어 있습니다.

메인 알람 끔
메인화면 알람이 꺼진 화면
알람킴 시간십초변화
알람이 켜진 화면 시간을 10초로 설정한 화면
시간십분변화 시간열시간변화
시간을 10분으로 설정한 화면 시간을 10시간으로 설정한 화면

위와 같은 표처럼 알람 시간과 알람 설정 그리고 시간을 4개의 스위치로 설정할 수 있도록 설계되어 있습니다.

전체 시스템 구성

하드웨어 구성

20Feadiym0 (2)

TEXT LCD는 7비트로 제어가 가능하도록 설계했으며, 스위치 외부 인터럽트를 사용하여 제어하도록 했습니다. 리모컨 센서는 외부 인터럽트와 타이머 카운트로 인식 받도록 설계했습니다. 적외선 센서는 AD변환을 통해 거리를 측정하도록 설계했습니다.

적외선 센서(L298)

20diyL298데이터시트
L298 데이터시트
20fediy03
모터 입력(설정)에 대한 움직임의 방향
20diyL298데이터시트2
L298의 데이터시트
20diyL298데이터시트3
L298N 부품

사용시스템

1) 리모컨 관련 시스템 및 설명
리모컨의 데이터 형식은 아래 그림과 같습니다. 처음에 리더 펄스가 나옵니다. D6121(구 삼성, 구 LG) 형식은 리더 펄스의 길이가 13.5msec이고 TC9012(삼성. LG) 형식은 리더 펄스의 길이가 9msec 입니다.
다음에 32 비트의 데이터가 따라 오는데, 처음 2바이트는 커스텀 코드이고, 다음 1바이트는 데이터, 마지막 바이트는 데이터 1의 보수입니다.

20fediy04
리모컨의 데이터 형식

각 비트는 1 또는 0을 나타내는데 펄스 폭의 길이로 구별합니다. (아래 그림 참조) 즉, 펄스 폭이 긴 것(A)이 1을 나타내고, 짧은 것(B)은 0을 나타냅니다. 시간은 각각 0.5625 msec와 1.6875 msec입니다.

20fediy005
데이터 펄스폭의 길이

아래에 삼성 리모컨(TV, VCR)의 코드 값을 예로 들었습니다. 우리는 이 코드표를 사용하는 것이 아니라, 프로그램을 통해서 이 코드 값을 알아내고(표와 일치하는지에 대한 확인), 알아낸 코드 값을 이용해서 LCD를 제어하도록 하겠습니다. 삼성 리모컨뿐만 아니라 TC9012나 D6121 형식의 리모컨은 어떤 것이나 코드 값을 알아내서 사용하는 것이 목적입니다.

2) 삼성리모컨에 대한 전반적인 데이터 표

20Feadiym0 (4)

3) rc카 시스템

20diY RC카 rc카 윗부분
rc카 뒷부분 rc카 옆부분(왼)
20fediy006 ※ 볼게이트의 사용은 무게중심에 의해 앞으로 본체가 쏠리는 것을 방지하는데 도움을 주기 위해 장착하였습니다.

 

단계별 제작 과정
단순히 시계에 필요한 시간 변경 그리고 알람 등의 기능을 업그레이드하여 제작했습니다. 그리고 RC카에 필요한 모터 제어와 리모컨 수신 센서 제어를 포함하여 RC카를 제작했습니다. 적외선 센서를 제어하고 기존의 RC카에서 물체를 회피하는 기능을 연구하고 제작했습니다. 마지막으로 물체 회피와 RC카 그리고 시계 이 모든 것을 합친 Evalarm이 제작됐습니다.

 기타(회로도, 소스코드, 참고문헌 등)

소스코드보기

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

#define ir_ready 0
#define ir_lead 1
#define ir_data 2
unsigned char ir_rx_data[4];
//voliate
unsigned char ir_state;
unsigned char ir_timer_cnt;
unsigned char ir_bit_cnt;
unsigned char ir_rx_flag=0;
unsigned char ir_rx_temp;

void clock_data_m_change();
void clock_data_p_change();
void alarm_data_m_change();
void alarm_data_p_change();
void ir_car_run();

char moter_data;

void delay_us(unsigned int us)
{
unsigned int i;

for(i=0;i<us;i++){
asm(“PUSH R0″);
asm(“POP R0″);
asm(“PUSH R0″);
asm(“POP R0″);
asm(“PUSH R0″);
asm(“POP R0″);
}
}

void delay_ms(unsigned int ms)
{
unsigned int i;
for(i=0;i<ms;i++){
delay_us(1000);
}
}

#define CUR11 0×02 /* 커서 홈에 위치시키는 명령어 값 */
#define CUR21 0xC0 /* 커서 2라인 1열에 위치시키는 명령어 값 */
#define LCDON 0x0C /* LCD ON 명령어 값 */
#define LCDOFF 0×08 /* LCD OFF 명령어 값 */
#define MODE4 0×20 /* 4비트 인터페이스 설정 */
#define FSET 0×28 /* 기능설정, 4비트 인터페이스, NF=10 : 2행 5×7 폰트 */
#define LCDCLR 0×01 /* LCD 클리어 */
#define MODENT 0×06 /* 엔트리모드 설정, 표시는 이동 않고, AC 증가, 커서 우측 이동 */

typedef unsigned char byte;

void lcd_cmd(unsigned char ch){ /LCD에 명령어값 1바이트 쓰는 서브루틴, ch는 명령어 값 /
unsigned char temp0, temp1;
delay_ms(1); / 약 15msec delay /
DDRA = 0xff; // 포트C를 출력으로
temp0 = ch & 0xf0; / 상위4비트 마스크 /
temp1 = temp0 | 0×04; / 하위 4비트에 0100b(RS=R/W=0, E=1) 써넣기 /
PORTA = temp1; / LCD에 명령어값의 상위 4비트 써넣기 /
temp1 = temp0 & 0xf0; / 하위 4비트에 0000b(RS=R/W=0, E=0) 써넣기 /
PORTA = temp1; / LCD에 입력 불허 /
delay_us(10); / 약 0.01 msec 시간지연 /
temp0 = (ch << 4) & 0xf0 ; / 하위4비트를 상위로 옮기고 마스크 /
temp1 = temp0 | 0×04 ; / 하위 4비트에 0100b(RS=R/W=0, E=1) 써넣기 /
PORTA = temp1; / LCD에 명령어값의 하위 4비트 써넣기 /
temp1 = temp0 & 0xf0; / 하위 4비트에 0000b(RS=R/W=0, E=0) 써넣기 /
PORTA = temp1; / LCD에 입력 불허 /
}

void lcd_ln11(void){ /* LCD의 1행1열에 커서를 위치시키는 서브루틴 */
lcd_cmd(CUR11);
}

void lcd_dat(unsigned char ch){ /* LCD에 1글자 쓰는 서브루틴, ch는 글자의 아스키 코드 값 */
unsigned char temp0, temp1;
delay_ms(1); /* 약 15msec delay */
DDRA = 0xff; // 포트C를 출력으로
temp0 = ch & 0xf0; /* 상위4비트 마스크 */
temp1 = temp0 | 0×05; /* 하위 4비트에 0101b(RS=1, R/W=0, E=1) 써넣기 */
PORTA = temp1; /* LCD에 DATA값의 상위 4비트 써넣기 */
temp1 = temp0 | 0×01; /* 하위 4비트에 0001b(RS=1, R/W=0, E=0) 써넣기 */
PORTA = temp1; /* LCD에 입력 불허 */
delay_us(10); /* 약 0.01 msec 시간지연 */
temp0 = (ch << 4) & 0xf0 ; /* 하위4비트를 상위로 옮기고 마스크 */
temp1 = temp0 | 0×05; /* 하위 4비트에 0101b(RS=1, R/W=0, E=1) 써넣기 */
PORTA = temp1; /* LCD에 DATA값의 하위 4비트 써넣기 */
temp1 = temp0 | 0×01; /* 하위 4비트에 0001b(RS=1, R/W=0, E=0) 써넣기 */
PORTA = temp1; /* LCD에 입력 불허 */
}

void lcd_init(void){ /* LCD 초기화 서브루틴 */
delay_ms(15); /* 약 15msec delay */
delay_ms(5);
lcd_cmd(MODE4);
delay_ms(5);
delay_ms(5); /* 약 5msec delay */
lcd_cmd(MODE4); delay_us(100);
delay_ms(5);
lcd_cmd(MODE4);
delay_ms(5);
lcd_cmd(FSET); /* 기능설정, 4비트 인터페이스, NF=10 : 2행 5×7 폰트 */
delay_ms(5);
lcd_cmd(LCDON); /* LCD ON 명령어 값 */
delay_ms(5);
lcd_cmd(LCDCLR); /* LCD Clear 명령어 값 */
delay_ms(5);
lcd_cmd(MODENT); /* 엔트리모드 설정, 표시는 이동 않고, AC 증가, 커서 우측 이동 */
delay_ms(5);
}

void lcd_str(char *str){ /* LCD에 flash에 저장된 문자열을 출력하는 함수 */
unsigned int i=0;
for(i=0;str[i] != 0; i++)
lcd_dat(str[i]);
}

//시간 관련 소스
char s,m,h;
char as,am,ah;
char a_on;//알람

//키관련 소스
char key;
char key_on;
char key_mode;
char key_count;
char key_buffer;

//1s
int us100;
char count_on;

//시간 변경 함수
void clock_count(){
if(key_mode==1){
}else if(key_mode==11){
}else{
if(count_on==1){
s++;
if(s==60){
m++;
s=0;
}
if(m==60){
h++;
m=0;
}
if(h==60){
h=0;
}
count_on=0;
}
}
}

void key_data(){
if(key_on==1){
//일반 모드
if(key_mode==0){
if(key==4){
key_mode=1;//시간 변경 모드
}else if(key==5){
key_mode=2;//알람 시간 변경 모드
}else if(key==6){
key_mode=3;//알람 설정 모드
}else if(key==7){
key_mode=0;
key_count=0;
}
//시간 시분초 결정
}else if(key_mode==1){
if(key==4){
key_count=(key_count+1)%6;//시분초
}else if(key==5){
key_count=(key_count+5)%6;//시분초
}else if(key==6){//시분초 변경 결정
key_mode=11;
key_buffer=key_count;
}else if(key==7){
key_mode=0;
key_count=0;
}
//시간 시분초 변경
}else if(key_mode==11){
if(key==4){
clock_data_p_change();
}else if(key==5){
clock_data_m_change();
}else if(key==6){
key_mode=1;
}else if(key==7){
key_mode=0;
key_count=0;
}
}else if(key_mode==2){
if(key==4){
key_count=(key_count+1)%6;//시분초
}else if(key==5){
key_count=(key_count+5)%6;//시분초
}else if(key==6){//시분초 변경 결정
key_mode=21;
}else if(key==7){
key_count=0;
key_mode=0;
}
//시간 시분초 변경
}else if(key_mode==21){
if(key==4){
alarm_data_p_change();
}else if(key==5){
alarm_data_m_change();
}else if(key==6){
key_mode=2;
}else if(key==7){
key_mode=0;
key_count=0;
}
}else if(key_mode==3){
if(key==4){
a_on=1;
}else if(key==5){
a_on=0;
}else if(key==6){
key_mode=0;
key_count=0;
}else if(key==7){
key_mode=0;
key_count=0;
}
}
key_on=0;
}
}

//시간 시분초 변경
void clock_data_p_change(){
if(key_count==0){
if((s%10)==9){
s=s-9;
}else{
s=s+1;
}
}else if(key_count==1){
if((s/10)==5){
s=s-50;
}else{
s=s+10;
}
}else if(key_count==2){
if((m%10)==9){
m=m-9;
}else{
m=m+1;
}
}else if(key_count==3){
if((m/10)==5){
m=m-50;
}else{
m=m+10;
}
}else if(key_count==4){
if((h/10)==2){
if((h%10)==3){
h=h-3;
}else{
h=h+1;
}
}else{
if((h%10)==9){
h=h-9;
}else{
h=h+1;
}
}
}else if(key_count==5){
if((h%10)<4){
if((h/10)==2){
h=h-20;
}else{
h=h+10;
}
}else{
if((h/10)==1){
h=h-10;
}else{
h=h+10;
}
}
}
}

//시간 시분초 변경
void clock_data_m_change(){
if(key_count==0){
if((s%10)==0){
s=s+9;
}else{
s=s-1;
}
}else if(key_count==1){
if((s/10)==0){
s=s+50;
}else{
s=s-10;
}
}else if(key_count==2){
if((m%10)==0){
m=m+9;
}else{
m=m-1;
}
}else if(key_count==3){
if((m/10)==0){
m=m+50;
}else{
m=m-10;
}
}else if(key_count==4){
if((h/10)==2){
if((h%10)==0){
h=h+3;
}else{
h=h-1;
}
}else{
if((h%10)==0){
h=h+9;
}else{
h=h-1;
}
}
}else if(key_count==5){
if((h%10)<4){
if((h/10)==0){
h=h+20;
}else{
h=h-10;
}
}else{
if((h/10)==0){
h=h+10;
}else{
h=h-10;
}
}
}
}

//시간 시분초 변경
void alarm_data_p_change(){
if(key_count==0){
if((as%10)==9){
as=as-9;
}else{
as=as+1;
}
}else if(key_count==1){
if((as/10)==5){
as=as-50;
}else{
as=as+10;
}
}else if(key_count==2){
if((am%10)==9){
am=am-9;
}else{
am=am+1;
}
}else if(key_count==3){
if((am/10)==5){
am=am-50;
}else{
am=am+10;
}
}else if(key_count==4){
if((ah/10)==2){
if((ah%10)==3){
ah=ah-3;
}else{
ah=ah+1;
}
}else{
if((ah%10)==9){
ah=ah-9;
}else{
ah=ah+1;
}
}
}else if(key_count==5){
if((ah%10)<4){
if((ah/10)==2){
ah=ah-20;
}else{
ah=ah+10;
}
}else{
if((ah/10)==1){
ah=ah-10;
}else{
ah=ah+10;
}
}
}
}

//시간 시분초 변경
void alarm_data_m_change(){
if(key_count==0){
if((as%10)==0){
as=as+9;
}else{
as=as-1;
}
}else if(key_count==1){
if((as/10)==0){
as=as+50;
}else{
as=as-10;
}
}else if(key_count==2){
if((am%10)==0){
am=am+9;
}else{
am=am-1;
}
}else if(key_count==3){
if((am/10)==0){
am=am+50;
}else{
am=am-10;
}
}else if(key_count==4){
if((ah/10)==2){
if((ah%10)==0){
ah=ah+3;
}else{
ah=ah-1;
}
}else{
if((ah%10)==0){
ah=ah+9;
}else{
ah=ah-1;
}
}
}else if(key_count==5){
if((ah%10)<4){
if((ah/10)==0){
ah=ah+20;
}else{
ah=ah-10;
}
}else{
if((ah/10)==0){
ah=ah+10;
}else{
ah=ah-10;
}
}
}
}

void lcd_display(){
if(key_mode==0){
lcd_cmd(0×80);
if(a_on==1){
lcd_str(“a_on s]”);
}else{
lcd_str(“a_off s]”);
}
lcd_dat(ah/10+0×30);
lcd_dat(ah%10+0×30);
lcd_dat(‘:’);
lcd_dat(am/10+0×30);
lcd_dat(am%10+0×30);
lcd_dat(‘:’);
lcd_dat(as/10+0×30);
lcd_dat(as%10+0×30);
PORTC=0X00;
lcd_cmd(0xc0);
lcd_str(“time] “);
lcd_dat(h/10+0×30);
lcd_dat(h%10+0×30);
lcd_dat(‘:’);
lcd_dat(m/10+0×30);
lcd_dat(m%10+0×30);
lcd_dat(‘:’);
lcd_dat(s/10+0×30);
lcd_dat(s%10+0×30);
}else if(key_mode==1){
lcd_cmd(0×80);
lcd_str(“time setting “);
PORTC=0X00;
lcd_cmd(0xc0);
lcd_dat(h/10+0×30);
lcd_dat(h%10+0×30);
lcd_dat(‘:’);
lcd_dat(m/10+0×30);
lcd_dat(m%10+0×30);
lcd_dat(‘:’);
lcd_dat(s/10+0×30);
lcd_dat(s%10+0×30);
lcd_dat(‘ ‘);
if(key_count==0){
lcd_str(“1s “);
}else if(key_count==1){
lcd_str(“10s “);
}else if(key_count==2){
lcd_str(“1m “);
}else if(key_count==3){
lcd_str(“10m “);
}else if(key_count==4){
lcd_str(“1h “);
}else if(key_count==5){
lcd_str(“10h “);
}
}else if(key_mode==11){
lcd_cmd(0×80);
if(key_count==0){
lcd_str(“time(1s) change “);
}else if(key_count==1){
lcd_str(“time(10s) change “);
}else if(key_count==2){
lcd_str(“time(1m) change “);
}else if(key_count==3){
lcd_str(“time(10m) change “);
}else if(key_count==4){
lcd_str(“time(1h) change “);
}else if(key_count==5){
lcd_str(“time(10h) change “);
}
PORTC=0X00;
lcd_cmd(0xc0);
lcd_dat(h/10+0×30);
lcd_dat(h%10+0×30);
lcd_dat(‘:’);
lcd_dat(m/10+0×30);
lcd_dat(m%10+0×30);
lcd_dat(‘:’);
lcd_dat(s/10+0×30);
lcd_dat(s%10+0×30);
lcd_str(” “);
}else if(key_mode==2){
lcd_cmd(0×80);
lcd_str(“alarm setting “);
PORTC=0X00;
lcd_cmd(0xc0);
lcd_dat(ah/10+0×30);
lcd_dat(ah%10+0×30);
lcd_dat(‘:’);
lcd_dat(am/10+0×30);
lcd_dat(am%10+0×30);
lcd_dat(‘:’);
lcd_dat(as/10+0×30);
lcd_dat(as%10+0×30);
lcd_dat(‘ ‘);
if(key_count==0){
lcd_str(“1s “);
}else if(key_count==1){
lcd_str(“10s “);
}else if(key_count==2){
lcd_str(“1m “);
}else if(key_count==3){
lcd_str(“10m “);
}else if(key_count==4){
lcd_str(“1h “);
}else if(key_count==5){
lcd_str(“10h “);
}
}else if(key_mode==21){
lcd_cmd(0×80);
if(key_count==0){
lcd_str(“alarm(1s) change “);
}else if(key_count==1){
lcd_str(“alarm(10s) change “);
}else if(key_count==2){
lcd_str(“alarm(1m) change “);
}else if(key_count==3){
lcd_str(“alarm(10m) change “);
}else if(key_count==4){
lcd_str(“alarm(1h) change “);
}else if(key_count==5){
lcd_str(“alarm(10h) change “);
}
PORTC=0X00;
lcd_cmd(0xc0);
lcd_dat(ah/10+0×30);
lcd_dat(ah%10+0×30);
lcd_dat(‘:’);
lcd_dat(am/10+0×30);
lcd_dat(am%10+0×30);
lcd_dat(‘:’);
lcd_dat(as/10+0×30);
lcd_dat(as%10+0×30);
lcd_str(” “);
}else if(key_mode==3){
lcd_cmd(0×80);
lcd_str(“alarm setting “);
PORTC=0X00;

lcd_cmd(0xc0);
if(a_on==0){
lcd_str(“alarm off “);
}else if(a_on==1){
lcd_str(“alarm on “);
}
}
}

char buzzer=0;
char ir_go=0;
void alarm_oper(void){
if(a_on==1){
if(h==ah){
if(m==am){
if(s==as){
buzzer=1;
ir_go=1;
}
}
}
}
if(buzzer==1){
PORTD|=0X02;
}else{
PORTD&=0Xfd;
}

delay_ms(1);

if(ir_go==1){
ir_car_run();
}
}

char sr,sl,sf,sb;
char ad_data;

void senser(){
ADMUX=0X60; //어느핀 변화시킬건지
ADCSRA=0XC7; //ad 변환 시작
while((ADCSRA&0×10)==0); //변수=AD값 걸림
ad_data=ADCH; //변수값에 저장
if(ad_data<100){
sf=1;
}else{
sf=0;
}
delay_ms(1);
ADMUX=0X61; //어느핀 변화시킬건지
ADCSRA=0XC7; //ad 변환 시작
while((ADCSRA&0×10)==0); //변수=AD값 걸림
ad_data=ADCH; //변수값에 저장
if(ad_data<100){
sb=1;
}else{
sb=0;
}
delay_ms(1);

ADMUX=0X62; //어느핀 변화시킬건지
ADCSRA=0XC7; //ad 변환 시작
while((ADCSRA&0×10)==0); //변수=AD값 걸림
ad_data=ADCH; //변수값에 저장
if(ad_data<100){
sr=1;
}else{
sr=0;
}
delay_ms(1);

ADMUX=0X63; //어느핀 변화시킬건지
ADCSRA=0XC7; //ad 변환 시작
while((ADCSRA&0×10)==0); //변수=AD값 걸림
ad_data=ADCH; //변수값에 저장
if(ad_data<100){
sl=1;
}else{
sl=0;
}
delay_ms(1);
}

void ir_car_run(){
senser();
//전방 물체 감지
if(sf==0){
//전후방 물체 감지
if(sb==0){
//오른쪽 물체 감지
if(sr==0){
//오른쪽으로 회전 7
PORTC=0X9C;
}else{
//왼쪽으로 회전 6 5 4
PORTC=0X6C;
}
}else{
//후방으로 전진 3 0
PORTC=0X5C;
}
}else{
//후방 물체 감지
if(sb==0){
//전방으로 전진 2 0
PORTC=0XAC;

}else{
//오른쪽 물체 감지
if(sr==0){
//전방으로 전진 1
PORTC=0XAC;
}else{
//왼쪽 물체 감지
if(sl==0){
//전방으로 전진 1
PORTC=0XAC;
}else{
//정지 0
PORTC=0X00;
}
}
}
}
}

void putch(unsigned char ch){ /* 한개의 문자를 pc로 송신한다. */
while((UCSR0A & 0×20) == 0);
UDR0 = ch;
UCSR0A |= 0×20; // 문자 송신준비완료
}

char ir_oper;

void ir_remote(){
//RC카
if(ir_rx_flag == 1){//데이터 전송
ir_rx_flag =0;
putch(ir_rx_data[0]);
putch(ir_rx_data[1]);
putch(ir_rx_data[2]);
putch(ir_rx_data[3]);
ir_oper=1;
//rc_data=ir_rx_data[2];
}
if(ir_oper==1){
if(ir_rx_data[2]==0×05){
//전방
PORTC=0XAC;
}else if(ir_rx_data[2]==0×08){
//왼쪽
PORTC=0X9C;
}else if(ir_rx_data[2]==0x0a){
//오른쪽
PORTC=0X6C;
}else if(ir_rx_data[2]==0x0d){
//뒤로
PORTC=0X5C;
}else if(ir_rx_data[2]==0×04){
ir_go=1;
}else{
PORTC=0X00; }
ir_oper=0;
}
}

int main(void){
DDRA = 0Xff; //LCD
DDRC = 0Xff; //모터 구동
DDRD = 0×02; //리모컨 소자
DDRE = 0X0f; //스위치
DDRF = 0X00; //거리 측정 센서

//외무 인터럽트
EICRB = 0XFF;//상승 에지
EICRA = 0XFF;//상승 에지
EIMSK = 0Xf1;//0-초음파,1-리모컨
UBRR0H = 0;
UBRR0L = 103; // baud rate 9600
UCSR0A = 0;
UCSR0B = 0×98;
UCSR0C = 0×06;
//타이머 카운트 100us
TIMSK = 0X02;
TCCR0 = 0X0A;
OCR0 = 200;
SREG = 0X80;
putch(0×03);
lcd_init();
//lcd_ln11();
//lcd_str(“COME ON”);
delay_ms(10);
while(1){
//물체 회피 및 부저
alarm_oper();
//리모컨 컨트롤
ir_remote();
//키스위치
key_data();
//LCD
lcd_display();
//시간 카운터
clock_count();
delay_ms(10);
}
}

SIGNAL(INT4_vect){
// lcd_cmd(0×80);
// lcd_str(“adfag”);
// delay_ms(3000);
key_on=1;
key=4;
buzzer=0;
ir_go=0;
PORTC=0X00;
}
SIGNAL(INT5_vect){
key_on=1;
key=5;
buzzer=0;
ir_go=0;
PORTC=0X00;
}
SIGNAL(INT6_vect){
key_on=1;
key=6;
buzzer=0;
ir_go=0;
PORTC=0X00;
}
SIGNAL(INT7_vect){
key_on=1;
key=7;
buzzer=0;
ir_go=0;
PORTC=0X00;
}
SIGNAL(TIMER0_COMP_vect){
ir_timer_cnt++;
us100=us100+1;
if(us100==10000){
us100=0;
count_on=1;
}
}

SIGNAL(INT0_vect){
switch(ir_state){
case ir_ready : //오류 데이터
ir_state = ir_lead;
break;
case ir_lead ://시작 관련 데이터
//리드 펄스 데이터가 오면 4.5ms과 0.5ms의 합의 구간인 5ms
//0×30=48, 0×35=53이다
if((ir_timer_cnt>=0×30) && (ir_timer_cnt<0×35)){
ir_state = ir_data;
}else{
ir_state = ir_state;
}
ir_bit_cnt = 0;
ir_rx_temp = 0;
break;

case ir_data ://진짜 데이터 3번 데이터 활용
if((ir_timer_cnt>=8) && (ir_timer_cnt<15)){ //0데이터
}else if((ir_timer_cnt>=19) && (ir_timer_cnt<30)){//1데이터
ir_rx_temp = ir_rx_temp|0×80;
}else { //에러 데이터
ir_state = ir_lead;
break;
}
ir_bit_cnt++;
////8개의 비트가 다 들어 올 경우 데이터화 시킴
if((ir_bit_cnt%8)==0){
ir_rx_data[(ir_bit_cnt/8)-1] = ir_rx_temp;
ir_rx_temp = 0;
if(ir_bit_cnt >= 32){//모든 데이터 다 들어 올 경우
ir_state = ir_ready;
ir_bit_cnt = 0;
ir_rx_flag = 1;//새로운 데이터 인식 변수
}
}
ir_rx_temp = ir_rx_temp>>1;//데이터화 하는 과정
break;
default: break;
}
ir_timer_cnt = 0;
}

입선을 수상하신 경성대학교 김가희님 외 3분께 진심으로 축하의 말씀을 드립니다.
수상하신 팀에게는 적립금 10만원과 함께 (주)칩센에서 제공해드리는 소정의 경품이 제공되었습니다.
다음호에는 경성대학교 전력 실험실 김민욱 외 4명이 수상한 ‘천지인 스위치 광고판’이 소개될 예정입니다.