summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Sisco <36649949+devspeare@users.noreply.github.com>2025-02-11 11:56:57 -0800
committerAlexander Sisco <36649949+devspeare@users.noreply.github.com>2025-02-11 11:56:57 -0800
commita7cd808bb8bdcb4e191c7bcbb427953a0461eac4 (patch)
tree2e6ff3d83c15de31532b57f0b2aeac98824d49c9
parent20596bc290404a56b2f23e2d95aa35137239e06f (diff)
moved explanatory content below the broken code in
main so that the exercise functions more like a quiz
-rw-r--r--exercises/110_quiz9.zig419
1 files changed, 236 insertions, 183 deletions
diff --git a/exercises/110_quiz9.zig b/exercises/110_quiz9.zig
index 4ac2032..171a509 100644
--- a/exercises/110_quiz9.zig
+++ b/exercises/110_quiz9.zig
@@ -68,40 +68,31 @@ const testing = std.testing;
pub fn main() !void {
var PORTB: u4 = 0b0000; // only 4 bits wide for simplicity
+
+ // The LCD display on our robot is not behaving as expected. In order to
+ // get it functioning properly, we must initialize it by sending the
+ // correct sequence of half-bytes to PORTB's lower four pins.
//
- // 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.
+ // See if you can solve the following problems to get the lcd working and
+ // reveal the message our robot has stored in his EEPROM.
//
- // - 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.
+ // .--. .--.
+ // | | | |
+ // +--------------------------+
+ // | +----------------------+ |
+ // | | | |
+ // | | XXXXXXXX XXXXXXXX | | <-- LCD
+ // | | | |
+ // | +----------------------+ |
+ // | _________ |
+ // | |_|_|_|_|_| |
+ // | |
+ // +--------------------------+
+ // | |
//
- // 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.
+ // 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 to see some in depth
+ // explanations on toggling, setting, and clearing bits in Zig.
print("Toggle pins with XOR on PORTB\n", .{});
print("-----------------------------\n", .{});
@@ -121,49 +112,6 @@ pub fn main() !void {
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", .{});
@@ -183,86 +131,6 @@ pub fn main() !void {
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", .{});
@@ -283,37 +151,222 @@ pub fn main() !void {
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();
}
+
+// ************************************************************************
+// 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 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.
+//
+// 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)
+
+// 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);
+//
+
+// ------------------------------------------------------------------------
+// Conclusion
+// ------------------------------------------------------------------------
+//
+// While the examples in this quiz 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
// ----------------------------------------------------------------------------