Silicon ChipTeach-In 2024 - May 2024 SILICON CHIP
  1. Contents
  2. Subscriptions
  3. Back Issues
  4. Publisher's Letter: Welcome to May!
  5. Feature: Techno Talk - One step closer to a dystopian abyss? by Max the Magnificent
  6. Feature: Net Work by Alan Winstanley
  7. Feature: The Fox Report by Barry Fox
  8. Project: GPS-Disciplined Oscillator by Alan Cashin
  9. Project: Dual RF Amplifier for Signal generators by Charles Kosina
  10. Feature: UVM-30A Module Ultraviolet Light Sensor by Jim Rowe
  11. Project: Songbird by Andrew Woodfifield
  12. Feature: Teach-In 2024 by Mike Tooley
  13. Feature: Max’s Cool Beans by Max the Magnificent
  14. Feature: Audio Out by Jake Rothman
  15. Feature: Circuit Surgery by Ian Bell
  16. PartShop
  17. Market Centre
  18. Back Issues: Peak Test Instruments

This is only a preview of the May 2024 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)
Articles in this series:
  • Win a Microchip Explorer 8 Development Kit (April 2024)
  • Net Work (May 2024)
  • Net Work (June 2024)
  • Net Work (July 2024)
  • Net Work (August 2024)
  • Net Work (September 2024)
  • Net Work (October 2024)
  • Net Work (November 2024)
  • Net Work (December 2024)
  • Net Work (January 2025)
  • Net Work (February 2025)
  • Net Work (March 2025)
  • Net Work (April 2025)
Articles in this series:
  • Teach-In 2024 (April 2024)
  • Teach-In 2024 (May 2024)
  • Teach-In 2024 – Learn electronics with the ESP32 (June 2024)
  • Teach-In 2024 – Learn electronics with the ESP32 (July 2024)
  • Teach-In 2024 – Learn electronics with the ESP32 (August 2024)
  • Teach-In 2024 – Learn electronics with the ESP32 (September 2024)
  • Teach-In 2024 – Learn electronics with the ESP32 (October 2024)
  • Teach-In 2024 – Learn electronics with the ESP32 (November 2024)
Articles in this series:
  • Max’s Cool Beans (April 2024)
  • Max’s Cool Beans (May 2024)
  • Max’s Cool Beans (June 2024)
  • Max’s Cool Beans (July 2024)
  • Max’s Cool Beans (August 2024)
  • Max’s Cool Beans (September 2024)
  • Max’s Cool Beans (October 2024)
  • Max’s Cool Beans (November 2024)
  • Max’s Cool Beans (December 2024)
Articles in this series:
  • Audio Out (January 2024)
  • Audio Out (February 2024)
  • AUDIO OUT (April 2024)
  • Audio Out (May 2024)
  • Audio Out (June 2024)
  • Audio Out (July 2024)
  • Audio Out (August 2024)
  • Audio Out (September 2024)
  • Audio Out (October 2024)
  • Audio Out (March 2025)
  • Audio Out (April 2025)
  • Audio Out (May 2025)
  • Audio Out (June 2025)
Articles in this series:
  • Circuit Surgery (April 2024)
  • STEWART OF READING (April 2024)
  • Circuit Surgery (May 2024)
  • Circuit Surgery (June 2024)
  • Circuit Surgery (July 2024)
  • Circuit Surgery (August 2024)
  • Circuit Surgery (September 2024)
  • Circuit Surgery (October 2024)
  • Circuit Surgery (November 2024)
  • Circuit Surgery (December 2024)
  • Circuit Surgery (January 2025)
  • Circuit Surgery (February 2025)
  • Circuit Surgery (March 2025)
  • Circuit Surgery (April 2025)
  • Circuit Surgery (May 2025)
  • Circuit Surgery (June 2025)
