• Nenhum resultado encontrado

Mood lamp com controle serial – Análise do código

No documento Arduino Básico Michael McRoberts (páginas 91-99)

Efeitos com LEDs

Projeto 10 Mood lamp com controle serial – Análise do código

Este projeto apresenta vários novos conceitos, incluindo comunicação serial, ponteiros e manipulação de strings.

Primeiramente, você define um array de 18 caracteres (char) para armazenar sua string de texto, que é maior do que o máximo de 16 permitidos, para garantir que você não tenha erros de estouro de buffer.

char buffer[18];

Em seguida, você define os inteiros para armazenar os valores red, green e blue, assim como os valores para os pinos digitais:

int red, green, blue; int RedPin = 11; int GreenPin = 10; int BluePin = 9;

Em sua função de inicialização, você define os três pinos digitais como saídas. Antes disso, porém, temos o comando Serial.begin:

void setup() { Serial.begin(9600); Serial.flush(); pinMode(RedPin, OUTPUT); pinMode(GreenPin, OUTPUT); pinMode(BluePin, OUTPUT); }

Serial.begin diz ao Arduino para iniciar comunicações seriais; o número dentro dos parênteses (nesse caso, 9600) define a taxa de transmissão (de símbolos ou pulsos por segundo) na qual a linha serial se comunicará.

O comando Serial.flush liberará caracteres que estejam na linha serial, deixando-a vazia e pronta para entradas/saídas.

A linha de comunicações seriais é simplesmente uma forma do Arduino se comunicar com o mundo externo, que, nesse caso, ocorre de, e para, o PC e o Serial Monitor do IDE do Arduino.

No loop principal você tem uma instrução if:

if (Serial.available() > 0) {

Essa instrução está utilizando o comando Serial.available para verificar se algum ca-ractere foi enviado pela linha serial. Se caca-racteres já tiverem sido recebidos, a condição é atendida e o código dentro do bloco de instruções if é executado:

if (Serial.available() > 0) { int index=0;

delay(100); // deixe o buffer encher int numChar = Serial.available(); if (numChar>15) { numChar=15; } while (numChar--) { buffer[index++] = Serial.read(); } splitString(buffer); }

Um inteiro, index, é declarado e inicializado como zero. Esse inteiro armazenará a posição de um ponteiro para os caracteres do array char.

Você então define uma espera de 100, para garantir que o buffer serial (local na me-mória em que os dados recebidos são armazenados, antes do processamento) esteja cheio antes de processar os dados. Caso você não faça isso, é possível que a função seja executada e inicie o processamento da string de texto antes de você receber todos os dados. A linha de comunicações seriais é muito lenta quando comparada à veloci-dade de execução do restante do código. Quando você envia uma string de caracteres, a função Serial.available terá imediatamente um valor maior que zero, e a função if

iniciará sua execução. Se você não utilizasse a instrução delay(100), ela poderia iniciar a execução do código da instrução if antes que toda a string tivesse sido recebida, e os dados seriais poderiam ser apenas os primeiros caracteres da string de texto.

Depois de esperar 100ms para que o buffer serial se encha com os dados enviados, você declara e inicializa o inteiro numChar com o número de caracteres da string de texto. Assim, se enviássemos este texto no Serial Monitor

R255, G255, B255

O valor de numChar seria 17. Ele seria 17 e não 16, pois ao final de cada linha de texto há um caractere invisível, o caractere NULL, que informa ao Arduino quando atingimos o final da linha de texto.

A próxima instrução if verifica se o valor de numChar é maior que 15; se for, ela o define como 15. Isso garante que você não estoure o array char buffer[18].

Em seguida, temos um comando while, algo que você ainda não viu, por isso deixe-me explicá-lo.

Sua sintaxe é a seguinte:

while(expression) { // instrução(ões) }

Em seu código, o loop while é:

while (numChar--) {

buffer[index++] = Serial.read(); }

A condição que ele está verificando é numChar. Em outras palavras, o loop verifica se o valor armazenado no inteiro numChar não é zero. Note que numChar tem -- depois dele. Isso é um pós-decremento: o valor é diminuído depois de utilizado. Se você tivesse utilizado --numChar, o valor em numChar seria diminuído (subtraído em 1) antes de ser avaliado. Em nosso caso, o loop while verifica o valor de numChar e, apenas depois, sub-trai 1 dele. Se o valor de numChar for diferente de zero antes do decremento, o código do bloco será executado.

numChar está definida como o comprimento da string de texto que você digitou na janela do Serial Monitor. Assim, o código do loop while executará várias vezes.

O código do loop while é:

buffer[index++] = Serial.read();

Isso define cada elemento do array buffer como os caracteres lidos a partir da linha serial. Em outras palavras, preenche o array buffer com as letras que você digitou na janela de texto do Serial Monitor.

O comando Serial.read() lê dados seriais, um bit por vez. Dessa forma, agora que seu array de caracteres foi preenchido com os caracteres que você digitou no monitor serial, o loop

Depois do loop while, temos

splitString(buffer);

Uma chamada a uma das duas funções que você criou com o nome de splitString(). A função se parece com isso:

void splitString(char* data) { Serial.print("Data entered: "); Serial.println(data);

char* parameter;

parameter = strtok (data, " ,"); while (parameter != NULL) { setLED(parameter);

parameter = strtok (NULL, " ,"); }

// Limpa o texto e os buffers seriais for (int x=0; x<16; x++) {

buffer[x]='\0'; }

Serial.flush(); }

Como ela não retorna nenhum dado, seu tipo de dados foi definido como void. Você passa a ela um parâmetro: uma variável do tipo char, que você chama de data. En-tretanto, nas linguagens de programação C e C++, não é permitido que você envie um array de caracteres a uma função. Você contorna essa limitação utilizando um ponteiro. Você sabe que está utilizando um ponteiro, pois um asterisco foi adicionado ao nome da variável *data.

Ponteiros representam um tópico avançado em C, por isso não entrarei em muitos detalhes sobre eles. Caso você queira saber mais, consulte um livro sobre programação em C. Tudo que você tem de saber por ora é que, declarando data como um ponteiro, ela se torna uma variável que aponta para outra variável.Você pode apontar para o endereço no qual a variável está armazenada na memória utilizando o símbolo &, ou, em nosso caso, apontar para o valor armazenado nesse endereço na memória utilizando o símbolo *. Você utiliza esse recurso para contornar uma limitação, uma vez que, como mencionamos, não é permitido que você envie um array de caracteres a uma função. Entretanto, você pode enviar à sua função um ponteiro que aponte para um array de caracteres. Assim, você declarou uma variável de tipo de dado char, e chamou-a de data, mas o símbolo * antes dela significa que ela está apontando para o valor armazenado na variável buffer.

Ao chamar splitString(), você envia o conteúdo de buffer (mais propriamente, um ponteiro para ele, como acabamos de ver):

Assim, você chamou a função e passou a ela todo o conteúdo do array de caracteres buffer. O primeiro comando é

Serial.print("Data entered: ");

Essa é sua forma de enviar dados do Arduino para o PC. Nesse caso, o comando print

envia o que estiver dentro dos parênteses para o PC, por meio do cabo USB, e você pode ler o resultado na janela do Serial Monitor. Aqui, você enviou as palavras "Data entered: ". Note que o texto deve estar entre aspas. A próxima linha é semelhante:

Serial.println(data);

Novamente, você enviou dados de volta ao PC. Dessa vez, você enviou a variável data, que é uma cópia do conteúdo do array de caracteres buffer que você passou à função. Assim, se a string de texto digitada for

R255 G127 B56

Então o comando

Serial.println(data);

Enviará essa string de texto de volta ao PC, exibindo-a na janela do Serial Monitor. (Certifique-se de que você primeiro habilitou a janela do Serial Monitor.)

Dessa vez, o comando print tem ln ao seu final: println. Isso significa simplesmente “imprima com um avanço de linha”.

Quando você imprime utilizando o comando print, o cursor (o ponto em que o pró-ximo símbolo de texto surgirá) permanece ao final do que você imprimiu. Quando você utiliza o comando println, um comando de avanço de linha é emitido: imprime-se o texto e depois o cursor desce para a linha seguinte.

Serial.print("Data entered: "); Serial.println(data);

Dessa forma, se você analisar os dois comandos print, verá que o primeiro imprime

"Data entered: ", deixando o cursor ao final do texto, enquanto o comando de impres-são seguinte imprime data (o conteúdo do array buffer) e emite um avanço de linha, descendo o cursor para a linha seguinte. Se, depois disso, você utilizar mais uma instrução print ou println, o texto no monitor serial surgirá na linha seguinte. Agora, você cria uma nova variável de tipo char, parameter

char* parameter;

Como você está utilizando essa variável para acessar elementos do array data, ela deve ser do mesmo tipo, daí o símbolo *. Você não pode passar dados de uma variável de determinado tipo de dados para uma de outro tipo; os dados devem ser primeiramen-te convertidos. Essa variável é outro exemplo de variável que primeiramen-tem escopo local e que

pode ser vista apenas pelo código dentro dessa função. Assim, se você tentar acessar a variável parameter fora da função splitString(), receberá um erro.

Depois, você utiliza um comando strtok, muito útil para manipulação de strings de texto. strtok recebe esse nome por causa das palavras String e Token, pois tem como propósito dividir uma string utilizando tokens. Em seu caso, o token que estamos procurando é um espaço ou vírgula; o comando está sendo utilizado para dividir strings de texto em strings menores.

Você passa o array data ao comando strtok como primeiro argumento e os tokens (entre aspas) como segundo argumento. Portanto:

parameter = strtok (data, " ,");

Com isso, strtok divide a string quando encontra um espaço ou uma vírgula. Se sua string de texto for

R127 G56 B98

Então, depois dessa instrução, o valor de parameter será

R127

Pois o comando strtok divide a string na primeira ocorrência de uma vírgula. Depois que você tiver definido a variável parameter como a seção da string de texto que deseja extrair (por exemplo, o trecho até o primeiro espaço ou vírgula), você entra em um loop while com a condição de que parameter não esteja vazia (por exemplo, se você tiver atingido o final da string):

while (parameter != NULL) {

Dentro do loop chamamos nossa segunda função:

setLED(parameter);

(Veremos essa função mais detalhadamente no futuro.) Depois, você define a variável

parameter como a próxima seção da string, até o próximo espaço ou vírgula. Isso é feito passando para strtok um parâmetro NULL, da seguinte maneira:

parameter = strtok (NULL, " ,");

Isso diz ao comando strtok para continuar de onde tinha parado. Assim, todo este trecho da função:

char* parameter;

parameter = strtok (data, " ,"); while (parameter != NULL) { setLED(parameter);

parameter = strtok (NULL, " ,"); }

Está simplesmente removendo cada parte da string de texto, separadas por espaços ou vírgulas, e enviando essas partes à próxima função, setLED().

O trecho final dessa função simplesmente preenche o array buffer com caracteres NULL, o que é feito com o símbolo \0, e então libera os dados do buffer serial, deixando-o pronto para a entrada do próximo conjunto de dados:

// Limpe o texto e os buffers seriais for (int x=0; x<16; x++) {

buffer[x]='\0'; }

Serial.flush();

A função setLED() tomará cada parte da string de texto e definirá o LED correspon-dente com a cor que você escolheu. Assim, se a string de texto digitada por você for

G125 B55

A função splitString() dividirá essa linha em dois componentes separados

G125 B55

E enviará essas strings de texto abreviadas para a função setLED(), que fará a leitura dos dados e decidirá qual LED você escolheu, definindo para ele o valor de brilho correspondente.

Vamos retornar à segunda função, setLED():

void setLED(char* data) {

if ((data[0] == 'r') || (data[0] == 'R')) { int Ans = strtol(data+1, NULL, 10); Ans = constrain(Ans,0,255); analogWrite(RedPin, Ans); Serial.print("Red is set to: "); Serial.println(Ans);

}

if ((data[0] == 'g') || (data[0] == 'G')) { int Ans = strtol(data+1, NULL, 10); Ans = constrain(Ans,0,255); analogWrite(GreenPin, Ans); Serial.print("Green is set to: "); Serial.println(Ans);

}

if ((data[0] == 'b') || (data[0] == 'B')) { int Ans = strtol(data+1, NULL, 10); Ans = constrain(Ans,0,255); analogWrite(BluePin, Ans); Serial.print("Blue is set to: ");

Serial.println(Ans); }

}

Essa função contém três instruções if semelhantes, por isso vamos escolher uma e analisá-la:

if ((data[0] == 'r') || (data[0] == 'R')) { int Ans = strtol(data+1, NULL, 10); Ans = constrain(Ans,0,255); analogWrite(RedPin, Ans); Serial.print("Red is set to: "); Serial.println(Ans);

}

Essa instrução if verifica se o primeiro caractere na string data[0] é a letra r ou R (caracteres maiúsculos ou minúsculos são totalmente diferentes para a linguagem C). Você utiliza o comando lógico OU (o símbolo ||) para verificar se a letra é um r

ou um R, uma vez que as duas opções são válidas.

Se ela é um r ou um R, a instrução if sabe que você deseja alterar o brilho do LED vermelho (red), e seu código é executado. Primeiramente, você declara uma variável inteira Ans (que tem escopo local, apenas para a função setLED) e utiliza o comando

strtol (string para inteiro longo), para converter os caracteres depois da letra R em um inteiro. O comando strtol aceita três parâmetros: a string que você está passando, um ponteiro para o caractere depois do inteiro (que você não utilizará, pois já limpou a string utilizando o comando strtok e, portanto, passa um caractere NULL), e a base (nesse caso, base 10, pois você está utilizando números decimais normais, em vez de números binários, octais ou hexadecimais, que seriam base 2, 8 e 16, respectivamente). Em resumo, você declara um inteiro e o define como o valor da string de texto depois da letra R (o trecho com o número).

Depois, você utiliza o comando constrain para garantir que Ans vá apenas de 0 a 255 e não passe disso. Então, você utiliza um comando analogWrite para o pino vermelho, enviando a ele o valor de Ans. O código emite "Red is set to: ", seguido pelo valor de Ans, no Serial Monitor. As outras duas instruções if fazem exatamente o mesmo procedimento, só que para os LEDs verde e azul.

Você encontrou muitos novos tópicos e conceitos neste projeto. Para garantir que compreendeu exatamente o que fizemos, coloquei o código do projeto (que está em C, lembre-se) lado a lado com o pseudocódigo correspondente (essencialmente, a linguagem do computador descrita com mais detalhes, utilizando palavras e pensa-mentos). Consulte a tabela 3.2 para a comparação.

Tabela 3.2 – Explicação para o código do projeto 10 utilizando pseudocódigo

No documento Arduino Básico Michael McRoberts (páginas 91-99)