sexta-feira, dezembro 27, 2013

Conectando uma EEProm ao MSP430 - 24C256

Diferente dos microcontroladores PIC, ATmega e ATtiny, o MSP430 não possui uma memória EEProm interna. Em algumas aplicações a memória Flash pode ser usada em seu lugar, mas em outras é preciso conectar uma EEProm externa. Neste post veremos como conectar uma EEProm 24C256 a uma Launchpad com o MSP430G2231.



As memórias EEProm são memórias não voláteis (isto é, mantém o conteúdo mesmo sem alimentação) que podem ser lidas e gravadas posição a posição (a gravação das memórias Flash requer o apagamento de páginas ou blocos com várias posições).


A 24C256 é parte de uma família de memórias EEProm com capacidade ente 2Kbits (24C02) e 512Kbits (24C512). Estas memórias estão organizadas em bytes, a 24C02 possui 256 posições, a 24C256 32K e a 24C512 64K.

A comunicação entre o microcontrolador e a memória é serial, usando o protocolo I2C. Esta comunicação utiliza dois sinais: SCL (clock) e SDA (dados) e permite a ligação de vários dispositivos slave (escravo) a um master (mestre) através de um barramento (ou "varal").

O sinal SCL é sempre gerado pelo mestre. Já o sinal SDA é bi-direcional, podendo ser controlado pelo mestre ou pelo escravo. Do ponto de vista elétrico são usadas saídas open-colector, que podem ser colocadas em zero (terra) pelos dispositivos. Resistores de pull-up são necessários para manter os sinais em um quando nenhuma saída está ativada. Normalmente o sinal SDA só muda quando o sinal SCL está em zero. Duas violações desta regra caracterizam o início (start) e o fim (stop) de uma comunicação.

As figuras abaixo mostram como são comandadas as leituras e escritas na EEProm. O slave address é o endereço do chip de memória, parte dele é fixo e parte é definida através de pinos do integrado (para permitir colocar vários chips iguais no mesmo barramento). O bit de ACK é sempre gerado pelo slave e indica que foi recebido um byte como sucesso. Na leitura os bits marcados como DATA são gerados pelo slave. Todos os demais bits (incluindo start, stop e NOACK) são gerados pelo mestre.

O protocolo I2C pode ser implementado com entradas e saídas digitais, mas é bastante trabalhoso. Felizmente o MSP430 possui um periférico chamado USI que cuida de uma boa parte disso (o start e stop requerem um pouquinho de trabalho).

Mas chega de teoria. O circuito que vamos montar é bem simples. Vamos aproveitar os resistores de pull-up internos do MSP430, portanto basta o 24C256 e alguns fios. A placa Launchpad possui um LED que pode ser conectado ao pino P1.6, que é também o SCL, é necessário abrir o jumper correspondente.


As rotinas de leitura e escrita e um pequeno programa de teste ficam assim:
#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

// Endereçamento da EEProm
#define  ADDR       0xA0

// Valor para gerar interrupções cada 250us c/ clock de 1MHz
#define TEMPO_250us 249    // (250us * 1MHz)-1

// variáveis globais
static volatile unsigned int contDelay;

// rotinas locais
static void EEProm_Write (word ender, byte dado);
static byte EEProm_Read (word ender);
static byte I2C_TxByte  (byte b);
static byte I2C_RxByte  ();
static void I2C_TxStop  ();
static void I2C_TxStart ();
static void Delay (unsigned int ms);


int main( void )
{
    word i;
    byte dado;
  
    // 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
      
    // Alimentação já deve estar estável, vamos ligar o DCO
    BCSCTL1 = CALBC1_1MHZ;
    DCOCTL  = CALDCO_1MHZ;

    // Programa USI
      // Clock = SMCLK/4 = 1/4 MHZ = 250 KHz, ativo low
    USICKCTL  = USIDIV1 | USISSEL1 | USICKPL | USISWRST;
      // Habilitar SCL e SDA, MSB first, Master
    USICTL0 = USIPE7 | USIPE6 | USIMST;
      // Modo I2C
    USICTL1 = USII2C;
      // 8 bit shift register
    USICNT = 0;
      // Libera USI para operação
    USICKCTL  &= ~USISWRST;

    // Programar a interrupção de tempo real p/ cada 250 us
    TACCR0 = TEMPO_250us;
    TACTL = TASSEL_2 + MC_1 + TAIE; // SMCLK, up mode, interrupt
    
    _BIS_SR (GIE);

    // Grava dados de teste
    for (i = 0; i < 255; i++)
    {
        EEProm_Write (i, (byte) i);
        Delay (10);
    }
    for (i = 0; i < 7; i++)
    {
        EEProm_Write (1 << (i+8), (byte) i);
        Delay (10);
    }

    // Testa continuamente a leitura
    while (1)
    {
      for (i = 0; i < 255; i++)
      {
          dado = EEProm_Read (i);
          if (dado != i)
          {
            P1OUT |= LED;
            while (1)
              ;
          }
      }
      for (i = 0; i < 7; i++)
      {
          dado = EEProm_Read (1 << (i+8));
          if (dado != i)
          {
            P1OUT |= LED;
            while (1)
              ;
          }
      }
      Delay (5);
    }
}

static void EEProm_Write (word ender, byte dado)
{
    I2C_TxStart();
    I2C_TxByte (ADDR);
    I2C_TxByte ((byte) (ender >> 8));
    I2C_TxByte ((byte) (ender & 0xFF));
    I2C_TxByte (dado);
    I2C_TxStop();
}

static byte EEProm_Read (word ender)
{
    byte dado;
 
    I2C_TxStart();
    I2C_TxByte (ADDR);
    I2C_TxByte ((byte) (ender >> 8));
    I2C_TxByte ((byte) (ender & 0xFF));
    I2C_TxStart();
    I2C_TxByte (ADDR | 1);
    dado = I2C_RxByte ();
    I2C_TxStop();
    return dado;
}

// 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;
}

// Recebe um byte pelo I2C
static byte I2C_RxByte ()
{
    byte dado;
    
    USICTL0 &= ~USIOE;
    USICNT = 8;
    while ((USICTL1 & USIIFG) == 0)
        ;
    dado = USISRL;
    
    USISRL = 0xFF;
    USICTL0 |= USIOE;
    USICNT = 1;
    while ((USICTL1 & USIIFG) == 0)
        ;
    
    return dado;
}

// Transmite Start
static void I2C_TxStart ()
{
    USICTL0 |= USIOE;
    USISRL = 0xFF;
    USICNT = 1;
    while ((USICTL1 & USIIFG) == 0)
        ;
    USISRL = 0;
    USICTL0 |= USIGE;
    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);
}


// Aguarda ms milisegundos
// 1 <= ms <= 16383
static void Delay (unsigned int ms)
{
    _BIC_SR (GIE);
    contDelay = ms << 2;
    _BIS_SR (GIE);
    while (contDelay != 0)
        ;
}

// Tratamento da interrupção do Timer A
#pragma vector=TIMERA1_VECTOR
__interrupt void Timer_A_TO(void)
{
    switch (TAIV)
    {
        case 10:        // overflow
            if (contDelay)
                contDelay--;
            break;
    }
}
O programa de teste grava valores conhecidos em algumas posições da EEProm e depois fica continuamente testando-os, em caso de erro o LED ligado ao P1.0 é aceso.

No próximo post vamos ver uma outra opção de EEProm.

Nenhum comentário: