All posts by Per Magnusson

AC Current Sensor

This post describes a circuit that can detect a mains frequency AC current and translate it into a voltage that is proportional to the current. It does this while being electrically isolated from the AC current.

One way of measuring the current to a single phase AC load, like a motor or a heater, is to use a current sense transformer around a wire that conducts the current. One kind of current sense transformer is the Rogowski coil, which is basically an air-core toroidal coil where one of the leads is routed back inside the coil to end up on the same side as the other lead. The absence of an iron/ferrite core makes the Rogowski coil immune to saturation, so it can handle large currents while still being linear.

The output voltage of the coil is proportional to the rate of change of the current to be measured, so in order to get a voltage proportional to the current itself, one has to use an integrator. If the current is known to be a sinusoid of a fixed frequency, it is not necessary to integrate the voltage as a simple factor determines the relationship between the current and the coil voltage, but such a design would make the circuit very sensitive to high-frequency overtones. The circuit discussed here contains an integrator so that it gives accurate outputs for a wide range of frequencies.

Below is a schematic of the integrator circuit I came up with.

Rogowski-based AC sense amplifier.
Rogowski-based AC sense amplifier.

The components in the box to the left is a simulation model of the Rogowski coil PA3208NL. This device has an internal resistance of 37.6 ohm (R1) and gives an output voltage of 383 µV for a 1 A 50 Hz current. I1/L1 provides a voltage that is proportional to frequency and reaches 1 V RMS at 50 Hz, which is converted to 383 µV at 50 Hz by E1.

C1 provides some high-frequency (EMI) attenuation while R2 makes sure the input of the amplifier circuit is biased to GND even if the coil is not connected. These components have little effect on the simulations.

R3/C3 is partly another EMI low-pass filter, but the values are also chosen so that it takes over the integrating function from the amplifier around 15 kHz. C2 AC-couples the GND-referred signal at the input to the half-supply point used by the amplifier.

R4/R5 biases the amplifier input at half supply to maximize the available signal swing.

U1 approximates an integrator over the frequencies of interest. In this range C4 acts as a short and R7 has much higher impedance than C5. The frequency response is thus determined by C5/R6 and the gain is (1 + 1/(s*R6*C5)). The 1/(sR6C5) becomes shrinks to become equal to the 1-term at about 15 kHz, so well below this frequency the response is close to that of an integrator, which would be 1/(s*R6*C5). As mentioned above, R3/C3 are chosen so that they take over the integrating business (low-pass filtering) at this point, so the total circuit response is close to that of an integrator to frequencies quite a bit above 15 kHz, to around 100 kHz or so.

For low frequencies, R7 starts dominating over C5 around 3.3 Hz. The same cutoff frequency has been chosen for C4/R6, so 10 times this frequency (33 Hz) is approximately the lower point at which the circuit behaves as an integrator.

For very low frequencies, well below 3 Hz, C4 and C5 acts as open circuits and R7 provides DC feedback to establish the operating point. This takes care of a problem that ideal integrators have, namely infinite gain at DC, which would sooner or later rail the output of U1. By having C4 in series with R6 and R7 in parallel with C5, the gain at DC becomes 1 and since the input is AC-coupled (through C2), the only DC term at the output is one times the amplifier input offset.

Below is a frequency response plot of the most interesting nodes in the circuit.

Frequency response at some of the nodes.
Frequency response at some of the nodes.

The green curve shows the voltage linac inside the Rogowski model. Note that it reaches 1 V at 50 Hz and increases linearly with frequency.

The blue curve is the green curve scaled so that it has a value of 383 µV at 50 Hz.

The red curve is the input of the amplifier. It follows the blue curve up to just below 10 kHz, where the low-pass action of R3/C3 kicks in and flattens the curve. As mentioned above, this knee is chosen to coincide with the opposite knee of the amplifier caused by R7/C5 so that the total response, the grayish-green curve, is flat from 30 Hz to 100 kHz. The gain is just above 0.1 V/A, which makes this design suitable for applications with up to at least 10 A of maximum current.

One caveat when testing or using this circuit is that it takes a pretty long time for the 100 µF capacitor to charge up via the 2.2 megohm resistor after power up. The time constant here is more than three minutes (220 seconds), which means it will take a long time from power up until the circuit is operational. If this is a problem in any given application, one could consider  placing a CMOS switch (like 4066) across the 2.2 megohm resistor and let it be closed for a short period after power up.

The 10 µF capacitor at the input of the amplifier will also take some time to charge. Here the time constant is 5 seconds, so this is less of a problem than the 100 µF cap. This can be improved by reducing the 1 megohm bias resistors to 330k, which will have little effect on the overall response. Even 100k might be acceptable, although this will cause some unevenness in the response above 10 kHz.

In the schematic, the amplifier LTC6088 is present. I chose this as it was a suitable opamp available in the default LTSpice library. There are however many other amplifiers that would do well in this position. Important characteristics are:

  • Operation at 5 V
  • Preferably rail-to-rail output
  • Reasonably low input bias current, preferably below 50 nA or so
  • Enough gain-bandwidth for the application, at least a few MHz

Rotary Encoder Routines for Teensy

In this post I describe some code I wrote to handle the input from an incremental rotary encoder connected to a Teensy. The code should work well also on an Arduino, provided the rotary encoder signals are connected to pins that have interrupt functionality.

A rotary encoder is great for many kinds of user interfaces. It physically looks like a potentiometer, but it is not limited to less than one turn. The output is digital, so it can relatively easily be interpreted by a micro controller, even one without a built-in analog to digital converter.

A rotary encoder.
A rotary encoder. Looks like a potentiometer, but it is digital.

The number of pulses per rotation can vary, but between 12 and 24 is common. An encoder contains two switches and if it is rotated clockwise, switch A closes before switch B for each “click” and if it is turned counter clockwise, switch B closes before switch A. This is called quadrature encoding since the two square waves are 90 degrees out of phase from each other.

By looking at the order in which the switches close, it is possible to determine in which direction the knob is turned. The figure below illustrates the waveforms, assuming the switches connects their pins to ground and that the two signals are otherwise pulled up to a positive voltage by pull-up resistors.

Quadrature waveforms from a rotary encoder.
Quadrature waveforms from a rotary encoder. “D” marks a detent or click state.

Below is a diagram showing how a rotary encoder and a push button (which is often integrated into the encoder) can be hooked up to a Teensy 3.1.

Encoder and button connected to pull-up resistors and a Teensy 3.1.
Encoder and button connected to pull-up resistors and a Teensy 3.1.

A problem when trying to use a rotary encoder as part of a user interface in an embedded application is that the pulses can be pretty short and it is important to not miss any, as that would mean missed rotations or even rotations detected in the wrong direction. Furthermore, like for almost all switches, there is also contact bounce that one must handle in some way. Typically, the processor has to do other things while also responding to input from the rotary encoder.

This means that it is often very hard or impossible to robustly interpret the signals from an encoder by polling the signals from within the main loop of the program. To get around this problem, one can instead use interrupt routines that are triggered when a signal from the encoder changes level. This is precisely what the code below does. It was written for a Teensy 3.1 (or 3.2), but should work on other platforms as long as the encoder signals are connected to pins with interrupt capability.

The following function call

attachInterrupt(digitalPinToInterrupt(rotAPin), ISRrotAChange, CHANGE);

sets up an the function “ISRrotAChange()” to be called every time the rotAPin changes state. This function in turn looks at the pin and sets the rotAval variable accordingly before calling the more complicated function UpdateRot() which contains a state machine that keeps track of what the rotary encoder is doing and increments or decrements the rotAcc (rotary encoder accumulator) variable when it has determined that the encoder has moved a notch. No debounce timer is necessary since it is pretty safe to assume that the bounces of one switch will have died out before the next switch will be activated.

The rotAcc variable is changed only after the second signal becomes activated (low) while the first signal is still active since it is pretty common that one signal is activated and later deactivated without the second signal becoming active. This happens when the operator rotates the knob a fraction of a notch and then lets it return to the original position without going all the way to the next notch.

The main loop must not look directly at the rotAcc variable since it can be changed at any point within the program by an interrupt. It is only safe to access it while interrupts are disabled, so accesses have to be surrounded by cli() (clear interrupt enable bit) and sei() (set interrupt enable bit) function calls. This is done in the GetRotAcc() function which thus provides a convenient way of safely looking at the value of the accumulator. The interrupt service routines (ISRs) themselves do not need to mess with cli() and sei() when accessing the interrupt state variables since interrupts are automatically temporarily disabled while servicing an interrupt.

Since the global variables used by the interrupt routines can be changed at any point within the course of the main program, they have to be be declared with the keyword “volatile” to prevent the compiler from making optimizations that assume that they behave like normal variables that will never magically change under the feet of the main program code.

Expand the Teensy code
[cpp] /* Interrupt driven rotary encoder routines.
   Target: Teensy 3.1
   
   Written by Per Magnusson, http://www.axotron.se
   v 1.0 2015-11-15
   This program is public domain.
*/

// Rotary encoder push button pin, active low
static const int pushPin = 16;
// Rotary encoder phase A pin
static const int rotBPin = 17;
// Rotary encoder phase B pin
static const int rotAPin = 18;

#define DEMO 1

// Rotary encoder variables, used by interrupt routines
volatile int rotState = 0;
volatile int rotAval = 1;
volatile int rotBval = 1;
volatile int rotAcc = 0;

