Lucky Resistor
Menu
  • Home
  • Learn
    • Learn C++
    • Product Photography for Electronics
      • Required Equipment and Software
    • Soldering for Show
  • Projects
  • Libraries
  • Applications
  • Shop
  • About
    • About Me
    • Contact
    • Stay Informed
  •  
Menu

Bit Manipulation using Templates

Posted on 2019-07-232022-09-04 by Lucky Resistor

Did you read my article about C++ templates for embedded code? You learned how to use function templates. This post adds a practical example to this topic.

Bit Manipulation

You may be familiar with bit manipulation code like this:

void setup() {
  DDRB |= 0b00100000u;
  PORTB &= ~0b00100000u;
}

For simplicity, you like to select single bits using its index number.

void setup() {
  DDRB |= (1 << 5);
  PORTB &= ~(1 << 5);
}

This is a common request. Embedded libraries often implement a solution using macros 😞, like this one from the AVR toolchain:

#define _BV(bit) (1 << (bit))

You can use it to simplify your code like this:

void setup() {
  DDRB |= _BV(5);
  PORTB &= ~_BV(5);
}

These macros will clutter the global namespace and are not type-safe. I demonstrate the problem with this example:

#include <iostream>

#define _BV(bit) (1 << (bit))

int main() {
    for (int i = 0; i < 64; ++i) {
        std::cout << i << ": " << sizeof(_BV(i)) << " / " << _BV(i) << std::endl;
    }
    return 0;
}

This is regular C++ code you compile like this:

> c++ example.cpp -o example

If you run this program, which output do you expect?

Think...

Click on "Result" to reveal the output.
0: 4 / 1
1: 4 / 2
2: 4 / 4
3: 4 / 8
4: 4 / 16
5: 4 / 32
6: 4 / 64
7: 4 / 128
8: 4 / 256
9: 4 / 512
10: 4 / 1024
11: 4 / 2048
12: 4 / 4096
13: 4 / 8192
14: 4 / 16384
15: 4 / 32768
16: 4 / 65536
17: 4 / 131072
18: 4 / 262144
19: 4 / 524288
20: 4 / 1048576
21: 4 / 2097152
22: 4 / 4194304
23: 4 / 8388608
24: 4 / 16777216
25: 4 / 33554432
26: 4 / 67108864
27: 4 / 134217728
28: 4 / 268435456
29: 4 / 536870912
30: 4 / 1073741824
31: 4 / -2147483648
32: 4 / 1
33: 4 / 2
34: 4 / 4
35: 4 / 8
36: 4 / 16
37: 4 / 32
38: 4 / 64
39: 4 / 128
40: 4 / 256
41: 4 / 512
42: 4 / 1024
43: 4 / 2048
44: 4 / 4096
45: 4 / 8192
46: 4 / 16384
47: 4 / 32768
48: 4 / 65536
49: 4 / 131072
50: 4 / 262144
51: 4 / 524288
52: 4 / 1048576
53: 4 / 2097152
54: 4 / 4194304
55: 4 / 8388608
56: 4 / 16777216
57: 4 / 33554432
58: 4 / 67108864
59: 4 / 134217728
60: 4 / 268435456
61: 4 / 536870912
62: 4 / 1073741824
63: 4 / -2147483648

You always get a 32-bit signed integer from _BV(), therefore you see issues starting at bit index 31. If you use the macro correctly, the negative numbers are no problem, but if you work with 64-bit values the results are unexpected.

Templates

Let us write safer code with function templates for a pure C++ solution.

template<uint8_t bitIndex, typename Type>
constexpr Type oneBit() {
  return static_cast<Type>(1) << bitIndex;
}

This function template will not only give us a bit from an index but also requires the expected type. Knowing the type is important to prevent invalid index values and unexpected results.

(You will learn static assertions to implement additional checks at a later time.)

Use the new function template like this:

void setup() {
  DDRB |= oneBit<5, uint8_t>();
  PORTB &= ~oneBit<5, uint8_t>();
}

This is safer code, but looks ugly. The type uint8_t is repetitive and the compiler could deduce it from the variable. Also, the bitwise assignment operators |= and &= ~X maybe familiar to you, but they are not well readable.

