Lately, I built this voltmeter, having six inputs, as an in-house tool (at workplace).
It measures the tap voltages of a transformer of a mains relay-stabilizer (having up to six taps, not counting its ground terminal).
Its purpose is to deduce the turn-ratios, from the voltage readings, in case the power auto-transformer (for houses/offices/stores) is unknown.
The values of the six measurements (each 4 BCD digits, gathered in 2 bytes) could be saved (12 bytes total) in an external SEEP (actually, it is 24C32).
In this project (as it will be noticed on the PCB layout), the 24C32 IC is embedded in an in-house smart card.
For instance, I produced this card (1000 pcs), 10 years ago in China, for our moving message sign project; as a small media to transfer manually the message data from a user PC to the user sign panel (besides the IR and RF transfer methods).
A SEEP reader/programmer, via USB port, could be used to upload the SEEP contents into a bin file (as SEEP.bin). (A few years ago, I downloaded one “EE-Prog”, its open hardware and software, from internet since I can’t download/installed, for free or paid, any genuine new C complier/resource to access USB port). A simple hex editor displays the six measurements on SEEP.bin, each as 4-digit BCD (RMS voltage *10); for example, 20 53 means 205.3. (At work, I also copy these values on an Excel sheet to generate the optimum/best voltages table for the stabilizer board, produced in-house, to become suitable to control the unknown transformer of an old stabilizer which could be brought for repair by a consumer).
Concerning the DC power supply, it happened I had an old SMPS for mobile (from Nokia) whose output is about 5.7 V. Since I usually add a protection diode in series (to block the supply voltage in case its polarity is reversed), the MCU (actually, ATmega8) is supplied with about 5V. For this, the ADC reference is selected to be the internal regulated 2V56 one (it is 2V68 in the ATmega8A on my board).
The AC voltage (up to 230 Vrms, 50 Hz) is attenuated, rectified and smoothed in the same way for every input.
Although, in theory, the 6 interface circuits are similar, an individual gain adjustment is necessary. This is done by supplying all inputs by 220V and shorting PB6 (the adjust pin to ground pin) for 2 sec; note that the two pins are close on the DIP IC. If this adjust pin is soldered to ground later (detected at boot), a further adjustment is disabled.
ATmega8 has 6 ADC only. And the pins for “ADC_4 & ADC_5” are also for “SDA & SCL” of TWI (I2C). To isolate the two pairs, CD4066B is used which has 4 analog switches. The latter IC is controlled by 2 MCU pins.
To transfer the 12-byte buffer (in SRAM) into card, PD2 (the card pin) which is normally grounded is opened. This is done automatically when a card is inserted (the card socket has already an internal switch). If a conventional DIP 24C32 is used instead, an external push-button would be needed (and, likely, the polarity of PD2 may need to be reversed in code as well).
The bad news, for most I guess, is that I write always my code in assembly (my only choice due to world’s regulations).
Obviously, there is a lot to say/explain/comment about the project code and, perhaps, about the circuit as well. So, in case there is someone who will be interested in this project, in one way or another, it will be my pleasure to answer any question.
Since I still use a rather very old version of Kicad to draw my schematics and PCB layouts, I attached the project ones as PDF.
The assembly code (for ATmega8) is built by AS6.2; I attached both its source and hex files. At the end of the code source, there is a list of remarks related to the programmed fuse/lock bits. Their actual bytes could be read at the end of the hex file:
:10220000 24 AB d1 ff 00 03 FF FF FF FF FF FF FF FF FF FF 36
Lo-fuse= 0x24, Hi-fuse= 0xD1, lock= 0xFF (0xAB is for the internal RC calibration and 0x0003 denotes 8 MHz internal).
While designing this voltmeter, my main goal was to make, as fast as possible, something suitable for its intended task, but with an acceptable/practical accuracy. So neither its hardware nor its firmware was optimized in any other respect. On the other hand, I hope you know that when a project is presented for the first time, its documents usually include mistyping and other mistakes (likely minor).
As extra information, I also attached the LTspice files of the circuit which is between an AC voltage input and its corresponding ADC pin.
The files in the attached zip file are:
 Y93S_ALreg5_30-6ADC-Card.pdf , project schematic
 Y93W_ALreg5_30-6ADC-Card.pdf , components on PCB
 Y93_ALreg5_30-6ADC-Card.pdf , PCB (copper layout, seen from the front/components side)
 Vac2ADC_01.asc , circuit drawn on LTspice
 Vac2ADC_01.plt , a small file about the traces to be displayed during simulation
 Vac2ADC_01.png , screenshot
Here are some notes about the project code.
 The core of this code is the instruction
It is an indirect jump to (Z); ZH:ZL
Here, ZL only is used (0 to 255 & ZH=0 always).
Therefore, at address 0x0F00 (flash word address), there are 256 instructions of RJMP (256 words, 512 bytes of flash memory) .
 IJMP is executed every 104us (for internal RC frequency = 8 MHz). It is the time between two readings of ADC which is running in free mode (13 cycles) with 1/64 prescaler.
 The typical RJMP (of IJMP list) executes a certain routine and the common post-routine (or just the latter).
 We may call the code section between MAIN_lp: and IJMP the common pre-routine. It just saves the ADC reading of a new sample.
 The common post-routine starts at Vi_end_: , so every RJMP routine ends with “RJMP Vi_end_”. It detects the end of mains cycle (which is also the start of the next one), and it saves the number of samples, read during the last cycle, in “lstSplVi” which will be used to get the samples average in one cycle. It also checks if the mains frequency is very low [too many samples, < 40.0 Hz] or very high [a few samples, >64.9 Hz]. These two limits are set in MAX_smpl and MIN_smpl respectively. In these two cases, MCU is reset (RJMP RESET). For the next cycle, it updates “ADMUX” to select the following ADC port (ADC0-ADC5 loop).
 The final averages of the 6 voltages are calculated after 16 complete readings; 6 cycles (to read the six-input voltages) * 16 times ===> 96 cycles * 20ms = 1920 ms (mains 50Hz).