![]() |
Programação do LPC2378 usando o arm-elf-gcc Parte 8: Interface I2C: termômetro TMP100 e smartcard |
![]() |
Anterior | Indice | Próximo |
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 ispTestar 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 tserO 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.
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. |
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:
Leitura I2C.
A leitura é feita na seguinte seqüência:
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 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
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:
| ||||||||||||||||||||||||||||||||||
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.
Anterior | Indice | Próximo |