Silicon ChipMax’s Cool Beans cunning coding tips and tricks - July 2021 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: ATtiny816 Breakout and Development Board with Capacitive Touch by Tim Blythman
  11. Project: Infrared Remote Control Assistant by John Clarke
  12. Project: Touchscreen Wide-range RCL Box by Tim Blythman
  13. Feature: Practically Speaking
  14. Feature: PIC n’Mix by Mike Hibbett
  15. Feature: AUDIO OUT by Jake Rothman
  16. Feature: Make it with Micromite by Phil Boyce
  17. Back Issues: Circuit Surgery by Jake Rothman
  18. Feature: Circuit Surgery by Ian Bell
  19. Feature: Max’s Cool Beans by Max the Magnificent
  20. Feature: Max’s Cool Beans cunning coding tips and tricks
  21. PCB Order Form
  22. Advertising Index

This is only a preview of the July 2021 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 cunning coding tips and tricks B efore we jump into the fray with gusto and abandon (and aplomb, of course), I was recently chatting with my chum Steve Manley who features so prominently in my main Cool Beans column. I’m not sure how we got into this particular topic, but Steve taught me a very cunning coding trick as follows... I’ve reached my limit! It very often happens in my programs that I have an integer variable that I use as a pointer (or index) to a number of elements in an array or some such entity. For example, let’s say we have ten elements of something or other numbered from 0 to 9. For clarity in our code and to make it easy to modify in the future, we might define NUM_E (‘number of elements’) as being 10 and MAX_E (‘maximum element’) as being 9. (We could also define MIN_E as being 0, but we typically just use 0.) Let’s suppose our integer pointer is called PtrP. Sometimes we want to increment this pointer by adding 1 to its current value. Of course, when its current value is 9, we want its incremented value to ‘wrap around’ to be 0. In this case, remembering that the % modulo operator returns the remainder from an integer division, and based on our discussions from an earlier column (PE, March 2021), prior to my discussions with Steve, I might have used a statement like the following: // Increment the pointer PtrP = (PtrP + 1) % NUM_E; On other occasions, we want to decrement our pointer by subtracting 1 (or adding –1) to its current value. In this case, when its current value is 0, we want its decremented value to ‘wrap around’ to be 9. Once again, based on our earlier discussions, I might have used a statement like the following: // Decrement the pointer PtrP = (PtrP + MAX_E) % NUM_E; Well, Steve pointed out that the way he does this, assuming he has a variable called Delta, which may be assigned a value of +1 or –1 if we wish to increment or decrement, respectively, is as follows: // Increment/Decrement the pointer PtrP = (PtrP + NUM_E + Delta) % NUM_E; Good Golly, Miss Molly! This is so simple... so succinct... so sharp. I love it! 66 Thanks for the memory! I don’t know about you, but one of the things I worry about is losing my memory as I get older. This has happened to my mother’s sister – my auntie Shirley – who now lives in a home because she can no longer take care of herself and she no longer recognises any members of our family, including her own children. Fortunately, my 90-year-old mother still has a mind like a trap. In fact, my mom’s memory is so good that sometimes she remembers things that haven’t even happened yet! This meandering musing was triggered by my thinking of computer memory. In my main Cool Beans column, we noted that the Teensy – like virtually all microcontrollers – contains three types of memory. First, we have the Flash, which is used to store the main program (a.k.a. sketch). Next, we have the SRAM (static random-access memory), which is where the main program creates, stores, and manipulates variables when it runs. Finally, we have a form of memory that most beginners don’t even know about and rarely use – a small amount of EEPROM (electrically erasable programmable readonly memory) – in which programmers can store modest quantities of long-term information. The problem with SRAM is that it’s volatile, which means it forgets its contents when power is removed from the system. Flash memory is non-volatile, which means it remembers its contents when power is removed, but this is where the main program is stored. Suppose we decide to write a program that measures the ambient temperature once every hour and we want to save these values in such a way that we can retrieve them later, even if the microcontroller’s power supply fails at any time. In this case, one option would be to use the EEPROM. Similarly, in the case of the 10-character 21-segment Victorian displays that Steve and I are constructing, we want to use byte-sized unsigned integer values to keep track of a variety of user settings, like the preferred date format (eg, 0 = YYYY/MM/ DD, 1 = MM/DD/YYYY, 2 = DD/MM/ YYYY) and time format (eg, 0 = 12-hour, 1 = 24-hour) and location (eg, 0 = UK, 1 = USA) and how we are going to handle summertime (eg, 0 = by hand, 1 = automatic) and so forth. (FYI, ‘summertime’ is called ‘Daylight Saving Time’ (DST) in the US and ‘British Summer Time’ (BST) in the UK.) Of course, we will establish default values for these settings in our main program. However, we also want to allow the user to change these settings when the program is running, and we want our display to remember these user-defined values when power is removed from the system. Once again, one way to achieve this is to use the EEPROM. Remember that the term ‘byte’ refers to an 8-bit quantity. As we discussed in the main Cool Beans column, Teensy 3.2 and 3.6 microcontrollers have 2KB (2,048 bytes numbered from 0 to 2,047) and 4KB (4,096 bytes numbered from 0 to 4,095) of EEPROM, respectively. If we want to use this EEPROM in programs, we first need to include a special library that’s provided as part of the Arduino’s IDE: #include <EEPROM.h> Now, let’s suppose we wish to write a value of 128 into the EEPROM at its address 0. We could do so using the following statement: EEPROM.write(0, 128); Alternatively, if we declare an integer variable called Address to which we assign a value of 0, along with a byte-sized variable called Data to which we assign a value of 128, we could use the following statement: EEPROM.write(Address, Data); Contrawise, if at some stage we wish to read a byte of data out of the EEPROM’s address 0, we could use either of the following statements: Data = EEPROM.read(0); Data = EEPROM.read(Address); You can read more about the EEPROM library in the Arduino reference guide (https://bit.ly/2QYwsHO) and on the Teensy website (https://bit.ly/3y0L8H1), but just knowing the read() and write() functions provides us with enough knowledge to be dangerous. How many copies? In reality, we are going to have a bunch of different settings we wish to keep track of. Purely for the sake of these discussions, however, let’s assume that we have only the four byte-sized settings we discussed earlier: vdDate, vdTime, vdLocation, and vdSummer (where ‘vd’ stands for ‘Victorian Display’). In fact, we are going to want to keep three copies of these settings (as opposed to ‘copies,’ we might think of these as versions or instantiations). First, we will need a copy of our default values, Practical Electronics | July | 2021 which we will store in the program itself. Second, we will need a copy of the userdefined values, which we will store in the EEPROM. Finally, we will need a working copy of the values we are actually using. This may sound a little confusing at first, but it makes perfect sense when you think about it. Let’s suppose that we load our program onto a new microcontroller. In this case, when we run the program for the first time, there won’t be anything useful stored in the EEPROM. When we detect this fact (we will discuss this further in my next Tips and Tricks column), we will load our default values both into the EEPROM and into our working values. Since this is the first time that we’ve run the program, we will probably take this opportunity to modify the various settings to be just the way we like things. Of course, we might make additional changes in the future. The point is, for each setting we change, we will override the corresponding working value and EEPROM value with this new value. Some days you feel like an array When we come to copy the settings to and from the EEPROM, for example, it’s easier if we think of things as being an array. For example, assuming that we’ve defined NUM_SETTINGS as 4, we might define our default settings and our working settings in the form of arrays as follows: uint8_t DefSettings[NUM_SETTINGS]; uint8_t WrkSettings[NUM_SETTINGS]; Let’s assume that we are using locations 0, 1, 2 and 3 in these arrays to keep track of our vdDate, vdTime, vdLocation, and vdSummer settings, respectively. We won’t worry about how we initialise things here, let’s just assume that our DefSettings[] array has been loaded with appropriate values. If we wish to copy the values from the DefSettings[] array into the WrkSettings[] array, we could use: for (i = 0; i < NUM_SETTINGS; i++) WrkSettings[i] = DefSettings[i]; Similarly, if we wish to copy the values from the DefSettings[] array into the EEPROM, we could use: for (i = 0; i < NUM_SETTINGS; i++) EEPROM.write(i, DefSettings[i]); And, of course, if we wish to copy the values from the EEPROM into our WrkSettings[] array, we could use: for (i = 0; i < NUM_SETTINGS; i++) WrkSettings[i] = EEPROM.read(i); Some days you feel like a struct The problem with thinking of things as arrays is that it doesn’t make our code Practical Electronics | July | 2021 very readable later on. For example, what are we going to think if we are reading the main program and we see something like: if (WrkSettings[2] == 0)... Remember that, in the real application, we might have tens or hundreds of such settings. Thus, it would make our lives a lot easier to be able to think of our values as fields and say something like: if (WrkSettings.vdLocation == UK)... As you may recall, we introduced the concepts of typedef (type definitions), enum (enumerated types), and struct (structures) in Tips and Tricks, PE, December 2020. Based on this, we might decide to define and declare some structures as follows: typedef struct Settings { uint8_t vdDate; uint8_t vdTime; uint8_t vdLocation; uint8_t vdSummer; }; Settings DefSettings; Settings WrkSettings; Once again, we won’t worry about how we initialise things here, let’s just assume that our DefSettings structure has been loaded with appropriate values. Once we have the appropriate values in our WrkSettings structure, we’re good to go. The problem comes when we wish to load this structure. If we are loading it from our DefSettings structure, we are going to have to use a series of statements like: WrkSettings.vdDate = DefSettings.vdDate; WrkSettings.vdTime = DefSettings.vdTime; : etc. Alternatively, if we are loading the values in our WrkSettings structure from the EEPROM, we are going to have to use a series of statements like: WrkSettings.vdDate = EEPROM.read(0); WrkSettings.vdTime = EEPROM.read(1); : etc. It doesn’t take long to realise that, if we have tens or hundreds of settings, this is quickly going to become a pain in the nether regions and – trust me – this is the last place we want to have a pain. If only there was some way in which we could treat our settings both as an array and as a structure... Let’s form a union! It’s almost as if the folks who created the C programming language read our minds because they created a special data type called union that allows us to store different types of data in the same memory locations. Another way to think about this is that a union allows us to view the same memory locations in different ways. For example, consider the following (remember that NUM_SETTINGS has been defined as 4): typedef union Settings { struct { uint8_t vdDate; uint8_t vdTime; uint8_t vdLocation; uint8_t vdSummer; } vds; uint8_t vda[NUM_SETTINGS]; }; Settings DefSettings; Settings WrkSettings; First, we define a new type in the form of a union that we called Settings. As we see, this union offers two different ways to view / think of / treat the same four bytes of memory. The first method is to think of these four bytes as a structure we’ve called vdS; the second approach is to think of the same four bytes as an array we’ve called vdA. Next, we declare two variables, DefSettings and WrkSettings, both of which are of type Settings. As before, we won’t worry about how we initialise things here. Suffice it to say that, if we determine that the EEPROM contains a valid set of settings values, we can load our working settings as follows: for (i = 0; i < NUM_SETTINGS; i++) WrkSettings.vdA[i] = EEPROM.read(i); Later, in the body of the program, we can use statements like: if (WrkSettings.vdS.vdLocation == UK)... We’ve only touched on the power of the union type here because we’ve simply defined two different ways of looking at the same four bytes of memory. In fact, a union can provide three or more ways of looking at the same area of memory, where one member might think of things as 4-byte unsigned integers, another might think of each of these integers as four separate bytes, and yet another might think of things as individually named bits... and then things start to get complicated, but we can leave that for another day. As always, I welcome your comments, questions and suggestions. 67