We all know about voltmeters. Besides multimeters, there are some panel meters that show voltage, current, etc. Making such an AC voltmeter is sometimes becomes very helpful. Here in this article, we are going to learn how we can make our own single-phase AC voltmeter using a PIC16F76 micro-controller and capacitor power supply. So let’s start!

Disclaimer: Electricity is always dangerous. Proper skill is required to work with electricity. Do work at your own risk. The author will not be responsible for any misuse or harmful act or any mistake you make. The contents of this website are unique and copyright protected. Kindly don’t do any nonsensical act copying and claiming it as yours. Most of the articles published here are kept as open-source to help you. Take the knowledge for free and use it, but if you are interested you can buy the ready resources offered here. If you need any help or guide feel free to comment below, the author will try to help you. Thanks.

Warning!: The circuit uses direct AC. Connect the phase and neutral as like the circuit. Also, the circuit may carry live voltages. Do work on your own responsibility. Always check the phase using a tester before touching any part directly.

Before we start, if you missed the article on seven segments How to interface 4×1 7segment display with PIC micro-controller and Transformerless power supply design guide in detail with a calculator then I’ll request you to read those article first and if you already read those, thanks.

As theories are explained in those articles, so I’m escaping those and starting the circuit.

Circuit diagram:

microcontroller based voltmeter, seven segment display, AC voltmeter

Here, C1 is a high voltage capacitor, used for our capacitor power supply with other components like R2, R1, D1, D2, U2 & C2 & 3. This is a kind of capacitor power supply where we kept the neutral line common with the circuit GND pin. That is why you must ensure that you connected the phase and neutral correctly. Otherwise, high voltage shock is very easy to get.

Anyway, don’t be afraid. Here, R4 & R5 are used as the sensing resistors and R6 is used to protect the ADC pin although the ADC pins are internally protected. We just reduced the current flow. The other part on the right side is the display side. No, I did not use any resistor network but you can use it. Or just use 8 different resistors.

Note that, R1 gets a little bit warm. I tested this and found that it is over 40’C after 8hrs of a run. But no problem was found and the meter is running continuously since 27-06-2021, 3 PM.

MikroC coding:

The code is written in mikroC Pro for PIC. And of course, I always use valid licenses.

/*******************************************************************************
Program for, Simple AC voltmeter
Program written by_ Engr. Mithun K. Das; mithun060@gmail.com
MCU:PIC16F76; X-Tal:8MHz. C: mikroC pro for PIC v7.6.0
Date: 28-06-2021; Original file: 03-11-2013
*******************************************************************************/

unsigned short mask(int num)
{
   switch (num)
   {
       case 0 : return 0xC0;
       case 1 : return 0xF9;
       case 2 : return 0xA4;
       case 3 : return 0xB0;
       case 4 : return 0x99;
       case 5 : return 0x92;
       case 6 : return 0x82;
       case 7 : return 0xF8;
       case 8 : return 0x80;
       case 9 : return 0x90;
       case 10: return 0x40;
       case 11: return 0x79;
       case 12: return 0x24;
       case 13: return 0x30;
       case 14: return 0x19;
       case 15: return 0x12;
       case 16: return 0x02;
       case 17: return 0x78;
       case 18: return 0x00;
       case 19: return 0x10;
       case 20: return 0x8E; //F
   }
}
unsigned long adc_rd0,i=0, number;
unsigned short  portb_index,type;
unsigned int digit;
unsigned short portb_array[4];
unsigned int k = 0;
unsigned int max_point = 0,temp = 0;



void InitTimer1()
{
  T1CON         = 0x01;
  TMR1IF_bit    = 0;
  TMR1H         = 0xD8;
  TMR1L         = 0xF0;
  TMR1IE_bit    = 1;
  INTCON        = 0xC0;
}

void Interrupt() iv 0x0004 ics ICS_AUTO
{
  if (TMR1IF_bit)
  {
    TMR1IF_bit = 0;
    TMR1H         = 0xD8;
    TMR1L         = 0xF0;
    //Enter your code here
    if(type)
     {
         PORTB = ~(portb_array[portb_index]);
         if(portb_index==2)
          {
             RC7_bit = 0;
             RC6_bit = 1;
             RC5_bit = 1;
          }
          if(portb_index==1)
          {
             RC7_bit = 1;
             RC6_bit = 0;
             RC5_bit = 1;
          }
          if(portb_index==0)
          {
             RC7_bit = 1;
             RC6_bit = 1;
             RC5_bit = 0;
          }
      }
     if(!type)
     {
          PORTB = (portb_array[portb_index]);
          if(portb_index==2)
          {
             RC7_bit = 1;
             RC6_bit = 0;
             RC5_bit = 0;
          }
          if(portb_index==1)
          {
             RC7_bit = 0;
             RC6_bit = 1;
             RC5_bit = 0;
          }
          if(portb_index==0)
          {
             RC7_bit = 0;
             RC6_bit = 0;
             RC5_bit = 1;
          }
      }

      portb_index ++ ;
      if (portb_index > 2)portb_index = 0;
  }
}

void Get_AC_Voltage(void);

 void Display_AC(void)
 {
     digit = (number / 1u) % 10u;
     portb_array[0] = mask(digit);
     digit = (number / 10u) % 10u;
     portb_array[1] = mask(digit);
     digit = number / 100u;
     portb_array[2] = mask(digit);
 }


void main()
{

  type = 0; //segment type
  
  TRISA = 0xFF;
  TRISB = 0x00;
  PORTB = 0xff;
  TRISC= 0x00;
  PORTC = 0x00;
  ADCON1 = 0x00;// all analog in

  // tiemr1 settings...
  InitTimer1();
  
  digit = 0;
  portb_index = 0;
  number = 0;
  
  Display_AC(void);

  while(1)
  {

      
      Get_AC_Voltage(void);


  }//Endless loop;
}//End.



void Get_AC_Voltage(void)
{
      int mm;
      ADCON0 = 0b00100001; //set ADC ch4
      max_point = 0;//clear all data
      adc_rd0 = 0;//clear data
      for(mm=0;mm<60;mm++)
      {
          for(k=0;k<1000;k++)
          {
              if(temp = ADC_Read(4),temp>max_point)   //find max point
              {
                  max_point = temp;
              }
          }

          adc_rd0+= max_point*137/100;
          max_point = 0;//clear all data
          temp = 0;
      }
      adc_rd0/=60;
      number = adc_rd0; // Convert the result in millivolts
      Display_AC(void);
      max_point = 0;//clear all data
      adc_rd0 = 0;//clear all data
}






// end

Here, the first block converts the PORTB for each digit of the seven-segment display.

unsigned short mask(int num)
{
   switch (num)
   {
       case 0 : return 0xC0;
       case 1 : return 0xF9;
       case 2 : return 0xA4;
       case 3 : return 0xB0;
       case 4 : return 0x99;
       case 5 : return 0x92;
       case 6 : return 0x82;
       case 7 : return 0xF8;
       case 8 : return 0x80;
       case 9 : return 0x90;
       case 10: return 0x40;
       case 11: return 0x79;
       case 12: return 0x24;
       case 13: return 0x30;
       case 14: return 0x19;
       case 15: return 0x12;
       case 16: return 0x02;
       case 17: return 0x78;
       case 18: return 0x00;
       case 19: return 0x10;
       case 20: return 0x8E; //F
   }
}

Then the interrupt part shows the digits sequentially and trigger the data pins sequentially. This sequence and data pattern are predefined for each type of segment (CA/CC).

void Interrupt() iv 0x0004 ics ICS_AUTO
{
  if (TMR1IF_bit)
  {
    TMR1IF_bit = 0;
    TMR1H         = 0xD8;
    TMR1L         = 0xF0;
    //Enter your code here
    if(type)
     {
         PORTB = ~(portb_array[portb_index]);
         if(portb_index==2)
          {
             RC7_bit = 0;
             RC6_bit = 1;
             RC5_bit = 1;
          }
          if(portb_index==1)
          {
             RC7_bit = 1;
             RC6_bit = 0;
             RC5_bit = 1;
          }
          if(portb_index==0)
          {
             RC7_bit = 1;
             RC6_bit = 1;
             RC5_bit = 0;
          }
      }
     if(!type)
     {
          PORTB = (portb_array[portb_index]);
          if(portb_index==2)
          {
             RC7_bit = 1;
             RC6_bit = 0;
             RC5_bit = 0;
          }
          if(portb_index==1)
          {
             RC7_bit = 0;
             RC6_bit = 1;
             RC5_bit = 0;
          }
          if(portb_index==0)
          {
             RC7_bit = 0;
             RC6_bit = 0;
             RC5_bit = 1;
          }
      }

      portb_index ++ ;
      if (portb_index > 2)portb_index = 0;
  }
}

Code is written in a simple way so that you can understand it easily. After that, you can short it yourself as I do for myself.

 void Display_AC(void)
 {
     digit = (number / 1u) % 10u;
     portb_array[0] = mask(digit);
     digit = (number / 10u) % 10u;
     portb_array[1] = mask(digit);
     digit = number / 100u;
     portb_array[2] = mask(digit);
 }

The Display_AC function simply converting the number into PORTB pin configuration.

And here in Get_AC_voltage() ?

void Get_AC_Voltage(void)
{
      int mm;
      ADCON0 = 0b00100001; //set ADC ch4
      max_point = 0;//clear all data
      adc_rd0 = 0;//clear data
      for(mm=0;mm<60;mm++)
      {
          for(k=0;k<1000;k++)
          {
              if(temp = ADC_Read(4),temp>max_point)   //find max point
              {
                  max_point = temp;
              }
          }

          adc_rd0+= max_point*137/100;
          max_point = 0;//clear all data
          temp = 0;
      }
      adc_rd0/=60;
      number = adc_rd0; // Convert the result in millivolts
      Display_AC(void);
      max_point = 0;//clear all data
      adc_rd0 = 0;//clear all data
}

First, we are seeking the peak point of each cycle of the AC signal. Then multiplying with a constant 137/100 (I kept this way to reduce RAM consumption rather than writing 1.37). After each peak, we just calculated the RMS voltage of each cycle. But to get a pure and stable result we must get an average value from multiple peak points. That is why we took an average value of 60 of those.

Download the Hex file from here for Free!.

This is very simple. If you have any problem understanding, feel free to comment below. I’ll reply at a suitable time.

PCB:

Most of the time, I make PCBs for each project or product. Here is the PCB for our AC voltmeter using a PIC16F76 micro-controller and capacitor power supply. Note that I kept it multifunctional so that I can show how we can use the same design for different purposes. For this project, just follow the circuit diagram given above.

microcontroller based voltmeter.
microcontroller based voltmeter.

Test result:

I used resistors which is 5% intolerance. Using 1% resistors will improve the measuring performance.

Conclusion:

This project was very simple and useful if you use it for good. By minor changes, this can be a good product too. I think this article will help you. Don’t forget to subscribe, more projects are coming soon. Thanks for reading.

Loading

JLCPCB – Only $2 for PCB Prototype (Any Color)

24 Hours fast turnaround, Excellent quality & Unbeatable prices

$18 Welcome Bonus for new registrations Now!!! https://jlcpcb.com


Check this: 6V Lead-Acid battery Charger Circuits.


MKDas

I'm Mithun from Bangladesh. Electronics is my passion, love to work with electronics. This is my personal blog. Besides my regular works, I write some articles here. Most of them are open-source and everything is presented in the easiest way so that anyone can understand and learn it. Thanks.

13 Comments

RK Hamy · June 29, 2021 at 3:58 am

Many Thanks for the excellent project.

As per my understanding…
you takes 1000 samples from AC waveform while finds maximum (adc) value in that 1000 numbers.
then you calculate rms voltage value of that maximum number. after collecting 60 RMS voltage samples You divide it by 60 to get average RMS AC Voltage.

Can you kindly explain the RMS calculation part
what is 137/100 or 1.37 (I cannot understand)

Again many thanks

    MKDas · June 29, 2021 at 5:46 am

    First it comes from voltage divier equation, Then 0.707 for peak to rms conversion. After that a calibration factor is multiplied with that. This is the one way. And the other way is to back engineering.

      RK Hamy · June 29, 2021 at 8:03 am

      Thank you so much for taking time to reply me.

      When you have a free time, can you kindly explain following steps for me.
      Voltage divider equation
      Peak to RMS conversion
      Calculate calibration factor

      Really sorry if I am keep disturbing you,
      I really love to learn how AC calculation done in this circuit.

        MKDas · June 29, 2021 at 8:55 am

        These are available in engineering books. Besides, Google can help you. These are very minor issues to explain.
        Only one thing you may not understand is the calibration factor. This is very simple too. After putting all the mathematical values, you find that your meter is showing 210V while the actual reading is 215V. That means, your meter is reading 5 volts lower than the actual. This happens due to resistance tolerance. Now, what is your calibration factor? Simple, 215/210 = 1.0238 is your calibration factor. Now you need to multiply this with your previous value. I think it is clear to you now.
        Thank you for understanding.

          R K Hamy · June 29, 2021 at 9:09 am

          Thank you so much for explaining thing.

max · June 30, 2021 at 12:15 am

thank you sir
where can i download the hex file

    MKDas · June 30, 2021 at 4:50 am

    Check again. I’ve uploaded the hex.

max · July 1, 2021 at 9:40 pm

thank you so much sir

enrique · July 4, 2021 at 3:06 pm

Thank you very much for sharing your knowledge, I follow all your publications, I am learning thanks to you
Greetings from Colombia

R K Hamy · August 4, 2021 at 8:24 am

If possible Can you please publish an article and mikroC code of True RMS calculation.

    MKDas · August 4, 2021 at 1:29 pm

    Noted. I’ll try.

      R K Hamy · August 4, 2021 at 6:29 pm

      Thanks for the + response

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *