Publicidade:

quinta-feira, 28 de janeiro de 2016

Arduino - Dicas de Programação - Bit Fields

Quando se trata de programação de microcontroladores, precisamos estar sempre atento ao tamanho do data type a ser usado ao declarar uma variável, pois em geral contamos com poucos bytes a disposição para nossos programas.

Eu já fiz um vídeo (veja aqui) explicando como utilizar operações de bitwise, que se bem utilizadas nos garante uma boa economia de bytes. Mas nesse artigo quero falar sobre outro recurso que a liguagem C nos oferece: Os Bit Fields.

Bit fields são utilizados em conjunto com structs, que pra quem não sabe, são estruturas de dados que podem ser definidas pelo programador, para representar dados compostos, como por exemplo, dados sobre uma pessoa, que pode ter o nome, idade, etc.

Exemplo de struct:

struct Pessoa {
   char nome[10];
   int idade;
};

Além disso bit fields também podem ser utilizados em unions e classes.

Vamos a um exmeplo: É comum precisarmos utilizar flags em nossos programas, que são variáveis que irão receber possivelmente apenas dois valores, como 0 e 1, ou true e false, etc. Nesses casos é comum utilizarmos variáveis do tipo boolean, byte ou pior ainda int. As variáveis do tipo boolean e do tipo byte, possuem tamanho de 1 byte, já um int precisa de 2 bytes. Se a nossa intenção é armazenar valores que podem assumir no máximo dois valores, em teoria não precisaríamos de 1 byte completo (lembrando que um byte tem 8 bits), já que um único bit nesse caso seria suficiente para representar o nosso flag.

Infelizmente não temos como declarar variáveis do tipo bit, mas temos o recurso de Bit Fields que são muito fáceis de serem usados. Vamos a um exemplo:

struct Flags {
   byte flag_1 : 1;
};

Veja que adicionamos um sinal de ":" e após isso o número 1. Isso significa que estamos limitando o tamanho da variável flag_1 para que tenha apenas um bit, logo ela somente irá representar valores que podem ser 1 ou 0. Porém na prática, ainda não temos nenhuma economia de memória, pois apesar da variável flag_1 ter sido limitada no que pode representar, a memória alocada fisicamente ainda será de um byte por variável do tipo Flags.

Mas isso acontece, pois tivemos a necessidade de apenas um flag, se nós tivéssemos que utilizar dois, três ou mais, aí sim começaríamos a nos beneficiar dos bit fields. Então vamos incluir mais alguns flags nessa estrutura:

struct Flags {
   byte flag_1 : 1;
   byte flag_2 : 1;
   byte flag_3 : 1;
   byte flag_4 : 1;
   byte flag_5 : 1;
   byte flag_6 : 1;
   byte flag_7 : 1;
   byte flag_8 : 1;
};

Agora temos 8 flags e ainda assim o tamanho que uma variável do tipo Flags ocupará é de apenas 1 byte. Isso por que cada um dos bits fará parte de um único byte. Caso adicionássemos um "flag_9 : 1" aí um novo byte teria que ser alocado.

Apesar desse exemplo utilizar apenas 1 bit pra cada variável da struct, poderíamos ter outras quantidades de bits. Por exemplo, uma variável que tenha valores possíveis entre 0 e 7, poderia ser declarada da seguinte maneira:

struct MeuBitField {
   byte valor_1 : 3;  /*0..7 ou 000 ... 111*/
   byte valor_2 : 3;
   byte flag_1 : 1;
   byte flag_2 : 1;
};

Para provar que esse recurso realmente funciona, veja as imagens abaixo, onde na primeira compilação, sem o uso de bit fields tivemos o uso de 17 bytes. Enquanto que na segunda vez, já com o uso dos bit fields, tivemos o uso de apenas 10 bytes. E para provarmos que uma variável do tipo Flags irá consumir apenas 1 byte, compilamos também o código vazio, apenas com o loop e setup sem nenhum código. O que consumiu 9 bytes.







Orientação a Objetos

Uma desculpa que sempre vejo que alguns programadores dão para não usarem orientação a objetos em programas para microcontroladores é que objetos ocupam muita memória. Mas geralmente essas afirmações ficam apenas nisso, ninguém se dá ao trabalho de realmente tirar a prova ou demonstrar que objetos consomem mais memória que programação estrutural.

Um bom exemplo de que dá pra programar Orientado a objetos com código enxuto, é que classes também podem ter bit fields.

Veja:

class Teste {
  private:
    byte _x : 1;
    byte _y : 1;
  public:
    void setX(boolean x){ _x = x; }
    void setY(boolean y){ _y = y; }
    
    boolean getX(){ return _x; }
    boolean getY(){ return _y; }
};

Teste t;

void setup() {
  t.setX(true);
  t.setX(false);
}

void loop() { }

4 comentários:

  1. Muito obrigado pelos seus ensinamentos!!!
    Cada dia que passo aqui aprendo algo novo e muito útil,
    Amigo teria uma maneira ao copilar o código final para o Arduino de selecionar apenas as bibliotecas que voce precisa carregar .Ex: nao carregar o tone.cpp ,serial.cpp e outras?
    Obrigado.

    ResponderExcluir
    Respostas
    1. valeu. obrigado.

      esses arquivos são carregados conforme são usados... mas a ide quando compila o seu código, junta ele com outros arquivos que fazem parte da ide, onde são feitos alguns includes.

      vc pode ver isso abrindo esse arquivo:
      C:\[pasta do arduino]\hardware\arduino\avr\cores\arduino

      veja que lá é feito esse include:
      #include

      agora, como fazer pra não utilizar esses includes eu não sei exatamente como fazer...

      Excluir
    2. corrigindo:
      C:\[pasta do arduino]\hardware\arduino\avr\cores\arduino\main.cpp

      Excluir
  2. Para completar a pergunta acima, quando compilo aqui somente void setup() e void loop() vazios ja da 466 bytes.

    ResponderExcluir