Bad things happen when programmer finds a soldering iron

AuxPower1U: Requirements

This is post 1 in the series (next: Features).


As part of my home server setup, I have a few devices that have “free-floating” power supplies. For example, my modem, my wireless PoE adapters, and a few test boxes all have their power supply pushed into rack’s nooks and crannies. Even worse, since I want to have the capability to reset them, they are connected via ResetBox (or its type-C variant) making cable situation even messier. It’s way beyond time to sort that out!

In this blog series, I will go over what’s needed to design a nice 1U box that can fit all (or as many as I can) different power supplies togethe. Their outputs should be resettable by physical buttons on the device’s front. Since I actually didn’t finish the project as I’m typing this, expect the series not only to last a few months while I gather all necessary equipment but also for my “specifications” to shift slightly as I discover new things or rethink my old ways.

At first, let’s look at what power supplies we have currently:

  • 12V 25W: Arris SURFboard Modem
  • 22V-57V 20W: Mikrotik hAP ac
  • 24-57V 30W: Mikrotik Audience
  • 12-28V 40W: Mikrotik hAP ax3
  • 19.5V 65W: Dell OptiPlex 3050 Micro
  • 12-19V 65W: Intel NUC
  • 54V 15W: Netgear GS305EPP (150W max)

If I squint hard enough, there are three distinct power supplies to use there; the first one being a simple 12V power supply for modem. Power usage will be really low on this one, so any hardware we place will run from it too.

To the second power supply, I had to give a bit more thought. Without question, it has to run of the UPS but its target voltage is a bit of an unknown. Currently, I am running my hAP ac and Audience from a 20V type-C power supply, and Audience doesn’t seem to love it as any minor transient causes reset. And yes, officially Audience doesn’t run that low, but I got lucky, I guess. Previously, I was running my WiFi routers on 48V and both were fine with that; so there are my two daya points. Considering other devices, I was leaning toward selecting 24V as a second power supply.

However, that leaves my NetGear PoE switch a bit of a loner and outside of The Box. For it, I would need to provide a proper 55V PoE power supply or at least 48V if stars align. The downside of this approach is that it leaves my Mikrotik hAP ax3 either on 12V or for the last, non-UPS power supply. But the upside is that it allows for a bit of future-proofing.

That leaves 2 non-UPS computers. Why are they not using UPS? Well, in my setup, UPS power is really limited and is reserved for only two categories: my main server and my Internet delivery devices (modem, router, WiFi APs). Anything else just needs to handle a power loss. These two computers fall into “anything else” category.

Intel NUC, we can already see, is flexible with a power supply specification. When it comes to standard industrial voltages, it can handle both 12V and 15V inputs. However, it Dell bretheren officially are not that flexible. While there is a possibility my 3050 micro would work on 15V, anything higher is a no-go. And since Dell authenticates its chargers, to figure this out, I’d need to trick it first into accepting such voltage - all of which smells like another sub-project. :)

If Dell doesn’t want to cooperate, the only way forward would be to use one of many buck modules intended for RVs that brings 24V to 19V. I would really like to avoid this, if possible, because the last thing I need is yet another power supply. However, it’s good to have options. And yes, ideally, I would find a 19V power supply; but I have a feeling that finding one that can be properly mounted inside a 1U case is not going to happen.

This brings us to the following rough power supply distribution (with a bit of derating on power specification):

  • 12V 150W (100W if I kick out Mikrotik hAP ax3 from my network):

    • 25W: Arris SURFboard Modem
    • 25W: Control boards
    • 40W: Mikrotik hAP ax3
  • 15V/19V/24V 200W (150W should be realistically more than fine):

    • 65W: Dell OptiPlex 3050 Micro
    • 65W: Intel NUC
  • 48V/55V 100W (going higher than this might be good for the future PoE devices):

    • 20W: Mikrotik hAP ac
    • 30W: Mikrotik Audience
    • 15W: Netgear GS30s5EPP

There is an alternative at a bit lower voltage:

  • 12V 75W

    • 25W: Arris SURFboard Modem
    • 25W: Control boards
  • 15V/19V/24V 200W (150W should be realistically more than fine):

    • 65W: Dell OptiPlex 3050 Micro
    • 65W: Intel NUC
  • 24V 150W

    • 20W: Mikrotik hAP ac
    • 30W: Mikrotik Audience
    • 40W: Mikrotik hAP ax3
  • Out-of-scope

    • 15W: Netgear GS30s5EPP

I am strongly leaning toward option 1, but option 2 is a good alternative. And yes, Mikrotik is not as power hungry as it seems above; I’ve never seen it reach its maximum power usage. However, since I really love my network, I use those numbers to bring an additional margin to the dimensioning process. If I find a nice power supply that’s slightly below what I need, I will get it and not worry about it. But, before I get to that, this fudged accounting provides more visibility into what brings the most value.

For various protection circuits, I’, going to rely onto power supplies to protect themselves. Thus, at minimum, I expect any selected power supply to have over-voltage, over-current, short-circuit, and over-temperature protection built-in.

When it comes to controlling this, I would say that ability to reset my Modem and WiFi is a must. And these can be two buttons as I want to be able to separately restart modem. For the hAP ax3, I don’t care as much since it has “under test” status in my network at this time. But let’s argue that it needs to be a separate button. At this time, I do control each of my computers inside the rack via smart plug so moving them into The Box, I might want to see a button for each, but I wouldn’t really mind if both go down at the same time. This brings the total number of reset buttons to somewhere between a minimum of three and a maximum of seven.

Lastly, this blog post leaves us with the following action items I will probably get around to solving:

  • Chck can Netgear GS305EPP work on 48V
  • Make trigger board for Dell
  • Check does Dell Optiplex 3050 Micro work on 15V

Powering External Devices while Protected

Boards for this project were sponsored by PCBWay.


Illustration

I have designed a few framework expansion cards already, but I tried to keep them data-only interface. That is, I avoided providing power as much as possible. And, in my opinion, that’s the proper way of doing it. However, what if I occasionally want to provide some power too? Can it be done (reasonably) safely?

Considering my intentions of using an expansion card, and thus sourcing power directly from the laptop, my goals were to avoid the two most common issues: current backflow and current limiting.

While using fuses for current limiting is a traditionally accepted method, it’s neither precise nor fast enough to protect a laptop. Fortunately, there are load switches that allow for not only precise current limiting but also for fast response.

My choice fell onto AP22652Wx family. These devices allow for quite precise current limiting using external resistor, short-circuit and reverse voltage/current blocking. All that in a small SOT26 (aka SOT23-6) package.

As it often goes, we do need a few external components too - at least one resistor (to limit current) and at least 100 nF decoupling capacitor. Realistically, if you’re not sure what gets connected on the other side, you need a few more capacitors. I usually opt for 100 nF/10 μF combo on both input and output. And here is the biggest disadvantage of using load switch - quite a lot of PCB real estate.

Second issue I mentioned (current “backflow”) is also known as reverse current. This happens when one accidentally connect a positive voltage to a power output. As often in life, bigger force “wins” and thus current will start flowing into the output pin. When that device is a framework expansion card powered by laptop itself, this is definitely not the most comfortable situation.

Load switch I selected does offer basic reverse-current protection, but I wanted a bit of a foolproof solution and that comes in the form of a humble diode. Such forward biased diode will allow current to flow towards output but it will stop conducting if output voltage goes higher than that. And yes, that comes at a cost of voltage drop. However, if you keep current low, voltage drop is still within a few hundred millivolts.

In my case I decided to limit current to 400 mA and thus CUS08F30 I selected has not only healthy headroom with current but it would drop under 400 mV at a full load. Yes, there are diodes that have even better characteristics but this one comes in quite a small package and I already had it lying around.

With theory solved, it was time to test it. For that purpose I’ve created a new Power framework expansion card https://github.com/medo64/PowerFEC. I used PIC16F1454 to interface it to USB, small ZXCT1009 to measure current, in addition to load switch and diode described above.

PCBs were manufactured courtesy of PCBWay. When it comes to framework expansion cards, they became my go-to manufacturer for two reasons. The first one is their standard routing bit seems to be much smaller than the one used by competition. This is important when it comes to the Type-C edge connector which requires a really small feature that large routing bits will mangle. The second nice thing is that PCBWay doesn’t use break-away connections, opting instead for V-groove. This means the edge is as perfect as it gets, saving me from manual sanding.

And yes, they’re not perfect. They still put their screen markings (you can pay extra to keep it clean). They still use leaded HASL (you can pay extra for non-leaded or ENIG). And sometimes waiting for somebody to manually check PCBs really clashes with my habit of finishing PCBs just before my bedtime thus forcing me to wait (albeit never too long).

But, all in all, those are all things that are minor enough not to bother me too much. I haven’t had bad PCBs from them in ages. Quite often with small PCBs I would get a few more than I ordered. And HASL is fine for pretty much all my use cases. While this PCB was sponsored, I used PCBWay on my own dime without complaint.

In order to make it all work, I decided to reuse my old code and make device behave as a serial port. It will output voltage and current in addition to allowing for a few basic commands (e.g., enable and disable).

After messing with it quite a while, I found protection working pretty much perfectly, thus expect a few updated expansion card designs in store.

As always, hardware and software are available on GitHub

Rendering KiCAD PCB to PNG

Illustration

I have pretty much automated creation of KiCAD export files thanks to its command line interfacehttps://docs.kicad.org/8.0/en/cli/cli.html). However, there was always one thing missing - image for each PCB. Yes, not really necessary for manufacturing but it’s really handy when you want to just quickly check things without starting KiCAD and/or for any inventory system.

Before KiCAD 8, getting a PCB image required exporting to .step file and then converting stuff around. It got complicated enough with all the prerequisites that I have essentially given up. Fortunately, that’s not true anymore, and now we can get .wrl files that can be often used directly.

kicad-cli pcb export vrml --output "board.wrl" "board.kicad_pcb"

While exported files don’t include lights, this usually doesn’t matter to viewers who add their own. However, for our processing, we want to add light that is just slightly to the top-left (at 0.1 -0.1 -1) of the camera (at 0 0 -1).

