Low Voltage AC Source (Part 9)

electronics AC PWM transistors

We built ourselves a working, but quite complex AC source. However, our solution is not the only possible one.

An Alternative Way to Build an AC Source

While our previous current AC source design worked well, with both the MCP4725 and the PWM DAC, the circuit has become quite complex. We needed quite a lot of parts for the three parts of our circuit: signal generation, amplification with the op amp and the external push-pull stage. Our solution was the following: we created a weak analog signal, which we then amplified with an op amp and the push-pull stage. If we use PWM to generate our signal, we can use a different method. We can amplify the digital PWM signal instead of an analog signal. The amplified output signal is then transformed into a sine wave afterwards using an LC filter. Let's get started and look at how this alternative solution works!

Building the Circuit

Here is the new circuit:

While this circuit looks similar to the push-pull stage we used in the previous part of this project, the working principle of this circuit is different. In this circuit the transistors are used as switches and not as amplifier for an analog signal. The circuit is now directly driven by the PWM signal. The LC filter is what transforms the digital signal into a sine wave. An LC filter has a similar effect as an RC filter except that both the inductor and the capacitor suppress the high frequency parts of the signal.

An LC filter is a second order filter and has a frequency curve with a higher slope of -40 dB/decade instead of only -20 dB/decade. In the graph below, you can see the frequency response of both filter types for a cut-off frequency of 100 Hz. The red line shows the frequency response of the first order RC filter, the blue line shows the frequency response of an LC filter.

Frequency response for a 2nd order low pass filter compared with a first order filter (fc = 100 Hz)

In the circuit itself a 1 mH inductor and a 10 uF ceramic capacitor are used. This filter has the following cut-off frequency:
\(f_c = {1 \over 2 \pi \sqrt{L C}} = {1 \over 2 \pi \sqrt{1 mH \cdot 10 uF}} \approx 1.6 kHz\)

The benefit of this type of filter is that it is easier build one with a comparatively low DC resistance. This is important because, the signal is now filtered after the amplification and thus the filter needs powerful enough for the connected load. The LC filter I used has a DC resistance of 5 Ω which is quite high. It is possible to design LC filters with a smaller resistance and higher output powers, by choosing a more appropriate coil.

Additionally, to the LC filter there is a 10 uF capacitor across 5 V and GND. This capacitor stabilizes the Arduino's supply voltage while the transistors switch. Due to the coil, high current spikes can occur during switching due to the magnetic field breaking down. In a more professional circuit one would add fly back diodes. I decided against it to make the circuit as simple as possible. In our case it's not a huge issue, as inductance and voltage are small.

But, let's see how the transistor circuit itself works. If the PWM output D9 is HIGH and the PWM output D10 is LOW, only the upper-left and the lower-right transistor are turned on via transistor T5. The left side of the load is connected to 5 V while the right side is connected to ground. Like shown in the image below a current flows through the two transistors and the load. The current flow is depicted using the conventional current direction.

Current flow if D9 is HIGH and D10 is LOW (conventional current direction)

If D10 is HIGH and D9 is LOW the current flows in the opposite direction. This way, we achieve a negative voltage.

Current flow if D9 is LOW and D10 is HIGH (conventional current direction)

The transistor circuit is also known as H-bridge and is often used to control motors. For motors this enables us to rotate the motor in both directions. Another use case for this circuit are so-called h-bridge inverters that transform a low DC voltage like 12 V into 230 V AC. Our circuit works in the same way as these inverters. It is, however, only designed for a small output voltage and power.

Commercial inverters, are much more advanced and include safety measures like over current and over temperature protection. Do not attempt to use this circuit with high voltages or high output powers.

The final output voltage after filtering depends on the duty cycle. Just like in our previous solution, we can create a sine wave by changing the duty cycle according to the sine wave values. Because we have only two voltage levels in the PWM signal, our type of inverter is also called 2-level PWM inverter.

The duty cycle is adjusted to create the desired sine wave signal

When working with h-bridges, it is very important that D9 and D10 are never turned on at the same time. If both transistors in one leg of the h-bridge are turned on at the same time, they basically create a short, which leads to the destruction of the h-bridge.

Understanding the Code

The complete program looks like this:

#include <EEPROM.h>

// PWM update frequency is 31.25 kHz (~32 us)
const int usPerCommand = 100;

// Dead Time to prevent shoot through
const int deadTime = 5;

// Precalculated Voltage Buffer
const int BUFFER_SIZE = 256;
unsigned int voltages[BUFFER_SIZE];
unsigned int steps;
unsigned int usPerStep;

unsigned int current_step = 0;
unsigned long start_time;

// Setup frequency
void setup() {
  // Read desired frequency
  Serial.print("Enter Frequency (Hz): ");

  // Wait 10s for input otherwise take stored value
  float frequency = Serial.parseFloat();
  if(frequency == 0) EEPROM.get(0, frequency);
  else EEPROM.put(0, frequency);


  // Calculate number of possible steps
  int possible_steps = 1000000/usPerCommand/frequency;

  // Steps need to be a multiple of 4 to keep the sine form
  steps = (possible_steps  / 4) * 4;
  if(steps > BUFFER_SIZE) steps = BUFFER_SIZE;
  if(steps < 4) steps = 4;

  // Time per Step
  usPerStep = 1000000 / (frequency * steps);
  if(usPerStep < usPerCommand) usPerStep = usPerCommand;

  // Precalculate Sine Values 0-5 V
  for(int i = 0; i < steps; i++) {
    int value = sin(i*3.14*2/steps) * (126.5-deadTime) + 127.5;
    voltages[i] = constrain(value, 1, 254);

  Serial.print("Number of output steps: ");

  Serial.print("Microseconds per step: ");

  Serial.print("Archieved Frequency (Hz): ");

  // Enable the fastest possible frequency for timer 1
  // choose phase correct mode and invert output 10
  TCCR1A = (1<<WGM10)|(1 << COM1B0);
  TCCR1B = (1<<CS10);
  TIMSK1 |= (1<<TOIE1);

  // Initially set start time
  start_time = micros();

ISR(TIMER1_OVF_vect) {
  analogWrite(9, voltages[current_step]-deadTime);
  analogWrite(10, voltages[current_step]+deadTime);

// Output values
void loop() {
  if(current_step >= steps) current_step = 0;

  while(micros()-start_time < usPerStep);
  start_time += usPerStep;

Overall this program is very similar to the one for out previous solution. There is however, a very important change. The following line of code inverts the PWM output at D10 and enables the so-called phase correct mode:

TCCR1A = (1<<WGM10)|(1 << COM1B0);

These features are provided by the ATmega328P microcontroller used on the Arduino Uno. You can look up the details in its datasheet.

As the hardware handles the inversion of the output signal at D10 we can simply set the same duty cycle for both PWM pins:

analogWrite(9, voltages[current_step]-deadTime);
analogWrite(10, voltages[current_step]+deadTime);

There are, however, multiple issue with this method that we need to take care of. The first one: the Arduino core libraries are not prepared for the case that the output at D10 is inverted. This becomes an issue for the values 0 and 255. For both values the Arduino core library that provides the analogWrite function turns off the PWM functionality and sets the pin to HIGH or LOW. As it does not know, that we inverted the output at D10, both pins, D10 and D9, are set to LOW if the value is 0 and to HIGH if the value is 255. This is not what we want. To solve this issue, the code contains a workaround, that ensures that the values 255 or 0 do not occur. This is handled by the following lines of code:

// Precalculate Sine Values 0-5 V
for(int i = 0; i < steps; i++) {
  int value = sin(i*3.14*2/steps) * (126.5-deadTime) + 127.5;
  voltages[i] = constrain(value, 1, 254);

First, the amplitude is chosen in a way that 0 and 255 do not occur. The additional use of the function constrain ensures that even if the value gets set to 0, a value of 1 is used instead. Similar for values above 255, the value 254 is used instead. This call is not necessary, however, it prevents you from running into issues when experimenting with the code and adjusting the values in it. It is a safe guard against accidentally creating a short. A value of 0 or 255 could e.g. occur due to the conversion from float to int if the values for the calculation are chosen differently.

The second issue is, that the inversion of the signal alone is not enough to ensure that the two outputs are never enabled at the same time. The issue is shown in the following image:

Why it is necessary to introduce dead time

Even though we have a digital output signal, due to capacitances in the circuit, the transition between HIGH and LOW always needs a bit of time. During the transition time it is possible that both transistors are turned on at the same time. To really ensure that this does not happen we thus have to introduce so-called dead time. Additional time, where both transistors are turned off. For this we shorten the on-times in the PWM signals. As the output on D10 is inverted, we need to add the dead time in this special case.

analogWrite(9, voltages[current_step]-deadTime);
analogWrite(10, voltages[current_step]+deadTime);

The last issue that needs to be solved is a bit tricky. If we get unlucky, the timer could update the duty cycle in between our two analogWrite calls. If this happens, the duty cycle for D9 might have been updated while the duty cycle for D10 was not. In this case we can again run into the problem, that both transistors are turned on at the same time. The underlying issue is, that the calls to analogWrite are not synchronized with the timer. If we want to update these values synchronous to the timer we need to update them inside the so-called interrupt service routine (ISR) for timer 1. If you have wondered why the analogWrite calls are now in different method with the strange name ISR(TIMER1_OVF_vect), you now know the answer. This method is exactly the ISR, I mentioned before. If we want to use this ISR, we need to enable the corresponding interrupt. This is done by the following line of code in setup:

TIMSK1 |= (1<<TOIE1);

The Result

Let's have a look at the result. Without the load resistor we get a sine wave with 5 V amplitude. That's nice.

Output signal on the oscilloscope

However, once we connect the load resistor the sine wave amplitude decreases. The image below shows the output signal with connected 51 Ω load resistor:

Output signal with 51 Ω load

The main reason for this is the DC resistance of the coil. The coil and the load resistor form a voltage divider. This causes the signal amplitude to be dependent on load resistance. With rising current, the voltage drop over the transistors increases too. Both effects cause a lower amplitude at bigger loads.

In this example the output power is 150 mW. This is not much, but sufficient for small circuits. With different transistors and a different coil higher output powers are easily possible. Additionally, it is possible to connect the H-Bridge to a higher DC voltage than the 5 V from the Arduino. Instead of connecting it to 5 V you can connect it to Vin and then power the Arduino with a higher voltage.

What about the efficiency? Well, it is not easy to calculate the efficiency for a circuit that switches with a high frequency. However, I did some measurements at 5 V with the 51 Ω load. For these parameters, the efficiency is roughly 25 %. The reason for this low efficiency is the voltage drop over the coil and the fact, that in this example, the base current for the transistors is almost as high as the output current. For higher output voltages, the efficiency increases. In theory, h-bridge inverters can have an incredibly high efficiency of almost 99 %. With a custom circuit and low voltages it is, however, not easily possible to reach such a high efficiency. If you aim for efficiency, it is advisable to replace the bipolar junction transistors with MOSFETs. In the next part of this series, we are going to look into this topic and replace our custom-made h-bridge with an h-bridge module that uses them.

Previous Post Next Post