## ZX81 LPRINT bug and software archaeology

04.11.2019 19:07

By some coincidence I happened to stumble upon a week-old, unanswered question posted to Hacker News regarding a bug in Sinclair BASIC on a Timex Sinclair 1000 microcomputer. While I never owned a TS1000, the post attracted my interest. I've studied ZX81, an almost identical microcomputer, extensively when I was doing my research on Galaksija. It also reminded me of a now almost forgotten idea to write a post on some obscure BASIC bugs in Galaksija's ROM that I found mentioned in contemporary literature.

The question on Hacker News is about the cause of a bug where the computer, when attached to a printer, would print out certain floating point numbers incorrectly. The most famous example, mentioned in the Wikipedia article on Timex Sinclair 1000, is the printout of 0.00001. The BASIC statement:

LPRINT 0.00001


unexpectedly types out the following on paper:

0.0XYZ1


This bug occurs both on Timex Sinclair 1000 as well as on Sinclair ZX81, since both computers share the same ROM code. Only the first zero after the decimal point is printed correctly while the subsequent zeros seem to be replaced with random alphanumeric characters. The non-zero digit at the end is again printed correctly. Interestingly, this only happens when using the LPRINT (line-printer print) statement that makes a hard-copy of the output on paper using a printer. The similar PRINT statement that displays the output on the TV screen works correctly (you can try it out on JtyOne's Online Emulator).

The cause of the bug lies in the code that takes a numerical value in the internal format of the BASIC's floating point calculator and prints out individual characters. One particular part of the code determines the number of zeros after the decimal point and uses a loop to print them out:

;; PF-ZEROS
L16B2:  NEG                     ; Prepare number of zeros
LD      B,A             ; to print in B.

LD      A,$1B ; Print out character '.' RST 10H ; LD A,$1C           ; Prepare character '0'
; to print out in A.

;; PF-ZRO-LP
L16BA:  RST     10H             ; Call "print character" routine
DJNZ    L16BA           ; and loop back B times.


(This assembly listing is taken from Geoff Wearmouth's disassembly. Comments are mine.)

The restart 10h takes a character code in register A and either prints it out on the screen or sends it to the printer. Restarts are a bit like simple system calls - they are an efficient way to call an often-used routine on the Z80 CPU. The problem lies in the fact that this restart doesn't preserve the contents of the A register. It does preserve the contents of register B and other main registers through the use of the EXX instruction and the shadow registers, however the original contents of A is lost after the call returns.

Since the code above doesn't reset the contents of the A register after each iteration, only the first zero after the decimal point is printed correctly. Subsequent zeros are replaced with whatever was junk left in the A register by the 10h restart code. Solution is to simply adjust the DJNZ instruction to loop back two bytes earlier, to the LD instruction, so that the character code is stored to A in each iteration. You can see this fix in Geoff's customized ZX81 ROM, or in Timex Sinclair 1500 ROM (see line 3835 in this diff between TS1500 and TS1000).

This exact same code is also used when displaying numbers on the TV screen, however in that case it works correctly. The reason is that when set to print to screen, printing character 0 via the 10h restart actually preserves the contents of register A. Looking at the disassembly I suspect that was simply a lucky coincidence and not a conscious decision by the programmer. Any code calling 10h doesn't know whether the printer or the screen is used, and hence must assume that A isn't preserved anyway.

Of course, I'm far from being the first person to write about this particular Sinclair bug. Why then does the post on Hacker News say that there's little information to be found about it? The Wikipedia article doesn't cite a reference for this bug either.

It turns out that during my search for the answer, the three most useful pages were no longer on-line. Paul Farrow's ZX resource centre, S. C. Agate's ZX81 ROMs page and Geoff Wearmouth's Sinclair ROM disassemblies are wonderful historical resources that must have taken a lot of love and effort to put together. Sadly, they are now only accessible through the snapshots on the Internet Archive's Wayback Machine. If I wouldn't know about them beforehand, I probably wouldn't find them now. For the last one you even need to know what particular time range to look at on Archive.org, since the domain was taken over by squatters and recent snapshots only show ads (incidentally, this is also the reason why I'm re-hosting some of its former content).

I feel like we can still learn a lot from these early home computers and I'm happy that questions about them still pop-up in various forums. This LPRINT bug seems to be a case of a faulty generalization. It's a well known type of a mistake where the programmer wrongly generalizes an assumption (10h preserves A) that is in fact only true in a special case (displaying character on screen). History tends to repeat itself and I believe that many of the blunders in modern software wouldn't happen if software developers would be more aware of the history of their trade.

It's sad that these old devices are disappearing and that primary literature sources about them are hard to find, but I find it even more concerning that now it seems also these secondary sources are slowly fading out from general accessibility on the web.

Posted by | Categories: Code | Comments »

## Experiences in product certification

24.10.2019 9:45

Yesterday I was invited to give a presentation at a seminar on procedures for product certification. My former colleagues at the Department of Communication Systems at the Jožef Stefan Institute invited three companies to share their experiences in getting electronics products to European market. We discussed compliance with the CE mark, testing for safety and electromagnetic compatibility standards and various other approvals that are required before you can put mass-produced electronics on the shelves.

In my presentation (slides are here) I've discussed my view of the two certification cycles I've participated in at Klevio during roughly the last year and a half. I did a short intro about the company and products and then listed what we chose to certify and how. I didn't do any general intro into the certification procedures and individual measurements. This in the end turned out just fine, because others did it better than I could. Most of the time I spent talking about purely practical lessons we learned on how to best prepare for the task. Looking back at it now, I feel like most of my advice boils down to having a good understanding of what is involved and not basing your expectations purely on certification lab's sales pitches.

I wasn't sure how much time I would have and what the interest of the audience was. Because of that I've put the details on debugging specific compliance problems into a separate part of the presentation. I've ended up doing that part of the talk as well. I discussed a problem we had with an ESD test temporarily disabling an audio codec IC and a problem with radiated emissions that took 3 months to debug and led to some pretty significant changes to one of the DC-DC converters.

I wanted to talk more about some interesting home-brew methods of estimating radiated emissions. I spent a lot of time researching and experimenting with them when I was debugging Klevio's compliance problems. In the end I realized that I could spend a whole talk just on that topic. It also turned out that none of those methods were actually useful in finding a solution, so it didn't make much sense to do more than just list them out. For more info on that, here's an article I found useful on making magnetic loop probes. The idea for the common-mode current probe based on a ferrite ring is from the Application Note AN045 by Richtek. The SDR-based method was my own idea based on my previous research on spectrum sensing and I might eventually do a longer write up on that in the future.

In the end, it was interesting to compare notes and what others have learned solving similar problems. I found that even though our EMI problem took longer to solve than others presented at the seminar, Klevio's experience didn't differ much in regard to timelines or certification lab practicalities. It seems almost everyone stumbles upon some problems during certifications, and while specific issues are unique, the biggest obstacle to finding a solution seems pretty universal: reproducing the problem outside of the certification lab.

Posted by | Categories: Life | Comments »

## Some more notes on Denon RCD-M41DAB

19.10.2019 16:13

Back in January I bought a new micro Hi-Fi after the transformer in my old one failed and I couldn't repair it. Since then I've been pretty happy using Denon RCD-M41DAB for listening to music and radio. I previously did some measurements on its output amplifier and found it meets its ratings in regards to power and distortion. In any case, that was more to satisfy my electrical engineering curiosity and I'm not particularly sensitive to reproduction quality. However after extended use two problems did pop up. Since I see this model is still being sold, I thought I might do a short follow up.

The first thing is that when streaming audio to the Hi-Fi over Bluetooth there are about 2 seconds of lag. It looks as if there's some kind of an audio processing buffer inside the RCD-M41DAB, or it might simply be the choice of the codec used for wireless transfer. When listening to music the delay isn't that annoying. For example, when clicking "next track" or changing volume on the music player on the phone it takes about 2 seconds before you can actually hear the change. However this lag does make a Bluetooth-connected RCD-M41DAB completely useless as an audio output when playing videos or anything interactive. The lag happens on iOS and Android and I'm pretty sure this is not something related to my smartphone, since when I'm using Bluetooth-connected headphones, there is no discernible lag there.

The other annoyance is that the CD player has problems reading some CDs in my collection. Apparently it reads the table of contents fine, because the number of tracks is displayed correctly. However it has problems seeking to tracks. For example, one disc won't play any tracks at all and I can just hear the head continuously seeking. Another disc won't start playing the first track, but if I skip it manually, player will find and play the second one just fine. All such discs play normally in the CD drive in my desktop computer and other CD players I've tried. It still might be that the discs have some kind of an error in them or have bad reflectivity (all of the problematic ones are kind of niche releases I bought from Bandcamp), but other players apparently are able to work around it.

Posted by | Categories: Life | Comments »

## Specializing C++ templates on AVR pins

09.10.2019 19:56

AVR is a popular processor core for microcontrollers, probably most well known from the ATmega chips on the classic Arduino boards. There's an excellent, free software C and C++ compiler toolchain available for them, which also usually sits behind the well-known Arduino IDE. AVR is a Harvard architecture, which means that code and data memory (i.e. flash and RAM) have separate address spaces. On these small, 8-bit chips there is very little RAM space (can be as little as 128 bytes on ATtiny devices). On the other hand, the amount of flash is usually much less restrictive.

Sometimes you want the microcontroller to drive multiple instances of the same external peripheral. Let's say for example this peripheral uses one GPIO pin and two instances differ only in the pin they are connected to. This means that the code in the microcontroller for peripheral 1 differs from code for peripheral 2 only by the address of the special function register (SFR) it uses and a bit mask. A naive way of implementing this in clean C++ would be something like the following:

#include <avr/io.h>

class DIO {
public:
PORT_t * const port;
const int pin_bm;

DIO(PORT_t* port_, int pin_bm_)
: port(port_), pin_bm(pin_bm_) {}

void setup(void) const
{
port->DIR |= pin_bm;
}

// other methods would go here ...
};

DIO dio1(&PORTB, PIN4_bm);
DIO dio2(&PORTB, PIN5_bm);

int main(void)
{
dio1.setup();
dio2.setup();

// other code would go here ...

while (1);
}


Note that I'm using the nice PORT_t struct and the bit mask constants provided by avr-gcc headers instead of messing around with register addresses and such.

Unfortunately, this isn't optimal. Even though the class members are declared as const, compiler stores them in RAM. This means that each instance of DIO class costs 4 precious bytes of RAM that, baring a stray cosmic particle, will never ever change during the course of the program.

Compiling the code above for the ATtiny416 with avr-gcc 5.4.0 confirms this:

Program Memory Usage  : 204 bytes   5,0 % Full
Data Memory Usage     : 8 bytes   3,1 % Full


We could store member variables as data in program space. This is possible by using some special instructions and variable attributes (PROGMEM, pgm_read_byte() etc.), but it's kind of ugly and doesn't play nicely with the C language. Another way is to actually have copies of the binary code for each peripheral, but only change the constants in each copy.

Obviously we don't want to have multiple copies in the source as well. In C the usual approach is to do something rude with C preprocessor and macros. Luckily, C++ offers a nicer way via templates. We can make a template class that takes non-type parameters and specialize that class on the register and bitmap it should use. Ideally, we would use something simple like this:

template <PORT_t* port, int pin_bm> class DIO {
public:
void setup(void) {
port->DIR |= pin_bm;
}
};

DIO<(&PORTB), PIN4_bm> dio1;
DIO<(&PORTB), PIN5_bm> dio1;


Sadly, this does not work. Behind the scenes, PORTB is a macro that casts a constant integer to the PORT_t struct via a pointer:

#define PORTB    (*(PORT_t *) 0x0620)


&PORTB evaluates back to an integral constant, however the compiler isn't smart enough to realize that and balks at seeing a structure and a pointer dereference:

main.cpp(8,7): error: a cast to a type other than an integral or enumeration type cannot appear in a constant-expression
DIO<(&PORTB), PIN4_bm> dio1;
main.cpp(8,7): error: '*' cannot appear in a constant-expression
main.cpp(8,7): error: '&' cannot appear in a constant-expression


I found a thread on the AVR freaks forum which talks about this exact problem. The discussion meanders around the usefulness of software abstractions and the ability of hardware engineers to follow orders, but fails to find a solution that does not involve a soup of C preprocessor macros.

Fortunately, there does seem to exist an elegant C++ solution. The compiler's restriction can be worked around without resorting to preprocessor magic and while still using the convenient constants defined in the avr-gcc headers. We only need to evaluate the macro in another expression and do the cast to PORT_t inside the template:

template <int port_addr, int pin_bm> class DIO {
public:
void setup(void) {
port()->DIR |= pin_bm;

}

static PORT_t* port()
{
}
};

static const int PORTB_ADDR = (int) &PORTB;


This compiles and works fine with avr-gcc. As intended, it uses zero bytes of RAM. Interestingly enough, it also significantly reduces the code size, something I did not expect:

Program Memory Usage  : 108 bytes   2,6 % Full
Data Memory Usage     : 0 bytes   0,0 % Full


I'm not sure why the first version using member variables produces so much more code. A brief glance at the disassembly suggests that the extra code comes from the constructor. I would guess that using a code-free constructor with initializer lists should generate minimal extra instructions. Apparently that is not so here. In any case, I'm not complaining. I'm also sure that in a non-toy example, the template version will use more flash space. After all, It has to duplicate the code for all methods.

You might also wonder how Arduino does this with the digitalWrite() function and friends. They store a number of data tables in program memory and then have preprocessor macros to read them out using pgm_read_byte(). As I mentioned above, this works, but isn't nicely structured and it's pretty far from clean, object-oriented C++ programming.

Posted by | Categories: Code | Comments »

## Voltage divider cheat sheet

16.09.2019 19:46

The two most common electrical measurements I do these days is measuring output impedance of a source and input impedance of a load. I don't have any special equipment for that. I'm not doing it so often that it would warrant buying or making a specialized tool. Since it's all either DC or around audio frequencies and I'm not interested in very precise values it's a really simple measurement. I usually just use an oscilloscope and whatever else is available around the shop to use as reference loads and sources.

Deriving unknown impedances or resistances from the voltage measurements and reference values is straightforward. It only takes a minute and I must have done it a hundred times at this point by inverting the voltage divider formula. I still don't know the resulting equations by heart though. So to avoid doing it the hundred and first time and the odd mistake, I've made myself a nice cheat sheet to stick above my desk. It contains the formulas for the three most common measurements I make.

The notes about the best accuracy refer to the selection of the reference impedance such that the result is least affected by errors in voltage measurements (which, when measuring amplitudes with an oscilloscope, is the largest source of error in my case). The selection is quite obvious, but I guess I added it there for the sake of rigor.

The cheat sheet is made in LaTeX using the circuitikz package for schematics. I'm mostly putting it here for my own future reference, but maybe someone else will find it useful.

Posted by | Categories: Analog | Comments »

## z80dasm 1.1.6

09.09.2019 20:32

z80dasm is a command-line disassembler for the Z80 CPU. I initially released it way back in 2007 when I was first exploring Galaksija's ROM and other disassemblers I could find didn't do a good enough job for me. Since then it accumulated a few more improvements and bug fixes as I received feedback and patches from various contributors.

Version 1.1.6 is a release that has been way overdue. Most importantly, it fixes a segmentation fault bug that several people have reported. The patch for the bug has actually been committed to the git repository since June last year, but somehow I forgot to bump the version and roll up a new release.

The problem appeared when feeding a symbol file that was generated by z80dasm back as input to z80dasm (possibly after editing some symbol names). This is something that the man page explicitly mentions is supported. However, when this was done together with block definitions, it caused z80dasm to segfault with a NULL pointer dereference. Some code didn't expect that the symbol automatically generated to mark a block start could already be defined via the input symbol file. Thanks to J. B. Langston for first sending me the report and analysis of the crash.

I took this opportunity to review the rest of the symbol handling code and do some further clean ups. It has also led me to implement a feature that I have been asked for in the past. z80dasm now has the ability to sort the symbol table before writing it out to the symbol file.

More specifically, there is now a --sym-order command-line option that either takes a default or frequency argument. Default leaves the ordering as it was in the previous versions - ordered by symbol value. Frequency sorts the symbol table by how frequently a symbol is used in the disassembly. The most commonly used symbols are written at the start of the symbol file. When first approaching an unknown binary, this might help you to identify the most commonly used subroutines.

Anyway, the new release is available from the usual place. See the included README file for build and installation instructions. z80dasm is also included in Debian, however the new release is not in yet (if you're a Debian developer and would like to sponsor an upload, please get in touch).

Posted by | Categories: Code | Comments »

## What is a good USB cable?

31.08.2019 20:36

In conclusion of my recent series on the simple matter of USB cable resistance, I would like to attempt the question of what is a good USB cable. After having a reliable measurement of a cable's resistance, the next obvious question is of course whether that resistance complies with the USB standard and whether such a cable is suitable for powering single-board computers like the Raspberry Pi. Claims that most cables don't comply with the standard are quite common whenever this topic is discussed. I'm by no means an expert on USB, but luckily USB Implementers Forum publishes the standards documents in their on-line library. I went in and studied some of the documents on the Cable and Connector Specification topic which, among other things, specify cable resistance.

I've started my reading with USB 2.0, because micro- and mini-USB cables I tested in my previous post are unlikely to be older than USB 2.0. The standard is now nearly 20 years old and over the years it seems to have received many revisions and updates. Hence it's hard to pick up a random cable from the pile and say with certainty with which document it should comply. In addition, I find that the text of the standard itself often isn't particularly clear. For example, the following text implicitly defines the maximum allowable cable resistance in the Universal Serial Bus Cables and Connectors Class Document, revision 2.0 from August 2007:

Image by USB Implementers Forum

Initially I thought this means voltage drop over the pair of wires. As in, total voltage drop over VBUS and GND wires should be less than 125 mV at 500 mA (effectively 250 mΩ round-trip resistance). However the fact that most cables seem to be around 500 mΩ suggests that manufacturers read this as 250 mΩ per wire (500 mΩ round-trip).

A later document amends this definition somewhat and makes it clearer that the voltage drops are for each wire separately and that this voltage drop includes contact resistance. The following is from Universal Serial Bus 3.0 Connectors and Cable Assemblies Compliance Document, revision 1.0 draft from October 2010. Also note that both the measurement current and the allowable voltage drop were increased. The measurement must now be done at 900 mA, however maximum effective single-wire resistance is still 250 mΩ, same as in USB 2.0:

Image by USB Implementers Forum

An even later document addresses cable compliance with older revisions of the standard. USB 3.1 Legacy Cable and Connector Revision 1.0 from 2017 contains this calculation:

Image by USB 3.0 Promoter Group

This equation clearly shows that the 250 mΩ figure from the other documents is supposed to be combined from two 30 mΩ contact resistances and a 190 mΩ wire resistance. It also multiplies the voltage drop by two due to the round trip through both VBUS and GND wires.

USB type C specification tries to make this even clearer and comes with schematics that explicitly show where voltage drop must be measured. Since in USB type C you can have different types of cables that are rated for different currents, that standard only specifies maximum voltage drop. Note also that in type C the requirements for the VBUS line were relaxed compared to previous standards. Previously, for a cable delivering 1 A of current, the VBUS line must have had a maximum resistance of 250 mΩ while in type C up to 500 mΩ is allowed.

Image by USB 3.0 Promoter Group

7 out of 12 micro USB and 5 out of 6 mini USB cables I found at my home have less than 500 mΩ round-trip resistance. So according to my understanding of the standard for pre-type C cables, roughly 70% of my cables comply with it. Here are my resistance measurements plotted versus cable length. I've also included measurements published by Balaur on EEVblog and martinm on their blog. Points in the shaded area represent cables that comply with the standard.

So strictly according the USB standards, the situation out there isn't perfect, but it doesn't look like the majority of cable are completely out of spec either. This seems a bit at odds with the general opinion that finding a good cable for running Raspberry Pi is hard. However, things start getting a bit clearer when you look at what exactly Raspberry Pi boards demand from these cables.

In the following table I've extracted maximum required power for all Raspberry Pi model Bs from the Wikipedia article. These boards will display the infamous under-voltage warning when their power supply voltage falls under approximately 4.63V. Assuming a perfect 5 V power supply, this is enough data to calculate the maximum allowable cable resistance for powering these boards:

R_{max} = \frac{U_{supply} - U_{min}}{I_{max}} = \frac{5.00\mathrm{V} - 4.63\mathrm{V}}{I_{max}}
Model Max. supply
current [mA]
Max. cable
resistance [mΩ]
RPi 1 Model B 700 529
RPi 1 Model B+ 350 1057
RPi 2 Model B 820 451
RPi 3 Model B 1340 276
RPi 3 Model B+ 1130 327
RPi 4 Model B 1250 296

Raspberry Pi model Bs after version 2 appear to require cables with resistance well below 500 mΩ that the standard requires for micro USB cables. Only 3 cables from my collection would be able to power a Raspberry Pi 3 model B. Raspberry Pi 4 gets a pass because the type C standard is flexible enough and doesn't directly specify cable resistance (although its type C implementation has other power-related issues). Yet, since type C cables have 750 mV maximum voltage drop at rated current, it requires a cable rated for 3 A or more according to this estimate (I'm not sure if Raspberry Pi 4 uses the same APX803 voltage monitor as earlier versions).

Also note that this calculation is for a perfect 5V power supply, which is optimistic. Power supplies don't have perfect regulation and the calculations in the USB standard assume worst case 4.75 V at the source. Such a worst case power supply, even if it provides sufficient current, would require practically zero ohm cables to power a Raspberry Pi without under-voltage warnings and associated CPU throttling.

To sum up, yes there are USB cables out there that are out of spec. However based on this limited sample, most micro and mini USB cables do seem to actually comply with the standard. Also worth noting is that shorter ones tend to have a better chance of being compliant. On the other hand, at least part of the blame for the grief surrounding USB cables appears to fall onto the Raspberry Pi itself since they designed their boards with an requirement for better-than-the-standard cables and power supplies.

Posted by | Categories: Analog | Comments »

## Resistance measurements of various USB cables

23.08.2019 10:23

After I made my USB cable resistance tester I naturally wanted to measure some cables. I searched my apartment and ended up with a big jumble of 18 micro and mini USB cables of various lengths and origins. I didn't imagine I would find that many, but I guess today just about everything comes with one and I apparently never throw away anything. In fact some cables were in a very bad shape and already had insulation flaking off from old age.

I measured the resistance of each cable at 1 A using the voltage ratio method I described in my previous post. The following table lists the results. For a lot of cables I don't know their origin and they must have came bundled with various devices. I've listed the brand if it was printed on or if I knew for certain which device the cable came with. I'm afraid this comparison isn't very useful as a guide which cable brand to buy, but it does give an interesting statistic of what kind of cables can be found out there in the wild.

N Brand Color Type Length [cm] R [mΩ]
1 Wacom Black A / micro B 28 199
2 CellularLine Gray A / micro B 207 212
3 White A / micro B 105 224
4 White A / micro B 51 294
5 Wacom Black A / micro B 98 334
6 Samsung Black A / micro B 82 408
7 Nokia Black / gray A / micro B 115 490
8 CubeSensors White A / micro B 103 522
9 Black A / micro B 103 569
10 HTC Black A / micro B 128 597
11 Google Black A / micro B 153 613
12 Amazon White A / micro B 182 739
13 Silver A / mini B 30 177
14 Black A / mini B 146 323
15 Black A / mini B 125 396
16 Silver A / mini B 56 412
17 Canon White A / mini B 125 435
18 Silver A / mini B 180 804

Unsurprisingly, two short 30 cm cables came out as best in terms of resistance, measuring below 200 mΩ. A bit more unexpected was finding out that the 2 m CellularLine isn't far behind. This is a fancy and laughably overpriced cable I bought in a physical store not so long ago, the only one on this list that I'm sure didn't come bundled with any device. It appears in this case the price was at least somewhat justified.

I was also a bit surprised that some cables that came bundled with devices measured pretty high. The white Amazon was for charging a Kindle 3 and it had the highest resistance among the micro B cables I tested. On the other hand, it was also in pretty bad shape, so it might be that it was damaged somehow. Cables bundled with an HTC phone and Google Chromecast also measured more than 500 mΩ.

Other measurements I could find on the web seem to roughly agree with mine. martinm lists measured values between 289 and 1429 mΩ. Balaur on EEVblog forum measured between 276 and 947 mΩ on his cables. The only report that was really off was this forum post by d_t_a where most of the cables listed are lower than 200 mΩ.

Another thing I was interested in was how repeatable these measurements were. I mentioned several times in my previous posts that contact resistance can play a big role. Since each time you plug in a cable the contacts sit differently and have a slightly different resistance, contact resistance behaves like a random variable in the measurement results. When I was doing the measurements above this was quite obvious. Minimal movements of the cable caused the voltage displayed on the voltmeter to dance around.

I repeated the measurement of cable 16 from the table above 10 times. Before each repetition I unplugged and re-plugged both ends of the cable. Above you can see the histogram of those measurements. The results only vary for approximately ±1%, which is much less than I thought they would. This is about the same as the expected error of the measurement itself due to the accuracy of the reference resistor. Of course, this was all done over a short period of time. I'm guessing the resistance would change more over longer periods of time and more cycles as contacts deteriorate or gather dirt.

I also checked how the measurement is affected if I plug something between the tester and the cable. Gino mentioned in a comment they used an adapter and an extension cable in their measurement. So I repeated the measurement of cable 1 from the table with a short type A-to-type A extension in series. Just for fun, I also tested how much resistance a cheap USB multimeter adds:

Assembly R [mΩ]
Cable 1 202
Cable 1 + 45 cm extension 522
Cable 1 + Muker V21 multimeter 442

As you can see from the results above, both of these added quite a lot. With the excellent 200 mΩ cable, both more than doubled the total resistance. Even with an average 500 mΩ cable, this multimeter would add around 240 mΩ or approximately 50% on top. Battery-powered devices like smartphones adjust their charging current according to the voltage drop they see on their end. Hence they might charge significantly slower when the multimeter is in series with the cable compared to just using a cable. This puts some doubt on the usability of these USB multimeters for evaluating USB cables and power supplies.

Posted by | Categories: Analog | Comments »

## USB cable resistance tester

18.08.2019 17:04

Back in June I did a short survey of tools for measuring resistance of power supply lines in USB cables. I was motivated by the common complaint about bad cables, often in the context of powering single board computers like the Raspberry Pi. I wasn't particularly happy with what I found: the tool wanted to buy was out of stock, and I've seen various issues with others. Having already dug too deep into this topic I then set out to make my own tool for this purpose.

So roughly two months later I have a working prototype in my hands. It works as designed and I spent a few hours measuring all the USB cables I could get my hands on. I'm reasonably happy with it and can share the design if anyone else wants to make it.

As I mentioned in my previous post, I really liked the approach of FemtoCow's USB cable resistance tester and I basically copied their idea. Since USB type C is gaining in popularity I've added connectors so that A-to-C and C-to-C cables can be tested in addition to A-to-mini B and A-to-micro B, I've taken care that even with the added connectors, the voltmeter still has Kelvin connections in all combinations. I've also added proper 4 mm test sockets for all connections.

The principle of operation is very simple. Electrically, the resistance tester consists of two parts. On one end of the cable is a reasonably accurate 1 Ω resistor in series with the cable's VBUS and GND lines. The other end only shorts the VBUS and GND lines together. The power supply is used to set a current through the cable. The measured resistance of the cable, which consists of the sum of the four contact resistances and resistances of the two copper cores, can then be calculated as:

R_{measured} = R_{VBUS} + R_{GND} = R_{ref}\frac{U_{measure}}{U_{calibrate}}

Or, if set current is 1 A, the voltmeter reading in volts directly corresponds to the measured resistance in ohms:

R_{measured} [\Omega] = U_{measure} [\mathrm{V}]

The nice thing about this approach is that the cable can be tested at an arbitrary current. If the first equation is used, the accuracy of the method does not depend on the accuracy of the current setting. It even doesn't depend much on the calibration accuracy of the voltmeter: since a ratio of similar voltages is used, any linear error cancels out. The only requirement is that the voltmeter is reasonably linear over the 0.1 V to 1 V range. Since Kelvin connections are used, the resistance of the PCB traces has negligible effect on measurements as well.

The only critical component is the reference resistor. 1% resistors are widely available, so getting to that kind of measurement accuracy should be trivial. With some more effort and a bit higher price, 0.1% current sense resistors aren't that exotic either. For my tool I went with a cheap 1% thick-film resistor since I considered that good enough.

After measuring a pile of cables, some shortcomings did become apparent that I didn't think of earlier: I really should have added a switch for the voltmeter instead of having four test sockets. Constantly re-plugging the test leads gets tiring really fast. It also affects the measuring accuracy somewhat since it's hard to re-plug the cables without moving the tool slightly. Since moving the connectors results in slightly changing their contact resistances, it's hard to measure both voltages in the exactly the same setup. The errors because of that seem minimal though.

Another thing I noticed is that with my analog power supply, setting the current to exactly 1 A wasn't possible. Since I have only one knob that goes from 0 to 25 V in one rotation, setting low voltages requires very small movements of the knob and isn't very accurate. Hence I mostly used the ratio equation for my measurements. My power supply also tended to drift a bit which was a bit annoying. The power supply at work with a digital interface worked much better in that respect.

Finally, I'm not sure how harmful this kind of test is for type C cables that contain active parts, like the electronically marked power delivery cables. I didn't test any so far. All schematics I could find show that the power delivery ID chip is powered from the VCONN line, which is left unconnected in this tool, so that should be fine. On the other hand, the active cables that do signal conditioning do seem to be powered from VBUS. It's possible, although I think unlikely, those could respond weirdly or even be damaged by the low voltage applied during this test.

If you want to make a tool like this, you can find all required Gerber files and the bill of materials in the GitHub repository. While it might be possible to etch and drill the board yourself, I highly recommend using one of the cheap PCB prototyping services instead. The USB C connectors require very small holes and SMD pads that I think would be pretty challenging to get right in a home workshop. There are some more notes in the README file regarding that. On the other hand, the Würth connectors listed in the BOM are solderable with only a soldering iron, so manual assembly is reasonably straightforward with no hot air station required. However again the type C ones can be pretty tricky due to the fine pitch.

Posted by | Categories: Analog | Comments »

## Quick and ugly WFM data export for Rigol DS2072A

15.08.2019 14:48

At work I use a Rigol DS2072A oscilloscope. It's quite a featureful little two-channel digital scope that mostly does the job that I need it for. It can be buggy at times though and with experience I learned to avoid some of its features. Like for example the screenshot tool that sometimes, but not always, captures a perfectly plausible PNG that actually contains something different than what was displayed on the physical screen at the time. I'm not joking - I think there's some kind of a double-buffering issue there.

Recently I was using it to capture some waveforms that I wanted to further process on my computer. On most modern digital scopes that's a simple matter of exporting a trace to a CSV file on a USB stick. DS2072A indeed has this feature, however I soon found out that it is unbearably slow. Exporting 1.4 Msamples took nearly 6 minutes. I'm guessing exporting a full 14 Msample capture would take an hour - I've never had the patience to actually wait for one to finish and the progress indicator indeed remained pegged at 0% until I reset the scope in frustration. I planned to do many captures, so that approach was clearly unusable.

Luckily, there's also an option for a binary export that creates WFM files. Exporting to those is much faster than to the text-based CSV format, but on the other hand it creates binary blobs that apparently only the scope itself can read. I found the open source pyRigolWFM tool for reading WFM files, but unfortunately it only seems to support the DS1000 series and doesn't work with files produced by DS2072A. There's also Rigol's WFM converter, but again it only works with DS4000 and DS6000 series, so I had no luck with that either.

I noticed that the sizes of WFM files in bytes were similar to the number of samples they were supposed to contain, so I guessed extracting raw data from them wouldn't be that complicated - they can't be compressed and there are only that many ways you can shuffle bytes around. The only weird thing was that the files containing the same number of samples were all of a slightly different size. A comment on the pyRigolWFM issue tracker mentioned that the WFM files are more or less a memory dump of the scope's memory which gave me hope that their format isn't very complicated.

After some messing around in a Jupyter Notebook I came up with the following code that extracted the data I needed from WFM files into a Numpy array:

import numpy as np
import struct

with open(path, 'rb') as f:

assert magic == b'\xa5\xa5'

offset_1 = struct.unpack('<i', header[0x044:0x048])[0]
offset_2 = struct.unpack('<i', header[0x048:0x04c])[0]
n_samples = struct.unpack('<i', header[0x05c:0x060])[0]
sample_rate = struct.unpack('<i', header[0x17c:0x180])[0]

assert n_samples % 2 == 0

pagesize = n_samples//2

data = np.fromfile(path, dtype=np.uint8)

t = np.arange(n_samples)/sample_rate
x0 = np.empty(n_samples)

# Samples are interleaved on two (?) pages
x0[0::2] = data[offset_1:offset_1+pagesize]
x0[1::2] = data[offset_2:offset_2+pagesize]

# These will depend on attenuator settings. I'm not sure
# how to read them from the file, but it's easy to guess
# them manually when comparing file contents to what is
# shown on the screen.
n = -0.4
k = 0.2

x = x0*k + n

return t, x

t, x = load_wfm("Newfile1.wfm")


Basically, the file consists of a header and sample buffer. The header contains metadata about the capture, like the sample rate and number of captured samples. It also contains pointers into the buffer. Each sample in a trace is represented by one byte. I'm guessing it is a raw, unsigned 8-bit value from the ADC. That value needs to be scaled according to the attenuator and probe settings to get the measured voltage level. I didn't manage to figure out how the attenuator settings were stored in the header. I calculated the scaling constants manually, by comparing the raw values with what was displayed on the scope's screen. Since I was doing all captures at the same settings that worked for me.

I also didn't bother to completely understand the layout of the file. The code above worked for exports containing only channel 1. In all my files the samples were interleaved in two memory pages: even samples were in one page, odd samples in another. I'm not sure if that's always the case and the code obviously does not attempt to cover any other layout.

Here is a plot of the data I extracted for the trace that is shown on the photograph above:

I compared the trace data I extracted from the WFM file with the data from the CSV file that is generated by the oscilloscope's own slow CSV export function. The differences between the two are on the order of 10-15. That is most likely due to the floating point precision. For all practical purposes, the values from both exports are identical:

Anyway, I hope that's useful for anyone else that needs to extract data from these scopes. Just please be aware that is only a minimal viable solution for what I needed to do - the code will need some further hacking if you will apply it to your own files.

Posted by | Categories: Code | Comments »