Monday, 1 June 2026

ABS Sensor Testing

My son's car (2016 Peugeot 208) quite suddenly threw up some alarming-looking warnings recently about ABS, ESP and tyre-pressure monitoring not working. Reading the error-codes from the ECU memory threw up DTC C1331, indicating a fault with the right-hand side ABS sensor:-

 
For reasons that needn't detain us here, I wound out swapping out both front sensors - presumtively one good and one bad.  
 
What is someone with an oscilloscope and some time on their hands supposed to do?
 
I had already determined that the sensor was a hall-effect sensor that couldn't be tested with a multimeter. It was an "active" sensor that required power to operate.  The sensor part number (for the benefit of future generations searching for this information) is PA66-GF26.  It has a two-pin connector on it.
 
 
I struggled to find any good information about how this sensor should be connected up and what the output of it should look like (and indeed how the output of this active sensor would be communicated to the ABS Control Unit, considering that it needs power and only has two pins.  It looks like pin 1 takes power via a resistor (that is internal to the ABS module) and pin 2 is ground:-
 
 
 
The meter in the diagram shows where I hooked up a multimeter (initially) and an oscilloscope (later).  
 
The outcome is interesting.  I had assumed I would see a simple binary "on" or "off" depending on the presence of a magnetic field. That's not what I got at all. Instead, the sensor sends an 18KHz square wave (varying from about +2V to +8.5V) on the + line when there is a magnetic field present.  When there is no magnetic field present, the line just sits at +5V.  There are also periodic (every 2ms) "gaps" in the 18KHz signal, presumably so the ECU can distinguish between a real signal and just any old 18KHz noise.
 
Here are the oscilloscope traces captured with a magnet held close to the sensor:-
 

 The horizontal and vertical cursors show what's going on.  The vertical cursors show the signal on pin 1 being varied from +2.1V to +8.4V (which can only happen is there is a fairly high output impedence on the input to pin 1: I took a complete shot in the dark and used a 1K resistor between the 12V supply and that pin).  The horizontal cursors show the 2ms interval between "gaps" in the 18KHz signal, presumably to allow the ABS module to distinguish the signal from random noise (I'm guessing).
 


It's not a symmetrical square wave: the duty cycles is about 81%.
 
 Now I just need to think of something useful to do with the good sensor I took out of the car 🙂 
 

Friday, 29 August 2025

Getting UTF-8 from PHP and MySQL

This turned out to be more annoying problem than I expected. 

Consider this MySQL table:- 

 MariaDB [demo]> show create table users\G
*************************** 1. row ***************************
       Table: users
