이글의 전부 또는 일부, 사진, 소스프로그램 등은 저작자의 동의 없이는 상업적인 사용을 금지합니다. 또한, 비상업적인 목적이라하더라도 출처를 밝히지 않고 게시하는 것은 금지합니다.

 


(2015.01.05.에 다음 블로그(http://blog.daum.net/microcontroller/13651143)에 적었던 글을 옮겨왔습니다.)

 

 

앞의 CNK HUD, I2C 프로그래밍 - AT24C256 Serial EEPROM(1)에서 작성한 기본적인 I2C 함수를 이용해서 실제로 AT24C256B 및 터치컨트롤러 CT1N07과 통신하는 함수를 만들어 보도록 합니다.

먼저 serial eeprom AT24C256B와 통신하는 루틴을 작성하고, 이를 응용하여 터치패드인 CT1N07과 통신하는 루틴을 만들어 보고자 합니다.


앞의 글에서 언급한 바 있습니다만, I2C 통신에서는 마스터가 통신을 시작한 후(즉, I2C_Start() 함수를 호출한 후)에 자신과 통신할 슬레이브의 디바이스의 주소(address)를 지정해 주어야 합니다. 주소는 주로 한 바이트로 전송합니다. 한바이트 중 bit7~bit1까지의 7bit는 실제 통신 대상인 슬레이브의 주소를 담고 있으며, 마지막 비트(bit0)은 마스터가 슬레이브로 부터 읽을 것(Read)인지, 슬레이브에 전달(Write)하는지를 알려 주는 용도로 사용합니다.
AT24C256B(이하 24C256)의 데이터시트에 있는 디바이스 주소 부분을 보면 다음과 같이 되어 있습니다.




Serial EEPROM의 주소는 앞의 4바이트가 0B1010으로 약속되어 있습니다.
24C256의 경우 핀 1, 2, 3이 각각 A0, A1, A2로 되어 각각 위의 A0, A1, A2에 대응됩니다. 즉, 24C256의 1, 2, 3 핀 모두를 GND에 연결하면 A0 = 0, A1 = 0, A2 = 0이 되어 이 serial EEPROM의 실제 주소는 0B1010000x이됩니다. (마지막 x는 위에서 언급한대로 R/W 신호로 사용되므로 0으로 해야 합니다.) 이 serial eeprom의 완전한 주소는 0B10100000 (0xA0)가 됩니다.

이런 원리로 생각하면 하나의 I2C 라인에 24C256은 8개까지 부착할 수 있으면 각각의 주소는 0xA0, 0xA2, 0xA4, 0xA6, 0xA8, 0xAA, 0xAC, 0xAE가 될 것입니다.
앞의 글에서 다룬대로 CNK HUD의 24C256의 경우 A0, A1, A2가 모두 저항을 거쳐 GND에 연결되어 있으므로, 24C256의 주소는 0B10100000 (0xA0)입니다. 이 값은 앞의 글에 매크로에 ID_AT24C256로정의되어 있습니다.

24C256에 한 바이트를 기록하는 함수를 구현해 보기로 합니다.
24C256 데이터시트에 있는 한 바이트 기록하는 동작에 관한 그림입니다.




24C256은 내부에 256 kilo bit 즉 32 kilo byte의 기억용량을 가지고 있습니다.
32 kilo byte를 바이트 단위로 접근하려면 내부에 15비트 길이의 주소를 가지고 있어야 합니다. 따라서 24C256 내부의 특정 번지에 접근하려면 다음과 같은 단계를 거쳐야 합니다.
1) 디바이스 주소로 24C256을 지정한다.(앞에서 살펴본 24C256의 주소 0B10100000)
2) 24C256 내부의 번지를 지정한다.(예 0x1CDB 번지)
3) 내용을 기록한다.

24C256에 한 바이트를 기록하는 함수 I2C_ByteWrite() 함수는 device ID, address, data 이렇게 세 개의 매개 변수를 받도록 만들었습니다.

void I2C_ByteWrite(unsigned char ID,int address,unsigned char data)
{
    I2C_Start();
    I2C_Write(ID & 0xFE);              // slave device address + write
    I2C_Write(address >> 8);           // address upper byte
    I2C_Write(address & 0xFF);         // address lower byte
    I2C_Write(data);                   // data
    I2C_Stop();
    __delay_ms(3);
}

