The following circuit is known as a Schmitt trigger, implemented with BJT transistors. The main purpose of a Schmitt trigger is to generate a digital signal, which stated in other words is a signal whose only possible values are \(V_{cc}\) (logic 1) or ground (logic 0). The original analog signal can vary slowly in time so that the transition periods from high/low to low/high might not be fast enough. This circuit will act as a comparator with hysteresis whose thresholds for setting the output high or low will be defined by the design parameters.

Throughout the analysis we are going to consider for simplicity a voltage \(V_{be} = 0.6~V\) for the NPN to leave cut off and \(V_{ce} = 0.0~V\) when in saturation.

Since \(v_{in} = 5V\), \(Q_1\) is in saturation and in turn \(Q_2\) is cut off, then: \[i_2 = \frac{5~V}{1~k\Omega + 20~\Omega} = 4.9~mA\] \[i_3 = 0~mA\]\[v_{out} = 5~V\]\[v_{e} = i_2 \cdot R_4 = 4.9~mA · 20~\Omega = 98~mV\] \[v_{b1} = v_e + v_{be1} = 98~mV + 0.6~V \approx 0.7~V\]

A couple of details that can be extracted from here:

  • By having a voltage able to take \(Q_1\) into saturation, the output goes to \(V_{cc}\).
  • In order to get \(Q_1\) cut off, we need to drop \(in\) approximately \(100~mV\) higher than the \(v_{be}\) voltage, i.e., \(Q_1\) would cut off around \(in \approx 0.7~V\).

Therefore, let’s see what happens when from \(in = 5~V\) we drop \(in\) to \(0.7~V\).

\(Q_1\) and \(Q_2\) were in saturation and cut off respectively.
However, now that \(in = 0.7~V\), \(Q_1\) starts to stop driving current.
This makes the voltage at \(V_{b2}\) to rise due to a lower voltage drop across \(R_2\) and \(Q_2\) enters in saturation.
Since \(Q_2\) is in saturation, \(v_{be} = 0.6~V\)

In order to compute the currents going through the circuit in this state we can perform a mesh analysis taking into account the \(V_{BE}\) voltage between the base and the emitter and considering \(V_{CE} = 0\).

The two equations from meshes A and B are:
\[-V_{cc} + \left(R_2 + R_4\right)\cdot i_A + V_{be} – R_2\cdot i_B = 0\] \[-V_{be} – R_2\cdot i_A + \left(R_2 + R_3\right)\cdot i_B = 0\] Which can be written in matrix form as:\[\begin{pmatrix}
R_2 + R_4 & -R_2 \\
-R_2 & R_2 + R_3 \\
\end{pmatrix}
\begin{pmatrix}
i_A\\
i_B\\
\end{pmatrix} =
\begin{pmatrix}
V_{cc} – V_{be} \\
V_{be}
\end{pmatrix}\] Solving this equation as \(X = A^{-1} B\) we get that:\[i_A = 9.03~mA,~~ i_B = 4.81~mA\]With these current values, the voltage at the Q2 emitter is: \[V_{e} = i_A \cdot R_4 = 9.03~mA \cdot 20~\Omega = 180.6~mV\]

Again, what we can extract from these results is:

  • By having a voltage able to set \(Q_1\) cut off, the output goes to \(180~mV\), which in most of the digital families such as TTL or CMOS can be safely considered as a logic 0.
  • In order to get \(Q_1\) in saturation again, we need to increase \(in\) approximately \(180~mV\) above the \(v_{be} = 0.6\) voltage, i.e., \(Q_1\) would saturate around \(in \approx 0.78~V\).

So as we can see the circuit has hysteresis since thresholds for low to high and high to low are placed at different input voltages. This effect avoids having potentially multiple toggles at the output when the input voltage is near the threshold if the threshold would have been the same in both cases (low to high and high to low). Finally, the hysteresis voltage \(\Delta V\) can be approximated as \(\Delta V = \frac{R4}{R3} \cdot V_{cc}\).

LaTeX used to generate this article’s images:

\begin{circuitikz}
	\ctikzset{transistors/arrow pos=end}
	\draw (0,0) node[npn] (Q1){Q1};
	\draw (Q1.B) to[R, l2=$R_1$ and \SI{25}{\kilo\ohm}, l2 halign=c] ++(-1.5, 0) coordinate(in);
	\draw (in) to[short, -*] ++(-0.25,0) node[left]{in};
	\draw (Q1.C) -- +(1,0) node[npn, anchor=B](Q2){Q2} ;
	\draw (Q2.E) -- (Q2.E |- Q1.E);
	\draw (Q1.E) -- (Q1.E -| Q2.E);
	\draw (Q2.C) to[short, -*] +(0.5, 0) node[right]{out};
	\draw (Q1.E -| Q2.E) -- +(0, -0.25) to[R, l2=$R_4$ and \SI{20}{\ohm}] ++(0, -1.5) node[ground]{};
	\draw (Q1.C) -- (Q1.C |- Q2.C);
	\draw (Q1.C |- Q2.C)  to[R, l2=$R_2$ and \SI{1}{\kilo\ohm}, -*] ++(0, 2) coordinate(R2_top);
	\draw (Q2.C) to[R, l2_=$R_3$ and \SI{1}{\kilo\ohm}, -*] ++(0, 2) coordinate (R3_top);
	\draw (R3_top) -- (R2_top) -- +(-0.5, 0);
	\draw (R3_top) -- +(0.5, 0)  node[right]{$V_{cc}$};
