.NET Plugins Without a Common Assembly

For a new project of mine, I wanted to use a plugin architecture. Since it had been a while since I did plugins in .NET the last time, I wanted to see what's new in .NET 8. Well, no news at all - plugins for .NET application are literally the same as they always were.

Don't misunderstand me, there were some steps forward. For one, there is a working example showing you exactly how it's done. And honestly, that is an example you should use. However, this is the same way we did it back in .NET 2.0 days.

And yes, I am a bit unfair since there were a lot of upgrades in the backend and .NET 8 will give you more options on how to load stuff. Let's not even get into performance improvements. However, I still have to create a common assembly that inevitably becomes a hell to maintain. And what about single-file publishing? Nope, still not supported.

While I was ok doing it the classical-style, I really hated not having a single-file, self-contained deployment. They are just so freeing when it comes to actual deployments and worth every additional byte they consume. And, since the whole framework is bundled in a single package, there is no real reason why it cannot be done. Is there?

Well, I decided to give it a try.

But before dealing with single-file deployments, what about removing the need for common assembly? Well, if you can get away with simple Get/Set interface that returns objects that can then use other standard interfaces; or, said plainly, if you can get away with forwarding standard .NET classes/interfaces, answer as always lies in good old IDesignerOptionService.

While quite a lot of interfaces you could use for plugins got trimmed with time (especially during the Windows Forms exodus), this one somehow survived. And it's almost perfect for lously-coupled plugins. It gives you two methods: get and set. Thus, you can simply use something like that:

public class MyPlugin : IDesignerOptionService {

    public object GetOptionValue(string pageName, string valueName) {
        switch (pageName) {
            // do something that returns object
        }
    }

    public void SetOptionValue(string pageName, string valueName, object value) {
        switch (pageName) {
            // do something that with object
        }
    }

}

