Sunday, March 29, 2015

Create accurate custom meter legends

My ability to give a shit has worn abnormally thin lately; bear with me.  Maybe if I gain some reason to believe that this entire blog is anything more than a masturbatory distraction from an interminable hopelessness, I might bother to put some effort into things.

Analog meters are handy for making things with.  Stick it on an amplifier project or an ugly cobbled-together kludge you like to pretend is a remotely useful bit of test gear.  It doesn't matter where you use them; sometimes it's just easier to use an analog meter than a digital one.  The only real obstacle in any application is creating the corresponding legend for the meter face.  Here, I'll show my method which works well for nonlinear scales.

With original meter face
In the course of finishing up my twice-aborted variant of Manfred Mornhinweg's ESR meter (I should've started from scratch), I decided that using a marker and some masking tape was just too ghetto for my tastes.  The scales in this application are not linear and I ended up needing dual scales to match the two range settings required in order to have a broad range and sufficient resolution for low resistance readings.  

To start, I took a set of known resistors in the meter's range and correlated their values to the existing 0-16 linear scale.  I performed this test in both ranges, resulting in two sets of resistances and scale values.  I then measured the radius and chord length of the existing meter scale.  

Measure this shit so you can find section angle and legend size
Using MATLAB, the measurements are interpolated onto a new semi-logarithmic resistance space to yield a legend with convenient divisions.  Depending on the application, curve fitting, linear interpolation, or spline interpolation can be used.  I decided to just use a spline with some endslope definitions to taste.

%% ESR meter scales
clc; clf; clear all;
format compact;

rtlo=[0.18 1 1.5 3 6.8 10 25];
rthi=[0.18 1 1.5 3 6.8 10 25 46];
xlo=[15.2 11.2 9.4 5 1 0.2 0];
xhi=[16 15.8 15.6 14.8 12.6 10 3.2 0.4];

% create semi logarithmic resistance space
%z = -1:1; d=1:9;
%R=cell2mat(arrayfun(@(i) d.*10^z(i),1:length(z),'UniformOutput',false));
R=[0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 ...
    1 1.5 2 2.5 3 4 5 6 7 8 9 ...
    10 15 20 25 30 40 50];

% linear interpolation
%Xlo=interp1(rtlo,xlo,R);
%Xhi=interp1(rthi,xhi,R);

% or calculate from rational fit (or other function type; try cftool)
%fitlo=[-12.57 162.3 2.023 10.16];
%fithi=[-103.5 6662 -4.942e+04 -8.055 355.9 -3069];
%Xlo=(fitlo(1)*R + fitlo(2)) ./ (R.^2 + fitlo(3)*R + fitlo(4));
%Xhi=(fithi(1)*R.^2 + fithi(2)*R + fithi(3)) ./ (R.^3 + fithi(4)*R.^2 + fithi(5)*R + fithi(6));

% or use splines see if i care
Xlo=spline(rtlo,xlo,R);
Xhi=spline(rthi,[-0.15 xhi -0.10],R);

figure(1);
semilogx(rtlo,xlo,'r',R,Xlo,'c'); hold on; grid on;
semilogx(rthi,xhi,'r',R,Xhi,'c');

Spline and original data (scale value versus resistance value)
The next step is to use plot functions to create a vector image that can be used as the final meter face. A sane person might invest this programmatic effort into directly assembling an SVG or something, but I'm just going to do it this way.   
%% curved scales
clf;
figure(2);
r=52; % scale centerline radius in mm
l=76; % scale chord length in mm
sw=2*acos(l/(2*r)); % scale swing in rad
fontsize=18;
linescale=0.5;

plot([0],[0],'+');

axis([-40 40 0 60]);
axis equal;
for n=1:22;
    if Xlo(n)<=16;
        phi=sw/2 - (Xlo(n)/16)*sw + pi/2;
        if sum(find(R(n)==[1.5 2.5 15 25]))==0;
            textrad=r+2.5;
            offset=0.005*fontsize/10;
            textstring=num2str(R(n));
            if R(n) < 1;
                t=text(textrad*cos(phi+offset),textrad*sin(phi+offset),textstring(2:end));
            else
                t=text(textrad*cos(phi+offset),textrad*sin(phi+offset),textstring);
            end
            set(t,'Rotation',(phi*180/pi)-90);
            set(t,'FontWeight','bold');
            set(t,'FontSize',fontsize);
        end
        linerad=[r r+1.5];
        h=line(linerad*cos(phi),linerad*sin(phi));
        if sum(find(R(n)==[1.5 2.5 15 25]))==0;
            set(h,'LineWidth',2*linescale)
        else
            set(h,'LineWidth',1*linescale)
        end
    end
end

for n=1:length(Xhi);
    if Xhi(n)<=16 && R(n)>=1;
        phi=sw/2 - (Xhi(n)/16)*sw + pi/2;
        if sum(find(R(n)==[1.5 2.5 15 25]))==0;
            textrad=r-2.5;
            offset=0.01*fontsize/10;
            t=text(textrad*cos(phi+offset),textrad*sin(phi+offset),num2str(R(n)));
            set(t,'Rotation',(phi*180/pi)-90);
            set(t,'FontWeight','bold');
            set(t,'FontSize',fontsize);
        end
        linerad=[r-0 r-1.5];
        h=line(linerad*cos(phi),linerad*sin(phi));
        if sum(find(R(n)==[1.5 2.5 15 25]))==0;
            set(h,'LineWidth',2*linescale)
        else
            set(h,'LineWidth',1*linescale)
        end
    end
end

th=pi/2-sw/2:sw/100:pi/2+sw/2;
h=line(r*cos(th),r*sin(th));
set(h,'LineWidth',3*linescale)

linerad=[r+3 r-4];
line(linerad*cos(pi/2-sw/2),linerad*sin(pi/2-sw/2));
set(h,'LineWidth',1*linescale)

The scale as produced by the script using plot/draw tools
The font size and line weights can be set.  Number alignment can be adjusted for each scale using an offset factor.  If at this point, the results look a little off here or there, that's okay.  Once things are exported, they can be adjusted. Save the plot as a .eps file and throw it in Inkscape to adjust a few things that don't line up and then scale the output for the physical world.  
 
The scale in Inkscape after adjusting a few things
Proper print scaling should result in the X-Y scales on the original plot coming out such that they read in millimeters on the page.  A simpler way to ensure the scale size is correct is to simply print the thing and measure it to back-calculate a scaling correction factor.  Reprint the legend with the correction factor and use the original meter scales to help with positioning. Cut it to size and mount it with spray adhesive.  Turn off the lights and stop caring.
 
Measuring a bad LUXON capacitor with the new meter face
This beats trying to eyeball things anyway.  Maybe someday I'll write about the ESR meter and the changes I made to the original design.  Maybe someday I'll have better things to do.  Maybe someday unicorns will fly out of my ass. 

No wait.  Not unicorns.  That would be bad.

Monday, March 9, 2015

Is that a varistor or a capacitor?

It's a question that's been posed before in forums, but often the answers aren't exactly satisfying or functionally useful.  Say you have a circuit board or a new or used loose component, but you can't tell if what you're looking at is a filter capacitor or a MOV.  Sometimes they look similar, and X1 filter caps and varistors share similar locations in a circuit.  To make things worse, the part markings and appearances are often confusingly similar. I hope to try to clear up the confusion as best I can ... by hiding my contribution here where nobody will ever read it.

There are a number of things to look for first.  Contrary to common suggestion, looking for certification marks is (in my opinion) a waste of time.  They may or may not be present in either case.  A quick determination comes though if the part is marked for X1 or Y1 filter duty.  If so, it's a capacitor.

If nothing obvious comes out of the markings at first glance, let's try one quick test to make sure we're even on the right track.  Measure the resistance across the terminals.  It shouldn't be conductive.  If it is, you may be looking at a PTC or NTC thermistor or polyfuse.  You can verify this with a heat gun.

Current-limiting thermistors can be identified with an ohmmeter.
Provided it's not a resistive device, look for the number code on the part.  Both capacitors and varistors may have a similar number code consisting of two significant digits and a multiplier.  For instance a code "682" expands to "6800".  If this were a capacitor, this is the capacitance in picofarads -- i.e. 6.8nF.  If this were a varistor, this would be the nominal varistor voltage at 1mA.  A 6.8 kV varistor would certainly be an uncommon find.  Some such codes would make the conclusion obvious.  Many varistors also have the voltage code prefaced by a size designation (in mm) such as 07, 10, 14, or 20.  This results in a longer code format that's uncommon in capacitor markings.

Various capacitors: 1nF, 2.2nF, 4.7nF, 680pF, 4.7nF, 3.3nF.  XY mark may be on either side.
if we can assume that a part in the front end of a 120v appliance isn't likely a 6800v varistor, we might begin to recognize that there are typical ranges of values corresponding to nominal mains voltages.  A typical selection routine for a varistor on mains may be to select a part with an AC RMS withstand rating greater than 1.15 times the nominal mains voltage.  For 120v, this is a part with a 1mA rating around 220v.  For 240v, a varistor may have a 1mA rating of 470v.  Just judging by my junk drawer, 470v MOV's seem quite common.

Varistors: 68v, 270v, 200v, 330v, 470v, 430v.  Markings are the 1mA voltage. Also note size markings (20mm, 14mm, 7mm)
If you're still not sure, you can check one last thing if you have a L/C meter.  Varistors act as capacitors too, but the values generally differ from what would be expected of a capacitor with the same marking.  Find the number code on the part and check to see if the device capacitance is a match.  If you have a 10mm diameter part marked "471", it should be 470pF if it's a capacitor.  If its a varistor, it will have a capacitance about half that.  Since the capacitance of a varistor depends on its diameter and thickness (obviously), don't assume it'll always be half.  Grab a random datasheet or two for a quick sanity check.

As an example, these were the measured capacitances for the pictured varistors:
  • 680K :: 20mm 68v = 10nF (not a 68pF cap)
  • 20K271U :: 20mm 270v = 1.65nF (not a 270pF cap)
  • 14K201U :: 14mm 200v = 1.05nF (not a 200pF cap)
  • G331K :: 7mm 330v = 215pF (not a 330pF cap)
  • 7N431K :: 7mm 430v = 200pF (not a 430pF cap)
  • 07D471K :: 7mm 470v = 110pF (not a 470pF cap)
Don't expect the measured capacitance to exactly match an offhand datasheet, but rough numbers are sufficient to indicate that none of these are capacitors.

These guidelines for marking codes are common, though not universal.  I have run across varistors where the value code denotes the working AC voltage instead of the zener voltage.  I have also run across varistors with a literal voltage value code instead of the scientific notation code.  If you really want to be sure of the part, you can put together a circuit to test the breakover characteristics of the part, but that pretty much assumes it's a MOV.  Otherwise, it will be a destructive test for sure.

As far as testing, an "octopus circuit" can be used if you have a multi-channel scope.  I only have a single scope channel, so all i use is a step-up transformer and large-value limiting resistors.  A standard F96T12 magnetic ballast can provide 370v and >490v outputs depending on configuration.  One could probably also use a diode multiplier ladder to generate a set of high test voltages.  Pay attention to the peak values and the maximum voltage allowable on the scope input and on the probes themselves.  Use sense dividers where necessary.  Don't shunt mains through the scope ground.  This method also works well for finding the breakdown voltage of gas tubes

A varistor marked in nominal 150Vac (breakdown at ~240V)

GTA exhibiting negative resistance characteristic
Ultimately, if you're a hobbyist and you're scrounging parts from scrapped equipment, you might be better off just pitching those questionable parts.  Consider that varistors are very inexpensive on ebay for common values, and that even a cheap chinese part is likely no more unreliable than a 20 year old used part of unknown history.  Currently 300Vac 7mm (07D471) parts can be had for $1.90 per 10 on ebay.

Sunday, February 22, 2015

Streamline a hopeless task

I mentioned previously about how to use YARIP to block search results on job boards.  Given the utter futility of trying to use the search tools on these sites to sort the unending stream of useless garbage results, I have gone on a bit of a crusade to reduce the amount of time it takes to sweep the boards I frequent.  Here I will include the YARIP blacklist templates I've compiled for these sites, but the keyword blacklisting technique for enforcing search exclusion is a bit of an inelegance.  Since this keyword list would ideally be used for all sites, it makes more sense to do this sort of multiple-site element blacklisting with a userscript rather than YARIP.  Since I suck at writing Javascript, I just wrote a bash script to generate the XML files for YARIP. 

For jobs.ieee.org:
//div[@id='ad_module_120x90']
//div[@id='ad_module_300x250']
//div[child::div/div/div/h3/a[contains(translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'),  'keyword')]]
For LinkedIn:
//div[@id='header']
//div[@id='responsive-nav-scrollable']
//div[@id='top-header']
//li[child::div/h3/a[contains(translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'),  'keyword')]]
For Indeed.com:
//div[@id='bjobalerts']
//div[@id='femp_list']/div[2][@class='femp_item']
//div[@id='serpRecommendations']
//div[@id='tjobalerts']
//div[child::h2/a[contains(translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'),  'keyword')]]
For Ziprecruiter.com:
//div[@id='email_alert_form_wrapper']
//li[child::a/h4/span[contains(translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'),  'keyword')]]
For Beyond.com:
//body[@id='body']/div[4][@class='container']/div[@class='row']/div[@class='col-md-12 ']/div[@class='panel panel-default']/div[@class='panel-body no-padding']/div[@class='row']/div[2][@class='col-sm-8 col-md-6']/ul[@class='list-group list-group-lined margin-bottom-sm']/li[28][@class='list-group-item list-group-item-registration-panel']
//body[@id='body']/div[4][@class='container']/div[@class='row']/div[@class='col-md-12 ']/div[@class='panel panel-default']/div[@class='panel-body no-padding']/div[@class='row']/div[3][@class='col-sm-4 col-md-3']
//body[@id='body']/div[4][@class='container']/div[@class='row']/div[@class='col-md-12 ']/div[@class='panel panel-default']/div[@class='panel-body no-padding']/div[@class='row']/div[3][@class='col-sm-4 col-md-3']/div[3][@class='row ng-scope']/div[@class='col-xs-12']/ul[@class='list-group list-group-lined no-margin-bottom-sm']/li[2][@class='list-group-item']
//body[@id='body']/header/nav[1][@class='navbar navbar-default navbar-fixed-top']
//div[@class='job-companylogo hidden-xs']
//div[@class='job-footer']
//div[@id='Display_Header_Top_Text_Position1']
//div[@id='TopNav']/div[@class='navbar-left navbar-center ng-scope']
//img[@class='Areas/Jobs/Search-Index/FeaturedJobStar.gif job-featured-icon']
//li[@class='list-group-item google-ad-zone-bg']
//li[child::div/a/h4[contains(translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'),  'keyword')]]
Beyond.com search results with cleanup and keyword exclusion blacklists

Indeed.com search results with cleanup and keyword exclusion blacklists
As the images hopefully convey, a bit of work with YARIP blacklists makes it easy to see at a glance if there are any interesting results on a given page.  Removing the clutter and known irrelevant chaff speed up the process and make the experience much less frustrating ... though it does nothing to make filling out broken applications any less of a nightmare.  Of course, it takes a while to build a suitable blacklist of keywords based on what you are and are not looking for.

The hazard that comes with forcing search relevance is that you may find that very few jobs are posted that will fit even an unreasonably broad set of criteria.  With my short list of keywords to essentially enforce "entry level electrical engineer", I eliminated roughly 60% to 80% of all search results.  If something similar happens to you, do not be alarmed, but know that the people who speak of booming demand for tech jobs and a robust economic recovery are not merely mistaken, but lying

Friday, November 21, 2014

Remove unwanted results and other garbage from google searches

Do I have to even explain why this is useful?  I would just assume that people shouldn't naturally enjoy having their daily tasks obstructed by the injection of useless garbage into the information they're trying to parse, but the fact that part of this solution addresses social media links tells me this is probably not the case.