\end{circuitikz}
\begin{circuitikz}
        \ctikzset{transistors/arrow pos=end}
	\draw (0,0) node[npn] (Q1){Q1};
	\draw (Q1.B) to[R, l2=$R_1$ and \SI{10}{\kilo\ohm}, l2 halign=c, f<_=\SI{430}{\micro\ampere}, current arrow scale=24] ++(-1.5, 0) coordinate(in);
	\draw (Q1.B) node[below, xshift=-2]{$+$};
    \draw (Q1.E) node[anchor=south east, yshift=-9]{$-$};
    %\draw ($(Q1.B)!0.5!(Q1.E)$) coordinate(vbe_label);
    %\draw (vbe_label) node[yshift=-3, xshift=-2]{$V_{be1}$};
    \draw (Q1) node[anchor=north east, yshift=-7, xshift=-3]{$V_{be1}$};
	\draw (in) to[short, -*] ++(-0.25,0) node[left]{$\text{in} = \SI{5}{\volt}$};
	\draw (Q1.C) -- +(1,0) node[npn, anchor=B](Q2){Q2} ;
	\draw (Q2.E) -- (Q2.E |- Q1.E);
	\draw (Q1.E) -- (Q1.E -| Q2.E);
	\draw (Q2.C) to[short, -*] +(0.5, 0) node[right]{out};
	\draw (Q2.B) node[below, xshift=-3]{$+$};
    \draw (Q2.E) node[left, yshift=-3]{$-$};
	\draw (Q2) node[anchor=north east, yshift=-9, xshift=-5]{$V_{be2}$};
	\draw (Q1.E -| Q2.E) -- +(0, -0.25) coordinate(R4_top) to[R, l2=$R_4$ and \SI{20}{\ohm}, f_=\SI{5.43}{\milli\ampere}, l2 halign=c] ++(0, -1.5) node[ground]{};
	\draw (Q1.C) -- (Q1.C |- Q2.C);
	\draw (Q1.C |- Q2.C)  to[R, l2=$R_2$ and \SI{1}{\kilo\ohm}, -*, f<^=\SI{4.9}{\milli\ampere}, l2 halign=c] ++(0, 2) coordinate(R2_top);
	\draw (Q2.C) to[R, l2_=$R_3$ and \SI{1}{\kilo\ohm}, -*, f<^=\SI{0}{\milli\ampere}, l2 halign=c] ++(0, 2) coordinate (R3_top);
	\draw (R3_top) -- (R2_top) -- +(-0.5, 0);
	\draw (R3_top) -- +(0.5, 0)  node[right]{$V_{cc}=\SI{5}{\volt}$};
\end{circuitikz}
\begin{circuitikz}
	\draw (0, 0) to[R, l2_=$R_2$ and \SI{1}{\kilo\ohm}] +(0, -2) coordinate(R2_bottom);
	\draw (R2_bottom) to[vsource, l_=$V_{BE}$]  +(2, 0) coordinate(QB);
	\draw (R2_bottom -| QB) to[R, l2_=$R_3$ and \SI{1}{\kilo\ohm}] +(0, 2) coordinate(R3_top);
	\draw (QB) to [R, l=$R_4$, l2=$R_4$ and \SI{20}{\ohm}] +(0, -2) coordinate (R4_bottom);
	\draw (0,0) -- (R3_top);
	\draw (0,0) -- (-2.5, 0) -- (-2.5, -1.75);
	\draw (-2.5, -1.75) to[vsource, l=$V_{CC}$] +(0, -1.0) coordinate(Vcc_bottom) -- (R4_bottom -| Vcc_bottom) -- (R4_bottom);
	 \draw [Latex-]  (1.0,-1.25) coordinate(loop) node [anchor=center, yshift=10] {$i_B$} arc (-90:145:3.5mm);
	 \draw [Latex-]  (-0.75,-3.25) coordinate(loop) node [anchor=center, yshift=13] {$i_A$} arc (-90:145:4.5mm);
\end{circuitikz}

The LaTeX package CircuiTikZ is an excelent way to draw circuits in a standard and elegant manner. However, it can get quite tricky when you need to specify the absolute coordinates of all the nodes of your schematic.

In order to facilitate this process coordinates and orthogonal coordinates come to the rescue.

Coordinates

You can provide a name for every node in your circuit. For instance, in the following line:

\draw (0,0) -- (2,0) coordinate(pointA);

point (2,0) can be accessed from now on using (pointA):

\draw (0,0) -- (2,0) coordinate(pointA);
\draw (pointA) to[R] ++(2,0);

The resistor will be placed from pointA, i.e., (2,0), to (4,0). Remember that the operator ++() makes a relative movement with respect the previous point (in this case (2,0)) and «stores» the position so that any new component in the same \draw statement will start from that point, i.e., (4,0). Also, +() makes the relative movement with respect the original point without storing the new position and not affecting any subsequent element in the \draw statement.

When using hardcoded coordinates like in previous example it might not be very powerful per se, but at least it may help to understand the way the diagram is written.

However, they can solve a lot of headaches when you need to draw two or more components in parallel.

Orthogonal coordinates

In case you want to align to different nodes, orthogonal coordinates computes automatically the X and Y of the new node.

From CircuiTikZ manual, section 2.2, where a classic non-inverting amplifier using an op amp is being drawn:

\draw (FB) to[R=$R_2$] (FB -| OA.out) -- (OA.out)

The way the previous line can be read is:

  1. From the coordinate FB, add a resistor to
  2. The horizontal value of FB (that’s why - is next to FB) and the veritical of OA.out (that’s why | is before OA.out)
  3. Then draw a line from this previous point to the op amp output OA.out

Example

Below you will find an example where all previous concepts are applied in order to draw the schematic of a basic pulse generator with BJT’s.

\usepackage[american]{circuitikz}
\usepackage{siunitx}

\begin{circuitikz} []
   \draw (0,0) node[bjtnpn] (Q1){Q1};
   \draw (Q1.B) to[R, l2={$R_1$ and \SI{10}{\kilo\ohm}}, l2 halign =c] ++(-2,0) to[short,-o] ++(-0.1, 0) node[above]{in};
   \draw (Q1.C) to[R, l2={$R_2$ and \SI{1}{\kilo\ohm}}] ++(0, 2) coordinate(R2t);
   \draw (Q1.E) to[short] ++(0, -0.1) coordinate(Q1gnd) node[ground] (GND){} ;
   \draw (Q1.C) to[C, l2_=$C_1$ and \SI{10}{\nano\farad}, l2 halign =c] ++(2.5,0) coordinate(c1n) to[short] +(0,-1) node[bjtnpn, anchor=B](Q2){Q2};
   \draw (c1n) to[R,l2=$R_3$ and \SI{10}{\kilo\ohm}] ++(0,2) coordinate (R3t);
   \draw (R2t) -- (R3t);
   \draw (R2t) -- +(-0.5,0);
   \draw (R3t) -- +(0.5, 0);
   \draw (Q2.E) -- (Q2.E |- Q1gnd) node[ground]{};
   \draw (Q2.C) -- (Q2.C |- c1n) coordinate(R4b) to[R,l2_=$R_4$ and \SI{1}{\kilo\ohm}] (Q2.C |- R2t) coordinate(R4t) -- (R3t);
   \draw (R4t) -- ++(0.5,0);
   \draw (Q2.C) to[short, -o] ++(1,0) node[above]{out};
\end{circuitikz}

Design a full-wave bridge rectifier circuit to deliver 10 V dc with less than 0.1 V (pp) ripple into a load drawing up to 10 mA. Choose the appropriate ac input voltage, assuming 0.6 V diode drops. Be sure to use the correct ripple frequency in your calculation.

The relationship between current and voltage in a capacitor is:
\[ Q = C \cdot V \]\[ \int{I dt} = C \cdot V \]\[ I dt = C \cdot dV \Rightarrow I = C \frac{dV}{dt} \]\[ dV = \frac{I}{C} dt \]\[ \Delta V = \frac{I}{C} \Delta t~~~\left(1\right) \]

Frequency of the ac signal: \(f\)
\(\Delta t = \frac{1}{2f}\)

If we need \(\delta V = 0.1~Vpp\) by drawing 10 mA, then, from Equation 1:
\[ \Delta V = \frac{I}{C} \Delta t \]\[ C = \frac{I}{\Delta V} \Delta t\]\[ C = \frac{10^{-2}}{0.1~V}\cdot \frac{1}{2f}\]

Let’s assume \(f = 60 Hz\), then:
\[C = \frac{10^{-2}~A}{0.1~V}\cdot \frac{1}{2\cdot 60~Hz} = 833~\mu F\]

The capacitor is going to be charged to the input amplitude – the diode voltage drop. Therefore, the amplitude of the ac input voltage should be:
\[ V_{out~max~pp} = V_{in~pp} – 2 \cdot V_{diode~drop}\]\[
V_{in~pp} = V_{out~max~pp} + 2 \cdot V_{diode~drop} = 10~V + 2\cdot 0.6~V = 11.2~V
\]
If simulated on a SPICE simulator, the results are confirmed. The difference on the final rectified voltage comes from the dynamic behaviour of the model used for the diodes, which is not an ideal model with 0.6 V threshold as assumed in the exercise’s data. The approximation of 0.6 V drop is more or less decent since the final voltage is around 9.5 V.

Therefore, the key aspects to remember from this circuit are:

  1. The output voltage only depends on the input voltage
  2. The ripple depends both in the output current and the capacitance value. The more current, the more charge is extracted from the capacitor. Therefore, voltage discharge increases. Same rationale works on the capacitance: the greater the capacitance, the more charge can be stored for a given capacitor voltage