As long as you stick to built-in objects (or you're willing to do a lot of reflection), you're golden. I agree, there is a performance impact and design is not as clean as it could be, but I would argue it's quite often worth it since we don't have to deal with common assembly versioning and all the fun that can cause.

Thus, that only leaves single-file deployment as our goal. Is it really not supported?

Indeed, if you try to make a single-file deployment of the plugin dll, it will say that you cannot do that unless OutputType is Exe. And, if you try to combine that with common PluginBase assembly, it will not be able to load anything because PluginBase as a separate assembly is not the same as PluginBase that got packed. However, if you are ok with this janky IDesignerOptionService setup, you can make your host a single-file application.

And remember, the whole .NET is essentially packed there so this application (assuming you didn't trim it), will have no issues loading our plugin DLLs.

So, to summarize, you can have your host application deployed as a single file (only the executable is needed) and then load any class from plugin dll that implements IDesignerOptionService interface. Such class will then use .NET from a host itself to run without .NET being installed separately.

To see it in action, [download the example](). Don't forget to run Make.sh in order to copy files around.

AuxPower1U: Switching the High Voltage

This is a post 9 in the series (previous: Fan Controller).


Using a MOSFET as a switch is easy. Select any P-MOSFET, make sure it works at your board's logic level, supports enough sweet amps, and you're golden. I can almost guarantee that any MOSFET that satisfies those parameters will be good enough. Just pull it down to earth when you want lights to go out.

However, this doesn't work for high-voltage DC circuits. Now, people can disagree on where the "high voltage" begins. When it comes to switching DC power, for me that limit is somewhere around 20V. This is the limit where you cannot use "normal" components without care, nor can you expect your circuits that worked just fine to continue operating. It's the land of magic smoke.

For AuxPower1U, power supplies go up to 60V. And it's not hard to find a MOSFET that goes that high. However, driving that MOSFET is another story. In order for it to be fully off, you cannot just use 5V - that's not high enough to turn the MOSFET off if there's 60V passing through it.

A simple solution is just pulling the gate up to your switched voltage - 60V will definitely turn it off. The only problem is that this also brings 60V at
your microcontroller's doorstep. But, with a bit of thought, you can see it's not a catastrophic issue - an optocoupler or even a simple transistor will provide enough isolation to keep the microcontroller happy.

But more devious problem lurks underneath - often overlooked Vgs specification. Most of the time, you only get a 20V difference between gate and source to play with. In practice, to drive 60V, you can only go as low as 40V on the gate. Going all the way to ground is definitely out of question.

To resolve this, we can (ab)use the fact that a MOSFET has quite a high input impedance. Thus, a humble voltage divider will allow us to keep it at just below 20V of difference. In a 60V case, that means 10K/24K resistor values resulting in about 18V of voltage difference. If we want to be extra safe, placing a zener diode will further limit the maximum voltage. Ideally, you want the zener's voltage rating to be slightly above the expected voltage (while still under 20V) in order to minimize power usage. In this case, an 18V zener will do nicely since the voltage divider sits just under that voltage.

Making this circuit usable for many different voltages involves a decision on the minimum voltage and setting up a voltage divider to the minimum viable Vgs value. We expect the zener to "clip" higher voltages so that's the only calculation you need. Zener's power rating is not really important in this case since the currents involved are low enough.

Of course, having a working switch is just the start of the story. Depending on your needs, you might need to fiddle with the circuit a bit. For example, increasing resistance lowers power usage but it might not work properly over the whole range (moslty dependant on the MOSFET input leakage). And let's not even go into what happens if we want to do fast switching.

This is a generic, high-voltage MOSFET driving circuit intended for "slow" switching of DC voltage, and it's a good starting point.


PS: Yes, I know that I am missing a minus (-) sign in front of many voltages since we are dealing with P-MOSTFETs. I decided to remove them for clarity.

AuxPower1U: Fan Controller

This is post 8 in the series (previous: Fan Controller Design).

This post is sponsored by PCBWay.
Check and/or participate in their PCBWay 7th Project Design Contest.


With all design decisions done in the previous post, it was time to get PCBs made. As PCBWay was interested in the project, it was an easy choice. :) That said, if you go to my GitHub, you'll notice I have many more electronics projects that I haven't gotten around to write about and they are not sponsored by anybody (patience of my wife, excluded). Yet still, I quite often go for PCBWay anyhow.

In this particular case, PCB was simple enough that any manufacturer would be able to produce it. Or so I thought before PCBWay contacted me due to an issue. You see, my KiCAD export workflow relies on a script. Thus, usually results are quite repeatable. So I thought it was a false positive. But nope - PCBWay was right, I had some of my layers swapped - darn copy/paste when I was updating the script. I am not saying that some other manufacturers wouldn't catch this, but it wouldn't be a surprise if I got wrong PCBs due to this mistake on my part. With PCBWay, I never managed to sneak an error by them.

Once the board arrived, I started creating firmware and the first step was generating the 25 kHz PWM signal. A few initialization settings later, I had a perfect 50% duty cycle signal. So I connected my fans only to be surprised by their noise. I mean, I expected some noise - those are server fans after all. But I also expected that noise at 50% wouldn't be that bad. Then again, they did move a lot of air, so I decided to bring PWM down to 10%. And... fans were equally loud and moved an equal amount of air.

It took me a few visits to the fan datasheet to notice one discrepancy. Unlike normal PC fans that have PWM on wire 4, Delta's have PWM on wire 3. Wire 4 is the tachometer output. Why the heck they opted for a non-standard pinout is beyond me; but I am sure there is some historical reason behind it. In an case, I swapped wires to be in a standard fan arrangement and what I got was almost silent cooling.

Since I don't know how much cooling I'll need, I opted to have 3 fans together. And while this was probably an overkill because these beasts move air like there's no tomorrow, it also enabled me to keep fans at low speed by default, going up in speed (and noise) only when temperature requires that.

So, after consulting the power supply datasheet, I decided on the following curve:

  • 10% speed until it reaches 40°C
  • speed linearly increases up to 50% between 40-60°C
  • stay at 50% up to 70°C
  • if above 70°C, go to full 100%

The idea behind this is to balance fan noise and cooling, unless we get into temperatures that might jeopardize power supplies. Since my selected power supplies shutdown at 85°C, giving them some cooling buffer seemed like a good idea. Mind you, I don't expect them to ever reach that temperature, but it's nice to know that fans will do their best, if they do.

Those reading fan datasheet will notice that, while fans work just fine at low RPM, they do require 30% PWM to start. Such requirements are the reason why, on servers, you will hear fans running at 100% for a few seconds before slowing down to normal speeds - they have to ensure successful startup. For me, I didn't need to be that aggressive but I still wanted to give them a decent chance. So, every time power is applied, the fan controller will push 30% PWM out for 500ms, followed by a 250ms burst to full speed. After that, speed goes down to 10% and temperature control loop takes over. Just to be fancy, not all fans spin at the same time, but are offset by 250ms. There is literally no reason for this other than it giving an interesting sound profile.

I mentioned temperature a few times now, so you might be wondering how I am measuring it. The nswer is the MCP9701A temperature sensor. It gives you a temperature range from -40°C all the way to 125°C with a slope of 19.53 mV/°C. While this number looks a bit crazy, it actually translates almost perfectly into 4 bits (10-bit ADC) per °C if you use a 5V reference input. Since I don't expect sub-zero temperatures, my temperature calculation is as simple as removing 82 bits from the raw reading to make the scale start at 0°C and then dividing the whole thing by 4 (those wanting to optimize would simply use shift operation here).

Now, PIC12F1501 does have an internal temperature sensor you can use. And that would work in a pinch, but it's not without its drawbacks. The first one is that it measures the die temperature, that varies not only due to external temperature, but also based on what's happening in microcontroller itself. If you drive a lot of things, your reading will be higher even though external temperature remains the same. Other, probably related issue, is its instability. It's not what you would call a precise sensor to start with, but then you'll have to deal with smoothing out data coming out of it because it can change value by couple degrees in mere seconds. It's not that you cannot filter those things out - an long-period averaging will take care of that - but it's just a pain in the butt. Thus, I went with a proper temperature sensor.

There are some things I intentionally omitted. But one that causes the most doubt is speed readback. There are two ways I could have gotten that feature for free. One is to only have 3 channels, which would allow me to use 3 pins to control PWM and 3 pins to read back the speed. Yes, that would also mean switching to the internal temperature sensor, but I could make that work. Other way of doing that would be to have just 1 PWM signal going to all 4 fans, 4 speed inputs, and that even leaves 1 pin for a temperature input. But then the question becomes "why".

Since this board is a standalone thing, there is literally no way of letting the user know that one fan is stuck. Yes, it could have enabled an even smoother startup (since I can verify it's started instead of blindly going to 30%) but, other than that, there is simply no use for it. It would only make sense if I went onto a bigger microcontroller so I can have UART or I2C connecting it to the rest of the system, and I think that would be overkill.

And no, the system as a whole will not kill itself if fans stop spinning. My other part is controlling power supplies and that board will have its temperature sensor. If temperature goes dangerously high (e.g. because fans are not spinning), that board is in a situation to either stop overloaded power supply, inform the user, or do whatever I deem necessary in that situation. After giving it a lot of thought, I decided to keep it simple.

Source for PCBs and firmware can be found on project's GitHub pages. And in video below you can see the fan controller startup in action.

AuxPower1U: Fan Controller Design

This is post 7 in the series (previous: Cooling, next: Fan Controller).

This post is sponsored by PCBWay.


I haven't tried to build myself a fan controller, I swear. My original plan was to get one of many PC fan boards and call it a day. However, I was surprised at how little of those fan controllers could properly work without PC. Even worse, most of their fan profiles would cause my 1U fans to scream. So, making my own became easier solution than figuring how to make the existing ones work.

First, about the fans. I had 1U case that was made of quite thick plastic. That left only 36.576 millimeters to work with. If we round down this overly precise number in order to account for some tolerances, the biggest fan I can place here is 36x36 mm in size. And that's definitely not a standard size you'll find in your desktop computer. However, it's standard enough for there to be multiple fans of that size. I opted to got with Delta FFB03612EHN primarily due to its 36x36x28 mm dimensions that would just fit into my case. But my second concern was not less valuable - it was a PWM controlled fan.

When it comes to fans, you have 3-pin ones that always run at the same speed and the only way to slow them down is to lower the voltage. This wasn't what I was interested in. I wanted 4-pin fans that have PWM signal for speed control. And my Deltas were just such fan.

To control pretty much any fan speed, you need to have a 25kHz PWM signal. There is quite a bit of latitude allowed (for Delta fans, it's usually 20-30 kHz) but different fans have different tolerances, so keeping it as close to 25 kHz is probably the best idea. Fan speed itself is not controlled by PWM frequency but by duty cycle. If the duty cycle is 50%, you run fan at 50%; if the duty cycle is 25%, fan runs at 25%; if the duty cycle is 75%, you get the drill...

Officially, a duty cycle control needs to be done by an open-drain output. That is, you only ever pull the PWM signal down, letting it go to the fan's internal 5V pull-up for the rest of a PWM cycle.

Keeping this in mind, I went to search for a microcontroller I could use for the project. It had to be low-pin count, so I don't waste a lot of PCB space; it had to have hardware PWM, so I don't need to bit-bang; it had to have open-drain PWM output; it had to have an analog input for temperature; and lastly, I had to have one available in my drawers.

Looking at Microchip PICs I had available, I quite quickly went toward the PIC12F1501. This one is a little gem. It has not one, but four PWM channels thus allowing me to control 4 fans in a completely independent manner. It also has 4 ADC channels (some pins overlap) thus allowing for an external temperature sensor (I had MCP9701A lying around). It even has an internal temperature sensor which is the fact I noticed only once I already had my PCBs made, so I ended up not using it.

But you will notice it didn't have one thing I needed - an open-drain PWM output. However, since the pull-up value within the fan is specified to be a maximum of 5.25 V, I was reasonably sure I could ignore that. Any voltage differential would be small and thus any extra current going through pull-up resistor would be way lower than the current it already had to carry during its "off" cycle when it gets connected directly to GND. In short, I was willing to ignore this part of the specification.

After messing around in KiCAD for a while, I had my design finished and it was off to my sponsor, PCBWay, to manufacture the PCBs. But let's continue that part of fan controller story in the next post.