[본 문서는 서울 IoT 센터에서 진행할 교육을 위해 작성된 한글문서입니다.]
[2019. 8. 14 업데이트]
이 강좌는 아래 샘플소스와 함께 기본으로 설명하고 있습니다. 우선 아래 소스를 받아주세요.
```sh
git clone https://git.tizen.org/cgit/apps/native/rcc illuminance -b illuminance
```
:::hw
Board|RPI3 Model B+|1|based on Tizen 5.5
Sensor|Light Sensor|1|GY-302 – Light Intensity Sensor Module(BH1750)
:::
:::sw
OS|Tizen 5.5|IoT Platform image
SDK|Tizen Studio 3.3|IoT-Headless Extension
:::
# 조도센서
## 조도는 무엇인가
조도란 단위면적당 비춰지는 빛의 밝기를 의미한다. 조도는 인간의 눈으로 감지해낼 수 있는 파장을 기준으로 만들어졌다.
**밝기차**|**예**
:-----:|:-----:
0.00001 lux|가장 밝은 별(시리우스)의 빛
0.0001 lux|하늘을 덮은 완전한 별빛
0.002 lux|대기광이 있는 달 없는 맑은 밤 하늘
0.01 lux|초승달
0.27 lux|맑은 밤의 보름달
3.4 lux|맑은 하늘 아래의 어두운 황혼
50 lux|거실
80 lux|복도/화장실
100 lux|매우 어두운 낮
320 lux|권장 오피스 조명 (오스트레일리아)
400 lux|맑은 날의 해돋이 또는 해넘이
1000 lux|인공 조명; 일반적인 TV 스튜디오 조명
10,000–25,000 lux|낮 (직사광선이 없을 때)
32,000–130,000 lux|직사광선
럭스의 예(출처 : 위키피디아 럭스 항목)
단위로는 럭스를 사용하고 lx라고 표기한다. 촛불 하나를 1m 밖에서 볼 때 밝기가 1lx이다. 따라서 lx를 우리말로 촉광(candle-light)이라고 표현하기도 한다. 실내공간의 조도는 50~500lx 정도이다. 화창한 낮, 태양의 조도는 4만에서 10만lx에 이른다.
### QUIZ : 이 곳의 조도는 어느 정도일까?
## 조도센서 준비하기
조도를 측정하는 조도센서는 인간의 생활공간의 조도를 바탕으로 만들어졌다. 사람 눈의 감도를 기준으로 조도센서의 감도를 보정한다. 센서의 감도가 눈의 감도와 다른 경우 보정필터 등을 추가로 사용하여 감도를 보정하기도 한다.
대다수의 조도센서는 0lx부터 65535lx까지의 범위에서 동작한다. 이 범위로 빛이 없는 공간의 조도부터 직사광선이 내리쬐는 대낮의 조도까지 측정할 수 있다. 1lx보다 훨씬 작은 단위에서 움직이는 별빛은 일반적인 조도센서가 측정할 수 없다.

위의 센서는 조도센서 모델 GY-302이다. 검색 엔진에서 쉽게 찾을 수 있다. 가격도 비교적 저렴하여 몇천원 정도면 구매할 수 있다. 해외업체에서 센서를 주문하면 국내업체보다 배송이 오래 걸리지만 보다 저렴하다. 자신의 상황에 맞추어 센서를 구매한다.
### QUIZ : 조도를 측정하는 칩은 무엇일까?
## RPI3와 조도센서를 물리적으로 연결하기
이제 조도센서를 RPI3에 연결해보자. 조도센서의 핀 5개를 RPI3의 핀에 연결해보려 한다. 각 핀에서 요구하는 전압은 아래 표와 같다. 센서의 각 핀에서 요구하는 전압을 반드시 확인한다. 만약 센서에서 요구하는 전압보다 낮은 값이 들어가면, 센서는 제대로 동작하지 않는다. 반대로 더 높은 경우에는 센서에 문제가 생길 수도 있다. 센서가 뜨거워지고 김이 모락모락 피어오르면 타버린 것이다.
**항목**|**내용**
:-----:|:-----:
Vcc|2.4V ~ 3.6V
GND|0V
SCL, SDA High 값|2.1V ~ 3.6V
SCL, SDA Low 값|0.0V ~ 0.9V
ADDR|GND 혹은 Vcc
GY-302이 동작하려면 Vcc에 DC 2.4V ~ 3.6V 범위 내의 전압이 필요하다. 다행스럽게도 RPI3는 DC 3.3V의 전압을 제공하고 있다. 따라서 GY-302은 RPI3로부터 지속적으로 전원을 공급받을 수 있다.
GY-302의 SCL과 SDA 핀으로 데이터 통신을 하게 된다. 이 때 두 핀의 최대 허용전압은 3.6V이다. RPI3의 경우 SCL과 SDA 핀은 0~3.3V의 전압 범위를 가지고 동작하고 있다. 따라서 GY-302과 RPI3의 핀들도 상호 연결하여 사용할 수 있다.
마지막으로 주소의 타입을 정할 수 있는 ADDR 핀에는 GND 혹은 Vcc 두 가지 값을 줄 수 있다. 이 값들도 모두 허용전압에 포함되어 있다.
## RPI3 핀맵