Basic operation

The left hand side of the keyboard uses a Microchip MCP23018 IC, which is an I/O expander with open drain outputs and it can be configured through I2C or SPI. In this case we are going to focus on the I2C protocol only since it is the one used by the main MCU. The MCU is a Teensy 2.0 and it is placed on the right hand side of the Ergodox. It is used for monitoring all the switch status, converting the pressed switches into its corresponding character and sending it through USB to interact with the computer as a usual keyboard.

The MCP23018 has two different GPIO ports, GPIOA and GPIOB, each with 8 I/O pads. In the original Ergodox PCB, port A is used to interact with the columns and port B with the rows.
GPIO pads on port B (rows) are configured as inputs and the internal pull-up resistor is enabled on every port B pad. Regarding GPIO pads on port A, they are configured as outputs. According to the datasheet, output pads on the MCP23018 are open-drain, which basically means that the pad value can be internally driven to ground or left floating (high impedance). Since the rows and columns are connected through the mechanical switch and the diode, GPIO’s on port B can determine if the switch is closed (pressed) or open (released) depending on the voltage found at it. A diagram of this scheme is shown below.

The way the Ergodox firmware determines the state of every switch (closed/pressed or open/released) is by sequentially setting one column to ground while leaving the rest floating. Then, the state of the rows is read. Depending on the state of the switch, the GPIOB reading will be:

  • If the switch is released, the voltage at GPIOB will be VDD due to its pull-up resistor. The pad value will be read as 1.
  • If the switch is closed, the voltage at GPIOB will be set to 0.0 V. The pad value will be read as 0.

Since the rest of columns are left floating, they won’t interfere on the reading. Once the state of a particular row is read, its value is stored and the operation is repeated until all the available rows are covered. The process of reading the state of all the switches is called a «scan».

Debugging MCP23018 with Arduino

When I assembled my Ergodox, I loaded the QMK firmware expecting the keyboard to just work. However, there seems to be a hardware issue since the comunication between the LHS and the RHS is not successful. Therefore, I decided to replicated the QMK firmware on an Arduino to have a better understanding on how the MCP and the Teensy interact together and also figure out if there’s really any hardware issue on the PCB’s.

QMK firmware is highly customizable and therefore it can be a little bit intricate when trying to read its code. I tried to distil the most basic functionality regarding the writing and reading done from the Teensy to the Mcp23018 through I2C.

The code below will perform a scan every 1 second and will print through the Serial interface the keyboard matrix result.

mcp23018_debug.c

#include <Wire.h>
#include "mcp23018.h"
#include "i2c_wrapper.h"
#include "utils.h"

#define KB_ROWS     6
#define KB_COLUMNS  7  // 14 in total, 7 in left hand side

int data;
bool matrix[KB_ROWS][KB_COLUMNS];

void setup() {
  Wire.begin();

  Serial.begin(9600);
  while (!Serial); // Leonardo: wait for Serial Monitor
  Serial.println("\nMCP23018 debugger");
  delay(2000);

  mcp23018_init();
}

void loop() {

  while(true) {
    // Clear matrix values
    clear_matrix();

    for (uint8_t col = 0; col < KB_COLUMNS; col++) {
      // Drive column to ground
      drive_column(col);
      // Read GPIO's on port B
      data = i2c_rd(I2C_ADDR, GPIOB);
      for (uint8_t row=0; row < KB_ROWS; row++) {
        // Store read values into the matrix
        matrix[row][col] = !( data & (1<<(5-row)) );
      }
    }

    print_matrix();
    // Leave column floating again
    drive_column_hiz();
    // Wait 1 second
    delay(1000);
  }

}

void drive_column(uint8_t col) {
   i2c_wr(I2C_ADDR, GPIOA, 0xFF & ~(1 << col));
}

void drive_column_hiz() {
  i2c_wr(I2C_ADDR, GPIOA, 0xFF);
}

void clear_matrix() {
  for (uint8_t row = 0; row < KB_ROWS; row++) {
    for (uint8_t col = 0; col < KB_COLUMNS; col++) {
       matrix[row][col] = 0;
    }
  }
}

void print_matrix() {
   for (uint8_t col = 0; col < KB_COLUMNS; col++) {
      if (col == 0) {
        Serial.println("[ ");
      }
      for (uint8_t row = 0; row < KB_ROWS; row++) {
         Serial.print(matrix[row][col] ? 1 : 0);
         Serial.print("  ");
      }
      Serial.println();
      if (col == 6) {
        Serial.println("]");
      }
   }
}

Output:

MCP23018 debugger
[ 
0  0  0  0  0  0  
0  0  0  0  1  0  
0  0  0  0  0  0  
0  0  0  0  0  0  
0  0  0  0  0  0  
0  0  0  0  0  0  
0  0  0  0  0  0  
]

where 1 means the switch is pressed and 0 released.

Auxiliary files

i2c_wrapper.h

#include <Wire.h>

