diff options
| author | Chris Boesch <chrboesch@noreply.codeberg.org> | 2026-04-03 13:35:56 +0200 |
|---|---|---|
| committer | Chris Boesch <chrboesch@noreply.codeberg.org> | 2026-04-03 13:35:56 +0200 |
| commit | e0259f43a726f61da14686de802021fcdb9aacd0 (patch) | |
| tree | 57359102e5e898289b91ada2d65ca742ec118c8b /exercises/110_quiz9.zig | |
| parent | ffde357f303e7459a12cfe4b785ae9e8ef9ebe30 (diff) | |
Insert space for additional async exercises
Diffstat (limited to 'exercises/110_quiz9.zig')
| -rw-r--r-- | exercises/110_quiz9.zig | 484 |
1 files changed, 0 insertions, 484 deletions
diff --git a/exercises/110_quiz9.zig b/exercises/110_quiz9.zig deleted file mode 100644 index 8f5cb61..0000000 --- a/exercises/110_quiz9.zig +++ /dev/null @@ -1,484 +0,0 @@ -// ---------------------------------------------------------------------------- -// 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(); -} |
