Silicon ChipMax’s Cool Beans - July 2023 SILICON CHIP
  1. Outer Front Cover
  2. Contents
  3. Subscriptions: PE Subscription
  4. Subscriptions
  5. Back Issues: Hare & Forbes Machineryhouse
  6. Publisher's Letter: Check your meter
  7. Feature: AI and robots – what could possibly go wrong? by Max the Magnificent
  8. Feature: The Fox Report by Barry Fox
  9. Feature: Net Work by Alan Winstanley
  10. Project: MIDI SYNTHESISER by JEREMY LEACH
  11. Project: Multimeter -Checker -Calibrator by Tim Blythman
  12. Feature: MOS metal oxide semiconductor Air Quality Sensors by Jim Rowe
  13. Feature: KickStart by MIKE TOOLEY
  14. Feature: Circuit Surgery by Ian Bell
  15. Feature: Max’s Cool Beans by Max the Magnificent
  16. Feature: AUDIO OUT by Jake Rothman
  17. PCB Order Form
  18. Advertising Index

This is only a preview of the July 2023 issue of Practical Electronics.

You can view 0 of the 72 pages in the full issue.

Articles in this series:
  • (November 2020)
  • Techno Talk (December 2020)
  • Techno Talk (January 2021)
  • Techno Talk (February 2021)
  • Techno Talk (March 2021)
  • Techno Talk (April 2021)
  • Techno Talk (May 2021)
  • Techno Talk (June 2021)
  • Techno Talk (July 2021)
  • Techno Talk (August 2021)
  • Techno Talk (September 2021)
  • Techno Talk (October 2021)
  • Techno Talk (November 2021)
  • Techno Talk (December 2021)
  • Communing with nature (January 2022)
  • Should we be worried? (February 2022)
  • How resilient is your lifeline? (March 2022)
  • Go eco, get ethical! (April 2022)
  • From nano to bio (May 2022)
  • Positivity follows the gloom (June 2022)
  • Mixed menu (July 2022)
  • Time for a total rethink? (August 2022)
  • What’s in a name? (September 2022)
  • Forget leaves on the line! (October 2022)
  • Giant Boost for Batteries (December 2022)
  • Raudive Voices Revisited (January 2023)
  • A thousand words (February 2023)
  • It’s handover time (March 2023)
  • AI, Robots, Horticulture and Agriculture (April 2023)
  • Prophecy can be perplexing (May 2023)
  • Technology comes in different shapes and sizes (June 2023)
  • AI and robots – what could possibly go wrong? (July 2023)
  • How long until we’re all out of work? (August 2023)
  • We both have truths, are mine the same as yours? (September 2023)
  • Holy Spheres, Batman! (October 2023)
  • Where’s my pneumatic car? (November 2023)
  • Good grief! (December 2023)
  • Cheeky chiplets (January 2024)
  • Cheeky chiplets (February 2024)
  • The Wibbly-Wobbly World of Quantum (March 2024)
  • Techno Talk - Wait! What? Really? (April 2024)
  • Techno Talk - One step closer to a dystopian abyss? (May 2024)
  • Techno Talk - Program that! (June 2024)
  • Techno Talk (July 2024)
  • Techno Talk - That makes so much sense! (August 2024)
  • Techno Talk - I don’t want to be a Norbert... (September 2024)
  • Techno Talk - Sticking the landing (October 2024)
  • Techno Talk (November 2024)
  • Techno Talk (December 2024)
  • Techno Talk (January 2025)
  • Techno Talk (February 2025)
  • Techno Talk (March 2025)
  • Techno Talk (April 2025)
  • Techno Talk (May 2025)
  • Techno Talk (June 2025)
