Silicon ChipCircuit Surgery - September 2025 SILICON CHIP
  1. Contents
  2. Publisher's Letter: Why I don’t put a space between numbers and units
  3. Feature: The Fox Report by Barry Fox
  4. Project: Compact Hi-Fi Headphone Amplifier, part one by Nicholas Vinen
  5. Feature: 0.91-inch monochrome OLED display modules by Jim Rowe
  6. Project: Two Discrete Ideal Bridge Rectifiers by Phil Prosser & Ian Ashford
  7. Feature: Max’s Cool Beans by Max the Magnificent
  8. Project: Automatic LQ Meter by Charles Kosina
  9. Back Issues
  10. Feature: Net Work by Alan Winstanley
  11. Feature: Circuit Surgery by Ian Bell
  12. Feature: Altium Designer 2025 review by Tim Blythman
  13. Project: Dual-Rail Load Protector by Stefan Keller -Tuberg
  14. Back Issues
  15. Feature: Audio Out by Max the Magnificent
  16. PartShop
  17. Market Centre
  18. Advertising Index
  19. Back Issues

This is only a preview of the September 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)
  • The Fox Report (August 2025)
  • The Fox Report (August 2025)
  • The Fox Report (September 2025)
  • The Fox Report (September 2025)
Items relevant to "Compact Hi-Fi Headphone Amplifier, part one":
  • Compact HiFi Headphone Amplifier PCB [01103241] (AUD $7.50)
  • Dual Horizontal PCB-mounting RCA sockets (white/red) [RCA-210] (Component, AUD $2.50)
  • Compact HiFi Headphone Amplifier kit (Component, AUD $70.00)
  • Compact HiFi Headphone Amplifier PCB pattern (PDF download) [01103241] (Free)
  • Compact HiFi Headphone Amplifier panel drilling diagram (Panel Artwork, Free)
Articles in this series:
  • Compact HiFi Headphone Amp (December 2024)
  • Compact HiFi Headphone Amp (December 2024)
  • Compact HiFi Headphone Amp (January 2025)
  • Compact HiFi Headphone Amp (January 2025)
  • Compact Hi-Fi Headphone Amplifier, part one (September 2025)
  • Compact Hi-Fi Headphone Amplifier, part one (September 2025)
