One of the standard patterns for extracting a bit field is (reg >> offset) & mask , where reg is the register (or other memory location) you are reading, offset is the number of least significant bits that you skip, and mask is a set of bits that matters. Step >> offset can be omitted if offset is 0. mask usually 2 width -1 or (1 << width) - 1 in C, where width is the number of bits in the field.
So, looking at what you have:
buttons[0] = indata[byteindex]&1;
Here offset is 0 (it was omitted), and mask is 1. Thus, it only gets the low-order bit in indata[byteindex] :
bit number -> 7 6 5 4 3 2 1 0 +-+-+-+-+-+-+-+-+ indata[byteindex] | | | | | | | |*| +-+-+-+-+-+-+-+-+ | \----> buttons[0]
Further:
buttons[1] = (indata[byteindex]>>1)&1;
Here offset is 1 and width is 1 ...
bit number -> 7 6 5 4 3 2 1 0 +-+-+-+-+-+-+-+-+ indata[byteindex] | | | | | | |*| | +-+-+-+-+-+-+-+-+ | \------> buttons[1]
And finally:
rawaxes[7] = (indata[byteindex]>>4)&0xf;
Here offset is 4 and width is 4 (2 4 -1 = 16 - 1 = 15 = 0xf):
bit number -> 7 6 5 4 3 2 1 0 +-+-+-+-+-+-+-+-+ indata[byteindex] |*|*|*|*| | | | | +-+-+-+-+-+-+-+-+ | | | | \--v--/ | \---------------> rawaxes[7]
EDIT ...
but I don’t understand what the point is ...
Mike pulls up the rocking chair and sits down.
In the old days of 8-bit processors, a computer typically had 64 KB (65,536 bytes) of address space. Now we wanted to do everything we could with our fantastic whistling machines, so we will do things like buy 64 KB of RAM and display everything in RAM. Shazam, 64 KB of RAM and praise rights around.
But a computer that can only access RAM is not very good. It needs some ROM for the OS (or at least the BIOS), and some addresses for I / O. (You are in the backseat. I know that Intel chips have a separate address space for I / O, but this does not help here, because the I / O space was much less than the memory, so you faced the same limitations.)
The address space used for ROM and I / O was a space that was not available as RAM, so you wanted to minimize how much space was not used for RAM. So, for example, when your peripheral I / O device had five different things, the status of which was one bit each, instead of giving each of these bits its own byte (and therefore address), they got the brilliant idea of packing all five of these bits into one byte, leaving three bits that did nothing. Voila, the Interrupt Status Register appeared.
Hardware designers were also impressed with the fact that fewer addresses resulted in fewer address bits (since address bits are a stream of a logarithmic base-2 number of addresses), which means fewer address contacts on the chip, freeing up contacts for other purposes. (These were the days when 48-pin microcircuits were considered large, and 64-pin huge and trellis packages could not be ruled out because multi-level printed circuit boards were excessively expensive, and also a few days before address multiplexing and data on the same pins has become commonplace.)
So, the chips were cut and fabricated, and the hardware was built, and then the programmers needed to do the hardware work. And so, the programmers said: "WTF? I just want to know if there is a byte to read in the bloody serial port, but there are all these other bits, like a" receiver "on the way." And the hardware guys reviewed it and said, "hard cookies, handle it."
So, the programmers went to the Guru, the guy who did not forget his Boolean algebra and was happy not to write COBOL. And the Guru said: "Use the bit AND operation to force those bits that you do not need to be 0. If you need a number, not just zero or nonzero, use logical right shift (LSR) to the result." And they tried it. It worked, and there was a lot of joy, although the wiser began to be interested in things such as race conditions in the read-modify-write cycle, but this is a story at a different time.
Thus, the technique of packing freely or completely unrelated bits into registers has become commonplace. People who develop protocols that always want to use fewer bits also jumped on these methods. So, even today, with our gigabytes of RAM and gigabytes of bandwidth, we still collect and unpack bit fields with expressions whose clarity borders on a keyboard hit.
(Yes, I know that bit fields will probably go back to ENIAC and maybe even to the difference engine if Lady Ada needs to fill in two data items in one register, but I haven’t been alive for so long, okay? , what do I know.)
(Note that the hardware developers are there: there really aren't many excuses for packing things like status flags and control bits that the driver developer will want to use on their own. I have done several projects with one 32-bit bit In many cases, no bit shifting or masking, no races, driver code is easier to write and understand, and the address decoding logic is trivially more complex. If the driver software is complex, simplifying the flag and bit pattern Botko can save you a lot of ROM and CPU cycles.)
(More random trivia: the Atmel AVR architecture (used by Arduino, among many other places) has some specialized instructions for bit-bit and bit-clarity. The avr-libc library used to provide macros for these instructions, but now the gcc compiler is enough smart to recognize that reg |= (1 << bitNum); bit is set, and reg &= ~(1 << bitNum); little clear and fits into the correct instruction. I'm sure other architectures have similar optimizations.)