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