Silicon ChipMax’s Cool Beans - October 2020 SILICON CHIP
  1. Outer Front Cover
  2. Contents
  3. Subscriptions: PE Subscription
  4. Subscriptions: PicoLog Cloud
  5. Back Issues: PICOLOG
  6. Publisher's Letter
  7. Feature: The Fox Report by Barry Fox
  8. Feature: Techno Talk by Mark Nelson
  9. Feature: Net Work by Alan Winstanley
  10. Project: HIGH-POWER 45V/8A VARIABLE LINEAR SUPPLY by Tim Blythman
  11. Back Issues: LFSR Random Number Generator Using Logic ICs by Tim Blythman
  12. Project: PRECISION ‘AUDIO’ SIGNAL AMPLIFIER by Jim Rowe
  13. Project: ARDUINO-BASED DIGITAL AUDIO MILLIVOLTMETER by Jim Rowe
  14. Feature: Circuit Surgery by Ian Bell
  15. Feature: Practically Speaking by Mike Hibbett
  16. Feature: Max’s Cool Beans by Max the Magnificent
  17. Feature: Make it with Micromite by Phil Boyce
  18. Feature: Pedal Power Station! by Julian Edgar
  19. PCB Order Form: Max’s Cool Beans by Max the Magnificent
  20. Feature: AUDIO OUT by Jake Rothman
  21. Advertising Index

This is only a preview of the October 2020 issue of Practical Electronics.

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

Articles in this series:
  • Techno Talk (October 2020)
  • Techno Talk (October 2020)
  • (November 2020)
  • (November 2020)
  • Techno Talk (December 2020)
  • Techno Talk (December 2020)
  • Techno Talk (January 2021)
  • Techno Talk (January 2021)
  • Techno Talk (February 2021)
  • Techno Talk (February 2021)
  • Techno Talk (March 2021)
  • Techno Talk (March 2021)
  • Techno Talk (April 2021)
  • Techno Talk (April 2021)
  • Techno Talk (May 2021)
  • Techno Talk (May 2021)
  • Techno Talk (June 2021)
  • Techno Talk (June 2021)
  • Techno Talk (July 2021)
  • Techno Talk (July 2021)
  • Techno Talk (August 2021)
  • Techno Talk (August 2021)
  • Techno Talk (September 2021)
  • Techno Talk (September 2021)
  • Techno Talk (October 2021)
  • Techno Talk (October 2021)
  • Techno Talk (November 2021)
  • Techno Talk (November 2021)
  • Techno Talk (December 2021)
  • Techno Talk (December 2021)
  • Communing with nature (January 2022)
  • Communing with nature (January 2022)
  • Should we be worried? (February 2022)
  • Should we be worried? (February 2022)
  • How resilient is your lifeline? (March 2022)
  • How resilient is your lifeline? (March 2022)
  • Go eco, get ethical! (April 2022)
  • Go eco, get ethical! (April 2022)
  • From nano to bio (May 2022)
  • From nano to bio (May 2022)
  • Positivity follows the gloom (June 2022)
  • Positivity follows the gloom (June 2022)
  • Mixed menu (July 2022)
  • Mixed menu (July 2022)
  • Time for a total rethink? (August 2022)
  • Time for a total rethink? (August 2022)
  • What’s in a name? (September 2022)
  • What’s in a name? (September 2022)
  • Forget leaves on the line! (October 2022)
  • Forget leaves on the line! (October 2022)
  • Giant Boost for Batteries (December 2022)
  • Giant Boost for Batteries (December 2022)
  • Raudive Voices Revisited (January 2023)
  • Raudive Voices Revisited (January 2023)
  • A thousand words (February 2023)
  • A thousand words (February 2023)
  • It’s handover time (March 2023)
  • It’s handover time (March 2023)
  • AI, Robots, Horticulture and Agriculture (April 2023)
  • AI, Robots, Horticulture and Agriculture (April 2023)
  • Prophecy can be perplexing (May 2023)
  • Prophecy can be perplexing (May 2023)
  • Technology comes in different shapes and sizes (June 2023)
  • Technology comes in different shapes and sizes (June 2023)
  • AI and robots – what could possibly go wrong? (July 2023)
  • AI and robots – what could possibly go wrong? (July 2023)
  • How long until we’re all out of work? (August 2023)
  • How long until we’re all out of work? (August 2023)
  • We both have truths, are mine the same as yours? (September 2023)
  • We both have truths, are mine the same as yours? (September 2023)
  • Holy Spheres, Batman! (October 2023)
  • Holy Spheres, Batman! (October 2023)
  • Where’s my pneumatic car? (November 2023)
  • Where’s my pneumatic car? (November 2023)
  • Good grief! (December 2023)
  • Good grief! (December 2023)
  • Cheeky chiplets (January 2024)
  • Cheeky chiplets (January 2024)
  • Cheeky chiplets (February 2024)
  • Cheeky chiplets (February 2024)
  • The Wibbly-Wobbly World of Quantum (March 2024)
  • The Wibbly-Wobbly World of Quantum (March 2024)
  • Techno Talk - Wait! What? Really? (April 2024)
  • Techno Talk - Wait! What? Really? (April 2024)
  • Techno Talk - One step closer to a dystopian abyss? (May 2024)
  • Techno Talk - One step closer to a dystopian abyss? (May 2024)
  • Techno Talk - Program that! (June 2024)
  • Techno Talk - Program that! (June 2024)
  • Techno Talk (July 2024)
  • Techno Talk (July 2024)
  • Techno Talk - That makes so much sense! (August 2024)
  • Techno Talk - That makes so much sense! (August 2024)
  • Techno Talk - I don’t want to be a Norbert... (September 2024)
  • Techno Talk - I don’t want to be a Norbert... (September 2024)
  • Techno Talk - Sticking the landing (October 2024)
  • Techno Talk - Sticking the landing (October 2024)
  • Techno Talk (November 2024)
  • Techno Talk (November 2024)
  • Techno Talk (December 2024)
  • Techno Talk (December 2024)
  • Techno Talk (January 2025)
  • Techno Talk (January 2025)
  • Techno Talk (February 2025)
  • Techno Talk (February 2025)
  • Techno Talk (March 2025)
  • Techno Talk (March 2025)
  • Techno Talk (April 2025)
  • Techno Talk (April 2025)
  • Techno Talk (May 2025)
  • Techno Talk (May 2025)
  • Techno Talk (June 2025)
  • Techno Talk (June 2025)
