sábado, 29 de outubro de 2011

Recursos Exclusivos do CCS C (compilador C para Microcontrolador PIC)

Print Friendly and PDF
O Compilador C da CCS (Custom Computer Services) talvez seja um dos mais fáceis de ser usado para gerar código c embarcado (ou embutido) para microcontroladores PIC. Talvez, também, seja um dos mais odiados por profissionais que o acusam de gerar código grande, lento, frente à outros como o HI-tech C, IAR ou Microchip C18, este para linha PIC18. Bem, o fato é a CCS tem melhorado seu produto. Então, resolvi postar aqui alguns recursos que a CCS diz serem exclusivos de seu compilador, conforme menciona a página do produto no site da empresa (Features Exclusive to CCS Compiler). Quem sabe depois disso, o compilador CCS deixe, ao menos um pouco, de ser tão criticado.


Recursos Exclusivos do Compilador C CCS (para PIC) (Exclusive Features to CCS C Compiler)




Constantes strings de comprimento variável

Examine esta ineficiente estrutura de constantes strings:

const char strings[3][15] =
{
   "HELLO",
   "WORLD",
   "EXTRALONGERWORD"
};
No exemplo acima, foi deixado o comprimento máximo de cada string em 15 caracteres, isto devido à string "EXTRALONGERWORD". Mas, um vez que as strings "HELLO" e "WORLD" possuem somente 6 caracteres cada (não devemos esquecer do caractere de terminação null), 9 bytes são perdidos para cada um destas strings.
Para amenizar este problema, use o método para constantes strings de comprimento variável:

const char strings[][*] =
{
   "HELLO",
   "WORLD",
   "EXTRALONGERWORD"
};
Obs.: Isto é possível devido à adição de inteligência na indexação de tabela de dados constantes. Devido à isto, não é possível criar ponteiros para strings constantes de comprimento variável.




Maior flexibilidade na manipulação de constantes

O compilador adiciona ponteiros para constantes:

/*
um simples exemplo mostrando a atribuição de ponteiro para constante com o endereço de uma string constante:
*/ 

const char version[] = "PRODUCT ID V1.01";
const char *ptr;

ptr = &version[0];

/*
Um exemplo mais complexo que cria um estrutura de ponteiros para strings constantes:
*/ 

const char *strings[] =
{
   "HELLO",
   "WORLD",
   "CONST",
   "STRINGS"
};

/*
Acessando os ponteiros para constantes acima:
*/ 

const char *ptr;
while (i = 0; i < (sizeof(strings) / sizeof(const char *)); i++)
{
   ptr = strings[i];
   printf("%s", ptr);
}



Em adição, strings constantes podem ser passadas para funções que  normalmente recebem ponteiros para caracteres:

/*
A diretiva a seguir habilita o recurso do compilador para copiar strings constantes dentro da RAM quando estas são passadas como parâmetros a uma função:
*/ 

#device PASS_STRINGS=IN_RAM

/*
um exemplo do uso deste novo recurso:
*/ 

if (stricmp(buffer,"ATDT\r")==0)
{
   //faz alguma coisa
}

Note que o qualificador const sempre significa que o dado será colocado na memória de programa e, portanto, este dado é "somente leitura". Isto não segue a definição ANSI que estabelece simplesmente que o qualificador const apenas significa ser "somente leitura".




Diretiva #USE I2C()

Experimente a poderosa biblioteca I²C (Inter-Integrated Circuit - Circuito Inter-Integrado) incluída no compilador. Primeiro, você pode atribuir à suas portas I²C diferentes identificadores de stream (fluxo de dados). Atribuindo diferentes identificadores de stream aos canais I²C, fica fácil diferenciar no código qual porta está sendo usada.
Segundo, a função i2c_start() pode enviar um sinal de início (I²C start) ou um sinal de reinício (I²C restart). O parâmetro de reinício para a função i2c_start() pode ser configurado para um valor "2" para forçar um reinício ao invés de um início. Um valor "1" fará um início normal. Se o reinício não é especificado ou é "0", então o reinício será feito somente se o compilador encontrou por último um i2c_start() e não um i2c_stop().

/*
Isto configura duas portas I²C, cada qual com seu identificador de stream diferente.
*/ 

#use i2c(sda=PIN_C4, scl=PIN_C3, stream=I2C_HW)
#use i2c(sda=PIN_B1, scl=PIN_B2, stream=I2C_SW)

/*
A função seguinte lê um byte de dado da EEPROM I²C 24LC16 da Microchip, usando a porta identificada como I2C_HW:
*/ 

int Read2416(int address)
{
   i2c_start(I2C_HW, 1); //perform a start
   i2c_write(I2C_HW, 0xA0);
   i2c_write(I2C_HW, address);
   i2c_start(I2C_HW, 2); //perform a restart
   i2c_write(I2C_HW, 0xA1);
   data=i2c_read(IC2_HW, 0);
   i2c_stop(I2C_HW);
   return(data);
}




Diretiva #USE SPI()


Algumas das bibliotecas mais poderosas do CCS são as que dão suporte à comunicação serial com protocolos RS-232 e I²C, as quais dão ao programador a flexibilidade de usar múltiplas portas RS-232 e I²C ao mesmo tempo que estiver usando qualquer conjunto de pinos de I/O de uso geral, sem restringir o uso destes pinos somente aos periféricos. Bibliotecas SPI (Serial Peripheral Interface - Interface Serial para Periféricos) estão incluídas no CCS e permitem ao usuário o uso de qualquer pino I/O de uso geral, configuração de clock, qualquer número de bits de dados, streams, freqüência de clock e mais.
/*
O
#use SPI configura a porta SPI. Aqui tem-se um configuração simples:
*/ 
#use SPI(
   DO = PIN_B0,
   DI = PIN_B1,
   CLK = PIN_B2,
   baud = 100000,
   BITS = 8,
   LSB_FIRST,
   SAMPLE_RISE,
   stream = SPI_PORT0
)

/*
Lendo um byte de dados, no caso, de uma memória EEPROM 9356 usando o novo stream SPI:
*/ 

void Read9356(long address, int data)
{
   output_high(EEPROM_9356_SELECT);
   SPI_XFER(SPI_PORT0, 0x18);
   SPI_XFER(SPI_PORT0, address);
   data=SPI_XFER(SPI_PORT0, 0);
   output_low(EEPROM_9356_SELECT);
}



Diretiva #USE RS232()

A biblioteca para RS-232 do compilador, incluí as seguintes opções:


  • Dois parâmetros adicionais para #use rs232(): UART1 e UART2. Escolhendo um destes parâmetros, os pinos de transmissão e recepção da biblioteca RS-232 do CCS serão automaticamente configurados para os pinos de transmissão e recepção do hardware MSSP do microcontrolador PIC.
  • Um parâmetro de timeout (tempo excedido) será incluído, atribuindo à função getc() um timeout de um determinado tempo em milissegundos.
  • Um parâmetro de clock permite que se possa configurar o clock do sistema, ao invés de usar o clock especificado pela diretiva #use delay. Quando este parâmetro não é especificado, será utilizado o clock especificado na diretiva #use delay.
  • O número de stop bits (bits de parada) pode ser definido.
  • O RS-232 pode ser utilizado no modo síncrono master (mestre) ou síncrono slave (escravo).
  • A opção baud rate (taxa de transmissão em bytes por segundo) suporta vírgulas, períodos, e os seguintes prefixos: K, KHZ, MHZ. Por ex., agora são válidos: BAUD=9.6k, BUAD=115,200, BAUD=115,2k, etc.


