quarta-feira, 5 de junho de 2013

Montando um conversor RS232 para Ethernet com o ATmega328.

Eu estava aqui brincando com uma plaquinha na qual eu fiz um conversor ethernet para serial /serial para ethernet e resolvi postar o código fonte pra vcs. Se alguém quiser um aplicativo que funciona como terminal ethernet é só me enviar um e-mail, para o serial existe no mercado um monte de opção. neste projeto o único porem que vi foi quando envio dados da serial para o pc pois se for maior que 40 caracteres ele acaba perdendo o restante ,para minha aplicação do jeito que está basta ,veja a figura abaixo que demonstra como configurar os pinos do AVR como o modulo wiznet (o qual apresentei em posts anteriores.) 







O código fonte :

//***********************************************************************************************************
//                               conversor ethernet - serial RS232
//
//  versão                        : 1.0
//  descrição                   : conversor ethernet-serial/serial-ethernet
//  Autor                      : Aguivone
//  microcontrolador           : ATmega328 P
//  compilador                   : AVR-GCC / Atmel AVR Studio 4.17
//  Data do projeto            : 05 / 06 / 2013.

//*************************************************************************************************************
#include <avr/io.h>
#include <string.h>
#include <stdio.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>// AVRJazz Mega328 SPI I/O
#include <avr/eeprom.h> ///para usar a eeprom
////////////////////////funçoes auxiliares/////////////////////////////
#define liga_pino(x,y) x |= _BV(y)
#define desliga_pino(x,y) x &= ~(_BV(y))
#define alterna_pino(x,y) x ^= _BV(y)
#define pino_alto(x,y) ((x & _BV(y)) == _BV(y)) //o pino deve estar como entrada ,testa se está em nivel logico 1
/////////////////////////////////////////////////////////////////////////////////////////////
//configuração dos pinos SPI
#define SPI_PORT PORTB //porta que será usado para SPI
#define SPI_DDR  DDRB  
#define SPI_SS   PORTB2//seleção de chip(habilita comunicação SPI)
// Wiznet W5100 Op Code usados
#define WIZNET_ESCREVE_OPCODE 0xF0  
#define WIZNET_LER_OPCODE     0x0F
// Wiznet W5100 endereço dos registradores
#define MR         0x0000   // Registrador de modo
#define GAR           0x0001   // endereço(inicial) de Gateway : 0x0001 a 0x0004
#define SUBR       0x0005   // mascara de subrede           : 0x0005 a 0x0008
#define SAR           0x0009   // Source Address Register , endereço de hardware (MAC)   : 0x0009 a 0x000E
#define SIPR       0x000F   // IP do hardware(escolhido pelo programador): 0x000F to 0x0012 //Socket 0
#define RMSR       0x001A   // RX tamanho do buffer(memória) de recepção
#define TMSR       0x001B   // TX tamanho do buffer(memória) de envio

#define S0_MR       0x0400      // Socket 0: registrador do socket0 
#define S0_CR       0x0401      // Socket 0: registrador de comandos do socket0
#define S0_IR       0x0402      // Socket 0: registrador de interrupçoes do socket0
#define S0_SR       0x0403      // Socket 0: registrador de status do socket0
#define S0_PORT    0x0404      // Socket 0: aqui é colocado o valor da porta TCP ->  endereço = 0x0404 a 0x0405
#define S0_DPORT   0x0410      // Socket 0: aqui é colocado o valor da porta TCP de destino->  endereço = 0x0410 a 0x0411
#define S0_DHAR    0x0406      // Socket 0: endereçamento de registro de hardware(tipo MAC)->  endereço = 0x0406 a 0x040B
#define S0_DIPR    0x040C      // Socket 0: IP de acesso remoto ->  endereço = 0x040C a 0x040F
#define SO_TX_FSR  0x0420      // Socket 0: tamanho do registrador de transmissão: 0x0420 to 0x0421
#define S0_TX_RD   0x0422      // Socket 0: ponteiro de leitura do buffer de transmissão : 0x0422 a 0x0423
#define S0_TX_WR   0x0424      // Socket 0: ponteiro de escrita do buffer de transmissão: 0x0424 a 0x0425
#define S0_RX_RSR  0x0426      // Socket 0: tamanho do registrador de recepção: 0x0426 a 0x0427
#define S0_RX_RD   0x0428      // Socket 0: ponteiro de leitura do tamanho dos dados: 0x0428 a 0x0429

#define TXBUFADDR  0x4000      // W5100 posição inicial do buffer de escrita
#define RXBUFADDR  0x6000      // W5100 posição inicial do buffer de recepção
//S0_MR (possiveis configuração)
#define MR_CLOSE      0x00    // socket não usado
#define MR_TCP          0x01    // TCP
#define MR_UDP          0x02    // UDP
#define MR_IPRAW      0x03      // IP LAYER RAW SOCKET
#define MR_MACRAW      0x04      // MAC LAYER RAW SOCKET
#define MR_PPPOE      0x05      // PPPoE
#define MR_ND          0x20      // sem delay de Ack(somente TCP) 
#define MR_MULTI      0x80      // suporte ao multicast(valido somente para UDP)
// S0_CR values
#define CR_OPEN          0x01      // Initializa ou abre socket
#define CR_LISTEN        0x02      // espera resposta de um ponto na rede (no modo tcp(modo servidor))
#define CR_CONNECT       0x04      // envia resposta a um ponto da rede(modo cliente)
#define CR_DISCON        0x08      // finaliza conexão e gera interrupção se falhar(desconexão)
#define CR_CLOSE         0x10      // desconecta socket
#define CR_SEND          0x20      // atualiza memoria de transmissão e envia dados
#define CR_SEND_MAC      0x21      // envia dados com o endereço do MAC, sem o processo ARP(modo UDP)
#define CR_SEND_KEEP     0x22      // ckeca estado da conexão - envia um byte pra testar a conexão (modo tcp)
#define CR_RECV          0x40      // indica uma recepção no socket
// S0_SR values
#define SOCK_CLOSED      0x00     // socket fechado
#define SOCK_INIT        0x13      // inicializa conexão
#define SOCK_LISTEN      0x14      // verifica estado(listen)
#define SOCK_SYNSENT     0x15      // comando de conexão foi iniciado
#define SOCK_SYNRECV     0x16      // houve sincronismo de recepção
#define SOCK_ESTABLISHED 0x17      // sucesso na conexão
#define SOCK_FIN_WAIT    0x18      // esperando finalizar
#define SOCK_CLOSING     0x1A      // finalizando conexão
#define SOCK_TIME_WAIT     0x1B      // espera tempo para finaliza
#define SOCK_CLOSE_WAIT  0x1C      // recebeu comando de fechar pelo host
#define SOCK_LAST_ACK    0x1D      // durante a finalização é o ultimo ack
#define SOCK_UDP         0x22      // UDP     socket
#define SOCK_IPRAW       0x32      // IP raw  socket
#define SOCK_MACRAW      0x42      // MAC raw socket
#define SOCK_PPPOE       0x5F      // PPPOE   socket
#define TX_BUF_MASK      0x07FF   // Mascara do buffer de transmissão 2K :
#define RX_BUF_MASK      0x07FF   // Mascara do buffer de recepção 2K :
#define NET_MEMORIA      0x05     // Use 8K de Tx/Rx Buffer(pode se usar até 8K)
#define TCP_PORT         0X1388       // porta TCP/IP usada pelo pc porta 5000
#define MAX_BUF          100      // tamanho maximo do buffer de recepção reduzi de 100 pra 10
/////////
//////// variaveis globais ////////////////////////////////////////
////////
char buf[MAX_BUF];//o maior dado será #C-255-255-255-255-255-255-255-255-255-255-255-1023-1022*
char buffer_serial[MAX_BUF];//buffer da serial

