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.
  1. #include <msp430.h>  
  2.   
  3. typedef unsigned char  byte;  
  4. typedef unsigned int   word;  
  5.   
  6. // definições dos pinos de E/S no port 1  
  7. #define  I2C_SDA    0x80  
  8. #define  I2C_SCL    0x40  
  9. #define  LED        0x01  
  10.   
  11. // Constantes para comando do display  
  12. #define ADDR_DISP   0x7C  
  13. #define SEL_REG     0    
  14. #define SEL_DADO    0x40  
  15.   
  16. // Valor para gerar int a cada 500uS c/ clock de 1MHz  
  17. #define TEMPO_500US  499    // (1MHz*500uS)-1  
  18.   
  19. static volatile unsigned int contDelay;  
  20.   
  21. static void    DisplayInit     (void);  
  22. static void    DisplayWriteDec (byte l, byte c, byte num);  
  23. static void    DisplayWrite    (byte l, byte c, char *msg);  
  24. static void    DisplayOut      (byte c, byte d);  
  25.   
  26. static byte I2C_TxByte  (byte b);  
  27. static void I2C_TxStop  ();  
  28. static void I2C_TxStart ();  
  29.   
  30. static void Delay (unsigned int ms);  
  31.   
  32.   
  33. int main( void )  
  34. {  
  35.     byte seg, minuto, hora;    
  36.     byte ack;  
  37.       
  38.     // Desliga Watchdog  
  39.     WDTCTL = WDTPW + WDTSSEL + WDTHOLD;  
  40.   
  41.     // Programa entradas e saídas  
  42.     P1SEL = 0;  
  43.     P1DIR = 0xFF;               // Todo mundo é saída  
  44.     P1OUT = I2C_SCL | I2C_SDA;  
  45.     P1REN = I2C_SCL | I2C_SDA;  // Ativa resistores de pull up  
  46.   
  47.     P2SEL = 0;                  // Todos os pinos como I/O  
  48.     P2DIR = 0xFF;               // Todos os pinos como saída  
  49.     P2OUT = 0;                  // Todos as saídas em zero  
  50.       
  51.     // Alimentação já deve estar estável, vamos ligar o DCO  
  52.     BCSCTL1 = CALBC1_1MHZ;  
  53.     DCOCTL  = CALDCO_1MHZ;  
  54.       
  55.     // Programa USI  
  56.     // Clock = SMCLK/8 = 1/8 MHZ = 125 KHz, ativo low  
  57.     USICKCTL = USIDIV1 | USIDIV0 | USISSEL1 | USICKPL;  
  58.     // Habilitar SCL e SDA, MSB first, Master  
  59.     USICTL0 = USIPE7 | USIPE6 | USIMST | USISWRST;  
  60.     // Modo I2C  
  61.     USICTL1 = USII2C;  
  62.     // 8 bit shift register  
  63.     USICNT = 0;  
  64.     // Libera USI para operação  
  65.     USICTL0  &= ~USISWRST;  
  66.       
  67.     // Programar a interrupção de tempo real  
  68.     TACCR0 = TEMPO_500US;  
  69.     TACTL = TASSEL_2 + MC_1 + TAIE; // SMCLK, up mode, interrupt  
  70.       
  71.     _BIS_SR (GIE);  
  72.       
  73.     // Testa presença do display  
  74.     I2C_TxStart ();  
  75.     ack = I2C_TxByte (ADDR_DISP);  
  76.     I2C_TxStop ();  
  77.     if (ack)  
  78.         P1OUT |= LED;  
  79.       
  80.     // Configura o display    
  81.     DisplayInit();    
  82.   
  83.     // Mostra mensagens    
  84.     DisplayWrite (0, 0, "DQSoft");     
  85.     DisplayWrite (1, 0, "00:00:00");     
  86.     seg = minuto = hora = 0;  
  87.       
  88.     for (;;)  
  89.     {  
  90.       // Aguarda 1 segundo  
  91.       Delay (1000);  
  92.         
  93.       // Avanca o relogio  
  94.       if (++seg == 60)  
  95.       {  
  96.           seg = 0;  
  97.           if (++minuto == 60)  
  98.           {  
  99.               minuto = 0;  
  100.               if (++hora == 100)  
  101.                   hora = 0;  
  102.           }  
  103.       }  
  104.         
  105.       // Mostra o relogio atualizado  
  106.       DisplayWriteDec (1, 0, hora);    
  107.       DisplayWriteDec (1, 3, minuto);    
  108.       DisplayWriteDec (1, 6, seg);    
  109.     }  
  110. }  
  111.   
  112. // Faz a iniciação do display    
  113. static void DisplayInit ()    
  114. {    
  115.   Delay(100);  
  116.   DisplayOut (SEL_REG, 0x38);    
  117.   DisplayOut (SEL_REG, 0x39);    
  118.   DisplayOut (SEL_REG, 0x14);  
  119.     
  120.   // Valores abaixo para alimentação de 3V  
  121.   DisplayOut (SEL_REG, 0x74);    
  122.   DisplayOut (SEL_REG, 0x54);    
  123.   DisplayOut (SEL_REG, 0x6F);    
  124.     
  125.   DisplayOut (SEL_REG, 0x0C);    
  126.   DisplayOut (SEL_REG, 0x01);  
  127. }    
  128.     
  129. // Mostra número decimal de 00 a 99    
  130. static void DisplayWriteDec (byte l, byte c, byte num)    
  131. {    
  132.   char aux[3];    
  133.       
  134.   aux[0] = 0x30 + num / 10;    
  135.   aux[1] = 0x30 + num % 10;    
  136.   aux[2] = 0;    
  137.   DisplayWrite (l, c, aux);    
  138. }    
  139.     
  140. // Posiciona o cursor e mostra mensagem    
  141. static void DisplayWrite (byte l, byte c, char *msg)    
  142. {    
  143.   byte addr = c;    
  144.   if (l == 1)    
  145.     addr += 0x40;    
  146.   DisplayOut (SEL_REG, 0x80 + addr);    
  147.   while (*msg)    
  148.   {    
  149.     DisplayOut (SEL_DADO, *msg++);    
  150.   }    
  151. }    
  152.     
  153. // Envia um byte de controle e um byte de dado    
  154. static void DisplayOut (byte c, byte d)    
  155. {  
  156.     I2C_TxStart ();  
  157.     I2C_TxByte (ADDR_DISP);  
  158.     I2C_TxByte (c);  
  159.     I2C_TxByte (d);  
  160.     I2C_TxStop ();  
  161.     Delay (30);    
  162. }    
  163.   
  164. // Transmite um byte pelo I2C  
  165. static byte I2C_TxByte (byte b)  
  166. {  
  167.     USISRL = b;  
  168.     USICTL0 |= USIOE;  
  169.     USICNT = 8;  
  170.     while ((USICTL1 & USIIFG) == 0)  
  171.         ;  
  172.     USICTL0 &= ~USIOE;  
  173.     USICNT = 1;  
  174.     while ((USICTL1 & USIIFG) == 0)  
  175.         ;  
  176.     return (USISRL & 1) == 0;  
  177. }  
  178.   
  179. // Transmite Start  
  180. static void I2C_TxStart ()  
  181. {  
  182.     USISRL = 0;  
  183.     USICTL0 |= USIGE | USIOE;  
  184.     USICTL0 &= ~USIGE;  
  185. }  
  186.   
  187. // Transmite Stop  
  188. static void I2C_TxStop ()  
  189. {  
  190.     USICTL0 |= USIOE;  
  191.     USISRL = 0;  
  192.     USICNT = 1;  
  193.     while ((USICTL1 & USIIFG) == 0)  
  194.         ;  
  195.     USISRL = 0xFF;  
  196.     USICTL0 |= USIGE;  
  197.     USICTL0 &= ~(USIGE | USIOE);  
  198. }  
  199.   
  200. // Tratamento da interrupção do Timer A  
  201. // Ocorre a cada 500 uS  
  202. #pragma vector=TIMERA1_VECTOR  
  203. __interrupt void Timer_A_TO(void)  
  204. {  
  205.     static byte cont = 0;  
  206.       
  207.     switch (TAIV)  
  208.     {  
  209.         case 10:        // overflow  
  210.             cont++;  
  211.             //if (cont == 0)  
  212.             //    P1OUT ^= LED;  
  213.             if (contDelay)  
  214.                 contDelay--;  
  215.             break;  
  216.            
  217.         case 2:         // CCR1, não utilizado  
  218.             break;  
  219.    }  
  220. }  
  221.   
  222. // Aguarda ms milisegundos  
  223. // 1 <= ms <= 32000  
  224. static void Delay (unsigned int ms)  
  225. {  
  226.     _BIC_SR (GIE);  
  227.     contDelay = ms*2;  
  228.     _BIS_SR (GIE);  
  229.     while (contDelay != 0)  
  230.         ;  
  231. }  

Nenhum comentário: