quinta-feira, junho 18, 2015

Display I2C com o MSP430

Esticando um pouco mais este assunto,  vou mostrar agora o display alfanumérico com interface I2C ligado a uma Launchpad com o MSP430G2231.


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: