19. Funções 19.1. Forma Geral As funções em linguagem C podem ser encaradas como algo similar as sub-rotinas em programas escritos em Assembly, guardadas as suas devidas proporções. Sua forma geral, descrita pelo padrão ANSI C, é mostrada a seguir: {tipo da função} nome_da_função ({parâmetros}) { comandoA; comandoB; .... } Onde: • Tipo da função: especifica o tipo de dado (int, char, float, doble, etc.) que a função irá devolver para o local de onde ela foi chamada. • Nome_da_função: identificador a ser utilizado para referenciar aquela função. Ela passará a ser reconhecida pelo resto do programa por este nome. • Parâmetros: são utilizados para a passagem de valores para que a função possa utilizá-los e efetuar os procedimentos para o qual foi escrita. Veja um exemplo de funções no programa (EXEMPLO-14): //****************************************************************************** // Exemplo de funções // // Alessandro Ferreira da Cunha // Tech Training - Engenharia e Treinamentos // Janeiro 2009 // Extraído do Livro "PIC - Programação em C", do Fábio Pereira // Editora Érica - Página 139 // Built with IAR Embedded Workbench Version: 4.11.2.9 //****************************************************************************** #include <msp430xG46x.h> #include <stdio.h> int soma ( int a , int b) { return a + b; } main ( ) { printf("1 + 1 = %d \n", soma (1,1)); } Página 95 19.1.1. Funções do tipo void Um dos usos de void é declarar explicitamente funções que não devolvem valores. Isso evita seu uso em expressões e ajuda a afastar um mau uso acidental. Antes de poder usar qualquer função como void, você deve declarar seu protótipo. Se isto não for feito, o C assumirá que ela devolve um valor inteiro e, quando o compilador encontrar de fato a função, ela declarará um erro de incompatibilidade. 19.1.2. O comando return O comando return tem dois importantes usos. Primeiro, ele provoca uma saída imediata da função que o contém. Isto é, faz com que a execução do programa retorne ao código que o chamou. Se o comando return for executado na função main, então o programa será encerrado. Segundo, ele pode ser utilizado para devolver um valor a função que o chamou. O primeiro programa (EXEMPLO-1) dá uma demonstração de como as funções podem trocar valores entre si: //****************************************************************************** // Exemplo de variáveis globais e locais // Built with IAR Embedded Workbench Version: 4.11.2.9 //****************************************************************************** #include <msp430xG46x.h> #include <stdio.h> int somatorio; // VARIÁVEL GLOBAL! SERÁ ACESSADA POR TODAS AS FUNÇÕES void soma (int valor) //AO ACESSAR ESTA FUNÇÃO, O PARÂMETRO VALOR RECEBE DADOS DE QUEM O CHAMOU { int conta; // VARIÁVEL LOCAL! SERÁ ACESSADA APENAS PELA FUNÇÃO SOMA somatorio = somatorio + valor; printf("0"); for (conta = 1;(conta<(valor+1));conta++) { printf("+%u",conta); } printf(" = %u\r\n",somatorio); } void main() { WDTCTL = WDTPW+WDTHOLD; // Stop WDT int conta; // VARIÁVEL LOCAL! SERÁ ACESSADA APENAS PELA FUNÇÃO MAIN somatorio = 0; // A VARIÁVEL GLOBAL É INICIALIZADA for (conta=1;conta<20;conta++) { soma(conta); // É CHAMADA A FUNÇÃO SOMA, ONDE É PASSADO O VALOR DE CONTA } } Página 96 19.2. Passagem de parâmetros 19.2.1. Fixo (por valor) Exatamente como vem sendo executado até agora nos programas exemplos. 19.2.2. Variável (por referência) Utilizando ponteiros. Veja o exemplo abaixo (EXEMPLO-15): //****************************************************************************** // Exemplo de funções // // Alessandro Ferreira da Cunha // Tech Training - Engenharia e Treinamentos // Janeiro 2009 // Extraído do Livro "PIC - Programação em C", do Fábio Pereira // Editora Érica - Página 141 // Built with IAR Embedded Workbench Version: 4.11.2.9 //****************************************************************************** #include <msp430xG46x.h> #include <stdio.h> float divide ( float a , float b) { if (!b) return 0; a /= b; return a; } main ( ) { float a , b , c ; a = 7 ; b = 3 ; c = divide ( a , b ); printf("a = %f , b = %f , c = %f \n", a , b , c); } 19.3. Protótipos de função Em programas profissionais, muito extensos, com diversas funções, pode acontecer de determinada função ser chamada e ainda não ter sido definida. Isto irá gerar um erro no compilador. Para evitar que isto ocorra, utiliza-se o protótipo da função, que será uma declaração prévia com o intuito de informar ao compilador que mais adiante no programa esta função será definida. Para isto basta colocar a primeira linha da função, que contem os dados do tipo da função, do nome da função e os parâmetros da função, como pode ser visto abaixo: {tipo da função} nome_da_função ({parâmetros}) Página 97 20. Tipos de dados avançados 20.1. Ponteiros Ponteiros são um dos recursos mais poderosos da linguagem C. Qualquer programa de utilidade prática escrito em C dificilmente dispensará o uso de ponteiros. A tentativa de evitálos implicará quase sempre códigos maiores e de execução mais lenta. Para quem está começando, pode parecer (e algumas vezes é) um tanto difícil. Mas não há outro caminho senão enfrentar a realidade. São muitas as aplicações de ponteiros. A seguir, relação das mais comuns. • • • • • • Acessar endereços de memória que o programa aloca em tempo de execução. Acessar variáveis que não são visíveis em uma função. Manipulação de arrays. Manipulação de strings. Passar o endereço de uma função para outra. Retornar mais de um valor para uma função. Genericamente, um ponteiro é uma variável utilizada para guardar o endereço de outra variável, ou seja, um ponteiro é um apontador de outra variável. Veja um exemplo de ponteiros no programa (EXEMPLO-16): //****************************************************************************** // Exemplo de ponteiro // // Alessandro Ferreira da Cunha // Tech Training - Engenharia e Treinamentos // Janeiro 2009 // Built with IAR Embedded Workbench Version: 4.11.2.9 //****************************************************************************** #include <msp430xG46x.h> #include <stdio.h> int main() { char letra = 's'; int idade = 35; char nome[10] = "samuel"; float peso = 87.8; float altura = 1.82; printf("Exibindo o printf("O valor da printf("O valor da printf("O valor da printf("O valor da printf("O valor da endereço variável variável variável variável variável de memória de variáveis\n\n"); letra é %c e seu endereço é %x\n",letra,&letra); idade é %d e seu endereço é %x\n",idade,&idade); nome é %s e seu endereço é %x\n",nome,&nome); peso é %2.1f e seu endereço é %x\n",peso,&peso); altura é %1.2f e seu endereço é %x\n",altura,&altura); } Para entender exatamente como os ponteiros podem ser utilizados, execute os programas exemplos que vão de (EXEMPLO-18) a (EXEMPLO-23). Página 98 (EXEMPLO-18) //****************************************************************************** // Exemplo de ponteiro // // Alessandro Ferreira da Cunha // Tech Training - Engenharia e Treinamentos // Janeiro 2009 // Built with IAR Embedded Workbench Version: 4.11.2.9 //****************************************************************************** #include <msp430xG46x.h> #include <stdio.h> /* atribuindo valor a uma variável ponteiro */ int main() { int idade = 35; int *ptr_idade; ptr_idade = &idade; /* foi atribuído o endereço da variável idade a variável ponteiro ptr_idade. Observe o uso do operador & que devolve o endereço de memória da variável idade. */ printf("O valor da variável idade é %d\n",idade); printf("O endereço da variável idade é %x\n",&idade); printf("O valor da variável ponteiro ptr_idade é %x\n",ptr_idade); return(0); } (EXEMPLO-19) //****************************************************************************** // Exemplo de ponteiro // // Alessandro Ferreira da Cunha // Tech Training - Engenharia e Treinamentos // Janeiro 2009 // Built with IAR Embedded Workbench Version: 4.11.2.9 //****************************************************************************** #include <msp430xG46x.h> #include <stdio.h> /* atribuindo valor a uma variável ponteiro */ int main() { int idade = 35; int *ptr_idade; ptr_idade = &idade; /* foi atribuído o endereço da variável idade a variável ponteiro ptr_idade. Observe o uso do operador & que devolve o endereço de memória da variável idade. */ printf("O valor da variável idade é %d\n",idade); printf("O endereço da variável idade é %x\n",&idade); printf("O valor da variável ponteiro ptr_idade é %x\n",ptr_idade); printf("O valor apontado por ptr_idade é %d\n",*ptr_idade); /* observe, na linha acima, o uso do operador de indireção ( * ) * para desreferenciar o ponteiro ptr_idade e, assim, exibir * o valor armazenado no endereço de memória apontado por ele. */ return(0); } Página 99 (EXEMPLO-20) //****************************************************************************** // Exemplo de ponteiro // Alessandro Ferreira da Cunha // Tech Training - Engenharia e Treinamentos // Janeiro 2009 // Built with IAR Embedded Workbench Version: 4.11.2.9 //****************************************************************************** #include <msp430xG46x.h> #include <stdio.h> /* Alterando o valor armazenado no endereço apontado * por um ponteiro */ int main() { int numero = 35; int *ptr; ptr = № /* atribuindo o endereço de numero a ptr */ printf("O ponteiro ptr armazena o endereço %x que,\npor sua vez,\ armazena o valor %d\n",ptr,*ptr); *ptr = 25; /* alterando o valor armazenado no endereço * apontado por ptr. Observe que o ponteiro * deve ser desreferenciado.*/ printf("\nAgora o ponteiro ptr armazena o endereço %x que,\npor sua vez,\ armazena o valor %d\n",ptr,*ptr); return(0); } (EXEMPLO-21) //****************************************************************************** // Exemplo de ponteiro // Alessandro Ferreira da Cunha // Tech Training - Engenharia e Treinamentos // Janeiro 2009 // Built with IAR Embedded Workbench Version: 4.11.2.9 //****************************************************************************** #include <msp430xG46x.h> #include <stdio.h> /* ponteiros como parâmteros de função */ /* a função que recebe como argumento o valor * da variável não consegue alterar o valor * deste */ int valor(int a) { a = 35;/* alterando o valor do argumento passado */ } /* a função que recebe como argumento um ponteiro * consegue alterar o valor apontado por este*/ int ponteiro(int *a) { *a = 35; /* alterando o valor do argumento passado */ } int main() { int nr = 26; int *ptr_nr; printf("O valor inicial de nr é %d\n",nr); valor(nr); /* função que recebe o valor. Não consegue alterar este */ printf("Valor de nr após a chamada da função valor = %d\n",nr); ptr_nr = &nr; ponteiro(ptr_nr); /* função que recebe ponteiro. Consegue alterar valor * apontado*/ printf("Valor de nr após a chamada da função ponteiro = %d\n",nr); return(0); } Página 100 (EXEMPLO-22) //****************************************************************************** // Exemplo de ponteiro // // Alessandro Ferreira da Cunha // Tech Training - Engenharia e Treinamentos // Janeiro 2009 // Built with IAR Embedded Workbench Version: 4.11.2.9 //****************************************************************************** #include <msp430xG46x.h> #include <stdio.h> /* visualizando como funciona a aritmética de ponteiros */ int main() { char letra[6] = {'a','e','i','o','u','\0'}; int contador, nr[5] = {30,12,67,13,41}; char *ptr_letra; int *ptr_nr; ptr_letra = letra; ptr_nr = nr; printf("Visualizando como funciona a aritmética de ponteiros\n"); printf("\nmatriz letra = a, e, i, o, u\n"); printf("matriz nr = 30,12,67,13,41\n"); printf("\nVerificando o tamanho dos tipos de dados\n"); printf("tamanho do tipo de dado char = %d\n",sizeof(char)); printf("tamanho do tipo de dado int = %d\n",sizeof(int)); printf("\nPonteiro para letra aponta para %c no endereço %x\n",*ptr_letra,ptr_letra); printf("Ponteiro para nr aponta para %d no endereço %x\n",*ptr_nr,ptr_nr); printf("\nIncrementando os ponteiros\n"); printf("ptr_letra + 3, ptr_nr + 2\n"); ptr_letra += 3; ptr_nr += 2; printf("\nPonteiro para letra agora aponta para %c no endereço %x\n",*ptr_letra,ptr_letra); printf("Ponteiro para nr agora aponta para %d no endereço %x\n",*ptr_nr,ptr_nr); return(0); } Página 101 (EXEMPLO-23) //****************************************************************************** // Exemplo de ponteiro // // Alessandro Ferreira da Cunha // Tech Training - Engenharia e Treinamentos // Janeiro 2009 // Built with IAR Embedded Workbench Version: 4.11.2.9 //****************************************************************************** #include <msp430xG46x.h> #include <stdio.h> /* percorrendo uma matriz de strings com um ponteiro */ int main() { char *dia[] = {"Domingo","Segunda","Terça","Quarta","Quinta","Sexta","Sábado",0}; char **ptr_dia; /* *dia é um ponteiro para uma string e * **ptr_dia é um ponteiro para um ponteiro para uma string */ ptr_dia = dia; /* apontando ptr_dia para o início da matriz dia */ while(*ptr_dia) { printf("%s\n",*ptr_dia); ptr_dia++; } return(0); } /* Quando você declara uma matriz de strings o compilador * não acrescenta um caractere NULL para indicar o final * da matriz como o faz com uma matriz de caracteres (strings). * Por isso você mesmo tem que inserir o caractere NULL para * indicar o final da matriz. * * Foi isso que foi feito ao inserir 0 no final da matriz dia */ 20.2. Matrizes de dados Uma matriz é uma estrutura de dados que pode armazenar vários valores do mesmo tipo. A sintaxe para declarar uma matriz é: TIPO nome_da_matriz [QUANTIDADE]; Onde: • TIPO: é o tipo dos dados que serão armazenados na matriz. Todos os dados colocados na matriz devem ser deste tipo. • NOME: é o nome a ser dado a matriz. Este nome identificará a matriz no código do programa. • QUANTIDADE: é a quantidade máxima de itens a ser armazenados. Página 102 Exemplo: int nr_de_livros [50]; //esta matriz pode armazenar até 50 valores do tipo int float nota [30]; //esta matriz pode armazenar até 30 valores do tipo float Os valores armazenados na matriz são chamados de "elementos da matriz". O primeiro elemento da matriz é indexado como item zero e o último é indexado como QUANTIDADE menos 1. Assim, para nossa matriz nota, mostrada no exemplo acima, o primeiro elemento é nota[0] e o último elemento é nota[29]. Você pode inicializar os elementos de uma matriz na sua declaração usando a sintaxe: int notas [5] = {60,70,35,50,68}; // No exemplo acima o elemento zero da matriz notas receberá o valor 60, o elemento 1 receberá o valor 70, e assim por diante. Para melhorar o entendimento observe o código do exemplo (EXEMPLO-24). Esta matriz pode armazenar até 50 valores do tipo int //****************************************************************************** // Exemplo de matriz // // Alessandro Ferreira da Cunha // Tech Training - Engenharia e Treinamentos // Janeiro 2009 // Built with IAR Embedded Workbench Version: 4.11.2.9 //****************************************************************************** #include <msp430xG46x.h> #include <stdio.h> int main() { int notas[5] = {60,70,35,50,68}; printf("Analisando os elementos da matriz notas\n"); printf("O primeiro elemento tem o valor %d\n",notas[0]); printf("O segundo elemento tem o valor %d\n",notas[1]); printf("O terceiro elemento tem o valor %d\n",notas[2]); printf("O quarto elemento tem o valor %d\n",notas[3]); printf("O quinto e último elemento tem o valor %d\n",notas[4]); return(0); } Página 103 Praticamente o mesmo efeito obtido pelo exemplo anterior pode ser obtido de modo muito mais eficiente pelo próximo programa (EXEMPLO-25): //****************************************************************************** // Exemplo de matriz // // Alessandro Ferreira da Cunha // Tech Training - Engenharia e Treinamentos // Janeiro 2009 // Built with IAR Embedded Workbench Version: 4.11.2.9 //****************************************************************************** #include <msp430xG46x.h> #include <stdio.h> int main() { int notas[5] = {60,70,35,50,68}; int contador; printf("Analisando os elementos da matriz notas\n"); for(contador = 0;contador < 5;contador++) printf("O %do elemento tem o valor %d\n",contador+1,notas[contador]); return(0); } Para entender exatamente como as matrizes podem ser utilizadas, execute os programas (EXEMPLO-26) e (EXEMPLO-27). (EXEMPLO-26) //****************************************************************************** // Exemplo de matriz // // Alessandro Ferreira da Cunha // Tech Training - Engenharia e Treinamentos // Janeiro 2009 // Built with IAR Embedded Workbench Version: 4.11.2.9 //****************************************************************************** #include <msp430xG46x.h> #include <stdio.h> /* visualizando strings como matrizes de caracteres */ int main() { char palavra[7] = "matriz"; int contador; printf("Em C strings são matrizes de caracteres e podem ser manipuladas como tal.\n"); printf("\nA string é %s\n",palavra); printf("\nExibindo cada elemento da matriz palavra\n"); for(contador = 0;contador < 7;contador++) printf("%c\n",palavra[contador]); return(0); } Página 104 (EXEMPLO-27) //****************************************************************************** // Exemplo de matriz // // Alessandro Ferreira da Cunha // Tech Training - Engenharia e Treinamentos // Janeiro 2009 // Built with IAR Embedded Workbench Version: 4.11.2.9 //****************************************************************************** #include <msp430xG46x.h> #include <stdio.h> void exibe(int matriz[],int elementos) { int contador; for(contador = 0;contador < elementos;contador++) printf("O %do elemento tem o valor %d\n",contador+1,matriz[contador]); } int main() { int notas[5] = {60,70,35,50,68}; int contador; printf("Analisando os elementos da matriz notas\n"); exibe(notas,5); return(0); } 20.2.1. Matrizes bi-dimensionais Imagine uma matriz bidimensional como uma tabela de linhas e colunas. Por exemplo, a matriz: pesos [3][5]; Ela pode ser imaginada como o seguinte arranjo de linhas e colunas, formando células: Observe que o primeiro índice ([3]) indica as linhas da matriz e o segundo ([5]) indica as colunas. Como sabemos que [3] varia de zero a 2 e [5] varia de zero a 4, fica fácil determinar os índices de cada posição da matriz: 0,0 0,1 0,2 0,3 0,4 1,0 1,1 1,2 1,3 1,4 2,0 2,1 2,2 2,3 2,4 Página 105 Visto a posição de cada índice vamos preencher nossa matriz pesos com valores: 10 30 45 70 36 86 44 63 82 80 70 61 52 63 74 De tudo que foi exposto acima podemos entender que: pesos pesos pesos pesos [1][3] [0][4] [0][0] [2][4] = = = = 82; 36; 10; 74; Para preencher nossa matriz com os valores mostrados na tabela acima podemos usar uma declaração como: int pesos [3][5] = {{10,30,45,70,36},{86,44,63,82,80},{70,61,52,63,74}}; Podemos manipular os elementos de nossa matriz bidimensional usando duas variáveis e um laço for da mesma maneira que fizemos com as matrizes comuns. Observe o código do programa (EXEMPLO-28): //****************************************************************************** // Exemplo de matriz // // Alessandro Ferreira da Cunha // Tech Training - Engenharia e Treinamentos // Janeiro 2009 // Built with IAR Embedded Workbench Version: 4.11.2.9 //****************************************************************************** #include <msp430xG46x.h> #include <stdio.h> /* manipulando uma matriz bidimensional */ int main() { int pesos[3][5] = {{10,30,45,70,36}, {86,44,63,82,80}, {70,61,52,63,74}}; int linha,coluna; for(linha = 0;linha < 3;linha++) for(coluna = 0;coluna < 5; coluna++) printf("elemento[%d][%d] = %d\n",linha,coluna,pesos[linha][coluna]); return(0); } Página 106 20.2.2. Passando uma matriz bi-dimensional para uma função Uma função que manipula uma matriz bidimensional deve receber a matriz e o número de linhas desta matriz. O número de colunas da matriz também deve estar especificado nesta declaração. Ao chamar a função, deve-se passar a matriz e o número de linhas. Como isto funciona? Veja o programa (EXEMPLO-29): //****************************************************************************** // Exemplo de matriz // // Alessandro Ferreira da Cunha // Tech Training - Engenharia e Treinamentos // Janeiro 2009 // Built with IAR Embedded Workbench Version: 4.11.2.9 //****************************************************************************** #include <msp430xG46x.h> #include <stdio.h> /* manipulando uma matriz bidimensional */ void exibe(int matriz[][5], int linhas) { int linha,coluna; for(linha = 0;linha < 3;linha++) for(coluna = 0;coluna < 5; coluna++) printf("elemento[%d][%d] = %d\n",linha,coluna,matriz[linha][coluna]); } int main() { int pesos[3][5] = {{10,30,45,70,36}, {86,44,63,82,80}, {70,61,52,63,74}}; exibe(pesos,3); return(0); } 20.3. Estruturas de dados As estruturas são utilizadas para agrupar informações relacionadas de tipos de dados diferentes. Digamos que você precisa controlar os seguintes dados relacionados ao estoque de um pequeno estabelecimento comercial: • • • • • • • código nome do produto quantidade estocada valor de compra valor a ser vendido lucro observacões sobre o produto Página 107 Este seria um caso para o uso de estruturas, pois relacionados a cada produto teremos dados do tipo int(código,quantidade), char(nome, observações) e float(valor de compra, valor de venda, lucro). A sintaxe para a declaração (ou criação) de uma estrutura é: struct nome_da_estrutura { tipo campo1; tipo campo1; .... tipo campoN; } Para o caso exemplificado no item anterior poderíamos ter algo como: struct produto { int codigo; char nome[50]; int quantidade; float valor_compra; float valor_venda; float lucro; char obs[200]; } É importante observar que a declaração da estrutura não cria, ainda, uma variável. A declaração da estrutura apenas cria um novo tipo de dado. Após criar a estrutura você pode declarar variáveis do tipo de estrutura criado. 20.3.1. Declarando variáveis do tipo de uma estrutura criada Após a declaração da estrutura você pode declarar variáveis do tipo da estrutura com a sintaxe: struct nome_da_estrutura nome_da_variável struct produto item Página 108 Observe que esta sintaxe obedece a sintaxe normal para a declaração de variáveis: tipo nome_da_variável sendo que o TIPO da variável, a nova estrutura criada. Você também pode declarar a variável logo após a declaração da estrutura com uma sintaxe do tipo: struct produto { int codigo; char nome[50]; int quantidade; float valor_compra; float valor_venda; float lucro; char obs[200]; } item; 20.3.2. Acessando os campos de uma estrutura A sintaxe para acessar e manipular campos de estruturas é a seguinte: nome_da_estrutura.campo Observe o código do programa (EXEMPLO-30), mostrado a seguir, para um melhor esclarecimento: Página 109 //****************************************************************************** // Exemplo de struct // // Alessandro Ferreira da Cunha // Tech Training - Engenharia e Treinamentos // Janeiro 2009 // Built with IAR Embedded Workbench Version: 4.11.2.9 //****************************************************************************** #include <msp430xG46x.h> #include <stdio.h> /* acessando os campos de uma estrutura */ /* criando um novo tipo de dado "produto" */ struct produto { int codigo; char nome[50]; int quantidade; float valor_compra; float valor_venda; }; int main() { struct produto item; /* declarando uma variável "item" do tipo "struct produto" */ printf("Preenchendo a variável \"item\"\n"); printf("Item............:\n"); fgets(item.nome,50,stdin); // este commando apenas funciona com a FULLDLIB printf("Código..........:\n"); scanf("%d",&item.codigo); printf("Quantidade......:\n"); scanf("%d",&item.quantidade); printf("Valor de compra.:\n"); scanf("%f",&item.valor_compra); printf("Valor de revenda:\n"); scanf("%f",&item.valor_venda); printf("\n"); printf("Exibindo os dados\n"); printf("Código..........:%d\n",item.codigo); printf("Item............:%s\n",item.nome); printf("Quantidade......:%d\n",item.quantidade); printf("Valor de compra.:%.2f\n",item.valor_compra); printf("Valor de revenda:%.2f\n",item.valor_venda); return(0); } 20.3.3. Acessando uma estrutura com ponteiros Para acessar uma estrutura usando ponteiros você pode usar duas sintaxes: (*nome_da_estrutura).campo nome_da_estrutura -> campo Observe o código do programa (EXEMPLO-31), mostrado a seguir, para um melhor esclarecimento: Página 110 //****************************************************************************** // Exemplo de struct // // Alessandro Ferreira da Cunha // Tech Training - Engenharia e Treinamentos // Janeiro 2009 // Built with IAR Embedded Workbench Version: 4.11.2.9 //****************************************************************************** #include <msp430xG46x.h> #include <stdio.h> /* acessando uma estrutura com ponteiros */ struct registro { char nome[30]; int idade; }; altera_estrutura1(struct registro *ficha) { (*ficha).idade -= 10; } altera_estrutura2(struct registro *ficha) { ficha->idade += 20; } int main() { struct registro ficha; printf("Entre com seu nome:\n"); fgets(ficha.nome,30,stdin); // este commando apenas funciona com a FULLDLIB printf("Qual sua idade?\n"); scanf("%d",&ficha.idade); printf("\nExibindo os dados iniciais\n"); printf("Nome: %s \n",ficha.nome); printf("Idade: %d.\n",ficha.idade); altera_estrutura1(&ficha); printf("\nExibindo os dados após a primeira alteração\n"); printf("Nome: %s \n",ficha.nome); printf("Idade: %d.\n",ficha.idade); altera_estrutura2(&ficha); printf("\nExibindo os dados após a segunda alteração\n"); printf("Nome: %s \n",ficha.nome); printf("Idade: %d.\n",ficha.idade); return(0); } Página 111 20.4. Uniões Em C uma union é uma posição de memória que é compartilhada por duas ou mais variáveis diferentes, geralmente de tipo de diferentes, em momentos diferentes. A definição de uma union é semelhante à definição de estrutura. Sua forma geral é: union identificador { tipo nome_da_variável; tipo nome_da_variável; tipo nome_da_variável; ... } variáveis_união; Exemplo: union teste { int i; char ch; }; Página 112 21. EXERCÍCIO: Tipos de dados avançados 21.1. Armazenamento e tratamento de dados (EXERCÍCIO-13) Já foi identificado no diagrama elétrico da Experimenter Board, mostrado no item 9, as seguintes conexões, envolvendo pinos do MSP430FG4618 e hardwares externos: a) Botão S1 Æ pino P1.0; b) Botão S2 Æ pino P1.1; c) LED1 Æ pino P2.1; d) LED2 Æ pino P2.2; e) LED4 Æpino P5.1. Com estas informações escreva um programa que execute o algoritmo mostrado abaixo, de modo que o programa fique em Low Power Mode e apenas saia deste estado para executar ações, fazendo economia de energia e realizando as atividades pedidas. 1. Aguardar o usuário pressionar o botão S1 para iniciar as atividades. Enquanto o botão não for pressionado, a Experimenter Board deve ficar em LPM3. 2. Ao pressionar o botão S1 deve ser aceso o LED1 e o terminal I/O deve mostrar a mensagem: “Insira o dado 01:” E o programa deve ficar aguardando, com o LED1 aceso, o usuário inserir, via terminal I/O, um número entre 0 e 65535. 3. Ao término da digitação, o programa deve apagar o LED1, acender o LED2 e aguardar o usuário pressionar o botão S2. Enquanto o usuário não pressionar o botão S2, a Experimenter Board deve ficar em LPM3. 4. Ao pressionar o botão S2 deve ser aceso o LED1 e o terminal I/O deve mostrar a mensagem: “Insira o dado 02:” E o programa deve ficar aguardando, com o LED1 aceso, o usuário inserir, via terminal I/O, um número entre 0 e 65535. 5. O ciclo composto pelos passos 2, 3 e 4 deve se repetir até que o usuário entre com o vigésimo dado. 6. Ao final da entrada do vigésimo dado o LED4 deve ficar aceso e a Experimenter Board deve ficar em LPM3 enquanto o usuário não pressionar o botão S1. Página 113 7. Ao pressionar o botão S1, o LED4 deve ser apagado, o LED1 deve ser aceso e o terminal I/O deve mostrar a mensagem: “O dado 01 é XX” Onde XX é exatamente o mesmo valor que foi inserido pelo usuário no passo 2. 8. Um segundo após mostrar a mensagem, o LED1 deve ser apagado, o LED4 aceso e a Experimenter Board deve ficar em LPM3 enquanto o usuário não pressionar o botão S1. 9. Ao pressionar o botão S1, o LED4 deve ser apagado, o LED1 deve ser aceso e o terminal I/O deve mostrar a mensagem: “O dado 02 é YY” Onde YY é exatamente o mesmo valor que foi inserido pelo usuário no passo 4. 10. Um segundo após mostrar a mensagem, o LED1 deve ser apagado, o LED4 aceso e a Experimenter Board deve ficar em LPM3 enquanto o usuário não pressionar o botão S1. 11. O ciclo composto pelos passos 7, 8, 9 e 10 deve se repetir até que seja mostrado no terminal I/O o último dado armazenado. 12. Ao final da amostragem do vigésimo dado o programa deve retornar ao passo 1, e recomeçar tudo novamente. Página 114 22. REAL TIME CLOCK Este periférico presente nos chips da família 4 é composto de registradores de propósito geral que podem ser configurados como um timer de 32 bits ou como um relógio de tempo real. Dentre as funcionalidades deste módulo estão: • Modos de funcionamento como relógio e calendário • Contador de 32 bits com fonte de clock de contagem selecionável • Quando configurado no modo calendário é feito o incremento automático dos segundos, minutos, horas, dias da semana, dia do mês, mês e ano • Capacidade de gerar interrupções • Capacidade de trabalhar em BCD O diagrama deste módulo é mostrado na figura abaixo: Todos os valores dos registradores que pertencem ao módulo RTC tem valor inicial em estado X. Isto significa que o programador deve inicializar cada um deles adequadamente. Página 115 Os modos de funcionamento são ajustáveis pelo registrador RTCCTL (Real Time Clock Control Register), mostrado a seguir: Página 116 22.1. Operação como contador Os bits RTCMODEx com qualquer valor diferente de 11 fazem o módulo RTC operar como um contador de 32 bits, totalmente acessível via software. Caso haja uma troca do modo de operação de contador para RTC todos os registradores com valores serão resetados. Quatro resitradores de 8 bits são cascateados (RTCNTx), formando um contador de 32 bits. Com esta arquitetura é possível obter interrupções quando ocorre um estourou em qualquer um destes quatro registradores (em 8 bits, 16 bits, 24 bits ou 32 bits). Qualquer um destes registradores são totalmente acessíveis para escrita ou leitura. 22.2. Operação como calendário Quando os bits RTCMODEx são ajustados com 11 módulo RTC opera como calendário. Neste modo de operação o RTC fornecerá segundos, minutos, horas, dia da semana, dia do mês, mês e ano. Estas informações podem ser fornecidas em formato hexadecimal ou em BCD. Caso haja uma troca do modo de operação de RTC para contador faz com que os registradores dos com valores segundos, minutos, horas, dia da semana e ano sejam restados. Já os registradores com os valores do dia do mês e do mês serão receberão os valor 1. O algoritmo interno deste módulo permite ajuste de data em qualquer dia entre os anos de 1901 e 2099, incluindo a contagem dos anos bissextos. 32 bits, totalmente acessível via software. Caso haja uma troca do modo de operação de contador para RTC ou vice-versa, todos os registradores com valores serão resetados. 22.3. Interação entre o RTC e o Basic Timer 1 Quando o RTC está ajustado para o modo de calendário, o Basic Timer 1 é automaticamente configurado como um divisor prévio para o RTC, com os dois registradores de 8 bits do Basic Timer cascateados e o ACLK selecionado como fonte de clock do Basic Timer. Os ajustes que tenham sido feitos nos bits BTSSEL, BTHOLD and BTDIV do Basic Timer serão ignorados. O bit RTCHOLD do módulo RTC passará a controlar os dois periféricos simultaneamente (RTC e BT1). Página 117 22.4. As interrupções do RTC O módulo RTC utiliza duas fontes de controle de interrupção: • BT1IF • RTCIE O RTC compartilha as flags com o BT1. Quando RTCIE = 0, o BT1 passará a controlar a interrupção através do bit BTIPx. Neste caso, os bits RTCTEVx serão responsáveis por selecionar o intervalo em que será setado o bit RTCIF, mas este bit, mesmo quando levado para nível lógico 1, não irá gerar um evento de interrupção. Mesmo assim ainda haverá a necessidade de que o software apague o bit RTCIF para que haja um novo registro de que aconteceu mais uma interrupção. Quando RTCIE = 1, o RTC passará a controlar a interrupção e os bits BTIPx serão ignorados. Neste caso, os bits RTCFG e BT1FG serão setados a cada intervalo de tempo que foi previamente ajustado pelos bits RTCEVx. Um evento de interrupção será gerado automaticamente cada vez que um destes dois bits sejam setados, desde que o bit GIE já esteja ajustado. Ao entrar na rotina de interrupção estes dois bits serão resetados automaticamente, não necessitando de intervenção do programador. Mesmo assim ainda haverá a necessidade de que o software apague o bit RTCIF para que haja um novo registro de que aconteceu mais uma interrupção. 22.5. Todos os registradores do RTC A listagem de todos os registradores utilizados no RTC são mostrados na figura a seguir. Página 118 Página 119 Observe o código do programa (EXEMPLO-31), mostrado a seguir, para um melhor esclarecimento: //****************************************************************************** // MSP430xG461x Demo - Real Time Clock, Toggle P5.1 Inside ISR, 32kHz ACLK // // Description: This program toggles P5.1 by xor'ing P5.1 inside of // a Real Time Clock ISR. The Real Time Clock ISR is called once a minute using // the Alarm function provided by the RTC. ACLK used to clock basic timer. // ACLK = LFXT1 = 32768Hz, MCLK = SMCLK = default DCO = 32 x ACLK = 1048576Hz // //* An external watch crystal between XIN & XOUT is required for ACLK *// // // MSP430FG4619 // ----------------// /|\| XIN|// | | | 32kHz // --|RST XOUT|// | | // | P5.1|-->LED // // S.Schauer / A. Dannenberg // Texas Instruments Inc. // June 2007 // Built with IAR Embedded Workbench Version: 3.42A //****************************************************************************** #include <msp430xG46x.h> //-----------------------------------------------------------------------------void main(void) { WDTCTL = WDTPW + WDTHOLD; // Stop Watchdog Timer FLL_CTL0 = XCAP14PF; // Configure load caps RTCCTL = RTCBCD+RTCHOLD+RTCMODE_3+RTCTEV_0+RTCIE; // RTC enable, BCD mode, // alarm every Minute, // enable RTC interrupt // Init time RTCSEC = 0x00; // Set Seconds RTCMIN = 0x00; // Set Minutes RTCHOUR = 0x08; // Set Hours // Init date RTCDOW = 0x02; RTCDAY = 0x23; RTCMON = 0x08; RTCYEAR = 0x2005; // // // // RTCCTL &= ~RTCHOLD; // Enable RTC P5DIR |= 0x02; // Set P5.1 to output direction __bis_SR_register(LPM3_bits + GIE); // Enter LPM3 w/ interrupt Set Set Set Set DOW Day Month Year } // Basic Timer interrupt service routine #pragma vector=BASICTIMER_VECTOR __interrupt void basic_timer(void) { P5OUT ^= 0x02; // Toggle P5.1 using exclusive-OR } Página 120 23. EXERCÍCIO: Relógio digital 23.1. Relógio Digital com RTC (EXERCÍCIO-14) Já foi identificado no diagrama elétrico da Experimenter Board, mostrado no item 9, as seguintes conexões, envolvendo pinos do MSP430FG4618 e hardwares externos: a) Botão S1 Æ pino P1.0; b) Botão S2 Æ pino P1.1; c) LED1 Æ pino P2.1; d) LED2 Æ pino P2.2; e) LED4 Æpino P5.1. Com estas informações escreva um programa que execute o algoritmo mostrado abaixo, de modo que o programa fique em Low Power Mode e apenas saia deste estado para executar ações, fazendo economia de energia e realizando as atividades pedidas. 1. Aguardar o usuário pressionar o botão S1 para iniciar as atividades. Enquanto o botão não for pressionado, a Experimenter Board deve ficar em LPM3. 2. Ao pressionar o botão S1 deve ser aceso o LED1 e o terminal I/O deve mostrar a mensagem: “Insira o ano:” E o programa deve ficar aguardando, com o LED1 aceso, o usuário inserir, via terminal I/O, o ano atual. 3. Ao término da digitação, o programa deve apagar o LED1, acender o LED2 e aguardar o usuário pressionar o botão S2. Enquanto o usuário não pressionar o botão S2, a Experimenter Board deve ficar em LPM3. 4. Ao pressionar o botão S2 deve ser aceso o LED1 e o terminal I/O deve mostrar a mensagem: “Insira o mês:” E o programa deve ficar aguardando, com o LED1 aceso, o usuário inserir, via terminal I/O, o mês atual. 5. O ciclo composto pelos passos 2, 3 e 4 deve se repetir até que o usuário entre com o o ano, mês, dia da semana, dia do mês, hora, minuto e segundo atual. 6. Ao terminar este processo o LED4 deve ficar aceso e a Experimenter Board deve ficar em LPM3 enquanto o usuário não pressionar o botão S1. Página 121 7. Ao pressionar o botão S1, o LED4 deve ser apagado, o LED1 deve ser aceso e o terminal I/O deve mostrar a mensagem: “Hora do alarme:” E o programa deve ficar aguardando, com o LED1 aceso, o usuário inserir, via terminal I/O, a hora do alarme. 8. Ao término da digitação, o programa deve apagar o LED1, acender o LED4 e aguardar o usuário pressionar o botão S1. Enquanto o usuário não pressionar o botão S1, a Experimenter Board deve ficar em LPM3. 9. Ao pressionar o botão S1, o LED4 deve ser apagado, o LED1 deve ser aceso e o terminal I/O deve mostrar a mensagem: “Minuto do alarme:” E o programa deve ficar aguardando, com o LED1 aceso, o usuário inserir, via terminal I/O, a hora do alarme. 10. Ao término da digitação, o programa deve apagar o LED1, acender o LED4 e aguardar o usuário pressionar o botão S1. Enquanto o usuário não pressionar o botão S1, a Experimenter Board deve ficar em LPM3. 11. Ao pressionar o botão S1, o LED4 deve ser apagado, o LED1 deve ser aceso e o terminal I/O deve mostrar a mensagem: “Segundo do alarme:” E o programa deve ficar aguardando, com o LED1 aceso, o usuário inserir, via terminal I/O, a hora do alarme. 12. Ao término da digitação, o programa deve passar a piscar rapidamente os três LEDs da placa (LED1, LED2 e LED4) a cada segundo computado pelo RTC. Quando o RTC atingir o horário estipulado pelo alarme, o buzzer deve soar por um minuto e o programa deve voltar ao passo inicial. Durante todo este processo a Experimenter Board deve ficar em LPM3 na maior parte do tempo possível. Página 122 24. CONTROLADOR DE DISPLAY DE LCD O hardware LCD_A tem a capacidade de alimentar diretamente displays de LCD através da criação de sinais AC por segmento e tensões comuns automaticamente. Suas principais características são: • • • • • • • Uma memória onde são armazenados os dados a serem exibidos no display; Geração automática dos sinais necessários ao funcionamento do display; Freqüência de exibição de quadro configurável; Possibilidade de piscar o display independentemente do sinal de clock; Fonte de alimentação regulada; Controle de contraste via software; Suporte a quatro tipos de displays de LCD: o Estático; o 2-mux, ½ bias ou ⅓ bias o 3-mux, ½ bias ou ⅓ bias o 4-mux, ½ bias ou ⅓ bias. O diagrama em blocos deste controlador pode ser visto na figura a seguir: 24.1. Memória para o LCD No mapeamento de memória do MSP 430 existe um trecho dedicado ao funcionamento do controlador de LCD, conforme mostrado na figura a seguir. Cada um dos bits deste mapeamento de memória corresponde a um segmento do display de LCD, o que pode ser utilizado ou não, dependendo do modelo do LCD e do dispositivo utilizado. Acionar um determinado segmento do display LCD corresponde a colocar um bit em nível lógico 1. 24.2. Piscando o LCD O controlador de LCD permite que todos os segmentos ativos pisquem, sem a necessidade de modificação da taxa de tempo. Isto é feito através do bit LCDSON. Quando LCDSON = 1, cada segmento que está ativo permanece ligado. Os segmentos que não estão ativos permanecem desligados. Quando LCDSON = 0, todos os do LCD são desligados, independentemente de seu estado ativo ou não. Página 123 Página 124 24.3. Controle de tensão e de geração de sinal de offset O módulo LCD permite selecionar qual será a fonte para gerar o sinal de tensão de saída que é aplicada aos planos de fundo (back planes) do display (chamada de tensão V1), bem como a fração desta tensão que será utilizada como offset (de V2 a V5). Isto possibilita que o LCD seja alimentado através do AVCC, de um gerador interno ou de uma fonte externa ao microcontrolador. Todas as fontes de alimentação internas são desligadas quando o ACLK está desligado (OSCOFF = 1) ou quando o módulo LCD_A é desligado (LCDON = 0). 24.3.1. Selecionando as fontes de tensão Para determinar qual será a fonte de tensão a ser utilizada pelo módulo é necessário atuar sobre dois registradores de controle vistos a seguir: LCDAVCTL0 e LCDAVCTL0. Eles atuam sob o hardware de tensão mostrado a seguir. Página 125 Página 126 24.3.2. Controle de contraste. A tensão de saída em conjunto com o modo de operação e o offset determinam qual será o contraste a ser utilizado no LCD. Assim, um ajuste de contraste via software necessitará que seja feito um ajuste no gerador de voltagem, atuando sobre os bits VLCDx, presentes no registrador LCDAVCTL0. A taxa de contraste dependerá display LCD utilizado e do offset escolhido. Um exemplo desta configuração é mostrada na tabela abaixo. Página 127 24.4. Freqüência de operação do LCD_A O módulo LCD_A utiliza o sinal fLCD (proveniente do Basic Timer), que deve ser previamente ajustado a partir do ACLK para gerar a base de tempo necessária ao funcionamento dos displays de LCD. O valor da freqüência a ser ajustada é controlada através dos bits LCDFREQx, presentes no registrador LCDACTL. O valor correto depende do tipo de LCD que se está utilizando. Então uma consulta ao manual do fabricante do LCD se faz necessário. A taxa de quadros que serão amostrados no display quando este fizer uso de vários back planes, e tiver a necessidade de ser multiplexado, é calculada pela expressão abaixo: f LCD = 2 ⋅ mux ⋅ f FRAME Por exemplo: um LCD com três back planes, que necessita de um mux de três vias, com uma freqüência de quadro entre 30 Hz e 100 Hz: f LCD = 2 ⋅ 3 ⋅ f FRAME f LCDMÍNIMO = 180Hz ⇔ f LCDMÁXIMO = 600Hz Isto faz com que o microcontrolador seja ajustado para as seguintes opções: f LCD 32768 Hz = = 256 Hz 128 f LCD = f LCD = 32768 Hz = 341Hz 96 32768 Hz = 512 Hz 64 Página 128 24.5. Saídas do módulo LCD Alguns dos segmentos do display de LCD são multiplexados com funções de I/O digital. Então estes pinos podem ter a função de I/O ou de LCD. Em que momento cada pino deste será ajustado para qual função dependerá exclusivamente do ajuste feito através do registrador PxSELx aplicável a porta onde o LCD está conectado. Página 129 24.6. Modo estático (um único back plane) Neste modo cada pino de segmento do driver do MSP430 controla apenas um segmento de LCD e apenas a linha comum COM0 é utilizada. Um exemplo dos sinais gerados seguir e da conexão a ser feita pode ser vista nas figuras abaixo. Página 130 24.7. Modo 2-Mux (dois back planes) Página 131 24.8. Modo 3-Mux (três back planes) Página 132 24.9. Modo 4-Mux (quatro back planes) Página 133 24.10. Outros registradores que controlam o LCD Página 134 25. EXERCÍCIO: Configurar e utilizar o display de LCD da Experimenter Board. O Primeiro passo para configurar e utilizar o display de LCD presente na Experimenter Board é buscar o datasheet do mesmo, o que pode ser feito através do site do fabricante. www.softbaugh.com O modelo na placa é o SBLCDA4. O segundo passo é verificar como os terminais do display estão conectados na placa, o que é feito no próximo item. Página 135 25.1. O display LCD da Experimenter Board A conexão entre os pinos do Display SoftBaugh SBLCDA4 e o MSP430FG4618 são mostrados nas duas figuras a seguir. A seguir são mostrados todos os segmentos deste display e a suas respectivas numerações. Página 136 Página 137 25.2. (EXERCÍCIO-15) Æ Mapear a memória do MSP430 Preencha o mapeamento de memória do MSP430 mostrado abaixo adequadamente. Para isto você deve indicar onde cada bit da memória representa cada um dos segmentos do display SBLCDA4 da SoftBaugh. Página 138 25.3. (EXERCÍCIO-16) Æ Relógio Digital com display de LCD Aproveite o exercício 5, que foi desenvolvido para o RTC, e faça com que as mensagens que são enviadas ao Terminal I/O, para entrada e saída de dados, sejam mostradas no Display de LCD. Para isto, crie imagens que substituam adequadamente as mensagens e que possibilitem ao usuário o entendimento do que se está pedindo para fazer. Não formataremos qual é esta mensagem. Deixaremos que utilizem sua criatividade! 25.4. (EXERCÍCIO-17) Æ Relógio Digital com display de LCD Utilize o exercício anterior e acrescente a seguinte funcionalidade a ele: Para ativar o programa (sair do estado inicial de Low Power Mode), o usuário deve manter pressionado o botão S1 por 30 segundos. Somente após este tempo é que o software deve iniciar os procedimentos de entrada para ajuste do relógio e do despertador. Página 139