7

I'm using an Atmega32 to read various sensors using its ADC.

Using digital logic and a few multiplexers, I've multiplexed the entirety of PORTA, using PA0:6 for address and PA7 as the input. Therefore, I can have a maximum of 128 inputs from just a single PORT.

Now, since the user will be operating the system via a computer (using RS232), it'll be possible to add or remove sensors, so the system must keep track of which addresses are free to add sensors and which addresses to read from.

I was thinking of using a 128 bit boolean array as flags to indicate if there is a sensor at a certain address.

While C doesn't have native support for single bit variables, it is possible to use bitfields to pack together up to 8 "bool variables" into a single unsigned char.

So, I've created the following struct:

typedef struct bool_s{
      uint8_t bit1:1;
}bool_t;

Then, I created a array of type bool_t with a size of 128, hoping that everything would be nicely packed together, but sizeof() tells me that the array size is 128 bytes instead of the 16 bytes I was expecting.

Technically, I could create a single struct with 128 1 bit variables:

typedef struct Flag_s{
      uint8_t F1:1;
      uint8_t F2:1;
      uint8_t F3:1;
      [...]
      uint8_t F128:1;
}Flag_t;

The problem with this approach is that while it does reduce memory usage, it's not very practical to use and it occupies way too much real estate in the code.

Is there any easy way to create a huge amount of flags or am I asking for too much? I mean, it's not like saving 112 bytes is going to make a big difference when you have 2K available, but what if I needed even more flags?

Chi
  • 896
  • 8
  • 29
  • 1
    Shouldn't this be on Stack Overflow.... – Sarima Nov 10 '15 at 14:34
  • 2
    @Joshpbarron http://meta.electronics.stackexchange.com/questions/2622/are-pure-c-questions-on-topic, [the writing of firmware for bare-metal or RTOS applications](http://electronics.stackexchange.com/help/on-topic) – Bob Nov 10 '15 at 14:47
  • 1
    @Joshpbarron This is specific to embedded C, boolean variables do exist in C99 and besides, it's not like using a few extra bytes here and there is going to make any significant difference when you have GiBs of memory available, and if I said I was using microcontrollers, they'd probably tell me to go to the EE stack exchange. – Chi Nov 10 '15 at 19:12

3 Answers3

13

Bit fields don't work like that. Cventu's answer shows a correct way to use them, but in this case, I recommend avoiding them altogether.

Instead, create an array of 8-bit values and use shifting and masking to access it:

uint8_t flags[16];

//Initialize the flags
void init_flags(void)
{
    for (i = 0; i < 16; i++)
    {
        flags[i] = 0x00;
    }
}

//Set one flag based on the address
void set_flag(uint8_t address)
{
    flags[address/8] |= 0x1 << (address % 8);
}

//Clear one flag based on the address
void clear_flag(uint8_t address)
{
    flags[address/8] &= ~(0x1 << (address % 8));
}

//Check whether a flag is set
bool get_flag_state(uint8_t address)
{
    return (flags[address/8] & (0x1 << (address % 0x8))) != 0x00;
}

This is probably what the compiler will do with bit field accesses anyway, and it's easier to work with. Some optimizers are bad at optimizing bit fields, so the compiler might even do worse. All compilers I've seen turn division and modulus by a constant power of two into right-shift and AND instructions. You can use those operations directly if you're feeling paranoid:

flags[address>>3] |= 0x1 << (address & 0x7);

Bit fields are more like structures than arrays. In my experience, they're only useful for things that have names, like register fields.

Adam Haun
  • 21,331
  • 4
  • 50
  • 91
  • 1
    On more advanced processors (like x86), bitfields are usually implemented using even larger elementary variables, like uint32_t. But the idea is the same: bitwise OR is used to set the flags, bitwise NOT+AND is used to clear the flags, and bitwise AND is used to check the flags, with the mask being either generated using the `<<` operator or defined as constants with macros. – Laszlo Valko Nov 10 '15 at 06:25
  • I came here to say https://github.com/vonj/snippets/blob/master/bitarray.c but you beat me to it. :) – Prof. Falken Nov 10 '15 at 10:17
  • 2
    That's some expensive arithmetic (divide & mod) for low-powered micros. – John U Nov 10 '15 at 12:53
  • 1
    While your code is more generic, some MCUs (e.g. PIC18) have the ability to read/set single bits in RAM directly. This would reduce the load a lot. One may check this, as compilers do not always recognize what you are doing there. – sweber Nov 10 '15 at 14:25
  • @JohnU powers of two are pretty cheap to do div and mod on, % 8 is the same getting the lower 3 bits (& 0x07) and /8 can be done with a bitshift (>>0x03) – Gorloth Nov 10 '15 at 16:15
  • Yeah modern compilers will often be able to replace the div/mod with shr/and for divisors that are powers of two – Mark K Cowan Nov 10 '15 at 18:31
  • This is just perfect: memory efficient, portable and easy to use. Thanks, I really appreciate your help! – Chi Nov 10 '15 at 19:24
  • 2
    @JohnU I've never seen division or modulus division by a constant power of two compile into anything other than shift and AND instructions. But you definitely don't want to divide or mod by a variable. – Adam Haun Nov 10 '15 at 19:57
  • 4
    The *reason* bitfields don't work like that is because every element of an array has to have an address and be amenable to pointer arithmetic, which precludes an element from taking up less than the size of char (i.e. one byte). Multiple bitfields within a struct can "share" a byte, but no such luck with array elements. – hobbs Nov 10 '15 at 20:11
  • @Gorloth - true, why not just write it that way in the 1st place rather than hope the compiler changes it? – John U Nov 11 '15 at 11:10
  • 1
    @JohnU Because you should strive to write as readable code as possible. Simply do that, then disassemble the code and see how it ended up. You should only manually optimize code if you find out that the compiler is broken and didn't replace division by shifts (given that optimizations are enabled in the first place). In which case you need to comment why you did manual optimizations and also file a bug report to the compiler vendor. – Lundin Nov 16 '15 at 12:03
10

It sounds a bit confusing, but maybe something like this can help:

struct bits_field {
                       unsigned char bit_7 :1;
                       unsigned char bit_6 :1;
                       unsigned char bit_5 :1;
                       unsigned char bit_4 :1;
                       unsigned char bit_3 :1;
                       unsigned char bit_2 :1;
                       unsigned char bit_1 :1;
                       unsigned char bit_0 :1;

                   };

union dt {
             struct bits_field a;
             unsigned char b;
         };

After defining those, declare the following variable:

union dt my_data;

You will be able to write a entire group of eight bits togheter using: my_data.b

Or you can write each bit individually using: my_data.a.bit_7 (or 6,5,4,3,2,1,0)

In case that work for you, replicate this 16 times (using an array or struct) and you will be able to handle 128 bits individually.

Let us know if it worked! Good luck

cventu
  • 377
  • 1
  • 14
  • I also thought about using a struct with 8 1bit variables, but then I'd have to write some sort of function to translate the address number into something I could use with such an array, which would just add confusion and overhead. Oh, and by the way, finally someone has showed me a good example of how the union data type can be useful! :P – Chi Nov 10 '15 at 19:37
  • The main problem here is that bit fields may contain any number of padding bits and bytes, one of many things that make them unreliable and non-portable. A union doesn't solve that. – Lundin Nov 16 '15 at 12:07
0

Create a structure where you declare eight bit flags that end up being a byte. Then create an array of these 8-bit structures.

Michael Karas
  • 56,889
  • 3
  • 70
  • 138