Let us introduce additional function templates to name the two operations.

template<uint8_t bitIndex, typename Type>
inline void setBit(Type &value) {
  value |= oneBit<bitIndex, Type>();
}

template<uint8_t bitIndex, typename Type>
inline void clearBit(Type &value) {
  value &= ~(oneBit<bitIndex, Type>());
}

Now, you can write code like this:

void setup() {
  setBit<5>(DDRB);
  clearBit<5>(PORTB);
}

We only pass the bit index to the function template, the type is deduced from the variable. This works, because we declared template parameter which can be guessed by the compiler at the end of the parameter list.

Compiled Firmware

Let us compile the example code for Arduino Uno and check the results:

 1aa:	25 9a       	sbi	0x04, 5	; 4
 1ac:	2d 98       	cbi	0x05, 5	; 5

This is all we got — It is the smallest possible code for the bit operations on these IO registers.

Exercises

  • Implement the bit manipulation using regular functions; without templates and macros. Do you get the same firmware?
  • What is the difference if you use the bit index as template parameter or as a function parameter?
  • These examples only work with a static implementation. Create function templates which support dynamic bit indexes.

Conclusion

C++ templates do not only replace macros, but they also lead to safer and cleaner code.

Do you have any questions, miss information or have feedback? Add a comment below or ask a question on Twitter!

Read Part 2

Learn More

While you are waiting for the next article, check out these:

Units of Measurements for Safe C++ Variables

Units of Measurements for Safe C++ Variables

In your program code, you often deal with unitless values. You can add the units of measurements in the variable or function name, or by adding a comment, but there is still the risk you ...
Read More
Make your Code Safe and Readable with Flags

Make your Code Safe and Readable with Flags

Flags play an important role in embedded software development. Microcontrollers and chips are using registers where single bits or combinations of bits play a big role in the configuration. All the bits and their role ...
Read More
It's Time to Use #pragma once

It’s Time to Use #pragma once

In my opinion, preprocessor macros and the outdated #include mechanism are one of the worst parts of the C++ language. It is not just these things are causing a lot of problems, even more, it ...
Read More
Consistent Error Handling

Consistent Error Handling

Error handling in firmware is a difficult task. If you decide to ignore errors, the best you can expect is a deadlock, but you could also damage the hardware. When reviewing existing code, I often ...
Read More
How to Deal with Badly Written Code

How to Deal with Badly Written Code

Sadly there is a ton of badly written code out in the wild. Hardware related code, seem to suffer more in this regards. I imagine, many developer in this segment are unwillingly to invest time ...
Read More
How and Why to use Namespaces

How and Why to use Namespaces

Namespaces are a feature of C++ which address the problem of name conflicts. There is a "global" namespace, where everything lives which was declared without namespace. Especially the Arduino environment declares a huge amount of ...
Read More

Leave a Reply Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Stay Updated

Join me on Mastodon!

Top Posts & Pages

  • Storage Boxes System for 3D Print
  • Use Enum with More Class!
  • Simple Box with Lid for 3D-Print
  • Fan Controller
  • Shop
  • Real Time Counter and Integer Overflow
  • How and Why to use Namespaces
  • The Hinges and its Secrets for Perfect PETG Print
  • Extreme Integers – Doom from Below
  • Build a 3D Printer Enclosure

Latest Posts

  • The Importance of Wall Profiles in 3D Printing2023-02-12
  • The Hinges and its Secrets for Perfect PETG Print2023-02-07
  • Better Bridging with Slicer Guides2023-02-04
  • Stronger 3D Printed Parts with Vertical Perimeter Linking2023-02-02
  • Logic Gates Puzzle 1012023-02-02
  • Candlelight Emulation – Complexity with Layering2023-02-01
  • Three Ways to Integrate LED Light Into the Modular Lantern2023-01-29
  • The 3D Printed Modular Lantern2023-01-17

Categories

  • 3D Printing
  • Build
  • Common
  • Fail
  • Fun
  • Learn
  • Projects
  • Puzzle
  • Recommendations
  • Request for Comments
  • Review
  • Software
Copyright (c)2022 by Lucky Resistor. All rights reserved.