Create Table: CREATE TABLE `users` (
  `name` varchar(64) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci
1 row in set (0.000 sec)

MariaDB [demo]> select * from users;
+------------------------+
| name                   |
+------------------------+
| Éámónn McGóníglé       |
+------------------------+
1 row in set (0.000 sec)

Note all of the accented characters (called "síneadh fada" or just "fadas" in Irish), which are about to cause us some headaches.

Now consider this bit of PHP code which retrieves names from that database:- 

<?php
$dsn = "mysql:host=localhost;dbname=demo";
$dbh = new PDO($dsn, "root");
$stmt = $dbh->query("SELECT name FROM users");
$res = $stmt->fetch(PDO::FETCH_ASSOC);
print($res['name']."\n");
 

The result is not what you might expect:-

$ php demo.php 
nn McGlamonn 

All of the accented characters have been lost and there is clearly some other weirdness going on. In a web-browser, there is a weird "placeholder" character everywhere an accented character should be:-


What's going on?  The charset of the database table is set to "UTF-8" and the "Content-type:" header of the response coming from the web server is "text/html; charset=UTF-8", just as it should be.  So why the mess?  

StackExchange proposed all sorts of solutions, but the one that worked was a change to the MySQL connect string, adding a ";charset=utf8" suffix:-

 <?php
$dsn = "mysql:host=localhost;dbname=demo;charset=utf8";

With that small change...

eamonn@redranger:~/public_html$ php demo.php 
Éámónn McGóníglé

Credit where it is due, the Stack Overflow post that helped was https://stackoverflow.com/questions/4475548/pdo-mysql-and-broken-utf-8-encoding/21373793#21373793

 One nuance is that

 <?php
$dsn = "mysql:host=localhost;dbname=demo;charset=utf8mb4";

 ...might be a better choice.  Apparently, in MySQL's world, "utf8" admits only three-byte UTF-8 characters (so not full UTF-8) while "utf8mb4" allows for four-byte UTF-8 characters.  Sure enough, if I try to insert a name with a four-byte UTF-8 character into the database with a charset of "utf8", it fails:-

MariaDB [demo]> insert into users values ("Smiley McSmiley 🙂");
ERROR 1366 (22007): Incorrect string value: '\xF0\x9F\x99\x82' for column `demo`.`users`.`name` at row 1

So I recreate the database table with a charset of "utf8mb4":-

MariaDB [demo]> create table users  (name varchar(64)) charset utf8mb4;
Query OK, 0 rows affected (0.214 sec)

MariaDB [demo]> set names utf8mb4;
Query OK, 0 rows affected (0.000 sec)

MariaDB [demo]> insert into users values ("Smiley McSmiley 🙂");
Query OK, 1 row affected (0.046 sec)

MariaDB [demo]> select * from users;
+----------------------+
| name                 |
+----------------------+
| Smiley McSmiley 🙂     |
+----------------------+
1 row in set (0.000 sec)

However, the PHP script fails to crack a smile:-

$ php demo.php 
Smiley McSmiley ?


 But if I change...

<?php
$dsn = "mysql:host=localhost;dbname=demo;charset=utf8";
 

...to...

<?php
$dsn = "mysql:host=localhost;dbname=demo;charset=utf8mb4";
 

 ...it's smiles all-round:-

$ php demo.php 
Smiley McSmiley 🙂


 

Another solution which is more trouble and less good is to use the PHP call mb_convert_encoding.  I did get it to work for accented characters.  The trick is to convert the ISO-8859-1 character that MySQL returns (without the ";charset=utf8" specified in the connect string) to a UTF-8 character. This works for accented characters such as á, é etc. that have a single-byte mapping in ISO-8859-1, but not for 🙂.  UTF-8 is a better way to do it.

 

 

 

 


Wednesday, 14 August 2024

JavaScript Destructuring Assignment Gotcha

This wasted several hours on me. I'll explain exactly what I was doing that got me into this mess a little later, but the bare essence of the problem that I ran into was this:-

let a = "this is a" let b = "this is b" [a,b] = ["foo", "bar"] console.log(a) console.log(b)

What would you expect it to output? I expected:-

foo bar

That's not what I got, though:-

$ node foo.js /home/eamonn/tmp/foo.js:4 [a,b] = ["foo", "bar"] ^ ReferenceError: Cannot access 'b' before initialization at Object. (/home/eamonn/tmp/foo.js:4:4) at Module._compile (node:internal/modules/cjs/loader:1358:14) at Module._extensions..js (node:internal/modules/cjs/loader:1416:10) at Module.load (node:internal/modules/cjs/loader:1208:32) at Module._load (node:internal/modules/cjs/loader:1024:12) at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:174:12) at node:internal/main/run_main_module:28:49

The error-message made no sense to me. To fast-forward past lots of tearing-out-of-hair, changing the code to this (note the addition of the ";" on the second line):-

let a = "this is a" let b = "this is b"; [a,b] = ["foo", "bar"] console.log(a) console.log(b)

...makes everything work as it should:-

$ node foo.js foo bar

So what's going on? The answer is that the two lines...

let b = "this is b" [a,b] = ["foo", "bar"]

...are actually parsed as:-

let b = "this is b"[a,b] = ["foo", "bar"]

Note that b is being used to to try to dereference the "array" that is the string "this is b". But, of course, b isn't defined at this point so can't be used to dereference anything (and in this light the error-message makes more sense).

My JavaScript coding style is was to omit trailing ";" on the basis that they aren't necessary. It turns out that this is only true most of the time. I'm starting to seriously rethink that style now.

What was I doing? I had two "fetch" commands that I wanted to kick off in parallel. I had some processing to do that depended on the results of both of them. Rather than running them sequentially, I wanted to do this:-