Articles in this series:
  • El Cheapo Modules From Asia - Part 1 (October 2016)
  • El Cheapo Modules From Asia - Part 1 (October 2016)
  • El Cheapo Modules From Asia - Part 2 (December 2016)
  • El Cheapo Modules From Asia - Part 2 (December 2016)
  • El Cheapo Modules From Asia - Part 3 (January 2017)
  • El Cheapo Modules From Asia - Part 3 (January 2017)
  • El Cheapo Modules from Asia - Part 4 (February 2017)
  • El Cheapo Modules from Asia - Part 4 (February 2017)
  • El Cheapo Modules, Part 5: LCD module with I²C (March 2017)
  • El Cheapo Modules, Part 5: LCD module with I²C (March 2017)
  • El Cheapo Modules, Part 6: Direct Digital Synthesiser (April 2017)
  • El Cheapo Modules, Part 6: Direct Digital Synthesiser (April 2017)
  • El Cheapo Modules, Part 7: LED Matrix displays (June 2017)
  • El Cheapo Modules, Part 7: LED Matrix displays (June 2017)
  • El Cheapo Modules: Li-ion & LiPo Chargers (August 2017)
  • El Cheapo Modules: Li-ion & LiPo Chargers (August 2017)
  • El Cheapo modules Part 9: AD9850 DDS module (September 2017)
  • El Cheapo modules Part 9: AD9850 DDS module (September 2017)
  • El Cheapo Modules Part 10: GPS receivers (October 2017)
  • El Cheapo Modules Part 10: GPS receivers (October 2017)
  • El Cheapo Modules 11: Pressure/Temperature Sensors (December 2017)
  • El Cheapo Modules 11: Pressure/Temperature Sensors (December 2017)
  • El Cheapo Modules 12: 2.4GHz Wireless Data Modules (January 2018)
  • El Cheapo Modules 12: 2.4GHz Wireless Data Modules (January 2018)
  • El Cheapo Modules 13: sensing motion and moisture (February 2018)
  • El Cheapo Modules 13: sensing motion and moisture (February 2018)
  • El Cheapo Modules 14: Logarithmic RF Detector (March 2018)
  • El Cheapo Modules 14: Logarithmic RF Detector (March 2018)
  • El Cheapo Modules 16: 35-4400MHz frequency generator (May 2018)
  • El Cheapo Modules 16: 35-4400MHz frequency generator (May 2018)
  • El Cheapo Modules 17: 4GHz digital attenuator (June 2018)
  • El Cheapo Modules 17: 4GHz digital attenuator (June 2018)
  • El Cheapo: 500MHz frequency counter and preamp (July 2018)
  • El Cheapo: 500MHz frequency counter and preamp (July 2018)
  • El Cheapo modules Part 19 – Arduino NFC Shield (September 2018)
  • El Cheapo modules Part 19 – Arduino NFC Shield (September 2018)
  • El cheapo modules, part 20: two tiny compass modules (November 2018)
  • El cheapo modules, part 20: two tiny compass modules (November 2018)
  • El cheapo modules, part 21: stamp-sized audio player (December 2018)
  • El cheapo modules, part 21: stamp-sized audio player (December 2018)
  • El Cheapo Modules 22: Stepper Motor Drivers (February 2019)
  • El Cheapo Modules 22: Stepper Motor Drivers (February 2019)
  • El Cheapo Modules 23: Galvanic Skin Response (March 2019)
  • El Cheapo Modules 23: Galvanic Skin Response (March 2019)
  • El Cheapo Modules: Class D amplifier modules (May 2019)
  • El Cheapo Modules: Class D amplifier modules (May 2019)
  • El Cheapo Modules: Long Range (LoRa) Transceivers (June 2019)
  • El Cheapo Modules: Long Range (LoRa) Transceivers (June 2019)
  • El Cheapo Modules: AD584 Precision Voltage References (July 2019)
  • El Cheapo Modules: AD584 Precision Voltage References (July 2019)
  • Three I-O Expanders to give you more control! (November 2019)
  • Three I-O Expanders to give you more control! (November 2019)
  • El Cheapo modules: “Intelligent” 8x8 RGB LED Matrix (January 2020)
  • El Cheapo modules: “Intelligent” 8x8 RGB LED Matrix (January 2020)
  • El Cheapo modules: 8-channel USB Logic Analyser (February 2020)
  • El Cheapo modules: 8-channel USB Logic Analyser (February 2020)
  • New w-i-d-e-b-a-n-d RTL-SDR modules (May 2020)
  • New w-i-d-e-b-a-n-d RTL-SDR modules (May 2020)
  • New w-i-d-e-b-a-n-d RTL-SDR modules, Part 2 (June 2020)
  • New w-i-d-e-b-a-n-d RTL-SDR modules, Part 2 (June 2020)
  • El Cheapo Modules: Mini Digital Volt/Amp Panel Meters (December 2020)
  • El Cheapo Modules: Mini Digital Volt/Amp Panel Meters (December 2020)
  • El Cheapo Modules: Mini Digital AC Panel Meters (January 2021)
  • El Cheapo Modules: Mini Digital AC Panel Meters (January 2021)
  • El Cheapo Modules: LCR-T4 Digital Multi-Tester (February 2021)
  • El Cheapo Modules: LCR-T4 Digital Multi-Tester (February 2021)
  • El Cheapo Modules: USB-PD chargers (July 2021)
  • El Cheapo Modules: USB-PD chargers (July 2021)
  • El Cheapo Modules: USB-PD Triggers (August 2021)
  • El Cheapo Modules: USB-PD Triggers (August 2021)
  • El Cheapo Modules: 3.8GHz Digital Attenuator (October 2021)
  • El Cheapo Modules: 3.8GHz Digital Attenuator (October 2021)
  • El Cheapo Modules: 6GHz Digital Attenuator (November 2021)
  • El Cheapo Modules: 6GHz Digital Attenuator (November 2021)
  • El Cheapo Modules: 35MHz-4.4GHz Signal Generator (December 2021)
  • El Cheapo Modules: 35MHz-4.4GHz Signal Generator (December 2021)
  • El Cheapo Modules: LTDZ Spectrum Analyser (January 2022)
  • El Cheapo Modules: LTDZ Spectrum Analyser (January 2022)
  • Low-noise HF-UHF Amplifiers (February 2022)
  • Low-noise HF-UHF Amplifiers (February 2022)
  • A Gesture Recognition Module (March 2022)
  • A Gesture Recognition Module (March 2022)
  • Air Quality Sensors (May 2022)
  • Air Quality Sensors (May 2022)
  • MOS Air Quality Sensors (June 2022)
  • MOS Air Quality Sensors (June 2022)
  • PAS CO2 Air Quality Sensor (July 2022)
  • PAS CO2 Air Quality Sensor (July 2022)
  • Particulate Matter (PM) Sensors (November 2022)
  • Particulate Matter (PM) Sensors (November 2022)
  • Heart Rate Sensor Module (February 2023)
  • Heart Rate Sensor Module (February 2023)
  • UVM-30A UV Light Sensor (May 2023)
  • UVM-30A UV Light Sensor (May 2023)
  • VL6180X Rangefinding Module (July 2023)
  • VL6180X Rangefinding Module (July 2023)
  • pH Meter Module (September 2023)
  • pH Meter Module (September 2023)
  • 1.3in Monochrome OLED Display (October 2023)
  • 1.3in Monochrome OLED Display (October 2023)
  • 16-bit precision 4-input ADC (November 2023)
  • 16-bit precision 4-input ADC (November 2023)
  • 1-24V USB Power Supply (October 2024)
  • 1-24V USB Power Supply (October 2024)
  • 14-segment, 4-digit LED Display Modules (November 2024)
  • 0.91-inch OLED Screen (November 2024)
  • 14-segment, 4-digit LED Display Modules (November 2024)
  • 0.91-inch OLED Screen (November 2024)
  • The Quason VL6180X laser rangefinder module (January 2025)
  • The Quason VL6180X laser rangefinder module (January 2025)
  • TCS230 Colour Sensor (January 2025)
  • TCS230 Colour Sensor (January 2025)
  • Using Electronic Modules: 1-24V Adjustable USB Power Supply (February 2025)
  • Using Electronic Modules: 1-24V Adjustable USB Power Supply (February 2025)
  • Low-cost electronic modules: 8×16 LED Matrix module (July 2025)
  • Low-cost electronic modules: 8×16 LED Matrix module (July 2025)
  • Modules: Thin-Film Pressure Sensor (August 2025)
  • Modules: Thin-Film Pressure Sensor (August 2025)
  • 0.91-inch monochrome OLED display modules (September 2025)
  • 0.91-inch monochrome OLED display modules (September 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)
  • Max’s Cool Beans (August 2025)
  • Max’s Cool Beans (August 2025)
  • Max’s Cool Beans (September 2025)
  • Max’s Cool Beans (September 2025)
