Commit cc40a3c0 by cjt

updated schematic tool files

parent 73622538
......@@ -44,6 +44,7 @@ cktsim = (function() {
this.devices = []; // list of devices
this.device_map = new Array(); // map name -> device
this.voltage_sources = []; // list of voltage sources
this.finalized = false;
this.diddc = false;
......@@ -104,10 +105,14 @@ cktsim = (function() {
var type = component[0];
// ignore wires, ground connections, scope probes and view info
if (type == 'view' || type == 'w' || type == 'g' || type == 's' || type == 'L') continue;
if (type == 'view' || type == 'w' || type == 'g' || type == 's' || type == 'L') {
continue;
}
var properties = component[2];
var name = properties['name'];
if (name==undefined || name=='')
name = '_' + properties['_json_'].toString();
// convert node names to circuit indicies
var connections = component[3];
......@@ -134,11 +139,9 @@ cktsim = (function() {
else if (type == 'o') // op amp
this.opamp(connections[0],connections[1],connections[2],properties['A'],name);
else if (type == 'n') // n fet
this.n(connections[0],connections[1],connections[2],
properties['W/L'],name);
this.n(connections[0],connections[1],connections[2],properties['W/L'],name);
else if (type == 'p') // p fet
this.p(connections[0],connections[1],connections[2],
properties['W/L'],name);
this.p(connections[0],connections[1],connections[2],properties['W/L'],name);
}
}
......@@ -214,10 +217,16 @@ cktsim = (function() {
this.diddc = true;
// create solution dictionary
var result = new Array();
// capture node voltages
for (var name in this.node_map) {
var index = this.node_map[name];
result[name] = (index == -1) ? 0 : this.solution[index];
}
// capture branch currents from voltage sources
for (var i = this.voltage_sources.length - 1; i >= 0; --i) {
var v = this.voltage_sources[i];
result['I('+v.name+')'] = this.solution[v.branch];
}
return result;
}
}
......@@ -457,6 +466,12 @@ cktsim = (function() {
var index = this.node_map[name];
result[name] = (index == -1) ? 0 : response[index];
}
// capture branch currents from voltage sources
for (var i = this.voltage_sources.length - 1; i >= 0; --i) {
var v = this.voltage_sources[i];
result['I('+v.name+')'] = response[v.branch];
}
result['time'] = response[this.N];
return result;
}
......@@ -537,6 +552,7 @@ cktsim = (function() {
Circuit.prototype.add_device = function(d,name) {
// Add device to list of devices and to device map
this.devices.push(d);
d.name = name;
if (name) {
if (this.device_map[name] === undefined)
this.device_map[name] = d;
......@@ -599,6 +615,7 @@ cktsim = (function() {
Circuit.prototype.v = function(n1,n2,v,name) {
var branch = this.node(undefined,T_CURRENT);
var d = new VSource(n1,n2,branch,v);
this.voltage_sources.push(d);
return this.add_device(d, name);
}
......@@ -1029,9 +1046,11 @@ cktsim = (function() {
///////////////////////////////////////////////////////////////////////////////
// argument is a string describing the source's value (see comments for details)
// source types: dc,step,square,triangle,sin,pulse,pwl,pwlr
// source types: dc,step,square,triangle,sin,pulse,pwl,pwl_repeating
// returns an object with the following attributes:
// fun -- name of source function
// args -- list of argument values
// value(t) -- compute source value at time t
// inflection_point(t) -- compute time after t when a time point is needed
// dc -- value at time 0
......@@ -1071,32 +1090,35 @@ cktsim = (function() {
// post-processing for constant sources
// dc(v)
if (src.fun == 'dc') {
var value = src.args[0];
if (value === undefined) value = 0;
src.value = function(t) { return value; } // closure
var v = arg_value(src.args,0,0);
src.args = [v];
src.value = function(t) { return v; } // closure
}
// post-processing for step sources
// step(v_init,v_plateau,t_delay,t_rise,t_fall)
// step(v_init,v_plateau,t_delay,t_rise)
else if (src.fun == 'step') {
var v1 = arg_value(src.args,0,0); // default init value: 0V
var v2 = arg_value(src.args,1,1); // default plateau value: 1V
var td = Math.max(0,arg_value(src.args,2,0)); // time step starts
var tr = Math.abs(arg_value(src.args,3,1e-9)); // default rise time: 1ns
src.args = [v1,v2,td,tr]; // remember any defaulted values
pwl_source(src,[td,v1,td+tr,v2],false);
}
// post-processing for square wave
// square(v_init,v_plateau,t_period)
// square(v_init,v_plateau,freq)
else if (src.fun == 'square') {
var v1 = arg_value(src.args,0,0); // default init value: 0V
var v2 = arg_value(src.args,1,1); // default plateau value: 1V
var freq = Math.abs(arg_value(src.args,2,1)); // default frequency: 1s
var freq = Math.abs(arg_value(src.args,2,1)); // default frequency: 1Hz
src.args = [v1,v2,freq]; // remember any defaulted values
var per = freq == 0 ? Infinity : 1/freq;
var t_change = 0.01 * per; // rise and fall time
var t_pw = 0.49 * per; // half the cycle minus rise and fall time
pwl_source(src,[0,v1,t_change,v2,t_change+t_pw,v2,t_change+t_pw+t_change,v1,per,v1],true);
pwl_source(src,[0,v1,t_change,v2,t_change+t_pw,
v2,t_change+t_pw+t_change,v1,per,v1],true);
}
// post-processing for triangle
......@@ -1105,6 +1127,7 @@ cktsim = (function() {
var v1 = arg_value(src.args,0,0); // default init value: 0V
var v2 = arg_value(src.args,1,1); // default plateau value: 1V
var freq = Math.abs(arg_value(src.args,2,1)); // default frequency: 1s
src.args = [v1,v2,freq]; // remember any defaulted values
var per = freq == 0 ? Infinity : 1/freq;
pwl_source(src,[0,v1,per/2,v2,per,v1],true);
......@@ -1112,8 +1135,8 @@ cktsim = (function() {
// post-processing for pwl and pwlr sources
// pwl[r](t1,v1,t2,v2,...)
else if (src.fun == 'pwl' || src.fun == 'pwlr') {
pwl_source(src,src.args,src.fun == 'pwlr');
else if (src.fun == 'pwl' || src.fun == 'pwl_repeating') {
pwl_source(src,src.args,src.fun == 'pwl_repeating');
}
// post-processing for pulsed sources
......@@ -1122,18 +1145,18 @@ cktsim = (function() {
var v1 = arg_value(src.args,0,0); // default init value: 0V
var v2 = arg_value(src.args,1,1); // default plateau value: 1V
var td = Math.max(0,arg_value(src.args,2,0)); // time pulse starts
var tr = Math.abs(arg_value(src.args,3,1e-9)); // default rise time: 1ns
var tf = Math.abs(arg_value(src.args,4,1e-9)); // default rise time: 1ns
var pw = Math.abs(arg_value(src.args,5,1e9)); // default pulse width: "infinite"
var per = Math.abs(arg_value(src.args,6,1e9)); // default period: "infinite"
src.args = [v1,v2,td,tr,tf,pw,per];
var t1 = td; // time when v1 -> v2 transition starts
var t2 = t1 + tr; // time when v1 -> v2 transition ends
var t3 = t2 + pw; // time when v2 -> v1 transition starts
var t4 = t3 + tf; // time when v2 -> v1 transition ends
pwl_source(src,[t1,v1,t2,v2,t3,v2,t4,v1,per,v1],true);
pwl_source(src,[t1,v1, t2,v2, t3,v2, t4,v1, per,v1],true);
}
// post-processing for sinusoidal sources
......@@ -1144,16 +1167,14 @@ cktsim = (function() {
var freq = Math.abs(arg_value(src.args,2,1)); // default frequency: 1Hz
var td = Math.max(0,arg_value(src.args,3,0)); // default time delay: 0sec
var phase = arg_value(src.args,4,0); // default phase offset: 0 degrees
src.args = [voffset,va,freq,td,phase];
phase /= 360.0;
// return value of source at time t
src.value = function(t) { // closure
if (t < td) return voffset + va*Math.sin(2*Math.PI*phase);
else {
var val = voffset + va*Math.sin(2*Math.PI*(freq*(t - td) + phase));
return val;
}
else return voffset + va*Math.sin(2*Math.PI*(freq*(t - td) + phase));
}
// return time of next inflection point after time t
......@@ -1163,9 +1184,6 @@ cktsim = (function() {
}
}
// to do:
// post-processing for piece-wise linear sources
// object has all the necessary info to compute the source value and inflection points
src.dc = src.value(0); // DC value is value at time 0
return src;
......@@ -1531,6 +1549,7 @@ cktsim = (function() {
var module = {
'Circuit': Circuit,
'parse_number': parse_number,
'parse_source': parse_source,
}
return module;
}());
......@@ -594,8 +594,9 @@ schematic = (function() {
var json = [];
// output all the components/wires in the diagram
for (var i = this.components.length - 1; i >=0; --i)
json.push(this.components[i].json());
var n = this.components.length;
for (var i = 0; i < n; i++)
json.push(this.components[i].json(i));
// capture the current view parameters
json.push(['view',this.origin_x,this.origin_y,this.scale,
......@@ -932,6 +933,10 @@ schematic = (function() {
// for each electrical node
for (var location in this.connection_points)
(this.connection_points[location])[0].display_voltage(c,temp);
// let components display branch current info if available
for (var i = this.components.length - 1; i >= 0; --i)
this.components[i].display_current(c,temp)
}
}
......@@ -980,9 +985,6 @@ schematic = (function() {
c.fillText(text,(x - this.origin_x) * this.scale,(y - this.origin_y) * this.scale);
}
HTMLCanvasElement.prototype.totalOffset = function(){
}
// add method to canvas to compute relative coords for event
HTMLCanvasElement.prototype.relMouseCoords = function(event){
// run up the DOM tree to figure out coords for top,left of canvas
......@@ -1678,8 +1680,9 @@ schematic = (function() {
return [vmin,vmax,1.0/scale];
}
function engineering_notation(n,nplaces) {
function engineering_notation(n,nplaces,trim) {
if (n == 0) return("0");
if (trim == undefined) trim = true;
var sign = n < 0 ? -1 : 1;
var log10 = Math.log(sign*n)/Math.LN10;
......@@ -1694,8 +1697,10 @@ schematic = (function() {
if (nplaces > 0) {
endindex += nplaces + 1;
if (endindex > mlen) endindex = mlen;
while (mstring.charAt(endindex-1) == '0') endindex -= 1;
if (mstring.charAt(endindex-1) == '.') endindex -= 1;
if (trim) {
while (mstring.charAt(endindex-1) == '0') endindex -= 1;
if (mstring.charAt(endindex-1) == '.') endindex -= 1;
}
}
if (endindex < mlen)
mstring = mstring.substring(0,endindex);
......@@ -1717,6 +1722,9 @@ schematic = (function() {
return n.toString();
}
var grid_pattern = [1,2];
var cursor_pattern = [5,5];
// x_values is an array of x coordinates for each of the plots
// y_values is an array of [color, value_array], one entry for each plot
Schematic.prototype.graph = function(x_values,y_values,x_legend,y_legend) {
......@@ -1727,7 +1735,6 @@ schematic = (function() {
var right_margin = 25;
var bottom_margin = 45;
var tick_length = 5;
var pattern = [1,2];
var w = pwidth + left_margin + right_margin;
var h = pheight + top_margin + bottom_margin;
......@@ -1735,7 +1742,7 @@ schematic = (function() {
var canvas = document.createElement('canvas');
canvas.width = w;
canvas.height = h;
// the graph itself will be drawn here and this image will be copied
// onto canvas, where it can be overlayed with mouse cursors, etc.
var bg_image = document.createElement('canvas');
......@@ -1779,7 +1786,7 @@ schematic = (function() {
c.moveTo(temp,top_margin);
c.lineTo(temp,end);
} else
c.dashedLineTo(temp,top_margin,temp,end,pattern);
c.dashedLineTo(temp,top_margin,temp,end,grid_pattern);
c.stroke();
// tick mark
......@@ -1821,7 +1828,7 @@ schematic = (function() {
c.moveTo(left_margin,temp);
c.lineTo(left_margin + pwidth,temp);
} else
c.dashedLineTo(left_margin,temp,left_margin + pwidth,temp,pattern);
c.dashedLineTo(left_margin,temp,left_margin + pwidth,temp,grid_pattern);
c.stroke();
// tick mark
......@@ -1864,6 +1871,27 @@ schematic = (function() {
c.fillText(y_legend,0,0);
c.restore();
// save info need for interactions with the graph
canvas.x_values = x_values;
canvas.y_values = y_values;
canvas.x_legend = x_legend;
canvas.y_legend = y_legend;
canvas.x_min = x_min;
canvas.x_scale = x_scale;
canvas.y_min = y_min;
canvas.y_scale = y_scale;
canvas.left_margin = left_margin;
canvas.top_margin = top_margin;
canvas.pwidth = pwidth;
canvas.pheight = pheight;
canvas.tick_length = tick_length;
canvas.cursor_x = undefined;
canvas.sch = this;
// do something useful when user mouses over graph
canvas.addEventListener('mousemove',graph_mouse_move,false);
// return our masterpiece
redraw_plot(canvas);
return canvas;
......@@ -1883,9 +1911,73 @@ schematic = (function() {
return min;
}
function redraw_plot(canvas) {
var c = canvas.getContext('2d');
c.drawImage(canvas.bg_image,0,0);
function redraw_plot(graph) {
var c = graph.getContext('2d');
c.drawImage(graph.bg_image,0,0);
if (graph.cursor_x != undefined) {
// draw dashed vertical marker that follows mouse
var x = graph.left_margin + graph.cursor_x;
var end_y = graph.top_margin + graph.pheight + graph.tick_length;
c.strokeStyle = grid_style;
c.lineWidth = 1;
c.beginPath();
c.dashedLineTo(x,graph.top_margin,x,end_y,cursor_pattern);
c.stroke();
// add x label at bottom of marker
var graph_x = graph.cursor_x/graph.x_scale + graph.x_min;
c.font = '10pt sans-serif';
c.textAlign = 'center';
c.textBaseline = 'top';
c.fillStyle = background_style;
c.fillText('\u2588\u2588\u2588\u2588\u2588',x,end_y);
c.fillStyle = normal_style;
c.fillText(engineering_notation(graph_x,3,false),x,end_y);
// compute which points marker is between
var x_values = graph.x_values;
var len = x_values.length;
var index = 0;
while (index < len && graph_x >= x_values[index]) index += 1;
var x1 = (index == 0) ? x_values[0] : x_values[index-1];
var x2 = x_values[index];
// for each plot, interpolate and output value at intersection with marker
c.textAlign = 'left';
var tx = graph.left_margin + 4;
var ty = graph.top_margin;
for (var plot = 0; plot < graph.y_values.length; plot++) {
var values = graph.y_values[plot][1];
// interpolate signal value at graph_x using values[index-1] and values[index]
var y1 = (index == 0) ? values[0] : values[index-1];
var y2 = values[index];
var y = y1;
if (graph_x != x1) y += (graph_x - x1)*(y2 - y1)/(x2 - x1);
// annotate plot with value of signal at marker
c.fillStyle = element_style;
c.fillText('\u2588\u2588\u2588\u2588\u2588',tx-3,ty);
c.fillStyle = probe_colors_rgb[graph.y_values[plot][0]];
c.fillText(engineering_notation(y,3,false),tx,ty);
ty += 14;
}
}
}
function graph_mouse_move(event) {
if (!event) event = window.event;
var g = (window.event) ? event.srcElement : event.target;
g.relMouseCoords(event);
// not sure yet where the 3,-3 offset correction comes from (borders? padding?)
var gx = g.mouse_x - g.left_margin - 3;
var gy = g.pheight - (g.mouse_y - g.top_margin) + 3;
if (gx >= 0 && gx <= g.pwidth && gy >=0 && gy <= g.pheight) g.cursor_x = gx;
else g.cursor_x = undefined;
redraw_plot(g);
}
///////////////////////////////////////////////////////////////////////////////
......@@ -2112,7 +2204,9 @@ schematic = (function() {
this.connections = [];
}
Component.prototype.json = function() {
Component.prototype.json = function(index) {
this.properties['_json_'] = index; // remember where we are in the JSON list
var props = {};
for (var p in this.properties) props[p] = this.properties[p];
......@@ -2343,7 +2437,9 @@ schematic = (function() {
// make an <input> widget for each property
var fields = new Array();
for (var i in this.properties)
fields[i] = build_input('text',10,this.properties[i]);
// underscore at beginning of property name => system property
if (i.charAt(0) != '_')
fields[i] = build_input('text',10,this.properties[i]);
var content = build_table(fields);
content.fields = fields;
......@@ -2383,6 +2479,10 @@ schematic = (function() {
}
}
// default behavior: nothing to display for DC analysis
Component.prototype.display_current = function(c,vmap) {
}
////////////////////////////////////////////////////////////////////////////////
//
// Connection point
......@@ -2507,7 +2607,7 @@ schematic = (function() {
return '<Wire ('+this.x+','+this.y+') ('+(this.x+this.dx)+','+(this.y+this.dy)+')>';
}
Wire.prototype.json = function() {
Wire.prototype.json = function(index) {
var json = ['w',[this.x, this.y, this.x+this.dx, this.y+this.dy]];
return json;
}
......@@ -3024,14 +3124,18 @@ schematic = (function() {
//
////////////////////////////////////////////////////////////////////////////////
function Source(x,y,rotation,name,type,value) {
Component.call(this,type,x,y,rotation);
this.properties['name'] = name;
this.properties['value'] = value ? value : '1';
if (value == undefined) value = 'dc(1)';
this.properties['value'] = value;
this.add_connection(0,0);
this.add_connection(0,48);
this.bounding_box = [-12,0,12,48];
this.update_coords();
this.content = document.createElement('div'); // used by edit_properties
}
Source.prototype = new Component();
Source.prototype.constructor = Source;
......@@ -3068,10 +3172,141 @@ schematic = (function() {
this.draw_text(c,this.properties['value'],13,24,3,property_size);
}
Source.prototype.clone = function(x,y) {
return new Source(x,y,this.rotation,this.properties['name'],this.type,this.properties['value']);
// map source function name to labels for each source parameter
source_functions = {
'dc': ['DC value'],
'step': ['Initial value',
'Plateau value',
'Delay until step (secs)',
'Rise time (secs)'],
'square': ['Initial value',
'Plateau value',
'Frequency (Hz)'],
'triangle': ['Initial value',
'Plateau value',
'Frequency (Hz)'],
'pwl': ['Comma-separated list of alternating times and values'],
'pwl_repeating': ['Comma-separated list of alternating times and values'],
'pulse': ['Initial value',
'Plateau value',
'Delay until pulse (secs)',
'Time for first transition (secs)',
'Time for second transition (secs)',
'Pulse width (secs)',
'Period (secs)'],
'sin': ['Offset value',
'Amplitude',
'Frequency (Hz)',
'Delay until sin starts (secs)',
'Phase offset (degrees)'],
}
// build property editor div
Source.prototype.build_content = function(src) {
// make an <input> widget for each property
var fields = []
fields['name'] = build_input('text',10,this.properties['name']);
if (src == undefined) {
fields['value'] = this.properties['value'];
} else {
// fancy version: add select tag for source type
var src_types = [];
for (var t in source_functions) src_types.push(t);
var type_select = build_select(src_types,src.fun);
type_select.component = this;
type_select.addEventListener('change',source_type_changed,false)
fields['type'] = type_select;
if (src.fun == 'pwl' || src.run == 'pwl_repeating') {
var v = '';
var first = true;
for (var i = 0; i < src.args.length; i++) {
if (first) first = false;
else v += ',';
v += engineering_notation(src.args[i],3);
if (i % 2 == 0) v += 's';
}
fields[source_functions[src.fun][0]] = build_input('text',30,v);
} else {
// followed separate input tag for each parameter
var labels = source_functions[src.fun];
for (var i = 0; i < labels.length; i++) {
var v = engineering_notation(src.args[i],3);
fields[labels[i]] = build_input('text',10,v);
}
}
}
var div = this.content;
if (div.hasChildNodes())
div.removeChild(div.firstChild); // remove table of input fields
div.appendChild(build_table(fields));
div.fields = fields;
div.component = this;
return div;
}
function source_type_changed(event) {
if (!event) event = window.event;
var select = (window.event) ? event.srcElement : event.target;
// see where to get source parameters from
var type = select.options[select.selectedIndex].value;
var src = undefined;
if (this.src != undefined && type == this.src.fun)
src = this.src;
else if (typeof cktsim != 'undefined')
src = cktsim.parse_source(type+'()');
select.component.build_content(src);
}
Source.prototype.edit_properties = function(x,y) {
if (this.near(x,y)) {
this.src = undefined;
if (typeof cktsim != 'undefined')
this.src = cktsim.parse_source(this.properties['value']);
var content = this.build_content(this.src);
this.sch.dialog('Edit Properties',content,function(content) {
var c = content.component;
var fields = content.fields;
var first = true;
var value = '';
for (var label in fields) {
if (label == 'name')
c.properties['name'] = fields['name'].value;
else if (label == 'value') {
// if unknown source type
value = fields['value'].value;
c.sch.redraw_background();
return;
} else if (label == 'type') {
var select = fields['type'];
value = select.options[select.selectedIndex].value + '(';
} else {
if (first) first = false;
else value += ',';
value += fields[label].value;
}
}
c.properties['value'] = value + ')';
c.sch.redraw_background();
});
return true;
} else return false;
}
function VSource(x,y,rotation,name,value) {
Source.call(this,x,y,rotation,name,'v',value);
this.type = 'v';
......@@ -3081,6 +3316,32 @@ schematic = (function() {
VSource.prototype.toString = Source.prototype.toString;
VSource.prototype.draw = Source.prototype.draw;
VSource.prototype.clone = Source.prototype.clone;
VSource.prototype.build_content = Source.prototype.build_content;
VSource.prototype.edit_properties = Source.prototype.edit_properties;
// display current for DC analysis
VSource.prototype.display_current = function(c,vmap) {
var name = this.properties['name'];
var label = 'I(' + (name ? name : '_' + this.properties['_json_']) + ')';
var v = vmap[label];
if (v != undefined) {
// first draw some solid blocks in the background
c.globalAlpha = 0.85;
this.draw_text(c,'\u2588\u2588\u2588',0,24,4,annotation_size,element_style);
c.globalAlpha = 1.0;
// display the node voltage at this connection point
var i = engineering_notation(v,2) + 'A';
this.draw_text(c,i,0,24,4,annotation_size,annotation_style);
// only display each current once
delete vmap[label];
}
}
VSource.prototype.clone = function(x,y) {
return new VSource(x,y,this.rotation,this.properties['name'],this.properties['value']);
}
function ISource(x,y,rotation,name,value) {
Source.call(this,x,y,rotation,name,'i',value);
......@@ -3091,6 +3352,12 @@ schematic = (function() {
ISource.prototype.toString = Source.prototype.toString;
ISource.prototype.draw = Source.prototype.draw;
ISource.prototype.clone = Source.prototype.clone;
ISource.prototype.build_content = Source.prototype.build_content;
ISource.prototype.edit_properties = Source.prototype.edit_properties;
ISource.prototype.clone = function(x,y) {
return new ISource(x,y,this.rotation,this.properties['name'],this.properties['value']);
}
///////////////////////////////////////////////////////////////////////////////
//
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment