110_quiz9.zig 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. // ----------------------------------------------------------------------------
  2. // Quiz Time: Toggling, Setting, and Clearing Bits
  3. // ----------------------------------------------------------------------------
  4. //
  5. // Another exciting thing about Zig is its suitability for embedded
  6. // programming. Your Zig code doesn't have to remain on your laptop; you can
  7. // also deploy your code to microcontrollers! This means you can write Zig to
  8. // drive your next robot or greenhouse climate control system! Ready to enter
  9. // the exciting world of embedded programming? Let's get started!
  10. //
  11. // ----------------------------------------------------------------------------
  12. // Some Background
  13. // ----------------------------------------------------------------------------
  14. //
  15. // A common activity in microcontroller programming is setting and clearing
  16. // bits on input and output pins. This lets you control LEDs, sensors, motors
  17. // and more! In a previous exercise (097_bit_manipulation.zig) you learned how
  18. // to swap two bytes using the ^ (XOR - exclusive or) operator. This quiz will
  19. // test your knowledge of bit manipulationh in Zig while giving you a taste of
  20. // what it's like to control registers in a real microcontroller. Included at
  21. // the end are some helper functions that demonstrate how we might make our
  22. // code a little more readable.
  23. //
  24. // Below is a pinout diagram for the famous ATmega328 AVR microcontroller used
  25. // as the primary microchip on popular microcontroller platforms like the
  26. // Arduino UNO.
  27. //
  28. // ============ PINOUT DIAGRAM FOR ATMEGA328 MICROCONTROLLER ============
  29. // _____ _____
  30. // | U |
  31. // (RESET) PC6 --| 1 28 |-- PC5
  32. // PD0 --| 2 27 |-- PC4
  33. // PD1 --| 3 26 |-- PC3
  34. // PD2 --| 4 25 |-- PC2
  35. // PD3 --| 5 24 |-- PC1
  36. // PD4 --| 6 23 |-- PC0
  37. // VCC --| 7 22 |-- GND
  38. // GND --| 8 21 |-- AREF
  39. // |-- PB6 --| 9 20 |-- AVCC
  40. // |-- PB7 --| 10 19 |-- PB5 --|
  41. // | PD5 --| 11 18 |-- PB4 --|
  42. // | PD6 --| 12 17 |-- PB3 --|
  43. // | PD7 --| 13 16 |-- PB2 --|
  44. // |-- PB0 --| 14 15 |-- PB1 --|
  45. // | |___________| |
  46. // \_______________________________/
  47. // |
  48. // PORTB
  49. //
  50. // Drawing inspiration from this diagram, we'll use the pins for PORTB as our
  51. // mental model for this quiz on bit manipulation. It should be noted that
  52. // in the following problems we are using ordinary variables, one of which we
  53. // have named PORTB, to simulate modifying the bits of real hardware registers.
  54. // But in actual microcontroller code, PORTB would be defined something like
  55. // this:
  56. // pub const PORTB = @as(*volatile u8, @ptrFromInt(0x25));
  57. //
  58. // This lets the compiler know not to make any optimizations to PORTB so that
  59. // the IO pins are properly mapped to our code.
  60. //
  61. // NOTE : To keep things simple, the following problems are given using type
  62. // u4, so applying the output to PORTB would only affect the lower four pins
  63. // PB0..PB3. Of course, there is nothing to prevent you from swapping the u4
  64. // with a u8 so you can control all 8 of PORTB's IO pins.
  65. const std = @import("std");
  66. const print = std.debug.print;
  67. const testing = std.testing;
  68. pub fn main() !void {
  69. var PORTB: u4 = 0b0000; // only 4 bits wide for simplicity
  70. // The LCD display on our robot is not behaving as expected. In order to
  71. // get it functioning properly, we must initialize it by sending the
  72. // correct sequence of half-bytes to PORTB's lower four pins.
  73. //
  74. // See if you can solve the following problems to get the lcd working and
  75. // reveal the message our robot has stored in his EEPROM.
  76. //
  77. // .--. .--.
  78. // | | | |
  79. // +--------------------------+
  80. // | +----------------------+ |
  81. // | | | |
  82. // | | XXXXXXXX XXXXXXXX | | <-- LCD
  83. // | | | |
  84. // | +----------------------+ |
  85. // | _________ |
  86. // | |_|_|_|_|_| |
  87. // | |
  88. // +--------------------------+
  89. // | |
  90. //
  91. // The last two problems throw you a bit of a curve ball. Try solving them
  92. // on your own. If you need help, scroll to the bottom to see some in depth
  93. // explanations on toggling, setting, and clearing bits in Zig.
  94. print("Toggle pins with XOR on PORTB\n", .{});
  95. print("-----------------------------\n", .{});
  96. PORTB = 0b1100;
  97. print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB});
  98. print("^ {b:0>4} // (bitmask)\n", .{0b0101});
  99. PORTB ^= (1 << 1) | (1 << 0); // What's wrong here?
  100. checkAnswer(0b1001, PORTB);
  101. newline();
  102. PORTB = 0b1100;
  103. print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB});
  104. print("^ {b:0>4} // (bitmask)\n", .{0b0011});
  105. PORTB ^= (1 << 1) & (1 << 0); // What's wrong here?
  106. checkAnswer(0b1111, PORTB);
  107. newline();
  108. print("Set pins with OR on PORTB\n", .{});
  109. print("-------------------------\n", .{});
  110. PORTB = 0b1001; // reset PORTB
  111. print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB});
  112. print("| {b:0>4} // (bitmask)\n", .{0b0100});
  113. PORTB = PORTB ??? (1 << 2); // What's missing here?
  114. checkAnswer(0b1101, PORTB);
  115. newline();
  116. PORTB = 0b1001; // reset PORTB
  117. print(" {b:0>4} // (reset state)\n", .{PORTB});
  118. print("| {b:0>4} // (bitmask)\n", .{0b0100});
  119. PORTB ??? (1 << 2); // What's missing here?
  120. checkAnswer(0b1101, PORTB);
  121. newline();
  122. print("Clear pins with AND and NOT on PORTB\n", .{});
  123. print("------------------------------------\n", .{});
  124. PORTB = 0b1110; // reset PORTB
  125. print(" {b:0>4} // (initial state of PORTB)\n", .{PORTB});
  126. print("& {b:0>4} // (bitmask)\n", .{0b1011});
  127. PORTB = PORTB & ???@as(u4, 1 << 2); // What character is missing here?
  128. checkAnswer(0b1010, PORTB);
  129. newline();
  130. PORTB = 0b0111; // reset PORTB
  131. print(" {b:0>4} // (reset state)\n", .{PORTB});
  132. print("& {b:0>4} // (bitmask)\n", .{0b1110});
  133. PORTB &= ~(1 << 0); // What's missing here?
  134. checkAnswer(0b0110, PORTB);
  135. newline();
  136. newline();
  137. }
  138. // ************************************************************************
  139. // IN-DEPTH EXPLANATIONS BELOW
  140. // ************************************************************************
  141. //
  142. //
  143. //
  144. //
  145. //
  146. //
  147. //
  148. //
  149. //
  150. //
  151. //
  152. // ------------------------------------------------------------------------
  153. // Toggling bits with XOR:
  154. // ------------------------------------------------------------------------
  155. // XOR stands for "exclusive or". We can toggle bits with the ^ (XOR)
  156. // bitwise operator, like so:
  157. //
  158. //
  159. // In order to output a 1, the logic of an XOR operation requires that the
  160. // two input bits are of different values. Therefore, 0 ^ 1 and 1 ^ 0 will
  161. // both yield a 1 but 0 ^ 0 and 1 ^ 1 will output 0. XOR's unique behavior
  162. // of outputing a 0 when both inputs are 1s is what makes it different from
  163. // the OR operator; it also gives us the ability to toggle bits by putting
  164. // 1s into our bitmask.
  165. //
  166. // - 1s in our bitmask operand, can be thought of as causing the
  167. // corresponding bits in the other operand to flip to the opposite value.
  168. // - 0s cause no change.
  169. //
  170. // The 0s in our bitmask preserve these values
  171. // -XOR op- ---expanded--- in the output.
  172. // _______________/
  173. // / /
  174. // 0110 1 1 0 0
  175. // ^ 1111 0 1 0 1 (bitmask)
  176. // ------ - - - -
  177. // = 1001 1 0 0 1 <- This bit was already cleared.
  178. // \_______\
  179. // \
  180. // We can think of these bits having flipped
  181. // because of the presence of 1s in those columns
  182. // of our bitmask.
  183. //
  184. // Now let's take a look at setting bits with the | operator.
  185. //
  186. //
  187. //
  188. //
  189. //
  190. // ------------------------------------------------------------------------
  191. // Setting bits with OR:
  192. // ------------------------------------------------------------------------
  193. // We can set bits on PORTB with the | (OR) operator, like so:
  194. //
  195. // var PORTB: u4 = 0b1001;
  196. // PORTB = PORTB | 0b0010;
  197. // print("PORTB: {b:0>4}\n", .{PORTB}); // output: 1011
  198. //
  199. // -OR op- ---expanded---
  200. // _ Set only this bit.
  201. // /
  202. // 1001 1 0 0 1
  203. // | 0010 0 0 1 0 (bit mask)
  204. // ------ - - - -
  205. // = 1011 1 0 1 1
  206. // \___\_______\
  207. // \
  208. // These bits remain untouched because OR-ing with
  209. // a 0 effects no change.
  210. //
  211. // ------------------------------------------------------------------------
  212. // To create a bit mask like 0b0010 used above:
  213. //
  214. // 1. First, shift the value 1 over one place with the bitwise << (shift
  215. // left) operator as indicated below:
  216. // 1 << 0 -> 0001
  217. // 1 << 1 -> 0010 <-- Shift 1 one place to the left
  218. // 1 << 2 -> 0100
  219. // 1 << 3 -> 1000
  220. //
  221. // This allows us to rewrite the above code like this:
  222. //
  223. // var PORTB: u4 = 0b1001;
  224. // PORTB = PORTB | (1 << 1);
  225. // print("PORTB: {b:0>4}\n", .{PORTB}); // output: 1011
  226. //
  227. // Finally, as in the C language, Zig allows us to use the |= operator, so
  228. // we can rewrite our code again in an even more compact and idiomatic
  229. // form: PORTB |= (1 << 1)
  230. // So now we've covered how to toggle and set bits. What about clearing
  231. // them? Well, this is where Zig throws us a curve ball. Don't worry we'll
  232. // go through it step by step.
  233. //
  234. //
  235. //
  236. //
  237. //
  238. // ------------------------------------------------------------------------
  239. // Clearing bits with AND and NOT:
  240. // ------------------------------------------------------------------------
  241. // We can clear bits with the & (AND) bitwise operator, like so:
  242. // PORTB = 0b1110; // reset PORTB
  243. // PORTB = PORTB & 0b1011;
  244. // print("PORTB: {b:0>4}\n", .{PORTB}); // output -> 1010
  245. //
  246. // - 0s clear bits when used in conjuction with a bitwise AND.
  247. // - 1s do nothing, thus preserving the original bits.
  248. //
  249. // -AND op- ---expanded---
  250. // __________ Clear only this bit.
  251. // /
  252. // 1110 1 1 1 0
  253. // & 1011 1 0 1 1 (bit mask)
  254. // ------ - - - -
  255. // = 1010 1 0 1 0 <- This bit was already cleared.
  256. // \_______\
  257. // \
  258. // These bits remain untouched because AND-ing with a
  259. // 1 preserves the original bit value whether 0 or 1.
  260. //
  261. // ------------------------------------------------------------------------
  262. // We can use the ~ (NOT) operator to easily create a bit mask like 1011:
  263. //
  264. // 1. First, shift the value 1 over two places with the bit-wise << (shift
  265. // left) operator as indicated below:
  266. // 1 << 0 -> 0001
  267. // 1 << 1 -> 0010
  268. // 1 << 2 -> 0100 <- The 1 has been shifted two places to the left
  269. // 1 << 3 -> 1000
  270. //
  271. // 2. The second step in creating our bit mask is to invert the bits
  272. // ~0100 -> 1011
  273. // in C we would write this as:
  274. // ~(1 << 2) -> 1011
  275. //
  276. // But if we try to compile ~(1 << 2) in Zig, we'll get an error:
  277. // unable to perform binary not operation on type 'comptime_int'
  278. //
  279. // Before Zig can invert our bits, it needs to know the number of
  280. // bits it's being asked to invert.
  281. //
  282. // We do this with the @as (cast as) built-in like this:
  283. // @as(u4, 1 << 2) -> 0100
  284. //
  285. // Finally, we can invert our new mask by placing the NOT ~ operator
  286. // before our expression, like this:
  287. // ~@as(u4, 1 << 2) -> 1011
  288. //
  289. // If you are offput by the fact that you can't simply invert bits like
  290. // you can in languages such as C without casting to a particular size
  291. // of integer, you're not alone. However, this is actually another
  292. // instance where Zig is really helpful because it protects you from
  293. // difficult to debug integer overflow bugs that can have you tearing
  294. // your hair out. In the interest of keeping things sane, Zig requires
  295. // you simply to tell it the size of number you are inverting. In the
  296. // words of Andrew Kelley, "If you want to invert the bits of an
  297. // integer, zig has to know how many bits there are."
  298. //
  299. // For more insight into the Zig team's position on why the language
  300. // takes the approach it does with the ~ operator, take a look at
  301. // Andrew's comments on the following github issue:
  302. // https://github.com/ziglang/zig/issues/1382#issuecomment-414459529
  303. //
  304. // Whew, so after all that what we end up with is:
  305. // PORTB = PORTB & ~@as(u4, 1 << 2);
  306. //
  307. // We can shorten this with the &= combined AND and assignment operator,
  308. // which applies the AND operator on PORTB and then reassigns PORTB. Here's
  309. // what that looks like:
  310. // PORTB &= ~@as(u4, 1 << 2);
  311. //
  312. // ------------------------------------------------------------------------
  313. // Conclusion
  314. // ------------------------------------------------------------------------
  315. //
  316. // While the examples in this quiz have used only 4-bit wide variables,
  317. // working with 8 bits is no different. Here's a an example where we set
  318. // every other bit beginning with the two's place:
  319. // var PORTD: u8 = 0b0000_0000;
  320. // print("PORTD: {b:0>8}\n", .{PORTD});
  321. // PORTD |= (1 << 1);
  322. // PORTD = setBit(u8, PORTD, 3);
  323. // PORTD |= (1 << 5) | (1 << 7);
  324. // print("PORTD: {b:0>8} // set every other bit\n", .{PORTD});
  325. // PORTD = ~PORTD;
  326. // print("PORTD: {b:0>8} // bits flipped with NOT (~)\n", .{PORTD});
  327. // newline();
  328. //
  329. // // Here we clear every other bit beginning with the two's place.
  330. //
  331. // PORTD = 0b1111_1111;
  332. // print("PORTD: {b:0>8}\n", .{PORTD});
  333. // PORTD &= ~@as(u8, 1 << 1);
  334. // PORTD = clearBit(u8, PORTD, 3);
  335. // PORTD &= ~@as(u8, (1 << 5) | (1 << 7));
  336. // print("PORTD: {b:0>8} // clear every other bit\n", .{PORTD});
  337. // PORTD = ~PORTD;
  338. // print("PORTD: {b:0>8} // bits flipped with NOT (~)\n", .{PORTD});
  339. // newline();
  340. // ----------------------------------------------------------------------------
  341. // Here are some helper functions for manipulating bits
  342. // ----------------------------------------------------------------------------
  343. // Functions for setting, clearing, and toggling a single bit
  344. fn setBit(comptime T: type, byte: T, comptime bit_pos: T) !T {
  345. return byte | (1 << bit_pos);
  346. }
  347. test "setBit" {
  348. try testing.expectEqual(setBit(u8, 0b0000_0000, 3), 0b0000_1000);
  349. }
  350. fn clearBit(comptime T: type, byte: T, comptime bit_pos: T) T {
  351. return byte & ~@as(T, (1 << bit_pos));
  352. }
  353. test "clearBit" {
  354. try testing.expectEqual(clearBit(u8, 0b1111_1111, 0), 0b1111_1110);
  355. }
  356. fn toggleBit(comptime T: type, byte: T, comptime bit_pos: T) T {
  357. return byte ^ (1 << bit_pos);
  358. }
  359. test "toggleBit" {
  360. var byte = toggleBit(u8, 0b0000_0000, 0);
  361. try testing.expectEqual(byte, 0b0000_0001);
  362. byte = toggleBit(u8, byte, 0);
  363. try testing.expectEqual(byte, 0b0000_0000);
  364. }
  365. // ----------------------------------------------------------------------------
  366. // Some additional functions for setting, clearing, and toggling multiple bits
  367. // at once with a tuple because, hey, why not?
  368. // ----------------------------------------------------------------------------
  369. //
  370. fn createBitmask(comptime T: type, comptime bits: anytype) !T {
  371. comptime var bitmask: T = 0;
  372. inline for (bits) |bit| {
  373. if (bit >= @bitSizeOf(T)) return error.BitPosTooLarge;
  374. if (bit < 0) return error.BitPosTooSmall;
  375. bitmask |= (1 << bit);
  376. }
  377. return bitmask;
  378. }
  379. test "creating bitmasks from a tuple" {
  380. try testing.expectEqual(createBitmask(u8, .{0}), 0b0000_0001);
  381. try testing.expectEqual(createBitmask(u8, .{1}), 0b0000_0010);
  382. try testing.expectEqual(createBitmask(u8, .{2}), 0b0000_0100);
  383. try testing.expectEqual(createBitmask(u8, .{3}), 0b0000_1000);
  384. //
  385. try testing.expectEqual(createBitmask(u8, .{ 0, 4 }), 0b0001_0001);
  386. try testing.expectEqual(createBitmask(u8, .{ 1, 5 }), 0b0010_0010);
  387. try testing.expectEqual(createBitmask(u8, .{ 2, 6 }), 0b0100_0100);
  388. try testing.expectEqual(createBitmask(u8, .{ 3, 7 }), 0b1000_1000);
  389. try testing.expectError(error.BitPosTooLarge, createBitmask(u4, .{4}));
  390. }
  391. fn setBits(byte: u8, bits: anytype) !u8 {
  392. const bitmask = try createBitmask(u8, bits);
  393. return byte | bitmask;
  394. }
  395. test "setBits" {
  396. try testing.expectEqual(setBits(0b0000_0000, .{0}), 0b0000_0001);
  397. try testing.expectEqual(setBits(0b0000_0000, .{7}), 0b1000_0000);
  398. try testing.expectEqual(setBits(0b0000_0000, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_1111);
  399. try testing.expectEqual(setBits(0b1111_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_1111);
  400. try testing.expectEqual(setBits(0b0000_0000, .{ 2, 3, 4, 5 }), 0b0011_1100);
  401. try testing.expectError(error.BitPosTooLarge, setBits(0b1111_1111, .{8}));
  402. try testing.expectError(error.BitPosTooSmall, setBits(0b1111_1111, .{-1}));
  403. }
  404. fn clearBits(comptime byte: u8, comptime bits: anytype) !u8 {
  405. const bitmask: u8 = try createBitmask(u8, bits);
  406. return byte & ~@as(u8, bitmask);
  407. }
  408. test "clearBits" {
  409. try testing.expectEqual(clearBits(0b1111_1111, .{0}), 0b1111_1110);
  410. try testing.expectEqual(clearBits(0b1111_1111, .{7}), 0b0111_1111);
  411. try testing.expectEqual(clearBits(0b1111_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b000_0000);
  412. try testing.expectEqual(clearBits(0b0000_0000, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b000_0000);
  413. try testing.expectEqual(clearBits(0b1111_1111, .{ 0, 1, 6, 7 }), 0b0011_1100);
  414. try testing.expectError(error.BitPosTooLarge, clearBits(0b1111_1111, .{8}));
  415. try testing.expectError(error.BitPosTooSmall, clearBits(0b1111_1111, .{-1}));
  416. }
  417. fn toggleBits(comptime byte: u8, comptime bits: anytype) !u8 {
  418. const bitmask = try createBitmask(u8, bits);
  419. return byte ^ bitmask;
  420. }
  421. test "toggleBits" {
  422. try testing.expectEqual(toggleBits(0b0000_0000, .{0}), 0b0000_0001);
  423. try testing.expectEqual(toggleBits(0b0000_0000, .{7}), 0b1000_0000);
  424. try testing.expectEqual(toggleBits(0b1111_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b000_0000);
  425. try testing.expectEqual(toggleBits(0b0000_0000, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_1111);
  426. try testing.expectEqual(toggleBits(0b0000_1111, .{ 0, 1, 2, 3, 4, 5, 6, 7 }), 0b1111_0000);
  427. try testing.expectEqual(toggleBits(0b0000_1111, .{ 0, 1, 2, 3 }), 0b0000_0000);
  428. try testing.expectEqual(toggleBits(0b0000_0000, .{ 0, 2, 4, 6 }), 0b0101_0101);
  429. try testing.expectError(error.BitPosTooLarge, toggleBits(0b1111_1111, .{8}));
  430. try testing.expectError(error.BitPosTooSmall, toggleBits(0b1111_1111, .{-1}));
  431. }
  432. // ----------------------------------------------------------------------------
  433. // Utility functions
  434. // ----------------------------------------------------------------------------
  435. fn newline() void {
  436. print("\n", .{});
  437. }
  438. fn checkAnswer(expected: u4, answer: u4) void {
  439. if (expected != answer) {
  440. print("*************************************************************\n", .{});
  441. print("= {b:0>4} <- INCORRECT! THE EXPECTED OUTPUT IS {b:0>4}\n", .{ answer, expected });
  442. print("*************************************************************\n", .{});
  443. } else {
  444. print("= {b:0>4}", .{answer});
  445. }
  446. newline();
  447. }