2

I have to implement a set of serial shift registers with each 8 outputs. The outputs are connected to switches, so I'm currently using boolean arrays in C that either turn the switch on (true) or off(false).

So each shift register has an array of 8 boolean values, which is in fact a 8 bit unsigned integer. I could continue to work with arrays but I assume thats a lot slower then just bitwise manipulating the integer. Also passing the integer to the SPI interface is a lot easier than an array.

Is there an easy way to convert boolean arrays into integers or manipulate the integers in the same way I could manipulate an array?

I.e.:

bool switch[8];
switch[0] = True; //Switch 1 on
switch[1] = False; //Switch 2 off
...

is the same as

uint8_t switch;
switch = 0b00000001;

But is harder to read and program when thinking in individual switches.

Performance is key, since my SPI needs to be very fast.

Julian
  • 915
  • 8
  • 24
  • 2
    Why do you assume that using a boolean array is slower? – Konrad Rudolph Apr 09 '19 at 10:36
  • because I have to use a loop if I wanted to set them all to 1 for example, for an integer I could just assign them all at once. So in essence, more for loops, and for loops are slow correct? – Julian Apr 09 '19 at 10:42
  • maybe work with a union of your boolean array and the integer value. – Mike Apr 09 '19 at 10:43
  • Yeah, a union seems promising, will research that – Julian Apr 09 '19 at 10:44
  • For this purpose, don't use a bool array, don't use bit-fields, don't use enums, don't use fluffy poodles, don't use hydraulic excavators. Use a `uint8_t`. And that's it. – Lundin Apr 09 '19 at 10:52
  • @Julian If you need a loop in one code then you also need a loop for the other code. If you can do the bitwise assignment directly then the array assignment also doesn’t need a loop. – Konrad Rudolph Apr 09 '19 at 10:52
  • Some nitpicks: `switch` is a keyword. `True`, `False` and `0b00000001` are non-standard. Use `true`, `false` from same header as `bool`. Use hex constants `0x01` or explicit shift `(1u << 0)` to refer to bit values. – user694733 Apr 09 '19 at 11:06
  • @user694733 good catch about the true and false, I see that the 0b... method is non standard, but it shows the switch position much more easily then a hex would, so I would favor it over other methods. – Julian Apr 09 '19 at 14:26

5 Answers5

2

You cannot use the array syntax (this would need operator overloading, which is possible with C++). But you can use some helperfunction to set and get a bit (/ 8 and % 8 are optimized to bitshifts and ands, note that this is not the same with int as a type for index)

typedef uint8_t bit_mem_t;

static inline void set_bit(bit_mem_t* array, unsigned index, bool value) {
    array[index/8] = (array[index/8] | 1 << (index%8)) ^ !value << (index%8);
}

static inline bool get_bit(bit_mem_t const* array, unsigned index) {
    return (array[index/8] & 1 << (index%8)) != 0;
}

static inline void flip_bit(bit_mem_t* array, unsigned index) {
    array[index/8] ^= 1 << (index%8);
}

/*static inline size_t bit_array_size(size_t bit_count) {
    return (bit_count+7) / 8;
}*/
#define bit_array_size(bit_count) (((size_t)(count)+7)/8)

int main() {
    bit_mem_t array[bit_array_size(3)] {}; // clear it to 0s

    set_bit(array, 0, true);
    set_bit(array, 1, false);
    set_bit(array, 2, !get_bit(array, 1));
}
cmdLP
  • 1,658
  • 9
  • 19
2

Use uint8_t for storing 8 bit hardware-related data, period. If it is a memory-mapped hardware register, it also needs to be volatile qualified.

If you wish to name individual pins you can do it with defines:

#define SWITCH3 (1u << 3);

Access with bitwise operators.