int i2c_rd(int i2c_addr, int reg_addr) {
   int rd_data;
   // Start condition + 7 bit I2C device address
   Wire.beginTransmission(i2c_addr);
   // Write bit + next byte
   Wire.write(reg_addr);
   // Stop condition
   Wire.endTransmission();
   // Start condition + address + read bit
   Wire.requestFrom(i2c_addr, 1); // request 1 byte from slave device
   if(Wire.available()) {
    rd_data = Wire.read();
   }
   Wire.endTransmission();
   return rd_data;
}

int i2c_wr(int i2c_addr, int reg_addr, uint8_t wr_data) {
   // Start condition + 7 bit I2C device address
   Wire.beginTransmission(I2C_ADDR);
   // Byte with address to be written
   Wire.write(reg_addr);
   // Data to be written
   Wire.write(wr_data);
   // Stop condition
   return Wire.endTransmission();
}

mcp23018.h

#define I2C_ADDR        0b0100000
// i/o direction register
#define IODIRA          0x00
#define IODIRB          0x01
// GPIO pull-up resistor register
#define GPPUA           0x0C
#define GPPUB           0x0D
// general purpose i/o port register (write modifies OLAT)
#define GPIOA           0x12
#define GPIOB           0x13

utils.h

#ifndef _UINT8_T
#define _UINT8_T
typedef unsigned char uint8_t;
#endif /* _UINT8_T */

int print_addr_val(int addr, int val) {
    Serial.print("Addr ");
    Serial.print(addr, HEX);
    Serial.print(": ");
    Serial.println(val, BIN);
}

 

Motivation

Several months ago I started trying to find how charge distribution across different capacitors works and I came up to this thought experiment. I used CircuitLab to simulate it and confirm what my intuition said it would ocurr. However, this intuition was wrong and led me to post this question in Electronical Engineering of Stack Exchange in order to find an explanation. Several people took some time to answer the question (really appreaciated), however the answers were not actually satisfactory since they were not really explaining analytically what was behind it. Arout seven months later, I finally found the solution thanks to this video.

For a basic explanation on how to deal with capacitors and charge, please refer to this previous post, written the very same day I started struggling trying to find a solution to this.

Explanation

In this circuit, there is an independent DC current source of \(1~mA\) and switches SW1 and SW2 are closed at time \(t=t_1 = 1ns\). From time \(0 < t < t_1\), only \(C_1\) is connected to the current source, so the expected voltage \(V_1\) could be expressed as:

\[V_1 \left( t \right) = \int{\frac{i_1}{C_1}dt} = \frac{i_1 \cdot t}{C_1}\]

The rest of capacitors are discharged and therefore its voltage is \(0~V\) as shown in the following plot:

Now, at time \(t = t_1 = 1 ns\), switches SW1 and SW2 are closed and capacitors \(C_2\) and \(C_3\) are connected to the circuit. At this point, the current source will keep injecting charge into the capacitors. However, we need first to compute how charge is distributed across capacitors \(C_1\), \(C_2\), \(C_3\). The total charge of the circuit at time \(t = t^-_1\) is:

\[Q_T\left(t^-_1\right) = Q_1 = C_1 \cdot V_1\left(t_1\right)\]

\[V_1\left(t_1\right) = \frac{i_1 \cdot t_1}{C_1} = \frac{1~mA\cdot 1ns}{1~pF} = 1~V\]

By applying superposition theorem, we can determine the contribution of the \(C_1\) voltage on the rest of capacitors. To do so, let’s analyze the following equivalent circuit:

Capacitors \(C_2\) and \(C_3\) are actually connected in series since they share the ground node. So, the circuit could be arranged in the following manner:


The equivalent capacitance would be:

\[ C_{eq} = \frac{C_2 \cdot C_3}{C_2 + C_3} \]

Now, let’s compute the total charge of the system. To do so, let’s rearrange again the circuit. In order to be able to compare the total charge at \(t = t^+_1\) with the total charge \(Q_T\left(t^-_1\right)\), we need to keep being compliant with sign declaration. So \(Q_T\) must be computed from the following point:

Then, the total charge of the system \(Q_T\left(t^+_1\right)\) would be:

\[Q_T\left(t^+_1\right)= Q_1 – Q_{eq} = V_1\left(t^+_1\right) \cdot C_1 – V_{eq}\left(t^+_1\right) \cdot C_{eq}\]

Due to the way the capacitors are connected, we can assert that \(V_{eq} = -V_1\). Therefore, the total charge of the system can be expressed as a function of \(V_1\):

\[Q_T\left(t^+_1\right)= V_1\left(t^+_1\right) \cdot C_1 – V_{eq}\left(t^+_1\right) \cdot C_{eq} = V_{1}\left(t^+_1\right) \cdot C_{1} + V_1\left(t^+_1\right) \cdot C_{eq}\]

Since the charge in the system must keep constant after the switches position changed, it can be said that:

\[ Q_T\left(t^-_1\right) = Q_T\left(t^+_1\right)\]

This can be expanded to:

\[ C_1 \cdot V_1\left(t^-_1\right) = V_{1}\left(t^+_1\right) \cdot C_{1} + V_1\left(t^+_1\right) \cdot C_{eq}\]

Solving for the new \(V_1\) at time \(t = t^+_1\):

\[ V_1\left(t^+_1\right) = \frac{C_1}{C_1 + C_{eq}} V_1\left(t^-_1\right) \]

Using the values of our example, i.e. \(C_1 = 1~pF\) and \(C_{eq} = \frac{1~pF\cdot0.5~pF}{1~pF+0.5~pF} = 0.333~pF\):

\[ V_1\left(t^+_1\right) = \frac{1~pF}{1~pF + 0.333~pF} \cdot 1~V = 0.75~V \]

\[ V_{eq}\left(t^+_1\right) = -0.75~V \]

Now, we just need to compute the voltages at capacitors \(C_2\) and \(C_3\).

Since voltage \(V_{eq}\) across them is known, we can compute the voltages \(V_{C3}\) and \(V_{C2}\) by solving the capacitive divider they constitute:

\[V_{C3} = \frac{C_2}{C_2 + C_3} V_{eq}\]

\[V_{C2} = \frac{C_3}{C_2+C_3}V_{eq}\]

Replacing the previous expressions with our example values, we get the following voltages:

\[V_{C3} = \frac{1~pF}{1~pF + 0.5~pF}\cdot\left(-0.75\right) = -0.5~V \]

\[V_{C2} = \frac{0.5~pF}{1~pF + 0.5~pF}\cdot\left(-0.75\right) =  -0.25~V\]

Finally, we can conclude that:

\[ V_{2} = V_{C2} = -0.25~V\]

\[ V_{3} = -V_{C3} = 0.5~V\]

Theses results are confirmed in simulation:

Introduction

Any two-terminal network whose internal circuitry is made up solely of resistors, current sources and voltage sources can be simplified to a voltage source (\(V_{TH}\)) with a resistor in series (\(R_{TH}\)). This is called Thevenin’s theorem.

In order the find the Thevenin’s equivalent circuit, it is necessary to follow two steps:

  1. Find \(R_{th}\) by turning off all internal sources and connecting an external current source.
  2. Find \(V_{th}\) by turning on all internal sources and leaving the external current source in open circuit.

When we turn off all internal supply sources we set \(v_{int} = 0\), i.e., shorting the internal voltage supply sources, and we set \(i_{int} = 0\), i.e., opening the internal current supply sources. Then, we apply an external current supply source (\(I_{ext}\)) and compute the correspondent voltage between the two terminals (\(V_{1}\)). The value of \(R_{TH}\) will be:
\[
R_{th} = \frac{V_1}{I_{ext}}
\]

On the second step, we will turn on again all the internal sources and set the external current source as open circuit. The voltage seen at the two terminals of interest will be \(V_{th}\)

Example

We will start the circuit analysis by setting all internal supply sources off. All the voltage sources will be shorted whereas the current sources will be left in open circuit.

From here, now we are going to compute what is the voltage \(V_{1}\).

Since there is no path to the negative terminal on \(R_1\) and \(R_3\) is shorted, they don’t contribute to the output voltage \(V_1\).

Instead, \(V_1\) can be computed as:
\[
V_1 = i_{ext} \cdot R_2
\]

Therefore, the Thevenin’s resistance is:
\[
R_{TH} = \frac{V_1}{i_{ext}} = \frac{i_{ext} \cdot R_2}{i_{ext}} = R_2
\]

Now we need to compute Thevenin’s voltage \(V_{TH}\). For this we are going to turn on again all the internal sources, i.e., leaving the circuit as it originally was, and computing the voltage across its two terminals.

If we apply the Kirchhoff’s current law (KCL) on every node:

\[
I_2 + i_3 = i_5
\]

\[
I_1 = i_3
\]

\[
i_5 = I_2 + i_4 + i_6
\]

From Equation 1, we find that

\[
i_1 + i_3 = \frac{V_{TH} – V_3}{R_2} \Rightarrow \left(i_1 + i_3\right) R_2 + V_3 = V_{TH}
\]

\[
V_{TH} = \left(0.5~mA + 1~{mA}\right) \cdot 10~{k}\Omega + 10~{V} = 25~V
\]

With this information, we can say that an equivalent circuit seen from these two terminals can be:

A capacitor is an electronic device able to store electrical energy in an electrical field. Usually, the capacitor is defined in its most simple version as a device with two plates with area \(A\), separated by air (or any other dielectric material) a distance \(d\).

Parallel plate capacitor.svg
By inductiveload – own drawing, done in Inkscape 0.44, Public Domain, Link

If a current source is forced through the capacitor, the electrons (charge) will be deposited in one of the plates, creating in turn a electrical field across them. There won’t be any effective charge transference from one plate to the other because the space between them is filled with a dielectric material (non conductive). However, the electrical field across it can force the repulsion or attraction of charge at the other side of the plate.

Capacitor schematic with dielectric.svg
By Papa November – self-made SVG version of Image:Dielectric.png, incorporating Image:Capacitor schematic.svg as its base., CC BY-SA 3.0, Link

