Very quick and timed analog read - is it possible?

Hello everyone,

I have yet another question for the gurus out there :wink: I would appreciate any input on this.

Here's what I'm trying to accomplish:
I'm driving a MOSFET transistor with PWM to drive a 12V DC motor. It works great, I can vary the speed of the motor from 0 all the way to 100%. To eliminate audible noise from the motor the PWM is running at 20kHz (I'm too old to hear it)
The MOSFET has a current sense output that I need to use to measure the current consumed by the motor... and this is where the problem is.
At 20kHz each analog read takes waaaay too long to get an accurate reading. I got random junk when I tried it and it became very clear why when I connected my oscilloscope.
Here's a view of PWM trigger signal from the Arduino board in yellow and analog read in blue:

As you can see from the scope view the read time is way to long to get a good value. I don't even know when during this window the actual value is locked in. As a result I get garbage - from negative values (undershoot spikes) all the way to above actual values (overshoot spikes). Even when trying to average the reading I get poor results.
I do not want to use a hardware filter because I need fast response time. I need to detect overloads and shorts fairly quickly.

The actual current sense output trace from the transistor looks even worst. It's a fairly complex output stage and the result is not pretty. Here it is:

I have marked the point where I need to take the measurement (in red). Is it even possible to make the analog read that quick?

Another issue is that my analog read is nowhere in sync with the PWM output. So, as a result the readings are taken at random intervals and in random places on the trace.
Is it possible to sync the analog read with the PWM output?

As you can see I am fairly new to the Arduino platform (you might have guessed that from a couple of my other questions on the forum). Anyway, the processor runs at 16MHz and the PWM runs at 20kHz. It should be able to time it correctly but is it really possible?

I appreciate your input.

Not without going to an external SPI ADC that can do a reading in 2 SPI.transfers at 8 MHz clock rate.

Aren't you faced with three problems

  • the need for a system to trigger the ADC reading at the correct moment
  • the need for a fast ADC that can take the reading in the short time available
  • the need for the Arduino to be able to receive and act on the reading in the time available

I suspect the last one of these will be the biggest obstacle. At 20kHz you have only 50 microsecs between readings.

And, I don't see how the Arduino can manage either of the first two - they would have to involve external components.

...R

In order to read the current you have to filter the signal from the current sensor. That is you have to smooth out those spikes to give you close to a DC level. After all it is not the peak you are trying to measure but the area under the curve.

Another possibility is a sample and hold approach.

The MOSFET has a current sense output that I need to use to measure the current consumed by the motor.

What exactly is the device you are using? A MOSFET doesn't measure current.

Need to see the circuit diagram too.

Those big spikes on the supposed current trace are unexpected, inductive loads smooth out
current fluctuations.

MarkT:
What exactly is the device you are using? A MOSFET doesn't measure current.

Need to see the circuit diagram too.

Those big spikes on the supposed current trace are unexpected, inductive loads smooth out
current fluctuations.

Yes, some MOSFETs have a terminal to measure current. It's been a while, but I have seen it somewhere.

You are WAY over-thinking this. Low-pass filter the current sense output, and read it using the standard analogRead(). It will work just fine. I do it all the time.

Regards,
Ray L.

Yes. Ray is right.

If you could read the current this fast, like the oscilloscope does, then you will get two answers: Lots of current or no current. The average current is what's important.

I have several of the Sparkfun Mega Motor Shields. They have a current feedback circuit connected to an analog pin, which exhibits exactly this problem. The solution is to solder a bigger capacitor onto the one that's there, to smooth out the on/off cycle into a readable analog value. Specifically, the capacitor reduces the frequency response of the output to less than the PWM frequency. It can still respond in milliseconds when the current rises rapidly so I have very tight control over the current delivered to the motor.

Thank you to all for your input. I was hoping it was possible with just the Arduino but seems I will have to expand my hardware circuit and use an external ADC.
Here are some answers to your posts:

Robin2 - You're right, there are three problems to solve. I will most likely use an external ADC that will be triggered by the PWM rising edge and take a reading with appropriate delay. Result will be sent to Arduino for it to process. It won't be instantaneous but it should be quick enough.

Grumpy_Mike - That's exactly what I'm trying to avoid. I don't want to smooth out the current sense output. I want to measure between the peaks.

aarg - What's on your mind? How do I do that what should I expect as a result?

MarkT - There's a lot of MOSFETs with current sense output. It is not a function of the MOSFET itself but some of them have a dedicated CS pin and internal structure. The one I'm using right now is an Infineon BTN8982. It is a half bridge motor driver with current sense and lots of other very useful features. It's a protected motor driver - if it overheats it shuts of, if it's overloaded it shuts off, if you short the output it shuts off. It's great!
The large spikes are to be expected with loads such as DC motors. An inductive load might smooth it out but a motor will generate nasty amount of noise when freewheeling. The moment the PWM goes low the motor turns into a generator and those are the spikes we see in the trace. The returning current generated during freewheeling passes through the body diode in the MOSFET until the lower MOSFET activates in the BTN8982 and shorts it to the ground. I'm running a 12V 12A DC motor - looots of crap generated.

RayLivingston and MorganS - Not exactly guys. I'm not really interested in reading the average current. When current is read at the right time in PWM cycle you get the actual current the motor consumes. In my case I should get 12A even at 20% PWM or there about. This allows me to react quickly to any changes in current (condition of the motor) without calculating what the current should be at certain PWM value. I only have one value to worry about. This way I set my limits for + or - 25% and that's it. With average current I have to calculate what the current will be at less than 100% add the tolerance and decide if it's still good. Besides, average is always less accurate than instantaneous. Do an average of the mains AC line with time base of exactly one cycle and the result will be 0V... that's not good. Do a peak-to-peak measurement and you'll see that it's not 0V and it fact it will kill you if you touch it.
A large capacitor will smooth out the CS output but it will slow the response at the same time. It might react in ms but it's not the same as reacting with each and every PWM cycle. With each cycle measured you can detect issues long before they become serious. You can detect things like brushes wearing out and alert the user in advance that a replacement will be needed. With averaged reading small changes and spikes will be filtered out.

Any suggestions as to what ADC I should use for this application?

Thanks guys!

MarkT - There's a lot of MOSFETs with current sense output. It is not a function of the MOSFET itself but some of them have a dedicated CS pin and internal structure. The one I'm using right now is an Infineon BTN8982. It is a half bridge motor driver with current sense and lots of other very useful features. It's a protected motor driver - if it overheats it shuts of, if it's overloaded it shuts off, if you short the output it shuts off. It's great!

That's not a MOSFET! Its actually 3 chips in one package.
The "current sense" output isn't just current sense it has error value out and is only looking
at the high-side switch. The datasheet seems coy about its actual performance (linearity and
so forth, and judging from the scope trace its pretty poor and picks up spurious peaks and dips
during transients which are not there.)

You'd get a more useable value from a decent current sensor on the load, which won't be jumping around
as the switches operate.

For a fast ADC the Microchip family MC3204/8 are reasonable - good performers at 5V and 100kSPS

Jamesik:
RayLivingston and MorganS - Not exactly guys. I'm not really interested in reading the average current. When current is read at the right time in PWM cycle you get the actual current the motor consumes. In my case I should get 12A even at 20% PWM or there about.

I do not know much about electronics, but could you use a zener diode to get rid of that spike, and then a capacitor discharging over a resistor to get the average current? If you know the duty-cycle of the pwm, then you could do a little math to work out the current during the time you were interested in.

MarkT:
For a fast ADC the Microchip family MC3204/8 are reasonable - good performers at 5V and 100kSPS

Interesting. I had in mind that fast would be some millions of samples per second - I have a very old (Philips?) chip that does 40msps (or so the data sheet says :slight_smile: )

...R