Articles in this series:
  • Win a Microchip Explorer 8 Development Kit (April 2024)
  • Win a Microchip Explorer 8 Development Kit (April 2024)
  • Net Work (May 2024)
  • Net Work (May 2024)
  • Net Work (June 2024)
  • Net Work (June 2024)
  • Net Work (July 2024)
  • Net Work (July 2024)
  • Net Work (August 2024)
  • Net Work (August 2024)
  • Net Work (September 2024)
  • Net Work (September 2024)
  • Net Work (October 2024)
  • Net Work (October 2024)
  • Net Work (November 2024)
  • Net Work (November 2024)
  • Net Work (December 2024)
  • Net Work (December 2024)
  • Net Work (January 2025)
  • Net Work (January 2025)
  • Net Work (February 2025)
  • Net Work (February 2025)
  • Net Work (March 2025)
  • Net Work (March 2025)
  • Net Work (April 2025)
  • Net Work (April 2025)
  • Net Work (September 2025)
  • Net Work (September 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)
  • Circuit Surgery (August 2025)
  • Circuit Surgery (August 2025)
  • Circuit Surgery (September 2025)
  • Circuit Surgery (September 2025)
Articles in this series:
  • Techno Talk (February 2020)
  • Techno Talk (February 2020)
  • Techno Talk (March 2020)
  • Techno Talk (March 2020)
  • (April 2020)
  • (April 2020)
  • Techno Talk (May 2020)
  • Techno Talk (May 2020)
  • Techno Talk (June 2020)
  • Techno Talk (June 2020)
  • Techno Talk (July 2020)
  • Techno Talk (July 2020)
  • 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)
  • Techno Talk (August 2025)
  • Techno Talk (August 2025)
  • Audio Out (September 2025)
  • Audio Out (September 2025)