(async () => { let first_response let second_response try { // Schedule the two fetches to run asynchronously const first_promise = fetch("http://my.server.ie/firstthing.json") const second_promise = fetch("http://my.server.ie/secondthing.json") // Wait for the two fetches to complete [first_response, second_response] = await Promise.all([first_promise, second_promise]) } catch(error) { ...do something if either of the fetch() calls craps out } // Process the retrieved data const first_result = await first_response.json() const second_result = await second_response.json() process_results(first_result, second_result) })()

But it would fail:-

$ node baa.js ReferenceError: Cannot access 'second_promise' before initialization at /home/eamonn/tmp/baa.js:12:79 [...]

Adding in that semicolon to clear up the "ambiguity" about the destructuring assignment solves the problem just fine:-

(async () => { let first_response let second_response try { // Schedule the two fetches to run asynchronously const first_promise = fetch("http://my.server.ie/firstthing.json") const second_promise = fetch("http://my.server.ie/secondthing.json"); // Wait for the two fetches to complete [first_response, second_response] = await Promise.all([first_promise, second_promise]) } catch(error) { ...do something if either of the fetch() calls craps out } // Process the retrieved data const first_result = await first_response.json() const second_result = await second_response.json() process_results(first_result, second_result) })()

...and the code works as it should

References

I got the clue I needed from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment. There is a particular sentence buried in there:-

If your coding style does not include trailing semicolons, the ( ... ) expression needs to be preceded by a semicolon, or it may be used to execute a function on the previous line.

Not exactly my issue, but close enough that it pointed me in the right direction. Thanks, Mozilla Developer Network, you are endlessly useful and informative.

Having seen the light, I did some research into the merits and demerits of relying on automatic semicolon insertion. The (now deprecated in favour of Typescript) Google JavaScript Style Guide is pretty emphatic (https://google.github.io/styleguide/jsguide.html#formatting-semicolons-are-required):-

Every statement must be terminated with a semicolon. Relying on automatic semicolon insertion is forbidden.

That's good enough for me.

Thursday, 25 April 2024

try...catch...finally - Not (quite) what I expected

Consider this bit of code:-

def foo(): print("Called foo()") try: print("In the 'try' block") 10 / 0 except Exception as e: print("Got an exception: ", str(e)) finally: print("In the 'finally' block") print("About to return from foo() at the bottom") foo()

What do you think it will output? It probably won't come as a surprise:-

$ python3 a.py Called foo() In the 'try' block Got an exception: division by zero In the 'finally' block About to return from foo() at the bottom

Now consider this slightly variation:-

def foo(): print("Called foo()") try: print("In the 'try' block") 10 / 0 except Exception as e: print("Got an exception: ", str(e)) return print("In the 'except' block, after the 'return' statement") finally: print("In the 'finally' block") print("About to return from foo() at the bottom") foo()

Note the addition of the return statement to the except block. I would have bet money that it would behave differently: that the return in the except block would return straight out of foo(). and the finally block would not be reached. I would have lost the money:-

$ python3 a.py Called foo() In the 'try' block Got an exception: division by zero In the 'finally' block

The return statement in the except block does not exit foo() immediately: rather it runs the finally block and then returns. I can't claim credit for noticing this: my youngest son got caught out by this behaviour and told me about it.

It got me to wondering if other languages behaved the same way. Let's try it in Javascript:-

function foo() { console.log("Called foo()") try { console.log("In the 'try' block") throw new Error("oops") } catch(e) { console.log("Got an exception: ", e) return console.log("In the 'catch' block, after the 'return' statement") } finally { console.log("In the 'finally' block") } console.log("About to return from foo() at the bottom") } foo()

It behaves in exactly the same way:-

$ node a.js Called foo() In the 'try' block Got an exception: Error: oops at foo (/home/eamonn/tmp/a.js:7:15) at Object. (/home/eamonn/tmp/a.js:22:1) at Module._compile (node:internal/modules/cjs/loader:1198:14) at Object.Module._extensions..js (node:internal/modules/cjs/loader:1252:10) at Module.load (node:internal/modules/cjs/loader:1076:32) at Function.Module._load (node:internal/modules/cjs/loader:911:12) at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12) at node:internal/main/run_main_module:22:47 In the 'finally' block

Ofcourse, this is all well documented: it's just not exactly how I thought it worked. RTFM, people 😉

Wednesday, 3 April 2024

Antenna Experiments - Reprise

This is a follow-up to my earlier earlier post: if you haven't already done so, I suggest reading that one first.

I caved and bought a NanoVNA. It is a useful piece of equipment for not very much money, if you have an interest in RF.

Anyway, hooking up one of those "DF Robot" 433MHz antennae to it produced some interesting (in that they differ sharply from the previous results) results:-

The maximum return-loss/lowest VSWR occurs at 436MHz. Between 433.5MHz and 438.5MHz, the VSWR < 1.5. This is much closer to the expected values and suggest that my previous attempts to measure antenna resonance using just a spectrum analyser are not to be relied-upon.

Turning to the antenna consisting of a bit of coax with 16.5cm of exposed inner conductor, recall that theory suggested that the resonant frequency of that should be 454.5MHz.  It turns out that reality concurs pretty closely:-



 That red marker is at 452MHz, which is pretty close. It also suggests that the velocity factor of the signal in the coax should not be considered when doing the calculation. It isn't completely obvious (to me, anyway) why not: my intuition suggests that it should. But nothing I have read supports this intuition and now the measurements also indicate that my intuition is wrong. If you know why, please do leave a comment letting me know.

Also, it looks like the "rubber ducky" antenna is a much better antenna than the piece of coax at all of the relevant frequencies.


Sunday, 17 March 2024

Avoiding Arduino millis() Rollover Bugs

If you are using the millis() function to measure time on an Arduino, you might be tempted to do something like this:-

unsigned long timer_expires_at = millis() + SOME_DURATION; while (millis() < timer_expires_at) { [do other stuff while waiting for the timer to expire] } [do stuff that should happen after the timer expires]

For the most part, that will work well. But after around 49.7 days (4,294,967,296 a.k.a 232 milliseconds, to be exact), you might run into a problem. If millis() + SOME_DURATION exceeds 4,294,967,295, it will rollover and your comparison will match prematurely. Of course, you would have to be pretty unlucky for this to occur in practice, but - as Mary Chapin Carpenter put it - Sometimes you're the Louisville Slugger, sometimes you're the ball.

I had plans sketched out for a small C++ class to handle this elegantly when I decided to do a quick bit of Googling to see if I was reinventing the wheel entirely here and I came across the most beautiful simple, and elegant solution to the problem in https://arduino.stackexchange.com/questions/12587/how-can-i-handle-the-millis-rollover/12588#12588. It is a long, detailed reply and well-worth reading, but the gist of it is to just change the logic very slightly:-

unsigned long timer_started_at = millis(); while (millis() - SOME_DURATION < timer_started_at) { [do other stuff while waiting for the timer to expire] } [do stuff that should happen after the timer expires]

It should be pretty clear how this avoids the problem after ~49.7 days, but it may be less obvious why it doesn't just shift the problem to the other end.  Imagine that your Arduino has been powered on for 30 seconds, so a call to millis() will return a value of 30,000. You want to wait for 40 seconds, so you set SOME_DURATION to 40,000. Subtracting 40,000 from 30,000 in the condition of the while() loop will indeed cause a rollover (30,000 - 40,000 mod 4,294,967,296 = 4,294,957,296), but the result of that rollover will still satisfy the condition in the way we want.

Antenna Experiments

Let me state from the outset that my knowledge of RF and antenna design is very rudimentary. I basically consider it to be black magic.

With that out of the way, I read something a while back that made me wonder if it was possible to measure antenna resonance with a track-generator equipped spectrum analyser.  In particular, with two identical antennae - one connected to the track-generator output and one connected to the spectrum analyser input - would there be an obvious peak in response at the antenna's resonant frequency.  That is an experiment worth trying.  

Here is the initial setup:-


 It is sweeping from 0 - 1.5GHz and there is already a very obvious peak.  Looking closer:-

The antennae are intended for use with DFRobot APC220 radios which have a frequency-range of 418-455MHz.  So in the ballpark, even if the measured peak is kind of low.  There is a significant difference between performance at the two ends of that frequency range, though. Reducing the frequency span on the spectrum analyser from to 418-455MHz:-


There is nearly 15dB difference between 418MHz and 455MHz.  Still...I can't say for sure what the resonant frequency of the antenna is actually supposed to be: perhaps it is 407.5MHz and the result is completely valid.

For the second test, I replaced the both antennae with a simple monopole consisting of a piece of coax with 16.5cm of the shielding stripped back.  This should resonate at a wavelength of 4 × 16.5cm = 66cm.  Assuming the speed of light is  3 × 108m/s, that should be equivalent to 454.5MHz.  Let's see:-

Far from conclusive. The peak is at 152.5MHz, way over on the left.  There is a second peak at about 415MHz.  Zooming in to the 418-455MHz range:-


There is no sign of a peak of any kind near the resonant frequency.  

My "assumption" above about the speed of light wasn't entirely tongue-in-cheek.  The headline figure of 3 × 108m/s is the speed of light in a vacuum. Signals travel through coax cable significantly slower than this.  According to a datasheet for RG179 coax that I checked (I think that the kind of coax I have), the velocity factor is 0.7.  That would put the speed of the signal propagating through that coax at closer to 2.1 × 108m/s and - if the wavelength-to-frequency computation should take this into account - would put the resonant frequency at 318MHz.  I have no idea if the velocity factor should be applied here or not: if you know, please leave a comment.  There is certainly no bump in the output of the spectrum analyser at or near 318MHz that would give a clue either way.

Finally, I replaced the two coax monopoles with two lengths of wire, balanced precariously in the SMA connectors:-


The pieces of wire are identical and are both 10.5cm long. Looking at them as 1/4 wavelength monopoles and applying the equation, their resonant frequency should be 714MHz.  It doesn't appear to be:


There is indeed a strong peak, but it is nowhere near where it is supposed to be.  Interestingly, if I apply the velocity factor of 0.7, things look a lot better: that would imply a resonant frequency of 500MHz.  But I have no reason to believe that the velocity factor of a scrap of wire is 0.7, even if I knew that it was supposed to be included in the calculation.  I'm just fudging numbers here.

I got a similar result with 16.7cm of the same wire:


A good strong peak at 345MHz where is nowhere near the predicted 449MHz, but in the right ballpark if I apply the magic velocity factor of 0.7.

If you read down this far hoping for a satisfying conclusion and some enlightenment then I can only apologise for the disappointment you must be feeling.  I promise you, I feel your pain.

Nothing in any of this has done anything to shake my conviction that RF and antenna design is a black art.


Rotary Encoders

These are wonderful little things.

I got to thinking a bit about exactly how they worked, so I hooked one up to a power-supply and an oscilloscope.  There is 3V being delivered to the centre pin and one oscilloscope channel connected to each of the the two outer pins.  At rest, the centre pin isn't connected to either of the outer pins.  Turning the encoder clockwise...

...you can see that the blue trace goes to 3V a little before the yellow trace (the blue channel is the left-hand "A" pin on the encoder). 

Turning the encoder counter-clockwise and the opposite happens:-

Here the yellow trace (the "B" pin) goes to 3V before the blue one.  The key thing is that as the encoder passes over every detent, there is a point at which both pins are connected to the centre pin, but the direction of rotation determines whether the "A" pin gets there first or the "B" pin.

What sent me down this particular rabbit-hole was a very interesting pair of YouTube videos about using using just digital logic (i.e. no microcontrollers), here and here.  I had never previously thought about using these without a microcontroller and thinking about them in this way does reveal some interesting insights.

 

Wednesday, 5 July 2023

Sweet Mystery

 This packet contains 12 sweets of 4 different colours (black, red, yellow and - my favourites - green). 

 

 
The number and order of the colours in each pack appears to be random (based on many, many samples 😉 ). If my calculations are correct and if they are really randomly distributed, one in every 4.2 million packets will contain 12 sweets of the same colour. Something I have often wondered about: how do the manufacturers avoid this? I can see a number of possibilities:  
 
  • Don't worry about it - it happens infrequently enough not to care
  • Optically scan each group of 12 sweets just before wrapping them and if they aren't sufficiently heterogeneous, don't wrap them - throw them back into the pot and try again. This only seems like it would be worth doing if they were optically scanning them anyway for other reasons (e.g. quality control)
  • They don't really throw all of the colours into one big pot and select them at random: they keep the individual colours separate right up to the packing stage and just shuffle the order in which they pick them to create the appearance of randomness.
Does anyone have any theories or insights? I'd love to know. In the meantime I'll keep eating them - purely in the interests of science, of course 🙂

Saturday, 3 June 2023

GIMP Start-up Hang

This was a pain-in-the-ass to troubleshoot, so I'm sharing the story here in the hope that it will help someone else.

I have GIMP installed on a Windows 10 machine. Out of the blue, it started hanging on start-up: the main window would appear, but then it would freeze for about 5 minutes. Uninstalling and reinstalling didn't help and no amount of Googling yielded any solution.

The first useful clue I got was after it did become responsive (finally, after the 5-minute delay) and I clicked on the "File" menu-item, causing it to hang again for another 5 minutes. That hinted that the problem was with the "Recently Used" file list.  So where does GIMP store the list of recently-used files? Nowhere obvious, it turns out: on Windows, GIMP stores its recently-used file list in C:\Users\%USERNAME%\appdata\Local\recently-used.xbel (which may be shared by other applications).  It is an XML-format file.  Sure enough, in there are a bunch of references to files on a network share that no longer exists:-

Contents of recently-used.xbel file, showing references to non-existent network share

I could probably have just deleted the entire file, but it was sufficient to just delete the <bookmark>...</bookmark> sections that referenced the now-absent network share.

I have to give a hat-tip Bing for leading me to the solution.  None of my Google searches along the lines of "where does gimp store its recently used file list" yielded any useful hints about where to look.  I was just about to start reading through the GIMP source-code - a daunting prospect - when I decided to try the same search on Bing:-

Bing search result referencing recently-used.xbel

Even though this result refers to GIMP running on Linux, it was enough: one quick search for a file called "recently-used.xbel" and I was away. And so the score stands at Bing 1, Google 0.

Wednesday, 6 January 2021

Window Repainting Delays in Windows 10

A weird Windows 10 problem this morning. When I booted up the PC, it appeared very "sluggish". There was no obvious reason for this: CPU and disk I/O were all very low. On closer examination, the problem was with window refreshes: things were working OK in the background, but the contents of windows weren't being repainted for several seconds after. Switching away from a window and switching straight back seemed to "trigger" an update that should have happened all by itself.  

Anyway, after a bit of Google searching, I happened upon a description of a similar problem and its solution that I don't think I would ever have come up with by myself.  Somehow, the screen refresh rate had been changed from 60Hz to 59Hz:-

 

 

Sure enough, changing this back to 60Hz resolved the problem immediately.

I hope this saves someone else some pain (or saves me reliving the pain if it happens again and I have forgotten what the fix was !)

Wednesday, 27 May 2020

40dB Stereo Pad + Balanced-to-Unbalanced

The 30/40dB pad from my previous post works fantastically well. I have been using it to bring the balanced line-level output from a mixing desk down to an acceptable level for the microphone input on my camera and the sound-quality is crystal-clear. 

But it is time for version 2.0.  The things I want to change:-
  • Stereo. Up to now, the cable I have been using to connect the pad to the camera simply connects the camera's L and R inputs together. It works fine, but the camera records two audio channels and I want to use them. 
  • The first version is nice and general-purpose: it provides balanced output, handy for feeding into a balanced microphone input, if I ever wanted to. But my camera doesn't have a balanced input, so a camera-specific version with unbalanced output to a more convenient connector (phono or 1/4" stereo) would make connecting up a little easier (although I do love how rock-solid XLR connectors are). I'm willing to sacrifice some generality for convenience here.
  • I don't really need it to have a switchable attentuation level.  The mixer has an output level fader and the camera has variable sensitivity on its microphone input. I'm willing to sacrifice the flexibility of a switchable attenuation level to reduce the (slight) risk that the contacts in the switch will become oxidised and noisy at some point.
Here is the schematic for version 2.0


The pad part is mostly the same as the earlier version.  I included some extra resistors (R1, R3, R6 and R8) that I have bridged with little wire links to give myself some flexibility.  If I want to increase the attenuation, I can snip the wire links which will increase the attenuation to around 45dB.  I had the board space (and the resistors) so it seemed a shame not to use it.  At the output, I have given myself a choice of either 1/4" stereo jack output or 2 x phonos.

If I had thought of it, I might have included a way to bypass the pad completely so that I could use this to connect two dynamic microphones (with balanced XLR outputs) directly to the camera input without any attenuation, but I didn't think of it (until now).  That's what version 3.0 is for, I guess.

This is what it looks like:-


I haven't tried it in anger yet, but initial testing is positive.  I'll report back once it has had its first proper outing.

UPDATE January 2021: I have been using this gadget regularly since May and it works absolutely perfectly: I am very happy with the result.  The amount of attenuation is applies is just about perfect and I haven't felt the need to snip the wires bypassing R1, R3, R6 and R8.  I do have about 12dB of attenuation dialed in on the camera's microphone input, bringing total attenuation up to about 42dB, which sounds about right and - more importantly - lands the recorded audio levels just about where I want them.

Tuesday, 21 April 2020

30dB/40dB Switchable Balanced Pad

I had a requirement to connect the (line-level) output from a small mixing desk into the microphone input on my camera (mic-level, obviously). I was using a DI box with a 30dB pad in it, but that required a 9V battery which was sure to run out at the most inopportune moment.  Since the camera didn't take balanced input anyway, there was no real need for the DI (in fact, the cable from the DI into the camera was wired as a balanced-to-unbalanced cable).  A little bit of internet research led me to http://www.uneeda-audio.com/pads/ which has all of the relevant information (but does require careful study !).  Based on that, here's what I came up with:-


This gives me the option of either 30dB or 40dB of attenuation, depending on the position of the switch (I find that with the in-camera gain turned down as far as it will go, 30dB is enough, but the flexibility is nice to have).  The input impedance seen by the mixer is about 27K at 40dB and about 6.8K at 30dB.  The output impedance presented to the camera is 220Ω.  It works pretty well.  I built it into a small plastic project box lined with copper foil (originally bought with the intention of shielding a guitar cavity):-



The resistors are of the metal film type to minimise thermal noise.

This is the end-result:-


It is fairly compact and rugged, completely noiseless and doesn't require any power.  

The justification for the choice of resistor values is as follows.  The general equation for gain (which will be negative) of a resistor divider network is:-
...where R2 is the lower resistor in the circuit diagram and R1 is the sum of the resistors labelled R1.n in the circuit diagram above.  So, for 30dB of attenuation, the switch is closed and the 10K resistors are bypassed and the equation becomes:-
With the switch open, the 10K resistors are included in series and the equation becomes:-


I could (possibly should?) have used a slightly lower resistor value than 10K to land a little closer to -40dB.  7.5K would have done it, but it had already been a long night.  a 30K resistor in parallel with each of the 10Ks would do the trick.

Monday, 1 April 2019

Experiments with a Smartphone Barometer

Modern smartphones have a wealth of sensors that provide all sorts of avenues for amusement for modern geeks.  One of them is a barometric pressure sensor, which is the topic of this post.  I have an app called Phyphox which provides direct access to the data from those sensors.

I was in London a while ago, working on the 18th floor of an office-block. There is an express lift which gets from the ground to the 18th floor (60m up) in 15 seconds non-stop. The locals tell me that when the lifts went in first, they used to be even faster but they hurt peoples' ears and had to be slowed down. I have no trouble believing that.

These graphs were produced using the Phyphox app on my phone, measuring the changes in air pressure with altitude (like the altimeter in a plane) as well as vertical acceleration.  This is from ground straight up to the 18th floor.
 

This from the 18th down, but with stops on the 17th and 16th (other people wanting to use the lift and messing with my experiment...no consideration !) - hence the "staircase" effect for the first 45 seconds or so.
 


A bit later on, I managed to capture a "clean" descent all the way from the 18th down to the ground:-
 

The cabin of an aeroplane is another interesting place to try this.  These were taken on the flight home during take-off and climb-out:


...and coming in to land:-


(I'm probably on a no-fly list now). You can see the cabin-pressure gradually drop from ground-level pressure to around 760hPa as the plane climbs and gradually increase from that back to ground-level pressure again as the plane comes in to land. The change is made gradually over the course of about 14 minutes each time.
 
Of course the cabin of the plane is pressurised and outside air-pressure at the cruising altitude of 36,000ft is far lower than 760hPa. The cabin pressure is equivalent to being at an altitude of around 2,300-2,400m (a good bit less than ⅓ of the way up Mount Everest).
 
The Phyphox app really is an awful lot of fun. It gives you direct access to all of the sensors on a modern smartphone (and there are lots of them) and lets you run experiments like these with them.

Sunday, 21 October 2018

Experiments with an AD633 Multiplier IC

Something I have always found a little confusing was the idea of signal mixing.  The source of my puzzlement probably has its roots in the audio world of mixing desks for combining the sound from instruments and singers for music recording or live performance.  These mixing desks are (or are supposed to be) additive: their output should be the simple arithmetic sum of all of the inputs, scaled according to how far the sound engineer pushes the fader.

In the RF world, mixing means something quite different.  When we mix RF signals, we (usually) specifically want them to interfere with each other to create frequencies that weren't present in either (because we're usually talking about two) of the original signals.  That is usually the last thing in the world you want to happen in the audio world, but it how a heterodyne radio works (more on this a little later).  For this to happen, the RF signals need to be mixed in a non-linear way, usually by multiplying them together.  It took a while before this difference dawned on me.

The maths behind mixing in the RF sense of the word is pretty straightforward.  It relies on the trignometric relationship:-

sin(a) ✕ sin(b) = ½sin(a+b) + ½sin(a-b)

In plain English, if two sinusoidal signals are multiplied together, the resulting signal will consist of two sinusoidal signals at the sum and difference of the two original frequencies. 

That has to be tried.

Inspired by some of the many great W2AEW videos (Alan is my hero!) - specifically this one and this one - I did some some experiments with multiplying a sine wave by a lower-frequency square wave using the diode-switching technique and got some interesting results.  However, the real fun was had with an Analog Devices AD633 multiplier IC.  Its a little beauty: it takes in two signals (differential) and outputs the result of multiplying them together (÷10).

Here is a 1KHz sine wave being multipled by a 800Hz square wave (the yellow trace is the result):-



Its pretty easy to see what's going on here - the inversions of the sine wave when the square wave changes state are pretty evident.

Here's 10KHz and 800Hz:-


Its a little less obvious here, but if you look carefully, you can still see the sine wave change phase by 180° when the polarity of the square wave changes. 

We'll come back to this in a bit.  For now, let's try multiplying two sine waves together, one at 10KHz and one at 800Hz.  In theory, the result will be a signal which is the sum of two new sine waves, one at 9.2KHz and one at 10.8KHz.


Bingo !!  The purple trace is the FFT and - sure enough - there are two (and only two) peaks at exactly the frequencies that the maths predicts.  Note also that there is none of the original 10KHz signal present (there is no 800Hz signal there either, although it would be off-screen to the left even if there was).

Returning to the 10KHz sine wave and the 800Hz square wave: a square wave consists of the sum of the odd harmonics of the fundamental.  So our 800Hz square wave has components at 800Hz and 2400Hz and 4000Hz etc.  So multiplying this by our 10KHz sine wave should produce an output with tones at 10KHz +/- (800Hz, 2400Hz, 4000Hz...).  Let's see if it does:-


Yep.  The cursors show the fundamental.  The horizontal divisions are 1.25KHz each, so you can see that the other tones are all about 1600Hz above and below the fundamentals.  Again, note that there is no sign of the original 10KHz signal at all...just at the theory predicts. 

"Why?", you might be tempted to ask.

The plan for the AD633 is to use it as the basis of a bat detector.  Bats' echo-location sounds are somewhere around 48KHz (at least the Pipestrelle bats that are common in Ireland) - far above the range of human hearing.  The idea is to mix the signal from an ultrasonic transducer (hopefully from lots of hungry bats) with - say - a 40KHz sine wave, thus hopefully shifting the bats' sounds into the human-audible range.  This is the same basic principle on which a heterodyne radio receiver works, with a local oscillator being mixed with the target RF signal to produce a intermediate frequency which is then detected with a high-Q filter.  

Saturday, 23 June 2018

555 Timer IC

I have been studying the classic and venerable 555 Timer IC recently and have created an animation showing the various signals on it as it moves from state to state.

http://bogpeople.com/555/

Also a video...



Saturday, 26 August 2017

Wireshark Traps For Young Players

I ran into an interesting problem in work today.  An Ethernet switch appeared not to be behaving correctly: frames that I know were being received on an 802.1q trunk (coming in from a WAN carrier) weren't being sent out an access port for no reason I could see.  The switch configuration was fine...there was no obvious reason it shouldn't have been working.

A bit of background.  

The IEEE 802.1q header has a bit in it call the CFI bit.  CFI stands for "Canonical Format Indicator". It is (or was) used to distinguish between Ethernet-format MAC addresses ("canonical") and Token Ring-format MAC addresses ("non-canonical").  Basically, this bit is set to 0 on Ethernet frames and 1 on Token Ring frames.  Since Token Ring is (mercifully) now a thing of the dim-and-distant past, this bit is now always set to 0 (which is why most people - including me - have never noticed it and may be oblivious to its existence).  The 802.1q standard specifies that frames received with the CFI bit set (indicating Token Ring frames) should not be transmitted on untagged Ethernet interfaces (because the MAC address format is different and won't make any sense).  This will become important later.

The more recent 802.1ad specification which - among other things - codifies "Q-in-Q" (a.k.a. double-tagging) renames and repurposes this bit so it is now called the DEI bit.  This is where my problems started, but more on that later.  DEI stands for "Discard Eligible Indicator" and is a hint that this packet can be discarded in the event of congestion on a link.  It is in the same bit position as the CFI bit was...it just has a completely different meaning now.  

This gets me to my first problem.  I was told that my carrier was sending me tagged frames with the DEI bit set (there was a legitimate reason for this).  I figured that my Ethernet switch might interpret the DEI bit as a CFI bit (remember, same bit position in the 802.1q header) and might (correctly, from its point of view) refuse to send the frame on an untagged Ethernet interface.  Sure enough, once the carrier stopped setting the DEI bit, everything started working OK.

That would be the end of the story except that when I ran packet-captures to see what was what was going on, the frames coming in from the carrier had the DEI bit clear.  I can think of a few possibilities:-

  • My tip-off that the carrier was setting the DEI was incorrect and there was something else going on. Not likely since the problem went away as soon as the carrier changed this (and my theory around this seemed pretty sound).
  • The interface-mirroring on the switch was "lying" about the CFI/DEI bit being set (which didn't seem hugely likely either)
  • There was a problem with my packet-captures.

After some research, I found this (short but very informative) thread https://lists.linuxfoundation.org/pipermail/bridge/2016-March/009940.html.  The TL;DR version is that the Linux kernel "abuses" the CFI/DEI bit for its own internal purposes and always clears it !  This was fine when the bit was CFI (and therefore always 0), but it is distinctly not fine now that the bit should be interpreted as DEI.  As it happened, I had done all of my packet-captures on Linux machines 😞

So I set up an experiment.  I have a machine transmitting Ethernet frames (using this https://gist.github.com/austinmarton/1922600) with with the following characteristics:-
  • IEEE 802.1q encapsulation (Ethertype 0x8100)
  • VLAN tag 120
  • CFI bit in the 802.1q header set to 1
Then I used Wireshark to capture these frames on a few platforms.  The results were eye-opening.

Linux


Sure enough, the 802.1q header and the VLAN tag are there, but the CFI/DEI bit is set to 0.  Bingo !!

Windows

The second packet-capture is done on the same NIC on the same laptop (it dual-boots Windows and Linux)

Even worse !!  Under Windows, I don't even get to the the 802.1q header.  I tried this on two different Windows machines (both Windows 10, admittedly) with the same results.  This could really send you off on a wild goose-chase if you were trying to troubleshoot a real problem.

Mac

Perfect: I can see the 802.1q header and the CFI/DEI bit is still set to 1, just as it should be.

The message thread indicates that this only happens with if the Ethertype value is 0x8100 (802.1q) or 0x88a8 (802.1ad) but not if another Ethertype is used.  Let's try that:-


Look at that !  Changing the Ethertype from 0x8100 to 0x8101 and now the Linux laptop leaves the CFI/DEI bit untouched (I had to twist Wireshark's arm to get it to parse 0x8101 as an 802.1q frame).  That makes sense: the Linux kernel no longer sees the frame as an 802.1q/802.1ad frame and therefore doesn't see the following two bytes as an 802.1q/802.1ad header that it can mess with.

The first lesson is that one needs to be very circumspect about packet-captures unless you are very sure about the behaviour of the platform you are doing the capturing on.

Second, if you have a WAN provider setting that DEI bit (e.g. on a "best-effort" WAN service), be aware that your switch may be refuse to pass those frames through.