As usual, YARIP comes to the rescue.  To get rid of the top, bottom, and side ads:
//div[@id='tvcap']
//div[@id='bottomads']
//div[@id='rhs_block']
To get rid of extended results boxes for local business locations:
//div/li[@id='lclbox']
To remove all listings from a particular website from the search results:
//li[child::div/div/div/div/cite[contains(., 'pinterest.com')]]
//li[child::div/div/div/div/cite[contains(., 'facebook.com')]]
//li[child::div/div/div/div/cite[contains(., 'twitter.com')]]
//li[child::div/div/div/div/cite[contains(., 'huffingtonpost.com')]]
//li[child::div/div/div/div/cite[contains(., 'pitchfork.com')]]
While it's certainly nice to be able to slap that useless trash off the visible web, there are other more useful strategies for single-site blocking.  How about getting rid of sites that just dilute the results with repeated similar pages?
//li[child::div/div/div/div/cite[contains(., 'alibaba.com')]]
Google does provide several different forms of "extended results", such as the local business listings, maps, conversion utilities, image search previews, and youtube items.  Each of these can also be blocked depending on what irks you and what your browsing habits cause you to encounter.

Exclusion through YARIP is also a quick cure for frustration on any other sites that tend to return volumes of irrelevant or unwanted results for specific searches or where available search tools don't allow exclusions.  One of my current favorite applications is dealing with poor search refinement tools on internet job boards.  For example, perform case insensitive exclusion with in the job title field on LinkedIn:
//li[child::div/h3/a[contains(translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'),  'sales engineer')]]
//li[child::div/h3/a[contains(translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'),  'senior')]]
//li[child::div/h3/a[contains(translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'),  'federal government')]]
//li[child::div/h3/a[contains(translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'),  'information systems')]]
//li[child::div/h3/a[contains(translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'),  'information technology')]]
//li[child::div/h3/a[contains(translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'),  'executive')]]
Of course, each site and set of search tools has its own limitations and it's the conflict between these limitations and your usage patterns that will dictate how useful any of this might be. 

Matlab: Encode images in audio spectrum

UPDATE: This sandbox script has evolved into the functions im2spectrogram() and text2spectrogram() in my aptly named Matlab Image Mangling Toolbox

A while back, I got bored and was looking for various ways to shove pictures where they didn't belong.  Among simpler ideas like concatenation, arbitrary character encoding schemes, and spreadsheet conversion, I tried my hand at conversion to audio.  Using this particular STFT/ISTFT set of tools, as well as basic parts of the image processing toolbox, I threw together this kludge:
%% simplified automatic spectrogram obfuscation

clc; clear all;
format compact;

% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

projdir='/home/assbutt/projects/imagepooper/';
inpict=imread([projdir 'test.jpg'], 'jpeg');
outwavname=[projdir 'soundpicture.wav']; 

% typical image adjustments
invert=0;           % invert if 1
flip=0;             % flip horizontal if 1
bluramt=1;          % gaussian blur amount (approx 0.5-10) (zero for no blur)
blurrad=3;          % gaussian blur radius (approx 2-5)

alteraspect=0.80;   % correct for viewer distortion
padbar=0.08;        % relative height of top padding (H=1+padwidth)
volume=1;           % adjust signal volume (will clip beyond unity)

% add blur to reduce bright edge artifacts
% use nonzero padbar to keep image below mp3 cutoff (0 for none)

% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% desaturate color images (choose method if desired)
if (ndims(inpict) == 3); inpict=mrgb2gray(inpict,'default'); end

% invert where requested
if (invert == 1); inpict=255-inpict; end

% flip where requested
if (flip == 1); inpict=fliplr(inpict); end

inpict=flipud(inpict);      % flip image (low-f on bottom)
inpict=imadjust(inpict);    % set image contrast

magicnum=660;       
inaspect=length(inpict(1,:))/length(inpict(:,1));
inpict=padarray(inpict,[round(padbar*length(inpict(:,1))) 0],'post'); 
inpict=imresize(inpict,magicnum*[1 alteraspect*inaspect/(1+padbar)]);

if (bluramt > 0);
    h=fspecial('gaussian', blurrad*[1 1], bluramt);
    inpict=imfilter(inpict,h);
end

nft=length(inpict(:,1))*2-2;
h=nft; 
samplefreq=44100;
[x,t]=istft(inpict,h,nft,samplefreq);
[stft, f, t_stft]=stft(x, nft, h, nft, samplefreq);

figure(1)
subplot(1,2,1); plot(t, x);
subplot(1,2,2); imshow(flipdim(real(stft),1));
cmap=colormap('gray');
colormap(flipud(cmap))

xp=x/(max(abs(x))*1.0001)*volume;
wavwrite(xp, samplefreq, 32, outwavname)
Of course, one kind of has to guess at the transform size a person might use when configuring the script parameters, otherwise the arbitrariness of frequency-time scaling makes it basically impossible to enforce any first-view aspect ratio. Most photographs sound pretty terrible, though feeding it high-contrast images with little white content produce nicer audio sweeps.

We take this image
And the script poops out an audio file with a spectrogram like this (using foobar2000)
View your output with foobar2000, audacity, or baudline.  The behavior could probably be improved, but this script is just a novelty.  Nobody cares. I don't care anymore either.

At the time I made this, I also pooped out a version that encodes text strings as a marquee in the top end of the frequency spectrum.  The output is a sound file containing the text.  Simply mix one or more of these files with some music to obtain a song full of hidden inaudible text. I probably should've just made the core of these scripts modular, but I wasn't sure what parts would be common.
%% create text marquee for spectrogram

clc; clear all;
format compact;

% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

instring='put a whole shitload of text here';

projdir='/home/assbutt/projects/imagepooper/';
outwavname=[projdir 'soundpicture.wav']; 

% typical image adjustments
alteraspect=0.80;   % correct for viewer distortion
textheight=0.03;    % relative height of text
textlocation=19000; % frequency center of text
volume=0.1;         % adjust signal volume (will clip beyond unity)

bluramt=5;          % gaussian blur amount (approx 0.5-10) (zero for no blur)
blurrad=3;          % gaussian blur radius (approx 2-5)

% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

magicnum=660;       
samplefreq=44100;

inpict=uint8(text2im(instring));
inpict=255*(1-inpict);

toplim=samplefreq*(1-textheight/2)/2;
botlim=samplefreq/2-toplim;
if (textlocation > toplim); 
    textlocation=toplim; disp('supramaximal text center');end
if (textlocation < botlim); 
    textlocation=botlim; disp('subminimal text center');end

inaspect=length(inpict(1,:))/length(inpict(:,1));
inpict=imresize(inpict,magicnum*textheight*[1 inaspect]);
botpad=floor(magicnum*((2*textlocation/samplefreq) - textheight/2));
toppad=ceil(magicnum*(1-textheight)-botpad);

inpict=padarray(inpict,[0 10],'both'); % pad ends
inpict=padarray(inpict,[toppad 0],'pre'); % top pad
inpict=padarray(inpict,[botpad 0],'post'); % bottom pad
inaspect=length(inpict(1,:))/length(inpict(:,1)); % recalculate
inpict=imresize(inpict,magicnum*[1 alteraspect*inaspect]);
inpict=flipud(inpict);      % flip image (low-f on bottom)

if (bluramt > 0);
    h=fspecial('gaussian', blurrad*[1 1], bluramt);
    inpict=imfilter(inpict,h);
end

nft=length(inpict(:,1))*2-2;
h=nft; 
[x,t]=istft(inpict,h,nft,samplefreq);
[stft, f, t_stft]=stft(x, nft, h, nft, samplefreq);

figure(1)
subplot(1,2,1); plot(t, x);
subplot(1,2,2); imshow(flipdim(real(stft),1));
cmap=colormap('gray');
colormap(flipud(cmap))

xp=x/(max(abs(x))*1.0001)*volume;
wavwrite(xp, samplefreq, 32, outwavname)

Multiple rows of text (one is flipped) mixed into a song
Of course, I only noticed after the fact that I had reinvented the wheel.

Wednesday, November 19, 2014

Matlab: Compile all open figures as a single pdf

As part of my network status log processing script, I needed a way to export weekly overview plots in a format that could be used to convey problems to other people at a glance.  The larger body of the script is a block-mode kludge which does the actual log file processing and plotting in whatever manner I choose.  The final block is a brief bit which rolls the current figure windows into a single pdf.
%% print plots to pdfs; use pdfshuffler to cat or fix rotations

filebasename='/home/ikilledsuperman/plot_%d.pdf'
outputname='/home/ikilledsuperman/outputlogfile.pdf'

m=length(findall(0,'type','figure'));
filelist=cell(m,1);
for n=1:1:m
    figure(n);
    set(n,'PaperUnits','normalized');
    set(n,'PaperPosition', [0 0 1 1]);  %fit to page
    filelist(n)=cellstr(sprintf(filebasename,n));
    print(gcf, '-dpdf', sprintf(filebasename,n));
end

delete(outputname)
append_pdfs(outputname, filelist{:});
delete(filelist{:})
As the comment says, pdfshuffler can be used to put the finishing touches on any generated pdfs.  Close any unneeded figure windows with 'close all'.  This script makes use of append_pdfs.  

Friday, November 7, 2014

Reduce risk of mains flashover on a marginal PCB assembly

Thunderstorms are probably of little concern to most people, but lightning has an absurdly strong tendency to destroy my equipment.  Despite robust grounding and protection measures, hardly a year has gone by without loss of televisions, phones, modems, motors, motherboards, or HVAC controls.  It's for this reason and the occasional phase loss on the three phase service that I was faced with a charred phase protection relay.
Kailn TG30s
The particular device was another chinese Ebay special: the Kailn TG30s.  The relay is a compact rail-mountable package and had performed suitably under test.  The internals were quite similar to a quick and dirty design I had sketched up before realizing that the autumnal comfort of my sustained apathy in purposelessness was more valuable than the $8 needed to buy the part from china.  Less than a week after installation, a storm rolled through and blew it to smithereens along with an unrelated compressor motor.

The relay is constructed from two boards soldered to each other at right angles with metal support straps on either side.  The mains sensing/power networks are combinations of resistive and reactive dividers in series with protection thermistors.  The capacitors and thermistors are through-hole devices placed in a very compact arrangement, though creepage distances do meet the IEC61010-1 minimum spec for reinforced insulation. 

Inside the case, there is nothing but black soot and the smell of burned epoxy.  After a bit of degreaser and soap, the failure sequence became apparent. A few observations led me to my conclusions:

  • Extensive charring on the top side near the mains terminals and thermistors
  • Fused traces leading to divider networks, but no caps were punctured
  • Deep burning and metallizing of case adjacent to PCB near thermistor leads
  • The device tripped a 30A thermal breaker nearly an entire second after the lightning strike
  • PCB is contaminated with flux, solder balls and stray solder from post-line hand assembly
  • Thermistor and cap leads are trimmed relatively long


Missing traces and charring down to the glass (after cleaning)

Metallization and charring of plastic case (after cleaning)
My guess is that the strike flashed over on the board near the terminal strip or thermistor leads, either between untrimmed leads or possibly along the board surface.  In a confined case, with freshly carbonized and metallized surfaces everywhere, conduction continued long after the strike impulse passed, allowing ample time for deep charring and thermal loading of the main breaker.

I should clarify that the device wasn't entirely to blame for the failure.  While inexpensive, it did get hit with a strike impulse that was also able to damage other equipment.  This venture is more about using my knowledge to add value to the inexpensive so that I can keep motors protected while not going broke (as fast).  It's a simplistic exercise in general techniques for enforcing isolation when voltages are high or when humidity or environmental contamination becomes a risk.  

  • Trim and ball component leads
  • Clean the board of residues and solder fines
  • Apply conformal coating

When the new device arrived, I immediately popped it open and looked it over.  The build finish was similar: long untrimmed leads, no-clean flux residues and solder fines.  I clipped the component leads short and added sufficient solder to ball the joints.  After thoroughly cleaning the assembly, I brushed on a couple applications of conformal coating in the board areas associated with mains-level voltages.  Attention should also be paid to keeping the coating from encroaching closer than a few mm from the board edge, as the PCB fits into a retainer groove in its case.  I wanted to make sure that the gap between the PCB edges was sealed, so I allowed the coating to flow through from the top before coating the bottom.  In this manner, air entrapment was avoided.

Coated top side (note balled terminal strips)
New board (top) after balling and coating.  Old board (bottom) showing untrimmed leads and missing traces
The coating material I used is not a product marketed for the purpose.  I used a solution of clear Dap Sidewinder sealant diluted with xylene.  Sidewinder is (as far as I know) a modified silicone.  The cured material has some excellent properties -- and no, it's not corrosive to the assembly as an acetoxy cure RTV silicone would be. With a bit of patience and stirring, the resin can be diluted to consistencies ranging from honey to warm maple syrup.  A brushed coating is relatively self-leveling and will dry to the touch in a few hours, though a day or so should be allowed for full curing.