Sigma-delta using arduino - Page 1
Forum section: electronics / projects

author: pitagoras
author_rank: Regular Contributor
timestamp: March 18, 2017, 01:48:32 am
content: Hello,Based on this application note (Fig 2), I've put together a minimal sigma-delta converter.http://ww1.microchip.com/downloads/en/AppNotes/00700a.pdfI used an arduino mini pro 5V (which runs at 16MHz) and is barely enough for 50Khz sample rate.Evaluating performance would surely result in "bad" as it uses the internal Vref and the internal comparator.My next take will be a dual-slope using external integrator and comparator.I share with you the code which I expect is self explained. If anyone interested wants more detail, just ask.Code:/*Sigma-Delta converter using internal analog comparator.Inspired by http://ww1.microchip.com/downloads/en/AppNotes/00700a.pdfArduino Timer interrupts http://www.instructables.com/id/Arduino-Timer-Interrupts/*/// PIN constantsconst int AIN_POSITIVE = 6;const int AIN_NEGATIVE = 7;const int INTEGRATOR = 8;// Sampling constantsconst long SAMPLE_FREQUENCY = 50000 ; //Hertzconst int RESOLUTION = 13; //bitsconst long SAMPLES_TO_GET = 1 << RESOLUTION;//volatile boolean sample_available = false;long counter = 0;long result = 0;long result_snap = 0;void setup() {Serial.begin(9600);pinMode(LED_BUILTIN, OUTPUT);// ADC setuppinMode(INTEGRATOR, OUTPUT); // output of 1-bit DACpinMode(AIN_NEGATIVE, INPUT); // negative input of analog comparatorACSR |= (0 << ACD); // Analog Comparator: EnabledACSR |= (1 << ACBG); // Analog Comparator Bandgap onACSR |= (1 << ACO); // Analog Comparator Output: OnACSR |= (1 << ACI); // Analog Comparator Interrupt FlagACSR |= (0 << ACIE); // Analog Comparator Interrupt EnableACSR |= (0 << ACIC); // Analog Comparator Input Capture: DisabledACSR |= (1 << ACIS1); // Comparator Interrupt on Rising Output EdgeACSR |= (1 << ACIS0); // Comparator Interrupt on Rising Output Edge// Sampling Timer setupTCCR0A = 0;// set entire TCCR0A register to 0TCCR0B = 0;// same for TCCR0BTCNT0 = 0;//initialize counter value to 0// set compare match register for SAMPLE_FREQUENCY incrementsOCR0A = (16000000 / (8 * SAMPLE_FREQUENCY)) - 1; // must be < 256TCCR0A |= (1 << WGM01); // turn on CTC modeTCCR0B |= (0 << CS02) | (1 << CS01) | (0 << CS00); // Set CS02 CS01 and CS00 bits for 8 prescalerTIMSK0 |= (1 << OCIE0A); // enable timer compare interruptsei();}ISR(ANALOG_COMP_vect) {}// SAMPLEISR(TIMER0_COMPA_vect) {byte analog_comparator_output = (ACSR >> ACO) & 1;digitalWrite(INTEGRATOR, analog_comparator_output);result += (1- analog_comparator_output);counter++;if (counter >= SAMPLES_TO_GET) {counter = 0;result_snap = result;result = 0;sample_available = true;digitalWrite(LED_BUILTIN, HIGH);}}void loop() {{if (sample_available) {Serial.print("Value: ");Serial.println(result_snap, DEC);digitalWrite(LED_BUILTIN, LOW);sample_available = false;}} // end of loop}

author: NiHaoMike
author_rank: Super Contributor
timestamp: March 18, 2017, 02:06:08 am
content: Try the high performance I/O library.https://github.com/projectgus/digitalIOPerformanceIgnore the note. It just means that it is only compatible with the "classic" Arduinos.I also suggest upgrading to a 32 bit processor if you want to do fast 32 bit arithmetic. (At least you didn't use floating point on an Arduino like one of my classmates did...)

author: Kleinstein
author_rank: Super Contributor
timestamp: March 18, 2017, 09:30:15 am
content: The sampling frequency is rather low at 50 kHz - I would expect that a higher speed is possible. I don't know if the Arduino environment adds another interrupt in the background - if so this should be definitely avoided.With only a 13 Bit or so result, there is not need to use 32 Bit numbers - on an 8 Bit µC 16 bit numbers can be about twice as fast (possibly even faster as the µC registers might be all it takes). There are a few other possible optimizations for higher speed.Another point is that there is some jitter in the interrupts due to completion of instructions. This about 3-4 clock cycle uncertainty might not be that important at the low 50 kHz clock, but it will set a limit at higher speed. It gets really bad if there is an occasional other interrupt. One might get around this, by using timer hardware (or similar) to set the output for exactly the right time - slight jitter in the comparator sampling is not that bad as this error will not accumulate.

author: nuno
author_rank: Frequent Contributor
timestamp: March 18, 2017, 11:48:32 am
content: Quote from: Kleinstein on March 18, 2017, 09:30:15 amIt has (ticking at 4us), but pitagoras is reconfiguring a timer interrupt, I don't know from the top of my head if it is the one used by the Arduino environment or not.pitagoras, you're doing nothing on the comparator interrupt, you can disable it. The status bits should still reflect the comparator's state, if I recall correctly (that's how all peripherals usually work). But if you insist in having the comparator's interrupt for some reason, there's a special MACRO that makes an interrupt as short as possible:EMPTY_INTERRUPT(<interrupt name>)But I think you don't want to let the comparator issue an interrupt dor another reason. If your' signal "wiggles" a little around the transition point, you can have bursts of interrupts.You can also consume less power by using sleep in the main loop. In the lightest mode, sleep will unblock whenever there's an interrupt, so it will unblock as soon as execution leaves the timer interrupt - the perfect time to get the new sampleThanks for the sharing!

author: amspire
author_rank: Super Contributor
timestamp: March 18, 2017, 11:48:54 am
content: This is just not the method that suits the Atmega. It can run PWM flawlessly at a 16MHz PWM bit clock, and when you change the PWM value, it is not updated till the start of the next PWM cycle.So instead of a single bit changing every 50uS, you can change a 256bit PWM value every 16us. That is an order of improvement of a factor of 800, and you also eliminate the jitter problem.Or if an interrupt every 16us is to short, you can have a 10 bit PWM updated every 64us. Or a 12 bit PWM updated every 256us.Richard

author: Kleinstein
author_rank: Super Contributor
timestamp: March 18, 2017, 12:04:39 pm
content: Using sleep in the main loop is a good idea - not just for saving power. It also eliminates the interrupt jitter problem, as there will be always the same delay for the interrupt to start.Other interrupts should be blocked of cause - they can upset the timing.

author: b_force
author_rank: Super Contributor
timestamp: March 18, 2017, 12:08:01 pm
content: What is the benefit of using delta-sigma modulation besides PWM?(in a practical sense)

author: nuno
author_rank: Frequent Contributor
timestamp: March 18, 2017, 12:13:47 pm
content: Quote from: Kleinstein on March 18, 2017, 12:04:39 pmGood observation

author: amspire
author_rank: Super Contributor
timestamp: March 18, 2017, 12:20:28 pm
content: Quote from: b_force on March 18, 2017, 12:08:01 pmIt is no longer delta-sigma. That is the point - delta-sigma is very simply implemented in hardware - it just is very inefficient for Atmega processors because the CPU cannot do it as well as the simple hardware. If you still just want to use the comparator, you will end up with feedback that will ring a little, but at an amplitude less then the delta-sigma ripple. Probably just add a little bit of intelligence to the PWM increments and decrements, so that when the comparator starts reversing every PWM cycle, gradually reduce the PWM change size.At a first attempt, I would leave the code basically the same as the Delta-Sigma, but every time the comparator reverses, decrease the PWM increment or decrement by a factor of 2. Every time the comparator does not reverse, increase the PWM increment or decrement by a factor of 2. That should work fine.I guess it depends on what you are trying to do. If you are using this as a way to A/D a DC voltage, this method would work. If the goal is to get a delta-sigma output stream to track a very slowly changing voltage, then the original method would sort of work.Because the original post was suggesting a dual slope A/D for a second attempt, I assume the goal is to measure a DC voltage.

author: pitagoras
author_rank: Regular Contributor
timestamp: March 18, 2017, 10:12:16 pm
content: Quote from: amspire on March 18, 2017, 12:20:28 pmA use for this project is self-educational in ADC related topics, specially precision stuff.But since you commented hereI must say that another goal is a digital control for a linear PSU, both providing output voltages for CV and CC (via the high-res PWM ideas you mentioned in your PSU design thread!) and measuring actual volts and current output.Would be nice to keep external components minimal.I am well aware that this is probably too ambitious for a single arduino. But in my defenseI will say that looooong time ago (yes I'm old) I programmed a 6805 to control a PABX and it was really extreme. Timing in interrupts was critical, as everything were software controlled: the generation of tones, the detection of (rotary) dialing... so it is the kind of things that I used to mess with.I will try all the ideas suggested to tune the solution as I don't know arduino in deep yet.

author: Kleinstein
author_rank: Super Contributor
timestamp: March 19, 2017, 10:06:57 am
content: Sigma delta modulation can be very useful when using PWM as an DAC. This is using a relatively fast PWM and modulate the PWM value to get a higher resolution.Trying the get a higher resolution ADC by SD in software is tricky. It could be done, but it tends to need external HW (e.g. OP) and might have the µC busy. I one did it with an AVR (tiny26 would also run on newer tiny261) - using the ADC for feedback and PWM. It ended up at 16-18 Bits of effective resolution (1 Hz reading) and the µC busy, just for the ADC and sending out the result via UART. Not bad, but still not as good as the MCP3421. The SD ADC chip is SOT23-6 size, includes a reasonable quality reference and does not cost much more than the µC.If one would like to get higher resolution with an µC controlled ADC, the more viable way is likely an multi-slope ADC like used in DMMs. Something like the ADC of the HP34401 (also used in some HP power supplies) might be a starting point. For just a supply I would still prefer a SD ADC chip.

author: Fungus
author_rank: Super Contributor
timestamp: March 19, 2017, 10:09:19 am
content: Quote from: nuno on March 18, 2017, 12:13:47 pmThe ATmega even has a special sleep mode that wakes up the chip when an ADC operation is complete.

author: amspire
author_rank: Super Contributor
timestamp: March 19, 2017, 01:00:23 pm
content: Quote from: pitagoras on March 18, 2017, 10:12:16 pmThe Arduino could do the measurement of voltage and current, but it cannot manage the control of voltage and current well. Basically, to get a high A/D resolution means slowing down the measurement bandwidth to perhaps 100Hz. You need more like 10KHz A/D bandwidth for decent regulation. Much better off with opamps doing the regulation. It is possible for the Arduino to do a fabulous job at providing the reference voltages for the opamps to use for voltage and current regulation.

author: nuno
author_rank: Frequent Contributor
timestamp: March 19, 2017, 01:07:54 pm
content: Quote from: Kleinstein on March 19, 2017, 10:06:57 amATtiny26(1) is special, it has an internal PLL for counter clock generation that can do 64MHz.

author: pitagoras
author_rank: Regular Contributor
timestamp: March 19, 2017, 02:50:22 pm
content: Quote from: amspire on March 19, 2017, 01:00:23 pmYes, that is the idea, provide cc and cv control voltajes. I will be publishing everything but it is still paperware..

author: amspire
author_rank: Super Contributor
timestamp: March 19, 2017, 08:46:40 pm
content: I started a thread several years ago demonstrating that you could use Atmel pwm to produce two pwm analog outputs to 23 bit monotonic resolution, and with care in the design, a lot of accuracy. Just typing this on my phone at the moment, but I can dig up the thread later if you are interested. Richard.

author: Kleinstein
author_rank: Super Contributor
timestamp: March 19, 2017, 09:56:33 pm
content: there are two way to get high resolution DAC function through PWM:1) PWM and SD type modulation. The speed is limited by the clock, so 20 bit resolution for 1 MHz and 1 Hz period - though different from normal PWM low amplitude at the low frequency.2) There is also the option to combine 2 PWM channels, with one for the higher bits and a second scaled down for the lower ones. The scaling factor only needs to be accurate for the extra bits to be gained. Still the PWM for the coarse part needs to be accurate for the full resolution. Some higher quality calibrators work this way. A µC is good way to generate the PWM signals with low effort. It still take accurate switches and the filter.So something like 16 Bit coarse PWM + something like 8 Bits from scaled down PWM to get a 24 Bit resolution should be possible.

author: pitagoras
author_rank: Regular Contributor
timestamp: March 19, 2017, 10:43:15 pm
content: Quote from: amspire on March 19, 2017, 08:46:40 pmThere is a lot about this in the PSU thread. Or there is something more?In any case I'm interested.

author: amspire
author_rank: Super Contributor
timestamp: March 19, 2017, 11:05:46 pm
content: Quote from: pitagoras on March 19, 2017, 10:43:15 pmThat thread basically covers it:https://www.eevblog.com/forum/projects/general-purpose-power-supply-design-7488/msg106890/#msg106890is probably where I first explained it well.If you skip through the thread, you will find that a number of us got pretty remarkable results both with the Atmega's and the Atiny's. This is a case where the Atmels seem way superior to the PIC processors. The Atmel processors are capable of very low jitter edge timing, and so the accuracy is mostly dependant on the other parts you use - such as the reference and buffer opamp.

author: Fungus
author_rank: Super Contributor
timestamp: March 20, 2017, 11:33:04 am
content: Quote from: nuno on March 19, 2017, 01:07:54 pmIt's notthatspecial. Lots of Atmel chips have this ability, eg. the Tiny85.The timer's only 8 bits so I'm guessing you generate the pulse over several complete cycles and use the CPU to reprogram it when it crosses zero.Maybe you could combine the output of two timers using an OR gate to get completely hardware pulse generation and send the chip to sleep for most of the pulse to reduce noise.

author: amspire
author_rank: Super Contributor
timestamp: March 20, 2017, 11:45:03 am
content: Quote from: Fungus on March 20, 2017, 11:33:04 amOne of the things I discovered that by using an 8 bit counter and varying it each cycle by 1 bit to give the exact correct average voltage, you get signifigantly better resultant accuracy then if you used a bigger counter - like a 16 bit. But the speed of the Atiny PWMs versus cpu speed limits the cycles available to update the counter every PWM cycle. You probably end up with better results with an Atmega168 or similar.

author: Fungus
author_rank: Super Contributor
timestamp: March 20, 2017, 12:53:08 pm
content: Quote from: amspire on March 20, 2017, 11:45:03 amThe compare registers are staged/latched internally so you get 64 clock cycles to update them. That should be enough...

author: pitagoras
author_rank: Regular Contributor
timestamp: April 10, 2017, 02:19:39 am
content: I think I should post an update on my progress.Following the ideas of modulating pwm with sigma-delta I implemented the following code using the same circuit as the original paper.Code:// PIN constantsconst int AIN_POSITIVE = 6;const int AIN_NEGATIVE = 7;const int PWM_OUT_2A = 11;const int PWM_OUT_2B = 3;const int CONTROL = 4;// Sampling constantsconst unsigned long SAMPLES_TO_GET = 80;//volatile boolean sample_available = false;int sample = 0;unsigned long result = 0;unsigned long result_snap = 0;int pwm_output = 0;int pwm_delta = 1;byte previous_analog_comparator_output = 0;void setup() {Serial.begin(9600);pinMode(LED_BUILTIN, OUTPUT);pinMode(PWM_OUT_2B, OUTPUT);pinMode(CONTROL, OUTPUT);// COMPARATOR SETUPpinMode(AIN_NEGATIVE, INPUT); // negative input of analog comparator// Set comparatorACSR |= (0 << ACD); // Analog Comparator: EnabledACSR |= (1 << ACBG); // Analog Comparator Bandgap onACSR |= (1 << ACO); // Analog Comparator Output: OnACSR |= (0 << ACI); // Analog Comparator Interrupt FlagACSR |= (0 << ACIE); // Analog Comparator Interrupt DISABLEACSR |= (0 << ACIC); // Analog Comparator Input Capture: DisabledACSR |= (1 << ACIS1); // Comparator Interrupt on Rising Output EdgeACSR |= (1 << ACIS0); // Comparator Interrupt on Rising Output EdgeTCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);TCCR2B = _BV(CS20); // NO PRESCALERTIMSK2 |= (1 << TOIE1); // enable timer overflow interruptsei();Serial.print("HELLO");}byte control = LOW;//// SAMPLEISR(TIMER2_OVF_vect) {control = !control;digitalWrite(CONTROL, control);byte analog_comparator_output = (ACSR >> ACO) & 1;if (analog_comparator_output != previous_analog_comparator_output) {pwm_delta *= 2;if (pwm_delta > 64) pwm_delta = 64;} else {pwm_delta /= 2;if (pwm_delta < 1) pwm_delta = 1;}previous_analog_comparator_output = analog_comparator_output;if (!analog_comparator_output) {pwm_output -= pwm_delta;} else {pwm_output += pwm_delta;}if (pwm_output > 255) pwm_output = 255;if (pwm_output < 0) pwm_output = 0;result += pwm_output;OCR2B = pwm_output;sample++;if (sample >= SAMPLES_TO_GET) {sample = 0;result_snap = result;result = 0;sample_available = true;digitalWrite(LED_BUILTIN, HIGH);}}void loop() {{if (sample_available) {Serial.print(result_snap*2200/(SAMPLES_TO_GET*255));Serial.print("\t");Serial.println();digitalWrite(LED_BUILTIN, LOW);sample_available = false;}} // end of loop}As the arduino has an internal reference of 1.1V, the PWM output should swing from 0 to 2.2V thus I've put a divider with 2k2-2k7 resistors in the pwm output.The integrator +differentiator is the same as the OP paper, two 47K resistors plus a 100nF, giving tau=2.35mSIn theory I should be able to get 2048 counts by using 8 samples, but the result in this case is quite noisy.Averaging 80 samples the output is also quite noise and glitchy.I attach the graph of the serial plotter for this case.I cannot determine why the glitchs happen, probably I should have a moving average instead of reseting every 80. Also probably I should use another strategy for updating the PWM. A next test would be using a high resolution PWM output as per Richard's DAC post.As the code shows, I scaled the output to 2200mV, but the readings are not good at all compared with the multimeter. I calibrated them (the actual maximum is 2247mV) but still it's very non-linear (from 1V to 2.2V it's ok, the below 1V is wrong).So far the solution is not even barely good, even for a PSU voltmeter.

author: pitagoras
author_rank: Regular Contributor
timestamp: April 10, 2017, 02:35:37 am
content: By further thinking, it's no surprise that the "meter" is that bad.I have at least 3 sources of inaccuracies.The internal reference is very inaccurate and has unknown tempcoThe voltage divider is imprecise and acts over the supply voltageThe internal comparator I have no idea if it is goodExternal reference could be a way for improvement, but with this solution there are 2 voltages needed (VREF and 2xVREF to be switched by the PWM).So to fix all that I need a reference with 1x and 2x outputs, and a low rs mosfet to modulate 2x reference with pwm output. And still we have the doubt about the comparator...I think those are too many constraints to deal with for a simple and cheap solution. It is probably better to add external hardware for an integrator, a comparator and a reference, and implement a single slope ADC (for the sake of self satisfaction; or just use a simple SD ADC chip as suggested, for the sake of simplicity).