uint8_t switch = 0;
switch = SWITCH1 | SWITCH3;   // set all bits
switch |= SWITCH3;            // set a specific bit
switch &= (uint8_t)~SWITCH3;  // clear a specific bit
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • 1
    @Ctx *How is this answer better than the others before?* It doesn't use bitfields. ;-) – Andrew Henle Apr 09 '19 at 11:26
  • 2
    @AndrewHenle I did not refer to _my_ answer, of course ;) It's not quite fair to cite only a part of my comment and distort its meaning that way – Ctx Apr 09 '19 at 11:27
  • @Ctx Well, that's the only part I have enough information to comment on, as I don't know what's going on with the other posters and their answers, nor who the sock puppet might be. :-D – Andrew Henle Apr 09 '19 at 11:31
  • @AndrewHenle "Who" the sock puppet may be is quite clear I think ;) Well, I simply observed that this answer basically went the same route than two other answers, but was immediately upvoted (while the others weren't) albeit it was posted several minutes later. That smells, and that's what I meant to express. But there may be other reasons of course... – Ctx Apr 09 '19 at 11:35
1

Use macros:

uint8_t sw;
#define SW_ON(s)    sw=(sw|(1u<<(s)))
#define SW_OFF(s)   sw=(sw&(~(1u<<(s))))

and use as:

SW_OFF(3);
SW_ON(2);
Paul Ogilvie
  • 25,048
  • 4
  • 23
  • 41
  • Consider using `1u` integer constants to avoid surprises during integer promotion. Your SW_OFF macro most often returns a negative integer value, which might not be what the programmer expects. – Lundin Apr 09 '19 at 11:13
  • Thanks, @Lundin, fixed that. Actually the expressions should be assignments to achieve what OP wants. Fixed that too. – Paul Ogilvie Apr 09 '19 at 11:56
1
#define SWITCH1 1U
#define SWITCH2 2U
#define SWITCH3 4U
#define SWITCH4 8U
#define SWITCH5 16U
#define SWITCH6 32U
#define SWITCH7 64U
#define SWITCH8 128U


unsigned int switches = 0x0U;

To turn on a switch, lets says switch switch 4

switches = (switches | SWITCH4)

To turn off switch, lets says switch switch 4

switches = (switches & ~SWITCH4)
Lundin
  • 195,001
  • 40
  • 254
  • 396
Sumit Trehan
  • 3,985
  • 3
  • 27
  • 42
0

You could use a bitfield like that:

union {
     struct {
          unsigned char s1:1,
                        s2:1,
                        s3:1,
                        s4:1,
                        s5:1,
                        s6:1,
                        s7:1,
                        s8:1
     } sw;
    unsigned char nr[1];
} u;

You can access the fields individually (u.sw.s1 - u.sw.s8) for the switches, but also access the whole number in u.nr[0].

It might also be possible to use unsigned char nr instead of an array with 1 member, but the standard only declares it valid for char arrays, so better play it safe.

Ctx
  • 18,090
  • 24
  • 36
  • 51
  • 1
    Using bit-fields is a very bad idea. Using `char` for bit-fields is an incredibly bad idea. If `char` is signed, there is no telling what this code will do. In addition, there is no telling if `s1` is bit 0 or something else. "but the standard only declares it valid for char arrays" No it doesn't. 6.7/5 "A bit-field shall have a type that is a qualified or unqualified version of _Bool, signed int, unsigned int, or some other implementation-defined type." So this code could literally do anything. – Lundin Apr 09 '19 at 11:00
  • @Lundin Not for the bit field, but for the union. But you are correct, should be unsigned char. – Ctx Apr 09 '19 at 11:01
  • The standard allows you to use any type for unions. – Lundin Apr 09 '19 at 11:02
  • @Lundin But it only allows you to reinterpret any type as char array. – Ctx Apr 09 '19 at 11:03
  • 2
    No it allows all manner of wild type punning. You can only iterate through some other larger type by using a pointer to _character type_, but that's another story. Character type meaning `char`, `signed char`, `unsigned char`, `uint8_t` etc. – Lundin Apr 09 '19 at 11:05
  • @Lundin No, it doesn't, accessing an integer as a float/double is UB for example. – Ctx Apr 09 '19 at 11:06
  • All that matter is effective type and alignment. If those are fine you can type pun away. Don't mix up C with C++. – Lundin Apr 09 '19 at 11:11
  • @Lundin For example, accessing an integer as a float might be a trap representation leading to UB. – Ctx Apr 09 '19 at 11:14
  • Yes because then you are using the wrong effective type. But there is no difference between `char`, `unsigned char` and `unsigned char[1]` when it comes to type punning. – Lundin Apr 09 '19 at 11:17
  • @Lundin The only type which is guaranteed to _not_ have a trap representation is unsigned char. So yes, it indeed makes a difference. – Ctx Apr 09 '19 at 11:20
  • 1
    `signed char` is not allowed to have padding bits/trap either. Trap representations are irrelevant anyway, if you access through a pointer which is allowed to alias with a pointer to the effective type of what's stored there. [What is the strict aliasing rule?](https://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule) – Lundin Apr 09 '19 at 11:25
  • @Lundin there are discussions about that topic, for example here: https://gustedt.wordpress.com/2010/09/21/anatomy-of-integer-types-in-c/ <- I agree with Jens here, signed char is not safe. – Ctx Apr 09 '19 at 11:42
  • Trap representation of plain integer types is mostly fiction and a system using trap representations for char types would be quite useless in the real world that exists outside ISO 9899. Regardless, it still doesn't explain 1) why you are using `unsigned char nr[1]` instead of `unsigned char`. 2) Why you would ever access this union through a pointer to a type that isn't compatible. Of course a struct bit-field may not necessarily alias an unsigned char, but that's no fault of the unsigned char, but because bit-fields are so poorly defined in the first place. – Lundin Apr 09 '19 at 13:11