Lab. Processadores: Programação do LPC2378 usando o gcc

| Laboratório de Processadores Programação do LPC2378 usando o arm-elf-gcc Parte 8: Interface I2C: termômetro TMP100 e smartcard |

### Compilação e uso do Programa

Neste exemplo vamos fazer o programa a08_tmp101 para fazer um relógio que também le a temperatura através de uma interface I2C. Para configurar e testar o programa fazemos como de costume:

Configurar para gravar na memória Flash

Colocar o kit em modo de programação

Compilar e gravar a flash com o comando:

    make isp

Testar chamando o terminal `ltser` e iniciar a execução apertando no botão de RESET.

Configurar para gravar na memória RAM

Pode-se executar o programa na RAM. Até os vertores de interrupção podem rer remapeados para funcionar a partir da RAM a partir do endereço 0x40000000. Um registrador de configuração do LPC2378 chamado MEMMAP permite remapear os vetores de interrupção para a RAM.

O programa mon23 inclui um carregador de arquivos *.hex que pode colocar o programa na RAM usando o terminal ltser. Isto é feito automaticamente usando a diretiva `make tser` deste projeto. Antes de executar `make tser` é necessário que o monitor mon23 esteja gravado na Flash e sendo executado. Então basta fazer

    make tser

O programa será compilado com a configuração para executar na RAM e carregado na RAM usando o terminal `ltser`. Para executa-lo usa-se o comando P do monitor mon23.

### Análise do programa: Configuração da interface I2C

Agora veremos como este programa foi feito, estudando os recursos do LPC2378 que foram usados. O programa foi subdividido em módulos conforme a tabela a seguir:

uart.c | Configuração do clock e da interface serial.
i2c.c | Rotinas de acesso à interface I2C para a configuração e leitura do termômetro TMP100
lcd.c | Configuração e acesso ao display LCD
main.c | Programa principal que faz um relógio com termômetro.

### Descrição Genérica da interface I2C

A interface I2C é um padrão de comunicação criado pela Philips Semiconductors (Atualmente NXP) para possibilitar que microcontroladores acessem dispositivos periféricos usando apenas 2 pinos de E/S. A velocidade de comunicação pode ser de 0 a 100kBit/s no modo padrão ou até 400kBit/s no modo rápido (Fast mode). Como o modo rápido exige certos cuidados especiais, como a terminação dos sinais e dispositivos especiais, vamos considerar apenas o modo padrão.

A interface I2C usa dois sinais com acionamento de dreno aberto: O SDA (Serial DAta) é usado para os dados seriais; e o SCL (Serial CLock) é um sinal de relógio usado para sincronizar os dados. O acionamento em dreno aberto significa que os dispositivos ligados ao barramento I2C podem aterrar as linhas de sinal SDA ou SCL, mas não podem obriga-las a ir para nível alto. O nível alto nos sinais I2C ocorre quando nenhum dispositivo ligado ao barramento está puxando para baixo. Neste caso resistores externos pull-up fazem subir o nível de tensão das linhas de sinal.

Existem dois tipos de dispositivo conectado a um barramento I2C: o mestre e os escravos. Normalmente so tem um mestre, podendo ter vários escravos. O mestre (geralmente o microcontrolador) é que toma a iniciativa nas transações, enquanto os escravos operam de modo passivo.

A sincronização dos dados SDA com o clock é feita de modo que SDA pode mudar enquanto SCL=0 e deve permanecer estável enquanto SCL=1. Esta regra tem duas exeções: o START bit e o STOP bit.

Escrita I2C.

A escrita é feita na seguinte seqüência:

1. Gera o START fazendo descer primeiro SDA e depois SCL
2. Escreve os 7 bits do endereço I2C do dispositivo.
3. Escreve o bit R/W, que para a escrita é zero.
4. Libera SDA para ler o ACK. O ACK da escrita é feito pelo periférico (escravo).
5. Escreve 8 bits de dados e recebe o ACK. Esta operação pode ser repetida para enviar vários bytes.
6. Gera o STOP, fazendo subir primeiro o SCL e depois SDA.

Leitura I2C.

A leitura é feita na seguinte seqüência:

1. Gera o START fazendo descer primeiro SDA e depois SCL
2. Escreve os 7 bits do endereço I2C do dispositivo.
3. Escreve o bit R/W, que para a escrita é um.
4. O mestre libera o SDA e le o ACK gerado pelo periférico.
5. O mestre le os 8 bits de dados e gera o sinal ACK ou NACK. Esta operação pode ser repetida para ler vários bytes na mesma transação. O mestre deve responder NACK no último byte lido e ACK nos outros bytes.
6. Gera o STOP, fazendo subir primeiro o SCL e depois SDA.

Na figura acima os dados em Verde são gerados pelo microprocessador e em Vermelho são as respostas do periférico. Observe que no último dado lido o microcontrolador responde NACK.

### O termômetro I2C tipo TMP100

O TMP100 da Texas Instruments é um sensor de temperatura com calibração e conversão para digital interna, fornecendo a temperatura em graus Celcius através de uma interface I2C.

O TMP100 tem pinos de endereço que permitem configura-lo para atender em endereços que vão de 0x90 a 0x9E. No nosso kit de Lab. Processadores o TMP100 está configurado para atender no endereço 0x96. Desejamos fazer com ele dois tipos de operação: Escrever no registrador de configuração e ler a temperatura.

Registrador de configuração:

> Os bits deste registrador permitem configurar o TMP100 para a máxima resolução: 1/16 Grau Celcius. Os bits do Registrador de configuração são os seguintes:

> 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0
> OS/ALERT | R1 | R0 | F1 | F0 | POL | TM | SD

> No caso os bits R1 e R0 definem a resolução. 11 corresponde a uma resolução de 1/16 grau Celcius. Veja a função dos outros bits no manual do TMP100. No nesso programa vamos usar o valor 0x60 neste registrador. Para programa-lo temos que fazer a seguinte sequencia de operações:
> - Gerar o START bit para iniciar a transação.
> - Escrever o endereço 0x96
> - Escrever o número de acesso ao registrador de configuração: 0x01
> - Escrever o valor do registrador de configuração: 0x60
> - Gerar o STOP bit

Leitura da Temperatura

> Antes de poder ler a temperatura é necessario escrever o valor 0 (zero) para selecionar o registrador de temperatura. A temperatura aparece em 2 bytes: o primeiro tem a parte inteira de graus Celcius. O segundo tem a parte fracionária, com os bits alinhados à esquerda. Portanto as seguintes operações são necessárias:
> - Gerar o START bit para iniciar a transação.
> - Escrever o endereço 0x96
> - Escrever o número de acesso à leitura da temperatura 0x00
> - Gerar o STOP bit
> - Gerar um START
> - Escrever o endereço 0x97 para leitura
> - Ler a parte inteira da temperatura
> - Ler a parte fracionária da temperatura (com NACK)
> - Gerar o STOP

### A interface I2C no LPC2378

Os microcontroladores da família LPC23xx possuem hardware dedicado para comunicar-se através de uma interface I2C. Podem ser configurados para atuar como mestre ou como escravo. Na leitura do TMP100 queremos que o microcontrolador seja o mestre.

O LPC2378 tem 3 interfaces I2C suportadas em hardware. No kit temos um termômetro tipo TMP100 conectado à interface número 2. Os seguintes registradores são relevantes para este programa:

I2C2SCLH 0xE0080010 | Divisor que determina o tempo alto do clock SCL
I2C2SCLL 0xE0080014 | Divisor que determina o tempo baixo do clock SCL
I2C2CONCLR 0xE0080018 | Apaga bits do registrador de controle do I2C.
I2C2CONSET 0xE0080000 | Liga bits do registrador de controle do I2C. Os bits são os seguintes:
Bit | Valor | Símbolo | Descrição
--- | --- | --- | ---
7 | 0x80 | - | Não usado
6 | 0x40 | I2EN | Habilita o funcionamento da interface I2C
5 | 0x20 | STA | Inicia uma transação gerando um START bit
4 | 0x10 | STO | Escrevendo 1 aqui gera um STOP bit
3 | 0x08 | SI | Indicador de Interrupção: Liga quando mudou o estado.
2 | 0x04 | AA | Determina que após a leitura deve gerar ACK
1 | - | Não usado
0 | - | Não usado
I2C2STAT 0xE0080004 | Os bits 7 a 3 deste registrador indicam o estado de uma transação I2C em andamento. Os bits 0, 1 e 2 são sempre 0. O softare deve consultar este registrador para verificar qual ´ a próxima coisa a fazer.
I2C2DAT 0xE0080008 | Byte de dados. Neste registrador pode-se ler um byte recebido ou escrever para transmitir um byte.