Max’s Cool Beans By Max the Magnificent Flashing LEDs and drooling engineers – Part 8 Beauty and the Beast – but which is which? I don’t know about you, but I can barely keep up with everything that’s happening. There’s so much fun stuff to do, but never enough time to do it. At the moment, for example, I’m happily experimenting with my 12 × 12 ping-pong ball array, where each ball contains a tricolor LED called a NeoPixel. In my previous column (PE, September 2020), we pretended that the array was lying flat on the floor and that occasional drips of virtual water were randomly falling from the sky. Whenever a virtual drip landed on one of our pixels (ping-pong balls), that pixel lit up bright white for a short period of time. Next, we decided to represent our drips using random colours selected from a palette comprising three primary colours (red, green and blue), three secondary colours (yellow, cyan and magenta) and six tertiary colours (flush orange, chartreuse, spring green, azure, electric indigo and rose). The thing is, simply turning our pixels hard on and hard off is not very subtle. In order to add a soupçon of sophistication, we decided to fade the colour up, hold it steady, and then fade it back down again, so that’s what we are going to do this month. Keep it simple In a moment, we’re going to create a small suite of functions that will perform the fading effect for us. One trick to writing functions is to make them as simple and as generalpurpose as possible. As part of this, it can be advantageous to split parts of our algorithm out into sub-functions that can be reused by other functions in the future. As usual, it may be advantageous for you to download a copy of this sketch in order to follow along with my meandering musings (just mosey over to the October 2020 page of the PE web50 site and download CB-Oct20-01.txt). 32 bits 8 bits The only sizes 31 24 23 16 15 8 7 0 of fixed-width unsigned integers we have available to > > 8 Discard us are 8, 16, 32 and > > 16 64-bits wide. Our red, green and blue Fig.1. Deconstructing a 32-bit colour value colour channels are into three 8-bit fields. each 8-bits wide. As we discussed earlier, we are predominantly representing our colours as single hexadecimal values. For example, we define the COLOR_WHITE as 0xFFFFFFU (remember that adding a ‘U’ or ‘u’ character on the end of a value directs the compiler to regard it as being unsigned). Did you spot the fact that FFFFFF is only 24 bits wide? Since the smallest unsigned integer we can use to hold this value is 32-bits wide, this means we are leaving the most-signifi cant eight bits unused. Apart from anything else, we should really have specified our COLOR_WHITE as 0x00FFFFFFU. The reason we didn’t do so is that we know the compiler will add any required leading zeros when it performs its magic. Winkle-picking colour channels Keeping all of this in mind, let’s start with three low-level functions called GetRed(), GetGreen(), and GetBlue(). In each case, we are going to pass in a 32-bit colour value and the function will return the appropriate 8-bit colour to us (Fig.1). Let’s start with GetBlue(). We pass this a 32-bit value called tmpColor. We then use a bitwise & (AND) operator to mask out the least-significant eight bits and return the result, which we cast as an 8-bit value (the topic of casting is discussed in this month’s Tips and Tricks column at the end of this article). uint8_t GetBlue (uint32_t tmpColor) { return (uint8_t) (tmpColor & 0xFFU); } There are a couple of things to note here. Let’s start with the fact that using (tmpColor & 0xFFU) to perform the mask operation is the same as saying (tmpColor & 0x000000FFU) because, once again, the compiler will insert the leading zeros for us. More importantly, we don’t actually need to perform the mask operation in the first place because the most-significant 24 bits of our 32-bit value will be discarded when we perform the cast. Following on from the previous point, believe it or not, we actually don’t even need to use the (uint8_t) to cast the result as an 8-bit value, because we already declared the GetBlue() function as returning Practical Electronics | October | 2020 a uint8_t value, which means the compiler will automatically perform this cast for us. As a result of all this, we could, if we wished, write our function as follows: uint8_t GetBlue (uint32_t tmpColor) { return tmpColor; } 32-bit unsigned integer and we shift the result eight bits to the left. When it comes to our 8-bit blue channel, all we need do is cast it to be a 32-bit value. Finally, we use | (bitwise OR) operators to merge these three values together to form a single 32-bit colour, which we return to whatever called this function in the first place. Watch me fade! The advantage of including both the mask and the cast is that it makes our intent clear to anyone who has to understand and maintain this program in the future (remembering that this could well be us). Furthermore, explicitly doing this sort of thing throughout our code can help prevent hard-to-detect bugs from creeping in while we aren’t looking. And, just in case you were wondering (or worrying) about any overhead imposed by these instructions, you may rest assured that the compiler’s optimisation knowhow should enable it to recognise any operations that are superfluous to requirements and remove them (if it fails to do so, you can take any additional execution time out of my vacation). Extracting the blue channel was the low-hanging fruit, because it was already where we needed it to be with regard to its position in our 32-bit colour value. In the case of the green channel and our GetGreen() function, we’re going to have to shift our 32-bit value eight bits to the right before performing the mask operation. For reasons that will become apparent in a future column, we’re going to implement a ‘master clock’ with a duration of 10ms (milliseconds). This is defined by TICK in our sketch. Purely for the sake of discussion, let’s suppose we decide to take 100ms to perform our fade. Using our 10ms master clock, this means we’re going to require 100/10 = 10 steps When it comes to actually performing the fade, the next function we require is one that can calculate a new colour value that’s formed as a specified proportion of two different colours. Also, just for giggles and grins, we will need to perform this calculation on each of the red, green and blue channels independently. If you look at our sketch, you’ll see a function called CrossFadeColor(). When we call this function, we pass in four arguments, the:  32-bit startColor  32-bit endColor  Total number of steps in this fade: numSteps  Step we’re currently on currentStep. uint8_t GetGreen (uint32_t tmpColor) { return (uint8_t) ( (tmpColor >> 8) & 0xFFU ); } Let’s look at the statement we use to process the red channel as follows (observe that this statement calls our GetRed() function two times to extract the red channels from startColor and endColor): Similarly, in the case of the red channel and our GetRed() function, we’re going to have to shift our 32-bit value 16 bits to the right before performing the mask operation. tmpRed = ((GetRed(startColor) * (numSteps currentStep)) + (GetRed(endColor) * currentStep)) / numSteps; uint8_t GetRed (uint32_t tmpColor) { return (uint8_t) ( (tmpColor >> 16) & 0xFFU ); } Now, this can be a little tricky to wrap one’s brain around. The best way for you to comprehend the nuances of all this will be to draw a table that contains columns for each of the possible currentStep values, which would be 0 to numStep (ie, 0 to 10). Your table should also contain four rows; one each for the:  Results of the startColor calculations  Results of the endColor calculations  Results of adding these two values  Ultimate results following the final division operations. As an aside, using the & (bitwise AND), | (bitwise OR), and ^ (bitwise XOR) operands is an interesting topic in its own right, especially when it comes to performing masking operations. Sad to relate, we don’t have the time to delve into this here, but for those who are excited to learn more, I discuss this in excruciating detail on my Cool Beans website – see: https://bit.ly/3itQGCa Regenerating colours The counterpoint to extracting the red, green and blue channels from a 32-bit colour value is to take a triad, trio, or troika of 8-bit colour channels and use them to construct a 32-bit colour value (Fig.2). We perform this magic using a BuildColor() function: uint32_t BuildColor (uint8_t red, uint8_t green, uint8_t blue) { return ( (((uint32_t) red) << 16) | (((uint32_t) green) << 8) | ((uint32_t) blue) ); } A little thought shows that we’re doing things in reverse to our GetColour() functions. First, we cast our 8-bit red channel to be a 32-bit unsigned integer and we shift the result 16 bits to the left. Next, we cast our 8-bit green channel to be a Practical Electronics | October | 2020 I suggest you begin by assuming that the red component of startColor has a value of 0 (fully off), while the red component of endColor has a value of 255 (fully on). Next, create a second table using a startColor value of 255 and an endColor value of 0. Finally, do the whole thing again with both values set to 255, which could easily happen if the main colours were magenta (red and blue) and yellow (red and green). Do you recall earlier on when I said, ‘One trick to writing functions is to make them as simple as possible’? I don’t know about you, but I think the calculation 8 bits 32 bits we just introduced 31 24 23 16 15 8 7 0 is rather cunning. If you look at our < < 16 CrossFadeColor() < < 8 function, you’ll see < < 0 that it contains only four lines of code that are doing the Fig.2. Using three 8-bit fields to construct ‘heavy lifting.’ Three a 32-bit value. 51 Time 10ms 30ms 50ms 70ms 90ms 0ms 20ms 40ms 60ms 80ms 100ms S teps S tart C o l o r 1 2 3 4 5 6 7 8 9 10 E nd C o l o r Computations U pload NeoPix els Pad Delay Your best bet since MAPLIN Chock-a-Block with Stock Visit: www.cricklewoodelectronics.com O r phone our f riendly know ledgeable staf f on 02 0 8 4 5 2 01 6 1 Fig.3. Graphical representation of 100ms fade with 10ms master clock. Components • Audio • Video • Connectors • Cables Arduino • Test Equipment etc, etc of these lines calculate the new red, green, and blue channel values, while the fourth calls our BuildColor() function to merge everything back together again. All that is required now is one more function we’ll call CrossFade(), which we’ll use to orchestrate our fade effect. If you look at the sketch, you’ll see that the first thing we do in this function is to calculate the number of fade steps (numFadeSteps) based on the required fade duration and the value of our master clock tick. The next thing we do is to perform a loop that calls our CrossFadeColor() function as follows: for (int iStep = 1; iStep <= numFadeSteps; iStep++) { fadeColor = CrossFadeColor(startColor, endColor, numFadeSteps, iStep); // More stuff here } Before we proceed, let’s pause to ponder the fact that there are four main options for the range of values we could assign to iStep to control our loop:  0 to < numFadeSteps  0 to <= numFadeSteps  1 to < numFadeSteps  1 to <= numFadeSteps. So, why did we choose the latter option? Well, when it comes to this sort of loop construct, the initial value and terminating condition will depend on our algorithm and what we’re trying to do with it. There’s a common issue in computer programming that’s called the ‘off-by-one error’ or ‘off-by-one bug.’ This is also commonly known as a ‘fence post error’ based on the so-called ‘fence post problem.’ For example, assuming we have fence posts spaced 10 feet apart, how many posts will be required to support a 100-foot length of fence? The answer is 11 – there is always one more fence post than there are fence spans – because we need a post at the beginning of the fence. Another way to look at this is that a single 10-foot length of fence will require two posts – one at either end. A similar condition can occur in our programs when an iterative loop iterates one time too few or one time too many. I’m sure professional computer programmers have no problem with this sort of thing, but it typically makes my head hurt. On the bright side, I’m a visually oriented person, so I usually find it helps me to draw things out. Let’s create a graphical representation of our 100ms fade with a 10ms master clock (Fig.3). Before we commence our fade, we can assume that we already have 100% of the original (start) colour and 0% of the new (end) colour. By the time we finish the fade, we wish to have 0% of the original 52 Vis it o u r S h o p , C al l o r B u y o nl ine at: w w w . crick l ew o o d el ectro nics . co m 02 0 8 4 5 2 01 6 1 Vis it o u r s h o p at: 4 0- 4 2 C rick l ew o o d B ro ad w ay L o nd o n NW 2 3 E T colour and 100% of the new colour. The ‘per cent’ values of the intermediate colours will depend on the current step when compared to the total number of steps. As defined by the NeoPixel library, the clock used to upload our NeoPixel string is running at 800kHz. Each NeoPixel requires 24 bits of data, which takes (1/800,000) * 24 = 30µs. Since we have 145 NeoPixels (including our ‘sacrificial pixel’), this equates to a total delay of 30 * 145 = 4,350µs = 4.35ms. If we ‘guestimate’ that all of our computations require 0.65ms (we will return to consider this in more detail in a future column), then this means we need to add a padding delay of 10 – (0.65 + 4.35) = 5ms to each of our steps to build things up to our 10ms master clock tick (you will see this defined as INTER_TICK_PAD_DELAY in our sketch). Once again, do you recall earlier on when I said, ‘One trick to writing functions is to make them as general-purpose as possible’? Well, the beauty of our CrossFade() and CrossFadeColor() functions is that it doesn’t matter whether we are ‘fading up’ (from black to a colour) or ‘fading down’ (from a colour to black), because black is just another colour. In turn, this means that we can use these functions to fade from any colour to any other colour. For your delectation and delight, I’ve created a video showing our colour-fading sketch in action (https://bit.ly/2EXQ20k). In my next column, we will delve more deeply into various multi-colour combinations we might decide to employ. Until that frabjous day, please Callooh! Callay! – in other words have a good one! Cool bean Max Maxfield (Hawaiian shirt, on the right) is emperor of all he surveys at CliveMaxfield.com – the go-to site for the latest and greatest in technological geekdom. Comments or questions? Email Max at: max<at>CliveMaxfield.com Practical Electronics | October | 2020 Max’s Cool Beans cunning coding tips and tricks O n the one hand, I keep on saying I’m a hardware designer by trade and my software skills are rudimentary at best. On the other hand, I constantly amaze myself with all the tidbits of trivia and nuggets of knowledge that have managed to take root in my poor old noggin. Want an argument? As one example of the above, someone just emailed me to ask why I sometimes use the term ‘parameter’ and at other times say ‘argument’. Well, consider the following declaration of a meaningless function: int SillyFunction (int a, int b) { int y = a + b; return y; } When we declare a function, part of this declaration is a list of parameters associated with the function. In this case, our function has two parameters: a and b. Of course, it’s also possible for a function to be declared with an empty parameter list. Now consider when we call our function from somewhere else in the program; for example: the compiler will automatically convert one of the operands into the same type as the other. If the operand goes from a smaller domain (eg, short) to a larger domain (eg, long), this is called promotion. By comparison, if the operand goes from a larger domain to a smaller domain, this is known as demotion. Promotion typically isn’t a problem because the range of values associated with the smaller integer forms a subset of the larger domain to which it is being promoted. In the case of demotion, however, problems may occur if the value being demoted is too large to fit into the target type, in which case the result will be truncated (this may, of course, be just what we want to occur). Different languages deal with all of this in different ways. In the case of C/C++, we can perform explicit-type conversion using a cast operator, which involves the name of the new data type surrounded by parentheses. For example, suppose we declare two unsigned integer variables: one 8-bits wide and the other 32-bits wide, as follows: uint8_t myInt8; uint32_t myInt32; Now suppose that, somewhere in our program, we wish to copy the contents of the 32-bit value into the 8-bit value. In order to achieve this, we would cast the 32-bit value into its 8-bit counterpart as follows: int sillyResult = SillyFunction(40, 2); myInt8 = (uint8_t) myInt32; When we call a function, we pass in a list of arguments. In this case, we pass two arguments, 40 and 2, into SillyFunction(). Of course, if a function is declared with an empty parameter list, then when we call it we will pass in an empty argument list. In the real world, a lot of people use the terms ‘parameters’ and ‘arguments’ interchangeably. So long as the person you are talking to understands the message you are trying to convey, then there’s ‘no harm, no foul,’ as they say. The problem comes when you are talking with professional programmers who will take great delight in painstakingly instructing you in the error of your ways. Personally, I don’t care to give the little rascals the satisfaction. Curiouser and curiouser Towards the end of Chapter 1 in Alice’s Adventures in Wonderland, Alice foolishly guzzles the contents of a small bottle marked ‘Drink Me’ and shrinks to only ten inches in height. A little later, the little scamp cannot restrain herself from munching down on a small cake with ‘Eat Me’ marked in currants. At the start of Chapter 2, Alice cries ‘Curiouser and curiouser!’ as she rapidly grows to giant size. If this ever happens to me (again), I’m sure I will say much the same (or words to that effect). The reason I’m waffling on about this here is that something similar can happen with integers in C/C++ (actually, it can happen with all data types, but we will focus on integers for the purpose of these discussions). This is known as ‘type conversion’, of which there are two flavors: implicit and explicit. In the case of implicit-type conversion, the compiler accomplishes this automatically without our having to instruct it to do so. Alternatively, we can use explicit type conversion to ‘cast’ (transform) a value from one data type to another. If you take a look at last month’s Tips and Tricks column (PE, September 2020), you’ll see that we have short, regular and long versions of signed and unsigned integers (we also have fixedwidth data types like uint8_t and uint32_t). Whenever we try to perform a binary operation on operands of different types, Practical Electronics | October | 2020 One of the purposes of the cast operator is to inform the compiler that we know what we’re doing. In this case, the most-significant 24 bits of the 32-bit myInt32 will be discarded, and only its least-significant eight bits will be copied into myInt8. As we’ve seen, the cast operator has only one operand, which is to the right of the operator. As always, problems lurk for the unwary. Suppose we want to shift the value in myInt32 eight bits to the right and then copy the least-significant eight bits of the result into myInt8. Consider the following statement: myInt8 = (uint8_t) myInt32 >> 8; What do you expect this to do? Many beginners would expect this to first perform the shift operation followed by the cast. In reality, the cast operator has a higher precedence than the bitwise shift operator, so the operation that will actually be performed can be represented as follows: myInt8 = ((uint8_t) myInt32) >> 8; That is, the 32-bit value will first be cast into an 8-bit value, which will subsequently be shifted eight bits to the right. The result will be to leave myInt8 containing 0 (00000000 in binary, 0x00 in hexadecimal), which is not what we were hoping for. In order to achieve the desired result, we would actually have to write our statement as follows: myInt8 = (uint8_t) (myInt32 >> 8); Adding these parentheses forces the shift to be implemented first, after which the cast is performed on the result. You may not be surprised to learn that the reason we are talking about this here is that in my Cool Beans columns we are poised to start casting like champions (I’ve just dispatched the butler to fetch my casting trousers). 53