I2C를 시작하고 device id를 지정합니다. 이 때 (ID & 0xFE) 함으로써 혹시 주소를 잘못 지정한 경우라도 /Write 동작을 하도록 처리하였습니다. 두 바이트로 24C256 내부의 번지를 지정한 다음에 data를 기록합니다.

CNK HUD의 경우 위와 같이 하여도 에러 없이 잘 동작하므로 생략하였습니다만 원칙적으로는 에러 처리 루틴을 두어야 합니다. 앞 글에서 만든 I2C_Write() 함수의 끝부분에서 ACK가 오는지 여부만 검사하여 ACK가 왔으면 ACK(0)을 오지 않았을 경우에는 NAK(1)을 리턴하도록 하였습니다. I2C 통신을 원칙대로 하자면, ACK가 오는지를 검사하여, 오지 않으면 일정 시간 대기하였다가 그 결과를 리턴하도록 했어야 합니다.
또한 위의 I2C_ByteWrite() 함수에서도 I2C_Write() 함수가 ACK를 가져 오지 않으면 에러로 간주하여 에러 처리를 하는 부분을 구현해야 합니다.


24C256은 바이트 단위로 읽고 쓸 수 있지만 실제 메모리는 페이지 단위로 구성되어 있습니다. 24C256의 경우 페이지 크기는 64바이트이고 총 페이지 수는 512개 입니다. 즉 내부에 64바이트 단위로 512 페이지가 있습니다. 여러 바이트를 기록해야할 경우에는 페이지 단위로 기록할 수 있습니다.

다음은 여러 바이트를 기록하는 동작에 관한 그림입니다.




바이트를 기록하는 것과 특별히 다른 점은 없습니다. 바이트를 기록하는 루틴 뒤에 계속하여 데이터를 기록하기만 하면 됩니다. 이 동작을 구현하기 위하여 다음과 같이 I2C_PageWrite() 함수를 구현하였습니다.

void I2C_PageWrite(unsigned char ID,int address,int bytes,unsigned char *pdata)
{
    int i;

    I2C_Start();
    I2C_Write(ID & 0xFE);             // slave device address + write
    I2C_Write(address >> 8);          // address upper byte
    I2C_Write(address & 0xFF);        // address lower byte
    for(i = 0;i < bytes;i++)
        I2C_Write(*(pdata + i));      // data
    I2C_Stop();
    __delay_ms(3);
}

기록할 데이터는 매개 변수인 포인터 pdata로 받고, 기록할 바이트 수는 매개 변수 bytes로 받습니다.
여러 바이트를 기록할 때에 시작하는 주소는 어디라도 상관이 없습니다. 다만 페이지의 범위를 넘어 기록하려는 경우에는 페이지가 변경되지 않고 페이지의 맨 앞 바이트에 기록이 됩니다.
즉, 0페이지는 0번지부터 63번지까지 64바이트입니다. 60번지부터 시작하여 10바이트를 기록하는 경우 60, 61, 62, 63번지 다음에 0, 1, 2, 3, 4, 5, 6번지에 기록됩니다.


한 바이트를 읽어 오는 함수 I2C_ByteRead() 함수를 만들어 봅니다.
다음은 한 바이트를 읽는 동작에 관한 그림입니다.



마스터가 한 바이트만 읽고 더 이상 읽지 않을 것이므로 NO ACK를 보내야 합니다.


unsigned char I2C_ByteRead(unsigned char ID)
{
    unsigned char data;

    I2C_Start();
    I2C_Write(ID | 0x01);            // slave device address + read
    data = I2C_Read(NAK);            // data
    I2C_Stop();
    return data;
}


마스터가 슬레이브로부터 읽어 올 것이기 때문에 디바이스 주소 맨 마지막 비트(bit0)을 1로 하기 위하여 (ID | 0x01) 연산을 하였습니다. 또한 데이터를 읽고 난 후에 NO ACK를 슬레이브로 보내야 하므로, 앞 글에서 만든 I2C_Read() 함수에 매개 변수로 NAK를 보냅니다. 이 함수는 24C256의 내부 번지를 지정하지 않았으므로 24C256 내부의 현재 번지에서 한 바이트 읽어 옵니다.