//////////////valores padrao

uint8_t sockstat=0;
char comando;
int ip_placa=5;
int rsize;
int tempo_250ms=0;

//para serial

 unsigned char caracter;//
 //int  tam_buffer;//usado para contar o tamanho do buffer de recepção
 volatile char rec_buffer ='S';
 int dados_serial_conta=0;


int timeout_serial =0;

///////////////////////função de escrita SPI////////////////////////

void spi_escreve(unsigned int endereco,unsigned char dados)
{
  // ativa pino de habilitação do SPI
  SPI_PORT &= ~(1<<SPI_SS);  
  SPDR = WIZNET_ESCREVE_OPCODE;//inicializa escrita pelo W5100  
  // espera terminar a comunicação
  while(!(SPSR & (1<<SPIF))); 
   // escreve bytes mais significativo no buffer spi
  SPDR = (endereco & 0xFF00) >> 8;
   // espera finalizar a transmissão
  while(!(SPSR & (1<<SPIF))); 
   //escreve os bytes menos significativo no buffer spi
  SPDR = endereco & 0x00FF;  
  // espera finalizar a transmissão
  while(!(SPSR & (1<<SPIF)));   

  SPDR = dados;  // inicializa transmissão de dados
  while(!(SPSR & (1<<SPIF))); // espera finalizar a transmissão
  // finaliza comunicação.
  SPI_PORT |= (1<<SPI_SS);
}
/////////////////////////de leitura////////////////////////////////
unsigned char spi_ler(unsigned int endereco)
{
  // ativa pino de habilitação do SPI
  SPI_PORT &= ~(1<<SPI_SS);
  //inicializa leitura pelo W5100 
  SPDR = WIZNET_LER_OPCODE;
   // espera finalizar a transmissão
  while(!(SPSR & (1<<SPIF)));
   // inicialização do Wiznet W5100 envia os bytes mais significativo
  SPDR = (endereco & 0xFF00) >> 8;
   // espera finalizar a transmissão
  while(!(SPSR & (1<<SPIF)));
  //inicialização do Wiznet W5100 envia os bytes menos significativo
  SPDR = endereco & 0x00FF;
   // espera finalizar a transmissão
  while(!(SPSR & (1<<SPIF)));   

  // envia um valor qualquer para ler os dados(pois isto liga o sinal de clock do registrado SPI)
  SPDR = 0x00;
  // espera finalizar a transmissão
  while(!(SPSR & (1<<SPIF))); 
  // finaliza comunicação.
  SPI_PORT |= (1<<SPI_SS);
  return(SPDR);//retorna o valor lido do W5100
}
////////////////////////inicializa ethernet //////////////////////////////////
void W5100_Inicializa(void)
{
  unsigned char porta[] = {0x13,0X88};// ex 5000 = 0X1388 -> 0X13 +0X88 //porta 5000
  //unsigned char porta[] = {0x00,0X50};// ex 80 = 0X0050 -> 0X00 +0X50 //porta tcp
  unsigned char mac_addr[] = {0x00,0x16,0x36,0xDE,0x58,0xF6};//MAC usado pelo microcontrolador(para teste)
  unsigned char ip_addr[] = {192,168,0,ip_placa};//enderreço de ip do servidor(valor para teste,do microcontrolador)
  unsigned char sub_mask[] = {255,255,255,0};//mascara de sub_rede
  unsigned char gtw_addr[] = {192,168,0,1};//enderreço de ip do gateway cliente (valor para teste,do pc)
  //este ip não é necessariamente o do pc mas deve estar nesta faixa

  spi_escreve(MR,0x80);            // MR = 0b10000000; isso inicicializa os registradores internos automaticamente
  _delay_ms(1);
  //configura porta de comunicação para acesso remoto
  spi_escreve(S0_PORT , porta[0]);
  spi_escreve(S0_PORT + 1,porta[1]);//porta de envio e de destino são iguais 
  spi_escreve(S0_DPORT , porta[0]);
  spi_escreve(S0_DPORT + 1,porta[1]);
  spi_escreve(S0_MR,MR_TCP);    // configura o registrador de modo (MR)
  _delay_ms(1);
  // configura o MAC de acesso remoto
  spi_escreve(S0_DHAR , mac_addr[0]);
  spi_escreve(S0_DHAR + 1,mac_addr[1]);
  spi_escreve(S0_DHAR + 2,mac_addr[2]);
  spi_escreve(S0_DHAR + 3,mac_addr[3]);
  spi_escreve(S0_DHAR + 4,mac_addr[4]);
  spi_escreve(S0_DHAR + 5,mac_addr[5]);
  _delay_ms(1);
  // endereço de IP de acesso remoto será sempre 1 a mais
  spi_escreve(S0_DIPR,ip_addr[0]);
  spi_escreve(S0_DIPR + 1,ip_addr[1]);
  spi_escreve(S0_DIPR + 2,ip_addr[2]);
  spi_escreve(S0_DIPR + 3,ip_placa + 1);
  _delay_ms(1);
  //configura gateway
  spi_escreve(GAR + 0,gtw_addr[0]);///o gateway deve ter mesma faixa de ip senão não funciona,mas nunca usar ip 1 pois é do gateway.
  spi_escreve(GAR + 1,gtw_addr[1]);
  spi_escreve(GAR + 2,gtw_addr[2]);
  spi_escreve(GAR + 3,gtw_addr[3]);
  _delay_ms(1);
  // configura o MAC (SAR): 0x0009 à 0x000E
  spi_escreve(SAR ,mac_addr[0]);
  spi_escreve(SAR + 1,mac_addr[1]);
  spi_escreve(SAR + 2,mac_addr[2]);
  spi_escreve(SAR + 3,mac_addr[3]);
  spi_escreve(SAR + 4,mac_addr[4]);
  spi_escreve(SAR + 5,mac_addr[5]);
  _delay_ms(1);
  // mascara de sub rede (SUBR): 0x0005 à 0x0008
  spi_escreve(SUBR ,sub_mask[0]);
  spi_escreve(SUBR + 1,sub_mask[1]);
  spi_escreve(SUBR + 2,sub_mask[2]);
  spi_escreve(SUBR + 3,sub_mask[3]);
  _delay_ms(1);
  // endereço de IP (SIPR): 0x000F à 0x0012
  spi_escreve(SIPR,ip_addr[0]);
  spi_escreve(SIPR + 1,ip_addr[1]);
  spi_escreve(SIPR + 2,ip_addr[2]);
  spi_escreve(SIPR + 3,ip_addr[3]);
  _delay_ms(1);
  // configura o tamanho do buffer  de memoria de escrita e leitura 
  spi_escreve(RMSR,NET_MEMORIA);//configura para cada socket 2K de memoria de leitura(no W5100 tem 4 socket mas vamos usar somente o socket 0)
  spi_escreve(TMSR,NET_MEMORIA);//configura para cada socket 2K de memoria de transmissao(no W5100 tem 4 socket mas vamos usar somente o socket 0)
}
//////////////////////////////////fecha socket 0  ///////////////////////
void fechar_socket(uint8_t socket)
{
   if (socket != 0)return;
   // manda fechar
   spi_escreve(S0_CR,CR_CLOSE);   // espera que S0_CR seja zerado(finalizado)
   while(spi_ler(S0_CR));
}

