Silicon ChipCircuit Surgery - July 2025 SILICON CHIP
  1. Contents
  2. Publisher's Letter: ChatGPT can analyse circuit diagrams
  3. Subscriptions
  4. Feature: The Fox Report by Barry Fox
  5. Feature: Circuit Surgery by Ian Bell
  6. Project: Compact OLED Clock/Timer by Tim Blythman
  7. Feature: Techno Talk by Max the Magnificent
  8. Feature: Max’s Cool Beans by Max the Magnificent
  9. Back Issues
  10. Project: 180-230V DC Motor Speed Controller by John Clarke
  11. Feature: Precision Electronics, part seven by Andrew Levido
  12. Project: Repurposing the Mains Power-Up Sequencer by John Clarke
  13. Feature: Audio Out by Jake Rothman
  14. Project: Intelligent Dual Hybrid Power Supply,.Part 2 by Phil Prosser
  15. PartShop
  16. Market Centre
  17. Advertising Index
  18. Back Issues

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

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

Articles in this series:
  • The Fox Report (July 2024)
  • The Fox Report (July 2024)
  • The Fox Report (September 2024)
  • The Fox Report (September 2024)
  • The Fox Report (October 2024)
  • The Fox Report (October 2024)
  • The Fox Report (November 2024)
  • The Fox Report (November 2024)
  • The Fox Report (December 2024)
  • The Fox Report (December 2024)
  • The Fox Report (January 2025)
  • The Fox Report (January 2025)
  • The Fox Report (February 2025)
  • The Fox Report (February 2025)
  • The Fox Report (March 2025)
  • The Fox Report (March 2025)
  • The Fox Report (April 2025)
  • The Fox Report (April 2025)
  • The Fox Report (May 2025)
  • The Fox Report (May 2025)
  • The Fox Report (July 2025)
  • The Fox Report (July 2025)
Articles in this series:
  • Circuit Surgery (April 2024)
  • STEWART OF READING (April 2024)
  • Circuit Surgery (April 2024)
  • STEWART OF READING (April 2024)
  • Circuit Surgery (May 2024)
  • Circuit Surgery (May 2024)
  • Circuit Surgery (June 2024)
  • Circuit Surgery (June 2024)
  • Circuit Surgery (July 2024)
  • Circuit Surgery (July 2024)
  • Circuit Surgery (August 2024)
  • Circuit Surgery (August 2024)
  • Circuit Surgery (September 2024)
  • Circuit Surgery (September 2024)
  • Circuit Surgery (October 2024)
  • Circuit Surgery (October 2024)
  • Circuit Surgery (November 2024)
  • Circuit Surgery (November 2024)
  • Circuit Surgery (December 2024)
  • Circuit Surgery (December 2024)
  • Circuit Surgery (January 2025)
  • Circuit Surgery (January 2025)
  • Circuit Surgery (February 2025)
  • Circuit Surgery (February 2025)
  • Circuit Surgery (March 2025)
  • Circuit Surgery (March 2025)
  • Circuit Surgery (April 2025)
  • Circuit Surgery (April 2025)
  • Circuit Surgery (May 2025)
  • Circuit Surgery (May 2025)
  • Circuit Surgery (June 2025)
  • Circuit Surgery (June 2025)
  • Circuit Surgery (July 2025)
  • Circuit Surgery (July 2025)
Articles in this series:
  • Techno Talk (August 2020)
  • Techno Talk (August 2020)
  • Techno Talk (September 2020)
  • Techno Talk (September 2020)
  • 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)
  • Techno Talk (July 2025)
  • Techno Talk (July 2025)
Articles in this series:
  • Max’s Cool Beans (January 2025)
  • Max’s Cool Beans (January 2025)
  • Max’s Cool Beans (February 2025)
  • Max’s Cool Beans (February 2025)
  • Max’s Cool Beans (March 2025)
  • Max’s Cool Beans (March 2025)
  • Max’s Cool Beans (April 2025)
  • Max’s Cool Beans (April 2025)
  • Max’s Cool Beans (May 2025)
  • Max’s Cool Beans (May 2025)
  • Max’s Cool Beans (June 2025)
  • Max’s Cool Beans (June 2025)
  • Max’s Cool Beans (July 2025)
  • Max’s Cool Beans (July 2025)
Items relevant to "180-230V DC Motor Speed Controller":
  • 180-230V DC Motor Speed Controller PCB [11104241] (AUD $15.00)
  • 180-230V DC Motor Speed Controller PCB pattern (PDF download) [11104241] (Free)
  • 180-230V DC Motor Speed Controller lid panel artwork and drilling templates (Free)
Articles in this series:
  • 180-230V DC Motor Speed Controller (July 2024)
  • 180-230V DC Motor Speed Controller (July 2024)
  • 180-230V DC Motor Speed Controller Part 2 (August 2024)
  • 180-230V DC Motor Speed Controller Part 2 (August 2024)
  • 180-230V DC Motor Speed Controller (July 2025)
  • 180-230V DC Motor Speed Controller (July 2025)
Articles in this series:
  • Precision Electronics, Part 1 (November 2024)
  • Precision Electronics, Part 1 (November 2024)
  • Precision Electronics, Part 2 (December 2024)
  • Precision Electronics, Part 2 (December 2024)
  • Precision Electronics, Part 3 (January 2025)
  • Precision Electronics, part one (January 2025)
  • Precision Electronics, part one (January 2025)
  • Precision Electronics, Part 3 (January 2025)
  • Precision Electronics, part two (February 2025)
  • Precision Electronics, Part 4 (February 2025)
  • Precision Electronics, Part 4 (February 2025)
  • Precision Electronics, part two (February 2025)
  • Precision Electronics, part three (March 2025)
  • Precision Electronics, part three (March 2025)
  • Precision Electronics, Part 5 (March 2025)
  • Precision Electronics, Part 5 (March 2025)
  • Precision Electronics, Part 6 (April 2025)
  • Precision Electronics, Part 6 (April 2025)
  • Precision Electronics, part four (April 2025)
  • Precision Electronics, part four (April 2025)
  • Precision Electronics, part five (May 2025)
  • Precision Electronics, Part 7: ADCs (May 2025)
  • Precision Electronics, part five (May 2025)
  • Precision Electronics, Part 7: ADCs (May 2025)
  • Precision Electronics, Part 8: Voltage References (June 2025)
  • Precision Electronics, part six (June 2025)
  • Precision Electronics, part six (June 2025)
  • Precision Electronics, Part 8: Voltage References (June 2025)
  • Precision Electronics, part seven (July 2025)
  • Precision Electronics, part seven (July 2025)
Items relevant to "Repurposing the Mains Power-Up Sequencer":
  • Mains Power-Up Sequencer PCB [10108231] (AUD $15.00)
  • Mains Power-Up Sequencer hard-to-get parts (Component, AUD $95.00)
  • Firmware (ASM and HEX) files for the Mains Power-Up Sequencer (Software, Free)
  • Mains Power-Up Sequencer PCB pattern (PDF download) [10108231] (Free)
  • Panel labels and cutting diagrams for the Mains Power-Up Sequencer (Panel Artwork, Free)
Articles in this series:
  • Mains Power-Up Sequencer, Pt1 (February 2024)
  • Mains Power-Up Sequencer, Pt1 (February 2024)
  • Mains Power-Up Sequencer, Pt2 (March 2024)
  • Mains Power-Up Sequencer, Pt2 (March 2024)
  • New use for Mains Sequencer (July 2024)
  • New use for Mains Sequencer (July 2024)
  • Mains Power-Up Sequencer, part one (February 2025)
  • Mains Power-Up Sequencer, part one (February 2025)
  • Mains Power-Up Sequencer, part two (March 2025)
  • Mains Power-Up Sequencer, part two (March 2025)
  • Repurposing the Mains Power-Up Sequencer (July 2025)
  • Repurposing the Mains Power-Up Sequencer (July 2025)
Articles in this series:
  • Audio Out (January 2024)
  • Audio Out (January 2024)
  • Audio Out (February 2024)
  • Audio Out (February 2024)
  • AUDIO OUT (April 2024)
  • AUDIO OUT (April 2024)
  • Audio Out (May 2024)
  • Audio Out (May 2024)
  • Audio Out (June 2024)
  • Audio Out (June 2024)
  • Audio Out (July 2024)
  • Audio Out (July 2024)
  • Audio Out (August 2024)
  • Audio Out (August 2024)
  • Audio Out (September 2024)
  • Audio Out (September 2024)
  • Audio Out (October 2024)
  • Audio Out (October 2024)
  • Audio Out (March 2025)
  • Audio Out (March 2025)
  • Audio Out (April 2025)
  • Audio Out (April 2025)
  • Audio Out (May 2025)
  • Audio Out (May 2025)
  • Audio Out (June 2025)
  • Audio Out (June 2025)
  • Audio Out (July 2025)
  • Audio Out (July 2025)
Items relevant to "Intelligent Dual Hybrid Power Supply,.Part 2":
  • Intelligent Dual Hybrid Power Supply PCB set (AUD $25.00)
  • Intelligent Dual Hybrid Power Supply regulator PCB [18107211] (AUD $7.50)
  • Intelligent Dual Hybrid Power Supply front panel control PCB [18107212] (AUD $2.50)
  • DSP Crossover CPU PCB [01106193] (AUD $5.00)
  • DSP Crossover LCD Adaptor PCB [01106196] (AUD $2.50)
  • PIC32MZ2048EFH064-250I/PT programmed for the Intelligent Dual Hybrid Power Supply [0110619A.HEX] (Programmed Microcontroller, AUD $30.00)
  • 128x64 Blue LCD screen with KS0108-compatible controller (Component, AUD $30.00)
  • Hard-to-get parts for the Intelligent Dual Hybrid Power Supply regulator board (Component, AUD $100.00)
  • Hard-to-get parts for the Intelligent Dual Hybrid Power Supply CPU board (Component, AUD $60.00)
  • LCD panel bezel for the Dual Intelligent Hybrid Power Supply (PCB, AUD $5.00)
  • Intelligent Dual Hybrid Power Supply firmware [0110619A.HEX] (Software, Free)
  • Intelligent Dual Hybrid Power Supply PCB patterns [18107211/2] (Free)
  • DSP Active Crossover/DDS/Reflow Oven PCB patterns (PDF download) [01106191-6] (Free)
Articles in this series:
  • Dual Hybrid Power Supply – Pt1 (February 2022)
  • Dual Hybrid Power Supply – Pt1 (February 2022)
  • Dual Hybrid Power Supply, part two (March 2022)
  • Dual Hybrid Power Supply, part two (March 2022)
  • Intelligent Dual Hybrid Power Supply, part one (June 2025)
  • Intelligent Dual Hybrid Power Supply, part one (June 2025)
  • Intelligent Dual Hybrid Power Supply,.Part 2 (July 2025)
  • Intelligent Dual Hybrid Power Supply,.Part 2 (July 2025)
Circuit Surgery Regular clinic by Ian Bell Topics in digital signal processing – Implementing DSP on a microcontroller, part two W e are looking at various topics related to digital signal processing (DSP). DSP covers a wide range of electronics applications where signals are manipulated, analysed, generated, stored or displayed as digital data but originate from and/or are converted to real-world signals for interaction with humans or other parts of the physical world. Fig.1 shows the key elements of a generic DSP system with a signal path from an analog input via digital processing to an analog output. This does not necessarily represent every DSP system (not all have all the parts shown), but it serves as a reference for the various subsystems we will look at. Last month, we introduced an example microcontroller-based DSP implementation that we will be working through. The aim is to implement a sinc filter, which we discussed in detail in earlier articles. We will use a DSP-capable processor from the STM32 family from ST Microelectronics on a B-L475E-IOT01A development board. However, other suitable processors and boards from the STM32 series could also be used. Last month, we gave an overview of some relevant aspects of the development board and ST’s development software (STM32­CubeIDE), relating this to the generic DSP structure in Fig.1. We will not give a full step-by-step walk-through on using the software. Therefore, if you would like to try these examples and are not already familiar with STM32CubeIDE, you should look at some online tutorials that cover basic projects, such as LED blinkers, to gain familiarity with the development tools. Analog In Antialiasing filter Sample and hold STM32CubeIDE can automatically generate the code required to initialise the microcontroller, configure pin usage and set-up on-chip peripheral hardware, such as the digital-to-analog converter (DAC). The requirements for a given project are set via the Device Configuration Tool. On saving the configuration, the software generates or updates the main code file for the project, writing the code required to set up the processor for the chosen development board and any user options from the Configuration Tool. The software installation includes a library of functions, referred to as the Hardware Abstraction Layer (HAL). This makes it easy to control the microcontroller and its peripherals without having detailed low-level knowledge of its hardware, such as knowing what binary codes to write to specific registers to achieve a hardware operation. These are used in the automatically generated configuration code, and should also be used where appropriate in user-written code (eg, to write a value to the DAC). Before looking at the implementation of filters, we will discuss individual aspects of the system: the DAC and ADC (analog-to-digital converter), sample timing and data transfer. We will look at simple approaches and their disadvantages and more advanced approaches that make better use of the microcontroller’s resources. We will provide code examples to illustrate the principles discussed. These can be run as fully operational projects on the development board. First we will consider controlling the DAC, which leads naturally to how sample timing can be organised. Digital ADC Digital processing Fig.1: a generic digital signal processing (DSP) system structure. 6 Reconstruction filter To test the basic operation of the DAC, we can make it generate a simple waveform and view it on an oscilloscope. Our final goal is to implement a filter, not a waveform synthesis system, so we will keep this very straightforward and just generate a stepped sawtooth (see Fig.2). This is simple to code, and includes some large jumps in output voltage, which will show how fast the DAC output can change. The step waveform is generated by initially setting the DAC output to DAC_Start, then subtracting DAC_Step each time a new value is required until the current value written to the DAC (DAC_Value) is less than the step value. At this point, the value will be set back to DAC_Start, and the process is repeated. We will use relatively large steps; stepping the DAC through every code value would result in a long, slow slope that may be difficult to resolve to individual steps on a basic oscilloscope. The waveform in Fig.2 is shown as a positive-only voltage, as this is what is available on the development board. The DAC output voltage range is determined by its reference voltage inputs. On the B-L475E-IOT01A1 board the VREF+ pin is connected to the 3.3V analog power supply (VDDA) and the VREF- pin is connected to ground (0V), as per page 3 of the schematic (https:// pemag.au/link/ac5j). The DAC output voltage therefore ranges from 0V to 3.3V. The DAC is a 12-bit device, so the maximum for DAC_Value Voltage DAC_Start DAC_Step Analog DAC Initial DAC setup Out Sample period Time Fig.2: the desired test signal from our DAC. Practical Electronics | July | 2025 is 2 12 – 1 = 4095, and the output voltage is given by: DAC_OUT = VREF+ × DAC_Value ÷ 4095 So, in this case: DAC_OUT = 3.3 × DAC_Value ÷ 4095 It is interesting to see how fast the DAC can be updated, so initially, no attempt will be made to set the sample period to a specific value. The code will comprise a free-running loop to calculate and apply the waveform described above. It is useful to have a digital output to indicate when a new value was sent to the DAC. Bringing a digital output high before a function is called to update the DAC and bringing it low again afterwards will result in waveforms as shown in Fig.3. The time between corresponding edges of the pulse waveform in successive cycles will be equal to the sampling time. The code required to create the waveforms in Figs.2 & 3 can be defined by the pseudocode shown in Listing 1. Auto-generated and user code Last month, we looked at the microcontroller’s DAC hardware and development board schematic, and determined that we will use DAC1’s output channel 2 on pin PA5. To briefly recap, this requires changing the pin usage of PA5 to DAC1_OUT2 in the Device Configuration Tool. We also need to configure the DAC itself (DAC1 in the Analog section of the Categories list). Here, in the Mode panel we change “OUT2 connected to” from “Disable” to “only to external pin” – this will enable the DAC and route its output signal to the pin. After making these changes, save the configuration and that will generate C code for the project. The generated main.c file contains about 700 lines of code, not all of which is directly relevant to this project. A lot of it configures the processor to be compatible with the development board. As is typical for embedded software, there is initialisation/configuration code that Voltage Digital waveform indicating DAC update DAC output Sample period Time Fig.3: pulses from an output help us measure the DAC output’s update timing. Practical Electronics | July | 2025 runs once, upon board reset or powerup, after which the program enters an infinite loop where the main operations are performed. The generated main loop is initially empty, so the program will initialise the hardware and then enter an infinite loop that does nothing. We need to add code before the main loop to define any variables and such that we require, plus code to perform any initialisation that is not automatically generated. We also need to write the code for the main loop. We can add functions to the file and call these from our code. As well as the main loop, we can also write code that is triggered to run by events in the hardware – we will be discussing this in more detail later. Listing 2 shows the code added to the auto-generated main.c file to implement the stepped DAC waveform, which is based on Listing 1. The variables discussed above are declared on lines 59 to 61. The code for the main loop is on lines 127 to 142. All of our code is placed between corresponding “User Code” start and end comments. We must always do this to prevent the code we add from being overwritten by any configuration updates. Some user code sections are expected to be used for specific purposes; for example, “PV” (Listing 2, line 58) is for private variables. Others are more general and just numbered. The main loop has two user sections, making it relatively easy to mistakenly add code between the sections (Listing 2, line 130), which would be erased after 58 59 60 61 62 … 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 Initialize DAC Start DAC converting DAC_Value = DAC_Start while (1) { if (DAC_Value < DAC_Step) DAC_Value = DAC_Start else DAC_Value = DAC_Value - DAC_Step Set digital output to 1 Set DAC output to DAC_Value Set digital output to 0 } Listing 1: pseudocode for generating a stepped sawtooth waveform. making any configuration changes. So avoid doing that. Starting the DAC Before the DAC can be used, it must be initialised to operate as needed by the design. The registers that control its operation, and the hardware on the chip that controls signal routing, must be set up as required. Most of this work is done by the code that is automatically generated by the Device Configuration Tool. The generated main.c file contains the definition of a global variable called hdac1, ie: DAC_HandleTypeDef hdac1; This variable is used to reference the DAC whenever we perform an operation on it using the HAL code library functions. A function named MX_DAC1_Init is called during the initialisation part of the microcontroller software, along /* USER CODE BEGIN PV */ uint32_t DAC_Start = 4095; // Value at peak of sawtooth wave uint32_t DAC_Value = 4095; // Current DAC output value uint32_t DAC_Step = 255; // Change in DAC value at each step (sample) /* USER CODE END PV */ /* USER CODE BEGIN 2 */ HAL_DAC_Start(&hdac1, DAC_CHANNEL_2); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ // Calculate next sawtooth waveform value for DAC if (DAC_Value < DAC_Step) DAC_Value = DAC_Start; else DAC_Value = DAC_Value - DAC_Step; // Update the DAC and pulse the D8 pin to help measure timing HAL_GPIO_WritePin(GPIOB, ARD_D8_Pin, GPIO_PIN_SET); HAL_DAC_SetValue (&hdac1, DAC_CHANNEL_2, DAC_ALIGN_12B_R, DAC_Value); HAL_GPIO_WritePin(GPIOB, ARD_D8_Pin, GPIO_PIN_RESET); } /* USER CODE END 3 */ Listing 2: user code for controlling the DAC. 7 Timing pulse DAC output Fig.4: the output from the DAC (blue) and timing pulses (red) from running the code in Listing 2, as captured by an oscilloscope. with various other peripheral configuration functions. It assigns the hdac1 handle, initialises the DAC and configures output channel 2 in accordance with our configuration settings. The function code itself can be found about around a third of the way through the generated main.c file. While the auto-generated code initialises the DAC and its output channel, we still must start it operating (performing conversions) by calling the HAL_DAC_Start function. This function needs to be added to the code before the main while() loop (Listing 2, line 122). HAL_DAC_Start requires two parameters. The first is the handle mentioned above, while the second specifies the DAC channel to use. We’re using channel 2, identified by DAC_CHANNEL_2, a value that is #defined in the HAL library. It is possible to do all this from scratch by finding details of the relevant definitions, functions and how to use them in the Description of STM32L4/L4+ HAL and low-layer drivers user manual (https://pemag.au/link/ac5h). Controlling the DAC output To set the output voltage of the DAC, we use the HAL library function HAL_DAC_SetValue (Listing 2, line 140). This function has four parameters. The first two are the DAC handle address and channel, as for the start function. The third is a data alignment specifier, while the fourth is the DAC output value. The DAC is a 12-bit bit type, but the value parameter is 32-bits, so the alignment specifier determines which of the 32 bits (eg, leftmost 12 or rightmost 12) are the DAC value. The code in Listing 2 uses the rightmost (lower) 12 bits, as this means the DAC value is equal to the numerical value of the parameter. However, care 8 must be taken to keep the parameter in the valid range of 0 to 4095. The value DAC_ALIGN_12B_R is defined in the library to set right 12-bit alignment. The digital output is set using the HAL_GPIO_WritePin function, which takes three parameters. The first identifies the GPIO port of the pin to be controlled (Port A to Port H); GPIOA to GPIOH are #defined to reference these ports. In this case, port B is being used (the pin used is PB2, ie, Port B, pin 2). The next parameter is the pin number. The file main.h, which is also automatically created as part of the project, has #defines for the board signals, relating them to the pins. So the value ARD_D8_Pin can be used here. It would also be possible to use GPIO_PIN_2, as that is #defined in the HAL library for pin 2. The final parameter is the state to set the pin to; GPIO_PIN_RESET and GPIO_PIN_SET are #defined for logic low and high, respectively. 58 59 60 61 62 63 … 89 90 91 92 93 … 138 139 140 141 142 143 144 145 146 147 Execution results I captured the DAC output from running the code in Listing 2 on an oscilloscope, as shown in Fig.4; it clearly corresponds with Figs.2 & 3. The timing pulse has a frequency of 564kHz (a 1.77μs period), as measured by the oscilloscope. Remember that we are not setting or controlling this sampling rate – the DAC update is free-running. The waveform is not quite as it should be, because the DAC takes longer than one sample period to change its output for the largest step; Fig.5 shows this in detail. The largest step is labelled as step 1, with others following from this. The positive edge of the timing pulse indicates the approximate time at which a new value is written to the DAC to request a new output value. For the large step (step 1), the request for the next value (step 2) occurs before the output has finished changing. Thus, the DAC output does not quite reach the final value for step 1 before it starts changing to the step 2 value. Step 2 /* USER CODE BEGIN PV */ uint32_t DAC_Start = 4095; // Value at peak of sawtooth wave uint32_t DAC_Value = 4095; // Current DAC output value uint32_t DAC_Step = 255; // Change in DAC value at each step (sample) uint32_t LoopDelay = 51; // Loop delay to set sample period /* USER CODE END PV */ int main(void) { /* USER CODE BEGIN 1 */ uint32_t i; // Loop counter for delay loop /* USER CODE END 1 */ // Update the DAC and pulse the D8 pin to help measure timing HAL_GPIO_WritePin(GPIOB, ARD_D8_Pin, GPIO_PIN_SET); HAL_DAC_SetValue (&hdac1, DAC_CHANNEL_2, DAC_ALIGN_12B_R, DAC_Value); HAL_GPIO_WritePin(GPIOB, ARD_D8_Pin, GPIO_PIN_RESET); // Add delay for (i=0; i<=LoopDelay; i++){} } /* USER CODE END 3 */ Listing 3: adding a delay to the DAC loop. Practical Electronics | July | 2025 Step 1 requested Step 1 almost Step 3 achieved requested Step 2 Step 2 Step 3 requested achieved achieved Timing pulse DAC output Fig.5: a zoom-in on the waveforms in Fig.4, allowing the timing to be seen more clearly. to step 3 operates correctly due to the much smaller output voltage change. We have no reason to expect that letting this DAC run freely would give perfect results, but this serves to show that we need to take account of the DAC’s capabilities. Setting the sampling period The microcontroller datasheet indicates that the DAC could take up to 3μs to change its output, so a 1.77μs sample period is too short. We could slow the DAC update down by adding a delay to the main loop. The HAL_Delay() function is potentially useful here; inserting a call to this in the loop could be used to control the duration of each iteration. However, HAL_Delay() only provides delays in milliseconds – so it can only be used to control sampling at 1ms intervals (a 1kHz sampling frequency or lower), which is far too slow for many applications. Another problem we have is that the HAL_Delay() function is not particularly accurate with respect to the passed delay parameter, particularly with smaller delays. A call of HAL_Delay(D) produces a delay between D and D+1 milliseconds. This is because it is implemented using a free-running hardware counter, and the +1 effect is due to ensuring the delay is at least D, even if the function is called just before the next counter update occurs. If the HAL_Delay() function is called repeatedly with a short gap between calls (as would happen in a loop with little other code), the calls will occur just after a counter update, so the delay will be close to D+1 each time. We can add shorter delays using a forloop that counts for a specified number of iterations and adjust this to control the timing (see Listing 3, line 144). To use the loop, we need to declare a loop variable (Listing 3, line 92) and set a value for the number of iterations to use (Listing 3, line 62). Fig.6 shows the output of the DAC with the for-loop delay added. The slower sampling rate means that the DAC is able to reach the required output level well before the next value is requested, even for the largest step. By experiment, a value of 51 for LoopDelay resulted in a sampling frequency of 96.4kHz, the closest to 96kHz (a commonly used sample frequency for audio) that could be obtained. This illustrates a problem with this approach – it is difficult to precisely set the sample frequency. LoopDelay values of 50 and 52 gave 97.9kHz and 94.9kHz, respectively, so 51 was the best option, but it is not really good enough. Another potential problem is that if the other code in the loop includes conditional operations (eg, if… then… else…), the number of instructions encountered on each loop iteration could be different, resulting in a varying delay value. This is bad news if we are implementing DSP that relies on a fixed sampling rate. In conclusion, adding a delay to a loop like this is a crude technique that does not allow precise control of timing, although it is easy to do. Hardware timers Accurate time measurement can be readily achieved using hardware timers. These are digital counter circuits on the microcontroller that are clocked using specific, accurately controlled frequencies. Such counter/timers can be started and stopped by the processor, and their count values can be set to specific values or read by the processor. The value in a counter incremented at a precise rate is directly proportional to the time elapsed since it started counting (or we can calculate the difference from when it was previously read). This can be used make time or frequency Timing pulse DAC output Fig.6: the DAC waveform with a sampling rate (frequency) of 96kHz. Practical Electronics | July | 2025 9 10 /1 80 SYSCLK (MHz) 80 AHB Prescaler /1 80 PCLK1 80 80MHz max AHB2 Prescaler /1 80 80 APB1 peripheral clocks (MHz) 80 APB1 timer clocks (MHz) X1 80 PCLK2 80MHz max > measurements of external events, or to control operations or events that must be accurately timed. Both one-off and repetitive timed operations can be controlled this way. The previously mentioned function HAL_Delay() uses count difference values, but does not meet our requirements primary due to the fact that only millisecond timing is available. To measure or control time with a timer, we need to set the frequency at which it is clocked. In STM32CubeIDE, this can be checked (and, if necessary, modified) in the Clock Configuration tab of the Device Configuration Tool. This provides a schematic of the complete clock system of the microcontroller, which is quite complex. The microcontroller datasheet (https:// pemag.au/link/ac5i) provides full details on the clock system. The block schematic diagram in the Clock Configuration tab of the Configuration Tool shows that the timers are connected to the APB1/APB2 timer clocks that run at the 80MHz system clock (SYSCLK) frequency by default (APB is Advanced Peripheral Bus). Fig.7 shows the relevant part of the diagram. The timer clock frequency can be reduced using the timer’s prescaler (the APB1/APB2 prescalers in Fig.7). A prescaler is a circuit that reduces a high frequency to a lower one by an integer factor. The longer the period that needs timing, the lower the counter clock frequency needs to be for the counter to be able to count to a number within its range in the desired period. The prescaler facilitates a very wide range of timing capability for the on-chip timers. For this project, dividing by 8 in the prescaler will make the counter count at 10MHz (one count every 0.1μs), which will facilitate easy calculation of delay values in the code. There are several different timer types on STM32 microcontrollers (basic, general purpose, advanced etc). The microcontroller documentation provides details on this, allowing users to determine which best fits their requirements. We need a basic timer, and PCLK1 80MHz max > Listing 4: pseudocode for using a timer to control the DAC output loop rate. 80 FCLK cortex clock (MHz) AHB1 Prescaler > Initialize DAC Initialize Timer Start DAC converting Start Timer running while(1) { Reset Timer to zero Calculate new DAC value Update the DAC Wait until the Timer has reached the required count value } 80 APB2 peripheral clocks (MHz) 80 APB2 timer clocks (MHz) X1 Fig.7: timer connections in the microcontroller’s clock tree. timer 6 (TIM6) on our microcontroller fulfils this role. Before using the timer, we need to set it up using the Configuration Tool. To do this, go to the Pinout & Configuration tab of the Device Configuration Tool, click on Timers in the Categories list to expand it, then click on TIM6. Click on the Activated checkbox, which will cause the configuration settings to be displayed (Fig.8). The frequency is divided by one plus the value entered into the prescaler configuration, so entering zero gives no frequency reduction (divide by 1). To set divide-by-eight, a value of seven is required in the “Prescaler (PSC – 16 bits value)” field of the configuration (Parameter Settings tab). It is common practice for STM32CubeIDE users to write this as “8-1” (eight minus one) to make the required division value more obvious to read. Using the timer We will start with a simple use of the timer, similar to the previous example and outlined by the pseudocode in Listing 4. A timer is set to start counting at the beginning of the loop. As this is separate hardware, it is not affected by running other code, including the DAC update code. Once the DAC is updated, the code waits for the counter to reach a specific value before finishing the loop. The time taken to execute each loop iteration, equal to the DAC sampling rate, is the time taken for the counter to reach the specified count value, so: DAC_sample_period = counter_period × delay_count_value The sample period must be set to be longer than the time it takes to update the DAC and check the timer at least once; otherwise, the required delay time will already have passed before the waiting part is reached, and the timer will not be in control of how long the loop takes to iterate. It is not a problem if the code has condition operations and may take different times on each iteration (unlike the forloop delay) as long as the slowest case is fast enough. If follows that we do not need to know exactly how long the DAC update code takes, as the wait-for-timer step will control the loop iteration time. However, the delay time must also be longer than the time the DAC output voltage takes to change, which is likely to be longer than the time taken to run the ‘update DAC’ code (as seen in the previous examples). Timer-based DAC code This example was created as a new Fig.8: the microcontroller’s timer configuration settings in the IDE. Practical Electronics | July | 2025 STM32CubeIDE project, with the pins and DAC configured as in the previous example and Timer 6 configured as just discussed. The DAC waveform was calculated in the same way as before, so the same variables are declared for this, along with Time_Delay for setting the required counter value in the waiting step (Listing 5, lines 61–64). The timer is started in the User Code 2 section, along with the DAC. Timer 6 is referenced using the htim6 handle in the same way as hdac1 is used for the DAC (Listing 5, lines 126 & 127). The main loop uses the same code as before to calculate the DAC values and set its output and timing pulse (Listing 5, lines 140–148). As indicated in the pseudocode, we need to start the counter from zero in each loop iteration. This can be done by writing zero directly to the count register (Listing 5, line 137). To check if the counter has reached the required value for the delay, its current value can be read using a HAL function __HAL_TIM_GET_COUNTER(&htim6) (Listing 5, line 151). For a 96kHz sampling rate, we need a sample period of 1 ÷ 96kHz = 10.417μs. Given that the counter increments at 0.1μs intervals, we should use a value of 104 for Time_Delay. However, experiments indicated that 101 gives the closest value at 96.7kHz. The DAC waveform is essentially the same as shown in Fig.6. 60 61 62 63 64 65 … 125 126 127 128 … 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 The loop delay is about 0.3μs longer than the timer value used. This is due to the time between the counter reaching its ultimate value and it being reset. During this time, the processor is executing the code that controls the loop iterations for the waiting and main loops. Using a prescaler value of 8 makes the time increment nice round 0.1μs steps, but this limits the choice of frequencies, particularly for higher values. Interrupts This example makes use of a hardware timer, but not in the most efficient way. Software-based delays such as using for-loops or the HAL_Delay() function are referred to as blocking code – they tie up the processor when it could be doing more useful operations. Although in the second example, the DAC is updated while the timer is independently running, we still have blocking code constantly checking the timer. A better approach it to use an interrupt to automatically trigger some specific code to run (in this case, the DAC update) when the required time has elapsed (the DAC sampling time in this case). In general, an interrupt is an input to a processor that causes it to stop what it is doing and respond to the interrupt event by running some specific code. After completing the interrupt code, /* USER CODE BEGIN PV */ uint32_t DAC_Start = 4095; // Value at peak of sawtooth wave uint32_t DAC_Value = 4095; // Current DAC output value uint32_t DAC_Step = 255; // Change in DAC value at each step (sample) uint32_t Time_Delay = 101; // TIM6 timer delay count /* USER CODE END PV */ /* USER CODE BEGIN 2 */ HAL_DAC_Start(&hdac1, DAC_CHANNEL_2); HAL_TIM_Base_Start(&htim6); /* USER CODE END 2 */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ htim6.Instance->CNT = 0; // Reset counter // Calculate next sawtooth waveform value for DAC if (DAC_Value < DAC_Step) DAC_Value = DAC_Start; else DAC_Value = DAC_Value - DAC_Step; // Update the DAC and pulse the D8 pin to help measure timing HAL_GPIO_WritePin(GPIOB, ARD_D8_Pin, GPIO_PIN_SET); HAL_DAC_SetValue (&hdac1, DAC_CHANNEL_2, DAC_ALIGN_12B_R, DAC_Value); HAL_GPIO_WritePin(GPIOB, ARD_D8_Pin, GPIO_PIN_RESET); // Wait until the TIM6 counter reaches the time delay before iterating while (__HAL_TIM_GET_COUNTER(&htim6) < Time_Delay); } /* USER CODE END 3 */ Listing 5: using a timer to control the DAC’s sample update rate timing. Practical Electronics | July | 2025 called an Interrupt Service Routine (ISR), the processor returns to the previous activity exactly where it left off. Interrupts can be triggered by any event that can be created or detected by hardware, either internal (on-chip) or external to the processor (such as a digital input pin changing state). For timers, an interrupt can be generated after a specific elapsed time, causing the ISR code to run at that time. To produce a signal from a DAC, a hardware timer can be used to generate an interrupt at regular intervals (at the DAC sampling rate) with the ISR configured to set the DAC value. Like other hardware setups, the Device Configuration Tool is used to control what conditions can cause interrupts in a design – for example, a timer can be set to cause an interrupt when it reaches a particular count value. In a later article, we will see how data transfer from a memory buffer to a DAC can be achieved automatically without having to explicitly write to the DAC in our code. This is called direct memory access (DMA). We just have to periodically update the buffer with the DAC waveform; interrupts can be arranged to occur when this needs doing. Callbacks The STM32 code libraries include all the general code required to make ISRs work correctly (eg, to enable the processor to switch safely from what it was doing to the ISR and back). However, the libraries cannot contain the code the developer wants to run when a specific interrupt occurs. To achieve this, the library defines special ‘callback’ functions that the user can define to handle their specific part of the ISR. Callback functions are widely used in software in a situation where there is code that needs to do ‘something’ when a particular condition occurs, but at the time of writing the code managing the condition detection (eg, by the microcontroller library developer), it is not known what that ‘something’ is. A mechanism is required that can later define what the ‘something’ is to run without changing the general condition/interrupt handling code. There are a few ways to do this; one is to define the name of a function that will get called when the ‘something’ needs to be done. Effectively, the function already exists but does nothing; however, if another function with the same name is written (by the user) then it will be run instead. The user simply writes the required code in a function with the predefined name, and it automatically 11 Fig.9: the timer settings for interrupt use (a portion of the Parameter tab is at the top, while the NVIC tab is below it). runs when the related condition (eg, an interrupt) occurs. The STM32 HAL library provides many predefined callback functions. In this case, we want an interrupt after a time period has elapsed, as measured by a timer, so we use the function named HAL_TIM_PeriodElapsedCallback(). This function is called when various timers trigger an interrupt, so the code has to check which timer caused it to be called. A timer handle is passed to the function as a parameter named htim. This can be used to find out which timer caused the callback to run. For example, the code if (htim == &htim6) will only run what follows that statement if it was Timer 6. Full details are shown in the example code later. set PSC = 8 (as previously) and ARR = 100 (entered as 100-1 in the configuration), the period between interrupt calls is (100 × 8) ÷ 80MHz = 10μs. This will be the sampling period if the timer period elapsed ISR callback is used to update the DAC (a sampling frequency of 100kHz). To set this up in the Device Configuration Tool, select TIM6, check that it is activated and has the prescaler value set to 8-1 as previously. In the Parameter Settings tab, set the Counter Period to 100-1, as shown in Fig.9. Leave other values as their defaults. In the NVIC (Nested Vectored Interrupt Controller) Settings Tab, enable the global interrupt for TIM6 by checking the Enable box (see Fig.9). Configuring a timer for interrupts In the examples discussed so far, the timer is set up via the Device Configuration Tool. This is fine in some situations, but it might be useful to be able set the sampling frequency in user code (eg, if it needs to vary). We can look at the auto-generated code to find out how to configure the timer (or check the datasheet). The function MX_TIM6_Init() is called before the main loop, along with various other hardware initialisation functions. The code of the function itself can be found about halfway through the auto-generated main.c file. In the MX_TIM6_Init() function, The period of a timer can be set in the Device Configuration Tool and is referred to as the Count Period or the AutoReload Register (ARR) value. The counter elapsed interrupt occurs once every ARR counts of the counter. As discussed above, the clock period of the counter is given by the APBx timer clock (f APB) divided by the prescaler (PSC) value. Thus, the period for one count increment is PSC ÷ fAPB. The interrupt occurs every ARR counts, so the period between interrupt calls is (ARR × PSC) ÷ fAPB. For example, knowing fAPB = 80MHz, if we 144 145 156 147 148 149 150 151 152 153 Configuring the timer with code /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ HAL_GPIO_TogglePin(GPIOB, LED2_Pin); HAL_Delay(500); } /* USER CODE END 3 */ Listing 7: blinking the LED simultaneously with the DAC waveform generation. 12 various values are set via the htim6 handle, based on the values entered via the Device Configuration Tool. For example, the period value of 100 discussed above is set using the code htim6.Init.Period = 100-1; After this, HAL_TIM_Base_Init() is called to initialise the timer with the appropriate values. The function returns a status value, which should equal HAL_OK. An error handling function, Error_Handler(), is called if there is a problem. Similar code can be used again later (in user code) to change the timer settings. Code can also be written to calculate the counter parameter values from other data; for example, the period value can be calculated, based on having the required frequency (in kHz) and prescaler values, using P e r i o d = (80000.0/Prescaler)/Frequency; where 80000 is the 80MHz APBx timer clock frequency in kHz. Timer-Interrupt DAC project code This example was again created as a new STM32CubeIDE project. The configuration of the pins and DAC was as in the previous example, and Timer 6 was set up in the Configuration Tool as just described. The DAC waveform is calculated as in the previous examples, so uses the same variables (Listing 6, lines 61–63). Other variables are declared for prescaler, period and frequency, to allow these to be manipulated in code as just discussed (Listing 6, lines 64–66). The period and frequency are floating-point values to ensure accurate calculation. Before the main while() loop, in the User Code 2 section, the DAC is started as previously (Listing 6, line 128). Then the period configuration value for the timer is calculated as just described, and the prescaler and period values are set via the htim6 handle, followed by reinitialisation of the timer. Practical Electronics | July | 2025 The timer is then started (Listing 6, lines 130–140), at which point interrupts will be start being generated at timer period intervals. In this example, the prescaler value is fixed for simplicity, but code could be written to set the prescaler to the most suitable value for a given frequency. The DAC waveform and timing pulse code are the same as in previous examples, but these are now placed in the HAL_TIM_PeriodElapsedCallback() ISR in the User Code 4 section, near the end of the auto-generated main.c file (Listing 6, lines 750–767). As discussed above, the ISR checks that Timer 6 caused the interrupt (Listing 6, line 753) before running the DAC code. The DAC and timing pulse waveforms are again as in Fig.6, but now the measured frequency is very close to the required value (measured at 96.02kHz by the oscilloscope) due to the lower prescaler value. The main while() loop does not contain any code in this version, but to illustrate the processor performing other operations when it is not serving the timer interrupt we can add LED blinking code to the main loop, as shown in Listing 7. This toggles a digital output pin connected to one LED on the development board every half second (500ms), using the HAL_Delay() function. The LED blinking does not have any effect on the DAC’s sampling frequency. PE 60 61 62 63 64 65 66 67 … 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 … 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 /* USER CODE BEGIN PV */ uint32_t DAC_Start = 4095; // Value at peak of sawtooth wave uint32_t DAC_Value = 4095; // Current DAC output value uint32_t DAC_Step = 255; // Change in DAC value at each step (sample) uint32_t Prescaler = 1; // Timer prescaler setting float Period; // Timer period in us float Frequency = 96.0; // DAC sample frequency in kHz /* USER CODE END PV */ /* USER CODE BEGIN 2 */ HAL_DAC_Start(&hdac1, DAC_CHANNEL_2); // Calculate TIM6 counter period (ARR) from required DAC sample frequency // Clock frequency is 80 MHz Period = (80000.0/Prescaler)/Frequency; // F in KHz htim6.Init.Prescaler = Prescaler-1; htim6.Init.Period = Period-1; // Reinitialise TIM6 with new values and start it if (HAL_TIM_Base_Init(&htim6) != HAL_OK) { Error_Handler(); Listing 6: using timer } interrupts to control the DAC’s HAL_TIM_Base_Start_IT(&htim6); /* USER CODE END 2 */ sample update rate timing. /* USER CODE BEGIN 4 */ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim == &htim6) { // Calculate next sawtooth waveform value for DAC if (DAC_Value <= DAC_Step) DAC_Value = DAC_Start; else DAC_Value = DAC_Value - DAC_Step; // Update the DAC and pulse the D8 pin to help measure timing HAL_GPIO_WritePin(GPIOB, ARD_D8_Pin, GPIO_PIN_SET); HAL_DAC_SetValue (&hdac1, DAC_CHANNEL_2, DAC_ALIGN_12B_R, DAC_Value); HAL_GPIO_WritePin(GPIOB, ARD_D8_Pin, GPIO_PIN_RESET); } } /* USER CODE END 4 */ 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)