Skip to content

← Computer systems

How Does a Heater, or a Drone, Hold Steady?

Topic 17 · Grades 6–8 · Feedback control

Nothing for the tank; the programs in Python, from Grade 8 (loops, lists, functions and the micro:bit board).

What this builds on from class
In this unit 9 sections
  1. Try it
  2. How it works
  3. Program it
  4. What your program shows
  5. How a drone holds still
  6. Going further still
  7. Challenges
  8. Questions
  9. Where to go next

A drone can stop in the air and stay there while the wind pushes it about. An egg incubator keeps its eggs at 37.5 °C for three weeks, never much warmer or colder. Nobody is turning knobs inside either: a small computer measures, again and again, and corrects a little each time. How much it corrects turns out to be the whole secret.

Start with something simpler: a water tank. Water comes down a long pipe from a valve you control, so whatever you do at the valve reaches the tank 2 seconds later, and the house below uses water all the time. Keep the water at the dashed line, 60 cm, for a minute. The valve has only two settings, as many real switches do: shut, or fully open.

Try it

Hold the line

Press Start, then use Shut and Open to keep the water at the dashed line. The pipe at the top shows the water still on its way: darker means more.

house20.0 cm0204060801000204060level (cm)seconds
Valve:

Press Start, then keep the water at the dashed line for a minute.

Second
0
Valve set
–
Arriving
–

Before you play much: with a valve that is only ever shut or fully open, what do you think happens?

Pick an answer to see why.

You need: nothing to play with the tank. For the programs (Python, from Grade 8), loops, lists and functions, and the micro:bit board from Grade 8.

Try it

  • a.Only shut or open. Play a few times with the tank above. Can you keep it within 2 cm of the line? Watch what happens just after you shut the valve.
  • b.Any amount. This tank has a valve you can open part of the way. Fill it to the line, then look for a setting that keeps the water still.

Try it

Hold the line with a valve that opens part way

Press Start, then move the slider. 100% is fully open, as fast as the first tank’s Open.

house20.0 cm0204060801000204060level (cm)seconds

Press Start, then keep the water at the dashed line for a minute.

Second
0
Valve set
–
Arriving
–

How it works

Every machine that holds something steady goes round the same loop. It measures (the water’s level, the box’s temperature, the drone’s height). It compares that with the target, the value it should hold; the difference is the error, here 60 − the level. Then it corrects: it sets the valve, the heater or the motors. The world changes, and the loop goes round again. Acting on what was just measured is called feedback, and a program or device that runs it is a controller.

Measurethe level nowComparehow far off?Correctset the valveThe worldwater in and outagain,every second
Figure 17.1 The feedback loop of a controller. A heater’s goes round the same way: measure the box, compare with 37 °C, set the heater. A drone’s measures its height and sets its motors.

The only question is what to do in the third box. There are two simple answers.

  • On/off control: if the error says too low, fully open; otherwise, shut. The water level controller in Grade 8’s Physical Computing switches its pump fully on or fully off; so does a simple electric iron’s thermostat.
  • Proportional control: correct in proportion to the error. A long way below the line, open wide; a little below, open a little; on the line, shut. For example, open the valve 5% for every centimeter below the line: 40 cm below gives 200%, so fully open; 10 cm below, 50%; 1 cm below, 5%.

On/off wobbles for two reasons: water takes time to arrive, and the valve knows only two speeds while the house needs a third, 1 cm a second. A proportional rule slows down as it gets close, so little water is still in the pipe when it arrives.

With the proportional rule (5% for every centimeter below 60), how far open is the valve when the water is at 54 cm?

Pick an answer to see why.

Program it

The tank in Python (from Grade 8)

A list, pipe, holds the valve’s last 2 settings, oldest first. Each second the new setting joins the end (append) and the oldest leaves the front (pop(0)); the level goes up by what arrived (5 cm a second when fully open) and down 1 cm for the house. First, on/off:

level = 20        # cm of water in the tankpipe = [0, 0]     # the valve's last 2 settings, still coming down the pipefor second in range(1, 41):    if level < 60:        valve = 100    else:        valve = 0    pipe.append(valve)    arriving = pipe.pop(0)    level = level + 5 * arriving / 100 - 1    if second % 2 == 0:        print(second, round(level, 1), valve)