여러 바이트를 읽는 과정에 관한 그림입니다.



슬레이브에서 마스터로 바이트를 읽어 오는 것이므로, 마스터에서 ACK 신호를 보내주어야 합니다. 다만 맨 마지막 읽기 후에는 NO ACK를 보낸다는 점에 유의합니다. I2C_PageRead() 함수를 구현해 봅니다.

void I2C_PageRead(unsigned char ID,int bytes,unsigned char *pdata)
{
    int i;

    I2C_Start();
    I2C_Write(ID | 0x01);
    for(i = 0;i < bytes;i++)
        *(pdata + i) = I2C_Read((i < bytes - 1) ? ACK:NAK);
    I2C_Stop();
}

연속으로 읽어 올 때에는 기록할 때와 마찬가지로 페이지를 바꿀 수 없습니다. 페이지 범위를 넘어가게 되면 그 페이지의 맨 앞 바이트부터 읽어옵니다.


지정된 번지로부터 한 바이트를 읽어 오는 I2C_RandomByteRead() 함수입니다.

unsigned char I2C_RandomByteRead(unsigned char ID,int address)
{
    unsigned char data;

    I2C_Start();
    I2C_Write(ID & 0xFE);              // slave device address + write
    I2C_Write(address >> 8);           // address upper byte
    I2C_Write(address & 0xFF);         // address lower byte
    I2C_Start();                       // RE_START
    I2C_Write(ID | 0x01);              // slave device address + read
    data = I2C_Read(NAK);              // data
    I2C_Stop();
    return data;
}


지정된 지정된 번지로부터 여러 바이트를 읽어 오는 과정에 관한 그림입니다.




위 그림에 따라 구현한 I2C_RandomPageRead() 함수입니다.

void I2C_RandomPageRead(unsigned char ID,int address,int bytes,unsigned char *pdata)
{
    int i;

    I2C_Start();
    I2C_Write(ID & 0xFE);            // slave device address + write
    I2C_Write(address >> 8);         // address upper byte
    I2C_Write(address & 0xFF);       // address lower byte
    I2C_Start();                     // RE_START
    I2C_Write(ID | 0x01);            // slave device address + read
    for(i = 0;i < bytes;i++)
        *(pdata + i) = I2C_Read((i < bytes - 1) ? ACK:NAK);   // data
    I2C_Stop();
}

이상으로 24C256과 I2C 통신을 하기 위한 함수는 모두 구현되었습니다.



다음은 터치 컨트롤러인 CT1N07과 통신하기 위한 함수를 구현해 보기로 합니다.
CT1N07의 데이터시트에 있는 I2C 통신 Write 과정에 관한 그림입니다.



데이터시트에 디바이스 주소에 관한 정보가 없어서 고생했습니다. 데이터시트의 command 부분에서 겨우 찾은 값은 (B8h,0xB8)인데 제대로 동작하지 않았습니다. 위 그림에서 디바이스 주소가 들어갈 위치를 Control Byte로 표시하였는데, 그 값이 0B10111010 (0xBA)입니다. 이 값을 디바이스 주소로 삼아 통신해보니 잘 됩니다. 즉 CT1N07의 디바이스 주소는 0xBA이고 이 값은 앞의 글에서 매크로 ID_CT1N07로 정의해 두었습니다. 전반적으로 24C256에 한 바이트를 기록하는 과정과 비슷합니다. 다만, 24C256은 내부 번지가 두 바이트로 기록되는데 비하여 CT1N07은 한 바이트라는 점만 다릅니다. 이에 따라 두 번째 매개 변수의 데이터형을 unsigned char로 하였습니다.


void I2C_ByteWrite_CT1N07(unsigned char ID,unsigned char address,unsigned data)
{
    I2C_Start();
    I2C_Write(ID & 0xFE);          // slave device address + write
    I2C_Write(address);            // Write address
    I2C_Write(data);               // Write data
    I2C_Stop();
}

사실 데이터시트에는 Address Byte(주소)로 되어 있지만 실질적인 내용을 검색해보니까, 주소라기 보다는 명령으로 해석하는 것이 옳을 듯합니다. 이 명령에는 CT1N07의 데이터 시트 중 다음과 같은 것들을 사용할 수 있는 듯합니다.



