Homebrew Lock-In Amplifier - Page 1
Forum section: electronics / projects

author: Picuino
author_rank: Super Contributor
timestamp: April 26, 2024, 12:03:59 pm
content: Some time ago I bought the necessary chips to assemble a homebrew lock-in amplifier and I would like to assemble it and experiment with it.Has anyone assembled the lock-in amplifier based on the AD630 that is circulating on the internet?With which PCB can it be assembled with guarantees that it will work well?Has anyone experimented with it and what interesting experiments can be done?I know there are a lot of questions, but they can be summed up in just one. Can anyone tell us about your experiences with this instrument?Homebrew Lock-In amplifier:https://www.holographyforum.org/data/pdf/aa-Collection_a_k/aa-Laser/aa-lockin/Homebrew_lockin_amplifier.htmlhttps://www.instructables.com/Lock-in-Amplifier/More professional version:https://physicsopenlab.org/2019/08/20/lock-in-amplifier/https://www.ebay.com/itm/275596051897?epid=2282365081What is a Lock-In Amplifier:https://en.wikipedia.org/wiki/Lock-in_amplifierhttps://www.zhinst.com/en/resources/principles-of-lock-in-detectionhttps://www.thinksrs.com/downloads/pdfs/applicationnotes/AboutLIAs.pdfhttps://www.liquidinstruments.com/digital-lock-in-amplifier/Application notes:https://www.analog.com/en/resources/analog-dialogue/articles/low-power-synchronous-demodulator.htmlhttps://www.analog.com/en/resources/analog-dialogue/articles/synchronous-detectors-facilitate-precision.htmlSynchronous System Measures uOhmhttps://www.analog.com/media/en/technical-documentation/application-notes/AN-306.pdf

author: Kleinstein
author_rank: Super Contributor
timestamp: April 26, 2024, 07:42:00 pm
content: I have no experiance with this plan / kit, but I have quite a bit experiance with using lock-in amplifiers and I have build my own version of a lock-in amplifier. My version looks quite different though and has plenty mistakes to learn from.There are a few issue with this plan:1) The outputs are directly from the amplfiers. To avoid capacitive loading to the amplifiers it is a good idea to have some 50 or 100 ohm in series to the outputs.2) The usualy way is to used the output DC coupled. So the capacitor for the output is a thing to drop - a good place for one of the 50 ohm resistors.3) The amplifier is rather basic. The higher gain setting of more than about x 1000 have a limited bandwidth. This may be just a little much gain for one stage. It limits the use to relatively low frequencies (e.g. 1 kHZ or less). This can be OK for some uses, but could be an issue for others. In many cases one may have a custom pre-amplifier for the signal source anyway. A protected power source may be substitude a better amplifier to some degree.4) a point missing are a way to detect clipping, like 2 comparators or simple diode / capacitor detectors for the peak values. Not absolutely needed but a nice to have point.5) Another usefull and commonly found part with the input is a mains frequency notch filter, possibly also the 2x mains.6) The output filter has settings for rather low time constants - these make little sense with an amplifier that is limited BW. It is more that a longer time scale could make sense, though today a medium time scale like 100 ms and than digital averaging low pass filtering at the putput would be a good way. So the longer time constants may not be that important. Using electrolytic capacitors with the filter is not ideal. At least the 100 ms range should get way without electrolytic capacitors.7) depending on how the output is read / displayed one may want more output gain as on option (e.g. with a signal with low SNR)The reference input is directly to the chip - this is kind of a start to add a reference section there. The reference section ideally includes quite some circuitry, so a start, but yet quite ready. One usually wants a bit of protection and signal forming, so that one can start with a crude signal and still get a reasonable sine or optionally square wave with defiened amplitude. Ideally one would also get a fine phase adjust with a precise 90 deg. jump option (e.g. via a PLL). The 90 deg. phase shift part is very handy to adjust the phase. Quite often the ref. side also include some simple generator option (e.g. comes easy with a PLL or so).Old style analog lockin amplifiers are to a large part replaced with digital solutions. So digitize the signal with enough dynamic range (e.g. a sound card or similar) and than do much of the LI technique digitally.For the experiments that can be done, there are a lot of optics experiments (e.g. look at absorbtion or reflection). This can be with modulated LEDs as a light source or a mechical chopper.Another nice experiment can be a field mill to measure electic fields. Utrasonic , acoustics is possible to, though it may need a higher BW amplifier to not get too much phase shift from there.LVDT mechanical displacement sensor can be read out with a lock-in. Similar DMS can use a carrier frequency amplifier, which is a lockin pluse oscillartor for the reference.A LCR bridge could use a lock in amplifier - though this really needs the phase shift part.I have done photo-acoustic detection of light: so use a microphone to detect the pressure rise from modulated light to heat up some gas (e.g. water wapor to absorb 950 nm) or solid surface.

author: Picuino
author_rank: Super Contributor
timestamp: April 27, 2024, 11:53:07 am
content: Thank you for your advice.I was also thinking about making a LIA with a digital processor, but I need first to have a reference analog LIA to check that the digital one works well as I program it.I will make the preamp with 2 separate stages. One stage with 4 options (x1, x2, x5 and x10) and another stage that can have 2 options (x1 and x10) so I can choose many options between x1 and x100 with simpler knobs and it won't lower the bandwidth too much. If I need more amplification, it can be done with an external preamp.Another advice I am missing is about the PCB routing, which is not shown in the tutorial and I think it can be important in such a sensitive instrument. How do I connect the grounds? Is a ground plane enough or do I have to wire all the lines to a single point with separate routes?

author: Kleinstein
author_rank: Super Contributor
timestamp: April 27, 2024, 01:18:17 pm
content: With very high gain and precision in mind it is usually better to have a single ground point (star ground). A ground plane is more thing to avoid EMI and good for a digital circuit.The point with a lock-in amplifier is to keep the input and reference part separate, so avoid the ref. side to cause any input signal with the same frequency. In the plan there is not yet much for the ref. side. For quite some applications one would want a phase adjustment there.One would want at least some protection.If one needs fine steps with the gain depends on what is used to read / display the output: with an analog meter one may need 1, 2,5,10 steps for the gain. With a digital read out with sufficient resolution steps of 1 , 10, 100, 1000 are enough. Only a gain range from 1 to 100 may be a bit small.Depending on the precision of the amplifier at the input one may want a 2nd AC coupling step before the phase sensitive detection.The display / output side also determines how much gain is useful at the output (DC) side. With a rel. low resolution ADC or an analog movement one can use quite some DC gain there. Chances are the AD630 output can be stable to something like 10 µV. So the output side should ideally be able to resole to that level. If a good dmm is used the range settings there can provider some of the total gain.

author: RoGeorge
author_rank: Super Contributor
timestamp: April 28, 2024, 07:10:29 am
content: In case you don't have the time to build a dedicated LIA, this oscilloscope trick might be a good-enough replacement:https://www.eevblog.com/forum/projects/oscilloscope-with-trace-averaging-as-a-lock-in-amplifier-(rigol-ds1054z)/

author: Picuino
author_rank: Super Contributor
timestamp: April 29, 2024, 12:00:46 pm
content: I don't really have much time right now. I am preparing the project for this summer when I will have enough time to assemble and test it.I want to make areal LIA, which is fast enough to decode sounds in real time (10kHz minimum) from a higher frequency signal.I don't care if it has a lot of quality, just enough for basic experiments to work. That's why I'm considering making the LIA with cheaper, simpler powered components (amplifiers). The problem is that I can't replace the AD630 with a simpler power supply. Sometimes I consider doing a digital LIA directly, but then I would not have any standard to compare.

author: Kleinstein
author_rank: Super Contributor
timestamp: April 29, 2024, 12:14:42 pm
content: One can replace the AD630 with a CMOS swich (e.g. DG419) a few resistors and OP-amp.For simple testing one should include some simple generator that also generate a quadrature signal. Many of the experents may wand a ref signal from a generator and it easier to start with 2 or 4 x the frequency and than make it an accurate 50:50 signal and get a quadrature signal than to generate this from a PLL. A simple µC to generatore the ref. signal and a source drive signal could be a good idea, as it allows to get at least some phase shift rather easy.Parts of the design depend on how one looks at the output / result. E.g. when using an ADC there anyway, one would not really need the very long time constants at the output. The resolution at the output recording also determines how many gain steps and outout gain is useful. HIgh resolution there can substiture some gain steps.

author: RoGeorge
author_rank: Super Contributor
timestamp: April 29, 2024, 12:17:58 pm
content: A LIA only works if you have a strong and clean reference signal, and in sync with the small and noisy signal to be measure, and only works at a constant frequency, or with very slow variations. A LIA does not decode an unknown signal, it only averages the signal by a pattern given by the reference signal.In regards to the max frequency, a classic LIA usually goes up to 100kHz or so, but they are very sensitive and very low noise in their analog input stage. In contrast with that, the oscilloscope method is less sensitive, more noisy, but can work at frequencies as high as the oscilloscope can display, so virtually hundreds of MHz. Which one to use depends of the measurement that needs to be done.Quote from: Picuino on April 29, 2024, 12:00:46 pmFor what kind of experiments do you need the LIA, what do you plan to measure with it?

author: Picuino
author_rank: Super Contributor
timestamp: April 29, 2024, 01:17:11 pm
content: I want start measuring several experiments:* Milliohms in pcb traces* Coupling between different twisted pairs of cables.* Voice with a laser in a crystal. (Microphone)

author: Terry Bites
author_rank: Super Contributor
timestamp: April 29, 2024, 01:41:09 pm
content: I agree with Kleinstein, Analog switches are great for low cost mod demod. The switch method gives the same result as mutiplying the input with a squarewave. Plenty of examples in AD and LT app notes.

author: shabaz
author_rank: Frequent Contributor
timestamp: April 29, 2024, 02:10:30 pm
content: This won't meet the OP's needs, but it might be of slight interest to beginners: I created an educational project. I was hoping people could create new experiments with it to improve on my simple ones, but there's no traction (at least, I'm not aware of anyone trying it out).The project is calledWave Miner (Github link)and has 10 starter experiments so far.It uses a DSP board from AliExpress and a simple breakout board to BNC connectors (the photo shows it attached to a Raspberry Pi for programming the DSP; the Pi can be detached afterward).The Gerber files for that breakout board are at that Github link. From the10 starter experiments, experiment #9 is a very crude lock-in amplifier.Here is a video demo of the LIA (before I constructed the breakout board).The experiments are very basic and for fun/learning rather than serious use, and the DSP used is extremely limited, but on the plus side, very little construction is needed (the breakout board is entirely through-hole construction for simplicity).

author: Picuino
author_rank: Super Contributor
timestamp: April 30, 2024, 03:42:48 pm
content: Quote from: Kleinstein on April 29, 2024, 12:14:42 pmSo, if I generate a square signal from a microcontroller and with the same microcontroller take samples with the ADC synchronized with the square signal, I could make a simple Lock-In Amplifier without multiplying the analog signal. I could do that with a simple microcontroller as a proof of concept.The microcontroller should internally perform the operation of adding or subtracting the value read by the ADC depending on the state of the output signal.

author: Picuino
author_rank: Super Contributor
timestamp: April 30, 2024, 03:51:46 pm
content: I am trying to set up an STM32 board (NUCLEO-L412KB) for testing the digital LIA. If I don't succeed, I already have other boards running with 16-bit PICs. They are slower, but I think they will also work without problems and can be powered at 5V (an advantage).Ultimately I can buy an Arduino UNO R4 and do the project with it. That way it would be much more replicable by others who want to take advantage of the project to assemble it themselves.

author: Picuino
author_rank: Super Contributor
timestamp: April 30, 2024, 04:14:23 pm
content: I also have a microchip MCP6G02 circuit (dual programmable R-R amplifier x1, x10 and x50) to make the input stage.It has much worse quality than the AD620, but it is much easier to start testing. It has a wide range of programmable gains from the microcontroller.https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/22004b.pdf

author: Kleinstein
author_rank: Super Contributor
timestamp: April 30, 2024, 04:53:22 pm
content: The MCP6G02 is not really a good choice for an input amplifier. The noise is quite high - so if at all only for later stages. In addition it only is for a 5 V supply and thus has a limited dynamic range.The amplifier part for the input would be similar for an analog an digital solution.

author: RoGeorge
author_rank: Super Contributor
timestamp: April 30, 2024, 08:15:50 pm
content: Quote from: Picuino on April 30, 2024, 03:42:48 pmIt is not possible to take samples without multiplying. Samplingismultiplying, just that the multiplying factors only take the values 0 and 1, and yes, the setup you proposed will work.The classic LIA uses analog multipliers because back then there was no other option. Cheap and fast ADC were not yet available, resolution was lame, there were no fast enough CPUs and no DSPs to compete with analog. Even today, it is hard to achieve digitally the same specs as a good analog LIA.Where a dedicated LIA shines is in the analog performance: expensive ones are very carefully designed, their performance is close to the theoretical limits, with very low noise, very high dynamic range, very good separation between signals and between channels, they often have a low jitter PLL to generate accurate quadrature signals, etc.

author: Kleinstein
author_rank: Super Contributor
timestamp: April 30, 2024, 08:57:19 pm
content: The theory for lock in amplifiers often assumes an analog multiplication with a sine wave. However the actual implementation often uses the +-1 square wave multiplication (like the AD630 or with CMOS switches). A problem is that the analog multiplication (e.g. AD633) tends to be drifty and not very stable. One can limit the response to the sine part with an extra bandpass filter before the multiplier.Working around the multiplier drift is tricky, e.g. with an additional feedback of a compensating square wave.It is already quite some time (starting around 2000) that good digital lock-in amplifiers are superior to analog ones in most aspects, especially at not so high a frequency.One can get really low noise, close to the limits with both versions. Even with a primarily analog LI it makes sense to do at least the final integration / low pass filtering at least partially digital. An analog low pass filter has an anoying long settling time, while digital averaging or a FIR low pass fitler is easy and settling faster.For a digital lockin, the way that DSOs implement the averaging mode is a good starting point. Boxcar averaging looks different from classic lockin function, but one can still start with the waveform averaging. The the lockin does the multipication with the ref. waveform (e.g. a sine or square wave) first and than averages / integrates. The boxcar method first averages the data point by point in the time domain and to get the lockin result one multiplies only at the end after averaging. From the math side the result is the same - one can swap the squence. The boxcar method needs more memory and fewer multiplications. In addition one could provide additional information from the waveform or fast look at different harmonics or afterwards apply a different test function besides the classic sine and square.The ADC for a digital lockin amplifier should be relatively good, so that one has a reasonable dynamic range. The µC internal ADCs of often 12 bit or less would limit the dynamic range and thus the ability to detect really small signals. So this is more something for a fast 16-24 bit ADC, maybe audio type ADCs.

author: mawyatt
author_rank: Super Contributor
timestamp: April 30, 2024, 10:16:51 pm
content: There is no known better signal distinguishing factor that Bi-Phase Modulation (multiplying by +- 1 or BPSK). Any other type of basic waveform modulation is inferior to BPSK when it comes to signal recovery from noise within coherent systems, and why it's chosen for deep space communications such a Voyager.With this in mind it should be no surprise that noise buried signal recovery utilizing Synchronous Sampling or Demodulation by means of Bi-Phase demodulation is the best form of signal recovery and why we utilized such in many of our applications dated back over half a century ago.Honestly, would be surprised if a Lock-In amplifier based upon Sine-Wave Demodulation would outperform one based upon Bi-Phase Demodulation. If so this might point to an inferior Bi-Phase Demodulation design rather than the fundamentally superior sine-wave approach.Intuitively if one considers a mixing or multiplication process, flipping the waveform in polarity (+- 1 multiplication) has less of the multiplying waveform unwanted artifacts than a sinusoid multiplicand such as close in phase noise.For example it's well known that for a low noise heterodyne systems with the usual means of a diode based mixer for down conversion, when the diode mixer is heavily driven produces the lowest noise result, and the heavily driven approaching a Bi-Phase multiplication by the LO. This not only improves the mixer conversion loss but also reduces the amplitude uncertainty or noise from the driving signal (LO) further improving the result.So one might suspect the same for utilizing a pure Bi-Phase vs. Sinusoid multiplicand for Synchronous Sampling or Demodulation as in a Lock-In amplifier application.Anyway, just some random thoughts on such.Best,

author: RoGeorge
author_rank: Super Contributor
timestamp: April 30, 2024, 11:52:37 pm
content: I was wandering if using a poly-phase mixer could improve a LIA, because of the reduced noise factor specific to poly phase mixers.Should be easy to double the frequency of the reference signal (in order to divide by 2 again, while producing precise quadrature I/Q of the same frequency as the reference) then to use the I/Q to drive the switches of the poly-phase mixer.Side note now that the quadrature of the reference signal was mentioned, the schematic of the kit in the first post is not exactly a LIA, in my opinion that schematic is rather a synchronous detector. What I'm trying to say is, a LIA (for me) has to have 2 mixers, one for each of the I/Q components of the quadrature reference, in order to extract both the amplitude and the phase of the fain signal.It's 2:30 AM here and didn't check against the datasheets, but the schematic of the kit seems to have only a single analog multiplier, and no I/Q splitting of the reference signal. Though, sometimes the architecture with 2 mixers is called a two-phase lock-in amplifier. Well, I've always thought a LIA should be able to tell both the phase and the amplitude of the small signal. If it doesn't have quadrature reference and 2 multipliers, I think of it as a synchronous detector (and I don't call it a LIA). But I'm not sure if I use the proper naming.

author: mawyatt
author_rank: Super Contributor
timestamp: May 01, 2024, 01:49:22 am
content: The PPM will naturally create I and Q outputs. The 8 phase version seems to be the "sweet spot" wrt to performance vs complexity.Best,

author: Kleinstein
author_rank: Super Contributor
timestamp: May 01, 2024, 08:03:42 am
content: A basic lock in amplifier has 1 phase only, but there are better models to have 2 phases in quadrature. In some applications this can help quite a bit, while for others (e.g. optics) it is not really helping (only faster settling to check the quadrature part). So far I have not seen an analog build lock in that uses more than 2 phases.The extra effort for an extra quadrature channel is not that high, if one has a PLL for the reference section anyway. A PLL for the reference section make absolute sense to get stable phase shifts, especially the 90 deg. steps. Much of the effort is in the input amplifier, filters and ref. side PLL / phase shift, not so much the actual phase sensitive detector.It depends on the application if a sine demodulation makes sense or the simpler +-1 case is better. In some cases if one looks more at phase shifts or frequency dependent effects one does not want the contribution from the harmonics. Its not about noise, but avoiding side effects / systematic errors that complicate the interpretation of the data.The point is fitlering out the harmonics, not how the actual demodulator is build.When it comes to noise the best demodulation function corresponds to the signal waveform, or at least the stable part of it.With optics experiments one may well have a more square modulation can also use the power in the harmonics, tough if there is jitter (e.g. from a mechanical chopper) it could help to exclude some of the transition region.For a digital implementation one has anyway the option to use the boxcar like averaging first and than use a suitable demodulation waveform, that may be neither sine not square suitable for the experiment. One could also still save the full (or somewhat filtered) averaged waveform and decide even later which way to look at the data.Especially digital (but also a few analog ones) lock-in amplifiers also allow to look at the harmonics separately (sepcially 2 x the frequency, 3 x the frequency can have artifacts from the demodulator).

author: Picuino
author_rank: Super Contributor
timestamp: May 01, 2024, 07:57:57 pm
content: Quote from: Kleinstein on April 30, 2024, 04:53:22 pmOk. I have ordered an AD8422 amplifier that can be used with 5V single supply. I am going to use it along with a microcontroller to make a milliohm meter as a starter project.https://www.analog.com/media/en/technical-documentation/data-sheets/ad8422.pdf

author: RoGeorge
author_rank: Super Contributor
timestamp: May 01, 2024, 09:26:03 pm
content: Also from Analog Devices: AN-306, Synchronous System Measures \$\mu\$\$\Omega\$shttps://www.analog.com/media/en/technical-documentation/application-notes/AN-306.pdf

author: zrq
author_rank: Frequent Contributor
timestamp: May 02, 2024, 12:42:09 am
content: I have exactly the same thought.But sine wave may still have advantages for systems with nonlinearity, the modern DSP lockins usually support demodulation at an arbitrary harmonic frequency, although myself only used second harmonic for peak locking which should also work with a square wave modulation.BTW sometimes in physics experiments we also do boxcar averaging for low duty cycle signals.For the original post, working with sound cards is probably a good idea for low frequency experiments. Inexpensive sound cards can have >120 dB dynamic range, probably even more if you don't care about harmonics and spurs for lock-in detection, which is in priniple comparable with todays' best lock-in amplifers from SRS or ZI, not to mention the older analog ones. Although not sure if channel cross-talk can be limiting. I was thinking about playing with my Focusrite but couldn't get the time...Also although I have a very bad feeling towards red pitaya, they also works as a DSP LIA for higher frequency. Koheron also sells a interesting Zynq based board which my colleagues built laser phase noise characterizations systems around.

author: gnuarm
author_rank: Super Contributor
timestamp: May 02, 2024, 02:20:47 am
content: Quote from: Kleinstein on April 26, 2024, 07:42:00 pmSo, what were you using this for?Many years ago, I did a bit of work with photo-acoustic signal collection. We were using it to obtain spectra from samples that could not be put into solution so easily. We used a rotating disc with slots cut evenly, to produce a modulated light beam. Then the wavelength of the light was varied to produce a scan. The AC component of the signal gave us the amplitude of the absorbance of the sample.Is this anything like what you were doing? Back in '75, this was pretty virgin territory.

author: RoGeorge
author_rank: Super Contributor
timestamp: May 02, 2024, 06:08:17 am
content: About experiments that can be performed, I always wanted to try this one, too.Not to spy, but to see if it really is possible.First time seen such setup in a movie, with a surveillance van spying on some important figures with an infra-red laser, when somebody from the surveilled room sees the laser spot by looking through red wine. I would like to see the eavesdropping scene again, to figure it if it was possible at all to hear dialogues from the glass vibrations of a window. Anybody happens to know the title of that movie?Back to LIA,Quote from: Picuino on May 01, 2024, 07:57:57 pmThat would be a good start. For PCB trace and finding short-circuited capacitors (MLCC that decouples the power traces often fails as a solid short-circuit, hard to detect since the power traces are thich, and often there are many capacitors in parallel), Jaromir's audible "Shorty" is a very useful tool (from a practical standpoint, not as an accurate miliohm-meter).Somebody here added a digital display to it:https://www.eevblog.com/forum/testgear/finding-short-on-motherboards-with-a-shorty-(with-display)/If the LIA project is mostly to learn/experiment with electronics ideas, then the poly phase mixer (AKA N-path filter) can work too as a synchronous mixer, and might be easier to build:N path filters: basics & demo - icdutwentenlIf you just need a LIA for physics experiments in general, then it might be cheaper to hunt for a second hand instrument made by Standford Research or by some other brand name.Saying this because the analog part is hard to do in DIY regime, and even if you manage to somehow build one with similar performance as a commercial LIA, that build would cost you much more than a second hand LIA (aside from a serious time investment).

author: zrq
author_rank: Frequent Contributor
timestamp: May 02, 2024, 08:44:53 am
content: Is this "listening device" idea can be achieved by a laser doppler vibrometer? The bistatic idea in the figure also looks plasuable. I was also interested in it but still, no time to look into it and form a project.

author: Picuino
author_rank: Super Contributor
timestamp: May 02, 2024, 09:27:51 am
content: Quote from: RoGeorge on May 01, 2024, 09:26:03 pmSomething like that I want to do, but with a square signal coming from a microcontroller and the decoding done in the same microcontroller so that it can send the readings by UART to a PC.The decoding I am preparing does the following:* Outputs a 1kHz output signal on one pin of the uC.* Takes 10000 ADC readings per second, synchronized with the output signal (5 readings per level).* Adds up all the ADC readings in an A register.* Adds or subtracts the ADC readings according to the output signal in a B register.* After 1024 output edges it averages the A register (average input level or DC signal) and averages the B register (reading of the input signal variation or resistance).

author: Kleinstein
author_rank: Super Contributor
timestamp: May 02, 2024, 09:44:08 am
content: For a µOhms measurement one usually wants a relatively low frequency. So not so much 1 kHz but more like 10 Hz. At 1 kHz one has a good chance to already get some effect from the inductance. One can separate it from the phase, but this requires the amplifier to not have extra phase shift.For this application the digital version makes absolute sense.

author: Picuino
author_rank: Super Contributor
timestamp: May 02, 2024, 09:54:24 am
content: Although the synchronization is a software problem, it should not be too complicated to change the working frequency and check the effect it has.I am concerned about several effects:* That it does not measure the mains signal (50Hz or harmonics).* That it does not measure the 1kHz (or less) injected signal itself through the grounds or through induction on the test leads.* The test leads should be in 4-wire (Kelvin) configuration, of course, but there is always a small point where they should be joined and considering how sensitive the device can be I will have to be especially careful about this.Until I assemble the prototype I cannot test and check these effects.

author: Picuino
author_rank: Super Contributor
timestamp: May 02, 2024, 10:01:13 am
content: Quote from: Kleinstein on May 02, 2024, 09:44:08 amI could also add and subtract in another variable C the ADC measurement 90º out of phase with respect to the reference signal, in order to measure the self-inductance of the resistor, comparing both measurements (B without phase and C with 90º phase).I leave it as an extension to the project.

author: Picuino
author_rank: Super Contributor
timestamp: May 02, 2024, 10:18:32 am
content: RoGeorge:Thanks for the links, I hadn't seen your post with the forum page change.The idea I have is to experiment with the components and learn. Yes, for more serious experiments it is best to buy a second hand LIA.The "Shorty" looks like quite a useful device, I didn't know it, thanks.https://hackaday.io/project/3635-shorty-short-circuit-finderhttps://www.eevblog.com/forum/testgear/finding-short-on-motherboards-with-a-shorty-(with-display)/My idea is to be able to measure the milliohms with a numerical value. I want to use it for printed circuit boards and for screw connections (terminals). It may also be useful in other occasions.My Agilent 34401A can do those measurements without a problem anyway, so measuring milliohms is not a real need. I want to experiment with the concept of synchronous measurement, which I have never used before.