Output

2 18.0 1004 26.0 1006 34.0 1008 42.0 10010 50.0 10012 58.0 10014 66.0 016 69.0 018 67.0 020 65.0 022 63.0 024 61.0 026 59.0 028 57.0 10030 65.0 032 68.0 034 66.0 036 64.0 038 62.0 040 60.0 0

Each line is the second, the level and the valve. In the first 2 seconds the level falls, though the valve is open: the water hasn’t arrived yet. It passes the line in second 13, the valve shuts at second 14, and the level goes on up to 70 cm at second 15 (the program prints only even seconds). Then it falls for 13 seconds, and the next round starts again from 57.

Let a function set the valve

A rule for the valve is a function: given the level, it gives back how far open, 0 to 100. The tank below calls your valve every second and does the same sums as the program above. Press Fill. Then change the rule (Edit) to the proportional one, return 5 * (60 - level), and fill again: the last two runs stay on the graph, faint.

def valve(level):    if level < 60:        return 100    return 0

Try it

Your valve

Your function sets the valve every second for a minute. A number above 100 counts as 100, and one below 0 as 0.

house20.0 cm0204060801000204060level (cm)seconds

Press Fill to let your valve() set the valve every second for a minute.

Second
0
Valve set
–
Arriving
–

What your valve() prints, and any error, appears here.

Three valves, one program

Now let one program try three rules and report how each did: on/off, the gentle proportional rule (5% a centimeter), and a strong one (30% a centimeter). fill takes the rule as its input, as the Moon lander’s fly took a pilot.

def on_off(level):    if level < 60:        return 100    return 0 def gentle(level):    return 5 * (60 - level) def strong(level):    return 30 * (60 - level) def fill(name, valve):    level = 20    pipe = [0, 0]    levels = []    for second in range(60):        v = valve(level)        if v > 100:            v = 100        if v < 0:            v = 0        pipe.append(v)        arriving = pipe.pop(0)        level = level + 5 * arriving / 100 - 1        levels.append(level)    last = levels[30:]    print(name, 'highest', round(max(levels), 1), '| last 30 s:', round(min(last), 1), 'to', round(max(last), 1)) fill('on_off', on_off)fill('gentle', gentle)fill('strong', strong)

Output

on_off highest 70.0 | last 30 s: 57.0 to 69.0gentle highest 58.1 | last 30 s: 56.0 to 56.0strong highest 68.0 | last 30 s: 57.2 to 65.7

The same idea on the micro:bit

An egg incubator is a box with a heater and a sensor. On the board below, the heater is wired to pin 0 (on a real board through a relay: as Grade 8 showed for a pump, a pin is far too weak to run it). pin0.write_digital(1) switches it fully on, pin0.write_digital(0) off, and temperature() reads the box in whole degrees.

Try it

On and off

Run the program and watch the box’s temperature for half a minute. The strip under the graph is dark while the heater is on.

AB0123VGND
202530354045°Cheaterthe last 300 seconds of the box
Box
20.0 °C
Heater
0%
Highest
–

The box’s time runs 10 times faster than the board’s: each 0.1 s here is a second for the box.

Press Run to send the program to the board.

from microbit import * # The heater is wired to pin0. Keep the box at 37 °C.while True:    if temperature() < 37:        pin0.write_digital(1)    # heater fully on        display.show(Image.SQUARE)    else:        pin0.write_digital(0)    # heater off        display.clear()    sleep(100)

Serial output

A heater warms its coil, and the coil warms the air. When the air reaches 37 °C and the heater switches off, the hot coil goes on warming the box, as the water in the pipe kept arriving.

For a gentle heater, pin0.write_analog(n), with n from 0 to 1023, switches the pin on and off many times a second, on for that share of the time (PWM, pulse-width modulation). The coil only feels the average, so 511 is about half power. (A real heater would then be switched by a transistor: a relay can’t click that fast.)

Try it

In proportion

This program gives the heater a tenth of full power for every degree the box is too cold. The middle LED glows brighter the more power it gives. Run it and compare the graph. Then try error * 1 in place of error * 0.1: full power when just a degree too cold.

AB0123VGND
202530354045°Cheaterthe last 300 seconds of the box
Box
20.0 °C
Heater
0%
Highest
–

The box’s time runs 10 times faster than the board’s: each 0.1 s here is a second for the box.

Press Run to send the program to the board.

from microbit import * # The heater is wired to pin0. Keep the box at 37 °C.while True:    error = 37 - temperature()   # how many degrees too cold    power = error * 0.1          # a tenth of full power for each degree    if power > 1:        power = 1    if power < 0:        power = 0    pin0.write_analog(power * 1023)    display.set_pixel(2, 2, round(power * 9))    sleep(100)

Serial output

What your program shows

Switched on and off, the tank went 10 cm past the line, to 70 cm, and in the last half minute was still swinging from 57 to 69 cm: nothing in the rule ever lets it settle. The incubator climbed to about 41 °C, 3.5 degrees above the 37.5 °C eggs do best at, and poultry experts warn that too hot does more harm than too cold.

The gentle rule never reached the line (highest 58.1 cm), and then the water held perfectly still. The gentle heater went just past 37, to 37.4 °C (the coil’s stored heat), then held steady. Correcting in proportion to the error settles them.

But look closely: the gentle tank settled at 56 cm, not 60, and the gentle heater a degree and a half short too, at about 35.5 °C. The house takes 1 cm a second, so the level only stays still with the valve 20% open, and the gentle rule opens 20% only when the error is 20 ÷ 5 = 4 cm. It needs to be off the line to open the valve at all.

The strong rule, 30% a centimeter, overshot nearly as far as on/off (68 cm) and was still swinging from 57.2 to 65.7: a big correction for a small error, arriving late, is nearly on/off again. The heater with error * 1 is exactly on/off: temperature() gives whole degrees, so a degree too cold already means full power, and the graph is the same, peak 41 °C. So a controller is tuned: strong enough to get there, gentle enough not to overshoot.

How a drone holds still

A drone is harder: its motors push it up against gravity, so the push changes the speed, and the speed changes the height, as the Moon lander’s burn did. Try the proportional rule on a drone that should hover 10 m up: push just hard enough to cancel gravity (9.8 m/s² on Earth), plus 1 m/s² more for every meter too low. The program flies it a tenth of a second at a time; brake is its second trick, 0 at first.

def hover(brake):    height = 0     # m above the ground    speed = 0      # m/s, upwards    heights = []    for tenth in range(1, 201):            # 20 seconds, a tenth of a second at a time        thrust = 9.8 + (10 - height) - brake * speed        if thrust < 0:            thrust = 0                     # the propellers can only push        speed = speed + (thrust - 9.8) / 10        height = height + speed / 10        if tenth % 20 == 0:                # every 2 seconds            heights.append(round(height, 1))    print('brake', brake, ':', heights) hover(0)hover(2)

Output

brake 0 : [14.6, 16.2, 0.3, 11.9, 18.2, 1.3, 9.1, 19.5, 3.1, 6.3]brake 2 : [6.2, 9.1, 9.8, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0]

With brake 0 the drone bounces like a ball on a spring, from the ground to 20 m and back, for as long as it flies (the heights printed every 2 seconds catch it at 0.3 and 19.5): it reaches 10 m moving fast, and a rule that looks only at the height ignores the speed. The second run also pushes against the speed, 2 m/s² for every m/s, like a brake: it slows as it nears 10 m and holds there.

A real drone’s flight controller does this, much faster, for each motor, from three parts: one in proportion to the error (P), one against how fast the error is changing, as brake does (D, for derivative), and one that grows while an error lasts, to hold against a steady wind (I, for integral): a PID controller. The open-source PX4 autopilot runs such loops one inside another, for the drone’s position, speed, tilt and turning.

The tank, the heater and this drone are simple made-up models: a real house’s use changes all day, a real box loses heat faster on a cold night, and a real drone meets air resistance and gusts. Real controllers are tuned on the real machine, but the lessons hold.

The Khumbu Icefall: a jumble of huge blocks of white and blue ice pouring down between steep mountain walls, with the tents of Everest Base Camp on the rocks below it
Figure 17.2 The Khumbu Icefall, seen from Everest Base Camp: the tents in front are the camp, and the route to Camp 1 climbs through the broken ice behind them. Photo: McKay Savage from London, UK, CC BY 2.0