Configuração inicial

O manual da NXP recomenda configurar a interface I2C para gerar interupção sempre que ocorre uma mudança de estado. Para não complicar demais logo no início, vou primeiro mostrar como funciona o programa arm07/ que le o TMP100 sem usar interrupção.

O seguinte trecho de código inicializa a interface deixando-a prota para uso:

    /* Inicializa o canal 2 de I2C */
    void ini_i2c(void)
    {
    PINSEL0 &= 0xffafffff;
    PINSEL0 |= 0x00a00000;	/* Seleciona pinos do SDA2 e SCL2 */
    I22CONCLR = 0xff;
    I22CONSET = 0x40;	/* Habilita o I2C-2 um modo mestre */
    I22SCLH   = 100;	/* Tempo alto do SCL	*/
    I22SCLL   = 100;	/* Tempo baixo do SCL	*/
    }

Tres coisas precisam ser feitas: Selecionar os pinos dos sinais SDA2 e SCL2 no seletor de funçõs dos pinos; habilitar a interface I2C2 em modo mestre e configurar a frequência do clock de I2C. Com o valor 100 escrito em I22SCLH e I22SCLL obtém-se um clock I2C de 60kHz quando o clock principal é de 12MHz.

Escrita

A escrita inicia ligando o bit STA no I22CONSET. As operações subsequentes são feitas a medida que a leitura do I22STAT indica que as etapas ateriores foram concluídas.

    /* Escreve 2 bytes na interface i2c. Se val<0 escreve so o dado. */
    void escreve_i2c(int addr, int dado, int val)
    {
    int n;	/* Conta bytes a escrever */
    int i;	/* Conta votas esperando no loop (timeout) */
    /* Inicia a transacao: Envia o endereco 0x90 para escrita no TMP101 */
    I22CONCLR = 0xFF;	/* Limpa bits de controle */
    I22CONSET = 0x60;	/* Habilita e gera START */
    k=2; i=1000;
    do	{
    	switch(I22STAT){
    		case 0x08: /* Terminou START; envia endereco */
    			I22CONCLR=0x20;
    		case 0x20: /* NACK do endreco: reenvia */
    			I22DAT = addr; I22CONCLR=8; i=1000; break;
    		case 0x18: /* ACK do endereco: envia dado */
    			I22DAT = dado; I22CONCLR=8; i=1000; break;
    		case 0x28: /* ACK do dado */
    			if(k < 2) I22CONSET = 0x10;	/* Gera STOP */
    			else I22DAT=val;	/* Envia outro dado */
    			I22CONCLR=8;
    			i=1000; k--;
    			break;
    		}
    	} while(k && --i);
    }

Acesso ao I2C com interupção

O hardware de I2C pode gerar interupção sempre que uma etapa da transação é concluída. O programa letmp100/ implementa um relógio que também le um termômetro tipo TMP100 ligado no I2C número 2, usando interrupção.

A inicialização do I2C com interrupção pode ser feita com o seguinte trecho de código:

    /* Inicializa o canal 2 de I2C com interrupcao */
    void ini_i2c(void)
    {
    PCONP |= 0x04000080;	/* Liga energia do I2C0 e I2C2 */
    PINSEL0 &= 0xffafffff;
    PINSEL0 |= 0x00a00000;	/* Seleciona pinos do SDA2 e SCL2 */
    I22CONCLR = 0xff;
    I22CONSET = 0x40;	/* Habilita o I2C-2 um modo mestre */
    I22SCLH   = 100;	/* Tempo alto do SCL	*/
    I22SCLL   = 100;	/* Tempo baixo do SCL	*/

    /* Habilita a interrupcao do i2c2 como IRQ no VIC	*/
    desabilitaIRQ();	/* Definida no #include "vic_cpsr.h"	*/
    VICIntSelect &= ~0x40000000;	/* i2c2=bit 30 como IRQ	*/
    VICIntEnable |= 0x40000000;	/* Habilita int do i2c2 no VIC*/
    VICVectAddr30 = (int)IRQ_i2c2;	/* Vetor para atendimento do I2C2 */
    i2cn=0;
    habilitaIRQ();	/* Definida no #include "vic_cpsr.h"	*/
    I22CONSET=0x10;
    }