//////////////////////////////////fecha conexão//////////////////////////
void desconectar(uint8_t sock)
{
   if (sock != 0) return;   // envia comando para desconectar
   spi_escreve(S0_CR,CR_DISCON);   // espera desconexão.
   while(spi_ler(S0_CR));
}
////////////////////////////////conecta pelo modo servidor////////////////////
uint8_t conex_servidor(uint8_t socket)
{
    uint8_t retval=0;   
     if (socket != 0)//teste socket0
      return retval;
     if (spi_ler(S0_SR) == SOCK_CLOSED) // verificando se o socket 0 está fechado.
      {
      fechar_socket(socket);
      }     
    // abrindo o  Socket 0
    spi_escreve(S0_CR,CR_OPEN);                   // abre socket 
    while(spi_ler(S0_CR)); //espera abrir  
    if (spi_ler(S0_SR) == SOCK_INIT)//checa estado inicial
      retval=1;
    else
      fechar_socket(socket);
    return retval;
}

/////////////////////////////"escuta" a rede/////////////////////////////////
uint8_t escutar_rede(uint8_t socket)
{
     uint8_t retval = 0;
     if (socket != 0)
     return retval;
     if (spi_ler(S0_SR) == SOCK_INIT) 
     { 
         spi_escreve(S0_CR,CR_LISTEN);//envia comando para escutar a rede
         while(spi_ler(S0_CR));     // espera terninar processo de escuta
         if (spi_ler(S0_SR) == SOCK_LISTEN) //verifica status de escuta
           retval=1;
         else
           fechar_socket(socket);
     }
    return retval;
}
void put_serial(unsigned char *carac)
{
           while ( !( UCSR0A & (1<<UDRE0)) ); //espera transmitir para enviar um novo
           UDR0 = *carac;
           /* Wait for empty transmit buffer */

}
void escreve_caracter(unsigned char carac)
{
           while ( !( UCSR0A & (1<<UDRE0)) ); //espera transmitir para enviar um novo
           UDR0 = carac;
}
////////////////////////////envia pacote////////////////////////////////////
uint16_t envia_pacote(unsigned char pacote[],uint16_t buffer_tamanho)
{
    unsigned int ponteiro,ende_temp,ende_real,tx_tamanho,timeout;   

    if (buffer_tamanho <= 0 )return 0;//verifica se tem espaço livre para transmitir
    tx_tamanho=spi_ler(SO_TX_FSR);
    tx_tamanho=(((tx_tamanho & 0x00FF) << 8 ) + spi_ler(SO_TX_FSR + 1));//calcula tamanho do buffer de transmissão.
    timeout=0;
    while (tx_tamanho < buffer_tamanho) 
    {
          _delay_ms(1);
         tx_tamanho= spi_ler(SO_TX_FSR);
         tx_tamanho=(((tx_tamanho & 0x00FF) << 8 ) + spi_ler(SO_TX_FSR + 1));     // Timeout para aproximadamente 10ms
         if (timeout++ > 10) 
           {
            // desconecta socket 0 pois não há espaço livre após 10ms
           desconectar(0);
           return 0;
           }
    }
    tx_tamanho=0;    
   ponteiro = spi_ler(S0_TX_WR);//lê o ponteiro de escrita.
   ende_temp = (((ponteiro & 0x00FF) << 8 ) + spi_ler(S0_TX_WR + 1));
    while(buffer_tamanho) 
    {
      buffer_tamanho--;
      // calcula o endereço real do buffer de transmissão. 
      ende_real = TXBUFADDR + (ende_temp & TX_BUF_MASK);      // copia pacote para W5100(TX_buffer)
      spi_escreve(ende_real,pacote[tx_tamanho]);
      ende_temp++;
      tx_tamanho++;
    }
    // incrementa valor de  S0_TX_WR , então este será o ponto da proxima transmissão
    spi_escreve(S0_TX_WR,( ende_temp & 0xFF00) >> 8 );
    spi_escreve(S0_TX_WR + 1,( ende_temp & 0x00FF));
    // agora envia o comando de enviar dados para rede.
    spi_escreve(S0_CR,CR_SEND);
    // espera terminar o envio
    while(spi_ler(S0_CR));
    return 1;
}


/////////////////////////////////recebe pacote//////////////////////////////////////////
void  recebe_pacote(char pacote[],int taman)
{
        unsigned int ponteiro,ende_temp,ende_real; 
        char recebido;
        ponteiro = spi_ler(S0_RX_RD);//pega localização do dado recebido(tamanho)
        ende_temp = (((ponteiro & 0x00FF) << 8 ) + spi_ler(S0_RX_RD + 1));////pega localização do dado recebido por ser 16 bits "ponteiro" deve ser descolado 
       while(taman>0) 
        {
          taman--;
          ende_real=RXBUFADDR + (ende_temp & RX_BUF_MASK);//endereço inicial de leitura + endereço dos dados recebidos 
          recebido = spi_ler(ende_real);
          escreve_caracter(recebido);
          ende_temp++;          
        }
    // incrementa S0_RX_RD ,este será o ponto da proxima recepção
    spi_escreve(S0_RX_RD,(ende_temp & 0xFF00) >> 8 );
    spi_escreve(S0_RX_RD + 1,(ende_temp & 0x00FF));///////////////////////////////////////////////////vou ver isso//////////
    //envia comando para leitura 
    spi_escreve(S0_CR,CR_RECV);
    _delay_us(5);    // espera terminar leitura.
}
///////////////////////////////////////////////////////////////////////
int Buffer_recepcao(void)
{
  return (((spi_ler(S0_RX_RSR) & 0x00FF) << 8 ) + spi_ler(S0_RX_RSR + 1)); //
}

