How to simulate resistive voltage divider using PySpice

The following code simulates a resistive 10kΩ / 1kΩ voltage divider using PySpice and can serve as a good starting point for simulating simple circuits.

This post shows you how to simulate the voltage divider using transient analysis. Also see an alternative variant of this post using DC sweep analysis instead: How to DC-sweep resistive voltage divider using PySpice

import PySpice.Logging.Logging as Logging
logger = Logging.setup_logging()

from PySpice.Probe.Plot import plot
from PySpice.Spice.Netlist import Circuit
from PySpice.Unit import *

circuit = Circuit("MyCircuit")
# Create voltage source: 5V DC
source = circuit.VoltageSource('V1', 'in', circuit.gnd, dc_value=5@u_V)
# Create resistor divider
r1 = circuit.R('R1', 'in', 'n1', 10@u_kΩ)
r2 = circuit.R('R2', 'n1', circuit.gnd, 1@u_kΩ)
# Simulate for 1 second with steps of 1 millisecond
simulator = circuit.simulator(temperature=25, nominal_temperature=25)
analysis = simulator.transient(step_time=1@u_ms, end_time=1@u_s)

You can access the array of output voltages of the divider (i.e. node n1) using analysis['n1']:

This is the code we used to plot this:

import matplotlib.ticker as mtick
import matplotlib.pyplot as plt
from UliEngineering.EngineerIO import format_value

def format_volts(value, pos=None):
    return format_value(value, 'V')

plt.style.use("ggplot")
plt.xlabel("Time [ms]")
plt.ylim([0.0, 5.5])
plt.gca().yaxis.set_major_formatter(mtick.FuncFormatter(format_volts))
plt.gcf().set_size_inches(8,5)
plt.plot(analysis["in"], label="Input voltage")
plt.plot(analysis["n1"], label="Voltage divider output")
plt.gca().legend()
plt.savefig("/ram/PySpice-Voltage-Divider.svg")

 

Posted by Uli Köhler in Electronics, Python, SPICE

How to create resistor in PySpice: Minimal example

This creates a resistor named R1 which has its + pin connected to node n1 and its - pin connected to GND. The value of the resistor is set to 1kΩ.

r1 = circuit.R('R1', 'n1', circuit.gnd, 1@u_kΩ)

Full example:

import PySpice.Logging.Logging as Logging
logger = Logging.setup_logging()

from PySpice.Probe.Plot import plot
from PySpice.Spice.Netlist import Circuit
from PySpice.Unit import *

circuit = Circuit("MyCircuit")
r1 = circuit.R('R1', 'n1', circuit.gnd, 1@u_kΩ)

 

Posted by Uli Köhler in Electronics, Python, SPICE

How to create constant voltage source in PySpice

This creates a voltage source named V1 which has its + pin connected to node n1 and its - pin connected to GND. The voltage source is set to 5V DC.

source = circuit.VoltageSource('V1', 'n1', circuit.gnd, dc_value=5@u_V)

Full example:

import PySpice.Logging.Logging as Logging
logger = Logging.setup_logging()

from PySpice.Probe.Plot import plot
from PySpice.Spice.Netlist import Circuit
from PySpice.Unit import *

circuit = Circuit("MyCircuit")
source = circuit.VoltageSource('V1', 'n1', circuit.gnd, dc_value=5@u_V)

 

Posted by Uli Köhler in Electronics, Python, SPICE

Simple OpAmp selectable gain amplifier: Select gain 1x or 10x via GPIO

Download KiCAD 6.x Selectable gain amplifier schematic SGA.kicad_sch

This circuit is a simple variant of a selectable gain amplifier.

If CTRL is pulled low (i.e. to GND), effectively the circuit will look like this:

Due to negligible input bias current for JFET opamps like the TL084 (i.e. most modern opamps below 100 MHz GBW), this circuit is basically just a Gain 1 buffer.

If CTRL is pulled high (i.e. pulled to 5V for example), the circuit effectively looks like this:


which has a gain of (\frac{10 k\Omega}{10 k\Omega + 90 k\Omega} = \frac{10 k\Omega}{100 k\Omega} = 10. Adjust these

One aspect to consider here is the RDSon of the MOSFET Q1. This is added to R1 and introduces a gain error to the system. The BSS123, for example, has a relatively high RDSon of up to 10 Ohms at VGS = 4.5V. For R1 = 10kOhm, the effective worst-case gain is:

\frac{10 k\Omega + 10 \Omega}{10 k\Omega + 10 \Omega + 90 k\Omega} = \frac{10 k\Omega}{100 k\Omega} = 10.001  or 0.01%. For most applications this does not matter since other aspects like the resistnance tolerance or resistance coefficient of temperature will affect the system error orders of magnitude This gain error will vary with temperature since the RDSon is temperature-sensitive and often the variation will be a larger issue than the gain error itself.

One way to mitigate is to choose large values for R1 and R2. Choosing R1=100kOhm and R2=900kOhm in this example will reduce both the gain error and the gain error temperature coefficient from the RDSon of Q1 by an order of magnitude. I recommend not to choose resistors larger than 1 MOhm without taking further consideration of opamp bias current, bias current temperatur drift etc, since those start to get relevant even for JFET input opamps when exceeding a couple of Megaohms.

Note that the RDSon of the MOSFET will be worse for VGS=3.3V will be worse and some MOSFETs will not switch on properly at this voltage. Choose a MOSFET that fits both your logic voltage and the RDSon i.e. gain error requirements. I often start with the BSS138 since I have many of those in stock, which tends to have less than 1 Ohm resistance in most practical conditions.

Another consideration for this circuit is that the MOSFET body diode will conduct if the voltage at the inverting input of the opamp will go beyond the Vf of the MOSFET body diode (typically you should use 0.7V here even if the specified body diode voltage is slightly higher, in order to have sufficient headroom). In effect, this typically means that you should not use this circuit without modification when using bipolar supplies and signals that go more negative than GND.

Posted by Uli Köhler in Analog, Electronics

How to check only a single schematic symbol against KLC (KiCAD library conventions)

In our previous post How to check KiCAD symbol library against KLC (KiCAD library conventions) we showed how to use check_symbol.py to check a symbol library against the KiCAD library guidelines.

You can also use it to check only a specific symbol in that library by name, which removes most of the clutter from the output. Use -c "CD4066B" to just check the component named CD4066B.

~/kicad-library-utils/klc-check/check_symbol.py MyLibrary.kicad_sym -vv -c "CD4066B"

This will print, for example:

Checking symbol 'Analog_Switch:CD4066B':
  Violating S3.1
    Origin is centered on the middle of the symbol
    Symbol unit 3 slightly off-center
     -   Center calculated @ (-25, -25)
    Symbol unit 4 slightly off-center
     -   Center calculated @ (-25, -25)
    Symbol unit 5 not centered on origin
     - Center calculated @ (-100, 0)
  Violating S4.1
    General pin requirements
    Pins not located on 100mil (=2.54mm) grid:
     - Pin C (6) @ (-400,50) 
     - Pin C2 (9) @ (350,-100) 
     - Pin D2 (10) @ (350,-100) 
     - Pin D (12) @ (-400,50) 
  Violating S5.2
    Footprint filters should match all appropriate footprints
    No footprint filters defined

 

Posted by Uli Köhler in Electronics, KiCAD

How to check KiCAD symbol library against KLC (KiCAD library conventions)

First, clone kicad-library-utils using

git clone https://gitlab.com/kicad/libraries/kicad-library-utils.git

Then, run the check script against your library using

~/kicad-library-utils/klc-check/check_symbol.py MyLibrary.kicad_sym -vv

You might need to adjust the path to kicad-library-utils accordingly.

This will provide colored output on the command line such as

Checking symbol 'Analog_Switch:FSA3157L6X':
Checking symbol 'Analog_Switch:NC7SB3157P6X':
  Violating S3.1
    Origin is centered on the middle of the symbol
    Symbol unit 1 not centered on origin
     - Center calculated @ (0, -112)
  Violating S3.6
    Pin name position offset
    Pin offset outside allowed range
     - Pin offset (5) should not be below 20mils

 

Posted by Uli Köhler in Electronics, KiCAD

How to connect CHIP_PU pin on ESP32Sx

The CHIP_PU (Chip PowerUp) pin on ESP32 family processors such as the ESP32S2 is equivalent to the EN (enable) or ~RST pin on other microcontrollers.

When CHIP_PU is pulled high (i.e. to 3.3V), the ESP32 is enabled and will run the firmware.

When CHIP_PU is pulled low (i.e. to GND), the ESP32 is disabled and shut down and will not run the firmware.

In other words, in most applications you want to connect a 10kOhm resistor from CHIP_PU to 3.3V and – if needed – a reset button from the CHIP_PU pin to GNDYou can not operate the ESP32S2 without a pullup resistor . This is clearly stated in the ESP32S2 datasheet, section 2.2:

Note: Do not leave the CHIP_PU pin floating.

On the ESP32S2-WROOM-I module, the CHIP_PU is directly connected to the module’s EN pin wut

Posted by Uli Köhler in ESP8266/ESP32

How to generate caddy basic auth password using docker-compose

docker-compose run caddy caddy hash-password

then enter the password.

Posted by Uli Köhler in Networking

How much RAM does the ESP32-C3 have?

The ESP32-C3 has 400 kBytes of integrated SRAM (16 kbytes cache).

Source: ESP32-C3 datasheet

Posted by Uli Köhler in ESP8266/ESP32

How to replace numpy NaNs by last non-NaN value

Use bottleneck.push:

import bottleneck

y = bottleneck.push(y, axis=0)

If not installed already, you can install bottleneck using

pip install bottleneck

 

Posted by Uli Köhler in Python

How to set matplotlib X axis timestamp format to hours and minutes

import matplotlib.dates as mdates

plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))

 

Posted by Uli Köhler in Python

How to change matplotlib X axis timestamp format

Sometimes you get weird timestamp format choices in Matplotlib such as 22 19:40 (meaning 19:40 o’clock on the 22nd of whatever month).

This is easy to fix:

import matplotlib.dates as mdates

plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))

which will result in this image, for example:

Posted by Uli Köhler in Python

How I fixed project transfer error Gitlab URI::InvalidURIError (query conflicts with opaque)

Problem:

Any time I was trying to transfer a project in my docker-hosted gitlab instance. the transfer failed with error 500 and I was presented with the following error log:

==> /var/log/gitlab/gitlab-rails/production.log <==
  
URI::InvalidURIError (query conflicts with opaque):
  
lib/container_registry/client.rb:84:in `repository_tags'
app/models/container_repository.rb:94:in `manifest'
app/models/container_repository.rb:98:in `tags'
app/models/container_repository.rb:118:in `has_tags?'
app/models/project.rb:2890:in `has_root_container_repository_tags?'
app/models/project.rb:1037:in `has_container_registry_tags?'
app/services/projects/transfer_service.rb:61:in `transfer'
app/services/projects/transfer_service.rb:35:in `execute'
app/controllers/projects_controller.rb:120:in `transfer'
app/controllers/application_controller.rb:490:in `set_current_admin'
lib/gitlab/session.rb:11:in `with_session'
app/controllers/application_controller.rb:481:in `set_session_storage'
lib/gitlab/i18n.rb:105:in `with_locale'
lib/gitlab/i18n.rb:111:in `with_user_locale'
app/controllers/application_controller.rb:475:in `set_locale'
app/controllers/application_controller.rb:469:in `set_current_context'
lib/gitlab/metrics/elasticsearch_rack_middleware.rb:16:in `call'
lib/gitlab/middleware/rails_queue_duration.rb:33:in `call'
lib/gitlab/middleware/speedscope.rb:13:in `call'
lib/gitlab/request_profiler/middleware.rb:17:in `call'
lib/gitlab/database/load_balancing/rack_middleware.rb:23:in `call'
lib/gitlab/metrics/rack_middleware.rb:16:in `block in call'
lib/gitlab/metrics/web_transaction.rb:46:in `run'
lib/gitlab/metrics/rack_middleware.rb:16:in `call'
lib/gitlab/jira/middleware.rb:19:in `call'
lib/gitlab/middleware/go.rb:20:in `call'
lib/gitlab/etag_caching/middleware.rb:21:in `call'
lib/gitlab/middleware/multipart.rb:173:in `call'
lib/gitlab/middleware/read_only/controller.rb:50:in `call'
lib/gitlab/middleware/read_only.rb:18:in `call'
lib/gitlab/middleware/same_site_cookies.rb:27:in `call'
lib/gitlab/middleware/handle_malformed_strings.rb:21:in `call'
lib/gitlab/middleware/basic_health_check.rb:25:in `call'
lib/gitlab/middleware/handle_ip_spoof_attack_error.rb:25:in `call'
lib/gitlab/middleware/request_context.rb:21:in `call'
lib/gitlab/middleware/webhook_recursion_detection.rb:15:in `call'
config/initializers/fix_local_cache_middleware.rb:11:in `call'
lib/gitlab/middleware/compressed_json.rb:26:in `call'
lib/gitlab/middleware/rack_multipart_tempfile_factory.rb:19:in `call'
lib/gitlab/middleware/sidekiq_web_static.rb:20:in `call'
lib/gitlab/metrics/requests_rack_middleware.rb:75:in `call'
lib/gitlab/middleware/release_env.rb:13:in `call'

