Den 27:e mars till och med 24:e april pågår min utställning av träslöjdsföremål på Slöjdens hus i Skövde. Temat är: “Traditionellt hantverk möter ny teknik” och jag visar bland annat upp föremål som vakuumstabiliserats, datorfrästs eller fått Lichtenbergfigurer med hjälp av högspänning.
Här är en film som demonstrerar dessa tekniker. Filmen visas även i utställningslokalen och är med flit ljudlös.
En digital vernissage hålls på Facebook den 27:e mars kl 11:
This article describes a simple timer that disconnects a number of lithium ion batteries from their chargers after a predetermined time. The timer consists of a Teensy LC (Arduino compatible) processor board, a 2×16 alphanumeric LCD, MOSFETs to connect/disconnect the chargers and batteries and a few 3D-printed parts.
Why it is needed
The youth section of my local orienteering club has a few head lamps to lend out during trainings to kids who do not have their own. The batteries need to be charged after each use, but it might be several days or even weeks before the leaders are back at the club house to disconnect the batteries from the chargers. There are reports of accidents where the chargers or batteries have caught fire in situations like this, so it would be good if the charging time could be limited.
The first idea was to put a timer on the mains supply to the chargers. After a quick investigation, I noticed that the chargers actually consume current from the batteries when they are not plugged into mains (an LED on the chargers is even lit in this case), so this was not a good solution as the batteries would be empty after a week or two in this state.
Instead I decided to build a timer that interrupts the DC connection between the chargers and batteries rather than the mains supply to the chargers.
Circuitry
As the processor board I decided to use a Teensy LC board, which is a pretty small and inexpensive “Arduino compatible” board (i.e. it can be programmed in the Arduino environment) with an ARM Cortex-M0+ processor. It has 27 I/O pins, which is more than enough for this simple application. The Arduino/Teensy environment provides useful and time-saving libraries for controlling the alphanumeric LCD, doing switch debouncing and keeping track of the time.
As the user interface, I decided to use a 2×16 LCD together with four push buttons. The LCD can be used in either 8-bit or 4-bit mode and I opted for the 4-bit mode since it requires fewer wires to be soldered between the Teensy and the LCD. The LCD also needs a potentiometer to control the contrast. Without that, or with the wrong setting of the pot, it will probably not show any characters at all.
I connected four momentary closing push buttons between pins on the Teensy and GND. By enabling internal pull-ups on these pins, no resistors are needed – reducing the amount of soldering required.
All of the pieces described so far are mounted to the back of a 3D-printed front panel as shown in the photo below.
The USB signals are brought out to a separate mini-B connector mounted so that it is close to the side of the enclosure. The LCD is powered directly from the USB 5-V line. The 10-kohm potentiometer that provides the contrast signal to the LCD is connected between the LCD VCC pin (5 V) and GND. The push buttons are soldered to a scrap piece of perf board which is screwed to the front panel. Since the Teensy LC board is so small and lightweight, it works fine to just have it hanging in its short wires behind the LCD. An alternative would be to solder a few unused pins of the Teensy to the perf board.
The connection between the pieces are so few that they can rather easily be determined from the photo above. The code shown at the end of this post also defines which pins are used for what on the Teensy.
One pin of the Teensy controls the MOSFETs via the orange wire in the photo above. The MOSFETs are located on a separate piece of perf board, where most of the soldering in this project takes place:
I used MOSFETs I had on hand, but unfortunately, I did not have six identical ones, so one of six channels is equipped with a different kind of transistor. The schematic for each of the channels is shown below:
I did not want to control the MOSFET (M1) with the 3.3 V signal coming directly from the Teensy, since it might not turn on well enough with just 3.3 V. Instead I used a pull-up (R3) to the roughly 8-V voltage coming from the charger. When the NPN transistor Q1 is on, it pulls the gate of M1 low and thus turns it off and disconnects the battery from the charger. When the Teensy pulls the DISCONNECT signal low, the base current of Q1 (coming via R1) is diverted so that Q1 turns off and R3 can pull the gate of M1 high. Thus M1 turns on and connects the battery to the charger.
There is just a single DISCONNECT signal from the Teensy, connected to all the channels. The channels also share the same GND.
The Teensy is powered via a separate USB cable, so there could be a situation where the Teensy is unpowered, while there are chargers and batteries attached to the timer. I wanted the batteries to be disconnected from the chargers in this case and that is the reason for the diode D1. When the Teensy is unpowered, any of its pins will probably be about a diode drop (ESD diode) above GND if a small current is flowing into the pin. This might be a low enough voltage to prevent Q1 from turning on and M1 would be on, which I do not want. By adding the diode D1 in series with the DISCONNECT signal, we can be pretty sure Q1 will have enough base current to be on even when the Teensy is unpowered, and thus the batteries will be safely disconnected from the chargers in this case.
The MOSFETs I used (mostly STD35NF06T4) as M1 are vastly overkill in that they can handle much larger currents and much higher voltages than what will actually be needed here, so cheaper and smaller devices would work just as well. But I used what I had on hand that would fit the bill and I found no smaller suitable devices in the junk bin.
To connect the chargers and batteries to the timer, I used extension cords that were supplied with the headlamps, but which are not really needed when the lamps are used for running at night. I cut these cables in half and soldered them to the MOSFET board. Unfortunately only four such cables were available, so I will have to add two more in the future to enable channels five and six.
Mechanics
As the main part of the enclosure, I used a CU-1874-B box from Bud Industries, but as mentioned above, I replaced the lid with a custom 3D-printed part I designed in Fusion 360. It has screw posts for the display and for the board with the switches, as well as rectangular walled holes for 3D printed buttons that push on the switches. The front also has some reinforcements to make it more sturdy, although it gets pretty sturdy anyway when the LCD and the switch board have been screwed into it.
I designed the screw posts so that M2.5 (for the LCD) and M3 (for the switch board) screws could be self-threaded into them. The holes are square with sides of 2.1 mm for M2.5 and 2.6 mm for M3. The entrances of the holes are chamfered to make it easier to start the threads.
To mount the MOSFET board to the bottom of the box, I 3D-printed a frame (also with screw posts) that I then glued to the bottom of the box using CA glue. This way I did not have to drill any screw holes in either the front or the bottom of the box.
I wanted to have proper strain reliefs on the cables and I 3D-printed those to fit all six cables at once. Again with square holes to allow M3 screws to self-tap into the material.
Even though no screw holes were needed in the box, there still had to be holes for the cables and for the USB connector to power (and if necessary reprogram) the Teensy. Since I have a CNC machine, I used that to mill these holes. The cable holes could easily have been made with simpler tools, but the wall around the USB hole had to be thinned for a proper fit of the connector and this was easier done with the CNC than with alternative methods.
Code
The program running in the Teensy is a simple Arduino “sketch”. The LiquidCrystal library is used for communication with the LCD and the Bounce2 library is used for button debouncing. Time is kept by an elapsedMillis timer, a variable that automatically increments every millisecond.
The setup() routine – which is automatically called once at the beginning of an Arduino sketch – defines a custom LCD character (a right-arrow, play-symbol) used on the start screen to refer to the button to press to start the charging. It then sets up the I/O lines used for the four buttons; start, stop, increment and decrement. The line for the DISCONNECT pin and the on-board LED are set up as outputs.
The loop() routine is automatically called over and over again, indefinitely, in an Arduino sketch. In this case, it contains a simple state machine that keeps track of whether the timer is running or if it is stopped. If it is stopped, it only reacts to the start (or “play” if you will) button. By default, it starts a 15 hour countdown when play is pressed.
If instead the timer is already running, it will react to either increment, decrement or stop. Stop is self explanatory, while increment and decrement increase or decrease the remaining time. Initially the steps are 1 hour, but when the remaining time is lower, the steps are reduced accordingly. A maximum time of 48 hours have been defined earlier in the program.
When the timer is running, the LCD shows the remaining time in the format HH:MM:SS and this is updated every second. The LED is also toggled every second, although this will not be visible to the user as it is embedded deep in the opaque box.
Finally the program contains a few functions to help with showing information on the LCD.
Here is the complete code:
/* Head lamp charge timer.
Disconnects a charger from a (head lamp) (LiIon) battery after a given time
to reduce the risks of having a charger connected for a long time to
a LiIon battery.
The user interface consists of a 2x16 LCD panel and some buttons.
Target: Teensy LC
Written by Per Magnusson, http://www.axotron.se
v 1.0 2021-01-08
This program is public domain.
*/
#include <Arduino.h>
#include <LiquidCrystal.h>
#include <Bounce2.h>
static const int32_t DEFAULT_TIME = 15*60*60*1000; // In milliseconds
static const int32_t MAX_TIME = 48*60*60*1000; // In milliseconds
static const int32_t TIME_STEP = 1*60*60*1000; // In milliseconds
static const int32_t TIME_STEP2 = 10*60*1000; // In milliseconds
static const int32_t TIME_STEP3 = 1*60*1000; // In milliseconds
static const int32_t TIME_STEP4 = 10*1000; // In milliseconds
// Stated
static const int32_t STATE_OFF = 0; // Not charging
static const int32_t STATE_ON = 1; // Charging
// Pins
static const int START_PIN = 2; // Start timer (and turn charging on)
static const int STOP_PIN = 3; // Stop timer (and turn charging off)
static const int INC_PIN = 4; // Increment timer by 1 hour
static const int DEC_PIN = 5; // Decrement timer by 1 hour
static const int DISCONNECT_PIN = 12; // High disconnects charger from battery
// LCD RS E D4 D5 D6 D7
LiquidCrystal lcd(6, 7, 8, 9, 10, 11);
static const int LED_PIN = 13;
elapsedMillis timer;
uint32_t end_time;
int32_t state;
Bounce b_start = Bounce();
Bounce b_stop = Bounce();
Bounce b_inc = Bounce();
Bounce b_dec = Bounce();
// Custom character
byte play_glyph[] = {
B00000,
B10000,
B11000,
B11100,
B11110,
B11100,
B11000,
B10000
};
const byte play_char = 0;
void setup()
{
Serial.begin(57600);
lcd.begin(16, 2);
lcd.createChar(play_char, play_glyph);
b_start.attach(START_PIN, INPUT_PULLUP);
b_stop.attach(STOP_PIN, INPUT_PULLUP);
b_inc.attach(INC_PIN, INPUT_PULLUP);
b_dec.attach(DEC_PIN, INPUT_PULLUP);
b_start.interval(25);
b_stop.interval(25);
b_inc.interval(25);
b_dec.interval(25);
timer = 0;
end_time = 0;
state = STATE_OFF;
pinMode(DISCONNECT_PIN, OUTPUT);
digitalWrite(DISCONNECT_PIN, HIGH);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH);
lcd_splash();
digitalWrite(LED_PIN, LOW);
Serial.println("Charge timer");
}
void loop()
{
static uint32_t last_update = 0; // The last time the display was updated
b_start.update();
b_stop.update();
b_inc.update();
b_dec.update();
if(state == STATE_OFF) {
// Timer is off
if(b_start.fell()) {
// Start timer
timer = 0;
end_time = DEFAULT_TIME;
state = STATE_ON;
digitalWrite(DISCONNECT_PIN, LOW);
digitalWrite(LED_PIN, LOW);
lcd_update();
last_update = 0;
}
} else {
// Timer is on
if(timer > end_time) {
// Time has run out
state = STATE_OFF;
digitalWrite(DISCONNECT_PIN, HIGH);
lcd_done();
} else if(b_stop.fell()) {
// The stop button was pressed
state = STATE_OFF;
digitalWrite(DISCONNECT_PIN, HIGH);
lcd_stop();
} else if(b_inc.fell()) {
// Increment time
if(end_time - timer < TIME_STEP3) {
end_time += TIME_STEP4;
} else if(end_time - timer < TIME_STEP2) {
end_time += TIME_STEP3;
} else if(end_time - timer < TIME_STEP) {
end_time += TIME_STEP2;
} else {
end_time += TIME_STEP;
}
if(end_time > MAX_TIME) {
end_time = MAX_TIME;
timer = 0;
last_update = 0;
}
lcd_update();
} else if(b_dec.fell()) {
// Decrement time
if(end_time - timer < TIME_STEP3 + 3 * TIME_STEP4) {
end_time -= TIME_STEP4;
} else if(end_time - timer < TIME_STEP2 + 5 * TIME_STEP3) {
end_time -= TIME_STEP3;
} else if(end_time - timer < TIME_STEP + 3 * TIME_STEP2) {
end_time -= TIME_STEP2;
} else {
end_time -= TIME_STEP;
}
lcd_update();
}
if((state == STATE_ON) && (timer > last_update + 1000)) {
lcd_update();
last_update = (timer / 1000) * 1000;
if(digitalRead(LED_PIN)) {
digitalWrite(LED_PIN, LOW);
} else {
digitalWrite(LED_PIN, HIGH);
}
}
}
}
// Draw the splash screen
void lcd_splash()
{
lcd.clear();
lcd.noCursor();
lcd.noBlink();
lcd.setCursor(0, 0);
// 0123456789012345
lcd.print(" Laddningstimer "); // Charging timer
lcd.setCursor(0, 3);
// 0123456789012345
lcd.print(" Tryck "); // Press
lcd.write(play_char); // >
}
// Draw the screen showing that charging has finished
void lcd_done()
{
lcd.clear();
lcd.noCursor();
lcd.noBlink();
lcd.setCursor(0, 0);
// 0123456789012345
lcd.print(" Laddning "); // Charging
lcd.setCursor(0, 3);
// 0123456789012345
lcd.print(" klar "); // done
}
// Draw the screen showing that charging has finished
void lcd_stop()
{
lcd.clear();
lcd.noCursor();
lcd.noBlink();
lcd.setCursor(0, 0);
// 0123456789012345
lcd.print(" Laddning "); // Charging
lcd.setCursor(0, 3);
// 0123456789012345
lcd.print(" avbruten "); // interrupted
}
// Convert a positive number between 0 and 99 to a nul-terminated
// string with two digits. The first character is 0 for numbers below 10.
void int_to_00str(uint32_t num, char *str) {
str[2] = '\0';
str[1] = (num % 10) + '0';
str[0] = (num - (num % 10))/10 + '0';
}
// Convert a positive number between 0 and 999 to a nul-terminated
// string with three digits. Leading zeros are replaced by spaces.
void int_to_3str(uint32_t num, char *str) {
str[3] = '\0';
str[2] = (num % 10) + '0';
num -= (num%10);
num /= 10;
str[1] = ' ';
if(num > 0) {
str[1] = (num % 10) + '0';
num -= (num%10);
num /= 10;
}
str[0] = ' ';
if(num > 0) {
str[0] = (num % 10) + '0';
}
}
// Update the countdown timer on the LCD
void lcd_update()
{
uint32_t time_left;
uint32_t seconds;
uint32_t minutes;
uint32_t hours;
char sec_str[3];
char min_str[3];
char hour_str[4];
time_left = end_time - timer; // ms
if(time_left < 0) {
time_left = 0;
}
time_left /= 1000; // s
seconds = time_left % 60;
time_left -= seconds;
time_left /= 60; // minutes
minutes = time_left % 60;
time_left -= minutes;
time_left /= 60; // hours
hours = time_left;
int_to_00str(seconds, sec_str);
int_to_00str(minutes, min_str);
int_to_3str(hours, hour_str);
lcd.setCursor(0, 0);
// 0123456789012345
lcd.print(" Tid kvar ");
lcd.setCursor(0, 1);
// 01234567890123456789
lcd.print(" ");
lcd.print(hour_str);
lcd.print(":");
lcd.print(min_str);
lcd.print(":");
lcd.print(sec_str);
lcd.print(" ");
}
Mascot 719 is an old (probably designed in the 1980s or early 90s) and simple lab power supply. It can output 0-15 V at up to 2 A and 0-30 V at up to 1.5 A. I received a broken unit from a friend and set out to fix it.
At first glance, it was obvious that the resistor R4 had burned up. It was discolored, so it was unclear what its color code was, but it looked like 100 ohms, so I put a new 100 ohm, ¼ W resistor in its place. I also noticed that the solder joints of the front panel pots for setting voltage and current limits were very poorly soldered, so I fixed that as well.
The area around R2, R3 and D5 was discolored from heat, but the components seemed to have reasonable values when checked with a multimeter, so I let them be. The output transistor on the heatsink in the back checked out OK with simple diode tests and so did T2, a BD136 driving it, as well as T3, a BC547B driving T2.
After powering it on again, the new R4 quickly let out its smoke and simultaneously desoldered itself from the board. At the same time T3 and T2 died.
Schematics
At this point I decided it was time to trace out the schematics so that I could figure out how the supply was supposed to work. This seemed doable, since the board is a rather simple 1-layer board with about 40 large through-hole components. Still, the process took several hours split over three days. Repeatedly rearranging the schematics and finding and correcting mistakes were major parts of the work.
Here is the result (click for a higher resolution image):
The “brains” of the unit is a quad opamp, LM324. One section of it, IC1A, controls the output voltage based on the setting of the front panel pot R27. LM324 is of course not powerful enough to directly drive the output, so it does this via a cascade of three transistors, T3 (BC547B), T2 (BD136) and T1 (2N3055).
When the output of IC1A increases, the base current of T3 increases and so does the collector current through R4 and therefore also the base current of T2. R4 affects the voltage gain from base to collector of T3 and since there is negative AC feedback via C5 and R6, it affects the loop dynamics. It also limits the maximum amount of current that T3 can sink.
+V_RECT is around 30 V in the 15-V mode and around 50 V in the 30-V mode. If the control loop decides it needs to raise the output by a lot and therefore applies a lot of base current to T3, the current through R4 can get very large if R4 is only 100 ohms, and it is easy to see that the power dissipation can get very far outside of the rated ¼ W. In the worst case, almost the full +V_RECT is applied across R4, which means more than 20 W in the 30-V mode and almost 10 W in the 15-V mode. I think something along these lines is what happened, but I do not know for sure why it happened. One hypothesis is that T2 was broken so that it did not provide any collector current and therefore T1 did not turn, keeping the output low while the control loop tried harder and harder to increase the output by increasing the base current to T3 and therefore pulling larger and larger currents through the poor R4.
To prevent this from happening, I decided to increase R4. The supply shall be able to provide 2 A and T1 has a minimum current gain of 20 according to a datasheet, so T1’s base current needs to be able to reach at least 100 mA. T2 has a minimum current gain of 40, which means its base current needs to be up to 2.5 mA. With 20 V across R4 (some margin from 30 V) it could be up to 8 kohms. 50 V across 8 kohms (should never happen, but better safe than sorry) dissipates 0.31 W, so a single ¼ W resistor is perhaps not enough. I decided to put two 3.9 kohm resistors in series as R4. Another option would have been to select 10 k which would dissipate precisely 0.25 W at 50 V. I have written 10 k in the schematic.
It is possible that 10 k is actually the original value for R4. The color ring that indicates the number of zeros on the original resistor is discolored due to the overheating, so it is hard to tell whether it was brown (as I initially assumed) or perhaps orange. The difference is 100 ohms vs 10 kohms…
I put in a new BC547B as T3, put 2*3k9 as R4 and replaced T2 with a BD132 as I did not have any BD136 on hand. BD132 seems to be have very similar specifications.
After this the unit basically worked and no smoke was emitted!
Adjustments
However, it was possible to turn up the output to more than 30 V and the current limit did max out at more than 2 A in the 15-V mode. Maybe this is good and useful, but I wanted to follow the original specs for the supply, so I turned to the schematics to figure out how to adjust the trimmer pots to align the unit.
IC1B forms an adjustable voltage reference that creates a voltage referred to SENSE-. This reference voltage (which I called VREF_6V1 in the schematics) determines the maximum output voltage setting since the potentiometer that sets the output voltage, R27, is connected across it (via R28). The output voltage is scaled down by R30/R31 by a factor of 0.15, so when the output is 30 V, the negative input to IC1A becomes 4.45 V. The upper end of R27 is at 0.73 times the reference voltage and to make it 4.45 V, the reference voltage must be 6.1 V.
So, the first step is to trim R9 until the reference voltage (pin 7 of IC1B) is at 6.1 V. Alternatively, and perhaps better (to account for tolerances in the pot and resistors): Put the supply in the 30-V mode, crank up the voltage setting knob to max and adjust R9 until the output voltage is 30 V.
To trim the maximum output current, R12 has to be adjusted. One way of doing this is to set the supply in the 15-V mode, crank up the voltage and current knobs to max and connect a power resistor of between 5 and 7.5 ohms to the output. An ammeter should be used to measure the current through the load while R12 is adjusted to limit the current to 2 A.
There is also a minimum current limit according to the front panel, namely 30 mA. This is trimmed by adjusting R37 in the following manner:
Set the current limit to minimum. Turn down the voltage to something suitably low, like 3 V. Connect the ammeter directly across the output (or in series with a limiting resistor) and adjust R37 until the ammeter reads 30 mA.
Other tweaks
Since the series resistors in the crude shunt zener voltage reference R2/R3/D5 gets very hot, particularly in the 30-V mode, I decided to replace them with several resistors to better handle the power dissipation. With +V_RECT at 50 V, 35 V lies across R2 and R3 which are both 2.2 kohms, so the power dissipation is more than 0.5 W. Not good for resistors that seem to have the ordinary ¼ W rating. My solution was to replace each of R2 and R3 with a string of two 1.2 kohm resistors with a rating of at least 0.5 W each, which I had on hand. The slightly higher resulting resistance is hardly of any importance. I also soldered ~10 mm stumps of used copper desoldering braid to the legs of D5 to help it get rid of heat more easily.
In general, it is a good idea to replace electrolytic capacitors in old equipment, since electrolytics tend to degrade over time. I measured the ripple on +V_RECT and found the valleys at maximum load to always be at least 7 V above the maximum rated output voltage (15 or 30 V depending on setting), so it does not seem to be necessary to replace C2 to smooth +V_RECT further. I did some changes to C7 however. See the section on stability below.
An extra feature one could quite easily add is an LED that lights up when the current limit kicks in. To do this, one needs to understand how the current limiting works:
Current limiting
IC1E handles the normal adjustable current limit. When the current is lower than the set value, its output is high (14 V or so) and it does not affect the output voltage since D7 therefore is back-biased and does not conduct. As soon as the voltage across the current-measuring resistor R17 reaches the level set by the current limit potentiometer R15, the output of IC1E goes low(er) and diverts current from the base of T3 to limit the output voltage and thereby the output current. A switch cleverly connects R14 across the potentiometer R15 when in the 30-V mode. This reduces the voltage at the top of R15 so that the maximum current one can set using R15 is reduced from 2 A to 1.5 A.
IC1D acts as another current limiter that works in a similar way. Its purpose however is to reduce the maximum allowed output current if the output voltage is low. (Curves printed on the front panel illustrate this behavior.) As in any linear regulator, the output transistor (T1) is subjected to the highest power dissipation when the output current is high while at the same time the output voltage is low. To limit the dissipation in T1, it therefore makes sense to not allow as high output current when the output voltage is low. The resistors R21, R24 and R25 form a voltage divider that scales the output voltage so that it can be used by IC1D to compare with the voltage drop across the current sensor R17. In the 15-V mode, a switch engages the voltage divider R20/R19 to scale down the voltage drop across R17 so that higher currents are allowed before the protection kicks in.
Both IC1D and IC1E are connected via diodes to a node near the base of T3 and can therefore override the voltage regulating IC1A and reduce the output voltage (and thereby current) by reducing the base current to T3. The diodes prevent these opamps from ever increasing the output voltage above what IC1A wants it to be, they can just reduce it when necessary.
Now that we understand the current regulating parts, we can see that when current limiting kicks in, either of IC1D or IC1E pulls their outputs low. To signal that this is happening, we can build an OR-gate that controls an LED by connecting the cathodes of two diodes to each of these outputs (pins 8 and 14), join the anodes and connect them to the LED in series with a resistor connected to +15V. This works, but I also wanted an LED that lights up when the power is on and, furthermore, I found it wasteful to have so much voltage headroom (up to 50 V originally) and use separate currents from this voltage to power two different LEDs. I therefore came up with a somewhat more complex solution shown below. The two LEDs are connected in series, so that they use the same current. A transistor controlled by the diode OR-gate shorts out the current limit LED when no current limiting is going on. This changes the brightness of the power LED very slightly, but this is almost imperceptible.
Stability
Feedback systems like voltage and current regulators can be unstable, so I wanted to check whether this power supply suffered from that. I varied the output voltage from 0 V to max while loading the output with either nothing, 22 ohms or 7 ohms. I also varied the current limit setting.
Unfortunately, there were tendencies of instability for low output voltages (0.5 V to 4 V) with a load of 7 ohms. A roughly 70 kHz tone of up to 150 mVpp appeared at the output. Not good.
I modeled the voltage control loop in LTSpice and were able to see a similar behavior there as well.
After lots of back-and-forth between LTSpice and the real board, I came up with a solution that did not seem to oscillate under any of my test conditions. In addition to the original 100 µF capacitor across the output, I added four more 47 µF, 63 V capacitors. I also had to change C6 from 10 nF to 1.5 nF. I still do not fully trust the supply to always be perfectly stable, but it is certainly much better than before this modification.
Some observations
+V_RECT is about 50 V in the 30-V mode. BD136 (T2) is rated to V_CEO and V_CBO = 45 V. This is marginal to say the least, as almost the entire +V_RECT will be across the transistor when the output is at 0 V. BD140 which has a rating of 80 V seems like a much better choice as T2 and an upgrade seems like a very good idea. Maybe this is what caused the original T2 to fail?
BC547B (T3) also has a V_CEO rating of 45 V, so this too is not a good choice in a position where it can be subjected to 50 V. I think BC546B with its higher voltage rating would be much safer.
The power supply was designed while the mains voltage was 220 V (it says 220V~ 50Hz on the front). It has since been raised to 230 V (in Sweden this happened in 1988, maybe it was the same in Norway?), which results in a slightly higher +V_RECT, but even with 220 V, the voltage would have been too high for a BD136 and BC547.
The shunt regulator R2/R3/D5 is a very crude way of creating a somewhat stable voltage and it costs a lot of wasted power if it is to accommodate a wide input voltage range while supplying some output current. It would make sense to increase the resistors by a factor of 100 or so and augment it with an emitter follower that supplies the “regulated” output. This would improve the poor efficiency. One could even replace the zener regulator entirely with a modern three-terminal regulator. LR12N3 from Microchip/Supertex or TL783 or LM317HV from TI might be suitable.
There is an alternate footprint for a trimmer pot where R32 is placed, so if one wants to be able to fine tune the voltage reading on the front panel instrument, a 100-kohm pot could be used to replace R32. (It seems like it would be better to have a series connection of a 56 kohm resistor and a 10 kohm pot to make this adjustment easier and more precise.) I have not made this modification.
There is no similar trimmer option at R34/R35 for the current.
It does of course not make economical sense to spend all this time on an old and simple lab power supply with unimpressive specifications, but it was rather fun and perhaps others can find the information useful when repairing other Mascot 719 units.