Going further still

Fix the 4 cm with the I of PID: keep a running total of the error and add a little of it to the valve. While the water stays low the total grows and opens the valve wider, until the water reaches the line (the ★★★ challenge).

A gap in the middle. A pump that switches every few seconds wears out, so a roof tank’s controller starts it when the tank is low and stops it only when full (the ★★ challenge).

Challenges

★ The gentle valve (Python, from Grade 8)

Write valve(level) for the gentle rule: open the valve 5% for every centimeter below 60, but never more than 100 or less than 0.

def valve(level):    # 5% for every cm below 60, from 0 to 100    return 0 print(valve(50))

Try it

★ The gentle valve

  1. 50 cm valve(50) Should give 50 Yours —
  2. 56 cm valve(56) Should give 20 Yours —
Tests passed
— / 8
Challenge
★ solved

Write your valve in the program above (Edit), then check it.

A test your function gets wrong, anything it prints, and any error appear here.

Hint
Answer

★★ A pump with a gap (Python, from Grade 8)

Write pump(level, on) for a pump with a gap, on saying whether it is running now: below 55 cm it runs, above 65 cm it stops, and in between it carries on as it is. Give back True to run it, False to stop it.

def pump(level, on):    # True to run the pump, False to stop it    return on print(pump(50, False))

Try it

★★ A pump with a gap

  1. 50 cm, pump off pump(50, False) Should give True Yours —
  2. 60 cm, pump off pump(60, False) Should give False Yours —
  3. 60 cm, pump running pump(60, True) Should give True Yours —
Tests passed
— / 9
Challenge
★★ solved

Write your pump in the program above (Edit), then check it.

A test your function gets wrong, anything it prints, and any error appear here.

Hint
Answer

★★★ Hit the line (Python, from Grade 8)

The gentle valve settles short of 60, by more when the house uses more. Change only valve so the tank ends up steady on the line in every test, from different starts, with the house using 0.5 to 3 cm a second. valve is also given memory, a dictionary fill keeps from one second to the next for it to remember things in. fill(start, use) runs 2 minutes and says 'steady' (the last 20 seconds all within 0.5 cm of 60), 'wobbling' or 'off the line'.

def valve(level, memory):    return 5 * (60 - level) def fill(start, use):    level = start    pipe = [0, 0]    memory = {}    levels = []    for second in range(120):        v = valve(level, memory)        if v > 100:            v = 100        if v < 0:            v = 0        pipe.append(v)        level = level + 5 * pipe.pop(0) / 100 - use        if level < 0:            level = 0        if level > 100:            level = 100        levels.append(level)    last = levels[-20:]    if max(last) - min(last) > 1:        return 'wobbling'    if all(abs(x - 60) <= 0.5 for x in last):        return 'steady'    return 'off the line' print(fill(60, 2))

Try it

★★★ Hit the line

  1. Start at 60 cm, the house uses 2 cm a second fill(60, 2) Should give 'steady' Yours —
  2. Start at 60 cm, the house uses 1 cm a second fill(60, 1) Should give 'steady' Yours —
Tests passed
— / 6
Challenge
★★★ solved

Write your fill in the program above (Edit), then check it.

A test your function gets wrong, anything it prints, and any error appear here.

Hint
Another hint
Answer

Questions

Why does a heater that switches fully on and off go past its target temperature?

Pick an answer to see why.

a.

Name the four steps of a feedback loop, and say what each is for a drone holding its height.

Answer
b.

The gentle tank settled at 56 cm with the house using 1 cm a second. Without a program, where would it settle if the house used 2 cm a second? Explain.

Answer
c.

A drone controller that looks only at the height bounced between the ground and 20 m. What did the second controller add, and why did it help?

Answer
d.

An incubator’s heater can only be on or off. Suggest one way to make its temperature swing less, and say what it costs.

Answer

Where to go next

  • Can you land on the Moon? The drone’s sums, flown by your own autopilot.
  • Grade 8, Physical Computing: the micro:bit board, its sensors, and the water level controller.
  • Grade 8, Functions: functions that give back an answer, like valve.
  • How did the Moon landing’s computer survive an overload? The computer that flew Eagle down (Grades 9–12). (Coming soon.)