I2C no MSP430
Como o ATtiny25, o MSP430G2231 dispõe de uma interface que suporta comunicação serial nos padrões I2C e SPI (mas não suporta comunicação serial assíncrona). Até o nome é igual: USI. Os recursos, entretanto, são um pouco diferentes.
Novamente o coração é um shift register e temos um contador. No MSP430 os dois são acionados pelo mesmo clock. Como é costume no MSP430, existem várias opções para o clock: ACLK, SMCLK, externo, comparação do timer ou por software. O clock escolhido pode ser dividido por uma potência de 2 entre 1 e 128. A USI do MSP430 é capaz de gerar interrupção quando o contador chega a zero (fim de recepção ou transmissão) e na detecção de Start.
A geração de Start e Stop requerem algumas "sambadinhas" nos registradores de controle, mas o manual explica direitinho (com direito a exemplo em assembler).
Montagem do Hardware
Esta parte parecia muito simples, dadas as minhas experiências anteriores com o display e com I2C na Launchpad (aqui e aqui). Tão simples que resolvi aproveitar e colocar o display para operar a 3V. Só que a montagem óbvia não funcionou.
Após alguns dias experimentando no lado do software, finalmente liguei um osciloscópio e percebi que os sinais SDA e SCL não estavam bons. Usando apenas os pull-ups internos do MSP430 os tempos de subida eram muito longos. Com resistores externos de 1K a súbida ficou boa, mas o display não conseguia puxar o sinal para terra, tornando errática a recepção do ACK. No final usei resistor de 5.6K (era o tinha ao alcance da mão) no SDA; o ACK ainda ficou um pouco acima do terra mas dentro dos limites.
O circuito final ficou assim:
Operação do Display a 3V
Esta parte é um pouco curiosa, o que só ficou claro para mim por não ter funcionado de primeira. O display é capaz de se comunicar e processar comandos apenas com a alimentação de 3V (sem os capacitores e com o pino Vout desconectado).
O display precisa de 5V para conseguir acionar o LCD. É aí que entram os dois capacitores externos e mais o booster interno. Este booster é acionado por programação. Portanto, alguns parâmetros de iniciação são diferentes para operação a 3 e 5V. O booster não deve ser ligado quando a alimentação é 5V. Em outras palavras, usar a programação de 3V com o display alimentado com 5V irá danificá-lo.
Software
Resolvidos os problemas acima, o software fica simples. Como resultado das várias experiências com o software eu acabei fazendo uma rotina mais simples para geração do Start, mas o resto é semelhante aos meus programas anteriores de I2C e de uso do display.
#include <msp430.h> typedef unsigned char byte; typedef unsigned int word; // definições dos pinos de E/S no port 1 #define I2C_SDA 0x80 #define I2C_SCL 0x40 #define LED 0x01 // Constantes para comando do display #define ADDR_DISP 0x7C #define SEL_REG 0 #define SEL_DADO 0x40 // Valor para gerar int a cada 500uS c/ clock de 1MHz #define TEMPO_500US 499 // (1MHz*500uS)-1 static volatile unsigned int contDelay; static void DisplayInit (void); static void DisplayWriteDec (byte l, byte c, byte num); static void DisplayWrite (byte l, byte c, char *msg); static void DisplayOut (byte c, byte d); static byte I2C_TxByte (byte b); static void I2C_TxStop (); static void I2C_TxStart (); static void Delay (unsigned int ms); int main( void ) { byte seg, minuto, hora; byte ack; // Desliga Watchdog WDTCTL = WDTPW + WDTSSEL + WDTHOLD; // Programa entradas e saídas P1SEL = 0; P1DIR = 0xFF; // Todo mundo é saída P1OUT = I2C_SCL | I2C_SDA; P1REN = I2C_SCL | I2C_SDA; // Ativa resistores de pull up P2SEL = 0; // Todos os pinos como I/O P2DIR = 0xFF; // Todos os pinos como saída P2OUT = 0; // Todos as saídas em zero // Alimentação já deve estar estável, vamos ligar o DCO BCSCTL1 = CALBC1_1MHZ; DCOCTL = CALDCO_1MHZ; // Programa USI // Clock = SMCLK/8 = 1/8 MHZ = 125 KHz, ativo low USICKCTL = USIDIV1 | USIDIV0 | USISSEL1 | USICKPL; // Habilitar SCL e SDA, MSB first, Master USICTL0 = USIPE7 | USIPE6 | USIMST | USISWRST; // Modo I2C USICTL1 = USII2C; // 8 bit shift register USICNT = 0; // Libera USI para operação USICTL0 &= ~USISWRST; // Programar a interrupção de tempo real TACCR0 = TEMPO_500US; TACTL = TASSEL_2 + MC_1 + TAIE; // SMCLK, up mode, interrupt _BIS_SR (GIE); // Testa presença do display I2C_TxStart (); ack = I2C_TxByte (ADDR_DISP); I2C_TxStop (); if (ack) P1OUT |= LED; // Configura o display DisplayInit(); // Mostra mensagens DisplayWrite (0, 0, "DQSoft"); DisplayWrite (1, 0, "00:00:00"); seg = minuto = hora = 0; for (;;) { // Aguarda 1 segundo Delay (1000); // Avanca o relogio if (++seg == 60) { seg = 0; if (++minuto == 60) { minuto = 0; if (++hora == 100) hora = 0; } } // Mostra o relogio atualizado DisplayWriteDec (1, 0, hora); DisplayWriteDec (1, 3, minuto); DisplayWriteDec (1, 6, seg); } } // Faz a iniciação do display static void DisplayInit () { Delay(100); DisplayOut (SEL_REG, 0x38); DisplayOut (SEL_REG, 0x39); DisplayOut (SEL_REG, 0x14); // Valores abaixo para alimentação de 3V DisplayOut (SEL_REG, 0x74); DisplayOut (SEL_REG, 0x54); DisplayOut (SEL_REG, 0x6F); DisplayOut (SEL_REG, 0x0C); DisplayOut (SEL_REG, 0x01); } // Mostra número decimal de 00 a 99 static void DisplayWriteDec (byte l, byte c, byte num) { char aux[3]; aux[0] = 0x30 + num / 10; aux[1] = 0x30 + num % 10; aux[2] = 0; DisplayWrite (l, c, aux); } // Posiciona o cursor e mostra mensagem static void DisplayWrite (byte l, byte c, char *msg) { byte addr = c; if (l == 1) addr += 0x40; DisplayOut (SEL_REG, 0x80 + addr); while (*msg) { DisplayOut (SEL_DADO, *msg++); } } // Envia um byte de controle e um byte de dado static void DisplayOut (byte c, byte d) { I2C_TxStart (); I2C_TxByte (ADDR_DISP); I2C_TxByte (c); I2C_TxByte (d); I2C_TxStop (); Delay (30); } // Transmite um byte pelo I2C static byte I2C_TxByte (byte b) { USISRL = b; USICTL0 |= USIOE; USICNT = 8; while ((USICTL1 & USIIFG) == 0) ; USICTL0 &= ~USIOE; USICNT = 1; while ((USICTL1 & USIIFG) == 0) ; return (USISRL & 1) == 0; } // Transmite Start static void I2C_TxStart () { USISRL = 0; USICTL0 |= USIGE | USIOE; USICTL0 &= ~USIGE; } // Transmite Stop static void I2C_TxStop () { USICTL0 |= USIOE; USISRL = 0; USICNT = 1; while ((USICTL1 & USIIFG) == 0) ; USISRL = 0xFF; USICTL0 |= USIGE; USICTL0 &= ~(USIGE | USIOE); } // Tratamento da interrupção do Timer A // Ocorre a cada 500 uS #pragma vector=TIMERA1_VECTOR __interrupt void Timer_A_TO(void) { static byte cont = 0; switch (TAIV) { case 10: // overflow cont++; //if (cont == 0) // P1OUT ^= LED; if (contDelay) contDelay--; break; case 2: // CCR1, não utilizado break; } } // Aguarda ms milisegundos // 1 <= ms <= 32000 static void Delay (unsigned int ms) { _BIC_SR (GIE); contDelay = ms*2; _BIS_SR (GIE); while (contDelay != 0) ; }
Nenhum comentário:
Postar um comentário