summaryrefslogtreecommitdiff
path: root/exercises/113_quiz9.zig
diff options
context:
space:
mode:
authorChris Boesch <chrboesch@noreply.codeberg.org>2026-04-03 19:32:53 +0200
committerChris Boesch <chrboesch@noreply.codeberg.org>2026-04-03 19:32:53 +0200
commit5307b2a338a92130bc498fb1dc7d21a9fd1b0db4 (patch)
tree51279ca4fbd7bd90294dd563640c12a8c25c79c6 /exercises/113_quiz9.zig
parent3056a2b5442f2f1ec58db3f3493109064ad2a2a5 (diff)
parentf6a6798c8b6b813bd2ceee81db276e05327a76e0 (diff)
Merge pull request 'revival of the async-io functions' (#383) from asyncIo into main
Reviewed-on: https://codeberg.org/ziglings/exercises/pulls/383
Diffstat (limited to 'exercises/113_quiz9.zig')
-rw-r--r--exercises/113_quiz9.zig484
1 files changed, 484 insertions, 0 deletions
diff --git a/exercises/113_quiz9.zig b/exercises/113_quiz9.zig
new file mode 100644
index 0000000..8f5cb61
--- /dev/null
+++ b/exercises/113_quiz9.zig
@@ -0,0 +1,484 @@
+// ----------------------------------------------------------------------------
+// Quiz Time: 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? Let's get started!
+//
+// ----------------------------------------------------------------------------
+// Some Background
+// ----------------------------------------------------------------------------
+//
+// 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. This quiz will
+// test your knowledge of bit manipulation in Zig while giving you a taste of
+// what it's like to control registers in a real microcontroller. Included at
+// the end 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 quiz on bit manipulation. It should be noted that
+// in the following problems 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 problems 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
+
+ // ------------------------------------------------------------------------
+ // Quiz
+ // ------------------------------------------------------------------------
+
+ // See if you can solve the following problems. The last two problems throw
+ // you a bit of a curve ball. Try solving them on your own. If you need
+ // help, scroll to the bottom of main to see some in depth explanations on
+ // toggling, setting, and clearing bits in Zig.
+
+ 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();
+
+ 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();
+
+ 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();
+}
+
+// ************************************************************************
+// IN-DEPTH EXPLANATIONS BELOW
+// ************************************************************************
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+// ------------------------------------------------------------------------
+// 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 outputting 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.
+// _______________/
+// / /
+// 1100 1 1 0 0
+// ^ 0101 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.
+//
+// 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 (bitmask)
+// ------ - - - -
+// = 1011 1 0 1 1
+// \___\_______\
+// \
+// These bits remain untouched because OR-ing with
+// a 0 effects no change.
+//
+// ------------------------------------------------------------------------
+// To create a bitmask 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)
+
+// 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 conjunction 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 (bitmask)
+// ------ - - - -
+// = 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 bitmask 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 bitmask 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);
+//
+
+// ------------------------------------------------------------------------
+// Conclusion
+// ------------------------------------------------------------------------
+//
+// While the examples in this quiz have used only 4-bit wide variables,
+// working with 8 bits is no different. Here's 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();
+}