123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454 |
- // ----------------------------------------------------------------------------
- // Toggling, Setting, and Clearing Bits
- // ----------------------------------------------------------------------------
- //
- // Another exciting thing about Zig is its suitability for embedded
- // programming. Your Zig code doesn't have to remain on your laptop. You can
- // also deploy your code to microcontrollers! This means you can write Zig to
- // drive your next robot or greenhouse climate control system! Ready to enter
- // the exciting world of embedded programming? This exercise is designed to
- // give you a taste of what it's like to control registers in a
- // microcontroller. Let's get started!
- //
- // A common activity in microcontroller programming is setting and clearing
- // bits on input and output pins. This lets you control LEDs, sensors, motors
- // and more! In a previous exercise (097_bit_manipulation.zig) you learned how
- // to swap two bytes using the ^ (XOR - exclusive or) operator. In this
- // exercise, we'll take a closer look at bit manipulation and how we can write
- // code that sets and clears specific bits as we would if we were programming
- // the pins on a real microcontroller. Included at the end of this exercise are
- // some helper functions that demonstrate how we might make our code a little
- // more readable.
- //
- // Below is a pinout diagram for the famous ATmega328 AVR microcontroller used
- // as the primary microchip on popular microcontroller platforms like the
- // Arduino UNO.
- //
- // ============ PINOUT DIAGRAM FOR ATMEGA328 MICROCONTROLLER ============
- // _____ _____
- // | U |
- // (RESET) PC6 --| 1 28 |-- PC5
- // PD0 --| 2 27 |-- PC4
- // PD1 --| 3 26 |-- PC3
- // PD2 --| 4 25 |-- PC2
- // PD3 --| 5 24 |-- PC1
- // PD4 --| 6 23 |-- PC0
- // VCC --| 7 22 |-- GND
- // GND --| 8 21 |-- AREF
- // |-- PB6 --| 9 20 |-- AVCC
- // |-- PB7 --| 10 19 |-- PB5 --|
- // | PD5 --| 11 18 |-- PB4 --|
- // | PD6 --| 12 17 |-- PB3 --|
- // | PD7 --| 13 16 |-- PB2 --|
- // |-- PB0 --| 14 15 |-- PB1 --|
- // | |___________| |
- // \_______________________________/
- // |
- // PORTB
- //
- // Drawing inspiration from this diagram, we'll use the pins for PORTB as our
- // mental model for this exercise in bit manipulation. It should be noted that
- // in the following examples we are using ordinary variables, one of which we
- // have named PORTB, to simulate modifying the bits of real hardware registers.
- // But in actual microcontroller code, PORTB would be defined something like
- // this:
- // pub const PORTB = @as(*volatile u8, @ptrFromInt(0x25));
- //
- // This lets the compiler know not to make any optimizations to PORTB so that
- // the IO pins are properly mapped to our code.
- //
- // NOTE : To keep things simple, the following examples are given using type
- // u4, so applying the output to PORTB would only affect the lower four pins
- // PB0..PB3. Of course, there is nothing to prevent you from swapping the u4
- // with a u8 so you can control all 8 of PORTB's IO pins.
- const std = @import("std");
- const print = std.debug.print;
- const testing = std.testing;
- pub fn main() !void {
- var PORTB: u4 = 0b0000; // only 4 bits wide for simplicity
- //
- // Let's first take a look at toggling bits.
- //
- // ------------------------------------------------------------------------
- // Toggling bits with XOR:
- // ------------------------------------------------------------------------
- // XOR stands for "exclusive or". We can toggle bits with the ^ (XOR)
- // bitwise operator, like so:
- //
- //
- // In order to output a 1, the logic of an XOR operation requires that the
- // two input bits are of different values. Therefore, 0 ^ 1 and 1 ^ 0 will
- // both yield a 1 but 0 ^ 0 and 1 ^ 1 will output 0. XOR's unique behavior
- // of outputing a 0 when both inputs are 1s is what makes it different from
- // the OR operator; it also gives us the ability to toggle bits by putting
- // 1s into our bitmask.
- //
- // - 1s in our bitmask operand, can be thought of as causing the
- // corresponding bits in the other operand to flip to the opposite value.
- // - 0s cause no change.
- //
- // The 0s in our bitmask preserve these values
- // -XOR op- ---expanded--- in the output.
- // _______________/
- // / /
- // 0110 1 1 0 0
- // ^ 1111 0 1 0 1 (bitmask)
- // ------ - - - -
- // = 1001 1 0 0 1 <- This bit was already cleared.
- // \_______\
- // \
- // We can think of these bits having flipped
- // because of the presence of 1s in those columns
- // of our bitmask.
- print("Toggle pins with XOR on PORTB\n", .{});
- print("-----------------------------\n", .{});
- PORTB = 0b1100;
- print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB});
- print("^ {b:0>4} // (bitmask)\n", .{0b0101});
- PORTB ^= (1 << 1) | (1 << 0); // What's wrong here?
- checkAnswer(0b1001, PORTB);
- newline();
- PORTB = 0b1100;
- print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB});
- print("^ {b:0>4} // (bitmask)\n", .{0b0011});
- PORTB ^= (1 << 1) & (1 << 0); // What's wrong here?
- checkAnswer(0b1111, PORTB);
- newline();
- // Now let's take a look at setting bits with the | operator.
- //
- // ------------------------------------------------------------------------
- // Setting bits with OR:
- // ------------------------------------------------------------------------
- // We can set bits on PORTB with the | (OR) operator, like so:
- //
- // var PORTB: u4 = 0b1001;
- // PORTB = PORTB | 0b0010;
- // print("PORTB: {b:0>4}\n", .{PORTB}); // output: 1011
- //
- // -OR op- ---expanded---
- // _ Set only this bit.
- // /
- // 1001 1 0 0 1
- // | 0010 0 0 1 0 (bit mask)
- // ------ - - - -
- // = 1011 1 0 1 1
- // \___\_______\
- // \
- // These bits remain untouched because OR-ing with
- // a 0 effects no change.
- //
- // ------------------------------------------------------------------------
- // To create a bit mask like 0b0010 used above:
- //
- // 1. First, shift the value 1 over one place with the bitwise << (shift
- // left) operator as indicated below:
- // 1 << 0 -> 0001
- // 1 << 1 -> 0010 <-- Shift 1 one place to the left
- // 1 << 2 -> 0100
- // 1 << 3 -> 1000
- //
- // This allows us to rewrite the above code like this:
- //
- // var PORTB: u4 = 0b1001;
- // PORTB = PORTB | (1 << 1);
- // print("PORTB: {b:0>4}\n", .{PORTB}); // output: 1011
- //
- // Finally, as in the C language, Zig allows us to use the |= operator, so
- // we can rewrite our code again in an even more compact and idiomatic
- // form: PORTB |= (1 << 1)
- print("Set pins with OR on PORTB\n", .{});
- print("-------------------------\n", .{});
- PORTB = 0b1001; // reset PORTB
- print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB});
- print("| {b:0>4} // (bitmask)\n", .{0b0100});
- PORTB = PORTB ??? (1 << 2); // What's missing here?
- checkAnswer(0b1101, PORTB);
- newline();
- PORTB = 0b1001; // reset PORTB
- print(" {b:0>4} // (reset state)\n", .{PORTB});
- print("| {b:0>4} // (bitmask)\n", .{0b0100});
- PORTB ??? (1 << 2); // What's missing here?
- checkAnswer(0b1101, PORTB);
- newline();
- // So now we've covered how to toggle and set bits. What about clearing
- // them? Well, this is where Zig throws us a curve ball. Don't worry we'll
- // go through it step by step.
- // ------------------------------------------------------------------------
- // Clearing bits with AND and NOT:
- // ------------------------------------------------------------------------
- // We can clear bits with the & (AND) bitwise operator, like so:
- // PORTB = 0b1110; // reset PORTB
- // PORTB = PORTB & 0b1011;
- // print("PORTB: {b:0>4}\n", .{PORTB}); // output -> 1010
- //
- // - 0s clear bits when used in conjuction with a bitwise AND.
- // - 1s do nothing, thus preserving the original bits.
- //
- // -AND op- ---expanded---
- // __________ Clear only this bit.
- // /
- // 1110 1 1 1 0
- // & 1011 1 0 1 1 (bit mask)
- // ------ - - - -
- // = 1010 1 0 1 0 <- This bit was already cleared.
- // \_______\
- // \
- // These bits remain untouched because AND-ing with a
- // 1 preserves the original bit value whether 0 or 1.
- //
- // ------------------------------------------------------------------------
- // We can use the ~ (NOT) operator to easily create a bit mask like 1011:
- //
- // 1. First, shift the value 1 over two places with the bit-wise << (shift
- // left) operator as indicated below:
- // 1 << 0 -> 0001
- // 1 << 1 -> 0010
- // 1 << 2 -> 0100 <- The 1 has been shifted two places to the left
- // 1 << 3 -> 1000
- //
- // 2. The second step in creating our bit mask is to invert the bits
- // ~0100 -> 1011
- // in C we would write this as:
- // ~(1 << 2) -> 1011
- //
- // But if we try to compile ~(1 << 2) in Zig, we'll get an error:
- // unable to perform binary not operation on type 'comptime_int'
- //
- // Before Zig can invert our bits, it needs to know the number of
- // bits it's being asked to invert.
- //
- // We do this with the @as (cast as) built-in like this:
- // @as(u4, 1 << 2) -> 0100
- //
- // Finally, we can invert our new mask by placing the NOT ~ operator
- // before our expression, like this:
- // ~@as(u4, 1 << 2) -> 1011
- //
- // If you are offput by the fact that you can't simply invert bits like
- // you can in languages such as C without casting to a particular size
- // of integer, you're not alone. However, this is actually another
- // instance where Zig is really helpful because it protects you from
- // difficult to debug integer overflow bugs that can have you tearing
- // your hair out. In the interest of keeping things sane, Zig requires
- // you simply to tell it the size of number you are inverting. In the
- // words of Andrew Kelley, "If you want to invert the bits of an
- // integer, zig has to know how many bits there are."
- //
- // For more insight into the Zig team's position on why the language
- // takes the approach it does with the ~ operator, take a look at
- // Andrew's comments on the following github issue:
- // https://github.com/ziglang/zig/issues/1382#issuecomment-414459529
- //
- // Whew, so after all that what we end up with is:
- // PORTB = PORTB & ~@as(u4, 1 << 2);
- //
- // We can shorten this with the &= combined AND and assignment operator,
- // which applies the AND operator on PORTB and then reassigns PORTB. Here's
- // what that looks like:
- // PORTB &= ~@as(u4, 1 << 2);
- //
- print("Clear pins with AND and NOT on PORTB\n", .{});
- print("------------------------------------\n", .{});
- PORTB = 0b1110; // reset PORTB
- print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB});
- print("& {b:0>4} // (bitmask)\n", .{0b1011});
- PORTB = PORTB & ???@as(u4, 1 << 2); // What character is missing here?
- checkAnswer(0b1010, PORTB);
- newline();
- PORTB = 0b0111; // reset PORTB
- print(" {b:0>4} // (reset state)\n", .{PORTB});
- print("& {b:0>4} // (bitmask)\n", .{0b1110});
- PORTB &= ~(1 << 0); // What's missing here?
- checkAnswer(0b0110, PORTB);
- newline();
- newline();
- // ------------------------------------------------------------------------
- // Conclusion
- // ------------------------------------------------------------------------
- //
- // While the examples in this exercise have used only 4-bit wide variables,
- // working with 8 bits is no different. Here's a an example where we set
- // every other bit beginning with the two's place:
- // var PORTD: u8 = 0b0000_0000;
- // print("PORTD: {b:0>8}\n", .{PORTD});
- // PORTD |= (1 << 1);
- // PORTD = setBit(u8, PORTD, 3);
- // PORTD |= (1 << 5) | (1 << 7);
- // print("PORTD: {b:0>8} // set every other bit\n", .{PORTD});
- // PORTD = ~PORTD;
- // print("PORTD: {b:0>8} // bits flipped with NOT (~)\n", .{PORTD});
- // newline();
- //
- // // Here we clear every other bit beginning with the two's place.
- //
- // PORTD = 0b1111_1111;
- // print("PORTD: {b:0>8}\n", .{PORTD});
- // PORTD &= ~@as(u8, 1 << 1);
- // PORTD = clearBit(u8, PORTD, 3);
- // PORTD &= ~@as(u8, (1 << 5) | (1 << 7));
- // print("PORTD: {b:0>8} // clear every other bit\n", .{PORTD});
- // PORTD = ~PORTD;
- // print("PORTD: {b:0>8} // bits flipped with NOT (~)\n", .{PORTD});
- // newline();
- }
- // ----------------------------------------------------------------------------
- // Here are some helper functions for manipulating bits
- // ----------------------------------------------------------------------------
- // Functions for setting, clearing, and toggling a single bit
- fn setBit(comptime T: type, byte: T, comptime bit_pos: T) !T {
- return byte | (1 << bit_pos);
- }
- test "setBit" {
- try testing.expectEqual(setBit(u8, 0b0000_0000, 3), 0b0000_1000);
- }
- fn clearBit(comptime T: type, byte: T, comptime bit_pos: T) T {
- return byte & ~@as(T, (1 << bit_pos));
- }
- test "clearBit" {
- try testing.expectEqual(clearBit(u8, 0b1111_1111, 0), 0b1111_1110);
- }
- fn toggleBit(comptime T: type, byte: T, comptime bit_pos: T) T {
- return byte ^ (1 << bit_pos);
- }
- test "toggleBit" {
- var byte = toggleBit(u8, 0b0000_0000, 0);
- try testing.expectEqual(byte, 0b0000_0001);
- byte = toggleBit(u8, byte, 0);
- try testing.expectEqual(byte, 0b0000_0000);
- }
- // ----------------------------------------------------------------------------
- // Some additional functions for setting, clearing, and toggling multiple bits
- // at once with a tuple because, hey, why not?
- // ----------------------------------------------------------------------------
- //
- fn createBitmask(comptime T: type, comptime bits: anytype) !T {
- comptime var bitmask: T = 0;
- inline for (bits) |bit| {
- if (bit >= @bitSizeOf(T)) return error.BitPosTooLarge;
- if (bit < 0) return error.BitPosTooSmall;
- bitmask |= (1 << bit);
- }
- return bitmask;
- }
- test "creating bitmasks from a tuple" {
- try testing.expectEqual(createBitmask(u8, .{0}), 0b0000_0001);
- try testing.expectEqual(createBitmask(u8, .{1}), 0b0000_0010);
- try testing.expectEqual(createBitmask(u8, .{2}), 0b0000_0100);
- try testing.expectEqual(createBitmask(u8, .{3}), 0b0000_1000);
- //
- try testing.expectEqual(createBitmask(u8, .{ 0, 4 }), 0b0001_0001);
- try testing.expectEqual(createBitmask(u8, .{ 1, 5 }), 0b0010_0010);
- try testing.expectEqual(createBitmask(u8, .{ 2, 6 }), 0b0100_0100);
- try testing.expectEqual(createBitmask(u8, .{ 3, 7 }), 0b1000_1000);
- try testing.expectError(error.BitPosTooLarge, createBitmask(u4, .{4}));
- }
- fn setBits(byte: u8, bits: anytype) !u8 {
- const bitmask = try createBitmask(u8, bits);
- return byte | bitmask;
- }
- test "setBits" {
- try testing.expectEqual(setBits(0b0000_0000, .{0}), 0b0000_0001);
- try testing.expectEqual(setBits(0b0000_0000, .{7}), 0b1000_0000);
- try testing.expectEqual(setBits(0b0000_0000, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_1111);
- try testing.expectEqual(setBits(0b1111_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_1111);
- try testing.expectEqual(setBits(0b0000_0000, .{ 2, 3, 4, 5 }), 0b0011_1100);
- try testing.expectError(error.BitPosTooLarge, setBits(0b1111_1111, .{8}));
- try testing.expectError(error.BitPosTooSmall, setBits(0b1111_1111, .{-1}));
- }
- fn clearBits(comptime byte: u8, comptime bits: anytype) !u8 {
- const bitmask: u8 = try createBitmask(u8, bits);
- return byte & ~@as(u8, bitmask);
- }
- test "clearBits" {
- try testing.expectEqual(clearBits(0b1111_1111, .{0}), 0b1111_1110);
- try testing.expectEqual(clearBits(0b1111_1111, .{7}), 0b0111_1111);
- try testing.expectEqual(clearBits(0b1111_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b000_0000);
- try testing.expectEqual(clearBits(0b0000_0000, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b000_0000);
- try testing.expectEqual(clearBits(0b1111_1111, .{ 0, 1, 6, 7 }), 0b0011_1100);
- try testing.expectError(error.BitPosTooLarge, clearBits(0b1111_1111, .{8}));
- try testing.expectError(error.BitPosTooSmall, clearBits(0b1111_1111, .{-1}));
- }
- fn toggleBits(comptime byte: u8, comptime bits: anytype) !u8 {
- const bitmask = try createBitmask(u8, bits);
- return byte ^ bitmask;
- }
- test "toggleBits" {
- try testing.expectEqual(toggleBits(0b0000_0000, .{0}), 0b0000_0001);
- try testing.expectEqual(toggleBits(0b0000_0000, .{7}), 0b1000_0000);
- try testing.expectEqual(toggleBits(0b1111_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b000_0000);
- try testing.expectEqual(toggleBits(0b0000_0000, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_1111);
- try testing.expectEqual(toggleBits(0b0000_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_0000);
- try testing.expectEqual(toggleBits(0b0000_1111, .{ 0, 1, 2, 3 }), 0b0000_0000);
- try testing.expectEqual(toggleBits(0b0000_0000, .{ 0, 2, 4, 6 }), 0b0101_0101);
- try testing.expectError(error.BitPosTooLarge, toggleBits(0b1111_1111, .{8}));
- try testing.expectError(error.BitPosTooSmall, toggleBits(0b1111_1111, .{-1}));
- }
- // ----------------------------------------------------------------------------
- // Utility functions
- // ----------------------------------------------------------------------------
- fn newline() void {
- print("\n", .{});
- }
- fn checkAnswer(expected: u4, answer: u4) void {
- if (expected != answer) {
- print("*************************************************************\n", .{});
- print("= {b:0>4} <- INCORRECT! THE EXPECTED OUTPUT IS {b:0>4}\n", .{ answer, expected });
- print("*************************************************************\n", .{});
- } else {
- print("= {b:0>4}", .{answer});
- }
- newline();
- }
|