Circuit Surgery Regular clinic by Ian Bell Topics in digital signal processing – Implementing DSP on a microcontroller, part four 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. In the previous few articles, we have been working through an example microcontroller-­b ased DSP implementation. Before looking at the implementation of a sinc filter, we discussed individual aspects of the system: the DAC (digital-to-analog converter) and ADC (analog-to-digital converter), sample timing and data transfer. Last month, we discussed the ADC on the microcontroller we are using, and developed code to transfer the ADC readings to the DAC. This code creates a replica of the ADC input waveform on the DAC output. Initially, we used a timer-driven interrupt routine to transfer data, but this proved to be slow, running at a maximum sampling rate of around 60kHz. Far more efficient data transfer is achieved using direct memory access (DMA), in which independent hardware (the DMA controller) moves data between the microcontroller’s memory and peripherals such as the ADC and DAC. We introduced the basics of DMA and used it to implement ADC to DAC transfer. Analog In Antialiasing filter Sample and hold This requires two buffers (contiguous areas of memory) to hold data read from the ADC and to be written to the DAC. The buffers are ‘circular’; when the read operation reaches the upper address limit, it continues from the lower end of the buffer’s address range. The ADC-to-DAC transfer code simply has to copy data from the ADC buffer to the DAC buffer, coordinated with the DMA transfer timing. The microcontroller’s software library provides two callback functions that run when the buffer is half-full and full. Copying the data in these two functions means that code operations take place on stable data in one half of the buffer while the other half is being accessed by the DMA controller. We concluded last month with the code shown in Listing 1. This approach provided a significant improvement in speed – it could run far faster than the DAC’s maximum conversion rate of around 330kHz. FIR filter recap This month we will look at the implementation of a finite impulse response (FIR) filter, specifically a windowed sinc digital filter. We discussed digital filters in detail from December 2024 to May 2025, focusing on the theory, finding coefficient values and simulating the filters in LTspice. As previously mentioned, our aim here is to develop a basic implementation of the filter, without attempting to use any advanced algorithmic or coding techniques. We will not use library code such as Arm’s Common Microcontroller Software Interface Standard (CMSIS) DSP library so that the processing operations are not hidden in calls to library functions. Digital ADC Digital processing Analog DAC Reconstruction filter The structure of an FIR filter is shown in Fig.2; its output is obtained from a weighted sum of the present and past samples; the weights are the coefficients (a) of the filter (collectively called the kernel). The samples are written as a set of indexed values with index n (input x[n], output y[n]) referring to the most recent sample. The previous input is x[n – 1], the one before that x[n – 2] and so on. In a software implementation of a filter, the samples are stored in system memory. Storing the previous sample and using it at the current time is equivalent to passing if through a delay (Δt) of one sample period (T). For a filter with N coefficients, the output value is calculated from the present input sample and past (delayed) N – 1 inputs. The input delayed by i samples (x[n – i]) is scaled by the coefficient a[i]. Mathematically, we are performing a convolution of the kernel and input signal (see Circuit Surgery, August 2024), which can be written as the summation (Σ) of aix[n – i] for i from 0 to N – 1, ie: N −1 y [ n ] = ∑ a [ i ] x [ n−i ] i=0 a0 x(n) × Σ y(n) a1 Δt x(n–1) × a1x(n–1) Σ a2 Δt x(n–2) × a2x(n–2) Σ aN–1 Δt Out a0x(n) Memory x(n–N–1) × aNx(n–N–1) Processing Fig.1: a generic digital signal processing (DSP) system structure. Fig.2: the structure of a finite impulse response (FIR) filter. 48 Practical Electronics | September | 2025 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 /* USER CODE BEGIN 4 */ void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc) { HAL_GPIO_WritePin(GPIOB, ARD_D8_Pin, GPIO_PIN_SET); for (int i= 0; i < HALF_BUF_SIZE; i++) { DAC_buffer[i] = ADC_buffer[i]; } HAL_GPIO_WritePin(GPIOB, ARD_D8_Pin, GPIO_PIN_RESET); } void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { HAL_GPIO_WritePin(GPIOB, ARD_D8_Pin, GPIO_PIN_SET); for (int i= HALF_BUF_SIZE; i < BUF_SIZE; i++) { DAC_buffer[i] = ADC_buffer[i]; } HAL_GPIO_WritePin(GPIOB, ARD_D8_Pin, GPIO_PIN_RESET); } /* USER CODE END 4 */ Listing 1: the DMA callback functions we concluded with last month. 1 2 3 4 5 Result = 0; for (int i = 0; i < COEFF_SIZE; i++) { Result = Result + Coefficient[i] * filter_input[n-i]; } Listing 2: the FIR filter calculation for one output sample. 1 2 3 4 for (int i = 0; i < HALF_BUF_SIZE; i++) { filter_input[i] = (float) ADC_buffer[i]; } 5 6 7 8 for (int i = HALF_BUF_SIZE; i < BUF_SIZE; i++) { filter_input[i] = (float) ADC_buffer[i]; } Listing 3: code snippets for converting and copying DAC data to the filter input buffer. Lines 1-4 are for the lower half of the buffer, with lines 5-8 for the upper half. For example, for four coefficients, the FIR filter output for y[n] is given by: y[n] = a[0] × x[n] + a[1] × x[n-1] + a[2] × x[n-2] + a[3] × x[n-3] There are normally more coefficients, but I wanted to keep this example simple. Basic filter code Coding the filter calculation in basic form is straightforward. If we assume that we have the coefficients in a floating-point array called Coefficient and the input samples in a floating-point array called filter_input, the code snippet in Listing 2 performs the calculation for y[n], where n is the index of the filter_input array corresponding with the set of input samples (x[]) in the equation above. The code is generalised for any number of coefficients using the #defined value COEFF_SIZE value. The result is a floating-point number holding the accumulated summation. After the loop, the value of Result is the output sample value for sample n (y[n]). Practical Electronics | September | 2025 This code would be fine if we had the whole waveform that we wanted to filter in the filter_input array, and we were performing one-off operation – we could put the code in Listing 2 inside another loop that runs through the input data (stepping n in the outer loop) and writing the output to another array. Things are not so simple for processing a continuous signal from the DMA buffer, which will be constantly changing as the DMA controller writes to it and will not contain the whole waveform. The mathematical operation is the same, but it takes a bit more work to get the correct data from the buffer to the calculation than using the static array implied by Listing 2. ADC DMA buffer operation The filter code shares its basic structure with the ADC-to-DAC transfer operation in Fig.3: an early ADC buffer snapshot. Fig.4: the first lowerhalf buffer transfer. ADC buffer Listing 1. However, depending on the size of the buffer relative to the number of coefficients, we may need to access the whole buffer for one output sample calculation. This is potentially a problem because only half the buffer can be regarded as stable at any time, with the other half being overwritten by the DMA controller. We also have to convert the integer data in the ADC buffer to floating-point values for the calculations. Both problems can be solved by copying the buffer contents to a separate floating-point array at the start of the two callback functions. Listing 3 shows code snippets for the transfer and conversion of data from the ADC buffer (ADC_buffer) to the filter input buffer (filter_input). This code comprises loops placed at the start of the relevant callback function, like the ADC to DAC transfer in Listing 1, with the index range of the loop depending on the callback (which half of the buffer is being copied). Similarly, the size of the ADC buffer (and filter input buffer) is defined as BUF_SIZE. The half-size of the buffer is also defined as HALF_BUF_SIZE. The ADC buffer holds 12-bit integer values directly from the ADC via DMA, which are converted to floating point by the type-cast ((float)) in the copy operation. After the copy, operations on data in the whole filter input buffer can be used in filter calculations, unlike the ADC buffer, where only half the buffer can be used at one time. Data movement To understand the code, it is helpful to track the movement and usage of data in the buffers in a small example – for the transfer and conversion just discussed, and later for the filter calculation. We will use a small buffer with 10 elements for the example (BUF_SIZE = 10, HALF_BUF_SIZE = 5). Usually, a real implementation would employ a larger buffer. Fig.3 shows the state of the buffers after the first three samples x[0], x[1] and x[2] have been read into the memory at the start of processing. In general, sample x[n] is the latest sample and x[n – 1], x[n – 2] etc. are earlier samples. At this stage, the filter input buffer (filter_input) is empty. When the buffer is half-full (as in Fig.4), the HAL_ADC_ConvHalfCpltCallback function is called. The code in Listing 3 x[0] x[1] x[2] ADC buffer x[0] x[1] x[2] x[3] x[4] Filter input buffer x[0] x[1] x[2] x[3] x[4] Filter input buffer 49 transfers data from the lower half of the ADC buffer to the filter input buffer, converting it from integer to floating point as it does so. The HAL_ADC_ConvHalfCpltCallback function also processes data to implement the filter, but this will be discussed later. The DMA system continues to fill the ADC buffer. This happens in parallel with the data transfer, with the HAL_ADC_ConvHalfCpltCallback function also performing data processing, because the DMA system is separate from the processor running the code in the function. Fig.5 shows a further two samples in the ADC buffer. When the buffer is full (see Fig.6), the HAL_ADC_ConvCpltCallback function is called. This code transfers the data from the upper half of ADC buffer to the filter input buffer, converting it from integer to floating point as it does so (and performing processing) – it performs the same function as the half full back, but on the other half of the buffer. The DMA system writes values from the ADC to the ADC buffer continuously. When it gets to the end of the buffer, it returns to the start and overwrites the old data (circular buffer operation). Fig.7 shows the situation after the first wraparound – samples x[10] and x[11] have been written to the start of the buffer over the old data. The filter input buffer continues to hold the older data. Every time the buffer is half-full, the HAL_ADC_ConvHalfCpltCallback function transfers a new batch of data from the lower half of the ADC buffer into the corresponding part of the filter input buffer (see Fig.8). After the transfer, the filter input buffer contains the BUF_SIZE most recent samples (10 samples in this case). However, these are not in time-order through the whole buffer (as can be seen in Fig.8). The oldest samples are in the upper half of the buffer (x[5] to x[9] in the example), and the most recent samples are in the lower half (x[10] to x[14] in the example). This makes processing a range of sample data within the function HAL_ADC_ConvHalfCpltCallback more complex than if they were sequential order. Every time the buffer is full, the HAL_ADC_ConvCpltCallback function transfers a new batch of data from the upper half of the ADC buffer into the corresponding part of the filter input buffer (see Fig.9). After the transfer, the filter input buffer contains the BUF_SIZE most recent samples (10 samples in this case) in time order through the buffer. The fact that the samples are in time order makes processing a sequence of samples simpler than in the case for half-full buffer. 50 Fig.5: more ADC buffer data arrives in the ADC Filter input buffer buffer. Fig.6: the first upper half buffer transfer. x[0] x[1] x[2] x[3] x[4] x[5] x[6] x[0] x[1] x[2] x[3] x[4] ADC buffer x[0] x[1] x[2] x[3] x[4] x[5] x[6] x[7] x[8] x[9] Filter input buffer x[0] x[1] x[2] x[3] x[4] x[5] x[6] x[7] x[8] x[9] x[10] x[11] x[2] x[3] x[4] x[5] x[6] x[7] x[8] x[9] x[0] x[1] x[2] x[3] x[4] x[5] x[6] x[7] x[8] x[9] x[10] x[11] x[12] x[13] x[14] x[5] x[6] x[7] x[8] x[9] x[10] x[11] x[12] x[13] x[14] x[5] x[6] x[7] x[8] x[9] ADC buffer x[10] x[11] x[12] x[13] x[14] x[15] x[16] x[17] x[18] x[19] Filter input buffer x[10] x[11] x[12] x[13] x[14] x[15] x[16] x[17] x[18] x[19] Fig.7: the ADC buffer DMA unit wraps Filter input buffer to the start. ADC buffer Fig.8: the second halffull data Filter input buffer transfer. Fig.9: the second bufferfull data transfer. Calculating y[n] Index Fig.10: how the output Coefficients sample y[n] Index is calculated Filter input buffer from input samples DAC buffer x[n-3] to x[n]. Calculating y[n-1] Fig.11: output y[n-1] is calculated from input samples x[n-4] to x[n-1]. 0 1 2 3 a[0] a[1] a[2] a[3] 0 1 2 3 4 5 6 7 8 9 x[n-9] x[n-8] x[n-7] x[n-6] x[n-5] x[n-4] x[n-3] x[n-2] x[n-1] x[n] y[n-9] y[n-8] y[n-7] y[n-6] y[n-5] y[n-4] y[n-3] y[n-2] y[n-1] y[n] Index 0 1 2 3 Coefficients a[0] a[1] a[2] a[3] Index 0 1 2 3 4 5 6 7 8 9 Filter input buffer x[n-9] x[n-8] x[n-7] x[n-6] x[n-5] x[n-4] x[n-3] x[n-2] x[n-1] x[n] DAC buffer y[n-9] y[n-8] y[n-7] y[n-6] y[n-5] y[n-4] y[n-3] y[n-2] y[n-1] y[n] To perform the FIR filter calculations, it is necessary to access the correct data from the filter in buffer and coefficient arrays. In both callback functions, after data is transferred to the filter input buffer, it can be used, along with the coefficients array, to perform the FIR filter calculations. We will look at the bufferfull case first, because, as just noted, this is the more straightforward case. Buffer full callback data processing After the transfer of data from the ADC buffer in the buffer-full callback, the filter input buffer contains the BUF_SIZE (10 in this case) most recent samples in order (see Fig.10). The oldest sample (x[n – 9] in this case) is in FilterInBuffer[0] (index 0) and the most sample recent in FilterInBuffer[BUF_SIZE-1] (index 9 in this case). The FIR filter calculations for output y[n] require the input values from sample x[n] back to sample x[n – N + 1], where N is the number of coefficients. For example, for four coefficients (a[0], a[1], a[2] and a[3]) the calculation for y[n] uses x[n], x[n – 1], x[n – 2] and x[n – 3], as shown in Fig.10. The result is placed in the DAC buffer ready for the DMA system to send it to the DAC. In the buffer-full callback function, it is necessary to calculate the values for all the output samples in the upper half of the DAC buffer (y[n] down to y[n – 4] in this case). Fig.11 shows the values required to calculate y[n – 1]. This pattern is repeated down to the final value in the upper half of the DAC buffer (y[n – 4] in this case), which is calculated from x[n – 4] down to x[n – 7] – see Fig.12. Some calculations (such as that for y[n – 4]) require data from the lower half of the buffer as well as the upper half. Calculating the value for the bottom of the upper half of the DAC buffer uses mainly values from the lower half of the filter input buffer, specifically the top COEFF_SIZE-1 values from that Practical Electronics | September | 2025 Calculating y[n-4] Fig.12: output y[n-4] is calculated from input samples x[n-7] to x[n-4]. Index 0 1 2 3 Coefficients a[0] a[1] a[2] a[3] Index 0 1 2 3 4 5 6 7 8 9 Filter input buffer x[n-9] x[n-8] x[n-7] x[n-6] x[n-5] x[n-4] x[n-3] x[n-2] x[n-1] x[n] DAC buffer Calculating y[n] y[n-9] y[n-8] y[n-7] y[n-6] y[n-5] y[n-4] y[n-3] y[n-2] y[n-1] y[n] Index Fig.13: Coefficients calculating the output Index sample Filter input buffer y[n] in the half-full buffer callback. Calculating DAC y[n-1] buffer Index Fig.14: Coefficients calculating the output Index sample Filter input buffer y[n-1] in the halffull buffer DAC buffer callback. Calculating y[n-4] Index Fig.15: Coefficients calculating the output Index sample Filter input buffer y[n-4] in the buffer-full callback. DAC buffer 1 2 3 4 5 6 7 8 9 0 1 2 3 a[0] a[1] a[2] a[3] 0 1 2 3 4 5 6 7 8 9 x[n-4] x[n-3] x[n-2] x[n-1] x[n] x[n-9] x[n-8] x[n-7] x[n-6] x[n-5] y[n-4] y[n-3] y[n-2] y[n-1] y[n] y[n-9] y[n-8] y[n-7] y[n-6] y[n-5] 0 1 2 3 a[0] a[1] a[2] a[3] 0 1 2 3 4 5 6 7 8 9 x[n-4] x[n-3] x[n-2] x[n-1] x[n] x[n-9] x[n-8] x[n-7] x[n-6] x[n-5] y[n-4] y[n-3] y[n-2] y[n-1] y[n] y[n-9] y[n-8] y[n-7] y[n-6] y[n-5] 0 1 2 3 a[0] a[1] a[2] a[3] 0 1 2 3 4 5 6 7 8 9 x[n-4] x[n-3] x[n-2] x[n-1] x[n] x[n-9] x[n-8] x[n-7] x[n-6] x[n-5] y[n-4] y[n-3] y[n-2] y[n-1] y[n] y[n-9] y[n-8] y[n-7] y[n-6] y[n-5] for (int n = HALF_BUF_SIZE; n < BUF_SIZE; n++) { Result = 0; for (int i = 0; i < COEFF_SIZE; i++) { Result = Result + Coefficient[i] * filter_input[n-i]; } DAC_buffer[n] = (uint32_t)Result; } Listing 4: the filter calculations for a buffer-full callback. half. This means that the maximum number of coefficients we can have is HALF_BUF_SIZE+1. Otherwise, we would run off the bottom of the filter input buffer. In all cases for the full buffer callback, the input samples (x values) are in a continuous block in the filter input buffer. This makes it easy to iterate over the input values required to calculate a given output using a single for loop. The data in the lower half of the filter input buffer is stable and safe to use as long as the full callback function completes running before the next half-full callback. FIR filter code for buffer-full callback The buffer-full callback must calculate all the output values in the upper half of the DAC buffer (indexes from HALF_BUF_SIZE to BUF_SIZE-1). This can be achieved with the code from Listing 2, with n running over the required index range (see Listing 4). Practical Electronics | September | 2025 After the calculation loop, each value of Result (y values) is converted to an integer and stored in the DAC buffer for output to the DAC via DMA. For the example buffers shown above (BUF_SIZE = 10 and COEFF_SIZE = 4), the code in Listing 4 performs the following FIR filter calculations, where a[i] is the value at index i in the coefficient array and b[i] is the value at index i in the filter input buffer array. y[n-4] = a[0]b[5] + a[1]b[4] + a[2]b[3] + a[3]b[2] y[n-3] = a[0]b[6] + a[1]b[5] + a[2]b[4] + a[3]b[3] y[n-2] = a[0]b[7] + a[1]b[6] + a[2]b[5] + a[3]b[4] y[n-1] = a[0]b[8] + a[1]b[7] + a[2]b[6] + a[3]b[5] y[n] = a[0]b[9] + a[1]b[8] + a[2]b[7] + a[3]b[6] Buffer half-full callback FIR calculation Like the buffer-full callback, after the transfer of data from the ADC buffer in the half-full callback, the filter input buffer contains the BUF_SIZE most recent samples. However, the sample order is more complex than in the buffer-­full case. Fig.13 shows the situation at a buffer half-full callback. The filter input buffer is in a state equivalent to that shown in Fig.8, with n = 14. The newest samples are in the first half of the filter input buffer. The first half ends with the newest input sample, x[n], in filter_input[HALF_BUF_SIZE -1] (index 4 in this case). The oldest samples are in the second half of the buffer; the second half starts with the oldest sample being at filter_input[HALF_BUF_SIZE] (index 5 in this case) and runs in sequence to the final sample index at filter_input[BUF_SIZE-1] (ie, filter_input[9]). The next newest sample is at index 0, at the bottom of the first half. The FIR filter calculations require the same data from the filter input buffer as the buffer-full callback, but the location of the data in the filter input buffer is different. Calculation for y[n] uses x[n], x[n – 1], x[n – 2] and x[n – 3], and the coefficients, as shown in Fig.13. This is similar to the example in Fig.10, but using lower-half data. In the buffer half-full callback, it is necessary to calculate the values for all the output samples in the lower half of the DAC buffer (x[n] down to x[n – 4] in this case). The example in Fig.14 shows the values used to calculate y[n – 1]. This pattern is repeated down to the final value in the lower half of the DAC buffer. For example, y[n – 4] is calculated from x[n – 4] down to x[n – 7] and the coefficients (see Fig.15). Some calculations (such as that for y[n – 4]) require data from the upper half of the filter input buffer as well as the lower half. The data values used are the same as for the buffer full callback, but finding the correct index to access the data in the buffer is more complex because the data is in two separate blocks in the lower and upper halves. In a similar way to the buffer full case, data in the upper half of the filter input buffer is stable and safe to use as long as the half-full callback function completes running before the next full callback. The maximum number of coefficients is the same for this case. FIR filter code for buffer half-full callback The mathematics for the FIR filter calculation in the half-full callback function is the same as for the full callback function, so the FIR filter calculation is the same as the full buffer case (line 6 in Listing 4). However, the code is more complex because the buffer indices of the data do not always form a continuous block. If the data is in two groups 51 (as in the y[n – 4] example above), two loops are required. The input buffer index is n – i for a calculation of y[n] where i is the coefficient index. Therefore, if n – i is greater than zero for the largest value of i (the last value in the coefficient loop), the set of input sample values (x) used in the calculation will all be in a continuous block in the lower half of the buffer, so the same loop code as the buffer-full case can be used (Listing 4, lines 3 to 7). The largest value of i is N – 1 (for N coefficients), so the lowest index in a potentially continuous block of sample data is n – N + 1. If this index is zero or above, then a simple loop can be used. The code in Listing 5 finds the lowest index (the integer Lowest_index in the code, see line 2), and if this is greater than or equal to zero, runs the calculation for y[n] in the same way as the buffer-full callback case. If the value of Lowest_index calculated above is less than zero, some of the input samples will be in the upper half of the buffer. Two loops need to be used, one to iterate over the values in the lower half and the other for the upper half (this is shown in Listing 6, which is the “else” part of the “if” in Listing 5). The first loop needs to take the value of the coefficient index (loop counter i) from 0 to n (for y[n]), so the input sample index n – i ranges down to 1 2 3 4 5 6 7 8 9 zero (see Listing 6, lines 3 to 6). The value of i reached by this first loop is used to determine the range of the second loop. The number of samples covered by the second loop is equal to the number of coefficients remaining after the first loop. As n coefficients were covered in the first loop, this number is N – n. In the code, the integer Remaining_Samples is used to hold this value and hence control iterations of the second loop (see Listing 6, line 7). The filter input sample values required by the second loop start at the top of the filter input buffer (at index BUFF_SIZE – 1) and range down to BUFF_SIZE - Remaining_Samples. Thus, the loop index (i) needs to run from 1 to Remaining_Samples, and the filter input buffer can be indexed using BUFF_SIZE - i (see Listing 7, lines 8 to 11). At the same time, the coefficient index has to range from n + 1 (the last coefficient index in the first loop was n) to N – 1. In the code, we use a separate integer in the loop (c), initialised to n + 1, and incremented on each iteration, to index the coefficients array. After the calculation loop(s) (whichever is used), each value of Result (output value y) is converted to an integer and stored in the DAC buffer for output to the DAC via DMA. This is the same as the buffer-full callback (Listing 4, line 8). Result = 0; Lowest_index = n - COEFF_SIZE + 1; if (Lowest_index >= 0) { for (int i = 0; i < COEFF_SIZE; i++) { Result = Result + Coefficient[i] * filter_input[n-i]; } } Listing 5: the single loop used when all necessary samples are in a continuous block. 1 2 3 4 5 6 7 8 9 for (int i = 0; i <= n; i++) { Result = Result + Coefficient[i] * filter_input[n-i]; } Remaining_Samples = COEFF_SIZE - n; for (int i = 1, c = n+1; i <= Remaining_Samples; i++, c++) { Result = Result + Coefficient[c] * filter_input[BUF_SIZE - i]; } Listing 6: two loops used when the required samples are not continuous. 3.3V Fig.16: the input protection circuit I added to the microcontroller board for testing the filter implementation. Input R3 100Ω In C1 1µF D1 BAT54 R1 100kΩ Arduino_A5 to ADC D2 BAT54 R2 100kΩ GND 52 For the example buffers shown above, the code performs the following FIR filter calculations; the notation is as in the previous example: y[n-4] = a[0]x[0] + a[1]x[9] + a[2]x[8] + a[3]x[7] y[n-3] = a[0]x[1] + a[1]x[0] + a[2]x[9] + a[3]x[8] y[n-2] = a[0]x[2] + a[1]x[1] + a[2]x[0] + a[3]x[9] y[n-1] = a[0]x[3] + a[1]x[2] + a[2]x[1] + a[3]x[0] y[n] = a[0]x[4] + a[1]x[3] + a[2]x[2] + a[3]x[1] The first three of these require the two loops; the last two use the single loop. The majority of code for the filtering operation is shown in Listing 7. This includes the #defines of the buffer and coefficient array sizes (lines 34 to 36); the coefficient array initialisation (lines 68 to 79); declaration of the variables used in the filter calculations (lines 81 to 86); and the callback functions (lines 872 to 934), which include the snippets in Listings 3 to 6 discussed above. The callbacks also control a timing pulse, as in previous examples. Listing 7 does not include the DMA and timer initialising in the User Code 2 section, as this is the same as the example last month. Implementation details The filter was implemented using a new STM32CubeIDE project configured in the same way as the ADC-to-DAC DMA example from last month. An example filter was configured with a 1kHz low-pass response using 37 coefficients and sampling at 48kHz. The filter coefficients were obtained using the online tool at fiiir.com (this site was introduced in the May issue). Last month, we discussed the ADC input protection and DC shift circuit with reference to LTspice simulations that included parasitic capacitance and a model of the ADC input. Fig.16 shows the circuit I constructed, rather than the model. I built it on an Arduino Uno Prototyping Shield (TSX00083). The B-L475E-IOT01A development board for the microcontroller provides Arduino Uno V3 connectivity, so can conveniently be used with Arduino shield boards. The Prototyping Shield has an array of through-hole solder pads for mounting components. Unlike stripboard, these are not connected in rows, so the component leads and interconnect wires are soldered together (after suitable bending and cutting) on the underside of the board. The input to the protection circuit is provided by a 3-way pin header (the third pin provides for monitoring/measuring the signal, if required). A 2-way pin header was connected to the row of solder pads connected to ground Practical Electronics | September | 2025 33 34 35 36 37 … 66 67 68 69 … 78 79 80 81 82 83 84 85 86 87 88 89 90 … 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 /* USER CODE BEGIN PD */ #define BUF_SIZE 76 #define HALF_BUF_SIZE 38 #define COEFF_SIZE 37 // Must be smaller than HALF_BUF_SIZE+2 /* USER CODE END PD */ Listing 7: the full code for the FIR filter parts of the project. /* USER CODE BEGIN PV */ // FIR filter coefficients, fc =1 kHz, fs = 48 kHz, rectangular window const float Coefficient[COEFF_SIZE] = { 0.011167887305860052, 0.013267107833265871, 0.015387539289897745, 0.017509857539699709, … 0.011167887305860052 }; float Result; // Fir Filter output value int Lowest_index; // Used for buffer index tracking in DMA callback int Remaining_Samples; // Used for buffer index tracking in DMA callback uint32_t ADC_buffer[BUF_SIZE]; // DMA ADC input buffer uint32_t DAC_buffer[BUF_SIZE]; // DMA DAC output buffer float filter_input[BUF_SIZE]; // input buffer for FIR calculations uint32_t Prescaler = 1; // Timer prescaler setting float Period; // Timer period in us float Frequency = 48.0; // Sample frequency in kHz /* USER CODE END PV */ /* USER CODE BEGIN 4 */ void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc) { HAL_GPIO_WritePin(GPIOB, ARD_D8_Pin, GPIO_PIN_SET); // Timing monitoring pulse on // Copy DMA data to filter input buffer for (int i = 0; i < HALF_BUF_SIZE; i++) { filter_input[i] = (float) ADC_buffer[i]; } // Calculate FIR for (int n = 0; n < HALF_BUF_SIZE; n++) { Result = 0; Lowest_index = n - COEFF_SIZE + 1; if (Lowest_index >= 0) { // All samples in contiguous block in lower half of buffer - simple loop for (int i = 0; i < COEFF_SIZE; i++) { Result = Result + Coefficient[i] * filter_input[n-i]; } } else { // Samples not contiguous - need two loops // First loop in lower half of buffer (index down to zero) for (int i = 0; i <= n; i++) { Result = Result + Coefficient[i] * filter_input[n-i]; } // Second loop in upper half of buffer (working back from the top) Remaining_Samples = COEFF_SIZE - n; // Total samples minus number already done for (int i = 1, c = n + 1; i <= Remaining_Samples; i++, c++) { Result = Result + Coefficient[c] * filter_input[BUF_SIZE - i]; } } // Copy result to DAC DMA buffer DAC_buffer[n] = (uint32_t)Result; } HAL_GPIO_WritePin(GPIOB, ARD_D8_Pin, GPIO_PIN_RESET); // Timing monitoring pulse off } void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { HAL_GPIO_WritePin(GPIOB, ARD_D8_Pin, GPIO_PIN_SET); // Timing monitoring pulse on // Copy DMA data to filter input buffer for (int i= HALF_BUF_SIZE; i < BUF_SIZE; i++) { filter_input[i] = (float) ADC_buffer[i]; } // Calculate FIR for (int n= HALF_BUF_SIZE; n < BUF_SIZE; n++) { Result = 0; for (int i = 0; i < COEFF_SIZE; i++) { Result = Result + Coefficient[i] * filter_input[n-i]; } // Copy result to DAC DMA buffer DAC_buffer[n] = (uint32_t)Result; } HAL_GPIO_WritePin(GPIOB, ARD_D8_Pin, GPIO_PIN_RESET); // Timing monitoring pulse off } /* USER CODE END 4 */ Practical Electronics | September | 2025 53 www.poscope.com/epe Fig.18: the 500Hz, 3.15V peak-to-peak input signal (blue) and the output signal (red). Fig.19: the 1250Hz, 3.15V peak-to-peak input signal (blue) and output signal (red). (visible on the right edge of the Prototyping Shield in Fig.17). This is useful for connecting test instrument ground clips. The BAT54 schottky diodes used in the protection circuit are available in a few formats, including single and dualdiode configurations in different SMD packages (eg, SOT-23, SC-70 and SOD323). I chose the single diode BAT54WS-G variant as it is the best fit to the 1.27mm (0.05in/50 thou) pitch SMD solder pads on the Prototyping Shield (these are intended for an SOIC integrated circuit). As inputs containing frequencies above the Nyquist rate were not going to be used for testing, no anti-aliasing filter was provided at the input. A simple first-order RC low-pass reconstruction filter with R = 8.2kΩ and C = 8.2nF was used on the DAC output. This has a cutoff frequency of 2.4kHz (1 ÷ 2πRC). At the Nyquist rate of 24kHz for the example digital filter, the RC filter has an attenuation of about 20dB (⅒th), which is sufficient for testing with sinewave inputs and a filter cutoff of 1kHz. The filter was tested by applying sinewaves at close to the maximum amplitude (3.15V peak-to-peak, just within the ADC range of 3.3V). This confirmed that the gain dropped at frequencies above 1kHz. Two examples are shown in Figs.18 & 19, which are for 500Hz and 1250Hz, respectively. Ideally, we need to plot the frequency response. This could be done manually by measuring the gain a various frequencies using measurements, such as in Figs.18 & 19, but this would be a tedious process. Next month, we will look at automated frequency response measurement. PE DAC output (D13) Timing pulse (D8) Input Ground pins BAT54 diodes - USB - Ethernet - Web server - Modbus - CNC (Mach3/4) - IO - PWM - Encoders - LCD - Analog inputs - Compact PLC - up to 256 - up to 32 microsteps microsteps - 50 V / 6 A - 30 V / 2.5 A - USB configuration - Isolated PoScope Mega1+ PoScope Mega50 ADC input (A5) Fig.17: the development board with the input protection circuit added on a prototyping shield. 54 - up to 50MS/s - resolution up to 12bit - Lowest power consumption - Smallest and lightest - 7 in 1: Oscilloscope, FFT, X/Y, Recorder, Logic Analyzer, Protocol decoder, Signal generator Practical Electronics | September | 2025