void setup()
{
  Serial.begin(57600);

  pinMode(rotAPin, INPUT);
  pinMode(rotBPin, INPUT);
  pinMode(pushPin, INPUT);
  attachInterrupt(digitalPinToInterrupt(rotAPin),
    ISRrotAChange, CHANGE);
  attachInterrupt(digitalPinToInterrupt(rotBPin),
    ISRrotBChange, CHANGE);
}

void loop()
{
  static int oldRotAcc = 0; // Remember previous state
  int newRotAcc;
 
  // Do not look directly at the rotary accumulator variable,
  // it must be done in an interrupt safe manner
  newRotAcc = GetRotAcc();

  if(!digitalRead(pushPin)) {
    // The button was pushed.
    // Button detection is not interrupt driven, but detection of the
    // rotary encoder movements goes on also in the busy way below.
    Serial.println("Button down");
    delay(10); // Debounce
    while(!digitalRead(pushPin))
      ;
    delay(10); // Debounce
    Serial.println("Button up");
  } else if(newRotAcc != oldRotAcc) {
    // The encoder was rotated at least one step.
    Serial.println(newRotAcc);
    oldRotAcc = newRotAcc;
  }
}

// Return the current value of the rotaryEncoder
// counter in an interrupt safe way.
int GetRotAcc()
{
  int rot;
 
  cli();
  rot = rotAcc;
  sei();
  return rot;
}

// Interrupt routines
void ISRrotAChange()
{
  if(digitalRead(rotAPin)) {
    rotAval = 1;
    UpdateRot();
  } else {
    rotAval = 0;
    UpdateRot();
  }
}

void ISRrotBChange()
{
  if(digitalRead(rotBPin)) {
    rotBval = 1;
    UpdateRot();
  } else {
    rotBval = 0;
    UpdateRot();
  }
}

// Update rotary encoder accumulator.
// This function is called by the interrupt routines.
void UpdateRot()
{
  // Increment rotAcc if going CW, decrement it if going CCW.
  // Do not increment anything if it was just a glitch.
  switch(rotState) {
    case 0: // Idle state, look for direction
      if(!rotBval) {
        rotState = 1;  // CW 1
      }
      if(!rotAval) {
        rotState = 11; // CCW 1
      }
      break;
    case 1: // CW, wait for A low while B is low
      if(!rotBval) {
        if(!rotAval) {
          rotAcc++;
#ifdef DEMO          
          // Remove this when not in demo mode
          Serial.print(" right ");
#endif
          rotState = 2; // CW 2
        }
      } else {
        if(rotAval) {
          // It was just a glitch on B, go back to start
          rotState = 0;
        }
      }
      break;
    case 2: // CW, wait for B high
      if(rotBval) {
        rotState = 3; // CW 3
      }
      break;
    case 3: // CW, wait for A high
      if(rotAval) {
        rotState = 0; // back to idle (detent) state
      }
      break;
    case 11: // CCW, wait for B low while A is low
      if(!rotAval) {
        if(!rotBval) {
          rotAcc–;
#ifdef DEMO
          // Remove this when not in demo mode
          Serial.print(" left ");
#endif
          rotState = 12; // CCW 2
        }
      } else {
        if(rotBval) {
          // It was just a glitch on A, go back to start
          rotState = 0;
        }
      }
      break;
    case 12: // CCW, wait for A high
      if(rotAval) {
        rotState = 13; // CCW 3
      }
      break;
    case 13: // CCW, wait for B high
      if(rotBval) {
        rotState = 0; // back to idle (detent) state
      }
      break;
  }
}
[/cpp]

 

In this demo version of the code, status information is sent back over the USB serial port so that a terminal window (e.g. the one in the Arduino environment) can display it. The texts “left” or “right” are printed by the ISR when it changes the value of rotAcc and the main loop prints the value of rotAcc when it detects that it has changed.

The main loop handles the push button. This is done through normal polling. A delay is used to filter out contact bounces. While the button is held down, the main loop is stuck and cannot print updated rotAcc values, but the ISR routines happily execute if the encoder is rotated, so the “left” and “right” strings can still appear while the button is pressed.

Here is an excerpt from a terminal session:

 right 1
 right 2
 right 3
 right 4
 right 5
 left 4
 left 3
Button down
 right  right  right  right  left  right Button up
7
 right 8
 right 9
 left 8
 left 7

The encoder was rotated clockwise (CW) five steps (right 1 to right 5) and then counter clockwise (CCW) by two steps (left 3). Then the button was pushed and held down (Button down) while the encoder was rotated four notches CW, one CCW and one CW  before the button was released. After this the main loop resumed and detected that the rotAcc value had changed to 7. Then the encoder was rotated two more notches CW and two CCW.

When not using this to demo the functionality of these specific routines, the Serial.print() statements in the interrupt routines should be removed, either by not #defining DEMO or by removing those lines of code completely.

Solution to why Windows 10 randomly wakes up from hibernation

Here is another issue I have been having since upgrading to Windows 10: I often find that the computer has woken up from hibernation, which is of course irritating since it unnecessarily consumes power. I did some googling and found a number of things that could be the cause, like the mouse, keyboard or network interface being able to wake the computer up. Turning all of these off did however not stop the computer from waking up.

Also, a recommendation was to issue the following command in a cmd window to figure out why the computer woke up:

C:\WINDOWS\system32>powercfg /lastwake
Wake History Count - 1
Wake History [0]
  Wake Source Count - 0

In my case the response unfortunately says nothing about why the computer woke up, which seems like a major shortcoming.

Then I tried the following:

C:\WINDOWS\system32>powercfg /waketimers
Timer set by [SERVICE] \Device\HarddiskVolume2\Windows\System32\svchost.exe (SystemEventsBroker) expires at 15:46:51 on 2015-09-27.
  Reason: Windows kör den schemalagda aktiviteten NT TASK\Microsoft\Windows\Media Center\mcupdate_scheduled som kräver att datorn aktiveras.

This did give a useful hint since it told me that mcupdate_scheduled was set to wake the computer up at 15:46:51 the next day, presumably to update Media Center. I do not want the computer to wake up to install updates, so I found out that scheduled tasks can be controlled by “Schedule tasks” (or “Schemalägg aktiviteter” as it is called in my Swedish W10 installation). In it I was able to dig down to Media Center and disable its daily checking for updates, see screenshot below (again, it is from my Swedish installation, but I guess it is it trivial to figure out how to do the same thing in an English W10).

Media Center update scheduling
Media Center update scheduling.

I clicked the circled Deactivate (“Inaktivera”) button to disable the scheduled updates and after this the powercfg /waketimers gave the following response:

C:\WINDOWS\system32>powercfg /waketimers
There are no active wake timers in the system.

Another way of checking for tasks that could wake up the computer is to run Power Shell and the following command:

PS C:\Users\Per> Get-ScheduledTask | where {$_.settings.waketorun}

TaskPath                                       TaskName                          State
--------                                       --------                          -----
\Microsoft\Windows\.NET Framework\             .NET Framework NGEN v4.0.30319... Disabled
\Microsoft\Windows\.NET Framework\             .NET Framework NGEN v4.0.30319... Disabled
\Microsoft\Windows\Media Center\               mcupdate_scheduled                Disabled

Apparently, no task is currently about to wake the computer up. Good.

I also found the following Power Shell command that can be used to disable scheduled wake ups:

Get-ScheduledTask | ? {$_.Settings.WakeToRun -eq $true -and $_.State -ne "Disabled"} | % {$_.Settings.WakeToRun = $false; Set-ScheduledTask $_}

I hope that this will be the end of the computer waking up all by itself. If it is not, I will continue to run the above commands again to see if any new wake timers have popped up.

Update 2016-01-04

Even though the frequency of unwanted wakeups seemed to be much reduced by the above measures, they sometimes occured anyway. Maybe I have just not found and disabled enough things that can cause a wakeup. I have given up on hunting these down, so instead I connected the computer and monitor to a power strip with a switch that I use to remove power from the computer once it has started to hibernate. As long as I remember to turn off the switch, this is guaranteed to prevent the computer from waking up while in hibernation.

Update 2016-10-01

A couple of times I have noticed that my mouse has reverted to being able to wake the computer up. Maybe this happens during some Windows updates. Some of the spurious wake-ups have probably been due to e.g. the scroll wheel on the mouse moving half a notch by itself which might happen if it accidentally was left between two notches. A straightforward way to verify if this can happen is to put the computer into hibernation and then start moving, clicking and scrolling on the mouse. Also try the keyboard while you are at it. To disable wakeup from mouse or keyboard, go to the device manager, right click on the device, select the Power Management tab and disable the wake-up option.

Another thing I have found that can wake the computer up is if I connect am mp3-player to a USB hub while the computer is hibernating, but I have found no setting to disable this.

Still, the trusted old power switch on the power strip without any software involved remains the most reliable method of preventing the computer from waking up when it should not.

Update 2018-03-24

It seems like Windows repeatedly starts allowing mice and keyboards to wake the computer up. One way to check which devices can currently wake the PC up is to issue the following command in a cmd window:

powercfg -devicequery wake_armed

Then one can go into device manager, right click on the offending devices, select Properties and in the Power management tab disable the wakeup capability.

It would be really nice if one could set a flag somewhere to never ever enable anything but the power button to wake the computer up to avoid the PC unnecessarily being on and consuming power for long periods when it is not used.