Configuração Automática dos fusíveis (#fuses)

O compilador configura automaticamente algumas configurações de bits (#fuses) baseado no código:


  • Por padrão, a opção (fusível) NOLVP será configurada (desligando a programação em baixa tensão - low voltage programming).
  • Por padrão, a opção (fusível) PUT será configurada (ligando o power-up timer - temporizador do estado de reset que quando ligado, mantém o PIC em reset por cerca de 72ms após este ter sido ligado).
  • Se não existe a função restart_wdt() no código, a opção NOWDT será configurada. Caso exista a função restart_wdt() no código, então a opção WDT será configurada.
  • Os bits de configuração do oscilador serão configurados com base na diretiva #use delay (ver próxima seção).
  • Se o debugger está habilitado na IDE PCW, a opção DEBUG será configurada.
Com as opções básicas dos fusíveis sendo configurados automaticamente muitos programas não precisaram de utilizar a diretiva #fuses.
Este recurso pode ser desabilitado usando a compatibilidade retroativa CCS3 (veja a seção CCS backwards compatibility no site da CCS).



Addressmod: capacidade para criar espaços definidos pelo usuário em qualquer tipo de memória (interna ou externa)
Parte do padrão IEEE Embedded C standard (ISO/IEC TR 18037), o qualificador addressmod permite o uso de identificadores personalizados que servem para criação de varáveis em qualquer tipo de memória (interna ou externa). O identificador assim criado, pode ser usado com tipo de dados, incluindo estruturas, uniões, vetores, e ponteiros. Verifique o exemplo a seguir que usa o addressmod para criar variáveis na ram externa:
/*
A sintaxe para o addressmod é:
addressmod (identifier,read,write,start,end)
   identifier - o nome de seu identificador personalizado
   read/write - as funções de leitura e escrita para acesso à memória externa
   start/end - a faixa de endereços que este identificador pode acessar
*/ 

addressmod(extram, readextram, writeextram, 0, 0xFFFF)

/*
Cria um vetor extenso; o conteúdo real deste vetor será armazenado na memória externa.
*/

extram largeBuffer[2000]

/*
Cria um ponteiro para a memória externa. O ponteiro em si será armazenado na memória do microcontrolador.
*/ 

extram char *extramPtr;

/*
Alguns exemplos de uso.
*/ 

//acesso direto
largeBuffer[0] = 5;

//atribuição do endereço ao ponteiro
extramPtr = &largeBuffer[0];

//acessando a memória externa indiretamente
*extramPtr = 5;

Parâmetros Default (padrão)



Este recurso foi tomado emprestado da linguagem C++, sendo incluído no compilador. Parâmetros Default podem ser usados nas funções. Assim, se o parâmetro não é passado à função, o valor padrão  deste é utilizado.
int mygetc(char *c, int n=100)

/*

Esta função espera n milisegundos por um caractere enviado via comunicação RS232. Se um caractere é recebido, este é salvo no ponteiro c e retorna TRUE. Se ocorrer um timeout, um FALSE é retornado.
*/ 
}

//obtém caractere, espera 100ms para timeout
mygetc(&c);

//obtém caractere, espera 200ms para timeout
mygetc(&c, 200);

Número variável de parâmetros



É possível usar funções que possuam número variável de parâmetros. Isto é mais comumente encontrado em funções tais como as das bibliotecas printf e fprintf.
/*
stdarg.h possui as macros e o tipo de dado va_list necessários para se usar o recurso de número variável de parâmetros.
*/ 

#include <stdarg.h>
/*


Uma função com número variável de parâmetros necessita de duas coisas. Primeiro, necessita das reticências (...), o qual precisa ser o último parâmetro da função. As reticências representam a lista de argumentos variável. Segundo, é necessária uma variável a mais antes das reticências (...). Usualmente usa-se esta variável como método para determinar quantas variáveis serão passadas no lugar das reticências.

Aqui, uma função que calcula e retorna a soma de todas as variáveis:
*/ 

int Sum(int count, ...)
{
   //um ponteiro para a lista de argumentos
   va_list al;

   int x, sum=0;

   //inicia a lista de argumentos
   //count é a primeira variável antes das reticências
   va_start(al, count);

   while(count--) {
      //obtém um inteiro da lista
      x = var_arg(al, int);

      sum += x;
   }

   //termina usando a lista
   va_end(al);

   return(sum);
}

/*
Alguns exemplos usando esta nova função:
*/ 

x=Sum(5, 10, 20, 30, 40, 50);
y=Sum(3, a, b, c);

Sobrecarga de funções (overloading)



Tomado emprestado da linguagem C++, este recurso permite ao usuário implementar diversas funções com mesmo nome, porém diferenciando-as em número e tipos de parâmetros.
/*
Aqui tem-se um exemplo de sobrecarga de funções: As duas funções a seguir têm o mesmo nome, mas diferenciam-se quanto ao tipo usado nos parâmetros. O compilador determina o tipo de parâmetro que está sendo passado e chama a função apropriada.
*/ 


void FindSquareRoot(long *n)
{
/*
Esta função encontra a raiz quadrada de uma variável do tipo inteiro longo (do ponteiro), sendo o resultado retornado de volta ao ponteiro.
*/

}

void FindSquareRoot(float *n)
{
/*
Esta função encontra a raiz quadrada de uma variável do tipo inteiro ponto flutuante (do ponteiro), sendo o resultado retornado de volta ao ponteiro.
*/

}

/*
A função FindSquareRoot é chamada. Se a variável é do tipo inteiro longo, a primeira implementação da FindSquareRoot() será usada. Caso seja do tipo ponto flutuante, a segunda implementação é que será usada.
*/

FindSquareRoot(&variable);

Decimal de ponto fixo



Uma recurso poderoso do compilador CCS é a habilidade de representar decimais usando um novo tipo de dado, o decimal de ponto fixo. Decimal de ponto fixo proporciona representação decimal, mas com a velocidade de um inteiro. Isto proporciona um aumento de velocidade fenomenal em relação a operação com ponto flutuante. Isto é realizado através do qualificador: _fixed(x), onde o parâmetro x é o número de dígitos após o ponto decimal que o tipo de dado pode reter.
/*
Cria uma variável de 16bits com uma faixa de 0.00 to 655.35
*/

int16 _fixed(2) dollars;

/*
Atribuindo 1.23 à variável dollars. Internamente, 123 será salvo no int16.
*/

dollars=1.23;

/*
Somando 3.00 à dollars. Internamente, 300 adicionado ao int16.
*/

dollars += 3;

/*
printf exibirá 4.23
*/

printf("%w", dollars);

Nenhum comentário:

Postar um comentário

LinkWithin

Related Posts Plugin for WordPress, Blogger...