author: Picuino
author_rank: Super Contributor
timestamp: May 02, 2024, 06:43:58 pm
content: While the order for low noise amplifiers is coming in, I have been experimenting with a Microchip MCP6N11-10.https://ww1.microchip.com/downloads/en/DeviceDoc/25073A.pdfAttached is the schematic I have mounted on a prototyping board to measure small resistors.The program for the Arduino nano is as follows:Code:/*Copyright 2024 PicuinoPermission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software"), to dealin the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sellcopies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be includedin all copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISINGFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGSIN THE SOFTWARE.*/const int PIN_VDD_OPAMP = 6;const int PIN_PWM_OUT = 3;const int PIN_ANALOG = A0;const int SAMPLES_PER_EDGE = 80;const int EDGES_PER_MEASURE = 5000 / SAMPLES_PER_EDGE;volatile unsigned int adc_value;volatile unsigned char adc_on;volatile long adc_sum;volatile unsigned int adc_samples;volatile unsigned char timer0_state;void setup() {Serial.begin(9600);// Power up instrumentation amplifierpinMode(PIN_VDD_OPAMP, OUTPUT);digitalWrite(PIN_VDD_OPAMP, HIGH);// Set up output reference signal pinpinMode(PIN_PWM_OUT, OUTPUT);setup_timer0();// Set up ADCsetup_adc();// Main Loopfloat resistance;adc_on = 0;adc_sum = 0;adc_samples = 0;timer0_state = 0;adc_on = 1;for (;;) {if (adc_samples > SAMPLES_PER_EDGE * EDGES_PER_MEASURE * 2) {adc_on = 0;if (adc_sum < 0)adc_sum = 0;resistance = adc_sum;resistance = resistance * (1.0 / (SAMPLES_PER_EDGE * EDGES_PER_MEASURE));Serial.println(resistance);adc_sum = 0;adc_samples = 0;timer0_state = 0;adc_on = 1;}}}void loop() {}void setup_adc(void) {analogRead(PIN_ANALOG);cli(); //stop interruptsADMUX = (1 << 6) | (0 << ADLAR);ADCSRA = (1 << ADEN) | (0 << ADSC) | (0 << ADATE) | (0 << ADIE) | (0b111);ADCSRB = 0x00;sei(); //allow interrupts}void setup_timer0(void) {cli(); //stop interrupts//set timer0 interrupt at 2kHzTCCR0A = 0; // set entire TCCR2A register to 0TCCR0B = 0; // same for TCCR2BTCNT0 = 0; //initialize counter value to 0// set compare match registerOCR0A = 26; // f = 16000000 / ((OCR0A + 1) * 64) = 9259 Hz// turn on CTC modeTCCR0A |= (1 << WGM01);// Set CS01 and CS00 bits for 64 prescalerTCCR0B |= (1 << CS01) | (1 << CS00);// enable timer compare interruptTIMSK0 |= (1 << OCIE0A);sei(); //allow interrupts}ISR(TIMER0_COMPA_vect) {if (adc_on) {// ADC manageadc_value = (ADCL | (ADCH << 8));if (adc_samples > 0) {if (timer0_state < SAMPLES_PER_EDGE) {adc_sum -= adc_value;}else {adc_sum += adc_value;}}ADCSRA |= (1 << ADSC); // ADC Start Conversionadc_samples++;delayMicroseconds(12); // Wait for Sample and Hold// Update next stateif (++timer0_state >= (SAMPLES_PER_EDGE * 2)) {timer0_state = 0;}// Update output reference signalif (timer0_state < SAMPLES_PER_EDGE) {PORTD |= (1 << PIN_PWM_OUT);}else {PORTD &= ~(1 << PIN_PWM_OUT);}}}The first problem I am encountering is that the output signal from the amplifier (Vout) has little noise but a lot of variation between high and low level.On the oscilloscope I measure variations from 0.50V to 0.58V in the amplified square wave.Attached is the wave of Vout.Measures sended by Arduino Nano board (with a lot of noise):Code:113.42113.01113.09113.11107.85114.78115.18115.32109.24107.46109.21112.78107.72109.01113.22107.68109.41113.01113.54113.30113.22107.67109.30113.46113.24113.34113.62113.48113.36113.14113.08107.76114.96114.58115.14115.06115.22114.90115.05114.92114.90

author: Picuino
author_rank: Super Contributor
timestamp: May 02, 2024, 07:07:16 pm
content: Averaging input with the scope, the signal looks better.

author: Kleinstein
author_rank: Super Contributor
timestamp: May 02, 2024, 07:19:19 pm
content: The code may have a problem with identifying the output state for the last sample of one type. The ADC reading is done with the old settings and the check for the math is with the updates counter, so 1 reading that is used wrong.With the current setting one a rather small part of the ADC range is actually used. One may wand some DC shift or AC coupling.

author: Picuino
author_rank: Super Contributor
timestamp: May 02, 2024, 08:15:45 pm
content: New program with continuous sampling:Code:/*Copyright 2024 PicuinoPermission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software"), to dealin the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sellcopies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be includedin all copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISINGFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGSIN THE SOFTWARE.*/const int PIN_VDD_OPAMP = 6;const int PIN_PWM_OUT = 3;const int PIN_ANALOG = A0;const int SAMPLES_PER_EDGE = 80;const int EDGES_PER_MEASURE = 5000 / SAMPLES_PER_EDGE;volatile long adc_sum;volatile long adc_result;volatile unsigned int adc_value;volatile unsigned int adc_samples;volatile unsigned char timer0_state;volatile unsigned char output_edge;volatile unsigned char adc_measure_end;void setup() {Serial.begin(9600);// Power up instrumentation amplifierpinMode(PIN_VDD_OPAMP, OUTPUT);digitalWrite(PIN_VDD_OPAMP, HIGH);// Set up output reference signal pinpinMode(PIN_PWM_OUT, OUTPUT);setup_timer0();// Set up ADCsetup_adc();// Main Loopfloat resistance;adc_sum = 0;adc_samples = 0;timer0_state = 0;for (;;) {if (adc_measure_end == 1) {adc_measure_end = 0;resistance = adc_result;resistance = resistance * (1.0 / (SAMPLES_PER_EDGE * EDGES_PER_MEASURE));Serial.println(resistance);}}}void loop() {}void setup_adc(void) {analogRead(PIN_ANALOG);cli(); //stop interruptsADMUX = (1 << 6) | (0 << ADLAR);ADCSRA = (1 << ADEN) | (0 << ADSC) | (0 << ADATE) | (0 << ADIE) | (0b111);ADCSRB = 0x00;sei(); //allow interrupts}void setup_timer0(void) {cli(); //stop interrupts//set timer0 interrupt at 2kHzTCCR0A = 0; // set entire TCCR2A register to 0TCCR0B = 0; // same for TCCR2BTCNT0 = 0; //initialize counter value to 0// set compare match registerOCR0A = 26; // f = 16000000 / ((OCR0A + 1) * 64) = 9259 Hz// turn on CTC modeTCCR0A |= (1 << WGM01);// Set CS01 and CS00 bits for 64 prescalerTCCR0B |= (1 << CS01) | (1 << CS00);// enable timer compare interruptTIMSK0 |= (1 << OCIE0A);sei(); //allow interrupts}ISR(TIMER0_COMPA_vect) {// ADC manageadc_value = (ADCL | (ADCH << 8));if (output_edge == 1) {adc_sum -= adc_value;}if (output_edge == 0) {adc_sum += adc_value;}ADCSRA |= (1 << ADSC); // ADC Start Conversionadc_samples++;delayMicroseconds(12); // Wait for Sample and Hold// Update next stateif (++timer0_state >= (SAMPLES_PER_EDGE * 2)) {timer0_state = 0;}// Update output reference signalif (timer0_state < SAMPLES_PER_EDGE) {PORTD |= (1 << PIN_PWM_OUT);output_edge = 1;}else {PORTD &= ~(1 << PIN_PWM_OUT);output_edge = 0;}// Take one measure after several samplesif (adc_samples > SAMPLES_PER_EDGE * EDGES_PER_MEASURE * 2) {adc_measure_end = 1;adc_result = adc_sum;adc_sum = 0;adc_samples = 0;}}Same error in the measurements:Code:112.91113.61112.85112.79112.99113.02112.92112.65112.78112.89113.14112.81112.90112.59112.80112.54112.44112.39112.34112.19112.12111.70111.89111.71112.10111.82111.44111.78111.46111.78112.02112.03111.91112.08112.20111.95112.28112.41112.53112.73112.37112.36112.54112.75112.59112.38112.94113.09112.98112.76113.23112.77113.00112.72112.96113.23112.65112.76112.94112.90113.11113.01112.76112.87112.77112.61112.98112.77112.94113.08112.88113.07113.01113.13112.89112.93112.65113.11113.20112.97113.52113.30113.30113.43113.32113.28113.13113.18113.41113.34113.38113.09113.43113.25113.19112.89113.00113.11113.27113.19113.12113.22113.20112.81113.31112.92113.22

author: Kleinstein
author_rank: Super Contributor
timestamp: May 02, 2024, 08:26:15 pm
content: The new data look a lot less noisy. Especially the consecutive readings are relatively similar. There is a chance to have some thermal variations if the actual signal or amplifier gain.

author: Picuino
author_rank: Super Contributor
timestamp: May 03, 2024, 04:23:12 pm
content: I have made several changes to the software and have filtered with resistors and capacitors the MCP6N11 power supply trying to make the measurements more stable and they don't seem to improve anymore:Code:/*Copyright 2024 PicuinoPermission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software"), to dealin the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sellcopies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be includedin all copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISINGFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGSIN THE SOFTWARE.*/const int PIN_PWM_OUT = 3;const int PIN_ANALOG = A0;const int SAMPLES_PER_EDGE = 80;const int EDGES_PER_MEASURE = 5000 / SAMPLES_PER_EDGE;volatile long adc_sum;volatile unsigned int adc_enable;volatile unsigned int adc_value;volatile unsigned int adc_samples;volatile unsigned char timer0_state;volatile unsigned char output_level;volatile unsigned char output_level_old;volatile unsigned char adc_measure_end;void setup() {Serial.begin(9600);// Set up output reference signal pinpinMode(PIN_PWM_OUT, OUTPUT);timer0_setup();// Set up ADCadc_setup();// Main Loopfloat resistance;adc_init();for (;;) {if (adc_measure_end == 1) {resistance = adc_sum;resistance = resistance * (1.0 / (SAMPLES_PER_EDGE * EDGES_PER_MEASURE));Serial.println(resistance);adc_init();}}}void loop() {}void adc_init(void) {adc_measure_end = 0;adc_sum = 0;timer0_state = 0;adc_samples = 0;PORTD |= (1 << PIN_PWM_OUT);output_level = 1;output_level_old = 1;delayMicroseconds(200);adc_enable = 1;}void adc_setup(void) {analogRead(PIN_ANALOG);cli(); //stop interruptsADMUX = (1 << 6) | (0 << ADLAR);ADCSRA = (1 << ADEN) | (0 << ADSC) | (0 << ADATE) | (0 << ADIE) | (0b111);ADCSRB = 0x00;sei(); //allow interrupts}void timer0_setup(void) {cli(); //stop interrupts//set timer0 interrupt at 2kHzTCCR0A = 0; // set entire TCCR2A register to 0TCCR0B = 0; // same for TCCR2BTCNT0 = 0; //initialize counter value to 0// set compare match registerOCR0A = 26; // f = 16000000 / ((OCR0A + 1) * 64) = 9259 Hz// turn on CTC modeTCCR0A |= (1 << WGM01);// Set CS01 and CS00 bits for 64 prescalerTCCR0B |= (1 << CS01) | (1 << CS00);// enable timer compare interruptTIMSK0 |= (1 << OCIE0A);sei(); //allow interrupts}ISR(TIMER0_COMPA_vect) {if (adc_enable) {if (adc_samples) {// Read ADC measureadc_value = (ADCL | (ADCH << 8));// Add measure to accumulatorif (output_level_old == 1) {adc_sum -= adc_value;}if (output_level_old == 0) {adc_sum += adc_value;}}// ADC Start ConversionADCSRA |= (1 << ADSC);adc_samples++;delayMicroseconds(12); // Wait for Sample and Holdoutput_level_old = output_level;// Update next stateif (++timer0_state >= (SAMPLES_PER_EDGE * 2)) {timer0_state = 0;}// Update output reference signalif (timer0_state < SAMPLES_PER_EDGE) {PORTD |= (1 << PIN_PWM_OUT);output_level = 1;}else {PORTD &= ~(1 << PIN_PWM_OUT);output_level = 0;}// Take one measure after several samplesif (adc_samples > SAMPLES_PER_EDGE * EDGES_PER_MEASURE * 2) {adc_measure_end = 1;adc_enable = 0;}}}Output measures:Code:84.2284.4984.4084.4284.4784.1884.4784.4584.2684.0984.2384.3683.7984.1583.9084.0283.9784.1284.1183.9683.6784.0284.0684.0584.3683.8183.9783.9583.9083.9384.0683.9883.8184.0883.7883.9583.7083.5583.7583.9983.7883.8383.7183.6483.5383.6783.6783.7383.4483.6783.6583.8083.4683.6383.6783.3083.4783.5183.8083.4383.64The real resistance under test is, more or less, R_ut = 0.4 Ohms.The signal in Vout have an amplitude of 430mV = 88 dots of ADC

author: Picuino
author_rank: Super Contributor
timestamp: May 03, 2024, 04:29:24 pm
content: It seems that making many measurements simply removes some of the noise, but it is not able to give more resolution than the ADC itself would give with just a couple of measurements.The principle of extracting more information buried in the noise doesn't seem to work in this circuit.I would have to prototype with soldering to be 100% sure. But I don't think I would gain much. I would expect to get at least one decimal place more accuracy than the ADC itself gives.

author: Picuino
author_rank: Super Contributor
timestamp: May 03, 2024, 04:44:51 pm
content: I can't find the “Shorty” software to study its operation.The original link is broken:+https://hackaday.io/project/3635-shorty-short-circuit-finder+https://github.com/jaromir-sukuba/shorty/blob/master/fw/shorty.ino(Link to firmware)Where can I get it?Can someone upload it?

author: RoGeorge
author_rank: Super Contributor
timestamp: May 03, 2024, 05:15:20 pm
content: I've took a peek at the other shorty with display, from kripton2035 links/pages, has sources:http://kripton2035.free.fr/Continuity%20Meters/continuity-short.htmlhttp://kripton2035.free.fr/Resources/shorty.ino

author: Picuino
author_rank: Super Contributor
timestamp: May 03, 2024, 05:17:39 pm
content: After checking that the signal coming out of the Arduino (D3) is quite noisy, I connected the output to a BC557 PNP transistor to turn resistor R1 on and off at Vcc.The result is a signal on R1 with much less noise, but the amplified signal is still quite noisy.The problem I think is in the 3.3V reference voltage. I will have to generate it myself from the outside, because the one the Arduino delivers, even filtering it, doesn't get rid of having enough noise.

author: Picuino
author_rank: Super Contributor
timestamp: May 03, 2024, 05:19:22 pm
content: Quote from: RoGeorge on May 03, 2024, 05:15:20 pmI'm going to include the program in a post, just in case:Code:#include <avr/io.h>#include <util/delay.h>#ifndef F_CPU#define F_CPU 12000000UL#endif#define _BAUD 9600 // Baud rate (9600 is default)#define _UBRR (F_CPU/16)/_BAUD - 1 // Used for UBRRL and UBRRH#define BUZZER_PORT PORTD#define BUZZER_DDR DDRD#define BUZZER 6#define LED_TIMER_ON 500#define LED_TIMER_MAX 550unsigned int adc_val;#define AVERAGE_SIZE 5unsigned int adc_val_avg, adc_val_acc;unsigned char adc_val_avg_ptr, i, probe_state;unsigned int led_timer, led_;unsigned int probe_timer;int main (void){UBRR0H = ((_UBRR) & 0xF00); // define speedUBRR0L = (uint8_t) ((_UBRR) & 0xFF); // for serial uartUCSR0B |= _BV(TXEN0);UCSR0B |= _BV(RXEN0);DDRD &= ~(1 << DDD3); // input en D3 buttonDDRD |= (1 << DDD6); // output on D6 buzzerPORTD |= (1 << PORTD3);DDRC |= (1 << DDC3); // analog output 3 for pnp seedingDDRB |= (1 << DDC5); // analog input 5 for adc samplingPORTC &= ~(1 << PORTC3);OCR0A = 120;TCCR0A = 0x42;TCCR0B = 0x05;ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS0);ADMUX = 0x45;// ADMUX |= (1 << ADLAR); // Left adjust ADC result to allow easy 8 bit readingprobe_timer = 0;led_timer = 0;probe_state = 1;while (1){//if probe is active, blink a LEDled_timer++;if ((led_timer > LED_TIMER_ON) & (probe_state == 1))PORTB |= (1 << 5);elsePORTB &= ~(1 << 5);if (led_timer > LED_TIMER_MAX)led_timer = 0;//now decide, if the probe wasn't inactive for too longprobe_timer++;if (probe_timer > 15000){if (probe_state == 1)probe_state = 0;}//inactive probe codeif (probe_state == 0){PORTC |= (1 << PORTC3); // change A3=1DDRD &= ~(1 << DDD6); // stop buzzer D6_delay_ms(1);if ((PIND & (0x08)) == 0) // check D3 button state{probe_state = 1; // reactivate device if button is pressedprobe_timer = 0;}}//active probe codeif (probe_state == 1){PORTC &= ~(1 << PORTC3); // turns off A3=0_delay_ms(1);//take adc sampleADCSRA |= (1 << ADSC); // Start conversionwhile (ADCSRA & (1 << ADSC)); // wait for conversion to completeadc_val = ADCW; // ADCL reads only the low byte (8bit) of the ADC conversion result and ADCH reads only the High byte.// ADC & ADCW are indeed the same//take value and put it into floating averageadc_val_avg[adc_val_avg_ptr++] = adc_val;if (adc_val_avg_ptr > AVERAGE_SIZE) adc_val_avg_ptr = 0;adc_val_acc = 0;for (i = 0; i < AVERAGE_SIZE; i++)adc_val_acc = adc_val_acc + adc_val_avg;adc_val_acc = adc_val_acc / 20;adc_val = adc_val_acc;//decide what to doif (adc_val > 200) adc_val = 200;adc_val = adc_val + 2;if (adc_val < 200){OCR0A = adc_val; // The Output Compare Register A contains an 8-bit value that is continuously compared with the counter value (TCNT0)DDRD |= (1 << DDD6); // turns on buzzer D6probe_timer = 0;}else{DDRD &= ~(1 << DDD6); // turns off buzzer D6} // DDRD is the direction register for Port D (Arduino digital pins 0-7)}}}/*void usart_tx_b(uint8_t data){while (!(UCSR0A & _BV(UDRE0)));UDR0 = data;}void usart_tx_hexa (uint8_t value){uint8_t temp;temp = value;usart_tx_b('0');usart_tx_b('x');temp = ((temp>>4)&0x0F);if (temp<10) temp = temp + '0';else temp = temp + 'A'- 10;usart_tx_b(temp);temp = value;temp = ((temp>>0)&0x0F);if (temp<10) temp = temp + '0';else temp = temp + 'A' - 10;usart_tx_b(temp);usart_tx_b(' ');}*/

author: RoGeorge
author_rank: Super Contributor
timestamp: May 03, 2024, 05:36:48 pm
content: Quote from: Picuino on May 03, 2024, 05:17:39 pmI don't recall AVR chips to have a 3.3V reference, so I guess the 3.3V reference is in fact the MCU Vcc. Have you double check with the MCU datasheet, if it really has a 3.3V reference?If your Arduino board is with a classic ATmega48, that MCU has an internal bandgap Vref. Try using the 1.1V range (full scale) for the ADC. That will use the internal Vref instead of Vcc as Vref.I've tried to look at your source code, but when I've seen that 1 page of license written half in CAPS LOCK, i've give up and didn't read the code at all.

author: Kleinstein
author_rank: Super Contributor
timestamp: May 03, 2024, 06:10:26 pm
content: For a resistance measurement the current source / driving resistor should use the same reference voltage as the ADC. So in this case likely the supply.

author: Picuino
author_rank: Super Contributor
timestamp: May 03, 2024, 06:11:00 pm
content: The 3.3 volt reference comes from an LDO regulator on the Arduino Nano board itself.I replaced it with a follower op amp that amplifies a 4 volt resistive divider. It has much less noise than the Arduino's 3.3V reference, but the readings still vary much more than I expected.The problem is that the amplified signal Vout has a lot of variation. The oscilloscope with 16-sample average input gives the following Vout values for the resistor I am testing now: from 187mV to 197mV amplitude.

author: Picuino
author_rank: Super Contributor
timestamp: May 03, 2024, 06:15:15 pm
content: Quote from: Kleinstein on May 03, 2024, 06:10:26 pmYes, I am using the 5V coming from a lab power supply to power the amplifier, the resistor R1 and the internal voltage reference of the ADC.

author: Picuino
author_rank: Super Contributor
timestamp: May 04, 2024, 08:49:47 am
content: I replaced the MCP6N11 amplifier with a better one that I found in my SMD component book, theAD8220.The results have improved.Output measurements:Code:21.2021.2220.9620.9921.1721.1021.0821.0721.0221.0221.1220.9721.1821.1021.0921.1321.0421.0621.1521.2121.0121.2021.3021.0621.2821.1621.1321.0021.0421.1521.1721.0221.0920.9421.1221.1820.9521.0121.0921.1521.1121.0821.0221.0321.1621.0521.0721.0221.1621.0221.2221.1021.0820.9921.1120.9721.1220.9721.1721.0621.0921.1021.0420.9721.1821.0721.0221.1021.0521.0821.1921.0721.0920.9521.1521.0221.1321.0021.0121.1121.1621.0521.0921.1021.1421.0821.0421.0621.0021.2021.0921.0221.0721.1021.0421.0021.0321.1521.2021.0820.9721.0320.9521.1421.0921.0221.0521.0121.0420.9720.9921.0320.9721.0721.0821.1621.0820.9720.9321.0821.1021.0221.1420.9921.0021.1321.1221.1221.0921.1621.1521.1521.1621.0921.1221.0121.1220.9521.2121.1321.0821.0421.0621.2021.1321.0721.1021.0621.2221.1421.0921.1021.0520.9321.1621.1621.1121.1221.1921.0521.1221.0421.0820.9321.0721.0821.0020.9721.1721.0321.1120.9221.1121.1121.1021.0421.1121.0520.9821.2721.1421.1521.0821.0021.0721.2121.0121.0121.0621.0521.1021.1821.0521.1621.0821.1621.0821.1321.2221.1121.0821.1321.0121.1821.1121.0220.9721.0921.0121.2221.1021.0821.04I have added a calibration value to the program so that the results are measured in milliohms. In this case it is measuring about 20 milliohms from a small wire about 20cm long.Arduino code:Code:/*Copyright 2024 PicuinoPermission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software"), to dealin the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sellcopies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be includedin all copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISINGFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGSIN THE SOFTWARE.*/const int PIN_PWM_OUT = 3;const int PIN_ANALOG = A0;const int SAMPLES_PER_EDGE = 80;const int EDGES_PER_MEASURE = 5000 / SAMPLES_PER_EDGE;const float BOARD_CALIBRATION = 4.0;volatile long adc_sum;volatile unsigned int adc_enable;volatile unsigned int adc_value;volatile unsigned int adc_samples;volatile unsigned char timer0_state;volatile unsigned char output_level;volatile unsigned char output_level_old;volatile unsigned char adc_measure_end;void setup() {Serial.begin(9600);// Set up output reference signal pinpinMode(PIN_PWM_OUT, OUTPUT);timer0_setup();// Set up ADCadc_setup();// Main Loopfloat resistance;adc_init();for (;;) {if (adc_measure_end == 1) {resistance = adc_sum;resistance = resistance * (BOARD_CALIBRATION / (SAMPLES_PER_EDGE * EDGES_PER_MEASURE));Serial.println(resistance);adc_init();}}}void loop() {}void adc_init(void) {adc_measure_end = 0;adc_sum = 0;timer0_state = 0;adc_samples = 0;PORTD |= (1 << PIN_PWM_OUT);output_level = 1;output_level_old = 1;delayMicroseconds(200);adc_enable = 1;}void adc_setup(void) {analogRead(PIN_ANALOG);cli(); //stop interruptsADMUX = (1 << 6) | (0 << ADLAR);ADCSRA = (1 << ADEN) | (0 << ADSC) | (0 << ADATE) | (0 << ADIE) | (0b111);ADCSRB = 0x00;sei(); //allow interrupts}void timer0_setup(void) {cli(); //stop interrupts//set timer0 interrupt at 2kHzTCCR0A = 0; // set entire TCCR2A register to 0TCCR0B = 0; // same for TCCR2BTCNT0 = 0; //initialize counter value to 0// set compare match registerOCR0A = 26; // f = 16000000 / ((OCR0A + 1) * 64) = 9259 Hz// turn on CTC modeTCCR0A |= (1 << WGM01);// Set CS01 and CS00 bits for 64 prescalerTCCR0B |= (1 << CS01) | (1 << CS00);// enable timer compare interruptTIMSK0 |= (1 << OCIE0A);sei(); //allow interrupts}ISR(TIMER0_COMPA_vect) {if (adc_enable) {if (adc_samples) {// Read ADC measureadc_value = (ADCL | (ADCH << 8));// Add measure to accumulatorif (output_level_old == 1) {adc_sum -= adc_value;}if (output_level_old == 0) {adc_sum += adc_value;}}// ADC Start ConversionADCSRA |= (1 << ADSC);adc_samples++;delayMicroseconds(12); // Wait for Sample and Holdoutput_level_old = output_level;// Update next stateif (++timer0_state >= (SAMPLES_PER_EDGE * 2)) {timer0_state = 0;}// Update output reference signalif (timer0_state < SAMPLES_PER_EDGE) {PORTD |= (1 << PIN_PWM_OUT);output_level = 1;}else {PORTD &= ~(1 << PIN_PWM_OUT);output_level = 0;}// Take one measure after several samplesif (adc_samples > SAMPLES_PER_EDGE * EDGES_PER_MEASURE * 2) {adc_measure_end = 1;adc_enable = 0;}}}

author: Sariel
author_rank: Regular Contributor
timestamp: May 04, 2024, 11:38:31 am
content: I made a very nice Lock in amplifier based on AD630.A short time ago, I actually posted a LinkedIn post about this with some results:https://www.linkedin.com/posts/sariel-hodisan-792363120_lock-in-amplifiers-lias-are-incredibly-activity-7180141558162178049-_iK3?utm_source=share&utm_medium=member_android

author: Picuino
author_rank: Super Contributor
timestamp: May 04, 2024, 08:53:30 pm
content: I have corrected a software problem that caused some ADC conversions to be lost. The ADC conversion now starts at the beginning of the interrupt routine so that there are no delays with overlapping ADC conversions.It is tested and works better than before.Arduino program Version 1.0:Code:/*Version 1.0 (04/05/2024)Copyright 2024 PicuinoPermission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software"), to dealin the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sellcopies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be includedin all copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISINGFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGSIN THE SOFTWARE.*/const int PIN_PWM_OUT = 3;const int PIN_ANALOG = A0;#define CLK_BOARD 16000000#define UART_BAUDS 9600#define TIMER0_PRESET 26 // Must be >= 26#define TIMER0_FREQ (CLK_BOARD / (((TIMER0_PRESET) + 1) * 64))const int SAMPLES_PER_LEVEL = 20;const int LEVELS_PER_MEASURE = TIMER0_FREQ / (SAMPLES_PER_LEVEL * 2);const float BOARD_CALIBRATION = 4.0;volatile long adc_sum;volatile unsigned int adc_enable;volatile unsigned int adc_value;volatile unsigned int adc_samples;volatile unsigned int timer0_state;volatile unsigned char output_level;volatile unsigned char output_level_old;volatile unsigned char adc_measure_end;void setup() {Serial.begin(UART_BAUDS);// Set up output reference signal pinpinMode(PIN_PWM_OUT, OUTPUT);// Set up peripheralstimer0_setup();adc_setup();// Main Loopfloat resistance;adc_init();for (;;) {if (adc_measure_end == 1) {resistance = adc_sum;resistance = resistance * (BOARD_CALIBRATION / (1.0 * SAMPLES_PER_LEVEL * LEVELS_PER_MEASURE));Serial.println(resistance);adc_init();}}}void loop() {}void adc_init(void) {adc_measure_end = 0;adc_sum = 0;timer0_state = 0;adc_samples = 0;PORTD |= (1 << PIN_PWM_OUT);output_level = 1;output_level_old = 1;delayMicroseconds(200);adc_enable = 1;}void adc_setup(void) {analogRead(PIN_ANALOG);cli(); //stop interruptsADMUX = (1 << 6) | (0 << ADLAR);ADCSRA = (1 << ADEN) | (0 << ADSC) | (0 << ADATE) | (0 << ADIE) | (0b111);ADCSRB = 0x00;sei(); //allow interrupts}void timer0_setup(void) {cli(); //stop interrupts//set timer0 interrupt at 2kHzTCCR0A = 0; // set entire TCCR2A register to 0TCCR0B = 0; // same for TCCR2BTCNT0 = 0; //initialize counter value to 0// set compare match registerOCR0A = TIMER0_PRESET;// turn on CTC modeTCCR0A |= (1 << WGM01);// Set CS01 and CS00 bits for 64 prescalerTCCR0B |= (1 << CS01) | (1 << CS00);// enable timer compare interruptTIMSK0 |= (1 << OCIE0A);sei(); //allow interrupts}ISR(TIMER0_COMPA_vect) {if (adc_enable) {// ADC Start ConversionADCSRA |= (1 << ADSC);adc_samples++;delayMicroseconds(15); // Wait for Sample and Hold// Read and accumulate old ADC valueif (adc_samples) {// Read ADC old measureadc_value = ADCW;// Add measure to accumulatorif (output_level_old == 1) {adc_sum -= adc_value;}if (output_level_old == 0) {adc_sum += adc_value;}}// Update next stateif (++timer0_state >= (SAMPLES_PER_LEVEL * 2)) {timer0_state = 0;}// Update output reference signaloutput_level_old = output_level;if (timer0_state < SAMPLES_PER_LEVEL) {PORTD |= (1 << PIN_PWM_OUT);output_level = 1;}else {PORTD &= ~(1 << PIN_PWM_OUT);output_level = 0;}// Take one measure after several samplesif (adc_samples > SAMPLES_PER_LEVEL * LEVELS_PER_MEASURE * 2) {adc_measure_end = 1;adc_enable = 0;}}}Output measures:Code:22.5722.4822.5222.5022.4322.4922.4022.4122.5422.3522.3822.5422.5322.5122.4022.3522.4022.4522.4822.5222.5322.4422.5122.4022.4122.4722.4222.5322.4822.3022.3522.4922.4222.4122.5222.4122.3522.3322.3622.3822.3722.4622.4922.4722.4122.3822.4122.4322.5522.3222.3922.4622.1922.4822.3422.3622.4022.3722.4022.45I think that this time I have managed to recover part of the signal “buried” in the noise.Attached: Vout

author: Picuino
author_rank: Super Contributor
timestamp: May 04, 2024, 09:59:13 pm
content: Schematic in PNG format and KiCad 7.0 format.https://www.analog.com/media/en/technical-documentation/data-sheets/AD8220.pdfhttps://www.ti.com/lit/ds/symlink/lmv358a.pdf

author: RoGeorge
author_rank: Super Contributor
timestamp: May 05, 2024, 08:02:34 am
content: What I would try:- split the 4k7 in two of 2k2 or so, one to the +5V, and one to the GND, with the DUT in the middle- remove the transistor, and use 4 MCU pins in parallel instead of GND, and another 4 parallel pins as V+ for the 2k2+DUT+2k2 divider (the average Rds ON is about 60ohms for 1 DO, and the mismatch between the upper and the lower side is about 10% or less)- once the divider connected between the MCU pins, alternate the polarity (output 11110000 then 00001111 repeatedly), and measure the peak to peak amplitude, such that the thermocouple voltages and opamp offset are eliminated- trigger the ADC always at the same distance from the polarity flipping edge, somewhere at the middle- use the low-noise mode conversion if possible (don't recall the exact conditions), the one in which other unnecessary blocks of the microcontroller are powered down, so to make less noise during a conversion- keep all arithmetic with integers (maybe keep a running average on the MCU, not sure), and only use floating point to scale the received data on the PC- use the highest possible clock for the CPU, and use a constant time loop (an infinite loop that always execute in the same amount of machine cycles rather than interrupts, if possible), to minimize the jitter

author: Picuino
author_rank: Super Contributor
timestamp: May 05, 2024, 10:06:57 am
content: Thank you very much RoGeorge.I have an ADC AD7691:https://www.analog.com/media/en/technical-documentation/data-sheets/ad7691.pdfMy next step will be to sample with this more accurate adc that can separate the analog from the digital part.With that I hope to get some more accuracy, but I'm afraid I'm not going to get much error reduction due to the noise. Anyway I will try to check the results.I will take into account the advice to try to reduce jitter.As for the signal directly from the digital outputs, it doesn't seem to be a good idea, because when I remove the transistor and feed the resistor under test directly from the digital output of the microcontroller, the noise increases.I will try to separate as much as possible the analog part from the digital part.

author: Kleinstein
author_rank: Super Contributor
timestamp: May 05, 2024, 01:09:49 pm
content: Using the µC to directly drive the DUT is a 2 sided thing: if eliminates the transistor and gets maybe closer to a ratiometric measurement. However it also add the somewhat temperature dependent output resistance. So the gain drift and fluctuations from that side can get worse.With the current test current of some 1 mA 0,1 mOhm is 100 nV at the DUT. So the observed noise is not that bad.It depends on the avearging time if the observed noise is about what one can expect from the AD8220. With a lock-in amplifier one can trade speed for noise. With relatively short integration one may still get some effect of mains hum.

author: RoGeorge
author_rank: Super Contributor
timestamp: May 05, 2024, 01:28:58 pm
content: What are the aimed specs? What range of resistance, with what accuracy, and at what resolution should the instrument measure?

author: Picuino
author_rank: Super Contributor
timestamp: May 05, 2024, 07:20:04 pm
content: Quote from: Kleinstein on May 05, 2024, 01:09:49 pmAt this time the measurement time is approximately 1 second in which more than 9200 samples are taken.100nV of noise is OK, but that is the residual noise after averaging all samples. In the individual samples the Vout signal coming out of the amplifier AD8220 has a noise greater than 20mV and I think it is very high. Anyway for a prototype board mounted circuit the final noise it is not too bad.Quote from: RoGeorge on May 05, 2024, 01:28:58 pmFor now I am just experimenting to see what can be achieved with this type of circuitry.With what I have already achieved, the circuit would have a resolution of 0.1 milliohm and a range of about 4 ohms (this would require coupling the input of the instrumentation amplifier capacitively).Good range and resolution for measure ESR of capacitors or measure the resistance of copper traces of a PCB.

author: jbb
author_rank: Super Contributor
timestamp: May 05, 2024, 08:02:02 pm
content: At risk of totally derailing the proceedings, I have an alternative suggestion: the Texas Instruments ADS1235. It has a 24 bit ADC, Programmable Gain Amplifier (PGA) and control logic for AC excitation. Seems like it can apply positive excitation, sample the ADC, apply negative excitation and sample the ADC again for you.https://www.ti.com/product/ADS1235

author: Kleinstein
author_rank: Super Contributor
timestamp: May 05, 2024, 08:03:50 pm
content: It is normal for the individual readings from the ADC to have quite some noise. Some noise is actually good, so that oversampling works as intended and is not stuck a certain ADC levels.For low noise one may acutually want some anti aliasing filter (e.g. for some 5 -10 kHz). Other wise the µC internal ADC could pic up noise up to some 50 kHz (or 15 kHz from the amplifier). The limited BW of the AD8220 here also does some filtering, but more could improve things a little more.The settling time of the fitler would likely want skipping some samples to reduce the effect of the settling speed.20 mV at the output of an amplifier with a gain of 1000 is not that bad.

author: trobbins
author_rank: Frequent Contributor
timestamp: May 06, 2024, 01:19:20 am
content: Back in the 90's I used an AD630 to extract a battery impedance measurement from industrial noisy live battery systems using a portable tool that applied about 0.1Arms sinewave into a battery (cell or string) and kelvin probe extracted a signal that was amplified and presented to the AD630. An ICL8038 sine generator provided reasonable amplitude stability, muting, and a sync signal for the AD630. The test frequency was aligned to nominal zero-phase shift response of target batteries, and not aligned to any mains related processes. At that time only a 12-bit ADC was practical, with software flipping the sensed signal polarity and averaging, to achieve a max error tolerance of <0.3% along with temperature errors mainly due to the 8038 and current sense resistor, for FS reading of 50 milliohm and resolution down to single micro-ohm level. The AD630 was not the main concern for performance, but was a pricey part.

author: RoGeorge
author_rank: Super Contributor
timestamp: May 06, 2024, 05:41:35 am
content: A few measures to lower the noise:- keep the breadboard wires short and the loops short. A circular shape is the largest area a string can enclose, and the larger the area of a loop, the more induced noise. If the breadboarding wires are too long, then run them parallel, or twisted. Preferably cut the wires just the right length, and run then as if it were a PCB (for example use single core wires stripped from a LAN patch cord, and cut them on the spot, just the right length, LAN wires are made of copper, and thick enough for the breadboard contacts).Not so good (wires making loops with big area, components with long legs also making loops):Better (wires were cut just the right length and routed straight, components terminals not very long):Well, maybe the capacitors and resistors terminals could have been cut even shorter, like here:https://www.eevblog.com/forum/projects/t20347/?action=dlattach;attach=1242762;imagebut you got the idea, avoid big area loops.- avoid ground loops. Use star-connection for GND wires, with a single GND point from where all the ground wires are leaving to different places in the circuit. Do not chain the GND wires one after another. A single GND point also separates the analog and digital GNDs.- if the above is not possible in practice, at least keep the analog and digital current loops separated:https://www.analog.com/media/en/training-seminars/tutorials/MT-031.pdfhttps://www.analog.com/media/en/technical-documentation/application-notes/AN-202.pdf- enclose the low signal stages in a grounded metal shield, shielding makes a big difference, it eliminates the noises induced from the outside of the circuit

author: Picuino
author_rank: Super Contributor
timestamp: May 06, 2024, 11:10:21 am
content: Quote from: jbb on May 05, 2024, 08:02:02 pmI am going to add an 18bit ADC to see if it improves the result over the 10bit ADC I am using now. Anyway the resolution improvement should be achieved thanks to the averaging of many measurements.Quote from: trobbins on May 06, 2024, 01:19:20 amI am going to increase the test current to check if with that change I can increase the resolution or if, on the contrary, the noise also goes up.RoGeorge:I will replace the most critical cables (analog part) with cables taken from a network cable, with the right size.I used to always do the assemblies this way, but it's a lot of work and it doesn't always improve the result.

author: Picuino
author_rank: Super Contributor
timestamp: May 06, 2024, 05:58:15 pm
content: With 10 times more current (R2=470 Ohm), I have achieved 10 times more resolution.I now measure tens of microohms instead of hundreds of microohms.Output measures:Code:21.499mOhm21.502mOhm21.496mOhm21.502mOhm21.504mOhm21.499mOhm21.508mOhm21.502mOhm21.500mOhm21.503mOhm21.508mOhm21.500mOhm21.505mOhm21.504mOhm21.507mOhm21.500mOhm21.512mOhm21.499mOhm21.505mOhm21.504mOhm21.505mOhm21.493mOhm21.496mOhm21.502mOhm21.499mOhm21.501mOhm21.502mOhm21.495mOhm21.492mOhm21.499mOhm21.486mOhm21.488mOhm21.499mOhm21.505mOhm21.494mOhm21.509mOhm21.501mOhm21.490mOhm21.501mOhm21.485mOhm21.492mOhm21.495mOhm21.493mOhm21.497mOhm21.492mOhm21.497mOhm21.501mOhm

author: Picuino
author_rank: Super Contributor
timestamp: May 06, 2024, 06:14:07 pm
content: If I reduce the gain of the amplifier (from 1050 to 105), the resolution is again worse.DUT = 0.020 OhmMeasure time = 1 secondConfiguration 1:R2 = 4700 OhmI_R2 = 1,04 mAAmplifier gain = 1050Output signal variation = 21 mVOutput signal noise = 20mVppMeasures = 21.500 +- 0.1 mOhmConfiguration 2:R2 = 470 OhmI_R2 = 10,4 mAAmplifier gain = 1050Output signal variation = 210 mVOutput signal noise = 20mVppMeasures = 21.500 +- 0.01 mOhmConfiguration 3:R2 = 470 OhmI_R2 = 10,4 mAAmplifier gain = 105Output signal variation = 21 mVOutput signal noise = 20mVppMeasures = 21.500 +- 0.1 mOhmEDIT:The signal-to-noise ratio remains constant at approximately 50000 at full range (5Vpp in Vout).

author: RoGeorge
author_rank: Super Contributor
timestamp: May 06, 2024, 06:39:52 pm
content: Nice!Is this with the internal 10bit ADC of ATmega328, or with the 18bits ADC?

author: Kleinstein
author_rank: Super Contributor
timestamp: May 06, 2024, 07:02:28 pm
content: The high gain of the amplifier also works as a kind of AA filter due to the limited bandwidth. So less gain can cause additional noise aliasing.

author: Picuino
author_rank: Super Contributor
timestamp: May 06, 2024, 07:33:19 pm
content: Changing the measurement time (summation time) also affects the result.New program without measure error due to an error in the first sample of the ADC:Code:/*Version 1.1 (06/05/2024)Copyright 2024 PicuinoPermission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software"), to dealin the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sellcopies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be includedin all copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISINGFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGSIN THE SOFTWARE.*/const int PIN_PWM_OUT = 3;const int PIN_ANALOG = A6;const int PIN_ANALOG_MUX = 6;#define CLK_BOARD 16000000#define UART_BAUDS 115200#define TIMER0_PRESET 27 // Must be >= 27#define TIMER0_FREQ (CLK_BOARD / (((TIMER0_PRESET) + 1) * 64)) // 9250const float MEASURE_TIME = 1; // Secondsconst long SAMPLES_PER_LEVEL = 40; // ADC samples per output levelconst long LEVELS_PER_MEASURE = 2 * (long)((MEASURE_TIME * TIMER0_FREQ) / (SAMPLES_PER_LEVEL * 2));const float BOARD_CALIBRATION = 8.0; // Converts measure to milliohmsvolatile long adc_sum;volatile unsigned int adc_enable;volatile unsigned int adc_value;volatile unsigned long adc_samples;volatile unsigned int level_state;volatile unsigned char output_level;volatile unsigned char output_level_old;volatile unsigned char adc_measure_end;char buff;float resistance;void setup() {Serial.begin(UART_BAUDS);Serial.print("MEASURE_TIME = "); Serial.println(1.0 * SAMPLES_PER_LEVEL * LEVELS_PER_MEASURE / TIMER0_FREQ);Serial.print("SAMPLES_PER_LEVEL = "); Serial.println(SAMPLES_PER_LEVEL);Serial.print("LEVELS_PER_MEASURE = "); Serial.println(LEVELS_PER_MEASURE);// Set up output reference signal pinpinMode(PIN_PWM_OUT, OUTPUT);// Set up peripheralstimer0_setup();adc_setup();// Main Loopadc_init();for (;;) {if (adc_measure_end == 1) {resistance = adc_sum;resistance *= (BOARD_CALIBRATION / (SAMPLES_PER_LEVEL * LEVELS_PER_MEASURE));Serial.print(resistance, 2);Serial.println("\tmOhm");adc_init();}}}void loop() {}void adc_init(void) {cli();PORTD |= (1 << PIN_PWM_OUT);output_level = 0;level_state = 0;delayMicroseconds(100);adc_sum = 0;adc_samples = 0;output_level = 0;adc_measure_end = 0;adc_enable = 1;sei();}void adc_setup(void) {analogRead(PIN_ANALOG);cli(); //stop interruptsADMUX = (1 << 6) | (0 << ADLAR) | (PIN_ANALOG_MUX << 0);ADCSRA = (1 << ADEN) | (0 << ADSC) | (0 << ADATE) | (0 << ADIE) | (0b111);ADCSRB = 0x00;sei(); //allow interrupts}void timer0_setup(void) {cli(); //stop interrupts//set timer0 interrupt at 2kHzTCCR0A = 0; // set entire TCCR2A register to 0TCCR0B = 0; // same for TCCR2BTCNT0 = 0; //initialize counter value to 0// set compare match registerOCR0A = TIMER0_PRESET;// turn on CTC modeTCCR0A |= (1 << WGM01);// Set CS01 and CS00 bits for 64 prescalerTCCR0B |= (1 << CS01) | (1 << CS00);// enable timer compare interruptTIMSK0 |= (1 << OCIE0A);sei(); //allow interrupts}ISR(TIMER0_COMPA_vect) {if (adc_enable) {// ADC Start ConversionADCSRA |= (1 << ADSC);delayMicroseconds(12); // Wait for Sample and Hold// Read and accumulate old ADC valueif (adc_samples) {// Read ADC old measureadc_value = ADCW;// Add measure to accumulatorif (output_level_old == 0) {adc_sum -= adc_value;}if (output_level_old == 1) {adc_sum += adc_value;}}adc_samples++;// Update next statelevel_state++;if (level_state >= (2 * SAMPLES_PER_LEVEL)) {level_state = 0;}// Update output reference signaloutput_level_old = output_level;if (level_state < SAMPLES_PER_LEVEL) {PORTD |= (1 << PIN_PWM_OUT);output_level = 0;}else {PORTD &= ~(1 << PIN_PWM_OUT);output_level = 1;}// Take one measure after several samplesif (adc_samples > SAMPLES_PER_LEVEL * LEVELS_PER_MEASURE) {PORTD |= (1 << PIN_PWM_OUT);output_level = 0;adc_measure_end = 1;adc_enable = 0;}}}Code:MEASURE_TIME = 0.10SAMPLES_PER_LEVEL = 40LEVELS_PER_MEASURE = 2222.04mOhm21.55mOhm21.41mOhm21.90mOhm21.52mOhm21.76mOhm22.08mOhm21.52mOhm21.89mOhm21.86mOhm21.71mOhm21.98mOhm21.60mOhm21.79mOhm22.03mOhm21.50mOhm21.98mOhm21.65mOhm21.57mOhm22.15mOhm21.48mOhm21.95mOhm21.99mOhm21.65mOhm21.74mOhm21.59mOhm21.81mOhm21.76mOhm22.13mOhm21.75mOhm21.70mOhm22.00mOhm21.54mOhm21.89mOhm21.90mOhm21.43mOhm22.26mOhm21.35mOhm21.89mOhm21.80mOhm21.38mOhm21.95mOhm21.79mOhm21.50mOhm21.99mOhm21.90mOhm21.33mOhm22.08mOhm21.33mOhm21.75mOhm21.58mOhm21.57mOhm21.78mOhm21.28mOhm21.67mOhm22.05mOhm21.76mOhm21.28mOhm21.86mOhm22.06mOhm21.40mOhm21.72mOhm21.83mOhm21.46mOhm22.15mOhm21.25mOhm21.77mOhmCode:MEASURE_TIME = 0.99SAMPLES_PER_LEVEL = 40LEVELS_PER_MEASURE = 22221.62mOhm21.68mOhm21.73mOhm21.62mOhm21.67mOhm21.64mOhm21.71mOhm21.66mOhm21.69mOhm21.68mOhm21.67mOhm21.69mOhm21.71mOhm21.74mOhm21.78mOhm21.64mOhm21.71mOhm21.72mOhm21.66mOhm21.59mOhm21.70mOhm21.71mOhm21.60mOhm21.68mOhm21.69mOhm21.68mOhm21.57mOhm21.63mOhm21.66mOhm21.64mOhm21.71mOhm21.74mOhm21.63mOhm21.76mOhm21.74mOhm21.74mOhm21.76mOhm21.70mOhm21.68mOhm21.65mOhm21.64mOhm21.67mOhm21.61mOhm21.52mOhm21.65mOhm21.64mOhm21.64mOhm21.67mOhm21.70mOhm21.53mOhm21.54mOhmCode:MEASURE_TIME = 10.00SAMPLES_PER_LEVEL = 40LEVELS_PER_MEASURE = 223221.62mOhm21.59mOhm21.63mOhm21.60mOhm21.63mOhm21.67mOhm21.68mOhm21.72mOhm21.77mOhm21.72mOhm21.76mOhm21.75mOhm21.74mOhm21.61mOhm21.59mOhmEDIT:Attached the schematic version 1.1 (with minor changes)

author: Picuino
author_rank: Super Contributor
timestamp: May 06, 2024, 07:35:13 pm
content: Quote from: RoGeorge on May 06, 2024, 06:39:52 pmAll this measures still with the internal 10-bit ADC.I wanted to do the tests before changing the results too much with the new ADC.

author: Picuino
author_rank: Super Contributor
timestamp: May 07, 2024, 01:17:32 pm
content: Before continuing, I am going to measure with the multimeter (Agilent 34401A) the AC voltage (noise) at various points in the circuit to see if there is a way to reduce the amplifier's output noise.First I keep the Arduino nano in an infinite loop without doing anything (cli(); for(;Voltages:GND to GND (test probes in short circuit) = 5 uVACGND to VCC (main 5V supply) = 80 uVACGND to VCC (main 5V supply) = 2 uVAC (without Arduino nano connected)From now on I disconnect the Arduino nano from the 5V power supply and only power it via USB so that it does not produce so much noise on the power supply.R5-R6 voltage divisor = 3 uVACLMV358 output pin 1 ( 2.5Vref ) = 1 uVACDUT pin + = 4 uVACDUT pin - = 4 uVACInstrumentation amplifier output = 12000 uVAC (Gain = 1050)Instrumentation amplifier output = 540 uVAC (Gain = 105)Instrumentation amplifier Vcc = 5 uVACInstrumentation amplifier Vcc = 4 uVAC

author: Picuino
author_rank: Super Contributor
timestamp: May 07, 2024, 01:23:07 pm
content: I have changed the position of the gain resistor so that it is as close to the circuit as possible.Now the noise has decreased quite a bit.Instrumentation amplifier output = 2200 uVAC (Gain = 1050)

author: Picuino
author_rank: Super Contributor
timestamp: May 07, 2024, 01:29:26 pm
content: The measures shows lower noise:Code:MEASURE_TIME = 0.99SAMPLES_PER_LEVEL = 40LEVELS_PER_MEASURE = 22221.09mOhm21.10mOhm21.06mOhm20.98mOhm20.99mOhm20.98mOhm21.11mOhm21.00mOhm21.01mOhm21.00mOhm21.03mOhm21.04mOhm21.06mOhm20.99mOhm21.04mOhm20.98mOhm21.00mOhm21.11mOhm21.06mOhm21.04mOhm20.99mOhm21.02mOhm21.06mOhm21.07mOhm21.04mOhm21.10mOhm21.08mOhm21.10mOhm20.98mOhm21.12mOhm21.02mOhm21.08mOhmEDIT:Conclusion: the position of the gain resistor R1 is critical. Keep the resistor as close as possible to the integrated and free of noise.

author: Picuino
author_rank: Super Contributor
timestamp: May 07, 2024, 03:30:09 pm
content: I have added aquadrature accumulatorto be able to make Impedance measurements.To achieve this I had to make some changes in the schematic:* I have eliminated the transistor (it seems that the noise was not coming from there).* I have added capacitive coupling to the instrumentation amplifier.New program:Code:/*Version 2.0 (07/05/2024)Copyright 2024 PicuinoPermission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software"), to dealin the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sellcopies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be includedin all copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISINGFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGSIN THE SOFTWARE.*/const int PIN_PWM_OUT = 3;const int PIN_ANALOG = A6;const int PIN_ANALOG_MUX = 6;#define CLK_BOARD 16000000#define UART_BAUDS 115200#define TIMER0_PRESET 27 // Must be >= 27#define TIMER0_FREQ (CLK_BOARD / (((TIMER0_PRESET) + 1) * 64)) // 9250const float MEASURE_TIME = 1; // Secondsconst long SAMPLES_PER_LEVEL = 6; // ADC samples per output level. must be an even number.const long LEVELS_PER_MEASURE = 2 * (long)(((MEASURE_TIME) * (TIMER0_FREQ)) / (2 * SAMPLES_PER_LEVEL));const float BOARD_CALIBRATION = 8.0; // Converts measure to milliohmsvolatile long adc_acc_inphase;volatile long adc_acc_quadrature;volatile unsigned char adc_enable;volatile unsigned char adc_measure_end;volatile unsigned int adc_value;volatile unsigned long adc_samples;volatile unsigned int level_state;volatile unsigned char level_state_old;float resistance_inphase;float resistance_quadrature;void setup() {Serial.begin(UART_BAUDS);Serial.print("MEASURE_TIME = ");Serial.print(1.0 * (SAMPLES_PER_LEVEL) * (LEVELS_PER_MEASURE) / (TIMER0_FREQ));Serial.println(" s");Serial.print("MEASURE_FREQUECY = ");Serial.print((TIMER0_FREQ) / (2.0 * SAMPLES_PER_LEVEL));Serial.println(" Hz");Serial.print("SAMPLES_PER_LEVEL = ");Serial.print(SAMPLES_PER_LEVEL);Serial.println(" Samples");// Set up output reference signal pinpinMode(PIN_PWM_OUT, OUTPUT);// Set up peripheralstimer0_setup();adc_setup();// Main Loopadc_init();for (;;) {if (adc_measure_end == 1) {resistance_inphase = adc_acc_inphase;resistance_quadrature = adc_acc_quadrature;resistance_inphase *= (BOARD_CALIBRATION / (SAMPLES_PER_LEVEL * LEVELS_PER_MEASURE));resistance_quadrature *= (BOARD_CALIBRATION / (SAMPLES_PER_LEVEL * LEVELS_PER_MEASURE));Serial.print(resistance_inphase, 2);Serial.print("\tmOhm R \t");Serial.print(resistance_quadrature, 2);Serial.println("\tmOhm Zc");adc_init();}}}void loop() {}void adc_init(void) {cli();PORTD |= (1 << PIN_PWM_OUT);level_state = 0;delayMicroseconds(100);adc_acc_inphase = 0;adc_acc_quadrature = 0;adc_samples = 0;adc_measure_end = 0;adc_enable = 1;sei();}void adc_setup(void) {analogRead(PIN_ANALOG);cli(); //stop interruptsADMUX = (1 << 6) | (0 << ADLAR) | (PIN_ANALOG_MUX << 0);ADCSRA = (1 << ADEN) | (0 << ADSC) | (0 << ADATE) | (0 << ADIE) | (0b111);ADCSRB = 0x00;sei(); //allow interrupts}void timer0_setup(void) {cli(); //stop interrupts//set timer0 interrupt at 2kHzTCCR0A = 0; // set entire TCCR2A register to 0TCCR0B = 0; // same for TCCR2BTCNT0 = 0; //initialize counter value to 0// set compare match registerOCR0A = TIMER0_PRESET;// turn on CTC modeTCCR0A |= (1 << WGM01);// Set CS01 and CS00 bits for 64 prescalerTCCR0B |= (1 << CS01) | (1 << CS00);// enable timer compare interruptTIMSK0 |= (1 << OCIE0A);sei(); //allow interrupts}ISR(TIMER0_COMPA_vect) {if (adc_enable) {// ADC Start ConversionADCSRA |= (1 << ADSC);delayMicroseconds(12); // Wait for Sample and Hold// Read and accumulate old ADC valueif (adc_samples) {// Read ADC old measureadc_value = ADCW;// Add measure to accumulatorif (level_state_old < SAMPLES_PER_LEVEL) {adc_acc_inphase -= adc_value;}else {adc_acc_inphase += adc_value;}if ((level_state_old >= 0.5 * SAMPLES_PER_LEVEL) && (level_state_old < 1.5 * SAMPLES_PER_LEVEL)) {adc_acc_quadrature -= adc_value;}else {adc_acc_quadrature += adc_value;}}adc_samples++;// Update next statelevel_state_old = level_state;level_state++;if (level_state >= (2 * SAMPLES_PER_LEVEL)) {level_state = 0;}// Update output reference signalif (level_state < SAMPLES_PER_LEVEL) {PORTD |= (1 << PIN_PWM_OUT);}else {PORTD &= ~(1 << PIN_PWM_OUT);}// Take one measure after several samplesif (adc_samples > SAMPLES_PER_LEVEL * LEVELS_PER_MEASURE) {adc_measure_end = 1;adc_enable = 0;}}}Measurements of a one-turn coiled cable with a diameter of 5 cm:Code:MEASURE_TIME = 1.00 sMEASURE_FREQUECY = 744.00 HzSAMPLES_PER_LEVEL = 6 Samples485.72mOhm R-0.18mOhm Zc485.68mOhm R-0.26mOhm Zc485.81mOhm R-0.21mOhm Zc486.26mOhm R-0.21mOhm Zc486.30mOhm R-0.29mOhm Zc486.78mOhm R-0.19mOhm Zc487.34mOhm R-0.22mOhm Zc487.63mOhm R-0.28mOhm Zc487.70mOhm R-0.27mOhm Zc487.77mOhm R-0.31mOhm Zc487.68mOhm R-0.25mOhm Zc487.91mOhm R-0.29mOhm Zc487.96mOhm R-0.28mOhm Zc488.01mOhm R-0.24mOhm Zc487.99mOhm R-0.24mOhm Zc487.98mOhm R-0.25mOhm Zc488.02mOhm R-0.20mOhm Zc488.25mOhm R-0.25mOhm Zc488.23mOhm R-0.24mOhm Zc488.44mOhm R-0.31mOhm Zc488.39mOhm R-0.28mOhm Zc488.41mOhm R-0.26mOhm Zc488.35mOhm R-0.32mOhm ZcMeasurements of a capacitor (1000uF, 16V):Code:MEASURE_TIME = 1.00 sMEASURE_FREQUECY = 744.00 HzSAMPLES_PER_LEVEL = 6 Samples88.13mOhm R130.24mOhm Zc108.88mOhm R161.26mOhm Zc108.79mOhm R161.40mOhm Zc108.70mOhm R161.41mOhm Zc108.73mOhm R161.39mOhm Zc108.71mOhm R161.38mOhm Zc108.87mOhm R161.48mOhm Zc108.74mOhm R161.47mOhm Zc108.74mOhm R161.47mOhm Zc108.75mOhm R161.36mOhm Zc108.76mOhm R161.42mOhm Zc108.75mOhm R161.52mOhm Zc108.78mOhm R161.48mOhm Zc108.83mOhm R161.53mOhm Zc108.76mOhm R161.51mOhm Zc108.80mOhm R161.44mOhm Zc108.83mOhm R161.44mOhm Zc108.69mOhm R161.44mOhm Zc108.88mOhm R161.45mOhm Zc108.81mOhm R161.49mOhm Zc108.91mOhm R161.40mOhm Zc108.92mOhm R161.49mOhm Zc108.89mOhm R161.64mOhm Zc108.92mOhm R161.53mOhm Zc109.02mOhm R161.53mOhm Zc108.91mOhm R161.44mOhm Zc108.93mOhm R161.55mOhm Zc108.95mOhm R161.55mOhm Zc108.94mOhm R161.56mOhm Zc108.99mOhm R161.63mOhm Zc

author: Picuino
author_rank: Super Contributor
timestamp: May 07, 2024, 03:56:02 pm
content: Attached:Signal in instrumentation amplifier output measuring a capacitor of 1000uF.

author: Picuino
author_rank: Super Contributor
timestamp: May 08, 2024, 09:42:36 am
content: I am trying to start up the ADCAD7691, but it just makes strange measurements.With both inputs connected to 2.5V it should measure zero, but it makes full scale measurements:Code:65534655336553265533655346553265533655336553365532655346553265532655336553465532655336553365533655336553365533655346553265533I am only getting the first 16 bits, discarding the last two bits.

author: Kleinstein
author_rank: Super Contributor
timestamp: May 08, 2024, 09:50:27 am
content: The input range of the AD7691 is +-Vref. So the result should be signed and not an unsigned number. So the results looks like there is a slight negative offset of some 3 LSBs, which looks reasonable.

author: Picuino
author_rank: Super Contributor
timestamp: May 08, 2024, 10:13:49 am
content: Yes, I have changed the negative input by connecting it to ground and the readings are correct.

author: Picuino
author_rank: Super Contributor
timestamp: May 08, 2024, 11:37:34 am
content: I already have the first measurements with the new ADC.Program:Code:/*Version 3.0 (08/05/2024)Copyright 2024 PicuinoPermission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software"), to dealin the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sellcopies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be includedin all copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISINGFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGSIN THE SOFTWARE.*/#define PIN_SIGNAL_OUT 3#define PIN_DEBUG_OUT 5#define PIN_ANALOG A6#define PIN_ANALOG_MUX 6#define PIN_SCK 13#define PIN_SDI 12#define PIN_CNV 10#define CLK_BOARD 16000000#define UART_BAUDS 115200#define TIMER0_PRESET 27 // Must be >= 10#define TIMER0_FREQ (CLK_BOARD / (((TIMER0_PRESET) + 1) * 64))const float MEASURE_TIME = 1; // Secondsconst long SAMPLES_PER_LEVEL = 6; // ADC samples per output level. must be an even number.const long LEVELS_PER_MEASURE = 2 * (long)(((MEASURE_TIME) * (TIMER0_FREQ)) / (2 * SAMPLES_PER_LEVEL));const float BOARD_CALIBRATION = 0.1664; // Converts measure to milliohmsvolatile long adc_acc_inphase;volatile long adc_acc_quadrature;volatile unsigned char adc_enable;volatile unsigned char adc_measure_end;volatile unsigned long adc_samples;volatile unsigned char level_state;volatile unsigned char level_state_old;volatile union {unsigned int w;struct {unsigned char lo;unsigned char hi;};} adc_value;float resistance_inphase;float resistance_quadrature;void setup() {Serial.begin(UART_BAUDS);Serial.print("MEASURE_TIME = ");Serial.print(1.0 * (SAMPLES_PER_LEVEL) * (LEVELS_PER_MEASURE) / (TIMER0_FREQ));Serial.println(" s");Serial.print("MEASURE_FREQUECY = ");Serial.print((TIMER0_FREQ) / (2.0 * SAMPLES_PER_LEVEL));Serial.println(" Hz");Serial.print("SAMPLE_FREQUENCY = ");Serial.print(TIMER0_FREQ);Serial.println(" Hz");Serial.print("SAMPLES_PER_LEVEL = ");Serial.print(SAMPLES_PER_LEVEL);Serial.println(" Samples");// Set up output reference signal pinpinMode(PIN_SIGNAL_OUT, OUTPUT);pinMode(PIN_DEBUG_OUT, OUTPUT);// Set up peripheralstimer0_setup();spi_setup();}void loop() {// Main Loopmeasure_init();while (1) {if (adc_measure_end == 1) {resistance_inphase = adc_acc_inphase;resistance_quadrature = adc_acc_quadrature;resistance_inphase *= (BOARD_CALIBRATION / (SAMPLES_PER_LEVEL * LEVELS_PER_MEASURE));resistance_quadrature *= (BOARD_CALIBRATION / (SAMPLES_PER_LEVEL * LEVELS_PER_MEASURE));Serial.print(resistance_inphase, 2);Serial.print("\tmOhm R \t");Serial.print(resistance_quadrature, 2);Serial.println("\tmOhm Zc");measure_init();}}}void spi_setup(void) {// Define SPI InterfacepinMode(PIN_SCK, OUTPUT);pinMode(PIN_SDI, INPUT);pinMode(PIN_CNV, OUTPUT);digitalWrite(PIN_SCK, LOW);digitalWrite(PIN_CNV, LOW);SPCR = (0 << 7) | // SPI interrupt enable(1 << 6) | // SPI enable(0 << 5) | // Data order(1 << 4) | // Master = 1 / Slave = 0(0 << 3) | // Clock polarity (1 = High when idle)(0 << 2) | // Clock Phase(0 << 0); // SCK Frequency 0 = fosc/2SPSR = (0 << 7) | // SPI interrupt flag(0 << 6) | // WCOL Write collision flag(1 << 0); // SPI Speed 2x}void measure_init(void) {cli();PORTD |= (1 << PIN_SIGNAL_OUT);delayMicroseconds(100);adc_acc_inphase = 0;adc_acc_quadrature = 0;level_state = 0;adc_samples = 0;adc_measure_end = 0;adc_enable = 1;sei();}void timer0_setup(void) {cli(); //stop interrupts//set timer0 interrupt at 2kHzTCCR0A = 0; // set entire TCCR2A register to 0TCCR0B = 0; // same for TCCR2BTCNT0 = 0; //initialize counter value to 0// set compare match registerOCR0A = TIMER0_PRESET;// turn on CTC modeTCCR0A |= (1 << WGM01);// Set CS01 and CS00 bits for 64 prescalerTCCR0B |= (1 << CS01) | (1 << CS00);// enable timer compare interruptTIMSK0 |= (1 << OCIE0A);sei(); //allow interrupts}ISR(TIMER0_COMPA_vect) {if (adc_enable) {if (adc_samples) {// ADC Start ConversionPORTB |= (1 << (PIN_CNV - 8)); // CoNVert enabledelayMicroseconds(3); // Wait for conversionPORTB &= ~(1 << (PIN_CNV - 8)); // CoNVert End// ADC Start AcquisitionSPDR = 0;while (!(SPSR & (1 << SPIF))); // Wait for transmission completeadc_value.hi = SPDR;SPDR = 0;while (!(SPSR & (1 << SPIF))); // Wait for transmission completeadc_value.lo = SPDR;// Read and accumulate old ADC value// Add measure to accumulatorif (level_state_old < SAMPLES_PER_LEVEL) {adc_acc_inphase -= adc_value.w;}else {adc_acc_inphase += adc_value.w;}if ((level_state_old >= 0.5 * SAMPLES_PER_LEVEL) && (level_state_old < 1.5 * SAMPLES_PER_LEVEL)) {adc_acc_quadrature -= adc_value.w;}else {adc_acc_quadrature += adc_value.w;}}// Update next statelevel_state_old = level_state;level_state++;if (level_state >= (2 * SAMPLES_PER_LEVEL)) {level_state = 0;}// Update output reference signalif (level_state < SAMPLES_PER_LEVEL) {PORTD |= (1 << PIN_SIGNAL_OUT);}else {PORTD &= ~(1 << PIN_SIGNAL_OUT);}// Take one measure after several samplesadc_samples++;if (adc_samples > SAMPLES_PER_LEVEL * LEVELS_PER_MEASURE) {adc_measure_end = 1;adc_enable = 0;}}}Measurements of a capacitor (1000uF, 16V):Code:MEASURE_TIME = 1.00 sMEASURE_FREQUECY = 744.00 HzSAMPLE_FREQUENCY = 8928 HzSAMPLES_PER_LEVEL = 6 Samples88.81mOhm R57.57mOhm Zc109.42mOhm R71.00mOhm Zc109.42mOhm R70.99mOhm Zc109.42mOhm R70.93mOhm Zc109.43mOhm R70.93mOhm Zc109.42mOhm R70.98mOhm Zc109.41mOhm R70.96mOhm Zc109.41mOhm R70.99mOhm Zc109.45mOhm R70.98mOhm Zc109.43mOhm R70.94mOhm Zc109.42mOhm R70.96mOhm Zc109.40mOhm R70.97mOhm Zc109.38mOhm R70.96mOhm Zc109.39mOhm R70.94mOhm Zc109.40mOhm R70.97mOhm Zc109.40mOhm R70.98mOhm Zc109.45mOhm R70.96mOhm Zc109.39mOhm R70.98mOhm Zc109.41mOhm R70.98mOhm Zc109.39mOhm R70.98mOhm Zc109.43mOhm R70.99mOhm Zc109.40mOhm R71.00mOhm Zc109.40mOhm R71.01mOhm Zc109.41mOhm R71.00mOhm Zc

author: Picuino
author_rank: Super Contributor
timestamp: May 08, 2024, 11:47:14 am
content: Results seem to improve only slightly.Not even one more decimal place of accuracy is improved.It seems that it is the base noise that defines the maximum accuracy or resolution that can be achieved.With a 10bit ADC I have been able to get up to 100000 points of resolution at full scale by adding 10000 samples (100 times the resolution of the ADC).But with a 16bit ADC the result is not improved because noise contaminates the signal.EDIT:I imagine it will be the same principle of Delta-Sigma ADCs, which only convert with 1 bit resolution, but take many samples that are later filtered to obtain up to 24 bits of final resolution.If anyone knows more about the subject maybe they can tell us more and find out if I am right.

author: Picuino
author_rank: Super Contributor
timestamp: May 08, 2024, 11:56:31 am
content: Another strange effect that has occurred is that the impedance measurement has dropped quite a bit.I actually adjusted the calibration of the board so that the resistance measurement gave the same value that previously, but I did not actually calibrate it against a known resistor. On the breadboard it is quite complicated because each connection hole adds approximately 60 to 100 milliohms and it is difficult to keep a low resistance measurement stable with that added error.

author: gf
author_rank: Super Contributor
timestamp: May 08, 2024, 12:15:38 pm
content: Quote from: Picuino on May 07, 2024, 03:30:09 pmA square wave stimulus in conjunction with a square wave LO works perfectly forohmicimpedances, i.e. if the DUT's response is not time or frequency dependent.But an impedance with a reactive component is a function of frequency, so you want to measure itat a particular frequency, and you don't want to get a result that is a weighted sum of the DUT's impedance at different frequencies (fundamental+harmonics). That's meaningless.In order to measure the DUT's response at a single frequency, you must prevent the harmonics from mixing down to DC, and this can be achieved if either the stimulus or the LO is asine wave(or both). A quadrature mixer with a (complex) sine wave NCO is not difficult to implement. Just sum upadc_valuej*cos(2*pi*f/sample_rate*j)and-adc_valuej*sin(2*pi*f/sample_rate*j)over the integration interval, in order to get the I and Q values. Since your sample rate is an integer multiple of your stimulus frequency f, you can use precalculated sin/cos tables. With (say) sample_rate=14*f, the sin/cos tables only need 14 entries for a full period.You also need to take care of aliasing when the ADC samples the signal. If the sample rate is (say) 14*f, then the harmonics at 13*f, 15*f, 29*f, 31*f, etc. will fold back to 1*f in the first Nyquist band and therefore interfere with the fundamental frequency you want to measure.

author: Picuino
author_rank: Super Contributor
timestamp: May 08, 2024, 12:50:10 pm
content: I was hoping to keep the microcontroller simple (Arduino) so that it would be easier to understand what I am doing and so that it can be easily replicated by others.On several occasions I have thought about swapping out the Arduino for a my dear 16-bit PIC that has a DAC to generate a sine output and has MAC instructions and 40-bit registers to do the fast calculations required.The PIC also has an ADC with higher sampling rate (200ksps) and higher resolution (12bits).The problem is that with another micro, the experiments become more confusing and difficult to replicate or modify by others. The complexity of the program will also increase.For now I wanted to perform simple tests that would give me information on how far you can go with something as simple as a 10bit, 10ksps ADC and a square excitation signal.A small improvement I have come up with is tointegrate the square signal so that it becomes a ramped signal. It is simple and avoids many problems of the square signal, while filtering out a lot of harmonics.The problem is to be able to do the necessary multiplications by the microcontroller. I would have to go to a bigger one.Another idea is to move to the Arduino UNO Revision 4, with 32-bit microcontroller. The problem is that controlling the registers directly is veeery complex and using the libraries is usually much slower.

author: gf
author_rank: Super Contributor
timestamp: May 08, 2024, 03:02:21 pm
content: Quote from: Picuino on May 08, 2024, 12:50:10 pmThe processor is an ATmega328 (avr5 in gcc), right? Then it should have 8x8and 16x16multiply instructions.In this example, I/Q mixing with a complex sine wave and accumulation is about 50 instructions per sample (compiled with AVR gcc).Don't know how many cycles this is, but I think the calculation is less than 10% of the sampling interval.[ The sin/cos tables could, of course, also be initialized statically in order to avoid the floating point stuff. ]

author: RoGeorge
author_rank: Super Contributor
timestamp: May 08, 2024, 03:39:00 pm
content: IIRC ATmega 328 has a hardware multiplier. I guess GCC would take advantage of that while multiplying integers, but I never tried if so. If not, use some inline ASM.Isn't the driving signal still square wave? If so, multiplier is +/-1, so addition.

author: gf
author_rank: Super Contributor
timestamp: May 08, 2024, 03:53:27 pm
content: Quote from: RoGeorge on May 08, 2024, 03:39:00 pmSorry, mistake. I have to withdraw my previous statement. It seems that it has only an 8x8 bit multiplier.

author: Kleinstein
author_rank: Super Contributor
timestamp: May 08, 2024, 04:35:32 pm
content: A good part of the noise is from the amplifier and maybe the drive signal strength (e.g. contacts or thermal effect on R_on). So the gain with the higher resolution ADC is limited. One point where one can gain is having a higher speed and this way an easier way to avoid aliasing (e.g. noise from higher frequencies folding back).Even with the HW multipier the AVR may have a hard time keeping up with the math. One can still do quite a bit, e.g. use only a 8 bit sine table - still quite good suppression on the harmonics. It could also help to use ASM to do math with mixed resolution, like 8 bit x 16 bit multiplication or directly a MAC with a 40 bit accumulator. The compilers usually don't support mixed resolution math and would do higher resolution math.Another way could be to first average in boxcar (time domain) mode and only do the sine wave multiplication one averaged data. This however needs more memory, that could also be limited with the AVR.It may really be worth looking for a more powerfull µC with the better ADC.One could still do square wave drive and sine multiplication. Sine wave drive and square wave demodulation adds a bit extra noise from the harminics, but is still not too bad.

author: Picuino
author_rank: Super Contributor
timestamp: May 08, 2024, 05:35:47 pm
content: Thanks gf, I had forgotten that the Atmega had 8x8 multiplier. Maybe that will be enough.Anyway I'm going to start testing with a triangular signal. Since the signal is simply a linear ramp, the multiplications can be done simply with shifts and additions. If for example I have to do x2, x4, x6, x8, all are shifts except x6 which is 2 shifts and a 16 bit integer sum. All very simple.The triangular signal is not as good as the sine signal, but it is very simple to generate and multiply and is much better than a square signal.EDIT:But first I want to generate the square signal and sample separately, with two different timers that are synchronized.That will take some time.

author: Kleinstein
author_rank: Super Contributor
timestamp: May 08, 2024, 06:38:46 pm
content: There is not need to do separate samples for the quadrature signal. The same data can be used for multiple analysis ways (e.g. quadrature and if needed also harmonics separate).For the quadrature signal ot could help if the ADC samples per period is a multiple of 4.With a fixed number of points per period (excitation signal generated by the µC and thus more a carrier - frequency amplifer and not a full lock-in) it is relatively easy to use a memory table for the weight factors (e.g. sine). For the table 8 bit values would be usually good enough and like with a DDS generator one can use symmertry to shorten the table.A point to consider is doing separate averaging in a boxcar integrator mode. This can help with looking at the amplifier and detect transisent effects like slew rate limits or ringing.

author: Picuino
author_rank: Super Contributor
timestamp: May 08, 2024, 07:16:59 pm
content: I do not know the boxcar averager.I will study how it works:https://en.wikipedia.org/wiki/Boxcar_averagerhttps://www.zhinst.com/sites/default/files/documents/2022-01/zi_whitepaper_principles_of_boxcar_averaging_latest.pdfhttps://www.analog.com/en/resources/analog-dialogue/studentzone/studentzone-february-2022.html

author: gnuarm
author_rank: Super Contributor
timestamp: May 08, 2024, 10:19:01 pm
content: A low-pass, boxcar filter is used because it is very, very simple to design. It is just a running average (think sum) of the previous N samples. You need a buffer of N samples to implement a FIFO. On each new sample, the oldest is subtracted from the sum and the new sample is added to the FIFO and the sum.The corner frequency of the filter is determined by the length of the FIFO. I don't recall the formula, but I'm sure your references have that info.

author: gnuarm
author_rank: Super Contributor
timestamp: May 08, 2024, 10:21:39 pm
content: Quote from: Picuino on May 08, 2024, 05:35:47 pmYou can cascade multiplies for longer word sizes. four 8x8 multiplies will give you a single 16x16 multiply.

author: Picuino
author_rank: Super Contributor
timestamp: May 09, 2024, 08:08:45 am
content: I have studied which number between 8 and 65 is more efficient to multiply to the sine function and produces less error when converting the result into integers, using 16 points for each wave and the result is 52.This allows me to multiply the result of the ADC (with values up to 1023) by another number that results in an integer. This makes the multiplication easier.Attached excel sheet.

author: gnuarm
author_rank: Super Contributor
timestamp: May 09, 2024, 09:43:28 am
content: Quote from: Picuino on May 09, 2024, 08:08:45 amI don't understand what you mean by this. Maybe I missed something earlier in this thread. I don't think I read it all. Why do you need to multiply the ADC data at all?QuotePerhaps you are not aware that the data from the ADC doesn't have a decimal point. You can call it integer, you can treat it as all fraction, or you can treat is as fixed point with some bits integer, and some bits fraction. You can think of this as multiplying by powers of two, which are done by moving where you consider the radix point to be.

author: Picuino
author_rank: Super Contributor
timestamp: May 09, 2024, 09:52:55 am
content: I am multiplying the ADC reading by a sine wave signal to obtain, at the end of one second, the component of the input signal that exactly matches the sine wave signal I am multiplying by. This is the lock-in amplifier principle.In a sense it is like doing the fourier transform for a single specific frequency that coincides with the excitation frequency of the experiment or Device Under Test.Until now I have been using an approximation to the sine wave that consists of taking the positive half-cycle always with value 1 and the negative half-cycle always with value -1. Now I am going to check if using values closer to the sine wave I can improve the results.

author: Picuino
author_rank: Super Contributor
timestamp: May 09, 2024, 10:56:04 am
content: I have made a small mistake by hand calculating the coefficients with the smallest error. After programming a small macro in Python I have discovered a coefficient (47) that generates still a minor error.Code:import mathdef main():# Compute dots and sinesdots_len = 16mult_range = list(range(8, 65+1))dots = [i for i in range(1, dots_len*2 + 1, 2)]sines = [math.sin(d*math.pi/dots_len) for d in dots]# Compute errorserrors = []for mul in mult_range:errors.append([mul, error_desvest(mul, sines)])errors = sorted(errors, key=lambda x: x)# Print resultsprint(["multiplier", "integer sin error"])for i in range(len(mult_range)):print(errors)def error_desvest(mul, sines):desvest = 0for s in sines:sin = abs(s * mul)err2 = (sin - int(sin + 0.5)) ** 2desvest += err2return desvest / len(sines)main()Code:['multiplier', 'integer sin error'][47, 0.01419672743708739][52, 0.022252210097423654][11, 0.024933494804284462][36, 0.025018241501426012][41, 0.025572615089517102][61, 0.03256542624870799][65, 0.044613485564976386][42, 0.04811188327041043][40, 0.052183114594890244][58, 0.05324220298131744][16, 0.05341992312335985][56, 0.05387694689333673][54, 0.056970410716863824][60, 0.058588617251868][45, 0.06007387714071964][43, 0.06363624882408199][48, 0.06435534252668071][31, 0.06643450640699458][46, 0.06809762717126693][63, 0.06879879471389944][51, 0.06963421974653153][12, 0.07024942179786148][57, 0.07227833125141978][50, 0.07319497061657543][25, 0.07589074013395344][20, 0.07677131511984413][59, 0.07746480845138684][49, 0.0784466514760001][30, 0.07876000969017867][53, 0.07965624974239474][9, 0.08076021505278731][14, 0.08076527563319745][10, 0.08394127698731896][29, 0.08459899905181871][34, 0.08498865252540211][35, 0.08626299054272088][55, 0.08635297939335956][18, 0.0898143494882416][13, 0.09085964458863942][37, 0.09594836673475386][22, 0.09973397921713785][32, 0.1027303821044008][38, 0.10488779361514296][15, 0.10564462086848388][17, 0.10571708031947587][24, 0.10675661923312965][62, 0.11139433312783675][19, 0.11435423006872109][21, 0.12387698993609458][23, 0.12427839606698096][27, 0.1263228020520691][39, 0.12734894449264433][64, 0.13282014919568044][8, 0.1338755781826966][44, 0.14189353072879649][26, 0.1496127645642278][33, 0.1573585798918617][28, 0.175894108639449]

author: gf
author_rank: Super Contributor
timestamp: May 09, 2024, 11:15:57 am
content: Quote from: Picuino on May 09, 2024, 10:56:04 amIn the frequency domain I get the following THD and SFDR as a function of the integer scaling factor (see plot).I'd use 117 or 120, since this is the largest number which fits into a signed byte.

author: Picuino
author_rank: Super Contributor
timestamp: May 09, 2024, 11:16:31 am
content: With 18 samples/period and multiplier=34 it is also a good approximation, better than with 16 samples/period.Code:import mathdef main():for dots_len in range(12, 40+1, 2):# Compute dots and sinesmult_range = list(range(8, 65+1))dots = [i for i in range(1, dots_len*2 + 1, 2)]sines = [math.sin(d*math.pi/dots_len) for d in dots]# Compute errorserrors = compute_errors(mult_range, sines)# Print resultsprint(f"Dots_len={dots_len} \tBest multiplier={errors} \terror={int(1000*errors)}")def compute_errors(mult_range, sines):errors = []for mul in mult_range:errors.append([mul, error_desvest(mul, sines)])errors = sorted(errors, key=lambda x: x)return errorsdef error_desvest(mul, sines):desvest = 0for s in sines:sin = abs(s * mul)err2 = (sin - int(sin + 0.5)) ** 2desvest += err2return desvest / len(sines)main()EDIT:I change the image, which had an error in the multiplier.

author: Picuino
author_rank: Super Contributor
timestamp: May 09, 2024, 11:19:31 am
content: Quote from: gf on May 09, 2024, 11:15:57 amHow do you calculate points and distortion?

author: gf
author_rank: Super Contributor
timestamp: May 09, 2024, 11:58:56 am
content: Quote from: Picuino on May 09, 2024, 11:19:31 amSee below. Btw, with samples per period (nsamplesin the Octave script below) I mean the number of samples for afull(0...2*pi) period of the waveform, including the point at 0, but excluding the point at 2*pi, since the latter is considered the first point of the next period.Code:nsamples = 18;x = repmat(sin([0:nsamples-1]/nsamples*2*pi)',1,127);y = zeros(nsamples,127);for i=1:127% quantize to 2*i+1 levelsy(:,i) = floor(0.5+x(:,i)*i)/i;endY = zeros(nsamples,127);for i=1:127Y(:,i) = fft(y(:,i))/nsamples;endP = abs(Y).**2;Pfund = P(2,:);harmidx = [ 2:nsamples/2 ] + 1;Pharm = sum(P(harmidx,:));Pspur = max(P(harmidx,:));plot(10*log10(Pharm./Pfund),";THD;");hold onplot(10*log10(Pspur./Pfund),";SFDR;");title(sprintf("%d samples/period",nsamples))grid onhold offylabel("dB")xlim([20 127])ylim([-65 -30])EDIT:QuoteWith 18 samples/period, I rather get a (very good) minimum with a multiplicator of 126. Plots attached.EDIT:I consider the spectral purity in the frequency domain more important than the MSE or maximum approximation error in the time domain.

author: Picuino
author_rank: Super Contributor
timestamp: May 09, 2024, 01:30:09 pm
content: Yes, much better. The problem is that the multiplication should not be greater than 65535, which is the maximum of an integer, to simplify the arithmetic. That's why I was limiting the multiplier to a maximum value of 64.Another problem that I have now realized is that, in order to best calculate the quadrature signal, it is necessary to have a number of samples per period that are a multiple of 4.Back to the 16 samples.Anyway, these calculations and macros will come in handy if later I try to make the amplifier with a more powerful and faster micro. Thank you.

author: Picuino
author_rank: Super Contributor
timestamp: May 09, 2024, 03:33:35 pm
content: First configuration achieved.Two different timers synchronized together give the necessary signals to:1. generate a 625Hz square wave output (yellow).2. Generate 16 interrupts each output wave to initiate ADC conversion (cyan).

author: Picuino
author_rank: Super Contributor
timestamp: May 09, 2024, 08:50:15 pm
content: I already have a first version, probably with some bugs.The excitation signal is still square, but the voltage measured by the ADC is averaged with an approximate sine wave.The measurements are giving more error than with the previous version. I will have to study it better.Program:Code:/*Version 4.0 (09/05/2024)Copyright 2024 PicuinoPermission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software"), to dealin the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sellcopies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be includedin all copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISINGFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGSIN THE SOFTWARE.*/#define PIN_SIGNAL_OUT 3#define PIN_DEBUG_OUT 5#define PIN_ANALOG A6#define PIN_ANALOG_MUX 6#define PIN_SCK 13#define PIN_SDI 12#define PIN_CNV 10#define TIMER2_PERIOD 220#define CLK_BOARD 16000000#define UART_BAUDS 115200#define MEASURE_TIME 1#define TIMER0_PERIOD (TIMER2_PERIOD - 1)#define TIMER0_FREQ (CLK_BOARD / ((TIMER0_PERIOD + 1) * 8))#define SAMPLES_PER_MEASURE (16 * (long) ((MEASURE_TIME) * (TIMER0_FREQ) / 16))const float BOARD_CALIBRATION = 0.3474 / (SAMPLES_PER_MEASURE); // Converts measure to milliohmsconst int SIN_INTEGER[] = {9, 26, 39, 46, 46, 39, 26, 9 };volatile long adc_acc_inphase;volatile long adc_acc_quadrature;volatile unsigned int adc_samples;volatile unsigned int adc_value;volatile unsigned char adc_measure_end;volatile unsigned char level_state;volatile unsigned char level_state_old;volatile unsigned char timer0_first_isr;float resistance_inphase;float resistance_quadrature;void setup() {Serial.begin(UART_BAUDS);Serial.println();Serial.print("SAMPLE_FREQUENCY = ");Serial.print(1.0 * TIMER0_FREQ);Serial.println(" Hz");Serial.print("MEASURE_FREQUENCY = ");Serial.print(TIMER0_FREQ/16.0);Serial.println(" Hz");Serial.print("SAMPLE_TIME = ");Serial.print(1.0 * SAMPLES_PER_MEASURE / TIMER0_FREQ);Serial.println(" s");// Set up output reference signal pinpinMode(PIN_SIGNAL_OUT, OUTPUT);pinMode(PIN_DEBUG_OUT, OUTPUT);// Set up peripheralstimer0_setup();timer2_setup();timer_synchronize();adc_setup();measure_init();}void loop() {// Main Loopwhile (1) {if (adc_measure_end == 1) {resistance_inphase = adc_acc_inphase;resistance_quadrature = -adc_acc_quadrature;resistance_inphase *= BOARD_CALIBRATION;resistance_quadrature *= BOARD_CALIBRATION;Serial.print(resistance_inphase, 2);Serial.print("\tmOhm R \t");if (resistance_quadrature > 0) {Serial.print(resistance_quadrature, 2);Serial.print("\tmOhm Z_C \t");Serial.print(1000000000.0 / (resistance_quadrature * (TIMER0_FREQ / 16.0) * 2.0 * 3.1415927));Serial.println("\tuFarads");}else {Serial.print(-resistance_quadrature, 2);Serial.println("\tmOhm Z_L");}measure_init();}}}void adc_setup(void) {analogRead(PIN_ANALOG);cli(); // Stop interruptsADMUX = (1 << 6) |(0 << ADLAR) |(PIN_ANALOG_MUX << 0);ADCSRA = (1 << ADEN) |(0 << ADSC) |(0 << ADATE) |(0 << ADIE) |(0b111); // Division factorADCSRB = 0x00;sei(); // Allow interrupts}void measure_init(void) {delayMicroseconds(1000);cli();adc_acc_inphase = 0;adc_acc_quadrature = 0;level_state = 7;level_state_old = 0;adc_samples = 0;adc_measure_end = 0;timer0_first_isr = 1;ADCW = 0;while ((PIND & (1 << PIN_SIGNAL_OUT)) != 0);while ((PIND & (1 << PIN_SIGNAL_OUT)) == 0);TIFR0 = 0;sei();}void timer0_setup(void) {cli(); // Stop interrupts// set compare match registerTCCR0A = (0 << 6) | // OOM0A. 0=OC0A disconnected. 1=Toggle OC0A on compare match (p.84)(0 << 4) | // COM0B. 0=OC0B disconnected. 1=Toggle OC0B on compare match (p.85)(2 << 0); // WGM0. PWM mode. 1=phase correct 2=CTC (p.86)TCCR0B = (0 << 7) | // FOC0A.(0 << 6) | // FOC0B.(0 << 3) | // WGM02.(2 << 0); // CLOCK source.OCR0A = TIMER0_PERIOD;OCR0B = TIMER0_PERIOD / 2;TIMSK0 = (0 << 2) | // OCIE0B. Match B Interrupt Enable(1 << 1) | // OCIE0A. Match A Interrupt Enable(0 << 0); // TOIE0. Overflow Interrupt EnableTIFR0 = 0;TCNT0 = 0; // Initialize Timer0 countersei(); // Allow interrupts}void timer2_setup(void) {cli(); // Stop interruptsTCCR2A = (1 << 6) | // OOM2A. 0=OC2A disconnected. 1=Toggle OC2A on compare match (p.128)(2 << 4) | // COM2B. 2=Clear OC2B on compare match (p.129)(1 << 0); // WGM2. PWM mode. 1=phase correct (p.130)TCCR2B = (0 << 7) | // FOC2A.(0 << 6) | // FOC2B.(1 << 3) | // WGM22.(4 << 0); // CLOCK source.OCR2A = TIMER2_PERIOD;OCR2B = TIMER2_PERIOD / 2;TIMSK2 = (0 << 2) | // OCIE2B. Match B Interrupt Enable(0 << 1) | // OCIE2A. Match A Interrupt Enable(0 << 0); // TOIE2. Overflow Interrupt EnableTIFR2 = 0;TCNT2 = 0; // Initialize Timer2 countersei(); // Allow interrupts}void timer_synchronize(void) {cli(); // Stop interruptswhile ((PIND & (1 << PIN_SIGNAL_OUT)) != 0);while ((PIND & (1 << PIN_SIGNAL_OUT)) == 0);GTCCR = (1 << TSM) | (1 << PSRASY) | (1 << PSRSYNC); // halt all timersTCNT0 = TIMER0_PERIOD / 2 + 4; // Initialize Timer0 counterGTCCR = 0; // release all timerssei(); // Allow interrupts}// Timer0 interrupt handlerISR(TIMER0_COMPA_vect) {if (adc_measure_end == 0 && timer0_first_isr == 0) {// ADC Start ConversionADCSRA |= (1 << ADSC);// Read last conversionadc_value = ADCW;// Accumulate valuesswitch (level_state_old) {case 7:case 0:adc_acc_inphase += adc_value * SIN_INTEGER;break;case 6:case 1:adc_acc_inphase += adc_value * SIN_INTEGER;break;case 5:case 2:adc_acc_inphase += adc_value * SIN_INTEGER;break;case 4:case 3:adc_acc_inphase += adc_value * SIN_INTEGER;break;case 15:case 8:adc_acc_inphase -= adc_value * SIN_INTEGER;break;case 14:case 9:adc_acc_inphase -= adc_value * SIN_INTEGER;break;case 13:case 10:adc_acc_inphase -= adc_value * SIN_INTEGER;break;case 12:case 11:adc_acc_inphase -= adc_value * SIN_INTEGER;break;}switch ((level_state_old + 4) & 0x0F) {case 7:case 0:adc_acc_quadrature += adc_value * SIN_INTEGER;break;case 6:case 1:adc_acc_quadrature += adc_value * SIN_INTEGER;break;case 5:case 2:adc_acc_quadrature += adc_value * SIN_INTEGER;break;case 4:case 3:adc_acc_quadrature += adc_value * SIN_INTEGER;break;case 15:case 8:adc_acc_quadrature -= adc_value * SIN_INTEGER;break;case 14:case 9:adc_acc_quadrature -= adc_value * SIN_INTEGER;break;case 13:case 10:adc_acc_quadrature -= adc_value * SIN_INTEGER;break;case 12:case 11:adc_acc_quadrature -= adc_value * SIN_INTEGER;break;}// Update next statelevel_state_old = level_state;level_state++;level_state &= 0x0F;adc_samples++;if (adc_samples > SAMPLES_PER_MEASURE) {adc_measure_end = 1;}}timer0_first_isr = 0;}// Timer2 interrupt handlerISR(TIMER2_COMPA_vect) {}void debug_pin_pulse(void) {PORTD |= (1 << PIN_DEBUG_OUT);delayMicroseconds(4);PORTD &= ~(1 << PIN_DEBUG_OUT);}Output measuring a capacitor of 1000uF and 16V:Code:SAMPLE_FREQUENCY = 9090.00 HzMEASURE_FREQUENCY = 568.13 HzSAMPLE_TIME = 1.00 s157.19mOhm R252.28mOhm Z_C1110.43uFarads192.02mOhm R313.45mOhm Z_C893.73uFarads192.88mOhm R311.39mOhm Z_C899.64uFarads193.19mOhm R311.87mOhm Z_C898.26uFarads193.11mOhm R312.13mOhm Z_C897.52uFarads191.35mOhm R312.51mOhm Z_C896.42uFarads190.47mOhm R312.65mOhm Z_C896.02uFarads194.14mOhm R312.17mOhm Z_C897.39uFarads194.67mOhm R312.46mOhm Z_C896.55uFarads193.80mOhm R311.56mOhm Z_C899.15uFarads191.92mOhm R312.49mOhm Z_C896.48uFarads194.46mOhm R311.44mOhm Z_C899.49uFarads192.97mOhm R312.05mOhm Z_C897.73uFarads192.89mOhm R310.00mOhm Z_C903.69uFarads193.21mOhm R311.79mOhm Z_C898.48uFarads195.52mOhm R311.11mOhm Z_C900.46uFarads193.99mOhm R311.31mOhm Z_C899.89uFarads193.34mOhm R313.72mOhm Z_C892.96uFarads192.43mOhm R312.59mOhm Z_C896.19uFarads194.31mOhm R311.95mOhm Z_C898.03uFarads193.04mOhm R311.95mOhm Z_C898.04uFarads192.46mOhm R311.98mOhm Z_C897.93uFarads192.70mOhm R312.91mOhm Z_C895.28uFarads192.61mOhm R311.90mOhm Z_C898.19uFarads190.74mOhm R312.19mOhm Z_C897.33uFarads191.34mOhm R312.57mOhm Z_C896.26uFarads191.62mOhm R313.41mOhm Z_C893.85uFarads192.28mOhm R313.01mOhm Z_C894.98uFarads193.83mOhm R311.46mOhm Z_C899.45uFarads193.31mOhm R312.13mOhm Z_C897.51uFarads194.30mOhm R310.69mOhm Z_C901.66uFarads193.66mOhm R310.44mOhm Z_C902.39uFarads193.57mOhm R312.56mOhm Z_C896.28uFarads

author: Picuino
author_rank: Super Contributor
timestamp: May 09, 2024, 08:53:47 pm
content: The measurements are calibrated (approximately by measuring the full scale resistor)Measurements of a short cable:Code:SAMPLE_FREQUENCY = 9090.00 HzMEASURE_FREQUENCY = 568.13 HzSAMPLE_TIME = 1.00 s435.02mOhm R0.10mOhm Z_L434.25mOhm R0.15mOhm Z_C1813534.62uFarads433.48mOhm R0.31mOhm Z_L435.25mOhm R0.36mOhm Z_C787925.37uFarads432.50mOhm R1.28mOhm Z_L432.45mOhm R0.28mOhm Z_C989267.43uFarads433.10mOhm R0.09mOhm Z_L435.26mOhm R0.11mOhm Z_L433.27mOhm R1.90mOhm Z_C147481.29uFarads434.63mOhm R2.31mOhm Z_C121495.60uFarads434.15mOhm R0.37mOhm Z_L432.38mOhm R0.65mOhm Z_L437.47mOhm R2.32mOhm Z_C120932.24uFarads434.59mOhm R0.40mOhm Z_L434.68mOhm R1.04mOhm Z_L432.84mOhm R0.78mOhm Z_C360973.96uFarads431.79mOhm R1.26mOhm Z_L431.90mOhm R0.33mOhm Z_L434.69mOhm R2.79mOhm Z_C100255.73uFarads434.34mOhm R0.31mOhm Z_L435.86mOhm R0.51mOhm Z_C549197.68uFarads432.65mOhm R0.18mOhm Z_C1518544.00uFarads434.65mOhm R1.28mOhm Z_C218317.84uFarads433.30mOhm R1.06mOhm Z_L434.93mOhm R1.63mOhm Z_C171490.93uFarads434.82mOhm R0.21mOhm Z_C1323788.62uFarads434.83mOhm R0.37mOhm Z_C761006.56uFarads436.03mOhm R0.84mOhm Z_L431.63mOhm R0.05mOhm Z_L433.32mOhm R0.76mOhm Z_L434.65mOhm R0.39mOhm Z_L432.88mOhm R1.15mOhm Z_L432.27mOhm R0.55mOhm Z_L

author: Picuino
author_rank: Super Contributor
timestamp: May 09, 2024, 08:57:45 pm
content: The calculations have turned out to be much faster than I expected. Upgrading the two accumulators takes about 5us. Considering that the measurement cycle is greater than 100us, there is still plenty of time left over.Extract of the program:Code:const int SIN_INTEGER[] = {9, 26, 39, 46, 46, 39, 26, 9 };adc_acc_inphase += adc_value * SIN_INTEGER; // 2.5usadc_acc_quadrature += adc_value * SIN_INTEGER; // 2.5 us

author: gf
author_rank: Super Contributor
timestamp: May 09, 2024, 09:21:40 pm
content: Quote from: Picuino on May 09, 2024, 01:30:09 pmThe number should be even, for perfect rejection of DC offset, but it does not need to be a multiple of 4. If it is not a multiple of 4, then you need separate cos and sin tables. If it is a multiple of 4, you can fetch the cos and sin values from the same lookup table.I had forgotten something regarding the multiplicator optimization: The optimization should not be limited to an ordinary sine wave, but it should consider a complex sine wave (i.e. both, cos and sin components together). Here is an update.Code:nsamples = 16;x = repmat(exp(-1j*[0:nsamples-1]/nsamples*2*pi)',1,127);y = zeros(nsamples,127);for i=1:127% quantize to 2*i+1 levelsy(:,i) = floor(0.5+0.5i+x(:,i)*i)/i;endY = zeros(nsamples,127);for i=1:127Y(:,i) = fft(y(:,i))/nsamples;endP = abs(Y).**2;Pfund = P(2,:);harmidx = [ 1 3:nsamples ];Pharm = sum(P(harmidx,:));Pspur = max(P(harmidx,:));plot(10*log10(Pharm./Pfund),";THD;");hold onplot(10*log10(Pspur./Pfund),";SFDR;");title(sprintf("%d samples/period",nsamples))grid onhold offylabel("dB")xlim([20 127])ylim([-65 -30])

author: gnuarm
author_rank: Super Contributor
timestamp: May 09, 2024, 11:02:21 pm
content: Quote from: Picuino on May 09, 2024, 01:30:09 pmI don't know what "16 samples" means.QuoteThis is why I typically don't use MCUs. I much prefer fpgas. They provide much more powerful resources and typically don't need to be upgraded, unless you are doing crazy complicated stuff. It takes a bit of effort to learn to use them effectively, but in the end, they beat MCUs at nearly everything.

author: gf
author_rank: Super Contributor
timestamp: May 10, 2024, 10:34:08 am
content: Quote from: Picuino on May 09, 2024, 08:50:15 pmWhy do you makeadc_valueglobal and volatile? It is only used in the ISR. Just make it a local variable in the ISR, and the compiler can renounce several unnecessary load/store instructions and keep adc_value in a register.Your SIN_INTEGER table is declared int[]. This leads to a 16x16 -> 16 multiplication. Why don't you declare it int8_t[] if you want the compiler to generate cheaper 16x8 -> 16 multiplication?There is also something wrong with the types in the multiplication. Adc_value is declared unsigned, and the SIN_INTEGER table entries are (signed) int. In this case, C's type conversion rules lead to anunsignedmultiplication, but you want it to be signed. Therefore, declare adc_value with type int16_t (i.e signed).I don't know if the 10-bit value in ADCW is MSB or LSB aligned, and I also can't see how ADCW is declared. If it is unsigned, then either doCode:int16_t adc_value = (ADCW >> 6) - 512;orCode:int16_t adc_value = ADCW - 512;depending on whether ADCW returns a 0...65535 value (with the lower 6 bits zero) or a 0...1023 value.If ADCW is signed, then either doCode:int16_t adc_value = ADCW >> 6;orCode:int16_t adc_value = ADCW;depending on whether ADCW returns a -32768...32767 value (with the lower 6 bits zero) or a -512...511 value.Quote from: Picuino on May 09, 2024, 08:57:45 pmI would not use switch statements with a case for each of the 16 samples per period.If you change samples per period, you need to change the switch statement as well.And the two switch statements also cost additional cycles.I would rather spend 12 bytes more RAM and doCode:// 1+1/4 periodsconst int8_t SIN_INTEGER[16+16/4] = {9,26,39,46,46,39,26,9,-9,-26,-39,-46,-46,-39,-26,-9,9,26,39,46};Code:ISR(TIMER0_COMPA_vect){...int16_t adc_value = ...; // see above// use a temp variable to prevent 2x load, since level_state_old was declared volatileuint8_t idx = level_state_old;adc_acc_inphase += adc_value * SIN_INTEGER;adc_acc_quadrature += adc_value * SIN_INTEGER[idx+SAMPLES_PER_PERIOD/4];// note, that's the point which requires SAMPLES_PER_PERIOD % 4 == 0...}QuoteCouldn't you increase C5 significantly, in order to get at least a little anti-aliasing filtering in front of the ADC?I mean something in the order of C5 = 1 / (2 * PI * f * R8), where f is the excitation signal frequency.[ Sure, that introduces a phase shift, but it can be calibrated out. ]One more suggestion: For portable code, don't rely on the sizes of short, int, unsigned, long, long long, or the signedness of char, which may be different on different platforms/compilers. Better include stdint.h and use int8_t, uint8_t, int16_t, uint16_t, int32_t, etc. in order to declare variables with a well-defined size and signedness.

author: gf
author_rank: Super Contributor
timestamp: May 10, 2024, 12:17:37 pm
content: Quote from: gf on May 09, 2024, 09:21:40 pmAccording to the updated optimization, the best multiplier < 64 seems to be 55 (for 16 samples per period).

author: Picuino
author_rank: Super Contributor
timestamp: May 10, 2024, 01:55:06 pm
content: I solved a problem on the breadboard with the connection between the amplifier and the ADC, which produced a lot of noise.I have also changed the data types and tested that they work correctly for larger values of the coefficients. The only problem is that now the accumulator instructions take 10us to execute. As it is still a short time I will leave it like that.Program:Code:/*Version 4.1 (10/05/2024)Copyright 2024 PicuinoPermission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software"), to dealin the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sellcopies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be includedin all copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISINGFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGSIN THE SOFTWARE.*/#include <stdint.h>#define PIN_SIGNAL_OUT 3#define PIN_DEBUG_OUT 5#define PIN_ANALOG A6#define PIN_ANALOG_MUX 6#define PIN_SCK 13#define PIN_SDI 12#define PIN_CNV 10#define TIMER2_PERIOD 220#define CLK_BOARD 16000000#define UART_BAUDS 115200#define MEASURE_TIME 1#define TIMER0_PERIOD (TIMER2_PERIOD - 1)#define TIMER0_FREQ (CLK_BOARD / ((TIMER0_PERIOD + 1) * 8))#define SAMPLES_PER_MEASURE (16 * (long) ((MEASURE_TIME) * (TIMER0_FREQ) / 16))const int16_t SIN_INTEGER[] = {9, 26, 39, 46, 46, 39, 26, 9, -9, -26, -39, -46, -46, -39, -26, -9 };const int16_t COS_INTEGER[] = {46, 39, 26, 9, -9, -26, -39, -46, -46, -39, -26, -9, 9, 26, 39, 46 };const float BOARD_CALIBRATION = 0.3150 / (SAMPLES_PER_MEASURE); // Converts measure to milliohmsvolatile int32_t adc_acc_inphase;volatile int32_t adc_acc_quadrature;volatile int16_t adc_samples;volatile uint8_t adc_measure_end;volatile uint8_t level_state;volatile uint8_t level_state_old;float resistance_inphase;float resistance_quadrature;void setup() {Serial.begin(UART_BAUDS);Serial.println();Serial.print("SAMPLE_FREQUENCY = ");Serial.print(1.0 * TIMER0_FREQ);Serial.println(" Hz");Serial.print("MEASURE_FREQUENCY = ");Serial.print(TIMER0_FREQ / 16.0);Serial.println(" Hz");Serial.print("SAMPLE_TIME = ");Serial.print(1.0 * SAMPLES_PER_MEASURE / TIMER0_FREQ);Serial.println(" s");// Set up output reference signal pinpinMode(PIN_SIGNAL_OUT, OUTPUT);pinMode(PIN_DEBUG_OUT, OUTPUT);// Set up peripheralstimer0_setup();timer2_setup();timer_synchronize();adc_setup();measure_init();while(adc_measure_end == 0);measure_init();}void loop() {// Main Loopwhile (1) {if (adc_measure_end == 1) {resistance_inphase = -adc_acc_inphase;resistance_quadrature = -adc_acc_quadrature;resistance_inphase *= BOARD_CALIBRATION;resistance_quadrature *= BOARD_CALIBRATION;print_values(resistance_inphase, resistance_quadrature);measure_init();}}}void print_values(float resistance_inphase, float resistance_quadrature) {Serial.print(resistance_inphase, 2);Serial.print("\tmOhm R \t");if (resistance_quadrature > 0) {Serial.print(resistance_quadrature, 2);Serial.print("\tmOhm Z_L \t");if (resistance_quadrature > 5.0) {Serial.print(-resistance_quadrature * 1000.0 / ((TIMER0_FREQ / 16.0) * 2.0 * 3.1415927));Serial.println("\tuHenrys");}else {Serial.println();}}else {Serial.print(-resistance_quadrature, 2);Serial.print("\tmOhm Z_C \t");if (resistance_quadrature < -5.0) {Serial.print(1000000000.0 / (-resistance_quadrature * (TIMER0_FREQ / 16.0) * 2.0 * 3.1415927));Serial.println("\tuFarads");}else {Serial.println();}}}void adc_setup(void) {analogRead(PIN_ANALOG);cli(); // Stop interruptsADMUX = (1 << 6) |(0 << ADLAR) |(PIN_ANALOG_MUX << 0);ADCSRA = (1 << ADEN) |(0 << ADSC) |(0 << ADATE) |(0 << ADIE) |(0b111); // Division factorADCSRB = 0x00;sei(); // Allow interrupts}void measure_init(void) {delayMicroseconds(1000);cli();adc_acc_inphase = 0;adc_acc_quadrature = 0;level_state = 0;level_state_old = 0;adc_samples = 0;ADCW = 0;sei();while ((PIND & (1 << PIN_SIGNAL_OUT)) != 0);while ((PIND & (1 << PIN_SIGNAL_OUT)) == 0);adc_measure_end = 0;}void timer0_setup(void) {cli(); // Stop interrupts// set compare match registerTCCR0A = (0 << 6) | // OOM0A. 0=OC0A disconnected. 1=Toggle OC0A on compare match (p.84)(0 << 4) | // COM0B. 0=OC0B disconnected. 1=Toggle OC0B on compare match (p.85)(2 << 0); // WGM0. PWM mode. 1=phase correct 2=CTC (p.86)TCCR0B = (0 << 7) | // FOC0A.(0 << 6) | // FOC0B.(0 << 3) | // WGM02.(2 << 0); // CLOCK source.OCR0A = TIMER0_PERIOD;OCR0B = TIMER0_PERIOD / 2;TIMSK0 = (0 << 2) | // OCIE0B. Match B Interrupt Enable(1 << 1) | // OCIE0A. Match A Interrupt Enable(0 << 0); // TOIE0. Overflow Interrupt EnableTIFR0 = 0;TCNT0 = 0; // Initialize Timer0 countersei(); // Allow interrupts}void timer2_setup(void) {cli(); // Stop interruptsTCCR2A = (1 << 6) | // OOM2A. 0=OC2A disconnected. 1=Toggle OC2A on compare match (p.128)(2 << 4) | // COM2B. 2=Clear OC2B on compare match (p.129)(1 << 0); // WGM2. PWM mode. 1=phase correct (p.130)TCCR2B = (0 << 7) | // FOC2A.(0 << 6) | // FOC2B.(1 << 3) | // WGM22.(4 << 0); // CLOCK source.OCR2A = TIMER2_PERIOD;OCR2B = TIMER2_PERIOD / 2;TIMSK2 = (0 << 2) | // OCIE2B. Match B Interrupt Enable(0 << 1) | // OCIE2A. Match A Interrupt Enable(0 << 0); // TOIE2. Overflow Interrupt EnableTIFR2 = 0;TCNT2 = 0; // Initialize Timer2 countersei(); // Allow interrupts}void timer_synchronize(void) {cli(); // Stop interruptswhile ((PIND & (1 << PIN_SIGNAL_OUT)) != 0);while ((PIND & (1 << PIN_SIGNAL_OUT)) == 0);GTCCR = (1 << TSM) | (1 << PSRASY) | (1 << PSRSYNC); // halt all timersTCNT0 = TIMER0_PERIOD / 2 + 4; // Initialize Timer0 counterGTCCR = 0; // release all timerssei(); // Allow interrupts}// Timer0 interrupt handlerISR(TIMER0_COMPA_vect) {int16_t adc_value;if (adc_measure_end == 0) {debug_pin_pulse();// ADC Start ConversionADCSRA |= (1 << ADSC);// Read last conversionadc_value = ADCW;// Accumulate values (10us)adc_acc_inphase += (int32_t) adc_value * SIN_INTEGER;adc_acc_quadrature += (int32_t) adc_value * COS_INTEGER;// Update next statelevel_state_old = level_state;level_state++;level_state &= 0x0F;adc_samples++;if (adc_samples > SAMPLES_PER_MEASURE) {adc_measure_end = 1;}}}// Timer2 interrupt handlerISR(TIMER2_COMPA_vect) {}void debug_pin_pulse(void) {PORTD |= (1 << PIN_DEBUG_OUT);delayMicroseconds(4);PORTD &= ~(1 << PIN_DEBUG_OUT);}Output measuring a cable:Code:SAMPLE_FREQUENCY = 9090.00 HzMEASURE_FREQUENCY = 568.13 HzSAMPLE_TIME = 1.00 s325.35mOhm R1.03mOhm Z_C325.32mOhm R0.90mOhm Z_C325.17mOhm R0.84mOhm Z_C325.30mOhm R1.01mOhm Z_C325.15mOhm R0.89mOhm Z_C325.19mOhm R0.91mOhm Z_C325.02mOhm R0.96mOhm Z_C324.94mOhm R0.84mOhm Z_C324.90mOhm R1.00mOhm Z_C324.90mOhm R0.90mOhm Z_C324.84mOhm R0.98mOhm Z_C324.95mOhm R0.96mOhm Z_C324.75mOhm R0.84mOhm Z_C324.87mOhm R0.89mOhm Z_C324.80mOhm R0.90mOhm Z_C324.60mOhm R0.99mOhm Z_C324.45mOhm R0.95mOhm Z_C324.60mOhm R1.02mOhm Z_C324.42mOhm R0.93mOhm Z_C324.38mOhm R0.93mOhm Z_C324.16mOhm R1.07mOhm Z_C324.36mOhm R0.81mOhm Z_C324.38mOhm R1.05mOhm Z_C324.14mOhm R0.84mOhm Z_C324.10mOhm R0.92mOhm Z_C324.07mOhm R0.97mOhm Z_C324.05mOhm R0.91mOhm Z_C324.16mOhm R0.95mOhm Z_C324.02mOhm R0.90mOhm Z_C324.10mOhm R0.92mOhm Z_C324.08mOhm R0.95mOhm Z_C324.11mOhm R0.90mOhm Z_C323.94mOhm R0.97mOhm Z_C323.96mOhm R1.01mOhm Z_C323.92mOhm R0.95mOhm Z_C323.89mOhm R0.89mOhm Z_C323.90mOhm R0.87mOhm Z_C323.93mOhm R0.85mOhm Z_C323.93mOhm R0.95mOhm Z_C323.85mOhm R0.94mOhm Z_C323.89mOhm R0.91mOhm Z_C323.94mOhm R0.86mOhm Z_C323.75mOhm R0.87mOhm Z_C323.76mOhm R0.85mOhm Z_C323.92mOhm R1.00mOhm Z_C323.75mOhm R0.92mOhm Z_C323.95mOhm R1.03mOhm Z_C323.86mOhm R0.81mOhm Z_C323.77mOhm R0.93mOhm Z_C323.73mOhm R0.96mOhm Z_C323.64mOhm R1.09mOhm Z_C323.74mOhm R0.85mOhm Z_C323.66mOhm R0.97mOhm Z_C323.76mOhm R0.96mOhm Z_C323.79mOhm R0.99mOhm Z_C323.74mOhm R0.88mOhm Z_C323.73mOhm R0.92mOhm Z_C323.48mOhm R0.92mOhm Z_C323.74mOhm R0.93mOhm Z_C323.71mOhm R0.93mOhm Z_C323.69mOhm R0.92mOhm Z_C323.49mOhm R0.95mOhm Z_C323.66mOhm R0.86mOhm Z_C323.54mOhm R0.97mOhm Z_C323.58mOhm R0.97mOhm Z_C323.45mOhm R0.99mOhm Z_C323.52mOhm R1.00mOhm Z_C323.52mOhm R1.06mOhm Z_C323.56mOhm R1.02mOhm Z_C323.40mOhm R1.01mOhm Z_C323.22mOhm R0.84mOhm Z_C322.69mOhm R1.00mOhm Z_C323.39mOhm R0.91mOhm Z_C323.92mOhm R0.89mOhm Z_C323.84mOhm R0.82mOhm Z_C323.77mOhm R0.85mOhm Z_C323.81mOhm R0.80mOhm Z_C323.69mOhm R0.87mOhm Z_C323.70mOhm R0.71mOhm Z_C323.66mOhm R0.95mOhm Z_C323.72mOhm R1.00mOhm Z_C323.49mOhm R0.86mOhm Z_C323.70mOhm R0.99mOhm Z_C323.64mOhm R1.03mOhm Z_C323.74mOhm R0.97mOhm Z_C323.68mOhm R0.86mOhm Z_C323.74mOhm R0.91mOhm Z_C323.64mOhm R0.97mOhm Z_C323.64mOhm R0.91mOhm Z_C323.57mOhm R0.88mOhm Z_C323.42mOhm R0.86mOhm Z_C323.63mOhm R0.79mOhm Z_C323.61mOhm R0.93mOhm Z_C323.64mOhm R0.90mOhm Z_C323.54mOhm R0.85mOhm Z_C323.73mOhm R0.91mOhm Z_C323.51mOhm R0.92mOhm Z_C323.67mOhm R0.77mOhm Z_C323.65mOhm R0.93mOhm Z_C323.56mOhm R0.86mOhm Z_C323.64mOhm R1.04mOhm Z_C323.64mOhm R0.83mOhm Z_C323.69mOhm R0.82mOhm Z_C323.71mOhm R0.73mOhm Z_C323.64mOhm R0.90mOhm Z_C323.62mOhm R1.00mOhm Z_C323.72mOhm R0.84mOhm Z_C323.71mOhm R1.04mOhm Z_C323.70mOhm R0.97mOhm Z_C323.64mOhm R0.96mOhm Z_C323.98mOhm R0.93mOhm Z_C323.91mOhm R1.00mOhm Z_C323.86mOhm R1.06mOhm Z_C323.69mOhm R0.88mOhm Z_C323.65mOhm R0.91mOhm Z_C323.63mOhm R0.97mOhm Z_C323.67mOhm R0.90mOhm Z_C323.73mOhm R0.89mOhm Z_C323.82mOhm R0.92mOhm Z_C323.91mOhm R0.92mOhm Z_C323.73mOhm R0.93mOhm Z_C323.85mOhm R0.93mOhm Z_C323.73mOhm R0.98mOhm Z_C323.68mOhm R0.87mOhm Z_C323.62mOhm R0.93mOhm Z_C323.68mOhm R0.89mOhm Z_C323.58mOhm R0.96mOhm Z_C323.68mOhm R1.02mOhm Z_C323.58mOhm R1.01mOhm Z_C323.58mOhm R0.94mOhm Z_C323.57mOhm R1.01mOhm Z_C323.55mOhm R0.87mOhm Z_C323.58mOhm R0.94mOhm Z_C323.65mOhm R0.88mOhm Z_C323.68mOhm R0.81mOhm Z_C323.55mOhm R1.02mOhm Z_C323.77mOhm R1.00mOhm Z_C323.59mOhm R0.89mOhm Z_C323.40mOhm R0.92mOhm Z_C323.52mOhm R0.93mOhm Z_C323.53mOhm R0.90mOhm Z_C323.50mOhm R1.02mOhm Z_COutput measuring a Capacitor of 1000uF 16V:Code:SAMPLE_FREQUENCY = 9090.00 HzMEASURE_FREQUENCY = 568.13 HzSAMPLE_TIME = 1.00 s48.23mOhm R316.88mOhm Z_C884.05uFarads48.20mOhm R316.77mOhm Z_C884.36uFarads48.21mOhm R316.84mOhm Z_C884.16uFarads48.30mOhm R316.98mOhm Z_C883.77uFarads48.22mOhm R316.84mOhm Z_C884.18uFarads48.27mOhm R316.92mOhm Z_C883.96uFarads48.19mOhm R316.92mOhm Z_C883.94uFarads48.14mOhm R316.90mOhm Z_C884.01uFarads48.15mOhm R316.92mOhm Z_C883.96uFarads48.03mOhm R316.69mOhm Z_C884.58uFarads48.23mOhm R317.01mOhm Z_C883.68uFarads48.17mOhm R316.87mOhm Z_C884.08uFarads48.09mOhm R316.80mOhm Z_C884.28uFarads48.27mOhm R316.84mOhm Z_C884.16uFarads48.22mOhm R316.79mOhm Z_C884.32uFarads48.17mOhm R316.94mOhm Z_C883.89uFarads48.23mOhm R316.99mOhm Z_C883.75uFarads48.01mOhm R316.95mOhm Z_C883.86uFarads48.16mOhm R316.91mOhm Z_C883.99uFarads48.07mOhm R316.90mOhm Z_C884.01uFarads48.18mOhm R316.87mOhm Z_C884.08uFarads48.11mOhm R316.84mOhm Z_C884.18uFarads48.11mOhm R316.82mOhm Z_C884.21uFarads48.13mOhm R316.79mOhm Z_C884.31uFarads48.22mOhm R316.93mOhm Z_C883.91uFarads48.03mOhm R316.92mOhm Z_C883.94uFarads48.07mOhm R316.92mOhm Z_C883.96uFarads48.16mOhm R316.84mOhm Z_C884.17uFarads48.12mOhm R316.83mOhm Z_C884.19uFarads48.20mOhm R316.71mOhm Z_C884.55uFarads48.12mOhm R316.95mOhm Z_C883.87uFaradsClearly there is a measurement problem with resistive ohms. I'll get into it after converting the square signal to a triangular signal. The triangular signal is closer to the sine signal and allows to measure inductances, which the square signal does not allow as it generates a peak impossible to measure.

author: Picuino
author_rank: Super Contributor
timestamp: May 10, 2024, 07:35:25 pm
content: New program with improved table of sines.New schematic with triangular excitation signal.It makes good resistance, capacitance and inductance measurements. I have yet to calibrate the measured values to see how accurate they are.Program:Code:/*Version 4.2 (10/05/2024)Copyright 2024 PicuinoPermission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software"), to dealin the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sellcopies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be includedin all copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISINGFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGSIN THE SOFTWARE.*/#include <stdint.h>#define CLK_BOARD 16000000#define UART_BAUDS 115200#define MEASURE_TIME 1#define SAMPLES_PER_WAVE 16#define PIN_SIGNAL_OUT 3#define PIN_DEBUG_OUT 5#define PIN_ANALOG A6#define PIN_ANALOG_MUX 6#define PIN_SCK 13#define PIN_SDI 12#define PIN_CNV 10#define TIMER2_PERIOD 220#define TIMER2_FREQ (CLK_BOARD / ((TIMER2_PERIOD + 1) * 64 * 2))#define TIMER0_PERIOD (TIMER2_PERIOD - 1)#define TIMER0_FREQ (CLK_BOARD / ((TIMER0_PERIOD + 1) * 8))#define SAMPLES_PER_MEASURE (SAMPLES_PER_WAVE * (long) ((MEASURE_TIME) * (TIMER0_FREQ) / SAMPLES_PER_WAVE))const int16_t SIN_INTEGER[SAMPLES_PER_WAVE + SAMPLES_PER_WAVE / 4] = {9, 26, 39, 46,46, 39, 26, 9,-9, -26, -39, -46,-46, -39, -26, -9,9, 26, 39, 46,};const float BOARD_CALIBRATION = 0.5040 / (SAMPLES_PER_MEASURE); // Converts measure to milliohmsvolatile int32_t adc_acc_inphase;volatile int32_t adc_acc_quadrature;volatile int16_t adc_samples;volatile uint8_t adc_measure_end;volatile uint8_t level_state;volatile uint8_t level_state_old;float resistance_inphase;float resistance_quadrature;void setup() {Serial.begin(UART_BAUDS);// Set up output reference signal pinpinMode(PIN_SIGNAL_OUT, OUTPUT);pinMode(PIN_DEBUG_OUT, OUTPUT);// Print initial infoprint_info();// Set up peripheralstimer0_setup();timer2_setup();timer_synchronize();adc_setup();measure_init();while (adc_measure_end == 0);measure_init();}void loop() {// Main Loopwhile (1) {if (adc_measure_end == 1) {resistance_inphase = -adc_acc_inphase;resistance_quadrature = -adc_acc_quadrature;resistance_inphase *= BOARD_CALIBRATION;resistance_quadrature *= BOARD_CALIBRATION;print_values(resistance_inphase, resistance_quadrature);measure_init();}}}void print_info(void) {Serial.println();Serial.print("SAMPLE_FREQUENCY = ");Serial.print(1.0 * TIMER0_FREQ);Serial.println(" Hz");Serial.print("MEASURE_SIGNAL_FREQUENCY = ");Serial.print(1.0 * TIMER2_FREQ);Serial.println(" Hz");Serial.print("SAMPLE_TIME = ");Serial.print(1.0 * SAMPLES_PER_MEASURE / TIMER0_FREQ);Serial.println(" s");}void print_values(float resistance_inphase, float resistance_quadrature) {Serial.print(resistance_inphase, 2);Serial.print("\tmOhm R \t");if (resistance_quadrature > 0) {Serial.print(resistance_quadrature, 2);Serial.print("\tmOhm Z_L \t");if (resistance_quadrature > 5.0) {Serial.print(resistance_quadrature * 1000.0 / (TIMER2_FREQ * 2.0 * 3.1415927));Serial.println("\tuHenrys");}else {Serial.println();}}else {Serial.print(-resistance_quadrature, 2);Serial.print("\tmOhm Z_C \t");if (resistance_quadrature < -5.0) {Serial.print(1000000000.0 / (-resistance_quadrature * TIMER2_FREQ * 2.0 * 3.1415927));Serial.println("\tuFarads");}else {Serial.println();}}}void adc_setup(void) {analogRead(PIN_ANALOG);cli(); // Stop interruptsADMUX = (1 << 6) |(0 << ADLAR) |(PIN_ANALOG_MUX << 0);ADCSRA = (1 << ADEN) |(0 << ADSC) |(0 << ADATE) |(0 << ADIE) |(0b111); // Division factorADCSRB = 0x00;sei(); // Allow interrupts}void measure_init(void) {delayMicroseconds(1000);cli();adc_acc_inphase = 0;adc_acc_quadrature = 0;level_state = SAMPLES_PER_WAVE * 0.25;level_state_old = 0;adc_samples = 0;ADCW = 0;sei();while ((PIND & (1 << PIN_SIGNAL_OUT)) != 0);while ((PIND & (1 << PIN_SIGNAL_OUT)) == 0);adc_measure_end = 0;}void timer0_setup(void) {cli(); // Stop interrupts// set compare match registerTCCR0A = (0 << 6) | // OOM0A. 0=OC0A disconnected. 1=Toggle OC0A on compare match (p.84)(0 << 4) | // COM0B. 0=OC0B disconnected. 1=Toggle OC0B on compare match (p.85)(2 << 0); // WGM0. PWM mode. 1=phase correct 2=CTC (p.86)TCCR0B = (0 << 7) | // FOC0A.(0 << 6) | // FOC0B.(0 << 3) | // WGM02.(2 << 0); // CLOCK source.OCR0A = TIMER0_PERIOD;OCR0B = TIMER0_PERIOD / 2;TIMSK0 = (0 << 2) | // OCIE0B. Match B Interrupt Enable(1 << 1) | // OCIE0A. Match A Interrupt Enable(0 << 0); // TOIE0. Overflow Interrupt EnableTIFR0 = 0;TCNT0 = 0; // Initialize Timer0 countersei(); // Allow interrupts}void timer2_setup(void) {cli(); // Stop interruptsTCCR2A = (1 << 6) | // OOM2A. 0=OC2A disconnected. 1=Toggle OC2A on compare match (p.128)(2 << 4) | // COM2B. 2=Clear OC2B on compare match (p.129)(1 << 0); // WGM2. PWM mode. 1=phase correct (p.130)TCCR2B = (0 << 7) | // FOC2A.(0 << 6) | // FOC2B.(1 << 3) | // WGM22.(4 << 0); // CLOCK source.OCR2A = TIMER2_PERIOD;OCR2B = TIMER2_PERIOD / 2;TIMSK2 = (0 << 2) | // OCIE2B. Match B Interrupt Enable(0 << 1) | // OCIE2A. Match A Interrupt Enable(0 << 0); // TOIE2. Overflow Interrupt EnableTIFR2 = 0;TCNT2 = 0; // Initialize Timer2 countersei(); // Allow interrupts}void timer_synchronize(void) {cli(); // Stop interruptswhile ((PIND & (1 << PIN_SIGNAL_OUT)) != 0);while ((PIND & (1 << PIN_SIGNAL_OUT)) == 0);GTCCR = (1 << TSM) | (1 << PSRASY) | (1 << PSRSYNC); // halt all timersTCNT0 = TIMER0_PERIOD / 2 + 4; // Initialize Timer0 counterGTCCR = 0; // release all timerssei(); // Allow interrupts}// Timer0 interrupt handlerISR(TIMER0_COMPA_vect) {int16_t adc_value;if (adc_measure_end == 0) {// ADC Start ConversionADCSRA |= (1 << ADSC);// Read last conversionadc_value = ADCW;// Accumulate values (10us)adc_acc_inphase += (int32_t) adc_value * SIN_INTEGER;adc_acc_quadrature += (int32_t) adc_value * SIN_INTEGER[level_state_old + SAMPLES_PER_WAVE / 4];// Update next statelevel_state_old = level_state;level_state++;level_state &= 0x0F;adc_samples++;if (adc_samples > SAMPLES_PER_MEASURE) {adc_measure_end = 1;}}}// Timer2 interrupt handlerISR(TIMER2_COMPA_vect) {}void debug_pin_pulse(void) {PORTD |= (1 << PIN_DEBUG_OUT);delayMicroseconds(4);PORTD &= ~(1 << PIN_DEBUG_OUT);}

author: Picuino
author_rank: Super Contributor
timestamp: May 10, 2024, 07:43:40 pm
content: Some signals:Amplifier output measuring a capacitor (1000uF)Amplifier output measuring an inductance (68uH)Excitation (Triangular wave)

author: gf
author_rank: Super Contributor
timestamp: May 10, 2024, 08:35:59 pm
content: Quote from: Picuino on May 10, 2024, 07:43:40 pmAlso check the settling behavior of the integrator when you turn the square wave on. Does it overshoot and possibly even clip? You may need to wait until it has settled to a steady state before you start taking readings.

author: Picuino
author_rank: Super Contributor
timestamp: May 10, 2024, 09:00:40 pm
content: The square wave output signal oscillates all the time without stopping (generated by timer2 in PWM mode). Even when not measuring, the signal keeps oscillating.In addition, at startup the program waits one second before taking measurements.

author: Picuino
author_rank: Super Contributor
timestamp: May 11, 2024, 08:51:11 am
content: New test.I have found that if I increase the ADC frequency a little above what is recommended, the outputs are even more stable.I have increased the samples per cycle to 32. I had to modify the program a bit. It is now more flexible. I have also added a name for one “magic number”. The program works well measuring inductances, capacitors and resistors. Although it still has more error than I would like.Code:/*Version 4.3 (11/05/2024)Copyright 2024 PicuinoPermission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software"), to dealin the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sellcopies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be includedin all copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISINGFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGSIN THE SOFTWARE.*/#include <stdint.h>#define CLK_BOARD 16000000#define UART_BAUDS 115200#define MEASURE_TIME 1#define SAMPLES_PER_WAVE 32#define PIN_SIGNAL_OUT 3#define PIN_DEBUG_OUT 5#define PIN_ANALOG A6#define PIN_ANALOG_MUX 6#define PIN_SCK 13#define PIN_SDI 12#define PIN_CNV 10#define TIMER2_PERIOD 220#define TIMER2_FREQ (CLK_BOARD / ((TIMER2_PERIOD + 1) * 64 * 2))#define TIMER0_PERIOD (110 - 1)#define TIMER0_PHASE_ADJUST (-20)#define TIMER0_FREQ (CLK_BOARD / ((TIMER0_PERIOD + 1) * 8))#define SAMPLES_PER_MEASURE (SAMPLES_PER_WAVE * (long) ((MEASURE_TIME) * (TIMER0_FREQ) / SAMPLES_PER_WAVE))const int16_t SIN_INTEGER[SAMPLES_PER_WAVE + SAMPLES_PER_WAVE / 4] = {5,14,23,31,38,43,47,49,49,47,43,38,31,23,14,5,-5,-14,-23,-31,-38,-43,-47,-49,-49,-47,-43,-38,-31,-23,-14,-5,5,14,23,31,38,43,47,49,};const float BOARD_CALIBRATION = 0.5040 / (SAMPLES_PER_MEASURE); // Converts measure to milliohmsvolatile int32_t adc_acc_inphase;volatile int32_t adc_acc_quadrature;volatile int16_t adc_samples;volatile uint8_t adc_measuring;volatile uint8_t level_state;volatile uint8_t level_state_old;float resistance_inphase;float resistance_quadrature;void setup() {Serial.begin(UART_BAUDS);// Set up output reference signal pinpinMode(PIN_SIGNAL_OUT, OUTPUT);pinMode(PIN_DEBUG_OUT, OUTPUT);// Print initial infoprint_info();// Set up peripheralstimer0_setup();timer2_setup();timer_synchronize();adc_setup();// Inits measuremeasure_init();while (adc_measuring == 1);measure_init();}void loop() {// Main Loopwhile (1) {if (adc_measuring == 0) {resistance_inphase = -adc_acc_inphase;resistance_quadrature = -adc_acc_quadrature;resistance_inphase *= BOARD_CALIBRATION;resistance_quadrature *= BOARD_CALIBRATION;print_values(resistance_inphase, resistance_quadrature);measure_init();}}}void print_info(void) {Serial.println();Serial.print("SAMPLE_FREQUENCY = ");Serial.print(1.0 * TIMER0_FREQ);Serial.println(" Hz");Serial.print("MEASURE_SIGNAL_FREQUENCY = ");Serial.print(1.0 * TIMER2_FREQ);Serial.println(" Hz");Serial.print("SAMPLE_TIME = ");Serial.print(1.0 * SAMPLES_PER_MEASURE / TIMER0_FREQ);Serial.println(" s");}void print_values(float resistance_inphase, float resistance_quadrature) {Serial.print(resistance_inphase, 2);Serial.print("\tmOhm R \t");if (resistance_quadrature > 0) {Serial.print(resistance_quadrature, 2);Serial.print("\tmOhm Z_L \t");if (resistance_quadrature > 5.0) {Serial.print(resistance_quadrature * 1000.0 / (TIMER2_FREQ * 2.0 * 3.1415927));Serial.println("\tuHenrys");}else {Serial.println();}}else {Serial.print(-resistance_quadrature, 2);Serial.print("\tmOhm Z_C \t");if (resistance_quadrature < -5.0) {Serial.print(1000000000.0 / (-resistance_quadrature * TIMER2_FREQ * 2.0 * 3.1415927));Serial.println("\tuFarads");}else {Serial.println();}}}void adc_setup(void) {analogRead(PIN_ANALOG);cli(); // Stop interruptsADMUX = (1 << 6) |(0 << ADLAR) |(PIN_ANALOG_MUX << 0);ADCSRA = (1 << ADEN) |(0 << ADSC) |(0 << ADATE) |(0 << ADIE) |(0b110); // Division factorADCSRB = 0x00;sei(); // Allow interrupts}void measure_init(void) {delayMicroseconds(1000);cli();adc_acc_inphase = 0;adc_acc_quadrature = 0;level_state = SAMPLES_PER_WAVE * 0.25;level_state_old = 0;adc_samples = 0;ADCW = 0;sei();while ((PIND & (1 << PIN_SIGNAL_OUT)) != 0);while ((PIND & (1 << PIN_SIGNAL_OUT)) == 0);adc_measuring = 1;}void timer0_setup(void) {cli(); // Stop interrupts// set compare match registerTCCR0A = (0 << 6) | // OOM0A. 0=OC0A disconnected. 1=Toggle OC0A on compare match (p.84)(0 << 4) | // COM0B. 0=OC0B disconnected. 1=Toggle OC0B on compare match (p.85)(2 << 0); // WGM0. PWM mode. 1=phase correct 2=CTC (p.86)TCCR0B = (0 << 7) | // FOC0A.(0 << 6) | // FOC0B.(0 << 3) | // WGM02.(2 << 0); // CLOCK source.OCR0A = TIMER0_PERIOD;OCR0B = TIMER0_PERIOD / 2;TIMSK0 = (0 << 2) | // OCIE0B. Match B Interrupt Enable(1 << 1) | // OCIE0A. Match A Interrupt Enable(0 << 0); // TOIE0. Overflow Interrupt EnableTIFR0 = 0;TCNT0 = 0; // Initialize Timer0 countersei(); // Allow interrupts}void timer2_setup(void) {cli(); // Stop interruptsTCCR2A = (1 << 6) | // OOM2A. 0=OC2A disconnected. 1=Toggle OC2A on compare match (p.128)(2 << 4) | // COM2B. 2=Clear OC2B on compare match (p.129)(1 << 0); // WGM2. PWM mode. 1=phase correct (p.130)TCCR2B = (0 << 7) | // FOC2A.(0 << 6) | // FOC2B.(1 << 3) | // WGM22.(4 << 0); // CLOCK source.OCR2A = TIMER2_PERIOD;OCR2B = TIMER2_PERIOD / 2;TIMSK2 = (0 << 2) | // OCIE2B. Match B Interrupt Enable(0 << 1) | // OCIE2A. Match A Interrupt Enable(0 << 0); // TOIE2. Overflow Interrupt EnableTIFR2 = 0;TCNT2 = 0; // Initialize Timer2 countersei(); // Allow interrupts}void timer_synchronize(void) {cli(); // Stop interruptswhile ((PIND & (1 << PIN_SIGNAL_OUT)) != 0);while ((PIND & (1 << PIN_SIGNAL_OUT)) == 0);GTCCR = (1 << TSM) | (1 << PSRASY) | (1 << PSRSYNC); // halt all timersTCNT0 = TIMER0_PERIOD / 2 + TIMER0_PHASE_ADJUST; // Initialize Timer0 counterGTCCR = 0; // release all timerssei(); // Allow interrupts}// Timer0 interrupt handlerISR(TIMER0_COMPA_vect) {int16_t adc_value;if (adc_measuring == 1) {// ADC Start ConversionADCSRA |= (1 << ADSC);// Read last conversionadc_value = ADCW;// Accumulate values (10us)adc_acc_inphase += (int32_t) adc_value * SIN_INTEGER;adc_acc_quadrature += (int32_t) adc_value * SIN_INTEGER[level_state_old + SAMPLES_PER_WAVE / 4];// Update next statelevel_state_old = level_state;level_state++;if (level_state >= SAMPLES_PER_WAVE)level_state = 0;adc_samples++;if (adc_samples > SAMPLES_PER_MEASURE) {adc_measuring = 0;}}}// Timer2 interrupt handlerISR(TIMER2_COMPA_vect) {}void debug_pin_pulse(void) {PORTD |= (1 << PIN_DEBUG_OUT);delayMicroseconds(4);PORTD &= ~(1 << PIN_DEBUG_OUT);}Measuring a cable:Code:SAMPLE_FREQUENCY = 18181.00 HzMEASURE_SIGNAL_FREQUENCY = 565.00 HzSAMPLE_TIME = 1.00 s71.26mOhm R1.62mOhm Z_C71.19mOhm R1.77mOhm Z_C71.28mOhm R1.70mOhm Z_C71.39mOhm R1.63mOhm Z_C71.27mOhm R1.68mOhm Z_C71.21mOhm R1.49mOhm Z_C71.31mOhm R1.62mOhm Z_C71.18mOhm R1.51mOhm Z_C71.39mOhm R1.58mOhm Z_C71.10mOhm R1.66mOhm Z_C71.15mOhm R1.53mOhm Z_C71.17mOhm R1.50mOhm Z_C71.26mOhm R1.47mOhm Z_C71.15mOhm R1.52mOhm Z_C71.35mOhm R1.73mOhm Z_C71.22mOhm R1.67mOhm Z_C71.14mOhm R1.55mOhm Z_C71.07mOhm R1.41mOhm Z_C71.24mOhm R1.71mOhm Z_C71.31mOhm R1.45mOhm Z_C71.07mOhm R1.44mOhm Z_C71.19mOhm R1.59mOhm Z_C71.16mOhm R1.69mOhm Z_C71.16mOhm R1.51mOhm Z_C71.15mOhm R1.73mOhm Z_C71.25mOhm R1.66mOhm Z_C71.04mOhm R1.71mOhm Z_C71.07mOhm R1.77mOhm Z_CMeasuring an inductance:Code:SAMPLE_FREQUENCY = 18181.00 HzMEASURE_SIGNAL_FREQUENCY = 565.00 HzSAMPLE_TIME = 1.00 s25.11mOhm R198.29mOhm Z_L55.86uHenrys25.00mOhm R198.23mOhm Z_L55.84uHenrys24.96mOhm R198.24mOhm Z_L55.84uHenrys25.05mOhm R198.28mOhm Z_L55.85uHenrys25.06mOhm R198.04mOhm Z_L55.79uHenrys25.00mOhm R198.28mOhm Z_L55.85uHenrys24.98mOhm R198.09mOhm Z_L55.80uHenrys25.01mOhm R198.22mOhm Z_L55.84uHenrys25.06mOhm R198.16mOhm Z_L55.82uHenrys25.08mOhm R198.15mOhm Z_L55.82uHenrys24.97mOhm R198.34mOhm Z_L55.87uHenrys24.96mOhm R198.33mOhm Z_L55.87uHenrys24.86mOhm R198.09mOhm Z_L55.80uHenrys25.03mOhm R198.13mOhm Z_L55.81uHenrys25.00mOhm R198.29mOhm Z_L55.86uHenrys25.10mOhm R198.17mOhm Z_L55.82uHenrys25.02mOhm R198.02mOhm Z_L55.78uHenrys25.10mOhm R198.40mOhm Z_L55.89uHenrys25.08mOhm R198.21mOhm Z_L55.83uHenrys25.02mOhm R198.21mOhm Z_L55.83uHenrys25.07mOhm R198.04mOhm Z_L55.79uHenrys25.01mOhm R198.15mOhm Z_L55.82uHenrys24.99mOhm R198.16mOhm Z_L55.82uHenrys24.97mOhm R198.28mOhm Z_L55.85uHenrys24.87mOhm R198.05mOhm Z_L55.79uHenrys24.90mOhm R198.02mOhm Z_L55.78uHenrys24.87mOhm R198.26mOhm Z_L55.85uHenrys25.01mOhm R198.29mOhm Z_L55.86uHenrysMeasuring a capacitor:Code:SAMPLE_FREQUENCY = 18181.00 HzMEASURE_SIGNAL_FREQUENCY = 565.00 HzSAMPLE_TIME = 1.00 s107.67mOhm R322.08mOhm Z_C874.60uFarads107.67mOhm R321.94mOhm Z_C874.98uFarads107.60mOhm R321.87mOhm Z_C875.15uFarads107.64mOhm R322.09mOhm Z_C874.57uFarads107.53mOhm R321.84mOhm Z_C875.24uFarads107.52mOhm R322.13mOhm Z_C874.47uFarads107.47mOhm R321.90mOhm Z_C875.10uFarads107.62mOhm R321.91mOhm Z_C875.07uFarads107.41mOhm R321.94mOhm Z_C874.98uFarads107.52mOhm R322.02mOhm Z_C874.75uFarads107.48mOhm R322.00mOhm Z_C874.82uFarads107.60mOhm R321.84mOhm Z_C875.26uFarads107.47mOhm R321.93mOhm Z_C875.00uFarads107.48mOhm R321.68mOhm Z_C875.67uFarads107.34mOhm R321.85mOhm Z_C875.22uFarads107.38mOhm R321.95mOhm Z_C874.94uFarads107.45mOhm R321.84mOhm Z_C875.26uFarads107.48mOhm R321.93mOhm Z_C875.01uFarads107.21mOhm R321.91mOhm Z_C875.06uFarads107.28mOhm R321.87mOhm Z_C875.16uFarads107.61mOhm R321.83mOhm Z_C875.29uFarads107.34mOhm R321.89mOhm Z_C875.11uFarads107.44mOhm R321.96mOhm Z_C874.93uFarads107.28mOhm R321.81mOhm Z_C875.34uFarads107.29mOhm R321.88mOhm Z_C875.14uFarads107.38mOhm R321.86mOhm Z_C875.19uFarads

author: gf
author_rank: Super Contributor
timestamp: May 11, 2024, 09:39:11 am
content: Quote from: Picuino on May 11, 2024, 08:51:11 amCan you quantify the error?And how much error would you expect?How do you determine the "ground truth" value that you are going to compare to the measured value?The posted readings are meaningless without knowing the true value to compare.EDIT:What do you measure if you connect a resistor as DUT, but turn off the square wave?Do you measure zero (+- random noise), or do you see an offset?

author: Picuino
author_rank: Super Contributor
timestamp: May 11, 2024, 10:43:23 am
content: Unfortunately I do not have any capacitance or inductance measuring device or any capacitance or inductance standard, so I have to rely on the nominal measurement which can have +-10% error at best.The capacitor has 1000uFThe inductance has 68uHWhat I am trying for now is to get a stable reading of the first decimal place. That would give me an instrument with a resolution of 50000 points, taking into account that the full scale is 5000 milliohms.These are the measurements I get if I remove the square signal and connect it to ground and place a resistor as DUT:Code:SAMPLE_FREQUENCY = 18181.00 HzMEASURE_SIGNAL_FREQUENCY = 565.00 HzSAMPLE_TIME = 1.00 s0.24mOhm R0.63mOhm Z_C0.29mOhm R0.59mOhm Z_C0.28mOhm R0.63mOhm Z_C0.22mOhm R0.63mOhm Z_C0.25mOhm R0.54mOhm Z_C0.29mOhm R0.54mOhm Z_C0.34mOhm R0.60mOhm Z_C0.28mOhm R0.53mOhm Z_C0.42mOhm R0.78mOhm Z_C0.19mOhm R0.72mOhm Z_C0.21mOhm R0.68mOhm Z_C0.15mOhm R0.60mOhm Z_C0.25mOhm R0.71mOhm Z_C0.25mOhm R0.70mOhm Z_C0.16mOhm R0.55mOhm Z_C0.28mOhm R0.70mOhm Z_C0.25mOhm R0.67mOhm Z_C0.17mOhm R0.57mOhm Z_C0.22mOhm R0.80mOhm Z_C0.23mOhm R0.64mOhm Z_C0.16mOhm R0.50mOhm Z_C0.26mOhm R0.65mOhm Z_C0.17mOhm R0.66mOhm Z_C0.10mOhm R0.62mOhm Z_C0.14mOhm R0.63mOhm Z_C

author: Picuino
author_rank: Super Contributor
timestamp: May 11, 2024, 10:56:13 am
content: The problem with the resistance (which I can measure with precission) is that the breadboard adds an unknown and relatively high resistance to all contacts.It is possible that I am demanding too much of a breadboard project and need to move it to a solder board now.

author: gf
author_rank: Super Contributor
timestamp: May 11, 2024, 11:04:27 am
content: Quote from: Picuino on May 11, 2024, 10:56:13 amYou have a differential amplifier, so you can do Kelvin (4-wire) measurements.[ I mean exactly as you did draw it on the schematics. ]

author: gf
author_rank: Super Contributor
timestamp: May 11, 2024, 11:26:56 am
content: Quote from: Picuino on May 11, 2024, 10:43:23 amIn order to reduce uncorrelated random noise by a factor of 10 you need to increase measurement time by a factor of 100 (or take 100 subseqent readings and average them).QuoteSo there is a small offset. Good question where it is coming from. Either ADC non-linearity, or your circuit picks up some interferences (either mains harmonics, or whatsoever). What is you mains frequency? 50 or 60 Hz?

author: Picuino
author_rank: Super Contributor
timestamp: May 11, 2024, 11:35:32 am
content: 50Hz.And I can see the effects on the output of the instrumentation amplifier.

author: Picuino
author_rank: Super Contributor
timestamp: May 11, 2024, 11:37:21 am
content: Quote from: gf on May 11, 2024, 11:04:27 amYes, that's what I do. But the breadboard adds resistance to all contacts, so it is impossible to make a true kelvin measurement. I will have to move to a circuit with soldering and shielding.

author: Picuino
author_rank: Super Contributor
timestamp: May 11, 2024, 11:42:25 am
content: I just realized that the 5V power supply was off, so I was taking readings with the power coming from the Arduino's USB connection.I have to repeat tests.

author: gf
author_rank: Super Contributor
timestamp: May 11, 2024, 12:50:33 pm
content: Quote from: Picuino on May 11, 2024, 11:37:21 amThe amplifier has high input impedance, therefore the impedance of the wire from the DUT to the amplijfier is not critical. They just need to be attached to the DUT at the exact points between which you want to measure resistance. Any contact resistance on the current path leads to a common mode signal which should be rejected by the differential amplifier. According to the datasheet CMRR should be about 90dB.EDIT: What about R9? Do you consider it part of the amplifier, or part of the DUT?

author: Picuino
author_rank: Super Contributor
timestamp: May 11, 2024, 01:07:24 pm
content: Quote from: gf on May 11, 2024, 11:26:56 amI have made measurements with a 10-second period and the values hardly improve.I have also soldered a smd resistor on the instrumentation amplifier PCB and cut the Breadboard pins to try to improve the output noise. The noise has improved a bit, but the readings are still in error on the first decimal digit.

author: gf
author_rank: Super Contributor
timestamp: May 11, 2024, 01:20:57 pm
content: Quote from: Picuino on May 11, 2024, 01:07:24 pmCan you measure a 10s period without overflowing the 32-bit signed accumulator (1024*49*565*16*10 = 4535910400 worst case)?As I said, if it is random noise then you need to go from 1s to100sin order to gain 1 digit.

author: Picuino
author_rank: Super Contributor
timestamp: May 11, 2024, 01:46:33 pm
content: Quote from: gf on May 11, 2024, 12:50:33 pmR9 was part of the amplifier. It was necessary so that the capacitors would not charge up to 5V. Now it is not needed since the excitation signal is triangular.EDIT:But now it comes in handy not to load the capacitor with too much voltage. It is true that its effect would have to be eliminated from the final measurement with some calculations. For now I am not going to do it.

author: Picuino
author_rank: Super Contributor
timestamp: May 11, 2024, 01:50:59 pm
content: There is no overflow because not all measurements give 1024 and many measurements are subtracted, not added.Anyway, for now I'll stick with what I have. When I do the assembly with welds and shielding I'll see if it improves anything.Measurements of 100 seconds are not practical and I have the impression that I am not going to reduce the error because 10 seconds does not improve compared to 1 second. Besides, to test it I would have to change the program because in that case I could easily have an overflow.

author: Picuino
author_rank: Super Contributor
timestamp: May 11, 2024, 02:33:45 pm
content: I have no protoboards left to solder, so I have to place an order and wait. In the meantime I'm going to try to design a custom PCB for the assembly I already have done.I have added one small change: another connection from the microcontroller to the integrator with a larger resistor so I can make two triangular waves. One of higher frequency than the other.

author: Picuino
author_rank: Super Contributor
timestamp: May 12, 2024, 07:17:50 am
content: I found a small piece of prototyping board to solder and managed to assemble the analog part with SMD components.

author: Picuino
author_rank: Super Contributor
timestamp: May 12, 2024, 07:28:31 am
content: I am starting it up and everything works fine except the amplifier gain, which is zero.The amplifier output is always 2.5V, regardless of the input signal. I have checked continuity and the reference and output pins of the amplifier are not connected.I have also re-soldered the 47 Ohm resistor on pins 2 and 3.I don't know what to do anymore. The circuit was working fine on the breadboard.

author: Picuino
author_rank: Super Contributor
timestamp: May 12, 2024, 09:29:31 am
content: I have changed the amplifier integrated for another one and it still does not amplify.When I change the reference voltage (pin 6), the output voltage (pin 7, V_out) also changes with the same value. But the pins are not shorted together.The negative input has a ramp signal of almost 5Vpp and the output maintain 2.5V of DC.When I connect the ramped signal to the positive input and the negative input to ground, the output only increases in value when the positive input rises above 4 volts.Attached:Yellow = V_outBlue = V_in+V_in- = GND

author: gnuarm
author_rank: Super Contributor
timestamp: May 12, 2024, 10:42:24 am
content: I'm not completely following your description of what you are measuring. When you say Vin+, is that pin 1 or pin 4 on the connector on the schematic?You have a DC blocking cap on pin 1. If you ground that connector pin, it will be only an AC ground. What is the signal like on pin 1 of U1?

author: Picuino
author_rank: Super Contributor
timestamp: May 12, 2024, 10:56:07 am
content: Vin- = pin 1 U1 / pin 1 J4Vin+ = pin 4 U1 / pin 4 J4If I ground pin 4 by connecting it to pin 3 of J4, R3 grounds pin 1 also in DC.EDIT:The schematic is the same as the one I had mounted on the breadboard. Therefore the schematic should not have any errors.

author: gf
author_rank: Super Contributor
timestamp: May 12, 2024, 11:27:46 am
content: I think you are (significantly) overdriving the input. The input voltage range is only -0.1V...3V. If pin4 is grounded, the maximum AC input voltage on connector pin 1 would be 200mVpp. And even that would still drive the output into saturation (gain 1000).

author: Picuino
author_rank: Super Contributor
timestamp: May 12, 2024, 11:38:06 am
content: Yes, I am overdriving the input. It is the only way to get the output to show variation. With any other smaller signal, the output remains unchanged at 2.5V.

author: Picuino
author_rank: Super Contributor
timestamp: May 12, 2024, 01:24:11 pm
content: I have mounted on the breadboard another instrumentation amplifier circuit with an AD8421 to start from scratch.https://www.analog.com/media/en/technical-documentation/data-sheets/ad8421.pdfI continue to encounter problems. It does not amplify.I don't know what the hell I'm doing wrong.I've changed ICs, I've tried a different IC on the breadboard, I'm also trying it on solder board and in all cases the instrumentation amplifiers are not working.And this after it was working yesterday without problems.I'm pretty patient, but in this case I'm starting to get nervous.

author: gf
author_rank: Super Contributor
timestamp: May 12, 2024, 01:55:32 pm
content: Again, take care of the (rather narrow) input voltage range of the AD8421 with 5V single-supply. See red curve in figure 14 of the datasheet.You need to bias the inputs with a common mode voltage of about 3V above the negative supply rail (which is GND in your case). 2.5V is already outside the range.The other opamp was better suited for 5V single-supply.

author: Picuino
author_rank: Super Contributor
timestamp: May 12, 2024, 02:18:12 pm
content: I mounted the first AD8220 I removed, again on the breadboard. Now it works correctly.Conclusion: The AD8220 integrated circuits work correctly, but what doesn't work is the soldered PCB and I don't know why.

author: Picuino
author_rank: Super Contributor
timestamp: May 12, 2024, 02:35:27 pm
content: Eureka, I got it.The breadboard at the top has pads that connect to the bottom. I had trimmed the pins of R1 on the AD8220 board, but it was still making connection to the bottom of the board.Diagnosis: Rg (R1) shorted to ground.

author: Picuino
author_rank: Super Contributor
timestamp: May 12, 2024, 03:34:39 pm
content: I already have readings from several componentsProgram:Code:/*Version 4.4 (12/05/2024)Copyright 2024 PicuinoPermission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software"), to dealin the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sellcopies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be includedin all copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISINGFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGSIN THE SOFTWARE.*/#include <stdint.h>#define CLK_BOARD 16000000#define UART_BAUDS 115200#define MEASURE_TIME 1.0#define PIN_SIGNAL_OUT 3#define PIN_DEBUG_OUT 5#define PIN_ANALOG A6#define PIN_ANALOG_MUX 6#define PIN_SCK 13#define PIN_SDI 12#define PIN_CNV 10#define SAMPLES_16#ifdef SAMPLES_32#define SAMPLES_PER_WAVE 32#define ADC_PRESCALER (0b110)#define TIMER2_PERIOD 220#define TIMER2_FREQ (CLK_BOARD / ((TIMER2_PERIOD + 1) * 64 * 2))#define TIMER0_PERIOD (110 - 1)#define TIMER0_PHASE_ADJUST (0.40)#define TIMER0_FREQ (CLK_BOARD / ((TIMER0_PERIOD + 1) * 8))#define SAMPLES_PER_MEASURE (SAMPLES_PER_WAVE * (long) ((MEASURE_TIME) * (TIMER0_FREQ) / SAMPLES_PER_WAVE))const float BOARD_CALIBRATION = 0.2520 / (SAMPLES_PER_MEASURE); // Converts measure to milliohmsconst float BOARD_PHASE_ADJUST = -0.1074; // Radians adjustconst float BOARD_ADDED_RESISTOR = 0.0; // Substract board probes resistor. Sistematic Errorconst int16_t SIN_INTEGER[SAMPLES_PER_WAVE + SAMPLES_PER_WAVE / 4] = {5, 14, 23, 31, 38, 43, 47, 49,49, 47, 43, 38, 31, 23, 14, 5,-5, -14, -23, -31, -38, -43, -47, -49,-49, -47, -43, -38, -31, -23, -14, -5,5, 14, 23, 31, 38, 43, 47, 49,};#endif#ifdef SAMPLES_16#define SAMPLES_PER_WAVE 16#define ADC_PRESCALER (0b111)#define TIMER2_PERIOD 220#define TIMER2_FREQ (CLK_BOARD / ((TIMER2_PERIOD + 1) * 64 * 2))#define TIMER0_PERIOD (220 - 1)#define TIMER0_PHASE_ADJUST (0.4)#define TIMER0_FREQ (CLK_BOARD / ((TIMER0_PERIOD + 1) * 8))#define SAMPLES_PER_MEASURE (SAMPLES_PER_WAVE * (long) ((MEASURE_TIME) * (TIMER0_FREQ) / SAMPLES_PER_WAVE))const float BOARD_CALIBRATION = 0.2512 / (SAMPLES_PER_MEASURE); // Converts measure to milliohmsconst float BOARD_PHASE_ADJUST = +0.1074; // Radians adjustconst float BOARD_ADDED_RESISTOR = 69.0; // Substract board probes resistor. Sistematic Errorconst int16_t SIN_INTEGER[SAMPLES_PER_WAVE + SAMPLES_PER_WAVE / 4] = {9, 26, 39, 46,46, 39, 26, 9,-9, -26, -39, -46,-46, -39, -26, -9,9, 26, 39, 46,};#endifvolatile int32_t adc_acc_inphase;volatile int32_t adc_acc_quadrature;volatile int32_t adc_samples;volatile uint8_t adc_measuring;volatile uint8_t level_state;volatile uint8_t level_state_old;float impedance_inphase;float impedance_quadrature;float impedance_sign;void setup() {Serial.begin(UART_BAUDS);// Set up output reference signal pinpinMode(PIN_SIGNAL_OUT, OUTPUT);pinMode(PIN_DEBUG_OUT, OUTPUT);// Print initial infoprint_info();// Set up peripheralstimer0_setup();timer2_setup();timer_synchronize();adc_setup();// Inits measuremeasure_init();while (adc_measuring == 1);measure_init();}void loop() {// Main Loopwhile (1) {if (adc_measuring == 0) {read_float_values();phase_adjust();print_values();measure_init();}}}void read_float_values(void) {// Read accumulator valuesimpedance_inphase = -adc_acc_inphase;impedance_quadrature = -adc_acc_quadrature;// Rescale valuesimpedance_inphase *= BOARD_CALIBRATION;impedance_quadrature *= BOARD_CALIBRATION;}void phase_adjust(void) {// Get impedance signif (impedance_quadrature > 0) {impedance_sign = 1;}else {impedance_sign = -1;}impedance_quadrature = abs(impedance_quadrature);// Phase adjustfloat module = sqrt(impedance_inphase * impedance_inphase + impedance_quadrature * impedance_quadrature);float phase;if (abs(impedance_inphase) < 0.1) {phase = 90;}else {phase = atan(impedance_quadrature / impedance_inphase);}phase *= impedance_sign;phase += BOARD_PHASE_ADJUST;impedance_inphase = module * cos(phase);impedance_quadrature = module * sin(phase);// Get new impedance signif (impedance_quadrature > 0) {impedance_sign = 1;}else {impedance_sign = -1;}impedance_quadrature = abs(impedance_quadrature);// Substract board resistance (sistematic error)impedance_inphase -= BOARD_ADDED_RESISTOR;}void print_info(void) {Serial.println();Serial.print("SAMPLE_FREQUENCY = ");Serial.print(1.0 * TIMER0_FREQ);Serial.println(" Hz");Serial.print("MEASURE_SIGNAL_FREQUENCY = ");Serial.print(1.0 * TIMER2_FREQ);Serial.println(" Hz");Serial.print("SAMPLE_TIME = ");Serial.print(1.0 * SAMPLES_PER_MEASURE / TIMER0_FREQ);Serial.println(" s");}void print_values(void) {Serial.print(impedance_inphase, 1);Serial.print("\tmOhm R \t");if (impedance_sign > 0) {Serial.print(impedance_quadrature, 1);Serial.print("\tmOhm Z_L \t");if (impedance_quadrature > 5.0) {Serial.print(impedance_quadrature * 1000.0 / (TIMER2_FREQ * 2.0 * 3.1415927));Serial.println("\tuHenrys");}else {Serial.println();}}else {Serial.print(impedance_quadrature, 1);Serial.print("\tmOhm Z_C \t");if (impedance_quadrature > 5.0) {Serial.print(1000000000.0 / (impedance_quadrature * TIMER2_FREQ * 2.0 * 3.1415927));Serial.println("\tuFarads");}else {Serial.println();}}}void adc_setup(void) {analogRead(PIN_ANALOG);cli(); // Stop interruptsADMUX = (1 << 6) |(0 << ADLAR) |(PIN_ANALOG_MUX << 0);ADCSRA = (1 << ADEN) |(0 << ADSC) |(0 << ADATE) |(0 << ADIE) |(ADC_PRESCALER); // Division factorADCSRB = 0x00;sei(); // Allow interrupts}void measure_init(void) {delayMicroseconds(1000);cli();adc_acc_inphase = 0;adc_acc_quadrature = 0;level_state = SAMPLES_PER_WAVE * 0.25;level_state_old = 0;adc_samples = 0;ADCW = 0;sei();while ((PIND & (1 << PIN_SIGNAL_OUT)) != 0);while ((PIND & (1 << PIN_SIGNAL_OUT)) == 0);adc_measuring = 1;}void timer0_setup(void) {cli(); // Stop interrupts// set compare match registerTCCR0A = (0 << 6) | // OOM0A. 0=OC0A disconnected. 1=Toggle OC0A on compare match (p.84)(0 << 4) | // COM0B. 0=OC0B disconnected. 1=Toggle OC0B on compare match (p.85)(2 << 0); // WGM0. PWM mode. 1=phase correct 2=CTC (p.86)TCCR0B = (0 << 7) | // FOC0A.(0 << 6) | // FOC0B.(0 << 3) | // WGM02.(2 << 0); // CLOCK source.OCR0A = TIMER0_PERIOD;OCR0B = TIMER0_PERIOD / 2;TIMSK0 = (0 << 2) | // OCIE0B. Match B Interrupt Enable(1 << 1) | // OCIE0A. Match A Interrupt Enable(0 << 0); // TOIE0. Overflow Interrupt EnableTIFR0 = 0;TCNT0 = 0; // Initialize Timer0 countersei(); // Allow interrupts}void timer2_setup(void) {cli(); // Stop interruptsTCCR2A = (1 << 6) | // OOM2A. 0=OC2A disconnected. 1=Toggle OC2A on compare match (p.128)(2 << 4) | // COM2B. 2=Clear OC2B on compare match (p.129)(1 << 0); // WGM2. PWM mode. 1=phase correct (p.130)TCCR2B = (0 << 7) | // FOC2A.(0 << 6) | // FOC2B.(1 << 3) | // WGM22.(4 << 0); // CLOCK source.OCR2A = TIMER2_PERIOD;OCR2B = TIMER2_PERIOD / 2;TIMSK2 = (0 << 2) | // OCIE2B. Match B Interrupt Enable(0 << 1) | // OCIE2A. Match A Interrupt Enable(0 << 0); // TOIE2. Overflow Interrupt EnableTIFR2 = 0;TCNT2 = 0; // Initialize Timer2 countersei(); // Allow interrupts}void timer_synchronize(void) {cli(); // Stop interruptsGTCCR = (1 << TSM) | (1 << PSRASY) | (1 << PSRSYNC); // halt all timersTCNT0 = 0; // Initialize Timer0 counterTCNT2 = 0; // Initialize Timer2 counterGTCCR = 0; // release all timerssei(); // Allow interruptswhile ((PIND & (1 << PIN_SIGNAL_OUT)) != 0);while ((PIND & (1 << PIN_SIGNAL_OUT)) == 0);TCNT0 = TIMER0_PERIOD * TIMER0_PHASE_ADJUST; // Initialize Timer0 counter}// Timer0 interrupt handlerISR(TIMER0_COMPA_vect) {int16_t adc_value;if (adc_measuring == 1) {// ADC Start ConversionADCSRA |= (1 << ADSC);// Read last conversionadc_value = ADCW;// Accumulate values (10us)adc_acc_inphase += (int32_t) adc_value * SIN_INTEGER;adc_acc_quadrature += (int32_t) adc_value * SIN_INTEGER[level_state_old + SAMPLES_PER_WAVE / 4];// Update next statelevel_state_old = level_state;level_state++;if (level_state >= SAMPLES_PER_WAVE)level_state = 0;adc_samples++;if (adc_samples > SAMPLES_PER_MEASURE) {adc_measuring = 0;}}}// Timer2 interrupt handlerISR(TIMER2_COMPA_vect) {}void debug_pin_pulse(void) {PORTD |= (1 << PIN_DEBUG_OUT);delayMicroseconds(4);PORTD &= ~(1 << PIN_DEBUG_OUT);}Resistor 1 ohm 5% (I have calibrated the board so that this component gives an accurate value):Code:SAMPLE_FREQUENCY = 9090.00 HzMEASURE_SIGNAL_FREQUENCY = 565.00 HzSAMPLE_TIME = 1.00 s1001.3mOhm R0.4mOhm Z_C1001.3mOhm R0.5mOhm Z_C1001.4mOhm R0.4mOhm Z_C1001.2mOhm R0.3mOhm Z_C1001.2mOhm R0.4mOhm Z_C1001.3mOhm R0.4mOhm Z_C1001.3mOhm R0.4mOhm Z_C1001.2mOhm R0.4mOhm Z_C1001.1mOhm R0.3mOhm Z_C1000.9mOhm R0.3mOhm Z_C1000.8mOhm R0.4mOhm Z_C1001.0mOhm R0.4mOhm Z_C1001.0mOhm R0.4mOhm Z_C1000.9mOhm R0.3mOhm Z_C1000.9mOhm R0.3mOhm Z_C1000.9mOhm R0.3mOhm Z_C1000.9mOhm R0.4mOhm Z_C1001.0mOhm R0.4mOhm Z_C1000.7mOhm R0.4mOhm Z_C1000.9mOhm R0.3mOhm Z_C1000.6mOhm R0.2mOhm Z_C1000.9mOhm R0.2mOhm Z_C1000.7mOhm R0.1mOhm Z_C1000.9mOhm R0.2mOhm Z_C1000.7mOhm R0.2mOhm Z_C1000.8mOhm R0.2mOhm Z_C1000.8mOhm R0.2mOhm Z_C1000.8mOhm R0.3mOhm Z_C1000.8mOhm R0.4mOhm Z_C1001.0mOhm R0.3mOhm Z_C1000.7mOhm R0.3mOhm Z_C1000.9mOhm R0.3mOhm Z_C1000.8mOhm R0.3mOhm Z_C1000.7mOhm R0.3mOhm Z_C1000.8mOhm R0.3mOhm Z_C1000.9mOhm R0.3mOhm Z_C1000.8mOhm R0.4mOhm Z_C1000.9mOhm R0.2mOhm Z_C1000.7mOhm R0.3mOhm Z_C1000.9mOhm R0.3mOhm Z_C1000.9mOhm R0.3mOhm Z_C1000.9mOhm R0.3mOhm Z_C1000.6mOhm R0.3mOhm Z_C1000.8mOhm R0.4mOhm Z_C1000.7mOhm R0.3mOhm Z_CCapacitor 1000uF 16V 20%:Code:-0.7mOhm R310.7mOhm Z_C906.69uFarads-0.8mOhm R310.3mOhm Z_C907.67uFarads-0.8mOhm R310.6mOhm Z_C906.91uFarads-1.0mOhm R310.3mOhm Z_C907.80uFarads-1.1mOhm R310.3mOhm Z_C907.90uFarads-1.2mOhm R310.2mOhm Z_C908.01uFarads-1.2mOhm R310.4mOhm Z_C907.49uFarads-1.2mOhm R310.3mOhm Z_C907.87uFarads-1.4mOhm R310.3mOhm Z_C907.79uFarads-1.3mOhm R310.4mOhm Z_C907.46uFarads-1.3mOhm R310.3mOhm Z_C907.76uFarads-1.4mOhm R310.3mOhm Z_C907.71uFarads-1.2mOhm R310.3mOhm Z_C907.75uFarads-1.3mOhm R310.3mOhm Z_C907.92uFarads-1.4mOhm R310.2mOhm Z_C908.22uFarads-1.3mOhm R310.3mOhm Z_C907.94uFarads-1.3mOhm R310.1mOhm Z_C908.40uFarads-1.5mOhm R310.3mOhm Z_C907.85uFarads-1.4mOhm R310.2mOhm Z_C908.20uFarads-1.4mOhm R310.2mOhm Z_C908.20uFarads-1.4mOhm R310.2mOhm Z_C907.98uFarads-1.5mOhm R310.5mOhm Z_C907.36uFarads-1.4mOhm R310.3mOhm Z_C907.77uFarads-1.4mOhm R310.3mOhm Z_C907.85uFarads-1.4mOhm R310.2mOhm Z_C908.01uFaradsInductance 68uH 20%:Code:35.0mOhm R171.1mOhm Z_L48.20uHenrys35.1mOhm R171.1mOhm Z_L48.20uHenrys34.8mOhm R170.5mOhm Z_L48.03uHenrys34.7mOhm R170.6mOhm Z_L48.04uHenrys35.0mOhm R171.0mOhm Z_L48.17uHenrys34.8mOhm R171.0mOhm Z_L48.18uHenrys34.8mOhm R170.8mOhm Z_L48.11uHenrys34.8mOhm R170.7mOhm Z_L48.08uHenrys34.4mOhm R170.2mOhm Z_L47.95uHenrys34.5mOhm R169.9mOhm Z_L47.85uHenrys34.1mOhm R169.7mOhm Z_L47.80uHenrys34.2mOhm R169.8mOhm Z_L47.83uHenrys34.1mOhm R169.7mOhm Z_L47.79uHenrys34.2mOhm R170.0mOhm Z_L47.87uHenrys34.1mOhm R169.9mOhm Z_L47.87uHenrys34.1mOhm R170.0mOhm Z_L47.88uHenrys34.2mOhm R169.9mOhm Z_L47.87uHenrys34.1mOhm R170.0mOhm Z_L47.89uHenrys33.9mOhm R170.0mOhm Z_L47.88uHenrys34.1mOhm R170.1mOhm Z_L47.90uHenrys34.3mOhm R170.1mOhm Z_L47.90uHenrysObviously I have to calibrate the board resistor better so that the capacitor shows its ESR.But I like the preliminary results very much.

author: Picuino
author_rank: Super Contributor
timestamp: May 12, 2024, 04:08:15 pm
content: I think it's time to go further.The initial purpose of this thread was to make a Lock-in Amplifier and for that I have to get a more generic instrument that is capable of generating a square signal and a sine signal of the same frequency. The instrument must samples faster and has a wider bandwidth.Finally the outputs must be continuous and use a low pass filter.I don't really know where to start. Perhaps making a multistage input amplifier so that the gain of each stage is lower and the total bandwidth is higher.

author: Picuino
author_rank: Super Contributor
timestamp: May 12, 2024, 04:12:31 pm
content: I have placed an order for samples with Analog Devices and have been turned down because the shipping address is not a work address.I will try to reorder the samples to have them shipped to work and if I get rejected again I will have to order them from Mouser or Digikey.Components are not cheap, but more expensive is any other hobby. Even going out on a bike.

author: gf
author_rank: Super Contributor
timestamp: May 12, 2024, 07:30:31 pm
content: Quote from: Picuino on May 12, 2024, 03:34:39 pmWhy9090Hz? It must be exactly 565 * 16, and that would be 9040.

author: Picuino
author_rank: Super Contributor
timestamp: May 12, 2024, 08:19:23 pm
content: It is the result of rounding an integer operation. It is already corrected.I have also corrected the working frequency, which could not be that high because the ADC operates at a somewhat lower frequency.The following posts I am going to move them to a different thread that only deals with milliohm meter because in this thread I want to continue with other different setups more related to Lock-in Amplifiers.New thread for milliohm meter:https://www.eevblog.com/forum/projects/milliohm-meter-picuino

author: gf
author_rank: Super Contributor
timestamp: May 13, 2024, 09:07:25 am
content: Quote from: Picuino on May 12, 2024, 04:08:15 pmStart by specifying your requirements in quantitative terms.Which carrier frequency (or frequencies)? How much bandwidth? Which signal levels do you want to measure?

author: Picuino
author_rank: Super Contributor
timestamp: May 13, 2024, 10:07:19 am
content: In this second assembly I want to get just a little bit more. With 100kHz bandwidth I will be satisfied.Signal levels from 100nV (max resolution) to 100mV (full scale max input).

author: Picuino
author_rank: Super Contributor
timestamp: May 13, 2024, 10:55:09 am
content: In this case I want to use a NUCLEO-L412KB board:https://www.st.com/en/evaluation-tools/nucleo-l412kb.htmlWith a STM32L412KB processor (2x 12-bit ADC @ 5 Msps):https://www.st.com/en/microcontrollers-microprocessors/stm32l412kb.htmlhttps://www.st.com/resource/en/datasheet/stm32l412kb.pdfhttps://www.st.com/resource/en/reference_manual/rm0394-stm32l41xxx42xxx43xxx44xxx45xxx46xxx-advanced-armbased-32bit-mcus-stmicroelectronics.pdf

author: gf
author_rank: Super Contributor
timestamp: May 13, 2024, 12:06:03 pm
content: 100nV and 100kHz bandwidth do not go well together.If you aim for only 10dB SNR at a signal level of 100nV, we are talking about 0.1nV/sqrt(Hz)Which opamp do you have in mind?Maybe your rethink and lower your expectations?Note that your current circuit can achieve the low noise levelbecausethe filter has such alow bandwidth.(1Hz equivalent noise bandwidth with 1s measuring time)And what is the desired carrier frequency?

author: Picuino
author_rank: Super Contributor
timestamp: May 13, 2024, 12:12:37 pm
content: In principle I will experiment with the ADC of the board itself. I don't have any external ADC of that speed and I would have to work with an FPGA which I am not familiar with.I will design a bandwidth around 100 khz and see how far the circuit can go.EDIT:The instrumentation amplifier will be the same AD8220 that I already have. I have not started with the design. I will try to use of the samples I have, while I look at new Opamps to place a new order.

author: Kleinstein
author_rank: Super Contributor
timestamp: May 13, 2024, 12:32:50 pm
content: A lockin amplifier has a certain bandwidth for the input signal, that can be relatively large, like up to 100 kHz. That would be the maximum carrier frequency.The other point is the bandwidth after the mixer / demodulator and output fitler. Here the bandwidth is usually relatively low (like 1 Hz or 10 Hz) and thus low bandwidth is what allows for low noise.With a digital implementation the filtering often is a FIR type / sliding average and not necessary a classic 1st / 2nd order low pass.The ADC inside the STM32 is only 12 bit. This can be OK for some signals, but could be limiting with more demanding cases. To at least use most of the resolution one would than want fine (like 1:2) steps for the gain, and not just decades. It can would for a demonstartion / proof of principle, but would not be a really good instrument. AFAIR the early digital lock in amplifiers used 16 and 18 bit ADCs.So far the example use as a mohm meter is using a carrier frequency that is generated from the same circuit. This is a common and easy case. A general lock-in would also allow an external signal (e.g. from a mechanical chopper). If one wants a mechanical chopper, one could still consider starting with the electronic clock and have a PLL type speed regulator for the motor.

author: gf
author_rank: Super Contributor
timestamp: May 13, 2024, 12:45:49 pm
content: For the 3rd use case mentioned herehttps://www.eevblog.com/forum/projects/homebrew-lock-in-amplifier/msg5475103/#msg5475103it rather looks like the bandwidthafter demodulationis expected to cover audio (i.e. at least 4 kHz, better 10+ kHz).This rules out a narrow 1-10 Hz filter.EDIT: And I wonder which kind of modulation the vibrations of the glass apply to to the excitation signal. Really AM? Isn't rather time of flight modulated, resulting in FM (Doppler effect)?

author: Picuino
author_rank: Super Contributor
timestamp: May 13, 2024, 12:59:44 pm
content: Making a Lock-in Amplifier similar to those currently on the market is beyond my intentions.It would be very difficult and expensive, more so than buying a second hand LIA.As the STM32 has 2 ADCs, one of them can be used to sample the reference signal and the other ADC to sample the input signal.

author: Picuino
author_rank: Super Contributor
timestamp: May 13, 2024, 05:08:30 pm
content: Quote from: gf on May 13, 2024, 12:45:49 pmWith the low frequency of sound and the associated low velocity of motion, the doppler shift would be almost impossible to detect. I am sure that in this case what needs to be detected is AM modulation.

author: Picuino
author_rank: Super Contributor
timestamp: May 13, 2024, 05:12:07 pm
content: I have to study the detector. In principle I am going to use a simple infrared led as detector and another infrared led (the same type of led) with output through a couple of small holes in series to collimate the light beam.A laser would be more efficient and collimated, but I don't have one and I'm a little afraid to work with infrared lasers.As I am going to test with a small crystal on my own table, I don't think there will be any problems.

author: Picuino
author_rank: Super Contributor
timestamp: May 13, 2024, 06:46:30 pm
content: Quote from: gf on May 09, 2024, 09:21:40 pmI have installed Octave and I can't see the graphs.This is the error I get:Code:>> sin_integererror: parse error near line 15 of file C:\Users\User\sin_integer.msyntax error>>> P = abs(Y).**2;^>>

author: gf
author_rank: Super Contributor
timestamp: May 13, 2024, 07:47:53 pm
content: Quote from: Picuino on May 13, 2024, 06:46:30 pmI'm running an older version of Octave.Seems that operator .** was deprecated in the meantime?https://savannah.gnu.org/bugs/?60882Try .^ instead.I also noticed that you don't place the first sample at phase 0, but at pi/SAMPLES_PER_PERIOD.I have updated the script accordingly.Code:nsamples = 16;MAXMUL = 127;% sampling phase = 0% x = repmat(exp(-1j*[0:nsamples-1]/nsamples*2*pi)',1,MAXMUL);% sampling phase = pi/nsamplesx = repmat(exp(-1j*[0:2*nsamples-1]/2/nsamples*2*pi)(2:2:end)',1,MAXMUL);y = zeros(nsamples,MAXMUL);for i=1:MAXMUL% quantize to 2*i+1 levelsy(:,i) = floor(0.5+0.5i+x(:,i)*i)/i;endY = zeros(nsamples,MAXMUL);for i=1:MAXMULY(:,i) = fft(y(:,i))/nsamples;endP = abs(Y).^2;Pfund = P(2,:);harmidx = [ 1 3:nsamples ];Pharm = sum(P(harmidx,:));Pspur = max(P(harmidx,:));plot(10*log10(Pharm./Pfund),";THD;");hold onplot(10*log10(Pspur./Pfund),";SFDR;");title(sprintf("%d samples/period",nsamples))grid onhold offylabel("dB")xlim([20 MAXMUL])ylim([-65 -30])

author: Picuino
author_rank: Super Contributor
timestamp: May 13, 2024, 08:22:20 pm
content: Now it works.I have seen that the best (low) multiplier is 61.

author: Picuino
author_rank: Super Contributor
timestamp: May 13, 2024, 08:23:15 pm
content: I'm going to copy the script in the other thread, with your permission.

author: gf
author_rank: Super Contributor
timestamp: May 13, 2024, 08:25:05 pm
content: Quote from: Picuino on May 13, 2024, 08:22:20 pmAt least the best one below 64 (which is only imporant if you still want to optimize the multiplication performance).

author: Picuino
author_rank: Super Contributor
timestamp: May 17, 2024, 05:33:40 pm
content: I already have the STM32 microcontroller sampling the ADC at 5333kHz.The problem I have now is the large computational power needed to multiply two streams from the two ADCs at more than 5MHz in real time and implement, in addition, an IIR or FIR filter for the output.And all that for the microprocessor to do the same as a simple AD630, but worse.I think you should start with a much lower ADC speed and go up from there.I would also have to be clear about extra functions that the microcontroller is capable of to justify its use.

author: Kleinstein
author_rank: Super Contributor
timestamp: May 17, 2024, 06:11:53 pm
content: Usually one does not want an exact copy of the ref. signal to do the multiplication. One usually wants at least a constant amplitude for the reference signal.The operation of the AD630 is different: it only does a +-1 multiplication, not a true multiplication. The multiplication with a clean sine can suppress the other harmonics.To reduce the computational effort one can initially apply a little low pass filtering, kind of part of the anti aliasing. Even just averaging 2 readings can reduce the computational effort to nearly half.It can make sense to run the ADC at a little less than it's maximum speed. The max speed is kind of set by the noise / INL starting to get worse from there.

author: Picuino
author_rank: Super Contributor
timestamp: May 18, 2024, 10:04:37 am
content: I didn't realize that the STM32 board I am currently using (NUCLEO-L412KB) does not have a DAC.I have placed an order for another board, a NUCLEO-G431KB, which runs at more than twice the speed (170MHz) and has 2 ADCs and 2 buffered DACs.With them I can generate a reference output signal (1 DAC) and sample two signals, the reference output signal and the one coming from the DUT.By the way I have also bought some instrumentation amplifiers INA826 from Texas Instruments, which work at 3.3V although they are a bit slow.For further amplification I have bought precision opamps OPA2325, also from Texas Instruments.EDIT:https://www.st.com/resource/en/datasheet/stm32g431kb.pdfhttps://www.ti.com/lit/ds/symlink/ina826.pdfhttps://www.ti.com/lit/ds/symlink/opa2325.pdfOther amplifiers that I have not purchased, but should consider (recommended by TI):https://www.ti.com/lit/ds/symlink/ina333.pdfhttps://www.ti.com/lit/ds/symlink/opa322.pdfhttps://www.ti.com/lit/ds/symlink/opa320.pdfhttps://www.ti.com/lit/po/sbat003a/sbat003a.pdf

author: gf
author_rank: Super Contributor
timestamp: May 18, 2024, 11:46:11 am
content: Quote from: Picuino on May 17, 2024, 05:33:40 pmThe multiplication is not the problem. AFAIK, the SMLAL instruction takes only one cycle. And gcc does recognize the patternCode:int64_t c;int32_t a, b;c += (int64_t)a * b;and emits a SMLAL instruction. The problem is more the necessary glue code to shuffle the data around.Note that the clock frequency is only 5x higher than the clock frequency of your AVR, but you want a500xhigher data rate. And now you expect that the more efficient instruction set of the ARM processor can compensate the remaining gap of factor 100x? No, it cannot.Nevertheless I think that it may be feasible to run the algorithm of your Milliohm meter (single channel) at 4 MSa/s, if you do it smart. Collect the samples with double buffered DMA, and process all samples in a received DMA buffer at once. At that speed it is no longer possible to process an interrupt for each sample. That's ways too much overhead per sample.If you lookhere, you can see that the innermost loop (label .L2, which is executed for each sample) has 11 instructions, and I think that's 14...17 clock cycles, and the code outside the loop (which uses significantly more cycles) runs only once for 256 samples, so let's assume ~1 extra cycle per sample. At 80 MHz clock and 4 MSa/s, you can spend 20 cycles/sample. So I think that's in the ballpark.And if you decimate before demodulation (seehere), as Kleinstein suggested, then the overhead per sample becomes even smaller. Here, the loop at label .L2 now processes 8 samples at each iteration.QuoteI'm unsure if the amount of RAM is sufficient. It depends of course on the exact algorithms you want to run at the end. It may be possible to put constant tables in flash, but I'm not sure if data access to flash also has zero wait states.Btw, see also:https://wiki.st.com/stm32mcu/wiki/Getting_started_with_ADChttps://www.st.com/resource/en/application_note/an2834-how-to-optimize-the-adc-accuracy-in-the-stm32-mcus-stmicroelectronics.pdf

author: Picuino
author_rank: Super Contributor
timestamp: May 18, 2024, 12:09:28 pm
content: In this case I want to make a more generic Lock-in amplifier.The routine I am thinking of is something like this:* Fill with DMA two 256-sample (16bit type) buffers coming from the two ADCs.* With the half-transfer buffer interrupt, multiply the two half buffers into another (32bit type) half mult-buffer.* With the full-transfer buffer interrupt, do the same with the other half of the buffer.* With another timer interrupt, sum all the mult-buffer values and pass them through an IIR filter to deliver the value to the DAC output.The result should do, by software, the same as an AD630 plus a low-pass filter.

author: Picuino
author_rank: Super Contributor
timestamp: May 18, 2024, 12:16:54 pm
content: Analog Devices has a chip for digital synchronous detection, the ADA2200, which is now obsolete.https://www.analog.com/media/en/technical-documentation/data-sheets/ADA2200.pdfIt may be interesting to study it.

author: gnuarm
author_rank: Super Contributor
timestamp: May 18, 2024, 09:30:04 pm
content: This very much sounds like a problem for an fpga. You won't be constrained by the processing speed of your CPU, and you can make the multipliers work with any data size you wish. You will need to learn a new language. More importantly, you will need to learn to think in terms of a multiprocessing language. In fpgas, everything happens in parallel, or much of it, anyway.

author: Picuino
author_rank: Super Contributor
timestamp: May 19, 2024, 07:22:10 am
content: I have two FPGA development boards. A Cyclone II and an Alhambra Zum, but I have never run them.Keep in mind that my intention is to make a homebrew lock-in amp and working with FPGAs doesn't seem very compatible. I would need to add external ADCs with parallel connections, which would probably involve designing a dedicated PCB and soldering components with difficult encapsulations. It would have a lot of power, but it certainly wouldn't be homebrew.In the world of microcontrollers there are all kinds of models from the most basic 8-bit to the most powerful 32-bit models. There are options from THT and also SMD packages with easy to solder pins, to the smallest and most difficult.In the FPGA world, however, I don't see models in between. All I find are unwieldy models for home assembly.

author: Kleinstein
author_rank: Super Contributor
timestamp: May 19, 2024, 08:10:00 am
content: For a simple solution the µC with 12 bit ADCs is ok. The computation power should not be that much of an issue. After all even with 2x5 MSPS the CPU should still be able to keep up. It should not be much more than 1 MAC operation and 1 table lookup per sample. The speed would mainly effect the upper frequency limit and many application for a lock-in amplifier are lower frequency, like 100 Hz-1 kHz.There is also the alternative way of first average the time domain data and do the muliplication with the sine (or other function for correlation) only at the lower output rate.The more limiting factor is more the resolution of the ADC, so that the headroom for noise is limited. It can be OK for not so noisy signals, but one application for a lock-in amplifier is with signals that are burried in a lot of noise. AFAIR the lock in amplifier I use a lot had settings for the headroom of 40 and 60 dB. So in the higher reserve setting the signal would be something like 1000 times smaller than the input dynamic range. So an external ADC would not be such a bad idea for a higher performance lockin. It could still make sense to use a µC with an external ADC.The computational more demanding case could be if one wants a really external reference and thus a PLL for the reference part. A software PLL is possible and has some advantages over a hardware solution as it is easier to adapt over a large frequency range (e.g. changing the loop filter).

author: gnuarm
author_rank: Super Contributor
timestamp: May 19, 2024, 09:14:22 am
content: Quote from: Picuino on May 19, 2024, 07:22:10 amfpgas are available in quad flat packs, often 100 pin. You can fashion DAC by using a very fast clock and generating a pulse train with a high speed clock. The modulation would be pulse density and use a simple RC filter to remove the higher frequency artifacts. If you need limited resolution, but higher sample rates, you can use multiple output pins with an R/2R ladder. N bits uses N output lines.ADC is similar, but the effective sample rate would need to be moderately low. There's a sample rate/resolution trade off. 15 bits at 48 kSPS is realistic using a clock rate of ~32 MHz (if I remember the math correctly). I'm not sure what sample rates you require. An effective sample rate above 100 kSPS would likely need a separate ADC chip.I will say, I don't know what you mean by "homebrew". Can you explain what this means to you?

author: Picuino
author_rank: Super Contributor
timestamp: May 19, 2024, 10:22:20 am
content: For example the other experiment I have done I consider it homebrew.It uses an Arduino Nano as microcontroller (Atmega328P) easy to get and easy to program. As analog part I have used 8-pin SMD chips (MSOP-8) that can be soldered with soldering iron (no hot air station needed). I will also test an INA826, much cheaper than AD8220 and with an even simpler package (SOIC-8).I consider FPGAs far from a homebrew project for now.In any case, I have achieved acceptable results with a simple Atmega328 at 16MHz and with 10bits ADC at 10ksps ADC. With the STM32 I should be able to get something practical and better (170MHz and 12bit ADC at 5Msps).

author: gf
author_rank: Super Contributor
timestamp: May 19, 2024, 10:29:31 am
content: Quote from: Kleinstein on May 19, 2024, 08:10:00 amSome STM32 models (including the STM32L412xx) have a hardware oversampler that does exactly this, so you can benefit from the resolution enhancement and SNR reduction without overstraining the DSP code with too high a sample rate.

author: Picuino
author_rank: Super Contributor
timestamp: May 19, 2024, 10:44:32 am
content: I'm actually going to switch microcontroller to an STM32G431KB, which does have DACs and runs at double CPU speed.This other micro also has that function, which I will study to see if it is useful. The problem is that I wanted to be able to sample 100kHz signals with several samples and that is difficult to do below 1Msps.That microcontroller I want to run at 160MHz and the ADC clock speed at 80MHz (1/2), which produces 5333Msps.Maybe there is enough speed left to decimate 4 samples in one and reduce the sampling speed to 1.33Msps.

author: gnuarm
author_rank: Super Contributor
timestamp: May 19, 2024, 10:51:51 am
content: Quote from: Picuino on May 19, 2024, 10:22:20 amSorry, I have no idea why an MCU board bought off the Internet is "homebrew" and an fpga board bought off the Internet is not homebrew.As to the MCU with ADCs, remember that when run at high sample rates, the lower one or two bits are essentially "noise". Sounds like you don't really know what you need and are just toying around trying to get something to work. Good luck.

author: Picuino
author_rank: Super Contributor
timestamp: May 19, 2024, 11:24:25 am
content: Yes, I am playing with the STM32 with two targets. To know how to use it (I had not implemented it before) and to make with that microcontroller a Lock-in amplifier that is able to do something practical.It is not a job where I have to achieve a certain goal. I am a Technology teacher and I just try to keep up to date myself and make some practical instrument for demonstrations in class.EDIT:I don't rule out running one of my FPGA boards or another more modern one. But I have to go in parts. For now I'm too busy with the STM32.

author: Picuino
author_rank: Super Contributor
timestamp: May 19, 2024, 11:59:10 am
content: Of course, another goal is to make easy replicable projects by anyone else. FPGAs is a little bit more difficult than uC.

author: gf
author_rank: Super Contributor
timestamp: May 19, 2024, 12:23:42 pm
content: Quote from: Picuino on May 19, 2024, 10:44:32 amThe hardware oversampler can do this without any software overhead. It samples at 5.33 MSa/s and delivers decimated samples at 1.33 MSa/s to the processor. However, the summation of 4 samples each corresponds to a 1st order boxcar filter, and that's not a proper decimation filter. This implies that the (analog) anti-aliasing filter in front of the ADC must be steeper in order to provide sufficient attenuation for frequencies >= 1233 kHz (unless you can ensure otherwise that no interfering signals >= 1233 kHz are present at the ADC input). For example, a 4th order Butterworth with fc=160kHz gives >70dB attenuation for frequencies >= 1233 kHz, while 100kHz still fall into the passband, being attenuated only about 0.1dB.

author: Kleinstein
author_rank: Super Contributor
timestamp: May 19, 2024, 01:06:21 pm
content: An anti aliasing filter is a good idea with a SAR or similar ADC. The main reason would be avoiding extra noise, not so much a strong interference signal. For the noise suppresion there is no need for the AA fitler to get very strong attenuation, just 20 dB already do a lot for the noise. A strong interference is often more an issue at low frequencies, with mains hum and the 2nd harmonic.Especially with the limited ADC resolution filtering at the low end or notches can make sense.Averaging 4 samples is not a perfect filter, but it is still not that bad compared to a more conventional digital filter.Using a similar sampling and math also on the ref. channel is an unusual solution for a lock-in amplifier. It could be a first step to do a software PLL, though for the ref. side a simple on/off digital input is often sufficient.Digitizing 2 inputs and directly multiply and average would be part of getting the cross-correlation function. This is related to a LI and may be an interesting addition, but it is not the normal LI function.Many on the articles decribing LI operation are a bit short on the reference part and often asume quite some ideallization there. For the ref. side there are different cases, depending on the experiment / setup:1) generate the ref. / driving signal - this is the easiest case2) generate the ref. frequency and PLL drive a chopper: the PLL / regulator part could be external3) read an external ref signal and use it directly for the PSD / detection. This may need a way to correct for duty cycle changes4) read an external ref signal and use a PLL to generate a clean ref. signal for the PSD.5) read an external ref signal and use a PLL to generate a clean ref., with a delay for the input data to compensate for the group delay of the PLLThere are cases where one would read a 2nd signal, e.g. to compensate for intesity variations of a light source. The 2nd channel tends to be less demanding, with often a better S/N ratio. The amplitude ref. is however often separate from a frequency/phase reference.

author: Picuino
author_rank: Super Contributor
timestamp: May 21, 2024, 05:18:05 pm
content: Interesting documentation about digital filters and Lock-in Amplifiers:https://www.lakeshore.com/docs/default-source/default-document-library/ab1-00---fir-vs-iir-filtering-for-faster-lock-in-measurements.pdfhttps://www.advsolned.com/difference-between-iir-and-fir-filters-a-practical-design-guide/

author: zrq
author_rank: Frequent Contributor
timestamp: May 21, 2024, 06:33:13 pm
content: I just did some very quick and dirty tests using labview and my 3rd gen Focusrite 2x2 sound card to work as a lock-in ampliifer, apparently one can easily get >110 dB dynamic range at 311Hz and 1112 Hz carrier frequency for 1 Hz IF bandwidth, characterized by comparing the demodulated signal strength when (Line) input is open and when the loopback signal reaches full scale. And it appears to be limited by the crosstalk from the internal generator (using the headphone output), particularly at higher carrier frequencies, so by doing a simple "zeroing" it can be even better.