No desenvolvimento de firmware para sistemas embarcados, não raro precisamos obter e tratar dados que necessitam de uma certa precisão (entenda-se casas decimais depois da vírgula). Apenas para citar um exemplo de dados que necessitam um controle preciso, podemos citar o Medidor de Vazão Portátil desenvolvido pela Eletroeste. Uma pequena perda na precisão dos dados poderia resultar em desconsiderar valores importantes que tirariam a confiabilidade do equipamento. Além disso outros sistemas embarcados que medem pressão, velocidade e valores elétricos como tensão e corrente em geral necessitam de dados precisos. Para isso, seria normal utilizar uma variável float para o armazenamento dos dados. Entretanto, em geral tratar esses dados precisos envolvem cálculos e, quando trabalhamos com microcontroladores 8-bit, fazer excessivos cálculos com variáveis float (32 bits) pode se tornar inviável.
Isso acontece porque os microcontroladores 8-bits precisam mais de uma operação para trabalhar com variáveis de 32 bits. Ainda mais no caso de cálculos utilizando variáveis float, é necessário cálculos aritméticos da própria variável float, o que pode consumir um tempo relativamente grande. Isso pode ser um problema em sistemas que necessitam de uma resposta em tempo real.
Uma das soluções é transferir o valor do float para uma int (16-bits). Porém para manter a precisão antes de transferir para um int é preciso multiplica-lo por um valor de base 100. Abaixo podemos observar um exemplo:
Nesse exemplo estamos lendo um valor com a função recebe e retornando para uma variável do tipo float. Para mantermos a precisão, antes de passar esse valor para um int multiplicamos por 100 o valor de float. Dessa forma estamos preservando 2 casas decimais após a vírgula. Por fim atribuímos o valor da multiplicação a uma variável do tipo int que armazenará a parte inteira do float. Para esta solução deve-se ter cuidado para que a variável inteira não ocorra overflow.
Outro caso que podemos citar envolvendo as inconveniências de usar variáveis float é quando precisamos enviar esse valor para outro dispositivo utilizando uma comunicação serial. Em geral é necessário enviar 1 byte por vez. Assim precisamos "quebrar" o float para podermos enviar os 4 bytes separadamente.
Uma das solução que a linguagem de programação C nos oferece são as uniões (union). "Em C uma union é uma posição de memória que é compartilhada por duas ou mais variáveis diferentes, geralmente de tipos diferentes, em momentos diferentes."[1] Abaixo podemos observar a definição geral de uma union:
Essa definição não declara nenhuma variável, apenas define um tipo de union. Para declarar uma variável do tipo union, pode-se observar abaixo:
Nessa parte estamos declarando uma variável union var_union do tipo u_type. "Quando uma union é declarada, o compilador cria automaticamente uma variável grande o bastante para conter o maior tipo de variável da union."[1] No caso acima a nossa union tem 4 bytes. Assim, todas as variáveis de dentro da union estão apontando para o mesmo endereço inicial de memória, e é isso que nos ajuda. Para acessar um componente da union basta referenciarmos a union com o elemento que desejamos. Ex.: var_union.type_int;. Então este tipo de variável é um boa solução para a aplicação de comunicação serial e terá pouco processamento para o microcontrolador de 8-bits.
Vale ressaltar que o resultado destas soluções podem variar de compilador para compilador, então testes são necessários.
Vale ressaltar que o resultado destas soluções podem variar de compilador para compilador, então testes são necessários.
No desenvolvimento de firmware para sistemas embarcados é normal nos depararmos com situações e problemas que levam algum tempo para resolver. Entretanto, muitas vezes esses problemas não são tão difíceis de resolver quanto imaginamos. Conforme observamos nos 2 exemplos acima é necessário raciocínio lógico e conhecimento dos recursos da linguagem de programação. Até o próximo post...
Nenhum comentário:
Postar um comentário
Gostou do texto? Expresse aqui a sua opinião.