Mains hum filter

From Hamsterworks Wiki!

Jump to: navigation, search

This FPGA project was completed in July 2013.

Hum filter.jpg

Here is an example of using a low-pass moving average DSP filter to remove 50Hz hum from a signal. The raw sample rate is 1,200 Hz, and a twenty four point moving average filter is applied. This completely blocks any 50Hz signal and its harmonics. The input is just a jumper wire hanging from the ADC pin header.

You can see a video of this in action at


How does the filter work?

By sampling the incoming signal evenly over the mains cycle you can ensure that the hum components will will cancel out. For example, if you sample at four times the mains frequency you might end up getting the following results

Sample Value
0 ms 734
5 ms 827
10 ms 478
15 ms 384

The average value of these four samples is 605.75, and because of when the samples were taken this averaging process has removed any 50Hz component - and a lot of the higher frequency components too, up to the Nyquest limit of half the sample rate).

To verify this subtract the average from each sample, giving 128.25, 221.25, -127.75, -221.75 as the components that was removed.

What about other any other frequencies?

The frequency response at other frequencies is relatively easy to calculate. Assume that a sine (or cosine) wave is at it's maximum in the middle of the moving average window, then calculate the values that would be seen for each sample. The average of these values will be the peak response.

Seconds 1 Hz 10 Hz 25 Hz 50 HZ 60 Hz 100 Hz
-0.008 0.999 0.891 0.383 -0.707 -0.951 0.000
-0.003 1.000 0.988 0.924 0.707 0.588 0.000
0.003 1.000 0.988 0.924 0.707 0.588 0.000
0.008 0.999 0.891 0.383 -0.707 -0.951 0.000
Response 0.999 0.939 0.653 0.000 -0.182 0.000

If you are like me I would expect that a small number of samples would give a lumpy response - Rather counter-intuitively, the response is continuous. With the help of Excel here is a graph of the frequency response:

Moving average freq response.jpg

The frequency response above the Nyquest limit (above 100Hz when sampling every 5ms) is important - you can not effectively filter out any harmonics higher than half the sample rate.

If you need to suppress higher harmonics you will need to sample at a higher rate. In the FPGA project included on this page I'm sampling at 1.2 kHz, removing everything up to the 12th harmonic of the 50 Hz signal with great results.

On an Arduino

This technique is pretty easy to implement on a Arduino, especially if you can wait for 16ms or so while you capture the data. This will block 100% of 50 Hz humm, and about 80% of 60 Hz hum.

// Much like the AnalogInput example, this time with a 50Hz Digital filter
// Author : Mike Field <>

int sensorPin = A0;    // select the input pin for the potentiometer
int ledPin = 13;      // select the pin for the LED
int sensorValue = 0;  // variable to store the value coming from the sensor

void setup() {
  // declare the ledPin as an OUTPUT:
  pinMode(ledPin, OUTPUT);  

void wait_for_tick(void) {
  unsigned long timestamp;
  timestamp = millis();
  while(millis() == timestamp)

void wait_for_five_ticks(void) {

void loop() {
  int total;
  // Filter out any 50 Hz hum by using a 4-point moving average
  // Each sample takes about 0.1ms, so we have to wait a relatively 
  // long a while between samples.
  total = analogRead(sensorPin); 
  total += analogRead(sensorPin);    
  total += analogRead(sensorPin);    
  total += analogRead(sensorPin);    

  total  /= 4;
  // Flash LED at the rate determined by the ADC value
  digitalWrite(ledPin, HIGH);  
  digitalWrite(ledPin, LOW);   

On an FPGA

To implement a moving average filter on an FPGA is dead simple, and performance is outstanding - you can process one sample each clock tick. This example is for on the Papilio One and the LogicStart MegaWing. It uses 0.5% of a Spartan 3E-250, which is quite a tiny FPGA.


Here's the guts of the filter - a fully 'generic'ed VHDL module:

-- low_pass : A generic DSP low pass filter.
-- It is a moving sum, as it doesn't divide by the 
-- number of samples. Make sure that data_width is wide 
-- enough that no overflows occur - you might have to 
-- add leading zeros to sample_in!
-- PS. Also note that sample_in is unsigned.
-- Author : Mike Field <>

library IEEE;

entity low_pass is
    generic ( data_width   : natural := 15;
              window_width : natural := 5);
    Port ( clk        : in  STD_LOGIC;
           enable     : in STD_LOGIC;
           sample_in  : in  STD_LOGIC_VECTOR (data_width-1 downto 0);
           sample_out : out  STD_LOGIC_VECTOR (data_width-1 downto 0));
end low_pass;

architecture Behavioral of low_pass is
   signal total : unsigned( data_width-1 downto 0) := (others =>'0');
   signal end_sample : STD_LOGIC_VECTOR ( data_width-1 downto 0);
   signal delay_line : STD_LOGIC_VECTOR (window_width * data_width-1 downto 0) := (others => '0');  
   end_sample <= Delay_line(Delay_line'high downto Delay_line'high-data_width+1);
   sample_out <= std_logic_vector(total);   
    if rising_edge(clk) then
      if enable = '1' then
         total      <= total + unsigned(sample_in) - unsigned(end_sample);      
         delay_line <= Delay_line(Delay_line'high-data_width downto 0) & sample_in;
      end if;
    end if;
  end process;
end Behavioral;

Personal tools