RPI3에는 총 40개의 핀이 있다. 그 중 1번, 17번 핀이 센서의 Vcc에서 필요로하는 3.3V 전압을 제공한다. 그리고 6번, 9번, 14번, 20번, 25번, 30번, 34번, 39번은 접지이다. SDA와 SCL 핀은 선택의 여지없이 각각 3번과 5번에 연결할 수 있다. 그리고 ADDR은 Vcc나 GND에 연결한다.


최종적으로 위의 그림에서 RPI3와 조도센서의 연결상태를 볼 수 있다. 조도센서의 핀을 기준으로 살펴보자. ADDR과 GND는 함께 묶여 RPI3의 GND와 연결된다. SDA는 RPI3의 3번, SCL은 5번에 연결되어 있다. 마지막으로 Vcc를 연결한다. 이제 모든 하드웨어 준비는 끝났다.
## 조도센서를 위한 I2C 프로토콜

I2C 인터페이스 다이어그램(출처 : 타이젠 공식 홈페이지)
### SCL & SDA
I2C 프로토콜은 SCL(Serial Clock Line)과 SDA(Serial Data Line) 핀만으로 다수의 노드를 제어할 수 있다. 다수의 노드는 위의 그림에서 볼 수 있듯, 마스터 노드와 슬레이브 노드역할로 나뉜다. 한 버스 단위에 마스터 역할의 노드는 여러 개 올 수 있다. 하지만, 이 구도에서는 RPI3만을 유일한 마스터로 사용한다. 그에 반해 슬레이브 노드는 다수 존재한다.
마스터의 SCL 선은 모든 슬레이브의 SCL과 동시에 연결된다. 마스터 노드의 주역할은 모두를 동기화시키기 위한 클락을 생성하는 데에 있다. 마스터 노드가 SCL 선으로 클락을 보내면, 모든 슬레이브가 동시에 클락신호를 받는다. 마스터의 클락신호에 맞춰서 슬레이브가 동작한다.
마스터의 SDA 선 또한 모든 슬레이브의 SDA와 동시에 연결된다. 데이터 교환은 이 SDA 선을 타고 이뤄진다. 비트 단위의 데이터가 시리얼 방식으로 넘어가고 넘어온다. 데이터를 위한 선이 하나이기 때문에 마스터와 슬레이브가 동시에 데이터를 보낼 수는 없다. 마스터가 보낼 때는 슬레이브는 받고, 슬레이브가 보낼 때는 마스터가 받는다. 이러한 방식을 반이중 통신 혹은 Half-duplex라고 일컫는다.
### 비트 단위 전달