head -1 "board.wrl" > "board.front.wrl"
cat <<EOF >> "board.front.wrl"
Transform {
    children [
        DirectionalLight {
            on TRUE
            intensity 0.63
            ambientIntensity 0.21
            color 1.0 1.0 1.0
            direction 0.1 -0.1 -1
        }
EOF
cat "board.wrl" >> "board.front.wrl"
echo "] }" >> "board.front.wrl"

And yes, this method is a bit crude but it does work.

In order to reduce this 3D model into 2D image, ray tracing comes in handy. However, for that we need an external tool. I found that RayHunter worked great from the command line. While you only need that tool, you might want to check Castle Model Viewer as it can show you parameters in a bit more interactive way. Please note you also need libpng-dev package for PNG output.

cd ~/Downloads
wget https://master.dl.sourceforge.net/project/castle-engine/rayhunter/rayhunter-1.3.4-linux-x86_64.tar.gz
tar xzvf rayhunter-1.3.4-linux-x86_64.tar.gz
sudo install -m 0755 ~/Downloads/rayhunter/rayhunter /usr/local/bin/rayhunter

sudo apt install --yes libpng-dev

With prerequisites out of way, now we can finally export our board, looking directly at its front side. Camera position is a bit further away than I usually need it for my board but resolution of 4320x4320 pixels is large enough to then later crop the unneeded pixels.

rayhunter classic 7 \
    4320 4320 \
    "board.front.wrl" \
    "board.front.png" \
    --camera-pos 0 0 6 \
    --camera-dir 0 0 -1 \
    --scene-bg-color 1 1 1

You can see what each parameter does in rayhunter documentation

If all went fine, this will give you PCB board nicely rendered but with quite a lot of whitespace. In order to remove those extra pixels, I like to use ImageMagick.

sudo apt install -y imagemagick

Using convert command I trim all the whitespace, resize it to 1060x1060 pixels, add 10 pixels boarder on each side, and finally extend it to the full 1080x1080 size.

convert \
    "board.front.png" \
    -trim \
    -resize 1060x1060 -normalize -density 600 \
    -bordercolor white -border 10 \
    -gravity center -extent 1080x1080 \
    "board.front.png"

Congrats, your PCB image should be looking good just about now.


PS: You can do this from any PCB orientation by just adjusting camera and light position.

Pulling Rotary Encoder State

Illustration

When it comes to microcontrollers reading a rotary encoder, I often see it being done using interrupts. While that is not necessarily the wrong thing to do, it can crowd the interrupt handler with something that’s not really critical functionality, especially if microcontroller can handle it within its base loop just as effectively.

To start with, the code is available here and consists of two exported functions:

void rotary_init(void);
enum ROTARY_DIRECTION rotary_getDirection(void);

If we disregards rotary_init which just initializes a few variables, all the significant code is in rotary_getDirection function. To use it, simply call this function once in a while from the main loop, and it will return one of three values (ROTARY_DIRECTION_NONE, ROTARY_DIRECTION_LEFT, or ROTARY_DIRECTION_RIGHT) corresponding to the detected movement.

The heart of the functionality is in the following code:

    uint8_t currRotState = getRotaryState();
    if (currRotState != lastRotState) {
        histRotState = (uint8_t)(histRotState << 2) | currRotState;
        lastRotState = currRotState;
        uint8_t filteredRotState = histRotState & 0x3F;
        if (filteredRotState == 0x07) { return ROTARY_DIRECTION_LEFT; }
        if (filteredRotState == 0x0B) { return ROTARY_DIRECTION_RIGHT; }
    }
    return ROTARY_DIRECTION_NONE;

First, we get the state in binary for each of the 2 contacts available. Thus, the output can be either 0x00, 0x01, 0b10, or 0b11, depending on which contacts are closed. If the state has changed compared to the previous reading, we append it to the variable used to store previous states. As each state is 2 bits long, this means we can easily fit all 4 states in a single 8-bit value by shifting it 2 bits at a time.

If your switch is new and shiny, this miis where you might stop. However, in the real world, switches are dirty and thus can skip a state or two. For example, one of my rotary encoders would skip a state every once in a while. This means that a proper code would simply make experience worse as time goes by.

However, due to redundancies in how the encoder functions, detecting three neighboring states still gives you plenty of information to go by without misidentifying the direction of scrolling. In my code, detection starts when both encoder contacts are active (i.e. 0b11 state). On all encoders I worked with (admitely, mostly PEC12R series from Bourns), this actually nicely aligns with steps and almost perfectly filters wear-induced noise.

And yes, you can adjust the code slightly to make it run in an interrupt routine if you are so inclined.

Driving the Plunger (part 3)

This is part 3 out of 3 in the series (see part 1 or 2).


Well, if after reading previous posts you were curious as to how it all looks, wait no more. Below is a quick video showing its usage. And yes, it was a minimum effort video so sound is not there.

First is the mounting method. Just place syringe around the plunger and rotate to fix in place. If you used another syringe before this, you might need to move the plunger ahead until it makes contact. In the video, you can see that I reverse it a bit and then I move it forward just to illustrate this point.

After dealing with the initial paste surge, you get to control the syringe using either the button in the top-left corner or the foot pedal. As I use a foot pedal, you won’t directly see my presses, but you’ll notice the LCD flashes.

As you can see in the video, this thing actually works. However, it’s not without its issues. The most noticeable one is speed and drooping. Every push creates pressure and, no matter how many pull-back tricks I’ve tried, it will not squirt paste fast and without excess. Each profile was essentially a balancing act between how much paste I push and how quickly.

Firmware development for this was actually quite easy and really depended on two programming exercises I already had made from before: OLED and rotary button. The rest of the firmware was just reading the button status and pushing text to an OLED. You can check how I did it on GitHub but I will probably also make a separate post on each.

Would I use it? Well, maybe. While it actually does what I intended to do, the whole process is just too slow. I can do non-paste soldering much faster. Also, different paste will react differently and will need adjustments.

It’s not great for regular work but it’s actually quite okay if you need repeatable solder paste dispensing for special components.

Driving the Plunger (part 2)

This is part 2 out of 3 in the series (see part 1 or 3).

PCBs for this project were sponsored by PCBWay.


So, we’re continuing on PlungerDriver development. If you’re not up-to-date, do check part 1.

For electronics, I decided early to keep it rather simple and use only components I had lying around. I mean, I was waiting for motors but there was no reason why I couldn’t just cobble something up.

The first decision I almost always like to make is how I will control the darn thing. In this case I saw myself using combination of a rotary button and a small OLED screen.

Illustration

The rotary button I had was the one I used for a few projects in the past, which was Bourns PEC12R, more precisely its PEC12R-4220F-S0024 variant. It’s 12mm square, has detents (which I love), and you can also press it to get button functionality. Essentially, you have left/right/button controls. Ideal for something like this.

The screen I also had decided early on. Small 128x64 I2C OLEDs are easy to obtain and easy to drive. Even better, I had quite a few of different ones laying around from previous projects (e.g., USB OLED).

Input-wise, I wanted to connect it all via just a standard 3.81 mm Phoenix-style MC connector. These are really easy to work with, available from multiple manufacturers, and I have a load of these always in my storage.

For output, I decided to use JST XH connector. Using a different connector here was just to make it really clear what goes where. If I can do it, I always try to do different connectors for each functionality board might have. Makes mistake a lot less likely to happen.

As a trigger for paste dispensing, I decided to use both an on-board button and an external connector. While having a button on board was a handy thing to have during the testing, I really wanted an external connection for a foot pedal. Not only are they easy and cheap to get, but they’re the only practical way to trigger something while keeping both of your hands free. And yes, in order to avoid potential errors, I used a JST PH connector here.

The last selection was a PIC microcontroller. And yes, I knew it would be a PIC since those are the only ones I have in my storage. I did play with some other microcontrollers out there, but my pipeline and knowledge are so heavily skewed toward PIC that I always select it for projects like this. And here I opted for PIC16F1454. While many other Microchip devices would work equally well, I had a load of PIC16F1454 in my storage.

Illustration

PCBs were made by PCBWay and provided to me for free. While they do sponsor me occasionally, I actually did quite a few orders with them where I paid my own money to get stuff made. They were fast (well, much faster than me doing my part) and boards ended up being exactly what I ordered. Unfortunately, that meant they had a few errors - none of which were PCBWay’s fault.

The first issue I noticed was actually reaching programming ICSP header. OLED simply didn’t leave enough space to reach the darn thing. Fortunately, I use Pogo pins with my PICkit so I could reach the header from the bottom. Had I been using just a standard header, it would have been really a deal breaker.

Second, a more serious issue was with the motor driver simply not working. All voltages were just fine when the motor was not connected. However, connect the motor and it would go nowhere. It took me a bit of troubleshooting to notice I messed up Vref voltage divider and that was causing any load to trip the current limit function on my motor driver.

As I already soldered the whole board, I had to unsolder the OLED to reach resistors underneath and place a bodge wire. Since I use lead-free solder, it took a considerable amount of heat to fix that and the board survived that without any issues. The same couldn’t be said for my OLED display which didn’t work after I placed it back. So I had to unsolder it again in order to solder a replacement.

Why I mention this? Well, playing with higher temperature quite often damages the soldermask. And I had that experience with many PCB manufacturers, including PCBWay. However, either PCBWay changed their soldermask formulation to handle the higher temperature or this red soldermask I selected is a bit more resilient as after cleaning it with alcohol, I couldn’t even tell it was ever worked on.

And that’s all for this installment of the project. See you next time when I get the firmware running and see how this works in practice.

Driving the Plunger (part 1)

This is part 1 out of 3 in the series (see part 2).


Illustration

After playing with TB67H450FNG motor driver for a project of mine, I was really taken away by this little jelly bean chip. While rather basic, it’s really protective of its user. Not only does it offer a nice current limiting functionality defined by a single resistor, but it also has basic back-EMF current sink built in. Even if that goes wrong, there is a thermal shutdown circuit to save the day. And all that in the small 9-pin SOP package.

Yes, you read it right - 9 pin. While 8 pins are exactly where you expect them, the 9th pin is exposed beneath the chip itself and it’s pain to solder. Before you ask, yes, solder it you must.

At first glance, soldering it should not be too difficult. Just put some paste on pad, hit it from above (or below) with some heat and things will work out. And that is true if you place just enough solder paste. Place too much and you’ll get shorted pins from the back that are really annoying to get rid of. Place too little and it will not make contact thus leading to overheating and thermal shutdown. While not catastrophic, I can assure you troubleshooting this is not fun.

What I needed for this was a way to always place the same amount of paste on the bottom pad. My hands, while marvelous in their own right, were just not precise enough for this duty. What I needed was a way to push some paste out from a syringe in a repeatable manner.

As I was playing with a motor driver, thoughts immediately went toward using it to drive some gears and push a small rod into the back of the syringe in order to get its rotation into a linear motion.

With that idea on my mind, I went on to selecting components. I already knew which motor driver I wanted to use but it took me a while to decide on the motor.

Illustration

The first thought was to use a 3D printer stepper driver I had around. It would be ideal for this as you can control it really precisely, it holds position well, and it can handle some torque even without gearing. That idea lasted until I took it into my hand. That darn thing was heavy. When I accounted for all parts I needed for pushing into the syringe, darn things would be way too top heavy for my taste. And that’s even without taking bulkiness into account.

No, I wanted something a bit slender and thankfully, such motors exist. Even more importantly for my case, they already had a shaft on saving me an extra step. After measuring my paste syringes, I decided onto 6V 15 rpm motor with 55 mm M3 thread.

With an M3 rod integrated into the motor, my only task left for the mechanical portion of the project was to design a plunger. And I did so in TinkerCAD. It was just a simple body taking the motor from one side and taking a custom-designed plunger from the other.

With all the mechanical stuff out of the way, it was time to design the electronics. But that’s a story for the next week.

Counting Geigers

Illustration

A long while ago I got myself a Geiger counter, soldered it together, stored it in a drawer, and forgot about it for literally more than a year. However, once I found it again, I did the only thing that could be done - I connected it to a computer and decided to track its values.

My default approach would usually be to create an application to track it. But, since I connected it to a Linux server, it seemed appropriate to merge this functionality into a bash script that already sends ZFS data to my telegraf server.

Since my Geiger counter has UART output, the natural way of collecting its output under Linux was a simple cat command:

cat /dev/ttyUSB0

That gave me a constant output of readings, about once a second:

CPS, 0, CPM, 12, uSv/hr, 0.06, SLOW

CPS, 1, CPM, 13, uSv/hr, 0.07, SLOW

CPS, 0, CPM, 13, uSv/hr, 0.07, SLOW

CPS, 1, CPM, 14, uSv/hr, 0.07, SLOW

CPS, 1, CPM, 15, uSv/hr, 0.08, SLOW

CPS, 0, CPM, 15, uSv/hr, 0.08, SLOW

In order to parse this line easier, I wanted two things. First, to remove extra empty lines caused by CRLF line ending, and secondly to have all values separated by a single space. Simple adjustment sorted that out:

cat /dev/ttyUSB0 | sed '/^$/d' | tr -d ','

While this actually gave me a perfectly usable stream of data, it would never exit. It would just keep showing new data. Perfectly suitable for some uses, but I wanted my script just to take the last data once a minute and be done with it. And no, you cannot just use tail command alone for this - it needs to be combined with something that will stop the stream - like timeout.

timeout --foreground 1 cat /dev/ttyUSB0 | sed '/^$/d' | tr -d ',' | tail -1

If we place this into a variable, we can extract specific values - I just ended up using uSv/h but the exact value might depend on your use case.

OUTPUT=`timeout --foreground 1 cat /dev/ttyUSB0 | sed '/^$/d' | tr -d ',' | tail -1`
CPS=`echo $OUTPUT | awk '{print $2}'`
CPM=`echo $OUTPUT | awk '{print $4}'`
USVHR=`echo $OUTPUT | awk '{print $6}'`
MODE=`echo $OUTPUT | awk '{print $7}'`

With those variables in hand, you can feed whatever upstream data sink you want.

Randomizing Serial Number During MPLAB X Build

Illustration

Quite often, especially when dealing with USB devices, a serial number comes really handy. One example is my TmpUsb project. If you don’t update its serial number, it will still work when only one is plugged in. But plug in two, and all kinds of shenanigans will ensue.

This is the exact reason why I already created a script to randomize its serial number. However, that script had one major fault - it didn’t work under Linux. So, it came time to rewrite the thing and maybe adjust it a bit.

The way the original script worked was as a post-build step in MPLAB X project that would patch the Intel Hex object file and that’s something I won’t change as it integrates flawlessly with programming steps.

The resulting serial number was 12 hexadecimal digits in length (48 bits or random data) and that was probably excessive. Even worse, that led to an overly complicated script that was essentially a C# program to patch hex after the modification was done since any randomization always impacted more than 1 line. As I wanted a solution that could work anywhere the Bash can run (even Windows), I wanted to make my life easier by limiting any change to a single line.

Well, first things first, to limit serial number length, I had to figure out how much of a serial number can fit in one. Looking at the Intel hex produced by MPLAB X, we can see that each line is 16 bytes, which means any serial number intended for USB consumption can be up to 8 characters. However, what if other code pushes the serial number further down the line? Well, now you get only a single character.

What we need is a way to fix the serial number location. The solution to this is in the __at keyword. Using it, we can align our string descriptors wherever we want them. In the TmpUsb example, that would be something like this

const struct {
    uint8_t bLength;
    uint8_t bDscType;
    uint16_t string[7];
} sd003 __at(0x1000) = {
    sizeof(sd003),
    USB_DESCRIPTOR_STRING,
    { '2','8','4','4','3','4','2' }
};

The whole USB descriptor structure has to fit into 16 bytes as to limit any subsequent modification to the single line. The first 2 bytes are length and type bytes, leaving us with 14 bytes for our serial. Since USB likes 16-bit unicode, this means we have 7 characters to play with. If we stay in the hexadecimal realm, this provides 28 bits of randomization. Not bad, but we can slightly improve on it by expanding character selection a bit.

That’s where base32 comes in. It’s a nice enough encoding that isn’t case sensitive and it omits most easily confused characters. And yes, it would take 40 bits to fully utilize base32 but trimming it at 7 will leave you with 35 bits which is plenty.

How do I get this serial number in an easy way? Well, getting 5 bytes using dd and then passing it to base32 will do.

NEW_SERIAL=`dd if=/dev/urandom bs=5 count=1 2>/dev/null | base32 | cut -c 1-7`

If we pass the non-random serial number in, with some mangling to get it expanded, it is trivial to swap it with the new one using sed:

LINE=`cat "$INPUT" | grep "$SERIAL_UNICODE"`
NEW_LINE=`echo -n "$LINE" | sed "s/$SERIAL_UNICODE/$NEW_SERIAL_UNICODE/g"`
sed -i "s/$LINE/$NEW_NEW_LINE/g" "$INPUT"

But no, this is no good. Changing content of a line will invalidate checksum character that comes at the end. To make this work we need to adjust that checksum. Thankfully, checksum is just a sum of all characters preceding with a bit of inversion as a last step. Something like this:

CHECKSUM=0
for ((i = 1; i < $(( ${#NEW_LINE} - 2 )); i += 2)); do
    BYTE_HEX="${NEW_LINE:$i:2}"
    NEW_NEW_LINE="$NEW_NEW_LINE$BYTE_HEX"
    BYTE_VALUE=$(printf "%d" 0x$BYTE_HEX)
    CHECKSUM=$(( (CHECKSUM + BYTE_VALUE) % 256 ))
done
CHECKSUM_HEX=`printf "%02X" $(( (~CHECKSUM + 1) % 256 )) | tail -c 2`

Once all this is combined into a script, we can call it by giving it a few arguments, most notably, project directory (${ProjectDir}), image path (${ImagePath}), and our predefined serial number (2844342):

bash "${ProjectDir}/../package/randomize-usb-serial.sh"
  "${ProjectDir}"
  "${ImagePath}"
  "2844342"

Script is available for download here but you can also see it in action as part of TmpUsb.

MPLABX and the Symbol Lookup Error

After performing the latest Ubuntu upgrade, my MPLAB X v6.15 installation stopped working. It would briefly display the splash screen and then crash. When attempting to run it manually, I encountered a symbol lookup error in libUSBAccessLink_3_38.so.

$ /opt/microchip/mplabx/v6.15/mplab_platform/bin/mplab_ide

/opt/microchip/mplabx/v6.15/sys/java/zulu8.64.0.19-ca-fx-jre8.0.345-linux_x64/bin/java: symbol lookup error: /tmp/mplab_ide/mplabcomm4864006927221691126/mplabcomm5312997795113373971libUSBAccessLink_3_38.so: undefined symbol: libusb_handle_events_timeout_completed

Since there were a few links to the old MPLAB directory, I cleaned up /usr/lib a bit, but that didn’t help. What did help was removing the older libusb altogether.

sudo rm /usr/local/lib/libusb-1.0.so.0

With that link out of the way, everything started working once again.