Teach-In 2024 Learn electronics with the ESP32 by Mike Tooley Part 3 – Analogue input and output I n the last month’s part of our Teach-In series, we introduced digital I/O and showed you how to interface buttons and switches, and how to drive loads such as LED, relays and sounders. We also showed you how to use the Serial Monitor to test and debug your code. In this third part we will be introducing the ESP32’s analogue-to-digital converter (ADC) and moving into the analogue world. Our Teach-In Practical Project is a tester for 1.5V alkaline batteries. The learning objectives for this third part of our series are to know how to: n Configure and use simple analogue I/O n Interface analogue sensors n Use binary, octal, hexadecimal and ASCII. ESP32 analogue input The ESP32 has two 12-bit analogue-to-digital converters (ADC). Each of these supports up to 18 analogue channels, but not all may be available in a particular development board implementation. For example, the 30-pin board that we’re using for most of our practical projects has just six ADC1 channels and nine ADC2 channels available. A further limitation is that ADC2 is unavailable when Wi-Fi is being used. Obviously, this isn’t an issue if Wi-Fi isn’t required, but it might be a problem if you need many analogue channels. Fig.3.1 shows the ADC pins available on the most common 30- and 36-pin ESP32 boards. The ESP32 ADC uses a technique known as ‘successive approximation’. This uses a comparator that compares an analogue input with the output of a digital-to-analogue converter (DAC), as shown in Fig.3.2. The digital input to the DAC (a 12-bit value) is held in a dedicated successive-approximation register (SAR). This holds a series of values that rapidly approximate to an SAR value that’s equivalent to the analogue input. The process stops when the data held in the SAR is converted (by About Teach-In Our latest Teach-In series is about using the popular ESP32 module as a basis for learning electronics and coding. We will be making no assumptions about your coding ability or your previous experience of electronics. If you know one but not the other, you have come to the right place. On the other hand, if you happen to be a complete newbie there’s no need to worry because the series will take a progressive hands-on approach. There will be plenty of time to build up your knowledge and plenty of opportunity to test things out along the way. We’ve not included too much basic theory because this can be easily found elsewhere, including several of our previous Teach-In series, see: https://bit.ly/pe-ti https://bit.ly/pe-ti-bundle Earch month, there’ll be projects and challenges to help you check and develop your understanding of the topics covered. the DAC) to an analogue value perceived by the comparator as being the same as the analogue input. Reading analogue inputs Last month, we used digitalRead() to sense the state of the ESP32’s GPIO pins, the result being either HIGH or LOW. The corresponding function for analogue inputs is analogRead(). Gotcha! The ESP32 supports 18 different analogue channels but not all of them may be present on a particular development board. 40 Fig.3.1. Analogue pins on two common ESP32 development boards (30-pin left/36-pin right). Practical Electronics | May | 2024 Fig.3.3. Using a potentiometer to test the ADC. Gotcha! ADC2 can’t be used if your application is using WiFi. So, if you do need Wi-Fi it’s important to use ADC1 instead. Fig.3.2. An ADC based on successive approximation. As you might expect, analogRead() requires a GPIO pin as an argument. Note that we would normally want to assign the pin before the main body of code using a statement like this: int analogPin = 15; // Sensor voltage on pin-15 Here, GPIO15 (often marked ‘D15’ on development boards) corresponds to ADC2 Channel 3. Later in the code we will need a variable in which to store the returned analogue value from pin-15. We can do this using: int rawReading Sensor value = analogRead(analogPin); // The result stored in rawReading will be a 12-bit value from the ADC. This can range from 0 to 4095 (where 4095 corresponds to the 3.3V DAC reference voltage). In some applications you might need to convert the 12-bit value from the ADC into a corresponding voltage value. To do this, you will need to convert the value returned by the ADC and store it in a float variable, as in: Float volts = rawReading * (3.3/4095); Convert to voltage Fig.3.4. Wiring arrangement for the circuit shown in Fig.3.3. Practical Electronics | May | 2024 There are two things to note from this. First, we need to use a float because we are no longer dealing with integer values for voltage. Second, a scaling factor (3.3/4095) is needed to convert our raw reading from the ADC into a corresponding voltage. It’s worth checking this out by connecting an ordinary potentiometer (10kΩ to 20kΩ would be ideal) across the 3.3V supply with the slider taken to the pin in question, as shown in Fig.3.3. A suggested wiring arrangement is shown in Fig.3.4. Enter or download the code shown in Listing 3.1 – all this month’s code is available for download from the May 2024 page of the PE website: https://bit.ly/pe-downloads When you execute the code and start the Serial Monitor you will see the voltage present at pin-15 updated every second. If you rotate the shaft of the potentiometer over its full range, you will see the voltage changing smoothly from 0V at one extreme to 3.3V at the other. Typical analogue readings are shown in Fig.3.5. // Fig.3.5. Typical analogue readings obtained from the Serial Monitor. 41 Gotcha! The ESP32’s ADC are non-linear at both extremes. It’s important to be aware that input voltages below 0.1V will be read as 0V, while inputs above 3.2V will be read as 3.2V. If you can live with these restrictions, then it’s worth noting that the device is reasonably linear from 0.5V to 2.5V. Gotcha! Voltages outside the range 0 to 3.3V must never be applied to the ESP32’s analogue input pins. If you need to measure larger voltages it will be necessary to use a potential divider at the input. Furthermore, in some applications it’s important to avoid reverse polarity at the input. Listing 3.1 Testing the ADC with a potentiometer /* Analogue input using a potentiometer */ int analogPin = 15; // Analogue input via ADC2 Channel 3 void setup() { Serial.begin(9600); } void loop() { int rawReading = analogRead(analogPin); // The raw reading from the potentiometer needs to be // converted to volts and stored as floating point float volts = rawReading * (3.3/4095); // Send the value and print it using the serial monitor Serial.println(volts); delay(1000); // Delay for 1s before repeating the loop } Listing 3.2 Using the serial plotter // Using the serial plotter void setup() { // Start serial communication at 9600 bps Serial.begin(9600); } Fig.3.6. The ESP32’s ADC step size. ESP32 ADC performance Before we move on to some practical ESP32 ADC applications it’s worth explaining some of the specifications and potential limitations of the device. Resolution The resolution of an ADC is defined by its step size. In the ESP32, a 3.3V supply reference is used with a 12-bit ADC. This achieves a comfortably small step size of approximately 0.8mV (3.3/4096). This step void loop() { // Select ADC2 Channel 3 (GPIO pin-15) int gpioPin = 15; // Read the voltage at D15 int analogVolts = analogReadMilliVolts(gpioPin); // Display the current voltage in mV Serial.printf(“ADC input = %d mV\n”,analogVolts); // Wait for a while delay(1000); } size is illustrated in Fig.3.6. If you don’t need the full default 12-bit resolution you can select a different value using analogReadResolution(). For example, 10-bit resolution (210 = 1024 different values) can be selected using analogReadResolution(10). Accuracy The accuracy of an ADC depends on the accuracy of its reference voltage source. The reference voltage for the ESP32 is derived from the 3.3V supply and there’s no provision for an external (and more accurate) reference voltage source. Fig.3.7. ESP32 ADC non-linearity (the range from 1V to 2.75V can be considered reasonably linear). 42 ESP32’s ADC is that it does exhibit some non-linearity, as shown in Fig.3.7. Range The ESP32’s analogue input range extends from 0V to 3.3V. The voltage applied must not be allowed to fall outside this range. Introducing the Serial Plotter Thus far in our series we’ve made extensive use of the IDE’s Serial Monitor, Linearity Ideally, an ADC should be perfectly linear. Unfortunately, a n u n c o m f o r t a b l e Fig.3.8. Circuit to demonstrate the use of the p e c u l i a r i t y o f t h e Serial Plotter. Practical Electronics | May | 2024 Listing 3.3 LDR test code /* Simple LDR analogue interface */ int analogPin = 15; // Use ADC2 Channel 3 void setup() { Serial.begin(9600); } void loop() { // Get the current input from the LDR int ldrValue = analogRead(analogPin); Serial.println(ldrValue); delay(1000); // Repeat forever } but there’s another useful tool that’s worth knowing about. This is the Serial Plotter, which will provide you with a neat way of visualising data that changes over time. We’ll now use the Serial Plotter to show how the voltage across a capacitor increases as it charges. The code is shown in Listing 3.2, the circuit in Fig.3.8, and a suggested wiring diagram is illustrated in Fig.3.9. When running Listing 3.2 you will need to start execution with the shorting link in place see Fig.3.8.) After the code in Listing 3.2 has been compiled and uploaded, select Tools from the IDE’s menu bar and then Serial Plotter. Next, remove the shorting link so that the capacitor begins to charge. You should observe a Serial Plotter display like that shown in Fig.3.10. After the capacitor has fully charged (ie, when the voltage at D15 has reached and flattened off at 3.3V) replace the link and repeat the measurement with different values for C1 and R1 (try 10µF, 47µF and 220µF). Note how this affects the charging rate. C-R circuits are widely used in Fig.3.11. Simple LDR interface. electronics, as we will see a little later when we need to average a voltage over a period of time. Interfacing analogue sensors The ESP32’s analogue inputs provide you with a means of interfacing a variety of simple, low-cost analogue sensors. As an example, we will show you how to sense ambient light level using a light-dependent resistor (LDR). Interfacing analogue sensors with the ESP32 is usually very straightforward, as we will now show using an LDR. Most LDRs exhibit resistances of several megohms in total darkness falling progressively to a few hundred ohms in bright sunlight. Since the ESP32 can’t sense resistance directly, this resistance change needs to be converted to a corresponding change in voltage. This is easily done by connecting a series resistor to supply current to the LDR. The voltage dropped across the LDR will then be inversely proportional to incident light. This voltage can then be passed to an analogue input for sensing. A simple arrangement for light sensing is shown in Fig.3.11 with R1 and LDR1 forming a potential divider across the 3.3V supply. The level of ambient light can be displayed using the Serial Monitor using the code shown in Listing 3.3. Once again, we’ve used ADC2 Channel 3 and GPIO pin D15. A suggested wiring diagram is shown in Fig.3.12. If you execute the code shown in Listing 3.3 you will be rewarded with values that decrease as the intensity of incident light increases. Our readings varied from as low as 200 in strong sunlight to 4095 in full darkness. Average room lighting produced a value of around 2200. Fig.3.9. Suggested wiring layout for Fig.3.8. Fig.3.10. Serial Plotter display for Listing 3.2. Practical Electronics | May | 2024 Fig.3.12. Suggested wiring diagram for Fig.3.11. 43 Listing 3.4 Code for the automatic light controller /* Simple analogue LDR lighting controller using a relay. NB: The relay interface is active ‘low’ so a LOW output will turn the load ‘on’ while a HIGH output will turn it ‘off’. */ int ldrPin = 15; int relayPin = 4; int thresholdValue = 2000; // Analogue input via ADC2 Channel 3 // Digital output via GPIO 4 // Set darkness threshold void setup() { pinMode(relayPin, OUTPUT); // Set the relay as an output digitalWrite(relayPin, HIGH); // Start with light ‘off’ } void loop() { // Get the current analogue input from the LDR int ldrValue = analogRead(ldrPin); if (ldrValue < thresholdValue) digitalWrite(relayPin, HIGH); // Enough light so switch ‘off’ else digitalWrite(relayPin, LOW); // Not enough light so switch ‘on’ delay(1000); // 1s delay // Repeat forever } interface (see last month). This relay is activated by a LOW state output from D4. A suggested wiring diagram is shown in Fig.3.14. Note that the relay derives it positive supply from the development board’s +5V/VIN pin rather than from the +3.3V pin. The code for the automatic light controller is shown in Listing 3.4. This has been liberally commented and should be reasonably selfexplanatory. Note that we have set the switching threshold to 2000. This is an arbitrary value and can be changed to suit your own situation. ESP32 analogue output Having dealt with analogue input, it’s now time to introduce analogue output. Unfortunately, the ESP32 does not incorporate a true digital-toanalogue converter (DAC) so analogue output is based on a technique known as pulse-width modulation (PWM). Because the ESP32 uses PWM the analogWrite() function produces a rectangular pulse waveform rather than a continuous analogue voltage. This can be a tricky concept to grasp, so we will take some time to explain how it works. Fig.3.15 shows three different pulse waveforms. At any time, the voltage described by these waveforms can only be either HIGH or LOW. The waveform in Fig.3.15(a) is a perfect square wave and is HIGH for 50% of the time and low for the other 50%. This is equivalent to a mark-space ratio (HIGH-LOW ratio) of 1:1 or a duty cycle of 50%. Fig.3.13. Circuit of the automatic light controller. Fig.3.15. PWM principle. Fig.3.14. A suggested wiring layout for Fig.3.13. Check it out! Having demonstrated how easy it is to sense light level using a low-cost LDR it is worth showing how this inexpensive component can form the basis of a simple automatic lighting controller. Fig.3.13 shows the circuit of our automatic light controller. The output of our ESP32 at D4 is fed to a relay 44 Practical Electronics | May | 2024 analogWrite(outPin, 200); // Output 2.64V Fig.3.16 shows the output waveform produced when this function is executed. Note that the pulsed output is not constant (as with a true analogue signal). You can also define a custom range of duty cycle values. If we need 10-bit instead of 8-bit resolution we can use: analogWrite(outPin, 511, 1023); // Output 1.65V The default PWM frequency for the analogue channels is 5kHz but this can be changed if required. To set the frequency to 10kHz you could use: analogWriteFrequency(10000); // Generating waveforms Let’s now move on to generating a waveform rather than a steady average. This is a little bit trickier because we will need to code the waveform into a repetitive loop in which successive values of duty cycle are output via the DAC. Listing 3.5 shows an example of generating a stepped waveform: If you enter and execute the code in Listing 3.5 and connect a DC voltmeter between pin-15 and ground you should be rewarded with series of voltage values that steadily increment from zero to about 2.9V in increments of about 0.32V. This is a simple low-speed stepped waveform. You might now be wondering if it’s possible to generate a sinewave so let’s examine Fig.3.16. PWM output waveform. Listing 3.5 Code for generating a stepped output voltage /* Simple application to produce a stepped output Using the default ult PWM setting (5kHz) */ Fig.3.17. Improving the output waveform using a low-pass filter. The waveform in Fig.3.15(b) is HIGH for 25% of the time and low for the remaining 75%. It has a mark-space-ratio of 1:3. The HIGH time is one quarter of the total time for the cycle and so the duty cycle is 25%. The waveform in Fig.3.15(c) is HIGH for 75% of the time and low for the remaining 25%. It has a mark-space-ratio of 3:1. The HIGH time is three quarters of the total time for the cycle and so the duty cycle is thus 75%. Now look again at the three waveforms in Fig.3.15 and note how the average value of voltage differs according to the duty cycle. In Fig.3.15(a) the average value is 0.5V where V is the maximum value. The corresponding averages for Figs.3.15(b) and 3.15(c) are 0.25V and 0.75V. Thus, as we change the duty cycle of the wave we also change its average value. The first of the two parameters used in the analogWrite() function is the GPIO pin number, while the second relates to the duty cycle of the waveform. By default, this parameter can range from 0 to 255, corresponding to duty cycles from zero to 100%. So, for example, to generate a 50% duty cycle waveform at D15 we could use lines of the form: int outPin = 15; // PWM output pin // Output 1.65V Note that 1.65V is 50% of the 3.3V reference voltage. As a further example, let’s assume that we need to produce an output of 2.64V. We can determine the value to use for the duty cycle parameter from (2.64/3.3) × 255 = 200 so the code required is: Practical Electronics | May | 2024 // Use GPIO D15 void setup() { } void loop() { for (int step = 0; step < 10; step++) { analogWrite(outPin, step * 25); delay(2000); // 2 sec. delay between levels } } Listing 3.6 Code for sinewave generation using a look-up table /* Low frequency sine wave generator. This code uses the default PWM settings. */ int outPin = 15; // Use GPIO D15 // This is the sinewave lookup table: const uint8_t sineLUT[] = { 128, 152, 176, 198, 218, 234, 245, 253, 255, 253, 245, 234, 218, 198, 176, 152, 128, 103, 79, 57, 37, 21, 10, 2, 0, 2, 10, 21, 37, 57, 79, 103 }; void setup() { } and analogWrite(outPin, 128); int outPin = 15; void loop() { for (int step = 0; step < 32; step++) { analogWrite(outPin, sineLUT[step]); delay(3); // Delay sets frequency } } 45 + Fig.3.18. Sinewave output produced by Listing 3.6. two ways of doing this; first, using a look-up table (LUT) and second, by calculating values using the maths library. The code in Listing 3.6 produces a sinewave of about 2Vpk-pk with a frequency of 10Hz. The output waveform benefits from the addition of the simple C-R low-pass filter (R1 and C1) shown in Fig.3.17. This helps to smooth the output waveform and improve its shape. The frequency can be changed by altering delay(). Varying the delay parameter from 1 to 20 will change the frequency from 31Hz to 1.6Hz respectively. Fig.3.18 shows the waveform produced. Fig.3.19. Sinewave output produced by Listing 3.7. Gotcha! When using the noTone() function to turn off your tone you must ensure that the ‘T’ is in upper case, notone() just won’t work! There’s another way of generating a sinewave that uses the built-in maths library. Here we repeatedly calculate values as we need them rather than extract them from a look-up table. Listing 3.7 shows how this is done. Once again, the delay parameter sets the frequency of the sinewave output. Fig.3.19 shows the waveform produced. Using tone() and noTone() The ESP32 can generate a simple square wave using the tone() function. This is simple and expedient in many applications. For example, the code in Listing 3.8 generates an alarm signal consisting of repeated one-second bursts of 1kHz tone. All that you need is an audible transducer connected between pin-15 and ground. Generating TTL levels You may sometimes find that you need to generate a goodquality square wave at TTL (5V logic) levels. This can be easily done by adding an external transistor buffer stage, as shown in Fig.3.20. The TTL-compatible square wave generated by Listing 3.9 is shown in Fig.3.21. Fig.3.20. Using an external transistor buffer to provide a 5Vpk-pk TTL-compatible output. Listing 3.7 Code for generating a sinewave using maths library /* Low frequency sine wave generator. This code uses the math library. */ int outPin = 15; int level; // Use GPIO D15 void setup() { } void loop() { for (int step = 0; step < 36; step++) { level = 128 + (127 * sin(step * 10 * (6.28 / 360))); analogWrite(outPin, level); delay(5); // Delay sets frequency } } 46 LED brightness control Another important application of PWM is controlling the brightness of an LED display. This can be easily done by varying the duty cycle of the pulses supplied to an LED, Listing 3.8 Testing tone() and noTone() functions /* Tone test */ int outPin = 15; // Use GPIO D15 void setup() { } void loop() { tone(outPin, 1000); delay(1000); noTone(1000); delay(1000); } Practical Electronics | May | 2024 is the ones digit, the next digit to the left is the eights digit, next is the 64s digit, and so on. The valid digits are 0 to 7. For example, the value of the digits present in the octal number 127 can be calculated from: 1278 = (1 × 64) + (2 × 8) + (7 × 1) = 64 + 16 + 7 = 8710 Hexadecim al (base 16) number system Although microcontrollers are quite comfortable working with binary numbers of 8, 16, or even 32 binary digits, Fig.3.22. Denary, binary, octal humans find it inconvenient and hexadecimal numbers. Fig.3.21. A TTL-compatible square wave generated by Listing to work with so many digits 3.8 using the transistor buffer shown in Fig.3.20. at a time. The hexadecimal (base 16) numbering system offers a practical compromise. One hexadecimal digit and the ESP32 provides several functions for doing this, including can exactly represent four binary digits so an 8-bit binary ledcSetup(), ledcAttachPin(), and ledcWrite(). Listing number can be expressed using just two hexadecimal digits. Hexadecimal notation is thus much more compact 3.9 shows how this is done. than binary notation and often easier to work with than decimal notation. Coding Workshop In the hexadecimal (base 16) number system, the In this month’s Coding Workshop we introduce ways of representing numbers and characters in our code. This can be weight of each digit is sixteen times as great as the particularly useful when we need to send or receive data to/ digit immediately to its right. The rightmost digit of a from external devices. We will start with the base 10 (denary or hexadecimal integer is the ones digit, the next digit to decimal) number system that we’re all familiar with and move the left is the sixteens digit, next is the 256 digit and so on to binary (base 2), octal (base 8) and hexadecimal (base 16) on. The valid digits are 0 to F. Note that, because we have run out of numerical characters beyond 9, we introduce number systems. the letters A to F to represent numbers from 10 to 15. Fig.3.22 shows the equivalence between the first sixteen Denary (base 10) number system The denary (or decimal) system of numbers is something decimal, binary, and hexadecimal numbers. As an example of hexadecimal notation, the value that we are all familiar with because we use it every day of our lives. The valid digits in a decimal number are 0 to 9 of the digits present in the hexadecimal number 7F and the weight of each digit is 10-times greater than the digit can be accumulated from the following weightings: immediately to its right. The rightmost digit of a denary integer 7F16 = (7 × 16) + (15 × 1) = 112 + 15 = 12710. (ie, a whole number with no fractional part) is the units place, the digit to its left is the tens digit, the next is the 100s digit, Conversion from binary to hexadecimal (and vice and so on. For example, the value of the digits present in the versa) is very easily performed by simply arranging the denary number 123 can be accumulated from the following: binary number into groups of four digits from right to 12310 = (1 × 100) + (2 × 10) + (3 × 1) Note that we have used the suffix subscript 10 to indicate the number base. This can help avoid confusion, particularly when different number bases are being used concurrently. Binary (base 2) number system In the binary system (base 2), the weight of each digit is twice as great as the digit immediately to its right. The rightmost digit of the binary integer is the ones digit, the next digit to the left is the twos digit, next is the fours digit, then the eights digit, and so on. The only valid digits in the binary system are 0 and 1. For example, the value of the digits present in the binary number 1011 can be accumulated from the following: 10112 = (1 × 8) + (0 × 4) + (1 × 2) + (1 × 1) = 8 + 2 + 1 = 1110. Octal (base 8) number system In the octal (base 8) number system, the weight of each digit is eight times as great as the digit immediately to its right. The rightmost digit of an octal integer Practical Electronics | May | 2024 Listing 3.9 Using PWM to control LED brightness /* Simple PWM application to slowly increase the brightness of an LED indicator */ int ledPin = 15; int ledChan = 0; int freq = 1000; int bits = 8; int delayTime = 50; // Use GPIO D15 // LED channel // PWM frequency // 8-bit resolution // Delay between levels void setup() { ledcSetup(ledChan, freq, bits); ledcAttachPin(ledPin, ledChan); ledcWrite(ledChan, 0); } // Configure the PWM // and attach the LED // Start with LED off void loop() { for (int dutyCycle = 0; dutyCycle <= 255; dutyCycle++) { ledcWrite(ledChan, dutyCycle); // Output to the LED delay(delayTime); } } 47 Listing 3.10 Decimal, binary, octal, and hexadecimal table /* Decimal, binary, octal, and hexadecimal table */ void setup() { //Initialize serial and wait for port to open: Serial.begin(9600); while (!Serial) { ; // Wait for the serial port } delay(1000); // Print the table heading and // use tabs to separate columns Serial.println(“”); Serial.print(“Dec.”); Serial.print(“\t”); Serial.print(“Binary”); Serial.print(“\t”); Serial.print(“Octal”); Serial.print(“\t”); Serial.print(“Hex.”); Serial.print(“\t”); Serial.println(“ASCII”); } int lineValue = 64; int endValue = 127; Fig.3.23. Conversion of binary to hexadecimal (and vice versa). // Start value // End value void loop() { delay(200); Serial.print(lineValue); Serial.print(“\t”); Serial.print(lineValue, BIN); Serial.print(“\t”); Serial.print(lineValue, OCT); Serial.print(“\t”); Serial.print(lineValue, HEX); Serial.print(“\t”); Serial.println(char(lineValue)); if (lineValue == endValue) { while (true) { continue; // Got to the end! } } lineValue++; // Go round again ... } Table 3.1 Number base prefix Name Base Prefix Valid digits Example Denary 10 none 0 to 9 109 Binary 2 0b 0 and 1 0b11001000 Octal 8 0 0 to 7 0102 Hexadecimal 16 0x 0 to F 0x2C Gotcha! It’s important to avoid inserting an unnecessary 0 before a number because it will be interpreted as an octal value. So, only use the zero prefix if you really do want to use octal numbers! Fig.3.24. Serial Monitor display from Listing 3.10. is the default way of showing numbers. Binary, octal and hexadecimal numbers are denoted by using a prefix, as shown in Table 3.1. As an example of using hexadecimal values, the following code fragment show how our sinewave look-up table (LUT) appears when coded in hexadecimal rather than in denary: Fig.3.25. Circuit for the 1.5V battery tester. left and then converting each digit to its hexadecimal equivalent, as shown in Fig.3.23. Number base prefix When writing code, denary (base 10) 48 Practical Electronics | May | 2024 Listing 3.11 Code for the 1.5V battery tester const uint8_t sineLUT[] = { 0x80, 0x98, 0xb0, 0xc6, 0xda, 0xea, 0xf5, 0xfd, 0xff, 0xfd, 0xf5, 0xea, 0xda, 0xc6, 0xb0, 0x98, 0x80, 0x67, 0x4f, 0x39, 0x25, 0x15, 0x0a, 0x02, 0x00, 0x02, 0x0a, 0x15, 0x25, 0x39, 0x4f, 0x67 }; When sending data via the Serial Monitor you can have your numerical values printed in decimal (default), binary, octal and hexadecimal. You can also print the corresponding American Standard Code for Information Interchange (ASCII) character. Listing 3.10 shows how this is done. If you enter and execute the code you will be rewarded with a handy reference table like that shown in Fig.3.24. Note, for example, that the ASCII character ‘A’ can be variously represented by 63 (in decimal), 0b1000001 (in binary), 0101 (in octal), and 0x41 (in hexadecimal). Practical project Our Practical Project involves the design, construction, and coding of a simple tester for 1.5V alkaline batteries. The tester is to provide three different indications using a ‘traffic light’ display. A green LED will indicate that a battery is fresh and ready for service. An amber LED will indicate that the battery is partially drained but still serviceable. A red LED will show that the battery is exhausted and should be rejected. The circuit of the 1.5V battery tester is shown in Fig.3.25. The analogue input of the ESP32 (via D15) is protected by means of silicon diode D1. This component will only conduct when the battery is inserted correctly. If the battery is fitted incorrectly in its holder then the diode will be reverse biased and will not conduct. The 15Ω resistor (R1) provides a test load for the battery which demands a test current of about 50mA (note that 0.7V will be dropped across D1 when it’s conducting). The three series current-limiting resistors (R2 to R4) are located on the small ‘traffic light’ circuit board (see last month for further details). The wiring layout for the 1.5V alkaline battery tester is shown in Fig.3.26. Fig.3.26. Wiring layout for the 1.5V battery tester. /* Simple ESP32 1.5V battery tester */ // Assign LEDs to digital I/O lines int redLED = 23; // End-of life LED is red int amberLED = 22; // Mid-life LED is amber int greenLED = 21; // Beginning of life LED is green int analogPin = 15; // Analogue input via ADC2 Channel 3 float end = 1.1; // End-of-life threshold = 1.1V float mid = 1.4; // Mid-life threshold = 1.4V float diodeFwd = 0.75; void setup() { // Initialize digital I/O pins outputs pinMode(redLED, OUTPUT); pinMode(amberLED, OUTPUT); pinMode(greenLED, OUTPUT); // We need to adjust the thresholds to take into // account the forward voltage drop of the diode end = end - diodeFwd; mid = mid - diodeFwd; } void loop() { // Get the terminal voltage of the battery on-load int rawReading = analogRead(analogPin); // Convert the raw reading from the analogue input // to volts and store it as floating point float volts = rawReading * (3.3 / 4095); if (volts > mid) { // It’s above the mid threshold, put green on digitalWrite(greenLED, HIGH); digitalWrite(amberLED, LOW); digitalWrite(redLED, LOW); } if ((volts > end) && (volts < mid)) { // It’s between the two thresholds, put amber on digitalWrite(greenLED, LOW); digitalWrite(amberLED, HIGH); digitalWrite(redLED, LOW); } if (volts < end) { // It’a below the end threshold, put red on digitalWrite(greenLED, LOW); digitalWrite(amberLED, LOW); digitalWrite(redLED, HIGH); } delay(100); // Short delay } Teach-In Challenge This month’s Teach-In Challenge involves a modification to the 1.5V alkaline battery tester. Connect a piezoelectric buzzer between D18 (GPIO18) and ground. Then modify the code so that the tester beeps continuously whenever a fresh battery is inserted into the test holder. This will provide users with an audible indication of the state of a good battery without having to refer to the LED display. Here are the lines that you need to add to the code – but, we will leave you to decide where to add them! int outPin = 18; // Tone ouput for buzzer tone(outPin, 1000); // Output a beep delay(500); noTone(1000); Next month In Part 4, we will introduce seven-segment LED and matrix displays. Coding workshop will deal with random number generation and our Practical Project will feature a dice roller. Practical Electronics | May | 2024 49