비트 단위 전달(출처 : 위키피디아)
위의 그림은 비트 단위로 데이터가 전달되는 추이를 나타낸다. 개략적으로 아래의 루틴으로 데이터가 전달된다.
* 시작비트(S) : SCL은 High일 때, SDA를 High에서 Low로 변환
* 데이터비트 설정 : SCL이 Low일때, SDA를 설정
* 데이터비트 샘플 : SCL이 High일 때, SDA에서 값을 읽기
* 데이터비트 설정과 샘플을 반복
* 마지막 비트 : SDA는 Low로 변환
* 종료비트(P) : SCL이 High일 때, SDA를 Low에서 High로 변환
한 번에 한 비트씩 이동하니 속도가 느렸다. 최초에 I2C가 소개되었을 무렵에는 100kbit/s의 속도였다. 한 바이트가 8비트이니 초당 12,500바이트 정도의 데이터가 전달되는 것이다. 4메가짜리 MP3 파일을 전송하기 위해서는 327초가 필요하다. 하지만, 최근까지 버전업을 계속하여 이제는 초당 메가단위의 데이터를 전달한다.
### 슬레이브 주소
위의 버스에는 다수의 슬레이브 중 하나를 선택하기 위한 선이 없다. 마스터가 SDA로 전달하는 데이터를 모든 슬레이브에서 받아볼 수 있다. 그렇다면 어떻게 다수의 슬레이브 중 하나를 고를 수 있을까? 마스터는 데이터를 수취할 대상의 '주소'를 데이터와 함께 보내 한 번에 한 슬레이브와만 소통한다.
사실 모든 슬레이브는 주소를 가지고 있다. 슬레이브의 주소는 제품 출시전부터 확정된다. 소프트웨어 개발자가 프로그래밍 과정에 슬레이브의 주소를 임의로 변경할 수 없다. 왜냐하면 슬레이브의 제조업체가 I2C 관리사와 협의하여 주소를 받기 때문이다. 그리고 그 주소를 제품의 스펙에 명시해둔다. 슬레이브를 사용하기 위해서는 스펙에 적혀져 있는 주소로만 접근해야 한다.
주소는 무한정 존재하지 않는다. I2C의 주소체계는 7비트 방식과 10비트 방식이 있다. 7비트 방식을 기준으로 주소체계를 보면, 2의 7승 128개의 주소가 존재하는 것을 알 수 있다. 주소는 0번부터 127번까지이다. 그렇지만, 이 중 일부는 다른 용도로 예약되어 사용한다. 따라서 실질적으로는 총 112개의 주소만 사용할 수 있다.
### GY-302 주소타입
GY-302에는 두 가지 타입의 주소가 있다고 언급하였다. 이 타입은 GY-302의 ADDR 핀에 넣어주는 전압으로 결정된다. 만약 전압이 0V라면 조도센서는 0x23의 주소를 갖고, 5V라면 0x5C를 갖는다. 위의 그림에서 조도센서의 ADDR을 접지와 연결하였으니 조도센서 슬레이브에 접근하기 위해서는 0x23 주소를 사용해야 한다. 향후 마스터역할을 하는 RPI3에서 이 주소를 사용하여 조도센서에 접근하도록 하겠다.
## 권한 추가하기
```xml
<privileges>
<privilege>http://tizen.org/privilege/peripheralio</privilege>
</privileges>
```
주변기기를 제어하기 위해서는 특별한 권한이 필요하다. 타이젠 스튜디오에서 tizen-manifest.xml에 'peripheralio' 권한을 추가하도록 한다.
## 헤더 추가하기
```c
#include <peripheral_io.h>
```
위의 헤더파일을 소스에 포함하여 I2C 관련 함수를 사용하고자 한다.
## 핸들 관리하기
```c
int peripheral_i2c_open(int bus, int address, peripheral_i2c_h *i2c)
```
위의 함수로 I2C 버스를 사용하기 위한 핸들을 생성한다.
첫번째 인자인 bus는 버스 번호를 의미한다. 타이젠의 레퍼런스 디바이스인 라즈베리파이에는 버스가 하나 존재한다. 따라서 버스 번호는 통상 1을 입력해준다. 하지만, 차후에 다른 레퍼런스 보드가 추가되는 경우 1 외에 다른 값도 올 수 있다.
**핀 이름**|**버스 번호**
:-----:|:-----:
I2C1_SDA, I2C1_SCL|1
두번째 인자 address는 슬레이브 노드에 접속하기 위한 주소를 의미한다. 이 주소는 플랫폼 단에서 정하는 것이 아니다. 위에서 언급한 바와 같이 제조업체에 의해 디바이스가 생산되는 시점에 이미 정해져서 나온다. 슬레이브의 스펙을 확인하도록 한다.
세번째 인자로 핸들을 받게 된다. 이 핸들을 매개로 하여 I2C 버스를 제어하게 된다.
```c
int peripheral_i2c_close(peripheral_i2c_h i2c)
```
I2C 핸들의 사용기한이 끝나면 위의 함수로 정리해주어야 한다. 인자로 핸들만 넘겨주도록 한다.
```c
peripheral_i2c_h i2c_h;
int ret;
// ADDRESS는 슬레이브 장치의 스펙에 명시된 주소를 적어준다.
ret = peripheral_i2c_open(1, ADDRESS, &i2c_h);
if (ret != PERIPHERAL_ERROR_NONE) {
// 에러처리
return;
}
// 필요루틴
// 핸들 정리하기
peripheral_i2c_close(i2c_h);
```
위의 코드에서 핸들을 생성하고 정리하는 일련의 과정을 엿볼 수 있다.
## 데이터 읽고 쓰기