기본적으로 CNK HUD는 하나의 키 값만 읽어서 사용하고 있습니다. 만약에 나중에 확장하여 사용할 때에 두 개 이상의 키를 동시에 누른 것을 이용하려면 CONCTCH(04h,0x04)의 D1을 1로 설정하면 됩니다.

CNK HUD 터치패드에는 4개의 버튼이 있습니다.

파워 키는 0x04, 전화기 키는 0x08, Parking 키는 0x01, 밝기 조절 키는 0x02의 값을 가집니다.
(이 값도 일관성을 가지지 않도록 배치되어 있습니다. ㅠㅠ)

즉 프로그램에서
I2C_ByteWrite_CT1N07(ID_CT1N07,CT1N07_CONTACT,2);
와 같이 호출하고 파워 키와 전화기 키를 다 누르면 0x0C(=0x04 + 0x08)의 값이 돌아옵니다.

I2C_ByteWrite_CT1N07(ID_CT1N07,CT1N07_CONTACT,0);
과 같이 호출한 뒤에는 파워 키와 전화기 키를 다 눌러도 먼저 눌린 키의 값만 돌아 옵니다.

디폴트 상태는 I2C_ByteWrite_CT1N07(ID_CT1N07,CT1N07_CONTACT,0)입니다.
(CT1N07_CONTACT는 앞 글의 매크로 정의에서 0x04의 값을 가지도록 정의되어 있습니다.)

맨 아랫 부분에 I2CDEVICEID에 Device ID : B8 fixed라 되어 있어 디바이스 주소가 0xB8인 줄 알았으나 데이터시트가 잘못 된 것이었습니다. 위에서 밝힌대로 디바이스 주소는 0xBA입니다.


다음은 CT1N07의 데이터시트에 있는 데이터를 읽어 오는 것에 관련된 그림입니다.




전체적인 흐름은 다음과 같습니다.
1) 슬레이브 디바이스 주소 (쓰기) 지정
2) 명령 지정
3) restart
4) 슬레이브 디바이스 주소 (읽기) 지정
5) data read


데이터시트가 좀 부정확 내지 불친절합니다.
위 그림에는 없지만 데이터시트의 위 그림 다음 페이지에 이런 그림이 있습니다.



CT1N07에 write 할 때는 그냥 바로 기록하면 되지만, read 할 때는 dummy read를 한 번 해야 한다는 의미입니다. 그러나 실제로 테스트한 결과 dummy read가 있으면 값이 넘어 오지 않습니다. dummy read 없이 바로 읽어와야 동작합니다. 실제로 동작하는 I2C_ByteRead_CT1N07() 함수입니다.

unsigned char I2C_ByteRead_CT1N07(unsigned char ID,unsigned char address)
{
    unsigned char data;

    I2C_Start();
    I2C_Write(ID & 0xFE);       // slave device address + write
    I2C_Write(address);         // address lower byte
    I2C_Start();                // RE_START
    I2C_Write(ID | 0x01);       // slave device address + read
    data = I2C_Read(NAK);       // data
    I2C_Stop();
    return data;
}


어느 키를 눌렀는지 읽어오려면 Touch_Data (2Ah, 0x2A)를 사용하면 됩니다.
key = I2C_ByteRead_CT1N07(ID_CT1N07,CT1N07_TOUCH_DATA);
하면 변수 key에 현재 눌린 키 값이 들어 옵니다.
CT1N07_TOUCH_DATA는 앞 글의 매크로 정의시에 0x2A의 값을 가지도록 정의되어 있습니다.

만약에 위에서 언급한 대로
I2C_ByteWrite_CT1N07(ID_CT1N07,CT1N07_CONTACT,2);
를 호출한 다음에 파워 키와 전화기 키를 모두 누르고

key = I2C_ByteRead_CT1N07(ID_CT1N07,CT1N07_TOUCH_DATA);
를 호출하면 key에 0x0C의 값이 옵니다.

이것으로 18F67J10과 24C256, CT1N07 간의 I2C 통신에 관한 설명을 모두 마칩니다.

다음 글은 18F67J10의 RS232 통신을 다루어 보도록 하겠습니다.

블로그 이미지

엠쿠스

Microprocessor(STM32, AVR)로 무엇인가를 만들어 보고자 학습 중입니다.

,