Max’s Cool Beans By Max the Magnificent Arduino Bootcamp – Part 7 I can’t believe we are already slip-sliding our way into the second half of the year. Sad to relate, I haven’t prepared a speech and I don’t have anything appropriate to wear. When I was a kid, it seemed elderly folks liked nothing better than to endlessly babble about how time seems to pass faster the older you get. You can only imagine my surprise to discover that the ancients actually had a clue as to what they were droning on about. Now, when I blink, another day disappears. When I sneeze, I lose another week. And if I… but we digress. Round, square, curly… I really enjoy receiving emails from members of the PE community, especially communiques that commence by conveying how wonderful I am (hint, hint). By some strange quirk of fate, I recently received such a missive from someone we’ll call Alan (because that’s his name). After making mention of how much he was enjoying our Arduino Bootcamp series (well played Alan), he continued as follows: ‘Whilst I have done some programming in the past, including BBC BASIC and assembly language for use on PIC micros, C/C++ is new to me, so I appreciate the way in which your series explains how the programs are built up. Having said this, I do have difficulty with some of the syntax, especially when to use curly brackets.’ ‘Well, strike me with a kipper,’ I thought (I think this a lot, but don’t ask me why), ‘if there’s one topic I love to waffle about endlessly, that topic is brackets in general and curly brackets in particular’ (I really do need to get out more). As you are doubtless aware, there are four primary types of brackets (Fig.1). In British usage they are known as: ‘round brackets’ (or simply ‘brackets’), ‘square brackets’, ‘curly brackets’ (known to some as ‘squiggly brackets’), and ‘angle brackets’; in American usage these are () [] {} <> Round brackets or parentheses Square brackets or brackets Curly (squiggly) brackets or braces Angle brackets or chevrons Fig.1. Different types of brackets. 56 respectively known as: ‘parentheses’, ‘brackets’, ‘braces’, and ‘chevrons’. Different programming languages make use of some or all of these symbols for diverse purposes. In fact, the same language can employ the same symbols in multiple roles. Don’t panic! This is nothing we’re not used to. Consider the artful apostrophe, for example, which can serve to indicate possessive cases (Harry’s hat), contractions (Harry’s lost his hat), and omitted letters (‘Arry’s ‘at) in regular written English. Squiggly wiggly As we previously discussed in a couple of Coding Tips and Tricks columns (PE, April and June 2020), in the case of C/ C++, one use of curly (squiggly) braces is to denote a block of code in which variables can be declared. The most obvious example of this is as part of a function declaration and definition, for example: void MyFunction () // Declaration { // Zero or more Definition // statements } Remember that, unlike some languages, C and C++ really don’t care overmuch about whitespace characters such as the space, tab (ie, horizontal tab), vertical tab, line feed (newline), carriage return and form feed. Having said this, my personal preference is for { } pairs to be vertically aligned, as shown above, because this makes our code easier to parse and debug. Also, if I’m using a simple editor, such as the one found in Version 1 of the Arduino’s integrated development environment (IDE), I prefer to use 4-space indentation. In the case of a more sophisticated editor, such as the one found in Arduino IDE V2 (the version we decided we were going to use at the beginning of this series), which automatically indicates indented blocks of code, I use 2-space indentation to match what the editor wants to do. Observe the comment in the above code saying, Zero or more statements. Why would we wish to create a function with nothing in it? Well, as we noted in an earlier column (PE, April 2023), one way to approach creating a new program is to start off with a skeleton (bare bones) framework, and this can include using one or more stub (empty) functions whose purpose is to check that they can be called from elsewhere in the program and that everything hangs together. We can return to fill in their contents later. When we talk about the ‘scope’ of a variable, we are referring to the extent that this variable can be ‘seen’ by various portions of the program. For example, suppose we declare two integer variables as illustrated below: int MyGlobalInt; void MyFunction () { int myLocalInt; // More statements } In this case, MyGlobalInt is said to be a ‘global variable’ because it’s declared at the top level of the program outside of any functions. This means that its scope is the entire program, and it can be seen and modified by any of the functions forming the program. By comparison, myLocalInt is said to be a ‘local variable’ because it’s declared inside MyFunction(). In this case, its scope is localised to MyFunction() and it cannot be seen or modified by any other functions. It also means that this variable is instantiated (brought into existence) when the function is called, and it ‘evaporates into the ether’ when the function terminates and returns control to whatever called it. This explains how we can declare variables of the same types and/or names in multiple functions without them being related to each other in any respect. As a little reminder, I use the camel case naming convention for my function and variable names. With this convention, which is also known as Pascal case, multiple words are joined together without spaces. When it comes to global variables and function names, I use UpperCamelCase in which the first letter Practical Electronics | July | 2023 of every word is capitalised. By comparison, with local variables, I use lowerCamelCase in which the first word is all lowercase, including the first letter. Using this convention makes it easy to differentiate between global and local entities when reading and writing code. and c. Somewhere in the function we might have a series of three statements as follows: Building blocks If you wish, you can use pairs of curly brackets inside a function to gather groups of statements together to make it clear to yourself and anyone else that you consider these statements to be related. These are often referred to as ‘blocks,’ and using this technique may be referred to as ‘block programming.’ Of particular interest to us here is that variables can be declared within a block, in which case the scope of those variables is limited to the block in which they are declared. Consider the following code snippet, for example: Observe how each of these statements is terminated by a semicolon. Now suppose we decide to gather these statements together in a block as shown below: void MyFunction () { int myLocalIntA; // More statements { // Start of block int myLocalIntB; // More statements } // End of block } In this case, the scope of myLocalIntA is the entire function, which means it can also be seen and modified from within our block. By comparison, the scope of myLocalIntB is limited to the block within which it is declared. Furthermore, this also means that myLocalIntB is instantiated (brought into existence) when it is declared at the beginning of the block and it disappears at the end of the block. A little thought brings us to the realisation that we can have multiple blocks in the same function and (if we so desire) we can declare variables of the same type and/or name in two or more of these blocks, where these variables will be totally distinct from each other. As one final point, we can have nested curly brackets, for example { { } }, { { { } } }, { { } { } { } } and so on, which means we can have blocks within blocks ad infinitum. Making a statement Computer programs are made up of a series of statements. Each statement instructs the computer to perform a specific action. Different programming languages do things in different ways. In C/C++, a statement is terminated with a semicolon ‘;’ character. Let’s assume we’ve already created a function, as part of which we’ve declared three local integer variables called a, b, Practical Electronics | July | 2023 a = 6; b = 4; c = a + b; { a = 6; b = 4; c = a + b; } In this case, observe that we still need to terminate each of the individual statements with semicolons, but we don’t need (cannot use) a semicolon following the ‘}’ that terminates the block. Similarly, we don’t need (cannot use) a semicolon following the ‘}’ that terminates the function itself. The word ‘compound’ is defined as something that is composed of two or more separate elements. This explains why another name for blocks is ‘compound statements’ because they are composed of two or more simple statements. There’s a condition We introduced the concept of if() conditional statements earlier in this series (PE, March and April 2023). Our newfound knowledge regarding curly brackets and compound statements (blocks) should serve to clarify our previous discussions. For example, assuming we’ve declared some local integer variables called a, b, c, d and e, we could have an if() associated with a single statement as follows: if (a > b) c = 10; In English this equates to ‘If a is greater than b then make c equal to 10.’ As we noted earlier, the C/C++ languages don’t care overmuch about whitespace characters, so some programmers would write this statement as follows: if (a > b) c = 10; The main thing to note is that, in both of the above cases, the entire statement is terminated by a semicolon. Alternatively, we could associate a compound statement with our condition as follows: if (a > b) { c = 6; d = 4; e = c + d; } As usual, each of the simple statements is terminated with a semicolon, but don’t need (cannot use) a semicolon following the ‘}’ that terminates the compound statement. It’s also worth reminding ourselves that, even if we have only a single statement associated with an if() condition, my personal preference is to still employ a { } block as illustrated below: if (a > b) { c = 10; } Although this may appear to be a case of ‘overkill,’ it makes things a whole lot clearer when you have multiple levels of if() … else if() … else() conditions. Also, it makes one’s life a whole lot simpler when adding (and later removing) additional temporary statements while debugging recalcitrant code. An array of confusion I feel confident that things would remain relatively clear if we could terminate our discussions of curly brackets at this point. Unfortunately, these little scamps are used for additional purposes, such as the initialisation of arrays (also the initialisation of enum and struct constructs, which we have not yet introduced). We said hello to arrays in an earlier column (PE, February 2023). Suppose we declare a 3-element integer array called x, as illustrated below: int x[3]; Observe the use of the square brackets to (a) indicate that this is an array and (b) specify the size of the array. Remember that we always start counting from zero, so the elements of this array will be numbered (indexed) as 0, 1, and 2. Later in the program, we might load values into our array as follows (these values have no meaning beyond serving as an example): x[0] = 7; x[1] = 2; x[2] = 5; In this case, we are using square brackets to indicate elements of interest. Alternatively, we can use curly brackets to initialise our array as part of its declaration as illustrated below: int x[3] = {7, 2, 5}; There are several points to observe here. First, the initialisation values are commaseparated. Second, no comma is required (or allowed) after the final value. Third, since this entire construct is a statement, it’s terminated with a semicolon, which appears outside the closing ‘}’ character. 57 5V 10kΩ 10kΩ A0 A1 S W0 S W1 GND To the Arduino’s A0 analogue pin To the Arduino’s A1 analogue pin Fig.2. Adding a second pushbutton switch. The fact that a semicolon is required here but not to terminate a compound statement can be endlessly confusing to beginners, but it all makes sense when you think about it. Trust me, have I ever lied to you before? You don’t know? Well, that just goes to show how good I am. As you may recall, we’ve seen something like this before when we first declared and initialised the array defining the segments that correspond to the numerical characters 0 to 9 on our 7-segment display (PE, April 2023): byte DigitSegs[NUM_DIGITS] = { // ABCDEFGDP B11111100, // 0 B01100000, // 1 B11011010, // 2 B11110010, // 3 B01100110, // 4 B10110110, // 5 B10111110, // 6 B11100000, // 7 B11111110, // 8 B11110110 // 9 }; Note the lack of a comma after the final value and the presence of a semicolon after the closing ‘}’ character. Now everything starts to make more sense, doesn’t it? Multidimensional madness While we are here, we may as well consider the concept of multidimensional arrays in C/C++. The simplest form is a two-dimensional (2D) array, but 3D, 4D, 5D, and higher dimensions may also be employed as required. For example, supposing we wish to declare and initialise a 2D integer array called x with three rows, each comprising five columns, we could do so as follows (again, these values have no meaning beyond serving as an example): int x[3][5] = { {10, 12, 99, 42, 45}, {18, 15, 21, 36, 54}, {72, 91, 14, 62, 77} }; 58 In this case, our three rows are numbered 0, 1, and 2, while our five columns are numbered 0, 1, 2, 3, and 4. If we subsequently wished to change the value of the fourth element in the second row (indicated in red) from 36 to 63, for example, we could do so as follows: x[1][3] = 63; Observe how we use nested { } character pairs in the initialisation example above. In this case, the outer pair is associated with the entire array, while the three inner pairs are each associated with a row. In particular, observe how the elements in a row encapsulated by { } characters are commaseparated with no comma after the final element. Also, how the rows themselves are comma-separated with no comma after the final row. Once again, this can be confusing to beginners. And, once again, it all makes sense when you think about it. Just for giggles and grins, we should probably note that it is also possible to initialise our array using a single pair of { } characters as follows: int x[3][5] = { 10, 12, 99, 42, 45, 18, 15, 21, 36, 54, 72, 91, 14, 62, 77 }; It’s all Alan’s fault To be honest, I really hadn’t intended to take such a diversion into the coding side of things. I know that our illustrious publisher, Matt Pulzer, is keen for us to perform as much nitty-gritty ‘hands-on’ experimentation as possible. However, everything we’ve discussed here will serve us well in the future. Also, I think it’s only fair to note that any blame for this excursion into the world of coding can be fairly and squarely laid at the feet of the aforementioned PE community member, Alan, who presented this poser in the first place. Clickerty switch OK, are you ready for some hands-on experiments? You are? Good. I am too. Two columns ago (PE, May 2023), I asked you to add a second switch to your breadboard (Fig.2). In our previous column (PE, June 2023), we added a passive piezoelectric buzzer and used it to present a more pronounced ‘click’ sound when we clicked our first switch. At the end of that column, I left you with two missions, the first being to add this click effect to our ‘count up’ code. Just to make sure we are all tap-dancing in synchronisation, as it were, you can download a copy of our current breadboard layout showing both switches, our piezoelectric buzzer, our 7-segment display, and the connections to our Arduino Uno (file CB-Jul23-01. pdf). As usual, all of the files mentioned in this column are available from the July 2023 page of the PE website at: https://bit.ly/pe-downloads After presenting our ‘HELLO’ message, our code currently commences by displaying 0. Pressing our first pushbutton causes the count to increment by 1. When the count reaches 9, the next press of the first pushbutton returns the count to 0. You can download the program containing the most recent incarnation of our code, including the Click() function that causes the piezoelectric buzzer to make our ‘click’ sound (file CB-Jul23-02.txt). The main function of interest to us at the moment is our GetNewDigit() function (Listing 1). As we discussed in our previous column, a lot of this code is there to make sure our program doesn’t inadvertently respond to switch bounce (ie, multiple events caused by the switch’s electromechanical contacts making and breaking several times before settling in their new state). Just to remind ourselves, the PinSw variable is associated with the Arduino’s A0 pin, which is connected to the first switch. On Line 95 we read the value on this pin and store it in a local integer variable called swVal. The portion of the code of interest to us here commences with the test on Line 97. If this test returns true, this means the switch has been pressed, in which case we execute the code in the associated compound statement (Lines 98 to 103). Look at us, saying things like ‘compound statement’ and actually having a clue what we’re talking about – my mum is going to be so proud. Observe the call to the Click() function on line 101. Also, observe Line 99, which is where the real magic takes place. The ‘+ 1’ part is where we increment the count. The % NUM_DIGITS part is where we make sure that, when the count value reaches 10, it’s returned to 0 again (we discussed this in excruciating exhilarating detail in PE, April 2023). Crash and burn This brings us to your second mission, which was to implement the code to get our second switch working. In this case, pressing the second pushbutton should cause the count to decrement by 1. Furthermore, if the count is at 0, then the next press of the second pushbutton should return the count to 9. The first thing you should have done is replace the PinSw variable associated with the Arduino’s A0 pin with two variables called something like PinSw0 and PinSw1, which should be associated with pins A0 and A1, respectively. As a related item, in the setup() function you should have replaced the existing Practical Electronics | July | 2023 Listing 1. Counting up. pinMode(PinSw, INPUT) statement with two new statements reflecting the fact we now have two switches. Also, you should have replaced our global LookFor variable with two new variables Listing 2. Counting up and down (first pass). Practical Electronics | July | 2023 called something like LookForSw0 and LookForSw1. Of course, your main task would have been to modify the GetNewDigit() function to accommodate both switches. For example, you may have captured some code looking something like that shown in Listing 2. You can download this to peruse and ponder at your leisure (file CB-Jul23-03. txt). Actually, now that I come to think about it, it would have made more sense to have named our pins PinSwUp and PinSwDn, or at least add comments in the code saying which switch is supposed to do what, but I’m not going to go back and change things now (let’s just treat this as a learning experience). As usual, there are several things wrong with this code. Take a moment to see if you can spot any obvious areas of concern. The first point is that we’ve totally divorced our handling of the two switches. We start by checking to see if our first switch has been pressed, in which case we respond accordingly, and then we move on to consider our second switch. What would happen if both switches were pressed at the same time? What should happen if both switches are pressed at the same time? Having both switches being activated or deactivated simultaneously may be unlikely (in some applications and implementations it may be impossible), but such a circumstance should at least be considered in the system’s specification and – if necessary – accounted for in the code. However, the real problem is located on Line 118, which is where we are attempting to decrement the count. As you can see, all we’ve done (I know it’s me who did this, but I’m assuming you attempted something similar) is to take our increment count statement from Line 104 and replace the +1 with –1, which – as it turns out – is not a good idea. The funny thing is that this will actually work some of the time, thereby making it a ‘close, but no cigar’ solution. Try running the program, wait for the initial ‘0’ to appear on the 7-segment display, and then click the ‘up’ count button (our first switch) five times, thereby leaving ‘5’ on the display. Now try clicking the ‘down’ count button (our second switch) five times, thereby returning the displayed value to ‘0’. The problem arises when we click the ‘down’ count button one more time. We want this to take us to a displayed value of ‘9’, but instead... Well, see what happens for yourself. Don’t be negative What’s gone wrong? Well, this is all to do with the way in which computers represent negative numbers. Do you remember when we introduced the concept of hexadecimal (PE, May 2023)? At that time, we noted that a 4-bit nybble can represent 24 = 16 different patterns of 0s and 1s from 0000 to 1111, and that we can use these patterns to represent positive integers in the range 0 to 15. At that 59 time, we failed to make mention of how we can also use these patterns to represent both positive and negative values. In fact, there are multiple methods available to us, two of the most common being known as ‘sign-magnitude’ and ‘signed binary.’ Let’s first remind ourselves that, in the case of an unsigned binary 4-bit nybble (Fig.3a), commencing with the rightmost bit, which is known as the least-significant bit (LSB), the column weights are 20 = 1, 21 = 2, 22 = 4, and 23 = 8. We determine the value of an unsigned binary number by multiplying each bit by its corresponding column weight and summing the results. For example, 0011 is (0 x 23) + (0 x 22) + (1 x 21) + (1 x 20), which is the same as saying (0 x 8) + (0 x 4) + (1 x 2) + (1 x 1) = 3. Similarly, 1011 is (1 x 23) + (0 x 22) + 1 x 21) + (1 x 20), which is the same as saying (1 x 8) + (0 x 4) + (1 x 2) + (1 x 1) = 11. By comparison, in the case of sign-magnitude binary (Fig.3b), the leftmost bit, which is known as the most-significant bit (MSB) doesn’t have a weight associated with it. Instead, it is used to represent the sign of the number, where 0 and 1 indicate positive and negative values, respectively. For this reason, not surprisingly, the MSB is also known as the ‘sign bit.’ Meanwhile, the three remaining bits continue to have the same weights as before. This means that, in the case of a nybble considered to represent sign-magnitude values, 0011 represents +3, while 1011 represents –(+3) = –3. On the one hand, the sign-magnitude form is intuitive and balanced (we can use it to represent values from –7 to +7), and it is certainly employed for specific applications. On the other hand, we end up with two types of zero, –0 and +0, and we just know that this is going to be problematic at some stage in the game (what happens if we compare variables containing –0 and +0 values to see which is bigger, for example?). And thus we come to signed binary (Fig.3c), which may appear to be a tad tricky at first, but which boasts an underlying elegance and sophistication (I’m a big fan). In this case, the MSB represents a negative quantity, which is –23 = –8 in the case of a 4-bit nybble. Once again, the three remaining bits continue to have the same positive weights as before. On the one hand this results in an unbalanced range of –8 to +7. On the other hand, we now have only a single zero, which has to make our lives easier in the long run. Let’s think this through. In this case, 0011 is (0 x –23) + (0 x 22) + (1 x 21) + (1 x 20), which is the same as saying (0 x –8) + (0 x 4) + (1 x 2) + (1 x 1) = 3. However, 1011 is (1 x –23) + (0 x 22) + 1 x 21) + (1 x 20), which is the same as saying (1 x –8) + (0 x 4) + (1 x 2) + (1 x 1), which boils down to –8 + 3 = –5. If this is the first time you are seeing this, you may be forgiven for thinking ‘What?’ and ‘What??’ and even ‘What???’ At first glance, signed binary might appear to be an exceedingly convoluted solution to a relatively trivial problem. All I can ask is that you stand firm and keep the faith because there is much more to the signed binary format than meets the eye, not least that it dramatically eases the way in which we assemble the logic functions that allow a digital computer to perform its magic. Binary Dec Hex 0 0 0 0 0 0 0 0 0 1 1 1 0 0 1 0 2 0 0 1 1 Binary Dec Hex 0 0 0 0 +0 0 0 0 0 1 +1 1 2 0 0 1 0 +2 3 3 0 0 1 1 0 1 0 0 4 4 0 1 0 1 5 0 1 1 0 Hex 0 0 0 0 +0 0 0 0 0 1 +1 1 2 0 0 1 0 +2 2 +3 3 0 0 1 1 +3 3 0 1 0 0 +4 4 0 1 0 0 +4 4 5 0 1 0 1 +5 5 0 1 0 1 +5 5 6 6 0 1 1 0 +6 6 0 1 1 0 +6 6 0 1 1 1 7 7 0 1 1 1 +7 7 0 1 1 1 +7 7 1 0 0 0 8 8 1 0 0 0 –0 8 1 0 0 0 –8 8 1 0 0 1 9 9 1 0 0 1 –1 9 1 0 0 1 –8 + 1 = –7 9 1 0 1 0 10 A 1 0 1 0 –2 A 1 0 1 0 –8 + 2 = –6 A 1 0 1 1 11 B 1 0 1 1 –3 B 1 0 1 1 –8 + 3 = –5 B 1 1 0 0 12 C 1 1 0 0 –4 C 1 1 0 0 –8 + 4 = –4 C 1 1 0 1 13 D 1 1 0 1 –5 D 1 1 0 1 –8 + 5 = –3 D 1 1 1 0 14 E 1 1 1 0 –6 E 1 1 1 0 –8 + 6 = –2 E 1 1 1 1 15 F 1 1 1 1 –7 F 1 1 1 1 –8 + 7 = –1 F ± 22 21 20 (a) Unsigned binary https://amzn.to/3Tk7Q87 Components from Part 6 Passive piezoelectric buzzer 60 https://amzn.to/3KmxjcX 0 (c) Signed binary Ah, that explains it! This is probably a good time to mention that, in the case of the Arduino Uno microcontroller which we are using for our experiments, the int (integer) data type is a 16-bit field. Also, we can declare integers as being signed (ie, signed binary) or unsigned, for example: signed int a; unsigned int b; If we simply declare a variable as being of type int, then the compiler will treat it as being signed by default. The really –2 Components from Part 2 Momentary pushbutton switches 1 More than a nybble Did you observe that the hexadecimal values remain unchanged, irrespective of whether we are considering our nybble to represent unsigned, sign-magnitude, or signed binary values (Fig.3)? This is because hexadecimal values are no more than a oneto-one mapping to their binary counterparts. But what about values larger than a 4-bit nybble (Fig.4a)? Well, in the case of an 8-bit byte (Fig.4b), the MSB would represent –27 = –128, while all the other bits continue to have positive weights. In turn, this means an 8-bit signed binary number can be used to represent values in the range –128 to +127. Similarly, in the case of a 16-bit word (Fig.4c), the MSB would represent –215 = –32,768, which means a 16-bit signed binary number can be used to represent values in the range –32,768 to +32,767. The big point here is that, in the case of the signed binary format, all 1s values always represent –1. By comparison, if we were considering these fields to represent unsigned binary values, then their MSBs would represent +23, +27, and +215, and their all 1s values would represent 15, 255, and 65,535 for our nybble, byte, and word examples, respectively. LEDs (assorted colours) https://amzn.to/3E7VAQE Resistors (assorted values) https://amzn.to/3O4RvBt Solderless breadboard https://amzn.to/3O2L3e8 Multicore jumper wires (male-male) https://amzn.to/3O4hnxk Components from Part 5 2 Fig.3. Two ways of representing negative numbers in binary. –2 https://amzn.to/3Afm8yu 3 –2 2 2 2 (b) Sign-magnitude binary Components from Part 1 7-segment display(s) Binary Decimal 23 22 21 20 –2 15 7 3 20 HEX DEC 0 1 1 1 7 (+7) 1 0 0 0 8 (–8) 1 1 1 1 F (–1) 0 1 1 1 1 1 1 1 7F (+127) 1 0 0 0 0 0 0 0 80 (–128) 1 1 1 1 1 1 1 1 FF (–1) 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 7FFF (+32,767) 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8000 (–32,768) 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 FFFF (a) 4-bit nybble (b) 8-bit byte (c) 16-bit word (–1) Fig.4. Signed binary nybbles, bytes, and words. Practical Electronics | July | 2023 FFFF (–1) 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 + 0001 (+1) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 = 0000 (0) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0000 (0) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + FFFF (–1) 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 9 (a) 0 1 8 2 7 3 6 (b) = FFFF (–1) 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 5 4 Fig.6. Circular reasoning. Fig.5. Operations of interest with signed binary. interesting thing is that signed and unsigned integers are stored and manipulated inside the computer in the same way – the only thing that’s different is the values we consider them to represent. We’re almost there. The penultimate thing to consider is what is going to happen if an integer variable already contains all 1s and we add 1 to it. In this case, whatever 16-bit hardware (register, memory location...) is being used to store this value will overflow and return to containing all 0s, irrespective of whether we are considering it to represent signed or unsigned values. Bearing this in mind, if we are considering our variable to represent signed binary values, which means an all 1s value represents –1, it should come as no surprise that –1 + 1 = 0 (Fig.5a). Contrariwise, what happens if our variable contains all 0s and we subtract one from it. In this case we have to remember that 0 – 1 is the same as saying 0 + (– 1). And, once again, since we know that all 1s represents –1, it should come as no surprise that 0 + (–1) = –1 (Fig.5b). All of this goes to explain why our counting down code went pear-shaped. The easiest way to wrap our brains around all of this is to employ a little circular reasoning. Let’s envisage our ten digits (0 to 9) as being arranged in a circle (Fig.6). Let’s start by reminding ourselves what happens when we count up using the statement on Line 104 of Listing 2 (remember that the modulo % operator returns the remainder from an integer division and that we’ve defined NUM_DIGITS to have a value of 10): tmpDigit = (DigitToDisplay + 1) % NUM_DIGITS; If DigitToDisplay is 0 to 8, adding 1 will take us in a clockwise direction, returning 1 to 9, respectively, and this is the remainder value that will be returned by the % 10 (ie, % NUM_ DIGITS) operation. By comparison, when DigitToDisplay is 9, adding 1 will give us 10, and 10 % 10 will return 0, which is what we want. Now, let’s consider what happens when we count down using the statement on Line 118 of Listing 2: JTAG Connector Plugs Directly into PCB!! No Header! No Brainer! Our patented range of Plug-of-Nails™ spring-pin cables plug directly into a tiny footprint of pads and locating holes in your PCB, eliminating the need for a mating header. Save Cost & Space on Every PCB!! Solutions for: PIC . dsPIC . ARM . MSP430 . Atmel . Generic JTAG . Altera Xilinx . BDM . C2000 . SPY-BI-WIRE . SPI / IIC . Altium Mini-HDMI . & More www.PlugOfNails.com Tag-Connector footprints as small as 0.02 sq. inch (0.13 sq cm) tmpDigit = (DigitToDisplay + NUM_DIGITS - 1) % NUM_DIGITS; In this case, if DigitToDisplay is 9 to 1, adding 10 will give 19 to 11, respectively; subtracting 1 will give 18 to 10, respectively; and performing % 10 will result in 8 to 0 as before. Furthermore, when DigitToDisplay is 0, adding 10 will give 10, subtracting 1 will give 9, and performing % 10 will result in a remainder value of 9, which is what we want. As usual, I feel a little ‘Tra-la’ is in order. Homework I’m sorry we’ve spent so much of our time in this column contemplating things that make our heads ache, but everything we’ve tmpDigit = (DigitToDisplay - 1) % NUM_DIGITS; learned here is going to come in very, very useful in the future. What I’d like you to do now is to think of at least three difIn this case, if DigitToDisplay is 9 to 1, subtracting 1 will ferent applications we can implement with our setup so far, take us in a anticlockwise direction, returning 8 to 0, respecwhich is two buttons, a piezoelectric buzzer, and our 7-segtively, and this is the remainder value that will be returned ment display. I’ll even start the ball rolling by suggesting one by the % 10 operation. By comparison, when DigitToDispossible application, which would be to implement a form play is 0, subtracting 1 will give us -1, but this is represented of electronic dice. Every time we press a button, our program by all 1s in signed binary, and your guess is as good as mine could display a random number from 1 to 6. as to what the % 10 operation will do with this (suffice it to How about we jazz this up a little by generating a series say that it won’t return a value of 9). of random numbers before settling on the final value? What Happily, there is a solution. Suppose we start by adding 10 about making the random numbers change quickly at first to whatever value is currently stored in DigitToDisplay, and then gradually slow down before eventually settling on which will take us a full turn clockwise round the dial in a final value? Might we want to arrange things such that each Fig.6. This means our updated version Line 118 in Listing 2 number is accompanied by a click on our buzzer and a merry will be as follows (file CB-Jul23-04.txt): little jingle plays when the final value is displayed? This counts as just one application. I’m sure you can come up with Cool bean Max Maxfield (Hawaiian shirt, on the right) is emperor of all he more (feel free to email me to let me surveys at CliveMaxfield.com – the go-to site for the latest and greatest know what you’re thinking, and I’ll in technological geekdom. respond by sharing my own ideas). Comments or questions? Email Max at: max<at>CliveMaxfield.com Until next time, have a good one! Practical Electronics | July | 2023 61