IOCTL로 I2C 슬레이브를 제어하는 디바이스 드라이버를 작성해 봅시다.
우선, IOCTL 캐릭터 디바이스 형태로 일종의 더미 디바이스를 만들어 주는 구조가 관리하기 편합니다. 이 더미 디바이스는 적절한 I/O 요청을 받아, I2C 슬레이브가 이해 가능한 형태로 번역해 줘야 합니다. 가장 먼저, 간단한 알고리즘 구현 혹은 테스트를 위해 기본적인 4가지 동작을 정의해 봅시다.
editor ioctl_motor_cmd.h # 기본 에디터로 작성할 헤더 파일 생성
먼저, 헤더의 중복 포함을 검사하기 위한 매크로를 다음과 같이 2줄 작성합니다.
#ifndef IOCTL_MOTOR_CMD_H
#define IOCTL_MOTOR_CMD_H
구문을 하나하나 우리말로 풀어 봅시다.
만약 IO 제어 모터 명령어 헤더가 미리 정의되지 않았다면,
IO 제어 명령어 헤더를 정의합니다.
그렇다면, 헤더를 두 번 포함시키려 해도 이 매크로에서 방지해 주게 됩니다.
그 말은,
과 같은 식으로 동작하게 된다는 것을 의미합니다.
이제 본격적으로 IOCTL에 대한 매크로와 자료형을 정의해 봅시다.
#include<linux/ioctl.h>
이 헤더를 포함시켜 주어 요구 사항을 충족시켜 주도록 합시다.
이제 정보를 보내기 위한 구조체를 보내 보도록 하겠습니다. Yahboom 라즈봇의 I2C 칩은 다음과 같은 명령어 양식을 받습니다.
시작 비트 | 왼쪽 바퀴 방향 | 왼쪽 바퀴 속도 | 오른쪽 바퀴 방향 | 오른쪽 바퀴 속도 |
---|---|---|---|---|
0x01 | 0 혹은 1 | 0 - 100 | 0 혹은 1 | 0 - 100 |
전진 | 후진 |
---|---|
0x01 | 0x00 |
속도는 100단계로 다소 단계 수가 적습니다. 0일 시 바퀴가 멈추고, 100일 시 전속력으로 회전합니다.
이에 맞는 IO 정보 구조체를 작성해 봅시다.
struct ioctl_info {
unsigned long size;
char buf[5];
}
IOCTL 형식의 IO 제어 구조체에서 보낼 정보에 대한 크기는 관습적으로 long입니다. 우리가 보내야 할 정보는 위에서 언급한 양식을 따르는 5 바이트 크기의 버퍼입니다.
따라서 이렇게 정의한 후 실제 사용할 때는 size는 늘 5가 될 것이고, size != 5일 시 오염된 요청으로 간주하고 해당 제어 요청을 거부하게 될 것입니다.
그리고, 앞서 언급했듯 사용자의 편의를 위해 기초 동작들을 고정 속도로 정의하기로 했습니다. 즉, 한 종류의 IOCTL 명령어만을 사용하지 않게 된 것입니다. 다양한 요청들에 대한 식별 번호를 열거형으로 쉽게 구현해 봅시다. 예약어들을 피하기 위해 3부터 열거합니다.
enum {
CMD_LEFT = 3,
CMD_RIGHT,
CMD_FORWARD,
CMD_FORWARD_SLOW,
cMD_BACKWARD,
CMD_STOP,
CMD_IO,
}
이렇게 선언하면 3, 4, 5 … 순으로 자연히 동작 별로 번호가 부여됩니다.
자, 그럼 이 IOCTL 디바이스의 호출을 검증하기 위한 매직 넘버를 지정하겠습니다. 이 매직이 일치하는지 검사하여 바른 요청인지를 알 수 있습니다. 이번에는 무난하게 문자형 ‘G’를 사용하도록 하겠습니다.
#define IOCTL_MAGIC 'G'
그럼 이제 실제 IOCTL 커맨드 양식으로, 호출을 정의하도록 하겠습니다.
정의하는 형식은,
_지정동작(매직넘버, 요청 번호, 전달할 자료형)
지정 동작에는 여러 종류가 있지만 대표적인 R/W에 대해 소개하겠습니다.
I2C 칩으로부터 동작을 읽어와야 할 경우,
_IOR(MAGIC, CMD_NUM, datatype)
으로 요청하게 됩니다.
그렇다면, 동작을 써야 하는 경우는 어떠할까요? 이 부분은 이번에 저와 함꼐 구현하게 되실 부분입니다.
_IOW(MAGIC, CMD_NUM, datatype)
앞서 말한 구조체 등을 활용하면,
#define PI_CMD_LEFT _IOW(IOCTL_MAGIC, CMD_LEFT, struct ioctl_info)
와 같이 동작을 정의할 수 있겠습니다. 나머지 동작들도 이와 같이 작성하면 어렵지 않습니다. 또한, 이번에는 R/W가 모두 필요한 경우를 가정해 봅시다, 이 때를 위한 동작은,
_IOWR(MAGIC, CMD_NUM, datatype)
과 같습니다.
이러한 동작이 필요한 동작을 가정해 봅시다.
I2C 칩은 적외선 LED에 대한 쓰기 권한과 스캔 위치의 표면 온도를 감지 가능한 열감지 센서에 대한 읽기 권한이 있다고 합시다.
호출은 적외선을 발산하는 동시에 표면 온도를 읽어 개발자에게 전달해야 합니다.
이러한 동작을 처리하기 위한 호출로는 _IOWR
은 아주 적합한 선택이 될 것입니다.
필요하다고 생각하는 동작들을 적당히 정의해 주신 후 다음 단계로 넘어가겠습니다.
이제, 앞서 말한 정보를 토대로 디바이스 드라이버의 초반부를 작성해 봅시다.
#include "ioctl_modor_cmd.h" //앞서 작성한 헤더 포함
#define MAX_SPEED 0x63 //십진수로 100
#define MID_SPEED 0x4f //십진수로 79
#define MIN_SPEED 0x00
#define I2C_BUS_AVAILABLE 1 //모터 제어 칩은 1번 버스에 연결되어 있음.
//주로 0번은 보드 내에 통합된 버스인 경우가 많으며, 확장 기기 등의 버스는 1번 이상임.
#define SLAVE_DEV_NAME "MOTORDEV" //적절하게 슬레이브에 대한 이름을 지어 주어야 함.
#define MOTOR_SLAVE_ADDR 0x16 //모터는 1번 버스, 0x16번 주소에 인식됨.
static struct i2c_adapter *motorI2CAdapter = NULL;
static i2c_client *motorI2CClient = NULL;
char LEFT[5] = { 0x01, 0x00, MID_SPEED, 0x01, MID_SPEED };
char RIGHT[5] = { 0x01, 0x01, MID_SPEED, 0x00, MID_SPEED };
/*
회전은 너무 빠른 속도로 진행될 시에 섬세한 제어가 힘이 듭니다.
아무리 간단한 예약어라고 하더라도 느리게 회전하는 편이 낫습니다.
*/
char FORWARD[5] = { 0x01, 0x01, MAX_SPEED, 0x01, MAX_SPEED };
char FORWARD_SLOW[5] = { 0x01, 0x01, MID_SPEED, 0x01, MID_SPEED };
//이 경우도 빠른 전진과 느린 전진으로 두 가지 경우를 선언하는 것이 좋아 보입니다.
char BACKWARD[5] = { 0x01, 0x00, MAX_SPEED, 0x00, MAX_SPEED };
/* 후진에 대한 정의입니다. 지나치게 속도가 빠른 감이 있으나,
이 기기에 후진을 위한 센서가 없으므로 실제로 사용될 경우는 적을 것입니다.
만약 앞선 정의에서 느린 후진 등을 정의하셨다면 그에 맞게 정의해 주세요.
예: char BACKWARD_SLOW[5] = { 0x01, 0x00, MID_SPEED, 0x00, MID_SPED };
*/
char STOP[5] = { 0x01, 0x00, 0x00, 0x00, 0x00 };
이제 하드웨어를 제어하기 위한 기본적인 초반 정의는 끝났습니다. 다음 시간에는, I2C 슬레이브 디바이스에 대해 알아보겠습니다.