MBED 를활용한 ARM Cortex-M 마이크로컨트롤러프로그래밍 김영주 이춘태 양해성 장은영 김대환지음 본교재는교육부의프라임 (PRIME) 사업사업비를받아개발한교재임
차례 1. 임베디드시스템, 마이크로컨트롤러, 그리고 ARM 1 1.1 개요 1 1.2 임베디드시스템개요 3 1.3 마이크로컨트롤러개요 5 1.4 프로그램의수행및프로그램언어 11 1.5 ARM 마이크로컨트롤러개요 14 2. 개발환경구축 22 2.1 실습보드 22 2.2 개발소프트웨어개요 25 2.3 개발소프트웨어다운로드및설치 30 2.4 MDK-ARM 소프트웨어라이센스등록 32 2.5 추가소프트웨어설치 34 2.6 개발소프트웨어실행테스트 40 3. MBED 라이브러리 48 3.1 MBED 라이브러리개요 49 3.2 MBED 온라인컴파일을이용한프로그램생성 56 3.3 MDK-ARM 툴을위한 MBED 프로젝트템플릿만들기 60 3.4 MDK-ARM 툴을이용한코드디버깅 65 i
4. LED 제어 69 4.1 LED 점멸하기 69 4.2 프로그래밍에러다루기 76 4.3 LED 점멸프로그램확장 79 5. 스위치제어 85 5.1. 스위치입력 85 5.2 Toggle 스위치 92 5.3 LED 반복점멸 95 5.4 타이머를이용한 LED 점멸 100 6. PC 와 USB 통신하기 104 6.1 터이널프로그램설치 104 6.2 에코 (Echo) 실습 106 6.3 시리얼카운터 (Serial Counter) 출력 109 7. 디지털입력센서 115 7.1 초음파거리센서 (Ultrasonic Range Finder) 115 8. PWM(Pulse Width Modulation) 120 8.1. DC 모터 122 8.2 RGB LED 134 ii
9. 소리 (Sound) 138 9.1 Beep음생성 138 9.2 고주파호루라기 140 9.4 사운드센서 147 10. 빛 (Light) 153 10.1 빛의세기측정 ( 조도센서 ) 153 10.2 적외선개요 156 10.3 적외선을이용한거리측정센서 157 10.4 적외선감지센서 (PIR, Pyroelectric InfRared Motion Sensor) 160 10.5 자외선센서 166 11. 인터럽트, 타이머그리고태스크 171 11.1 타이머 (Timer) 와인터럽트 (Interrupt) 171 11.2 태스크 (Task) 171 11.3 MBED 라이브러리를이용한인터럽트처리 175 11.4 타이머 (Timer) 179 11.5 MBED 타이머 (Timer) 181 11.6 MBED Timeout 183 11.7 MBED Ticker 185 11.8. 아날로그조이스틱 186 iii
12. 동기식시리얼통신 189 12.1 동기식시리얼통신 (Synchronous Serial Communication) 189 12.2 SPI(Serial Peripheral Interface) 통신 193 12.3 SPI 마스터설정 194 11.4 가속도센서 (Accelerometer) 195 12.5 I2C(Inter-Integrated Circuit) 통신 199 12.6 기압센서 (Barometric Pressure Sensor) 206 13. MBED 장치드라이버활용 212 13.1 온도-습도센서 212 13.2 심박센서 (Heart Rate Sensor) 216 13.3 OLED 디스플레이 222 14. 무선통신 (Wireless Communication) 225 14.1 블루투스통신 (Bluetooth Communication) 225 14.2 Wi-Fi 통신 (Wi-Fi Communication) 229 15. 응용과제 239 15.1 BLE, 클라우드서버와연동하는휴대용온 / 습도계 239 15.2 자동조명 245 15.3 스마트워치 251 15.4 스마트밴드 256 iv
부록 A C 언어문법 264 1. 프로그램의구성요소 264 2. 데이터형과연산자 266 3. 제어문 273 4. 함수와기억클래스 275 5. 배열과포인터 277 부록 B - Cortex-M4 아키텍처와프로그래머모델 280 1. Cortex-M4 아키텍처와프로그래머모델 280 2. ARM v7-m 프로그래머모델 297 부록 C MBED Library API 요약 324 1. Digital I/O 324 2. Analog I/O 327 3. Timers 328 4. Digital Interfaces 331 v
vi
1. 임베디드시스템, 마이크로컨트롤러, 그리고 ARM 1.1 개요 현재의세상은디지털세상이라한다. 일상생활에서사용하고있는많은기기들이아날로그방식에서디지털방식으로바뀌고있다. 아날로그 TV는성능좋은디지털 TV로바뀌고, LP 레코드는디지털방식을쓰는 CD로, VHS 비디오는 DVD로, 등등. 그렇지만많은사람들이디지털을사용하면기기들이왜좋은성능을갖는지는정확히알지못한다. 그냥디지털을쓰면아날로그보다좋은줄안다. 디지털의핵심은단순성이다. 디지털시스템에서신호선하나는단두개의상태를갖는다. 이두가지상태를 1과 0으로표시하기도하고 True/False 또는 HIGH/LOW 로표시한다. 얼마나간단한표시방법인가. 그러면이간단한신호로무엇을할수있을까? 디지털신호선 2개를사용하여보자. 신호1 신호2 0 0 0 1 1 0 1 1 =4 가지를표현할수있다. 아직도표현가능한개수는좀작아보인다. 신호선여러개사용할때표현가능한상태의가지수는 2의승수로표현된다. 신호선 3개를사용하면 =8 가지, 10개를사용하면 =1024 가지, 20개를사용하면 =1024 1024 가지로약 100만가지를표현할수있다. 신호선이늘어남에따라표현가능한개수는기하급수적으로늘어난다. 가장간단한신호를조합함으로써어마어마한양의정보를표현할수있는것이다. 디지털시스템에서는이와같이여러신호선을사용하여매우복잡한정보를표현한다. 그러나아무리많은신호선을사용하더라도표시할수있는정보의수는유한하다. 이는각정보를유한한숫자로표시할수있다는것이다. 디지털의사전적의미는숫자이다. 즉디지털은모든정보를숫자로표현하여전달하는것이다. 만약 4가지정보를전달하려면신호선 2개, 100만가지정보를전달하려면신호선 20개를사용하면된다. 이에비해아날로그신호는신호선하나로무한한수를표시한다. 그럼왜무한한 1
수를표시할수있는아날로그신호보다유한한수를표시할수있는디지털신호가왜사용하는걸까? 아날로그신호에서전달되는신호는무한히많은것중에하나이므로받는곳에서는이를 100% 정확하게판독하는것은불가능하다. 약간의오차가있을수밖에없다. 그러나디지털에서는 1/0만사용하는신호선을여러개사용하지만각신호선에서는보내는정보가 1/0으로만표시되므로이것을받는곳에서잘못판독할확률은거의없다. VHS 비디오테입과 DVD를비교해보라. 아날로그신호를담고있는 VHS 비디오테입은복사를여러번할수록화질이떨어지지만디지털신호를담고있는 DVD 는아무리복사를하여도화질이떨어지지않는다. 마이크로컨트롤러 (Microcontroller) 는이러한디지털신호를고속으로처리하는두뇌역할을하는장치이다. 디지털신호를처리하는거의모든기기에는마이크로컨트롤러가들어있다고생각하면된다. 마이크로컨트롤러는차량, 모바일폰, 가전제품, 오피스제품, 오락기, 항공기등셀수없을정도로많은곳에사용되어제품에서정보처리를담당한다. 마이크로컨트롤러는임베디드시스템이라부르는, 우리가매일사용하는제품에내장되어제품의기능을제어하고있다. 어린이용장난감, TV, 냉장고, 에어컨등가정용기기에서부터산업체의생산장비, 자동차, 선박, 항공기, 미사일, 인공위성등최첨단시스템에이르기까지거의모든시스템에서마이크로컨트롤러가핵심적인장치로서사용되고있다. 마이크로컨트롤러가보유한능력이란매우단순하다. 고속으로수의연산을처리하는것이다. 능력을어떻게사용할것인가는사람이부여한다. 같은마이크로컨트롤러로 DVD 플레이어를만들수도있고인공위성의제어장치를만들수도있는것이다. 마이크로컨트롤러가어떤일을하도록하는것을 마이크로컨트롤러를프로그래밍한다. 라고한다. 사람에게일을시킬때언어를사용하는것처럼마이크로컨트롤러에게일을시킬때도언어를사용한다. 이러한언어를컴퓨터언어라한다 ( 마이크로컨트롤러는컴퓨터를구성하는주요요소이므로마이크로컨트롤러와컴퓨터가구별없이사용되기도한다 ). 마이크로컨트롤러는엄청나게많은종류가있지만기본적인개념은거의유사하므로하나의마이크로컨트롤러를배우면다른마이크로컨트롤러를접하더라도쉽게응용할수있다. 불과얼마전까지만해도임베디스시스템을설계하던사람은전자공학전문가이거나소프트웨어전문가혹은이둘에모두능통한사람이어야했다. 이제는전문가나초보자모두사용자친화적이며뛰어난기능블록을이용하여빠르게, 그리고성공적인임베디드시스템설계에집중할수있게되었다. 2
1.2 임베디드시스템개요 우리모두는놀라운처리능력을보여주는데스크탑 (Desktop) 혹은랩탑 (Laptop) 컴퓨터에친숙하다. 이들컴퓨터는범용적이다. 우리는컴퓨터에서실행되는어플리케이션프로그램에따라, 그리고시점에따라각기다른일을처리하게하는것이가능하다. 이같은컴퓨터의중심에는복잡한전자회로로제작되어컴퓨터의핵심기능을수행하는마이크로컨트롤러가있다. 또한컴퓨팅과전혀관계가없는제품, 예컨대토스트, 카메라, 혹은세탁기와같은제품에도있을수있다. 컴퓨터가제품안에있지만사용자는볼수도없고, 그곳에있는지조차모를수있다. 더나아가키보드, 스크린혹은마우스같은컴퓨터부속물을어디에서도찾을수없다. 이런제품을임베디드시스템이라부르는데, 말그대로컴퓨터가제품속으로내장되기때문이다. 많은경우임베디드시스템은주로제어에집중하기때문에여기에서사용되는마이크로프로세서는범용머신과는다른특징을갖도록개발되며, 이를마이크로컨트롤러라부른다. 마이크로컨트롤러는마이크로프로세서에비해훨씬많은양이판매되고있고, 이에따른영향도상당히크기때문에전자시스템설계자에게는커다란기회가된다. 임베디드시스템은여러가지형태가있다. 세탁기, 식기세척기, 오븐등도임베디드시스템이다. 자동차는엔진관리, 보안 ( 예를들어, 도난방지시스템 ), 에어컨, 라디오등임베디드시스템으로가득차있다. 이외에도산업계에서기계제어, 공장자동화, 로봇, 전자상거래등에서임베디드시스템을발견할수있다. 이같은목록은끝이없을정도로점점성장하고있다. 그림 1.1은임베디드시스템을간단한블록도로표현한것이다. 보통은마이크로컨트롤러인임베디드컴퓨터가메모리에저장된해당어플리케이션의전용프로그램을계속실행한다. 마이크로컨트롤러는일반적으로여러프로그램을실행하는범용데스크탑컴퓨터와달리하나의프로그램을끊임없이실행한다. 그림 1.1 임베디드시스템구성및역할 3
입력단에서제공된정보를기초로시스템내에현재상태를반영하여출력값을계산하고이를출력단을통해내보낸다. 우리는전자부품과실제전자회로를 하드웨어 라하고하드웨어에서실행되는프로그램을종종 소프트웨어 라부른다. 이들외에, 키패드와디스플레이를통해서사용자와상호작용을하거나, 필수적인것은아니지만일반적인개념에의해그밖의다른서브시스템들과연동할수도있다. 이외의중요한변수는시간으로서우리가임베디드시스템에서하는모든일에영향을미치게된다. 우리는시간을측정하거나, 정해진시간에어떤일이정확하게일어나도록하거나, 데이터스트림또는시간에종속된시그널을만들거나, 예기치못한일에응답할수있는능력등을필요로하게된다. 자동판매기 (vending machine) 는임베디드시스템의한가지좋은예이다. 그림 1.2 는자동판매기의구성을개괄적으로보여주는블록도이다. 이기기의핵심은마이크로컨트롤러이다. 그림에서보여주듯이사용자키패드, 동전제어모듈, 그리고기기자체에서많은입력신호들을받아들이고, 입력에따라다양한출력신호를만들어낸다. 입력 출력 키패드 동전제어모듈 마이크로컨트롤러 액정표시장치 게이트위치센서 모바일통신모듈 그림 1.2 자동판매기구성도 만약이용자가버튼을누르거나동전을투입하면, 먼저키패드가시그널을마이크로컨트롤러에게보내게되고, 마이크로컨트롤러는입력된개별키값을인지해서보다복잡한메시지를만들게된다. 동전제어모듈역시얼마의돈이투입되는지에대한정보를보내게될것이다. 마이크로컨트롤러는수신된정보를바탕으로추론하여액정표시장치로상태정보를내보내게된다. 유효한제품과충분한돈이제공되었다면제품을배출하기위해게이트작동장치를구동하고, 그렇지않다면돈을추가로넣거나제품코드를다시입력하라는메시지를표시하게될것이다. 제품이배출되면기기에있는센서가그제품을이용할수있는지확인하고최종적으로동작 4
을완료하게될것이다. 이내용이그림 1.2에서는게이트위치센스로표시되어있다. 최신의자동판매기는여기서한걸음더나아가문제가진단되면직접관리팀으로보고하여문제를해결할수있도록모바일통신기능을가지기도한다. 또한기기의재고량을모바일이나인터넷통신을통해서비스팀으로보고하여제품이부족해질때마다방문해서채울수있도록할수도있다. 이런예는정확히처음에보여준그림 1.1의내용과일치한다. 마이크로컨트롤러는입력변수를받아서이를바탕으로계산하여결정을내리고응답을하게된다. 이경우사용자인터페이스가존재하게되고최신기기에서는네트워크인터페이스도갖추고있다. 위그림이시스템의물리적인하드웨어를설명하는것으로보일수있지만사실이모든것은설계자가작성한소프트웨어에의해제어된다. 소프트웨어는마이크로컨트롤러에서실행되어시스템이실제로무슨일을할지를결정한다. 마이크로컨트롤러는컴퓨터다. 하지만컴퓨터에비해연결방법이복잡하고직접연결하는것이귀찮게느껴질수도있다. 그러나입출력장치를연결하면마이크로컨트롤러는컴퓨터와동일한동작을수행할수있으며, 입출력장치들을연결함으로써컴퓨터보다더쉽게작은컴퓨터로기능하게할수있다. 1.3 마이크로컨트롤러개요 마이크로컨트롤러는주변장치를제거하고케이스를벗겨낸컴퓨터와동일하므로컴퓨터를이해하면좀더쉽게마이크로컨트롤러에다가갈수있다. 먼저컴퓨터의구조부터살펴보자. 범용컴퓨터는그림 1.3과같이연산의핵심이되는연산장치, 연산을제어하는제어장치로이루어지는중앙처리장치 (Central Processing Unit, CPU), 데이터입출력을위한입출력장치, 데이터를저장하는주기억장치및보조기억장치등으로구성된다. 중앙처리장치 (Central Processing Unit, CPU) 마이크로프로세서 라고도하며, 연산 ( 사칙연산, 비교등 ) 을수행하는장치로서내부에는연산에필요한제한된개수의레지스터 ( 일종의메모리 ) 를가지고있다. 일반적인마이크로프로세서 (Pentium Processor 등 ) 는많은양의데이터를저장할메모리를가지고있지않을뿐만아니라입출력을위한장치 ( 디지털입출력장치, 타이머등 ) 를가지고있지않다. 따라서컴퓨터를구성하기위해서는마이크로프로세서외부 5
그림 1.3 범용컴퓨터의구조 에메모리와입출력장치를장착하여야한다. 메모리 (Memory) 프로그램과데이터를저장하는장치이다. 프로그램은 CPU에내리는명령의집합으로메모리에저장된다. CPU는메모리에있는명령어를읽고, 이에따라작업을수행한다. 프로그램수행에필요한데이터역시메모리에저장된다. 메모리는크게두가지로분류된다. 1 RAM(Random Access Memory) : 읽기 / 쓰기가가능한메모리로서전원이꺼지면내용이사라진다. 2 ROM(Read Only Memory) : 읽기만가능한메모리로서전원이꺼지더라도내용이보존된다. PC에서는대부분이 RAM이며, ROM은아주작은부분을차지한다. ROM은전원이꺼지더라도내용이지워지지않으므로전원을켰을때처음수행하는작업은 ROM에저장한다. PC의 BIOS는전원을켰을때하드디스크에들어있는 Windows 를메모리에옮기는작업을하는프로그램으로 ROM에들어있다 ( 이런작업을부팅이라한다 ). 6
입출력장치 (Input/Output Device) 외부장치로부터입력을받거나, 외부장치에데이터를출력하는장치이다. 예로서 A/D 컨버터, 디지털입출력장치등이있다. 마이크로컨트롤러를말할때흔히 8-비트또는 16-비트마이크로컨트롤러라는용어를쓴다. 이때 8-비트마이크로컨트롤러라함은 CPU에서연산할때기본적인처리단위가 1바이트 (8-비트 ) 임을나타낸다. 8-비트마이크로컨트롤러의기본연산단위가 1바이트이므로 2바이트연산을위해서는 2번의계산이필요하게된다. 그러나 16-비트마이크로컨트롤러의기본연산단위는 2바이트이므로 1번의계산으로 2바이트연산을수행할수있어 8-비트마이크로컨트롤러보다는빠른처리속도를보인다. 1.3.1 메모리메모리는데이터를읽고쓸수있느냐읽기만할수있느냐에따라읽고쓰기메모리 ( 일반적으로 RAM이라고하나정확한표현은 Read Write Memory가맞다 ) 와읽기전용메모리 (ROM: Read Only Memory) 로나뉜다. 그림 1.4는컴퓨터에서기억장치로사용되고있는메모리의종류를보여준다. 그림 1.4 메모리장치분류 (1) RAM(Random Access Memory) RAM 은전원이꺼지면기억이소멸되는메모리이므로휘발성메모리 (Volatile 7
Memory) 라고도한다. SRAM(Static RAM) 전원이공급되어있는동안기억을유지하는메모리이다. 사용이간단한장점은있지만각각의기억비트 ( 기억장소의최소단위로 cell이라한다 ) 를구성하는데여러개의반도체소자가사용되어한메모리칩당구성할수있는기억용량이적다는단점이있다. 많은용량의메모리가필요하지않는제어시스템에서는주로 SRAM을사용한다. DRAM(Dynamic RAM) 메모리의한셀을구성하는데단한개의반도체소자를사용하기때문에같은면적의칩에많은용량의기억장소를구성할수있어서주로대용량의메모리시스템을구축하는데사용되지만전원이투입되어있어도일정시간 ( 보통 4 msec) 이경과되면저절로기억내용이소멸되기때문에주기적으로기억내용을다시충전 (refresh 라한다 ) 시켜야하는단점이있다. 이로인해메모리칩외부에리프레시를위한별도의복잡한회로가필요로하며, 간단한시스템에서는사용하기힘들고주로대용량메모리가필요한컴퓨터시스템에서사용된다. PC의주기억장치는거의전부 DRAM 을사용하고있다. (2) ROM(Read Only Memory) 전원이꺼져도기억이소멸되지않는메모리이다. 비휘발성메모리 (Non-volatile Memory) 라고도한다. 마스크 (mask) ROM ROM 제조시프로그램코드에맞게내부회로를고정시켜제작하는것으로서읽기만가능하고더이상의지우기와쓰기는불가능하다. 메모리제조회사에서대단위로판박이처럼만들기때문에가격이저렴하지만소량의주문은받지않아소규모생산에는적합하지않다. PROM(Programmable ROM) : OTP(One Time Programmable) 내부메모리셀들에전기적으로녹일수있는퓨즈를달아두고외부에서프로그래밍을통해퓨즈를녹일수있으며해당비트의퓨즈가녹아떨어지면 1, 그대로있으면 0이기억되는식으로단한번의프로그래밍이가능하다. 일단프로그램이써 8
지면 ROM 외부에서의어떤작업으로도내부에있는녹은퓨즈를다시붙일수가없 기때문에다시지우고쓰기가불가능하다. 이런특성때문에 OTP 라고도하며가격 이싸서마스크 ROM 을제작할수없는소량의제품개발시주로사용된다. EPROM(Erasable PROM) ROM 모듈에투명한창이달려있으며이창에자외선을쪼이면내부메모리셀들의기억이지워지고초기상태로돌아가서새로운쓰기가가능하다. 이런지우기 / 쓰기가수십만번반복적으로가능하여과거에주로사용되었던형태이다. EEPROM(Electrically Erasable Programmable ROM) EPROM 은기존의기억을지우기위해기판에삽입된 ROM을빼내서전용의자외선소거기에넣어기억을지운후다시전용의쓰기장치 (ROM Writer) 에넣어새로운프로그램을써넣어야하는불편한점이있었다. 반면 EEPROM 은 ROM이기판에삽입된상태로전기적인신호에의해기억을지울수있고새로운내용을써넣을수있어서아주편리하다. FLASH 메모리 EEPROM 의한종류로서요즘가장널리사용되는 ROM 메모리이다. EEPROM 과마찬가지로회로에삽입된채로지우고쓸수가있고특히메모리전체내용을한순간에지우고다시쓸수있어서종래의 EEPROM 보다속도가훨씬빠르다. 종래의 EEPROM 은한순간한바이트씩지우거나쓸수있었다. ROM 은한번프로그램하면거의변경하는일이없다. 이런의미로흔히 ROM 에쓰 진프로그램을하드웨어와소프트웨어의중간개념인펌웨어 (firmware) 라한다. 1.3.2 CPU와메모리의데이터교환 주소 (address) 메모리에데이터를쓰려면저장할장소의위치를지정하여야한다. 마찬가지로메모리에서데이터를읽어오려면데이터가있는위치를지정하여야한다. 이러한메모리의위치를주소라고한다. 메모리의기본접근단위는바이트이므로바이트단위로주소가지정된다. 메모리의주소는숫자로표시되며 2진수로표시된수가 CPU 9
와메모리를연결한주소버스를통해메모리에전달된다 ( 버스는다수의신호선을말하며주소를지정하기위해다수의신호선이사용하므로주소버스라부른다 ). 이때주소버스의크기, 즉, 비트수가 CPU에서지정할수있는메모리의주소의최대크기를정한다. ( 예 1) 주소버스가 16-bit 일때지정가능한주소는 16진수로 0x0000 부터 0xFFFF 까지이며, 메모리크기는 bytes = bytes = 64 Kbytes 이다. 따라서장착할수있는메모리의최대크기는 64Kbyte 이다. ( 예 2) 주소버스가 32-bit 이면장착가능한메모리크기는 bytes = bytes = 4 Gbytes 이다. 주소디코딩 CPU와메모리또는입출력장치간의데이터교환은 CPU와장치를연결하는데이터버스를통하여이루어진다. 이데이터버스는모든메모리와입출력장치가공유하므로 CPU가지정한장치만이데이터버스를사용하도록교통정리가필요하다. 이는주소를사용하여이루어지며, CPU가주소를지정하면해당주소를가진장치만데이터버스를사용하도록회로를구성한다. 이를주소디코더 (Address Decoder) 이라한다. 데이터교환메모리에서데이터를읽어들일때는 CPU는주소버스를통해메모리의위치를지정하게되고, 주소디코더에의해해당메모리는데이터버스에데이터를보낸다. CPU는데이터버스의내용을읽게된다. 메모리에데이터를쓸때는 CPU는메모리의위치를주소버스로전달하고, 데이터버스에데이터를보낸다. 해당메모리는데이터버스의데이터를받아저장한다. 데이터의단위 디지털세계에서는하나의신호선이 0 또는 1(False 또는 True) 의두가지경우만표 시하므로신호를표시하기위해서는 2 진수를사용하는것이가장편리하다. 따라서 데이터는 2 진수를사용하여나타낸다. Ÿ 비트 (bit) : 이진수 (Binary digits) 의약자로서 0 또는 1 을가진다. Ÿ 니블 (nibble) : 4 비트를니블이라부른다. 니블로 2 4 = 16 개의수를표시할수있으 므로 1 자리의 16 진수를표현할수있다. 10
Ÿ 바이트 (byte) : 8 비트를바이트라부른다. 이는데이터의크기를표시할때가장기 본적인단위이다. 1 바이트로 2 8 = 256 개의수를표시할수있다. Ÿ 워드 (word) : 2 바이트를워드라부른다. 1 워드로 2 16 = 65536 개의수를표시할수 있다. Ÿ 패러그래프 (Paragraph) : 16 바이트를말한다. Ÿ 페이지 (Page) : 256 바이트를말한다. Ÿ Kbyte : 일반적으로 Kilo 는 = 1000 배를뜻하나, 컴퓨터에서 Kilo 는 =1024 배를가리킨다. 따라서 1 Kbyte = 1024 bytes 이다. Ÿ Mbyte : 일반적으로 Mega 는 = = 1000000 배를뜻하나, 컴퓨터에 서는 = 배를가리킨다. 1 Mbyte=1024 Kbytes 이다. Ÿ Gbyte : 일반적으로 Giga 는 = 배를뜻하나, 컴퓨터에서는 = 배를가리킨다. 1 Gbyte = 1024 Mbytes 이다. 1.4 프로그램의수행및프로그램언어 그림 1.5 프로그램로딩과실행 프로그램의수행 프로그램은 CPU 가실행할명령어들의집합이며프로그램과프로그램수행에필 11
요한데이터는그림 1.5와같이메모리에저장된다. CPU내에는 Program Counter(PC) 라는레지스터가있어실행하여야할명령어가위치한메모리위치를가리킨다. CPU 는 PC 레지스터가가리키는명령어를 CPU 내부로읽어와실행한다. 명령어의실행과동시에 PC 레지스터는자동적으로다음명령어가있는메모리위치를가리키도록수정된다. 따라서프로그램을수행하기위해서는 PC 레지스터를프로그램의초기위치를가리키도록하면된다. 프로그램의로딩및수행 PC(Personal Computer) 와같은일반적인컴퓨터에서는여러종류의프로그램을필요에따라수행하여야하므로모든프로그램을미리메모리에저장할수가없다. 따라서필요에따라서프로그램을메모리에로딩 (loading) 하여실행시켜야한다. 보통프로그램들은하드디스크에저장되어있다. Windows에서는해당아이콘더블클릭하면해당프로그램을하드디스크에서읽어들여메모리에로딩하고 Program Counter 레지스터를프로그램의초기위치로설정하여프로그램실행을시작한다. 이러한일을수행하는 Windows 같은프로그램을운영체제 (Operating system) 라고한다. 제어용임베디드컴퓨터인경우주로하나의전용프로그램만실행한다. 따라서프로그램을메모리에미리저장해놓는다. 이때전원이꺼지더라도프로그램이없어지지않도록메모리는 ROM을사용한다. 그러나프로그램을개발할때는프로그램이계속변경되므로이방법을사용할수없다. 또한임베디드컴퓨터의구성이나성능등의이유로프로그램개발은임베디드컴퓨터에서는할수없으므로주로 PC에서개발을수행하고생성된실행이미지를임베디드컴퓨터의메모리에다운로드하여실행한다. 그러나 ROM으로플래시메모리를사용하기전에는 ROM에프로그램을저장하는것이 ROM Writer 전용장치를사용하여야하는매우번거로운작업이었다. 그래서개발중에는프로그램을 RAM에저장하고개발이끝나면 ROM Writer로완성된프로그램을 ROM에굽는방법을사용하였다. 그러나플래시메모리를 ROM으로사용하면서 PC에서손쉽게프로그램을플래시메모리에다운로드를할수있게되었다. 프로그램언어 마이크로컨트롤러에게작업을수행시키려면컴퓨터언어를사용하여작업이수행 되도록프로그램을작성하여야한다. 1 기계어 (Machine Language) : CPU 에대한명령어는숫자로표기되다. 이렇게 숫자로표기된명령어를기계어라하며메모리에프로그램은기계어형태로저장된 12
다. 마이크로컨트롤러마다고유한기계어를가지고있다. 2 어셈블리언어 (Assembly Language) : 숫자로표시된기계어를보고명령을이해하는것은거의불가능하므로마이크로컨트롤러제조사에서는각기계어명령에 1:1 대응하여명령의의미를내포하는문자 (symbol) 로표현하는어셈블리언어를제공한다. 예를들어 CPU내의 A 레지스터의내용을 B 레지스터에이동하는명령이기계어로 038H(16 진수 ) 라고하자. 038H 형태의명령은내용을이해하기힘드나이를다음과같이표현하면의미가명확해진다. 기계어 : 038H 어셈블리 : MOV A, B 어셈블리명령어는기계어에 1:1 대응하므로마이크로프로세서마다고유하다. 3 어셈블러 (Assembler) : CPU 가이해할수있는명령어는기계어이므로어셈블리 언어로작성된프로그램은기계어로번역되어야한다. 어셈블러는어셈블리언어로 작성된프로그램을기계어로번역하는소프트웨어를말한다. 4 고급언어 (High Level Language) : 어셈블러명령어가수행명령의의미를내포하고있다고는하나이를이용해서프로그램을하는것역시매우번거로운일뿐만아니라마이크로컨트롤러마다고유한어셈블리언어를가지고있으므로마이크로컨트롤러의종류가바뀌면어셈블리프로그램은재작성하여야한다. 이에비해고급언어는수학적인연산이나논리를사용자가이해하기쉽도록또한사용마이크로컨트롤러와는무관하도록정의하여프로그램작성을좀더용이하도록한다. C-언어, FORTRAN 등이대표적인고급언어이다. 5 컴파일러 (Compiler) : 고급언어로작성된프로그램을마이크로컨트롤러가수행가능한기계어로번역하는소프트웨어를컴파일러라한다 (C-컴파일러는 C-언어를기계어로번역하는소프트웨어이다 ). 고급언어는마이크로컨트롤러에무관하도록정의되었으므로컴파일러만있으면프로그램의수정없이다른마이크로컨트롤러에도사용할수있다 ( 경우에따라서약간의수정이필요하다 ). 2 진수와 16 진수의표현을번거로운아래첨자를사용하지않고 C- 언어나어셈블리언 어에서사용하는법을채용하여다음두가지방법을혼용해서사용할것이다. 13
어셈블리에서사용하는방법 11110000B - 뒤의 B 는 Binary Number(2 진수 ) 를나타냄. 0ABH - 앞의 0 은숫자임을나타내고, 뒤의 H 는 Hexadecimal Number(16 진수 ) 를나타냄. C- 언어에서사용하는방법 0b10010011 - 앞의 0b 는이진수임을나타낸다. 0xAB - 앞의 0x 는이수가 16 진수임을나타낸다. 1.5 ARM 마이크로컨트롤러개요 1.5.1 ARM 역사 1981년영국의방송회사가컴퓨터교육프로젝트를발족하고여기에사용될컴퓨터공급회사를공개입찰하였고, Acorn computer란회사가선정되었다. 이컴퓨터는아주대중적인기기가되었으며, 영국의학교와대학에서광범위하게사용되었다. Acorn은 MOS Technology 사에서만든 6502 마이크로프로세서를사용했다. 이것은 8- 비트프로세서로서오늘날에는거의사용되지않지만그당시에는아주뛰어난제품이었다. 데스크탑혹은개인용컴퓨터에대한관심이증가하는상황이었으므로 1981년 IBM에서보다강력한 Intel의 16-비트마이크로프로세서 8088을기반으로첫번째개인용컴퓨터 (PC: Personal Computer) 를만들었다. 당시 Apple을비롯하여유사한컴퓨터를생산하던많은회사가있었다. 이런초기기기들은서로호환되지않았고주도적인기기도분명하지않았다. 그러나 1980년대를지나면서 IBM PC의영향력이증대되고작은경쟁회사들은시장에서사라지게되었다. 영국에서는 Acorn 의성공에도불구하고제품이잘판매되지않았으며미래도밝지못했다. 이즈음 Acorn 의영리한엔지니어들이세가지커다란도약을성취하게되었다. 그들은새로운컴퓨터를출시하기원했고필수적으로 6502 마이크로프로세서에서벗어나야했지만그들이필요로하는만큼향상된성능의프로세서를찾을수없었다. 첫번째도약은그들이마이크로프로세서를설계할능력을가지고있으며, 다른곳에서이를구매할필요가없다는것을깨달은것이다. 작은팀이고상업적압력이심했기때문에작은프로세서를설계했지만정말로뛰어난프로세서였다. 이를가지 14
고만든컴퓨터 Archimedes 는매우진보된기기였지만 IBM의상업적힘에맞서싸워야했다. 회사는컴퓨터판매가신통치않음에도불구하고이같이뛰어난마이크로프로세서설계는유지하고있었다. 두번째도약은그들의미래가완성된컴퓨터자체를판매하는데있지않다는것을깨달은것이다. 따라서 1990년에 Acorn computers 는 Advanced RISC Machines Ltd, ARM이라불리는다른회사를캠브리지에설립하게되었다. 세번째도약은성공적인설계자가되기위해서설계에어떤아이디어가들어갔는지에관계없이직접실리콘을제조할필요가없다는것을깨닫기시작했다는것이다. 이런것들은 intellectual property (IP) 로판매될수있는것이다. 일련의성공적인스마트마이크로프로세서설계로인해전세계주요제조업체에 IP 판매가증가함으로써 ARM의개념이올바른방향으로나아가고있음을증명했다. 이회사는현재대단한성공을즐기고있으며 ARM Holdings 란이름으로불리고있다. ARM의디자인을구매한사람은이를자신의제품에집어넣고있다. 1.5.2 Cortex-M 코어 Cortex 마이크로프로세서코어는 32-비트프로세싱장치로서성공적인 ARM 프로세서라인을뒤따르고있다. 그림 1.6은 Cortex-M 코어구조를나타낸간단한블록도이다. 앞서살펴봤던컴퓨터의일부기능을다시보여주고있는데, ALU는앞에서설명한대로컴퓨터계산에서중심역할을한다. 명령어 (Instruction) 코드는프로그램메모리에서차례대로명령어를가져오는명령어인출 (instruction fetch) 메커니즘을통해 ALU에제공된다. 하나의명령어가실행될때, 그다음명령어은해석 (decode) 되고, 또그다음명령어는메모리에서인출된다. 각명령어가실행될때, ALU는동시에메모리에서데이터를가져오거나데이터를메모리로전달한다. 이런일은메모리인터페이스블록에서일어나다. 메모리는 Cortex-M 코어가아니다. 또한 ALU는자신과관련된레지스터블록을가진다. 이블록은로컬메모리처럼동작하여계산을할때빠르게액세스할수있고, 임시데이터를저장하는데사용된다. Cortex-M 코어는또한인터럽트인터페이스블록을포함하고있다. 인터럽트는컴퓨터구조에서매우중요한기능이다. 인터럽트는 CPU를현재실행중인프로그램섹션에서다른코드섹션으로분기하는데사용될수있는외부입력신호이다. 인터럽트컨트롤러는다양한인터럽트입력을관리한다. Cortex-M 코어에서몇가지버전이있다. Cortex-M4 는디지털신호처리 (DSP) 능력을가지고있다. Cortex-M3 는 automotive 와 industrial 을포함하여임베디드어플리케이션에주로사용된다. Cortex-M1 은 Field Programmable Gate Array (FPGA) 에내장되는작은프로세서이다. Cortex-M0 는가장작은사이즈와파워소비를가지는가장간단한프로세서이다. 15
Interrupt Interrupt Controller Register Bank Decoder ALU Instruction Fetch Test & Debug Interface Test Data Memory Interface Memory Protection Unit Bus Interconnect Memory and Peripheral 그림 1.6 Cortex-M 코어구조 1.5.3 Cortex-M4 소개본교재에서사용할보드는 ARM사의 Cortex-M4 코어를탑재한마이크로컨트롤러를사용하는보드이기때문에 Cortex-M4 의기능과특징에대해간략하게살펴본다. 마이크로컨트롤러시장은모터제어, 네트워크, 오디오, 스마트미터, 전력측정, 산업용제어기기, 의료분야및자동차에이르기까지적용범위가매우넓다. 이런시장에대응하기위해서는다양한마이크로컨트롤러장치들이요구된다. 이에 ARM사는 Cortex-M0, Cortex-M1, Cortex-M3 와같은장치를이미출시하였으며, 신호처리 (DSP:Digital Signal Processing) 에대한요구가증가함에따라이기능을내장한 Cortex-M4 장치또한출시하기에이르렀다. 여기서간단하게 ARM사의 Cortex 계열프로세서제품군을살펴보면서버, 셋톱박스, 노트북, 모바일어플리케이션등과같이저전력과고성능의기능을제공하는 Cortex-A 계열과디스크드라이버, 디지털카메라, 모바일베이스밴드등과같이실시간 (real-time) 임베디드환경에적합한 Cortex-R 계열, 그리고가전제품, 모터, 오디오제품등에사용되는 Cortex-M 계열의프로세서들로분류된다. Cortex-M 코어장치들간의연관관계와응용분야를그림 1.7로표현할수있으 16
며, Cortex-M0 가 state machine 과같이간단한기능을수행하는장치를대체하는것이라면 Cortex-M3 는 32-bit 마이크로컨트롤러의주류를형성하고있다. 32-bit 고성능 MCU(Micro-Controller Unit) 에 Digital Signal Controller(DSC) 를추가하여새로운시장에대응하고있는것이 Cortex-M4 장치이다. 그림 1.7 Cortex-M4 장치의특징과응용분야 그림 1.8 Cortex-M 코어간의호환성관계 17
Cortex-M 계열의프로세서들간에는아키텍처에있어크게다르지않으며, 사용하기쉽고매우낮은소비전력을가진다. 그리고바이너리에대한하위호환성역시유지된다. 그렇다면 Cortex-M4 만의특별한점은무엇일까? 그것은디지털신호를제어할수있는가장에너지효율적인 32-bit 임베디드프로세서로서보통의 MCU 프로그래머도고성능의신호처리를할수있도록지원한다는것이다. 달리말하면대부분의 MCU는 Cortex-M4 만큼신호처리알고리즘에대해에너지소비가효율적이지못할뿐만아니라대부분의전용 DSP(Digital Singal Processor) 가 Cortex-M4 만큼프로그래밍하기가쉽지않다는것이다. Cortex-M4 는이두가지부분에대해최적의밸런스를제공한다. DSP 확장기능을가진 16-bit 나 32-bit MCU 장치에비해 Cortex-M4 는 2배이상의효율성을보인다. 다시말해다른장치들에비해서반이하의사이클소비로태스크를수행할수있으며따라서동일사이클에서는훨씬많은일을할수있는여지가있다. 또한사이클소비가줄어들기때문에소비전력역시줄어들게된다. 그림1.9 는다른 16-bit 나 32-bit MCU와 Cortex-M4 MCU 사이의디지털신호처리성능을비교한것으로 Cortex-M4 MCU가훨씬처리속도가빠른것을알수있다. 그림 1.9 Cortex-M4 와일반 MCU 간의디지털신호처리성능비교또한프로그래밍역시쉽다. 대부분의전용 DSP에서는어셈블리레벨의최적화가필수지만 Cortex-M4 의경우는프로그램전체를 C 언어로프로그래밍하는것이가능하다. 이는어플리케이션개발을빠르게진행할수있도록해주며, 유지관리나코드재사용이훨씬쉬워진다. 코드를재사용한다는의미는굉장히중요하다. 예를들어, Cortex-M0 와 Cortex-M3 용으로개발된코드는완전한상위호환성을가지며 Cortex-M4 에대한 CMSIS 라이브러리패키지지원을이용할수있다. 또한 Cortex-M4 에대해 C 언어로프로그래밍하면 ARM 소프트웨어툴의컴파일러를이용하여높은수준의최적화를이룰수있다. 18
1.5.4 Cortex-M4 에적용된기술 그림 1.10 Cortext-M4 에적용된기술요소 Cortex-M4 는 ARM사의다른프로세서들과마찬가지로 RISC 타입의프로세서코어로서작은지연시간을갖는파이프라인과 deterministic operation 을수행할수있는고성능 32-bit 프로세서이다. 또한 16/32-bit 명령어들이혼합된 Thumb-2 기술이적용되어성능에대한손실없이 8-bit MCU에비해코드크기를 30% 이상작게만들수있다. Cortex-M4 에는 CoreSight debug 와 trace 기술이적용되어있어 JTAG이나 Serial Wire Debug(SWD) 연결및여러개의프로세서를동시에연결할수있도록해준다. 지원되는개발툴로는 ARM사의 MDK-ARM 소프트웨어툴과 ULink2, ULink-pro 등의디버거 (Debugger) 가있으며, 다른제조사가지원하는개발툴도있다. 소프트웨어개발을위해 Cortex Microcontroller Software Interface Standard(CMSIS) 라이브러리를이용할수있으며소프트웨어재사용이용이하다. Cortex-M4 의저전력모드는 sleep state에대한지원이내장되어있고여러가지의 power domain 이적용되어있어소프트웨어로제어된다. 내장된 Nested Vectored Interrupt Controller(NVIC) 를통해낮은지연의인터럽트응답이가능하고 C 언어로인터럽트서비스루틴의작성이가능해어셈블리프로그래밍이필요없다. CMSIS ARM Cortex Microcontroller Software Interface Standard (CMSIS) 는 Cortex-M 시리 19
즈프로세서에대해서제조사와무관한하드웨어추상화계층을제공한다. 이를통해주변장치, 실시간운영체제그리고미들웨어에대해서프로세서와일관되고단순한소프트웨어인터페이스를가능하게하고, 소프트웨어재사용을간단히할수있도록해준다. 이는새로운마이크로컨트롤러개발자에대해서학습시간을단축시켜주고새로운제품에대한시장출시를앞당길수있도록해준다. Nested Vectored Interrupt Controller(NVIC) NVIC는 Cortex-M 프로세서의일부로내장되어있으며, 프로세서가뛰어난인터럽트처리능력을가질수있도록해준다. Cortex-M0, Cortex-M0+ 그리고 Cortex-M1 프로세서의경우 NVIC는 32개의인터럽트 (IRQ), 하나의 Non-Maskable Interrupt(NMI) 그리고다양한 system exception 을가질수있도록지원하는반면, Cortex-M3 와 Cortex-M4 프로세서의 VIC는 240개의인터럽트를지원하도록확장되었다. 그림 1.11 Cortex-M4 코어의 NVIC 인터페이스구조 NVIC 설정의대부분을프로그래밍으로제어할수있으며 configuration register들은 memory map의일부로 C 언어의포인터처럼접근할수있다. CMSIS 라이브러리역시다양한 helper 함수들을제공해서인터럽트제어를더욱쉽게할수있도록해준다. NVIC 내의각각의인터럽트소스에는인터럽트우선순위가할당된다. NMI 처럼몇몇 system exception들은고정된인터럽트우선순위를가지며다른것들은우선순위레벨을프로그래밍할수있게되어있다. 각각의인터럽트에대해다른인터럽트우선순위를할당함으로써소프트웨어의어떤개입도없이 NVIC가자동으로중복된인터럽트를지원할수있도록해준다. 20
그림 1.12 Cortex-M4 NVIC 의중첩인터럽트처리방식 21
2. 개발환경구축 2 장에서는본교재의프로그래밍실습에사용하는실습보드와개발소프트웨어 도구에대해살펴보고, 개발소프트웨어및추가로필요한프로그램설치과정에대 해알아본다. 2.1 실습보드 본교재에서사용할실습보드는 STMicroeletronics 사의 Nucleo STM32F401RE 보드가장착된플랫폼이다. Nucleo STM32F401RE 보드는 ARM사의 Cortex-M4 디바이스를기반으로한, 구하기쉽고, 넓은확장성을지닌 STM32F401RE 마이크로컨트롤러를사용하고있다. 사용자가원하는프로토타입 (prototype) 을유연하게구성할수있기때문에새로운아이디어를시험해보거나다양한기능과특징들을조합하여임의의프로토타입을만들어볼수있도록유연하고조합가능한방법들을제공한다. 그림 2.1 실습보드구성 22
2.1.1 실습보드개요실습보드는 Arduino 쉴드보드들이제공되는센서들과웨어러블디바이스또는 IoT 장치에서사용되는다양한센서들을플랫폼베이스보드에미리장착하여개별적으로혹은선별적으로통합된프로젝트를꾸며실습할수있도록구성되어있다. 그림 2.2 실습보드의마이크컨트롤러와센서 Nucleo STM32F401RE 보드는기능을확장할수있도록 Arduino connectivity 와 ST Morpho headers 를통해연결성을제공해주는개방형플랫폼이다. 또한특정쉴드 (shields) 들을다양하게선택할수있어개발플랫폼으로서기능을쉽게확장할수있습니다. 여기서쉴드 (shields) 는 Arduino 개발보드의확장인터페이스로개발된보드를의미하며, STM32 Nucleo 보드는 Arduino 호환커넥터를제공함으로써이쉴드들을 Nucleo 보드에서도사용할수있도록해주고있다. 2.1.2 실습보드특징실습보드에는실생활에서다양한용도로사용되는필수센서들과 IoT 장치들에서많이응용되는센서들이장착되어있다. 이외에도각종제어와통신에사용되는모듈들을포함한다. 여기서는실습보드의특징과센서종류에대해서만살펴보고, 실제프로그래밍실습을진행하는뒤부분에서각각의센서장치의동작방식및연결회로등에대해자세히설명하도록한다. 23
128x64 OLED Display BLE (Bluetooth Low Energy) 통신 WiFi 통신모듈 (ESP8266) CAN (Controller Area Network) 툥신 자외선센서 심박 (Heartbeat) 센서 기압 / 고도 / 온도센서 소리감지센서 인체감지센서 적외선거리센서 온 / 습도센서 조도센서 가속도센서 스위치 / LED DC 모터 부저 RGB LED USB ST-Link Debug & Trace port Virtual COM port 마이크로컨트롤러로사용되는 STM32F401RE 는 LQFP64 패키지타입으로된고성 능디바이스로서주요사양은다음과같다. FPU가내장된 ARM Cortex-M4 CPU 84 MHz 메인클럭 512 KBytes Flash Memory 96 KBytes SRAM 16 채널까지사용가능한 12 bit ADC 2.4 Msps 최대 11개의타이머 최대 3개의 I2C, 3개의 USARTs, 4개의 SPIs, 1개의 SDIO on-chip PHY를갖는 USB 2.0 Full Speed 96 bit unique ID 그리고보드전원으로는 USB VBUS 혹은외부전원소스 (3.5V, 5V, 7V~12V) 을사 용할수있으며, 그리고호스트 PC 와인터페이스하는 on-board ST-LINK/V2.1. USB 연결을통해세가지통신채널들을지원한다. 24
standard tool chains/debuggers/programmers 와 STM32 Nucleo를함께사용할수있도록해주는 debug & programming port. PC와 Nucleo 보드와시리얼통신을지원하는 Virtual COM Port 드래그-앤- 드롭프로그래밍을지원하는 Mass storage(usb 디스크드라이버 ) 와부장치를연결할수있는확장인터페이스로는다음과같다. Arduino Uno Revision 3 connectivity( 그림 2.3) STM32F401RE 의모든 I/O에액세스할수있는 STMicroelectronics Morpho extension pin headers( 그림 2.4) 그림 2.3 Arduino 호환확장핀헤더 2.2 개발소프트웨어개요 ARM Cortex-M4 마이크로컨트롤러에대한개발소프트웨어로는다양한종류가현장에서사용되고있으며, 대표적인소프트웨어는다음과같다. 1 Keil MDK-ARM 2 IAR Embedded Workbench for ARM 3 GNU ARM Eclipse 25
그림 2.4 STMicroelectronics Morpho 확장핀헤더 4 TrueSTUDIO 5 TASKING VX-Toolset for ARM 개발소프트웨어의선택은개발에사용되는 MCU 칩종류, 개발내용, 개발소프트웨어의지원항목및라이센스, 구입비용등을고려하여결정하여야하며, 개발도구는전체개발기간을좌우할수있으므로신중하게선택하여야한다. 개발소프트웨어에대한자세한내용은위키피아의 List of ARM Cortex-M development tools (https://en.wikipedia.org /wiki/list_of_arm_cortex-m_development_tools) 페이지를참조하길바란다. 본교재에서는개발소프트웨어로 ARM사가공식적으로지원하고있는 Keil MDK-ARM 소프트웨어를사용하도록한다. 2.1.1 MDK-ARM 개요 Keil MDK-ARM 은 ARM 기반마이크로컨트롤러를위한개발소프트웨어로서임베디드어플리케이션을위한많은개발요소들을지원한다. MDK는 Microcontroller Development Kit의줄임말로서그림 2.5와같이 C/C++ 컴파일러, μvision IDE & Debugger, CMSIS 패키지및기타미들웨어등으로구성되어임베디드어플리케이션을보다쉽게개발할수있도록지원하고있다. 26
그림 2.5 Keil MDK-ARM 구성 2.1.2 MDK-ARM 구성 MDK-ARM 은그림 2.5와같이크게 MDK Tools와소프트웨어팩 (Software Packs) 으로구성된다. MDK Tools는 MDK-ARM 소프트웨어설치시에기본적으로설치되는, 소프트웨어개발에필수적인도구들로서다음과같은것들이있다. (1) ARM C/C++ Compiler C/C++ 소스를컴파일하여이진이미지를생성하는데필요한여러도구들로구성 되며, 최적의코드크기와성능을지원한다. (2) uvision IDE & Debugger 어플리케이션프로젝트생성, 컴파일, 업로드그리고디버깅등의일련의개발작업을 GUI 기반으로지원하는도구이다. 다른 IDE 도구와유사한인터페이스를제공하여사용하기쉬울뿐만아니라다양한디버깅작업을수행할수있다. 그리고유의할사항은마이크로소프트의 Windows 환경에서만실행된다. (3) DS-MDK Eclipse 기반의 IDE & Debugger 도구로서 Cortex-A 프로세서를활용한응용개발 을지원한다. 이도구는본교재의주제에서벗어난것이므로논의에서제외한다. 27
소프트웨어팩 (Software Packs) 은효과적인프로그램개발을지원하는일련의프로그램집합으로서 Cortex-M MCU 칩과평가보드 (Evaluation Board) 에종속적인장치지원프로그램, 어플리케이션프로그램개발을용이하게하는장치인터페이스 API를제공하는 CMSIS(Cortex Microcontroller Software Interface Standard) 프로그램그리고임베디드어플리케이션의다양한기능을지원하는미들웨어프로그램과예제코드등으로이루어져있다. 미들웨어프로그램의특징은 IoT 장치개발을쉽게할수있도록연결성을위한다양한솔루션을제공한다는것이다. 소프트웨어팩은정말로다양한프로그램팩들을지원하고있는데, 프로그램개발을위해서는사용하는 MCU 칩그리고개발하고자하는어플리케이션의특성등을고려하여필요한프로그램팩을선택하여팩설치자 (Pack Installer) 를이용하여설치하여야한다. 그러나소프트웨어라이센스에따라미들웨어라이브러리팩을포함하여일부팩에대한사용제한이있으니사전에고려하여야한다. 이들프로그램팩중에서 CMSIS 는 ARM사가 ARM 기반의마이크로컨트롤러에대한제어프로그램을용이하게개발하도록장치인터페이스에대한표준을정하고이를지원하기위해제공하는프로그램팩으로서그림 2.6을통해알수있듯이어플리케이션또는미들웨어프로그램과물리적인 MCU 장치사이에인터페이스를제공하는요소로서프로그램개발에반드시필요한프로그램팩이다. 본교재에서도실습을진행하기위해서팩설치자를이용하여장치 startup 코드팩과 CMSIS-Core 팩을설치하도록한다. 그림 2.6 CMSIS 소프트웨어팩구성 28
2.2.3 MDK-ARM 에디션과라이센스현재지원되고있는 MDK-ARM 에디션종류와차이점은그림 2.7과같다. MDK-ARM Lite는무료로설치하여사용할수있으나생성코드크기가 32 KBytes 로제한되어있어단순테스트프로그램생성및실행정도에적합하다. 본교재에서는 MDK-ARM Plus 에디션을사용하는것으로전제로하며, 이를위해네트워크를통해라이센스서버에접속하여사용인증을받는 FlexLM Floating 라이센스를사용하는것으로한다. 따라서 MDK-ARM 소프트웨어설치후에라이센스에대한추가적인설정이필요하며, 사용하는 PC가반드시네트워크에접속되어있어야개발소프트웨어사용이가능하다. 그림 2.7 MDK-ARM 에디션비교 29
2.3 개발소프트웨어다운로드및설치 MDK-ARM 소프트웨어는다음의 URL로접근하여 Keil사의웹사이트에서최신버전을다운로드할수있다. http://www2.keil.com/mdk5/ 그림 2.8 MDK-ARM 개발소프트웨어다운로드사이트 MDK-ARM 소프트웨어가다운로드된폴더로이동하여설치파일을더블클릭하여 실행한다. 그림 2.9 MDK-ARM 설치파일및실행설치과정은다음두단계로이루어진다. 1 MDK-ARM Core 설치단계 2 팩설치자 (Pack Installer) 를이용한소프트웨어팩설치단계 30
MDK-ARM Core 설치단계는아래의그림 2.10과같이차례대로진행되며, 중간에요구되는입력항목은기본값으로선택하고, 고객정보입력화면에서는자신의이름, 소속, 메일주소등을입력하고다음으로넘어가설치를계속한다. MDK-ARM Core 설치가완료되면그림 2.11과같이팩설치자가실행되어소프트웨어팩을선별적으로설치하여야하는데, 지금설치과정에서는별도의소프트웨어팩을설치하지않고종료하도록한다. 이는추후에필요한소프트웨어팩이있을경우에 uvision IDE의팩설치자를실행하여수시로설치할수있을뿐만아니라다음장에서설명하는 MBED 라이브러리를사용하는경우에는라이브러리내에필요한소프트웨어팩을내장하고있어별도의소프트웨어팩설치가필요없기때문이다. 그림 2.10 MDK-ARM Core 설치과정 31
그림 2.11 팩설치자를이용한소프트웨어팩 설치가종료되면 uvision IDE 가실행되어다음과같은화면이나타난다. 그림 2.12 uvision IDE 실행화면 2.4 MDK-ARM 소프트웨어라이센스등록 본교재에서는 MDK-ARM Plus 에디션을사용하기위해 FlexLM Floating 라이센스를사용한다. FlexLM Floating 라이센스는소프트웨어설치과정에서라이센스파일을등록하는것이아니라 uvision IDE에서프로그램소스를컴파일할때에라이센스서버에접속하여사용권한을확인하는방식으로동작한다. 따라서 MDK-ARM 소프트웨어를구동하는 PC가항상네트워크에연결되어있어야하며, uvision IDE에라이센스서버에관한정보를등록하여야한다. 라이센서정보등록과정은다음과같다. (1) uvision IDE에서 File > License Management... 메뉴를실행한다. 32
2.13 uvision IDE 의라이센스관리자실행메뉴 (2) License Management 다이얼로그창에서 FlexLM License 탭을선택한 다. 그리고 Use FlexLM 체크상자를선택하고 Edit... 버튼을클릭한다. 2.14 uvision IDE의라이센스관리자실행창 (3) 그림 2.15과같이서버정보입력창에서 port@hostname 형식의서버정보를입력하고 OK 버튼을클릭한다. 본교재에서의입력내용은다음과같다. 8224@mictes.silla.ac.kr 2.15 uvision IDE 의라이센스정보입력 33
(4) 입력된라이센스정보로정상적으로인증되면다음과같은화면이출력된다. 출력되는내용은사용하는상황에따라달라질수있다. 그림 2.16 라이센스정보등록완료화면 2.5 추가소프트웨어설치 2.5.1 STM32 Nucleo 보드의 ST-Link 드라이버설치일반적으로호스트 PC에개발된 MCU 제어프로그램은 MCU로다운로딩하여플래시메모리에프로그래밍한후에실행되는데이러한일련의과정을 MCU 프로그래밍 이라고한다. MCU 프로그래밍은별도의디버거장치를통해수행되며, PC의전용프로그래밍프로그램이생성된이미지를디버거장치로전송하면디버거장치는 MCU의플래시메모리에이미지를프로그래밍 (fusing) 하고, 프로그래밍이완료되면 MCU를초기화 (reset) 시켜프로그램을실행시킨다. MCU 프로그래밍디버거장치는플래시메모리프로그래밍뿐만아니라프로그램디버깅을위한인터페이스를지원한다. 그리고디버거장치종류로는모든 ARM Cortex 프로세서에적용할수있는범용디버거장치나특정칩제조사에서생산된프로세서에만적용할수있는전용디버거장치등이있어사용목적에맞게선택하여사용하여야한다. 그림 2.17은일반적으로많이접할수있는디버거장치들이다. MCU 제조사는새로운 MCU 칩을설계하여생산하면해당칩의사용을권장하기위해 MCU 칩을적용한평가보드 (Evaluation Board) 를제조하여같이제공한다. 이러한평가보드는해당 MCU을쉽게사용하도록할뿐만아니라성능평가를용이하게 34
하며, 제품개발에대한참조모델 (Reference Model) 역할을하기도한다. 최근 MCU 제조사는사용자가평가보드를더쉽게사용할수있도록프로그래밍디버거로직을평가보드에포함시킴으로써별도의디버거장치없이도사용할수있도록지원하고있다. 본교재에서는사용하는 Nucleo 보드도그림 2.18과같이 ST-Link 디버거로직을내장하고있으며, 이를 On-board Programmer 라고도한다. 그림 2.17 ARM MCU 디버거장치 그림 2.18 Nucleo-F401RE 보드의 ST-Link 디버거로직 uvision IDE에서 Nucleo 보드의내장된디버거를통해 MCU 프로그래밍하기위해서는내장디버거장치에대한장치드라이버를사전에설치하여야한다. 또한, Nucleo 보드는상용 ST-Link 디버거와달리더최신의 ST-Link 버전 (2.1) 을제공함으로이미 ST-Link 디버거를사용하고있는경우라도디버거장치드라이버를업그레이드하여야한다. (1) ST-Link 디버거의최신장치드라이버는다음의웹사이트에서다운받을수있다. https://www.st.com/en/evaluation-tools/nucleo-f401re.html 35
그림 2.19 ST-Link 디버거장치드라이버다운로드링크 (2) 다운로드후에압축을해제하고 PC 와 Nucleo 보드사이의연결을분리한다 음에드라이버설치파일을실행하여설치하도록한다. 그림 2.20 ST-Link 디버거장치드라이버설치파일 (3) 설치과정에서입력항목은기본값으로설정하고설치를지속한다. 그림 2.21 ST-Link 디버거장치드라이버설치과정 36
(4) 드라이버를설치한후모든것이정상적으로동작하는지검사하도록한다. USB 케이블을이용하여 Nucleo 보드를호스트 PC에연결한후에장치드라이버관리자에서 ST-Link programmer 와 Virtual Com Port 장치가생성되었는지확인하도록한다. 그림 2.22 ST-Link 디버거장치드라이버설치확인 2.5.2 Nucleo 펌웨어업그레이드 Nucleo 보드는 On-board ST-Link 디버거에대한펌웨어업그레이드가필요하다. 펌웨어업그레이드를하지않아도본교재의실습을실행하는데는문제가없으나, uvision IDE 소프트웨어가지속적으로업그레이드되고있어일부프로그래밍및디버깅동작을수행하는데문제가발생할수있어최신펌웨어로업그레이드하는것이바람직하다. 펌웨어업그레이드프로그램은그림 2.19에서제시된디바이스드라이버다운로드웹사이트에서다운로드받을수있으며, 펌웨어업그레이드는반드시 ST-Link 디버거디바이스드라이버를설치한후에실행하여야하며, 중간에중단되는경우가발생하지않도록유의하도록한다. 펌웨어업그레이드는다음과같이수행한다. (1) 최신 ST-Link V2.1 펌웨어업그레이드프로그램을다운로드한다. 다운로드링크는다음과같다. https://www.st.com/content/st_com/en/products/development-tools/software-dev 37
elopment-tools/stm32-software-development-tools/stm32-programmers/stsw-lin k007.html (2) 다운로드한파일에대해압축을푼후에생성된폴더의 Windows 하위폴더안에있는 ST-LinkUpgrade.exe 프로그램을실행한다. 그림 2.23 ST-Link 펌웨어업그레이드실행파일 (3) Nucleo 보드를 USB 케이블로 PC에연결하고 USB 장치로인식될때까지기다린다. (4) 화면의 Device Connect 버튼을클릭하면 Nucleo 보드의정보를읽어와현재펌웨어버전과업그레이드할펌웨어버전을출력한다. 그림 2.24 ST-Link 펌웨어업그레이드실행과정 보드연결 (5) 화면의 Yes >>>> 버튼을클릭하여펌웨어업그레이드를실행한다. 그림 2.25 ST-Link 펌웨어업그레이드실행과정 업그레이드실행 38
2.5.3 DFP(Device Family Package) 및관련소프트웨어팩설치 uvision IDE에서 Nucleo 보드를이용하여실습을진행하기위해사전에필요한소프트웨어팩을설치하도록한다. MDK-ARM 소프트웨어구성에서설명하였듯이평가보드를이용하여제어프로그램을개발하기위해서는 MCU 및평가보드에종속적인 DFP(Device Family Package) 와 CMSIS Core 팩이필수적으로요구됨으로이를설치하도록한다. (1) uvision IDE를실행하고메뉴에서팩설치자를실행한다. 그림 2.25 uvision IDE에서팩설치자실행 (2) 팩설치자가시작되면시작대화창을종료하고, 설치화면왼쪽부분의 Device 탭을선택하고장치중에서 STMicroelectronic => STM32F4 Series => STM32F401 => STM32F401RE 순으로장치를선택한다. (3) 설치화면오른쪽부분의 Packs 탭에서다음의소프트웨어팩에대해 install 버튼을클릭하여설치하도록한다. 1 Keil::STM32F4xx_DFP STM32F4 Series MCU DFP(Device Family Package) 로서장치초기화코드등을제공한다. 2 Keil::STM32NUCLEO_BSP STM32 Nucleo 보드를위한 BSP(Board Support Package) 로서 Nucleo 보드의확장된기능, 예를들면 ST-Link Debugger 등에대한정보및제어기능을제공한다. 3 ARM::CMSIS - CMSIS Core 소프트웨어패키지 4 ARM::CMSIS-Driver_Validation 5 Keil::ARM_Compiler (4) 소프트웨어팩이설치되면그림 2.26과같이설치되었음으로표시된다. 39
그림 2.26 팩설치자의소프트웨어팩설치화면여기서유의할점은 3장에서설명하는 MBED 라이이브러리를사용하여개발하는경우에는장치종속적인소스및라이브러리를 MBED 라이브러리의프로그램에내장하고있기장치종속적인소프트웨어팩, 즉 DFP과 BSP 팩등을별도로설치할필요가없다. 2.6 개발소프트웨어실행테스트 지금까지설치된개발소프트웨어가정상적으로설치되어동작하는지확인하기위해간단한테스트를진행하기로한다. 또한이과정을통해 uvision IDE를이용하여프로그램소스를빌드하고다운로드하여실행하는일련의과정에대한사용법에대해서도학습하기로한다. 테스트는소프트웨어팩에서제공하는예제소스를이용하여다음의절차로진행한다. 1 팩설치자를이용한 LED Blinky 예제설치 2 프로젝트옵션설정 3 프로젝트빌드하기 4 생성이미지다운로드및실행우선모든실습프로젝트를생성하여저장하는작업폴더로 es-lab 폴더를새로이만들어사용하도록한다. (1) 팩설치자를이용한 LED Blinky 예제설치 uvision IDE 를실행하고메뉴에서팩설치자를실행한다. 팩설치자창의오른쪽 부분의 Examples 폴더를선택한다. 40
그림 2.27 팩설치자에서의예제소프트웨어팩선택 Blinky 예제를선택하고 Copy 버튼을클릭하면다음의다이얼로그창이나타나며, 여기서 Browse... 버튼을클릭하여작업폴더인 es-lab 폴더로찾아설치폴더로지정하고 OK 버튼을클릭한다. 그림 2.28 예제소프트웨어팩의설치위치지정 그러면 es-lab 폴더에예제프로젝트가설치되고새로운 uvision IDE 가실행 된다. uvision IDE 의 project window 에서예제의내용을확인할수있다. 그림 2.29 예제프로젝트의설치내용 예제소프트웨어팩의설치동작은 uvision IDE 를이용하여새로운프로젝트를생 41
성하고, Run-Time Environment 설정을통해소프트웨어팩에서필요한프로그 램들을선택하여설정한다음, 원하는동작을수행하기위한프로그램소스코딩을 완료한단계와같다. (2) 프로젝트옵션설정프로젝트를빌드하고실행하거나디버깅하기위해서는먼저프로젝트옵션을적절하게설정하여야한다. 대부분의설정은기본값으로설정을하도록하며, 사용환경이나원하는작업에따라일부옵션을수정하면된다. 여기서는가장일반적으로수정하는프로젝트옵션에대해알아본다. 프로젝트옵션설정은메뉴에서 Options for Target... 버튼을클릭하여실행한다. 그림 2.30 프로젝트타겟옵션설정실행 1 Target 옵션설정 타겟장치에대한옵션을설정하며, 보드의메인클럭의 크기를사용하는보드에맞게 84.0 MHz 로설정한다. 그림 2.31 프로젝트옵션의타겟옵션설정 2 Debug 옵션설정 ( 그림 2.32) 사용할디버거장치에관해설정하며, 왼쪽부 분에서디버거종류를 ST-Link Debugger 로선택한다. 42
그림 2.32 프로젝트옵션의디버그옵션설정 3 Debugger 장치세부옵션설정 ( 그림 2.33) 그림 2.32에서디버거장치목록의 Settings 버튼을클릭하여세부옵션설정으로들어간다. Debug 탭에서사용한디버거타입을설정하고, Trace 탭에서 Core Clock 을 84MHz 로설정한다. 그리고 Flash Download 탭에서플래시메모리프로그래밍에사용할알고리즘을설정하는데, Add 버튼을클릭하고 STM32F4xx 512kB Flash 를선택한다. 그림 2.33 디버거세부설정 (1) 그림 2.34 디버거세부설정 (2) 43
(3) 프로젝트빌드하기프로젝트옵션설정을완료한후에프로젝트빌드를실행한다. 여기서바로빌드를실행하면컴파일오류가발생하는데, 이는 STM32F4xx DFP가계속업그레이드되면서예제소스와의일관성이지켜지지않아발생하는문제이며, 이를해결하기위해 system_stm32f4xx.c 소스를다음과같이수정하도록한다. Project => Build Target 메뉴를클릭하거나툴바에서 Rebuild 버튼을클 릭, 또는단축키 F7 을눌려프로젝트빌드를실행한다. 그림 2.35 프로젝트빌드실행 프로젝트빌드수행결과는 uvision IDE 의아래부분에 Build Output 창에출 력된다. 그림 2.36 프로젝트실행과정및결과출력 (4) 생성이미지다운로드및실행생성된이미지는 Nucleo 보드로다운로드하여플래시메모리에프로그래밍한후에실행된다. 여기서 Nucleo 보드를프로그래밍하는방법으로는크게 2가지방법이 44
있다. 첫째, uvision IDE 의다운로드기능을이용한프로그래밍 둘째, 가상외부저장장치에대한 Drag-and-Drop 를통한프로그래밍 각각의방법에대해자세히살펴본다. 1 uvision IDE의다운로드기능을이용한프로그래밍 uvision IDE는생성된이미지를다운로드할때에프로젝트빌드를통해기본적으로생성되는 axf 형식의파일을사용한다. uvision IDE의메뉴중에서 Flash => Download 메뉴를선택하거나툴바에서 Download 버튼을클릭하여다운로드및프로그래밍을실행한다. 그림 2.37 실행파일다운로드실행 다운로드결과는아래의 Build Output 창에출력되며, Nucleo 보드에서녹색 LED 가깜박이는동작을수행하는것을통해프로그램실행을확인할수있다. 그림 2.38 실행파일다운로드실행결과 2 가상외부저장장치에대한 Drag-and-Drop 를통한프로그래밍 Nucleo 보드를 PC에연결하면가상저장장치드라이브 ( 볼륨명 : NODE_F401RE) 가생성된다. 45
그림 2.39 Nucleo 보드의가상저장장치드라이브내용 가상저장장치드라이브로생성된이미지를복사하여붙여넣기 (Drag-and-Drop) 를 하면자동으로다운로드및프로그래밍이실행된다. 단, 여기서주의할점은복사하 여붙여넣기방식에사용하는이미지파일은 bin 형식의파일이여야한다. 따라서프 로젝트를빌드할때에 axf 형식의파일을이용하여 bin 형식을파일을생성하여야 한다. bin 파일을생성하기위해서는그림 2.30 과같이타겟옵션설정을실행하고 User 옵션설정에서빌드후에 bin 파일을생성하도록그림 2.40 과같이 After Build/Rebuild 옵션에다음과같이입력한다. fromelf --bin -o @L.bin!L 그림 2.40 bin 형식파일생성을위한프로젝트옵션설정그리고프로젝트빌드를다시실행하면 Build Output 창의메시지에서 bin 파일을생성하였음을확인할수있다. bin 파일이생성된폴더 ( 프로젝트폴더 ) 로이동한후에가상저장장치로복사하여붙여넣기를수행한다. 46
그림 2.41 복사하여붙여넣기를이용한장치프로그래밍 그러면앞의방식과동일하게장치프로그래밍이이루어지고프로그램이실행되 어 Nucleo 보드의녹색 LED 가깜박이는것을확인할수있다. 47
3. MBED 라이브러리 MCU(Micro-Controller Unit) 를이용한장치개발과정은일반적으로우선사용하는 MCU와개발지원프레임워크에대해학습하여이해한다음, 프로그램설계, 코딩그리고테스트과정을통해프로그램개발을수행한다. 장치개발에따른수고를줄이고개발기간을단축하기위해서는참고할자료가제대로지원되어야할뿐만아니라제대로갖추어진개발프레임워크선택이중요하다. 본교재에사용하는 STM32 기반의개발플랫폼의경우프로그래밍을위해참고할사용지침서와가이드문서들이대단히많이지원되고있으며, STMicroelectronics 는 STM32 플랫폼에대해서개발과정을빠르게진행할수있도록 STM32Cube (http://www.st.com/web/catalog/tools/fm147/cl1794/sc961/ss1743/ln1897) 라는공식적인프레임워크를제공하고있다. 그러나 STM32 플랫폼은강력하고복잡한플랫폼이기때문에 Atmel AVR과같이간단한플랫폼과비교하면플랫폼을배우는것만으로도상당한시간이걸릴수있다. 따라서 STM32 플랫폼을이용한빠른개발을위해서는 STM32Cube 만으로충분하지않으며, 또한잘갖추어진 IDE와고성능의컴파일러툴-체인또한필요로한다. 이에 STMicroelectronics 는 KEIL MDK-ARM 등과같은 IDE을이용한개발에대해서도문서나예제들을함께지원하고있어이러한 IDE 툴을충분히활용할수있다면개발시간과노력을절약할수있다. 그리고추가적으로고려해야할개발환경은 MBED 라이브러리이다. 다양한기능을지원하는장치를빠른시간에개발하고자하는경우에 MBED 프로젝트에서제공하는라이브러리를이용하는것이가장효과적인방법이다. MBED 는인터넷에연결하여동작하는 32-bit ARM Cortex-M 마이크로컨트롤러기반의장치를위한일종의플랫폼이며, OS(Operating System) 이다. MBED 프로젝트는 ARM사와 ARM사의기술파트너회사들과의협업으로개발되고있으며, 또한수많은개발자들이 MBED 웹사이트를통해 MBED 플랫폼에대한라이브러리와예제코드등을개발하여등록하고있어무한히확장되고있다. 따라서이러한오픈소스를적절히이용할수있다면개발을위해상당한시간과노력을줄일수있을것이다. 그림 3.1은 MBED 사이트에서제시하고있는 MBED OS 플랫폼의전체구성을보여준다. 본교재에서사용하는 Keil MDK-ARM 툴이개발을위해훌륭한프레임워크를제공하고있지만본교재는 MCU의기본적인이해와활용을교육목표로하고있어 MBED 라이브러리를집중적으로활용하여프로그래밍실습을진행할것이다. 이를위해 MBED 라이브러리에대해좀더자세히살펴보도록한다. 48
그림 3.1 MBED OS 플랫폼구성 3.1 MBED 라이브러리개요 ARM사는 2013년 permissive open source license 의 SDK(Software Development Kit) 와저비용의개발보드제작을지원하는새로운 HDK(Hardware Development Kit) 를함께제공하는 mbedtm project version 2.0을발표했다. 이후 ARM사는 ARM Cortex-M MCU를이용하여빠르고효율적으로프로토타입장치를개발할수있도록툴과소프트웨어를무료로제공하는 MBED 플랫폼을계속발전시키며지원하고있다. 본교재에서사용할 STMicroelectornics 의 Nucleo STM32F401RE 의보드의경우도 MBED 지원플랫폼이다. 수십만명의개발자가이미사용하고있는 MBED SDK는복잡한프로젝트를빠르게만들수있을만큼충분히직관적이며간결하다. 또한 C/C++ 플랫폼에대해서강력한하드웨어추상화기능을제공하며, ARM CMSIS low-level API들로만들어져있어서개발자는언제든필요할때하드웨어로바로다운로드하여실행시킬수있다. 이러한특징은여러툴체인들뿐만아니라 RTOS, USB, Networking 라이브러리와같은무료오픈소스미들웨어에이식을가능하게한다. MBED SDK는 MBED 개발자커뮤니티에서주변장치나모듈들을연결하기위해재사용이가능한드라이버라이브러리를수시로등록하고있어점차확대되어지고있다. MBED HDK는새로운하드웨어보드와제품에서사용될수있도록마이크로컨트롤러서브시스템의레퍼런스디자인을제공하다. MBED HDK는 MBED SDK와무료온-라인툴지원에따른이점을취할수있도록간단하고일관된빌딩블록을제공 49
한다. HDK 디자인에는간단히드래그- 앤-드롭프로그래밍과 USB CMSIS-DAP 디버그인터페이스표준을통해연결되는온-보드 USB 인터페이스를포함한다. 이디자인은현재공식적인 MBED microcontroller prototyping 모듈에서사용되고있고, 저가의평가보드들을만들고있는 MBED 파트너들에의해채택되고있다. 그리고이제는사용자가설계하는개발자보드에도이용되고있는추세이다. MBED 컴파일러는온라인 C/C++ IDE 환경에서무료로제공된다. 이를이용하여프로그램을빠르게작성하고, 컴파일한다음 ARM Cortex-M MCU로다운로드하여실행할수있다. MBED 온라인컴파일러는산업계의표준으로자리잡은 ARM professional C/C++ compiler 가사용되고있으며, mbed.org 개발자사이트와통합되어있어서한번의클릭만으로라이브러리를불러오기 (import) 하는것이가능하다. 또한프로젝트를 MDK-ARM 과같은오프-라인툴체인에맞게변환하여내보기 (export) 할수도있다. MBED 온라인컴파일러는웹어플리케이션으로서추가로뭔가를설치하거나설정할필요가전혀없으며, Windows, Linux, Mac, ios 등의환경에서인터넷에연결된웹브라우저만사용할수있다면어디에서도무료로이용이가능하다. MBED 라이브러리는그림 3.1에서보는것과같이현재 MBED OS 라고부를정도로어플리케이션개발및실행을위해완성도높은환경을제공하고있다. MBED OS의구성요소들을하나하나살펴보는것은본교재의논의범위에서벗어나고기본개념학습에걸림돌이될수있어제외하기로한다. 하지만 MBED 라이브러리를이용한프로그래밍실습을위해다음의두가지항목을이해할필요가있다. 첫째, 강력한하드웨어추상화지원둘째, 다양한외부연결성지원그림 3.1의복잡한내부구조에대해 MBED 라이브러리의계층구조를단순화시켜살펴보면그림 3.2와같다. 그림 3.2 MBED 라이브러리 API 의추상화계층 50
MBED 라이브러리를이용하여어플리케이션을개발할때에는 mbed API를포함한 MCU 독립적인계층의 API만을활용하여개발할수있다. 이러한하드웨어추상화지원을통해얻는이점은개발자가 MCU의내부구조및동작방식에대해사전에이해하고있는것은좋지만이를잘알지못해도제어프로그램을개발할수있다는것이다. 이는 MCU에대한학습시간을줄이고개발기간을단축시킬수있다. 좀더구체적인사례를통해하드웨어추상화에따른장점을살펴본다. 지금부터살펴보는사례들은하나의 LED를점멸하는간단한동작에대해그림 3.2의추상화단계별 API를사용하여구현한것들이며, 이들사례에대해프로그램소스의작성형태를중점적으로비교해보길바란다. 우선그림 3.2의추상화계층에서가장낮은수준의 MCU Registers 계층을이용한프로그래밍사례를살펴보면, 그림 3.3은 STM32 MCU 데이터시트에서 GPIO 출력을위한레지스터사양을기술한내용을발췌한것이다. 그림 3.3 STM32 MCU GPIO 포트제어레지스터사양일부물리적으로 LED를켜거나끄는동작은 LED 장치가연결된 GPIO 핀의신호출력을제어함으로써구현할수있다. GPIO 핀의신호출력은이를제어하는제어레지스터의해당비트를 0 또는 1로설정함으로써제어한다. 이를구현한프로그램소스는그림 3.4와같다. 51
그림 3.4 레지스터직접제어를통한 LED 점멸프로그램위의프로그램소스를보면레지스터의주소를이용하여직접레지스터를접근하고있으며, 레지스터의특정비트를설정하여 GPIO 핀출력을제어하고있다. 이러한저수준의프로그래밍은 MCU의내부구조와동작방식에대해충분히이해하여야가능하다는단점이있으며, 반대로최적의동작을구현할수있다는장점이있다. 다음은중간단계의추상화계층인 CMSIS-CORE 계층을이용한프로그래밍사례이다. CMSIS-CORE 라이브러리가저수준의레지스터접근을효과적으로지원하는구조체 (structure) 및매크로 (macro) 등을제공함으로써레지스터주소와같은상세정보대신에레지스터구조와역할등한단계추상화된하드웨어정보만알아도프로그래밍할수있게된다. 그림 3.5는 CMSIS-CORE 라이브러리에서 GPIO 출력레지스터접근을지원하는구조체및매크로정의를발췌한것이다. 그림 3.5 CMSIS-CORE 라이브러리소스일부 52
그림 3.6 의프로그램소스는 CMSIS-CORE 라이브러리를이용하여 LED 점멸동작 을구현한것으로앞의구현사례와비교하여살펴보면상대적으로코딩이용이함을 알수있다. 그림 3.6 CMSIS-CORE 라이브러리를이용한 LED 점멸프로그램마지막으로최상위의추상화계층인 mbed API를이용한구현사례를살펴본다. 우선 MBED 라이브러리의세부적인추상화계층에대해살펴보면, 그림 3.7과 3.8은 mbed HAL (Hardware Abstraction Layer) API 계층에서정의된 API 함수일부에대해함수정의부분과함수구현부분을발췌한것으로 API 함수내부에서레지스터를이용한출력동작등을구현하고있어상위계층에서는레지스터등의하드웨어구조에대한상세정보를알필요없이단순히이들 API 함수를호출함으로써 GPIO 핀의출력을제어할수있게지원한다. 그림 3.7 mbed HAL API 정의소스일부 53
그림 3.8 mbed HAL API 구현소스일부그림 3.9은 mbed API 계층에서 GPIO 입출력을위한 DigitalInOut 클래스구현사례를발췌한것으로객체지향프로그래밍의특징을활용하여개발자가훨씬쉽게이해하고프로그래밍할수있도록지원한고있다. mbed API를이용하여 LED 점멸동작을구현한프로그램소스는그림 3.10과같으며, MCU에대한상세정보를완전히배제한채로 LED 장치의동작을제어하는장치제어동작구현에집중되어있음을할수있다. 이처럼 MBED 라이브러리를사용하면하드웨어종속적인부분을배제하고프로그램논리구현에집중할수있을뿐만아니라 MBED 라이브러리가제공하는다양한부가기능을활용함으로써개발노력을덜수있고개발기간을단축시킬수있게되는것이다. 54
그림 3.9 mbed API 의 DigitalInOut 클래스정의소스일부 그림 3.10 mbed API를이용한 LED 점멸구현그림 3.4, 3.6 그리고 3.10에서제시된프로그램소스를차례대로비교해보면하드웨어추상화와그림 3.2에서제시한 API 추상화계층에대해이해할수있을것이다. 그리고 MBED OS는 ARM Cortex-M MCU 기반의 IoT 장치개발을위한최적의솔루션을제공하기위해다양한외부연결성을제공하고있다. 그림 3.1에서알수있듯이 IP 기반네트워크뿐만아니라 non-ip 기반네트워크연결, 그리고암호화기능을지원하는프레임워크를제공하고있어이를이용하면다양한네트워크환경에서동작하는 IoT 장치를손쉽게개발할수있다. 55
3.2 MBED 온라인컴파일을이용한프로그램생성 앞절에서언급한것과같이본교재에서는 Keil MDK-ARM 툴을이용해서프로젝트실습을진행한다. Keil MDK-ARM 툴이프로그램개발에유용한라이브러리를제공하고있지만 MBED OS에서제공하는라이브러리를이용하면하드웨어종속적인부분과다양한부가기능에대해직접코딩하는수고를덜어어플리케이션개발에집중할수있고개발생산성도높일수있다. 따라서본교재는 MBED 라이브러리를활용하여임베디드어플리케이션을개발하는데중점을두고자한다. Keil MDK-ARM 툴환경에서 MBED 라이브러리를활용하려면 MBED 라이브러리소스다운로드, 하드웨어종속부분포팅그리고복잡한 uvision 프로젝트설정등비교적복잡한프로젝트생성과정이요구된다. 하지만 MBED에서는이를쉽게해결할수있는방법을제공한다. MBED 사이트에서제공하는온라인컴파일러을이용하여프로젝트를생성하고, 이를 MDK-ARM 의 uvision 프로젝트로내보내기 (export) 한다음, 이를 uvision IDE에서프로젝트불러오기 (import) 를통해읽어들이면쉽게프로젝트를생성하여동작시킬수있다. 이절에서는 MBED 온라인컴파일러를이용하여프로젝트생성, 빌드그리고실행등의일련의과정에대해살펴보고, MDK-ARM uvision 프로젝트로내보내기를하는방법에대해알아본다. 3.2.1 MBED 사이트계정생성 MBED 온라인컴파일을사용하기위해서는 mbed.com 사이트의계정이있어야한다. 우선 MBED 사이트 (www.mbed.com) 에접속하여계정을생성하고로그인을하도록한다. 그림 3.11 MBED 사이트화면 56
3.2.2 사용보드선택 MBED 온라인컴파일러를이용하여프로젝트를생성하려면먼저사용할보드 (board) 를선택하여야한다. 만약이미사용할보드를등록한상태라면위그림의오른쪽상단의 Compiler 메뉴를선택하여바로 MBED 온라인컴파일러화면으로이동하면된다. 새로운보드를선택하기위해서는그림 3.12와같이상단 Hardware 메뉴를펼쳐 Boards 메뉴를선택하여보드를선택하는웹페이지가이동한다. 그림 3.12 보드선택화면이동메뉴본교재에서는 STMicroelectronics 의 Nucleo-F401RE 보드를사용하므로보드선택페이지의화면왼쪽에서 Target Vendor 의 STMicroelectronics 를선택하면화면오른쪽에서 MBED OS와호환되는보드종류가나타난다. 여기서 Nucleo-F401RE 보드를선택하면이보드를소개하는페이지로이동한다. 그림 3.13 보드선택화면에서의보드선택 57
사용보드를등록하기위해그림 3.14와같이보드소개페이지의화면오른쪽에서 Add to your Mbed Complier 메뉴를클릭하면 MBED 온라인컴파일러화면이새롭게생성되고 Create new program 창이뜨면서이를통해새로운프로젝트를생성할수있게된다. 그림 3.14 선택보드등록메뉴 그림 3.15 프로그램생성다이얼로그창 그림 3.15와같이 Template 와 Program Name 입력항목을설정한후에 OK 버튼을클릭하면 MBED 프로젝트가생성된다. Template 옵션은다양한예제프로젝트를제시하여개발하고자하는프로그램에근간이되는코드와프로젝트템플릿을제공한다. 선택한템플릿은 Nucleo-F401RE 보드에장착된 LED를깜박이는간단한테스트프로젝트이다. 그리고온라인컴파일화면의오른쪽상단에사용중인보드가명시된다. 그림 3.16 MBED 온라인컴파일러에서생성된프로그램과등록된보드 3.2.3 프로젝트빌드및실행 MBED 온라인컴파일러의툴바에서 Compile 메뉴의하부메뉴 Build Only 를선택하면 ( 그림 3.17) 온라인컴파일러는프로그램소스를컴파일한후에 bin 형식의파일 ( 바이너리코드 ) 을생성하고, 이를호스트 PC로자동적으로다운로드하는작업까지실행한다. 58
그림 3.17 프로그램빌드실행 그림 3.18 프로그램빌드완료후에다운로드된파일호스트 PC의다운로드폴더에서생성된 bin 형식파일을확인할수있으며 ( 그림 3.18), 이를실행하기위해실습키트의가상드라이브로파일을이동시키거나 (Drag-and-Drop) 복사하여붙여넣기 (Copy-and-Paste) 를수행하면플래시메모리프로그래밍을이루어지고초기화를통해프로그램이실행되면서타겟보드의녹색 LED 가점멸하는동작을확인할수있다. 3.2.4 MDK-ARM uvision 프로젝트로내보내기 MBED 온라인컴파일러에서는생성한 MBED 프로그램을 MDK-ARM 이나 ARM-GCC 와같은오프-라인툴에서사용하기위해프로그램내보내기 (export) 기능을이용하여원하는개발도구에맞게프로그램을변환하여다운로드받을수있다. 그림 3.19과같이온라인컴파일러화면에서내보내기를할프로그램을마우스우측버튼으로클릭하여팝업메뉴에서 Export Program... 항목을선택한다. 그림 3.19 프로그램내보내기기능선택 59
그러면, 그림 3.20과같이 Export program 다이얼로그창이나타나고, 변환대상이될보드와개발툴을선택할수있다. 보드항목에서는 NUCLEO-F401RE 을, 개발툴항목에서는 uvision5 항목을선택하고 확인 버튼을클릭하면변환된프로그램이호스트 PC로자동다운로드된다. 그림 3.20 프로그램내보내기설정 호스트 PC 의다운로드폴더에서압축파일형식의프로젝트파일을확인할수있 으며 ( 그림 3.21), 이파일을적절한위치에압축을풀고사용하면된다. 그림 3.21 MBED 온라인컴파일러에서내보내기한프로젝트파일 3.3 MDK-ARM 툴을위한 MBED 프로젝트템플릿만들기 프로그램실습을위해매번 MBED 온라인컴파일러를이용하여 MBED 프로젝트 를생성하고이를다운로드하여사용하는것을귀찮은과정이다. 이에원활한프로 60
그램실습을위해 MDK-ARM 툴을위한 MBED 프로젝트템플릿을만들고이를프로그램실습에활용하기로한다. 앞절에다운로드한 Nucleo_blink_led MBED 프로젝트를이용하여본교재만의 MBED 프로젝트템플릿을만드는과정을단계별로살펴본다. (1) 앞절에서다운로드한압축파일을작업폴더로복사한후에압축을푼다. 그 리고압축해제된폴더이름을 Nucleo_mbed_template 로변경한다. 그림 3.22 MBED 프로젝트템플릿폴더이름변경 (2) Nucleo_mbed_template 폴더를열고, 확장자가.uvprojx 와.uvoptx 인파일의이름을동일하게 Nucleo_mbed_template 으로변경한다 ( 그림 3.23). 이파일들은 uvsion IDE의프로젝트설정파일들이며파일이름을변경함으로써프로젝트이름이 Nucleo_mbed_template 로바뀌게된다. 향후이프로젝트템플릿을이용하여새로운프로젝트를생성하는경우에도폴더이름과이두파일의이름을원하는프로젝트이름으로변경하기만하면된다. 그림 3.23 프로젝트설정파일이름변경 (3) 아이콘을가진프로젝트설정파일 (*.uvprojx) 을더블클릭하여 uvision IDE 를 실행시킨다. 그리고그림 3.24 와같이 Nucleo_blink_led 로설정되어있는실행타 61
겟이름과소스그룹이름을각각 STM32F401 Flash 와 src 로변경한다. 그림 3.24 프로젝트타겟이름과소스그룹이름변경 (4) 불러온프로젝트에대해사용하는보드와실습환경에맞게프로젝트옵션을수정하도록한다. Target Options... 버튼을클릭하여타겟옵션설정다이얼로그창을열고 Target 탭에서클럭크기를 84 MHz로변경한다. 그리고 Output 탭을열고출력파일이름을 Nucleo_out 으로변경하여생성되는실행파일이름이 Nucleo_out.axf 가되도록한다. 그림 3.25 프로젝트옵션변경 (1) (5) bin 형식의이미지파일을생성하기위해 User 탭을열고, After Build/Rebuild 의첫번째입력항목에 fromelf --bin o @L.bin!L 을입력한다. 이에대한자세한설명은앞장의내용을참조하도록한다. 그리고 Linker 탭에서 Scatter File 항목을 scatter file을현재프로젝트폴더에서읽어오도록.\mbed\... 로시작하는항목으로변경한다. 62
그림 3.26 프로젝트옵션변경 (2) (6) 마지막으로디버거장치에대한설정을변경하도록한다. Debug 탭을열 어현재디버거장치가 ST-Link Debugger 로설정되어있는지확인하고 Settings 버튼을클릭하여세부설정으로들어간다. 그림 3.27 프로젝트옵션변경 (3) - 디버거세부설정세부설정의 Trace 탭을열고 Core Clock 을 84 MHz로설정한다. 그리고 Flash Download 탭을열어프로그래밍한후에초기화하여실행하도록 Reset and Run 항목을체크하고, Programming Algorithm 항목이 STM32F4xx 512KB Flash 로설정되어있는지확인한다. OK 버튼을클릭하여디버거세부설정을종료한다. 63
그림 3.28 프로젝트옵션변경 (4) - 디버거세부설정 Utilities 탭을열어플래시프로그래밍을위해앞에서설정한디버거장치를사용하도록그림과같이설정되어있는지확인한다. 그리고마지막으로다이얼로그창의하단에있는 OK 버튼을클릭하여지금까지설정한내용을반드시저장하여야한다. 그림 3.29 프로젝트옵션변경 (5) (7) 앞에서설정한내용들이정상적으로동작하는지확인하기위해프로젝트를빌 드하고다운로드하여동작여부를테스트한다. 프로젝트를빌드하기위해 Rebuild 버튼을클릭하고 Build Output 창의내용을확인한다. 64
그림 3.30 프로젝트빌드실행정상적으로빌드가완료되면실행이미지를타겟보드의플래시메모리에프로그래밍하기위해 Download 버튼을클릭하고 Build Output 창의출력내용을확인한다. 그림 3.31 실행파일다운로드실행 타겟보드의녹색 LED 가점멸하는동작이확인되면 MBED 프로젝트템플릿이정 상적으로만들어진것이다. (8) 생성된 MBED 프로젝트템플릿은새로운실습을위해 MBED 프로젝트를만들때에사용한다. 새 MBED 프로젝트를만들고코딩을시작하는과정은다음과같다. 1 Nucleo_mbed_template 폴더를원하는위치로복사한다. 2 복사한폴더이름과폴더내의.uvprojx 와.uvoptx 확장자파일의이름을새로운프로젝트이름으로변경한다. 3 uvision IDE에서프로젝트불러오기 (import) 를이용하여새로만든프로젝트를열거나복사한폴더를열고.uvprojx 확장자파일을더블클릭하면 uvision IDE가새로실행되면서만들어진프로젝트를자동으로열어준다. 4 src 소스그룹의 main.cpp 소스파일을수정하거나새로운소스파일을추가하는등의코딩과정을통해프로그램을개발한다. 3.4 MDK-ARM 툴을이용한코드디버깅 앞절에서 MBED 프로젝트템플릿을생성하고, 이것을이용하여새로운 MBED 프 로젝트를만드는방법에대해살펴보았다. 이절에서는 MDK-ARM 툴을이용하여코 65
드를디버깅하는방법에대해간단히살펴본다. 우선템플릿프로젝트파일이있는폴더로이동한다음프로젝트파일 (.uvprojx) 을더블클릭하여 uvision 창을연다. Rebuild 아이콘을클릭하여소스를빌드 (build) 한다. 빌드가완료되면바이너리파일 (.bin) 과 ELF 타입의실행파일 (.axf) 파일이생성된다. 먼저생성된파일들을직접실행하는방법을살펴보면앞장에서설명한것과같이두가지방법이있다. 첫째, 디버거를이용한플래시프로그래밍방법으로툴바에서 Download 버튼을클릭하면 uvision IDE는설정된디버거장치와플래시프로그래밍알고리즘을이용하여타겟보드의플래시메모리에실행코드를프로그래밍한후에초기화시켜코드를실행시킨다. 그림 3.32 실행파일다운로드메뉴둘째, 바이너리파일복사방법으로생성된 bin 형식의파일을타겟보드연결후에생성된가상저장장치폴더로복사하여붙여넣으면자동으로플래시프로그래밍이실행된다. 그림 3.33 바이너리파일의복사하여붙여넣기 (Drag-and-Drop) 다음으로생성된실행파일을이용하여코드를디버깅하는방법은그림 3.34의 Debug Session 아이콘을클릭한다. 그럼실행코드가보드로다운로드되어플래시메모리에프로그래밍되고 Debug 세션으로화면이전환되는것을볼수있다. 66
그림 3.34 디버깅세션메뉴그림 3.35와과같이 uvision 의 Debugger 창에서중단점 (breakpoint) 와추적 (trace) 등의다양한디버깅기능을이용하여소스코드를디버깅할수있다. 코드디버깅의일반적인개념과디버깅방법은관련자료를참조하여익히도록한다. 그림 3.35 uvision IDE의디버깅화면지금까지 MBED 온라인컴파일러에서생성한프로그램을내보내기하여오프-라인툴-체인환경인 Keil uvision IDE에서컴파일하고디버깅하는과정을간략하게살펴보았다. 이것으로 MBED 온라인컴파일러를이용하거나 MDK-ARM 툴을이용하거나프로젝트에적합한개발환경을자유롭게선택하여사용할수있다는것을알게되었다. 그리고어떤개발환경을선택하든지간에지금까지살펴본과정을수행하여야만 STMicroelectronics 의 Nucleo-F401RE 보드를이용하여새로운프로젝트를진행해나 67
갈수있다는것이다. 만약오프-라인환경에서개발이가능한 Keil MDK-ARM 툴을가지고있다면강력한컴파일러와디버거유틸리티를이용할수있어이툴을사용하는것이가장바람직하다. 한가지주의할점은어떤경우라도 MBED 프로젝트에제공하는 SDK( 라이브러리, 헤더파일등 ) 를이용하고있다는것이다. 68
4. LED 제어 앞장에서 Nucleo STM32F401RE 보드의 LED를깜박이게하는프로젝트를 MBED 온라인컴파일러에서만들어서이미실행해보았다. 이제부터는본격적으로 MDK-ARM 툴을이용하여 Cortex-M4 마이크로컨트롤러를제어하는프로그래밍을시작한다. 4.1 LED 점멸하기 LED 점멸프로그램은 Nucleo STM32F401RE 보드의 LED를깜박이게하거나확장보드의 LED를깜박이는동작을수행하는프로그램이다. 이프로그램은 MCU 제어프로그래밍에서디지털신호출력의대표적인사례로서 Hello World 프로그램에해당한다. 본절에서는 LED 점멸프로그램을새로이코딩하기보다는 MBED 온라인컴파일러에서생성한프로그램을통해 MCU 제어프로그램의기본구조와이를표현하는 C 언어의문법요소들을먼저살펴보도록한다. 이를통해앞으로우리가구현할프로그램들에대한이해를돕도록한다. 앞장에서다운로드한 MBED 온라인컴파일러프로젝트 Nucleo_blink_led 를 uvision IDE에서불러오기 (import) 를하면그림 4.1과같은화면을볼수있다. 그림 4.1에서 src 폴더안에있는 main.cpp 파일은프로그램소스파일이다. 그림 4.1 Mucleo_blink_led 프로젝트의 main.cpp main.cpp 파일에마우스우측버튼을클릭하여그림 4.2 처럼 Option for File 항목을선택한다. 69
그림 4.2 main.cpp 파일의옵션설정실행옵션설정창에서 C/C++ 탭을선택하면그림 4.3과같이컴파일러옵션 (-l 옵션 ) 으로 MBED 관련라이브러리들을링크하고있는것을확인할수있다. MBED 라이브러리와관련해서는앞으로좀더논의를진행할것이며, 여기서는단지우리가 MBED 라이브러리를사용하고있다는것을이해하면된다. 그림 4.3 main.cpp 파일의컴파일옵션 main.cpp 를클릭하여프로그램을편집창에서연다. 이프로그램파일은우리가직접코딩한것인아니라프로그램개발에도움을주기위해만들어진템플릿프로그램에서미리제공한것이다. 이파일의프로그램소스를자세히살펴봄으로써을 MCU 제어프로그램의기본구조와 C 언어의문법요소를이해하도록한다. 70
어떤프로그램이든지간에첫번째단계는사용할라이브러리를불러오는것이다. 라이브러리는우리가사용하는마이크로컨트롤러를위해특정동작을수행하도록특별히설계된명령들의모음이다. 이명령들은표준 C 명령들과는달리마이크로컨트롤러의모든기능을사용할수있도록추가된명령들이다. 우리가사용하고있는 Nucleo STM32F401RE 마이크로컨트롤러보드는 MBED에서지원하는호환플랫폼이기때문에 MBED 라이브러리를불러와사용할수있다. 앞서 MBED 온라인컴파일러에서 MBED 프로젝트를내보내기 (export) 할때에도보드목록에서 ST Nucleo F401RE 항목을선택하여야한다. 이는 Nucleo STM32F401RE 보드를사용한다는것을알려주어 STM32F401RE 마이크로컨트롤러에서필요한 MBED 라이브러리를자동으로함께내보내기를하게된다. 라이브러리를사용하기위해서는 #include 지시어를이용하여라이브러리가제공하는명령들을기술한헤드파일 (Header File) 을불러옴으로써프로그램소스내에서해당라이브러리가제공하는명령을사용할수있게된다. MBED 라이브러리를사용하기위해서는우선다음과같은형식을코드를통해 MBED 라이브러리헤드파일을불러와야한다. #include mbed.h C 언어는대소문자를구별한다는점에유의하도록한다. 즉, Include 와 include 는다른것이다. 다음단계로, 우리가사용하고자하는마이크로컨트롤러의입출력핀에대해적절한이름과기능을정의하도록한다. LED 동작을제어하기위해사용하는 LED1 핀, 좀더정확하게는그림 4.4의 PA_5 핀에대해 myled 라는이름을부여하여식별성을높임으로써코딩을보다쉽게할수있도록한다. 또한 ARM 마이크로컨트롤러에서대부분의핀은입력뿐만아니라출력으로도사용될수있기때문에이들을어떻게사용할지정의할필요가있다. 이를선언하는형식은다음과같다. type name(pin); // ex) DigitalOut myled (LED1); type 은어떤종류의기능을사용할것인지를가리킨다. ARM 마이크로컨트롤러의핀은대부분중복기능을가지고있어해당핀에한정해서사용할기능을선택하여사용할수있다. 예를들어, PA_5 핀은 digital 입출력핀또는 SPI 통신핀으로사용할수있으나 USB 통신핀으로는사용할수없다. 출력 (output) 은마이크로컨트 71
롤러에서해당핀에연결된장치로전력 (power) 이공급되어진다는의미이다. 예를들면하나의출력핀이 LED를켜는데사용될수있다. 입력 (input) 은전력 (power) 이마이크로컨트롤러쪽으로간다는의미이다. 예를들어, 하나의입력핀은스위치로부터신호를수신할수있다. LED 장치에연결된핀은 LED 장치에전력을공급하여동작시켜야함으로출력으로동작하도록타입을 DigitalOut 으로정의한다. name 은프로그램에서사용하고자하는이름으로 C 언어의식별자문법에준하여어떤것도가능하다. 보통쉽게이해할수있도록정보를제공할수있는이름이면좋다. 그러나코딩의부담을줄이기위해너무길지않도록하는것이좋다. pin 은그림 4.4처럼외부와연결되는물리적인핀을가리킨다. DigitalOut myled (LED1); 위문장을 LED1 은 DigitalOut 기능을수행하고나는이를 myled 라부를것이다 와같이읽을수있다. 어쩌면여러분은하나의명령다음에는세미콜론 (;) 이따라온다는것을눈치챘을것이다. C 언어에서이것은반드시필요하다. 한문장의명령어끝을나타낸다. 그렇게해야컴파일러는하나의명령어가언제끝나고시작하는지알수있게된다. 중괄호다음에는세미콜론을사용할필요가없다. 왜냐하면중괄호자체가명령의끝을가리키기때문이다. 이것은문장의마침표와유사하다. 가끔세미콜론을잊어버리곤하는데, 에러의원인이됨으로주의가필요하다. 그림 4.4 Nucleo-F401RE 보드의외부연결핀 72
지금까지프로그램의정의부분을살펴보았다. 다음은실제로명령을포함하는부분을살펴보는데, 이부분에서마이크로컨트롤러에게해야할일을지시한다. 일반적으로 C 언어에서는이부분을 main 함수안에기술한다. main 함수는다음과같은문법구조를갖는다. 프로그램은작업지시서와같다. 작업지시서의시작은 main 이라는함수의이름에서시작된다. int main() { 명령들을여기에둔다. 순서대로수행되어야할작업들이있는문장이다. 일견봐서는좀복잡해보인다. 우리는왜 int 와괄호가사용되었는지여기서논하지는않을것이다. 지금으로서는이것이필요하다는것만기억하자. main 함수의중괄호는컴파일러에게실제로어떤명령들이프로그램의 main 부분에속하는지를알려주는데사용된다. 프로그램소스의다음명령은 while 로서다음과같은문법구조를갖는다. while (condition) { 명령또는동작들을여기에둔다. condition 이 1( 참 ) 이면중괄호안에있는문장이수행된다. While 명령은특정조건에맞으면 이를 true 조건이라한다 중괄호사이의명령들을실행한다. 그림 4.1의프로그램소스에서조건으로명시된값은 1이다. C 언어에서 1은 true 를의미한다. 그리고 0은 true와반대인 false를의미한다. 그래서 while(1) 은 while(true) 으로읽을수있다. true는항상참 (true) 이므로이 while 문장은중괄호사이의명령들을무한반복으로실행한다는의미가된다. 프로그램은이루프를결코빠져나오지못한다. 때문에마이크로컨트롤러를끄지않는한절대멈추지않는다. 이부분이 MCU 제어프로그래밍에서시사하는바가대단히중요하다. 일반적으로 PC 어플리케이션프로그램은원하는작업의수행이끝나면프로그램을종료한다. 그러나 MCU 제어프로그램은 MCU가내장된장치를끊임없이제어하여야함으로일련을제어동작을무한반복하여수행하여야하며, 이를 while(1) { 문장과같이무한반복문으로표현하는것이다. 결론적으로 MCU 제어프로그램의주반복문은 73
무한반복하도록구현한다는것을명심하도록하자. 그림 4.1 의프로그램에서는아래명령들을무한반복으로실행한다. myled = 1; wait(0.2); myled = 0; wait(1.0); // HIGH ==> led on // 대기 // LOW ==> led off // 대기 이제 C 언어에서 1 이참 (true) 과같다는것을알게되었다. 또한이는디지털신 호에서 HIGH 와같은의미를갖는다. 그래서 LED1 에해당하는 myled 는아래 명령에의해서 HIGH 신호를출력하여 LED 를 on 하는동작하게된다. myled = 1; 반면 C 언어에서 0은거짓 (false) 과같으며, 디지털신호에서는 LOW 의의미를갖는다. 아래의명령은 myled가 LOW 신호를출력하도록하여 LED를 off하는동작을수행한다. 따라서위의명령과아래의명령을반복적으로실행하면 LED가깜박이게된다. myled = 0; LED가깜박이는동작을확인하기위해서는 LED의 on과 off 동작사이에약간의대기시간이필요하다. 그렇게하지않으면동작이너무빨라서 LED가변하는것을보지못하게된다. 전기적인동작은대단히빠르므로그것을확인하기위해서는어느정도의시간지연이필요함을알아두도록하자. wait 명령은지정된시간동안프로그램을지연하도록만든다. wait(seconds); 시간은초단위이기때문에 wait(0.2); 명령은프로그램을 0.2초 (200 msec) 동안지연하도록만든다. LED on, 프로그램지연, 그리고 LED off 동작을반복함으로써육안으로 LED가깜박이는것을확인할수있게된다. 이동작들을무한반복하지않으며 LED는단한번만깜박이고말게된다. 74
이제 LED 를깜박이게하는프로그램에서사용한모든 C 언어명령들을살펴봤다. 이를요약하면다음과같다. 프로그램 #include mbed.h DigitalOut myled(led1); int main() { while(1) { myled = 1; wait(0.2); myled = 0; wait(1.0); 설명 추가명령들을가지는라이브러리포함 LED1 에 myled 라는이름을부여하고, compiler 에게이것이출력이라고알려줌. 실제프로그램의시작 True 조건이면다음명령을실행. LED 를 on 0.2 초간대기 LED 를 off 1 초간대기 While 루프의마지막명령끝 실제프로그램의마지막명령끝 이제 uvision 에서툴바의 Rebuild 버튼을클릭하여프로그램을컴파일하고실행파 일을생성한다. 그림 4.5 프로젝트빌드실행 하단출력메시지창에컴파일이성공적으로이루어지고두가지형태의실행파 일, axf 파일과 bin 파일이생성된것을확인할수있다. 그림 4.6 프로젝트빌드실행메시지 75
다음은생성된바이너리파일을실습보드로다운로드하여실행하는것이다. uvision 에서툴바의 Load 버튼을클릭한다. 그러면생성된바이너리파일이 ST-Link 를통해마이크로컨트롤러로다운로드되어플래시메모리에프로그래밍되고초기화를통해프로그램을실행한다. 그림 4.7 실행파일다운로드및플래시메모리프로그래밍메시지잠시후, Nucleo STM32F401RE 보드의녹색 LED가깜박이는것을확인할수있다. 여기서앞에서살펴본내용중에 MBED 라이브러리와관련된사항을요약하면다음과같다. 1 마이크로컨트롤러핀을디지털출력으로사용하려면 DigitalOut 타입으로정의하여사용한다. 2 디지털출력핀이 HIGH 신호를출력하려면디지털출력핀에 1을할당한다. 3 디지털출력핀이 LOW 신호를출력하려면디지털출력핀에 0을할당한다. 4.2 프로그래밍에러다루기 위프로그램에는아무런에러도포함되어있지않지만만약에러가있다면어떻게해야할까? 초보자가범하기쉬운가장일반적인실수는명령의끝에세미콜론을추가하는것을잊는것이다. 그래서아래와같이 LED을켜는명령에서세미콜론을제거해보자. myled = 1 // myled = 1; 편집창의해당라인에대해에러가표시되는것을볼수있다. 76
그림 4.8 편집창에서의구문에러표시 이코드를저장하고 Rebuild 버튼을클릭하여컴파일을진행해보자. Build Output 창에다음과같은에러에대한상세한내용을볼수있다. 그림 4.9 에러발생시의빌드메시지 (1) 에러메시지는에러가어느파일의어느위치에서발생하였는지알려준다. 즉, 여기서는 main.cpp 파일의라인 8에서에러가발생하였음을알려주고있다. 그런데 8 번라인을보면세미콜론이제거된위치가아니라컴파일러가세미콜론이빠진것을알게된위치임을알수있을것이다. 이런경우라면정확한위치에세미콜론을두고문제를쉽게해결할수있을것이다. 그렇지만보통이런에러메시지가에러를이해하기어렵게만드는경우도있다. 왜냐하면명시된에러라인이세미콜론이없어에러를유발한라인이아니라그다음라인을명시하기때문이며, 이에유의하도록하자. 다시세미콜론을추가하고파일을저장한다. 많은경우에에러가발견된위치에서문제를해결할수없다. 아래와같이예제에서 while문의중괄호를제거하고프로그램을컴파일해보자. while(1) myled = 1; // LED is ON wait(0.2); // 200 ms myled = 0; // LED is off wait (1.0); // 1 sec 그러면다음그림과같은에러메시지가발생한다. 77
그림 4.10 에러발생시의빌드메시지 (2) 이번에러는이전보다덜명확하다. 먼저라인 8의구문이 unreachable 하다는 warning 이발생한다. 이유는만약 while문다음에단하나의문장만온다면중괄호를사용할필요가없기때문이다. 즉, 컴파일러는 while 문의아래문장까지만유효한 while 문으로여기게된다. while(1) myled = 1; // LED is ON 1은항상 true이기때문에작은 while 반복문 즉, myled = 1 명령 은결코종료되지않는다. 이는 while 문장이후의어떤명령도실행되지않는다는의미이다. 그다음명령은 unreachable 하게되는것이다. 기술적으로는이것이구문에러 (syntax error) 가아니기때문에 warning 을발생하게된다. 컴파일러는닫는괄호를만나면이를수용한다. 왜냐하면이는 main 문에속한다고생각하기때문이다. 그래서추가로라인 12의두번째닫는괄호에도달하면에러를발생하게된다. 만약, 프로그램이 warning 만을가진다면컴파일은성공하게될것이다. 그렇지만비록 warning 일지라도여러분은주의를기울여야한다. 프로그램이에러를포함하지않더라도원하는동작을하지않을수있기때문이다. 결론적으로에러메시지를읽기만해서는무엇이잘못되었는지항상쉽게원인을발견할수있는것이아니다. 이런연유로규모가큰프로그램을개발할때가장좋은방법은수시로컴파일해보는것이다. 그런다음에러가발생되면여러분이입력한마지막라인에서에러가발생했다는것을알게된다. 종종여러개의에러와 warnings 는서로연관되어있기도한다. 하나의괄호만을남겨둔이번예제에서는하나의 warning 과하나의에러로결과가나타났다. 이를디버깅 (debugging) 할때는첫번째에러와 warning 을해결하는것이가장좋은방법이다. 그런다음, 다시컴파일을한다. 운이좋다면다른문제들도함께해결될수있다. 이번절에서는코드작성시에흔히범하기쉬운실수와컴파일시에만날수있는에러와 warning 메시지에대해간단히살펴보았다. 아주간단한예를들어설명 78
했기때문에아마도쉽게이해했으리라생각된다. 이제타겟시스템을제어하는임 베디드프로그램의코딩과디버깅의첫발을내디딘것이라할수있겠다. 앞으로 진행되는실습프로젝트들을통해노하우와경험치를축적해나가기를바란다. 4.3 LED 점멸프로그램확장 실습장비의확장보드에는그림 4.11과같이 3개의 LED가장착되어있어 LED 출력을조합하여여러가지상태를표시할수있다. 3개의 LED는왼쪽부터초록색, 노란색그리고빨간색을출력하며, 각각마이크로컨트롤러의 PA_13, PB_10, PA_4 핀에연결되어있다. 그림 4.11 확장보드의 LED 장치이번절에서는간단하게 3개의 LED를번갈아가면서 1초간깜박이는프로그램을구현하는데, 구현방법을달리하여 3개의 LED를각각제어하는방법과 3개의 LED를일괄로동시에제어하는방법으로구현하도록한다. 4.3.1 DigitalOut 을이용한다수 LED 제어 먼저앞에서배운내용의복습차원에서 3 개의 LED 를개별적으로제어하여점멸 하는프로그램을실습하며, 실습과정을단계별로살펴보면다음과같다. 첫째, 우선새로운 MBED 프로젝트를생성하기위해 MBED 템플릿프로젝트폴더 ( Nucleo_mbed_template ) 를복사하여작업폴더 es-lab 에서붙여넣는다. 복사한폴더의이름과폴더내의.uvprojx 와.uvoptx 확장자파일의이름을새로운프로젝트이름인 blink_multiple_leds 로변경한다..uvprojx 확장자파일을더블클릭하여 uvision IDE를새로실행한다. 79
그림 4.12 blink_multiple_leds 프로젝트생성둘째, src 폴더의 main.cpp 파일을클릭하여편집창을열고, 프로그램소스를다음의소스로수정한다. #include "mbed.h" DigitalOut myled1(pa_13); DigitalOut myled2(pb_10); DigitalOut myled3(pa_4); // Green LED // Yellow LED // Red LED int main() { // initialize all LEDs to be off myled1 = 0; myled2 = 0; myled3 = 0; while(1) { myled1 = 1; wait(1.0); myled1 = 0; myled2 = 1; wait(1.0); myled2 = 0; myled3 = 1; wait(1.0); myled3 = 0; // Green LED is on // delay 1000 ms // Green LED is off // Yellow LED is on // delay 1000 ms // Yellow LED is off // Red LED is on // delay 1000 ms // Red LED is off 80
프로그램소스를간략하게살펴보면앞에서살펴본프로그램소스의구조와일치하며, 3개의 LED를순서대로제어하도록확장되었다. 1 #include 지시자를이용하여 MBED 라이브러리헤드파일을불러온다. 2 3개의 LED에출력하기위해연결된마이크로컨트롤러핀들을 DigitalOut 타입으로정의한다. 3 main 함수안에 LED를모두끄도록초기화하고 while 문을이용하여무한반복문을구현한다. 4 while 문안에 3개의 LED를순서대로 1초간깜박이도록구현한다. 셋째, 프로그램소스편집이완료되면툴바의 Rebuild 버튼을클릭하여프로그 램빌드를실행한다. 그림 4.13 uvision IDE 의 Rebuild 버튼 넷째, 프로그램빌드가완료되면툴바의 Load 버튼을클릭하여프로그램을 다운로드하여실행한다. 3 개의 LED 가초록, 노랑그리고빨강순서로깜박이는동작 을수행하는지확인한다. 그림 4.14 uvision IDE 의 Load 버튼 4.3.2 BusOut를이용한다수 LED 일괄제어디지털신호를출력할때에여러신호선들을묶어일괄로동시에출력하는것이편리한경우가많다. 앞예제의경우도 3개의 LED 출력신호선을묶어동시에출력한다면훨씬효과적일것이다. 이렇게연관성을가진여러신호선을묶어같이입출력을수행할때에이신호선들의묶음을보통 버스 (bus) 라고부른다. MBED 라이브러리도버스방식으로디지털신호입출력을지원하는 BusOut 과 BusIn 기 81
능을제공한다. 여기서는 BusOut 기능에대해서만살펴본다. BusOut 기능은 DigitalOut 기능과사용법이거의동일하다. 단지차이점은 DigitalOut 은하나의신호선에대해서만신호를출력하는반면, BusOut 은최대 16개의신호선들에대해동시에신호출력이가능하다는것이고, BusOut 기능을이용하여신호를출력할때에각각의신호선에출력되는신호값은버스에출력되는값의비트단위로제어한다는것이다. 여러출력핀에동시에신호를출력하려는경우에 BusOut 의버스출력기능을이용하면프로그램논리가간단해지고그에따라프로그램코딩도단순해진다. BusOut 기능을이용하여 3개의 LED를동시에제어하여순서대로점멸하게하는실습과정은다음과같다. 첫째, 앞예제와같이새로운 MBED 프로젝트를생성하기위해 MBED 템플릿프로젝트폴더 ( Nucleo_mbed_template ) 를복사하여작업폴더 es-lab 에서붙여넣는다. 복사한폴더의이름과폴더내의.uvprojx 와.uvoptx 확장자파일의이름을새로운프로젝트이름인 blink_led_bus 로변경한다..uvprojx 확장자파일을더블클릭하여 uvision IDE를새로실행한다. 그림 4.15 blink_led_bus 프로젝트생성 둘째, src 폴더의 main.cpp 파일을클릭하여편집창을열고, 프로그램소스를 다음의소스로수정한다. 82
#include "mbed.h" BusOut ledbus(pa_13, PB_10, PA_4); int ledmasks[] = { 0b00000001, // only LED1 ON 0b00000010, // only LED2 ON 0b00000100 // only LED3 ON ; int main() { // initialize all LEDs to be off ledbus = 0; while(1) { for (int i=0; i<3; i++) { ledbus = ledmasks[i]; wait(1.0); // delay 1000 ms 위의프로그램소스를간략하게살펴보면, 1 #include 지시자를이용하여 MBED 라이브러리헤드파일을불러온다. 2 3개의 LED에동시에출력하기위해 BusOut 타입의변수를정의하고, 버스로묶어줄 LED 핀을순서대로정의해준다. 3 ledmasks 배열은프로그램논리를단순화하기위해 LED 버스에출력할값들을미리정의한배열이다. BusOut 기능은신호출력을비트단위로제어하므로배열의원소를이진수상수형식으로정의하였으며, 첫번째원소는 Green LED만켜는값이며, 두번째원소는 Yellow LED만, 세번째원소는 Red LED 만켜는값이다. 4 main 함수안에서 ledbus 에 0 을출력하여 LED를모두끄도록초기화하고, while 문을이용하여무한반복문을구현한다. 5 while 문안에서 for문을이용하여 ledbus 에 ledmasks 배열의원소를순서대로출력하여 3개의 LED가순서대로 1초간깜박이도록구현한다. 셋째, 프로그램소스편집이완료되면툴바의 Rebuild 버튼을클릭하여프로그 83
램빌드를실행한다. 넷째, 프로그램빌드가완료되면툴바의 Load 버튼을클릭하여프로그램을 다운로드하여실행한다. 3 개의 LED 가번갈아가면서깜박이는동작을수행하는지확 인한다. 이번예제의프로그램소스에서는배열선언, 이진수표현그리고 for 반복문등다소복잡한 C 언어의문법요소를활용하였다. 이러한문법요소들은앞으로자세히설명할기회가있으며, 간단한문법형식은부록에따로정의되어있으니참고하길바란다. 84
5. 스위치제어 이장에서는디지털신호입력의대표적인사례인스위치입력에대해논의하도록한다. 이주제는그렇게복잡하지않기때문에조금더복잡한프로젝트를진행하기전에실습을통해디지털신호입력에대한개념을이해하는것이바람직하다. 또한, 만약 C 언어프로그래밍이처음이라면적어도이장은꼭읽어보기바란다. 왜냐하면새로운 C 언어명령들, 예컨대조건문 if-else 문장, 반복문 for 문장그리고논리연산과변수등에대해서알수있기때문이다. 5.1. 스위치입력 이절에서는스위치를눌렀을때 5초간 LED 하나를켜두는프로그램을만들어본다. 기술적으로이를 monostable multivibrator 라하지만, 타이머스위치로더많이알려져있다. 우선스위치입력을처리하는프로그램을살펴보기전에스위치제어를위해알아야하는기본적인하드웨어정보에대해살펴보자. 첫째, 스위치를마이크로컨트롤러에어떻게연결하여야하는가? 만약마이크로컨트롤러가스위치가연결된입력핀을계속읽고있는중에이핀이불안정하게 0과 1 사이를오가면 ( 부동상태, float 상태 ) 안된다. 디지털입력핀은항상안정적으로 0 또는 1의값을읽을수있어야한다. 이는입력핀에전원 (power) 또는접지 (ground) 에연결되어야한다는것을의미한다. 그러나전원에연결된스위치를마이크로컨트롤러의입력핀에연결하고스위치가닫히면 (close) 1이입력되지만, 스위치가단락 (open) 되면그핀은어디에도연결되지않은, 그래서허용되지않는 float 상태가된다. 따라서스위치를입력핀에연결할때에는입력핀에저항 (resistor) 을사용하여전원또는접지를연결함으로써스위치를열고닫혔을때에마이크로컨트롤러가입력값을안정적으로인지할수있도록해주어야한다. 스위치는그림 5.1과같이두가지방법으로연결할수있다. 그림 5.1의왼쪽스위치가열려있다 (open) 고가정해보자. 현재입력핀은 10KΩ 저항을통해접지에연결되어있어마이크로컨트롤러가입력핀을읽으면 low 값 (0 또는 false) 을읽게된다. 이러한경우에저항이입력핀을접지로끌어내린다고말할수있다. 이런이유로인해서이저항을 풀다운저항 (pull-down resistor) 이라부른다. 만약스위치가닫히면 (close), 입력핀은전원 (high, 1 혹은 true) 에직접연결된다. 그래서스위치가닫히면입력핀의신호는 HIGH가되고, 떨어지면입력핀의 85
신호는 LOW 가된다. 그림 5.1 스위치연결회로 풀업및풀다운저항연결그림 5.1의우측스위치는 풀업저항 (pull-up resistor) 을사용하여좌측스위치와는정반대로동작한다. 이경우는스위치를닫으면 LOW 신호가, 열리면 HIGH 신호가입력된다. 즉, 우리가직관적으로생각하는것과반대가되어혼동을줄수있으니유의하여야한다. 둘째, 그럼, 저항의크기는어느정도로하는것이안전하게동작하는가? 스위치가닫히면작은전류가저항을통해흐르기때문에비교적높은저항값을갖는것이좋다. 일반적으로풀다운또는풀업저항으로는 10KΩ 크기의저항이면적당하다. 오옴의법칙 (Ohm s Law) 을사용하면얼마나많은전류가저항을통해흐르는지계산할수있다. V = I * R 혹은 I = V/R, 혹은 R = V/I 여기서, V = 전압 (voltage, Volts), I = 전류 (current, Amps), R = 저항 (resistor, Ohm) 을나타낸다. 스위치회로의경우에는전류는 3.3 (volts) / 10,000 (ohm) = 3.3 * 10-4 A 혹은 0.33 ma가된다. 이것은무시할수있는값이지만, 왜이런것에주의를기울여야할까? 일반적으로실습장비는대략 500mA 정도의전류를공급하는 USB 포트에서전원을끌어온다. 현재사용하는마이크로컨트롤러보드는호스트 PC의손상을막기위 86
해서 450mA 에서전원을차단하도록 USB 전원에대해전류제한기 (current limiter) 를가진다. 이렇게소모전류에대한제한이있기때문에우리는늘전력소비에대해주의를기울여야된다. 다행스럽게도풀다운또는풀업저항을통해흐르는전류는대단히작아문제가되지않는다. 셋째, 스위치는마이크로컨트롤러의어떤핀에연결하여야하는가? 이제스위치를마이크로컨트롤러에어떻게연결해야하는지알게되었다. 그럼, 스위치를제어하기위해마이크로컨트롤러의어떤핀을사용할지결정해야한다. 그림 5.2은 Nucleo-F401RE 보드에서외부연결을지원하는인터페이스핀의구성과정보를보여주는데, 인터페이스핀의이름과기능그리고아두이노인터페이스핀과의맵핑관계등을보여주고있다. 스위치를연결하기위해서는이들디지털입출력핀중하나를사용할수있다. 그림 5.2 Nucleo-F401RE 보드의외부연결핀구성실습장비는그림 5.3과같이 3개의푸시버튼스위치를지원하며, 왼쪽부터각각마이크로컨트롤러의 PA_14, PB_7, PC_4 핀에연결하고있다. 스위치연결방식은그림 5.3의아래부분에서보여주는것과같이풀업저항을이용한연결회로로구성되어있다. 즉, 스위치가단락되었을때에 1, 닫혔을때에 0이입력된다. 87
그림 5.3 스위치구성및연결회로지금까지스위치에대한하드웨어구성에대해살펴보았다. 이제부터스위치제어를위한프로그램논리에대해살펴보자. 4장의프로젝트에서디지털출력핀을 DigitalOut 으로정의하여사용한것을기억할것이다. 스위치가연결되는핀은디지털입력핀이기때문에 DigitalIn 으로정의하여사용할것이라는것을직관적으로이해할수있을것이다. 스위치를연결한핀에 switch 와같은적당한이름을줄려고시도해볼수있다. 그러나불행히도 switch 는 C 언어에서사용하는명령어이기때문에이를변수이름으로사용할수없다. MDK-ARM 의 uvision 의편집창에서는 C 언어의명령어나자료형에대해서는파란색으로표시한다. 만약, 새로운변수이름을정하고이를입력했는데파란색으로변하면이를다른이름으로바꿔야한다. 실습프로그램에서는 button 이라는이름을사용하도록한다. #include mbed.h DigitalIn button(pa_14); DigitalOut myled(pa_13); // the first SW // Green LED 첫번째스위치가연결된 PA_14 핀은 LED1 과같이특별한이름이없기때문 에 PA_14 로참조해야한다. 프로그램내에서스위치입력값에따라다른일을 88
수행할수있다. 이를위해서새로운 C 명령어, if-else 조건문을활용할수있다. if ( 조건 ) { 조건이 true 이면명령실행 else { 조건이 false 이면명령실행 이는 while 명령과몇가지유사한점이있다. 괄호안에조건이오고, 중괄호안에명령들이온다. 만약조건이참 (true) 이면첫번째중괄호안의명령군이실행된다. 조건이거짓 (false) 이면 else 명령다음에오는두번째명령군이실행된다. 실습프로그램에서조건은 button의입력값이다. else 부분이필요하지않는경우도있다. 이경우에는 else 명령어부분을생략할수있다. if ( 조건 ) { 조건이 true 이면명령실행 스위치의입력값이 HIGH 일때에 5 초간 LED 를켜는동작을 C 언어명령으로기 술하면다음과같다. if (button == 1) { 5 초간 LED 를켠다. 여기서조건문에서이중등호가사용되었다는점에유의하자. 등호하나로된것 ( = ) 은할당연산자로서오른쪽에서계산된값을왼쪽변수에대입하라는것을의미한다. 이중등호 ( == ) 는비교연산자로서두개의값이같은지를검사할때사용한다. 따라서 button == 1 은 button 변수의값과 1이같은가? 라는의미가되는반면에 button = 1 은 button 변수에 1이라는값을대입하라는의미이다. 이것은소스코드작성시에자주실수하는부분이므로주의해야한다. 1이 true와동일하기때문에위의코드는다음과같이간단하게기술할수있다. 89
if (button) { 5 초간 LED 를켠다. 앞장에서실습을통해 LED 을어떻게켜고끄는지그리고어떻게프로그램을일 정시간지연시킬수있는지알아보았다. 이를이용하여소스코드를완성하고실행 해보도록하자. 첫째, 우선앞장의실습과동일한방법으로 MBED 템플릿프로젝트폴더를복사 하여새로운프로젝트 input_switch 를생성하도록한다. 둘째, src 폴더의 main.cpp 파일을더블클릭하여편집창을열고, 프로그 램소스를다음과같이편집한다. #include mbed.h DigitalIn button(pa_14); DigitalOut myled(pa_13); // the first SW // Green LED // button 을누르면 led 가 on 되고 5 초대기후 off 된다. int main() { while (1) { if (!button) { myled = 1; wait(5); myled = 0; 셋째, 툴바의 Rebuild 버튼을클릭하여위의프로그램을컴파일한다. 넷째, 툴바의 Load 버튼을클릭하여생성된실행이미지를마이크로컨트롤러 보드의플래시메모리로다운로드하여실행시킨다. 이제첫번째스위치를누르면 확장보드의녹색 LED 가 5 초간켜질것이다. 90
어쩌면여러분은이미프로그램을작성할때에반복문이나조건문이사용될때마다내부코드를들여쓰기한것을눈치챘을수도있다. 이렇게하면소스코드를읽기가훨씬쉽다. 그림 5.4는소스코드에들여쓰기를한것과하지않은것을보여준다. 두번째소스코드가상대적으로얼마나보기어려운지알수있을것이다. 들여쓰기한소스코드 #include mbed.h 들여쓰기하지않은소스코드 #include mbed.h DigitalIn button(pa_14); DigitalIn button(pa_14); DigitalOUt myled(led1); DigitalOUt myled(led1); int main() { while (1) { if(!button) { myled = 1; wait(5); myled = 0; int main() { while (1) { if(!button) { myled = 1; wait(5); myled = 0; 그림 5.4 프로그램코드의들여쓰기 들여쓰기는소스코드에서괄호나중괄호를닫는것을잊지않도록하는데도도움 이된다. 경험많은프로그래머는종종먼저괄호를입력한다음에내용을채우곤 한다. 그들은다음과같이시작할것이다. if (!button) { 그런다음, 이를아래와같이코드를확장해간다. if (!button) { myled = 1; wait(5); myled = 0; 91
이후의실습에서는프로젝트생성, 프로그램편집그리고프로그램의컴파일및 다운로드과정에대해서상세한설명없이진행하도록한다. 그래서향후실습에는 새로이생성되는프로젝트폴더와소스코드에집중하여설명하도록한다. 5.2 Toggle 스위치 이절에서는푸시버튼스위치를이용하여 LED를번갈아가면서 on/off 되게하는실습을진행한다. 기술적으로는이를 bistable multivibrator 라부른다. 그러나토글 (toggle) 또는 two way 스위치로더잘알려져있다. 버튼을누를때마다이를표시하도록 LED의상태를반대로변경하는것이다. 즉, ON 상태라면이를 OFF로만들고, OFF 상태라면이를다시 ON으로상태를변환시킨다. LED 상태를토클링하기위해서는출력핀을현재와는다른상태로바꾸어야한다. 이러한동작을위해논리 NOT 연산자 (logical NOT operator) 를이용하면 true(high 혹은 1) 를 false(low 혹은 0) 으로혹은그반대로상태를바꾸게된다. 논리연산자가어떻게동작하는지설명하기위해서종종진리표 (true table) 가사용된다. 표 5.1 NOT 논리연산자의진리표논리연산자입력출력 =! 입력 true false NOT false true NOT 연산자는느낌표 (!) 문자로표기하고 not 으로읽는다. 그래서 output =! Input 은 출력은입력의반대이다 (the output is not the input) 라고읽는다. 만약 LED의상태를반대로바꾸고싶으면, 즉 LED가 on이면 off 시키고, off이면 on 시키는동작을조건문 if-else 를사용하면다음과같이작성할수있다. if (myled == 1) { myled = 0; else { myled = 1; 위의동작을 NOT 연산자를이용하여표현하면더욱간단히나타낼수있다. 92
myled =!myled; 이를이용해서프로그램을완성하면다음과같다. #include mbed.h DigitalIn button(pa_14); DigitalOut myled(pa_13); // the first SW // Green LED int main() { while (1) { if (!button) { myled =! myled; 프로그램을실행하기위해 toggle_switch 프로젝트를생성하고 main.cpp 파일의내용을위의프로그램으로수정한다. 프로그램을컴파일하고다운로드하여실행해보면원하는대로동작은하지만빈번하게오동작이발생하는것을확인할수있다. 즉, LED가깜박이거나원래상태로돌아가버리는현상이발생한다. 지금사용하는스위치는기계적장치이다. 스위치를누르면내부접점이만난다음마지막에있던위치로미끄러져간다. 이렇게미끄러져가는동안접점이맞닿아있을수있고때로는떨어져있을수있다. 사람은이러한동작에대해어떤것도알아채지못하겠지만그림 5.5와같이 Nucleo STM32F401RE 보드에서의스위치입력값은 0과 1을왔다갔다하면서안정되게되는데, 그사이에 LED를빠르게깜박이게되고, 사용자가매우빠르게스위치를연속해서누르는것과같은현상이발생한다. 이와같은현상을스위치의 bouncing 현상이라고하며, 스위치장치를다룰때에반드시고려해야할항목이다. 스위치의 bouncing 현상에따른오동작은마이크로컨트롤러가스위치로부터새로운입력값이읽었을때에다시스위치의입력값을읽기전에매우짧은시간 ( 바운싱시간 ) 동안지연하게만들면쉽게해결할수있다. 이러한동작을스위치의 debouncing 동작이라고하며대기하는시간을 debounce 시간 이라고한다. 스위치의품질에따라 0.1초에서 0.5초면충분하다. 이번실습에서는 debounce 시간을 0.3초로선택한다. 93
그림 5.5 버튼바운싱 (Bouncing) 현상 이제스위치입력을통해 LED 를토클링하기위한프로그램을구현하기위해필 요한부분을모두살펴보았다. LED 토클링프로그램은다음과같다. #include mbed.h DigitalIn button(pa_14); DigitalOut myled(pa_13); // the first SW // Green LED int main() { while (1) { if(!button) { myled =! myled; while (!button) wait(0.3); 위의프로그램에서 myled =!myled; 문장과관련하여하나의위험 (risk) 이존재한다. 올바른동작을위해서마이크로컨트롤러는먼저출력핀의실제상태가어떤지확인하고핀의출력상태를바꾸는것이바람직하다. 핀에콘덴서 (capacitor) 가연결되어있어상태가 HIGH로변하기전에먼저전하를축전 (charging) 하는출력핀을 HIGH로출력한다고가정해보자. 그리고충전이완료되기전에그핀의상태를바꾸고마이크로컨트롤러가핀의상태를검사한다고가정하면이핀은계속해서 LOW 상태가된다. 그래서마이크로컨트롤러는다시핀을 HIGH로바꾸게된다. 만약콘덴서 (capacitor) 의부하 (load) 가매우크다면마이크로컨트롤러에게는결코 HIGH로보이지않을것이다. 핀의상태를바꾸는것을빠르게할수록, 혹은부하를더크게할수록점점문제가발생할가능성이높아진다. 현재프로젝트에서는핀에연결한것은 LED가전부 94
다. 그래서여유를갖고상태를바꿀수있으며, 문제가되지않는다. 만약속도가 높거나부하가너무높다면, 예를들어코일이나콘덴서 (capacitor) 를사용할때는다 음과같이이를중계해주는변수를사용하는것이좋다. flasg =!flag myled = flag; 이를위해서 flag 변수를정의할필요가있다. 다음실습프로젝트에서이것이어떻게사용될수있는지논의할것이다. 위의프로그램을일단실행하고첫번째스위치를눌러초록색 LED를켠다. 그런다음다시스위치를눌러초록색 LED를끈다. 만약이것이항상올바르게동작하지않는다면 debounce 시간을약간늘려보는것이좋다. 5.3 LED 반복점멸 이번실습프로젝트에서는버튼을누를때마다 0.1 초간격으로 LED 를세번점멸 하게만드는것이다. 이러한동작은아래프로그램과같이구현할수있다. #include mbed.h DigitalIn button(pa_14); DigitalOut myled(pa_13); // the first SW // Green LED int main() { while(1) { if (!button) { myled = 1; wait(0.1); myled = 0; wait(0.9); myled = 1; wait(0.1); myled = 0; wait(0.9); myled = 1; wait(0.1); 95
myled = 0; wait(0.9); 그렇지만 LED를 200번점멸한다고생각해보라. 위의구현방식으로는상당히곤란할것이다. 이때는카운터 (Counter) 를사용하는것이효과적이다. 카운터는 점멸하는수 와같이수를세는데사용되는변수 (variable) 이다. 변수의반대개념으로상수 (constant) 가있는데이것은값을변경할수없다. 변수는마이크로컨트롤러의메모리에저장된다. 변수에이름을지정할때에실제로일어나는동작은마이크로컨트롤러의특정메모리위치에이름을부여하는것이다. 프로그래머로서이같은특정위치가어디에있는지모르고알필요도없다. 왜냐하면우리는그곳에기억하기쉬운이름을부여하기때문이다. C 프로그래머는몇가지이유로인해카운터변수이름으로 i 라는문자를선호한다. 그래서여러분도카운터에대한이름으로이것을자주보게될것이다. 그래서변수 i를참조할때마다기본적으로는마이크로컨트롤러에게 i라는이름의메모리위치에무엇이있는지보고, 그곳에서찾은값을사용해 와같은지시를하게되는것이다. 이는서랍과매우유사하다. 서랍은메모리의특정위치가되고서랍에붙은라벨, 예를들어 양말 과같은이름은메모리위치의이름이된다. 그리고서랍에든것은그메모리위치의값이다 ( 예를들어, 양말 = 6). 하나의변수를사용하기전에컴파일러에게이변수에대해서얼마의공간이필요한지알려줄필요가있다 서랍의예에서서랍은얼마나커야하는지 변수가저장할값이커질수록우리는더많은공간을마련해야한다. 그러나정말로필요한것은필요이상으로많은공간을마련하지않도록하는것이다. 이러한동작을위해 C 언어에서는변수를선언할때에변수가가질수있는값의종류와할당공간의크기를알려주는 데이터타입 (Data Type) 을같이명시한다. 표 5.2는 C 언어에서사용할수있는데이터타입의일부를보여주고있다. 6자리정밀도 (precision) 는이들변수들이비록큰숫자값을저장할수있지만정확한것은단지 6자리라는의미이다. 비록여기서 10진수값을원하거나아주작은값을사용한다하더라도 float 혹은 double 로선택해야한다. 변수의범위는 wrap around 한다. 이는저장공간이한정된변수는저장할수있는값에대해두개의경계값, 최대값과최소값을가지게되는데, 한쪽경계값을벗어나게될경우다른쪽경계값을가지게된다는의미이다. 예를들어, 8-bit의 96
unsigned char 타입의변수 bound 를정의하면 bound 변수는 0~255 사이값을저장할수있다. 만약 bound 가 255라는값을가지고있고여기에여러분이 1를더한다고가정하면최대경계값을넘어서게된다. 그러면다른경계값에서계속되는값을가지게된다. 그래서 bound = 255 + 1 = 0 이된다. 같은방식으로 255 + 4 = 4 가된다. 표 5.2 C 언어의데이터타입데이터타입범위 (unsigned) char 0 ~ 255(8 bit,256) signed char -128 ~ 127 unsigned short 0 ~ 65,5359(2 byte), 부호가없는경우 (signed) short -32,768 ~ 32767 unsigned int 0 ~ 4,294,967,295(4 byte) (signed) int -2,147,483,648 ~ 2,147,483,647 Float +/-3.410 38 +6자리정밀도 (4 byte) Double +/-1.710 308 +15자리정밀도 (8 byte) 처음세가지타입은 unsigned 형태에서도이용할수있다. 이는음수값이허용되지않는다는의미이다. 그래서양의범위가더길어지게된다. Unsigned char의범위는 0에서 255 이다. 상위값이음수부호를무시하고정상적인범위에추가됨으로써계산될수있다. 그래서 128 + 127 = 255가되는것이다. 실습에서는카운트하는값이최대 3이기때문에아래와같이 char 데이터타입이적당할것이다. char i; 이제새로운 C 언어명령을소개한다. for (start; condition; action) { 반복실행할명령들. 이문장의의미는 start 상황에서시작하고 condition 이참인경우에만중괄호안 의명령들을실행하고그다음 action 을취하고, condition 이더이상참이아닐때까 지이러한동작을반복실행한다는것이다. 예를들어아래와같은경우, 97
for (i=0; i<2; i=i+1) { 반복실행할명령들. start 상황 (i=0) 에서시작해서 condition (i<2) 이참인지검사한다. 여기서는 i=0 이므 로 2 보다작아참이되고, 그래서중괄호안의명령들을수행하게된다. 그런다음 action 이취해지게된다. Action 은 i = i+1 이다. 그래서 i 는이제 1 이된다. 그리고조 건검사가이루어지고, 만약조건이여전히참이라면모든명령을반복실행한다음 다시 action 이행해진다. 1 은 2 보다작기때문에다시명령들을실행한다음다시 action 이행해져 i 가 2 가되고, 2 보다작지않으므로조건이거짓이되어 for 문장을 끝나고프로그램의다음명령실행을진행하게된다. < 연산자가생소한사람을위해주로사용되는비교연산자들에대한개요를 표 5.3 에서살펴본다. 표 5.3 C 언어의비교연산자 비교연산자! Not == equal to > larger than >= larger than or equal to < smaller than 설명 <= smaller than or equal to!= not equal to for (i=0; i<2; i=i+1) 을 i=0 에서시작하고, i 가 2 보다작은경우 loop 의끝에서 i 를 1 을증가시킨다 라고읽을수있다. 실습에서는 LED 를세번점멸하려고한다. 그래서아래와같이약간의수정이필요하다. for (i=0; i<3; i=i+1) { 반복실행할명령들. 더진행하기전에 for 명령이정말로세번실행되는지확인해보자. i=i+1 이 i++ 로줄여나타낸것을자주보게될것이다. 이같은축약표현식 (abbreviation) 은초보자에게는코드읽기를다소어렵게만들기도하지만많은사람들이이를사용하기때문에이를사용하기로한다. 이제완성된프로그램은다음과같다. 98
#include mbed.h DigitalIn button(pa_14); DigitalOut myled(pa_13); // the first SW // Green LED char i; int main() { while(1) { if(!button) { for(i=0; i<3; i++) { myled = 1; wait(0.1); myled = 0; wait(0.9); 위의프로그램을실행하기위해 loop_blink_led 프로젝트를생성하고, src 폴더의 main.cpp 파일을위의프로그램으로편집하다. 프로그램을컴파일하고 다운로드하여실행한다. 첫번째스위치를누르면초록색 LED는세번점멸하게될 것이다. 여기서는스위치입력에대해 debuncing 동작을구현하지않았는데, 이는다 시스위치입력을읽기전에 LED를반복점멸하는동작이지연동작으로작동하기 때문에별도 debuncing 동작이필요가없기때문이다. 표 5.4은본교재에서자주사용하는축약표현식일부를보여준다. 표 5.4 C 언어에서의축약표현식사례 축약 (abbreviation) 의미 설명 i++ i = i+1 i를 1만큼증가 i-- i = i-1 i를 1만큼감소 i+=5 i = i+5 i에 5를더함 i-=5 i = i-5 i에 5를뺀다 i*=5 i = i*5 i에 5를곱함 i/=5 i = i/5 i를 5로나눔 99
5.4 타이머를이용한 LED 점멸 이번실습에서는시간측정을위해타이머를사용할것이다. 카운터와달리타이머는보통의변수가아니다. 기본적으로스톱-와치처럼시작하고, 멈추고, 읽어볼수있는내장된스톱-와치이다. MBED 라이브러리에서는타이머수를제한없이사용할수있다. 각각의타이머는변수와마찬가지로자신의이름을가질수있다. Timer t; 위의실행문은 t 라는이름을갖는타이머하나를선언하고있다. 이타이머를이용하여표 5.5와같은명령들을사용할수있다. 표 5.5 Timer 명령어명령 (Command) 설명 t.start() 타이머 t를시작한다. t.reset() 타이머 t를 0으로리셋한다. t.stop() 타이머 t를멈춘다. t.read_us() 타이머의시간을 us (microseconds) 로읽는다. t.read_ms() 타이머의시간을 ms(milliseconds) 로읽는다. t.read_s() 타이머의시간을 S (seconds) 로읽는다. 타이머를읽어저장하기위해서는 int형데이터타입의변수를사용할필요가있다. 만약타이머의이름을달리주었다면, 표 5.5의 t 를주어진타이머의이름으로바꾸어야한다. 예를들어타이머의이름으로 mytimer 로지정했다면 start 명령은 mytimer.start() 가된다. 이번실습의내용은사용자가버튼을누른시간을측정하기위해서타이머를이용하고, 측정된시간과동일한시간동안 LED를점멸하게하는것이다. 만약사용자가버튼을 4초간눌렀다면사용자가버튼에손을뗐을때 LED가 4초간점멸하게하는것이다. LED는버튼을누르고있는동안에는점멸하지않는다. 구현프로그램은이전과같지만이번에는 t 라고부를타이머를선언한다. 타이머타입선언은대문자 T를사용한다는것에주의한다. DigitalIn button(pa_14); DigitalOut myled(pa_13); Timer t; // the first SW // Green LED 100
다음단계는변수를선언하는것이다. 사용자가버튼을얼마동안눌렀는지기록하기위해변수 i를사용하고밀리초 (milliseconds, msec) 단위로이를기록한다. 변수 i를정수 (integer) 로정의하면저장할수있는최대값은 2,147,483,647 msec 이다. 이것은대략 36시간정도된다. 버튼을그렇게오래누르고싶은사람은드물것이므로이정도면안전한선택이될것이다. int i; 버튼을누른시간을재는것은실제로아주쉽다. 프로그램시작위치에서타이머를시작할것이고, 사용자가버튼을누르는시점에타이머를 0으로리셋 (reset) 한다. 그런다음버튼에서뗄때까지기다린다. 일단버튼에서손을떼면즉시타이머값을변수 i에기록한다. 이때에 debouncing 동작으로 0.3초의프로그램지연을적용한다. 이러한 debouncing 동작은사용자가버튼에손을뗀뒤에도타이머는얼마간계속실행한다는것을의미한다. 그래서이를수정할필요가있으며, 시간이 msec로측정되기때문에 300msec 를빼준다. if (button) { t.reset(); while(button) wait(0.3); i = t.read_ms() - 300; 다음에할일은타이머를 0으로다시초기화하는것이다. 이제타이머가 0에서시작하여 i에저장한값에도달할때까지 LED를점멸하도록만든다. LED 반복점멸동작은 while 반복문을이용하여구현하도록하며, 반복조건을검사하기위해타이머값을 t.read_ms() 를이용하여읽어검사하도록한다. t.reset(); while(t.read_ms()<i) { myled = 1; wait(0.5); myled = 0; wait(0.5); while(t.read_ms()==i) 와같이사용하는것도이론적으로는옳다. 그렇지만 LED 를점멸하는것은 1 초다 (LED on 0.5 초, off 가 0.5 초 ). 그래서프로그램이검사하는순 101
간정확하게 1이되는경우는거의발생하기힘들것이다. 그래서타이머가이시간을넘어설가능성이높다. 따라서 while(t.read_ms() < i) 을사용하면이런문제를피할수있다. 지금쯤여러분은약간의설명문 (Comment) 만추가해도프로그램을읽기쉽다는것을깨달았을것이다. // 로시작하는설명문은컴파일러에의해무시되므로이를이용해서설명문을추가할수있다. // 이것은설명문입니다. 만약한줄이상의설명문을추가하려면, 줄마다 // 로시작할수도있고, /* 으로설명문를시작하고 */ 으로설명문을끝맺을수있다. /* 이것은설명문입니다. 그리고이것역시이것도설명문입니다. */ 명령문다음에설명문을추가할수도있다. int i; // 이변수는측정된시간을저장합니다. uvision IDE는설명문문장들을녹색으로표시하기때문에쉽게확인할수있다. 충분한설명문를추가하는것은좋은프로그래밍습관이다. 이렇게하면몇년이지난뒤에도그것이무엇을하는것인지쉽게이해할수있게된다. 전문적인분야에서도설명문을포함해야한다. 그래야다른프로젝트멤버들이그프로그램을이해할수있어유지관리가수월해진다. 코드를읽을때 C 언어에대해잘알고있다고가정하기때문에 C 언어명령을설명하는설명문는추가하지않는것이바람직하다. 다음은 main loop와일부설명문을가지는완성된프로그램이다. #include "mbed.h" DigitalIn button(pa_14); DigitalOut myled(pa_13); Timer t; // the first SW // Green LED 102
int i; // 이변수는측정된시간을저장한다. int main() { t.start(); while(1) { if(!button) { // 버튼이눌러졌고, 시간을기록한다. t.reset(); while(!button) wait(0.3); i = t.read_ms() - 300; // 버튼이해제되었고, LED 를점멸한다. t.reset(); while (t.read_ms() < i) { myled = 1; wait(0.5); myled = 0; wait(0.5); 위의프로그램을실행하기위해 timer_blink_led 프로젝트를새롭게생성하고, src 폴더의 main.cpp 파일을위의프로그램소스로편집한다. 프로그램을컴 파일하고다운로드하여실행시킨다. 첫번째스위치를 5 초정도누른다음에손을 떼고초록색 LED 가 5 초정도점멸하는지확인한다. 103
6. PC 와 USB 통신하기 USB 통신을거론하는것이초보자에게는일반적이지않지만, Nucleo STM32F401RE 보드에서는 USB 통신을너무나쉽게할수있고, 이를통한메시지출력을통해프로그램동작모니터링을수행할수있어먼저언급하고자한다. 이미 2장에서호스트 PC에 ST-Link 드라이버를설치해두었다. 이장에서는 USB 통신을위한터미널프로그램을설치한다. 설치된드라이버에대해서는다음에진행할에코 (Echo) 실습프로젝트에서논의할것이다. 6.1 터이널프로그램설치 Nucleo STM32F401RE 보드는 USB를통해호스트 PC와연결할수있다. 이같은연결을통해 PC에서는보드를저장장치로인식하고, 바이너리프로그램을저장하는데이용할수있었다. 또한같은연결을통해사용자와통신을하는데도이용할수있다. 이것은 ST-Link 드라이버설치를통해 Nucleo STM32F401RE 보드에서 PC로데이터를보낼수있다는의미이다. 드라이버설치가완료된상태라면그림 2.22와같이시리얼포트가 PC에추가된것을확인할수있다. 물론실체가아닌가상의시리얼포트이다. PC의모든프로그램에대해서이가상포트는실제하드웨어포트처럼보이지만사실은 Nucleo STM32F401RE 보드로직접연결된다. 이런이유로시리얼포트는 Nucleo STM32F401RE 가연결되어있을때만존재한다. PC의각시리얼포트는번호를가지며, 이가상포트역시마찬가지다. 불행히도이번호는사용하는 PC마다다를수있기때문에 Windows 의장치관리자를실행하여새로추가된포트번호가무엇인지확인하여기록해두어야한다. Vista 이전의모든 Windows 버전은 HyperTerm 이라부르는터미널프로그램을포함하고있었다. 터미널프로그램을사용하여 USB 연결을통해서 ST Nucleo- F401RE 보드와통신할것이다. 만약 HyperTerm 프로그램을포함하지않는 Windows 버전을사용한다면 TeraTerm 이나 putty 와같은무료터미널프로그램을사용할수있다. 본교재에서는 TeraTerm 프로그램을이용하여실습을진행할것이다. TeraTerm 프로그램은아래웹사이트에서다운로드할수있다. https://ko.osdn.net/projects/ttssh2/downloads/61280/teraterm-4.83.exe/ 104
TeraTerm 프로그램설치는다운로드받은설치파일을실행하고기본설정을이용하여설치를실행하면된다. 설치과정에서사용언어를 Korea 로선택하면한글인터페이스를이용할수있다. TeraTerm 프로그램을실행한다. TeraTerm 의 새연결 창이열리면통신방식으로 시리얼 라디오버튼을클릭하고포트목록에서 STMicroelectronics STLink Virtual Port 항목을선택한다음 확인 버튼을클릭한다. 만약새연결창이열리지않으면 메뉴 새로만들기 를선택하거나 Alt+N 단축키를누르면된다. 그림 6.1 TeraTerm 터미널프로그램의연결설정 정상적인시리얼통신을수행하기위해서는몇가지설정이필요하다. 설정 터미널 메뉴에서 줄바꿈 - 수신 항목을 LF 로설정한다. 그림 6.2 TeraTerm 의터미널옵션설정 설정 시리얼포트 메뉴에서통신속도등을그림 6.3 과같이설정한다. 여기 서주의할점은 포트 는자신의실습환경에맞게선택하는것이다. 105
그림 6.3 TeraTerm 의시리얼포트옵션설정 6.2 에코 (Echo) 실습 터미널프로그램의설치와설정이끝났으면사용자의키보드입력을터미널화면에그대로출력하는에코프로그램에대해실습한다. 이프로그램은 PC의터미널프로그램이사용자가입력한키보드입력을 Nucleo 보드로전송하면 Nucleo 보드는받은데이터를그대로 PC로전송하여터미널프로그램에출력하는프로그램이다. MBED 프로젝트템플릿을이용하여새로운프로젝트 echo_test 를생성하고, src 폴더의 main.cpp 파일의프로그램을다음과같이편집한다. #include mbed.h Serial pc(usbtx, USBRX); int main() { pc.printf( Echoes anything you type\n\r ); while (1) { pc.putc(pc.getc()); // 문자를읽고화면에출력한다. 106
먼저 TeraTerm 터미널프로그램을실행한다음, 위의프로그램을컴파일하고다운로드하여실행한다. 이실습에서는 Nucleo STM32F401RE 보드만있으면되며, 다른장치는사용하지않는다. 메시지 Echoes anything you type 이터미널화면에표시될것이다. 만약 PC 의키보드에서임의의키를누르면 USB를통해 ST Nucleo-F401RE 보드로전달되고, 이것이다시 PC로출력되어터미널화면에그대로표시하게될것이다. 그림 6.4 에코실습실행화면이프로그램은어떻게동작하는가? 위에서구현한프로그램소스에대해자세히알아본다. 다음문장은 Nucleo STM32F401RE 보드에통신을위한시리얼포트를만들고이포트를 pc 라는이름으로지칭한것이다. Serial pc(usbtx, USBRX); 표 6.1 은위에서사용한 Serial 데이터타입에서사용할수있는명령어를나 타낸것이다. 먼저짧은텍스트를화면으로출력하여사용자에게무엇을하고있는 지알려주도록하였다. pc.printf( Echoes anything you type\n\r ); 위의문장에서마지막 \n 과 \r 은화면의커서를제어하는명령으로서하 나의문자로표현된다. 이들을이용해서커서를다른위치로이동할수있다. 107
표 6.1 Serial 타입의명령어 명령 설명 pc.putc() 문자를 write 한다. pc.getc() 문자를 read 한다. pc.printf() 형식화된문자열을 write 한다. pc.scanf() 형식화된문자열을 read 한다. pc.readable() read 할것이있는지확인한다. pc.writeable() write가가능한지확인한다. pc.baud() 해당연결에대해서 baudrate를정한다. pc.format() 기타통신속성을정한다. 표 6.2 커서제어문자 커서제어문자 설명 \b 백스페이스로한칸뒤로간다 \n 커서를한줄아래로옮긴다. \r carriage return( 새줄의시작위치로이동 ) \t 한탭만큼이동 \n 문자는커서를한줄아래로옮긴다. \n 을사용하지않으면실행화면 에서는출력내용이한줄로이어져서표시된다. \r 문자는커서를새줄의시작 위치로옮긴다. 따라서 \r\n 문자는커서를다음줄의시작위치로옮기게된다. 원래이들명령들은프린터제어문자로사용하던것이다. 초기에컴퓨터는화면 출력장치를갖지않아프린터만을출력장치로사용하였는데, 화면출력장치가개발되 었을때에프린터제어문자를그대로화면제어문자로적용한것이다. 그러므로 carriage return 명령은프린터헤더의 carriage 를시작위치로되돌아간다는의미 이고, 오늘날에는줄의시작에있다는의미로사용된다. 여기서는단순히 커서를줄 의시작위치에옮겨라 는의미이다. 백슬래시 \ 문자는특수제어문자로화면에출력되지않는데, 만약 \ 문 자를화면에출력하려면 \\ 와같이 2 개를연속사용하는것이다. 첫번째는특별 한의미의문자이고두번째가출력될것이다. 이제사용자의입력을패치 (fetch) 하여출력하는것이다. getc() 함수를이용해서 문자를읽을수있고 putc() 함수를이용해서이를출력할수있다. while(1) { i = pc.getc(); // 문자열을입력받아변수 i 에대입한다. pc.putc(i); // i 을 write 혹은출력한다. 108
while(1) 에서 1은참 (true) 을나타내고 while문내에포함된모든문장을무한반복으로실행한다. 여기서원하는것은시리얼포트를통해수신한것을출력하는것이기때문에두개의명령을묶어서변수 i를선언하는부분을아래와같이생략할수있다. while(1) { pc.putc(pc.getc()); 만약 Nucleo STM32F401RE 보드의초기화 (reset) 버튼을다시누르면프로그램이 재시작되고 Echoes anything you type 메시지를 PC 로다시보내게된다. 6.3 시리얼카운터 (Serial Counter) 출력 이절에서는카운터를터미널프로그램으로표시하는프로그램을구현한다. 일견 쉬운것같아보인다. 예를들어, 아래와코드를반복실행하면될것이다. pc.putc(i); i++ 만약이것을실행해보면일정시점동안에아무일도일어나지않는부분이있고, 나머지는이상한문자들이출력되는것을보게될것이다. 그림 6.5 카운트출력실습실행화면 (1) 이렇게동작하는이유는시리얼통신이문자와숫자에대해서코딩시스템을사용하기때문이다. 이코딩시스템을 ASCII 라부른다. 만약 Nucleo STM32F401RE 보드가 0을보내게되면터미널프로그램에서는이를 ASCII 코드로취급해서 ASCII 코드 0을가지는문자를출력한다. 처음 31개의 ASCII 코드는제어코드이고화면에 109
출력되지않기때문에아무일도일어나지않는다. ASCII 코드 32는빈칸 (space) 이며, 33은느낌표부호이다. 자세한내용은아스키코드표 (ASCII Character Set) 를참조하기바란다. 숫자와형식을갖춘텍스트를출력하는가장쉬운방법은 printf 함수를사용하는것이다. 앞선실습에서이미 printf 함수를이용하여텍스트를출력하고커서위치를제어하기위해서어떻게사용되는지보았다. 숫자를출력하는것은아래와같이쉽게할수있다. pc.printf("counter %d", i); 이문장은 Counter 단어와공백그리고뒤이어 %d를출력한다. 여기서 %d는실제출력되는것이아니라출력형식문자이다. 단순히 이위치에정수를출력한다 라는의미를나타낸것이다. 그것이어떤정수인지는인용부호외부에표시되는데, 이경우는 i 가된다. 그래서만약 i의값이 55라면아래와같이출력된다. Counter 55 이것을형식지정자 (format specifier) 라부른다. 또한 %d 를텍스트사이에삽 입할수도있다. pc.printf("counter %d units", i); pc.printf 명령은 안에있는문자열을출력한다. 예를들면위명령은다음과 같이출력된다. Counter 55 units 여기서꽤괜찮은트릭한가지를소개한다. 만약우리가 \r 로시작하는텍스트를가지면각텍스트들이서로를겹쳐쓰여질것이다. 왜냐하면 \r 은라인의처음에서출력을시작하도록만들기때문이다. 이를위의코드에적용하면뒤에오는숫자만변하면서 Counter 단어가같은위치에머물도록할수있다. 그러나카운터가 0으로초기화하는등으로앞에출력한값보다작으면출력공간을더작게차지하여이전숫자의일부가표시되게만든다. 예를들어 100이라는숫자다음에 5 를출력하면 500을보게될것이다. 이것은정확한값이출력된것이아니다. 그래서 110
%d 다음에몇개의공백을추가하면공백이화면에남아있는숫자를지우게될것이다. 시리얼카운터출력을실습하기위해 MBED 템플릿프로젝트를이용하여새로운프로젝트 output_serial_count 을생성하고, src 폴더의 main.cpp 파일을열어다음의프로그램소스로편집한다. #include "mbed.h" Serial pc(usbtx, USBRX); int i = 0;// 정수형변수 i를선언하고 i=0로초기화한다. int main() { while (1) { pc.printf("\rcounter %d i++; wait(1.0); ", i); 먼저터미널프로그램을실행한상태에서프로그램을컴파일하고다운로드하여 실행한다. 터미널프로그램은다음그림과같이 1 초에한번씩출력할것이다. 그림 6.6 카운트출력실습실행화면 (2) 여기서한가지유의할점은 printf 명령에서형식지정자로 %d 를사용했지만이는정수를출력하기위한것이다. 표 6.3 printf 함수에서사용하는형식지정자지정자설명 %d 정수 (int) %f 실수 (float)/ 소수점 6자리까지출력 %c 한개의문자 (character) %s 문자열 (string) %u 부호없는정수 (unsigned int) 111
컴퓨터에사용하는데이터에는정수도있고실수, 문자, 문장열등도있다. 표 6.3 은사용할수있는다른형식지정자들을보여준다. 만약숫자나다른데이터타입 을출력하고싶다면정확한형식지정자를사용하여야한다. 앞에서커서를제어하는몇가지사례를보았다. 여기에몇가지새로운제어문자 를추가할것이다. 터미널프로그램을설치하여설정할때에터미널에뮬레이션모 드를 VT100 모드로설정하였다. 표 6.4는이전에논의했던것과함께새로운커서 제어문자들을보여준다. 표 6.4 VT100 모드에서의커서제어문자 커서제어문자 설명 에뮬레이션 \b 백스페이스 \n 행바꿈 \r carriage return ( 라인의시작위치로 ) \t 한탭이동 \x1b\x48 커서홈 ( 화면좌측상단 ) VT100 \x1b\x4a 커서위치부터화면클리어 VT100 \x1b\x59\row\column row와 column 위치로커서이동 VT100 첫번째 VT100 명령은커서홈 (Cursor Home) 인 \x1b\x48 이다. x1b 는 x 로시작함으로 16진수 (Hex) 로된숫자이다. 인간은 10진수시스템을사용한다. Dec는 10을의미한다. 이는십진수 (decimal) 시스템이 0부터 9까지 10개의숫자를갖기때문이다. Hex는 hexadecimal 시스템의약어로서 16을의미한다. 그래서 16진수 (hexadecimal) 은 16개의숫자혹은심볼을가진다. 16진수에서는 0부터 9까지는 10진수와동일하게숫자로표시하고 10에서 15까지의숫자에대해서는 A부터 F까지문자로표시한다. 즉, A는십진수 10을, F는십진수 15를뜻한다. 그래서 Hex로숫자를표시하면아래와같다. 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F Hex로숫자를세거나계산하는것이전혀복잡하지않다. 단순히 Hex가 0부터 F 까지 16개의숫자를뜻한다고기억하자. 그냥 12는 10진수 12을의미하고, x12는 Hex 숫자 12, 즉 10진수로 18을의미한다. Windows 의계산기프로그램을이용하여 Hex를 decimal 로또는그반대로숫자를변환할수있다. Windows 의계산기를실행하고보기메뉴에서프로그래머용으로선택한다. Dec 라디오버튼을클릭하고 18을입력한다. 그런다음 Hex 라디오버튼을클릭한다. 이제입력한숫자는자동으로 Hex 값으로변환되고 12를보여주게될것 112
이다. Hex 라디오버튼을선택하면 5 개의문자키가활성화되는데, 이는 Hex 에서 유효한숫자이기때문이다. 화면을지우고 1B 를입력한다. 그런다음 Dec 라디오버 튼을클릭한다. 1B 는즉시 10 진수로변환되고 27 을보여주게된다. 그림 6.7 Windows 계산기프로그램화면 VT100 명령에서 Hex 숫자는 x로시작한다. 만약 \x1b\x48 (cursor home) 명령다음에 \x1b\x4a 명령을사용하면커서부터화면을지운다. 효과적으로커서를 Home으로보낸다음화면을지우게된다. 만약이명령으로프로그램을시작하면터미널화면을다지운다음에커서를 Home 위치, 즉화면의첫번째라인의제일앞으로보낼수있다. pc.printf( \x1b\x48\x1b\x4a ); 만약익숙하지는않겠지만다음과같이아날로그값을읽어서이를출력하는명 령을사용하는프로그램이있다고가정해보자. V = read() * 3.3 pc.printf( \n\n\rvoltage %f volts, i*3.3); 위의문장이의미하는것은두줄아래로가서줄의시작위치로이동한다음텍 스트와실수값을출력한다는것이다. %f 형식지정자는 float 가 6 자리정확도를 가진다는것을알기때문에이명령은소숫점이하 6 개의십진자리수를갖는측정 113
값을출력할것이다. 예를들어, 0.253618 과같다. 여기에는잘못된것은없지만측정값에는존재하지않는정확도까지출력하고있어정보가잘못전달될수있다. 만약소숫점이하 6 자리가아닌 2 자리수를가졌다면 ( 소수점앞에하나, 그뒤로 2개 ) 좀더정확하게보일것이다. % 문자와 f 문자사이에필드의최소길이와점, 그리고 10진소수점뒤의자리수를입력할수있다. 그림 6.8은이러한표현을알기쉽게표현하고있다. 그림 6.8 소숫점자리수지정형식지정자그림 6.8에서 % 뒤에있는 6은 10진소수점이전의자리수를나타내는것이아니라십진소수점을포함하여전체출력필드에대한최소길이를나타낸다. 그래서 %4.2f 는 0.23처럼출력될것이다. %4.2f 를 float 값을하나의소수점과 2개의 10진값을갖는최소 4 자리값으로출력한다 라고읽을수있다. 10진소수점과 2개의 10진값은 3개의자리를차지한다. 필드길이를 4라고지정했기때문에 10진소수점앞에하나의자리를남겨둔다. pc.printf( \n\n\rvoltage %f volts, i*3.3); 첫번째숫자는 10진소수점앞의자리수가아니라필드의전체길이임을기억하자. 만약실수로필드길이를너무작게하면어떻게될까? 아무일도없다. 최소길이이기때문에프로그램은여전히전체수를출력할것이다. 만약필드길이가너무길면, 숫자는앞에공백을가지고출력될것이다. 이는매우편리한처리방식이다. 왜냐하면열에맞춰서깔끔하게다양한길이의숫자를출력할수있도록해준다. 다음은자리수지정형식지정자의사례를들고있다. Float 1234.567891 Printed using %4.2f 1234.56 Printed using %7.2f 1234.56 Printed using %9.2f 1234.56 Printed using %7.4f 1234.5678 114
7. 디지털입력센서 이번장에서는디지털신호를입력하는센서사례에대해살펴본다. 7.1 초음파거리센서 (Ultrasonic Range Finder) 초음파거리센서는대상에초음파를방사하고그반사파를검출하여거리나형상을알아내는장치이다. 일반적으로정밀도는그다지높지않은편이나비접촉으로계측할수있는이점이있어산업용로봇이나각종산업기계에이용되고있다. SRF-04 초음파거리센서는비교적저렴한초음파센서로서물체의거리를측정하는데음파 (sound wave) 를이용한다. 그림 7.1은 SRF-04 센서의동작방식을보여준다. 검출사이클은 Nucleo STM32F401RE 보드에서 펄스트리거라인 에 10usec (1/1,000,000 sec) 혹은그이상의펄스로전송함으로써시작된다. 이라인이 low로떨어지면바로초음파모듈이 40KHz 의 8개펄스를보낸다. 송신기와수신기를직접연결되는것을방지하기위해약간지연을준다음, 에코라인 을 high로설정된다. 이것은 Nucleo STM32F401RE 보드가시간측정을시작하는신호이다. 첫번째초음파의에코가수신되면 에코라인 을다시 low로전환한다. 이시점에 Nucleo STM32F401RE 보드는시간측정을멈추고, 에코의원인이된물체의거리는측정된시간에의해계산한다. 그림 7.1 SRF-04 초음파센서의거리측정원리초음파센서의동작과정을요약해보면아래와같다. 1 마이크로컨트롤러에서초음파센서모듈로트리거펄스신호를최소 10usec 동안보낸다. 115
2 트리거펄스신호를수신한초음파센서모듈은송신부에서초음파신호를내보내고, Echo 신호를 high로하여마이크로컨트롤러에게보내게된다. 3 Echo 신호를수신한마이크로컨트롤러는타이머를기동하여시간을측정하기시작한다. 4 초음파센서모듈의수신부에서반향된초음파신호를감지하면 Echo 신호를 low로만든다. 5 마이크로컨트롤러에서 Echo 신호가 low로바뀌면시간측정을중지한다. 마이크로컨트롤러에서는이렇게측정된시간을가지고거리를계산하게된다. 이런동작원리를참조하여초음파센서를이용한거리측정프로그램을구현하도록한다. 첫번째단계는적어도 10 usec 펄스를펄스트리거라인으로보낸다. 여기서는충분히긴시간이되도록 40 usec를택했다. 다음은이동작을구현한프로그램코드이다. // send trigger pulse trigger = 1; // 트리거펄스를 HIGH 로전송한다. wait(0.00004); // 0.00004 초를기다린다 trigger = 0; // 트리거펄스를 LOW 로전송한다. 에코라인은평소에는 low 이다. 이것이 high 가될때까지기다린다음, high 가되 면바로시간측정을시작해서다시 low 가될때까지진행한다. 이를위해타이머를 사용할것이다. 다음은시간을측정한동작을구현한프로그램코드이다. // wait for the echo line to go high // (!echo 의뜻은 echo 가발생한상태 ) while (!echo); // measure the length of the pulse t.reset(); // 타이머로시간측정시작 while (echo); i = t.read_us(); // echo 가 low 될때까지대기 // low 로된시간을 i 로저장한다 변수 i 는지금 usec 단위로에코펄스의길이를저장하기때문에거리가쉽게계 116
산될수있다. 실내온도에서공기를통해전달되는소리의속도는대략 342m/s 이 다. 11cm 거리에서에코는 617 usec 가측정되었다. 거리 = Vsound * t = 342 * 617 * 10 6 = 0.211 m 측정소리는물체까지부딪혀되돌아온것이기때문에물체까지의거리는측정된거리의반, 즉 10.6cm 가된다. 여기서계산과정을단순화시켜측정된 us의에코시간을 58(617/10.6 = 58) 로단순히나누기만하면거리를간단하게계산할수있다. 계산된거리는터미널에출력하여표시한다. // display result pc.printf("\x1b\x48"); // 커스홈으로이동 ( 화면좌측상단 ) pc.printf("n\n\rpulse length %6.0f us",i); i = i/58; pc.printf("n\n\rdistance %4.0f cm",i); 이제초음파센서를이용한거리측정을위해기본적인프로그램코드는구현하 였다. SRF-04 초음파센서는그림 7.2 의연결회로도와같이연결되어있으며, 이를 참조하여프로그램을완성한다. 그림 7.2 SRF-04 초음파센서의연결회로도프로그램실행을위해 MBED 템플릿프로젝트를이용하여새로운프로젝트 ultrasonic_range_finder 를생성하고, src 폴더의 main.cpp 파일를열어다음의프로그램소스로편집한다. 그리고프로그램을컴파일하여다운로드하여실행한다. 117
#include "mbed.h" //------------------------------------ // Terminal configuration // 9600 bauds, 8-bit data, no parity //------------------------------------ Serial pc(serial_tx, SERIAL_RX); DigitalOut Trig(D10); DigitalIn Echo(D7); Timer t; float i; int main() { t.start(); pc.printf("\x1b\x48\x1b\x4a"); pc.printf("---====[ Ultrsonic Range (SR04) ]===---"); while(1) { // 펄스를내보낸다 Trig = 1; wait(0.00004); Trig = 0; // 에코가 high 될때까지대기 while(!echo) ; // 펄스의길이를측정 t.reset(); while(echo); i = t.read_us(); // 결과를출력 pc.printf("\x1b\x48"); pc.printf("\n\n\rpulselength %6.0f us", i); i = i/58; pc.printf("\n\n\rdistance %4.0f cm", i); wait(2); 118
그림 7.3 은거리측정을수행하고있는실제동작을보여주고있다. 그림 7.3 SRGF-04 초음파센서를이용한거리측정실험화면 SRF-04 초음파센서는동작전압 5 V와 3.3 V에서모두동작하지만 3.3 V에서는최소측정거리가더길어져다소정밀도가떨어진다. 최대거리에서의차이는관측되지않았다. 표 7.1은실제측정값의데이터이니참고하길바란다. 표 7.1 SRF-04 초음파센서의실제측정데이터 전압 (volts) 최소거리 (cm) 3.3 7 5 1 119
8. PWM(Pulse Width Modulation) 앞장까지는마이크로컨트롤러의디지털신호입출력기능을활용하는사례와 MBED 라이브러리를이용하여디지털신호입출력동작을구현하는방법에대해살펴보았다. 이제부터는마이크로컨트롤러의아날로그신호입출력기능에대해살펴보는데, 이장에서는아날로그신호출력기능에대해살펴본다. 마이크로컨트롤러를이용하여어떤동작을수행하는액추에이터 (actuator) 장치를제어할때에 on/off 동작만을수행하는 LED와같은장치는디지털신호출력으로제어할수있지만회전속도제어와같이다양한상태값을가지는모터장치등은아날로그신호출력으로통해제어하여야한다. 다시말하면모터는 회전한다 / 안한다 라는두가지상태값으로동작하는것이아니라정지또는다양한속도로회전할수있도록제어가되어야한다. 이런동작은 High/Low 전압값만가지는디지털신호출력으로는제어할수없고 Low와 High 사이에다양한전압값을가지는아날로그신호출력을통해제어할수있다. 디지털시스템에서아날로그신호를출력하기위해서는상당히복잡한구성을가지는 DAC(Digital-to-Analog Converter) 회로가필요하다. 일반적으로경제적인이유등으로대부분의마이크로컨트롤러는 DAC 회로를내장하고있지않다. 대신에디지털신호출력을이용하여아날로그신호출력을에뮬레이션 (emulation) 하는 PWM 신호출력기능을지원한다. 디지털신호에서 High와 Low 구간을일정한주기로반복하여출력하는신호를펄스신호 (pulse signal) 이라고한다. 그림 8.1에보여주는각각의신호는펄스신호에해당한다. 그림 8.1 PWM 출력의출력펄스신호 120
PWM(Pulse Width Modulation) 은펄스신호의주기를일정하게유지하면서 High 구간인펄스의폭을변화시켜출력하는방식을말한다. 펄스신호에서펄스간의거리를주기 (period) 라하며, 주기에대해펄스의 High 구간의상대적인폭을 duty cycle 이라한다. 이는실제동작이수행되는부분 (duty, 의무 ), 즉전원이공급되는부분이기때문이다. 그림 8.1에서첫번째펄스신호의 duty cycle은 10% 인데, 이는주기에서전원이공급되는것이 10% 라는의미이다. 마지막펄스신호는 duty cycle이 100% 이므로전원이계속공급되는것을의미한다. 예를들어, Nucleo STM32F401RE 보드는 PWM 출력이가능한핀을여러개지원한다. PWM 출력핀에서 duty cycle을 100% 로설정하여신호를출력하면그핀에서 3.3V 전압의신호가출력되고, duty cycle을 50% 로설정하면 1.65V 전압의신호가, duty cycle을 10% 로설정하면 0.33V 전압의신호를출력하게된다. PWM 출력은 duty cycle를조정하여다양한전압의신호를출력함으로써아날로그신호출력기능을에뮬레이션하는것이다. 그리고 PWM의목적은손실을최소화하고효율을극대화하는것이다. PWM의시그널의 ON/OFF 의비율을조정해서최대한낭비를줄여효율을높이는것이다. 표 8.1은 MBED 라이브러리에서제공하는 PWM 출력관련명령들이다. 표 8.1 PwmOut 의명령어 PwmOut write read period 명령어 period_ms period_us pulsewidth pulsewidth_ms pulsewidth_us operator = operator float() 설명지정된핀으로연결된 PwmOut을생성 fraction (float) 로지정된출력 duty cycle을설정 fraction (float) 로측정된현재출력 duty cycle을반환초단위 (float) 로지정된 PWM 주기를설정밀리초단위 (int) 로지정된 PWM 주기를설정마이크로초단위 (int) 로지정된 PWM 주기를설정초단위 (float) 로지정된 PWM 펄스폭을설정밀리초단위 (int) 로지정된 PWM 펄스폭을설정마이크로초단위 (int) 로지정된 PWM 펄스폭을설정 write() 에대한연산자약어 read() 에대한연산자약어 표 8.1은 PwmOut 데이터타입과관련해서사용할수있는함수들을보여준다. 물론, 펄스폭 (duty cycle) 그리고주기는밀접하게관련되어있다. 일반적으로펄스폭또는주기중하나를설정한다. 그런다음펄스폭 (duty cycle) 을이용하여여러분이설정하지않았던변수들을제어한다. 121
8.1. DC 모터 이번실습에서는직류감속기어모터 (DC Reduced Gear Motor) 를구동시키는프로그램을구현할것이다. 실습보드에제공하는모터이외의임의의작은직류모터를사용할수있으며, 사용하는모터를구동하기위해서드라이버 IC를이용할수도있다. 어떤모터를선택하여사용하든실습보드의특성을고려해야한다. 다음중적어도하나의문제를가질수있다. 1 모터는 Nucleo STM32F401RE 보드가구동하는 3.3 volts 보다높은전압을필요로한다. 2 모터는 Nucleo STM32F401RE 보드가구동할수있는것보다높은전류를필요로한다. 3 모터의코일은마이크로컨트롤러를손상시킬수있는전압스파이크 (voltage spike) 의원인이된다. 예를들어, 실습보드에서사용하는모터는 12 volts의동작전압에서 350mA 의전류를사용한다. Nucleo STM32F401RE 보드의디지털출력핀은 3.3 volts와 40mA를제공한다. 따라서전압과전류에서직접제어할수없다. 그리고만약이부분을만족하더라도여전히주의해야할세번째문제가남아있다. 이러한문제를해결하는방법은모터드라이버 IC 칩을이용하여모터를제어하는것이다. 실습보드는 Toshiba 사의 TB6612FNG dual motor driver 칩을사용하여모터를제어하도록제작되어있다. 표 8.2 실습보드의모터드라이버특성 속성모터전류모터전압구동 (driver) Toshiba TB6612FNG 3.2A peak, 1.2A ave 2.5 ~ 13.5 volts dual (two) 표 8.2 에서제시된특성을고려할때에 TB6612FNG 드라이버는실습보드에서사 용하는 DC 모터를충분히지원할수있다. 그림 8.2 는실습보드에서 TB6612GNG 모 터드라이버와 DC 모터의연결에대한회로구성이다. 122
그림 8.2 TB6612FNG 모터드라이버의연결회로도 그림 8.3 채널별 IN1과 IN2 신호의연결구조그림 8.2과그림 8.4에서채널 A의 OUT1(1, 2번핀 ) 과 OUT2(5, 6번핀 ) 은채널 A 의 IN1(21 번핀 ) 과 IN2(22 번핀 ), 채널 A의 PWM(23 번핀 ) 그리고 STBY(19 번핀 ) 에의해제어된다. 마찬가지로채널 B의 OUT1와 OUT2는채널 B의 IN1, IN2, PWM 그리고 STBY 핀에의해제어된다. 그리고그림 8.3에제시된채널별로 IN1과 IN2 신호의연결구조를살펴보면마이크로컨트롤러의출력신호를반전하여 IN1 신호를입력하고, IN1 신호를반전하여 IN2 신호로입력하고있다. 즉 IN1 신호와 IN2 신호는항상반전되어입력된다. 이번실습에서는 DC 모터를순방향 ( 오른쪽회전 ) 정지 역방향 ( 왼쪽회전 ) 으로 123
구동하는것을테스트해볼것이다. 그림 8.4 TB6612FNG 모터드라이버의핀사양 그림 8.5 TB6612FNG 모터드라이버의입출력신호관계 그림 8.5 는모터드라이브의입력과출력신호에대한상관관계를나타내고있다. 이를참조하여모터제어논리를살펴보면 PWM 과 STBY 핀을 High 상태로두고 IN1 124
과 IN2 핀을각각 Low와 High로출력하면 OUT1 과 OUT2 핀출력이각각 Low와 High가되어모터가역방향으로회전하고, IN1과 IN2 핀을 High와 Low로출력하면모터는 OUT1과 OUT2 핀출력이각각 High와 Low가되어순방향으로회전한다. 그리고 PMW 핀에인가되는신호의전압값에따라회전속도가달라진다. PWM이 low이고 STBY가 high인상태에서 OUT1 과 OUT2 신호가 Low가되면 short brake mode로동작한다. 다시말해, 모터의회전방향과회전속도를제어하기위해모터드라이버의 IN1, IN2, PWM, STBY 핀을제어하여야한다. 그러나그림 8.2에서모터드라이버의 STBY 핀은 3.3V 전원에연결되어항상 High가입력되고, 그림 8.3에서 TTL 논리게이트를이용하여 IN1과 IN2은서로반전하도록구현되어있어 IN1과 PWM 핀만제어하면된다. 또한유의할점은순방향에서역방향혹은그반대의경우, 즉방향이바뀌는경우는항상모터를멈춤다음회전방향을바꾸도록코드를작성하도록하는것이다. 이제모터를구동하기위한프로그램코드를작성한다. 여기서는모터드라이버를제어하기위한기능만을모아별도의프로그램모듈로구현하는데클래스구문을이용할것이다. 클래스구문은우리가필요로하는기능을가진일종의구조체 (struct) 형태의함수들의모임이라고보면된다. 모터제어를대한파라미터는위에서살펴본것과같이회전방향과회전속도이다. 회전방향은모터드라이버의 IN1 핀에연결된 Nucleo STM32F401RE 보드의디지털출력핀의출력값을결정한다. 회전속도는모터드라이버의 PWM 핀에연결된 Nucleo STM32F401RE 보드의 PWM 출력핀에대한 duty cycle을결정할것이다. 이러한제어파라미터를고려하여모터의동작을제어하는함수를정의해보면, 1 forward(speed) - 모터를주어진 speed로정방향으로회전시킨다 2 backward(speed) - 모터를주어진 speed 로역방향으로회전시킨다 3 stop() - 모터회전을중지시킨다여기서, speed 파라미터는 0과 1 사이의값으로 PWM 출력의 duty cycle를지정하는것으로한다. 위의함수들을개별적으로구현할수있으나일련의데이터와그데이터를처리하는관련함수들을모아클래스구문으로정의할수있다. 클래스구문은클래스구성을정의하는헤더파일과클래스의기능을구현한소스파일로구성된다. 클래스구문을사용하면프로그램개발에서많은장점을취할수있으나, 여기서는단순히관련함수들을모아별도의모듈로구현하기위해클래스구문을사용하는것으로한다. 이번실습과정은프로그램모듈을추가하는등이전실습과정보다복잡하여단 125
계별로세부적으로살펴보도록한다. 첫째, motor_control 프로젝트생성하기 프로그램실습을위해 MBED 템플릿프로젝트를이용하여새로운프로젝트 motor_control 을생성한다. 이과정은앞의실습과동일하다. 둘째, 프로젝트에 include 코드그룹추가하기일반적으로프로그램개발과정에서상당히많은헤드파일과소스파일이생성되기때문에소스관리의편리를위해이들을별도의코드그룹으로나누어저장하여관리한다. MBED 템플릿프로젝트에서도이미소스파일을저장하기위한 src 그룹을생성하여사용하고있다. 그러면헤드파일을저장하기위해새로 include 그룹을추가하여사용하도록한다. 새로운코드그룹 include 을생성하려면그림 8.6과같이 uvision IDE의왼쪽 project 창의타겟 STM32F401 Flash 을선택하고마우스오른쪽버튼을클릭하여팝업메뉴를띄운다. 팝업메뉴의 Add Group... 메뉴를클릭하면새로운그룹이타겟에추가된다. 그리고그림 8.7과같이새로운그룹의이름을 include 로변경해주면된다. 그림 8.6 프로젝트에새로운코드그룹추가 그림 8.7 include 그룹추가 126
셋째, include 그룹에클래스헤드파일추가하고편집하기 include 그룹에 Motor 클래스의헤드파일을추가하도록하자. 그림 8.8과같이 project 창에서 include 그룹을선택하고마우스오른쪽버튼을클릭하여팝업메뉴를띄운다. 그림 8.8 include 그룹에항목추가하기팝업메뉴에서 Add new item to Group... 메뉴를클릭하면그림 8.9와같이새로운항목을추가하기위해대화창이나타난다. 대화창의상단부분에서추가하고자하는항목유형, 즉 Header File(.h) 항목을선택하고, 아래부분의 Name: 입력칸에헤드파일의이름 motordriver.h 을입력한다. 그리고 Add 버튼을클릭하여종료하면 motordriver.h 헤드파일의편집창이열린다. 그림 8.9 motordriver.h 헤드파일항목추가하기 127
motordriver.h 헤드파일의편집창에서다음의 Motor 클래스정의코드를 편집하도록한다. #ifndef #define MBED_MOTOR_H MBED_MOTOR_H #include mbed.h class Motor { public: Motor(PinName pwm, PinName dir); // 생성자함수 void forward(double speed); void backward(double speed); void stop(void); protected: PwmOut _pwm; // PWM 출력핀 DigitalOut _dir; // 모터회전방향제어핀 int sign; // 모터의현재상태. 이를이용하여순방향에서역방향으로바로방향을바꾸는것을방지한다. ; #endif 위의코드에서생성자함수정의 Motor(PinName pwm, PinName dir); 에서파라 미터 pwm 은속도제어를위한 PWM 출력핀을, dir 은회전방향을제어하기위한 디지털출력핀을의미한다. 넷째, 헤드파일검색경로추가하기일반적으로헤드파일을별도의폴더등에저장하여관리하는경우에컴파일러가이들헤드파일을찾아사용할수있도록헤드파일검색경로 (Include Path) 에등록해주어야한다. 우리는프로젝트에새로운그룹을추가하여헤드파일을저장하였기에 uvision IDE가자동으로헤드파일을검색해주어별다른설정이필요없다. 그러나일반적인경우를감안하여프로젝트에헤드파일검색경로를추가하는방법을살펴본다. 소스파일별로소스파일옵션설정을이용하여헤드파일검색경로를개별적으로설정할수있으나, 여기서는모든소스파일에동일하게적용되도록프로젝트타겟설정에서헤드파일검색경로를설정하도록한다. 그림 8.10과같이툴바에서 Options for Target... 버튼을클릭하여타겟옵션설정대화창을띄운다. 128
그림 8.10 타겟옵션설정실행 그림 8.11 과같이타겟옵션설정대화창에서 C/C++ 탭을선택하면아래부 분의 Include Paths 항목에서현재설정된헤드파일검색경로를보여준다. 그림 8.11 Include Paths 설정확인및추가하기여기에새로운경로를추가하려면가장오른쪽의 Folder Setup 버튼을클릭하면그림 8.12와같이 Folder Setup 대화창이나타나고현재설정된헤드파일검색경로를확인할수있다. 상단에 New 버튼을클릭하면그림 8.13과같이새로운경로를입력하는칸이추가되고, 여기에새로운경로인 include 그룹을설정해주면된다. 그림 8.12 Include Path 설정대화창 129
그림 8.13 Include Paths 에새로운경로추가하기 다섯째, src 그룹에클래스소스파일추가하고편집하기세번째과정에서코드그룹에새로운항목을추가하는과정을살펴보았다. 같은방법으로 src 그룹을선택하고마우스오른쪽버튼을클릭하여그림 8.14와같이팝업메뉴를띄운다. 그림 8.14 src 그룹에새로운항목추가하기팝업메뉴에서 Add new item to Group... 메뉴를클릭하면그림 8.15와같이새로운항목을추가하기위해대화창이나타난다. 대화창의상단부분에서추가하고자하는항목유형, 즉 C++ File(.cpp) 항목을선택하고, 아래부분의 Name: 입력칸에소스파일이름 motordriver.cpp 을입력한다. 그리고 Add 버튼을클릭하여종료하면 motordriver.cpp 소스파일의편집창이열린다. 여기서클래스소스파일은 C++ 언어형식으로작성하기때문에.cpp 파일형식을선택하도록하였다. 130
그림 8.15 motordriver.cpp 소스파일항목추가하기 motordriver.cpp 소스파일의편집창에서다음의 Motor 클래스구현소 스를편집한다. #include #include mbed.h motordriver.h Motor::Motor(PinName pwm, PinName dir): _pwm(pwm), _dir(dir) { // Set initial condition of PWM _pwm.period(0.001); _pwm = 0; // Initial condition of output enables _dir = 0; sign = 0; // i.e nothing. void Motor::forward(double speed) { float temp = 0; if (sign == -1) { _pwm = 0; wait (0.2); // 현재역방향회전중임 // 모터멈춤 131
_dir = 1; temp = abs(speed); _pwm = temp; sign = 1; void Motor::backward(double speed) { float temp = 0; if (sign == 1) { _pwm = 0; wait (0.2); _dir = 0; temp = abs(speed); _pwm = temp; sign = -1; // 현재정방향회전중임 // 모터멈춤 void Motor::stop(void) { _pwm = 0; sign = 0; 여섯째, main.cpp 소스파일편집하기이제앞에서구현한 Motor 클래스를활용하여모터를구동시키는프로그램을작성하도록한다. src 폴더의 main.cpp 파일을클릭하여편집창을열고다음소스코드를편집한다. #include #include mbed.h motordriver.h Motor A(D11, PC_8); // pwm, dir int main() { while (1) { // For speed test. for (double s= 0; s < 1.0 ; s += 0.1) { A.forward(s); 132
wait(1); A.stop(); wait(3); for (double s= 0; s < 1.0 ; s += 0.1) { A.backward(s); wait(1); 위의소스코드는실습보드에장착된 DC 모터를제어하는 Motor 클래스의객체 A 를정의하고이를이용하여정방향회전, 정지그리고역방향회전을반복하고있다. 그리고모터를회전시킬때에정지상태에서속도를 0.1씩증가시켜최대속도로회전하도록하였다. 일곱째, 프로그램실행하기프로그램편집이완료되면프로그램을컴파일하고다운로드하여실행시킨다. 그림 8.16에제시된 DC 모터가정방향회전, 정지그리고역방향회전을속도를바꾸어가면서반복수행하는지확인한다. 실습보드에장착된 DC 모터는하나뿐이다. 그러나모터드라이버는동시에두개의모터를제어할수있어실습보드는그림 8.16과같이외부모터를연결할수있는핀 (PWM: D12, IN1: PD_2) 을지원하고있어모터의연결커넥터케이블을꽂으면모터드라이버의다른채널에대한동작도확인할수있다. 즉, 모터를연결만하면두개를동시에제어가가능하고, 이를통해자동차의바퀴의조향장치등을모델링해볼수있을것이다. 그림 8.16 실습보드의외부 DC 모터연결핀 133
8.2 RGB LED 이절에서는 PWM을이용하여 RGB(Red, Green, Blue) Chip LED를제어하여다양한색을출력하는것을실습한다. RGB LED는한가지색을내는 LED와달리빨강, 초록, 파랑색을섞어서다양한색상을만들수있다. 그림 8.17 RGB LED의동작원리및동작사례 RGB LED는 3개의입력핀 R, G, B를가지는데, 이핀들에입력되는신호의전압값에따라각각출력되는색의강도가달라져다양한색상을만들수있는것이다. 그래서 RGB LED의세개의입력핀을마이크로컨트롤러의 PWM 출력핀들과연결하여제어하도록한다. 실습보드에서 RGB LED의연결구조는그림 8.18과같다. 그림 8.18 RGB LED의연결회로도 RGB LED의색상변화를확인하기위해 R, G, B 핀으로출력되는 PWM 신호에대해주기를 1msec 로하고, 각각의입력 duty cycle를주기의 1/100씩차례로증가또는감소시키면서색상변화를확인하도록한다. 이를실습하기위해 MBED 템플릿프로젝트를이용하여새로운프로젝트 134
rgb_led_control 을생성하고, src 폴더의 main.cpp 파일의편집창에서다 음소스코드를편집한다. #include "mbed.h" PwmOut r (A1); PwmOut g (PC_6); PwmOut b (A3); int main() { r.period(0.001); g.period(0.001); b.period(0.001); while(1) { for(double i = 0.0; i < 1.0 ; i += 0.001) { double p = 3 * i; g = 1.0 - ((p < 1.0)? 1.0 - p : (p > 2.0)? p - 2.0 : 0.0); r = 1.0 - ((p < 1.0)? p : (p < 2.0)? 1.0 : 2.0 - p); b = 1.0 - ((p < 1.0)? 0.0 : (p > 2.0)? p - 2.0 : 2.0 - p); wait (0.01); 위의프로그램은 R, G, B, 세가지색상에대해서 duty cycle 변화를주는시간 대를각각달리하여 RGB LED 의색상변화를관찰할수있게한다. 위의프로그램에 서다음의코드에대해자세히알아본다. g = 1.0 - ((p < 1.0)? 1.0 - p : (p > 2.0)? p - 2.0 : 0.0); 위의코드에서사용된연산자를삼항비교연산자라고한다. 기본적인구문은다음과같다. 조건? 실행문-1 : 실행문-2 삼항비교연산자의동작은조건이참이면실행문-1 을실행하고, 조건이거짓이면 135
실행문 -2 을실행한다. 조건문 if-else 와유사하며실행문이단순한경우에 if-else 조 건문대신에사용하면유용하다. 위의코드는삼항비교연산자안에또삼항비교연산자를사용하여다소복잡하 게보인다. 위코드의내용을 if-else 조건문으로풀어서살펴보면다음과같으며, 이 해하기가쉽다. if (p < 1.0) { g = 1.0 - (1.0 p); else if (p > 2.0) { g = 1.0 - (p 2.0); else { g = 1.0; 위의전체코드에서 for문을조건식에유의하여살펴보자. G(Green) 의주기는 0부터 0.001씩 100단계까지점점증가하다가같은시간만큼 1의값을유지한다. 그다음에 1부터 0.001씩 100단계까지감소한다. 이과정을계속되풀이할것이다. R(Red) 의주기는 1부터 0.001씩 100단계까지점점감소하다가같은시간만큼 0의값을유지한다음 0부터 1까지 0.001씩 100단계까지점점증가한다. 그리고이과정을반복한다. 마지막으로 B(Blue) 의주기는먼저 0.001씩 100단계까지 0을유지하다가 0부터 1까지 0.001씩 100단계를증가한다음 1부터 0까지 0.001씩 100단계를감소한다. 또한이과정을반복한다. 그림 8.19는이러한동작을그림으로나타낸것으로 R, G, B 신호변화를비교하여각각어떻게변하는지알수있다. 그림 8.19 RGB LED 의 R, G, B 신호변화비교 136
위프로그램을컴파일하고다운로드하여실행한다. R, G, B 신호의입력시간차이 로인해시간의흐름에따라 RGB LED 의색상변화를관찰할수있다. 그림 8.20 RGB LED 색상변화 RGB LED를응용하면웨어러블장치 ( 의복, 안경테, 등 ) 나무드등과같은다양한제품에적용할수있다. 예를들어, 자외선지수나조도등에따라장치의색을원하는색으로변하게할수도있다. 137
9. 소리 (Sound) 이장에서는몇가지소리와관련된실습들에대해서논의해보도록한다. 지시나경고의표시로사용될수있는간단한 beep음생성부터사운드센서를이용하여주변의소리를검출동작까지실습을수행한다. 이장에서실습하는주제는크게두가지이다. 첫째, 디지털신호출력을통해소리를재생한다. 둘째, 사운드센서로부터아날로그신호를입력받아처리한다. 여기서, 두번째주제는상당히중요하다. 마이크로컨트롤러가적용되는임베디드시스템에서사용하는대부분의센서들은아날로그신호를생성한다. 주변의환경요인을측정하여적정한동작을수행하기위해서는센서가출력하는아날로그신호를입력받아처리할수있어야한다. 이장에서는 ARM Cortex-M4 마이크로컨트롤러가지원하는아날로그신호입력기능과이를이용하기위한 MBED 라이브러리기능에대해자세히알아보도록한다. 9.1 Beep 음생성 우리가소리를들을수있는주파수범위 ( 가청주파수 ) 는일반적으로 20Hz 에서 20KHz 사이이다. 그림 9.1 가청주파수영역의음압 그림 9.1 에서주파수축위의라인들은인간의귀에같은크기로소리가들리는 것을표시해놓은것이다. 따라서 500Hz 에서 6,000Hz 사이에서가장잘들린다. 소리 138
를만드는가장간단한방법은스피커를전원에연결하여빠르게 on/off 하는것이다. High와 Low 부분의시간을기준으로계산되는주파수에해당하는소리가만들어진다. 이는하나의소리파형이양의부분과음의부분으로구성되기때문이다. 만약 High가 0.01초, Low가 0.01초라면소리주파수는아래와같으며, 사람이들을수있는파장의소리가생성된다. f=1/(0.01+0.01) = 50Hz 그림 9.2 KPI-G1410 Buzzer 장치및연결회로도그림 9.2는실습장비에장착되어있는 KPI-G1410 Sounder(Buzzer) 와연결회로도를보여주고있다. 회로도를통해알수있듯이부저를사용하기위해서는그림 9.2 의왼쪽그림에서부저왼쪽의스위치를아래로내려부저를핀에연결하여야한다. Beep음을생성하기위해부저장치에 0.01초간 High신호를출력한다음, 0.01초간 Low신호를출력하도록한다. #include "mbed.h" DigitalOut sound(pc_9); int main() { while (1) { sound=1; wait(0.001); sound=0; wait(0.001); 프로그램실행을위해새로운 beep_sound 프로젝트를생성하고, src 폴더 139
의 main.cpp 파일을위의프로그램소스로편집한다. 프로그램을컴파일하고다 운로드하여실행한다. 의미를알수없는 beep 음이출력되는지확인한다. 9.2 고주파호루라기 이번에는앞의예제를응용하여 16~20KHz의고주파호루라기를제작해본다. 젊은청년에비해나이든사람들은청력이감소하여고주파의소리를잘들을수없으므로이프로젝트를이용하여비밀초인종이나어른이들을수없는경고음을만들수있을것이다. 16KHz 의주파수를만들기위해 주기 = 1/ 주파수 이므로 1/16,000 = 6.25*10-5 의주기가필요하다. 소리파형은 High 펄스와 Low 펄스두개로나눠지기때문에각각의지연을위한 wait 문은주기의반이되어야한다. 즉, 3.13 * 10-5 이다. 여기서, 이와같이높은주파수의신호를발생시키는것은 Nucleo-F401RE 보드연산속도가일정부분영향을미친다. 즉, 마이크로컨트롤러의명령어처리시간이지연시간에더해져출력주파수가낮아지는원인이된다. 가장좋은해결책은계산된값에근접한지연을테스트하여어떤것이최적의결과를나타내는지확인하는것이다. 이를위해서스팩트럼분석기 (Spectrum Analyzer) 와같은장비를이용하면도움이된다. 그림 9.3 스팩트럼분석기 표 9.1 은계산된주파수와실제측정한주파수의값을보여주고있다. 표 9.1 소리주파수측정데이터 wait() 함수파라미터 계산한주파수 (KHz) 측정주파수 (KHz) 0.000035 14.3 13.1 0.000030 16.7 15.6 0.000025 20.0 18.4 0.000020 25.0 21.5 140
위데이터를그래프로살펴보면 ( 그림 9.4), 원하는주파수를대략 0.000028 의 wait 문으로만들수있음을알수있다. 같다. 그림 9.4 소리주파수측정데이터그래프 측정된지연시간을이용하여 16KHz 의고주파음을생성하는프로그램은다음과 #include "mbed.h" DigitalOut sound(pc_9); int main() { while (1) { sound=1; wait(0.000028); sound=0; wait(0.000028); 위의프로그램은타이밍에민감하기때문에프로그램이실행중인것을표시하기위해 LED를깜박이는것과같은작은기능을추가해도원하는타이밍을맞출수가없게된다. 이렇게직접시간지연을통해주기를맞추는것보다더나은방법은 PWM(Pulse Width Modulation) 을이용하는것이다. 앞장에서모터제어를위해 PWM 을사용하였다. PWM은기본적으로구형파 ( 사각파 ) 라서소리를만드는데도사용될수있다. 주기 = 1/ 주파수 이므로 16KHz 의주기는아래와같다. 주기 = 1/(16,000Hz) = 6.25*10-5 sec 또는 62.5 usec 141
우리는위의주기와 0.5 의 duty cycle 을설정하여대칭인구형파를만들것이다. #include "mbed.h PwmOut sound(pa_13); int main() { sound.period(0.0000625); sound = 0.5; 이프로그램의출력주파수를확인하기위해서스팩트럼분석기를사용해보면측정된주파수가 16.1KHz 이며, 이는충분히근접한값이다. 이와같은구현의장점은 PWM 출력이별도의하드웨어요소에의해수행됨으로프로그램내에서펄스신호생성에관해걱정할필요가없다는것이다. 간단히설정만으로마이크로컨트롤러가원하는주파수의펄스신호를생성해준다. 이는프로그램이실행중임을표시하기위해 LED를깜박이는프로그램을추가하는것이이제는쉬워졌다는의미이기도하다. 이를구현하면아래와같다. #include "mbed.h" PwmOut sound(pc_9); DigitalOut myled(pa_13); int main() { sound.period(0.0000625); sound = 0.5; myled = 0; while (1) { myled =!myled; wait(1); 프로그램실행을위해새로운프로젝트 high_freq_sound 를생성하고, src 폴더의 main.cpp 파일을위의프로그램소스로편집한다. 프로그램을컴파일하고다운로드하여실행해보면아마도주변사람들이날카로운고주파음으로인해귀를막고인상을찌푸릴수도있을것이다. 142
9.3 부저를이용한멜로디연주 피에조부저의소리는듣기에거북할수있지만주파수를적절히맞춰주면멜로 디를표현할수있다. 아래주파수에맞게부저에신호를주면그에맞는옥타브음 계가출력된다. 멜로디출력을위한실습을위해새로운프로젝트 melody_play 를생성한다. 그리고먼저위의그림을참조하여 3 에서 5 옥타브음계에대한주파수값을정의하 는헤드파일 pwm_tone.h 을다음과같이작성한다. #define Do3 131 //C #define Do3s 139 //C# #define Re3 147 //D #define Re3s 156//D# #define Mi3 165 //E #define Fa3 175 //F #define Fa3s 185 //F# #define So3 196 //G #define So3s 208 //G# #define La3 220 //A #define La3s 233 //A# #define Ti3 247 //B octave3 143
#define Do4 262 //C #define Do4s 277 //C# #define Re4 294 //D #define Re4s 311//D# #define Mi4 330 //E #define Fa4 349 //F #define Fa4s 370 //F# #define So4 392 //G #define So4s 415 //G# #define La4 440 //A #define La4s 466 //A# #define Ti4 494 //B #define Do5 523 //C #define Do5s 554 //C# #define Re5 587 //D #define Re5s 622//D# #define Mi5 659 //E #define Fa5 699 //F #define Fa5s 740 //F# #define So5 784 //G #define So5s 831 //G# #define La5 880 //A #define La5s 932 //A# #define Ti5 988 //B octave4 octave5 멜로디를출력하려면 PWM 신호의주기를멜로디에맞게변경해주어야한다. 주 기는음계주파수의역수가되므로아래와같이정의할수있으며, 이를 pwm_tome.h 헤드파일에추가한다. float C_3 = 1000000/Do3, Cs_3 = 1000000/Do3s, D_3 = 1000000/Re3, Ds_3 = 1000000/Re3s, E_3 = 1000000/Mi3, F_3 = 1000000/Fa3, Fs_3 = 1000000/Fa3s, G_3 = 1000000/So3, Gs_3 = 1000000/So3s, A_3 = 1000000/La3, 144
As_3 = 1000000/La3s, B_3 = 1000000/Ti3, C_4 = 1000000/Do4, Cs_4 = 1000000/Do4s, D_4 = 1000000/Re4, Ds_4 = 1000000/Re4s, E_4 = 1000000/Mi4, F_4 = 1000000/Fa4, Fs_4 = 1000000/Fa4s, G_4 = 1000000/So4, Gs_4 = 1000000/So4s, A_4 = 1000000/La4, As_4 = 1000000/La4s, B_4 = 1000000/Ti4, C_5 = 1000000/Do5, Cs_5 = 1000000/Do5s, D_5 = 1000000/Re5, Ds_5 = 1000000/Re5s, E_5 = 1000000/Mi5, F_5 = 1000000/Fa5, Fs_5 = 1000000/Fa5s, G_5 = 1000000/So5, Gs_5 = 1000000/So5s, A_5 = 1000000/La5, As_5 = 1000000/La5s, B_5 = 1000000/Ti5; 마찬가지로출력할멜로디데이터를위의 PWM 주기변수를이용하여아래와같 이정의할수있으며, 이를헤드파일에추가한다. int tones[] = {E_4, D_4, C_4, D_4, E_4, E_4, E_4, 1, D_4, D_4, D_4, B_5, E_4, G_4, G_4, 1, E_4, D_4, C_4, D_4, E_4, E_4, E_4, 1, D_4, D_4, E_4, D_4, C_4, 1, 1, 1; 끝으로, 위에서정의된멜로디데이터를이용하여부저에 PWM 신호를만들어입 력하면멜로디가출력될것이다. 이를위해 main.cpp 파일에아래의소스를편집 한다. 145
#include "mbed.h" #include "pwm_tone.h" PwmOut Buzzer(PC_9); int tones_num = 32; void Tune(PwmOut name, int period, int beat) { int delay; delay = beat*63; if (period == 1){ name.write(0); wait_ms(delay); else { name.period_us(period); name.write(0.50f); // 50% duty cycle wait_ms(delay); // 1 beat name.write(0); // Sound off void Auto_tunes(PwmOut name, int period, int beat) { int delay; delay = beat*63; name.period_us(period); name.write(0.50f); // 50% duty cycle wait_ms(delay); int main(void) { int i; 146
for(i=0; i<tones_num; i++) { Auto_tunes(Buzzer, tones[i], 4); // Auto performance Stop_tunes(Buzzer); 프로그램을컴파일하고다운로드하여실행해본다. 원하는멜로디를들을수있 는지를확인한다. 9.4 사운드센서 지금까지 Nucleo STM32F401RE 보드와부저를이용하여소리를생성하는실습을수행하였다. 이절에서는일렉트릿마이크로폰 (electret microphone) 을이용해서소리를검출하는간단한사운드센서응용프로젝트를실습한다. 사운드센서는소리의음향에너지 ( 파동 ) 를진동판떨림의진동에너지로, 그리고진동에너지를오디오전기신호로변환한다. 실습장비에서사운드센서로사용하는일렉트릿마이크로폰 ( 콘덴서마이크로폰 ) 은지름이 5~20mm 의얇은진동막에같은모양의고정전극을근소한간격으로마주보게하고콘덴서를형성하여음파에의한막의진동을정전용량의변화로전환시킨다. 그리고이러한콘덴서의축전과방전을통해아날로그오디오신호가출력된다. 그리고콘덴서에전압을걸어주기위한직류전원을필요로한다. 이러한마이크로폰은바이어스전원이필요없기때문에전치증폭기가간단해지면서저렴한가격에고성능을낼수있다. 소형화도가능하기때문에, 오늘날대부분의소형마이크로폰이이방식을사용하고있다. 예를들어거의모든휴대전화와컴퓨터, PDA 및헤드셋에주로이용되고있다. 마이크로폰은양의파형 (positive) 과음의파형 (negative) 둘다를만들게되는데음 (negative) 이뜻하는의미는 ground level 아래라는의미이다. 이런이유로전위차계 (Potentiometer) 를연결하여기준레벨 (default level) 로올려준다. 사운드센서를응용하여다양한장치들을만들수있다. 예를들어, 주변의소리나음악을전기적신호로변화하여화려한 LED 조명을제어하거나이퀄라이저등을구현할수도있다. 또는박수소리에부저를울리는 Clap and Buzz 와같이간단하고재미있는장치로분실한열쇠를찾는다든지리모콘을찾는데응용할수도있을 147
것이다. 그림 9.5 사운드센서를응용한작품과사운드센서모듈사운드센서의주요사양은표 9.2와같으며, 그리고실습보드에서는사운드센서가그림 9.6의회로도와같이연결되어있다. 표 9.2 사운드센서의주요사양속성값동작전압 3.5 10V 동작주파수 50 2000Hz 감도 -48 66 db 신호대잡음비 >58dB 출력신호범위아날로그신호 (0~ 입력전압 ) 그림 9.6 사운드센서의연결회로도이번실습에서는일정한크기이상의주변소리를감지하여경고 (alarm) 하기위해사운드센서가출력하는신호의크기가지정된임계값보다크면 LED를깜박이는동작을구현할것이다. 이동작구현을위해우선고려해야할사항은 사운드센서의아날로그신호를어떻게입력받아처리하는냐? 일것이다. 아날로그신호를마이크로컨트롤러에서처리하기위해서는디지털값으로변환되 148
어야한다. 이를위해서는 ADC(Analog-to-Digital Converter) 기능이필요한데, ARM Cortex-M4 마이크로컨트롤러는 12-bit 해상도를가진 ADC 기능을내장하고있으며, 아날로그신호를입력받을수있는입력핀을여러개지원하고있다. MBED 라이브러리에서는아날로그신호입력을위해 AnalogIn 기능을지원한다. 아날로그신호입력핀을 AnalogIn 타입으로선언하면입력핀을통해 0~3.3V 사이의입력신호전압을 12-bit 의정수값, 즉 0~4095 사이의정수값으로읽을수있다. 그리고이를 0.0~1.0 사이의정규화된실수값으로도읽을수있어상대적인크기를쉽게파악할수있다. ADC가 12-bit 의높은해상도를지원하여 ADC를통해읽은정수값의 1의크기는약 0.8mV 의전압크기를나타내며, 이는대단히낮은전압차이를측정할수있음을의미한다. AnalogIn 타입이지원하는명령 ( 함수 ) 에대한자세한내용을부록을참고하도록하자. 이제위에서제시한동작을수행하는프로그램을구현하도록한다. 그림 9.6에서사운드센서의아날로그출력은마이크로컨트롤러의아날로그신호핀 A5으로입력된다. 그래서 A5 핀을 AnalogIn 타입으로선언하고사운드센서에서출력되는아날로그신호의전압값을읽는다. 그리고이를사전에설정한임계값과비교하여소리의크기가특정수준보다높은지를판별한다. 만약임계값보다더높으면초록색 LED가 1초동안켜지고읽은값을터미널에출력한다. 이를구현프로그램소스는다음과같다. #include "mbed.h" Serial pc(usbtx, USBRX); AnalogIn loudness(a5); DigitalOut myled(pa_13); // Sound Sensor // Green LED double i; int main() { while(1) { i = loudness; if (i>0.80) { myled = 1; pc.printf("loudness: %f %\r\n", i*100); wait(1); else myled = 0; 149
프로그램실행을위해새로운프로젝트 sound_loudness 를생성하고, src 폴더의 main.cpp 파일을위의프로그램소스로편집한다. 먼저주변에소리가없도록하고사운드센서의 LED가꺼지는지점까지사운드센서의가변저항 (Potentiometer) 을조정하여기본전압값을설정한다. 터미널프로그램을먼저실행한후에위의프로그램을컴파일하고다운로드하여실행한다. 그리면임계값이상의소리를감지하면사운드센서를통해입력되는소리의크기를백분율로터미널프로그램의화면에표시하고초록색 LED가 1초가켜지는것을확인할수있을것이다. n 사운드센서의응용 ( 잃어버린물건찾기 ) 가끔집열쇠나 TV 리모콘을어디다둔지를몰라집안이곳저곳을찾아다닌경험이있을것이다. 이런경우를대비해서사운드센서를이용하여분실한열쇠나리모콘을쉽게찾아주는프로젝트를만들어보자. 앞에서배운사운드센서의동작원리와간단한프로그램을응용하면프로젝트를쉽게완성할수있다. 먼저주변에소리가없도록하고사운드센서의 LED가꺼지는지점까지사운드센서의가변저항 (Potentiometer) 을조정한다. 이제손뼉을치면 LED가 1초동안켜질것이다. 마이크로폰은증폭되지않기때문에민감도 (sensitivity) 가높지않지만손뼉소리를검출할만큼은된다. 같은원리로특정수준이상의박수소리를내면사운드센서가이를검출하여부저를울리게하는프로그램을작성하고자한다. 즉, 리모콘에사운드센서와부저가부착되어있다고상상해보면될것이다. 여기서는사운드센서의출력값의백분위가우리가설정한임계치 (80%) 를넘어설때부저를울리도록설정한다. 프로그램실행을위해앞의 sound_loudness 프로젝트를복사하여붙여넣기를하여새로운프로젝트 sound_loudness2 를생성한다. src 폴더의 main.cpp 파일을다음의프로그램소스로수정한다. #include "mbed.h" #define Threshold 80 // 백분위임계값 Serial pc(usbtx, USBRX); // UART Serial Comm. 150
DigitalOut myled (PA_13); PwmOut buzz(pc_9); AnalogIn loudness(a5); // Green LED // Buzzer // Sound sensor int main() { int loud_level; buzz.period(0.002); while (true) { loud_level = (int)(100*loudness.read()); if (loud_level > Threshold) { pc.printf("loudness: %d\r\n", loud_level); buzz = 0.5; myled = 1; wait(10); buzz = 0; myled = 0; 프로그램을컴파일하고다운로드하여실행한다. 박수소리등을내어소리크기가 임계치를넘어서면 10 초간부저를울리고초록색 LED 가켜진다. 지금까지 AnalogIn 으로정의한핀에서아날로그신호를입력받아그값을출 력하는프로그램예제를살펴보았다. 이제아날로그신호를디지털신호로변환하는 아날로그 - 디지털변환기 (ADC) 에대해간단히언급하고자한다. n 아날로그 - 디지털변환기 (ADC, Analog to Digital Converter) ADC는연속된아날로그신호를표본화 (Sampling), 양자화 (Quantization) 그리고부호화 (Binary Encoding) 를거쳐이진디지털신호로변환한다. 표본화는디지타이저 (Digitizer) 혹은샘플러 (Sampler) 라부르는계산로직에의해시간축방향으로일정간격마다연속신호를추출하여이산신호 (Discrete signal) 로변환한다. 양자화는샘플신호의진폭을특정대표값으로변환한다. 부호화는기계적신호처리가가능한이진코드형태로변환한다. 그림 9.7는 ADC의동작원리를그림으로나타낸것이다. 151
STM32F401RE 마이크로컨트롤러에내장된 ADC는 12-bit 아날로그-디지털변환기로서최대 16개의외부채널과공유되며, single-short 혹은 scan모드로변환을수행한다. Scan 모드에서는아날로그입력으로선택된그룹에대해자동변환이수행된다. ADC는 DMA Controller에의해서비스되기도한다. Analog watchdog 기능은하나, 일부, 혹은전체선택된채널들에대해매우정확하게변환된전압값들을모니터할수있다. 변환된전압값이프로그램되어있는임계치를벗어나면인터럽트가발생된다. ADC와타이머를동기화시키기위해서 TIM1~TIM5 타이머중임의의것에의해트리거될수있다. 그림 9.7 ADC 동작원리 152
10. 빛 (Light) 이장에서는빛과관련된주제로실습을수행한다. 먼저빛의세기를측정하는간 단한예제를수행하고빛의특성, 즉적외선및자외선을이용한실습들로논의를 확장해나갈것이다. 10.1 빛의세기측정 ( 조도센서 ) 조도센서는빛의양에따라내부저항이변하는가변저항처럼동작한다. 따라서일정한전압을인가하는상황에서센서의출력을보드의아날로그입력핀에연결하면전압값의변화를측정할수있다. 이값을이용하면주변환경의밝기를정확히측정할수있다. 이처럼조도센서는빛의세기를측정하며, 조도는단위면적당빛의양을의미하는 Lux 단위를사용하여나타낸다. 그림 10.1 조도센서연결구성과반응성빛의세기가커질수록저항성분은작아지며, 빛의세기가약해지면저항성분은커진다. 그러나이같은특성 ( 전도율 ) 이밝기에비례해서선형적으로증가하는것이아니기때문에정확한 Lux 값을구하기보다는밝고어두운정도만을판별하기에적합하다. 일반적으로조도센서의재료로는황화카드늄 (CdS) 이사용된다. 조도센서도일종의저항이지만빛의양이아주많으면저항이매우작아지기때문에과전류가흐를수있다. 따라서그림 10.1처럼 10KΩ의풀업저항을달아준다. 풀업 (Pull-Up) 저항사용시조도센서의저항값에따라전압분배가일어나며, 빛의 양이많을수록 ( 밝을수록 ) 측정되는전압의크기는작아집니다. 실습에사용할조도센서의주요사양은표 10.1 과같으며, 그림 10.2 와같이마 153
이크로컨트롤러와연결되어있다. 표 10.1 조도센서의주요사양 속성동작전압공급전류 Light Resistance Dark Resistance 3 ~ 5 V 0.5 ~ 3mA 20KΩ 1MΩ 값 그림 10.2 조도센서의외형및연결회로도조도센서에있는가변저항을조정하여기준레벨을정한다. 조도센서보드의녹색 LED가꺼지기전지점까지가변저항을조절한다. 그리고센서부분을손으로가리면녹색 LED가꺼지는것을볼수있을것이다. 조도센서를이용한프로그램예제는아래와같이간단하다. 아날로그입력핀으로들어오는값을읽은다음이값이임계값이상이면실습장치의초록색 LED를 on하고그렇지않은경우에는 off한다. 그리고임계값은실습환경의주변밝기에따라적절하게선택하도록한다. 프로그램실행을위해새로운프로젝트 brightness_sensor 을생성하고, src 폴더의 main.cpp 파일을아래의소스코드로편집한다. 프로그램을컴파일하고다운로드하여실행한다. #include "mbed.h" Serial pc(usbtx, USBRX); AnalogIn analog_value(a0); DigitalOut led(pa_13); int main() { 154
double meas; printf("\nanalogin example\n"); while(1) { meas = analog_value.read(); meas = meas * 1000; printf("measure = %.0f \r\n", meas); if (meas > 250) { // 임계값 led = 1; else { led = 0; wait(1); // 1000 ms 그림 10.3 조도센서테스트프로그램의실행화면동일한빛의세기에서도측정값이약간씩다를수있으며, 임계값부근에서는 LED on과 off를반복할수있는데이는 LED를켜는임계값과끄는임계값을각각두어서해결할수있다. 프로그램을다음과같이수정하여테스트한다. if (meas > 250) { led = 1; if (meas < 240) { led = 0; 155
위에서살펴본조도센서의예는아주간단하지만빛을사용하는분야와실생활 에서다양하게응용하여사용할수있다. 10.2 적외선개요 적외선 (infrared, IR) 은 0.75μm ~ 1,000μm 범위의파장을갖는전자파를의미하며파장범위가넓기때문에센서가반응하는특정파장대역으로세분화하여각기다른이름으로부르기도한다. 적외선을세분화하는표준화된방법은없으며적외선을이용하는분야에따라조금씩다른명칭을사용한다. 적외선검출기의응답에따라분류하면다음과같다. - 근적외선 (Near Infrared, NIR) : 0.70μm ~ 1.0μm - 단파적외선 (Short-wave Infrared, SWIR) : 1.0μm ~ 3.0μm - 중파적외선 (Mid-wave Infrared, MWIR) : 3.0μm ~ 5.0μm - 장파적외선 (Long-wave Infrared, LWIR) : 8μm ~ 12μm - 초장파적외선 (Very-Long wave Infrared, VLWIR) : 12μm ~ 30μm 적외선은인간의눈으로식별할수없다. 이점을이용하면보안분야에서유용하게사용할수있지만적외선측정과광학시스템에서는설계를어렵게만드는원인이된다. 적외선은파장이길어서장애물을쉽게회피할수있고복사에너지가작다. 그리고인체는물론가열된물체에서도적외선이방사된다. 이같은적외선의특징을이용하는다양한종류의센서가우리의일상생활에서유용하게쓰이고있다. 그림 10.4 빛의파장, 진동수, 에너지의관계 156
10.3 적외선을이용한거리측정센서 이절에서는적외선을이용한거리측정센서에대해알아본다. 적외선거리측정센서는송신부와수신부로구성되며, 송신부에서는전방을향해적외선의특정파장을내보낸다. 전방에물체가있어그빛이반사되면수신부에서이를측정하게된다. 수신기의신호레벨을판독할수있는경우에는그림 10.5와같이반사된적외선의양과각도를측정하면장애물의유무뿐만아니라거리를측정할수있고아울러장애물의밝기도식별해낼수있다. 그림 10.5 적외선센서측정원리현재위치에서물체까지의거리를측정할수있다면상황에따라거리측정값이매우유용하게사용될수있다. 예를들어, 자동차등에서주차시, 혹은사각지대의사람이나사물과의충돌같은위급한상황을알릴수있고, 로봇이나기타이동체의이동시장애물을인지하여충돌방지등에응용할수도있다. 실습장비에장착된 GP2Y0A21YK 센서모듈은아날로그방식으로데이터시트에따르면 10~80cm 까지측정이가능한것으로제시되어있다. 그렇지만주변환경에따른변수등을고려하면신뢰할수있는측정거리는대략 10 ~ 30 혹은 40cm 정도이다. GP2Y0A21YK 센서의구조는다음그림과같다. 그림 10.6 Sharp GP2Y0A21YK 적외선 거리센서 157
IR LED를통해적외선을방출하면물체에서반사되어돌아오는적외선이 Array Sensor 에서검출되고거리에따른반사각의차이로센서에서물체까지거리를측정하게된다. 즉, 렌즈와물체와의거리를 R, 렌즈간의거리를 D라하고, 렌즈와 position detector 와의거리를 r, 렌즈위치에서 detector 에서검출된수평거리를 d라하면다음과같은식이성립한다. (K는계수, V는출력전압 ) d = k * V R = (D / d) * r = (r * D) / d = (r * D) / (k * V) = K / V 여기서, K = (r * D) / k 있다. 그림 10.7 은 GP2Y0A21YK 센서의출력전압과반사체와의거리관계를보여주고 그림 10.7 반사체거리와출력전압의관계 80cm 거리에서 V 값이 0.4V 정도이므로, K 값은 32cm/V 가된다. 10cm 거리에서 V 값이 2.3V 정도이므로, K 값은 23cm/V 가된다. 이같은방식은그렇게정확하지않다. 위그래프에서 10cm 이하인경우는출력전압이동일한구간이발생하므로사용할수없다. 거리와출력전압간의기울기를보면 10cm에서대략 40cm까지의구간에서비교적정확한측정값을구할수있다. 이같은관계를이용하여 calibration 값을적용하고거리계산공식을만들어사용할수있다. Nucleo F401RE 보드의아날로그입력핀에대해 MBED API의입력값의범위는 0.0과 1.0사이값이다. 프로그램에서는아날로그입력핀의값을전압값의범위 (0~3200mV) 로변경한다음거리계산공식을적용하여거리를측정한다. 158
그림 10.8 은실습장비에장착된적외선거리센서와마이크로컨트롤러와의연결 구조를보여준다. 그림 10.8 적외선거리센서및연결회로도다음의프로그램은적외선거리센서에서읽은값과환산한측정거리를터미널프로그램의화면에출력하는것이다. 프로그램실행을위해새로운프로젝트 ir_distance_sensor 를생성하고, src 폴더의 main.cpp 파일을다음의프로그램소스로편집한다. 프로그램을컴파일하고다운로드하여실행한다음, 터미널프로그램을이용하여실행결과를확인한다. #include mbed.h AnalogIn IRSensor(PC_5); Serial pc(usbtx, USBRX); int dist = 0; double a, volt; double map(double x, double in_min, double in_max, double out_min, double out_max) { double scale = (x - in_min) / (in_max - in_min); return scale * (out_max - out_min) + out_min; int main() { pc.printf("ir Distance Sensor Test!!\r\n"); while(1) { wait(2); 159
a = IRSensor; pc.printf("ir sensor reads %2.2f\r\n", a); volt = map(irsensor, 0, 1.0, 0, 3200); dist = (int)((27.61f / (volt - 0.1696f)) * 1000); pc.printf("distance %d cm\r\n\n", dist); 그림 10.9 적외선거리센서프로그램실행화면 10.4 적외선감지센서 (PIR, Pyroelectric InfRared Motion Sensor) 적외선을이용해서인체의움직임을감지할수있다. 사람이나동물은약 10μm 파장의적외선을발산하는데적외선감지센서는이파장에반응하는소자로만들어졌다. 즉, 적외선을감지하면이적외선을전압의형태로변환하여출력한다. 그림 10.10처럼사람이접근하면센서의 2개의슬롯중하나에체온에의한적외선 ( 열 ) 이검출되고표면에전하가발생하게된다. 이로인해내부의 FET 디바이스는약한전압을발생 (Positive differential change) 하게되고이를증폭해서사용하게되는데, 이원리를 PIR이라한다. 반대로사람이측정범위에서멀어지면반대의변화 (Negative differential change) 가발생하게된다. 160
이러한센서를응용한제품으로가장흔하게볼수있는것이사람이출입하게 되면자동으로켜지고꺼지는현관등일것이다. 이외에도방범시스템이나자동화 시스템과같은다양한응용에적용할수있다. 그림 10.10 적외선감지센서작동원리실습장치에서사용하는적외선감지센서는그림 10.11과같으며, 마이크로컨트롤러와는그림 10.12와같이연결되어있다. 그리고센서의주요사양은표 10.2와같다. 그림 10.11 적외선감지센서외형및구성 161
그림 10.12 적외선감지센서의연결회로도 표 10.2 적외선감지센서의주요사양 속성동작전압소비전력 TTL 출력지연시간각도거리 값 5 ~ 20 V 65 ma 0 ~ 3.3 V 0.3 ~ 5 min 110 o 최대 6m 이제적외선감지센서를활용하는프로그램을작성하도록한다. 우선, 간단하게적외선감지센서가움직임을감지할때마다이를표시하기위해초록색 LED를 on 하고, 메시지를출력하는프로그램을작성하도록한다. 적외선감지센서가인체의움직임을감지하면디지털신호를 High로출력하고움직임없이일정시간이지연되면다시 Low로출력한다. 따라서움직임감시를위해프로그램에서는무한루프를돌면서센서의출력값을확인할것이다. 이를구현한프로그램은다음과같으며, 프로그램실행을위해프로젝트 pir_sensor 를생성하고, src 폴더의 main.cpp 파일을다음의프로그램소스로편집한다. #include mbed.h Serial pc(usbtx, USBRX); DigitalIn motion(d5); DigitalOut light(pa_13); volatile int Sensor = 0; 162
int main(void) { // pc.baud(115200); while(1) { Sensor = motion; if (Sensor) { light = 1; pc.printf( IR Detect: LED On \r\n ); wait(3); else light = 0; 프로그램을컴파일하고다운로드하여실행하면움직임이감지되었을때에초록색 LED 가켜지고그림 10.13 과같은결과를확인할수있다. 그림 10.13 적외선감지센서동작화면위의프로그램은센서값에따라동작을수행하기위해 while 반복문에서디지털입력값을계속읽으면서변화가있는지를관찰하는폴링 (polling) 방식으로구현되어있다. 이러한폴링방식으로센서값을추적하는경우에는발생주기가긴사건 ( 이벤트 ) 을검출하기위해지속적으로센서값을읽어야한다는부담이있다. 이러한부담을줄이기위해센서값의변화를추적해서프로그램에게알려주는방식을사용할수도있다. 즉, 센서가움직임을감지하면인터럽트를발생시키고프로그램의인터럽트루틴에서적절한동작을처리하도록구현하는방식이다. 인터럽트처리방식의장점은센서값의변화를지속적으로관찰할필요가없다는것이다. 이와같이검출하려는사건을인터럽트처리방식으로처리하면마이크로컨트롤러가계속해서폴링할필요가없으므로소비전력도줄어들게되며, 다른일을병행적으로처리하는데도훨씬유리하다. 163
n 인터럽트서비스루틴 MBED 라이브러리에서인터럽트입력을받아처리할수있도록지정하는데이터타입은 InterruptIn 이다. 실습장비에서적외선감지센서의출력값을 D5 핀으로입력받고있다. 따라서적외선감지센서로부터입력되는값에대해인터럽트로처리하기위해서는아래와같이 D5 핀을인터럽트입력핀으로선언한다. 인터럽트처리에관련된내용은 11장에서자세히다루기로하겠다. InterruptIn motion(d5); D5 핀의입력값이들어와서인터럽트가발생했을때수행할함수를아래와같이 등록한다. motion.rise(&irq_handler); // 인터럽트입력신호가 Low 에 High 로바뀔때에인터럽트발생 // 인터럽트핸들러함수 irq_handler 의주소값지정 motion.fall(&irq_handler); // 인터럽트입력신호가 High 에 Low 로바뀔때에인터럽트발생 // 인터럽트핸들러함수 irq_handler 의주소값지정 이제, 앞에서살펴본폴링방식의프로그램을인터럽트처리방식으로개선해보도록한다. 기능을추가하여움직임이감지될때에움직임감지횟수를카운팅하여화면에출력하도록한다. 수정된프로그램은다음과같다. 프로그램실행을위해 pir_sensor 프로젝트를복사하여붙여넣기를하여 pir_sensor_irq 프로젝트를생성한다. src 폴더의 main.cpp 파일을다음의프로그램소스로편집한다. #include "mbed.h" Serial pc(usbtx, USBRX); DigitalOut light(pa_13); InterruptIn motion(d5); int cnt= 0; 164
void rising_handler(void) { cnt++; // increment count variable light = 1; // turn on Green LED void falling_handler(void) { light = 0; // turn off Green LED int main(void) { int last_cnt = 0; motion.rise(&rising_handler); motion.fall(&falling_handler); cnt = 0; light = 0; while(1) { if(cnt!= last_cnt) { pc.printf("hello! I've detected %d times since reset\r\n", cnt); last_cnt = cnt; 프로그램을컴파일하고다운로드하여실행한다. 적외선감지센서근처로손을가 져가면그림 10.14 와같은실행결과를확인할수있다. 그림 10.14 적외선감지센서프로그램실행결과 165
10.5 자외선센서 자외선센서는자외선 (Ultraviolet Radiation) 의세기를측정하는데사용된다. 자외선은가시광선에비해파장이훨씬짧으며, 사람의눈에는보이지않는빛으로피부를태우거나살균작용을한다. 그러나과도하게노출될경우피부암에걸릴수있는위험도따른다. 자외선은일반적으로 UV-C, UV-B, 그리고 UV-A 세가지종류로분류된다. 성층권의오존층은이들자외선의일부만을흡수한다 ( 그림 10.15 참조 ). Ÿ UV_A : 오존층에흡수되지않는다. 파장영역이 0.32~0.40μm에해당하는자외선 UV-A 는 UV-B에비하여에너지량이적지만피부를그을릴수있다. 피부를태우는주역은 UV-B이지만 UV-A는피부를벌겋게만들뿐아니라피부면역체계에작용하여피부노화에따른장기적피부손상을일으킬수있다. Ÿ UV_B : 대부분은오존층에흡수되지만, 일부는지표면에도달한다. 지구에극소량이도달하는 UV-B는파장영역이 0.28~0.32μm에해당하는자외선이다. UV-B는동물체의피부를태우고피부조직을뚫고들어가며때로는피부암을일으키는데, 피부암발생의원인은대부분태양광선의노출및 UV-B와관련이있다. Ÿ UV_C : 오존층에완전히흡수된다. 파장영역이 0.20~0.29μm인자외선중 UV-C는염색체변이를일으키고단세포유기물을죽이며, 눈의각막을해치는등생명체에해로운영향을미친다. [ 내용출처 : 네이버지식백과 ] 그림 10.15 자외선종류및파장 166
이번실습에서사용할자외선센서는 GUAVA-S12D 센서이며, 200nm 에서 400nm 의넓은영역의스펙트럼을감지할수있다. 이센서는자외선의세기에따라변하는전기신호를출력한다. 자외선센서는제약회사, 자동차, 로봇산업등많은응용분야에서사용되고있다. 또한염색공정이나용매제를처리하는프린팅산업등, 화학산업계에서도널리사용되고있다. 자외선센서는자외선지수와광전류사이의비례관계를이용한다. 대체로한낮의실내자외선지수는 8~10%, 그늘진곳은 10~30%, 피부가탈정도는 70%, 그리고뜨거운한낮태양아래는 100% 정도가된다. GUAVA-S12D 센서의주요특징은다음과같으며, 주요규격은표 10.3과같다. 뛰어난안정성 높은감도 (Sensitivity) 낮은소비전력 Schottky type photodiode 센서 넓은응답범위표 10.3 GUAVA-S12D 센서의주요규격 그림 10.16 자외선센서모듈의외형및연결회로도 자외선센서모듈의외형과연결구조는그림 10.16 과같으며, 아날로그입력 A4 핀을통해자외선센서의출력값을읽어올수있다. 다음의프로그램코드는자외 167
선센서출력값을 wait(0.1) 함수에의해 100msec 간격으로반복해서읽으면서그값을터미널화면에보여준다. 프로그램실행을위해새로운프로젝트 uv_sensor 를생성하고 src 폴더의 main.cpp 파일을다음의프로그램소스로편집한다. 그리고프로그램컴파일하고다운로드하여실행하여터미널화면에서실행결과를확인한다. #include "mbed.h" AnalogIn sensoruv(a4); int main() { double value; while (true) { value = sensoruv; printf("\ruv Value = %3.2f%%", value*100); wait(0.1); 그림 10.17 자외선센서프로그램실행결과위의프로그램에서유의할점은앞선프로그램과달리 Serial 타입의객체를사용하지않고단순히출력함수 printf 을호출하여화면에출력하고있다는것이다. C 프로그램에서 printf 와같은표준출력함수는표준출력장치 ( 즉, 모니터 ) 를이용한출력을수행하는데, Nucleo-F401RE 보드에서의표준출력장치는기본적으로 USB로연결된장치를의미한다. 따라서 USB 연결을통해텍스트데이터를입출력하는경우에는 Serial 타입의객체선언없이직접입출력함수를호출하여사용할수있다. 앞절의적외선감지센서를이용하는실습에서인터럽트방식을사용하여보다효과적으로동작하도록구현하였다. 이번에는 MBED 라이브러리에서제공하는타이머틱 (Timer Tick) 기능을이용하여주기적으로일련의동작을반복수행하는것을구현하도록한다. 자세한내용은 11장에서살펴볼것이다. 168
임베디드시스템에서는제어에필요한동작을함수형태로작성하고필요할때에 API(Application Program Interface) 를통해직접호출하여실행하는것이일반적이다. 그런데경우에따라서시스템에서특정이벤트가발생하면이런이벤트처리를위해등록한함수를비동기적으로호출하여처리할수도있다. 이렇게이벤트에의해호출되는함수를 콜백 (Callback) 함수 라고한다. MBED 라이브러리의 Ticker 데이터타입은지정된시간을주기로타이머틱을발생시켜지정된함수를반복실행한다. 이를위해서는반복동작을수행하는함수를 Ticker 의콜백함수로등록해주어야한다. 다음프로그램에서 ticker_handler(void) 함수는주어진주기마다타이머틱이발생할때호출되는콜백함수이다. 이함수는 sensor_ticker.attach 함수에의해콜백함수로등록되고있다. 프로그램실행을위해이전의프로젝트 uv_sensor 를이용하여새로운프로젝트 uv_sensor_ticker 를생성하고 src 폴더의 main.cpp 파일을다음의프로그램소스로편집한다. #include "mbed.h" AnalogIn sensoruv(a0); Ticker sensor_ticker; void ticker_handler(void){ // get the value from the sensor and print it out double value; value = sensoruv; printf("\ruv Value = %3.2f%%",value*100); int main() { // set up ticker_handler() to be called every 0.1 second sensor_ticker.attach(&ticker_handler, 0.1); while (true) { // do something else //... 프로그램을컴파일하고다운로드하여실행한다. Ticker 에의해 100msec 마다 ticker_handler(void) 함수가호출되어자외선센서값을읽고이를터미널화면으로 출력한다. 169
170 그림 10.118 타이머틱을이용한프로그램실행결과
11. 인터럽트, 타이머그리고태스크 이번장에서는인터럽트, 타이머에대한내용을자세히살펴보고태스크에대한 내용도아울러설명한다. 이부분은어느정도임베디드시스템에대한경험이쌓이 면반드시알아야할중요한내용이기도하다. 11.1 타이머 (Timer) 와인터럽트 (Interrupt) 임베디드시스템은내 외부적으로발생하는다양한이벤트에대해서정해진시간내에응답해야한다. 이를위해서는다음과같은사항을지원할수있어야한다. 시간간격측정 시간에따른이벤트생성 - 한번혹은반복된이벤트일수있다. 외부이벤트에대해적절한속도로응답 - 외부이벤트는예측불가능한시점에발생할수있다. 이런모든일을수행하는중에시스템은이해의충돌즉, 동시에주의를기울여야할두가지일을갖게되는경우가있을수있다. 예를들어, 주기적인이벤트가발생하고있는데, 외부에서처리해야할이벤트가새로발생할수있다. 그러므로시스템은이벤트들간에먼저처리해야할이벤트를구별할필요가생긴다. 이는시간을기반으로한효율적인동작이이루어질수있도록지원하는도구와적절한사용기술이필요하다는것을의미한다. 이런도구들중에서중요한것들이여기에서설명하고자하는인터럽트와타이머이다. 간단히말해서타이머는시간을측정할수있도록해주는디지털회로이며, 정해진시간이되면어떤일이발생되게만들수있다. 인터럽트는실행중인프로그램을중단되고다른동작을하는곳으로분기될수있도록해준다. 11.2 태스크 (Task) 거의대부분의임베디드시스템에서프로그램은많은다른일들을수행해야한다. 추운환경에서버섯을키우는작은농장을예로들어보자. 버섯종자가자라는방의온도와습도를엄격히제어해야만할것이다. 모종이생육함에따라온도에변화를줄필요가생긴다. 사용자는시간에따라온도를표시하고로그를기록하며히터와팬을제어해야할것이다. 이같은각각의일들은분명히구분지을수있는일들이 171
다. 프로그래밍용어로표현하면이렇게구분지을수있는활동을 태스크 (task) 라한다. 보다진보된프로그램디자인에서프로그램을태스크별로나누는일은매우중요한기술이되었다. 일단하나의프로그램이하나이상의태스크들을가지게되면우리는멀티-태스킹 (Multitasking) 의영역으로진입하게된것이다. 태스크들의수가증가함에따라모든태스크의필요에응답해야하는일이점점큰문제가되고, 이를해결하기위해서많은기술들이개발되어있다. 11.2.1 이벤트트리거및타임트리거태스크 (Event-Triggered & Time- Triggered Tasks) 임베디드시스템에서수행되는태스크들은두가지부류로나눌수있다. 하나는 이벤트에의해서구동되는태스크와다른하나는시간에의해구동되는태스크이다. 이벤트에의해구동되는태스크는일반적으로예측할수없는외부이벤트의발생에 의한다. 시간에의해구동되는태스크는마이크로컨트롤러에의해정해진시간에서 한번또는주기적으로구동된다. 앞서살펴본버섯재배의예를프로그램태스크에 적용해보면아래표와유사할것이다. 표 11.1 타스크의구분예 태스크 이벤트혹은타임트리거 온도측정타임 ( 매분마다 ) 히터와팬설정타임 ( 매분마다 ) 사용자제어에응답 이벤트 온도기록과표시타임 ( 매분마다 ) 전력손실시예비전력으로전환 이벤트 11.2.2 이벤트트리거에대한응답이벤트에의해구동되는동작의간단한예는사용자가푸시버튼을눌렀을때이다. 이는경고없이언제라도발생할수있지만사용자는이에대한응답을기대한다. 이에대한구현방법중에하나는외부입력을끊임없이확인하는것이다. 이를폴링 (polling) 방식이라고하며, 그림 11.1에서보여주는것과같다. 172
그림 11.1 이벤트트리거에대한응답프로그램은계속해서반복적으로수행되는구조로되어있다. 루프안에서두개의버튼의입력을확인하고눌려진버튼에대해응답한다. 외부이벤트를직접검사하는이와같은방법을 폴링 (polling) 이라부른다. 프로그램은주기적으로입력상태를확인하고필요한경우에응답한다. 이것은지금까지우리가자주사용해온방식이다. 간단한시스템에서는이방법이잘동작한다. 하지만좀더복잡한시스템에서는적합하지않다. 그림 11.1의프로그램이좀더확장된경우를생각해보자. 마이크로컨트롤러가각루프마다확인해야할입력이수십개에달한다고가정하면입력데이터의변화를감지하지못할수있다. 예를들어, 프로그램이별로중요하지않는입력을확인하느라빠르게값이변하는중요한오류가발생한것을감지하지못한다면폴링방식이아무런도움이되지않는다. 폴링방식의두가지중요한문제가있다. 프로세서는폴링루틴을수행중에는어떤다른동작들도수행할수없다. 모든입력들이동등하게취급된다. 즉, 빠르게처리되어야할일도자기차례를기다리게된다. 이보다더나은방법은입력의변화를스스로알려주는것이다. 변화가있는지를찾기위해시간을낭비하지않아도된다. 어려운점은언제입력이변했는지를아는것이다. 173
11.2.3 인터럽트에의한이벤트응답인터럽트는폴링방식의접근과는아주다르다. 하드웨어가인터럽트를가지도록설계되어있으면외부이벤트에의해실행중인 CPU를멈추게할수있다. 예를들어, 밤에여러분이사는집으로도둑이침입할지모른다고가정해보자. 알람 (alarm) 시계를맞춰두고매 30분마다일어나서도둑이없는지확인할수있다. 그렇지만여러분은숙면을취하기는어려울것이다. 다른대안으로침입을알리는경보시스템을설치할수도있다. 이경우는경보알람이울려서잠을방해하지않는한여러분은숙면을취할수있을것이다. 이것이바로컴퓨터에서폴링과인터럽트의개념이다. 인터럽트는외부이벤트나장치가 CPU 동작에변화를줄수있도록해주는마이크로컨트롤러혹은마이크로프로세서구조의아주중요한부분이다. 예전에만들어진프로세서의인터럽트는주로중요한외부이벤트에대해서응답하기위해사용되었으며허용되는인터럽트소스도많지않았다. 그렇지만인터럽트의개념이유용하다는것이확인되면서점점더많은인터럽트소스들이도입되게되었다. 인터럽트에응답할경우, 대부분의마이크로프로세서들은그림 11.2와같은순서를따른다. 그림 11.2 마이크로프로세서의인터럽트응답 174
CPU는현재실행중인명령을먼저완료한다음실행할코드블록으로분기한다. 이때직전까지진행하고있던것에대한핵심정보인실행컨텍스트 (Context) 를저장해야한다. 실행컨텍스트는적어도 PC(Program Counter) 값을포함해야한다. 이는인터럽트처리가완료되었을때어디서부터실행을재개해야하는지에대한정보를담고있다. 그리고현재데이터값을저장하고있는주요레지스터들이포함되어야한다. 이모든것이스택 (Stack) 이라부르는작은메모리블록에저장된다. 그런다음 CPU는 ISR(Interrupt Service Routine) 이라부르는코드를실행한다. ISR 은발생하는인터럽트에대해응답하여처리하기위해작성된코드이다. ISR의주소는 Interrupt Vector Table라부르는메모리위치에서찾을수있다. ISR 실행을완료하면 CPU는즉시이전에실행하던코드에서인터럽트가발생한명령의다음위치로되돌아가게된다. 이위치는스택에저장된 PC 값을보면알수있다. 그다음에는마치아무일도없었던것처럼멈췄던프로그램실행을계속해나간다. 11.3 MBED 라이브러리를이용한인터럽트처리 MBED API(Application Programming Interface) 는마이크로컨트롤러가제공하는인터럽트기능중주로외부인터럽트에초점을맞추고있다. 인터럽트처리를위해사용할수있는 API 함수들은표 11.2에요약되어있다. 이를이용하면인터럽트입력을만들고이에해당하는 ISR 프로그램을작성하여 ISR과링크시킬수있다. 표 11.2 인터럽트처리를위한 MBED 라이브러리 API 함수 InterruptIn rise fall mode 사용예지정된핀에연결되는 InterruptIn 인스턴스를생성입력신호가 rising edge일때호출되는함수를 attach 입력신호가 falling edge일때호출되는함수를 attach 입력핀모드설정 다음프로그램은매우간단한인터럽트처리사례로서인터럽트가발생하면 LED 를토글 (toggle) 한다. 인터럽트는그림 11.3의회로도에서버튼이연결된 PA_14번핀의입력을인터럽트소스로사용하고, 이를위한 ISR이 ISR1 이라는이름으로작성되어있으며, 함수와같은구조로되어있다. 버튼회로가풀업저항을사용하고있어버튼을누르면입력신호가 High에서 Low로변함으로 ISR 함수의주소는아래와같은 fall 함수를이용하여인터럽트입력신호에 attach 된다. 175
button.fall(&isr1); 이예제에서는버튼을누를때 (falling edge), 인터럽트가활성화되면 ISR 이실행 되고, LED 는토글된다. 이와같이프로그램실행중에언제라도인터럽트는발생할 수있고마이크로컨트롤러는효율적으로트리거된작업을수행한다. 그림 11.3 스위치를이용한인터럽터회로도 #include "mbed.h" InterruptIn button(pa_14); //define and name the interrupt input Serial pc(usbtx, USBRX); DigitalOut led(led1); void ISR1() { led =!led; // this is the response to interrupt, i.e. the ISR int main() { button.fall(&isr1); while (1) { if (!button) { pc.printf ("\r\n Button wait (0.5); // attach the address of the ISR function // interrupt rising edge Pressed"); 176
프로그램실행을위해새로운프로젝트 button_interrupt 를생성하고, src 폴더의 main.cpp 파일을위의소스코드로편집한다. 그리고프로그램을컴파일하고다운로드하여실행한다. 푸시버튼을클릭할때인터럽트발생하고, LED가토글되는것을확인할수있을것이다. 그림 11.4 인터럽트처리프로그램실행결과 11.3.1 복잡한인터럽트구조 MBED 라이브러리의 API는앞서살펴본간단한인터럽트구조에서는아주편리하게사용할수있다. 그리고실습보드는웨어러블센서를포함하여주로일상생활에많이접하게되는센서들로구성되어있기때문에이것으로충분하다. 그러나 Nucleo-F401RE 보드에장착된 ARM Cortex-M4 마이크로컨트롤러는이보다훨씬진화한인터럽트구조를지원하고있다. 앞으로설명할내용은인터럽트구조에대해조금더이해의폭을넓히려는사람만참조하기바란다. 복잡한인터럽트의예를들기위해한학급의예를들어보자. 만일 A라는학생의요청에따라선생님이도움을주고있는와중에 B라는학생이도움을요청하면두가지경우가가능하다. 하나는 B 학생에게 A 학생의일이끝날때까지기다리라고말하고 A 학생을계속도와주거나, 다른하나는즉시 B 학생의일을도와준다음 A 학생과나머지일을하겠다고말하는것이다. 여기에만약교장선생님이와서 B 학생을수업이끝나기전에학교행사로데려가겠다고하고, 선생님은학생모두가자신의과제를수업전에완료하기를바란다면상황은더복잡해진다. 위경우처럼임베디드시스템에서도주의를기울여야할인터럽트소스들이여러개존재할수있다. 어떤것은아주중요하고, 다른것은좀덜중요할수있다. 그러므로대부분의프로세서들은 4 가지중요한메커니즘을포함한다. 1 인터럽트는우선순위를가질수있다. 일부인터럽트는다른인터럽트보다더중요하게정의된다는의미이다. 만약동시에두개의인터럽트가발생하면우선순위가높은것이먼저실행된다. 2 인터럽트는마스크 (mask) 될수있다. 그것들이필요하지않거나보다중요한일을해야한다면인터럽트처리를꺼둘수있다. 또한중요한프로그램부분이완료될때까지짧은시간동안만마스크하는것이가능하다. 177
3 인터럽트는중첩 (nest) 될수있다. 이것은높은우선순위의인터럽트가낮은우선순위의인터럽트를인터럽트하는것을의미한다. 즉, 위예에서 A 학생과의일을잠시미뤄두고 B 학생의일을먼저처리하는것과유사하다. 중첩된인터럽트형태로작업하는것은프로그래머에게더많은것을요구하게되므로, 엄밀히말해, 상급자를위한것이라할수있다. 4 메모리에서메모리맵에맞도록, 그리고프로그래머의뜻에맞도록 ISR의위치가선택될수있다. 보다중요한인터럽트의개념을좀더살펴보자. 위예에서 B 학생이도움이필요하다는것을깨달아손을들어선생님을부르는시점으로돌아가보자. 잠시시간이지나면선생님이 B 학생곁으로오게될것이다. 손을들고실제로선생님이도착하기까지의시간을인터럽트지연 (Interrupt Latency) 이라한다. 지연은많은원인에의해발생하게된다. B 학생이선생님이오기까지기다리는동안 B 학생의인터럽트는 pending 되었다고한다. 이모든것에대해좀더기술적인용어를사용해서인터럽트동작에대한이해를개선할수있다. 그림 11.5은인터럽트의처리순서를설명해주고있다. 그림 11.5 인터럽트의세부처리방식 인터럽터요청 (Interrupt Assert) 은 B 학생이손을든동작이라보면되겠다. 마이크로프로세서에서인터럽트입력은설정에서정한논리적인신호가될것이다. 즉, active high, active low, rising edge 혹은 falling edge에의해트리거될수있다. 이같은입력은인터럽트플래그 (Interrupt Flag) 가 set되게한다. 이것은보통내부레지스터의한개비트로되어있으며인터럽트가발생한사실을기록하게된다. 이것은 178
인터럽트가자동으로이비트에주의를기울일필요가있다는의미는아니다. 만약인터럽트를사용하지못하도록설정되어있다면 (Disable) 즉, 마스크 (Mask) 되어있으면응답하지않고, 플래그는 High 상태로남게된다. 그렇지만프로그램이나중에인터럽트를사용가능하도록설정하거나 (Enable) 혹은프로그램이인터럽트플래그를단지폴링할수도있다. ISR(Interrupt Service Routine) 을이미실행중에있는데다른인터럽트가발생하면, 즉중첩인터럽트가발생하면적어도바로응답을얻지는못한다. 만약, 뒤에발생한인터럽트의우선순위가높으면현재실행중인인터럽트처리는중단되고실행을허가받을수있다. 그러나우선순위가낮다면이미실행중인 ISR이완료되기를기다려야한다. 그림 11.5에서주의할점은이런동작들이차례로일어난다고오해를불러올수있다는것이다. 훌륭한인터럽트관리시스템은몇가지동작들이동시에일어난다. 예를들어인터럽트벡터는현재명령이수행되는중에액세스가가능하다. 11.4 타이머 (Timer) 기존의프로젝트들에서는실행하는동안일정시간을지연시키는것과같은타이밍동작들을수행하기위해 wait() 함수를사용했었다. 이는매우쉽고편리하지만지연시간동안마이크로컨트롤러는어떤다른동작도수행하지못한다. 시간을지연시키는 wait() 함수는말그대로시간을낭비하는것이다. 보다효율적인프로그래밍을작성하고자할때는이처럼단순한타이밍기법이부적절해진다. 이에프로그램이유용한일들을수행하는중에백그라운드에서타이밍동작을반복적으로실행하는방법이필요하다. 11.4.1 디지털타이머카운터 (Counter) 회로구현은디지털전자회로에서는쉬운일이다. 간단히플립- 플롭들을연결하면된다. 각각은한비트의정보를담고, 전체가모여디지털워드를구성하게된다. 이는간단하게그림11.6 과같이표현할수있다. 그림 11.6에서각각의작은블록은한비트의정보를담고있는하나의플립-플롭을표현한다. 만약이런배열의입력이클럭시그널에연결되면카운터는클럭펄스가발생할때마다이진수로카운트 (count) 하게될것이다. 카운터에저장된전체디지털숫자를읽어오는것은쉽다. 그리고특정숫자값으로로드하거나 0으로클리어하는로직은어렵지않다. 179
그림 11.6 카운터레지스터의구조카운터가카운트할수있는수는카운터의비트수에의해결정된다. 일반적으로 n-bit 카운터는 0부터 (2 n 1) 까지카운트할수있다. 만약카운터가최대값에도달하고입력클럭펄스가계속발생하면다시 0으로오버플로우 (overflow) 되어카운트를다시시작한다. 이런일이발생한다해도모두잃어버리는것은아니다. 사실우리는이를처리할준비가되어있다. 많은마이크로컨트롤러는카운터오버플로우일때인터럽트를발생시킨다. 이런인터럽트는오버플로우를기록하고카운트를유용한방법으로계속하는데사용될수있다. 11.4.2 타이머로카운터사용하기카운터의입력신호는예를들어, 문을통과하는사람을카운트하는것과같이외부소스에서오는연속된펄스일수있다. 혹은마이크로컨트롤러내부의클럭소스처럼고정된주파수신호일수도있다. 만약클럭주파수를알고안정적이라면카운터는타이머가된다. 하나의예로써클럭주파수가 1MHz( 즉, 주기가 1us) 이면, 카운터는 1usec 마다업데이트될것이다. 만약, 카운터가 0으로클리어된다음카운트를시작하면카운터에저장된값은카운트를시작한이후부터의시간을제공하게된다. 이것으로시간을측정하거나지정된시간에도달하면이벤트를발생시키는등의일에사용될수있다. 또한시리얼데이터통신이나 PWM 출력신호와같은시간을기준으로하는동작을제어하는데사용될수도있다. 만약카운터가계속되는클럭신호로단지 free-running 만한다면이는주기적인인터럽트발생이필요한곳에매우유용할것이다. 예를들어, 8-bit 카운터가 1MHz 의클럭주파수로클럭킹 (clocking) 되면 256us 안에최대값에도달하고다시 0으로오버플로우될것이다 (0부터시작해서 256번째펄스가오버플로우 ). 만약이것이계속실행되게두면계속되는인터럽트펄스가시간에기반한동작들을동기화하는데사용될수있다. 예를들어시리얼통신링크의데이터전송속도 (baud rate) 를정의할수있다. 180
이런원리에기초한타이머는마이크로컨트롤러에서매우중요하다. 대부분의마이크로컨트롤러는하나이상의타이머들을가지고다양한작업에적용시키고있다. 이들은 PWM 타이밍이나시리얼통신링크를만들거나외부이벤트의시간을측정하거나기타시간을기준으로하는동작들을만드는것을포함한다. 11.4.3 Nucleo STM32F401RE의타이머 STM32F401RE 마이크로컨트롤러는 4개의일반타이머와반복인터럽터타이머 (Repetitive Interrupt Timer) 그리고시스템틱타이머 (System Tick Timer) 가있다. 이들모두앞서언급한타이머의원리에기초하고있다. MBED 라이브러리는이들타이머를이용하여세가지유형의응용에이용할수있는 API들을지원한다. 이들은간단한타이밍응용이거나간단히시간을측정하는데사용되는 Timer, 미리정해놓은지연시간후에함수를호출하여실행하는 Timeout, 미리정해놓은시간마다함수를반복해서호출하여실행하는 Ticker 등이다. 또한시간과날짜를저장하는 Real Time Clock이있다. 11.5 MBED 타이머 (Timer) MBED 타이머는짧은시간내에기본적인타이밍동작을수행하거나시간측정을위한동작을지원한다. Timer 는 32-bit 카운터에기반하고있다. 타이머를생성하는수에는제한이없다. 타이머에사용되는 API는표 11.3과같다. 표 11.3 MBED 타이머 API 함수 start stop reset read read_ms read_us 기능 Timer 시작 Timer 멈춤 Timer를 0으로리셋초단위로시간읽기 Milliseconds 단위로시간읽기 Microseconds 단위로시간읽기 다음프로그램은간단하지만흥미로운예제이다. 이것은메시지를화면에보내는 데걸리는시간을측정하고측정된시간을메시지로출력한다. 181
#include "mbed.h Timer t; // define Timer with name t Serial pc(usbtx, USBRX); int main() { t.start(); // start the timer pc.printf("hello World!\n"); t.stop(); // stop the timer //print to pc pc.printf("the time taken was %f seconds\n", t.read()); 프로그램실행을위해새로운프로젝트 timer_test 를생성하고, src 폴더의 main.cpp 파일을위의소스코드로편집한다. 그리고프로그램을컴파일하고다운로드하여실행한다. 시간을측정하는코드중간에새로운코드를삽입해보거나데이터전송속도 (baud rate) 등을변경해가며실행시간의변화를살펴보도록한다. 그림 11.7 인터럽트테스트프로그램실행결과이번에는 Timer 를좀달리적용해보도록한다. Timer 의 rate에따라각각별개의함수를실행하도록한다. 이를표시하기위해두개의 LED를사용할것이다. 다음프로그램은두개의태스크 timer_fast 와 timer_slow 를만든다. 메인프로그램에서는현재시간을측정하고특정숫자와비교해서이를초과하면함수호출을통해해당 LED를깜박이게한다. #include "mbed.h" Timer timer_fast; // define Timer with name "timer_fast" Timer timer_slow; // define Timer with name "timer_slow" DigitalOut DigitalOut leda(led1); ledb(pc_6); 182
void task_fast(void); // function prototypes void task_slow(void); int main() { timer_fast.start(); // start the Timers timer_slow.start(); while (1){ if (timer_fast.read() > 0.2){ // test Timer value task_fast(); // call the task if trigger time is reached timer_fast.reset(); // and reset the Timer if (timer_slow.read() > 1){ // test Timer value task_slow(); timer_slow.reset(); void task_fast(void){ // Fast Task leda =!leda; void task_slow(void){ // Slow Task ledb =!ledb; 프로그램실행을위해새로운프로젝트 timer_tasks 를생성하고, src 폴더의 main.cpp 파일을위의소스코드로편집한다. 프로그램을컴파일하고타겟시스템에다운로드하여실행해본다. 0.2 초간격으로 Nucleo STM32F401RE 보드위의 LED가깜박이고, RGB LED의녹색 LED가 1초간격으로깜박이는것을확인할수있을것이다. 11.6 MBED Timeout 앞에서시간을기준으로이벤트를트리거하는데 MBED Timer 가효율적으로사용되는것을보았다. 그렇지만언제이벤트가트리거되어야하는지알기위해 Timer 값을계속읽어 (polling) 확인하도록구현했다. 반면, Timeout은폴링할필요없이특 183
정이벤트가인터럽트에의해트리거되게만들수있다. Timeout 은지정된지연시간뒤에함수가호출되도록인터럽트를설정한다. Timeout 을생성하는수에는제한이없다. Timeout API는표 11.4와같다. 표 11.4 MBED Timeout API 함수기능 attach 지정된지연시간후에호출되는함수를 attach(sec 단위 ) attach_us 지정된지연시간후에호출되는함수를 attach(msec 단위 ) detach 함수를 detach 다음프로그램은 Timeout 의간단한사용사례를보여준다. 프로그램에서는외부이벤트발생후에지정된시간이되면특정동작이트리거되도록구현되어있다. 프로그램은 main() 함수와 blink() 함수로구성되어있으며, Timeout 은 Response 라는이름으로생성된다. main() 함수를살펴보면 if 조건문을이용하여버튼스위치가눌러졌는지확인하고, 만약버튼스위치가눌러졌다면 blink() 함수가 Response Timeout 에 attach 된다. 이처럼 blink() 함수가 attach 되면 2초후에호출되어실행될것이다. 프로그램동작을확인하기위해서 LED를사용하고있다. #include "mbed.h" Timeout Response; //create a Timeout, and name it "Response" DigitalIn button (PA_14); DigitalOut led1(led1); DigitalOut led2(pc_6); void blink() { //this function is called at the end of the Timeout led2 = 1; wait(0.5); led2=0; int main() { while(1) { if (button == 0) { Response.attach(&blink,2.0); // attach blink function to // Response Timeout, to occur after 2 seconds led1=1; //shows button has been pressed else { 184
led1=0; 프로그램실행을위해새로운프로젝트 timeout_test 를생성하고, src 폴더의 main.cpp 파일을위의소스코드로편집한다. 프로그램을컴파일하고실행파일을다운로드하여동작을확인해본다. 버튼스위치를누르면 Nucleo STM32F401RE 보드의 LED가 ON 되고, 2초후에 RGB LED가한번깜박이게될것이다. 11.7 MBED Ticker Ticker 의기능은인터럽트를반복하도록설정하는것이다. 따라서프로그래머가지정한시간에따라주기적으로하나의함수를호출하는데사용될수있다. Ticker 를생성하는수에는제한이없다. Ticker 와관련된 API는표 11.5에요약되어있다. 표 11.5 MBED Ticker API 함수 attach attach_us detach 기능 일정간격을두고 Ticker 에의해호출되도록지정된함수를 attach (sec 단위 ) 일정간격을두고 Ticker 에의해호출되도록지정된함수를 attach (msec 단위 ) Attach 된함수를 detach 다음프로그램은 Ticker 를이용하여 200mS 마다 LED를깜박이게하는간단한프로그램이다. 주기적으로발생하는이벤트를만드는것은임베디드시스템에서는자연스럽고일반적인요구사항이다. 프로그램에서 wait 함수를이용하여 200ms 주기를만드는것도제한된범위내에서는유용하지만 CPU가그시간동안아무런일도할수없다는것이단점이다. 185
#include "mbed.h" void led_switch(void); Ticker time_up; DigitalOut myled(led1); void led_switch() { myled =!myled; int { main() time_up.attach(&led_switch, 0.2); while(1) { // 루프에서는아무일도하지않고, Ticker 인터럽트를기다린다. 프로그램실행을위해새로운프로젝트 ticker_test 를생성하고, src 폴더의 main.cpp 파일을위의소스코드로편집한다. 프로그램을컴파일하고실행파일을다운로드하여동작을확인해본다. 메인함수에서아무런동작을수행하지않지만 Nucleo STM32F401RE 보드의 LED가 0.2초단위로토글링하는것을확인할수있을것이다. 상기프로그램이어떻게동작하는지분석하는것은어렵지않다. CPU가정말로어떤일도할필요없이자유롭다는것이중요한진전이다. 반면 LED가깜박이는시간을측정하는일은백그라운드에서동작하는 Timer 하드웨어가수행하는것이다. 11.8. 아날로그조이스틱 이번에는 MBED Ticker 의응용사례로아날로그조이스틱의입력을주기적으로읽어출력하는실습을진행한다. 조이스틱은게임기뿐만아니라다양한분야에활용이가능하다. 실습에서사용할조이스틱은두가지방향의움직임에대해두가지다른아날로그신호를출력한다. 이신호를이용하면 x축과 y축의좌표를에뮬레이션할수있고, 이값을기준으로마우스와같은움직임이나방향, 속도등과같은다양한제어가가능해진다. 또한특 186
별한용도로사용할수있는푸시버튼도가지고있다. 마이크로컨트롤러는두개의아날로그입력과푸시버튼이눌려진것을검출할수있는디지털입력을받아서작업을진행하게될것이다. 실습에사용하는조이스틱모듈의외형과연결구조는그림 11.8과같다. 그림 11.8 조이스틱모듈외형및연결회로도다음프로그램은 0.5초주기로조이스틱의 x축값과 y축값을읽어출력한다. 이를위해 joystick 이라는이름의 Ticker 를생성하고조이스틱의입력값을출력하는함수 joystick_handler 를 Tciker 에 attach 하였다. 그리고시간주기를 0.5초로지정하였다. #include AnalogIn AnalogIn "mbed.h" x_axis(pc_2); y_axis(pc_3); int x, y; Ticker joystick; void joystick_handler() { x = x_axis.read() * 1000; y = y_axis.read() * 1000; printf("\rx=%3d, Y=%3d \n", x,y); int main() { joystick.attach(joystick_handler, 0.5); 187
while(1) { 프로그램실행을위해새로운프로젝트 joystick_ticker 를생성하고, src 폴더의 main.cpp 파일을위의소스코드로편집한다. 프로그램을컴파일하고실행파일을다운로드하여동작을확인해보면다음과같은화면출력을확인할수있을것이다. 그림 11.9 조이스틱프로그램실행결과 188
12. 동기식시리얼통신 12.1 동기식시리얼통신 (Synchronous Serial Communication) 통신은일반적으로시리얼 (Serial, 직렬 ) 통신과페러럴 (Parallel, 병렬 ) 통신으로구분한다. 병렬통신에서는데이터전송에데이터비트각각에대한연결선와동기화및제어를위한연결선이필요하다. 병렬통신은데이터전송속도는빠르지만각각의장치들을연결하기위해많은수의연결선이필요하다. 이는통신을위해많은수의입출력핀을사용하여야한다는것을의미하며, 이로인해 8-bit 마이크로컨트롤러를비롯하여 16-bit 나 32-bit 장치에서도외부와의입출력을위한핀의활용성이저하된다. 병렬통신의대안중하나가시리얼 (= 직렬 ) 통신이다. 비트단위로데이터비트들을차례로보내기때문에데이터전송에는하나의연결선만으로충분하며, 접지나동기화그리고제어를위해추가로몇개의연결선이더필요하다. 시리얼통신에서크게 2가지통식방식이있다. 그림 12.1과같이클럭펄스에동기화하여데이터비트를보내는동기식시리얼통신 (synchronous serial communication) 과클럭펄스를사용하지않고약속된일정한속도로데이터비트를전송하는비동기식 (Asynchronous) 시리얼통신이다. 비동기식시리얼통신은 6장의 PC와통신기능을구현할때에살펴보았으며, 이번장에서는동기식시리얼통신에대해서살펴본다. 동기식시리얼통신은클럭을동기신호로사용하는데, 데이터를보내지않을때는클럭라인에변화가없지만클럭펄스가발생할때마다하나의비트가송신기에서출력되고수신기에서는읽혀진다. 일반적으로수신기는클럭엣지 (Clock Edge) 와동기화하여데이터를읽어드린다. 그림 12.1의점선은상승엣지 (rising edge) 에서의동기화전송예를보여준다. 그림 12.1 동기식시리얼통신에서의데이터전송동기화그림 12.2는동기식시리얼통신의데이터링크 (data link) 를간단하게보여주고있다. 데이터링크에연결된각각의장치를노드 (node) 라부른다. 그림에서 Node 1은마스터 (Master) 로지정되어있으며마스터는클럭을제어한다. Node 2는슬레이브 (Slave) 라고하며, 슬레이브는마스터와유사하지만마스터의클럭신호를수신한다. 189
그림 12.2 동기식시리얼통신의신호선연결 n 시프트레지스터 (Shift Register) 시프트레지스트는플립플롭 (flip-flop) 들이일렬로연결되어만들어진다. 하나의플립플롭출력은그다음플립플롭입력으로연결된다. 플립플롭하나는 1 비트의정보를담아둘수있으며, 클럭펄스에맞추어각각의플립플롭이비트하나를옆으로전달하고반대쪽에서는새로운비트를입력받는다. 시프트레지스터의모든플립플롭에담긴데이터는병렬워드 (parallel word) 처럼동시에읽거나 (read) 새로운값을저장 (load) 할수있다. 요약하면시프트레지스터는엄청나게유용한하위시스템으로서직렬데이터를병렬데이터로변환하거나그반대로동작할수있으며, 직렬송신기및수신기로동작할수있다. 예를들어, 그림 12.2의시프트레지스터가 8-bit라고하면각각은 8개의플립플롭을갖는다. 각레지스터는새로운워드하나가저장 (load) 되고마스터는 8개의클럭펄스를발생시킨다. 각각의클럭펄스마다데이터한비트가출력단 (SDO, Serial Data Output) 을통해다른레지스터의입력단 (SDI, Serial Data Input) 으로이동한다. 따라서 8개의클럭사이클후에는마스터의시프트레지스터에있던워드가슬레이브로이동하고, 슬레이브의워드는마스터에이동하게된다. n 시프트레지스터종류및데이터전송원리 시프트레지스터의종류와각각의동작방식을그림으로살펴본다 ( 그림 12.3~ 12.8). 그림 12.9는널리사용되는시프트레지스터 IC 사례를보여주고있다. 일반적으로이러한동작회로대해마스터동작회로는마이크로컨트롤러내부에내장되어있으며, 슬레이브동작회로는다른마이크로컨트롤러나기타주변장치에위치한다. 보통시리얼데이터를보내거나받는마이크로컨트롤러내부의하드웨어회로와외부와의인터페이스를통칭하여시리얼포트 (serial port) 라부른다. 190
그림 12.3 시프트레지스터의데이터이동개념도 그림 12.4 4-bit SIPO(Serial-in to Parallel-out) 시프트레지스터구조 그림 12.5 4-bit SIPO 시프트레지스터의데이터입력및출력 191
그림 12.6 4-bit SISO(Serial-in to Serial-out) 시프트레지스터구조 그림 12.7 4-bit PISO(Parallel-in to Serial-out) 시프트레지스터구조 그림 12.8 4-bit PIPO(Parallel-in to Parallel-out) 시프트레지스터구조 그림 12.9 4-bit Universal Shift Register 74LS194 192
12.2 SPI(Serial Peripheral Interface) 통신 SPI 통신은그림 12.2와같은형태의동기식시리얼통신방식이다. National Semiconductors 와 Motorola 는그림 12.2와유사한형태의간단한시리얼통신규약을개발하고, 이것과인터페이스할수있는장치를개발할수있도록외부에공개하였다. 그리하여많은장치개발회사들이이통신방식을채택하여사용함에따라동기식시리얼통신의하나의표준으로자리잡았다. SPI(Serial Peripheral Interface) 라는용어는 Motorola 에서처음사용했고, National Semiconductors 에서는이를 Microwire 라불렀지만둘은매우유사했다. SPI 통신은주로하나의장치내부와같이주로짧은거리의시리얼통신에적용한다. SPI 통신은마이크로컨트롤러가마스터로지정되어모든동작을제어한다. 마스터는하나이상의슬레이브들과통신한다. 마스터는클럭을생성하고모든데이터전송을제어한다. 한장치의 SDO는다른장치의 SDI에연결된다. 1개의클럭이발생할때마다마스터에서슬레이브로하나의비트가이동하고, 반대로슬레이브에서도마스터로하나의비트가이동한다. 8개의클럭사이클후에는한바이트가이동하게되며데이터가양방향으로전달된다. 이런방식을풀듀플렉스 (full duplex) 라고한다. 슬레이브가하나이상인경우, 그림 12.10과같이연결된다. 임의의시점에는단하나의슬레이브만이활성화되는데이는마스터가어떤슬레이브셀렉터 (Slave Select, SS) 라인을선택하느냐로결정된다. SS 라인을 0으로 write하면해당라인이활성상태가된다 (Low Active). SS 입력에의해활성화된슬레이브만이클럭신호에응답한다. 슬레이브는임의의시점에하나만활성화된다. 마스터는활성화된그하나의슬레이브와통신한다. 만일 n개의슬레이브가있다면마스터는 n+3개 (n개의 SS, SCK, SDO, SDI) 의라인을필요하다. 시리얼통신의장점중하나가연결되는연결선수가적다는것인데슬레이브의수가많아지면이런장점이사라지게된다. 그림 12.10 SPI 통신에서마스터와슬레이브연결 193
12.3 SPI 마스터설정 다음프로그램은 MBED 라이브러리에서 SPI 통신을위해 SPI 마스터를설정하는예를보여주고있다. 마이크로컨트롤러는일반적으로여러개의 SPI 통신포트를지원하므로사용할 SPI 포트를골라 SPI 데이터타입으로선언한다. 예제프로그램에서는 SPI 포트에 ser_port 라는이름을지정하고관련핀들과함께초기화한다. SPI 포트를활용하는제어함수는표 12.1를참조한다. #include "mbed.h SPI ser_port(pb_15, PB_14, PB_13); // mosi, miso, sclk char switch_word; int main() { ser_port.format(8, 3); // 8 bit data, Mode 3 ser_port.frequency(2000000); // 2MHz clock rate while (1) { switch_word = 0xA1; // 전송될 word 설정 ser_port.write(switch_word); // switch_word 전송 wait_us (50); 표 12.1 SPI 포터제어관련함수 함수 SPI format frequency write 사용예 지정된핀으로연결된 SPI Master 생성 데이터전송데이터길이와모드설정 SPI 버스클럭주파수설정 SPI 슬레이브로데이터를전송하고응답을받아반환 SPI 통신방식을지정하는 format() 함수는두개의매개변수를필요로하며, 첫 번째매개변수는비트수이고두번째매개변수는모드 (mode) 이다. 모드에대한내 용은그림 12.11 을참조한다. 194
그림 11.11 SPI 설정에서의데이터전송동기화모드 Mode에서 polarity가 low이면 SCK가 low일때, 그리고 polarity가 high이면 SCK가 high일때, 의미있는신호가출력된다는뜻이다. 또한, Phase가 low이면 SCK의첫번째 edge에서신호를수신하고, Phase가 high이면 SCK의두번째 edge에서신호를수신한다는의미이다. Master 모드에서 Serial Data Out(SDO) 는 Slave 에서 Serial Data In(SDI) 와동일한 핀이사용된다. Master 의 MISO(Master In Slave Out) 에대응되는 Slave 핀은 MOSI(Master Out Slave In) 핀이다. 11.4 가속도센서 (Accelerometer) SPI 통신표준이소개된지꽤나오래되었음에도불구하고간단하게시리얼통 신을구현할수있어여전히광범위하게사용되고있으며, 거의모든전자장비나 IC 칩그리고유용한툴등에내장되고있다. 195
최근 IC칩에대한집적도가높아짐에따라센서, 제어장치, ADC(analog-to-digital) 그리고데이터인터페이스와같은기능들을단일칩내에집적화하는것이일반적인경향이되었다. 이같은장치들은새로운세대의지능형센서또는지능형측정장치 (Intelligent Instrumentation) 라한다. Analog Device사의 ADXL345 가속도센서는지능형센서의한종류이다. 이센서는 MEMS(Micro Electro Mechanical System) 기술을활용하여제작한센서의한예로서, 칩안에가속도측정기구 (accelerometer mechanics) 가매우작은크기로구현되어있다. 가속도계의출력은아날로그신호이며가속계의 x, y, z 세축에대한가속도를측정한다. 그림 12.12 3축가속도센서와자이로센서가속도계내부의각축을구성하는판에캐패시터가장착되어있다. 가속도는캐패시터판을움직여서가속도혹은힘에비례하는출력전압을발생시킨다. ADXL345 가속도계는아날로그전압변화를디지털로변환하고 SPI 시리얼링크를통해신호를외부로내보낸다. 그림 12.13 가속도센서의작동원리 그림 12.14 ADXL345 가속도센서모듈및핀사양 196
표 12.2 ADXL345 가속도센서의레지스터사양 표 12.3 ADXL345 가속도센서모듈의핀연결사양 ADXL345 시그널이름 VCC GND SCL MOSI(Master Out Slave n) MISO CS +3.3V GND PB_13 PB_15 PB_14 PB_1 핀 다음프로그램은 ADXL345 가속도센서의 3축에대한가속도를읽어서호스트 PC의화면에출력한다. 프로그램에서는 acc라는이름으로마스터 SPI 포트를초기화하고있다. 여기서두개의배열을선언하고있는데, buffer 배열은가속도계의레지스터에서직접읽은데이터를저장하고, data 배열은각축에대한데이터값을저장하기위한것이다. data 배열은 int16_t 데이터타입으로 16-비트정수를저장하도록선언되어있는데, 각각의축에대해서읽은두바이트값들을묶어서가속도계값으로저장하기위함이다. 197
main 함수는 SPI 포트를초기화하고, 데이터를출력하기위해서는먼저데이터를저장할가속도계레지스터의주소를출력한다음, 저장할데이터를출력한다. while 반복문에서는 multi-byte read 모드를설정하고데이터를읽어 buffer 배열에채운후에 data 배열에 buffer 배열의두바이트를연결하여저장한다. 그런다음이값들을실제중력가속도 (g) 값으로공식에의해변환한다. #include "mbed.h" Serial pc(usbtx, USBRX); // set up USB interface to host terminal SPI acc(pb_15, PB_14, PB_13); DigitalOut cs(pb_1); char buffer[6]; //raw data array type char int16_t data[3]; // 16-bit twos-complement integer data float x, y, z; // floating point data, to be displayed on-screen int main() { cs=1; // initially ADXL345 is not activated acc.format(8, 3); // 8 bit data, Mode 3 acc.frequency(2000000); // 2MHz clock rate cs=0; // select the device acc.write(0x31); // data format register acc.write(0x01); cs=1; // end of transmission cs=0; // start a new transmission acc.write(0x2d); // power ctrl register acc.write(0x08); // measure mode cs=1; // end of transmission while (1) { // infinite loop wait(0.2); cs=0; // start a transmission acc.write(0x80 0x40 0x32); // RW bit high, MB bit high, // plus address for (int i = 0;i<=5;i++) { buffer[i]=acc.write(0x00); // read back 6 data bytes cs=1; //end of transmission data[0] = buffer[1]<<8 buffer[0]; // combine MSB and LSB data[1] = buffer[3]<<8 buffer[2]; data[2] = buffer[5]<<8 buffer[4]; 198
// convert to float, actual g value x=0.0078*data[0]; y=0.0078*data[1]; z=0.0078*data[2]; pc.printf("x = %+1.2fg\t y = %+1.2fg\t z = %+1.2fg\n\r", x, y, z); 프로그램실행을위해새로운프로젝트 accel_sensor 를생성하고, src 폴더 의 main.cpp 파일을위의프로그램소스로편집한다. 프로그램을컴파일하고다 운로드하여실행한다. 그러면, 가속도계에서읽은값을화면에표시할것이다. 그림 12.15 가속도센서프로그램실행결과실습장비를회전하거나움직이면 g값도변하게된다. 만약가속도계를심하게흔들거나빠르게움직이면 1g를초과하는값이관찰될수있다. 가속도계에서읽은데이터가다소부정확하다는점에유의하기바란다. 실제어플리케이션에서는 configuration/calibration 루틴의개발과데이터평균방법등으로오차를보정하여사용한다. 12.5 I2C(Inter-Integrated Circuit) 통신 앞절에서 SPI 표준이굉장히효율적이고하드웨어가간단하여가격이저렴하며데이터를빠르게전달한다는것을살펴보았다. 그렇지만단점도있다. 수신하는쪽에서데이터를잘받았다는응답 (acknowledgement) 이없고, 주소를지정할수도없다. 슬레이브가여러개있는시스템이라면각각의슬레이브에대해별도의 SS 신호라인이있어야한다. 이렇게되면연결할수있는슬레이브의수가제한되기때문에시리얼통신본연의장점을잃게된다. 마지막으로에러를체크하는기능이없다. 일부전자장치가먼거리를통해연결된경우를생각해보면데이터와클럭값이변질될우려가있지만시스템에서는이를검출하거나수정할방법이없게된다. 따라서 199
SPI는간단하고, 편리하며, 저렴하지만복잡하거나높은신뢰성이요구되는시스템에는적절치않다. I2C(Inter-Integrated Circuit) 표준은이같은 SPI 표준의단점을극복하기위해서 Philips 에서개발되었다. 이름이의미하는것처럼짧은거리, 일반적으로는단일장치내에서의연결을목표로한다. I2C는단 2개의연결선만으로전송버스를구성하지만 128개의많은장치들이이버스에연결될수있다. 이들연결선을각각 SCL(Serial Clock) 과 SDA(Serial Data) 라고한다. 버스상의모든장치들은그림 12.16처럼이두개의연결선에연결된다. 그림 12.16 I2C 통신의연결구조 SDA 연결선은양방향이어서데이터는임의의시점에어느방향으로든갈수있다. 이를반이중 (half duplex) 통신이라한다. SCL 연결선은클럭펄스를전송하는선으로서 I2C 표준은클럭을사용하는동기식시리얼통신표준이다. I2C의특징중에서흥미로우며기능을풍부하게만들어주는것중하나는버스에연결된어떤노드 (node) 도 SCL 혹은 SDA 라인을로직 0으로만들수있다는것이다. 그러나로직 1로강제할수는없다. 이역할은각라인에연결된풀업저항 (pull-up resistor) 에의해수행된다. 노드가라인을로직 0으로끌어내리고이를릴리스 (release) 하면풀업저항은로직 1로되돌려놓는다. 그런데여기에는라인과관련된캐패시터성분이존재한다. 그림 12.16에서 stray 라는이름을붙였지만라인에연결되는반도체구조에서존재하는현실적으로피할수없는성분이다. 이캐패시터는라인에연결되는장치의수가증가할수록커지게된다. 캐패시터혹은풀업저항성분이커질수록 0에서 1로로직값이바뀌는 rise time이길어지게된다. I2C 표준에서는 SCL과 SDA의 rise time을 1000ns 이하로규정하고있다. 풀업저항의크기를매우정확하게계산하는것은가능한일이다. 간단한어플리케이션에서는 2.2~4.7 K Ω 정도면적당하다. 200
I2C 표준은몇번의개정이있었으며, 속도의증가와많은기술적변화를반영했다. 예를들어, 최소동작전압이줄어들었고, 1992년의표준 1.0에서데이터전송속도는 100 kbit/s였던것이최대 400 kbit/s로증가했다. 뒤에나온이버전은매우잘구성되어있어서지금도대부분의 I2C 구현에적용되고있다. 1998년의버전 2.0은 3.4 Mbit/s까지속도가증가했다. I2C 버스상의노드들은마스터혹은슬레이브로동작할수있다. 마스터는전송을개시하고종료한다. 그리고클럭을생성한다. 슬레이브는마스터에의해주소로지정된임의의장치이다. 시스템은비록특정시점에는단하나의마스터만존재해야하지만하나이상의마스터를가질수있다. 그러므로하나이상의마이크로컨트롤러가이버스에연결되어각기다른시점에마스터역할을요청할수있다. 하나이상의마스터가버스를제어하려들면정의된조정과정 (arbitration process) 이수행된다. 데이터전송은마스터가전송시작을알리는신호 (start condition) 을보냄으로써이루어진다. 이어서주소와제어정보를포함하는하나혹은두개의바이트가뒤따라온다. 시작조건은 SCL이 high일때, SDA가 high에서 low로이동하는것으로정의되고이어서전송될유효데이터가그림 12.17처럼실리게된다. 각데이터비트에대해클럭펄스하나가만들어지며, 클럭이 low일때데이터비트값이변할수있다. 시작조건다음에오는바이트는그림 12.18처럼 7개의주소비트와한개의입출력방향비트도구성된다. 그림 12.17 I2C 통신의시작, 종료, 데이터전송조건 그림 12.18 I2C 통신의주소및데이터전송 201
슬레이브는미리정의된장치주소를가지므로버스를모니터링해서자신의주소와연관된명령에대해서만응답한다. 자신의주소를인지한슬레이브는데이터를수신하거나버스에데이터를전송할준비를하게된다. 10-비트주소모드도이용할수있다. 전송되는모든데이터는한바이트안에있어야하고하나의메시지에서전송되는바이트수에는제한이없다. 각각의바이트는다음에는수신측에서한비트의 ACK(acknowledgement) 신호가뒤따라와야한다. SCL이 high인동안 SDA가 low에서 high로바뀌면종료조건 (stop condition) 이된다. MBED 라이브러리에서 I2C 마스터와슬레이브 API는각각표 12.4와표 12.5를참조하도록한다. 표 12.4 MBED 라이브러리의 I2C 마스터 API 표 12.5 MBED 라이브러리의 I2C 슬레이브 API 만약 I2C 버스에연결된두대의하드웨어장치가있다면각각을마스터와슬레이브로설정한후, 위의표에서예시한 API를이용하여작성된프로그램을구동하고동작을확인할수있다. 다음 2개의프로그램은각각 I2C 통신에서마스터기능을수행하는프로그램과슬레이브기능을수행하는프로그램이다. 먼저마스터프로그램에서는 I2C의포트이름을 i2c_port 로하고 SDA와 SCL 핀으 202
로는 D14와 D15를사용하는 I2C 객체 (instance) 를생성한다. 슬레이브의주소는임의로 0x52로했으며, switch_word 에전송할데이터를저장한다. 데이터는스위치입력값 2개로구성한다. 데이터전송은 1-바이트 I2C 데이터전송, 즉 start send address send data stop과같은방법을사용하고있다. 프로그램을추적 (trace) 하다보면유사한메시지구조로슬레이브에서데이터바이트를요청하는것을볼수있다. 수신받은데이터는 LED를켜기위해사용된다. 슬레이브주소는 0x01 과 OR 연산을실행하여 Read 를수행하도록주소에 R/W 비트 를설정한다. #include "mbed.h" I2C i2c_port(d14, D15); DigitalOut red_led(pa_4); DigitalOut green_led(pa_13); DigitalIn switch_ip1(pa_14); DigitalIn switch_ip2(pb_7); // configure SDA, SCL // red led // green led // input switch char switch_word ; // word we will send char recd_val; // value received from slave const int addr = 0x52; // slave address, an arbitrary even number int main() { while(1) { switch_word=0xa0; // set up a recognizable output pattern if (switch_ip1==1) switch_word=switch_word 0x01; //OR in LSB if (switch_ip2==1) switch_word=switch_word 0x02; //OR in next LSB //send a single byte of data, in correct I2C package i2c_port.start(); //force a start condition i2c_port.write(addr); // send the address i2c_port.write(switch_word); // send one byte of data i2c_port.stop(); // force a stop condition wait(0.002); //receive a single byte of data, in correct I2C package 203
i2c_port.start(); i2c_port.write(addr 0x01); //send address, with R/W bit set to Read recd_val=i2c_port.read(addr); //Read and save the received byte i2c_port.stop(); //force a stop condition //set leds according to incoming word from slave red_led=0; //preset both to 0 green_led=0; recd_val=recd_val&0x03; //AND out unwanted bits if (recd_val==1) red_led=1; if (recd_val==2) green_led=1; if (recd_val==3) { red_led=1; green_led=1; 슬레이브프로그램은다음과같이작성할수있다. SPI와마찬가지로 I2C 슬레이브는마스터의호출에만응답한다. I2CSlave 타입으로이름이 slave인슬레이브포트와핀을정의하고있다. main 함수안에서만슬레이브의주소가정의되고마스터프로그램에서봤던것과동일한 0x52가사용되었다. 동일하게 switch_word 값은 switch 의상태에따라설정되고, 마스터의요청에대한준비로 write 함수를사용하여값을저장한다. I2C로수신되었는지체크하기위해서 receive() 함수가사용된다. 만약해당슬레이브의주소가아니면 0을, read 주소가지정되면 1을, 그리고 write 주소가지정되면 3을반환한다. 만약, read라면이미저장되어있는값이자동으로전송된다. 만약그값이 3이라면프로그램은수신된데이터를저장하고 LED를동작시킨다. #include <mbed.h> I2CSlave slave(d14, D15); //Configure I2C slave DigitalOut red_led(pa_4); // red led DigitalOut green_led(pa_13); // green led DigitalIn switch_ip1(pa_14); // input switch DigitalIn switch_ip2(pb_7); 204
char switch_word ; //word we will send char recd_val; //value received from master int main() { slave.address(0x52); while (1) { //set up switch_word from switches that are pressed switch_word=0xa0; //set up a recognizable output pattern if (switch_ip1==1) switch_word=switch_word 0x01; if (switch_ip2==1) switch_word=switch_word 0x02; slave.write(switch_word); //load up word to send //test for I2C, and act accordingly int i = slave.receive(); if (i == 3) { // slave address && master write recd_val= slave.read(); //set leds according to incoming word from slave red_led=0; //preset both to 0 green_led=0; recd_val=recd_val&0x03; //AND out unwanted bits if (recd_val==1) red_led=1; if (recd_val==2) green_led=1; if (recd_val==3) { red_led=1; green_led=1; 프로그램실행을위해 2 대의실습장비를 I2C 포트 (D14, D15 핀 ) 를이용하여연결한다음, 한대에는마스터동작프로그램을, 한대에는슬레이브동작프로그램을각각편집, 컴파일, 다운로드하여실행하도록한다. 그리고실습장비의버튼스위치를눌려상대편실습장비의 LED가동작하는지확인한다. 205
12.6 기압센서 (Barometric Pressure Sensor) 앞서 I2C 버스프로토콜의인터페이스방식을살펴보았다. 이번에는기압센서를이용해대기압을측정하는실습을수행한다. 측정된값들은 I2C 버스인터페이스를통해마이크로컨트롤러로전송하게된다. 실습에서사용하는 BMP180( 또는 BMP085) 압력센서는 Bosch사에서제작된센서로대기압과온도를측정하는저렴한가격의센서이다. BMP180 센서는 I2C 버스인터페이스를통해마이크로컨트롤러에직접연결할수 있도록설계되어있으며, 압력과온도데이터는 BMP180 의 EEPROM 에저장된데이 터를보정해서사용한다. 고도가변하면대기압이변하기때문에고도계로서도사용할수있다. 또한 300 에 서 1100hPa 범위의압력을정확하게측정할수있는저전력디지털기압계로서높은 정밀도와안정성을갖추기위해압전저항기술 (Piezo-resistive technology) 을사용한 다. BMP180 압력센서는그림 12.19 와같이압전저항센서 (piezo-resistive sensor), ADC(analog to digital converter), EEPROM 제어로직과 I2C 버스인터페이스로구성 되어있다. 마이크로컨트롤러에서압력또는온도측정을시작하기위해압력센서에 start sequence 를보내면변환시간을거친후에 I2C 버스를통해 EEPROM 으로부터결과를 읽어올수있다. 동적측정을위한샘플링비율을표준모드 (standard mode) 에서초 당 128 개까지증가시킬수있지만, 이번실습에서는초당한번압력과온도를측정 하는것으로한다. 그림 12.20 은 BMP180 압력센서로부터압력및온도데이터를읽어와대기압과 온도를계산하는절차를나타낸것이다. 여기서, UP 와 UT 는각각다음을의미한다. UP = pressure data(16 to 19 bit) UT = temperature data(16 bit) 고도측정은센서로부터측정된압력 (Pa) 과해수면의기준압력 1013.25 hpa 를이 용하여아래공식으로계산할수있다. 206
그림 12.19 BMP180 압력센서의구조 그림 12.20 BMP180 압력센서의동작순서 207
그림 12.21 BMP180 압력센서의압력과고도사이의관계 BMP180 압력센서는입력전압으로 1.8V에서 3.6V를사용하지만실습에서는외부회로를덧붙여 3.3V와 5V사이의입력전압에동작할수있도록만들어진모듈형식의제품을사용한다. 실습에서사용하는압력센서모듈의외형과연결회로도는그림 12.22와같다. 그림 12.22 압력센서모듈의외형및연결회로도압력센서모듈의주요특징은다음과같다. 2-wire I2C 인터페이스 광범위한압력측정범위 저전력 -40 C ~ +85 C 동작범위, 허용오차 +/- 2 C I2C 주소 : 0x77 208
1Pa ( 파스칼 ) = N (newton)/m 2 = Kg*m/s 2 /m 2 = kg/(m*s 2 ) = 0.01hPa 기압센서는네비게이션, 등산, 레저스포츠, 날씨등에이용할수있다. 우리가 사용하는스마트폰에도기압센서가적용되어있다. 표 12.6 은기압센서의주요사양 을나타낸것이다. 표 12.6 BMP180 압력센서모듈의주요사양 속성 전압 3 ~ 5.5 V (Typical 5V) 전류 기압 I2C 통신속도 1.1 ~ 20 ua 값 300 ~ 1100 hpa 최대 3.4MHz 다음프로그램은압력센서에서측정된값들을 I2C 버스인터페이스를통해마이 크로컨트롤러에서읽어들여화면에표시한다. 측정값은온도와기압그리고고도이 다. 단위는간단한계산을통해변경할수있다. 프로그램에서사용되고있는 API 는 BMP180.h 파일에정의되어있다. #include <stdio.h> #include "mbed.h" #include "BMP180.h" I2C i2c(i2c_sda, I2C_SCL); BMP180 bmp180(&i2c); Serial pc(usbtx, USBRX); int main(void) { pc.baud(115200); while(1) { if (bmp180.init()!= 0) { pc.printf("error communicating with BMP180\n"); else { pc.printf("initialized BMP180\n"); break; wait(1); 209
while(1) { bmp180.starttemperature(); wait_ms(5); // Wait for conversion to complete float temp; if(bmp180.gettemperature(&temp)!= 0) { pc.printf("error getting temperature\n"); continue; bmp180.startpressure(bmp180::ultra_low_power); wait_ms(10); // Wait for conversion to complete int pressure; if(bmp180.getpressure(&pressure)!= 0) { pc.printf("error getting pressure\n"); continue; pc.printf("pressure = %d Pa, Temperature = %.1f C\n", pressure, temp); // Calculate altitude in meters float altitude; altitude = 44330.0f*( 1.0f - pow((pressure/102710.0f), (1.0f/5.255f))); pc.printf("altitude is %.1f m\n\r", altitude); wait(1); 위의프로그램에서사용한 BMP180.h 는 BMP180 압력센서의장치드라이브소스로서다음의사이트에서다운로드받을수있다. https://os.mbed.com/users/kgills/code/bmp180/ 프로그램실행을위해새로운프로젝트 barometric_sensor 를생성하고, src 폴더의 main.cpp 파일을위의프로그램소스로편집한다. 프로그램을컴파일하고다운로드하여실행한다. 그러면고도, 기압, 그리고온도값을측정해서터미널화면에출력하는것을볼수있을것이다. 210
그림 12.23 압력센서프로그램실행결과 211
13. MBED 장치드라이버활용 이번장에서는 MBED 라이브러리또는 MBED 커뮤니티에서지원하는다양한장치드라이버를활용하여장치를제어하는방법에대해살펴본다. 임베디드시스템의입출력장치들중에는복잡한입출력동작으로인해인터페이스방식이상대적으로복잡한장치들이있다. 이러한장치들을제어하기위해직접제어코드를작성할수있지만구현이어렵거나시간을많이요구된다. 반면 MBED 커뮤니티에서이들을위한장치드라이버를지원하고있어이를활용하면간단하게제어프로그램을작성할수있다. 실습장비에서지원하는몇몇장치에대해 MBED 장치드라이버를이용하여제어프로그램을작성하는방법을실습하도록한다. 13.1 온도 - 습도센서 온도는물체의차고뜨거운정도를수치로나타낸것이다. 온도는물리, 화학, 전자, 기계, 생체시스템등에서중요한물리량이다. 온도는직접측정이불가하고변위, 압력, 저항, 전압, 주파수등의다른물리량으로변환하여계측한다. 온도의종류는섭씨 (Celsius 혹은 Centigrade scale), 화씨 (Fahrenheit scale) 그리고절대온도 (Kelvin 혹은 absolute temperature) 의세가지가있다. 섭씨온도는얼음이녹는점을 0 C 물이끓는점을 100 C 로하여그사이를 100등분한단위이다. 화씨온도는얼음이녹는점을 32 F 물이끓는점을 212 F 로하여사이를 180등분한온도단위이다. 절대온도는물질의저항이 0이되는영하 273.15 C 를기준으로하며섭씨와같은간격으로눈금을붙인온도단위이다. 온도를측정하는센서는열전도 (thermal conduction) 에의해측정하는접촉식과열방사 (radiation) 에의해측정되는비접촉식이있다. 접촉식의경우열에너지가미약한대상에는부적절하며, 비접촉식은멀리떨어진물체의경우에온도측정이가능하긴하지만광학계와여러보조장치가필요하다. 습도는공기중에수증기가포함된정도를나타내며공기의건습정도를표현한다. 대부분습도라고하면상대습도를말한다. 대기중수증기량의표현법으로상대습도이외에혼합비, 수증기압, 비습 ( 比濕 ), 이슬점온도등을사용한다. 상대습도는대기중에포함되어있는수증기의양과그때의온도에서대기가함유할수있는최대수증기량 ( 포화수증기 ) 의비를백분율로나타낸것으로 R(%) = f/f * 100이다. 여기서, f는대기중의수증기압, F는그때의포화수증기압, R는상대습도이다. 습도센서는습도에반응하여전류의흐름이영향을받는현상을이용하여측정 212
한다. 습도센서의종류로는정전식 (Capacitive Type) 과저항식 (Resistive Type) 이있다. 정전식은직류전류에대한저항이매우높으며, 교류저항이센서의출력이된다. 저항식은직류저항이센서출력값이된다. 이번실습에서사용하는온습도센서는 RHT-02/DHT-22 센서로서온도와습도를측정하여 1-wire 프로토콜을이용하여온습도값을출력한다. 이센서로측정할수있는습도는 0~100%RH 이며, 온도는 -40~80 이다. 비교적저렴한가격에이용할수있는센서로정확도는다소떨어진다. DHT-22 온습도센서의주요특성은표 13.1에서요약하여제시하고있다. 그림 13.2는 DHT-22 온습도센서의외형과연결핀구성을, 그림 13.3의회로도는마이크로컨트롤러와연결구조를보여준다. 연결회로도에알수있듯이 DHT-22 온습도센서는하나의핀을통해데이터를출력한다. 표 13.1 DHT-22 온습도센서의주요특성속성값동작전압 / 전류 3.6 ~ 6V 습도 0 ~ 100% RH ( 허용오차 2%) 온도 -40 ~ 80 ( 허용오차 0.5 ) 측정간격 2초이하 그림 13.1 DHT-22 외형및연결핀구성 그림 13.2 DHT-22 센서의연결회로도 213
온습도센서가온도와습도를검출하여출력하는방법은종류마다다르다. 통신을위해 I2C 또는 SPI와같은표준규격을지원하기도하지만 DHT-22 온습도센서는하나의통신라인을통해 40-비트디지털값을출력하는독자적인통신프로토콜을사용한다 ( 그림 13.3). 그림 13.3 DHT-22 1-wire 통신프로토콜 ( 발췌 : https://www.waveshare.com/wiki/dht22_temperature-humidity_sensor) DHT-22 의 1-wire 통신프로토콜에대한자세한내용은여기서다루지않으므로센서의데이터시트를참조하도록한다. 그림 13.3에서제시된통신프로토콜을구현하기위해서는비교적정밀한신호타이밍제어가필요하며, 받은데이터를분석하여원하는값을얻기위한후처리과정이요구된다. 이에이번실습에서는다음의 MBED 커뮤니티 URL 페이지에서지원하는 DHT-22 장치드라이버를활용하도록한다. https://os.mbed.com/users/chiang404/code/dht22/ DHT-22 센서를활용하여온도와습도를측정하여출력하는실습을위해새로운 프로젝트 dht_22_temperature 을생성하고, 위의 URL 사이트에서 DHT22.h 헤 드파일과 DHT22.cpp 소스파일을다운로드하여프로젝트의 src 그룹에저장 한다. 소스관리를위해별도의소스그룹을생성하여저장하는방법도있지만실습 의편의를위해그냥 src 그룹에저장하는것으로한다. DHT-22 장치드라이버가지원하는기능을살펴보기위해 DHT22.h 헤드파일 의내용을살펴보면다음과같다. #ifndef MBED_DHT22_H #define MBED_DHT22_H #include "mbed.h" class DHT22 { private: int _temperature,_humidity; 214
PinName _data_pin; public: DHT22(PinName); bool sample(); int gettemperature(); int gethumidity(); ; #endif 위의소스에서 DHT-22 장치드라이버에서는간단하게온도와습도를읽을수있는 DHT22 클래스타입을정의하고있음을알수있다. 그래서이장치드라이버를이용하여온도와습도를읽어터미널화면에출력하는프로그램을다음과같이작성할수있다. #include "mbed.h" #include "DHT22.h" #include <stdio.h> DHT22 sensor (PB_2); Serial pc(usbtx, USBRX); int main() { float h=0.0f, c=0.0f; int Temp, Humid; while(1) { wait(2); // 측정간격 2 초 sensor.sample(); c = sensor.gettemperature()/10.0; h = sensor.gethumidity()/10.0; Temp = (int)c; Humid = (int)h; pc.printf("temp: %d, Humid: %d \r\n", Temp, Humid); 215
src 그룹의 main.cpp 파일을열고위의프로그램소스로편집한다. 프로그 램을컴파일하고다운로드하여실행한다. 그러면터미널화면에서다음그림과유사 하게출력되는것을확인할수있다. 그림 13.4 온습도센서프로그램실행결과여기서하나유의할점은 ARM Cortex-M 마이크로컨트롤러내부에서지원하는입출력장치에대해서는 MBED 라이브러리에서공식적인장치드라이버를지원하지만외부의다양한장치에대한장치드라이버는 MBED 커뮤니티에참여하고있는여러개발자들에의해지원되는오픈소스의장치드라이버이다. 따라서이들장치드라이버중에서용도에맞는것을적절하게선택하여사용하는것이가능하나성능에대한부분이검증되어있지않으므로이에유의하여사용하여야한다. 13.2 심박센서 (Heart Rate Sensor) 실습에서심박센서로사용하는펄스센서는본질적으로는심장박동을모니터하는의료장치로서잘알려진맥파 (PPG, photo plethysmograph) 를측정하는장치이다. PPG는때로혈액의산소포화도 (SpO2, Saturation of percutaneous oxygen) 를측정하는데사용되기도한다. 이번실습에서사용하는펄스센서와 PPG로측정되는심장박동신호는그림 13.5와같다. 작동원리는적외선 LED에서방출하는적외선이혈관압력이변화함에따라수신부에서미세하게전압이변한다. 이러한변화를측정하여심박측정을할수있다. 미약한적외선을감지하여수신하는회로이므로외부간섭에의해노이즈가발생될수있으니이를감안하여사용하여야한다. 216
그림 13.5 펄스센서와측정되는심장박동신호펄스센서는빛의세기에대한상대적인세기에반응한다. 즉, 펄스센서는펄스파형을전압성분의 1/2( 중간값 ) 부근에서정규화 (normalize) 한다. 따라서센서로들어오는빛의양이일정하면신호값은중간값 (1/2) 부근에머물게되고, 빛의양이많아지면위로, 작아지면반대방향으로움직인다. 센서로반사되어들어오는녹색 LED의빛은각펄스구간에서변한다. 이번실습의목적은심장박동의시간간격 (IBI, Inter-Beat Interval) 을측정하는것이다. 심장이몸전체로혈액을공급할때, 매심장박동마다동맥혈관에서부터센서가부착된모세혈관까지충격파가전달된다. 실제혈액은펄스파가전달되는것보다훨씬느리게순환한다. 그림 13.6의 T위치에서부터이벤트를따라가보도록하자. 펄스파가센서아래로지나갈때신호값의급격한상승이일어난다음다시아래로떨어진다. 때때로다른때에비해아래쪽으로더많이떨어지기도하지만일반적으로는다음펄스가지나가기전에신호가안정된다. 그림 13.6 심장박동에따른펄스파형의변화그림 13.6와같이파동이반복되므로각 peak 값간의시간계산을통해심장박동을측정할수있다. 그렇지만다이크로익노치 (dichroic notch or downward spike) 로인해잘못된값을읽어올수도있고센서잡음의영향을받을수도있다. 이와같은영향들이심장박동을찾는알고리즘이안정되지못하게한다. 따라서정확한 217
BPM(Beat Per Minute), HRV(Heart Rate Variability) 그리고 PTT(Pulse Transit Time) 측정등이매우중요해진다. 이것은도전해볼만한충분한가치가있지만이런내용 은학습목표와많이벗어나기때문에이것으로설명을마치도록한다. 그림 13.7 심박센서의연결회로도앞에서설명한것과같이심박센서를이용한심장박동수를측정하기위해서는장치의통신프로토콜뿐만아니라심장박동에측정이필요한사전지식이필요하다. 이에이번실습에서는다음의 MBED 커뮤니티페이지에서지원하는장치드라이버를사용하도록한다. https://os.mbed.com/users/ansond/code/groveearbudsensor/ 심박센서를활용하여심장박동수를측정하여출력하는실습을위해새로운프 로젝트 heart_rate 를생성하고, 위의 URL 사이트에서 GroveEarbudSensor.h 헤드파일과 GroveEarbudSensor.cpp 소스파일을다운로드하여프로젝트의 src 그룹에저장한다. 심박센서의장치드라이버가지원하는기능을살펴보기위해 GroveEarbud Sensor.h 헤드파일의내용을살펴보면다음과같다. #ifndef _GROVE_EARBUD_SENSOR_H_ #define _GROVE_EARBUD_SENSOR_H_ #include "mbed.h" // ********* BEGIN Tunables ***************** #define NUM_SLOTS 6 // set higher for greater accuracy (but slower callback frequency).. 218
#define HEARTPULSE_DUTY 2000 // Change to follow your system's request. System returns error if the duty overtrips by 2 seconds. (in MS) #define HEARTRATE_OFF 0 // earbud sensor is offline #define HEARTRATE_MIN 10 // min heartrate #define HEARTRATE_MAX 250 // max heartrate // ********* END Tunables ***************** // Callback function signature typedef void (GroveEarbudSensorCallback)(float,void *); class GroveEarbudSensor { private: RawSerial *m_pc; InterruptIn *m_rx; volatile unsigned long m_temp[num_slots]; volatile unsigned long m_sub; volatile unsigned char m_counter; volatile bool m_data_effect; float m_heartrate; GroveEarbudSensorCallback *m_cb_fn; void *m_cb_data; Timer *m_timer; bool m_internal_interrupt_instance; public: /** Default constructor @param rx input InterruptIn instance @param pc input RawSerial instance for debugging (if NULL, no debugging output will occur in the library) */ GroveEarbudSensor(InterruptIn *rx,rawserial *pc = NULL); /** constructor for internalized InterruptIn usage @param rx interrupt_pin InterruptIn pin name @param pc input RawSerial instance for debugging (if NULL, no debugging output will occur in the library) */ GroveEarbudSensor(PinName interrupt_pin,rawserial *pc = NULL); 219
/** Default destructor */ virtual ~GroveEarbudSensor(); /** registercallback - Register callback function @param cb_fn - callback function of type GroveEarbudSensorCallback @param cb_data - optional callback data to provide upon callback invocation (default NULL) */ void registercallback(groveearbudsensorcallback *cb_fn,void *cb_data = NULL); /** getheartrate - get the last sampled heartrate @return heartrate - the last calculated heartrate (may also be one of HEARTRATE_OFF, HEARTRATE_MIN, or HEARTRATE_MAX) */ float getheartrate(void); /** interrupt() - interrupt handler for our instance - not normally invoked manually */ void interrupt(void); ; protected: void initsummationarray(void); void sumandinvokecallback(void); #endif // _GROVE_EARBUD_SENSOR_H_ 위의장치드라이버를이용하여심장박동수를측정하여터미널화면에출력하는프로그램은다음과같다. src 폴더의 main.cpp 파일을다음의프로그램소스로편집한다. 프로그램을컴파일하고다운로드하여실행한다. 그러면그림 13.8과같이심장박동수를측정하여터미널화면에출력되는것을확인할수있다. 220
#include #include "mbed.h" "GroveEarbudSensor.h" // Grove Earbud Sensor include DigitalOut led(led1); // Blinky InterruptIn sensor(pb_1); // Our sensor as an InterruptIn // callback for receiving heart rate values void heartratecallback(float heartrate,void *data) { printf("callback: heartrate = %.1f\r\n",heartrate); int main() { // announce printf("grove Earbud Sensor Example v1.0.0\r\n"); // allocate the earbud sensor printf("allocating earbud sensor instance...\r\n"); GroveEarbudSensor earbud(&sensor); // register our callback function printf("registering callback...\r\n"); earbud.registercallback(heartratecallback); // begin main loop printf("beginning main while (true) { // blink... led =!led; wait(0.5); loop...\r\n"); // we can also call directly //printf("direct: heartrate = %.1f\r\n",earbud.getHeartRate()); 221
그림 13.8 심박센서프로그램실행결과 13.3 OLED 디스플레이 이번실습에사용할디스플레이장치는 16 컬러 OLED 모듈로서 I2C 통신을통해제어된다. OLED 디스플레이모듈은 128x64 dot matrix 모듈 (LY120) 과 SSD1306 드라이버칩을사용한다. 그리고밝기와명암비가높고, 얇고가벼우며동작온도범위가넓고저전력에서동작한다. OLED 모듈의주요특징은다음과같다. 96x96 dot matrix pixel Grayscale display (16 gray shades) Normal & Inverse Color Display Continuous Horizontal Scrolling의지원 5V 혹은 3.3V 동작전압 I2C 인터페이스 OLED 모듈의외형과연결구조는다음그림과같다. 그림 13.9 OLED 모듈외형및연결회로도 OLD 디스플레이모듈은많은기능을내장하고있어이를제어하기위한코드작 성은많은시간을요구한다. 이에이번실습에서는다음 URL 페이지에서지원하는 222
장치드라이버를활용하도록한다. https://os.mbed.com/users/nkhorman/code/adafruit_gfx/ 위의사이트에서제공하는장치드라이버를두가지라이브러리로구성되어있다. 하나는 SSD1306 라이브러리로서디스플레이장치를초기화하고직접입출력을수행하는낮은수준의디스플레이함수들을제공한다. 다른하나는 GFX 라이브러리로서텍스트를표시하고, 라인과그림을그려주는그래픽함수들을제공한다. OLD 디스플레이출력을테스트하기위한실습을위해새로운프로젝트 oled_display_test 를생성하고, 위의사이트에서 SSD1306 라이브러리소스 ( Adafruit_SSD1306.h 와 Adafruit_SSD1306.cpp ) 를다운로드하여 src 그룹에저장한다. 다음프로그램은라이브러리에내장된 Adafruit 회사로고이미지를출력하고, 그화면위에숫자값을증가시키면서표시한다. 이를 src 그룹의 main.cpp 파일에편집한다. #include "mbed.h" #include "Adafruit_SSD1306.h" DigitalOut myled(led1); // an I2C sub-class that provides a constructed default class I2CPreInit : public I2C { public: I2CPreInit(PinName sda, PinName scl) : I2C(sda, scl) { frequency(400000); start(); ; ; // I2CPreInit gi2c(pb_9,pb_8); I2C myi2c(i2c_sda,i2c_scl); Adafruit_SSD1306_I2c mygui(myi2c,d13,0x78,64,128); int main() { 223
uint16_t x=0; mygui.printf("%ux%u OLED Display\r\n", mygui.width(), mygui.height()); while(1) { myled = 1; wait(0.2); myled = 0; wait(0.2); mygui.printf("%u\r",x); mygui.display(); x++; wait(1.0); 프로그램을컴파일하고다운로드하여실행한다. 그러면 OLED 디스플레이에다음 그림과같이출력되는것을확인할수있다. 그림 13.10 OLED 제어프로그램 실행결과 224
14. 무선통신 (Wireless Communication) 14.1 블루투스통신 (Bluetooth Communication) 14.1.1 블루투스통신개요블루투스는 2.402~2.480 GHz 주파수대역에서동작하는, 널리사용되는디지털무선통신방식이다. 블루투스는모바일폰, 컴퓨터, 무선헤드셋, 그리고원거리센서사용을필요로하는시스템등과같은장치들간의무선데이터링크를제공한다. 블루투스의주요특징은다음과같다. Class 1 블루투스디바이스에서 100m, 그리고 Class2 블루투스디바이스에서는 20m 의통신범위를제공한다. Class 1 과 Class 2 블루투스디바이스들은각각대략 100mW 와 2.5mW 정도의비교적낮은소비전력을갖는다. Data rate는 3Mbps까지가능하다. Bluetooth Special Interest Group에의해관리되는블루투스표준에는블루투스장치들이서로를감지했을때서로연결할필요가있는지자동으로결정하게한다. 각각의장치는필요할경우통신하는장치를인지해서연결을초기화할수있는 Media Access Control(MAC) 을갖는다. 서로연결되어있는블루투스시스템들은하나의피코넷 (piconet) 네트워크를구성한다. 일단통신네트워크가구성되면피코넷의멤버들은자신의주파수호핑 (frequency hoping) 을동기화하여연결을유지한다. 하나의공간에는블루투스통신을하는장치들로구성된피코넷이여러개있을수있다. 14.1.2 블루투스모듈과스마트폰연결하기이번실습에서블루투스통신에사용하는장치는블루투스슬레이브모듈 HC-06 모델이다. 이블루투스모듈은 Nucleo STM32F401RE 보드와는 UART 시리얼인터페이스를통해연결되며, 간단한블루투스무선인터페이스를이용하여유선시리얼통신을대체할수있는장치이다. 예를들어요즘대부분의스마트폰은블루투스기능을탑재하고있기때문에 USB 데이터케이블을연결하지않아도무선네트워크를구성하여스마트폰과데이터를주고받을수있다. HC-06 블루투스모듈은마이크로컨트롤러와 UART 시리얼인터페이스로연결할 225
수있어 Tx 핀과 Rx 핀를연결하는것으로하드웨어구성이간단하게끝난다. 그림 14.1 은 HC-06 모듈외형과연결회로구성을보여준다. 그림 14.1 HC-06 블루투스모듈외형과연결회로도스마트폰과통신하기위해서스마트폰의블루투스기능을활성화하고 HC-06 모듈을 알려진블루투스모듈 로등록하여야한다. 안드로이드스마트폰에서무료로사용할수있는블루투스프로그램으로는 Bluetooth SPP Manager 앱이있으며, 구글 Play Store를통해설치하도록한다. 다음프로그램은 UART 시리얼통신을이용하여 HC-06 모듈과스마트폰사이에무선으로데이터를전송하는것을보여준다. 스마트폰앱에서 y 를입력하면실습장비의초록색 LED를켜고, n 을입력하면초록색 LED를끄는동작을수행한다. 또한, 호스트 PC의터미널프로그램으로동일한데이터를 USB 케이블을통해시리얼통신으로전송하는것을동시에보여주고있다. 터미널프로그램에서의입력에따라 LED on/off 동작을동일하게수행한다. 이것을통해유선시리얼통신을블루투스무선시리얼통신으로대체할수있음을확인할수있다. #include "mbed.h" Serial pc(serial_tx, SERIAL_RX); Serial bt(pa_11, PA_12); DigitalOut myled(pa_13); int main() { bt.baud(9600); //prints data on mobile bt.printf("connection Established"); 226
//print data on pc terminal pc.printf("connection Established"); while(1) { // For reading and writing data from/to bluetooth HC-06 // check if bluetooth is readable and execute commands // to toggle LED if (bt.readable()) { char input_key= bt.putc(bt.getc()); //tutn on LED if "y" is entered if(input_key == 'y') { myled = 1; bt.printf("led is ON"); //tutn on LED if "n" is entered if(input_key == 'n') { myled = 0; bt.printf("led is OFF"); //For reading and writing data from/to pc terminal //check if pc is readable and execute commands to toggle LED if (pc.readable()) { char input_key= pc.putc(pc.getc()); if(input_key == 'y') { myled = 1; pc.printf("led is ON"); if(input_key == 'n') { myled = 0; pc.printf("led is OFF"); 227
실습을위해새로운프로젝트 bluetooth_comm 를생성하고, src 폴더의 main.cpp 파일을위의프로그램소스로편집한다. 프로그램을컴파일하고다운로드하여실행하도록한다. 그러면다음의캡쳐화면과같이터미널프로그램의출력과스마트폰앱의동작을확인할수있다. 그림 14.2 블루투스통신프로그램실행결과 그림 14.3 스마트폰앱캡처화면 228
14.2 Wi-Fi 통신 (Wi-Fi Communication) 14.2.1 Wi-Fi 통신개요와이파이 (Wi-Fi) 는근거리무선통신기술로서와이파이얼라이언스 (Wi-Fi Alliance) 의상표명이다. IEEE 802.11 기반의무선랜연결과장치간연결구성 (PAN /LAN/WAN) 을지원하는일련의기술을의미하기도한다. IEEE 802.11 기반의표준에는속도와전력요구량에따라몇가지표준으로세분된다. 예를들어, 802.11b 는속도는가장느리지만비용은가장저렴했다. 이것의업그레이드버전인 802.11a 와 802.11g 는신호를분할하여간섭을줄이는기술이포함되었다. 가장최신버전인 802.11n 의경우는더길어진통신범위와데이터처리량을지원한다. 초기에는 Wi-Fi 의혁신성과비용때문에주로기관에서만사용되었지만시간이지남에따라가격이저렴해지면서많은사람들이이용할수있게되었다. 현재는컴퓨터, 태블릿, 스마트폰, 오디오장치등을포함하여수많은생활장비들속에 Wi-Fi 통신기능이내장되고있으며, 사용자는이들 Wi-Fi 통신장치들을인터넷에연결할수있다. 즉, Wi-Fi 무선랜단말과 AP(Access Point) 를연결하면외부와통신할수있게된다. Wi-Fi 의많은장점에도불구하고보안이큰문제가되기도한다. 네트워크는불특정다수가이용하게되므로핫-스팟 (Hot-Spot) 이용자들의컴퓨터는모든종류의해커와신원도용자에게쉽게노출될수있다. 또한사용자가증가하고데이터송수신이늘어날수록네트워크정체문제를야기할수도있다. 이문제는라우터와같은장비를증설하여해결할수있으나비용도함께증가하게된다. 14.2.2 ESP8266 Wi-Fi 모듈 Nucleo-F401RE 보드에는 Wi-Fi 통신기능이내장되어있지않으므로외부 Wi-Fi 무선통신모듈을사용하여야하며, 본실습에서사용할모듈은 ESP8266 Wi-Fi 모듈 이다. 이는 Espressif Systems 회사에서만든와이파이 SoC 모듈로서, 텐실리카 (Tensilica) 사의 80MHz Xtensa LX3 RISC CPU 코어를기반으로 64KB DRAM 과 96K SRAM 그리고 512KB 의 SPI 플래시메모리를포함하고있다. TCP/IP 스택을내장한이칩은 2~5 달러의저렴한가격에다양한형태의모듈로판매되고있다. 저렴한가격과가격대비좋은성능, 그리고사용의편의성때문에 IoT 기반의임베디드시스템을포함하여다양한용도로사용되고있으며사용자층도가장두터운편이다. ESP8266 모듈은 AT 펌웨어가탑재되어 AT 명령으로동작을제어한다. 또한 Lua 인터프리터가내장된 Nodemcu 펌웨어나 Micropython 인터프리터를펌웨어로탑재하 229
여스크립트프로그램을실행시킬수도있다. ESP8266 모듈을테스트하거나프로그램을업로드하기위해서는 ESP8266 모듈의 UART 핀을통해컴퓨터와연결할수있는 USB-to-Serial 장치를필요로한다. 그림 14.4 ESP8266 모듈과외부 USB-to-Serial 장치연결 별도구매가필요한 USB-to-Serial 장치가없다면터미널창에서 AT 명령어를실 행하여직접 Wi-Fi 모듈을제어할수있는프로그램을작성할수도있다. 그림 14.5 ESP8266 모듈의핀배치 ESP8266 모듈의핀배치는그림 14.5와같으며, 마이크로컨트롤러와의통신을위해직접연결방법은다음과같다. ESP8266 모듈을마이크로컨트롤러보드와연결해서사용할때는동작전압에특히주의를기울여야한다. ESP8266 모듈은 3.3V의전압을사용해야한다. 실수로 5V 전압을연결하게되면모듈이파손될수도있다. ESP8266 모듈의 UART TX 핀과 RX 핀은 Nucleo F401RE 보드의 TX 핀과 RX 핀에연결한다. 만약, 타겟보드의 RX 핀과 TX 핀이 5V로동작하는경우라면 ( 라즈베리파이보드혹은아두이노보드처럼 ) 230
전압차를고려하여 TX 핀과 RX 핀을 3.3V 전압레벨로낮춰서 ESP8266 모듈의 TX 핀과 RX 핀에연결해야한다. CH_EN 핀도마찬가지로 3.3V 에연결한다. 다행히 Nucleo F401RE 보드에장착된 Cortex-M4 디바이스의 IO 입출력핀은모두 3.3V 를 사용하고있다. Reset 핀의경우는연결하지않고오픈상태로두거나스위치를연 결하여 3.3V 전원을공급할수있도록회로를꾸밀수있다. GPIO-0 번핀은펌웨어 업그레이드시에만 GND 와연결하며, 평소에는오픈상태에두면된다. UART 를이용한시리얼데이터통신은터미널프로그램을필요로한다. 우리는 이미앞선실습프로젝트에서테라텀 (TeraTerm) 이라고하는터미널프로그램을사 용하고있다. 주의할점은 AT 명령어를전송할때에는명령어끝에 CR(Carriage Return) 과 NL(New Line) 문자도함께추가해주어야한다. 시리얼통신설정은 115200( 낮은펌웨어버전에서는 9600) baud rate, data bit 8, stop bit 1, parity bit none, flow control none 으로한다. 표 14.1 은자주사용하는 ESP8266 AT 명령어일부를요약해놓은것이다. 전체 AT 명령어를요약해놓은자료는인터넷검색으로쉽게구할수있다. 표 4.1 ESP8266 AT 명령어요약 명령기능응답 AT 명령테스트 OK AT+GMR 버전확인 OK+ADDR:<MAC> AT+RST 초기화및모듈준비 OK ready AT+CWMODE=< 모드 > 와이파이모듈설정 1( 스테이션 ), 2(AP), 3( 둘다 ) +CWMODE:< 모드 > OK AT+CWMODE? AT+CWJAP= <SSID>, < PWD> 와이파이모드확인 AP 에연결 +CWMODE:< 모드 > OK OK AT+CWLAP AP 목록출력 OK AT+CWSTAMAC? 스테이션 MAC 주소획득 +CWSTAMAC: 주소 OK mac AT+CWAPMAC? AP MAC 주소획득 AT+CIPSTATUS TCP/IP 연결상태확인 OK A T + C I P S T A R T = < 종류 >, < 주소 >,< 포트 > +CWAPMAC: mac 주소 OK TCP/UDP 연결 OK+Set< 파라미터 > 231
AT+CIPSEND=< 크기 > -> < 데이터 > AT+CIPCLOSE 특정바이트크기의데이터전송. > 프롬프트가나올때까지기다린후, 데이터를입력해야함. TCP/UDP 연결닫기 SEND OK AT+CIFSR IP 주소획득 <IP 주소 > AT+IPD,< 데이터 > AT+CIPSTA= <IP>, <Ga teway>, <Subnet> 데이터수신 IP 주소수동설정 그림 14.6은실습장비에서의 ESP8266 모듈의연결회로구성을보여주고있다. 주변의스위치들은 ESP8266 모듈과의통신에사용할 Tx 핀과 Rx 핀을선택할수있도록지원하거나 ESP8266 모듈의펌웨어업그레이드를위한 GPIO0 핀입력을선택할수있도록지원한다. 그림 14.6 ESP8266 모듈의연결회로 14.2.3 ESP8266 Wi-Fi 모듈테스트앞서언급한대로 USB-to-Serial 모듈이있다면 ESP8266 모듈과호스트 PC를 USB-to-Serial 모듈로직접연결하고, 호스트 PC에서터미널프로그램을통해 AT 명령을직접입력하여 ESP8266 모듈을설정할수있다. 그러나여기서는 Nucleo 232
F401RE 보드와 ESP8266 모듈을 UART 시리얼인터페이스로연결하고 AT 명령을전송하여설정하는프로그램을살펴보기로한다. 이번실습을위해새로운프로젝트 esp8266_test 를생성하고, src 폴더의 main.cpp 파일을다음의프로그램소스로편집한다. 다음의소스는 ESP8266 모듈을설정하는 main() 함수의구현소스이다. #include "mbed.h" Serial pc(usbtx, USBRX); Serial esp(pa_9, PA_10); Timer t; int count,ended,timeout; char buf[1024]; char snd[255]; // 호스트 PC와 MCU사이의 UART 통신포트 // MCU와 ESP8266을연결하는 Tx와 Rx 포트 // 타임아웃등시간측정을위한타이머선언 // 터미널출력을위한메시지저장 // ESP8266으로전송할데이터저장 char ssid[32] = "Silla_AP"; // Wi-Fi 라우터의 SSID, 사용자의것으로변경필요 char pwd[32] = "silla1234"; // Wi-Fi 라우터의암호, 사용자의것으로변경필요 // 프로그램에서사용할함수선언 void SendCMD(), getreply(), ESPconfig(), ESPsetbaudrate(); int main() { pc.baud(115200); // 호스트 PC와 USART 통신속도설정 pc.printf("\f\n\r-------------esp8266 Hardware Reset ------------- \n\r"); wait(0.5); esp.baud(115200); // ESP8266 의 USART 통신속도설정 변경가능 // ESPsetbaudrate(); // ESP8266 의 USART 속도변경시, 호출 ESPconfig(); // ESP8266 configuration // AP list and IP 주소를가져오는루틴반복수행 while(1) { pc.printf("\n---------- Listing Acces Points ----------\r\n"); strcpy(snd, "AT+CWLAP\r\n"); // AP 목록출력요청명령 233
SendCMD(); // ESP8266 으로명령전송 timeout=15; getreply(); // ESP8266 의응답을 buf에저장 pc.printf(buf); // 호스트 PC로응답내용출력 wait(2); pc.printf("\n---------- Get IP and MAC ----------\r\n"); strcpy(snd, "AT+CIFSR\r\n"); // ESP8266에할당된 IP와 MAC 주소요청 SendCMD(); timeout=10; getreply(); pc.printf(buf); wait(2); ESPconfig 함수를통해 ESP8266 모듈에대한기본적인설정이정상적으로끝나면 ESP8266 모듈에할당된 IP 주소와 MAC 주소등을알수있다. main 함수에서는 ESP8266 모듈이인식한 AP 목록과 ESP8266 에할당된 IP 주소및 MAC 주소를 ESP8266 모듈서받아와터미널화면에출력한다. 다음은 ESPconfig 함수의소스인데, 이를통해 AT 명령의활용사례와 ESP8266 모듈설정방법에대해알수있다. void ESPconfig() { wait(5); strcpy(snd,"at\r\n"); // AT 명령테스트. OK 를반환해야한다. SendCMD(); wait(1); strcpy(snd,"at\r\n"); SendCMD(); wait(1); strcpy(snd,"at\r\n"); SendCMD(); timeout=1; getreply(); wait(1); pc.printf("\f---------- Starting ESP Config ----------\r\n\n"); 234
pc.printf("---------- Reset & get Firmware ----------\r\n"); strcpy(snd,"at+rst\r\n"); // 모듈초기화 SendCMD(); timeout=5; getreply(); pc.printf(buf); wait(2); pc.printf("\n---------- Get Version ----------\r\n"); strcpy(snd,"at+gmr\r\n"); // 버전확인 SendCMD(); timeout=4; getreply(); pc.printf(buf); wait(3); // set CWMODE to 1=Station,2=AP,3=BOTH, default mode 1 (Station) pc.printf("\n---------- Setting Mode ----------\r\n"); strcpy(snd, "AT+CWMODE=3\r\n"); // Wi-Fi 모드설정 SendCMD(); timeout=4; getreply(); pc.printf(buf); wait(2); // set CIPMUX to 0=Single,1=Multi pc.printf("\n---------- Setting Connection Mode ----------\r\n"); strcpy(snd, "AT+CIPMUX=1\r\n"); SendCMD(); timeout=4; getreply(); pc.printf(buf); wait(2); pc.printf("\n---------- Listing Access Points ----------\r\n"); strcpy(snd, "AT+CWLAP\r\n"); // 액세스가능한 AP 목록 SendCMD(); 235
timeout=15; getreply(); pc.printf(buf); wait(2); pc.printf("\n---------- Connecting to AP ----------\r\n"); pc.printf("ssid = %s pwd = %s\r\n",ssid,pwd); // SSID와 Password 로연결 strcpy(snd, "AT+CWJAP=\""); strcat(snd, ssid); strcat(snd, "\",\""); strcat(snd, pwd); strcat(snd, "\"\r\n"); SendCMD(); timeout=10; getreply(); pc.printf(buf); wait(5); pc.printf("\n---------- Get IP's ----------\r\n"); strcpy(snd, "AT+CIFSR\r\n"); // IP 주소할당 SendCMD(); timeout=3; getreply(); pc.printf(buf); wait(1); pc.printf("\n---------- Get Connection Status ----------\r\n"); strcpy(snd, "AT+CIPSTATUS\r\n"); // 연결상태확인 SendCMD(); timeout=5; getreply(); pc.printf(buf); pc.printf("\n\n\n If you get a valid (non zero) IP, ESP8266 has been set up.\r\n"); pc.printf(" Run this if you want to reconfig the ESP8266 at any time.\r\n"); pc.printf(" It saves the SSID and password settings 236
internally\r\n"); wait(10); 프로그램편집이완료되면컴파일하고다운로드하여실행한다. 그럼다음그림과 유사한결과를확인할수있을것이다. 그림 14.7 ESP8266 테스트실행결과화면 237
지금까지 ESP8266 모듈이 AP에접속하여 IP 주소를할당받도록설정하는방법에대해살펴보았다. 이제할당받은 IP 주소를이용하여외부와 Wi-Fi 통신이가능한프로그램을작성할수있게된것이다. Wi-Fi 통신을이용한다양한어플리케이션작성이가능할것이다. 또한인터넷검색을통해다양한응용사례를찾아볼수있다. ESP8266 모듈을사용하는데있어한가지주의할점은네트워크요청이느리거나정지되었을때에 ESP8266 모듈이재부팅될수있다. 일정시간응답이없을때, watchdog 타이머에의해재부팅되도록설정되어있기때문이다. 응용어플리케이션을작성할때는이점을고려해서지속적으로응답이가능하게프로그래밍할필요가있다. 238
15. 응용과제 15.1 Bluetooth Low Energy(BLE), 클라우드서버와연동하는휴대용온 / 습도계 15.1.1 응용사례및동작원리 StormTag - A Bluetooth Weather Station https://www.kickstarter.com/projects/jonatherton/stormtag-a-bluetooth-weather-stati on-on-your-keyri?ref=nav_search&result=project&term=weather%20station 그림 15.1 StormTag 소개사이트 위사례에서보여주는 StormTag 는저전력블루투스기술인 BLE(Bluetooth Low Energy, Bluetooth 4.0) 을활용한휴대용기상대이다. 이제품은온도, 습도, 자외선, 기압을측정해서휴대폰으로데이터를전송한다. 휴대폰에서수집된데이터는클라우드서버로보내게된다. 많은사용자가 StormTag 를사용한다고가정하면클라우드서버에는전국각지의데이터가모이게되고, 기상데이터는더세밀해지고신뢰성이높아지게될것이다. 물론사용자는클라우드서버로부터기상데이터를받아볼수있게된다. BLE의저전력기술로인해 CR2032 코인배터리로 1년이상사용할수있는것도강점이다. 위사례의 StormTag 와는다르지만유사한휴대용온 / 습도계를만들어본다. BLE 장치, 휴대폰, 클라우드서버가유기적으로연결되기때문에온 / 습도계자체는매우단순하게만들수있다. 만약, 휴대폰용앱과클라우드서버를멋지게구현할수있 239
다면훌륭한개인용기상대를가질수있게될것이다. 15.1.2 준비물 DHT22 ( 혹은, DHT11) 온 / 습도센서 저렴하고구하기쉬운센서로서테스트용으로는충분하다. 그림 15.2 DHT-22 & DHT-11 온 / 습도센서모듈 HM-10 BLE 모듈 HM-10 BLE 모듈은 DIY용으로가장널리사용되고있다. 상대적으로관련자료가풍부하고다용한용도로사용하기위해필요한툴도잘갖춰져있다. HC-06과 HM-10 모듈은마이크로컨트롤러의입장에서는동일한프로그램을사용할수있지만, 앱을만들때사용되는블루투스 API가완전히다르고, HM-10 은안드로이드 4.3 이상에서만지원된다. 그림 15.3 HC-06 & HM-10 BLE 모듈 하드웨어연결 MCU 와온습도센서그리고 BLE 모듈은그림 15.4 와같이연결한다. 240
그림 15.4 MCU, DHT22, HM-10 연결회로도 15.1.3 프로그램작성 MCU 프로그램소스 #include "mbed.h" #include "DHT22.h" DHT22 sensor(pb_2); Serial bt(pa_11, PA_12); // Tx, Rx Serial pc(usbtx, USBRX); int count,ended,timeout; char buf_s[1024]; char snd[255]; void nsleep(int nminutes) { for (int i = 0; i < 3*nMinutes; i++) { wait(20); int main() { float h=0.0f, c=0.0f; pc.baud(115200); bt.baud(9600); while(1) { 241
%4.2f\r\n", sensor.sample(); c = sensor.gettemperature()/10.0; h = sensor.gethumidity()/10.0; pc.printf("temperature in CELCIUS: %4.2f, Humidity: c, h); bt.printf("thingspeak:key=xxx&field1="); bt.printf("%d", (int)c); bt.printf("&field2="); bt.printf("%d", (int)h); bt.printf("[*]"); nsleep(1); BLE Chat 앱다운로드 BLE 통신을이용해서 MCU와안드로이드앱이데이터를주고받을수있도록만든앱이다. MCU에서특정문자열을보내면 BLE Chat 앱에서이를인식하여지정된서버로 HTTP Request 를전송한다. 이기능을활용하면 MCU와안드로이드뿐만아니라인터넷서버까지연동할수있도록만들수있다. Google play store에서 BLE Chat 으로검색하여앱을다운로드한다. BLE Chat 앱은오픈소스라이선스로소스가공개되어있으므로이를수정해서사용할수있다. https://github.com/godstale/blechat 15.1.4 테스트 MCU의소스코드는온도와습도를측정해서블루투스를통해휴대폰으로전송하도록작성되어있다. 하지만온도와습도데이터를특정한형식을갖춰보내도록되어있다. 이렇게하는이유는측정한데이터를휴대폰뿐만아니라인터넷서버로전송하기위함이다. BLE Chat 앱은지정된형식과일치하는문자열이들어오면이문자열을채팅화면에표시하기도하지만데이터를추출해서지정된서버로보내주기도한다. 따라서집안에온 / 습도계를설치해두고태블릿혹은사용하지않는휴대폰과블루투스로연결하면집안의온 / 습도데이터가서버에저장되고웹을통해언제든집안의온 / 습도데이터를볼수있다. 242
데이터를저장하고서비스하기위해서는당연히서버가필요하다. 하지만이를직접만들기에는너무어렵고많은시간이소요되기에본교재에서는이런기능을제공해주는 IOT 서비스를이용한다. 대표적인서비스로 ThingSpeak 이있다. BLE Chat 앱도 ThingSpeak 와연동하도록작성되어있기때문에 ThingSpeak 서비스를이용하는방법을설명한다. http://thingspeak.com 으로접속하면 Sign-up 메뉴를통해회원가입을할수있다. 그림 15.5 ThinkSpeak 사이트의홈페이지 가입후, 로그인을한다음 Channels 메뉴를클릭하여채널을생성하도록한다. My Channels 에서 New Channel 버튼을클릭한다. 그림 15.6 ThinkSpeak 에서 channel 생성화면 채널의이름을지정하고데이터를담을 field 를활성화한다. 온도와습도데이터 를전송할것이기때문에 field1 과 field2 를활성화하고각필드의이름을입력한다 음하단의 Save 버튼을클릭하여설정을저장한다. 243
이제 Private View 페이지로이동한다. 앞서생성한두개의필드에대한그래프영역이표시되는데측정한온도와습도데이터가이곳에표시되도록할것이다. 여기서한가지확인해야할부분이있다. 상단의 API Key 탭을클릭하면 API Key를확인할수있는페이지가표시되는데 API Key는 ThingSpeak 서버로데이터를업데이트할때에데이터를저장해야할목적지를알려주고, 인증을겸하는문자열이다. 그림 15.7 ThinSpeak Channel API Key 화면 API Key를기억해두었다가프로그램에서입력해야한다. MCU 프로그램소스에서 BTL 모듈로출력하는부분의 key=xxx 문자열을본인의설정에맞게수정해주어야한다. 이제동작을확인해볼차례이다. BLE Chat 앱을실행하고상단의눈모양의아이콘을클릭하여원하는 BLE 모듈을찾아연결한다. 이후부터채팅화면을통해 BLE 를통해전송된문자열을확인할수있다. BLE Chat 앱에서수신한메시지는아래와같은형식이다. thingspeak:key=xxx&field1=xxx&field2=xxx[*] 안드로이드앱은수신한문자열에서 thingspeak: 문자열과 [*] 문자열을잘라내고나머지부분을그대로 thingspeak 서버로보내게된다. 즉, 아래와같은 HTTP Request가전송된다. http://184.106.153.149/update?key=xxx&field1=xxx&field2=xxx 여기서, key 값은앞서기억해둔 API Key가들어간다. 프로그램과안드로이드앱, 서버설정이모두맞으면휴대폰과 BLE가연결된후에는데이터가휴대폰에표시되고, 서버에저장되게된다. ThingSpeak 서버에로그인하여온 / 습도데이터에대한그래프가제대로그려지는지확인해보면그림 7.9와같은화면을볼수있을것이다. 244
처음에는휴대용기상센터와같은 IOT 시스템을고려하는것으로출발했다. 만약정원이나농장같은원격지의센서데이터를서버에저장하여볼수있다면또다른유용한시스템이될것이다. 더욱이집에남아도는태블릿, 또는휴대폰이없는경우혹은그같은환경을가정하기어려운경우라면어떻게할수있을까. 나중에 WiFi 모듈을이용해서직접서버로액세스하여데이터를저장하는예를구현해볼수있을것이다. 그림 15.8 ThinkSpeak 에서의온습도데이터변화그래프 15.2 자동조명 15.2.1 동작원리조도센서를활용하여주변의밝기를측정하고, 이에따라조명등을 ON/OFF 하고경우에따라색상을변경하여무드를바꿔보기로한다. 소스코드의일부를수정하여조명등의밝기도조절할수있다. 자동조명등을구현하기위해밝기를측정하는조도센서와조명등의색상과밝기를조절할수있는 RGB LED chip을이용한다. 응용하기에따라, 무드등, 가로등, 전조등, 위험방지등많은곳에활용할수있을것이다. 15.2.2 준비물 (1) 센서주변환경의여러가지물리적현상에서정보를받아들여, 이를전기적인신호로변환해주는것이센서이다. 다시말해, 사람이접하는대부분의자연적현상은빛, 소리, 열, 냄새, 속도등, 연속적인변화를갖는아날로그적특성을가지고있다. 이 245
런아날로그적현상을전기적으로다루고제어하기위해서물리적정보를전기적신호로변환해주는장치를센서라고한다. 주로마이크로컨트롤러가이같은센서데이터를받아제어에활용한다. 센서를통해얻어지는전기적인신호는 0 과 1 의디지털정보일수도있고, 연속적인전류혹은전압의변화를갖는아날로그정보일수있다. 연속적인변화를갖는아날로그신호를마이크로컨트롤러에서사용하려면디지털값으로변환하는 AD 변환기 (Analog Digital Converter) 를사용해야한다. 조도센서 ( 빛감지센서 ) 빛의밝기 ( 세기 ) 변화를인식하는센서로 CDS 혹은 LDR을사용하며, 주로 CDS가많이사용된다. CDS는빛세기가변하면저항값이변한다. 때문에일정한전압을인가하는환경에서센서의출력을아날로그입력핀에연결하면전압값의변화를측정할수있다. 이값을이용하면주변환경의밝기를가늠할수있다. 이처럼빛의세기를측정하는센서를조도센서라한다. 조도는단위면적에대한빛의양을의미하며단위는 Lux를사용한다. 그림 15.9 조도센서연결회로및반응성빛의세기가커질수록저항성분은작아지며, 빛의세기가약해지면저항성분은커진다. 그러나이같은성분 ( 전도율 ) 이밝기에비례해서선형적으로증가하는것이아니기때문에정확한 Lux 값을구하기보다는밝고어두운정도만을판별하기에적합하다. 조도센서도일종의저항이지만빛의양이아주많으면저항이매우작아지기때문에과전류가흐를수있다. 따라서그림 7.10과같이조도센서의저항값에따른전압을신호로입력받기위해서 10K 오옴의풀업저항을달아준다. 풀업 (Pull-Up) 저항사용시에조도센서의저항값에따라전압분배가일어나며, 빛의양이많을수록 ( 밝을수록 ) 측정되는전압의크기는작아진다. 실습에사용할조도센서모듈은아래와같은속성을지닌다. 246
속성동작전압공급전류 Light Resistance Dark Resistance 3-5 V 0.5 3mA 20KΩ 1MΩ 값 그림 15.10 조도센서모듈과연결회로도조도센서모듈위의가변저항을조절하여기준레벨을정할수있다. 녹색 LED 가꺼지기직전까지저항레벨을조절한다. 이후, 센서부분을손으로가려어둡게만들면녹색 LED가꺼지는것을볼수있을것이다. 조도센서모듈의출력은마이크로컨트롤러의아날로그입력핀과연결한다. 아날로그입력핀에서읽을수있는값의범위는 0.0 에서 1.0 사이의값이되며, 빛의세기가어두울수록입력되는값이커지고, 밝을수록값이작아진다. (2) RGB LED 본교재에서는조도센서에서측정된외부밝기에따라전구의색과밝기를제어하기위해 RGB (Red, Green, Blue) Chip LED를사용하기로한다. RGB LED는한가지색을내는 LED와달리빨강, 초록, 파랑색을섞어서다양한색상을만들수있으며, 다양한용도로활용이가능하다. 그림 15.11 RGB LED 동작원리및사례 247
RGB LED의색상이나밝기를제어하기위해각 LED 입력핀들을마이크로컨트롤러의출력핀들과 PWM 출력핀들과연결한다. 이프로젝트는매우단순하기때문에조도센서에서측정한외부밝기에따라, RGB LED의색상만을변경하고, LED 밝기는가장밝은값으로출력하기로한다. 프로그램을수정하면좀더세밀한조절이가능해진다. 그림 7.13은 RGB LED 모듈의연결회로도를보여준다. 그림 15.12 RGB LED 모듈의연결회로도 PWM (Pulse Width Modulation) 시그널을 On/Off 하여만든펄스의빈도 ( 주기 ) 를일정하게유지하면서펄스의폭 을변화시키는기술을 Pulse Width Modulation, 또는간단하게 PWM 이라한다. 그림 15.13 PWM 펄스예펄스의상대적인폭을 duty cycle 이라하며, 이는실제동작이수행되는 (duty, 의무 ) 부분, 즉전원이공급되는부분이기때문이다. 펄스간의거리는주기 (period) 라한다. 위그림의최상단그래프 duty cycle은 10% 인데, 이는주기에서전원이공급되는것이 10% 라는의미이다. 제일아래그래프는 100% 이다. 물론, 제일아래것은전원이계속공급되고있다는의미이다. 248
Nucleo STM32F401RE 보드는 PWM 으로사용할수있는핀들이여러개있다. 아 래표는 MBED 라이브러리에서제공하는 PWM 관련명령어들이다. 명령어 PwmOut write read period period_ms period_us pulsewidth pulsewidth_ms pulsewidth_us operator = operator float() 설명지정된핀으로연결된 PwmOut을생성 fraction(float) 로지정된출력 duty cycle을설정 fraction(float) 로측정된현재출력 duty cycle을반환초단위 (float) 로지정된 PWM 주기를설정밀리초단위 (int) 로지정된 PWM 주기를설정마이크로초단위 (int) 로지정된 PWM 주기를설정초단위 (float) 로지정된 PWM 펄스폭을설정밀리초단위 (int) 로지정된 PWM 펄스폭을설정마이크로초단위 (int) 로지정된 PWM 펄스폭을설정 write() 에대한연산자약어 read() 에대한연산자약어 위표는 PwmOut 핀들과관련해서사용할수있는함수들을보여준다. 물론, pulse width, duty cycle, 그리고 period 는밀접하게관련되어있다. 일반적으로 pulse width 나 period 중하나를설정한다. 그런다음 duty cycle을이용하여여러분이설정하지않았던변수들을제어한다. 15.2.3 프로그램작성 RGB LED에서입력받을신호들은마이크로컨트롤러의 PWM 출력핀에서제공하고, 조도센서로부터출력되는아날로그신호은마이크로컨트롤러의아날로그입력핀과연결한다. PwmOut r (A1); PwmOut g (PC_6); PwmOut b (A3); AnalogIn analog_value(a0); RGB LED 의 PWM 주기를 1ms 로설정한다. 249
r.period(0.001); g.period(0.001); b.period(0.001); 조도센서의입력값을읽어서백분율로표시하고, 입력값에따라 R, G, B LED 중하나를 ON 한다. while(1) { meas = analog_value.read(); meas = meas * 100; if ((meas > 25) && (meas < 50)) { r = 1.0; g = 0; b = 0; else if ((meas > 55) && (meas < 75)) { g = 1.0; r = 0; b = 0; else if ((meas > 80)) { b = 1; r = 0; g = 0; else if ((meas < 20)) { r = 0; g = 0; b = 0; 15.2.4 테스트소스코드에서 PWM 출력값을변경해가면서 LED 의밝기를변화시켜본다. 또한조도센서에서측정되는값의임계치를다양하게변경해가면서세밀하게원하는형태의색상과밝기를조절해본다. 250
15.3 스마트워치 15.3.1 동작원리페블의스마트워치라인업이주목받기시작하면서애플의스마트워치, 삼성의 S3 기어등, 스마트워치는웨어러블제품중가장뜨거운분야중하나로주목받고있다. 그러나여전히스마트워치보다는클래식시계가대부분사용되고있는실정이다. 아직스마트워치의존재가사용자들의기대를크게충족하지못하고있기때문일것이다. 이에본교재에서스마트워치를직접만들어그존재이유를고찰해보고개선점을찾아보는것도의미있는일이될것이다. 스마트워치의대부분데이터는휴대폰에서만들어져서가공된형태로블루투스를통해전달된다. 15.3.2 준비물 OLED 디스플레이디스플레이장치는 16 컬러흑백 OLED 모듈로서 4핀 I2C 인터페이스를통해액세스된다. OLED 모듈은 128X64 해상도를가지며, Adafruit_GFX, Adafruit_SSD1306 그래픽라이브러리를사용한다. 이디스플레이모듈은밝기와명암비가높고, 얇고가벼우며동작온도범위가넓고저전력에서동작한다. 그림 15.14 OLED 디스플레이모듈외형 그림 15.15 OLED 디스플레이모듈연결회로도 251
블루투스모듈블루투스는 2.402 2.480 GHz 주파수대역에서동작하는디지털무선통신의비교적새로운형식이다. 블루투스는모바일폰, 컴퓨터, 무선헤드셋, 그리고원거리센서사용을필요로하는시스템등과같이장치들간의무선데이터링크를제공한다. 블루투스의중요한특징은다음과같다. 1 Class-1 블루투스디바이스에서 100m, 그리고 Class-2 블루투스디바이스에서는 20m 의통신범위를제공한다. 2 Class-1 과 Class-2 블루투스디바이스들은각각대략 100mW 와 2.5mW 정도의비교적낮은소비전력을갖는다. 3 데이터전송대역폭은 3Mbps까지가능하다. Bluetooth Special Interest Group에의해관리되는블루투스표준에는블루투스디바이스들이서로를감지했을때서로연결이필요한지자동으로결정하게한다. 필요한경우, 각각의디바이스는통신하는디바이스를인지해서연결을초기화할수있는 Media Access Control(MAC) 을갖는다. 서로연결되어있는블루투스시스템들은하나의피코넷 (piconet) 을구성한다. 일단통신이구성되면피코넷의멤버들은자신의주파수호핑 (frequency hopping) 을동기화해서연결을유지한다. 하나의공간에는통신하는디바이스들로구성된피코넷이여러개있을수있다. 저전력블루투스인 BLE 모듈을사용하면좋겠지만우리가사용할안드로이드앱에서 BLE를아직지원하지않기때문에대신저렴하고쉽게구할수있는 HC-06 모듈을사용하며, Necleo STM32F401RE 보드와시리얼포트를통해연결한다. 그림 15.16 HC-06 모듈과연결회로도 스위치 디지털핀 (PA_14) 을통해스위치의상태를읽을수있도록연결한다. 여기서사 용하는스위치는풀 - 다운저항을통해연결되어있으며, 스위치를누르기전에는 252
HIGH(=5V) 값을, 스위치를누르면 LOW(=0V) 값을읽게된다. 그림 15.17 스위치연결회로도 15.3.3 프로그램작성스마트워치는처리할내용이많아소스코드가복잡한편이다. 소스코드가동작하는구조를먼저파악한다음세부적인내용은스스로찾아가분석해보도록한다. OLED 디스플레이와는 I2C 통신을한다. SDA (PB_9) 와 SCL (PB_8) 핀과연결하는 I2C 클래스객체를생성한다. 마이크로컨트롤러와연결한블루투스의시리얼핀 TX (PA_11), RX (PA_12) 를할당한다. 그리고사용자의버튼입력을읽어올핀 (PA_14) 을할당하고시간계산을위해 Timer 를선언한다. I2C myi2c(pb_9,pb_8); Adafruit_SSD1306_I2c display(myi2c,pc_13,0x78,64,128); Serial Serial Timer t; bt(pa_11, PA_12); pc(serial_tx, SERIAL_RX); DigitalIn buttonpin(pa_14); 휴대폰으로부터전달받은데이터를저장할버퍼를선언하고, 워치의시작이미지 표시와타이머동작을활성화한다. // 메시지버퍼초기화 init_emg_array(); init_msg_array(); // 프로그램시작이미지표시 drawstartup(); // 타이머시작 t.start(); 253
사용자가버튼을눌렀는지확인하고, 휴대폰으로부터받은메시지에따라해당함 수를호출한다. while(1) { if(buttonpin == LOW) isclicked = LOW; // Get button input // 블루투스수신메시지확인 if(bt.readable()) { c = bt.getc(); i f ( ( c = = 0 x F F ) & & ( T R A N S A C T I O N _ P O I N T E R! = TR_MODE_WAIT_MESSAGE)) printf("bt communication Error!!"); if(transaction_pointer == TR_MODE_IDLE) { parsestartsignal(c); else if(transaction_pointer == TR_MODE_WAIT_CMD) { parsecommand(c); else if(transaction_pointer == TR_MODE_WAIT_MESSAGE) { parsemessage(c); else if(transaction_pointer == TR_MODE_WAIT_TIME) { parsetime(c); else if(transaction_pointer == TR_MODE_WAIT_ID) { parseid(c); else if(transaction_pointer == TR_MODE_WAIT_COMPLETE) { istransactionended = parseendsignal(c); if(istransactionended == true) myled = 0; else myled = 1; // 현재시간업데이트 current_time = t.read_ms(); updatetime(current_time); 254
// OLED 디스플레이에시계표시 ondraw(current_time); 15.3.4 테스트스마트워치동작테스트를위해전용안드로이드앱을다운로드받아설치한다. Google play store에서 retrowatch 를검색한다. RetroWatch 앱은시계로보낼각종메시지를추출하기위해서안드로이드프레임워크가생성하는 Notification 을수집한다. Notification 이란화면최상단에표시되는안드로이드앱이생성하는알림메시지다. RetroWatch 앱을실행해서먼저할일을우측상단의 i 버튼을눌러 Notification 수집권한을주는일이다. 이후, 눈모양의아이콘을눌러블루투스장치와페이링시킨다. 페이링이완료되고조금있으면스마트워치의시간이현재시간으로바뀌는것을확인할수있다. 이제원하는메시지를스마트워치로전송되게설정해본다. 메시지탭에서앱이수집한주요메시지와 Notification 메시지를볼수있다. 여기서원하는메시지를클릭해서활성화하면이후부터는선택한앱이생성한메시지가보일때마다스마트워치로자동으로전송해준다. 일정한간격을두고전송되므로바로확인하고싶으면 [ 메뉴키 시계로데이터전송 ] 메뉴를선택한다. 스마트워치에메시지가도착하면화면상단에작은아이콘으로표시된다. 긴급한메시지가도착하면화면이긴급메시지확인화면으로자동전환되지만일반메시지는아이콘만표시되고, 버튼을눌러야메시지를확인할수있다. 그림 15.18 스마트워치테스트화면 255
15.4 스마트밴드 15.4.1 동작원리현재대부분의사람들이필수품으로가지고다니는휴대폰은블루투스통신이가능하다. 스마트밴드나스마트워치는블루투스통신으로휴대폰과연결되어기능할수있기때문에최근에많은각광을받고있다. 초기에는가격이꽤비싸서쉽게사용하기어려웠지만, 지금은샤오미의 Mi 밴드와같이저렴한제품들이출시되고있다. 스마트밴드에는특별한부품들이사용되는것은아니기때문에조그만노력을기울이면충분히만들어볼수있다. 스마트밴드의기본기능은몸의움직임을측정하고, 측정된데이터값을블루투스를통해전송하는것이다. 따라서우리는몸의움직임을측정할수있는가속도센서와통신용블루투스모듈을이용하여휴대폰과연동되는간단하지만유용한스마트밴드를만들어본다. 당연히판매되는상용제품은몸에착용할수있도록작게디자인되고, 측정된가속도데이터로몸의동작을추적하는흉내내기어려운알고리즘이내장되어있지만, 본교재에서모델링하는스마트밴드는사이즈크고단순화된알고리즘을사용하도록한다. 다만블루투스를이용한통신방법과센서를이용한데이터값추출그리고휴대폰과연동하는장치를만드는것으로충분한의미가있다. 15.4.2 준비물 ADXL-345 SPI 가속도센서모듈 (GY-291) ADXL-345 가속도센서의경우, SPI와 I2C 통신인터페이스둘다를지원한다. 본교재에서는이센서모듈과데이터를주고받을때 SPI(Serial Peripheral Interface) 인터페이스를이용하도록구현한다. SPI 표준이소개된지꽤나오래되었음에도불구하고너무나간단하게시리얼통신을구축할수있어여전히광범위하게사용되고있으며, 거의모든전자장비나 IC 칩, 그리고유용한툴등에내장되고있다. SPI가어떻게동작하는지에대한이해를바탕으로 SPI 인터페이스를통해디바이스와통신할수있다. 최근 IC 칩에대한집적도가높아짐에따라센서, 신호제어, analog-to-digital (ADC) 그리고데이터인터페이스와같은것들을단일칩내에포함하는것이일반적이다. 이같은장치들은새로운세대의 intelligent instrumentation 의일부이다. Analog Device 사가제작한 ADXL345 칩은집적된 intelligent 센서의예라고할수있다. 이장치의데이터시트를보면 microelectromechanical system (MEMS) 의예로 256
서 IC 칩구조안에 accelerometer mechanics 가실제로구현되어있다. 그림 15.19 ADXL-345 가속도센서가속도계의출력은본질적으로아날로그이며, 세축에대한가속도를측정한다. 가속도계내부의각축을구성하는판에캐패시터가장착되어있다. 가속도는캐패시터판을움직여서가속도혹은힘에비례하여출력전압을변하게한다. ADXL345 가속도계는아날로그전압변위를디지털로변환하고 SPI 시리얼링크를통해이들값을출력한다. ADXL345 에대한제어는시리얼링크를통해여러레지스터들로값을 write 함으로써이루어진다. 자세한내용은그림 7.21에예시되어있다. 디바이스는직접측정하는것이상으로잘동작한다. 디바이스를보정하고, 측정범위를변경하고, 특정이벤트를인지하게만드는것도가능하다. 측정은중력가속도 g (gravity, 9.81m/s2) 로표현된다. 그림 15.20 ADXL-345 가속도센서의레지스터사양 257
ADXL-345 가속도센서의연결핀사양은다음과같다. ADXL345 시그널이름 VCC GND SCL MOSI MISO /CS 핀 +3.3V GND PB_13 PB_15 PB_14 PB_1 블루투스모듈 (HC-06) 최근에는최신블루투스기술인 BLE (Bluetooth Low Energy, Bluetooth 4.x) 가주로사용되지만, 우리는저렴하고많이사용되는 HC-06 클래식블루투스모듈을사용한다. 마이크로컨트롤러의입장에서는 BLE 모듈이나 HC-06 클래식블루투스모듈이나사용방법이동일하다. 둘다시리얼통신으로마이크로컨트롤러와데이터를주고받기때문이다. 가장큰차이점은안드로이드앱을프로그래밍하는방식이완전히달라진다는점이다. 그리고 HC-06 의경우인증문제로인해아이폰과는연결이되지않는다. HC-06 모듈은 3.3V와 5V 동작전원을지원한다. 모듈형태와연결회로도는그림 16.16과동일하다. 15.4.3 프로그램작성 어느정도몸의움직임을분석하기위해 1 초에 20 회가속도센서데이터를수집 하고, 1 초에한번씩휴대폰으로수집된데이터를전송한다. 소스코드는 1 초에 20 회 센서데이터값을추출하여버퍼에저장하도록구현한다. 다음소스는 MCU 프로그 램의전체소스에서전역변수정의부분과 main 함수부분이다. #include "mbed.h" #define READ_INTERVAL 50 // Time #define ACC_BUFFER_COUNT 125 // Data Buffer char accbuffer[acc_buffer_count]; int accindex = 2; 258
void void void initbuffer(void); readfromsensor(void); sendtoremote(void); int { main() // ADXL345 가속도센서동작을위한초기화 cs=1; // initially ADXL345 is not activated acc.format(8,3); // 8 bit data, Mode 3 acc.frequency(2000000); // 2MHz clock rate cs=0; // select the device acc.write(0x31); // data format register //acc.write(0x0b); // format +/-16g, 0.004g/LSB acc.write(0x01); cs=1; // end of transmission cs=0; // start a new transmission acc.write(0x2d); // power ctrl register acc.write(0x08); // measure mode cs=1; // end of transmission // 데이터저장버퍼초기화 initbuffer(); // 타이머시작. 1 초에 20 회데이터측정을위해필요 t.start(); while(1) { // 50ms 마다센서값을읽어서버퍼에저장 if (t.read_ms() > READ_INTERVAL) { readfromsensor(); 이터를 // 센서값을 20 회읽었으면휴대폰으로버퍼에저장된데 // 전송한다음, 버퍼초기화 if(accindex >= (ACC_BUFFER_COUNT - 3)) { sendtoremote(); initbuffer(); t.reset(); // 타이머를 0 으로리셋 259
가속도센서는 X, Y, Z 축세방향에대한값을차례로측정하고, 각각은 2 byte 의 크기를가진다. 따라서한번측정할때마다 6 byte 의데이터가만들어진다. 1 초에 총 20 회측정하므로 120 byte 가필요하다. 나중에블루투스를통해휴대폰으로전송 할때, 데이터의시작과끝을알리는용도로 5 byte 가더사용되기때문에버퍼의 크기를 125 byte 로정의한다. 1 초에 20 회측정하기때문에 read 간격은 50ms 이고, 블루투스를통해데이터를전송하는간격은 1000ms (1 초 ) 이다. #include "mbed.h" #define READ_INTERVAL 50 // Time #define ACC_BUFFER_COUNT 125 // Data Buffer char accbuffer[acc_buffer_count]; int accindex = 2; main 함수에서 ADXL 가속도센서의초기화를수행한다. ADXL 가속도센서는 SPI 인터페이스를통해데이터를주고받는다. 타이머를이용해서 50ms 간격으로 readfromsensor 함수를통해센서측정값을읽고, 20회측정으로버퍼가차면 sendtoremote 함수를이용하여블루투스를통해데이터를휴대폰으로전송한다. 다음의소스에서정의하는세개의함수는버퍼초기화, 센서값 read, 그리고블루투스를통한데이터전송기능을수행한다. 블루투스를통해데이터를전송하는코드는상대적으로간단하다. void initbuffer() { accindex = 2; for(int i=accindex; i<acc_buffer_count; i++) { accbuffer[i] = 0x00; accbuffer[0] = 0xfe; accbuffer[1] = 0xfd; accbuffer[122] = 0xfd; accbuffer[123] = 0xfe; accbuffer[124] = 0x00; 260
void readfromsensor() { char temp[6]; cs = 0; acc.write(0x80 0x40 0x32); for (int i=0;i<=5;i++){ // accbuffer[accindex++] = acc.write(0x0); temp[i] = acc.write(0x0); cs = 1; // x if (temp[0] == 0x00) temp[0] = 0x7f; accbuffer[accindex++] = temp[0]; if (temp[1] == 0x00) temp[1] = 0x01; accbuffer[accindex++] = temp[1]; //y if (temp[2] == 0x00) temp[2] = 0x7f; accbuffer[accindex++] = temp[2]; if (temp[3] == 0x00) temp[3] = 0x01; accbuffer[accindex++] = temp[3]; // z if (temp[4] == 0x00) temp[4] = 0x7f; accbuffer[accindex++] = temp[4]; if (temp[5] == 0x00) temp[5] = 0x01; accbuffer[accindex++] = temp[5]; void sendtoremote() { bt.printf("accel"); bt.printf("%s", (char *)accbuffer); 15.4.4 테스트가속도센서를지면과평행하게두면 X와 Y축방향의값은작게나오고, 상대적으로 Z축방향의값은크게나온다. 실제로그렇게나오는지가속도센서를움직여서값을확인해본다. 센서의동작이문제가없으면이제블루투스통신으로휴대폰과연결한다. 그에앞서스마트밴드용앱을휴대폰에설치한다. Google Play Store에서 retroband 261
를검색하면 Tortuga 에서제작한앱을다운로드받을수있다. 앱을실행하고우측상단의눈모양아이콘을클릭한다. 팝업창에서블루투스기기를검색하고원하는기기와페어링을시도할수있다. Retroband 앱은세개의탭메뉴로구성되어있다. 1 Timeline: 시간별로누적된칼로리소모량을집계한다. 월 / 일 / 시간별로확인할수있다. 2 Graph: 매초들어오는가속도값을그래프로그려준다. X, Y, Z 축별로값이변화하는것을확인할수있다. 3 Settings: 앱동작에대한설정을변경할수있다. 여기서본인의체중을입력하면칼로리계산에반영된다. 앱화면종료후에도계속동작할수있도록백그라운드동작설정도가능하다가속도센서로부터몸의움직임을분석하기위해안드로이드앱에서는 Peak Detector 알고리즘을사용한다. 사람이움직일때의가속도센서값을그래프로그려보면사인파처럼일정한리듬을보인다. 여기서꼭지점에해당하는부분을찾아걸을수로환산하는것이 Peak Detector 알고리즘이다. 칼로리는걸음수와몸무게를이용해계산한다. 그림 15.21 스마트밴드테스트화면 262
부록 부록 A C 언어문법 부록 B Cortex-M4 아키텍처와프로그래머모델 부록 C MBED Library API 요약 263
부록 A C 언어문법 1. 프로그램의구성요소 1.1 일반적인형태 C 프로그램의일반적인형태는함수 main() 으로시작하여다음과같이중괄호 { 속에선언문및명령문을포함한다. #include <stdio.h> /* Header File */ void main( ) { 선언문 ; 명령문 ; 명령문 ; 프로그램이란하나의 task를처리하는명령문의집합이며, 명령문은선언적성격, 동사적성격의문장으로나누어진다. 명령문은 ; ( 세미콜론 ) 으로구분하여 1라인에한개의명령을기입하거나여러명령을써도좋으나, 단어와단어사이는적절히공백을줄수있다. 1.2 규칙과예약어 / 식별어프로그램이란명령문의집합이며, 각명령문은영문의알파벳과기호를써서 C 언어의언어규칙에맞도록작성해야한다. 컴파일러가언어규칙에맞는지를확인하여에러가없는프로그램만정상적으로실행시킬수있다. 1.2.1 프로그램명령문에사용가능한문자영문소문자및대문자 : a, b, c,..., z, A, B, C,..., Z( 문자 : letter) 숫자 : 0,1,2,3,4,5,6,7,8,9 (digit) 특수기호 : + - / = ( ) { < > " ' [ ]! # $ % & _ ^ ~ \. ; :? 기타 : blank, tab 및 new line ( 이것들을 white space라고함 ) 이러한문자들로서 C 프로그램의언어요소가만들어진다. 언어요소에는변수, 264
함수등을통칭하는식별어, 예약어, 상수, 연산자및분리기호가있다. 1.2.2 프로그램코딩규칙코딩규칙은일반적으로한명령문이끝날때마다 ; ( 세미콜론 ) 을쓴다. 1개라인은최대 248자 (Turbo C++) 까지허용되지만프로그램을보기쉽게하려면 1개라인에하나의명령문에다 80자이내로쓰는것이바람직하다. 1.2.3 예약어 C 언어는내장명령으로다음과같은예약어로이루어져있다. 예약어는용도가 미리정해져있으며, 해당용도로만사용한다. auto double int struct break else long switch case enum register typedef char extern rerurn union const float short unsigned for do while 1.2.4 표준함수 다 예약어와유사한성격으로고유의기능이있다. 예를들면 printf(), scanf() 등이 1.2.5 식별어원시프로그램에서쓰이는예약어이외의변수, 함수명, 배열명등을지칭하는명칭이다. 예를들어 x = x + 1 에서 x 는식별어로서프로그래머가정해쓰는변수명이다. 식별어의작성규칙은다음과같다. 첫자는영문자또는 _ 로시작하며, 처음 63자까지의문자만식별한다. 영문자와숫자를혼용할수있으며, 영문자의대문자및소문자는각각다른변수로인식한다. 1.3 헤더파일 (header file) 헤더파일이란사용자가컴파일러에게 source code 에정의된함수에대해알려주는파일이다. 표준헤더파일을지정할때는 #include <> 를사용하고 <> 안에파일이름을기입한다. ( 예 : #include <stdio.h> ) 265
헤더파일은.h 로끝나는이름을가지며, 여기에는함수의원형 (prototype) 을정의한다. 이부분은함수를배울때다시언급하자. 표준헤더파일이외에 source code와같은 directory 에있는사용자정의헤더파일을사용하려면, <> 대신에 를사용한다. ( 예 : #include myheader.h ) 다른 directory 에있는사용자정의헤더파일을사용하려면, path 를지정해야한다. ( 예 : #include c: \c \sample \myheader.h ) 2. 데이터형과연산자 2.1 변수 (variable) 변수는자료 (data) 를저장하기위한기억장소를의미하며, 임의의 memory 위치에필요한바이트수만큼의공간을확보하고이기억공간을칭하는이름을변수라한다. 즉, 프로그램안에서특정한자료를나타내는이름이다. 2.1.1변수의종류와선언프로그램에서변수를사용하기전에변수를선언해주어야한다. 변수의종류는자료형에따라정수형, 문자형, 실수형의 3가지로구분되며선언하는방법은다음과같다. 각자료형에대한자세한선언문설명은뒤에서하자. 자료형설명선언 int Number; 정수형 문자형 실수형 정수상수문자상수문자열은배열로표시함실수상수 Number = -12; 또는 int Number = -12 ; char Letter; Letter = B ; 또는 char Letter = M ; float Data1 = 3.141592 ; double Data2 = 3.141592653 ; 2.1.2 변수이름의작성방법 266
변수이름은영문자, 숫자, 언더스코어 (underscore) _ 만을사용한다. 변수이름은반드시영문자또는언더스코어 _ 로시작해야한다. 예약어는변수이름으로사용할수없다. 변수의사용역할에맞고사용자가기억하기편리한이름을사용한다. C 언어는대문자와소문자를식별할수있기때문에변수이름에대문자와소문자를구분하여쓴다. 2.1.3 데이터형과변수 - 상수 : 정수상수 ( 소수점포함하지않음 ), 실수상수 ( 수수점포함 ), 문자상수 ( 한개의문자를나타내는 ) 상수데이터를저장하는공간인변수 : 정수형변수와실수형변수, 문자형변수 정수 형 문자 형 부호있 음 부호없 음 부호있 음부호없 부동 음 소수점형 바이 자료형 설명 범위 트수 short int short형정수 2-32768~32767 int 정수 4-2147483648~214748 3647 long long형정수 4-2147483648~214748 3647 unsigned 부호없는 short형 2 0~65535 short 정수 unsigned int 부호없는정수 4 0~4294967295 unsigned 부호없는 long형 4 0~4294967295 long 정수 char 문자및정수 1-128~127 unsigned char float double 문자및부호없는 정수단일정밀도부동소 수점두배정밀도부동소 수점 1 0~255 4 1.2X10-38 ~ 3.4X10 38 8 2.2X10-308 ~ 1.8X10 308 267
(1) 정수형 short 는 short int와혼용하며, long은 long int와혼용하여 int를생략하여쓴다. 부호있는 (signed) 자료형은 int, short, long이며, 부호없는 (unsigned) 자료형은 unsigned int, unsigned short 및 unsigned long 형이다. (2) 실수형 25.6, -72.65, 12345.657 등과같이소수점이있는수를실수라고하며, 엄밀하게말하면소수점의위치가고정되지않고있다는뜻으로부동소수점형 (floating point type) 을의미한다. 자료형은 float, doble, long double 이다. (3) 문자형문자형식별어 [ 변수 ] 의선언은예약어 char를쓴다. char형상수나변수는 1 byte 의메모리를차지한다. 문자형의선언은정수 int형과같은원리이며, 초기값지정은홑따옴표 를사용한다. 문자열을나타낼때는겹따옴표 " " 로쓰지만문자 (1 개 ) 를나타낼때는단일인용부호 ' ' 를쓰므로 "A" 와 'A' 는다르다는것에유념하여야한다. 숫자 5와 '5' 는다르며 '5' 는문자5를의미한다. 그러므로문자 '5' 는 ASCII 코드로 35=(0011 0101) 이며숫자 5는 ASCII 코드 5=(0000 0101) 가된다. ASCII 코드값은문자의대소관계를부여하여, 문자의정렬 (sort), 단어의사전식분류등에이용된다. 문자 1개는 1 byte, 즉 8 bit 이므로 2^8=256 가지의다른값을나타낼수있다. 2.1.4 연산자 (1) 산술연산자 (+, -, *, /, %) : 정수형데이터간의연산결과 = 정수 / 실수형데이터간의연산결과 = 실수 산술연산자 사용법 의미 + a+b a의값과 b의값을더함 - a-b a의값에서 b의값을뺌 -a a에저장된값의부호를바꿈 * a*b a의값과 b의값을곱함 / a/b a의값을 b의값으로나눔 % a%b a의값을 b의값으로나눈나머지 268
연산자와피연산자인 A, B 사이는빈칸의존재유무에대해무관하다. 정수나눗셈의나머지를연산하는연산자 % 는피연산자로서정수를사용하여야한다. 산술연산자의우선순위는곱셈, 나눗셈, 나머지 ( 잉여 ) 연산이서로같고, 이들은덧셈과뺄셈보다우위이므로먼저연산해준다. 또, 수학에서와같이괄호 ( ) 가있으면괄호속의식을먼저연산하고, 함수가사용되었을때는함수가우선적으로수행되어야한다. 연산순위가같을때는왼쪽에서오른쪽으로연산해준다. 모든프로그램언어에서와같이식에는중괄호 { 나대괄호 [ ] 는사용할수없고 ( ) 만을사용하며, 가장안에있는괄호부터단계적으로계산한다. - 는단항연산자의역할을겸하기때문에 -a는 a의값에 -1을곱한것과같다. 따라서, -d%c -j/j*k는 ((-d)%c)-(j/g)*k) 로안쪽의괄호부터연산하면된다. (2) 증가, 감소연산자 (++, --) ++ 연산자 -- 연산자연산식의미연산식의미 a++; a--; a=a+1; a=a-1; ++a; --a; 변수에정수값 1을증가, 또는감소시키는단항연산자로 ++ 와 --가있다. j++ --> j 에 1증가, 즉 j=j+1, 증가시키기전에우선 j 사용 ++j --> j 에 1증가, 즉 j=j+1, 우선 j 를증가시키고 j 사용두가지연산 j++ 와 ++j는유사한것처럼보이나, j++ 는후치이고, ++j는전치로서연산순서에차이점이있다 (3) 대입연산자 (=, +=, *=,..) 대입연산자 사용예 연산과의미 = a=b=0; b=0; a=0; += a+=b; a=a+b; -= a-=b; a=a-b; *= a*=b; a=a*b; /= a/=b; a=a/b; %= a%/b; a=a%b; 269
(4) 관계연산자 (<, >, ==,!=,...) 참이냐거짓이냐의결과를내는조건식에는관계연산자 <, >, =, <=, >= 등이쓰이는관계식과다음절에서다룰논리식이있다. 이러한관계식과논리식은일반적으로 if 문등참, 거짓을필요로하는구문의조건식으로쓰이게된다. 관계연산자를이용한관계식은두식사이의대소관계를판단하는식으로서다음과같은연산자를사용한다. 관계연산자 사용법 의미 결과값 < a<b a에저장된값이 b의값보다작은가? <= a<=b a에저장된값이 b의값보다작거나같은가? 참이면 > a>b a에저장된값이 b의값보다큰가? 1 >= a>=b a에저장된값이 b의값보다크거나거짓이같은가? 면0 == a==b a에저장된값이 b의값과같은가?!= a!=b a에저장된값이 b의값과다른가? 특히, <= 와 >= 는 =< 나 => 로표시할수없고, 또중간에빈칸을넣어서 (< =) 로 써도안된다. 동등관계는 = 를두번써서 == 로쓰며같지않는다는!= 이다. 할당 연산자 = 와동등관계연산자 == 와는다르다는것에주의하라. (5) 조건연산자 (? :) 조건연산자 사용법 의미 수식1? 수식2 : 수식1의연산결과가참 (1) 이면수식2를처리? : 수식3 수식1의연산결과가거짓 (1) 이면수식3을처리 a가 b보다크면 k에 a값을저장 k=(a>b)? a : b 그렇지않으면 k에 b값을저장 270
(6) 논리연산자 (&&,,!) 논리연산자로는 &&, 및! 이있다. A, B 를수식또는하나의관계식이라고할 때, 기본적인논리식은다음과같다. 논리연산자 사용법 의미 && a&&b a에저장된값과 b에저장된값의논리곱 (AND) a b a에저장된값과 b에저장된값의논리합 (OR)!!a a에저장된값에대한부정 (NOT) 논리연산에대한진리표 a b a&&b a b!a 0 0 0 0 1 0 1 0 1 1 1 0 0 1 0 1 1 1 1 0 A, B 가하나의관계식일경우관계식의결과가참일때 1, 거짓일때 0을갖게 되므로 A나 B는 1 아니면 0 을갖는다. 참은 1, 거짓은 0 으로표시할때논리식의 결과, 즉진리표는위와같이정의한다. (7) 비트연산자 ( <<, >>, ^,...) 비트연산자는다음과같이구분 구분 비트연산자 사용법 의미 ~ ~a a에저장된값에대한 1의보수 a에저장된값과 b의값의비트단위논리논리 & a&b 곱연산자 a에저장된값과 b의값의비트단위논리 a b 합 시프트 a에저장된값과 b의값의비트단위베타 ^ a^b 적논리합 (shift) a에저장된값에대해 n비트만큼왼쪽으로 << a<<n 연산자시프트 271
>> a>>n a 에저장된값에대해 n 비트만큼오른쪽으 로시프트 (8) 콤마연산자 (, ) 콤마 (,) 로분리된수식들에대해왼쪽에서오른쪽방향으로평가, 제일마지막수 식의결과를취함 콤마연산자사용법 int I=10, b=5; x = (y+=3, y+15); 처리순서 int i=10; int b=5; y=y+3; x=y+15; (9) 형변환과캐스트연산자 암시적 (implicit) 형변환 : 컴파일러에의해자동적으로처리 명시적 (explicit) 형변환 : 데이터의형을강제로변환 캐스트연산자사용법 (float) a / 3.14; (int) (a/b); 의미 변수 a 를 float 형으로변환하여나눗셈처리 a/b 의연산결과를 int 형으로변환 - sizeof 연산자 상수, 변수그리고연산식결과의크기를 byte 로표시해주는연산자 - 연산자우선순위기본적 : 좌측에서우측방향으로이동하면서이뤄짐. 연산자에따라 *, / 연산은 +, - 보다우선순위 (precedence) 가높아먼저처리단, 식에괄호가있을경우괄호부분을우선적처리 272
3. 제어문 3.1 조건에대한판단과선택주어진조건식이참이냐거짓이냐를판별하여양자택일로문장을선택하여실행의흐름을결정하는 if 문이있다. if 문에쓰이는조건식은일반적으로대소와같음을판단하는관계연산자로이루어진관계식이있고, 관계식이결합된논리연산식이있다. if 문의기본형은다음과같다. - if 문 if ( 조건식 ) 조건식이참인경우에처리할문장 - if else 문 if ( 조건식 ) 조건식이참인경우에처리할문장 else 조건식이거짓인경우에처리할문장 if 문과 else 문의끝에는 ; 를붙이지않는다. if 조건이참이면명령문 1을수행하며, 거짓이면명령문 2를수행한다. 거짓인경우에처리할명령문이없으면 else 이하를생략할수있다. 일반적으로조건으로쓰이는것은 (x>2) 와같은관계연산식이대표적이며, C 언어는조건식이아니더라도그값이 0 이냐, 아니냐에따라거짓인가참인가를판단한다. 조건식이참일때처리할명령문이없다면, 명령문없이세미콜론 ; 만쓴다. 이러한문장을 empty statement 라고한다. - if~else if 문 if ( 조건식 1) 조건식 1 이참인경우에처리할문장 else if( 조건식2) 조건식2가참인경우에처리할문장 else 조건식모두거짓인경우에처리할문장 switch 문은식의값을여러가지로판단하여각값에따라실행할명령을선택하여진행시킬수있는조건문의한가지이다. 단순 if 문이나다중 if 문등은조건을참인가거짓인가에따라양자택일하여실행하지만, switch 문은분기조건을여러가지로명시할수있다. switch 문의기본형은다음과같다. 273
- switch case 문 switch (value) { case 1 : 입력값 (value) 이 1일때처리할문장 ; break; case 2 : 입력값 (value) 이 2일때처리할문장 ; break; case 3 : 입력값 (value) 이 3일때처리할문장 ; break; default : 입력값 (value) 과일치하게없을때처리할문장 ; switch 문은선택자 (selector) 로서정수식또는문자식이올수있으나, 실수형은 사용할수없다. 3.2 반복문정수변수 k 를 1 부터 k가 10 일때까지 1 씩증가시키면서, 이 k 를변수 hap 에더해나간후그합 hap 을화면에출력해보자. 이와같이하나의변수를일정한값만큼변경하면서반복처리할경우에는 for 문을사용한다. - for 문 for( 초기식 ; 조건식 ; 증감식 ) 조건식이참인경우에반복할문장 ; - 중첩된반 복문 for( 초기식 ; 조건식 1 ; 증감식 ) { // 조건식 1 이참일경우안에있는조건식 2 를실행 for( 초기식 ; 조건식 2 ; 증감식 ) 조건식 2 가참일경우반복할문장 while 문은조건식이참인동안 (while) 루프를반복하는반복문이다. for 문은제어변수의값을보통일정한값만큼증가 ( 또는감소 ) 시켜나가면서반복하는것이지만 while은이러한제어변수가없으므로보다일반적인반복문으로사용할수있다. 반 274
복문 while 의기본형은다음과같다. - while 문 wihle( 조건식 ) { 조건식이참일경우에반복할문장 ; 3.3 처리흐름조절 break 문과 continue 문은반복구문이나타나는 for 문, while 문및 do-while 문의반복부분, 즉루프속에서반복수행의제어를변경하는용도로쓰인다. break 문은 switch 문의명령문속에서도쓰며, 해당 switch 문을탈출할때도사용된다. break 문은반복루프내에서만쓸수있고, 이 break 문을만나면해당반복구문을조건식과관계없이완전히탈출한다. 4. 함수와기억클래스 4.1 사용자가정의하는함수 (1) 사용자가정의하는함수를사용하는프로그램의구성과형식 - 함수원형 (prototype) 의선언 : 사용자정의함수를사용하겠다는것을컴파일러에게미리알려주는역할 - 함수정의 (definition) 부분 : 함수가처리해야할기능들을구체적으로작성, 함수의결과값 ( 반환값 ) 이있는경우에는 return 문과함께변수또는연산식사용. 함수원형의선언데이터형함수이름 ( 함수인자들 ); 데이터형함수이름 ( 함수인자들 ); { 변수선언 ; 함수정의문장 ; [return 변수또는수식 ;] 275
- 결과값 ( 반환값 ) 이없는 void 형함수 #include <stdio.h> void line_print(viod); int main(void) { line_print(); return 0; void line_print(void) { 함수세부사항 - 결과값 ( 반환값 ) 이있는함수 #include <stdio.h> int add(int x); int main(void) { int n, sum; printf( 1부터 n까지의합 \n: ); printf( 정수 n 입력 : ); scanf( %d, &n); sum=add(n); // 함수호출 printf( 정수합 : %d\n, sum); return 0; int add(int x) { int i, result=0; for(i=1; i<=x; i+=1) result+=i; return result; 276
(2) 매크로상수와함수 (ex. #define START 50) : 프로그램작성단계에서결정할수없는상수를정의하는경우에사용 : 매크로의이름은일반적인변수와구별하기위해대문자사용 : 매크로상수로정의된값은변수로사용할수없으며프로그램내에서저장된값의변경이불가. (3) 재귀함수 : 호출된함수가실행중에자기자신을다시호출하는것. : 재귀호출에서는무한반복이이루어지므로이를해결하기위해적절한조건을주어함수를벗어나도록해주어야함. 5. 배열과포인터 5.1 배열 ( 번호가붙은동일한데이터형의연속된기억공간 ) (1) 1 차원배열 1차원배열선언방법 프로그램 배열크기만선언 int korea[4]; 배열의초기화 int korea[4]={15, 17, 27, 32; 배열초기화에서배열크기생략 int korea[]={15, 17, 27, 32; (2) 2 차원배열 냄. : 1 차원배열과는달리두개의첨자를사용 : 2 차원배열에서첫번째첨자는행 (row) 을, 두번째첨자는열 (column) 을나타 : 초기값지정시, 첫번째첨자의크기는생략가능. int mat[3][3]={{3, 8, 6, {4, 1, 7, {5, 2, 9; int mat[3][3]={{3, 8, 6, 4, 1, 7, 5, 2, 9; int mat[][3]={3, 8, 6, 4, 1, 7, 5, 2, 9; 277
(3) 함수와호출 : 인수가인자에전달될대변수자체가아닌변수에저장된값만전달 : 그러므로함수정의부분에서인자가변하더라도인수는영향받지않음. : 함수에서값의전달방법인값에의한호출 (call by value) : C언어에서함수호출방법은두가지방법, 즉값에의한호출과참조 ( 또는주소 ) 에의한호출 (call by reference, address) 을사용하는것과마찬가지로배열도가능. 5.2 포인터 포인터 (pointer) 는메모리에저장된데이터의위치를가리킴. (1) 포인터변수 : 오직주소로만을저장하고사용하기전에선언해야하며, 저장된값을다른주소로변경시킬수있음. : 포인터변수를선언할경우간접 (indirection) 연산자 * 를사용. 데이터형 * 포인터변수 ( 데이터형은포인터변수가가리키게될주소에저장된데이터형을가리킴 ) (2) 포인터와배열 : 배열요소들은메모리상에연속된기억공간에저장됨. : 배열의이름은그배열의첫번째요소가저장되어있는주소를가리키므로주소를가리킨다는점에서는배열과포인터는유사. (3) 포인터와문자열 배열또는포인터를이용하여문자열을초기화 방법 1 방법 2 char str1[]= copy ; char str1[9]= copy ; char str2= copy ; // 방법 1 의또다른쓰임 278
(4) 포인터에대한연산 : 포인터변수에는주소가저장되고주소는 4byte(Visual C++) 로표현된숫자이므로포인터변수나주소에대한연산이가능 : 주소는양의정수로표현하므로실수연산은불가 (5) 포인터를인자로사용하는함수호출 방법 1 방법 2 값의의한호출 (call by value) : 인자를전달할때항상그값 (value) 만을전달해줌. 주소에의한호출 (call by address) : 포인터 ( 주소 ) 를함수의인자로사용하여호출. (6) 포인터배열 : 포인터배열이란여러개의포인터변수를배열로사용. : 문자형포인터배열을사용할때유용 char na[4][17]={ Korea, Brazil, Germany, Papua New Guinea ; char *ct[]={ Korea, Brazil, Germany, Papua New Guinea ; (7) 포인터와함수 : 함수의주소를가리킴 : C언어에서포인터상수 ( 문자열상수, 배열의이름, 함수의이름 ) : 함수의이름은그함수가시작되는주소를나타내는포인터상수이므로함수포인터를사용하여함수를인자로전달가능데이터형 * 함수포인터 ( 함수인자들 ) 279
부록 B - Cortex-M4 아키텍처와프로그래머모델 1. Cortex-M4 아키텍처와프로그래머모델 여기서는 Cortex-M 프로세서아키텍처와 Programmer s Model에관해간단히살펴본다. 설명은 Cortex-M4 프로세서를기준으로하지만몇가지차이점을제외하면 Cortex-M0, Cortex-M1, 그리고 Cortex-M3 를비롯하여 Cortex-M 디바이스전체를쉽게이해할수있을것이다. ARMv7-M 아키텍처에기반한 Cortex-M4 프로세서는계층적구조를가지며, 인터럽트컨트롤러, 메모리보호, 시스템디버그및트레이스등과같은통합된기능들을갖추고있다. Cortex-M4 프로세서는첨단시스템주변장치들과함께 CM4Core로불리는프로세서코어에통합되어있다. Cortex-M4 프로세서의이같은주변장치들은광범위한응용분야에적용되고, 시스템요구사항에보다근접한구성을할수있도록해준다. Cortex-M4 코어와컴포넌트들은적은핀수와저전력, 그리고최소한의메모리로요구조건을충족하도록디자인되어있다아래그림은 Cortex-M4 코어의내부를간단한블록으로기술하고있다. 280
SW-DP (Serial-Wire Debug Port) 혹은 SWJ-DP (Serial-Wire JTAG Debug Port): SWJ-DP 는 JTAG-DP 와 SW-DP 가결합된것으로 2 와이어로구성되는 Serial Wire Debug (SWD) 또는 JTAG probe를통해타겟과연결할수있는방법을제공한다. JTAG 시그널핀이어떤 probe 에연결되었는지를자동검출하여 JTAG-DP 또는 SW-DP로연결한다. Nested Vectored Interrupt Controller (NVIC): NVIC는프로세서가인터럽트를처리할수있는능력을제공한다. 프로세서는 NVIC를통해짧은지연시간내 (low-latency) 에예외 (exceptions) 나인터럽트 (interrupts) 를처리를할수있으며전원관리 (power management) 를제어한다. NVIC는최대 256개까지우선순위레벨을가질수있으며, 240개까지인터럽트를처리할수있다. 인터럽트에대한우선순위는프로그램실행중에도변경이가능하다. NVIC와프로세서코어간의인터페이스가밀접하게연결되어있어서 low-latency 인터럽트와 late arriving 인터럽트를효과적으로처리할수있다. NVIC는또한스택되거나중첩된인터럽트의정보를유지하여 tail-chaining 을가능하게한다. 특권모드 (privileged mode) 에서는 NVIC를제약없이액세스할수있지만사용자모드에서의액세스는 bus fault의원인이된다. 모든 NVIC 레지스터와시스템디버그레지스터들은프로세서의엔디안 (endianness) 과는상관없이모두리틀-엔디안 (little-endian) 이다. Wake-up Interrupt Controller (WIC): WIC는프로세서코어를실제구현할때포함시킬수있는옵션항목이다. 프로세서와 NVIC가저전력 sleep 모드에진입하면 WIC가인터럽트를식별하고우선순위를지정하게된다. 프로세서는 WFI (Wait For Interrupt), WFE (Wait For Event), 그리고 SEV (Send Event) 명령을구현하고있으며, 추가로 SLEEPONEXIT 명령을지원하여프로세서가 exception handler 에서 thread mode로복귀할때자동으로 sleep mode에진입하게한다. 프로세서는레벨 (level) 인터럽트와펄스 (pulse) 인터럽트둘다를지원한다. 레벨인터럽트는디바이스를액세스하는 ISR (Interrupt Service Routine) 에의해인터럽트가클리어될때까지인터럽트상태를유지하는반면에, 펄스인터럽트는일종 edge model 이다. 레벨인터럽트에서는 ISR에서복귀하기전에시그널이비활성화되지않으면해 281
당인터럽트는다시대기 (pending) 상태에진입한다음활성화된다. 이런방식은 FIFO와버퍼형태의디바이스에서특히유용하다. 반복된호출로별도의작업없이이들이 drain 되는것을보장해줄수있기때문이다. 다시말해디바이스가빌때까지시그널이활성화된상태를유지하기때문이다. 펄스인터럽트는 ISR 내부에서다시발생할수있기때문에인터럽트와대기 (pending) 상태가동시에활성화되어있을수있다. AHB-AP (AMBA High performance Bus Access Port): Cortex-M4 시스템의선택적디버그액세스포트이며, 프로세서레지스터를포함한모든메모리와시스템내의레지스터들을액세스할수있다. 시스템액세스는프로세서상태와무관하게 SW-DP 혹은 SWJ-DP가 AHB-AP를액세스하는데사용된다. FPB (Flash Patch and Breakpoint Unit): hardware breakpoint 는기능을제공하며, 코드영역에서시스템영역의코드와데이터로 patch 할수있다. 완전한기능을가진 FPB unit은코드영역의 literal loads과일치하는지비교하고, 시스템영역내의해당위치로 remapping 하기위한두개의 literal comparators 를포함한다. 또한코드영역의 instruction fetch에대해서일치하는지여부와시스템영역내에서해당영역의 remapping 을위해여섯개의 instruction comparators 를포함한다. 간단한기능의 FPB unit은두개의 comparators 를포함한다. 사용자는 comparator 를개별적으로설정할수있고, 일치하게되면 breakpoint instruction 을프로세서로반환하여 hardware breakpoint 기능을제공한다. DWT (Data Watchpoint and Trace Unit (DWT): watchpoint, data tracing, 그리고프로세서에대한시스템프로파일링을제공하는 debug unit로프로세서구현시에선택할수있다. 전체기능을가진 DWT는 hardware watchpoint, ETM trigger, PC sampler event trigger, 그리고 data address sampler event trigger 와같이네가지구성가능한 comparator 를포함한다. 첫번째 comparator, DWT_COMP0 는클럭사이클카운터인 CYCCNT 에대해서비교할수있다. 또한두번째 comparator 인 DWT_COMP1 를 data comparator 로사용할수있다. 기능이축소된 DWT는 watchpoint 혹은 trigger 로사용할수있는하나의 282
comparator 를포함한다. 이것은 data matching 을지원하지는않는다. DWT는 clock cycle (CYCCNT), Load Store Unit (LSU) operations, sleep cycles, 첫번째사이클을제외한모든 instruction cycles 인 CPI, 그리고 interrupt overhead 에대한카운터들을포함한다. 카운터가오버플로우되면이벤트가발생된다. 사용자는정해진시간마다 PC sample 을만들도록 DWT를구서하고인터럽트이벤트정보를생성할수있다. 만약, TPIU가포함되어있으면 DWT는 ITM과 TPIU에대한프로토콜동기화를주기적으로요청한다. ITM (Instrumentation Trace macrocell): 오퍼레이팅시스템과어플리케이션이벤트를트레이스하기위해서 printf 스타일의디버깅을지원하는어플리케이션에의해구동되는트레이스소스로서프로세서구현시에포함될수있다. ITM은패킷으로트레이스정보를만든다. 여러개의소스가패킷들을만들수있으며만약, 여러개의소스가동시에패킷들을만들면 ITM은패킷이출력되는순서를조정하게된다. 이들소스는우선순위가낮아지는순서대로소프트웨어트레이스, 하드웨어트레이스, 타임스탬프이다. 소프트웨어트레이스는소프트웨어가패킷을생성하기위해직접 ITM stimulus 레지스터에 write 할수있다. 하드웨어트레이스는는 DWT가이들패킷들을만들고 ITM이출력한다. 타임스탬프는패킷과관련해서만들어진다. 타임스탬프를만들기위해 ITM은 21-비트카운터를포함한다. Cortex-M4 클럭이나 SWV (Serial Wire Viewer) 출력에대한비트클럭이이카운터를 clocking 한다. TPIU (Trace Port Interface Unit): ETM (Embedded Trace Macrocell) 과 ITM (Instrument Trace Macrocell) 의온-칩트레이스들간에브릿지역할을하며데이터스트림에대해서개별 ID를가지는장치로프로세서구현시에포함될수있다. TPIU가필요한 ID를 encapsulate 하고, 이후데이터스트림은 TPA (Trace Port Analyzer) 에의해캡처된다. Cortex-M 프로세서의 TPIU는특히저-비용의디버그를위해고안되었다. 이는 CoreSight TPIU의특별한버전이다. TPIU는 ITM 트레이스를지원하는것과 ITM과 ETM 디버그트레이스둘다를지원하는두가지가있다. 아래그림은두가지구성에대한장치레이아웃을보여준다. 283
Formatter 는소스의 ID 신호를데이터패킷에넣어서트레이스데이터가트레이스소스와다시연결될수있게한다. Formatter 는 Trace Port Module 이 active 되면항상 active가된다. Formatting 프로토콜은 CoreSight Architecture Specification 에기술되어있다. 사용자는 DWT에있는동기화된패킷들을인에이블하여해당 formatter 에대한동기화를제공한다. TPIU는 SWO (Serial Wire Output) 형식으로트레이스데이터를출력할수있다. 이때, TPIU_DEVID 에지원되는형식을지정하게되고, TPIU_SPPR 은사용하는 SWO 형식을지정한다. 두개의 SWO 모드중에서하나가선택되면트레이스출력을위해 formatter 를 bypass 하도록 TPIU를인에이블할수있다. 만약, formatter 가 bypass 되면 ITM과 DWT 트레이스소스만이통과된다. TPIU는 ETM 데이터를받은다음버린다. 이기능은 ETM을포함하는디바이스와 SWO 데이터만을캡처할수있는트레이스캡처디바이스를연결하기위해서사용된다. 1.1 Core Register Set R0 R12는데이터처리를위해서사용되는일반적인용도의 general-purpose register 이다. R13은 SP(Stack Pointer) 로사용된다. Thread mode에서 CONTROL 레지스터의 bit[1] 이 0 이면 MSP(Main Stack Pointer) 를의미하고 ( 리셋시, 초기값 ), 이값이 1이면 PSP(Process Stack Pointer) 를가리킨다. 리셋시에프로세서는 0x0 번지의값을갖는 MSP를로드한다. 284
R14는 LR(Linker Register) 로사용된다. LR은하위루틴이나함수호출, 그리고예외상황에서복귀할주소값을저장한다. 리셋시에프로세서는 LR 값을 0xFFFFFFFF 으로설정한다. R15는 PC(Program Counter) 로사용된다. PC는현재프로그램의주소를포함한다. 리셋시에프로세서는리셋벡터의 0x4 번지에있는값으로 PC를로드한다. Bit[0] 의값은 EPSR의 T 비트로 load 되고이값은반드시 1이어야한다. PSR(Program Status Register) 은 APSR(Application Program Status Register), IPSR (Interrupt Program Status Register), 그리고 EPSR(Execution Program Status Register) 을묶어놓은것이다. 이들레지스터들은 32-비트 PSR의서로다른비트필드들을가리킨다. 비트할당은아래와같다. 285
PSR 의조합과속성은다음과같다. APSR(Application Program Status Register) 은직전에실행한명령에의한 condition flag의상태를표시한다. Bits Name Function [31] N Negative result from ALU [30] Z Zero result from ALU [29] C ALU operation Carry or borrow [28] V ALU operation Overflow [27] Q DSP overflow and saturation math overflow [26:20] - Reserved [19:16] GE[3:0]Flags from Single Instruction Multiple Data (SIMD) operation [15:0] - Reserved IPSR(Interrupt Program Status Register) 은현재 ISR(Interrupt Service Routine) 의 exception type number를포함한다. Bits Name Function [31:9] - Reserved [8:0] ISR_NUMBER This is the number of the current exception: 0 = Thread mode, No exception active. 1 = Reserved 2 = NMI 3 = HardFault 286
4 = MemManage 5 = BusFault 6 = UsageFault 7-10 = Reserved 11 = SVCall 12 = Reserved for Debug 13 = Reserved 14 = PendSV 15 = SysTick 16 = IRQ0.... n+15 = IRQ(n-1) EPSR(Execution Program Status Register) 은 Thumb state 비트를포함하고, If-Then (IT) 명령이나혹은실행도중에중단된 load multiple 또는 store multiple 명 령에대해서 ICI (Interruptible-Continuable Instruction) 실행상태비트를포함한다. Bits Name Function [31:27] - Reserved. [26:25], [15:10] [ 2 6 : 2 5 ], [15:10] ICI IT [24] T Thumb state bit [23:16] - Reserved. [9:0] - Reserved. Interruptible-continuable instruction bits Indicates the execution state bits of the IT instruction EPSR 은 MRS 명령을사용하여간접적으로 read 할수있지만 write 는무시된다. ITI(Interruptible-continuable instructions) 는 LDM, STM, PUSH 또는 POP 명령을실 행중이거나 FPU(Floating Point Unit) 가 VLDM, VSTM, VPUSH, 또는 VPOP 을실행중 일때, 인터럽트가발생되면프로세서는동작을일시중단하고, multiple operation 의 287
다음레지스터오퍼랜드를 EPSR의 [15:12] 비트에저장한다. 인터럽트서비스가끝나후에프로세서는 [15:12] 비트가가리키는레지스터로복귀하고 multiple load 혹은 store와같은 multiple operation 명령을재개한다. EPSR이 ICI 실행상태를담아둘때는 [26:25, 11:10] 비트가 0 이다. If-Then 블록은 IT 명령다음에최대 4개까지명령을포함할수있다. 블록내각각의명령은조건명령이다. 이명령들의조건은전부같거나혹은일부는다른것과정반대가될수있다. 1.2 Cortex-M4F Floating Point Register FPU 는 32 개의 single-precision registers 를제공하며, 32x32-bit register, 16x64-bit register 혹은이둘의조합으로표시될수있다 1.3 파이프라인 288
Cortex-M4 프로세서는 3 단계파이프라인으로구성되어있으며, PC (Program Counter) 는파이프라인에서 Execute Stage의명령어주소값 + 4 를가리킨다. Prefetcher 는분기할곳의주소일부를미리가져오는 Branch Forwarding 을수행한다. IT 블록내의명령들은 folded out 될수있다. Cortex-M4 코어는 ARMv7-M architecture 로구현되며, ordering model 이매우간단하다. 그래서많은경우 barriers 명령없이소프트웨어를작성하는것이가능하지만 ARM은항상 barriers 명령사용을권장한다. 모든 load와 store 명령은항상프로그램순서대로완료된다. 모든 strongly-ordered load/stores 는명령스트림으로자동동기화된다. 인터럽트확인은 folded IT를제외하고 CPSIE 명령과 CPSID 명령사이의모든명령에대해서수행된다. 이같은단순한구현으로인해서코드가 Cortex-M4 에서실행될때 barriers 명령사용을상당히줄일수있다. 즉, Cortex-M4 에서실행되는모든 loads와 stores 명령순서대로수행되기때문에 DMB 명령의사용을줄일수있다. Cortex-M4 의 bit-band alias에대한액세스는 DMB 명령의개입을필요로하지않는다. Cortex-M4 의 instruction buffer로인해파이프라인동작이전통적인 RISC 프로세서와는다르다. 대부분의 instruction 이 16-bit 이므로한사이클에최대두개의명령이 fetch 될수있다. Decode 와 execution stages 의몇사이클전에명령어가 fetch 될수있다. 아래그림은 Cortex-M4 파이프라인시나리오를보여준다. 289
Fetch stage에서명령은 Code memory ( 혹은다른실행가능한메모리 ) 에서 fetch 되며, AHB 버스상의 word-aligned 32-bit read access 이다. 이는 32-bit opcode 하나이거나 16-bit 와 32-bit opcode 의절반으로이루어진조합이될수있다. Unaligned 32-bit branch target 는 fetch를위해두번액세스된다. Prefetch buffer는세개의 word로구성된다. 파이프라인 flush는 indirect branch, exception, breakpoint, ISB instruction 으로발생될수있다. 이때 prefetch buffer 역시 flush 된다. 직접분기는 opcode 내부에포함된 immediate (constant) offset 으로분기하며, target address 는 현재 PC 값 + offset 으로계산된다. 간접분기주소는 opcode 를아는순간발생되고, 조건에의한간접분기주소는추측에근거하여발생된다. 간접분기는레지스터에포함된값으로분기하게되며, 현재 PC 값에대해서계산된다. 분기주소는 opcode 자체에서추출될수없다. Taken branch 에따른분기패널티는줄어들게되는데이는분기주소가미리 fetch 되기때문이다. Untaken branch 는일반적으로파이프라인패널티가없다. 이는다음명령이이미 prefetch buffer 에존재하기때문이다. 이경우버스사이클은낭비되지만파이프라인은 stall 되지않는다. 서브루틴에서복귀하는경우도비슷하게 prefetch 된다. 복귀주소는 BX LR 혹은 MOV PC, LR 명령의 decode 시에발생된다. 만약, 명령이파이프라인을통과하지않으면이를 folded 되었다말한다. (zero 사이클에서실행 ) 이는명령어의영향이전부예측될수있고 prefetch buffer 가이미그다음명령을포함할경우에가능하다. Cortex-M4 파이프라인은 If-Then (IT) 를 fold 할수있다. 그러나분기에대한 pipeline folding 은지원하지않는다. 이는 Cortex-M 프로세서의경우게이트수가작은것이핵심적인특징이기때문이다. 즉, branch folding 은많은게이트수를소비한다. 290
1.4 Write Buffer 메모리에대한쓰기를디커플링하기위해서사용된다. 프로세서는데이터를버퍼에 write 하고나머지실행을계속할수있다. 버퍼에있는데이터는가능한시점에메모리에쓰여지게된다. 메모리액세스는항상순서대로발생하게된다. Read 동작은액세스전에 write buffer 가 drain 될것을요구한다. 1.5 Memory Map 프로세서코어는 4GB의고정된메모리맵을가진다. 그구성은아래의그램과같다.SRAM 과 Peripheral 영역은 bit-band 영역을포함할수있으며, bit-banding 은비트데이터에대해서 atomic operation 을제공한다.PPB(Private Peripheral Bus) 주소영역은 Core Peripheral Registers 를위해예비된영역이다. 아래테이블은메모리맵의각영역에대한액세스동작을나타내고있다. Address range 0x00000000-0x1FFFFFFF 0x20000000-0x3FFFFFFF 0x40000000-0x5FFFFFFF 0x60000000-0x9FFFFFFF 0xA0000000-0xDFFFFFFF 0xE0000000-0xE00FFFFF 0xE0100000-0xFFFFFFFF Memory region Memory type Code Normal - SRAM Normal - XN Peripheral Device XN E x t e r n a l RAM E x t e r n a l device P r i v a t e Peripheral Bus Description 프로그램코드에대한실행영역. 데이터가올수도있음. 데이터에대한실행영역, 코드가올수도있음. Normal - 데이터에대한실행영역 Device XN 외부디바이스메모리 Strongly - ordered XN Device Device XN 구현하기나름. NVIC, System timer, 그리고 system control block을포함 291
SRAM과 Peripheral 영역은 bit-band 와 bit-band alias 영역을포함할수있다. CODE, SRAM, 그리고 external RAM 영역에프로그램을저장할수있지만 ARM에서는프로그램이항상 Code 영역을사용하도록권장하고있다. Code 영역은프로세서가 instruction fetch와 data access 를동시에할수있도록별도의버스를가지기때문이다. 292
1.6 Bit-Banding ARM Instruction Set은바이트보다작은단위로메모리를액세스하는명령을제공하지않는다. 때문에기존에는바이트단위로메모리를읽어서각비트를 mask 하거나변경하여다시메모리에바이트단위로쓰는동작을수행했다. Bit-banding 이란 bit-band 영역에있는비트를 bit-band alias 영역에있는워드로각각맵핑하는것을말한다. 메모리맵에는두개의 1MB bit-band 영역과맵핑되는두개의 32MB alias 영역을가지고있다. 즉, 32MB SRAM alias 영역에대한액세스는 1MB bit-band 영역으로맵핑되고, 32MB peripheral alias 영역에대한액세스는 1MB peripheral bit-band 영역으로맵핑된다. 293
Address range 0x200FFFFF 0x23FFFFFF 0x400FFFFF 0x20000000-0x22000000-0x40000000-0x42000000-0x43FFFFFF Memory region SRAM bit-band region SRAM bit-band alias Peripheral bit-band alias Peripheral bit-band region Instruction and data access Direct accesses to this memory range behave as SRAM memory accesses, but this region is also bit addressable through bit-band alias Data accesses to this region are remapped to bit band region. A write operation is performed as read-modify-write. Instruction accesses are not remapped Direct accesses to this memory range behave as peripheral memory accesses, but this region is also bit addressable through bit-band alias. Data accesses to this region are remapped to bit band region. A write operation is performed as read-modify-write. Instruction accesses are not permitted. 아래공식은 bit-band alias 영역이어떻게 bit-band 영역으로맵핑되는지를보여준다. Bit_word_offset = (byte_offset x 32) + (bit_number x 4) Bit_word_address = bit_band_base + bit_word_offset Byte_offset 은 bit-band 영역에서해당비트가포함된바이트의위치를나타낸다. Bit_number 는해당비트의위치 (0 7) 를가리킨다. Bit_word_address 는 alias 메모리영역의워드주소로서 bit-band 영역의해당비트와맵핑된다. 예를들어, alias address 가 0x23FFFFE0 라면 bit-band alias 베이스주소 0x22000000 를빼면 0x1FFFFE0 가된다. 이중에서 bit_number x 4 로표현되는비트는 0 4 번비트까지이다. 따라서 bit_number 는이다섯개의비트를오른쪽으로 2비트시프트하고남는값이된다. 마찬가지로 0x1FFFFE0 를오른쪽으로 5비트시프트하고남는값이 byte_offset 이된다. 이렇게남는비트가 byte_offset x 32 를나타낸다. 그러므로 bit-band 의바이트주소는 0x200FFFFF (bit-band 베이스주소는 0x20000000) 이다. 예를하다더들면, bit-band alias가 0x23FFFFFC 일경우라면, bit_number 가 7 이된다. Byte_offset 은 0xFFFFF 이다. 따라서 bit-band 주소 0x200FFFFF 의 7번비트가 set 된것이다. 294
1.7 Aligned Access Aligned 된워드주소는 0부터시작해서 4의배수가되는주소이며이외의주소는 unaligned 주소가된다. Unaligned bus access 를하게되면예외를발생하는 Control 비트가존재한다. 1.8 Endian Configuration ARM은메모리의연속된바이트에주소를부여하고 ARM 레지스터는 32-비트폭을가진다. LDR/STR 동작후에메모리의내용과레지스터의내용을어떻게연결시키는지는엔디안 (endian) 에의해결정된다. ARM 프로세서는본질적으로리틀-엔디안이며, 명령어를액세스할때는항상리틀-엔디안으로동작한다. 리틀-엔디안에서프로세서는워드의최하위바이트를가장낮은번호를갖는바이트에저장한다. 295
프로세서를빅-엔디안메모리시스템에액세스하도록설정할수도있는데, 이를 byte-invariant big-endian 혹은 BE-8 이라고부른다. 데이터액세스에대한엔디안은리셋시에 BIGEND 입력으로결정된다. BE-8 인경우, 프로세서는워드의최상위바이트를가장낮은번호의바이트에다저당하고최하위바이트를가장높은번호의바이트에저장한다. 1.9 SCB (System Control Block) SCB는시스템구성에대한정보와시스템제어및 system exception 에대한내용등을포함한다. 이는클래식 ARM 코어의 co-processor #15를대체한다. Address Type Reset Value Function 0xE000E000 Read/Write 0x00000000 Master Control Register - RESERVED 0xE000E004 Read Only IMP DEFINED Interrupt Controller Type Register 296
0xE000ED00 Read Only IMP DEFINED CPUID Base Register 0xE000ED04 Read/Write 0x00000000 Interrupt Control State Register 0xE000ED08 Read/Write 0x00000000 Vector Table Offset Register 0xE000ED0C Read/Write Bits[10:8] = 000 Application Interrupt/Reset Control Register 0xE000ED10 Read/Write 0x00000000 System Control Register 0xE000ED14 Read/Write 0x00000000 Configuration Control Register 0xE000ED18 Read/Write 0x00000000 System Handlers 4-7 Priority Register 0xE000ED1C Read/Write 0x00000000 System Handlers 8-11 Priority Register 0xE000ED20 Read/Write 0x00000000 System Handlers 12-15 Priority Register 0xE000ED24 Read/Write 0x00000000 System Handler Control and State Register 0xE000ED28 Read/Write n/a status Configurable Fault Status Registers (3) 0xE000ED2C Read/Write n/a status HardFault Status Register 0xE000ED30 Read/Write n/a status DebugFault Status Register 0xE000ED34 Read/Write Unpredictable MemManage Address Register 0xE000ED38 Read/Write Unpredictable BusFault Address Register 0xE000ED3C Read/Write Unpredictable Auxiliary Fault Status Register (vendor specific) 0xE000EF00 Write Only Software Trigger Interrupt Register 2. ARM v7-m 프로그래머모델 Cortex-M4 와같은 v7-m 코어는마이크로컨트롤러시장을겨냥해서설계되었다. 전체어플리케이션을 C로프로그램할수있어프로그램이더욱간단해졌고 AP (Application Processor) 프로세서보다는기능이작다. 레지스터와 ISA (Instruction Set Architecture) 부분은클래식 ARM 코어와는다른약간의변화가생겼다. 즉, ARM Instruction Set을지원하지않으며, 단하나의레지스터셋만을지원한다. xpsr 레지스터는 CPSR 레지스터와는다른비트들을가진다. 프로세서모드와예외처리모델에도차이가존재한다. ARMv7-M 프로파일은 Thread 와 Handler 두가지모드만을지원하고벡터테이블은명령이아닌주소로채워진다. 그리고예외가발생하면스택에현재상태를 (R0 R3, R12, LR, xpsr, PC) 자동으로저장한다. 프로세서코어는고정된메모리맵을가지며코프로세서 15번이없는대신 memory mapped control register를통해제어한다. ARMv7-M 도 RISC 아키텍처를채택하고있으며 32-비트 load/store 아키텍처로서대부분의명령을단일사이클내에실행한다. 메모리액세스는 load와 store 명령에 297
의해서만허용된다. ARM 과관련해서사용되는데이터타입은 Byte 가 8- 비트, Halfword 가 16- 비트, Word 가 32- 비트, 그리고 Doubleword 가 64- 비트로정의되어있다. 2.1 프로세서모드와실행권한 어플리케이션은 Thread mode에서실행되며리셋을빠져나올때이모드로진입하게된다. 반면에, Handler mode는예외처리에사용된다. 모든예외처리가완료되면다시 Thread mode로복귀한다. Handler mode는항상특권 (privileged) 권한을가지지만 Thread mode는 CONTROL 레지스터의 npriv 값에따라비특권 (Non-privileged) 또는특권 (Privileged) 권한을가질수있다. 비특권권한에서는 MRS와 MSR 명령에의한액세스가제한되고 CPS 명령을사용할수없다. 또한시스템타이머 (System timer), NVIC, 그리고 SCB (System Control Block) 에액세스할수없다. 그리고메모리와주변장치에대한액세스가제한될수있다. 특권권한을갖는소프트웨어만이 CONTROL 레지스터를 write 할수있기때문에 Thread mode에서실행되는소프트웨어를특권권한으로변경할수있다. 비특권권한의소프트웨어는 SVC 명령을사용하여특권권한을갖는소프트웨어로제어권을넘겨줄수있다. 2.2 스택 ARM 프로세서는 full descending 방식으로스택을사용한다. 이는스택포인터가마지막으로메모리에스택되어진주소를보관하고있다는의미이다. 프로세서가새로운내용을스택에푸시 (push) 할때는스택포인터를먼저감소시키고새로가 298
리키는위치에그내용을저장한다. Cortex-M 프로세서는두개의스택을구현하는데각각독자적인레지스터를가지는메인스택 (main stack) 과프로세스스택 (process stack) 이그것이다. CONTROL 레지스터를통해 Thread mode에서메인스택을사용할지혹은프로세스스택을사용할지를제어한다. Handler mode에서프로세서는항상메인스택을사용한다. 리셋상태에서빠져나오면 Thread mode가 MSP (main stack pointer) 를사용하고 MSP의초기값은벡터테이블의첫번째엔트리에서가져온다. Thread mode에서는 PSP (Process stack pointer) 를선택하여사용할수도있으며, CONTROL 레지스터의 SPSEL 비트를통해인에이블된다. PSP는사용되기전에사용자에의해초기화되어야한다. 2.3 예외종류 (Exception Types) 예외에는아래와같은종류가있다. Reset 전원이인가되거나리셋버튼을눌렀을때호출된다. 리셋의경우는예외이지만특별히 thread mode에서처리된다. 리셋이되면프로세서는동작을멈추고, 리셋에서빠져나오면벡터테이블의리셋엔트리에있는주소에서부터실행을재개한다. 이때 thread mode는특권권한으로실행을재개한다. NMI NMI (Non Maskable Interrupt) 는주변장치나소프트웨어에의해트리거될수있으며리셋을제외하고가장높은우선순위를가진다. NMI는항상인에이블되며 -2 의고정된우선순위를가진다. NMI는다른예외에의해 mask 되거나 activate 될수없으며리셋을제외한다른예외에의해선점 (preempt) 되지않는다. Faults 외부메모리시스템에의해서발생되는 Data abort로서 Hard fault, MemManage fault, Bus fault, Usage fault가있다. PendSV 인터럽트로구동되는시스템- 레벨l의서비스에대한요청이다. OS 환경에서다른예외가 active 되어있지않다면문맥전환 (context switching) 을위해 PendSV를사용한다. 이는 Supervisor Call (SVC) 과비슷하지만 NVIC control register 의 PENDSVSET 비트로설정되고, 현재태스크가완료되기를기다리는다른 Supervisor Call 태스크가 299
있다는것을가리키도록 OS 호출내에서사용된다. SVCall SVC 명령에의해트리거된다. SysTick 시스템타이머가 0에도달하면예외를발생시킨다. 소프트웨어도 SysTick 예외를발생시킬수있다. OS 환경에서는프로세서가시스템틱 (system tick) 으로이예외를사용한다. Inerrupt (IRQ) 주변장치나혹은소프트웨어요청에의해발생되는예외. 2.4 명령어셋에대한지원 ARMv7-M 코어는 Thumb-2 기술이적용된 Thumb Instruction Set를구현한다. 거의모든 ARM Instruction Set의기능을구현하도록 16-비트와 32-비트명령들이혼합되어있다. 메모리내용은직접다루지않고 load/store instruction set을이용한다. 명령어의길이는기능에따라변할수있다. v7e-m 아키텍처는 DSP 명령들을추가하고있으며, 현재 Cortex-M4 에서지원된다. Single precision floating point instruction 들은현재 Cortex-M4F 에서지원된다. 보다자세한내용은 ARM사에서제공되는 TRM (Technical Reference Manual) 을참조할수있다. Cortex-M 프로세서코어가지원하는명령어들은 Binary Upwards Compatibility 를제공한다. 300
2.5 실습예제 Cortex-M4 프로세서코어기반의마이크로컨트롤러를이용해서예제프로그램을실행해보기로한다. 프로그램에서사용된코드는 ARM사의 MDK-ARM 툴을이용하여빌드를진행한다. 301
먼저, 여러분의 PC에 MDK-ARM 툴이설치되어있는것으로가정한다. MDK-ARM 툴에대한소개와설치및사용법은여기서는생략하기로한다. 우리가사용할마이크로컨트롤러는 ST Microelectronics 사에 Cortex-M4F 코어를베이스로설계한 STM32F401RE 디바이스이다. 디바이스의구조는앞의그림과같다. Cortex-M4 프로세서코어주변으로다양한주변장치들이연결된것을확인할수있다. 위그림을간단한블록으로아래그림과같이간단하게요약할수있다. 최대한쉽게설명하기위해마이크로컨트롤러외부의 LED를마이크로컨트롤러내부의 I/O 제어포트에연결해놓은간단한회로부터시작한다. 프로그램은프로세서코어가 I/O 장치를제어하여 LED를 ON/OFF 하는코드로작성될것이다. 실습에사용할보드는 STM32F401RE 디바이스가장착된 ST Microelectronics 사의 Nucleo F401RE 보드이다. 1. MDK-ARM 툴의 uvision 을실행하고새로운프로젝트를생성한다. Project New uvision Project 메뉴를선택한다. 302
사용할디바이스로 STM32F401RETx 를선택한다. Management Run-Time Environment 창이팝업되면 Board Support 항목에서 NUCLEO-F401 을선택하고 LED 항목을체크한다. 이어서 CMSIS-CORE 와 Device-Startup 항목을체크한다. LED 항목은마이크로컨트롤러의 GPIO 장치에대한디바이스드라이버코드를프로젝트에추가하게된다. CORE 항목은 Cortex-M4 프로세서코어를제어하는데필요한소스코드를포함하며, Startup 항목은타겟보드에전원이인가되었을때, 처음실행하게되는코드로서프로세서와타겟보드가정상적으로동작하는데필요한설정을하게되는코드로구성되어있다. 이항목들을체크함으로써필요한코드들이프로젝트에추가된다. 때문에사용자가직접작성해야할코드양이상당히줄어들게되고, 사용자는자신의어플리케이션에집중할수있다. Management Run-Time Environment 창은 uvision에서언제라도호출하여불러올수있다. 303
이제 main 함수를포함한코드를프로젝트에추가해야한다. uvision에서프로젝트항목을선택하고마우스우측버튼을클릭하여 Add New Item to Group 메뉴를선택한다음, C 소스파일을만든다. 여기서는파일이름을 main 으로지정했다. 304
Add 버튼을클릭하여 main.c 파일을프로젝트로추가한다. uvision 의프로젝트 창을보면지금까지의과정을통해프로젝트에새로운파일들이추가된것을 확인할수있다. 2. Main 함수작성 헤더파일추가 305
uvision의 Project 창에서 main.c 파일을더블-클릭하여편집창에서연다. 현재는아무런내용이없는빈파일이열릴것이다. 먼저, 프로그램에서참조할헤더파일을추가한다. Board Support LED 항목을선택한것을기억할것이다. 이를통해 LED를제어하기위한소스코드와헤더파일이프로젝트에이미추가되어있다. Main 함수에서 LED 소스를참조할것이기때문에 Board_LED.h 와마이크로컨트롤러의주변장치를제어하기위한 stm32f4xx.h 디바이스헤더파일을포함시킨다. #include "Board_LED.h" #include "stm32f4xx.h" // ::Board Support:LED // Device header 시스템코어클럭설정함수작성 프로세서는클럭에동기되어동작하는디지털소자이다. 따라서프로세서가사 용할클럭을만들어제공해주어야한다. 지금사용하고있는 STM32F401RE 마 이크로컨트롤러는최대 84MHz 에서동작하므로이클럭을만들수있어야한 다. 마이크로컨트롤러외부에는비교적낮은주파수의클럭공급소자 ( 오실레 이터혹은크리스털 ) 가장착된다. 이클럭소스를입력받아프로세서가사용할 클럭속도로만들어준다. STM32F401RE 에대한데이터시트를보면 RCC 레지 스터를설정해서필요한클럭을분주하는방법이설명되어있다. 그러나왠만한 개발경험이있는개발자라하더라도데이터시트만으로원하는동작을구현하 기가쉽지않다. ST Microelectronics 사는자사마이크로컨트롤러의클럭설정 코드를쉽게생성해주는 cubemx 라는유틸리티를제공하고있다. 아래코드 내용은이유틸리티를이용해생성된코드를그대로가져다사용한것이다. 함 수이름은 SystemCoreClockConfigure 로한다. 이함수이름은마이크로컨트롤 러의시스템디바이스코드에서도참조한다. /*---------------------------------------------------------------------- ------ * SystemCoreClockConfigure: configure SystemCoreClock using HSI (HSE is not populated on Nucleo board) *----------------------------------------------------------------------- -----*/ void SystemCoreClockConfigure(void) { 306
RCC->CR = ((uint32_t)rcc_cr_hsion); while ((RCC->CR & RCC_CR_HSIRDY) == 0); // Enable HSI // Wait for HSI Ready RCC->CFGR = RCC_CFGR_SW_HSI; // HSI is system clock // Wait for HSI used as system clock while ((RCC->CFGR & RCC_CFGR_SWS)!= RCC_CFGR_SWS_HSI); FLASH->ACR = FLASH_ACR_PRFTEN; Buffer FLASH->ACR = FLASH_ACR_ICEN; enable FLASH->ACR = FLASH_ACR_DCEN; FLASH->ACR = FLASH_ACR_LATENCY_5WS; RCC->CFGR = RCC_CFGR_HPRE_DIV1; RCC->CFGR = RCC_CFGR_PPRE1_DIV4; RCC->CFGR = RCC_CFGR_PPRE2_DIV2; RCC->CR &= ~RCC_CR_PLLON; // Enable Prefetch // Instruction cache // Data cache enable // Flash 5 wait state // HCLK = SYSCLK // APB1 = HCLK/4 // APB2 = HCLK/2 // Disable PLL // PLL configuration: VCO = HSI/M * N, Sysclk = VCO/P RCC->PLLCFGR = ( 16ul // PLL_M = 16 (384ul << 6) // PLL_N = 384 ( 3ul << 16) // PLL_P = 8 (RCC_PLLCFGR_PLLSRC_HSI) // PLL_SRC = HSI ( 8ul << 24) ); // PLL_Q = 8 RCC->CR = RCC_CR_PLLON; // Enable PLL while((rcc->cr & RCC_CR_PLLRDY) == 0) NOP(); // Wait till PLL is ready // Select PLL as system clock source RCC->CFGR &= ~RCC_CFGR_SW; RCC->CFGR = RCC_CFGR_SW_PLL; // Wait till PLL is system clock src while ((RCC->CFGR & RCC_CFGR_SWS)!= RCC_CFGR_SWS_PLL); Delay 함수작성 이번에는시간을측정하는함수를작성하기로한다. LED 소자가깜박이는것을 307
눈으로확인하기위해서는 LED ON/OFF 사이에지연시간이필요하다. 필요한 지연시간을구현하기위해서여기서는프로세서내부의 SysTick 타이머를이용 한다. SysTick 타이머값이 0 에도달하게되면인터럽트가발생하게된다. 이인 터럽트의발생회수를카운트하면시간측정이가능해진다. volatile uint32_t msticks; // counts 1ms timeticks /*---------------------------------------------------------------------- ------ * SysTick_Handler: *----------------------------------------------------------------------- -----*/ void SysTick_Handler(void) { msticks++; /*---------------------------------------------------------------------- ------ * Delay: delays a number of Systicks *----------------------------------------------------------------------- -----*/ void Delay (uint32_t dlyticks) { uint32_t curticks; curticks = msticks; while ((msticks - curticks) < dlyticks) { NOP(); 이제 main 함수의내용을채울차례이다. 308
/*---------------------------------------------------------------------- ------ * main: blink LED *----------------------------------------------------------------------- -----*/ int main (void) { int32_t max_num = LED_GetCount(); // 보드에장착된 LED 수확인여기서는 1 int32_t num = 0; SystemCoreClockConfigure(); SystemCoreClockUpdate(); LED_Initialize(); // configure HSI as System Clock // 실행중인코어의클럭을변경 // LED 동작을위한 I/O 포트초기화 SysTick_Config(SystemCoreClock / 1000); interrupts for (;;) { LED_On(num); Delay(500); LED_Off(num); Delay(500); // SysTick 1 msec // Turn specified LED on // Wait 500ms // Turn specified LED off // Wait 500ms num++; if (num >= max_num) { num = 0; // Change LED number // Restart with first LED LED_GetCount 함수는보드에장착된 LED의수를반환한다. Manage Run-Time Environment 를통해추가한파일에정의되어있으며, 반환값은 1 이다. SystemCoreCloukUpdate 함수는프로세서가실행중에도코어클럭을변경할수있게해준다. 코어프로세서의주변장치에액세스하는소스파일에정의되어있다. LED_Initialize 함수는 Manage Run-Time Environment를통해추가한파일에정의되어있으며 LED를제어하기위한 GPIO 포트를설정한다. LED_On/LED_Off 함수는 GPIO 포트출력을통해직접 LED를제어한다. Manage 309
Run-Time Environment 를통해추가한파일에정의되어있다. 3. 프로젝트빌드와디버깅소프트웨어생성에필요한소스코드가모두준비되었다. uvision 을이용하여소스코드를빌드하고, uvision 의디버그기능을이용하여코드가실제로어떻게실행되는지그리고타겟보드에서어떻게동작하는지살펴본다. 프로젝트빌드 파일을저장한다음, uvision 의 Project Build Target (F7) 또는 Project Rebuild all target files 메뉴를선택하여프로그램을빌드한다. 빌드과정이끝나면 uvision 의하단 Build Output 창에실행결과를보여주는메 시지가출력된다. 에러나경고문구가없는지확인한다. 310
디버그설정타겟보드와호스트 PC 간에 USB 케이블을연결한다. NUCLEO F401RE 보드는 USB 인터페이스를통해세가지기능을제공하고있다. 이를통해사용자는 ST-Link debug adapter, mass storage, 그리고 virtual COM port 를별도의장치나장비없이이용할수있다. 또한타겟보드의전원도 USB 케이블을통해공급받기때문에특별한이유가없다면외부전원공급장치가필요없다. 디버깅을진행하기앞서디버그설정을진행해야한다. uvision 에서 Project Options for Target (Alt+F7) 메뉴를선택한다. Debug 탭을선택하고드롭-다운메뉴에서 ST-Link Debugger 항목을선택한다. 앞서언급한대로 NUCLEO F401RE 보드에는 ST Microelectronics 사의 ST Link debug adapter 가내장되어있기때문에별도의디버그에뮬레이터가필요없다. 그리고 Run to main() 항목이체크되어있으면이를해제한다. 이항목은디버그모드로진입하면 main 함수까지코드를실행한다음타겟을멈춘다는의미이다. 우리는어플리케이션에서처음으로실행되는 Startup 코드부터살펴볼것이기때문에체크를해제한다. 311
이어서 Settings 버튼을클릭한다. 타겟보드와디버그에뮬레이터가정상적으로 연결되면아래그림처럼 SW Device 목록에디바이스가표시된다. Trace 탭을클릭하면 Core Clock 필드에 CPU가사용하는클럭주파수가표시된다. 여기서표시되는클럭주파수는데이터 Trace 시에사용되는클럭주파수이기때문에프로그램실행에는영향이없지만우리가사용하는코어클럭주파수 84MHz를정확히기재한다. 설정이끝났으면확인버튼을누르고화면에서빠져나온다. 디버그실행 uvision 에서디버그아이콘을클릭하거나 Ctrl+F5 키를눌러디버그세션에진입 312
한다. 코드가타겟보드로다운로드되고 uvision 의디버그화면으로전환되는 것을볼수있다. 타겟보드는 Reset_Handler 의첫번째명령어에서멈추게된 다. 4. 디버깅을통한코드분석과프로세서동작확인 벡터테이블소스창을스크롤하여프로그램시작위치로옮긴다. 프로그램에서사용할스택과힙영역의베이스주소와크기가정의되어있다. ARM 어셈블리코드로작성되어있기때문에어셈블리언어에대한지식과 ARM 컴파일러툴에대한배경지식이있어야만코드내용을충분히이해할수있을것이다. 코드에대해서는개략적인내용만살펴보고여기서는우리의관심을 Cortex-M 프로세서의아키텍처관점에두고이를이해해나가기로한다. 다시소스창을아래로조금만스크롤하면벡터테이블이코드로구현되어있 313
는것을볼수있다. Vectors 라는 label에서부터시작하는코드가 4바이트크기의엔트리들로작성되어있다. 각엔트리들은예외적인상황이발생하게되면해당예외를처리하기위해분기해야하는주소값들로채워지게된다. 때문에이를벡터테이블이라는이름을붙여부른다. 벡터테이블의첫번째엔트리는 initial_sp 로서스택공간으로할당된메모리의베이스주소값이다. 이값은코드의처음시작부분에정의되어있다. 두번째엔트리는 Reset_Handler 함수의주소값이다. 앞서우리는이함수에서타겟보드가멈춘것을확인하였다. 즉, 전원이인가되면이함수가첫번째로실행됨을알수있다. 부연해서설명하면타겟보드에전원을했을때 Cortex-M 프로세서아키텍처부분에서살펴봤던 NVIC 로직에의해벡터테이블의첫번째엔트리값은스택포인터 (R13) 로로드하고, PC (R15) 에는두번째엔트리의값이로드된다. 때문에처음으로시작하는함수가 Reset_Handler 가되는것이다. 이제 Registers 창의내용을잠깐살펴보기로한다. 스택포인터로사용되는 R13 레지스터와프로그램카운터로사용되는 R15 레지스터의값을확인하다. Banked 항목에는 MSP와 PSP 레지스터가있다. 현재 MSP 값이 0x20000668 이며, 이값은 R13 (SP) 의값과일치하고있다. 즉, 현재사용중인스택은 MSP 라는것을알수있다. Internal 부분을살펴보면현재프로세서의실행모드가 Thread 이며, 실행권한은 Privileged 라는것을표시하고있다. Cortex-M 아키텍처부분에서설명한대 314
로전원공급시발생하게되는리셋은매우특별한예외로서기본설정이특 권권한을가지며, MSP 를스택포인터로사용하고다른예외상황과달리 Thread 모드에서동작한다는것을보여주고있다. Reset Handler 타겟보드에전원이인가되면벡터테이블의리셋핸들러코드가처음으로실행된다고설명했다. 우리코드의리셋핸들러는 Reset_Handler 라는이름의함수이다. 이함수는 SystemInit 함수와 main 함수를차례로호출하고있다. SystemInit 315
함수는타겟보드가정상적으로동작하기위해서, 필요한하드웨어를초기화한 다. 아래 SystemInit 코드는 FPU, Clock, Memory Controller 등을초기화하고있 다. void SystemInit(void) { /* FPU settings ------------------------------------------------------------*/ #if ( FPU_PRESENT == 1) && ( FPU_USED == 1) SCB->CPACR = ((3UL << 10*2) (3UL << 11*2)); /* set CP10 and CP11 Full Access */ #endif /* Reset the RCC clock configuration to the default reset state ------------*/ /* Set HSION bit */ RCC->CR = (uint32_t)0x00000001; /* Reset CFGR register */ RCC->CFGR = 0x00000000; /* Reset HSEON, CSSON and PLLON bits */ RCC->CR &= (uint32_t)0xfef6ffff; /* Reset PLLCFGR register */ RCC->PLLCFGR = 0x24003010; /* Reset HSEBYP bit */ RCC->CR &= (uint32_t)0xfffbffff; /* Disable all interrupts */ RCC->CIR = 0x00000000; #if defined (DATA_IN_ExtSRAM) defined (DATA_IN_ExtSDRAM) SystemInit_ExtMemCtl(); #endif /* DATA_IN_ExtSRAM DATA_IN_ExtSDRAM */ /* Configure the Vector Table location add offset address ------------------*/ #ifdef VECT_TAB_SRAM SCB->VTOR = SRAM_BASE VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */ #else 316
SCB->VTOR = FLASH_BASE VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */ #endif 하드웨어초기화과정이끝나면 ARM 컴파일러툴에서제공하는라이브러리의시작함수인 main 을호출한다. main 함수는 Linker 에의해설정된주소에따른코드재배치, 압축된파일의해제, 라이브러리함수의초기화등과같은일을처리한다음, main 이라는이름이할당된주소로분기한다. 즉, 사용자가작성하는어플리케이션의시작함수로분기가일어나게되는것이다. Command 창에서디버그명령으로 main 함수에 breakpoint 를설정한다. Breakpoint 가설정되었다면다시한번 go 명령을실행하거나 Run 실행아이콘을클릭하거나또는 F5 단축키를클릭하여프로그램을실행한다. 프로그램은 main 함수에설정된 breakpoint 위치에서멈추게될것이다. 다시한번 Registers 창을보면푸른색으로표시된부분은레지스터값이변경되었음을의미한다. 프로세서모드는여전히 Thread 모드이며특권권한을가진다는것을알수있다. 참고로, 권한변경은시스템레지스터인 COTROL 레지스터를통해변경할수있다. 317
디바이스드라이버코드분석 디버그창에서 Step Over (F10) 키를눌러 main 함수에서 LED_Initialize 함수까지 실행한다. 318
LED_Initialize 함수에커서가위치하면 Step (F11) 키를눌러함수의내부로진 입한다. 함수는 LED가연결된 GPIO 포트의클럭을인에이블하고, 포트의동작을설정하고있다. RCC 레지스터는시스템내부주변장치의베이스주소, 0x4000_0000 번지에서 0x23800 번지오프셋위치에존재한다. 이주소값은 stm32f401xe.h 헤더파일에정의되어있다. 앞서아키텍처부분에서살펴본대로 Cortex-M 디바이스는고정된메모리맵을가진다. 따라서시스템내부의주변장치는 0x4000_0000 0x6000_0000 주소범위내에위치해야한다. STM32F401 디바이스의데이터시트에는 RCC 베이스주소가 0x4000_23800 번지에할당되어있다고기술하고있다. 다시말해 RCC는 Cortex-M 디바이스의고정된메모리맵내의올바른위치에할당되어있으며, 이를코드로기술하고있는것이다. 한편, GPIOA 포트는 0x4002_0000 번지를베이스주소로갖는다. 이역시같은헤더파일내에정의되어있으며, STM32F401 디바이스의데이터시트에있는내용을그대로반영하고있다. 코드에서는 GPIOA 포트를구조체구문으로표현하고 GPIOA 포트의개별설정레지스터는구조체멤버로액세스한다. 레지스터에대한설명과레지스터의각비트에대한기능은 STM32F401 데이터시트를참조하면상세히알수있다. 아래는이를간략히소개하는내용이다. GPIO_MODER (Mode Register): 00-Input, 01-Output, 10-Function Mode, 11-Analog Mode 319
GPIO_OTYPER (Output Type register): 0-Output Push-pull (Reset), 1-Output Open-drain GPIO_OSPEEDR (Output Speed Register): 00-Low, 01-Medium, 10-Fast, 11-High GPIO_PUPDR (Pull-up, Pull-down Register): 00-No pull-up, pull-down, 01-Pull-up, 10-Pull-down, 11- Reserved GPIO_IDR (Input Data Register): Read-Only. word mode access only. contain input value of corresponding I/O port. GPIO_ODR (Output Data Register): Read and Written by software. These bits can be individually set and reset by writing to the GPIO_BSRR GPIO_BSRR (Bit Set Reset Register) 마이크로컨트롤러의 GPIO는다양한용도로사용될수있도록 multi-function 으로구현된다. 즉, GPIO 포트핀을설정에따라입력으로도사용할수있고, 출력으로도사용할수있다. 뿐만아니라데이터통신과같은다른용도로도사용할수있다. 우리가구현하려고하는예제는 LED를제어할목적이므로출력포트로할당하여사용한다. 어플리케이션에서디바이스제어 LED_Initialize 함수를통해 LED를제어할 GPIOA 포트의특정핀을출력으로사용하도록설정한다음, 다시 main 함수로되돌아오면무한루프내의코드는 500 밀리초간격으로 LED를 ON/OFF 한다. LED_Initialize(); // LED 동작을위한 I/O 포트초기화 SysTick_Config(SystemCoreClock / 1000); for (;;) { LED_On(num); Delay(500); LED_Off(num); Delay(500); // SysTick 1 msec interrupts // Turn specified LED on // Wait 500ms // Turn specified LED off // Wait 500ms num++; if (num >= max_num) { num = 0; // Change LED number // Restart with first LED 320
LED 를 ON/OFF 하는함수는 LED_On 과 LED_Off 함수이다. LED ON/OFF 사이 의지연시간은 Delay 함수로구현하고있다. 이전과동일한방식으로 LED_On 함수내부로진입한다. int32_t LED_On (uint32_t num) { if (num < LED_NUM) { GPIOA->BSRR = (led_mask[num]); return (0); int32_t LED_Off (uint32_t num) { if (num < LED_NUM) { GPIOA->BSRR = (led_mask[num] << 16); return (0); LED를 ON/OFF 하는함수는 LED가연결된 GPIOA 포트의특정핀에대해출력값을 1로셋하고, 0으로클리어한다. 시스템내부의주변장치를제어하는방법을요약하면, 이들주변장치들은 Cortex-M 디바이스의고정된메모리맵내부에위치하고, 이들에대한제어를위해구조체구문의형태로정의하고있다. 디바이스드라이버코드는구조체로정의된디바이스를액세스하여용도에맞게초기화한다. 끝으로사용자어플리케이션에서는필요에따라해당디바이스를액세스하여동작시킨다. SysTick 타이머와인터럽트 앞서우리는 Delay 함수를구현했다. 이를조금더자세히살펴보기로한다. 321
void Delay (uint32_t dlyticks) { uint32_t curticks; curticks = msticks; while ((msticks - curticks) < dlyticks) { NOP(); 코드는매우간단하다. 사용자가지정한지연시간을 dlyticks 변수로전달한다. 첫번째코드는 msticks 의값을 curticks 변수에저장한다음 msticks 값과 curticks 값과의차이를 dlyticks 와비교하여이값이 dlyticks 보다커질때까지 while 루프내에서머문다. 이코드가성립하려면 msticks 의값이계속변해야 만한다. 다음은 SysTick_Handler 함수를살펴보자 void SysTick_Handler(void) { msticks++; 이함수내부는함수가호출때마다 msticks 변수를 1 증가하는것이전부다. 즉, msticks 의값은 SysTick_Handler 함수가호출된회수를의미한다. 기억할지모르지만 SysTick_Handler 함수는벡터테이블의 16번째엔트리에등록되어있다. 한가지기억해야할점은함수이름은주소를담아두는심볼로생각해야한다는점이다. 벡터테이블에서 16번째엔트리까지는프로세서내부에서발생하는예외들에대해서처리루틴을포함하는함수의주소가저장된다. 이들에대한엔트리위치는정해져있다. 따라서 SysTick 타이머에의해인터럽트가발생되면벡터테이블에등록된 SysTick_Handler 함수가호출되는것이다. 그러므로이함수의호출회수는 SysTick 타이머에의한인터럽트발생회수가되고, SysTick 타이머인터럽트는일정시간마다주기적으로발생하기때문에시간측정이가능해진다. SysTick_Handler 함수에 breakpoint 를설정하고, msticks 변수를선택한다음마우스우측버튼을클릭한다. 팝업메뉴에서 Add msticks to.. Watch1 메뉴를선택한다. 이는 msticks 변수를 watch 창에등록하여변수값의변화를관찰하기위해서이다. 322
이제 Run (F5) 버튼을클릭하여프로그램을실행한다. 프로그램이실행되면곧바로 breakpoint 위치에서프로그램이멈추게될것이다. 즉, SytTick 타이머에의해인터럽트가발생하고벡터테이블의 16번째엔트리주소가 PC로로드되어현재의 breakpoint 위치로오게된것이다. 몇번더실행버튼을클릭하고 Watch 1 창에등록된변수가변하는지살펴본다. 우리는 SysTick 타이머설정에서 1 밀리초로설정했기때문에 500번호출되면 500ms 의지연시간을갖게되는것으로계산할수있다. 지금까지 Cortex-M 프로세서코어의아키텍처에대한내용을간략히살펴보았고, 하드웨어제어에있어서가장단순한형태인 LED 제어실습코드를통해아키텍처의일부내용을확인해보았다. Cortex-M 프로세서코어는 ARM사에서디자인한다른프로세서코어에비해단순하고사용하기쉽게설계되었지만이를처음접하게되면배워야할내용이상당히많고어렵게느껴질것이다. 그러나어렵더라도주어진예제코드의일부만을변경해서사용하는수준을벗어나 Cortex-M 아키텍처가제공하는뛰어난기능들을제대로활용하게되면이프로세서가상당히많은일을할수있고매우강력하다는것을알게될것이다. 323