You're forgetting something very important - you're controlling a motor, and, by the sounds of it, a rather large one at that. Motors have very significant inductance, which means the current level CANNOT change rapidly. Any changes of interest will only take place over milliseconds, if not tens, or even hundreds, of milliseconds. This is exactly why filtering, and sampling at a low rate works just fine in the real world. There is absolutely no need whatsoever to sample during EVERY pulse of the PWM frequency. And, if all you're doing is limiting, then put in one or more gated comparators, and be done, with no need to A/D at all. Worst case, you use a gated sample/hold, feeding the (slow) AVR A/D. NOTHING of value is going to happen during that 100 uSec sampling window. You can also increase the sample rate, at the expense of resolution, so 8-bit sampling can be done at 25uSec.

You're trying to use a nuclear missile to kill a fly....

Regards,
Ray L.

MarkT - It is a half bridge motor driver but the actual switching is done by a MOSFET. The second (low side) is also a MOSFET and it is only used to short generated and unwanted current to ground. The third chip is just a control unit. It monitors the temperature, max current, does switching between upper and lower MOSFETs including deadtime. In reality you can treat it as a regular MOSFET.
The current sense output only monitors the high side switch because only this part is important. It makes absolutely no sense to monitor the freewheeling current that is shorted to the ground when the low side switch is on. Again, just like a regular MOSFET with current sense. I'm not worried about the other signals provided on the CS pin as they only appear when there is something to report. During normal operation it is just a regular CS output.
You're right about this CS out being pretty poor. I have to note that this output is already filtered because without anything on it the signal is totally unusable... it's just total crap with high frequency noise with p-p values in excess of the actual signal. It all looks great on paper but in reality it's not so great. I do have to say that the rest is great. It drives my motor without any issues and doesn't even get warm at 12A.
I was hoping I could get away without using an external current measuring device as I didn't have much luck with an Allegro ACS712 chip either.
Here's a picture of the screen of my scope (I was using the USB for something else and couldn't do a nice downloaded view this time). This is an output from an ACS712 current measuring chip driving the same load:

It's a little better but it's still not a nice square wave equal to the current. At least the spikes and dips are not there. Notice a lot of high frequency noise on that trace. Now, this sensor is running without any filtering on the output. I just connected it in series with the load to see what happens. It will be much better if I use some filtering.

Thank you for the tip about the ADC. It seems pretty simple to use but.... looks like it has been discontinued or is just hard to get.

PaulMurrayCbr - Yes, I can do that, although I'm not sure if using a zener would be a good idea here. It might overload the current sense pin. A simple RC filter should do the job of smoothing it out BUT this is something I'm trying to avoid as I have described in my previous post. I do not want to smooth it, I don't want to average it. I just want to read the full current when the transistor is ON (PWM high) so I know how much the motor is consuming. I don't need to know the average value, I just want to know the actual value. So, I don't need to know that my motor is using 6 amps average at 50% duty. I want to know that with each PWM cycle the current is 12A.

Robin2 - I believe that 40msps would be a slight overkill ;). 100k is enough. In reality I just need one, maybe two measurements that are timed just right. Get a trigger from the PWM rising edge, wait a certain amount of time and do a measurement. Then maybe add one more to filter out noise and that's it.

RayLivingston - You nailed it! I'm trying to nuke a fly and that's the whole idea :wink:
In reality I'm trying to get very accurate readings from the motor. Since I'm running the PWM at 20kHz the pulses are very short and this is where the important stuff happens.
My design is based on some industrial motor controller that I saw a few years ago. I believe this was a custom designed controller. The functionality it offered was amazing... it would warn the user that a motor will fail soon even though everything appeared perfectly fine. It did exactly what I'm trying to do now. It took measurements at each PWM high and displayed that info as motor current, actual current (which was calculated from duty cycle) and health of the motor.
I guess it's fair to say that I'm just asking too much from the little Arduino Uno.

Thank you guys for the input!

I believe the last scope picture is substantially correct. The current is more of a triangle wave. It's NOT a square wave. If you are trying to pick one sample out of the middle of the triangle wave, then you won't get much useful information on the rest of the cycle. I have done this kind of analysis myself on my own motors, with the help of a friend who owns several current transformers that cost as much as a car when new.

