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