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

 


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

 

 

CNK HUD에 사용한 Serial EEPROM AT24C256B(이하 24C256)과 터치패드 컨트롤러 CT1N07은 I2C 통신 기능을 가지고 있어서, MPU인 PIC18F67J10과 I2C 통신을 합니다.

24C256의 내용을 읽어보니 특정 번지에 전화번호 기록한 것 밖에 없습니다.
현재 CNK HUD 기능으로봐서도 별로 eeprom에 기록해 놓을 만한 것이 없기는 합니다.

개인적 견해로는 CNK HUD에서 이 I2C를 아주 비합리적으로 사용하고 있다고 봅니다.
18F67J10는 MSSP1과 MSSP2 두 개의 시리얼 통신 단자를 가지고 있습니다. 그 중 MSSP1은 지난 번에 다룬 EN25F16과 SPI 통신에 사용되고 있습니다. 사용하지 않고 있는 MSSP2로 24C256과 CT1N07을 제어하면, XC8 라이브러리에 있는 I2C 관련 함수를 사용하여 간단하게 프로그램할 수 있었을 것입니다. 뿐만 아니라 MSSP2와 관련된 인터럽트를 이용할 수 있어서 여러 모로 유리한 점이 많았을 것입니다. 그러나 어떤 이유에서인지 CNK HUD에서는 전혀 다른 핀을 사용하고 있습니다.

가장 치명적인 문제점은 전원 버튼을 눌렀을 때에 절전 모드를 사용하지 못한다는 점입니다. PIC18F 시리즈가 절전 모드에서 깨어나는 하나의 방법이 인터럽트를 거는 것입니다. CNK HUD의 구조상 절전모드에서 벗어나도록 인터럽트를 발생시킬 수 있는 유일한 방법이 터치패드를 터치하는 것입니다. 터치컨트롤러 CT1N07과 18F67J10이 MSSP2를 통해서 I2C 통신을 한다면, 터치패드를 터치했을 때에 인터럽트가 걸리도록 해서 절전모드에서 벗어나도록 할 수 있습니다. 그러나 CNK HUD에서는 MSSP2를 이용하지 않고 전혀 다른 포트를 이용해서 I2C 통신을 하기 때문에, 터치패드를 터치를 했을 때에 인터럽트를 발생시킬 수가 없습니다. 즉, 전원 버튼을 터치했을 때 절전모드로 들어가 전력소모를 최소로 줄이는 것은 가능한데, 절전모드에서 탈출시킬 적당한 방법이 없는 상태가 됩니다.
결론적으로 현재 상태의 CNK HUD에서는 절전모드(sleep 기능)을 사용할 수가 없습니다.
하드웨어적으로 기판의 배선을 끊어서 새로 연결하면 되기야 하겠지만 일단은 그냥 원래대로 연결된 상태에서 프로그래밍해 봅니다.

C:\Program Files\Microchip\xc8\v1.33\sources\pic18\plib\sw_i2c 폴더 안에 소프트웨어적으로 I2C 통신을 구현한 소스 파일들을 볼 수 있습니다. 기본적인 i2c 통신을 위한 start, stop, restart, getc, putc 등과 관련된 함수들을 볼 수 있습니다. 이것들을 적절히 조합하여 필요한 기능을 구현할 수도 있겠지만, I2C 통신 프로토콜을 익히기 위해서 처음부터 하나씩 만들어 보았습니다.

I2C 기본 기능부터 구현하는 함수를 만들다 보니 2편으로 나누어 글을 쓰겠습니다.
(1)편은 I2C 통신을 위한 기본 함수를 만들고, (2)편에서 실제로 24C256 및 CT1N07과 통신하는 기능을 구현해 보기로 합니다.

CNK HUD의 배선 상태를 보면 RC0를 I2C의 data(SDA)로 사용하고 있고, RC1을 I2C의 clock(SCL)로 사용하고 있습니다. 18F67J10이 늘 마스터 모드로 동작시킬 예정이므로 SCL은 항상 output으로 사용할 것이고, SDA는 입출력 방향에 따라 설정해서 쓰도록 합니다.

헤더 파일 swi2c.h를 만들고, 그 안에 다음과 같은 매크로를 만듭니다.

#define ID_AT24C256         0B10100000               // address for AT24C256B found in its datasheet
#define ID_CT1N07           0B10111010               // address for CT1N07 found in its datasheet
#define PAGE_SIZE_AT24C256  64
#define CT1N07_I2C_PIN      0
#define CT1N07_SLEEPSEL     4
#define CT1N07_CONTACT      CT1N07_SLEEPSEL
#define CT1N07_TOUCH_DATA   0x2A
#define CT1N07_SCAN_LOW     0x2B
#define CT1N07_SCAN_HIGH    0x2C

#define ACK                 0
#define NAK                 1