Solution:

This error seems to occur if you had a docker registry configured in previous gitlab versions (a legacy docker repository) but doesn’t disappear even after deconfiguring the registry.

In order to fix it, I logged into the container using

docker-compose exec gitlab /bin/bash

and edited /opt/gitlab/embedded/service/gitlab-rails/app/models/project.rb using

vi /opt/gitlab/embedded/service/gitlab-rails/app/models/project.rb

where on line 2890 you can find the following function:

##
# This method is here because of support for legacy container repository
# which has exactly the same path like project does, but which might not be
# persisted in `container_repositories` table.
#
def has_root_container_repository_tags?
  return false unless Gitlab.config.registry.enabled

  ContainerRepository.build_root_repository(self).has_tags?
end

We just want Gitlab to ignore the repository stuff, so we insert return false after the return false unless Gitlab.config.registry.enabled line:

##
# This method is here because of support for legacy container repository
# which has exactly the same path like project does, but which might not be
# persisted in `container_repositories` table.
#
def has_root_container_repository_tags?
  return false unless Gitlab.config.registry.enabled
  return false

  ContainerRepository.build_root_repository(self).has_tags?
end

to fake Gitlab into thinking that repository does not have any legacy registries. After that, save the file and

sudo gitlab-ctl restart

after which you should be able to transfer your repositories just fine.

Posted by Uli Köhler in Docker, git

Teensy 4.x timer interrupt example using TimerTree

void myTimerInterrupt() {
  // TODO Your code goes here
  // The following functions may be useful here:
  // Timer3.start(); // Start counting & clear counter
  // Timer3.stop(); // => stop counting but do not clear counter
  // Timer3.restart(); // => clear counter
  // Timer3.resume(); // => Start, without clearing counter
}

void setup()
{
  Timer3.initialize(20000);
  Timer3.attachInterrupt(myTimerInterrupt);
  Timer3.start();
}

 

Posted by Uli Köhler in Arduino, Electronics, Embedded, Teensy

How to Arduino attachInterrupt() with both RISING and FALLING edge

You can use attachInterupt() with CHANGE to trigger on both RISING and FALLING flanks.

#define INTERRUPT_PIN 13 // Choose any pin with interrupt functionality here.

void myInterrupt() {
  // TODO Your code goes here.
}

void setup() {
  attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), myInterrupt, CHANGE);
}

 

Posted by Uli Köhler in Arduino, Embedded

Teensy 4.1 Arduino external interrupt (pin interrupt) minimal example

In setup(), use

attachInterrupt(digitalPinToInterrupt(23), myInterrupt, RISING);

to configure the interrupt on Teensy Pin 23 on the RISING edge.

Add this function, which will be called during the interrupt:

void myInterrupt() {
  // Your code goes here
}

 

Posted by Uli Köhler in Electronics, Teensy

How to get first day of next month in Javascript using DayJS

See the dayjs docs for more info about the library.

const now = dayjs();
const nextMonth = now.month(now.month() + 1).date(1).hour(0).minute(0).second(0);

// Example usage
console.info(nextMonth.format())
// Prints, for example, "2022-03-01T00:00:00+01:00"

This will also work in december as DayJS processes month(13) correctly:

const now = dayjs('2021-12-09');
const nextMonth = now.month(now.month() + 1).date(1).hour(0).minute(0).second(0);

// This prints '2022-01-01T00:00:00+01:00'
console.info(nextMonth.format());

 

Posted by Uli Köhler in Javascript
This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Cookie settingsACCEPTPrivacy &amp; Cookies Policy