마스터 노드가 슬레이브 노드로부터 데이터를 읽으려면, 위와 같은 비트의 흐름을 거쳐야 한다. 우선 마스터 노드가 시작 신호에 이어 슬레이브 주소와 읽기모드 설정값을 보낸다. 그러면 해당 주소의 슬레이브 노드는 받았다는 Ack을 보내고 연이어서 데이터의 상위바이트를 보낸다. 그러면 마스터는 상위바이트를 잘 받았다는 Ack을 보낸다. 그러면 슬레이브가 하위 바이트를 다시 보낸다. 마스터는 다시 Ack을 보내고 정지신호로 통신을 마친다.
하지만, 이런 비트의 흐름을 모두 코딩하기에는 번거로움이 있다. 따라서 타이젠 IoT에서 별도의 함수군을 준비해두었는데 그게 바로 Peripheral I/O 함수이다.
```c
int peripheral_i2c_read(peripheral_i2c_h i2c, uint8_t *data, uint32_t length)
```
위의 함수로는 I2C 슬레이브 장치로부터 데이터를 읽어온다. 첫번째 인자로 I2C 핸들이 들어가고, 두번째 인자로는 버퍼주소를, 세번째에는 버퍼 사이즈를 입력한다. 위의 함수가 리턴될 때, 데이터가 두번째 인자로 넣어준 버퍼에 세번째 인자로 넣어준 사이즈만큼 채워져서 넘어온다. 위의 함수로 간단하게 데이터를 읽을 수 있다.