#define I2C_SDA             LATCbits.LATC0
#define I2C_SCL             LATCbits.LATC1
#define I2C_SDA_IN          PORTCbits.RC0
#define I2C_SDA_TRIS        TRISCbits.RC0
#define I2C_SCL_TRIS        TRISCbits.RC1
#define SCL_SET             I2C_SCL = 1
#define SCL_CLEAR           I2C_SCL = 0
#define SDA_SET             I2C_SDA = 1
#define SDA_CLEAR           I2C_SDA = 0
#define SDA_INPUT           I2C_SDA_TRIS = 1
#define SDA_OUTPUT          I2C_SDA_TRIS = 0
#define SCL_OUTPUT          I2C_SCL_TRIS = 0

I2C 통신에서는 통신 선로에 있는 각각의 slave 기기는 고유한 장치 주소(앞으로 디바이스 주소)를 가지고 있도록 해야하고, 실제 통신은 이 디바이스 주소를 전달하면서 이루어집니다. 즉 slave는 master가 보낸 패킷 중 디바이스 주소 부분을 검사해서 자신의 디바이스 주소와 일치하지 않으면 받은 내용을 무시하고, 일치하면 이 패킷의 내용에 해당하는 동작을 합니다. 만약 동작의 결과를 master에 보내야 한다면, master가 보내 주는 clock(SCL) 신호에 맞추어 자신의 디바이스 주소를 송출한 후에 결과를 리턴해야 합니다. 즉, 실제 통신을 시작하기 전에 24C256과 CT1N07의 각 장치의 디바이스 주소를 먼저 파악해야 합니다.


AT24C256과 CT1N07의 데이터시트에서 각각의 디바이스 주소를 찾아서 위의 매크로 ID_AT24C256과 ID_CT1N07로 정의해 두었습니다.
24C256의 디바이스 주소는 표준화 되어 있어서 쉽게 알아낼 수 있습니다. 24C256과 같은 serial eeprom의 디바이스 주소 앞 4비트는 0B1010으로 약속되어 있고, 나머지 주소는 칩의 A0, A1, A2 핀으로 정합니다. CNK HUD에서는 A0, A1, A2가 저항을 통해 GND와 연결되어 있기 때문에 실제로 사용하는 디바이스 주소는 0B10100000(0xA0)입니다.