////////////////////////funçoes auxiliares///////////////////////////////

void inicializa_spi(void)
{
// Seta MOSI (PORTB3),SCK (PORTB5) and PORTB2 (SS) , e os outros pinos como saida 
  SPI_DDR = (1<<PORTB3)|(1<<PORTB5)|(1<<PORTB2)|(1<<PORTB1);//coloca b1 como saida
  // SS não ativado
  SPI_PORT |= (1<<SPI_SS); 
  // habilita SPI, modo mestre 0, seta clock para fck/2
  SPCR = (1<<SPE)|(1<<MSTR);
  SPSR |= (1<<SPI2X);  
}
///////////////////////////////////////////////funçoes usadas pela usart////////////////////////////////////////////////

void serial_inicializa(int BAUD)
{
   int valor = ((F_CPU/16)/BAUD)-1; //verifique se na simulação não está habilitado pra divir por oito o clock do microcontrolador
/* baud rate */
   UCSR0A = 0X20;
/* habilita receiver and transmitter buffers */
  UBRR0H = (unsigned char)(valor>>8);
  UBRR0L = (unsigned char)valor;
/* habilita receiver and transmitter buffers */
  UCSR0B = 0X98;//deve se habilitar somente a interrupção de recepção para não travar o microcontrolador
/* modo assicrono,8 bits , 1 stop bit, assincrono , sem paridade */
   UCSR0C = 0X06;
  //UCSR0C = (1<<USBS0)|(3<<UCSZ00);
}




//////////////////////////interrupções requerida para usar a serial///////////////////////////////////
ISR(USART_RX_vect)
{
 caracter = (char)UDR0;
 buffer_serial[dados_serial_conta] = caracter;
 dados_serial_conta++ ;
 timeout_serial=0;
 rec_buffer ='D';
}

//////////////////////////////////////////////função principal/////////////////////////////////////
int main(void)
 {
  
  //////////////////inicializa port D e port C /////////////////////////////////////////////////////////////////////
  DDRD = 0xFF;//configura port D como saida       
  PORTD = 0x00;//limpa o port D
  DDRC = 0x00;//configura port C como entradas
  //i = PINB; //ler pinos
  inicializa_spi();    //comunicação spi
  ip_placa=5;
  W5100_Inicializa();  //ethernet
  serial_inicializa(9600);//não importa frequencia e 9600
  sei();
  //impri_serial("Use < ou >  \n ");
  //impri_serial("para enviar comando ! \n "); 
  desliga_pino(PORTB,PB0);///// //reseta o chip de ethernet
  _delay_ms(1);
  liga_pino(PORTB,PB0);
  ///////////////////////////////////
  sei();
  ///////////////////////////////////////////////////////
 //OBS : devo desabilitar o bit RSTDISBL para qu e o pino C6 possa ser usado como I/O
 ///////////////////////////////////////////////////////
  for(;;)
    {
       if(tempo_250ms > 30000)//tempo qualquer
          {
            alterna_pino(PORTB,PB1);
            tempo_250ms =0;            
         }
          tempo_250ms++;
          timeout_serial++;
                  
     ////////////////////////rotina ethernet////////////////////////
            sockstat=spi_ler(S0_SR);
            switch(sockstat) 
            {
            case SOCK_CLOSED: ///socket fechado. fica escutando a rede
                 if ( conex_servidor(0) > 0) 
                     {// Listen(escuta)  Socket 0
                        if (escutar_rede(0) <= 0)//socket 0
                             _delay_ms(1);
                     }
                     break;    
             case SOCK_ESTABLISHED://conectado
                 {
                   // lê o pacote recebido e verifica se é valido
                   if(timeout_serial > 3000)//se maior que alguns milisegundos verifica se houve novos dados
                   {
                      if(rec_buffer == 'D')
                      {///tem dados
                       envia_pacote(buffer_serial,MAX_BUF);//envia dados
                       while(dados_serial_conta>0)
                       {
                         buffer_serial[dados_serial_conta] = 0; 
                         dados_serial_conta--;
                       }
                       rec_buffer = 'S';
                      }
                    timeout_serial=0;
                   }
                    if (Buffer_recepcao() > 0)//checa buffer de recepção
                         {
                          // lê o pacote recebido e verifica se é valido

                              rsize=((spi_ler(S0_RX_RSR) & 0x00FF) << 8 ) + spi_ler(S0_RX_RSR + 1);//pega tamanho dos dados recebido
                              recebe_pacote(buf,rsize);
                          }
                              tempo_250ms++;//se conectado pisca 2X mais
                              tempo_250ms++;//se conectado pisca 3X mais rapido
              
                 }break;//fim socket estabilizado      
              case SOCK_FIN_WAIT:
              case SOCK_CLOSING:
              case SOCK_TIME_WAIT:
              case SOCK_CLOSE_WAIT:
              case SOCK_LAST_ACK:
              fechar_socket(0);// força o fechamento do socket 0
              break;
              }//chave do switch

      ////////////////////////fim da rotina ethernet////////////////////////

   }//chave do laço infinito (for)
}//chave de main

terça-feira, 28 de maio de 2013

controlando motor de passo via serial com MBlab XC8

 Hoje vou demonstrar como controlar um motor de passo via serial,note que o motor usado tem
 4 passos logo 90° por enrolamento, os comando para o controle são:
letra "A" - faz motor girar no sentido anti horário. 
letra "H" - faz girar no sentido horário.
letra "P" - para o motor
 
e os comandos :
"E****" - faz girar no sentido anti horário **** graus ,por exemplo  E0090, faz girar 90°. 
"D****" - faz girar no sentido horário **** graus ,por exemplo  E0180, faz girar 180°.
"V****" - altera a velocidade do motor, quanto menor o valor mais rápido, Ex. V0100.
 
O código fonte: 
 
/*
 *                            controlando motor de passo no MPlab XC8
 *
 * Compilador : MPlabXC8
 * Microcontrolador: 16F648A
 * Autor: aguivone
 * Versão: 1
 * Data :  28 de maio de 2013
 */