Paul, it would be interesting to try your idea but I think what will happen is it will throw away some of the real data which should have been averaged by the capacitor. The capacitor-resistor filter will capture all of the current peaks and troughs and smooth them out to give a real average of what's going into the motor. No zener required.

Jamesik:
Robin2 - I believe that 40msps would be a slight overkill ;). 100k is enough.

Yes, I do realize that. But I do think you need an ADC that can take a measurement in a very short period of time, even if you don't need to take the measurements very often.

How long (in microsecs) is the red squiggly bracket in the image in your Original Post? I would guess you need an ADC that can take a measurement is about a quarter of that time. (And, for all I know, 100ksps may do that).

...R

You can get the ADC somewhat faster (like 13 ยตS per sample) by changing the prescaler to 16:

Robin2 - The red bracket which I marked on the trace is about 24 us long but that changes depending on the duty of the PWM cycle.
I believe that a 100ksps ADC should do the trick. I'll give it a try or... I might just simplify the design and filter out or average out the signal after all. Will not be the nuke for a fly anymore but it will be a lot simpler to make.

A bit of scope 101 for anyone looking at this thread who doesn't know how to read what's on the screen - we are referring to the second picture in my first post - we are looking at the yellow trace which is marked as CH1 on bottom of the screen. Each division represents 100mV in my picture in vertical and horizontal represents 20 us. From this we just count the squares and get the voltage and time of the signal. In that picture the peak-to-peak voltage on channel 1 is about 650mV. Each cycle lasts about 50us. The red bracket is about 24us long.

Nick - Thank you very much for the link. Lots of useful stuff you got there mate! :wink: I will surely use some of that in my design. Again, thank you!

Jamesik:
Is it possible to sync the analog read with the PWM output?

There's a way to do that. Here's how:

  • Configure Timer1 to run with a 50 us cycle time, using ICR1 as TOP. That's mode 14, a fast PWM mode.
  • Use OCR1A to set the pulse width. That restricts the output pin for PWM to pin 9, OC1A.
  • Set the ADC prescaler to either 32 or 16, for an ADC clock frequency of 500 kHz or 1MHz, conversion time of 27 or 13.5 us, respectively, so that it can perform a conversion within the PWM cycle time.
  • Configure the ADC to autotrigger on Timer1 Compare Match B. The time that conversions are initiated, relative to the PWM cycle, will be controlled by the value of OCR1B
  • Set OCR1B to trigger a conversion at a point in the PWM cycle that you want to measure, when you can expect the analog to be stable, and at least 2.5 ADC clock cycles before the analog value will start to change. From the datasheet, the ADC's samle/hold occurs 1.5 ADC clock cycles after the conversion starts; from my tests, if the analog changes within a bit more than an ADC clock cycle of that time, the change will affect the reading.
  • Acquire the analog reading in an ADC interrupt service routine, rather than using analogRead().
  • There's no need for an ISR for Timer1 Compare Match B. If one exists, it will automatically reset the interrupt flag when the ISR executes; otherwise, there's nothing for it to do. Without that ISR, the ADC ISR will have to reset the Timer1 Compare Match B interrupt flag, so that its next rising edge can trigger another conversion. If the ADC ISR doesn't do this, and there's no Timer1 Compare MAtch B ISR, there will be only one ADC conversion, since the Timer1 Compare Match B interrupt flag will never have another rising edge. If you forget to do this, it will likely be hard to troubleshoot.

There are disadvantages. This method requires programming right down to the bare metal. You'll have to directly manipulate the control and status registers for the ADC and Timer1, and that will make the code non-portable. This kind of programming may be more than you want to do, considering that you say that you're new at using the Arduino.