A operação `habilitaIRQ();` é feita usando uma macro definida no arquivo de cabeçalho `vic.cpsr.h` para gerar uma sequência de instruções em assembly para ligar o bit de habilitação da IRQ no registrador de status CPSR:

    #define habilitaIRQ() asm volatile(\
    	"mrs r3, cpsr\n" \
    	"bic r3, r3, # 0x80\n" \
    	"msr cpsr, r3\n")

A rotina de atendimento da IRQ da interface I2C é implementada como uma máquina de estados com diretivas tipo switch-case. Dependendo da operação que foi concluída a rotina decide qual é a próxima coisa a ser feita:

    /* Variaveis globais da Interrupcao i2c */
    char i2cdados[32];	/* Buffer de dados lidos ou escritos */
    int slvaddr;		/* Endereco do dispositivo i2c	*/
    int i2cn;		/* Numero de bytes a ler/escrever */

    void IRQ_i2c2(void) __attribute__ ((interrupt("IRQ")));
    /*
    A rotina de interrupcao do I2C implementa uma maquina de estados.
    Dependendo do valor do I22STAT sabe o que foi feito e qual e' a
    proxima coisa a fazer. */
    void IRQ_i2c2(void)
    {
    static int k;		/* Indice do array de dados */
    I22CONSET = 4;		/* Normalmente responde ACK */
    switch(I22STAT){
    	case 0:	case 0x20:	/* Erro */
    	case 0x30:	/* Dado foi escrito recebendo NACK */
    	case 0x48:	/* Endereco de leitura recebeu NACK */
    		I22CONSET = 0x10;	/* Gera STOP */
    		i2cn=-1;		/* i2cn=-1 indica erro */
    		break;
    	case 8:		/* Terminou de enviar o START bit */
    		I22CONCLR=0x20;
    		I22DAT = slvaddr;	/* Escreve endereco	*/
    		k=0;
    		break;
    	case 0x18:	/* Enviou endereco de escrita recebendo ACK */
    	case 0x28:	/* Escreveu dado recebendo ACK */
    		/* Escreve mais dados */
    		if(i2cn) { I22DAT = i2cdados[k++]; i2cn--; }
    		else I22CONSET=0x14;	/* Fim dos dados: gera STOP */
    		break;
    	case 0x40:	/* Enviou endereco de leitura e recebeu ACK */
    	/* Prepara para iniciar a leitura de dados */
    		i2cdados[0]=I22DAT; k=0; /* Le lixo so para iniciar */
    		if(i2cn==1) I22CONCLR=4; /* So 1 dado responde NACK */
    		break;
    	case 0x50:	/* Dado recebido enviando ACK */
    		i2cdados[k++]=I22DAT;	/* Le o proximo dado */
    		/* Se for o ultimo envia NACK */
    		if(!(--i2cn)) I22CONCLR=4;
    		break;
    	case 0x58:	/* Leu enviando NACK */
    		i2cdados[k++]=I22DAT;	/* Le o ultimo dado */
    		i2cn=0;
    		I22CONSET=0x10;	/* Gera STOP */
    		break;
    	}
    I22CONCLR = 8;	/* Limpa o indicador de interrupcao do I2C */
    VICVectAddr = 0;	/* Limpa o vetor do VIC */
    }

Para fazer uma escrita ou leitura da interface I2C agora basta colocar os dados nas variaveis globais

    char i2cdados[32];	/* Buffer de dados lidos ou escritos */
    int slvaddr;		/* Endereco do dispositivo i2c	*/
    int i2cn;		/* Numero de bytes a ler/escrever */

e iniciar a transação escrevendo 0x20 no I22CONSET. Isto faz com que um START bit seja emitido. Depois disto a rotina de interrupção faz as operações necessárias para concluir a transação. Apenas devemos cuidar para dar tempo para que isto seja feito.

### Referências

- Apostila sobre o protocolo I2C.
- tmp100.pdf Manual do TMP100 na Texas Instruments.
- 24lc02.PDF O SmartCard tem as mesmas características de interface que a memória flash i2c tipo 24LC02. Estas informações podem ser usadas para acessar o SmartCard.
- http://en.wikipedia.org/wiki/I²C Explicação sobre I2C na Wikipedia