위의 흐름은 마스터가 데이터를 쓸 때 사용한다. 마스터는 시작신호와 함께 슬레이브 주소와 쓰기모드 설정값을 보낸다. 슬레이브는 이를 잘 받았다는 Ack을 날린다. 마스터는 다시 명령코드를 슬레이브에 전달한다. 슬레이브는 명령코드를 받고 Ack을 보낸다. 최종적으로 마스터는 정지신호로 마무리한다.
```c
int peripheral_i2c_write(peripheral_i2c_h i2c, uint8_t *data, uint32_t length)
```
위의 함수로 손쉽게 I2C 슬레이브 장치에 데이터를 쓸 수 있다. 첫번째 인자로 I2C 핸들을 넣고, 두번째에는 버퍼 주소를, 그리고 마지막으로 버퍼의 크기를 넘겨준다. 위의 함수는 버퍼의 데이터를 슬레이브 디바이스에 그대로 옮기게 된다.
```c
peripheral_i2c_h sensor_h;
int ret = PERIPHERAL_ERROR_NONE;
int value = 0;
unsigned char buf[10] = { 0, };
// I2C 프로토콜 핸들을 생성한다.
ret = peripheral_i2c_open(i2c_bus, 0x23, &sensor_h);
if (ret != PERIPHERAL_ERROR_NONE) {
// 에러처리
return;
}
// 조도센서에서 수행할 명령을 버퍼에 쓴다.
buf[0] = 0x10;
// 조도센서에 버퍼 데이터를 기록한다.
ret = peripheral_i2c_write(resource_sensor_s.sensor_h, buf, 1);
if (ret < 0) {
// 에러처리
return;
}
// 조도센서로부터 값을 얻는다.
ret = peripheral_i2c_read(resource_sensor_s.sensor_h, buf, 2);
if (ret < 0) {
// 에러처리
return;
}
// 조도센서로부터 받은 두 바이트 버퍼를 숫자로 만들고(빅앤디언),
// 1.2로 나눠서 센싱값을 얻는다.
value = (buf[0] << 8 | buf[1]) / 1.2;
```
조도센서로부터 데이터를 읽어오기 위해서는 명령코드가 필요하다. 명령코드는 센서 스펙에 명시되어 있다. 몇가지 주요한 명령코드는 아래 표와 같다.
**지시**|**명령코드**|**상세설명**
:-----:|:-----:|:-----:
Continuously H-Resolution Mode|0001_0000|1룩스 단위로 측정한다. 통상 120ms의 측정시간이 소요된다.
Continuously H-Resolution Mode2|0001_0001|0.5룩스 단위로 측정한다. 통상 120ms의 측정시간이 소요된다.
Continuously L-Resolution Mode|0001_0011|4룩스 단위로 측정한다. 통상 16ms의 측정시간이 소요된다.
One Time H-Resolution Mode|0010_0000|단 한차례 1룩스 단위로 측정한다. 통상 120ms의 측정시간이 소요된다.
One Time H-Resolution Mode2|0010_0001|단 한차례 0.5룩스 단위로 측정한다. 통상 120ms의 측정시간이 소요된다.
One Time L-Resolution Mode|0010_0011|단 한차례 4룩스 단위로 측정한다. 통상 16ms의 측정시간이 소요된다.
위의 표에서 단 한차례 시행하는 명령은 매번 값을 읽을 때마다 다시 설정해줘야 한다. 만약 지속적으로 값을 얻어오고자 할때는 Continuously 모드의 명령어를 사용하자. 여기 코드에서는 Continuously H-Resolution Mode(0x10)를 사용하였다.
센서로부터 추출한 값과 실제 럭스간에서는 차이가 있다. 따라서 둘 사이의 차이를 바로 잡아주어야 한다. 이 간극은 센서를 만든 제조업체에서 제공하는 스펙문서를 보고 확인할 수 있다. 여기서 그 차이는 센싱값을 1.2로 나눠주는 것으로 해소할 수 있다. 예를 들어 센서로부터 읽은 값이 12000이면 1.2로 나눈 10000이 최종 센싱값이 된다.
## RCC에 준비된 함수 이용하기
RCC 저장소에는 조도센서 GY-302을 위한 함수가 포함되어 있다.
int resource_read_illuminance_sensor(int i2c_bus, uint32_t *out_value)
위의 함수를 사용하면, 조도센서를 위한 핸들, 슬레이브의 주소, 읽기를 위한 명령코드, 센싱값의 보정 등에 대해 신경쓰지 않아도 된다. 그저 첫번째 인자로 I2C 버스를 입력해주면, 두번째 인자로 센싱값을 넘겨받는다. RPI3의 경우, 위의 버스값은 1로 고정되어 있기 때문에 신경쓸 것도 없다.
```c
uint32_t value = 0;
if (resource_read_illuminance_sensor(1, &value) < 0) {
// 에러처리
}
I("Illuminance : %d", value);
```
위의 코드만으로 조도센서로부터 센싱값을 추출할 수 있다. 이와 같은 함수는 GY-302 센서모델에 맞춰서만 사용할 수 있다. 이미 개발된 함수가 없다면 Peripheral I/O를 사용하는 수밖에 없다.
# Appendix : 서보모터
## 서보모터 준비하기

서보모터 HS-53
일반적으로 모터라고 하면, 원형으로 도는 모터를 떠올린다. 하지만, 서보모터라 하면 일반 모터와 강조점이 조금 다르다. 서보모터는 사용자의 요구에 따라 방향, 속도, 토크 등을 '정확하게' 제어할 수 있어야 한다. 서보모터에 제어신호를 보내면, 신호에 따라 꼭 목표치 만큼만 움직인다. 따라서 사용자의 적극적인 제어에 의해 움직임을 관리할 수 있다.
이번 장에서 사용하고자 하는 서보모터는 HS-53이다. 이 모터는 성인남성 검지손가락 한 마디 정도의 크기를 가지고 있다. 아마존에서 만원이 좀 넘는 금액으로 구입할 수 있다.
**항목**|**내용**
:-----:|:-----:
모델명|HS-53 Super-Econo Feather Servo
제조사|Hitec
동작전압폭|4.8V ~ 6.0V
속도|4.8V : 0.16초/60° 6.0V : 0.13초/60°
토크|4.8V : 1.22kg-cm 6.0V : 1.51kg-cm
위의 표에서 HS-53의 스펙을 확인할 수 있다. 서보모터에 공급하는 동작전압에 따라 모터나 내는 속도와 토크가 달라진다. 서보모터에 4.8V의 동작전원을 공급하면, 60° 움직이는데 0.16초가 소요된다. 이 때의 토크는 1.22kg-cm이다.
## PWM을 알아보기
PWM(Pulse Width Modulation)은 극히 짧은 시간만 흐르는 전류의 '시간폭을 변조'하여 소통하는 프로토콜이다. 이 프로토콜의 목적은 분명하다. 디지털 펄스 메시지로 전구나 모터 등의 전기기기를 섬세하게 조종하고자 한다.

작동 주기당 평균 전압(출처 : 타이젠 공식 홈페이지)
위의 그래프를 살펴보며, PWM의 동작원리를 간단하게 살펴보자. 가로축은 시간의 추이를 의미하고 세로축은 전압의 세기를 의미한다. 그래프 내내 전압의 값은 디지털 비트 신호처럼 Low 아니면 High 두가지로 고정되어 있다. 이는 스위치를 열거나 닫는 두가지 상황을 의미한다. 그렇다. 이 그래프는 목표에 맞게 스위치를 열고 닫는 모습을 보여준다.
그래프의 첫번째 주기에 서보모터 스위치에 공급하는 전압이 0V이다. 이 주기 동안 스위치는 내내 열려있다. 두번째 주기에서는 주기의 1/4 시간 만큼 스위치에 전압을 주어 전류가 흐르게 한다. 이 때에만 스위치가 닫히면서 모터에 힘이 전달되고 나머지 시간에는 힘이 가지 않는다. 나머지 시간 동안 모터는 관성의 힘으로 돌아간다. 세번째 주기에서는 처음 절반 동안 모터에 힘이 전달되고, 나머지는 관성으로 움직인다. 그리고 나머지 두 주기에는 모터에 전원이 공급되는 시간이 점차 늘어난다.
이런 방식으로 모터에 전원을 공급하는 스위치를 제어하여 모터 전체의 동작을 제어한다. 스위치를 제어하기위해 하드웨어-소프트웨어 단에서 모두 준비를 잘해두었다. 스위치를 제어하는 방법은 차차 다루도록 한다.
## PWM 지원현황
**프로토콜**|**ARTIK 530**|**Raspberry Pi 3**
:-----:|:-----:|:-----:
GPIO|지원|지원
PWM|지원|미지원
SPI|지원|지원
I2C|지원|지원
UART|지원|지원
타이젠 플랫폼에서 지원하는 프로토콜은 현재 기준으로 GPIO, PWM, SPI, I2C, UART 5개이다. 다만 위의 표에서 확인할 수 있다시피, 라즈베리파이에서는 안타깝게도 우리가 사용할 수 있는 PWM 핀이 없다. 따라서 아래 PWM 핀을 사용할 수 있는 SDTA7D 보드에서 진행하도록 한다.
보드과 서보모터는 세 가닥의 선으로 연결한다. 모터의 빨간선은 5V 핀에 연결하고, 검은선은 GND 핀에 연결한다. 그리고 모터의 노란선은 PWM0 핀과 연결한다. 세 가닥의 선을 연결하는 것으로 하드웨어 준비는 끝났다.
## 권한 추가하기
이제부터 타이젠 스튜디오에서 소프트웨어를 준비하도록 한다.
```xml
<privileges>
<privilege>http://tizen.org/privilege/peripheralio</privilege>
</privileges>
```
PWM 프로토콜을 사용하기 위해서는 적합한 권한이 필요하다. 위의 권한을 tizen-manifest.xml에 추가한다.
## 헤더 추가하기
```c
#include <peripheral_io.h>
```
PWM 프로토콜용 함수를 사용하고자 하는 소스에서 위의 헤더를 추가해준다.
## 핸들 관리하기
```c
int peripheral_pwm_open(int chip, int pin, peripheral_pwm_h *pwm);
```
PWM 핀을 사용하려면, 위의 함수로 핸들을 생성해야 한다.
**핀 이름**|**칩**|**채널**
:-----:|:-----:|:-----:
PWM0|0|0
PWM0 핀을 규정하기 위해서는 두 개의 부가 정보가 필요하다. 두 개의 부가 정보는 위의 표에 언급한대로 칩과 채널이다. PWM0 핀의 경우, 칩 번호가 0이고 채널 번호가 0이다. 칩 번호는 RPI3에 PWM 용으로 내장된 칩이 하나이기에 0인 것이다. 한 모듈에 칩이 여러 개가 있다면, 칩 번호도 달라질 수 있다. 채널은 동일 칩을 공유하는 핀마다 하나씩 부여된다. RPI3에는 두 개의 PWM 핀이 있기 때문에 독립적인 채널도 두 개가 존재한다고 보면 된다. 여기서 사용할 채널의 번호는 0번이다.
```c
int peripheral_pwm_close(peripheral_pwm_h pwm);
```
위에서 만든 핸들이 더 이상 필요가 없어지면, 반드시 정리해주어야 한다. peripheral_pwm_close() 함수는 인자로 정리할 핸들만을 받고 있다.
## 서보모터 제어를 위한 설정
```c
int peripheral_pwm_set_period(peripheral_pwm_h pwm, uint32_t period_ns);
```
위의 함수로 나노 단위의 피리어드 즉, 기간을 설정한다. 피리어드는 '작동 주기당 평균 전압' 그림에서 언급된 주기를 의미한다. 한 주기를 기준으로 전원공급비율을 결정한다. 주기는 기기와 상황마다 다르게 설정할 수 있다. 여기서는 일반적인 RC용 서보모터에서 사용하는 주기인 20ms를 사용하기로 한다.
```c
int ret;
Uint32_t period = 20000000;
// 한 주기를 20ms로 설정한다.
ret = peripheral_pwm_set_period(pwm_h, period);
if (ret != PERIPHERAL_ERROR_NONE) {
// 에러처리
}
```
위의 코드에서 20ms로 주기를 설정하는 것을 확인할 수 있다.
```c
int peripheral_pwm_set_duty_cycle(peripheral_pwm_h pwm, uint32_t duty_cycle_ns);
```
위의 함수로는 나노 단위의 작동주기 시간을 설정한다. 이 값은 기본 단위인 주기보다 적은 값이 들어가야 한다. 이 값을 면밀히 조정하여 원하는 목표치를 수행하도록 한다.
```c
int ret;
uint32_t duty_cycle = 2000000;
// 작동주기 시간을 2ms로 설정한다.
ret = peripheral_pwm_set_duty_cycle(pwm_h, duty_cycle);
if (ret != PERIPHERAL_ERROR_NONE) {
// 에러처리
}
```
위의 코드에서는 작동주기 시간을 2ms로 설정하였다.
서보모터의 모델마다 작동주기 시간의 범위가 다르다. 이 서보모터는 스펙문서상 0.553 ms ~ 2.227 ms의 범위를 가진다. 하지만 실험을 해보면 스펙과 실제 모터와의 차이가 있을 수 있다. 따라서 스펙보다 범위를 좁혀서 사용하는 것이 안전하다.
```c
int peripheral_pwm_set_polarity(peripheral_pwm_h pwm, peripheral_pwm_polarity_e polarity);
```
서보모터에 따라서 스위치의 동작방식이 다르다. 스위치에 전원을 공급하면, 회로가 닫히는 경우가 있고 반대로 열리는 경우도 있다. 따라서 위의 함수로 작동주기의 극성을 설정해야 한다. 두번째 인자로 들어가는 극성의 enum 값에는 아래 두가지 값이 있다.
* PERIPHERAL_PWM_POLARITY_ACTIVE_HIGH
* PERIPHERAL_PWM_POLARITY_ACTIVE_LOW
```c
int ret;
// 극성을 설정한다.
ret = peripheral_pwm_set_polarity(pwm_h, PERIPHERAL_PWM_POLARITY_ACTIVE_HIGH);
if (ret != PERIPHERAL_ERROR_NONE) {
// 에러처리
}
```
위의 코드에서는 전원이 공급하면 스위치가 닫히는 극성을 설정한다.
```c
int peripheral_pwm_set_enabled(peripheral_pwm_h pwm, bool enabled);
```
위의 함수로 비로소 설정값들을 활성화할 수 있다. 두번째 인자는 불리언 값으로 true 아니면 false 값을 넣어주면 된다.
```c
int ret;
bool enable = true;
// PWM 설정을 활성화한다.
ret = peripheral_pwm_set_enabled(pwm_h, enable);
if (ret != PERIPHERAL_ERROR_NONE) {
// 에러처리
}
```
위의 코드로 PWM 설정을 활성화한다. 여기서 언급한 함수들 만으로 서보모터를 완벽하게 제어할 수 있다.
## 서보모터 제어하기
```c
#include <peripheral_io.h>
#include "log.h"
```
위의 헤더파일은 서보모터 제어코드에서 필요로 한다. 아래 코드를 불러주기에 앞서 헤더를 소스에 포함하자.
```c
// 서보모터에서 사용하는 채널, 여기서는 0번 채널을 사용
#define SERVO_MOTOR_CHANNER (0)
// 하나의 서보모터를 연결하여 사용하기 위한 핸들
static peripheral_pwm_h g_pwm_h;
// 서보모터 자원을 해제할 때 사용하는 함수
void resource_close_servo_motor(void)
{
// 서보모터 자원을 할당하여 사용하는 경우 자원을 해제하기
if (g_pwm_h) {
peripheral_pwm_close(g_pwm_h);
g_pwm_h = NULL;
}
}
// 서보모터와 관련된 자원을 할당한 후 값을 설정하는 함수
int resource_set_servo_motor_value(double duty_cycle_ms)
{
int ret = 0;
// 서보모터와 관련된 자원이 아직 할당되지 않은 경우 서보모터 자원을 할당하기
if (!g_pwm_h) {
ret = peripheral_pwm_open(0, SERVO_MOTOR_CHANNER, &g_pwm_h);
if (ret != PERIPHERAL_ERROR_NONE) {
_E("failed to open servo motor with ch : %s", get_error_message(ret));
return -1;
}
}
// 서보모터의 피리어드를 설정하기
ret = peripheral_pwm_set_period(g_pwm_h, 20 * 1000 * 1000);
if (ret != PERIPHERAL_ERROR_NONE) {
_E("failed to set period : %s", get_error_message(ret));
return -1;
}
// 위에서 설정한 피리어드에서 얼마만큼 전압을 공급할지를 설정하기
ret = peripheral_pwm_set_duty_cycle(g_pwm_h, duty_cycle_ms * 1000 * 1000);
if (ret != PERIPHERAL_ERROR_NONE) {
_E("failed to set duty cycle : %s", get_error_message(ret));
return -1;
}
// 설정값으로 서보모터를 활성화하기
ret = peripheral_pwm_set_enabled(g_pwm_h, true);
if (ret != PERIPHERAL_ERROR_NONE) {
_E("failed to enable : %s", get_error_message(ret));
return -1;
}
return 0;
}
```
위의 코드에서 두가지 함수를 볼 수 있다.
먼저 나오는 함수는 서보모터를 사용하기 위해 할당받은 자원을 모두 해제하는 역할을 담당한다. 프로세스가 할당하여 사용한 자원을 제대로 해제해주지 않으면, 다른 프로세스도 해당 자원을 사용할 수 없게 된다. 따라서 더 이상 서보모터를 사용하지 않는 시점에 반드시 이 함수를 사용하여 자원을 해제하자.
두번째 함수는 서보모터에 적당한 값을 설정하여 실제로 동작하게 하는 함수이다. 여기서 period는 20ms로 확정하여 설정하였고 duty도 ms를 기준으로 설정할 수 있도록 준비해두었다. 만약 더 정교하게 모터를 제어하려면 ms 대신 um를 사용해도 무방하다.
### QUIZ : 다음에 들어갈 값은 어느 정도여야 하는가?
```c
ret = resource_set_servo_motor_value(/* duty_cycle */);
```
Notice
Are you sure to delete this post?
[KOR] Illuminance Sensor (Appendix : ServoMotor)
1
4
|
Last modified on August 20, 2019
Craft info. | |
Maker |
![]() |
Status | In Progress |
Period | 2018-08-20 ~ 2018-08-24 |
About This Craft | |
Seoul IoT Center Lecture.nKeywords are 'Illuminance', 'Servo motor', 'SmartThings' and 'Tizen IoT' | |
Making Note