Here's a sketch demonstrating the concept, to run on an Uno. Timer1 is set to mode 14, fast PWM with TOP=ICR1. Timer1 prescaler is set to 1, no prescaling, so the timer is clocked by the system's 16 MHz clock. ICR1 is set to 799, for a cycle time of 800 ticks, 50 us, 20 kHz. OCR1A sets the pulse width, and it's set at 400, at the middle of the PWM cycle. OCR1B determines the time that the ADC conversion is initiated, and it's varied from 100 ticks before the output switches to 100 ticks after. PWM output is on pin 9. For this test, pin 9 was connected directly to analog input A0. For each value of OCR1B, the sketch takes a number of analog readings, and reports the average value, maximum, and minimum of the analog readings.

#define NSAMPLES 256
volatile uint16_t analogs[NSAMPLES];
volatile uint16_t analogCtr = 0;
volatile uint16_t x;

void setup() {
  analogRead(A0);
  Serial.begin(115200);
  Serial.println("OK");
  pinMode(9, OUTPUT);
  setupTimer1();
  setupADC();
  startTimer1();

  for (uint16_t i = 300; i <= 500; i++) {
    OCR1B = i;
    delay(1);
    startAcquisition();
    while (analogCtr < NSAMPLES) {}
    uint16_t avg;
    uint16_t mx;
    uint16_t mn;
    avgMinMax(&avg, &mx, &mn);
    Serial.print(i);
    Serial.print('\t');
    Serial.print(avg);
    Serial.print('\t');
    Serial.print(mx);
    Serial.print('\t');
    Serial.print(mn);
    Serial.println();
    Serial.flush();
  }
}

void loop() {
}

void avgMinMax(uint16_t* avg, uint16_t* mx, uint16_t* mn) {
  uint32_t sum = 0;
  *mx = 0;
  *mn = 0x3FF;
  for (uint16_t i = 0; i < NSAMPLES; i++) {
    sum += analogs[i];
    if (analogs[i] < *mn) *mn = analogs[i];
    if (analogs[i] > *mx) *mx = analogs[i];
  }
  *avg = (sum + (NSAMPLES >> 1)) / NSAMPLES;
}

ISR(ADC_vect) {
  TIFR1 = (1 << OCF1B);
  analogs[analogCtr++] = ADC;
  if (analogCtr >= NSAMPLES) {
    ADCSRA &= ~(1 << ADIE);
  }
}

void startAcquisition() {
  analogCtr = 0;
  ADCSRA |= 1 << ADIE;
  TIFR1 = (1 << OCF1B);
}

void setupADC() {
  ADCSRA = 0;
  DIDR0 = 1;
  ADMUX = (1 << REFS0)|(0 << ADLAR)|(0 << MUX0);
  // REFS1:0 - Reference -> AVcc
  // MUX3:0 - Channel 0
  ADCSRA = (1 << ADEN)|(0 << ADSC)|(1 << ADATE)|(1 << ADIF)|(0 << ADIE)|(4 << ADPS0);
  // ADEN - Enable ADC
  // ADSC - Don't start a conversion
  // ADATE - Enable autotrigger
  // ADIF - Clear pending ADC interrupt
  // ADIE - Enable ADC interrupt
  // ADPS2:0 - Prescaler; 4 -> 16 -> 1MHz -> 16 -> 13.5 us
  ADCSRB = 5 << ADTS0; 
  // T1 OCR1B Compare Match Trigger
}

void setupTimer1() {
  TIMSK1 = 0;
  TCCR1B = 0; // Stop timer
  TCCR1A = 0; // Normal mode.
  ICR1 = 799; // 20 kHz at prescaler = 1
  OCR1B = 400; // ADC trigger
  OCR1A = 400; // Pulse width
  TCCR1A = (2 << COM1A0) | 2 << WGM10; // OC1A Set @ BOTTOM, clear on match
  TCCR1B = 3 << WGM12; // Mode = 14 - Fast PWM, TOP = ICR1
  TIMSK1 = 0;//1<<TOIE1;
}

void startTimer1() {
  TCCR1B |= (1 << CS10);
  // CS12:0 - Prescaler select -> 1
}

Attached is a graph of the average value of the analog reading vs the time a conversion was initiated, relative to the time that the PWM output switches.

[Edit: spelling; attach graph]

ADC Reading vs Time of Conversion.JPG