#include <stdio.h>
#include <string.h> //para usar funçoes de string deve se adicionar este header
#include <stdlib.h>
#define _XTAL_FREQ 4000000    // apesar de não usar cristal devo configurar pois esse define é usado para rotinas de tempo e da serial
#include <xc.h>

/////////////////////////////////////////////////////////configuraçôes//////////////////////////////////////////////////
#pragma config FOSC = INTOSCIO  // Oscillator Selection bits (INTOSC oscillator: I/O function on RA6/OSC2/CLKOUT pin, I/O function on RA7/OSC1/CLKIN)
//#pragma config FOSC = HS        // Oscillator Selection bits (HS oscillator: High-speed crystal/resonator on RA6/OSC2/CLKOUT and RA7/OSC1/CLKIN)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = ON       // Power-up Timer Enable bit (PWRT enabled)
#pragma config MCLRE = OFF      // RA5/MCLR/VPP Pin Function Select bit (RA5/MCLR/VPP pin function is digital input, MCLR internally tied to VDD)
#pragma config BOREN = OFF      // Brown-out Detect Enable bit (BOD disabled)
#pragma config LVP = OFF        // Low-Voltage Programming Enable bit (RB4/PGM pin has digital I/O function, HV on MCLR must be used for programming)
#pragma config CPD = OFF        // Data EE Memory Code Protection bit (Data memory code protection off)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

/////////////////////defines///////////////////////////////////////////////////////////////////////////////////////////////
#define A RA0     //enrolamento 1
#define B RA1     //enrolamento 2
#define C RA2     //enrolamento 3
#define D RA3     //enrolamento 4


////////////////////////////////////////////////variaveis globais///////////////////////////////////////////////////////////
char caracter,comando;
bit flag_interrupcao = 0;
int posi_atual=1;//se colocar zero ira travar
long velocidade =500;
char valor[4];
int conta_pos=0;
bit recebendo = 0;
int girando=0;
///////////////////////////////////////////////////interrupção//////////////////////////////////////////////////////////////
void interrupt RS232(void)//vetor de interrupção
 {
     caracter = RCREG;
     RCIF = 0;//  limpa flag de interrupção de recepção
     if((caracter == 'A')||(caracter == 'H')||(caracter=='P'))
     {
          comando = caracter;
          flag_interrupcao = 1; //indica interrupção
     }
     else
     {
         if((caracter == 'E')||(caracter == 'D')||(caracter=='V'))
         {
          comando = caracter;
          conta_pos=0;//prepara buffer
          recebendo = 1;
         }
         else
         {
            if((recebendo == 1)&&(conta_pos<4))
            {
                valor[conta_pos]=caracter;
                if(conta_pos == 3)
                {
                  recebendo = 0;//finaliza recepção
                  flag_interrupcao = 1; //indica interrupção
                }
                conta_pos++;
            }
            else
            {//houve algum erro ou caracter invalido
                recebendo = 0;//finaliza recepção
                flag_interrupcao = 0;
            }
         }

     }
 }

/////////////////////////////////funçoes usadas pela uart //////////////////////////////////////////////////////
void inicializa_RS232(long velocidade,int modo)
{////por padrão é usado o modo 8 bits e sem paridade, mas se necessario ajuste aqui a configuração desejada.
    //verifique datasheet para ver a porcentagem de erro e se a velocidade é possivel para o cristal utilizado.
    RCSTA = 0X90;//habilita porta serial,recepção de 8 bit em modo continuo,assincrono.
    int valor;
        if(modo == 1)
        {//modo = 1 ,modo alta velocidade
         TXSTA = 0X24;//modo assincrono,trasmissao 8 bits.
         valor =(int)(((_XTAL_FREQ/velocidade)-16)/16);//calculo do valor do gerador de baud rate
        }
        else
        {//modo = 0 ,modo baixa velocidade
         TXSTA = 0X20;//modo assincrono,trasmissao 8 bits.
         valor =(int)(((_XTAL_FREQ/velocidade)-64)/64);//calculo do valor do gerador de baud rate
        }
    SPBRG = valor;
    RCIE = 1;//habilita interrupção de recepção
    TXIE = 0;//deixa interrupção de transmissão desligado(pois corre se o risco de ter uma interrupção escrita e leitura ao mesmo tempo)
}
void escreve(char valor)
{
    TXIF = 0;//limpa flag que sinaliza envio completo.
    TXREG = valor;
    while(TXIF ==0);//espera enviar caracter
}
void imprime(const char frase[])
{
     char indice = 0;
     char tamanho = strlen(frase);
      while(indice < tamanho ) ///veja que o programa pode travar se aqui não tiver as duas aspas
       {
           escreve(frase[indice]);
           indice++;
       }
}
//////////////////////////////////////////////////////Rotina principal///////////////////////////////////////////////////////////////
/*
 *  if(flag_interrupcao ==  1)
        {//tem dados para ler
         imprime(" \n\r caracter digitado :");
         escreve(caracter);
         flag_interrupcao = 0;
        }

 */
//aprox 1 ms para clok interno

void delay_ms(long mili) {
    while (mili > 0) {
        _delay(1000); //aprox. 1ms com clock de 1mhz(oscilador interno)
        mili--;
    }
    // no caso do 18F o valor fica bem acima então é melhor montar essa funcão para que trabalhe bem
   //////////
}

//////////////rotinas do motor de passo//////////////////////////////////////////////////////////////////////
void desliga_motor(void)
{
             A=0;B=0;C=0;D=0; //desliga todos os enrolamentos
}

void graus_horario(long valor)
{
    valor = valor/90;//assim acha numero de pulsos necessarios
    while((valor>0)&&(comando != 'P'))//isso faz ele parar se receber o comando 'P'
    {
        posi_atual++;
        if(posi_atual > 4)
        {
          posi_atual = 1;
        }
         switch(posi_atual)
        {
            case 1:
            {
             A=0;B=0;C=0;D=1; //desliga todos os enrolamentos
            }break;
            case 2:
            {
             A=0;B=0;C=1;D=0; //desliga todos os enrolamentos
            }break;
            case 3:
            {
             A=0;B=1;C=0;D=0; //desliga todos os enrolamentos
            }break;
            case 4:
            {
             A=1;B=0;C=0;D=0; //desliga todos os enrolamentos
            }break;
        }
        delay_ms(velocidade);
        valor--;

    }
}
void graus_antihorario(long valor)
{
    valor = valor/90;//assim acha numero de pulsos necessarios
    while((valor>0)&&(comando != 'P'))//isso faz ele parar se receber o comando 'P'
    {
        posi_atual--;
        if(posi_atual == 0)
        {
          posi_atual = 4;
        }
        switch(posi_atual)
        {
            case 1:
            {
             A=0;B=0;C=0;D=1; //desliga todos os enrolamentos
            }break;
            case 2:
            {
             A=0;B=0;C=1;D=0; //desliga todos os enrolamentos
            }break;
            case 3:
            {
             A=0;B=1;C=0;D=0; //desliga todos os enrolamentos
            }break;
            case 4:
            {
             A=1;B=0;C=0;D=0; //desliga todos os enrolamentos
            }break;
        }
        delay_ms(velocidade);
        valor--;
    }
}
void girar_horario(void)
{
      graus_horario(360);
}
void girar_antihorario(void)
{

      graus_antihorario(360);
}
long char_to_long(void)
{
    long retorno=0;
    long valor3 = valor[3]-'0';//faz virar um long
    long valor2 = valor[2]-'0';//faz virar um long
    long valor1 = valor[1]-'0';//faz virar um long
    long valor0 = valor[0]-'0';//faz virar um long
    retorno = valor3+(valor2*10)+(valor1*100)+(valor0*1000);
    return(retorno);
}
///////////////////////////////////////////////////////////////////////////////
void main(void)
{
    TRISB = 0X02;//configura portB  B1 (pino RX) como entrada
    TRISA = 0X00;//configura portA como saida
    PORTB = 0;  // limpar as portas que estão configuradas como saidas
    inicializa_RS232(9600,1);//modo de alta velocidade
    PEIE = 1;//habilita interrupção de perifericos do pic
    GIE = 1; //habilita interrupção global 
    CMCON = 7;//desliga modulo comparador
    imprime("Controlador de motor de passo \n\r");
    desliga_motor();
    for(;;)
    {
        if(flag_interrupcao == 1)//tem dados na serial
        {
            switch(comando)
            {
                case 'A':
                {
                  girando = 1;
                }break;
                case 'H':
                {
                  girando = 2;
                }break;
                case 'P':
                {
                  girando = 0;
                }break;
                case 'V':
                {
                    velocidade = char_to_long();
                }break;
                case 'E':
                {
                    graus_antihorario(char_to_long());
                    girando = 0;
                }break;
                case 'D':
                {
                    graus_horario(char_to_long());
                    girando = 0;   
                }break;
            }
         comando = ' ' ;//zera comando
         flag_interrupcao = 0;//limpa flag
        }
        if(girando == 0)
        {
           desliga_motor();
        }
        else
        {
            if(girando == 1)
            {
             girar_antihorario();
            }
            else
            {
              girar_horario();
            }
        }
    }//loop infinito

}
 
A simulação: 
 
para a simulação use o motor-stepper. 
 
 

quarta-feira, 17 de abril de 2013

Comunicação SPI entre dois PIC com o 18F13K22:


               Quando fiz este código-fonte meu intuito era montar uma comunicação entre os dois chips mas devido a limitação do hardware do pic não foi possível(a menos que fizesse isso de modo manual sem usar o buffer do SPI) , então fiz somente um exemplo que demonstra como enviar os dados do mestre para o escravo sem que haja comunicação do escravo para o mestre,quem sabe futuramente posto aqui um modo de fazer isso manualmente(veja que no avr ATmega328 isso é perfeitamente possível), para quem quiser saber mais sobre o protocolo SPI e I2C veja este link:
http://dev.emcelettronica.com/i2c-or-spi-serial-communication-which-one-to-go
                 Neste código já está pronto o modo mestre e escravo bastando apenas mudar o nível logico do pino RA0(5V - modo mestre e GND - modo escravo), então o microcontrolador no modo mestre envia um carácter para o microcontrolador que está configurado como escravo, e se for o carácter esperado ele acende um led por um tempo e então deslia o led. Deixei até uma função para ler dados pela SPI no modo mestre("embora não foi utilizado") .

O código - fonte:

/*
 *                                              usando SPI no MPlab XC8(modo mestre e escravo)
 *
 * Compilador : MPlabXC8
 * Microcontrolador: 18F13K22
 * Autor: aguivone
 * Versão: 1
 * Data :  17 de abril de 2013
 */

#include <stdio.h>
#include <stdlib.h>
#define _XTAL_FREQ 12000000    // cristal de 20 Mhz
#include <xc.h>

#define SPI_SLAVE_WRITE 0x00
#define SPI_SLAVE_READ  0x01
/////////////////////////////////////////////////////configuraçôes do pic //////////////////////////////////////////////////

// CONFIG1H
#pragma config FOSC = HS        // Oscillator Selection bits (HS oscillator)
#pragma config PLLEN = OFF      // 4 X PLL Enable bit (PLL is under software control)
#pragma config PCLKEN = ON      // Primary Clock Enable bit (Primary clock enabled)
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor disabled)
#pragma config IESO = OFF       // Internal/External Oscillator Switchover bit (Oscillator Switchover mode disabled)

// CONFIG2L
#pragma config PWRTEN = OFF     // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOREN = OFF      // Brown-out Reset Enable bits (Brown-out Reset disabled in hardware and software)
#pragma config BORV = 19        // Brown Out Reset Voltage bits (VBOR set to 1.9 V nominal)

// CONFIG2H
#pragma config WDTEN = OFF      // Watchdog Timer Enable bit (WDT is controlled by SWDTEN bit of the WDTCON register)
#pragma config WDTPS = 32768    // Watchdog Timer Postscale Select bits (1:32768)

// CONFIG3H
#pragma config HFOFST = ON      // HFINTOSC Fast Start-up bit (HFINTOSC starts clocking the CPU without waiting for the oscillator to stablize.)
#pragma config MCLRE = OFF      // MCLR Pin Enable bit (RA3 input pin enabled; MCLR disabled)

// CONFIG4L
#pragma config STVREN = ON      // Stack Full/Underflow Reset Enable bit (Stack full/underflow will cause Reset)
#pragma config LVP = ON         // Single-Supply ICSP Enable bit (Single-Supply ICSP enabled)
#pragma config BBSIZ = OFF      // Boot Block Size Select bit (512W boot block size)
#pragma config XINST = OFF      // Extended Instruction Set Enable bit (Instruction set extension and Indexed Addressing mode disabled (Legacy mode))

// CONFIG5L
#pragma config CP0 = OFF        // Code Protection bit (Block 0 not code-protected)
#pragma config CP1 = OFF        // Code Protection bit (Block 1 not code-protected)

// CONFIG5H
#pragma config CPB = OFF        // Boot Block Code Protection bit (Boot block not code-protected)
#pragma config CPD = OFF        // Data EEPROM Code Protection bit (Data EEPROM not code-protected)

// CONFIG6L
#pragma config WRT0 = OFF       // Write Protection bit (Block 0 not write-protected)
#pragma config WRT1 = OFF       // Write Protection bit (Block 1 not write-protected)

// CONFIG6H
#pragma config WRTC = OFF       // Configuration Register Write Protection bit (Configuration registers not write-protected)
#pragma config WRTB = OFF       // Boot Block Write Protection bit (Boot block not write-protected)
#pragma config WRTD = OFF       // Data EEPROM Write Protection bit (Data EEPROM not write-protected)

// CONFIG7L
#pragma config EBTR0 = OFF      // Table Read Protection bit (Block 0 not protected from table reads executed in other blocks)
#pragma config EBTR1 = OFF      // Table Read Protection bit (Block 1 not protected from table reads executed in other blocks)

// CONFIG7H
#pragma config EBTRB = OFF      // Boot Block Table Read Protection bit (Boot block not protected from table reads executed in other blocks)


/////////////////////////////

// Delay in 1 ms (approximately) for 12 MHz Internal Clock
void delay_ms(long mili)
{
   while(mili > 0)
   {
      _delay(2927);//aprox. 1ms com clock de 12mhz
      mili--;
   }
   // no caso do 18F o valor fica bem acima então é melhor montar essa funcão para que trabalhe bem
}


void MASTER_ESC_SPI(unsigned char dados)
{

    LATCbits.LC6 = 0;  // ativa CS(chip select do MCP23S17)
    SSPBUF = 0X01;//opcode que sinaliza que quer deixar um dado
    while(!SSPSTATbits.BF);//espera completar envio de dados
    delay_ms(1);//espera slave processar informação
    SSPBUF = dados;//envia os dados que será colocado no registrador
    while(!SSPSTATbits.BF);//espera completar envio de dados
    LATCbits.LC6 = 1;//desabilita chip
}

unsigned char MASTER_LER_SPI()
{
   LATCbits.LC6 = 0;  // ativa SS(slave select)
   SSPBUF = 0X00;//um valor qualquer para pegar os dados
  //desse registrador(assim gera o clock necessario para isto)
   while(!SSPSTATbits.BF);//espera completar envio de dados
   LATCbits.LC6 = 1;//desabilita chip
  return(SSPBUF);
}


unsigned char SLAVE_LER_SPI()
{
  while(!SSPSTATbits.BF);//espera completar recepção
  return(SSPBUF);
}

void SPI_MASTER()//modo mestre
{
  SSPCON1 = 0X20;//habilita modo SPI // FOSC/4 //
  SSPSTAT = 0XC0;//pega amostras no fim do bit e a trasmissão será na borda de descida
  TRISB = 0X10;//configura portB como saida exceto o pino b4(SDI)
  TRISC = 0X00;//configura portc como saida

  LATCbits.LC6 = 1;
}
void SPI_SLAVE()//modo slave
{
  SSPCON1 = 0X24;//habilita modo SPI // habilita pino SS(slave select) pois é mais usual assim
  SSPSTAT = 0X40;//trasmissão será na borda de descida
  TRISB = 0XFF;//configura portB como entrada
  TRISC = 0X40;//configura portc como saida exceto C6(SS)
  LATCbits.LC7 = 1;//sdo = 1

}


void main(void)
{
  TRISA = 0XFF;
  ANSEL = 0x00;//desabilita porta analogicas(para não atrapalhar recepção no pino)
  ANSELH = 0x00;//em alguns casos não funciona nem a interrupção de recepção.
  if(PORTAbits.RA0 == 1)
                   {//ligado ao vcc é master
                          SPI_MASTER();
                   }
                   else
                   {//se ligado ao gnd é slave
                          SPI_SLAVE();
                   }
  for(;;) {
              
              if(PORTAbits.RA0 == 1)
               {//ligado ao vcc é master
                 MASTER_ESC_SPI('F'); // ENVIA UM DADO
                 delay_ms(500);//tempo para nova solicitação
               }
               else
               {//se ligado ao gnd é slave
                  if(PORTCbits.RC6 == 0)//pino de ativação foi habilitado?
                    {
                        if(SLAVE_LER_SPI() == 'F')//verifica se recebeu um dado do mestre
                        {
                            LATCbits.LC0 = 1;
                            delay_ms(100);
                            LATCbits.LC0 = 0;
                        }
                        
                    }
               }

          }
}

A simulação:


Faça uma visita na página do face, basta procurar por microcontroladores-C,e dê um "curtir" assim vc fica por dentro das novidades do blog!  

terça-feira, 16 de abril de 2013

Expandindo as portas do PIC:

          Vou demonstrar aqui como aumentar o números de pinos do PIC simplesmente usando um CI fabricado pela própria microchip o MCP23S17, com ele você  pode ler e escrever no pinos do chip via comunicação SPI .O programa é bem simples o microcontrolador fica lendo o portB do chip o tempo todo dai se o botão for pressionado ele escreve no portA do chip o valor "0X0F" e quando o botão é liberado ele limpa o portA do chip.
           Esse chip é vendido no site da microchip por 1,50 dólar.Um detalhe que deve ser ressaltado as familias 18F  é que para ler um pino se usar  PORTXbits.RXY e para escrever se usa LATXbits.LXY onde o 'X' é o port e o 'Y' o numero do pino(0 a 7).

O código - fonte:


/*
 *                           usando a SPI(mestre) para comunicar com modulo expansor MCP23S17
 *
 * Compilador : MPlabXC8
 * Microcontrolador: 18F13K22
 * Autor: aguivone
 * Versão: 1
 * Data :  16 de abril de 2013
 */

#include <stdio.h>
#include <stdlib.h>
#define _XTAL_FREQ 12000000    // cristal de 20 Mhz
#include <xc.h>

/////////////////////////////////////////////////////configuraçôes do pic //////////////////////////////////////////////////

// CONFIG1H
#pragma config FOSC = HS        // Oscillator Selection bits (HS oscillator)
#pragma config PLLEN = OFF      // 4 X PLL Enable bit (PLL is under software control)
#pragma config PCLKEN = ON      // Primary Clock Enable bit (Primary clock enabled)
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor disabled)
#pragma config IESO = OFF       // Internal/External Oscillator Switchover bit (Oscillator Switchover mode disabled)

// CONFIG2L
#pragma config PWRTEN = OFF     // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOREN = OFF      // Brown-out Reset Enable bits (Brown-out Reset disabled in hardware and software)
#pragma config BORV = 19        // Brown Out Reset Voltage bits (VBOR set to 1.9 V nominal)

// CONFIG2H
#pragma config WDTEN = OFF      // Watchdog Timer Enable bit (WDT is controlled by SWDTEN bit of the WDTCON register)
#pragma config WDTPS = 32768    // Watchdog Timer Postscale Select bits (1:32768)

// CONFIG3H
#pragma config HFOFST = ON      // HFINTOSC Fast Start-up bit (HFINTOSC starts clocking the CPU without waiting for the oscillator to stablize.)
#pragma config MCLRE = OFF      // MCLR Pin Enable bit (RA3 input pin enabled; MCLR disabled)

// CONFIG4L
#pragma config STVREN = ON      // Stack Full/Underflow Reset Enable bit (Stack full/underflow will cause Reset)
#pragma config LVP = ON         // Single-Supply ICSP Enable bit (Single-Supply ICSP enabled)
#pragma config BBSIZ = OFF      // Boot Block Size Select bit (512W boot block size)
#pragma config XINST = OFF      // Extended Instruction Set Enable bit (Instruction set extension and Indexed Addressing mode disabled (Legacy mode))

// CONFIG5L
#pragma config CP0 = OFF        // Code Protection bit (Block 0 not code-protected)
#pragma config CP1 = OFF        // Code Protection bit (Block 1 not code-protected)

// CONFIG5H
#pragma config CPB = OFF        // Boot Block Code Protection bit (Boot block not code-protected)
#pragma config CPD = OFF        // Data EEPROM Code Protection bit (Data EEPROM not code-protected)

// CONFIG6L
#pragma config WRT0 = OFF       // Write Protection bit (Block 0 not write-protected)
#pragma config WRT1 = OFF       // Write Protection bit (Block 1 not write-protected)

// CONFIG6H
#pragma config WRTC = OFF       // Configuration Register Write Protection bit (Configuration registers not write-protected)
#pragma config WRTB = OFF       // Boot Block Write Protection bit (Boot block not write-protected)
#pragma config WRTD = OFF       // Data EEPROM Write Protection bit (Data EEPROM not write-protected)

// CONFIG7L
#pragma config EBTR0 = OFF      // Table Read Protection bit (Block 0 not protected from table reads executed in other blocks)
#pragma config EBTR1 = OFF      // Table Read Protection bit (Block 1 not protected from table reads executed in other blocks)

// CONFIG7H
#pragma config EBTRB = OFF      // Boot Block Table Read Protection bit (Boot block not protected from table reads executed in other blocks)



// defines para o MCP23S17 SPI Slave
#define SPI_SLAVE_ID    0x40
#define SPI_SLAVE_ADDR  0x00      // A2=0,A1=0,A0=0 pois os pinos estão aterrados
#define SPI_SLAVE_WRITE 0x00
#define SPI_SLAVE_READ  0x01

// MCP23S17 registradores padrão ( BANK=0)
#define IODIRA 0x00
#define IODIRB 0x01
#define IOCONA 0x0A
#define GPPUA  0x0C
#define GPPUB  0x0D
#define GPIOA  0x12
#define GPIOB  0x13
//variaveis usadas///////////
  unsigned char conta = 0 ,Botao = 0,porta =0;
/////////////////////////////

// Delay in 1 ms (approximately) for 12 MHz Internal Clock
void delay_ms(long mili)
{
   while(mili > 0)
   {
      _delay(2927);//aprox. 1ms com clock de 12mhz
      mili--;
   }
   // no caso do 18F o valor fica bem acima então é melhor montar essa funcão para que trabalhe bem
}


void ESCREVE_SPI(unsigned char endereco,unsigned char dados)
{
   LATCbits.LC6 = 0;  // ativa CS(chip select do MCP23S17)
  // inicia MCP23S17 OpCode
  SSPBUF = SPI_SLAVE_ID | ((SPI_SLAVE_ADDR << 1) & 0x0E)| SPI_SLAVE_WRITE;  
  while(!SSPSTATbits.BF);//espera completar envio de dados
  SSPBUF = endereco;//envia o endereço do regitrador que se quer acessar
  while(!SSPSTATbits.BF);//espera completar envio de dados
  SSPBUF = dados;//envia os dados que será colocado no registrador
  while(!SSPSTATbits.BF);//espera completar envio de dados
   LATCbits.LC6 = 1;//desabilita chip
}

unsigned char LER_SPI(unsigned char endereco)
{
     LATCbits.LC6 = 0;  // ativa CS(chip select do MCP23S17)
  // inicia MCP23S17 OpCode
  SSPBUF = SPI_SLAVE_ID | ((SPI_SLAVE_ADDR << 1) & 0x0E)| SPI_SLAVE_READ;
  while(!SSPSTATbits.BF);//espera completar envio de dados
  SSPBUF = endereco;//envia o endereço do regitrador que se quer acessar
  while(!SSPSTATbits.BF);//espera completar envio de dados
  SSPBUF = 0X00;//um valor qualquer para pegar os dados
  //desse registrador(assim gera o clock necessario para isto)
  while(!SSPSTATbits.BF);//espera completar envio de dados
   LATCbits.LC6 = 1;//desabilita chip
  return(SSPBUF);
}
void SPI_MASTER()//modo mestre
{
  SSPCON1 = 0X20;//habilita modo SPI // FOSC/4 //
  SSPSTAT = 0XC0;//pega amostras no fim do bit e a trasmissão será na borda de descida
  TRISB = 0X10;//configura portB como saida exceto o pino b4(SDI)
  TRISC = 0X00;//configura portc como saida
  ANSEL = 0x00;//desabilita porta analogicas(para não atrapalhar recepção no pino)
  ANSELH = 0x00;//em alguns casos não funciona nem a interrupção de recepção.
   LATCbits.LC6 = 1;
}
void INICIALIZA_MCP23S17()
{
  ESCREVE_SPI(IOCONA,0x28);   // I/O Registrador de controle: BANK=0, SEQOP=1, HAEN=1 (habilita endereçamento)
  ESCREVE_SPI(IODIRA,0x00);   // GPIOA será saida
  ESCREVE_SPI(IODIRB,0xFF);   // GPIOB será entrada
  ESCREVE_SPI(GPPUB,0xFF);    // habilita resistores de pull up no GPIOB
  ESCREVE_SPI(GPIOA,0x00);    // limpa saidas da GPIOA
}

void main(void)
{
  SPI_MASTER();//inicializa SPI no modo master
  INICIALIZA_MCP23S17();
  for(;;) {
             porta=LER_SPI(GPIOB);    // lê o valor do GPIOB
         if (porta == 0xFE)
               {      // se botão pressionado
                  delay_ms(100);//debounce(filtro de ruidos)
                  porta=LER_SPI(GPIOB);  // lê novamente
                  if (porta == 0xFE)
                  {//pressionado
                    ESCREVE_SPI(GPIOA,0X0F);//muda estado dos bits mais baixos
                  }                  
            }
             else
                  { //não pressionado
                    ESCREVE_SPI(GPIOA,0x00); // limpa o portA
                  }
        delay_ms(1000);//tempo para visualizar mudanças
        }
}

A simulação:

Faça uma visita na página do face, basta procurar por microcontroladores-C,e dê um "curtir" assim vc fica por dentro das novidades do blog!