A capacitor is characterized by its capacitance. The capacitance is measured in Farads (F) and defines the ratio between the amount of charge needed to increase one volt at the terminals of the capacitor.

\[ C= \frac{Q}{V} \]

Therefore, a capacitor with 1 F will need 1 Coulomb (1 C) of charge to set 1 V across its terminals. Remember, that 1 C represents the amount of energy transported by a constant current of 1 A in 1 second.

Behavior of a capacitor connected to a DC current source

Let’s see what happens when we connect a DC current source to a capacitor. Transforming a little bit the previous expression, we can obtain:

\[ C = \frac{Q}{V}  \Rightarrow V = \frac{Q}{C} \]

As \(Q = \int{i\left(t\right) dt}\), we can get the voltage across the capacitor as a function of the time and the current:

\[ V\left(t\right) = \frac{1}{C} \int{i\left(t\right) dt} \]

Therefore, if we inject a constant current of \(1~mA\) for \(1~ns\), the voltage \(V_1\left(t\right)\) will be as follows:

In this example, what would be the charge stored in the capacitor at time \(t_1 = 1~ns\)?

\[ C = Q/V \Rightarrow Q = C \cdot V = 1~pF ·  1V = 1\cdot10^{-12}~C \]

Now, what would be the value of the voltage \(V_1\) if at time \(t_1 = 1~ns\) a second capacitor \(C_2\) (discharged) of \(2~pF\) of capacitance is connected?

At that very moment in which the second capacitor is connected in parallel, the charge in \(C_1\) will be distributed between \(C_1\) and \(C_2\).

The effective capacitance now is \(3~pF\) (\(1~pF  + 2~pF\)). Remember that using the capacitance definition, we got:

\[ V = \frac{Q}{C_p} = \frac{Q}{C_1 + C_2} = \frac{1\cdot 10^{-12} C}{1\cdot10^{-12}~F + 2\cdot10^{-12}~F} = 0.333~V \]

Therefore, as the capacitance now has increased and the charge remains the same (although shared between \(C_1\) and \(C_2\)), according to the previous expression the voltage has to drop.

If we would like to know the charge stored in every individual capacitor at time \(t_1 = 1~ns\), we could compute it as:
\[ Q = C \cdot V\]
\[ Q_1\left(t_1 = 1~ns\right) = C_1 \cdot V_1\left(t_1 = 1~ns\right) = 1~pF \cdot 0.333~V = 3.33\cdot10^{-13}~C\]
\[ Q_2\left(t_1 = 1~ns\right) = C_2 \cdot V_1\left(t_1 = 1~ns\right) = 2~pF \cdot 0.333~V = 6.66\cdot10^{-13}~C\]

The result is consistent with the previous calculation stating that the amount of charge at \(t_1=1~ns\) was \(Q = 10^{-12}~C\).

Finally, let’s see what happens if at time \(t_2 = 2~ns\) the capacitor \(C_2\) is again disconnected and only capacitor \(C_1\) is connected to the current source.

At time \(t_2 = 2~ns\) the voltage \(V_1\) will have increased to:

\[V\left(t_2 = 2~ns\right) = V_1\left(t_{1}^{+}\right) + \frac{1}{C_1 + C_2} \int_{t_1 = 1~ns}^{t_2 = 2~ns}{i\left(t\right) dt} \]

\[V\left(t_2 = 2~ns\right) = 0.333 V + \frac{1}{1~pF + 2~pF} \int_{t_1 = 1~ns}^{t_2 = 2~ns}{1~mA~dt} = 0.333~V +0.333~V = 0.666~V\]

Now the charge will be distributed as follows:

\[ Q_1\left(t_2 = 2~ns\right) = C_1 \cdot V_1\left(t_1 = 2~ns\right) = 1~pF \cdot 0.666~V  = 6.66\cdot10^{-13}~C\]
\[ Q_2\left(t_2 = 2~ns\right) = C_2 \cdot V_1\left(t_1 = 2~ns\right) = 2~pF \cdot 0.666~V = 1.22\cdot10^{-12}~C\]

If \(C_2\) is disconnected, the charge stored in it will be lost and won’t be redistributed towards \(C_1\). Therefore, the total amount of charge in the system at \(t_2 = 2~ns\) will only be that on \(C_1\).

Now, the voltage increment at the same rate as in the period \(0 < t < t_1 = 1~ns\).

Therefore, if we plot the voltage from \(t=0~ns\) to \(t_3 = 3~ns\), we would get the following voltage profile for \(V_1\):

The uvm_object class is the base class for all UVM classes. From it, all the rest of classes are extended. It provides basic functionalities such as print, compare, copy and similar methods.

This class can be used when defining reusable parts of a sequence items. For example, in a packet like uvm_sequence_item, we could define a uvm_object extended object for defining the header. This would be:

class packet_header extends uvm_object;

   rand bit [2:0] len;
   rand bit [2:0] addr;

   `uvm_object_utils_begin(packet_header)
      `uvm_field_int(len, UVM_DEFAULT)
      `uvm_field_int(addr, UVM_DEFAULT)
   `uvm_object_utils_end

   function new (string name="packet_header");
      super.new(name);
   endfunction : new

endclass : packet_header

This packet_header could be included in a packet class for conforming the uvm_sequence_item (the transaction) which will compose the sequences:

class simple_packet extends uvm_sequence_item;

   rand packet_header header;
   rand bit [4:0] payload;

   `uvm_object_utils_begin(simple_packet)
      `uvm_field_object(header, UVM_DEFAULT)
      `uvm_field_int(payload, UVM_DEFAULT)
   `uvm_object_utils_end

   function new (string name = "simple_packet");
      super.new(name);
      header = packet_header::type_id::create("header");
   endfunction : new

endclass : packet

 

\[ s_k = (k\cdot A) \bmod B\]

\(s_k\) is the pseudo-random number and \(A\) and \(B\) are prime numbers. \(k\) is in the range \([0,B-1]\). If \(k\) is greater than \(B-1\), the results will be repeat as \(B\) is the period of the sequence.
For example, \(A = 7\) and \(B = 17\). This sequence written in MATLAB could be:

A = 7;
B = 17;
n = [];

t = 0:B;
for i=0:B
    n = [n mod(i*A,B)];
end

stem(t,n);

 

Pseudo-random values
Periodicity of the sequence when k > B

Let’s z be a 2D point in the space as \(z = x + jy\), if we want to rotate this point a given angle \(\theta\), we get the following expressions:
\[e^{j\theta} \cdot z = \left(\cos{\theta} + j \sin{\theta}\right)\left(x+jy\right) \\ = x\cos{\theta}-y\sin{\theta} + j \left(y \cos{\theta} + x \sin{\theta} \right) \\ = x’ + j y’ \]

Then, for a generic point, the rotation can be expressed as an equation system, where \(x’\) and \(y’\) are the new coordinates, \(\theta\) is the rotation angle and \(x\) and \(y\) are the original coordinates:
\[\begin{bmatrix}
x’\\
y’
\end{bmatrix}=
\begin{bmatrix}
\cos{\theta} & -\sin{\theta}\\
\sin{\theta} & \cos{\theta}
\end{bmatrix}\begin{bmatrix}
x\\
y
\end{bmatrix} \]

This rotation can be coded in MATLAB as:


%% Function to rotate vector
function v = rotate(P, theta)
    rot = [cos(theta) -sin(theta);
           sin(theta) cos(theta)];
    v = rot*P;
end

A possible implementation of the cordic algorithm could be:


%% Clear all previous values
clear all;

%% Define vectors
A = [2;-3];
O = [0;0];

%% Define accuracy
error_limit = 0.01;

%% Initialize variables
start_angle = pi/2; % 90º
current_angle = start_angle;
acc_angle = 0;

A_rotated = A;
steps = 0;

% Second quadrant (90º - 180º)
if(A_rotated(1) < 0 && A_rotated(2) > 0)
    A_rotated = rotate(A_rotated, -pi/2);
    acc_angle = pi/2;
% Third quadrant (180º - 270º)
elseif(A_rotated(1) < 0 && A_rotated(2) < 0)
    A_rotated = rotate(A_rotated, -pi);
    acc_angle = pi;
% Forth quadrant (270º - 360º)
elseif(A_rotated(1) > 0 && A_rotated(2) < 0)
    A_rotated = rotate(A_rotated, -3*pi/2);
    acc_angle = 3*pi/2;    
end


%% Compute angle
% Keep rotating while error is too high
while(abs(A_rotated(2)) > error_limit)
    % Represent current vector
    quiver(0, 0,A_rotated(1), A_rotated(2));
    % Keep previous vectors
    hold on;
    % Decrease angle rotation
    current_angle = current_angle/2;
    % (For debugging purposes)
    current_angle_deg = current_angle*180/pi;
    % Save current error
    error = A_rotated(2);
    % If y coordinate is still positive
    if(error > 0)
        % Rotate again conterclockwise
        A_rotated = rotate(A_rotated, -1*current_angle);
        % Accumulate rotated angle
        acc_angle = acc_angle + current_angle;
    % If y coordinate is negative
    else
        % Rotate vector clockwise
        A_rotated = rotate(A_rotated, current_angle);
        % Substract current angle to the accumulator because we have
        % overcome the actual angle value
        acc_angle = acc_angle - current_angle;
    end
    
    % (For debugging purposes)
    acc_angle_deg = acc_angle*180/pi;
    % Increase step counter
    steps = steps + 1;
end

% Print angle, hypotenuse length and number of steps needed to compute
fprintf('Angle = %f\nHypotenuse = %f\nNumber of steps: %d\n', acc_angle*180/pi, A_rotated(1), steps);


%% Function to rotate vector
function v = rotate(P, theta)
    rot = [cos(theta) -sin(theta);
           sin(theta) cos(theta)];
    v = rot*P;
end

 

I have coded an interactive applet to illustrate the algorithm. It has been done using the p5.js library. The error limit has been set to \(0.5\).