반면에 CT1N07의 디바이스 주소를 알아내는 데는 좀 어려움이 있었습니다. 불행하게도 인터넷에서 구한 CT1N07의 데이터시트는 그 내용이 충실하지 못해 디바이스 주소를 명시적으로 알려주고 있지 않습니다. 더 자세한 데이터시트를 얻기 위해서 제조사를 찾다보니 CHEMTRONICS(http://www.chemtronics.co.kr)라는 우리나라 기업이었습니다. 데이터시트는 좀 부실했는데, 전화를 걸어 자료를 요청하니 엔지니어를 배치해 주겠다는 등 적극적인 지원 자세가 좋았습니다. 제조사의 홈페이지에서 검색해보니 이 칩은 이미 단종되었고, 8채널인 CT1N08C가 양산되고 있습니다. CT1N08E의 경우에는 데이터시트에 디바이스 주소가 0xB8이라고 명시되어 있는데, CT1N07의 데이터이트에는 없습니다. CT1N07의 데이터시트의 타이밍차트에 주소를 0B10111010 (0xBA)를 보내는 그림이 이써서, 혹시나 하는 마음으로 그 주소를 쓰니 제대로 작동됩니다. 그 값이 위에 매크로 ID_CT1N07로 정의한 0B10111010 (0xBA)입니다.

실제 소스에서 주로 사용하는 매크로를 설명합니다.
우선 I2C의 clock 인 SCL과 관련하여 SCL_OUTPUT, SCL_SET, SCL_CLEAR 세 개의 매크로를 사용합니다. SCL_OUTPUT은 SCL을 출력으로 설정하여 I2C의 마스터로 동작할 수 있게 합니다. SCL_SET은 클럭을 high(1)로 설정하고, SCL_CLEAR는 clock을 low(0)으로 설정합니다. 즉 SCL을 출력으로 설정하고
SCL_SET, SCL_CLEAR 하면 RC1 핀에 하나의 펄스가 발생합니다.
SDA는 I2C의 데이터 핀으로 SDA_OUTPUT, SDA_INPUT, SDA_SET, SDA_CLEAR 매크로의 기능은 SCL과 같습니다.

위의 매크로를 이용하여 I2C 기능을 초기화 하는 함수 I2C_Init()을 만들어 봅니다.

void I2C_Init(void)
{
    SDA_OUTPUT;                   // Set SDA(RC0) as output
    SCL_OUTPUT;                   // Set SCL(RC1) as output
    SDA_SET;                      // Set SDA high
    SCL_SET;                      // Set SCL high
}

RC0와 RC1을 출력으로 설정하고, SDA와 SCL을 high(1)로 합니다.

I2C 통신 중에 SCL이 low일 때에 SDA의 값이 변하는 것은 아무런 영향을 주지 않습니다.
그러나 SCL이 high일 때에 SDA의 값이 변하는 것은 특별한 의미를 갖습니다.
예를 들어 SCL이 high인 상태에서 SDA가 low로 변하면 I2C 통신을 시작한다는 신호가 됩니다. 이 때에 물론 SDA가 미리 high 상태에 있었어야 low로 변할 수 있겠지요?
반대로 SCL이 high인 상태에서 SDA가 high로 변하면 I2C 통신이 끝난다는 신호가 됩니다. 역시 SDA는 미리 low 상태에 있었어야 high로 변할 수 있을 것입니다.


다음 그림을 보면 이해하기가 쉬울 것입니다.



이 규칙에 따라 I2C 통신의 시작을 알리는 I2C_Start() 함수와 I2C_Stop() 함수를 구현해 봅니다.

void I2C_Start(void)
{
    SDA_SET;                     // Set SDA high
    Delay1TCY();                 // Delay one instruction cycle.
    SCL_SET;                     // Set SCL high
    Delay1TCY();
    SDA_CLEAR;                   // Set SDA low
    Delay1TCY();
    SCL_CLEAR;                   // Set SCL low
}

void I2C_Stop(void)
{
    SCL_CLEAR;
    Delay1TCY();
    SDA_CLEAR;
    Delay1TCY();
    SCL_SET;                     // Set SCL high
    Delay1TCY();
    SDA_SET;                     // Set SDA high
}


I2C 통신에서 마스터가 슬레이브에 한 바이트를 기록하는 함수 I2C_Write() 함수를 만들어 봅니다. 한 바이트를 기록하고 난 다음에 마스터는 슬레이브로부터 완료했다는 ACK 신호를 받아야 합니다. ACK 신호는 보통 low (0)입니다. ACK를 받지 못한 경우는 NAK (1)로 표현하기로 합니다. ACK와 NAK는 위에서 매크로로 정의했습니다.

unsigned char I2C_Write(unsigned char data) { unsigned char i; for(i = 0;i < 8;i++) { if((data & 0x80) == 0x80) SDA_SET; else SDA_CLEAR; Delay1TCY(); SCL_SET; Delay1TCY(); data <<= 1; Delay1TCY(); SCL_CLEAR; Delay1TCY(); } SDA_CLEAR; // Receive ACK; SDA_SET; SDA_INPUT; SCL_SET; Delay1TCY(); data = (I2C_SDA_IN ? NAK:ACK); Delay1TCY(); SCL_CLEAR; SDA_OUTPUT; // SDA to output SDA_CLEAR; return data; }

for문에서 한 바이트를 8개의 시리얼 신호로 변환하여 송신합니다.
한 바이트 송신 후에 SDA를 high(1)로 설정하고, 입출력 방향을 입력으로 한 후에 ACK 신호를 받아 변수 data에 담았다가 리턴합니다. 즉, ACK 신호를 받은 정상적인 경우에는 ACK(0)를, ACK 신호를 받지 못한 경우에는 NAK(1)을 리턴합니다.

마스터가 슬레이브로부터 한 바이트를 받는 I2C_Read() 함수를 그현해 보기로 합니다.
슬레이브로부터 한 바이트를 받은 후에도 성공적으로 받은 것을 슬레이브에 알려 주기 위해서 원칙적으로 ACK 신호를 보내 주어야 합니다. 그러나 마스터가 더 이상 수신하지 않으려는 경우에는 ACK 대신에 NAK를 보냅니다. Read 후에 ACK를 보낼지 NAK를 보낼지는 I2C_Read() 함수의 매개 변수 ack에 따라 결정합니다.

unsigned char I2C_Read(unsigned char ack)
{
    unsigned char i,temp=0;

    SDA_INPUT;   // SDA to input
    for(i = 0;i < 8;i++) {
        Delay1TCY();
        SCL_SET;
        Delay1TCY();
        if(I2C_SDA_IN) temp |= 1;
        if(i < 7) temp <<= 1;
        Delay1TCY();
        SCL_CLEAR;
        Delay1TCY();
    }
    // Send ACK or NAK
    SDA_OUTPUT;                 // SDA to output
    if(ack == ACK) SDA_CLEAR;
    else SDA_SET;
    Delay1TCY();
    SCL_SET;
    Delay1TCY();
    SCL_CLEAR;
    Delay1TCY();
    SDA_CLEAR;
    return temp;
}


다음 글에서는 위의 I2C_Start(), I2C_Stop(), I2C_Write(), I2C_Read() 함수들을 이용해서, serial EEPROM AT24C256 및 터치컨트롤러 CT1N07과 통신하는 것을 다루어 보겠습니다.



블로그 이미지

엠쿠스

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

,